@ttsc/lint 0.26.1 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/samchon/ttsc/blob/master/LICENSE) [![NPM Version](https://img.shields.io/npm/v/@ttsc/lint.svg)](https://www.npmjs.com/package/@ttsc/lint) [![NPM Downloads](https://img.shields.io/npm/dm/@ttsc/lint.svg)](https://www.npmjs.com/package/@ttsc/lint) [![Build Status](https://github.com/samchon/ttsc/workflows/test/badge.svg)](https://github.com/samchon/ttsc/actions?query=workflow%3Atest) [![Guide Documents](https://img.shields.io/badge/Guide-Documents-forestgreen)](https://ttsc.dev/docs) [![Discord Badge](https://img.shields.io/badge/discord-samchon-d91965?style=flat&labelColor=5866f2&logo=discord&logoColor=white&link=https://discord.gg/E94XhzrUCZ)](https://discord.gg/E94XhzrUCZ)
6
6
 
7
- A linter and formatter. Co-protagonist of the [`ttsc`](https://ttsc.dev) toolchain, paired with `ttsc`, it replaces `eslint` and `prettier`.
7
+ A linter and formatter. Co-protagonist of the [`ttsc`](https://ttsc.dev) toolchain, paired with `ttsc`, it replaces `eslint` and covers most of `prettier`.
8
8
 
9
9
  720+ rules across 21 families. Lint violations surface as `error TSxxxxx` from a single compile pass; the formatter applies via `ttsc format`.
10
10
 
@@ -102,6 +102,8 @@ npx ttsc format
102
102
 
103
103
  Configure the formatter through the `format` block in `lint.config.ts`. Keys mirror `.prettierrc`; the presence of the block, even empty `format: {}`, enables the always-on format rules at Prettier defaults so `ttsc format` rewrites your source to match.
104
104
 
105
+ One boundary to know before you drop `prettier`: no pass normalizes the whitespace between two tokens, so `a=1`, `if(x){`, and `const i : number` are left as written. See [Format → Scope](https://ttsc.dev/docs/lint/format#scope).
106
+
105
107
  ```ts
106
108
  // lint.config.ts
107
109
  import type { ITtscLintConfig } from "@ttsc/lint";
@@ -128,7 +130,7 @@ Each `format` key controls one behavior:
128
130
  | Config key | Effect |
129
131
  | --- | --- |
130
132
  | `severity` (default `"off"`) | Check-time diagnostic level for formatting. Does not gate `ttsc format`. |
131
- | `semi` | Insert trailing semicolons on ASI-terminated statements. |
133
+ | `semi` | Insert trailing semicolons on ASI-terminated statements, and own the member separator in interface, type-literal, mapped-type, and class bodies. |
132
134
  | `singleQuote` | Convert quoted strings to the preferred quote style. |
133
135
  | `arrowParens` | Add or remove parens around a single arrow parameter. |
134
136
  | `bracketSpacing` | Spaces inside object and named-import/export braces. |
package/lib/index.d.ts CHANGED
@@ -18,6 +18,9 @@ type TtscPluginDescriptor = {
18
18
  threadingArgs?: boolean;
19
19
  };
20
20
  contributors?: TtscPluginContributor[];
21
+ hostInputHashes?: Record<string, string | null>;
22
+ hostInputRealpaths?: Record<string, string | null>;
23
+ hostInputs?: string[];
21
24
  name: string;
22
25
  reportsTypeScriptDiagnostics?: boolean;
23
26
  source: string;
@@ -69,4 +72,4 @@ export default function createTtscPlugin(context: TtscPluginFactoryContext<ITtsc
69
72
  * template consumes its own escapes, so reading this file's text instead would
70
73
  * check characters no consumer ever sees.
71
74
  */
72
- export declare const TTSX_EXTRACTOR_SCRIPT = "// @ts-ignore -- internal loader must not require user-installed Node typings.\nimport * as fs from \"node:fs\";\n// @ts-ignore -- internal loader must not require user-installed Node typings.\nimport { Buffer } from \"node:buffer\";\n// @ts-ignore -- internal loader must not require user-installed Node typings.\nimport { createHash } from \"node:crypto\";\n// @ts-ignore -- internal loader must not require user-installed Node typings.\nimport { createRequire, registerHooks } from \"node:module\";\n// @ts-ignore -- internal loader must not require user-installed Node typings.\nimport * as path from \"node:path\";\n// @ts-ignore -- internal loader must not require user-installed Node typings.\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\n\nconst configUrl = %CONFIG_IMPORT%;\nconst outputPath = %CONFIG_OUTPUT%;\nconst resolutionRoot = path.resolve(%CONFIG_ROOT%);\nconst requireFromConfig = createRequire(configUrl);\nconst CONFIG_KEYS = new Set<string>([\n \"files\",\n \"ignores\",\n \"extends\",\n \"plugins\",\n \"rules\",\n \"format\",\n]);\nconst dependencies = new Map<string, {\n digest: string;\n kind: \"directory\" | \"file\" | \"optional-file\";\n path: string;\n owners: Set<string>;\n}>();\nconst graphNodes = new Map<string, string>();\nconst graphEdges: Array<{\n child: string;\n packageBoundary: boolean;\n parent: string;\n}> = [];\nconst configLocation = fileURLToPath(configUrl);\n// Every spelling of this config the module system might key an edge under.\n//\n// Which one it uses is not knowable from here, and guessing has failed in both\n// directions. A path handed in by another producer can be escaped by a rule\n// Node does not share. Node respells a resolved file module through its real\n// path unless \"--preserve-symlinks\" is set, so a config reached through a\n// symlinked directory is keyed by its target. And a Windows 8.3 short name is\n// not a symlink: fs.realpathSync expands it, the module resolver does not, so\n// asking the volume there produces a spelling no edge carries.\n//\n// A seed that names a URL no edge was keyed under sits on a node with no\n// outgoing edges, the walk ends immediately, and every dependency recorded\n// after the first import is demoted from watch to cache. That failure is\n// silent: the build still succeeds and simply stops reacting. Seeding every\n// spelling costs one extra queue entry and cannot be wrong.\nconst configUrlSpellings = [\n ...new Set([\n configUrl,\n pathToFileURL(configLocation).href,\n pathToFileURL(realConfigLocation()).href,\n ]),\n];\nfor (const spelling of configUrlSpellings) {\n graphNodes.set(spelling, configLocation);\n}\nrecordDependency(\n \"file\",\n configLocation,\n createHash(\"sha256\").update(fs.readFileSync(configLocation)).digest(\"hex\"),\n configUrlSpellings,\n);\nrecordPackageManifests(configLocation, configUrlSpellings);\n\ndeclare const process: {\n cwd(): string;\n platform: string;\n stdout: { write(value: string): void };\n stderr: { write(value: string): void };\n exit(code?: number): never;\n};\n\nconst hooks = registerHooks({\n resolve(specifier, context, nextResolve) {\n const resolved = nextResolve(specifier, context);\n if (typeof resolved.url !== \"string\" || !resolved.url.startsWith(\"file:\")) {\n return resolved;\n }\n const url = new URL(resolved.url).href;\n const parent = context.parentURL && new URL(context.parentURL).href;\n const location = fileURLToPath(url);\n // The entry is recognized by what was asked for, not only by what came\n // back. A module URL is assigned by whoever loaded it: a compiling loader\n // can serve the config from its emitted output, and a platform can hand\n // back a different spelling of the same file. Either way the URL bears no\n // resemblance to the one this process was given, so the config's own\n // imports would be rejected here \u2014 their parent is a URL no node was\n // recorded under \u2014 and the graph would collapse to the records made before\n // the first import. The request itself is unambiguous, so it decides.\n const entry =\n specifier === configUrl ||\n url === new URL(configUrl).href ||\n samePhysicalPath(location, configLocation);\n if (!entry && (parent === undefined || !graphNodes.has(parent))) {\n return resolved;\n }\n graphNodes.set(url, location);\n if (parent !== undefined) {\n graphEdges.push({\n child: url,\n packageBoundary:\n pathHasNodeModules(location) && !isLocalModuleSpecifier(specifier),\n parent,\n });\n recordResolutionTopology(\n specifier,\n parent,\n url,\n location,\n context.conditions,\n );\n }\n try {\n recordDependency(\n \"file\",\n location,\n createHash(\"sha256\").update(fs.readFileSync(location)).digest(\"hex\"),\n [url],\n );\n } catch {\n // The evaluator remains authoritative for the load error. An unreadable\n // dependency simply makes this result non-cacheable in the parent.\n recordDependency(\"file\", location, \"\", [url]);\n }\n return resolved;\n },\n});\n\n// Wrapped and settled explicitly rather than written as a top-level await.\n// The loader tsconfig's \"module\" now follows the config's own package, and\n// TS1378 rejects top-level await under a CommonJS module option however this\n// .mts file emits. The trailing catch is what a top-level await gave for\n// free: without it a throw from the finally would leave the promise\n// unsettled instead of failing the load.\n(async () => {\n try {\n const importedConfig = configLocation.toLowerCase().endsWith(\".json\")\n ? JSON.parse(fs.readFileSync(configLocation, \"utf8\").replace(/^\uFEFF/, \"\"))\n : await import(configUrl);\n const current = await resolveConfig(importedConfig, true);\n const pluginMaps = collectPluginObjects(current);\n const entries: Array<{ namespace: string; source: string }> = [];\n for (const map of pluginMaps) {\n for (const [namespace, value] of Object.entries(map)) {\n const source = extractPluginSource(value);\n if (source === undefined || source.length === 0) {\n throw new Error(\n `contributor ${JSON.stringify(namespace)} must resolve to an object with a non-empty \"source\" string`,\n );\n }\n entries.push({ namespace, source });\n }\n }\n fs.writeFileSync(outputPath, JSON.stringify({\n dependencies: finalizeDependencies(),\n entries,\n }), \"utf8\");\n } catch (error) {\n reportLoaderFailure(error);\n } finally {\n hooks.deregister();\n }\n})().catch((error) => {\n // Reached only when the finally above throws: the catch already ends the\n // process, so this is the deregistration's own failure, not the config's.\n reportLoaderFailure(error);\n});\n\n// reportLoaderFailure ends this loader on an error it can name, on both of the\n// channels the parent uses.\n//\n// The stack is for a reader and streams to stderr as it is written. The reason\n// is a fact about the user's config that a caller has to act on, so it travels\n// as data through the result file the parent already reads. Only a well-formed\n// envelope is honoured there, so a partially written or unrelated file leaves\n// the process status to speak for itself.\nfunction reportLoaderFailure(error: unknown): never {\n // The trailing newline ends the stack as a line of its own. Without it the\n // parent's own message, written to this same stream, starts mid-line.\n process.stderr.write((error instanceof Error && error.stack ? error.stack : String(error)) + \"\\n\");\n try {\n fs.writeFileSync(\n outputPath,\n JSON.stringify({ __ttscLoaderError: error instanceof Error ? error.message : String(error) }),\n \"utf8\",\n );\n } catch {\n // A reason that cannot be written leaves the exit status as the report.\n }\n return process.exit(1);\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\";\n}\n\nfunction recordDependency(\n kind: \"directory\" | \"file\" | \"optional-file\",\n location: string,\n digest: string,\n owners: readonly string[],\n): void {\n const key = kind + \"\\0\" + location;\n const previous = dependencies.get(key);\n const mergedOwners = previous?.owners ?? new Set<string>();\n for (const owner of owners) mergedOwners.add(owner);\n dependencies.set(key, {\n digest: previous !== undefined && previous.digest !== digest ? \"\" : digest,\n kind,\n owners: mergedOwners,\n path: location,\n });\n}\n\nfunction isLocalModuleSpecifier(specifier: string): boolean {\n return specifier.startsWith(\".\") ||\n specifier.startsWith(\"/\") ||\n specifier.startsWith(\"file:\") ||\n /^[A-Za-z]:[\\\\/]/.test(specifier);\n}\n\nfunction pathHasNodeModules(location: string): boolean {\n return location.replaceAll(\"\\\\\", \"/\").split(\"/\").includes(\"node_modules\");\n}\n\nfunction recordResolutionTopology(\n specifier: string,\n parentUrl: string,\n childUrl: string,\n childLocation: string,\n conditions: readonly string[],\n): void {\n const owners = [parentUrl, childUrl];\n const parentLocation = graphNodes.get(parentUrl);\n if (parentLocation !== undefined && isLocalModuleSpecifier(specifier)) {\n recordDirectoryDependency(path.dirname(parentLocation), owners);\n }\n recordDirectoryDependency(path.dirname(childLocation), owners);\n recordPackageManifests(childLocation, owners);\n if (parentLocation !== undefined && !isLocalModuleSpecifier(specifier)) {\n recordNodeModulesSearchDirectories(\n parentLocation,\n specifier,\n childLocation,\n owners,\n conditions,\n );\n }\n}\n\nfunction recordDirectoryDependency(\n location: string,\n owners: readonly string[],\n): void {\n try {\n recordDependency(\"directory\", location, directoryDigest(location), owners);\n } catch {\n recordDependency(\"directory\", location, \"\", owners);\n }\n}\n\nfunction directoryDigest(location: string): string {\n const entries: Buffer[] = [];\n if (process.platform === \"win32\") {\n for (const entry of fs.readdirSync(location, { withFileTypes: true })) {\n let target = Buffer.alloc(0);\n if (entry.isSymbolicLink()) {\n try {\n target = Buffer.from(\n fs.readlinkSync(path.join(location, entry.name)),\n \"utf8\",\n );\n } catch {\n target = Buffer.from(\"<unreadable>\");\n }\n }\n entries.push(directoryDigestRecord(Buffer.from(entry.name), entry, target));\n }\n } else {\n for (const entry of fs.readdirSync(location, {\n encoding: \"buffer\",\n withFileTypes: true,\n })) {\n let target = Buffer.alloc(0);\n if (entry.isSymbolicLink()) {\n try {\n target = fs.readlinkSync(\n Buffer.concat([\n Buffer.from(location),\n Buffer.from(path.sep),\n entry.name,\n ]),\n { encoding: \"buffer\" },\n );\n } catch {\n target = Buffer.from(\"<unreadable>\");\n }\n }\n entries.push(directoryDigestRecord(entry.name, entry, target));\n }\n }\n entries.sort(Buffer.compare);\n const serialized = Buffer.concat(\n entries.flatMap((entry, index) =>\n index === 0 ? [entry] : [Buffer.from([0]), entry],\n ),\n );\n return createHash(\"sha256\").update(serialized).digest(\"hex\");\n}\n\nfunction directoryDigestRecord(\n name: Buffer,\n entry: {\n isDirectory(): boolean;\n isFile(): boolean;\n isSymbolicLink(): boolean;\n },\n target: Buffer,\n): Buffer {\n const kind = entry.isDirectory()\n ? \"directory\"\n : entry.isFile()\n ? \"file\"\n : entry.isSymbolicLink()\n ? \"symlink\"\n : \"other\";\n return Buffer.concat([name, Buffer.from(\"\\0\" + kind + \"\\0\"), target]);\n}\n\nfunction optionalFileDigest(location: string): string {\n try {\n if (fs.statSync(location).isFile()) {\n return createHash(\"sha256\")\n .update(Buffer.concat([Buffer.from(\"file\\0\"), fs.readFileSync(location)]))\n .digest(\"hex\");\n }\n } catch {\n // Missing, unreadable, and non-file candidates share the absent state.\n }\n return createHash(\"sha256\").update(\"missing\\0\").digest(\"hex\");\n}\n\nfunction recordOptionalFileDependency(\n location: string,\n owners: readonly string[],\n): boolean {\n try {\n if (fs.statSync(location).isFile()) {\n recordDependency(\n \"file\",\n location,\n createHash(\"sha256\").update(fs.readFileSync(location)).digest(\"hex\"),\n owners,\n );\n return true;\n }\n } catch {\n // The exact missing path remains a dependency of the resolution result.\n }\n recordDependency(\"optional-file\", location, optionalFileDigest(location), owners);\n return false;\n}\n\nfunction recordPackageManifests(\n location: string,\n owners: readonly string[],\n): void {\n let current = path.dirname(location);\n while (true) {\n const manifest = path.join(current, \"package.json\");\n if (recordOptionalFileDependency(manifest, owners)) return;\n const parent = path.dirname(current);\n if (parent === current || path.basename(current) === \"node_modules\") return;\n current = parent;\n }\n}\n\nfunction recordNodeModulesSearchDirectories(\n parentLocation: string,\n specifier: string,\n childLocation: string,\n owners: readonly string[],\n conditions: readonly string[],\n): void {\n const packageName = modulePackageName(specifier);\n const scope =\n specifier.startsWith(\"@\") && specifier.includes(\"/\")\n ? specifier.slice(0, specifier.indexOf(\"/\"))\n : undefined;\n let current = path.dirname(parentLocation);\n while (true) {\n // A newly created nearer node_modules directory can shadow the package\n // selected by this evaluation, so missing search levels are dependencies.\n recordDirectoryDependency(current, owners);\n const modules = path.join(current, \"node_modules\");\n try {\n if (fs.statSync(modules).isDirectory()) {\n recordDirectoryDependency(modules, owners);\n if (scope !== undefined) {\n const scoped = path.join(modules, scope);\n try {\n if (fs.statSync(scoped).isDirectory()) {\n recordDirectoryDependency(scoped, owners);\n }\n } catch {\n // The directory digest of node_modules records a missing scope.\n }\n }\n if (packageName !== undefined) {\n const selected = recordPackageCandidateTopology(\n modules,\n packageName,\n specifier,\n childLocation,\n owners,\n conditions,\n );\n if (\n selected ||\n resolvedPackageContains(modules, packageName, childLocation)\n ) {\n return;\n }\n }\n }\n } catch {\n // Missing search levels do not participate in the current resolution.\n }\n if (\n packageName === undefined &&\n samePhysicalPath(current, resolutionRoot)\n ) {\n return;\n }\n const parent = path.dirname(current);\n if (parent === current) return;\n current = parent;\n }\n}\n\nfunction recordPackageCandidateTopology(\n modules: string,\n packageName: string,\n specifier: string,\n childLocation: string,\n owners: readonly string[],\n conditions: readonly string[],\n): boolean {\n const packageRoot = path.join(modules, packageName);\n try {\n if (!fs.statSync(packageRoot).isDirectory()) return false;\n } catch {\n return false;\n }\n const subpath = specifier\n .slice(packageName.length)\n .replace(/^[/\\\\]+/, \"\");\n const rootTopology = recordPackageRootTopology(\n packageRoot,\n owners,\n subpath === \"\",\n subpath === \"\" ? \".\" : \"./\" + subpath.replaceAll(\"\\\\\", \"/\"),\n childLocation,\n conditions,\n );\n if (subpath !== \"\" && !rootTopology.hasExports) {\n return (\n recordPackageSubpathTopology(\n packageRoot,\n subpath,\n childLocation,\n owners,\n ) || rootTopology.selected\n );\n }\n return rootTopology.selected;\n}\n\nfunction recordPackageRootTopology(\n packageRoot: string,\n owners: readonly string[],\n useMain: boolean,\n packageSubpath: string,\n childLocation: string,\n conditions: readonly string[],\n): { hasExports: boolean; selected: boolean } {\n const normalizedRoot = path.resolve(packageRoot);\n const manifest = path.join(normalizedRoot, \"package.json\");\n const legacySelected = (): boolean =>\n useMain &&\n packagePathCandidateMatchesChild(normalizedRoot, childLocation, true);\n if (!recordOptionalFileDependency(manifest, owners)) {\n const selected = legacySelected();\n if (!selected) {\n recordPackageIndexCandidates(normalizedRoot, useMain, owners);\n }\n return { hasExports: false, selected };\n }\n try {\n const value = JSON.parse(fs.readFileSync(manifest, \"utf8\"));\n if (value !== null && typeof value === \"object\") {\n const metadata = value as Record<string, unknown>;\n const hasExports =\n metadata.exports !== undefined && metadata.exports !== null;\n if (hasExports) {\n const target = selectPackageExportsTarget(\n metadata.exports,\n packageSubpath,\n new Set(conditions),\n );\n const candidate =\n typeof target === \"string\"\n ? packageExportsTarget(normalizedRoot, target)\n : undefined;\n const selected =\n candidate !== undefined &&\n packagePathCandidateMatchesChild(\n candidate,\n childLocation,\n false,\n );\n if (selected) {\n recordPackagePathCandidate(candidate, owners);\n } else if (candidate !== undefined) {\n // A nearer package the search skipped starts winning the moment its\n // own active target appears, and neither the parent node_modules\n // listing nor the manifest changes when only that file is created.\n recordOptionalFileDependency(candidate, owners);\n }\n return { hasExports: true, selected };\n }\n let selected = legacySelected();\n if (useMain && typeof metadata.main === \"string\") {\n // CommonJS main is a legacy path, not an exports target. Node resolves\n // it literally and permits absolute paths and paths outside the package.\n const main = path.resolve(normalizedRoot, metadata.main);\n recordPackagePathCandidate(main, owners);\n selected =\n packagePathCandidateMatchesChild(main, childLocation, true) ||\n selected;\n }\n if (!selected) {\n recordPackageIndexCandidates(normalizedRoot, useMain, owners);\n }\n return {\n hasExports: false,\n selected,\n };\n }\n } catch {\n // Node owns malformed-manifest diagnostics; the manifest digest is enough\n // to invalidate this evaluation when its contents change.\n }\n const selected = legacySelected();\n if (!selected) {\n recordPackageIndexCandidates(normalizedRoot, useMain, owners);\n }\n return { hasExports: false, selected };\n}\n\n// recordPackageIndexCandidates pins the LOAD_INDEX fallbacks of a package root\n// this resolution walked past without selecting. An empty package directory, or\n// one whose manifest declares no usable entry, becomes resolvable as soon as one\n// of these files exists, and that creation changes neither the parent directory\n// listing nor the manifest digest already recorded for the candidate.\nfunction recordPackageIndexCandidates(\n packageRoot: string,\n useMain: boolean,\n owners: readonly string[],\n): void {\n if (!useMain) return;\n for (const name of [\"index.js\", \"index.json\", \"index.node\"]) {\n recordOptionalFileDependency(path.join(packageRoot, name), owners);\n }\n}\n\nfunction selectPackageExportsTarget(\n exportsValue: unknown,\n packageSubpath: string,\n conditions: ReadonlySet<string>,\n): string | null | undefined {\n let mappings: unknown = exportsValue;\n if (\n typeof mappings === \"string\" ||\n Array.isArray(mappings) ||\n (isObject(mappings) &&\n Object.keys(mappings).every((key) => !key.startsWith(\".\")))\n ) {\n if (packageSubpath !== \".\") return undefined;\n return selectPackageTarget(mappings, \"\", false, conditions);\n }\n if (!isObject(mappings)) return undefined;\n if (\n Object.prototype.hasOwnProperty.call(mappings, packageSubpath) &&\n !packageSubpath.includes(\"*\") &&\n !packageSubpath.endsWith(\"/\")\n ) {\n return selectPackageTarget(\n mappings[packageSubpath],\n \"\",\n false,\n conditions,\n );\n }\n let bestMatch = \"\";\n let bestSubpath = \"\";\n for (const key of Object.keys(mappings)) {\n const wildcard = key.indexOf(\"*\");\n if (\n wildcard === -1 ||\n key.lastIndexOf(\"*\") !== wildcard ||\n !packageSubpath.startsWith(key.slice(0, wildcard))\n ) {\n continue;\n }\n const trailer = key.slice(wildcard + 1);\n if (\n packageSubpath.length < key.length ||\n !packageSubpath.endsWith(trailer) ||\n packagePatternKeyCompare(bestMatch, key) !== 1\n ) {\n continue;\n }\n bestMatch = key;\n bestSubpath = packageSubpath.slice(\n wildcard,\n packageSubpath.length - trailer.length,\n );\n }\n return bestMatch === \"\"\n ? undefined\n : selectPackageTarget(\n mappings[bestMatch],\n bestSubpath,\n true,\n conditions,\n );\n}\n\nfunction selectPackageTarget(\n target: unknown,\n subpath: string,\n pattern: boolean,\n conditions: ReadonlySet<string>,\n): string | null | undefined {\n if (typeof target === \"string\") {\n const selected = pattern ? target.replaceAll(\"*\", subpath) : target;\n return validPackageExportsTarget(selected) ? selected : undefined;\n }\n if (Array.isArray(target)) {\n for (const item of target) {\n const selected = selectPackageTarget(\n item,\n subpath,\n pattern,\n conditions,\n );\n if (selected !== undefined && selected !== null) return selected;\n }\n return null;\n }\n if (isObject(target)) {\n for (const [condition, value] of Object.entries(target)) {\n if (condition !== \"default\" && !conditions.has(condition)) continue;\n const selected = selectPackageTarget(\n value,\n subpath,\n pattern,\n conditions,\n );\n if (selected !== undefined) return selected;\n }\n return undefined;\n }\n return target === null ? null : undefined;\n}\n\nfunction packagePatternKeyCompare(left: string, right: string): number {\n const leftWildcard = left.indexOf(\"*\");\n const rightWildcard = right.indexOf(\"*\");\n const leftBase =\n leftWildcard === -1 ? left.length : leftWildcard + 1;\n const rightBase =\n rightWildcard === -1 ? right.length : rightWildcard + 1;\n if (leftBase > rightBase) return -1;\n if (rightBase > leftBase) return 1;\n if (leftWildcard === -1) return 1;\n if (rightWildcard === -1) return -1;\n if (left.length > right.length) return -1;\n if (right.length > left.length) return 1;\n return 0;\n}\n\nfunction packageExportsTarget(\n packageRoot: string,\n target: string,\n): string | undefined {\n if (!validPackageExportsTarget(target)) return undefined;\n try {\n // Node resolves an exports target as a URL against the package manifest,\n // so percent escapes, query strings, and fragments all take part in the\n // path it finally loads. Joining the raw target by hand diverges from that\n // whenever the target is anything but a plain relative path, and a target\n // Node resolves while this model rejects loses the selected file's\n // fingerprint, leaving a retargeted symlink cached as fresh.\n const packageUrl = pathToFileURL(path.join(packageRoot, \"package.json\"));\n const resolved = new URL(target, packageUrl);\n const packagePath = new URL(\".\", packageUrl).pathname;\n if (!resolved.pathname.startsWith(packagePath)) return undefined;\n return fileURLToPath(resolved);\n } catch {\n return undefined;\n }\n}\n\nfunction validPackageExportsTarget(target: string): boolean {\n if (!target.startsWith(\"./\") || /%2f|%5c/i.test(target)) return false;\n const components = target\n .slice(2)\n .replaceAll(\"\\\\\", \"/\")\n .split(\"/\");\n if (\n components.some(\n (component) => {\n try {\n const decoded = decodeURIComponent(component);\n return (\n decoded === \".\" ||\n decoded === \"..\" ||\n decoded.includes(\"/\") ||\n decoded.includes(\"\\\\\") ||\n decoded.toLowerCase() === \"node_modules\"\n );\n } catch {\n return true;\n }\n },\n )\n ) {\n return false;\n }\n return true;\n}\n\nfunction packagePathCandidateMatchesChild(\n candidate: string,\n childLocation: string,\n legacy: boolean,\n): boolean {\n let child: string;\n try {\n child = fs.realpathSync.native(childLocation);\n } catch {\n child = path.resolve(childLocation);\n }\n const candidates = legacy\n ? [\n candidate,\n candidate + \".js\",\n candidate + \".json\",\n candidate + \".node\",\n path.join(candidate, \"index.js\"),\n path.join(candidate, \"index.json\"),\n path.join(candidate, \"index.node\"),\n ]\n : [candidate];\n return candidates.some((location) => {\n try {\n return sameResolutionPath(fs.realpathSync.native(location), child);\n } catch {\n return false;\n }\n });\n}\n\nfunction recordPackageSubpathTopology(\n packageRoot: string,\n subpath: string,\n childLocation: string,\n owners: readonly string[],\n): boolean {\n const candidate = boundedPackageTarget(packageRoot, subpath);\n if (candidate === undefined) return false;\n recordPackagePathCandidate(candidate, owners);\n let selected = packagePathCandidateMatchesChild(\n candidate,\n childLocation,\n true,\n );\n try {\n if (!fs.statSync(candidate).isDirectory()) return selected;\n } catch {\n return selected;\n }\n const manifest = path.join(candidate, \"package.json\");\n if (!recordOptionalFileDependency(manifest, owners)) return selected;\n try {\n const value = JSON.parse(fs.readFileSync(manifest, \"utf8\"));\n if (value !== null && typeof value === \"object\") {\n const metadata = value as Record<string, unknown>;\n if (typeof metadata.main === \"string\") {\n const main = path.resolve(candidate, metadata.main);\n recordPackagePathCandidate(main, owners);\n selected =\n packagePathCandidateMatchesChild(main, childLocation, true) ||\n selected;\n }\n }\n } catch {\n // Node owns malformed nested-package diagnostics.\n }\n return selected;\n}\n\nfunction boundedPackageTarget(\n packageRoot: string,\n target: string,\n): string | undefined {\n const candidate = path.resolve(packageRoot, target);\n const relative = path.relative(packageRoot, candidate);\n if (\n relative === \"..\" ||\n relative.startsWith(\"..\" + path.sep) ||\n path.isAbsolute(relative)\n ) {\n return undefined;\n }\n return candidate;\n}\n\nfunction recordPackagePathCandidate(\n candidate: string,\n owners: readonly string[],\n visited: Set<string> = new Set(),\n depth = 0,\n): void {\n const normalized = path.resolve(candidate);\n // The depth bound owns termination. A platform-wide case fold would merge\n // paths that differ only by case, which a per-directory case-sensitive\n // Windows tree keeps distinct, and would truncate a valid symlink chain.\n if (depth >= 64 || visited.has(normalized)) return;\n visited.add(normalized);\n const parsed = path.parse(normalized);\n const components = normalized\n .slice(parsed.root.length)\n .split(path.sep)\n .filter(Boolean);\n let current = parsed.root;\n for (let index = 0; index < components.length; index++) {\n const component = components[index];\n const next = path.join(current, component);\n let entry: ReturnType<typeof fs.lstatSync>;\n try {\n entry = fs.lstatSync(next);\n } catch {\n recordDirectoryDependency(current, owners);\n return;\n }\n if (entry.isSymbolicLink()) {\n // The containing directory digest carries the raw link target.\n recordDirectoryDependency(current, owners);\n try {\n const target = fs.readlinkSync(next);\n const remainder = components.slice(index + 1);\n recordPackagePathCandidate(\n path.join(\n path.resolve(current, target),\n ...remainder,\n ),\n owners,\n visited,\n depth + 1,\n );\n } catch {\n // The lexical link record already carries the unreadable state.\n }\n }\n let isDirectory = entry.isDirectory();\n if (entry.isSymbolicLink()) {\n try {\n isDirectory = fs.statSync(next).isDirectory();\n } catch {\n return;\n }\n }\n if (index === components.length - 1) {\n recordDirectoryDependency(isDirectory ? next : current, owners);\n return;\n }\n if (!isDirectory) {\n recordDirectoryDependency(current, owners);\n return;\n }\n current = next;\n }\n recordDirectoryDependency(current, owners);\n}\n\nfunction modulePackageName(specifier: string): string | undefined {\n if (specifier.startsWith(\"@\")) {\n const components = specifier.split(\"/\");\n return components.length >= 2\n ? components[0] + \"/\" + components[1]\n : undefined;\n }\n const [name] = specifier.split(\"/\");\n return name && !name.startsWith(\"#\") ? name : undefined;\n}\n\nfunction resolvedPackageContains(\n modules: string,\n packageName: string,\n childLocation: string,\n): boolean {\n try {\n const packageRoot = fs.realpathSync(path.join(modules, packageName));\n const relative = path.relative(\n packageRoot,\n fs.realpathSync(childLocation),\n );\n return (\n relative === \"\" ||\n (relative !== \"..\" &&\n !relative.startsWith(\"..\" + path.sep) &&\n !path.isAbsolute(relative))\n );\n } catch {\n return false;\n }\n}\n\nfunction sameResolutionPath(left: string, right: string): boolean {\n return path.relative(left, right) === \"\";\n}\n\nfunction samePhysicalPath(left: string, right: string): boolean {\n try {\n return sameResolutionPath(realPath(left), realPath(right));\n } catch {\n // Fall back to the spellings themselves, folding case the way the platform\n // does. On the entry gate a false negative is catastrophic \u2014 the config\n // stops being recognized and its whole graph collapses \u2014 while a false\n // positive only over-includes, so the degradation has to lean toward \"same\n // file\". A drive-letter or component case difference is the ordinary\n // Windows situation; a per-directory case-sensitive tree is the rare one.\n return sameResolutionPath(left, right);\n }\n}\n\n/**\n * The config's real path, or its declared one when the volume will not say.\n *\n * A config can disappear between the host reading it and this loader starting,\n * and a throw here would replace a precise report from the import below with a\n * crash in bookkeeping. Seeding lexically instead only risks the demotion this\n * value exists to prevent, on a file that is already gone.\n */\nfunction realConfigLocation(): string {\n try {\n return realPath(configLocation);\n } catch {\n return configLocation;\n }\n}\n\nfunction realPath(location: string): string {\n return fs.realpathSync.native\n ? fs.realpathSync.native(location)\n : fs.realpathSync(location);\n}\n\nfunction finalizeDependencies(): Array<{\n digest: string;\n kind: \"directory\" | \"file\" | \"optional-file\";\n path: string;\n scope: \"cache\" | \"watch\";\n}> {\n const watched = graphWatchReachability();\n return [...dependencies.values()].map(({ owners, ...dependency }) => ({\n ...dependency,\n scope: [...owners].some((owner) => watched.has(owner))\n ? \"watch\"\n : \"cache\",\n }));\n}\n\nfunction graphWatchReachability(): Set<string> {\n const adjacency = new Map<string, typeof graphEdges>();\n for (const edge of graphEdges) {\n const outgoing = adjacency.get(edge.parent) ?? [];\n outgoing.push(edge);\n adjacency.set(edge.parent, outgoing);\n }\n const queue: Array<{ url: string; watched: boolean }> =\n configUrlSpellings.map((url) => ({ url, watched: true }));\n const visited = new Set<string>();\n const watched = new Set<string>();\n while (queue.length !== 0) {\n const state = queue.shift()!;\n const key = state.url + \"\\0\" + (state.watched ? \"1\" : \"0\");\n if (visited.has(key)) continue;\n visited.add(key);\n if (state.watched) watched.add(state.url);\n for (const edge of adjacency.get(state.url) ?? []) {\n const childLocation = graphNodes.get(edge.child);\n const childWatched = edge.packageBoundary\n ? false\n : childLocation !== undefined && !pathHasNodeModules(childLocation)\n ? true\n : state.watched;\n queue.push({ url: edge.child, watched: childWatched });\n }\n }\n return watched;\n}\n\nfunction hasOwn(value: Record<string, unknown>, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(value, key);\n}\n\nasync function resolveConfig(value: unknown, allowNamedConfig: boolean): Promise<unknown> {\n let current = value;\n for (let i = 0; i < 8; i++) {\n if (typeof current === \"function\") {\n current = await (current as () => unknown | Promise<unknown>)();\n allowNamedConfig = false;\n continue;\n }\n if (isObject(current) && !Array.isArray(current)) {\n if (hasOwn(current, \"default\")) {\n const defaultValue = current.default;\n if (isModuleNamespace(current) || !hasConfigKey(current)) {\n current = defaultValue;\n allowNamedConfig = false;\n continue;\n }\n const normalizedDefault = await resolveConfig(defaultValue, false);\n if (isObject(normalizedDefault) && !Array.isArray(normalizedDefault)) {\n current = mergeConfigObjects(normalizedDefault, current);\n allowNamedConfig = false;\n continue;\n }\n }\n if (allowNamedConfig && hasOwn(current, \"config\")) {\n current = current.config;\n allowNamedConfig = false;\n continue;\n }\n }\n break;\n }\n return current;\n}\n\nfunction isModuleNamespace(value: Record<string, unknown>): boolean {\n return Object.prototype.toString.call(value) === \"[object Module]\";\n}\n\nfunction hasConfigKey(value: Record<string, unknown>): boolean {\n for (const key of CONFIG_KEYS) {\n if (hasOwn(value, key)) {\n return true;\n }\n }\n return false;\n}\n\nfunction mergeConfigObjects(\n base: Record<string, unknown>,\n override: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const key of CONFIG_KEYS) {\n if (hasOwn(base, key)) {\n out[key] = base[key];\n }\n }\n for (const key of CONFIG_KEYS) {\n if (hasOwn(override, key)) {\n out[key] = override[key];\n }\n }\n return out;\n}\n\nfunction collectPluginObjects(value: unknown): Array<Record<string, unknown>> {\n const out: Array<Record<string, unknown>> = [];\n visit(value);\n return out;\n\n function visit(node: unknown): void {\n if (Array.isArray(node)) {\n for (const item of node) visit(item);\n return;\n }\n if (!isObject(node)) return;\n if (hasOwn(node, \"plugins\") && isObject(node.plugins)) {\n out.push(node.plugins as Record<string, unknown>);\n }\n }\n}\n\nfunction extractPluginSource(value: unknown): string | undefined {\n if (typeof value === \"string\") {\n value = requireFromConfig(value);\n }\n if (!isObject(value)) return undefined;\n // ESM-from-CJS interop wraps CJS modules' `exports.default` so the\n // plugin object can land under a `.default` indirection. Walk a few\n // hops so contributors authored as `export default plugin` and\n // contributors authored as plain `module.exports = plugin` both\n // resolve identically.\n let current: Record<string, unknown> = value;\n // 8 hops to match the outer-process unwrapDefault helper; previously\n // 4, which silently misrouted deeply re-exported plugins while\n // unwrapDefault would have unwrapped them.\n for (let i = 0; i < 8; i++) {\n if (typeof current.source === \"string\") break;\n const next = current.default;\n if (!isObject(next)) break;\n current = next;\n }\n const source = current.source;\n return typeof source === \"string\" ? source : undefined;\n}\n";
75
+ export declare const TTSX_EXTRACTOR_SCRIPT = "// @ts-ignore -- internal loader must not require user-installed Node typings.\nimport * as fs from \"node:fs\";\n// @ts-ignore -- internal loader must not require user-installed Node typings.\nimport { Buffer } from \"node:buffer\";\n// @ts-ignore -- internal loader must not require user-installed Node typings.\nimport { createHash } from \"node:crypto\";\n// @ts-ignore -- internal loader must not require user-installed Node typings.\nimport { createRequire, registerHooks } from \"node:module\";\n// @ts-ignore -- internal loader must not require user-installed Node typings.\nimport * as path from \"node:path\";\n// @ts-ignore -- internal loader must not require user-installed Node typings.\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\n\nconst configUrl = %CONFIG_IMPORT%;\nconst outputPath = %CONFIG_OUTPUT%;\nconst resolutionRoot = path.resolve(%CONFIG_ROOT%);\nconst requireFromConfig = createRequire(configUrl);\nconst CONFIG_KEYS = new Set<string>([\n \"files\",\n \"ignores\",\n \"extends\",\n \"plugins\",\n \"rules\",\n \"format\",\n]);\nconst dependencies = new Map<string, {\n digest: string;\n identityStable: boolean;\n kind: \"directory\" | \"file\" | \"optional-file\";\n path: string;\n owners: Set<string>;\n realpath: string | null;\n}>();\nconst graphNodes = new Map<string, string>();\nconst graphEdges: Array<{\n child: string;\n packageBoundary: boolean;\n parent: string;\n}> = [];\nconst configLocation = fileURLToPath(configUrl);\n// Every spelling of this config the module system might key an edge under.\n//\n// Which one it uses is not knowable from here, and guessing has failed in both\n// directions. A path handed in by another producer can be escaped by a rule\n// Node does not share. Node respells a resolved file module through its real\n// path unless \"--preserve-symlinks\" is set, so a config reached through a\n// symlinked directory is keyed by its target. And a Windows 8.3 short name is\n// not a symlink: fs.realpathSync expands it, the module resolver does not, so\n// asking the volume there produces a spelling no edge carries.\n//\n// A seed that names a URL no edge was keyed under sits on a node with no\n// outgoing edges, the walk ends immediately, and every dependency recorded\n// after the first import is demoted from watch to cache. That failure is\n// silent: the build still succeeds and simply stops reacting. Seeding every\n// spelling costs one extra queue entry and cannot be wrong.\nconst configUrlSpellings = [\n ...new Set([\n configUrl,\n pathToFileURL(configLocation).href,\n pathToFileURL(realConfigLocation()).href,\n ]),\n];\nconst moduleProbeExtensions = [\n \".ts\",\n \".tsx\",\n \".mts\",\n \".cts\",\n \".js\",\n \".mjs\",\n \".cjs\",\n \".json\",\n \".node\",\n] as const;\nconst jsToTsProbeExtensions = new Map<string, readonly string[]>([\n [\".js\", [\".ts\", \".tsx\"]],\n [\".jsx\", [\".tsx\"]],\n [\".mjs\", [\".mts\"]],\n [\".cjs\", [\".cts\"]],\n]);\nfor (const spelling of configUrlSpellings) {\n graphNodes.set(spelling, configLocation);\n}\nrecordDependency(\n \"file\",\n configLocation,\n createHash(\"sha256\").update(fs.readFileSync(configLocation)).digest(\"hex\"),\n configUrlSpellings,\n);\nrecordPackageManifests(configLocation, configUrlSpellings);\n\ndeclare const process: {\n cwd(): string;\n platform: string;\n stdout: { write(value: string): void };\n stderr: { write(value: string): void };\n exit(code?: number): never;\n};\n\nconst hooks = registerHooks({\n resolve(specifier, context, nextResolve) {\n const requestedParent =\n context.parentURL && new URL(context.parentURL).href;\n if (requestedParent !== undefined && graphNodes.has(requestedParent)) {\n recordLocalResolutionCandidates(specifier, requestedParent);\n }\n const resolved = nextResolve(specifier, context);\n if (typeof resolved.url !== \"string\" || !resolved.url.startsWith(\"file:\")) {\n return resolved;\n }\n const url = new URL(resolved.url).href;\n const parent = context.parentURL && new URL(context.parentURL).href;\n const location = fileURLToPath(url);\n // The entry is recognized by what was asked for, not only by what came\n // back. A module URL is assigned by whoever loaded it: a compiling loader\n // can serve the config from its emitted output, and a platform can hand\n // back a different spelling of the same file. Either way the URL bears no\n // resemblance to the one this process was given, so the config's own\n // imports would be rejected here \u2014 their parent is a URL no node was\n // recorded under \u2014 and the graph would collapse to the records made before\n // the first import. The request itself is unambiguous, so it decides.\n const entry =\n specifier === configUrl ||\n url === new URL(configUrl).href ||\n samePhysicalPath(location, configLocation);\n if (!entry && (parent === undefined || !graphNodes.has(parent))) {\n return resolved;\n }\n graphNodes.set(url, location);\n if (parent !== undefined) {\n graphEdges.push({\n child: url,\n packageBoundary:\n pathHasNodeModules(location) && !isLocalModuleSpecifier(specifier),\n parent,\n });\n recordResolutionTopology(\n specifier,\n parent,\n url,\n location,\n context.conditions,\n );\n }\n try {\n recordDependency(\n \"file\",\n location,\n createHash(\"sha256\").update(fs.readFileSync(location)).digest(\"hex\"),\n [url],\n );\n } catch {\n // The evaluator remains authoritative for the load error. An unreadable\n // dependency simply makes this result non-cacheable in the parent.\n recordDependency(\"file\", location, \"\", [url]);\n }\n return resolved;\n },\n});\n\n// Wrapped and settled explicitly rather than written as a top-level await.\n// The loader tsconfig's \"module\" now follows the config's own package, and\n// TS1378 rejects top-level await under a CommonJS module option however this\n// .mts file emits. The trailing catch is what a top-level await gave for\n// free: without it a throw from the finally would leave the promise\n// unsettled instead of failing the load.\n(async () => {\n try {\n const importedConfig = configLocation.toLowerCase().endsWith(\".json\")\n ? JSON.parse(fs.readFileSync(configLocation, \"utf8\").replace(/^\uFEFF/, \"\"))\n : await import(configUrl);\n const current = await resolveConfig(importedConfig, true);\n const pluginMaps = collectPluginObjects(current);\n const entries: Array<{ namespace: string; source: string }> = [];\n for (const map of pluginMaps) {\n for (const [namespace, value] of Object.entries(map)) {\n const source = extractPluginSource(value);\n if (source === undefined || source.length === 0) {\n throw new Error(\n `contributor ${JSON.stringify(namespace)} must resolve to an object with a non-empty \"source\" string`,\n );\n }\n entries.push({ namespace, source });\n }\n }\n fs.writeFileSync(outputPath, JSON.stringify({\n dependencies: finalizeDependencies(),\n entries,\n }), \"utf8\");\n } catch (error) {\n reportLoaderFailure(error);\n } finally {\n hooks.deregister();\n }\n})().catch((error) => {\n // Reached only when the finally above throws: the catch already ends the\n // process, so this is the deregistration's own failure, not the config's.\n reportLoaderFailure(error);\n});\n\n// reportLoaderFailure ends this loader on an error it can name, on both of the\n// channels the parent uses.\n//\n// The stack is for a reader and streams to stderr as it is written. The reason\n// is a fact about the user's config that a caller has to act on, so it travels\n// as data through the result file the parent already reads. Only a well-formed\n// envelope is honoured there, so a partially written or unrelated file leaves\n// the process status to speak for itself.\nfunction reportLoaderFailure(error: unknown): never {\n // The trailing newline ends the stack as a line of its own. Without it the\n // parent's own message, written to this same stream, starts mid-line.\n process.stderr.write((error instanceof Error && error.stack ? error.stack : String(error)) + \"\\n\");\n try {\n fs.writeFileSync(\n outputPath,\n JSON.stringify({ __ttscLoaderError: error instanceof Error ? error.message : String(error) }),\n \"utf8\",\n );\n } catch {\n // A reason that cannot be written leaves the exit status as the report.\n }\n return process.exit(1);\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\";\n}\n\nfunction recordDependency(\n kind: \"directory\" | \"file\" | \"optional-file\",\n location: string,\n digest: string,\n owners: readonly string[],\n): void {\n const key = kind + \"\\0\" + location;\n const previous = dependencies.get(key);\n const mergedOwners = previous?.owners ?? new Set<string>();\n for (const owner of owners) mergedOwners.add(owner);\n const realpath = dependencyRealpath(location);\n const identityStable =\n previous?.identityStable !== false &&\n (previous === undefined || previous.realpath === realpath);\n dependencies.set(key, {\n digest:\n !identityStable ||\n (previous !== undefined && previous.digest !== digest)\n ? \"\"\n : digest,\n identityStable,\n kind,\n owners: mergedOwners,\n path: location,\n realpath,\n });\n}\n\nfunction dependencyRealpath(location: string): string | null {\n try {\n return realPath(location);\n } catch {\n return null;\n }\n}\n\nfunction isLocalModuleSpecifier(specifier: string): boolean {\n return specifier.startsWith(\".\") ||\n specifier.startsWith(\"/\") ||\n specifier.startsWith(\"file:\") ||\n /^[A-Za-z]:[\\\\/]/.test(specifier);\n}\n\nfunction pathHasNodeModules(location: string): boolean {\n return location.replaceAll(\"\\\\\", \"/\").split(\"/\").includes(\"node_modules\");\n}\n\nfunction recordResolutionTopology(\n specifier: string,\n parentUrl: string,\n childUrl: string,\n childLocation: string,\n conditions: readonly string[],\n): void {\n const owners = [parentUrl, childUrl];\n const parentLocation = graphNodes.get(parentUrl);\n if (parentLocation !== undefined && isLocalModuleSpecifier(specifier)) {\n recordDirectoryDependency(path.dirname(parentLocation), owners);\n }\n recordDirectoryDependency(path.dirname(childLocation), owners);\n recordPackageManifests(childLocation, owners);\n if (parentLocation !== undefined && !isLocalModuleSpecifier(specifier)) {\n recordNodeModulesSearchDirectories(\n parentLocation,\n specifier,\n childLocation,\n owners,\n conditions,\n );\n }\n}\n\nfunction recordDirectoryDependency(\n location: string,\n owners: readonly string[],\n): void {\n try {\n recordDependency(\"directory\", location, directoryDigest(location), owners);\n } catch {\n recordDependency(\"directory\", location, \"\", owners);\n }\n}\n\nfunction directoryDigest(location: string): string {\n const entries: Buffer[] = [];\n if (process.platform === \"win32\") {\n for (const entry of fs.readdirSync(location, { withFileTypes: true })) {\n let target = Buffer.alloc(0);\n if (entry.isSymbolicLink()) {\n try {\n target = Buffer.from(\n fs.readlinkSync(path.join(location, entry.name)),\n \"utf8\",\n );\n } catch {\n target = Buffer.from(\"<unreadable>\");\n }\n }\n entries.push(directoryDigestRecord(Buffer.from(entry.name), entry, target));\n }\n } else {\n for (const entry of fs.readdirSync(location, {\n encoding: \"buffer\",\n withFileTypes: true,\n })) {\n let target = Buffer.alloc(0);\n if (entry.isSymbolicLink()) {\n try {\n target = fs.readlinkSync(\n Buffer.concat([\n Buffer.from(location),\n Buffer.from(path.sep),\n entry.name,\n ]),\n { encoding: \"buffer\" },\n );\n } catch {\n target = Buffer.from(\"<unreadable>\");\n }\n }\n entries.push(directoryDigestRecord(entry.name, entry, target));\n }\n }\n entries.sort(Buffer.compare);\n const serialized = Buffer.concat(\n entries.flatMap((entry, index) =>\n index === 0 ? [entry] : [Buffer.from([0]), entry],\n ),\n );\n return createHash(\"sha256\").update(serialized).digest(\"hex\");\n}\n\nfunction directoryDigestRecord(\n name: Buffer,\n entry: {\n isDirectory(): boolean;\n isFile(): boolean;\n isSymbolicLink(): boolean;\n },\n target: Buffer,\n): Buffer {\n const kind = entry.isDirectory()\n ? \"directory\"\n : entry.isFile()\n ? \"file\"\n : entry.isSymbolicLink()\n ? \"symlink\"\n : \"other\";\n return Buffer.concat([name, Buffer.from(\"\\0\" + kind + \"\\0\"), target]);\n}\n\nfunction optionalFileDigest(location: string): string {\n try {\n const entry = fs.statSync(location);\n if (entry.isFile()) {\n return createHash(\"sha256\")\n .update(Buffer.concat([Buffer.from(\"file\\0\"), fs.readFileSync(location)]))\n .digest(\"hex\");\n }\n if (entry.isDirectory()) {\n return createHash(\"sha256\")\n .update(\"ttsc:host-input:directory\\0\")\n .digest(\"hex\");\n }\n } catch {\n // Missing and unreadable candidates share the absent state.\n }\n return createHash(\"sha256\").update(\"missing\\0\").digest(\"hex\");\n}\n\nfunction recordOptionalFileDependency(\n location: string,\n owners: readonly string[],\n): boolean {\n try {\n if (fs.statSync(location).isFile()) {\n recordDependency(\n \"file\",\n location,\n createHash(\"sha256\").update(fs.readFileSync(location)).digest(\"hex\"),\n owners,\n );\n return true;\n }\n } catch {\n // The exact missing path remains a dependency of the resolution result.\n }\n recordDependency(\"optional-file\", location, optionalFileDigest(location), owners);\n return false;\n}\n\nfunction moduleResolutionCandidates(base: string): string[] {\n const extension = path.extname(base).toLowerCase();\n const substitutions = jsToTsProbeExtensions.get(extension) ?? [];\n const stem = base.slice(0, base.length - extension.length);\n return [\n base,\n ...substitutions.map((candidate) => stem + candidate),\n ...moduleProbeExtensions.map((candidate) => base + candidate),\n path.join(base, \"package.json\"),\n ...moduleProbeExtensions.map((candidate) =>\n path.join(base, \"index\" + candidate),\n ),\n ];\n}\n\n/** Record exact local probes before the runtime resolver chooses one. */\nfunction recordLocalResolutionCandidates(\n specifier: string,\n parentUrl: string,\n): void {\n if (!isLocalModuleSpecifier(specifier)) return;\n let bases: string[];\n try {\n if (specifier.startsWith(\"file:\")) {\n bases = [fileURLToPath(specifier)];\n } else {\n const directory = path.dirname(fileURLToPath(parentUrl));\n const raw = path.resolve(directory, specifier);\n const suffixStart = specifier.search(/[?#]/);\n const pathname =\n suffixStart === -1 ? specifier : specifier.slice(0, suffixStart);\n bases = pathname === \"\"\n ? [raw]\n : [...new Set([raw, path.resolve(directory, pathname)])];\n }\n } catch {\n return;\n }\n const owners = [parentUrl];\n for (const base of bases) {\n try {\n if (fs.statSync(base).isFile()) {\n recordOptionalFileDependency(base, owners);\n continue;\n }\n } catch {\n // A missing exact spelling falls through to source/extension/directory\n // probes, all of which can redirect a later evaluation.\n }\n for (const candidate of moduleResolutionCandidates(base)) {\n recordOptionalFileDependency(candidate, owners);\n }\n }\n}\n\n/** CommonJS LOAD_AS_FILE / LOAD_AS_DIRECTORY candidates for one legacy path. */\nfunction recordLegacyPackagePathCandidates(\n candidate: string,\n owners: readonly string[],\n): void {\n for (const file of [\n candidate,\n candidate + \".js\",\n candidate + \".json\",\n candidate + \".node\",\n path.join(candidate, \"package.json\"),\n path.join(candidate, \"index.js\"),\n path.join(candidate, \"index.json\"),\n path.join(candidate, \"index.node\"),\n ]) {\n recordOptionalFileDependency(file, owners);\n }\n}\n\nfunction recordPackageManifests(\n location: string,\n owners: readonly string[],\n): void {\n let current = path.dirname(location);\n while (true) {\n const manifest = path.join(current, \"package.json\");\n if (recordOptionalFileDependency(manifest, owners)) return;\n const parent = path.dirname(current);\n if (parent === current || path.basename(current) === \"node_modules\") return;\n current = parent;\n }\n}\n\nfunction recordNodeModulesSearchDirectories(\n parentLocation: string,\n specifier: string,\n childLocation: string,\n owners: readonly string[],\n conditions: readonly string[],\n): void {\n const packageName = modulePackageName(specifier);\n const scope =\n specifier.startsWith(\"@\") && specifier.includes(\"/\")\n ? specifier.slice(0, specifier.indexOf(\"/\"))\n : undefined;\n let current = path.dirname(parentLocation);\n while (true) {\n // A newly created nearer node_modules directory can shadow the package\n // selected by this evaluation, so missing search levels are dependencies.\n recordDirectoryDependency(current, owners);\n const modules = path.join(current, \"node_modules\");\n try {\n if (fs.statSync(modules).isDirectory()) {\n recordDirectoryDependency(modules, owners);\n if (scope !== undefined) {\n const scoped = path.join(modules, scope);\n try {\n if (fs.statSync(scoped).isDirectory()) {\n recordDirectoryDependency(scoped, owners);\n }\n } catch {\n // The directory digest of node_modules records a missing scope.\n }\n }\n }\n } catch {\n // Missing search levels do not participate in the current resolution.\n }\n if (packageName !== undefined) {\n const selected = recordPackageCandidateTopology(\n modules,\n packageName,\n specifier,\n childLocation,\n owners,\n conditions,\n );\n if (\n selected ||\n resolvedPackageContains(modules, packageName, childLocation)\n ) {\n return;\n }\n }\n if (\n packageName === undefined &&\n samePhysicalPath(current, resolutionRoot)\n ) {\n return;\n }\n const parent = path.dirname(current);\n if (parent === current) return;\n current = parent;\n }\n}\n\nfunction recordPackageCandidateTopology(\n modules: string,\n packageName: string,\n specifier: string,\n childLocation: string,\n owners: readonly string[],\n conditions: readonly string[],\n): boolean {\n const packageRoot = path.join(modules, packageName);\n const subpath = specifier\n .slice(packageName.length)\n .replace(/^[/\\\\]+/, \"\");\n try {\n if (!fs.statSync(packageRoot).isDirectory()) {\n recordOptionalFileDependency(\n path.join(packageRoot, \"package.json\"),\n owners,\n );\n recordLegacyPackagePathCandidates(\n subpath === \"\" ? packageRoot : path.join(packageRoot, subpath),\n owners,\n );\n return false;\n }\n } catch {\n recordOptionalFileDependency(\n path.join(packageRoot, \"package.json\"),\n owners,\n );\n recordLegacyPackagePathCandidates(\n subpath === \"\" ? packageRoot : path.join(packageRoot, subpath),\n owners,\n );\n return false;\n }\n const rootTopology = recordPackageRootTopology(\n packageRoot,\n owners,\n subpath === \"\",\n subpath === \"\" ? \".\" : \"./\" + subpath.replaceAll(\"\\\\\", \"/\"),\n childLocation,\n conditions,\n );\n if (subpath !== \"\" && !rootTopology.hasExports) {\n return (\n recordPackageSubpathTopology(\n packageRoot,\n subpath,\n childLocation,\n owners,\n ) || rootTopology.selected\n );\n }\n return rootTopology.selected;\n}\n\nfunction recordPackageRootTopology(\n packageRoot: string,\n owners: readonly string[],\n useMain: boolean,\n packageSubpath: string,\n childLocation: string,\n conditions: readonly string[],\n): { hasExports: boolean; selected: boolean } {\n const normalizedRoot = path.resolve(packageRoot);\n const manifest = path.join(normalizedRoot, \"package.json\");\n const legacySelected = (): boolean =>\n useMain &&\n packagePathCandidateMatchesChild(normalizedRoot, childLocation, true);\n if (useMain) recordLegacyPackagePathCandidates(normalizedRoot, owners);\n if (!recordOptionalFileDependency(manifest, owners)) {\n const selected = legacySelected();\n if (!selected) {\n recordPackageIndexCandidates(normalizedRoot, useMain, owners);\n }\n return { hasExports: false, selected };\n }\n try {\n const value = JSON.parse(fs.readFileSync(manifest, \"utf8\"));\n if (value !== null && typeof value === \"object\") {\n const metadata = value as Record<string, unknown>;\n const hasExports =\n metadata.exports !== undefined && metadata.exports !== null;\n if (hasExports) {\n const target = selectPackageExportsTarget(\n metadata.exports,\n packageSubpath,\n new Set(conditions),\n );\n const candidate =\n typeof target === \"string\"\n ? packageExportsTarget(normalizedRoot, target)\n : undefined;\n const selected =\n candidate !== undefined &&\n packagePathCandidateMatchesChild(\n candidate,\n childLocation,\n false,\n );\n if (selected) {\n recordPackagePathCandidate(candidate, owners);\n } else if (candidate !== undefined) {\n // A nearer package the search skipped starts winning the moment its\n // own active target appears, and neither the parent node_modules\n // listing nor the manifest changes when only that file is created.\n recordOptionalFileDependency(candidate, owners);\n }\n return { hasExports: true, selected };\n }\n let selected = legacySelected();\n if (useMain && typeof metadata.main === \"string\") {\n // CommonJS main is a legacy path, not an exports target. Node resolves\n // it literally and permits absolute paths and paths outside the package.\n const main = path.resolve(normalizedRoot, metadata.main);\n recordPackagePathCandidate(main, owners);\n recordLegacyPackagePathCandidates(main, owners);\n selected =\n packagePathCandidateMatchesChild(main, childLocation, true) ||\n selected;\n }\n if (!selected) {\n recordPackageIndexCandidates(normalizedRoot, useMain, owners);\n }\n return {\n hasExports: false,\n selected,\n };\n }\n } catch {\n // Node owns malformed-manifest diagnostics; the manifest digest is enough\n // to invalidate this evaluation when its contents change.\n }\n const selected = legacySelected();\n if (!selected) {\n recordPackageIndexCandidates(normalizedRoot, useMain, owners);\n }\n return { hasExports: false, selected };\n}\n\n// recordPackageIndexCandidates pins the LOAD_INDEX fallbacks of a package root\n// this resolution walked past without selecting. An empty package directory, or\n// one whose manifest declares no usable entry, becomes resolvable as soon as one\n// of these files exists, and that creation changes neither the parent directory\n// listing nor the manifest digest already recorded for the candidate.\nfunction recordPackageIndexCandidates(\n packageRoot: string,\n useMain: boolean,\n owners: readonly string[],\n): void {\n if (!useMain) return;\n for (const name of [\"index.js\", \"index.json\", \"index.node\"]) {\n recordOptionalFileDependency(path.join(packageRoot, name), owners);\n }\n}\n\nfunction selectPackageExportsTarget(\n exportsValue: unknown,\n packageSubpath: string,\n conditions: ReadonlySet<string>,\n): string | null | undefined {\n let mappings: unknown = exportsValue;\n if (\n typeof mappings === \"string\" ||\n Array.isArray(mappings) ||\n (isObject(mappings) &&\n Object.keys(mappings).every((key) => !key.startsWith(\".\")))\n ) {\n if (packageSubpath !== \".\") return undefined;\n return selectPackageTarget(mappings, \"\", false, conditions);\n }\n if (!isObject(mappings)) return undefined;\n if (\n Object.prototype.hasOwnProperty.call(mappings, packageSubpath) &&\n !packageSubpath.includes(\"*\") &&\n !packageSubpath.endsWith(\"/\")\n ) {\n return selectPackageTarget(\n mappings[packageSubpath],\n \"\",\n false,\n conditions,\n );\n }\n let bestMatch = \"\";\n let bestSubpath = \"\";\n for (const key of Object.keys(mappings)) {\n const wildcard = key.indexOf(\"*\");\n if (\n wildcard === -1 ||\n key.lastIndexOf(\"*\") !== wildcard ||\n !packageSubpath.startsWith(key.slice(0, wildcard))\n ) {\n continue;\n }\n const trailer = key.slice(wildcard + 1);\n if (\n packageSubpath.length < key.length ||\n !packageSubpath.endsWith(trailer) ||\n packagePatternKeyCompare(bestMatch, key) !== 1\n ) {\n continue;\n }\n bestMatch = key;\n bestSubpath = packageSubpath.slice(\n wildcard,\n packageSubpath.length - trailer.length,\n );\n }\n return bestMatch === \"\"\n ? undefined\n : selectPackageTarget(\n mappings[bestMatch],\n bestSubpath,\n true,\n conditions,\n );\n}\n\nfunction selectPackageTarget(\n target: unknown,\n subpath: string,\n pattern: boolean,\n conditions: ReadonlySet<string>,\n): string | null | undefined {\n if (typeof target === \"string\") {\n const selected = pattern ? target.replaceAll(\"*\", subpath) : target;\n return validPackageExportsTarget(selected) ? selected : undefined;\n }\n if (Array.isArray(target)) {\n for (const item of target) {\n const selected = selectPackageTarget(\n item,\n subpath,\n pattern,\n conditions,\n );\n if (selected !== undefined && selected !== null) return selected;\n }\n return null;\n }\n if (isObject(target)) {\n for (const [condition, value] of Object.entries(target)) {\n if (condition !== \"default\" && !conditions.has(condition)) continue;\n const selected = selectPackageTarget(\n value,\n subpath,\n pattern,\n conditions,\n );\n if (selected !== undefined) return selected;\n }\n return undefined;\n }\n return target === null ? null : undefined;\n}\n\nfunction packagePatternKeyCompare(left: string, right: string): number {\n const leftWildcard = left.indexOf(\"*\");\n const rightWildcard = right.indexOf(\"*\");\n const leftBase =\n leftWildcard === -1 ? left.length : leftWildcard + 1;\n const rightBase =\n rightWildcard === -1 ? right.length : rightWildcard + 1;\n if (leftBase > rightBase) return -1;\n if (rightBase > leftBase) return 1;\n if (leftWildcard === -1) return 1;\n if (rightWildcard === -1) return -1;\n if (left.length > right.length) return -1;\n if (right.length > left.length) return 1;\n return 0;\n}\n\nfunction packageExportsTarget(\n packageRoot: string,\n target: string,\n): string | undefined {\n if (!validPackageExportsTarget(target)) return undefined;\n try {\n // Node resolves an exports target as a URL against the package manifest,\n // so percent escapes, query strings, and fragments all take part in the\n // path it finally loads. Joining the raw target by hand diverges from that\n // whenever the target is anything but a plain relative path, and a target\n // Node resolves while this model rejects loses the selected file's\n // fingerprint, leaving a retargeted symlink cached as fresh.\n const packageUrl = pathToFileURL(path.join(packageRoot, \"package.json\"));\n const resolved = new URL(target, packageUrl);\n const packagePath = new URL(\".\", packageUrl).pathname;\n if (!resolved.pathname.startsWith(packagePath)) return undefined;\n return fileURLToPath(resolved);\n } catch {\n return undefined;\n }\n}\n\nfunction validPackageExportsTarget(target: string): boolean {\n if (!target.startsWith(\"./\") || /%2f|%5c/i.test(target)) return false;\n const components = target\n .slice(2)\n .replaceAll(\"\\\\\", \"/\")\n .split(\"/\");\n if (\n components.some(\n (component) => {\n try {\n const decoded = decodeURIComponent(component);\n return (\n decoded === \".\" ||\n decoded === \"..\" ||\n decoded.includes(\"/\") ||\n decoded.includes(\"\\\\\") ||\n decoded.toLowerCase() === \"node_modules\"\n );\n } catch {\n return true;\n }\n },\n )\n ) {\n return false;\n }\n return true;\n}\n\nfunction packagePathCandidateMatchesChild(\n candidate: string,\n childLocation: string,\n legacy: boolean,\n): boolean {\n let child: string;\n try {\n child = fs.realpathSync.native(childLocation);\n } catch {\n child = path.resolve(childLocation);\n }\n const candidates = legacy\n ? [\n candidate,\n candidate + \".js\",\n candidate + \".json\",\n candidate + \".node\",\n path.join(candidate, \"index.js\"),\n path.join(candidate, \"index.json\"),\n path.join(candidate, \"index.node\"),\n ]\n : [candidate];\n return candidates.some((location) => {\n try {\n return sameResolutionPath(fs.realpathSync.native(location), child);\n } catch {\n return false;\n }\n });\n}\n\nfunction recordPackageSubpathTopology(\n packageRoot: string,\n subpath: string,\n childLocation: string,\n owners: readonly string[],\n): boolean {\n const candidate = boundedPackageTarget(packageRoot, subpath);\n if (candidate === undefined) return false;\n recordPackagePathCandidate(candidate, owners);\n recordLegacyPackagePathCandidates(candidate, owners);\n let selected = packagePathCandidateMatchesChild(\n candidate,\n childLocation,\n true,\n );\n try {\n if (!fs.statSync(candidate).isDirectory()) return selected;\n } catch {\n return selected;\n }\n const manifest = path.join(candidate, \"package.json\");\n if (!recordOptionalFileDependency(manifest, owners)) return selected;\n try {\n const value = JSON.parse(fs.readFileSync(manifest, \"utf8\"));\n if (value !== null && typeof value === \"object\") {\n const metadata = value as Record<string, unknown>;\n if (typeof metadata.main === \"string\") {\n const main = path.resolve(candidate, metadata.main);\n recordPackagePathCandidate(main, owners);\n recordLegacyPackagePathCandidates(main, owners);\n selected =\n packagePathCandidateMatchesChild(main, childLocation, true) ||\n selected;\n }\n }\n } catch {\n // Node owns malformed nested-package diagnostics.\n }\n return selected;\n}\n\nfunction boundedPackageTarget(\n packageRoot: string,\n target: string,\n): string | undefined {\n const candidate = path.resolve(packageRoot, target);\n const relative = path.relative(packageRoot, candidate);\n if (\n relative === \"..\" ||\n relative.startsWith(\"..\" + path.sep) ||\n path.isAbsolute(relative)\n ) {\n return undefined;\n }\n return candidate;\n}\n\nfunction recordPackagePathCandidate(\n candidate: string,\n owners: readonly string[],\n visited: Set<string> = new Set(),\n depth = 0,\n): void {\n const normalized = path.resolve(candidate);\n // The depth bound owns termination. A platform-wide case fold would merge\n // paths that differ only by case, which a per-directory case-sensitive\n // Windows tree keeps distinct, and would truncate a valid symlink chain.\n if (depth >= 64 || visited.has(normalized)) return;\n visited.add(normalized);\n const parsed = path.parse(normalized);\n const components = normalized\n .slice(parsed.root.length)\n .split(path.sep)\n .filter(Boolean);\n let current = parsed.root;\n for (let index = 0; index < components.length; index++) {\n const component = components[index];\n const next = path.join(current, component);\n let entry: ReturnType<typeof fs.lstatSync>;\n try {\n entry = fs.lstatSync(next);\n } catch {\n recordDirectoryDependency(current, owners);\n return;\n }\n if (entry.isSymbolicLink()) {\n // The containing directory digest carries the raw link target.\n recordDirectoryDependency(current, owners);\n try {\n const target = fs.readlinkSync(next);\n const remainder = components.slice(index + 1);\n recordPackagePathCandidate(\n path.join(\n path.resolve(current, target),\n ...remainder,\n ),\n owners,\n visited,\n depth + 1,\n );\n } catch {\n // The lexical link record already carries the unreadable state.\n }\n }\n let isDirectory = entry.isDirectory();\n if (entry.isSymbolicLink()) {\n try {\n isDirectory = fs.statSync(next).isDirectory();\n } catch {\n return;\n }\n }\n if (index === components.length - 1) {\n recordDirectoryDependency(isDirectory ? next : current, owners);\n return;\n }\n if (!isDirectory) {\n recordDirectoryDependency(current, owners);\n return;\n }\n current = next;\n }\n recordDirectoryDependency(current, owners);\n}\n\nfunction modulePackageName(specifier: string): string | undefined {\n if (specifier.startsWith(\"@\")) {\n const components = specifier.split(\"/\");\n return components.length >= 2\n ? components[0] + \"/\" + components[1]\n : undefined;\n }\n const [name] = specifier.split(\"/\");\n return name && !name.startsWith(\"#\") ? name : undefined;\n}\n\nfunction resolvedPackageContains(\n modules: string,\n packageName: string,\n childLocation: string,\n): boolean {\n try {\n const packageRoot = fs.realpathSync(path.join(modules, packageName));\n const relative = path.relative(\n packageRoot,\n fs.realpathSync(childLocation),\n );\n return (\n relative === \"\" ||\n (relative !== \"..\" &&\n !relative.startsWith(\"..\" + path.sep) &&\n !path.isAbsolute(relative))\n );\n } catch {\n return false;\n }\n}\n\nfunction sameResolutionPath(left: string, right: string): boolean {\n return path.relative(left, right) === \"\";\n}\n\nfunction samePhysicalPath(left: string, right: string): boolean {\n try {\n return sameResolutionPath(realPath(left), realPath(right));\n } catch {\n // Fall back to the spellings themselves, folding case the way the platform\n // does. On the entry gate a false negative is catastrophic \u2014 the config\n // stops being recognized and its whole graph collapses \u2014 while a false\n // positive only over-includes, so the degradation has to lean toward \"same\n // file\". A drive-letter or component case difference is the ordinary\n // Windows situation; a per-directory case-sensitive tree is the rare one.\n return sameResolutionPath(left, right);\n }\n}\n\n/**\n * The config's real path, or its declared one when the volume will not say.\n *\n * A config can disappear between the host reading it and this loader starting,\n * and a throw here would replace a precise report from the import below with a\n * crash in bookkeeping. Seeding lexically instead only risks the demotion this\n * value exists to prevent, on a file that is already gone.\n */\nfunction realConfigLocation(): string {\n try {\n return realPath(configLocation);\n } catch {\n return configLocation;\n }\n}\n\nfunction realPath(location: string): string {\n return fs.realpathSync.native\n ? fs.realpathSync.native(location)\n : fs.realpathSync(location);\n}\n\nfunction finalizeDependencies(): Array<{\n digest: string;\n identityStable: boolean;\n kind: \"directory\" | \"file\" | \"optional-file\";\n path: string;\n realpath: string | null;\n scope: \"cache\" | \"watch\";\n}> {\n // The evaluator may have observed a dependency, run arbitrary config code,\n // and then serialize after that path changed again. Re-read every recorded\n // dependency under its original ownership set so an A -> B -> A transition\n // is marked identity-unstable instead of pairing transient output with the\n // restored fingerprint.\n for (const dependency of [...dependencies.values()]) {\n recordDependency(\n dependency.kind,\n dependency.path,\n currentDependencyDigest(dependency.kind, dependency.path),\n [...dependency.owners],\n );\n }\n const watched = graphWatchReachability();\n return [...dependencies.values()].map(({ owners, ...dependency }) => ({\n ...dependency,\n scope: [...owners].some((owner) => watched.has(owner))\n ? \"watch\"\n : \"cache\",\n }));\n}\n\nfunction currentDependencyDigest(\n kind: \"directory\" | \"file\" | \"optional-file\",\n location: string,\n): string {\n try {\n if (kind === \"directory\") return directoryDigest(location);\n if (kind === \"optional-file\") return optionalFileDigest(location);\n return createHash(\"sha256\")\n .update(fs.readFileSync(location))\n .digest(\"hex\");\n } catch {\n return \"\";\n }\n}\n\nfunction graphWatchReachability(): Set<string> {\n const adjacency = new Map<string, typeof graphEdges>();\n for (const edge of graphEdges) {\n const outgoing = adjacency.get(edge.parent) ?? [];\n outgoing.push(edge);\n adjacency.set(edge.parent, outgoing);\n }\n const queue: Array<{ url: string; watched: boolean }> =\n configUrlSpellings.map((url) => ({ url, watched: true }));\n const visited = new Set<string>();\n const watched = new Set<string>();\n while (queue.length !== 0) {\n const state = queue.shift()!;\n const key = state.url + \"\\0\" + (state.watched ? \"1\" : \"0\");\n if (visited.has(key)) continue;\n visited.add(key);\n if (state.watched) watched.add(state.url);\n for (const edge of adjacency.get(state.url) ?? []) {\n const childLocation = graphNodes.get(edge.child);\n const childWatched = edge.packageBoundary\n ? false\n : childLocation !== undefined && !pathHasNodeModules(childLocation)\n ? true\n : state.watched;\n queue.push({ url: edge.child, watched: childWatched });\n }\n }\n return watched;\n}\n\nfunction hasOwn(value: Record<string, unknown>, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(value, key);\n}\n\nasync function resolveConfig(value: unknown, allowNamedConfig: boolean): Promise<unknown> {\n let current = value;\n for (let i = 0; i < 8; i++) {\n if (typeof current === \"function\") {\n current = await (current as () => unknown | Promise<unknown>)();\n allowNamedConfig = false;\n continue;\n }\n if (isObject(current) && !Array.isArray(current)) {\n if (hasOwn(current, \"default\")) {\n const defaultValue = current.default;\n if (isModuleNamespace(current) || !hasConfigKey(current)) {\n current = defaultValue;\n allowNamedConfig = false;\n continue;\n }\n const normalizedDefault = await resolveConfig(defaultValue, false);\n if (isObject(normalizedDefault) && !Array.isArray(normalizedDefault)) {\n current = mergeConfigObjects(normalizedDefault, current);\n allowNamedConfig = false;\n continue;\n }\n }\n if (allowNamedConfig && hasOwn(current, \"config\")) {\n current = current.config;\n allowNamedConfig = false;\n continue;\n }\n }\n break;\n }\n return current;\n}\n\nfunction isModuleNamespace(value: Record<string, unknown>): boolean {\n return Object.prototype.toString.call(value) === \"[object Module]\";\n}\n\nfunction hasConfigKey(value: Record<string, unknown>): boolean {\n for (const key of CONFIG_KEYS) {\n if (hasOwn(value, key)) {\n return true;\n }\n }\n return false;\n}\n\nfunction mergeConfigObjects(\n base: Record<string, unknown>,\n override: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const key of CONFIG_KEYS) {\n if (hasOwn(base, key)) {\n out[key] = base[key];\n }\n }\n for (const key of CONFIG_KEYS) {\n if (hasOwn(override, key)) {\n out[key] = override[key];\n }\n }\n return out;\n}\n\nfunction collectPluginObjects(value: unknown): Array<Record<string, unknown>> {\n const out: Array<Record<string, unknown>> = [];\n visit(value);\n return out;\n\n function visit(node: unknown): void {\n if (Array.isArray(node)) {\n for (const item of node) visit(item);\n return;\n }\n if (!isObject(node)) return;\n if (hasOwn(node, \"plugins\") && isObject(node.plugins)) {\n out.push(node.plugins as Record<string, unknown>);\n }\n }\n}\n\nfunction extractPluginSource(value: unknown): string | undefined {\n if (typeof value === \"string\") {\n value = requireFromConfig(value);\n }\n if (!isObject(value)) return undefined;\n // ESM-from-CJS interop wraps CJS modules' `exports.default` so the\n // plugin object can land under a `.default` indirection. Walk a few\n // hops so contributors authored as `export default plugin` and\n // contributors authored as plain `module.exports = plugin` both\n // resolve identically.\n let current: Record<string, unknown> = value;\n // 8 hops to match the outer-process unwrapDefault helper; previously\n // 4, which silently misrouted deeply re-exported plugins while\n // unwrapDefault would have unwrapped them.\n for (let i = 0; i < 8; i++) {\n if (typeof current.source === \"string\") break;\n const next = current.default;\n if (!isObject(next)) break;\n current = next;\n }\n const source = current.source;\n return typeof source === \"string\" ? source : undefined;\n}\n";