text: // @flow strict-local
  
  import type {Async, FilePath, PackageJSON} from '@parcel/types';
  import type {StaticRunOpts} from '../RequestTracker';
  import type {Entry, InternalFile, ParcelOptions} from '../types';
  import type {FileSystem} from '@parcel/fs';
  
  import {
    isDirectoryInside,
    isGlob,
    glob,
    findAlternativeFiles,
  } from '@parcel/utils';
  import ThrowableDiagnostic, {
    md,
    generateJSONCodeHighlights,
    getJSONSourceLocation,
  } from '@parcel/diagnostic';
  import path from 'path';
  import jsonMap, {type Mapping} from 'json-source-map';
  import {
    type ProjectPath,
    fromProjectPath,
    fromProjectPathRelative,
    toProjectPath,
  } from '../projectPath';
  
  type RunOpts = {|
    input: ProjectPath,
    ...StaticRunOpts,
  |};
  
  export type EntryRequest = {|
    id: string,
    +type: 'entry_request',
    run: RunOpts => Async<EntryResult>,
    input: ProjectPath,
  |};
  
  export type EntryResult = {|
    entries: Array<Entry>,
    files: Array<InternalFile>,
  |};
  
  const type = 'entry_request';
  
  export default function createEntryRequest(input: ProjectPath): EntryRequest {
    return {
      id: `${type}:${fromProjectPathRelative(input)}`,
      type,
      run,
      input,
    };
  }
  
  async function run({input, api, options}: RunOpts): Promise<EntryResult> {
    let entryResolver = new EntryResolver(options);
    let filePath = fromProjectPath(options.projectRoot, input);
    let result = await entryResolver.resolveEntry(filePath);
  
    // Connect files like package.json that affect the entry
    // resolution so we invalidate when they change.
    for (let file of result.files) {
      api.invalidateOnFileUpdate(file.filePath);
      api.invalidateOnFileDelete(file.filePath);
    }
  
    // If the entry specifier is a glob, add a glob node so
    // we invalidate when a new file matches.
    if (isGlob(filePath)) {
      api.invalidateOnFileCreate({glob: input});
    }
  
    // Invalidate whenever an entry is deleted.
    // If the entry was a glob, we'll re-evaluate it, and otherwise
    // a proper entry error will be thrown.
    for (let entry of result.entries) {
      api.invalidateOnFileDelete(entry.filePath);
    }
  
    return result;
  }
  
  async function assertFile(
    fs: FileSystem,
    entry: FilePath,
    relativeSource: FilePath,
    pkgFilePath: FilePath,
    keyPath: string,
    options: ParcelOptions,
  ) {
    let source = path.join(entry, relativeSource);
    let stat;
    try {
      stat = await fs.stat(source);
    } catch (err) {
      let contents = await fs.readFile(pkgFilePath, 'utf8');
      let alternatives = await findAlternativeFiles(
        fs,
        relativeSource,
        entry,
        options.projectRoot,
        false,
      );
      throw new ThrowableDiagnostic({
        diagnostic: {
          origin: '@parcel/core',
          message: md`${path.relative(process.cwd(), source)} does not exist.`,
          codeFrames: [
            {
              filePath: pkgFilePath,
              codeHighlights: generateJSONCodeHighlights(contents, [
                {
                  key: keyPath,
                  type: 'value',
                },
              ]),
            },
          ],
          hints: alternatives.map(r => {
            return md`Did you mean '__${r}__'?`;
          }),
        },
      });
    }
  
    if (!stat.isFile()) {
      let contents = await fs.readFile(pkgFilePath, 'utf8');
      throw new ThrowableDiagnostic({
        diagnostic: {
          origin: '@parcel/core',
          message: md`${path.relative(process.cwd(), source)} is not a file.`,
          codeFrames: [
            {
              filePath: pkgFilePath,
              codeHighlights: generateJSONCodeHighlights(contents, [
                {
                  key: keyPath,
                  type: 'value',
                },
              ]),
            },
          ],
        },
      });
    }
  }
  
  export class EntryResolver {
    options: ParcelOptions;
  
    constructor(options: ParcelOptions) {
      this.options = options;
    }
  
    async resolveEntry(entry: FilePath): Promise<EntryResult> {
      if (isGlob(entry)) {
        let files = await glob(entry, this.options.inputFS, {
          absolute: true,
          onlyFiles: false,
        });
        let results = await Promise.all(
          files.map(f => this.resolveEntry(path.normalize(f))),
        );
        return results.reduce(
          (p, res) => ({
            entries: p.entries.concat(res.entries),
            files: p.files.concat(res.files),
          }),
          {entries: [], files: []},
        );
      }
  
      let stat;
      try {
        stat = await this.options.inputFS.stat(entry);
      } catch (err) {
        throw new ThrowableDiagnostic({
          diagnostic: {
            message: md`Entry ${entry} does not exist`,
          },
        });
      }
  
      if (stat.isDirectory()) {
        let pkg = await this.readPackage(entry);
  
        if (pkg) {
          let {filePath} = pkg;
          let entries = [];
          let files = [
            {
              filePath: toProjectPath(this.options.projectRoot, filePath),
            },
          ];
  
          let targetsWithSources = 0;
          if (pkg.targets) {
            for (let targetName in pkg.targets) {
              let target = pkg.targets[targetName];
              if (target.source != null) {
                targetsWithSources++;
                let targetSources = Array.isArray(target.source)
                  ? target.source
                  : [target.source];
                let i = 0;
                for (let relativeSource of targetSources) {
                  let source = path.join(entry, relativeSource);
                  let keyPath = `/targets/${targetName}/source${
                    Array.isArray(target.source) ? `/${i}` : ''
                  }`;
                  await assertFile(
                    this.options.inputFS,
                    entry,
                    relativeSource,
                    filePath,
                    keyPath,
                    this.options,
                  );
  
                  entries.push({
                    filePath: toProjectPath(this.options.projectRoot, source),
                    packagePath: toProjectPath(this.options.projectRoot, entry),
                    target: targetName,
                    loc: {
                      filePath: toProjectPath(
                        this.options.projectRoot,
                        pkg.filePath,
                      ),
                      ...getJSONSourceLocation(
                        pkg.map.pointers[keyPath],
                        'value',
                      ),
                    },
                  });
                  i++;
                }
              }
            }
          }
  
          let allTargetsHaveSource =
            targetsWithSources > 0 &&
            pkg != null &&
            pkg.targets != null &&
            Object.keys(pkg.targets).length === targetsWithSources;
  
          if (!allTargetsHaveSource && pkg.source != null) {
            let pkgSources = Array.isArray(pkg.source)
              ? pkg.source
              : [pkg.source];
            let i = 0;
            for (let pkgSource of pkgSources) {
              let source = path.join(path.dirname(filePath), pkgSource);
              let keyPath = `/source${Array.isArray(pkg.source) ? `/${i}` : ''}`;
              await assertFile(
                this.options.inputFS,
                entry,
                pkgSource,
                filePath,
                keyPath,
                this.options,
              );
              entries.push({
                filePath: toProjectPath(this.options.projectRoot, source),
                packagePath: toProjectPath(this.options.projectRoot, entry),
                loc: {
                  filePath: toProjectPath(this.options.projectRoot, pkg.filePath),
                  ...getJSONSourceLocation(pkg.map.pointers[keyPath], 'value'),
                },
              });
              i++;
            }
          }
  
          // Only return if we found any valid entries
          if (entries.length && files.length) {
            return {
              entries,
              files,
            };
          }
        }
  
        throw new ThrowableDiagnostic({
          diagnostic: {
            message: md`Could not find entry: ${entry}`,
          },
        });
      } else if (stat.isFile()) {
        let projectRoot = this.options.projectRoot;
        let packagePath = isDirectoryInside(
          this.options.inputFS.cwd(),
          projectRoot,
        )
          ? this.options.inputFS.cwd()
          : projectRoot;
  
        return {
          entries: [
            {
              filePath: toProjectPath(this.options.projectRoot, entry),
              packagePath: toProjectPath(this.options.projectRoot, packagePath),
            },
          ],
          files: [],
        };
      }
  
      throw new ThrowableDiagnostic({
        diagnostic: {
          message: md`Unknown entry: ${entry}`,
        },
      });
    }
  
    async readPackage(entry: FilePath): Promise<?{
      ...PackageJSON,
      filePath: FilePath,
      map: {|data: mixed, pointers: {|[string]: Mapping|}|},
      ...
    }> {
      let content, pkg;
      let pkgFile = path.join(entry, 'package.json');
      try {
        content = await this.options.inputFS.readFile(pkgFile, 'utf8');
      } catch (err) {
        return null;
      }
  
      try {
        pkg = JSON.parse(content);
      } catch (err) {
        // TODO: code frame?
        throw new ThrowableDiagnostic({
          diagnostic: {
            message: md`Error parsing ${path.relative(
              this.options.inputFS.cwd(),
              pkgFile,
            )}: ${err.message}`,
          },
        });
      }
  
      return {
        ...pkg,
        filePath: pkgFile,
        map: jsonMap.parse(content.replace(/\t/g, ' ')),
      };
    }
  }
  