graphPackage = async (
  name,
  readDescriptor,
  canonical,
  graph,
  { packageLocation, packageDescriptor },
  conditions,
  dev,
  languageOptions,
  strict,
  {
    commonDependencyDescriptors = {},
    preferredPackageLogicalPathMap = new Map(),
    logicalPath = [],
    log = noop,
  } = {},
) => {
  if (graph[packageLocation] !== undefined) {
    updateShortestPaths(
      graph,
      packageLocation,
      logicalPath,
      preferredPackageLogicalPathMap,
    );

    // Returning the promise here would create a causal cycle and stall recursion.
    return undefined;
  }

  if (packageDescriptor.name !== name) {
    log('Package name does not match location', {
      name,
      packageDescriptorName: packageDescriptor.name,
      packageLocation,
    });
  }

  const result = /** @type {Node} */ ({});
  graph[packageLocation] = result;

  /** @type {Node['dependencyLocations']} */
  const dependencyLocations = {};
  /** @type {ReturnType<typeof gatherDependency>[]} */
  const children = [];

  /**
   * A set containing dependency names which are considered "optional"
   */
  const optionals = new Set();

  /**
   * Contains the names of _all_ dependencies
   *
   * @type {Set<string>}
   */
  const allDependencies = new Set();

  // these are fields from package.json containing dependencies
  const {
    dependencies = {},
    peerDependencies = {},
    peerDependenciesMeta = {},
    bundleDependencies = {},
    optionalDependencies = {},
    devDependencies = {},
  } = packageDescriptor;

  for (const [name, descriptor] of entries(commonDependencyDescriptors)) {
    if (Object(descriptor) === descriptor) {
      allDependencies.add(name);
    }
  }

  // only consider devDependencies if dev flag is true
  for (const name of keys({
    ...dependencies,
    ...peerDependencies,
    ...bundleDependencies,
    ...optionalDependencies,
    ...(dev ? devDependencies : {}),
  })) {
    allDependencies.add(name);
  }

  // for historical reasons, some packages omit peerDependencies and only
  // use the peerDependenciesMeta field (because there was no way to define
  // an "optional" peerDependency prior to npm v7). this is plainly wrong,
  // but not exactly rare, either
  for (const [name, meta] of entries(peerDependenciesMeta)) {
    if (Object(meta) === meta && meta.optional) {
      optionals.add(name);
      allDependencies.add(name);
    }
  }

  for (const name of keys(optionalDependencies)) {
    optionals.add(name);
  }

  for (const name of [...allDependencies].sort()) {
    const optional = optionals.has(name);
    const childLogicalPath = [...logicalPath, name];
    children.push(
      // Mutual recursion ahead:
      // eslint-disable-next-line no-use-before-define
      gatherDependency(
        readDescriptor,
        canonical,
        graph,
        dependencyLocations,
        packageLocation,
        name,
        conditions,
        preferredPackageLogicalPathMap,
        languageOptions,
        strict,
        {
          childLogicalPath,
          optional,
          commonDependencyDescriptors,
          log,
        },
      ),
    );
  }

  const { version = '', exports: exportsDescriptor } = packageDescriptor;
  /** @type {Node['types']} */
  const types = {};

  /**
   * @param {string} path
   * @returns {Promise<PackageDescriptor>}
   */
  const readDescriptorUpwards = async path => {
    const location = resolveLocation(path, packageLocation);
    // readDescriptor coming from above is memoized, so this is not awfully slow
    const { data } = await searchDescriptor(location, readDescriptor);
    return data;
  };

  /** @type {Node['externalAliases']} */
  const externalAliases = {};
  /** @type {Node['internalAliases']} */
  const internalAliases = {};

  inferExportsAndAliases(
    packageDescriptor,
    externalAliases,
    internalAliases,
    conditions,
    types,
  );

  const parsers = inferParsers(
    packageDescriptor,
    packageLocation,
    languageOptions,
  );

  const sourceDirname = basename(packageLocation);

  assign(result, {
    name,
    path: logicalPath,
    label: `${name}${version ? `-v${version}` : ''}`,
    sourceDirname,
    explicitExports: exportsDescriptor !== undefined,
    externalAliases,
    internalAliases,
    dependencyLocations,
    types,
    parsers,
  });

  await Promise.all(
    values(result.externalAliases).map(async item => {
      const descriptor = await readDescriptorUpwards(item);
      if (descriptor && descriptor.type === 'module') {
        types[item] = 'mjs';
      }
    }),
  );

  await Promise.all(children);

  // handle commonDependencyDescriptors package aliases
  for (const [name, { alias }] of entries(commonDependencyDescriptors)) {
    // update the dependencyLocations to point to the common dependency
    const targetLocation = dependencyLocations[name];
    if (targetLocation === undefined) {
      throw Error(
        `Cannot find common dependency ${name} for ${packageLocation}`,
      );
    }
    dependencyLocations[alias] = targetLocation;
  }
  // handle internalAliases package aliases
  for (const specifier of keys(internalAliases).sort()) {
    const target = internalAliases[specifier];
    // ignore all internalAliases where the specifier or target is relative
    const specifierIsRelative = specifier.startsWith('./') || specifier === '.';
    // eslint-disable-next-line no-continue
    if (specifierIsRelative) continue;
    const targetIsRelative = target.startsWith('./') || target === '.';
    // eslint-disable-next-line no-continue
    if (targetIsRelative) continue;
    const targetLocation = dependencyLocations[target];
    if (targetLocation === undefined) {
      throw Error(`Cannot find dependency ${target} for ${packageLocation}`);
    }
    dependencyLocations[specifier] = targetLocation;
  }

  return undefined;
}
`graphPackage` and {@link gatherDependency} are mutually recursive functions that
gather the metadata for a package and its transitive dependencies.
The keys of the graph are the locations of the package descriptors.
The metadata include a label (which is informative and not necessarily
unique), the location of each shallow dependency, and names of the modules
that the package exports.

@param {string} name
@param {ReadDescriptorFn} readDescriptor
@param {CanonicalFn} canonical
@param {Graph} graph
@param {PackageDetails} packageDetails
@param {Set<string>} conditions
@param {boolean | undefined} dev
@param {LanguageOptions} languageOptions
@param {boolean} strict
@param {GraphPackageOptions} options
@returns {Promise<undefined>}