@hublo/sentinel 1.3.0 → 1.4.0-alpha.2

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.
Files changed (48) hide show
  1. package/README.md +8 -4
  2. package/dist/bin/sentinel.d.ts +0 -1
  3. package/dist/bin/sentinel.js +22 -8
  4. package/dist/chunk-2XLX6PFR.js +132 -0
  5. package/dist/chunk-3TDUIKVQ.js +178 -0
  6. package/dist/{chunk-676GBPMS.js → chunk-4UIZJ3TR.js} +3399 -549
  7. package/dist/chunk-CPCUPK4J.js +70 -0
  8. package/dist/chunk-NX4GHIHF.js +25 -0
  9. package/dist/chunk-PWV3BMDA.js +15 -0
  10. package/dist/chunk-WLFE5RUU.js +264 -0
  11. package/dist/index.js +2 -1
  12. package/dist/roles/build/nest/toolchain.d.ts +4 -36
  13. package/dist/roles/build/nest/toolchain.js +10 -178
  14. package/dist/roles/build/nest/toolchain.js.map +1 -0
  15. package/dist/roles/build/toolchain.js.map +1 -0
  16. package/dist/roles/test/nest/toolchain.d.ts +29 -0
  17. package/dist/roles/test/nest/toolchain.js +289 -0
  18. package/dist/roles/test/nest/toolchain.js.map +1 -0
  19. package/dist/roles/test/react/toolchain.d.ts +62 -0
  20. package/dist/roles/test/react/toolchain.js +5 -0
  21. package/dist/roles/test/react/toolchain.js.map +1 -0
  22. package/dist/roles/test/setup/mock-extended.d.ts +46 -0
  23. package/dist/roles/test/setup/mock-extended.js +65 -0
  24. package/dist/roles/test/setup/mock-extended.js.map +1 -0
  25. package/dist/roles/test/setup/msw-lifecycle.d.ts +16 -0
  26. package/dist/roles/test/setup/msw-lifecycle.js +12 -0
  27. package/dist/roles/test/setup/msw-lifecycle.js.map +1 -0
  28. package/dist/roles/test/setup/msw-server.d.ts +3 -0
  29. package/dist/roles/test/setup/msw-server.js +10 -0
  30. package/dist/roles/test/setup/msw-server.js.map +1 -0
  31. package/dist/roles/test/setup/nest.d.ts +2 -0
  32. package/dist/roles/test/setup/nest.js +159 -0
  33. package/dist/roles/test/setup/nest.js.map +1 -0
  34. package/dist/roles/test/setup/workspace-entry.d.ts +2 -0
  35. package/dist/roles/test/setup/workspace-entry.js +8 -0
  36. package/dist/roles/test/setup/workspace-entry.js.map +1 -0
  37. package/dist/tsconfig-aliases-Ce6axdJ4.d.ts +36 -0
  38. package/docs/.gitkeep +0 -0
  39. package/docs/build-adoption.md +521 -0
  40. package/docs/format-adoption.md +321 -0
  41. package/docs/lint-adoption.md +290 -0
  42. package/docs/performance.md +49 -0
  43. package/docs/test-adoption.md +175 -0
  44. package/docs/typescript-adoption.md +184 -0
  45. package/docs/typescript-traces.md +798 -0
  46. package/docs/using-sentinel.md +180 -0
  47. package/docs/validating-a-change.md +101 -0
  48. package/package.json +32 -5
@@ -0,0 +1,70 @@
1
+ import {
2
+ findWorkspaceRoot
3
+ } from "./chunk-WLFE5RUU.js";
4
+
5
+ // src/roles/test/setup/workspace.ts
6
+ import { createRequire } from "module";
7
+ import { join } from "path";
8
+ function isMissingModule(error) {
9
+ return error?.code === "ERR_MODULE_NOT_FOUND";
10
+ }
11
+ async function optional(specifier, use) {
12
+ try {
13
+ use(await import(
14
+ /* @vite-ignore */
15
+ specifier
16
+ ));
17
+ } catch (error) {
18
+ if (!isMissingModule(error)) throw error;
19
+ }
20
+ }
21
+ function fromModule(specifier) {
22
+ try {
23
+ const require2 = createRequire(join(process.cwd(), "noop.cjs"));
24
+ return require2(specifier);
25
+ } catch {
26
+ return void 0;
27
+ }
28
+ }
29
+ async function loadEnvironment() {
30
+ await optional("dotenv-flow", (module) => {
31
+ const flow = module;
32
+ const config = flow.config ?? flow.default?.config;
33
+ const workspaceRoot = findWorkspaceRoot(process.cwd());
34
+ config?.({
35
+ silent: true,
36
+ purge_dotenv: true,
37
+ ...workspaceRoot === void 0 ? {} : { path: workspaceRoot }
38
+ });
39
+ });
40
+ }
41
+ async function pinLuxonZone() {
42
+ let pinned = false;
43
+ await optional("luxon", (module) => {
44
+ const settings = module.Settings;
45
+ if (settings === void 0) return;
46
+ settings.defaultZone = "utc";
47
+ pinned = true;
48
+ });
49
+ if (pinned) return;
50
+ const own = fromModule("luxon");
51
+ if (own?.Settings !== void 0) {
52
+ own.Settings.defaultZone = "utc";
53
+ return;
54
+ }
55
+ process.stderr.write(
56
+ "sentinel (test): luxon was not found in this module, so the UTC default zone is NOT pinned. Dates will follow the machine zone, as they did not under jest.\n"
57
+ );
58
+ }
59
+ async function installWorkspaceSetup() {
60
+ await loadEnvironment();
61
+ }
62
+ async function pinWorkspaceTimezone() {
63
+ await pinLuxonZone();
64
+ }
65
+
66
+ export {
67
+ installWorkspaceSetup,
68
+ pinWorkspaceTimezone
69
+ };
70
+ //# sourceMappingURL=chunk-CPCUPK4J.js.map
@@ -0,0 +1,25 @@
1
+ // src/roles/test/react/jest-export-conditions.ts
2
+ var ABSENT_UNDER_JEST = /* @__PURE__ */ new Set(["development", "development|production"]);
3
+ function stripInPlace(conditions) {
4
+ if (conditions === void 0) return;
5
+ for (let index = conditions.length - 1; index >= 0; index -= 1) {
6
+ const condition = conditions[index];
7
+ if (condition !== void 0 && ABSENT_UNDER_JEST.has(condition)) conditions.splice(index, 1);
8
+ }
9
+ }
10
+ function jestExportConditions() {
11
+ return {
12
+ name: "sentinel:jest-export-conditions",
13
+ configResolved(config) {
14
+ const environments = config;
15
+ stripInPlace(config.resolve?.conditions);
16
+ stripInPlace(config.ssr?.resolve?.conditions);
17
+ stripInPlace(environments.environments?.ssr?.resolve?.conditions);
18
+ }
19
+ };
20
+ }
21
+
22
+ export {
23
+ jestExportConditions
24
+ };
25
+ //# sourceMappingURL=chunk-NX4GHIHF.js.map
@@ -0,0 +1,15 @@
1
+ // src/roles/test/setup/msw.ts
2
+ import { setupServer } from "msw/node";
3
+ var server = setupServer();
4
+ var LISTEN_OPTIONS = { onUnhandledRequest: "error" };
5
+ function installMswLifecycle(hooks) {
6
+ server.listen(LISTEN_OPTIONS);
7
+ hooks.beforeAll(() => server.listen(LISTEN_OPTIONS));
8
+ hooks.afterAll(() => server.close());
9
+ }
10
+
11
+ export {
12
+ server,
13
+ installMswLifecycle
14
+ };
15
+ //# sourceMappingURL=chunk-PWV3BMDA.js.map
@@ -0,0 +1,264 @@
1
+ // src/shared/package-json.ts
2
+ import { existsSync, readFileSync } from "fs";
3
+ import { basename, dirname, join } from "path";
4
+ import { fileURLToPath } from "url";
5
+ function readOwnPackage() {
6
+ let dir = dirname(fileURLToPath(import.meta.url));
7
+ for (; ; ) {
8
+ const pkgPath = join(dir, "package.json");
9
+ if (existsSync(pkgPath)) {
10
+ try {
11
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
12
+ if (typeof pkg.version === "string") {
13
+ const bin = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin ?? {})[0];
14
+ return { name: pkg.name ?? "@hublo/sentinel", version: pkg.version, bin };
15
+ }
16
+ } catch {
17
+ }
18
+ }
19
+ const parent = dirname(dir);
20
+ if (parent === dir) return { name: "@hublo/sentinel", version: "0.0.0" };
21
+ dir = parent;
22
+ }
23
+ }
24
+ function readOwnVersion() {
25
+ return readOwnPackage().version;
26
+ }
27
+ function readProjectPackageJson(dir) {
28
+ const path = join(dir, "package.json");
29
+ if (!existsSync(path)) return {};
30
+ try {
31
+ return JSON.parse(readFileSync(path, "utf8"));
32
+ } catch {
33
+ process.stderr.write(`sentinel: could not parse ${path}; ignoring for detection.
34
+ `);
35
+ return {};
36
+ }
37
+ }
38
+ function readNxProjectName(dir) {
39
+ const path = join(dir, "project.json");
40
+ if (!existsSync(path)) return void 0;
41
+ try {
42
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
43
+ return typeof parsed.name === "string" ? parsed.name : void 0;
44
+ } catch {
45
+ return void 0;
46
+ }
47
+ }
48
+ function moduleName(dir) {
49
+ const declared = readProjectPackageJson(dir).name;
50
+ return readNxProjectName(dir) ?? (declared === void 0 ? basename(dir) : declared);
51
+ }
52
+
53
+ // src/core/workspace-prep.ts
54
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
55
+ import { dirname as dirname2, join as join2, relative } from "path";
56
+
57
+ // src/core/settings.ts
58
+ var WORKSPACE_ROOT_MARKER = "nx.json";
59
+ var DEFAULT_MAX_DIAGNOSTICS = 100;
60
+
61
+ // src/core/workspace-prep.ts
62
+ var OVERRIDE_KEY = "i18next>typescript";
63
+ var NATIVE_TS_ALIAS = "@typescript/native";
64
+ var WORKSPACE_YAML = "pnpm-workspace.yaml";
65
+ var RELEASE_AGE_KEY = "minimumReleaseAgeExclude";
66
+ var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
67
+ var MAP_ENTRY = /^(\s+)(.*?):\s+(.*?)\s*$/;
68
+ function findWorkspaceRoot(startDir) {
69
+ let dir = startDir;
70
+ for (; ; ) {
71
+ if (existsSync2(join2(dir, WORKSPACE_ROOT_MARKER))) return dir;
72
+ const parent = dirname2(dir);
73
+ if (parent === dir) return void 0;
74
+ dir = parent;
75
+ }
76
+ }
77
+ function declaredNativeTs(pkg) {
78
+ const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS];
79
+ if (!spec) return void 0;
80
+ const version = spec.slice(spec.lastIndexOf("@") + 1).replace(/^[\^~>=<\s]+/, "");
81
+ return version || void 0;
82
+ }
83
+ function ensureI18nextSingleton(root, dryRun) {
84
+ const pkgPath = join2(root, "package.json");
85
+ if (!existsSync2(pkgPath)) return void 0;
86
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
87
+ const want = declaredNativeTs(pkg);
88
+ if (!want) return void 0;
89
+ const yamlPath = join2(root, WORKSPACE_YAML);
90
+ if (!existsSync2(yamlPath)) return void 0;
91
+ const lines = readFileSync2(yamlPath, "utf8").split("\n");
92
+ const entry = findOverrideEntry(lines);
93
+ if (entry.valueIdx !== void 0 && entry.value === want) return void 0;
94
+ if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} in ${WORKSPACE_YAML} (i18next singleton)`;
95
+ applyOverride(lines, entry, want);
96
+ writeFileSync(yamlPath, lines.join("\n"));
97
+ return `pinned ${OVERRIDE_KEY} to ${want} in ${WORKSPACE_YAML} (i18next singleton)`;
98
+ }
99
+ function findOverrideEntry(lines) {
100
+ const blockIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === "overrides:");
101
+ if (blockIdx === -1) return { indent: " " };
102
+ let lastEntryIdx = blockIdx;
103
+ let indent = " ";
104
+ for (let i = blockIdx + 1; i < lines.length; i++) {
105
+ const match = lines[i]?.match(MAP_ENTRY);
106
+ if (!match) break;
107
+ indent = match[1] ?? indent;
108
+ lastEntryIdx = i;
109
+ if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === OVERRIDE_KEY) {
110
+ return {
111
+ valueIdx: i,
112
+ value: (match[3] ?? "").replace(/^['"]|['"]$/g, ""),
113
+ blockIdx,
114
+ lastEntryIdx,
115
+ indent
116
+ };
117
+ }
118
+ }
119
+ return { blockIdx, lastEntryIdx, indent };
120
+ }
121
+ function applyOverride(lines, entry, want) {
122
+ const line = `${entry.indent}'${OVERRIDE_KEY}': ${want}`;
123
+ if (entry.valueIdx !== void 0) {
124
+ lines[entry.valueIdx] = line;
125
+ return;
126
+ }
127
+ if (entry.lastEntryIdx !== void 0) {
128
+ lines.splice(entry.lastEntryIdx + 1, 0, line);
129
+ return;
130
+ }
131
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
132
+ lines.push("overrides:", line, "");
133
+ }
134
+ function ensureReleaseAgeAllowList(root, dryRun) {
135
+ const yamlPath = join2(root, WORKSPACE_YAML);
136
+ if (!existsSync2(yamlPath)) return void 0;
137
+ const own = readOwnPackage().name;
138
+ const lines = readFileSync2(yamlPath, "utf8").split("\n");
139
+ const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
140
+ if (keyIdx === -1) return void 0;
141
+ let lastItemIdx = keyIdx;
142
+ let indent = " ";
143
+ for (let i = keyIdx + 1; i < lines.length; i++) {
144
+ const match = lines[i]?.match(LIST_ITEM);
145
+ if (!match) break;
146
+ indent = match[1] ?? indent;
147
+ lastItemIdx = i;
148
+ if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === own) return void 0;
149
+ }
150
+ if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`;
151
+ lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`);
152
+ writeFileSync(yamlPath, lines.join("\n"));
153
+ return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`;
154
+ }
155
+ var PRETTIER_IGNORE = ".prettierignore";
156
+ var EXCLUSION_HEADER = "# @hublo/sentinel: modules formatted by oxfmt, not by the root Prettier";
157
+ var ROOT_PRETTIER_CONFIGS = [
158
+ ".prettierrc",
159
+ ".prettierrc.json",
160
+ ".prettierrc.json5",
161
+ ".prettierrc.yml",
162
+ ".prettierrc.yaml",
163
+ ".prettierrc.js",
164
+ ".prettierrc.cjs",
165
+ ".prettierrc.mjs",
166
+ "prettier.config.js",
167
+ "prettier.config.cjs",
168
+ "prettier.config.mjs"
169
+ ];
170
+ function ensureFormatterExclusion(root, moduleDir, dryRun) {
171
+ const rel = relative(root, moduleDir).replaceAll("\\", "/");
172
+ if (rel === "" || rel.startsWith("..")) return void 0;
173
+ const ignorePath = join2(root, PRETTIER_IGNORE);
174
+ const hasPrettier = existsSync2(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync2(join2(root, name)));
175
+ if (!hasPrettier) return void 0;
176
+ const pattern = `/${rel}/`;
177
+ const existing = existsSync2(ignorePath) ? readFileSync2(ignorePath, "utf8") : "";
178
+ const lines = existing.split("\n");
179
+ if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
180
+ return void 0;
181
+ }
182
+ if (dryRun) return `would exclude ${rel} from the root Prettier (${PRETTIER_IGNORE})`;
183
+ const headerIdx = lines.findIndex((line) => line.trim() === EXCLUSION_HEADER);
184
+ if (headerIdx === -1) {
185
+ const prefix = existing.trim() === "" ? "" : `${existing.replace(/\n*$/, "")}
186
+
187
+ `;
188
+ writeFileSync(ignorePath, `${prefix}${EXCLUSION_HEADER}
189
+ ${pattern}
190
+ `);
191
+ } else {
192
+ let end = headerIdx + 1;
193
+ while (end < lines.length && lines[end]?.trim().startsWith("/")) end += 1;
194
+ const managed = [...lines.slice(headerIdx + 1, end), pattern].sort();
195
+ writeFileSync(
196
+ ignorePath,
197
+ [...lines.slice(0, headerIdx + 1), ...managed, ...lines.slice(end)].join("\n")
198
+ );
199
+ }
200
+ return `excluded ${rel} from the root Prettier (${PRETTIER_IGNORE}): it formats itself now`;
201
+ }
202
+ function ensureWorkspacePrep(opts) {
203
+ const dryRun = Boolean(opts.dryRun);
204
+ return [
205
+ ensureI18nextSingleton(opts.root, dryRun),
206
+ ensureReleaseAgeAllowList(opts.root, dryRun),
207
+ opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
208
+ ].filter((message) => message !== void 0);
209
+ }
210
+ function inspectWorkspacePrep(root) {
211
+ const entries = [];
212
+ let pkg = {};
213
+ const pkgPath = join2(root, "package.json");
214
+ if (existsSync2(pkgPath)) {
215
+ try {
216
+ pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
217
+ } catch {
218
+ pkg = {};
219
+ }
220
+ }
221
+ const yamlPath = join2(root, WORKSPACE_YAML);
222
+ const yaml = existsSync2(yamlPath) ? readFileSync2(yamlPath, "utf8") : void 0;
223
+ const pinned = yaml === void 0 ? void 0 : findOverrideEntry(yaml.split("\n")).value;
224
+ if (pinned !== void 0) {
225
+ const stillForks = declaredNativeTs(pkg);
226
+ entries.push({
227
+ rule: `${WORKSPACE_YAML} \u2192 overrides.${OVERRIDE_KEY}`,
228
+ reason: stillForks === void 0 ? `pinned to ${pinned}, but the root no longer declares ${NATIVE_TS_ALIAS}, so nothing forks any more. This override is now dead config and can be removed.` : `pinned to ${pinned}, matching the second TypeScript the root declares (${NATIVE_TS_ALIAS}). It re-resolves i18next for EVERY module that depends on it, not only adopted ones. It retires with the second TypeScript.`
229
+ });
230
+ }
231
+ const legacy = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
232
+ if (legacy !== void 0) {
233
+ entries.push({
234
+ rule: `package.json \u2192 pnpm.overrides.${OVERRIDE_KEY}`,
235
+ reason: `pinned to ${legacy}, but pnpm 11 no longer reads the \`pnpm\` field of package.json and says nothing about it, so this override is not applied. Move it to ${WORKSPACE_YAML} under \`overrides:\`, which pnpm 10 and 11 both read.`
236
+ });
237
+ }
238
+ const own = readOwnPackage().name;
239
+ if (yaml !== void 0) {
240
+ const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
241
+ if (listed) {
242
+ const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
243
+ entries.push({
244
+ rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
245
+ reason: gated ? `${own} is exempt from this workspace's release-age gate, so a freshly published version installs instead of being held. That is a standing exemption from a supply-chain control: the exposure is one bump wide, since the version is pinned and the lockfile carries its integrity, so a re-publish of the same version does not pass. Retire it once a bump can simply wait out the window.` : `${own} is allow-listed, but this workspace no longer sets minimumReleaseAge, so there is no gate to be exempt from. This entry is now dead config and can be removed.`
246
+ });
247
+ }
248
+ }
249
+ return entries;
250
+ }
251
+
252
+ export {
253
+ readOwnPackage,
254
+ readOwnVersion,
255
+ readProjectPackageJson,
256
+ readNxProjectName,
257
+ moduleName,
258
+ WORKSPACE_ROOT_MARKER,
259
+ DEFAULT_MAX_DIAGNOSTICS,
260
+ findWorkspaceRoot,
261
+ ensureWorkspacePrep,
262
+ inspectWorkspacePrep
263
+ };
264
+ //# sourceMappingURL=chunk-WLFE5RUU.js.map
package/dist/index.js CHANGED
@@ -7,7 +7,8 @@ import {
7
7
  registerAdapters,
8
8
  resolve,
9
9
  setDefaultRunner
10
- } from "./chunk-676GBPMS.js";
10
+ } from "./chunk-4UIZJ3TR.js";
11
+ import "./chunk-WLFE5RUU.js";
11
12
  export {
12
13
  BaseAdapter,
13
14
  all,
@@ -1,8 +1,9 @@
1
1
  import { UserConfig, Plugin } from 'vite';
2
2
  export { Plugin, PluginOption, UserConfig, UserConfigExport, defineConfig, loadEnv, mergeConfig } from 'vite';
3
+ export { A as Alias, D as DecoratorMetadataOptions, d as decoratorMetadata, t as tsconfigAliases } from '../../../tsconfig-aliases-Ce6axdJ4.js';
3
4
 
4
5
  /** A transformer as the target declares it, passed through to the config unchanged. */
5
- interface TransformerDeclaration$1 {
6
+ interface TransformerDeclaration {
6
7
  name: string;
7
8
  options?: Record<string, unknown>;
8
9
  }
@@ -41,7 +42,7 @@ interface NestServiceOptions {
41
42
  * without it succeeds and silently costs them most of their published contract (measured on
42
43
  * `institution`: 385 insertions, 1378 deletions in the committed OpenAPI document).
43
44
  */
44
- transformers?: readonly TransformerDeclaration$1[];
45
+ transformers?: readonly TransformerDeclaration[];
45
46
  /**
46
47
  * Files copied into the output beside the bundle, in the shape `@nx/webpack` declared them.
47
48
  *
@@ -60,27 +61,6 @@ interface NestServiceOptions {
60
61
  }
61
62
  declare const nestService: (options: NestServiceOptions) => UserConfig;
62
63
 
63
- /** A TypeScript transformer a service runs at build time, as its build target declares it. */
64
- interface TransformerDeclaration {
65
- /** The package to load it from, e.g. `@nestjs/swagger/plugin`. */
66
- name: string;
67
- /** Passed to the transformer's own factory, unread here. */
68
- options?: Record<string, unknown>;
69
- }
70
- interface DecoratorMetadataOptions {
71
- /** The module being built. Its tsconfig is the one whose emit must be preserved. */
72
- root: string;
73
- /**
74
- * An explicit tsconfig, when the service does not use either conventional name.
75
- *
76
- * Relative paths resolve against `root`.
77
- */
78
- tsconfig?: string;
79
- /** The transformers this service declared, translated from its webpack target. */
80
- transformers?: readonly TransformerDeclaration[];
81
- }
82
- declare const decoratorMetadata: (options: DecoratorMetadataOptions) => Plugin;
83
-
84
64
  interface NodeManifestOptions {
85
65
  /** The nx project name, which is how the graph is keyed. */
86
66
  project: string;
@@ -115,16 +95,4 @@ interface NodeManifestOptions {
115
95
  */
116
96
  declare const nodeManifest: (options: NodeManifestOptions) => Plugin;
117
97
 
118
- interface Alias {
119
- find: RegExp;
120
- replacement: string;
121
- }
122
- /**
123
- * Read the mappings from the workspace's base tsconfig.
124
- *
125
- * Longest pattern first, because Vite takes the first alias that matches and the mappings
126
- * overlap by design: `@front/theme/node` must not be swallowed by `@front/theme`.
127
- */
128
- declare const tsconfigAliases: (workspaceRoot: string, file?: string) => Alias[];
129
-
130
- export { type Alias, type DecoratorMetadataOptions, type NestServiceOptions, type NodeManifestOptions, decoratorMetadata, nestService, nodeManifest, tsconfigAliases };
98
+ export { type NestServiceOptions, type NodeManifestOptions, nestService, nodeManifest };
@@ -1,5 +1,10 @@
1
+ import {
2
+ decoratorMetadata,
3
+ tsconfigAliases
4
+ } from "../../../chunk-3TDUIKVQ.js";
5
+
1
6
  // src/roles/build/nest/nest-service.ts
2
- import { join as join4 } from "path";
7
+ import { join as join3 } from "path";
3
8
  import { mergeConfig } from "vite";
4
9
 
5
10
  // src/roles/build/nest/copy-assets.ts
@@ -76,161 +81,8 @@ var copyAssets = (options) => ({
76
81
  }
77
82
  });
78
83
 
79
- // src/roles/build/nest/decorator-metadata.ts
80
- import { existsSync as existsSync2 } from "fs";
81
- import { createRequire } from "module";
82
- import path from "path";
83
- var TYPESCRIPT_SOURCE = /\.ts$/;
84
- var TSCONFIG_CANDIDATES = ["tsconfig.app.json", "tsconfig.json"];
85
- function loadCompiler(root) {
86
- const require2 = createRequire(path.join(root, "noop.js"));
87
- let compiler;
88
- try {
89
- compiler = require2("typescript");
90
- } catch {
91
- throw new Error(
92
- `sentinel build(nest): no \`typescript\` resolvable from ${root}. The build emits decorator metadata with the module's own compiler, so one has to be installed there.`
93
- );
94
- }
95
- if (typeof compiler.createProgram !== "function") {
96
- throw new Error(
97
- `sentinel build(nest): the typescript resolved from ${root} (${compiler.version ?? "unknown"}) has no createProgram API. TypeScript 7 is the native port and exposes none; the emit API lives under @typescript/typescript6. Point this module's \`typescript\` at a compiler with an emit API, which is the one its \`typecheck\` target already uses.`
98
- );
99
- }
100
- return compiler;
101
- }
102
- function resolveTsconfig(options) {
103
- if (options.tsconfig) {
104
- const explicit = path.resolve(options.root, options.tsconfig);
105
- if (!existsSync2(explicit)) {
106
- throw new Error(`sentinel build(nest): tsconfig not found at ${explicit}`);
107
- }
108
- return explicit;
109
- }
110
- for (const candidate of TSCONFIG_CANDIDATES) {
111
- const found = path.join(options.root, candidate);
112
- if (existsSync2(found)) return found;
113
- }
114
- throw new Error(
115
- `sentinel build(nest): no ${TSCONFIG_CANDIDATES.join(" or ")} in ${options.root}. Pass \`tsconfig\` if this service keeps it elsewhere.`
116
- );
117
- }
118
- function readConfig(ts, options) {
119
- const configPath = resolveTsconfig(options);
120
- const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
121
- ...ts.sys,
122
- onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
123
- throw new Error(
124
- `sentinel build(nest): could not read ${configPath}: ` + ts.flattenDiagnosticMessageText(diagnostic.messageText, " ")
125
- );
126
- }
127
- });
128
- return {
129
- fileNames: parsed?.fileNames ?? [],
130
- compilerOptions: {
131
- ...parsed?.options,
132
- // ESM out, so Rollup sees imports and exports rather than an opaque `require` it cannot
133
- // follow. The service still SHIPS as CJS: that conversion is the bundler's, further down.
134
- module: ts.ModuleKind.ESNext,
135
- // Vite consumes the map; the tsconfig's own answer is about a different pipeline.
136
- sourceMap: true,
137
- inlineSourceMap: false,
138
- inlineSources: false,
139
- // Types are another target's job, and emitting them here would write into the source tree.
140
- declaration: false,
141
- declarationMap: false,
142
- emitDeclarationOnly: false,
143
- noEmit: false,
144
- // `composite` projects refuse to emit without a `tsBuildInfoFile`, and there is no
145
- // incremental build here to inform.
146
- composite: false,
147
- incremental: false
148
- }
149
- };
150
- }
151
- function loadTransformers(root, program, declared) {
152
- const require2 = createRequire(path.join(root, "noop.js"));
153
- return declared.map(({ name, options }) => {
154
- let loaded;
155
- try {
156
- loaded = require2(name);
157
- } catch (error) {
158
- throw new Error(
159
- `sentinel build(nest): the build target declares the transformer \`${name}\`, which cannot be loaded from ${root}: ${error instanceof Error ? error.message : String(error)}`,
160
- { cause: error }
161
- );
162
- }
163
- if (typeof loaded.before !== "function") {
164
- throw new Error(
165
- `sentinel build(nest): \`${name}\` exports no \`before\` factory, so it cannot run as a TypeScript transformer. Building without it would drop whatever it contributes.`
166
- );
167
- }
168
- const factory = loaded.before;
169
- return factory(options, program);
170
- });
171
- }
172
- var decoratorMetadata = (options) => {
173
- const typeAware = (options.transformers ?? []).length > 0;
174
- let ts;
175
- let program;
176
- let compilerOptions;
177
- let before = [];
178
- const withoutTypes = [];
179
- return {
180
- name: "sentinel:decorator-metadata",
181
- // Before Vite's own transform, so esbuild never sees the decorators it cannot handle, and so
182
- // the source this plugin reads from the Program is still the source on disk.
183
- enforce: "pre",
184
- buildStart() {
185
- ts = loadCompiler(options.root);
186
- const config = readConfig(ts, options);
187
- compilerOptions = config.compilerOptions;
188
- if (!typeAware) return;
189
- program = ts.createProgram(config.fileNames, config.compilerOptions);
190
- before = loadTransformers(options.root, program, options.transformers ?? []);
191
- },
192
- transform(code, id) {
193
- if (!TYPESCRIPT_SOURCE.test(id) || id.includes("node_modules")) return null;
194
- if (ts === void 0 || compilerOptions === void 0) {
195
- throw new Error(
196
- `sentinel build(nest): the compiler was never prepared, so ${id} would be emitted with defaults rather than this module's tsconfig.`
197
- );
198
- }
199
- const sourceFile = program?.getSourceFile(id);
200
- if (program === void 0 || sourceFile === void 0 || sourceFile.text !== code) {
201
- if (typeAware) withoutTypes.push(id);
202
- const output = ts.transpileModule(code, { fileName: id, compilerOptions });
203
- return { code: output.outputText, map: output.sourceMapText ?? null };
204
- }
205
- let emitted;
206
- let map;
207
- program.emit(
208
- sourceFile,
209
- (fileName, text) => {
210
- if (fileName.endsWith(".map")) map = text;
211
- else emitted = text;
212
- },
213
- void 0,
214
- false,
215
- { before }
216
- );
217
- if (emitted === void 0) {
218
- throw new Error(`sentinel build(nest): TypeScript emitted nothing for ${id}.`);
219
- }
220
- return { code: emitted, map: map ?? null };
221
- },
222
- closeBundle() {
223
- if (withoutTypes.length === 0) return;
224
- const shown = withoutTypes.slice(0, 5).map((file) => path.relative(options.root, file));
225
- this.warn(
226
- `sentinel build(nest): ${withoutTypes.length} file(s) were emitted without type information, so their decorator metadata may differ from what ts-loader produced (${shown.join(", ")}${withoutTypes.length > shown.length ? ", \u2026" : ""}). They are outside this module's tsconfig, or another plugin rewrote them first.`
227
- );
228
- }
229
- };
230
- };
231
-
232
84
  // src/roles/build/nest/node-manifest.ts
233
- import { existsSync as existsSync3, writeFileSync } from "fs";
85
+ import { existsSync as existsSync2, writeFileSync } from "fs";
234
86
  import { isBuiltin } from "module";
235
87
  import { join as join2 } from "path";
236
88
  var DEFAULT_PROVIDED_BY_IMAGE = ["@prisma"];
@@ -260,7 +112,7 @@ var nodeManifest = (options) => {
260
112
  },
261
113
  async closeBundle() {
262
114
  const entry = options.entry ?? "main.js";
263
- if (!existsSync3(join2(options.outDir, entry))) return;
115
+ if (!existsSync2(join2(options.outDir, entry))) return;
264
116
  const { createProjectGraphAsync } = await import("nx/src/devkit-exports.js");
265
117
  const { createPackageJson, createLockFile, getLockFileName } = await import("@nx/js");
266
118
  const graph = await createProjectGraphAsync({ exitOnError: false });
@@ -294,30 +146,10 @@ var assertManifestCovers = (required, manifest, options) => {
294
146
  );
295
147
  };
296
148
 
297
- // src/roles/build/nest/tsconfig-aliases.ts
298
- import { readFileSync } from "fs";
299
- import { join as join3 } from "path";
300
- var stripLineComments = (json) => json.replace(/^\s*\/\/.*$/gm, "");
301
- var toAlias = (workspaceRoot, pattern, target) => {
302
- const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, "(.*)");
303
- return {
304
- find: new RegExp(`^${escaped}$`),
305
- replacement: join3(workspaceRoot, target.replace(/\*/g, "$1"))
306
- };
307
- };
308
- var tsconfigAliases = (workspaceRoot, file = "tsconfig.base.json") => {
309
- const raw = readFileSync(join3(workspaceRoot, file), "utf8");
310
- const { compilerOptions } = JSON.parse(stripLineComments(raw));
311
- return Object.entries(compilerOptions?.paths ?? {}).flatMap(([pattern, targets]) => {
312
- const [target] = targets;
313
- return target === void 0 ? [] : [toAlias(workspaceRoot, pattern, target)];
314
- }).sort((a, b) => b.find.source.length - a.find.source.length);
315
- };
316
-
317
149
  // src/roles/build/nest/nest-service.ts
318
150
  var baseConfig = (options) => {
319
- const outDir = join4(options.workspaceRoot, options.outDir ?? `dist/${options.project}`);
320
- const entry = join4(options.root, options.entry ?? "src/main.ts");
151
+ const outDir = join3(options.workspaceRoot, options.outDir ?? `dist/${options.project}`);
152
+ const entry = join3(options.root, options.entry ?? "src/main.ts");
321
153
  return {
322
154
  plugins: [
323
155
  decoratorMetadata({