@hublo/sentinel 1.4.0-alpha.3 → 1.4.0-alpha.30
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/dist/bin/sentinel.d.ts +1 -0
- package/dist/bin/sentinel.js +43 -102
- package/dist/chunk-2XLX6PFR.js.map +1 -0
- package/dist/{chunk-3TDUIKVQ.js → chunk-3ZKTXODW.js} +6 -6
- package/dist/chunk-3ZKTXODW.js.map +1 -0
- package/dist/chunk-GCZKG4B2.js +4743 -0
- package/dist/{chunk-WLFE5RUU.js → chunk-NW6UHNYX.js} +6 -6
- package/dist/chunk-NW6UHNYX.js.map +1 -0
- package/dist/chunk-PWV3BMDA.js.map +1 -0
- package/dist/{chunk-CPCUPK4J.js → chunk-TKDVAYYV.js} +4 -4
- package/dist/chunk-TKDVAYYV.js.map +1 -0
- package/dist/{chunk-MUQWAZQN.js → chunk-Y2EKU6OE.js} +294 -4133
- package/dist/{chunk-SX5CBE3Z.js → chunk-ZV5KMOS7.js} +133 -16
- package/dist/chunk-ZV5KMOS7.js.map +1 -0
- package/dist/index.d.ts +28 -5
- package/dist/index.js +5 -4
- package/dist/roles/build/nest/toolchain.js +7 -7
- package/dist/roles/test/nest/toolchain.d.ts +1 -1
- package/dist/roles/test/nest/toolchain.js +2 -2
- package/dist/roles/test/react/toolchain.d.ts +1 -1
- package/dist/roles/test/react/toolchain.js +2 -2
- package/dist/roles/test/setup/a11y.d.ts +45 -0
- package/dist/roles/test/setup/a11y.js +72 -0
- package/dist/roles/test/setup/a11y.js.map +1 -0
- package/dist/roles/test/setup/{nest.js → jest-parity.js} +5 -5
- package/dist/roles/test/setup/jest-parity.js.map +1 -0
- package/dist/roles/test/setup/w3c.d.ts +75 -0
- package/dist/roles/test/setup/w3c.js +49 -0
- package/dist/roles/test/setup/w3c.js.map +1 -0
- package/dist/roles/test/setup/workspace-entry.js +2 -2
- package/dist/roles/test/setup/workspace-entry.js.map +1 -1
- package/dist/{shared-test-config-CGxvMlmk.d.ts → roles/test/shared-test-config.d.ts} +5 -1
- package/dist/roles/test/shared-test-config.js +8 -0
- package/dist/roles/test/shared-test-config.js.map +1 -0
- package/dist/roles/test/tools/msw.d.ts +1 -0
- package/dist/roles/test/tools/msw.js +3 -0
- package/dist/roles/test/tools/msw.js.map +1 -0
- package/dist/validate-ONAQPYJB.js +129 -0
- package/docs/test-adoption.md +188 -10
- package/docs/using-sentinel.md +25 -10
- package/package.json +24 -7
- package/types/jest-global.d.ts +85 -0
- package/types/mock-extended.d.ts +52 -0
- package/dist/roles/test/setup/nest.js.map +0 -1
- /package/dist/roles/test/setup/{nest.d.ts → jest-parity.d.ts} +0 -0
package/dist/bin/sentinel.d.ts
CHANGED
package/dist/bin/sentinel.js
CHANGED
|
@@ -14,118 +14,31 @@ import {
|
|
|
14
14
|
dispatch,
|
|
15
15
|
palette,
|
|
16
16
|
registerAdapters,
|
|
17
|
-
resolve
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
resolve
|
|
18
|
+
} from "../chunk-Y2EKU6OE.js";
|
|
19
|
+
import {
|
|
20
|
+
resolveContext
|
|
21
|
+
} from "../chunk-GCZKG4B2.js";
|
|
20
22
|
import {
|
|
21
|
-
WORKSPACE_ROOT_MARKER,
|
|
22
23
|
ensureWorkspacePrep,
|
|
23
24
|
findWorkspaceRoot,
|
|
24
25
|
inspectWorkspacePrep,
|
|
25
|
-
moduleName,
|
|
26
26
|
readOwnVersion,
|
|
27
27
|
readProjectPackageJson
|
|
28
|
-
} from "../chunk-
|
|
28
|
+
} from "../chunk-NW6UHNYX.js";
|
|
29
29
|
|
|
30
30
|
// bin/sentinel.ts
|
|
31
31
|
import { program } from "commander";
|
|
32
32
|
|
|
33
|
-
// src/core/context.ts
|
|
34
|
-
import { existsSync } from "fs";
|
|
35
|
-
import { join as join2 } from "path";
|
|
36
|
-
|
|
37
|
-
// src/core/discover-modules.ts
|
|
38
|
-
import { execFileSync } from "child_process";
|
|
39
|
-
import { mkdtempSync, readFileSync, rmSync } from "fs";
|
|
40
|
-
import { tmpdir } from "os";
|
|
41
|
-
import { join } from "path";
|
|
42
|
-
function runNx(cwd2, args) {
|
|
43
|
-
const nx = resolveBin(cwd2, "nx") ?? "nx";
|
|
44
|
-
try {
|
|
45
|
-
return execFileSync(nx, args, {
|
|
46
|
-
cwd: cwd2,
|
|
47
|
-
encoding: "utf8",
|
|
48
|
-
env: { ...process.env, NX_DAEMON: "false" }
|
|
49
|
-
});
|
|
50
|
-
} catch (error) {
|
|
51
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
52
|
-
throw new Error(
|
|
53
|
-
`sentinel: could not run nx (${message}). Is nx installed in this workspace, and are you at its root?`,
|
|
54
|
-
{ cause: error }
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function readGraph(cwd2) {
|
|
59
|
-
const dir = mkdtempSync(join(tmpdir(), "sentinel-nx-"));
|
|
60
|
-
const file = join(dir, "graph.json");
|
|
61
|
-
try {
|
|
62
|
-
runNx(cwd2, ["graph", "--file", file]);
|
|
63
|
-
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
64
|
-
const nodes = parsed.graph?.nodes;
|
|
65
|
-
if (!nodes || typeof nodes !== "object") {
|
|
66
|
-
throw new Error(
|
|
67
|
-
"sentinel: unexpected nx graph output (no graph.nodes); the installed nx version may be incompatible."
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
return Object.entries(nodes).map(([name, node]) => ({
|
|
71
|
-
name,
|
|
72
|
-
root: join(cwd2, node.data.root)
|
|
73
|
-
}));
|
|
74
|
-
} finally {
|
|
75
|
-
rmSync(dir, { recursive: true, force: true });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
function discoverModules(cwd2, options = {}) {
|
|
79
|
-
const modules = readGraph(cwd2);
|
|
80
|
-
if (!options.affected) return modules;
|
|
81
|
-
const affected = new Set(
|
|
82
|
-
JSON.parse(runNx(cwd2, ["show", "projects", "--affected", "--json"]))
|
|
83
|
-
);
|
|
84
|
-
return modules.filter((module) => affected.has(module.name));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// src/core/context.ts
|
|
88
|
-
var MODULE_MARKERS = ["package.json", "project.json"];
|
|
89
|
-
function isModuleDir(cwd2) {
|
|
90
|
-
return MODULE_MARKERS.some((marker) => existsSync(join2(cwd2, marker)));
|
|
91
|
-
}
|
|
92
|
-
function resolveContext(cwd2, opts2) {
|
|
93
|
-
const atRoot = existsSync(join2(cwd2, WORKSPACE_ROOT_MARKER));
|
|
94
|
-
if (!atRoot && isModuleDir(cwd2)) {
|
|
95
|
-
if (opts2.module) {
|
|
96
|
-
throw new Error(
|
|
97
|
-
"You are in a module directory: drop --module (the context is the current module)."
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
if (opts2.ci) {
|
|
101
|
-
throw new Error("--ci selects the affected set from the workspace root; run it there.");
|
|
102
|
-
}
|
|
103
|
-
const name = moduleName(cwd2);
|
|
104
|
-
return { modules: [{ name, root: cwd2 }], scope: "cwd-module" };
|
|
105
|
-
}
|
|
106
|
-
if (atRoot) {
|
|
107
|
-
if (opts2.module) {
|
|
108
|
-
const found = discoverModules(cwd2).find((module) => module.name === opts2.module);
|
|
109
|
-
if (!found) throw new Error(`module "${opts2.module}" not found in the workspace.`);
|
|
110
|
-
return { modules: [found], scope: "named-module" };
|
|
111
|
-
}
|
|
112
|
-
if (opts2.ci) return { modules: discoverModules(cwd2, { affected: true }), scope: "affected" };
|
|
113
|
-
return { modules: discoverModules(cwd2), scope: "all" };
|
|
114
|
-
}
|
|
115
|
-
throw new Error(
|
|
116
|
-
`Run sentinel from a module directory or the workspace root (found neither ${MODULE_MARKERS.join("/")} nor ${WORKSPACE_ROOT_MARKER} here).`
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
33
|
// src/roles/build/declined.ts
|
|
121
|
-
import { existsSync
|
|
122
|
-
import { join
|
|
34
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
35
|
+
import { join } from "node:path";
|
|
123
36
|
var DEPRECATED_NX_BUILDER = "@nx/webpack:webpack";
|
|
124
37
|
function buildsWithDeprecatedExecutor(cwd2) {
|
|
125
|
-
const projectJson =
|
|
126
|
-
if (!
|
|
38
|
+
const projectJson = join(cwd2, "project.json");
|
|
39
|
+
if (!existsSync(projectJson)) return false;
|
|
127
40
|
try {
|
|
128
|
-
const project = JSON.parse(
|
|
41
|
+
const project = JSON.parse(readFileSync(projectJson, "utf8"));
|
|
129
42
|
return project.targets?.build?.executor === DEPRECATED_NX_BUILDER;
|
|
130
43
|
} catch {
|
|
131
44
|
return false;
|
|
@@ -218,6 +131,32 @@ sentinel (${type}): ${asMessage(error)}
|
|
|
218
131
|
return worst;
|
|
219
132
|
}
|
|
220
133
|
|
|
134
|
+
// src/cli/run-validate.ts
|
|
135
|
+
async function runValidate(ctx) {
|
|
136
|
+
const { modules } = resolveContext(ctx.cwd, { module: ctx.module, ci: ctx.ci });
|
|
137
|
+
const unsupported = ctx.targets.filter((target) => target !== "test");
|
|
138
|
+
if (unsupported.length > 0) {
|
|
139
|
+
const named = unsupported.map((target) => `--${target}`).join(", ");
|
|
140
|
+
process.stdout.write(
|
|
141
|
+
` \xB7 ${named}: not available yet. \`--validate\` answers \`--test\` today, by recording a suite before a migration and comparing it after. Nothing was checked for ${named}.
|
|
142
|
+
`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
if (!ctx.targets.includes("test")) return 1;
|
|
146
|
+
const { validateModuleTests } = await import("../validate-ONAQPYJB.js");
|
|
147
|
+
const outcomes = modules.flatMap(
|
|
148
|
+
(module) => validateModuleTests(module.name, module.root)
|
|
149
|
+
);
|
|
150
|
+
for (const outcome of outcomes) {
|
|
151
|
+
const suite = outcome.suite === "main" ? "" : ` [${outcome.suite}]`;
|
|
152
|
+
process.stdout.write(
|
|
153
|
+
` ${outcome.ok ? "\u2713" : "\u2717"} ${outcome.module}${suite} \u2014 ${outcome.message}
|
|
154
|
+
`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return outcomes.every((outcome) => outcome.ok) ? 0 : 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
221
160
|
// src/core/orchestrate.ts
|
|
222
161
|
async function analyse(params) {
|
|
223
162
|
const results = [];
|
|
@@ -645,13 +584,16 @@ program.name("sentinel").description("One CLI that guards code health: presets,
|
|
|
645
584
|
"before",
|
|
646
585
|
[
|
|
647
586
|
"A check composes: verb + type + location.",
|
|
648
|
-
" verb what to do: --run --inspect --report --status --init (--migrate: planned)",
|
|
587
|
+
" verb what to do: --run --inspect --report --status --init --validate (--migrate: planned)",
|
|
649
588
|
" type which check: --lint --typescript ... (omit = all types; or --all)",
|
|
650
589
|
" where run from a MODULE dir \u2192 that module; from the ROOT \u2192 --module <name>,",
|
|
651
590
|
" --ci (affected), or all modules. --init targets one module only.",
|
|
652
591
|
""
|
|
653
592
|
].join("\n")
|
|
654
|
-
).option("--run", "execute the target tool").option("--inspect", "show the resolved configuration").option("--init", "set up a module: write its config stubs + the workspace prep it needs").option("--migrate", "planned: change an already-initialized setup (not available yet)").option(
|
|
593
|
+
).option("--run", "execute the target tool").option("--inspect", "show the resolved configuration").option("--init", "set up a module: write its config stubs + the workspace prep it needs").option("--migrate", "planned: change an already-initialized setup (not available yet)").option(
|
|
594
|
+
"--validate",
|
|
595
|
+
"prove a migration kept the suite: record the reference, then compare against it"
|
|
596
|
+
).option("--report", "deprecated: use --run --json").option("--status", "deprecated: use --inspect").option(
|
|
655
597
|
"--module <name>",
|
|
656
598
|
"from the workspace root: scope to one module (omit = all; inside a module dir, drop this)"
|
|
657
599
|
).option("--preset <name>", `the stack preset to apply (${PRESET_NAMES.join(", ")})`).option("--flavour <name>", "deprecated: use --preset").option("--runner <tool>", "override the default runner (e.g. eslint, biome)").option("--ci", "CI mode: from the root, only the affected modules; non-zero exit on failure").option("--fix", "auto-fix where applicable").option("--dry-run", "preview the changes without writing (--init)").option("--json", "machine-readable output, for every verb").option(
|
|
@@ -783,7 +725,7 @@ async function main() {
|
|
|
783
725
|
dryRun: Boolean(opts.dryRun),
|
|
784
726
|
fail: (message) => program.error(message)
|
|
785
727
|
};
|
|
786
|
-
const exitCode = verb === "init" ? await runInit(context) : await runVerb(context);
|
|
728
|
+
const exitCode = verb === "init" ? await runInit(context) : verb === "validate" ? await runValidate(context) : await runVerb(context);
|
|
787
729
|
await exitWithoutTruncating(exitCode);
|
|
788
730
|
}
|
|
789
731
|
main().catch((error) => {
|
|
@@ -792,4 +734,3 @@ sentinel: ${asMessage2(error)}
|
|
|
792
734
|
`);
|
|
793
735
|
void exitWithoutTruncating(1);
|
|
794
736
|
});
|
|
795
|
-
//# sourceMappingURL=sentinel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/roles/test/setup/mock-extended.ts"],"sourcesContent":["/**\n * `jest-mock-extended`'s deep mocks, made answerable to the question Vitest's `spyOn` asks.\n *\n * A deep mock is a Proxy that invents a property the first time something READS it. Nothing exists\n * until then, so the object reports itself empty:\n *\n * const provider = mockDeep<InstitutionProvider>()\n * 'getAdminFirstAndLastNames' in provider // false\n * Object.getOwnPropertyDescriptor(provider, 'getAdmin...') // undefined\n * typeof provider.getAdminFirstAndLastNames // 'function', and now it exists\n *\n * jest's `spyOn` reads the property, so the Proxy created it and the spy worked. Vitest's asks the\n * object whether it HAS the property first, gets no for both questions, and throws\n * `The property \"getAdminFirstAndLastNames\" is not defined on the object.` The file then reports no\n * test at all, so its names simply go missing rather than failing.\n *\n * Measured on `apps/cloud/shift`: one such spy took 37 of its 108 tests away. The repo has 128 of\n * them across 29 files, mostly in `apps/nest/microservices` and\n * `apps/nest/backends-for-frontends`.\n *\n * ## Why it is fixed here and not in the 128 test files\n *\n * Because it is a difference between two runners, not something 29 suites each got wrong. A\n * codemod rewriting every site would put a migration artefact in front of every reader of those\n * files forever, and teams would carry it. One adapter keeps the test files exactly as they are.\n *\n * The two traps answer by doing what jest's `spyOn` did: read the property once, then answer. The\n * read is the Proxy's own documented way of materialising it, so nothing here reimplements the\n * mock, it only asks the question in the form the underlying object understands.\n *\n * `then` is never materialised. A deep mock that suddenly HAS a `then` is a thenable, and awaiting\n * it, or returning it from an async function, would hang on a promise nothing resolves.\n */\nimport * as mockExtended from 'vitest-mock-extended'\n\n/**\n * Keys that must stay absent however they are asked for.\n *\n * Not an arbitrary list: every one of them is a PROTOCOL PROBE, a property a framework reads to\n * decide what KIND of value it is holding. A deep mock invents whatever it is asked for, so it\n * answers yes to every probe, and each yes is a lie about what it is.\n *\n * then a thenable. Awaiting the mock, or returning it from an async function,\n * hangs on a promise nothing resolves.\n * asymmetricMatch an asymmetric matcher. Measured: `expect(p).rejects.toThrow(deepMock)`\n * answers `expected error to match asymmetric matcher` instead of comparing\n * against the class, so the assertion stops testing what it says.\n * $$typeof a React element. Serializers branch on it and read fields that are not\n * there.\n * nodeType a DOM node, which testing-library and serializers both branch on.\n *\n * Symbols never reach here: the materialiser returns early for a non-string key, so `Symbol.\n * iterator` and friends are already safe.\n */\nconst NEVER_MATERIALISED = new Set(['then', 'asymmetricMatch', '$$typeof', 'nodeType'])\n\nfunction materialise(target: object, key: PropertyKey): void {\n if (typeof key !== 'string' || NEVER_MATERIALISED.has(key)) return\n // The read IS the creation: that is how the deep mock's own Proxy works.\n void (target as Record<string, unknown>)[key]\n}\n\n/**\n * Wrap a deep mock so that `in` and `getOwnPropertyDescriptor` see what a read would create.\n *\n * Only the object handed back is wrapped, not what it returns: a nested spy would need its own\n * wrapper, and the corpus has none. Wrapping every read would also mean a new wrapper per access\n * unless they were cached, and an identity that changes between two reads is a worse bug than the\n * one being fixed.\n */\n/**\n * One proxy per underlying mock, so identity is stable.\n *\n * A suite holds on to `db.timeSlot` and compares it later, or passes it to `toHaveBeenCalledWith`.\n * Handing back a fresh proxy on every read would make the same mock unequal to itself.\n */\nconst wrappers = new WeakMap<object, object>()\n\nfunction answeringProxy<T extends object>(mock: T): T {\n return new Proxy(mock, {\n /*\n * A NESTED mock has to answer the same questions as the top-level one.\n *\n * The adapter used to wrap only what `mockDeep()` returned, and the level below it was a raw\n * mock again. Measured: `'deleteMany' in db.timeSlot` answered false and its descriptor was\n * absent, so `vi.spyOn(db.timeSlot, 'deleteMany')` failed with\n * `The property \"deleteMany\" is not defined on the function` — the exact error\n * `apps/nest/microservices/hublo-pool` reports, and the exact failure this adapter was written\n * to remove, one level down.\n *\n * Read from the TARGET, never with this proxy as the receiver: forwarding the receiver changes\n * what the deep mock hands back.\n */\n get(target, key) {\n if (typeof key === 'string' && NEVER_MATERIALISED.has(key)) return undefined\n\n const value = Reflect.get(target, key) as unknown\n if (value === null) return value\n if (typeof value !== 'object' && typeof value !== 'function') return value\n\n /*\n * A Proxy invariant, not a preference: for a property that is non-configurable AND\n * non-writable, `get` MUST hand back the target's own value. Wrapping one throws\n * `'get' on proxy: property 'mock' is a read-only and non-configurable data property on the\n * proxy target but the proxy did not return its actual value`, which is what a mock's own\n * `mock` record is.\n */\n const descriptor = Reflect.getOwnPropertyDescriptor(target, key)\n if (\n descriptor !== undefined &&\n descriptor.configurable === false &&\n descriptor.writable === false\n ) {\n return value\n }\n\n const existing = wrappers.get(value as object)\n if (existing !== undefined) return existing\n\n const wrapper = answeringProxy(value as object)\n wrappers.set(value as object, wrapper)\n return wrapper\n },\n has(target, key) {\n if (Reflect.has(target, key)) return true\n materialise(target, key)\n return Reflect.has(target, key)\n },\n getOwnPropertyDescriptor(target, key) {\n const existing = Reflect.getOwnPropertyDescriptor(target, key)\n if (existing !== undefined) return existing\n materialise(target, key)\n return Reflect.getOwnPropertyDescriptor(target, key)\n },\n })\n}\n\n/*\n * Everything the fork exports, so a module importing `jest-mock-extended` still gets `mockReset`,\n * `mockClear`, `anyString`, the matchers and the types. The two names below are declared after it:\n * an explicit export wins over a star export, which is what lets this file override exactly two\n * functions and pass the rest through untouched.\n */\nexport * from 'vitest-mock-extended'\n\n/*\n * And the same names again, one by one.\n *\n * `export *` from a CJS package forwards only what a static read of that package can SEE.\n * `vitest-mock-extended` ships CommonJS and assembles part of its exports at runtime, so the star\n * carries the types and loses several of the functions. A spec importing one of them then reads\n * `undefined` and dies at the call, naming the binding and nothing else. Measured on\n * `apps/nest/microservices/mission`:\n *\n * TypeError: (0 , __vite_ssr_import_3__.mockReset) is not a function\n *\n * It is the same shape as the automock namespace built from a static export list, which\n * `server.deps.inline` exists to answer; here the file doing the re-export is ours, so it can\n * simply say the names out loud. A name that ever disappears from the fork breaks the build\n * instead of a suite, which is the better end of that trade.\n */\nexport {\n any,\n anyArray,\n anyBoolean,\n anyFunction,\n anyMap,\n anyNumber,\n anyObject,\n anySet,\n anyString,\n anySymbol,\n arrayIncludes,\n calledWithFn,\n captor,\n isA,\n isMockObject,\n mapHas,\n matches,\n mockClear,\n mocked,\n mockedFn,\n mockFn,\n mockReset,\n notEmpty,\n notNull,\n notUndefined,\n objectContainsKey,\n} from 'vitest-mock-extended'\n\n/**\n * Every deep mock handed out, so `vi.resetAllMocks()` can reach them.\n *\n * ⚠️ Kept on `globalThis`, and that is not laziness. This file is reached by TWO specifiers: the\n * setup imports it by its path, and a spec imports `jest-mock-extended`, which the preset aliases\n * here. Vite resolves those to two ids and instantiates the module twice, so a registry held in\n * module scope would be filled by one instance and read by the other, and the reset would find\n * nothing. It is the same dual-identity trap the axios alias exists to close, one layer up.\n *\n * ⚠️ It does not reach them on its own, and that is a silent divergence. jest's\n * `jest.resetAllMocks()` reset these because `jest-mock-extended` built them with `jest.fn()`, so\n * they sat in jest's own registry. `vitest-mock-extended` builds them its own way, so\n * `vi.resetAllMocks()` walks past them and their call history survives into the next test.\n *\n * Measured on `apps/nest/microservices/activity`, whose suite does exactly what jest expected:\n *\n * beforeEach(() => mocked.findEvents.mockResolvedValue([]))\n * afterEach(() => vi.resetAllMocks())\n *\n * Eleven tests asserting `toHaveBeenCalledTimes(0)` saw the call left by the test before them.\n * Each one PASSES on its own and fails right after its neighbour, which is the signature of\n * leakage rather than of a broken assertion.\n *\n * A plain Set: a test file gets its own module instance, so the registry lives and dies with it.\n */\nconst REGISTRY = Symbol.for('@hublo/sentinel/deep-mocks')\nconst handedOut: Set<object> = ((globalThis as Record<symbol, unknown>)[REGISTRY] ??=\n new Set<object>()) as Set<object>\n\n/** Reset every deep mock this adapter created, the way jest's registry did. */\nexport function resetDeepMocks(): void {\n for (const created of handedOut) mockExtended.mockReset(created as never)\n}\n\n/** Clear their calls without touching their implementations. */\nexport function clearDeepMocks(): void {\n for (const created of handedOut) mockExtended.mockClear(created as never)\n}\n\nexport const mock = ((...args: unknown[]) => {\n const created = (mockExtended.mock as (...rest: unknown[]) => object)(...args)\n handedOut.add(created)\n return answeringProxy(created)\n}) as unknown as typeof mockExtended.mock\n\nexport const mockDeep = ((...args: unknown[]) => {\n const created = (mockExtended.mockDeep as (...rest: unknown[]) => object)(...args)\n handedOut.add(created)\n return answeringProxy(created)\n}) as unknown as typeof mockExtended.mockDeep\n"],"mappings":";AAiCA,YAAY,kBAAkB;AA8G9B,cAAc;AAkBd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAtIP,IAAM,qBAAqB,oBAAI,IAAI,CAAC,QAAQ,mBAAmB,YAAY,UAAU,CAAC;AAEtF,SAAS,YAAY,QAAgB,KAAwB;AAC3D,MAAI,OAAO,QAAQ,YAAY,mBAAmB,IAAI,GAAG,EAAG;AAE5D,OAAM,OAAmC,GAAG;AAC9C;AAgBA,IAAM,WAAW,oBAAI,QAAwB;AAE7C,SAAS,eAAiCC,OAAY;AACpD,SAAO,IAAI,MAAMA,OAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAcrB,IAAI,QAAQ,KAAK;AACf,UAAI,OAAO,QAAQ,YAAY,mBAAmB,IAAI,GAAG,EAAG,QAAO;AAEnE,YAAM,QAAQ,QAAQ,IAAI,QAAQ,GAAG;AACrC,UAAI,UAAU,KAAM,QAAO;AAC3B,UAAI,OAAO,UAAU,YAAY,OAAO,UAAU,WAAY,QAAO;AASrE,YAAM,aAAa,QAAQ,yBAAyB,QAAQ,GAAG;AAC/D,UACE,eAAe,UACf,WAAW,iBAAiB,SAC5B,WAAW,aAAa,OACxB;AACA,eAAO;AAAA,MACT;AAEA,YAAM,WAAW,SAAS,IAAI,KAAe;AAC7C,UAAI,aAAa,OAAW,QAAO;AAEnC,YAAM,UAAU,eAAe,KAAe;AAC9C,eAAS,IAAI,OAAiB,OAAO;AACrC,aAAO;AAAA,IACT;AAAA,IACA,IAAI,QAAQ,KAAK;AACf,UAAI,QAAQ,IAAI,QAAQ,GAAG,EAAG,QAAO;AACrC,kBAAY,QAAQ,GAAG;AACvB,aAAO,QAAQ,IAAI,QAAQ,GAAG;AAAA,IAChC;AAAA,IACA,yBAAyB,QAAQ,KAAK;AACpC,YAAM,WAAW,QAAQ,yBAAyB,QAAQ,GAAG;AAC7D,UAAI,aAAa,OAAW,QAAO;AACnC,kBAAY,QAAQ,GAAG;AACvB,aAAO,QAAQ,yBAAyB,QAAQ,GAAG;AAAA,IACrD;AAAA,EACF,CAAC;AACH;AAgFA,IAAM,WAAW,uBAAO,IAAI,4BAA4B;AACxD,IAAM,YAA2B,WAAuC,QAAQ,MAC9E,oBAAI,IAAY;AAGX,SAAS,iBAAuB;AACrC,aAAW,WAAW,UAAW,CAAa,uBAAU,OAAgB;AAC1E;AAGO,SAAS,iBAAuB;AACrC,aAAW,WAAW,UAAW,CAAa,uBAAU,OAAgB;AAC1E;AAEO,IAAMA,SAAQ,IAAI,SAAoB;AAC3C,QAAM,UAAwB,kBAAwC,GAAG,IAAI;AAC7E,YAAU,IAAI,OAAO;AACrB,SAAO,eAAe,OAAO;AAC/B;AAEO,IAAMC,aAAY,IAAI,SAAoB;AAC/C,QAAM,UAAwB,sBAA4C,GAAG,IAAI;AACjF,YAAU,IAAI,OAAO;AACrB,SAAO,eAAe,OAAO;AAC/B;","names":["mockClear","mockReset","mock","mockDeep"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/roles/build/nest/decorator-metadata.ts
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import { createRequire } from "module";
|
|
4
|
-
import path from "path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
5
|
var TYPESCRIPT_SOURCE = /\.ts$/;
|
|
6
6
|
var TSCONFIG_CANDIDATES = ["tsconfig.app.json", "tsconfig.json"];
|
|
7
7
|
function loadCompiler(root) {
|
|
@@ -152,8 +152,8 @@ var decoratorMetadata = (options) => {
|
|
|
152
152
|
};
|
|
153
153
|
|
|
154
154
|
// src/roles/build/nest/tsconfig-aliases.ts
|
|
155
|
-
import { readFileSync } from "fs";
|
|
156
|
-
import { join } from "path";
|
|
155
|
+
import { readFileSync } from "node:fs";
|
|
156
|
+
import { join } from "node:path";
|
|
157
157
|
var stripLineComments = (json) => json.replace(/^\s*\/\/.*$/gm, "");
|
|
158
158
|
var toAlias = (workspaceRoot, pattern, target) => {
|
|
159
159
|
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, "(.*)");
|
|
@@ -175,4 +175,4 @@ export {
|
|
|
175
175
|
decoratorMetadata,
|
|
176
176
|
tsconfigAliases
|
|
177
177
|
};
|
|
178
|
-
//# sourceMappingURL=chunk-
|
|
178
|
+
//# sourceMappingURL=chunk-3ZKTXODW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/roles/build/nest/decorator-metadata.ts","../src/roles/build/nest/tsconfig-aliases.ts"],"sourcesContent":["/**\n * Decorator metadata, which esbuild cannot emit and Nest cannot live without, plus the TypeScript\n * transformers a service declared on its webpack target.\n *\n * Vite's default transformer is esbuild, and esbuild does not implement `emitDecoratorMetadata`.\n * Nest resolves a constructor's parameters from exactly that metadata, so without it the DI\n * container comes up empty and the application fails on its first module. Measured in the\n * monorepo this ships to: `emitDecoratorMetadata: true` in `tsconfig.base.json`, and 7365 files\n * carrying `@Injectable`, `@Module` or `@Controller`.\n *\n * ## TWO modes, chosen exactly as nx chooses them\n *\n * This is the one decision in this file that is not ours, and trying to make it ours cost a day.\n *\n * `@nx/webpack` configures ts-loader with one line:\n *\n * transpileOnly: !hasPlugin // hasPlugin = this target declares `transformers`\n *\n * So webpack already had two behaviours. A service with no transformers compiled with\n * `transpileOnly`, which is `ts.transpileModule` semantics: no type checker, one file at a time.\n * A service WITH transformers compiled through a full `ts.Program`, because that is the only way\n * a TypeScript transformer can run at all.\n *\n * The difference is visible, and measured. On a property typed by an imported string enum:\n *\n * Program : __metadata(\"design:type\", String)\n * transpileModule : __metadata(\"design:type\", typeof (_a = ...) === \"function\" ? _a : Object)\n *\n * and on a `@Body()` whose DTO arrives through `import type`, a Program emits `Function` where\n * transpileOnly emits `Object`, which `@nestjs/swagger` turns into four `$ref: Function` request\n * bodies that the published contract does not have.\n *\n * This plugin used only the first mode and this file claimed it was \"the serializer ts-loader was\n * already using\". Half true, and the wrong half: it is what ts-loader uses for the 29 services\n * with no transformers, and those came back byte-identical. Then a Program was used for ALL of\n * them, which fixed the eight and moved the contract of `network`, which had been correct.\n *\n * Both were the same mistake: choosing one mode for everyone, when the tool being replaced chose\n * per service. So the condition is copied rather than invented, and there is no third case to\n * discover: nx has two, and both are reproduced here.\n *\n * The Program costs about six seconds more on the largest service (1419 files: ~1.9s to build it,\n * ~3.3ms per file against ~0.9ms). Only the services that declare a transformer pay it, which is\n * also what webpack did.\n *\n * ## The transformers, which are configuration and are read, never invented\n *\n * Eight services and BFFs declare `@nestjs/swagger/plugin` on their webpack target, with\n * `introspectComments` and `classValidatorShim`. It writes the `@ApiProperty` decorators a\n * developer would otherwise write by hand, turns JSDoc into descriptions, and projects\n * class-validator rules into the schema.\n *\n * Dropping it does not fail a build. Measured on `institution` by regenerating its committed\n * contract after a sentinel build: 385 insertions and 1378 deletions. Response schemas and every\n * `minItems`, `minimum`, `maximum`, `minLength` and `format` disappeared, silently.\n *\n * So `--init` reads the declaration off the webpack target and writes it into the generated Vite\n * config, and this plugin loads it. Nothing here decides which transformers a service runs.\n *\n * ## Why the MODULE's compiler now, and not sentinel's own\n *\n * This plugin used to pin sentinel's TypeScript, on the grounds that `typescript` in the consumer\n * could resolve to the 7.x native port, which exports a version and no compiler API. Measured\n * from a service today it resolves to `@typescript/typescript6` 6.0.2, with the full API.\n *\n * The pin cannot survive the transformers anyway: a plugin loaded from the module's\n * `node_modules` imports the module's TypeScript, and a transformer built against one compiler\n * cannot be handed a Program built by another. One compiler, the module's, is also what ts-loader\n * used, so it is the faithful answer rather than merely the workable one. The API is asserted at\n * `buildStart`, where a failure is reported.\n *\n * ## Why the module's own tsconfig, and not options chosen here\n *\n * Emit follows compiler options, so options invented here would be a second, silent source of\n * divergence from what ts-loader produced. `target` alone decides how classes and fields are\n * downlevelled: this workspace is on `es2021`, where `useDefineForClassFields` defaults to false\n * and a declared-but-unassigned DTO field is therefore NOT emitted. Defaulting to `es2022` here\n * would flip that, `Object.defineProperty` would write `undefined` over every field, and\n * `class-transformer` payloads would arrive empty at runtime with nothing in the build to explain\n * it.\n *\n * So the config is read, `extends` resolved by the compiler itself, and only what this plugin\n * genuinely owns is overridden.\n *\n * ## Still no type CHECKING, which is a different thing from having types\n *\n * The Program gives this plugin a type checker; it does not use it to report errors. Every Nest\n * project already runs `tsc -b` in its own `typecheck` target, and the build never was the thing\n * that type-checked. Emitting with type information and refusing to type-check are independent,\n * and only the first is needed to reproduce what ts-loader wrote.\n */\nimport { existsSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\n\nimport type TS from 'typescript'\nimport type { Plugin } from 'vite'\n\n/** Files this transform owns: TypeScript sources, never a dependency's compiled output. */\nconst TYPESCRIPT_SOURCE = /\\.ts$/\n\n/**\n * Where a Nest service keeps the config it builds with, most specific first.\n *\n * `tsconfig.app.json` is the one Nx generates for the application's own sources and the one\n * ts-loader was pointed at; `tsconfig.json` is the fallback for a service that never split them.\n * Measured on `main`: all 37 services still on webpack name `tsconfig.app.json`.\n */\nconst TSCONFIG_CANDIDATES = ['tsconfig.app.json', 'tsconfig.json']\n\n/** A TypeScript transformer a service runs at build time, as its build target declares it. */\nexport interface TransformerDeclaration {\n /** The package to load it from, e.g. `@nestjs/swagger/plugin`. */\n name: string\n /** Passed to the transformer's own factory, unread here. */\n options?: Record<string, unknown>\n}\n\nexport interface DecoratorMetadataOptions {\n /** The module being built. Its tsconfig is the one whose emit must be preserved. */\n root: string\n /**\n * An explicit tsconfig, when the service does not use either conventional name.\n *\n * Relative paths resolve against `root`.\n */\n tsconfig?: string\n /** The transformers this service declared, translated from its webpack target. */\n transformers?: readonly TransformerDeclaration[]\n}\n\n/**\n * The module's own TypeScript, with its emit API asserted.\n *\n * Resolved from `root` rather than from here, because a transformer loaded from the module binds\n * to the compiler the module resolves, and Programs are not interchangeable between compilers.\n */\nfunction loadCompiler(root: string): typeof TS {\n const require = createRequire(path.join(root, 'noop.js'))\n let compiler: typeof TS\n try {\n compiler = require('typescript') as typeof TS\n } catch {\n throw new Error(\n `sentinel build(nest): no \\`typescript\\` resolvable from ${root}. The build emits decorator ` +\n `metadata with the module's own compiler, so one has to be installed there.`,\n )\n }\n if (typeof compiler.createProgram !== 'function') {\n throw new Error(\n `sentinel build(nest): the typescript resolved from ${root} (${compiler.version ?? 'unknown'}) ` +\n `has no createProgram API. TypeScript 7 is the native port and exposes none; the emit API ` +\n `lives under @typescript/typescript6. Point this module's \\`typescript\\` at a compiler ` +\n `with an emit API, which is the one its \\`typecheck\\` target already uses.`,\n )\n }\n return compiler\n}\n\nfunction resolveTsconfig(options: DecoratorMetadataOptions): string {\n if (options.tsconfig) {\n const explicit = path.resolve(options.root, options.tsconfig)\n if (!existsSync(explicit)) {\n throw new Error(`sentinel build(nest): tsconfig not found at ${explicit}`)\n }\n return explicit\n }\n for (const candidate of TSCONFIG_CANDIDATES) {\n const found = path.join(options.root, candidate)\n if (existsSync(found)) return found\n }\n throw new Error(\n `sentinel build(nest): no ${TSCONFIG_CANDIDATES.join(' or ')} in ${options.root}. ` +\n `Pass \\`tsconfig\\` if this service keeps it elsewhere.`,\n )\n}\n\n/** The module's compiler options, with `extends` already resolved, plus what this plugin owns. */\nfunction readConfig(\n ts: typeof TS,\n options: DecoratorMetadataOptions,\n): { fileNames: string[]; compilerOptions: TS.CompilerOptions } {\n const configPath = resolveTsconfig(options)\n const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {\n ...ts.sys,\n onUnRecoverableConfigFileDiagnostic: (diagnostic) => {\n throw new Error(\n `sentinel build(nest): could not read ${configPath}: ` +\n ts.flattenDiagnosticMessageText(diagnostic.messageText, ' '),\n )\n },\n } as TS.ParseConfigFileHost)\n\n return {\n fileNames: parsed?.fileNames ?? [],\n compilerOptions: {\n ...parsed?.options,\n // ESM out, so Rollup sees imports and exports rather than an opaque `require` it cannot\n // follow. The service still SHIPS as CJS: that conversion is the bundler's, further down.\n module: ts.ModuleKind.ESNext,\n // Vite consumes the map; the tsconfig's own answer is about a different pipeline.\n sourceMap: true,\n inlineSourceMap: false,\n inlineSources: false,\n // Types are another target's job, and emitting them here would write into the source tree.\n declaration: false,\n declarationMap: false,\n emitDeclarationOnly: false,\n noEmit: false,\n // `composite` projects refuse to emit without a `tsBuildInfoFile`, and there is no\n // incremental build here to inform.\n composite: false,\n incremental: false,\n },\n }\n}\n\n/**\n * The declared transformers, loaded from the MODULE so each binds to the compiler it expects.\n *\n * A name that cannot be loaded, or a package that exports no `before`, fails the build here\n * rather than producing a bundle quietly missing what the transformer contributes. That failure\n * mode is the reason this exists: dropping `@nestjs/swagger/plugin` costs a service most of its\n * published contract and nothing in the build says so.\n */\nfunction loadTransformers(\n root: string,\n program: TS.Program,\n declared: readonly TransformerDeclaration[],\n): TS.TransformerFactory<TS.SourceFile>[] {\n const require = createRequire(path.join(root, 'noop.js'))\n return declared.map(({ name, options }) => {\n let loaded: { before?: unknown }\n try {\n loaded = require(name) as { before?: unknown }\n } catch (error) {\n throw new Error(\n `sentinel build(nest): the build target declares the transformer \\`${name}\\`, which ` +\n `cannot be loaded from ${root}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n )\n }\n if (typeof loaded.before !== 'function') {\n throw new Error(\n `sentinel build(nest): \\`${name}\\` exports no \\`before\\` factory, so it cannot run as a ` +\n `TypeScript transformer. Building without it would drop whatever it contributes.`,\n )\n }\n const factory = loaded.before as (\n options: Record<string, unknown> | undefined,\n program: TS.Program,\n ) => TS.TransformerFactory<TS.SourceFile>\n return factory(options, program)\n })\n}\n\nexport const decoratorMetadata = (options: DecoratorMetadataOptions): Plugin => {\n /*\n * Everything is prepared in `buildStart`, and that placement is the whole point.\n *\n * It used to be resolved lazily inside `transform`, on the reasoning that an error belongs to\n * the build rather than to importing the preset. Half right: it belongs to the build, but\n * Rollup SWALLOWS a throwing `transform` hook. The wrong compiler, an unreadable tsconfig and a\n * missing one all produced the same symptom instead of their own message: \"1 modules\n * transformed\", nothing written, then an unrelated error from whichever later hook tripped over\n * the empty output directory. It cost two diagnoses to recognise.\n *\n * `buildStart` is reported. So the compiler is loaded, the config read, the Program built and\n * the transformers resolved exactly once, before a single file is transformed, where failing\n * says what failed.\n */\n /** `hasPlugin` in nx's own words: this service declares transformers, so it needs a Program. */\n const typeAware = (options.transformers ?? []).length > 0\n\n let ts: typeof TS | undefined\n let program: TS.Program | undefined\n let compilerOptions: TS.CompilerOptions | undefined\n let before: TS.TransformerFactory<TS.SourceFile>[] = []\n /** Files emitted without the Program, which is a fidelity gap and is reported, never silent. */\n const withoutTypes: string[] = []\n\n return {\n name: 'sentinel:decorator-metadata',\n // Before Vite's own transform, so esbuild never sees the decorators it cannot handle, and so\n // the source this plugin reads from the Program is still the source on disk.\n enforce: 'pre',\n buildStart() {\n ts = loadCompiler(options.root)\n const config = readConfig(ts, options)\n compilerOptions = config.compilerOptions\n // Built only in the type-aware mode. It costs ~1.9s on the largest service here and buys\n // nothing a service without transformers can use, which is why nx does not build one either.\n if (!typeAware) return\n program = ts.createProgram(config.fileNames, config.compilerOptions)\n before = loadTransformers(options.root, program, options.transformers ?? [])\n },\n transform(code, id) {\n if (!TYPESCRIPT_SOURCE.test(id) || id.includes('node_modules')) return null\n // Only reachable if `buildStart` did not run, which no Rollup build does. Named rather than\n // defaulted, so it cannot silently emit with TypeScript's defaults instead of the module's.\n if (ts === undefined || compilerOptions === undefined) {\n throw new Error(\n `sentinel build(nest): the compiler was never prepared, so ${id} would be emitted with ` +\n `defaults rather than this module's tsconfig.`,\n )\n }\n\n /*\n * The Program's own SourceFile, which is what carries the type information.\n *\n * Three cases emit without it. The service declares no transformer, which is `transpileOnly`\n * and is what ts-loader did for it. A file the Program never reached, because nothing\n * imports it from the entry. And a file another plugin has already rewritten, where the\n * Program's copy is no longer the truth: emitting that stale copy over someone else's\n * transform would be the worse failure.\n *\n * The last two are counted and reported at the end. The first is not a gap, it is the mode.\n *\n * Measured on the largest service here: neither of the last two fires, on any of its 5594\n * files. That is the point of reporting them rather than tolerating them quietly — a warning\n * from this build means something genuinely new, not a known rough edge.\n */\n const sourceFile = program?.getSourceFile(id)\n if (program === undefined || sourceFile === undefined || sourceFile.text !== code) {\n if (typeAware) withoutTypes.push(id)\n const output = ts.transpileModule(code, { fileName: id, compilerOptions })\n return { code: output.outputText, map: output.sourceMapText ?? null }\n }\n\n let emitted: string | undefined\n let map: string | undefined\n program.emit(\n sourceFile,\n (fileName, text) => {\n if (fileName.endsWith('.map')) map = text\n else emitted = text\n },\n undefined,\n false,\n { before },\n )\n if (emitted === undefined) {\n throw new Error(`sentinel build(nest): TypeScript emitted nothing for ${id}.`)\n }\n return { code: emitted, map: map ?? null }\n },\n closeBundle() {\n if (withoutTypes.length === 0) return\n const shown = withoutTypes.slice(0, 5).map((file) => path.relative(options.root, file))\n this.warn(\n `sentinel build(nest): ${withoutTypes.length} file(s) were emitted without type ` +\n `information, so their decorator metadata may differ from what ts-loader produced ` +\n `(${shown.join(', ')}${withoutTypes.length > shown.length ? ', …' : ''}). They are ` +\n `outside this module's tsconfig, or another plugin rewrote them first.`,\n )\n },\n }\n}\n","/**\n * The workspace's `paths` mappings, as Vite resolve aliases.\n *\n * They matter more than they look. The mappings point at SOURCE files, and that is what makes\n * a workspace library get BUNDLED rather than externalised: Vite treats anything outside\n * `node_modules` as source. Drop them and every `@hublo/nest/*` import becomes an external the\n * runtime cannot resolve, because those packages are never published.\n *\n * This reproduces what webpack did through `tsconfig-paths-webpack-plugin`, alongside\n * `webpack-node-externals` which only ever externalised real packages. Measured on the first\n * migrated service: 45 externals under Vite against 47 under webpack, and nothing externalised\n * by Vite that webpack did not.\n */\nimport { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nexport interface Alias {\n find: RegExp\n replacement: string\n}\n\n/** `tsconfig.base.json` carries `//` comments, which `JSON.parse` refuses. */\nconst stripLineComments = (json: string): string => json.replace(/^\\s*\\/\\/.*$/gm, '')\n\nconst toAlias = (workspaceRoot: string, pattern: string, target: string): Alias => {\n const escaped = pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\\\\\*/g, '(.*)')\n return {\n find: new RegExp(`^${escaped}$`),\n replacement: join(workspaceRoot, target.replace(/\\*/g, '$1')),\n }\n}\n\n/**\n * Read the mappings from the workspace's base tsconfig.\n *\n * Longest pattern first, because Vite takes the first alias that matches and the mappings\n * overlap by design: `@front/theme/node` must not be swallowed by `@front/theme`.\n */\nexport const tsconfigAliases = (workspaceRoot: string, file = 'tsconfig.base.json'): Alias[] => {\n const raw = readFileSync(join(workspaceRoot, file), 'utf8')\n const { compilerOptions } = JSON.parse(stripLineComments(raw)) as {\n compilerOptions?: { paths?: Record<string, string[]> }\n }\n return Object.entries(compilerOptions?.paths ?? {})\n .flatMap(([pattern, targets]) => {\n const [target] = targets\n return target === undefined ? [] : [toAlias(workspaceRoot, pattern, target)]\n })\n .sort((a, b) => b.find.source.length - a.find.source.length)\n}\n"],"mappings":"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","names":["require"]}
|