@hublo/sentinel 1.4.3 → 1.4.4

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.
@@ -15,10 +15,10 @@ import {
15
15
  palette,
16
16
  registerAdapters,
17
17
  resolve
18
- } from "../chunk-DYK6UEMO.js";
18
+ } from "../chunk-BSERYTT5.js";
19
19
  import {
20
20
  resolveContext
21
- } from "../chunk-TDNPPNGK.js";
21
+ } from "../chunk-NFGIHMXB.js";
22
22
  import "../chunk-O7REVMOC.js";
23
23
  import "../chunk-QXFCZON7.js";
24
24
  import {
@@ -145,7 +145,7 @@ async function runValidate(ctx) {
145
145
  );
146
146
  }
147
147
  if (!ctx.targets.includes("test")) return 1;
148
- const { validateModuleTests } = await import("../validate-3QMCX2HW.js");
148
+ const { validateModuleTests } = await import("../validate-L52THU22.js");
149
149
  const outcomes = modules.flatMap(
150
150
  (module) => validateModuleTests(module.name, module.root)
151
151
  );
@@ -53,7 +53,7 @@ import {
53
53
  selfCommand,
54
54
  splitToolArgs,
55
55
  walk
56
- } from "./chunk-TDNPPNGK.js";
56
+ } from "./chunk-NFGIHMXB.js";
57
57
  import {
58
58
  parseJsonc
59
59
  } from "./chunk-QXFCZON7.js";
@@ -4067,7 +4067,15 @@ function findSuppressionRenames(cwd, renames) {
4067
4067
  var DEFAULT_IGNORE_PATTERNS = [
4068
4068
  "**/dist/**",
4069
4069
  "**/node_modules/**",
4070
- "**/coverage/**",
4070
+ // Anchored, unlike the two above: this one names a word a domain can legitimately use.
4071
+ // `**/coverage/**` also swallowed `planning-requirement/src/coverage`, the RequirementResult
4072
+ // coverage feature, whose 48 TypeScript files were then linted by nothing at all — neither
4073
+ // the preset nor the module's own planning layer said a word about them. `dist` and
4074
+ // `node_modules` are tool output wherever they sit, so they stay unanchored; a folder called
4075
+ // `coverage` is only output at the module root. Measured on the monorepo before changing:
4076
+ // of the 55 modules carrying the unanchored entry, planning-requirement is the ONLY one with
4077
+ // a source folder of that name, so this narrows nothing anywhere else.
4078
+ "coverage/**",
4071
4079
  // `**/*.mock.ts` used to sit here and has been removed. It was the only entry in this list
4072
4080
  // with no reason written next to it, and it does not meet the bar the paragraph above sets:
4073
4081
  // a mock is not build output, not vendored and not generated. It is hand-written TypeScript
@@ -4900,7 +4908,11 @@ function localOnlyCompilerOptions(compilerOptions, preset) {
4900
4908
 
4901
4909
  // src/roles/typescript/resolve-tsconfig-target.ts
4902
4910
  var TSCONFIG_CANDIDATES = ["tsconfig.app.json", "tsconfig.json"];
4903
- var TSCONFIG_MARKERS = ["tsconfig.base.json", "@hublo/sentinel/tsconfig/"];
4911
+ var TSCONFIG_MARKERS = [
4912
+ "tsconfig.base.json",
4913
+ "@hublo/sentinel/typescript/",
4914
+ "@hublo/sentinel/tsconfig/"
4915
+ ];
4904
4916
  var TSCONFIG_FALLBACK = "tsconfig.json";
4905
4917
  function resolveTsconfigTarget(moduleDir) {
4906
4918
  return resolveConfigTarget(moduleDir, {
@@ -5341,6 +5341,28 @@ function specTypesOperation(cwd) {
5341
5341
  }
5342
5342
  };
5343
5343
  }
5344
+ function specTypesCleanupOperation(cwd) {
5345
+ const path = join25(cwd, SPEC_TSCONFIG);
5346
+ if (!existsSync19(path)) return void 0;
5347
+ let config;
5348
+ try {
5349
+ config = parseJsonc(
5350
+ readFileSync21(path, "utf8"),
5351
+ SPEC_TSCONFIG
5352
+ );
5353
+ } catch {
5354
+ return void 0;
5355
+ }
5356
+ const types = config?.compilerOptions?.types;
5357
+ if (!Array.isArray(types) || !types.includes("jest")) return void 0;
5358
+ const remaining = types.filter((entry) => entry !== "jest");
5359
+ if (remaining.length === 0) return void 0;
5360
+ return {
5361
+ kind: "merge-json",
5362
+ path: SPEC_TSCONFIG,
5363
+ value: { compilerOptions: { types: remaining } }
5364
+ };
5365
+ }
5344
5366
  function shippedTypesPath(cwd, declaration) {
5345
5367
  try {
5346
5368
  const manifest = createRequire5(join25(cwd, "noop.cjs")).resolve("@hublo/sentinel/package.json");
@@ -5703,6 +5725,8 @@ function plan(context) {
5703
5725
  keys: [["targets", TEST_SCRIPT_NAME]]
5704
5726
  });
5705
5727
  }
5728
+ const specTypes = specTypesCleanupOperation(context.cwd);
5729
+ if (specTypes !== void 0) operations2.push(specTypes);
5706
5730
  if (operations2.length === 0) {
5707
5731
  return {
5708
5732
  operations: [],
@@ -5715,7 +5739,11 @@ function plan(context) {
5715
5739
  // Names what is actually removed, never a shape this module does not have: half of these
5716
5740
  // modules carry a config with no target behind it, and claiming otherwise would describe
5717
5741
  // an operation the plan never performs.
5718
- `this module has no test file, so this is a cleanup rather than an adoption: ${[...adoption.jestConfigs, ...hasTarget ? ["the jest target it feeds"] : []].join(" and ")} removed, so nothing here breaks the day jest leaves the workspace. No test config is written in exchange, because there is nothing to configure.`
5742
+ `this module has no test file, so this is a cleanup rather than an adoption: ${[
5743
+ ...adoption.jestConfigs,
5744
+ ...hasTarget ? ["the jest target it feeds"] : [],
5745
+ ...specTypes !== void 0 ? ["the jest entry in tsconfig.spec.json"] : []
5746
+ ].join(" and ")} removed, so nothing here breaks the day jest leaves the workspace. No test config is written in exchange, because there is nothing to configure.`
5719
5747
  ]
5720
5748
  };
5721
5749
  }
package/dist/index.js CHANGED
@@ -6,10 +6,10 @@ import {
6
6
  registerAdapters,
7
7
  resolve,
8
8
  setDefaultRunner
9
- } from "./chunk-DYK6UEMO.js";
9
+ } from "./chunk-BSERYTT5.js";
10
10
  import {
11
11
  BaseAdapter
12
- } from "./chunk-TDNPPNGK.js";
12
+ } from "./chunk-NFGIHMXB.js";
13
13
  import "./chunk-O7REVMOC.js";
14
14
  import "./chunk-QXFCZON7.js";
15
15
  import "./chunk-KMKQDGI6.js";
@@ -8,7 +8,7 @@ import {
8
8
  import "../../../chunk-QXFCZON7.js";
9
9
 
10
10
  // src/roles/build/nest/nest-service.ts
11
- import { join as join3 } from "node:path";
11
+ import { join as join4 } from "node:path";
12
12
  import { mergeConfig } from "vite";
13
13
 
14
14
  // src/roles/build/nest/copy-assets.ts
@@ -59,10 +59,61 @@ var copyAssets = (options) => ({
59
59
  }
60
60
  });
61
61
 
62
- // src/roles/build/nest/node-manifest.ts
63
- import { existsSync as existsSync2, writeFileSync } from "node:fs";
62
+ // src/roles/build/nest/importer-externals.ts
63
+ import { existsSync as existsSync2, readFileSync } from "node:fs";
64
64
  import { isBuiltin } from "node:module";
65
- import { join as join2 } from "node:path";
65
+ import { dirname as dirname2, extname, join as join2, sep as sep2 } from "node:path";
66
+ var packageName = (specifier) => {
67
+ const parts = specifier.split("/");
68
+ return specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0] ?? specifier;
69
+ };
70
+ var isBareSpecifier = (specifier) => !specifier.startsWith(".") && !specifier.startsWith("/") && !specifier.startsWith("\0") && !isBuiltin(specifier);
71
+ var packageManifest = (file, name) => {
72
+ let directory = dirname2(file);
73
+ while (directory !== dirname2(directory)) {
74
+ const manifest = join2(directory, "package.json");
75
+ if (existsSync2(manifest)) {
76
+ try {
77
+ const data = JSON.parse(readFileSync(manifest, "utf8"));
78
+ if (data.name === name) return data;
79
+ } catch {
80
+ }
81
+ }
82
+ directory = dirname2(directory);
83
+ }
84
+ return void 0;
85
+ };
86
+ var externalId = (specifier, name, resolved) => {
87
+ if (specifier === name) return specifier;
88
+ if (extname(specifier) === extname(resolved)) return specifier;
89
+ if (packageManifest(resolved, name)?.exports !== void 0) return specifier;
90
+ const index = resolved.split(sep2).join("/").indexOf(specifier);
91
+ return index === -1 ? specifier : resolved.split(sep2).join("/").slice(index);
92
+ };
93
+ var importerExternals = (options) => ({
94
+ name: "sentinel:importer-externals",
95
+ enforce: "pre",
96
+ async resolveId(source, importer, resolveOptions) {
97
+ if (!importer || !isBareSpecifier(source)) return null;
98
+ const name = packageName(source);
99
+ if (options.noExternal.includes(name)) return null;
100
+ if (options.aliases.some((alias) => alias.find.test(source))) return null;
101
+ const resolved = await this.resolve(source, importer, { ...resolveOptions, skipSelf: true });
102
+ if (!resolved || resolved.external) return null;
103
+ const file = resolved.id.split("?")[0] ?? resolved.id;
104
+ if (!file.split(sep2).includes("node_modules")) return null;
105
+ return {
106
+ id: externalId(source, name, file),
107
+ external: true,
108
+ moduleSideEffects: resolved.moduleSideEffects
109
+ };
110
+ }
111
+ });
112
+
113
+ // src/roles/build/nest/node-manifest.ts
114
+ import { existsSync as existsSync3, writeFileSync } from "node:fs";
115
+ import { isBuiltin as isBuiltin2 } from "node:module";
116
+ import { join as join3 } from "node:path";
66
117
  var DEFAULT_PROVIDED_BY_IMAGE = ["@prisma"];
67
118
  var externalPackages = (bundle) => {
68
119
  const emitted = new Set(Object.keys(bundle));
@@ -72,7 +123,7 @@ var externalPackages = (bundle) => {
72
123
  for (const imported of emittedFile.imports) {
73
124
  if (emitted.has(imported)) continue;
74
125
  if (imported.startsWith(".") || imported.startsWith("/")) continue;
75
- if (isBuiltin(imported)) continue;
126
+ if (isBuiltin2(imported)) continue;
76
127
  const parts = imported.split("/");
77
128
  const name = imported.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
78
129
  if (name) packages.add(name);
@@ -90,7 +141,7 @@ var nodeManifest = (options) => {
90
141
  },
91
142
  async closeBundle() {
92
143
  const entry = options.entry ?? "main.js";
93
- if (!existsSync2(join2(options.outDir, entry))) return;
144
+ if (!existsSync3(join3(options.outDir, entry))) return;
94
145
  const { createProjectGraphAsync } = await import("nx/src/devkit-exports.js");
95
146
  const { createPackageJson, createLockFile, getLockFileName } = await import("@nx/js");
96
147
  const graph = await createProjectGraphAsync({ exitOnError: false });
@@ -105,10 +156,10 @@ var nodeManifest = (options) => {
105
156
  );
106
157
  }
107
158
  assertManifestCovers(required, manifest, options);
108
- writeFileSync(join2(options.outDir, "package.json"), `${JSON.stringify(manifest, null, 2)}
159
+ writeFileSync(join3(options.outDir, "package.json"), `${JSON.stringify(manifest, null, 2)}
109
160
  `);
110
161
  writeFileSync(
111
- join2(options.outDir, getLockFileName("pnpm")),
162
+ join3(options.outDir, getLockFileName("pnpm")),
112
163
  createLockFile(manifest, graph, "pnpm")
113
164
  );
114
165
  }
@@ -125,11 +176,14 @@ var assertManifestCovers = (required, manifest, options) => {
125
176
  };
126
177
 
127
178
  // src/roles/build/nest/nest-service.ts
179
+ var NEST_SERVICE_NO_EXTERNAL = ["tslib"];
128
180
  var baseConfig = (options) => {
129
- const outDir = join3(options.workspaceRoot, options.outDir ?? `dist/${options.project}`);
130
- const entry = join3(options.root, options.entry ?? "src/main.ts");
181
+ const outDir = join4(options.workspaceRoot, options.outDir ?? `dist/${options.project}`);
182
+ const entry = join4(options.root, options.entry ?? "src/main.ts");
183
+ const aliases = tsconfigAliases(options.workspaceRoot);
131
184
  return {
132
185
  plugins: [
186
+ importerExternals({ noExternal: NEST_SERVICE_NO_EXTERNAL, aliases }),
133
187
  decoratorMetadata({
134
188
  root: options.root,
135
189
  tsconfig: options.tsconfig,
@@ -142,19 +196,15 @@ var baseConfig = (options) => {
142
196
  outDir
143
197
  })
144
198
  ],
145
- resolve: { alias: tsconfigAliases(options.workspaceRoot) },
199
+ resolve: { alias: aliases },
146
200
  ssr: {
147
- // `importHelpers: true` in the workspace tsconfig makes TypeScript emit `require("tslib")`
148
- // for `__decorate` and `__metadata`, so every decorated file depends on it. Hoisted into
149
- // one scope Rollup inlined it and nobody noticed; preserving modules left it external, and
150
- // the manifest nx generates from the project graph does not list it, because no module
151
- // DECLARES tslib. The image therefore did not install it and the service died on its first
152
- // require, only in the image: locally and in CI the workspace root has tslib.
153
- noExternal: ["tslib"]
201
+ // See NEST_SERVICE_NO_EXTERNAL: `tslib`, inlined on purpose.
202
+ noExternal: [...NEST_SERVICE_NO_EXTERNAL]
154
203
  },
155
204
  build: {
156
205
  // An SSR build targets Node and leaves real packages external, which is what
157
206
  // `webpack-node-externals` did: the image installs them from the generated manifest.
207
+ // `importerExternals` holds that wherever the package is declared, not only at the root.
158
208
  ssr: entry,
159
209
  outDir,
160
210
  emptyOutDir: true,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/roles/build/nest/nest-service.ts","../../../../src/roles/build/nest/copy-assets.ts","../../../../src/roles/build/nest/node-manifest.ts","../../../../src/roles/build/nest/toolchain.ts"],"sourcesContent":["/**\n * The whole Vite config for a Nest service, so an adopted module holds two lines.\n *\n * ## Why a shape here, and pieces on the React side\n *\n * It looks inconsistent until you measure the two families. The four React apps each carry a\n * heavy, genuinely different config: one has a plugin wedged between two shared ones, another\n * has two different route-tree footers on the branches of one ternary. Two attempts at a\n * shared shape for them were abandoned, and the rule that survived is that sentinel rewrites\n * their IMPORTS and touches nothing else.\n *\n * The Nest side is the opposite by measurement: 38 services, one shared 25-line webpack config\n * between them, and nothing per-module to preserve. Owning the shape there is not a different\n * philosophy, it is the same one applied to a different fact: sentinel provides the FORM where\n * the form is shared, and the PIECES where it is not.\n *\n * The consequences are the point. An adopted module is readable in review. A correction to the\n * form is one publish instead of 38 diffs. And the day Nest becomes ESM-native, `format`\n * changes here rather than in 38 configs.\n *\n * ## It is a composition, not a block\n *\n * `nestService` assembles the same plugins this module exports individually. A service that\n * needs something else can pass `overrides`, or drop to the pieces entirely. And if the React\n * configs ever converge, a `reactApp()` can be built the same way with no new mechanism.\n */\nimport { join } from 'node:path'\n\nimport { mergeConfig, type UserConfig } from 'vite'\n\nimport { copyAssets } from './copy-assets.js'\nimport { decoratorMetadata } from './decorator-metadata.js'\nimport { nodeManifest } from './node-manifest.js'\nimport type { AssetDeclaration, TransformerDeclaration } from './nx-target.js'\nimport { tsconfigAliases } from './tsconfig-aliases.js'\n\nexport interface NestServiceOptions {\n /** The nx project name. Keys the dependency graph, and names the module in messages. */\n project: string\n /** Absolute path to the module's directory, normally `__dirname`. */\n root: string\n /** Absolute path to the workspace root. */\n workspaceRoot: string\n /** Entry point, relative to `root`. */\n entry?: string\n /**\n * The tsconfig whose emit must be preserved, when the service uses neither conventional name.\n *\n * Relative to `root`. Defaults to `tsconfig.app.json`, then `tsconfig.json`.\n */\n tsconfig?: string\n /** Output directory, relative to `workspaceRoot`. */\n outDir?: string\n /**\n * TypeScript transformers this service compiles with, as its build target declared them.\n *\n * Read off the webpack target by `--init` and written here, never inferred: what a service\n * compiles with is the service's own. Eight services and BFFs run `@nestjs/swagger/plugin`,\n * which writes the `@ApiProperty` decorators their DTOs do not declare by hand. Building\n * without it succeeds and silently costs them most of their published contract (measured on\n * `institution`: 385 insertions, 1378 deletions in the committed OpenAPI document).\n */\n transformers?: readonly TransformerDeclaration[]\n /**\n * Files copied into the output beside the bundle, in the shape `@nx/webpack` declared them.\n *\n * One service here uses it: `planning-period` ships the pug, css and ttf templates it renders\n * printed documents from. Without them the service starts and fails on its first print.\n */\n assets?: readonly AssetDeclaration[]\n /**\n * Anything this service needs that the shape does not give it.\n *\n * Merged with Vite's own `mergeConfig`, never with a merge of our own: plugins concatenate\n * and aliases stack the way Vite does it everywhere else, so there is no second set of\n * semantics to learn or to document.\n */\n overrides?: UserConfig\n}\n\nconst baseConfig = (options: NestServiceOptions): UserConfig => {\n const outDir = join(options.workspaceRoot, options.outDir ?? `dist/${options.project}`)\n const entry = join(options.root, options.entry ?? 'src/main.ts')\n return {\n plugins: [\n decoratorMetadata({\n root: options.root,\n tsconfig: options.tsconfig,\n transformers: options.transformers,\n }),\n ...(options.assets?.length\n ? [copyAssets({ workspaceRoot: options.workspaceRoot, outDir, assets: options.assets })]\n : []),\n nodeManifest({\n project: options.project,\n workspaceRoot: options.workspaceRoot,\n outDir,\n }),\n ],\n resolve: { alias: tsconfigAliases(options.workspaceRoot) },\n ssr: {\n // `importHelpers: true` in the workspace tsconfig makes TypeScript emit `require(\"tslib\")`\n // for `__decorate` and `__metadata`, so every decorated file depends on it. Hoisted into\n // one scope Rollup inlined it and nobody noticed; preserving modules left it external, and\n // the manifest nx generates from the project graph does not list it, because no module\n // DECLARES tslib. The image therefore did not install it and the service died on its first\n // require, only in the image: locally and in CI the workspace root has tslib.\n noExternal: ['tslib'],\n },\n build: {\n // An SSR build targets Node and leaves real packages external, which is what\n // `webpack-node-externals` did: the image installs them from the generated manifest.\n ssr: entry,\n outDir,\n emptyOutDir: true,\n sourcemap: true,\n target: 'node20',\n // The bundle is read by humans when a stack trace points into it, and minifying a\n // server bundle buys nothing: it is never downloaded.\n minify: false,\n rollupOptions: {\n output: {\n format: 'cjs',\n // One output file per source module, instead of hoisting everything into one scope.\n //\n // This is not a preference, it is the only shape that keeps the OpenAPI contract.\n // Merging every module into one scope forces Rollup to rename duplicate class names,\n // and `@nestjs/swagger` keys its schemas on `class.name`, so a renamed class silently\n // becomes a renamed schema in the published API. Measured on `network`, which has two\n // such collisions: single bundle renames `InvalidPermissionError` and\n // `PermissionNotFoundError`; preserving modules renames nothing and reproduces the\n // committed contract byte for byte.\n //\n // The cost is 4.5 MB across 2003 files against 2.9 MB in one, still well under\n // webpack's 6.9 MB for the same service, so nothing regresses against what it replaces.\n preserveModules: true,\n // The image runs `./dist/main.js`, so the entry keeps that name at the root while\n // every other module keeps its own path. Under `preserveModules` a plain string here\n // would be applied to all of them, which numbers them (`main962.js`) and loses the\n // entry.\n entryFileNames: (chunk) => (chunk.facadeModuleId === entry ? 'main.js' : '[name].js'),\n },\n // Nest registers metadata as an IMPORT SIDE EFFECT: a decorator writes into a catalog\n // when its module loads, and nothing references that module afterwards. Rollup may\n // drop such a module; webpack never did.\n //\n // Measured on the first migrated service this changes nothing, so it is insurance\n // rather than a fix, and it is recorded as such rather than credited with the smaller\n // bundle (that comes from barrel re-exports the service does not use).\n treeshake: { moduleSideEffects: true },\n },\n },\n }\n}\n\nexport const nestService = (options: NestServiceOptions): UserConfig =>\n options.overrides === undefined\n ? baseConfig(options)\n : mergeConfig(baseConfig(options), options.overrides)\n","/**\n * The files a service ships beside its bundle, which webpack copied and a bundler does not.\n *\n * `@nx/webpack` takes an `assets` list and copies each match into the output. Nothing in a Vite\n * build does that for a Node target: `publicDir` is a browser concept and is disabled for SSR\n * builds, so an unmigrated `assets` entry is not a smaller output, it is a service that starts\n * and then fails the first time it reaches for a file that is not there.\n *\n * One service in this repo declares it. `planning-period` ships the pug templates, stylesheet and\n * font it renders printed documents from, under `templates/`. Dropping them leaves a build that\n * succeeds, an image that boots, and a printing feature that throws.\n *\n * ## Copied at `closeBundle`, and only what the target declared\n *\n * The declaration is carried through from the webpack target unchanged, globs and all, so this\n * plugin has nothing to decide: it resolves each `input` against the workspace root, matches the\n * glob, and writes under `output` inside the bundle's directory. `closeBundle` because that is\n * when the output directory exists and Vite has finished emptying it.\n *\n * An entry matching nothing FAILS the build. A glob that has quietly stopped matching is the same\n * silent loss as no glob at all, and the point of this file is that this class of loss is loud.\n */\nimport { cpSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs'\nimport { dirname, join, relative, sep } from 'node:path'\n\nimport type { Plugin } from 'vite'\n\nimport { globToRegExp } from '../../../shared/glob.js'\n\nimport type { AssetDeclaration } from './nx-target.js'\n\nexport interface CopyAssetsOptions {\n /** Absolute path to the workspace root, which `input` is relative to. */\n workspaceRoot: string\n /** Absolute path to the build's output directory. */\n outDir: string\n assets: readonly AssetDeclaration[]\n}\n\n/** Every file under `root`, as paths relative to it and with forward slashes. */\nfunction filesUnder(root: string): string[] {\n const found: string[] = []\n const walk = (dir: string): void => {\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry)\n if (statSync(full).isDirectory()) walk(full)\n else found.push(relative(root, full).split(sep).join('/'))\n }\n }\n walk(root)\n return found\n}\n\n/** What each declaration copies, or the reason it cannot. */\nexport function resolveAssets(options: CopyAssetsOptions): { from: string; to: string }[] {\n const copies: { from: string; to: string }[] = []\n for (const asset of options.assets) {\n const input = join(options.workspaceRoot, asset.input)\n if (!existsSync(input)) {\n throw new Error(\n `sentinel build(nest): the build target declares an asset from \\`${asset.input}\\`, which ` +\n `does not exist. webpack copied it into the output, so the built service would be ` +\n `missing files it reads at runtime.`,\n )\n }\n const matcher = globToRegExp(asset.glob)\n const matched = filesUnder(input).filter((file) => matcher.test(file))\n if (matched.length === 0) {\n throw new Error(\n `sentinel build(nest): the asset glob \\`${asset.glob}\\` under \\`${asset.input}\\` matches ` +\n `no file. An entry that matches nothing is the same silent loss as no entry at all.`,\n )\n }\n for (const file of matched) {\n copies.push({ from: join(input, file), to: join(options.outDir, asset.output, file) })\n }\n }\n return copies\n}\n\nexport const copyAssets = (options: CopyAssetsOptions): Plugin => ({\n name: 'sentinel:copy-assets',\n // After the bundle is written and the output directory has stopped being emptied.\n closeBundle() {\n for (const { from, to } of resolveAssets(options)) {\n mkdirSync(dirname(to), { recursive: true })\n cpSync(from, to)\n }\n },\n})\n","/**\n * The pruned `package.json` and lockfile a Node image installs from.\n *\n * This is the half that made the nx executor look load-bearing. `--generatePackageJson` was an\n * option of `@nx/webpack:webpack`, and the backend image is built on its two outputs:\n *\n * COPY --from=build /app/dist/apps/nest/<service>/package.json /app/package.json\n * COPY --from=build /app/dist/apps/nest/<service>/pnpm-lock.yaml /app/pnpm-lock.yaml\n * RUN pnpm i --prod=true --frozen-lockfile\n *\n * It is not bound to the executor. The executor supplies one thing, the project graph, and\n * every function that does the work is exported by `@nx/js`. So the same two files can be\n * produced from a Vite plugin, and `--frozen-lockfile` accepting them afterwards is the proof\n * that they agree with each other: pnpm refuses the install otherwise.\n *\n * ## Why the graph, and not something simpler\n *\n * A Nest service here declares almost nothing of its own: measured, one dependency in its\n * manifest against 1463 installed in its image. Everything it uses is declared at the\n * workspace root and reaches it through source imports. So the dependency list can only be\n * computed from what the code imports, which is what the graph knows.\n *\n * Deriving it from the bundle's own externals is not equivalent either: measured on the first\n * service, the bundle statically requires 42 packages while the correct manifest holds 65. The\n * 23 missing ones are transitive or required dynamically at runtime (`fastify` behind\n * `@nestjs/platform-fastify`, `jsonwebtoken`, the LaunchDarkly SDK). Shipping the short list\n * fails in production, not in the build.\n */\nimport { existsSync, writeFileSync } from 'node:fs'\nimport { isBuiltin } from 'node:module'\nimport { join } from 'node:path'\n\nimport type { Plugin, Rollup } from 'vite'\n\nexport interface NodeManifestOptions {\n /** The nx project name, which is how the graph is keyed. */\n project: string\n /** Absolute path to the workspace root. */\n workspaceRoot: string\n /** Where the bundle is written; the two files land beside it. */\n outDir: string\n /** Entry file name, written as `main` so `node .` resolves inside the image. */\n entry?: string\n /**\n * Packages the IMAGE provides, which are therefore allowed to be required without being\n * declared in the manifest.\n *\n * The generated Prisma clients are the case this exists for: the Dockerfile copies\n * `node_modules/@prisma` from its own stage, so they resolve at runtime while no module\n * declares them. Everything else that is required and undeclared is a bug, and the check\n * below refuses the build.\n */\n providedByImage?: readonly string[]\n}\n\n/** What the Dockerfile copies in itself, so requiring it without declaring it is legitimate. */\nconst DEFAULT_PROVIDED_BY_IMAGE = ['@prisma'] as const\n\n/**\n * Every real package the emitted output requires, from Rollup's own record rather than a scan\n * of the text.\n *\n * `chunk.imports` mixes three things, and only the third is a dependency:\n *\n * - other EMITTED chunks. Under `preserveModules` these are relative output paths that do not\n * begin with `.`, so `apps/…`, `libs/…` and `_virtual/…` read exactly like package names.\n * They are recognised by being keys of the bundle itself.\n * - Node BUILTINS, which appear both bare (`crypto`) and prefixed (`node:crypto`).\n * - genuine external packages, which is what the manifest has to cover.\n */\nconst externalPackages = (bundle: Rollup.OutputBundle): Set<string> => {\n const emitted = new Set(Object.keys(bundle))\n const packages = new Set<string>()\n\n for (const emittedFile of Object.values(bundle)) {\n // Assets carry no imports, and the bundle holds both. Narrowing on the discriminant is what\n // replaced a cast through `unknown`, which asserted the same thing without checking it.\n if (emittedFile.type !== 'chunk') continue\n\n for (const imported of emittedFile.imports) {\n if (emitted.has(imported)) continue\n if (imported.startsWith('.') || imported.startsWith('/')) continue\n if (isBuiltin(imported)) continue\n\n const parts = imported.split('/')\n const name = imported.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]\n if (name) packages.add(name)\n }\n }\n return packages\n}\n\n/**\n * Writes the manifest and lockfile once the bundle is on disk.\n *\n * `closeBundle` rather than `writeBundle`, so the files land after Vite has finished with the\n * directory and cannot be cleared by `emptyOutDir`.\n *\n * `closeBundle` also runs after a FAILED build, where there is no directory to write into. Left\n * alone this hook then threw `ENOENT` on the manifest, and since it is the last error raised it\n * became the one Vite printed, hiding the failure that actually stopped the build. It cost two\n * diagnoses before being recognised, so the missing directory is now read as what it is: the\n * bundle was never written, and this plugin has nothing to say about why.\n */\nexport const nodeManifest = (options: NodeManifestOptions): Plugin => {\n /*\n * Captured while the bundle still exists in memory, and read back once the manifest is built.\n *\n * `undefined` until `generateBundle` runs, deliberately: an empty SET would make the coverage\n * check below pass by having seen nothing, which is the worst way for a guard to succeed. The\n * two states have to be told apart.\n */\n let required: Set<string> | undefined\n\n return {\n name: 'sentinel:node-manifest',\n apply: 'build',\n\n generateBundle(_outputOptions, bundle) {\n required = externalPackages(bundle)\n },\n\n async closeBundle() {\n // `closeBundle` runs after a FAILED build too. The ENTRY is what says a bundle was really\n // written: `emptyOutDir` leaves the directory in place, so its existence alone would let a\n // failed build still produce a manifest describing nothing.\n const entry = options.entry ?? 'main.js'\n if (!existsSync(join(options.outDir, entry))) return\n\n // Imported here rather than at module scope: a front config importing this file must not\n // pay for loading the nx graph machinery it will never call.\n const { createProjectGraphAsync } = await import('nx/src/devkit-exports.js')\n const { createPackageJson, createLockFile, getLockFileName } = await import('@nx/js')\n\n const graph = await createProjectGraphAsync({ exitOnError: false })\n const manifest = createPackageJson(options.project, graph, {\n root: options.workspaceRoot,\n isProduction: true,\n })\n manifest.main = entry\n\n if (required === undefined) {\n throw new Error(\n `sentinel build(nest): the bundle was written but never inspected, so the manifest ` +\n `could not be checked against what it requires. Refusing to write one that might ` +\n `not install.`,\n )\n }\n assertManifestCovers(required, manifest, options)\n\n writeFileSync(join(options.outDir, 'package.json'), `${JSON.stringify(manifest, null, 2)}\\n`)\n writeFileSync(\n join(options.outDir, getLockFileName('pnpm')),\n createLockFile(manifest, graph, 'pnpm'),\n )\n },\n }\n}\n\n/**\n * Refuse a bundle that requires something the image will not install.\n *\n * The manifest is derived from the project GRAPH, so it lists what modules declare. The bundle\n * requires what the bundler left external. Those two sets agreeing is an assumption, and it was\n * wrong: `tslib` arrives through `importHelpers` in the workspace tsconfig without any module\n * declaring it, so the manifest omitted it and the service died on its first require.\n *\n * It died only in the image. Locally and in CI the workspace root has the package, so the gap is\n * invisible until a container starts, which is the most expensive place to discover it. Checking\n * here turns that into a build failure naming the package.\n */\nconst assertManifestCovers = (\n required: ReadonlySet<string>,\n manifest: { dependencies?: Record<string, string> },\n options: NodeManifestOptions,\n): void => {\n const declared = new Set(Object.keys(manifest.dependencies ?? {}))\n const provided = options.providedByImage ?? DEFAULT_PROVIDED_BY_IMAGE\n const missing = [...required]\n .filter((name) => !declared.has(name))\n .filter((name) => !provided.some((prefix) => name === prefix || name.startsWith(`${prefix}/`)))\n .sort()\n\n if (missing.length === 0) return\n\n throw new Error(\n `sentinel build(nest): the bundle requires ${missing.length} package(s) the generated ` +\n `manifest does not declare, so the image would not install them: ${missing.join(', ')}. ` +\n `Either declare them in the module's package.json, inline them with ` +\n `\\`ssr.noExternal\\`, or list them in \\`providedByImage\\` if the Dockerfile copies them in.`,\n )\n}\n","/**\n * What `@hublo/sentinel/build/nest` gives an adopted service.\n *\n * The sibling `build/react` entry is a re-export surface: the module keeps its own config and\n * only its import lines move. This one carries a shape as well, because 38 Nest services share\n * one config and have nothing of their own to preserve. See `nest-service.ts` for why the two\n * answers differ, and why that is one rule rather than two.\n *\n * Its own entry point, like the React one, so importing it at build time never drags the CLI\n * and its adapters into a Vite config.\n */\nexport { nestService, type NestServiceOptions } from './nest-service.js'\n\n// The pieces the shape is built from, exported individually so a service that needs something\n// the shape does not give it can compose its own without leaving sentinel.\nexport { decoratorMetadata, type DecoratorMetadataOptions } from './decorator-metadata.js'\nexport { nodeManifest, type NodeManifestOptions } from './node-manifest.js'\nexport { tsconfigAliases, type Alias } from './tsconfig-aliases.js'\n\n// Vite's own, so an adopted config imports everything from one place and the module can drop\n// `vite` from its dependencies.\nexport { defineConfig, loadEnv, mergeConfig } from 'vite'\nexport type { Plugin, PluginOption, UserConfig, UserConfigExport } from 'vite'\n"],"mappings":";;;;;;;;;;AA0BA,SAAS,QAAAA,aAAY;AAErB,SAAS,mBAAoC;;;ACN7C,SAAS,QAAQ,YAAY,WAAW,aAAa,gBAAgB;AACrE,SAAS,SAAS,MAAM,UAAU,WAAW;AAiB7C,SAAS,WAAW,MAAwB;AAC1C,QAAM,QAAkB,CAAC;AACzB,QAAM,OAAO,CAAC,QAAsB;AAClC,eAAW,SAAS,YAAY,GAAG,GAAG;AACpC,YAAM,OAAO,KAAK,KAAK,KAAK;AAC5B,UAAI,SAAS,IAAI,EAAE,YAAY,EAAG,MAAK,IAAI;AAAA,UACtC,OAAM,KAAK,SAAS,MAAM,IAAI,EAAE,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAAA,IAC3D;AAAA,EACF;AACA,OAAK,IAAI;AACT,SAAO;AACT;AAGO,SAAS,cAAc,SAA4D;AACxF,QAAM,SAAyC,CAAC;AAChD,aAAW,SAAS,QAAQ,QAAQ;AAClC,UAAM,QAAQ,KAAK,QAAQ,eAAe,MAAM,KAAK;AACrD,QAAI,CAAC,WAAW,KAAK,GAAG;AACtB,YAAM,IAAI;AAAA,QACR,mEAAmE,MAAM,KAAK;AAAA,MAGhF;AAAA,IACF;AACA,UAAM,UAAU,aAAa,MAAM,IAAI;AACvC,UAAM,UAAU,WAAW,KAAK,EAAE,OAAO,CAAC,SAAS,QAAQ,KAAK,IAAI,CAAC;AACrE,QAAI,QAAQ,WAAW,GAAG;AACxB,YAAM,IAAI;AAAA,QACR,0CAA0C,MAAM,IAAI,cAAc,MAAM,KAAK;AAAA,MAE/E;AAAA,IACF;AACA,eAAW,QAAQ,SAAS;AAC1B,aAAO,KAAK,EAAE,MAAM,KAAK,OAAO,IAAI,GAAG,IAAI,KAAK,QAAQ,QAAQ,MAAM,QAAQ,IAAI,EAAE,CAAC;AAAA,IACvF;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,aAAa,CAAC,aAAwC;AAAA,EACjE,MAAM;AAAA;AAAA,EAEN,cAAc;AACZ,eAAW,EAAE,MAAM,GAAG,KAAK,cAAc,OAAO,GAAG;AACjD,gBAAU,QAAQ,EAAE,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1C,aAAO,MAAM,EAAE;AAAA,IACjB;AAAA,EACF;AACF;;;AC7DA,SAAS,cAAAC,aAAY,qBAAqB;AAC1C,SAAS,iBAAiB;AAC1B,SAAS,QAAAC,aAAY;AA0BrB,IAAM,4BAA4B,CAAC,SAAS;AAc5C,IAAM,mBAAmB,CAAC,WAA6C;AACrE,QAAM,UAAU,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC;AAC3C,QAAM,WAAW,oBAAI,IAAY;AAEjC,aAAW,eAAe,OAAO,OAAO,MAAM,GAAG;AAG/C,QAAI,YAAY,SAAS,QAAS;AAElC,eAAW,YAAY,YAAY,SAAS;AAC1C,UAAI,QAAQ,IAAI,QAAQ,EAAG;AAC3B,UAAI,SAAS,WAAW,GAAG,KAAK,SAAS,WAAW,GAAG,EAAG;AAC1D,UAAI,UAAU,QAAQ,EAAG;AAEzB,YAAM,QAAQ,SAAS,MAAM,GAAG;AAChC,YAAM,OAAO,SAAS,WAAW,GAAG,IAAI,MAAM,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,MAAM,CAAC;AAC7E,UAAI,KAAM,UAAS,IAAI,IAAI;AAAA,IAC7B;AAAA,EACF;AACA,SAAO;AACT;AAcO,IAAM,eAAe,CAAC,YAAyC;AAQpE,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IAEP,eAAe,gBAAgB,QAAQ;AACrC,iBAAW,iBAAiB,MAAM;AAAA,IACpC;AAAA,IAEA,MAAM,cAAc;AAIlB,YAAM,QAAQ,QAAQ,SAAS;AAC/B,UAAI,CAACD,YAAWC,MAAK,QAAQ,QAAQ,KAAK,CAAC,EAAG;AAI9C,YAAM,EAAE,wBAAwB,IAAI,MAAM,OAAO,0BAA0B;AAC3E,YAAM,EAAE,mBAAmB,gBAAgB,gBAAgB,IAAI,MAAM,OAAO,QAAQ;AAEpF,YAAM,QAAQ,MAAM,wBAAwB,EAAE,aAAa,MAAM,CAAC;AAClE,YAAM,WAAW,kBAAkB,QAAQ,SAAS,OAAO;AAAA,QACzD,MAAM,QAAQ;AAAA,QACd,cAAc;AAAA,MAChB,CAAC;AACD,eAAS,OAAO;AAEhB,UAAI,aAAa,QAAW;AAC1B,cAAM,IAAI;AAAA,UACR;AAAA,QAGF;AAAA,MACF;AACA,2BAAqB,UAAU,UAAU,OAAO;AAEhD,oBAAcA,MAAK,QAAQ,QAAQ,cAAc,GAAG,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,CAAI;AAC5F;AAAA,QACEA,MAAK,QAAQ,QAAQ,gBAAgB,MAAM,CAAC;AAAA,QAC5C,eAAe,UAAU,OAAO,MAAM;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACF;AAcA,IAAM,uBAAuB,CAC3B,UACA,UACA,YACS;AACT,QAAM,WAAW,IAAI,IAAI,OAAO,KAAK,SAAS,gBAAgB,CAAC,CAAC,CAAC;AACjE,QAAM,WAAW,QAAQ,mBAAmB;AAC5C,QAAM,UAAU,CAAC,GAAG,QAAQ,EACzB,OAAO,CAAC,SAAS,CAAC,SAAS,IAAI,IAAI,CAAC,EACpC,OAAO,CAAC,SAAS,CAAC,SAAS,KAAK,CAAC,WAAW,SAAS,UAAU,KAAK,WAAW,GAAG,MAAM,GAAG,CAAC,CAAC,EAC7F,KAAK;AAER,MAAI,QAAQ,WAAW,EAAG;AAE1B,QAAM,IAAI;AAAA,IACR,6CAA6C,QAAQ,MAAM,6FACU,QAAQ,KAAK,IAAI,CAAC;AAAA,EAGzF;AACF;;;AF/GA,IAAM,aAAa,CAAC,YAA4C;AAC9D,QAAM,SAASC,MAAK,QAAQ,eAAe,QAAQ,UAAU,QAAQ,QAAQ,OAAO,EAAE;AACtF,QAAM,QAAQA,MAAK,QAAQ,MAAM,QAAQ,SAAS,aAAa;AAC/D,SAAO;AAAA,IACL,SAAS;AAAA,MACP,kBAAkB;AAAA,QAChB,MAAM,QAAQ;AAAA,QACd,UAAU,QAAQ;AAAA,QAClB,cAAc,QAAQ;AAAA,MACxB,CAAC;AAAA,MACD,GAAI,QAAQ,QAAQ,SAChB,CAAC,WAAW,EAAE,eAAe,QAAQ,eAAe,QAAQ,QAAQ,QAAQ,OAAO,CAAC,CAAC,IACrF,CAAC;AAAA,MACL,aAAa;AAAA,QACX,SAAS,QAAQ;AAAA,QACjB,eAAe,QAAQ;AAAA,QACvB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,SAAS,EAAE,OAAO,gBAAgB,QAAQ,aAAa,EAAE;AAAA,IACzD,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOH,YAAY,CAAC,OAAO;AAAA,IACtB;AAAA,IACA,OAAO;AAAA;AAAA;AAAA,MAGL,KAAK;AAAA,MACL;AAAA,MACA,aAAa;AAAA,MACb,WAAW;AAAA,MACX,QAAQ;AAAA;AAAA;AAAA,MAGR,QAAQ;AAAA,MACR,eAAe;AAAA,QACb,QAAQ;AAAA,UACN,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAaR,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA,UAKjB,gBAAgB,CAAC,UAAW,MAAM,mBAAmB,QAAQ,YAAY;AAAA,QAC3E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAQA,WAAW,EAAE,mBAAmB,KAAK;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,cAAc,CAAC,YAC1B,QAAQ,cAAc,SAClB,WAAW,OAAO,IAClB,YAAY,WAAW,OAAO,GAAG,QAAQ,SAAS;;;AGzIxD,SAAS,cAAc,SAAS,eAAAC,oBAAmB;","names":["join","existsSync","join","join","mergeConfig"]}
1
+ {"version":3,"sources":["../../../../src/roles/build/nest/nest-service.ts","../../../../src/roles/build/nest/copy-assets.ts","../../../../src/roles/build/nest/importer-externals.ts","../../../../src/roles/build/nest/node-manifest.ts","../../../../src/roles/build/nest/toolchain.ts"],"sourcesContent":["/**\n * The whole Vite config for a Nest service, so an adopted module holds two lines.\n *\n * ## Why a shape here, and pieces on the React side\n *\n * It looks inconsistent until you measure the two families. The four React apps each carry a\n * heavy, genuinely different config: one has a plugin wedged between two shared ones, another\n * has two different route-tree footers on the branches of one ternary. Two attempts at a\n * shared shape for them were abandoned, and the rule that survived is that sentinel rewrites\n * their IMPORTS and touches nothing else.\n *\n * The Nest side is the opposite by measurement: 38 services, one shared 25-line webpack config\n * between them, and nothing per-module to preserve. Owning the shape there is not a different\n * philosophy, it is the same one applied to a different fact: sentinel provides the FORM where\n * the form is shared, and the PIECES where it is not.\n *\n * The consequences are the point. An adopted module is readable in review. A correction to the\n * form is one publish instead of 38 diffs. And the day Nest becomes ESM-native, `format`\n * changes here rather than in 38 configs.\n *\n * ## It is a composition, not a block\n *\n * `nestService` assembles the same plugins this module exports individually. A service that\n * needs something else can pass `overrides`, or drop to the pieces entirely. And if the React\n * configs ever converge, a `reactApp()` can be built the same way with no new mechanism.\n */\nimport { join } from 'node:path'\n\nimport { mergeConfig, type UserConfig } from 'vite'\n\nimport { copyAssets } from './copy-assets.js'\nimport { decoratorMetadata } from './decorator-metadata.js'\nimport { importerExternals } from './importer-externals.js'\nimport { nodeManifest } from './node-manifest.js'\nimport type { AssetDeclaration, TransformerDeclaration } from './nx-target.js'\nimport { tsconfigAliases } from './tsconfig-aliases.js'\n\nexport interface NestServiceOptions {\n /** The nx project name. Keys the dependency graph, and names the module in messages. */\n project: string\n /** Absolute path to the module's directory, normally `__dirname`. */\n root: string\n /** Absolute path to the workspace root. */\n workspaceRoot: string\n /** Entry point, relative to `root`. */\n entry?: string\n /**\n * The tsconfig whose emit must be preserved, when the service uses neither conventional name.\n *\n * Relative to `root`. Defaults to `tsconfig.app.json`, then `tsconfig.json`.\n */\n tsconfig?: string\n /** Output directory, relative to `workspaceRoot`. */\n outDir?: string\n /**\n * TypeScript transformers this service compiles with, as its build target declared them.\n *\n * Read off the webpack target by `--init` and written here, never inferred: what a service\n * compiles with is the service's own. Eight services and BFFs run `@nestjs/swagger/plugin`,\n * which writes the `@ApiProperty` decorators their DTOs do not declare by hand. Building\n * without it succeeds and silently costs them most of their published contract (measured on\n * `institution`: 385 insertions, 1378 deletions in the committed OpenAPI document).\n */\n transformers?: readonly TransformerDeclaration[]\n /**\n * Files copied into the output beside the bundle, in the shape `@nx/webpack` declared them.\n *\n * One service here uses it: `planning-period` ships the pug, css and ttf templates it renders\n * printed documents from. Without them the service starts and fails on its first print.\n */\n assets?: readonly AssetDeclaration[]\n /**\n * Anything this service needs that the shape does not give it.\n *\n * Merged with Vite's own `mergeConfig`, never with a merge of our own: plugins concatenate\n * and aliases stack the way Vite does it everywhere else, so there is no second set of\n * semantics to learn or to document.\n */\n overrides?: UserConfig\n}\n\n/**\n * Packages the bundle INLINES rather than requires.\n *\n * `importHelpers: true` in the workspace tsconfig makes TypeScript emit `require(\"tslib\")` for\n * `__decorate` and `__metadata`, so every decorated file depends on it. Hoisted into one scope\n * Rollup inlined it and nobody noticed; preserving modules left it external, and the manifest nx\n * generates from the project graph does not list it, because no module DECLARES tslib. The image\n * therefore did not install it and the service died on its first require, only in the image:\n * locally and in CI the workspace root has tslib.\n *\n * One list, read by both `ssr.noExternal` and `importerExternals`, because the second runs\n * `enforce: 'pre'` and would otherwise win against the first and bring the incident back.\n */\nexport const NEST_SERVICE_NO_EXTERNAL: readonly string[] = ['tslib']\n\nconst baseConfig = (options: NestServiceOptions): UserConfig => {\n const outDir = join(options.workspaceRoot, options.outDir ?? `dist/${options.project}`)\n const entry = join(options.root, options.entry ?? 'src/main.ts')\n const aliases = tsconfigAliases(options.workspaceRoot)\n return {\n plugins: [\n importerExternals({ noExternal: NEST_SERVICE_NO_EXTERNAL, aliases }),\n decoratorMetadata({\n root: options.root,\n tsconfig: options.tsconfig,\n transformers: options.transformers,\n }),\n ...(options.assets?.length\n ? [copyAssets({ workspaceRoot: options.workspaceRoot, outDir, assets: options.assets })]\n : []),\n nodeManifest({\n project: options.project,\n workspaceRoot: options.workspaceRoot,\n outDir,\n }),\n ],\n resolve: { alias: aliases },\n ssr: {\n // See NEST_SERVICE_NO_EXTERNAL: `tslib`, inlined on purpose.\n noExternal: [...NEST_SERVICE_NO_EXTERNAL],\n },\n build: {\n // An SSR build targets Node and leaves real packages external, which is what\n // `webpack-node-externals` did: the image installs them from the generated manifest.\n // `importerExternals` holds that wherever the package is declared, not only at the root.\n ssr: entry,\n outDir,\n emptyOutDir: true,\n sourcemap: true,\n target: 'node20',\n // The bundle is read by humans when a stack trace points into it, and minifying a\n // server bundle buys nothing: it is never downloaded.\n minify: false,\n rollupOptions: {\n output: {\n format: 'cjs',\n // One output file per source module, instead of hoisting everything into one scope.\n //\n // This is not a preference, it is the only shape that keeps the OpenAPI contract.\n // Merging every module into one scope forces Rollup to rename duplicate class names,\n // and `@nestjs/swagger` keys its schemas on `class.name`, so a renamed class silently\n // becomes a renamed schema in the published API. Measured on `network`, which has two\n // such collisions: single bundle renames `InvalidPermissionError` and\n // `PermissionNotFoundError`; preserving modules renames nothing and reproduces the\n // committed contract byte for byte.\n //\n // The cost is 4.5 MB across 2003 files against 2.9 MB in one, still well under\n // webpack's 6.9 MB for the same service, so nothing regresses against what it replaces.\n preserveModules: true,\n // The image runs `./dist/main.js`, so the entry keeps that name at the root while\n // every other module keeps its own path. Under `preserveModules` a plain string here\n // would be applied to all of them, which numbers them (`main962.js`) and loses the\n // entry.\n entryFileNames: (chunk) => (chunk.facadeModuleId === entry ? 'main.js' : '[name].js'),\n },\n // Nest registers metadata as an IMPORT SIDE EFFECT: a decorator writes into a catalog\n // when its module loads, and nothing references that module afterwards. Rollup may\n // drop such a module; webpack never did.\n //\n // Measured on the first migrated service this changes nothing, so it is insurance\n // rather than a fix, and it is recorded as such rather than credited with the smaller\n // bundle (that comes from barrel re-exports the service does not use).\n treeshake: { moduleSideEffects: true },\n },\n },\n }\n}\n\nexport const nestService = (options: NestServiceOptions): UserConfig =>\n options.overrides === undefined\n ? baseConfig(options)\n : mergeConfig(baseConfig(options), options.overrides)\n","/**\n * The files a service ships beside its bundle, which webpack copied and a bundler does not.\n *\n * `@nx/webpack` takes an `assets` list and copies each match into the output. Nothing in a Vite\n * build does that for a Node target: `publicDir` is a browser concept and is disabled for SSR\n * builds, so an unmigrated `assets` entry is not a smaller output, it is a service that starts\n * and then fails the first time it reaches for a file that is not there.\n *\n * One service in this repo declares it. `planning-period` ships the pug templates, stylesheet and\n * font it renders printed documents from, under `templates/`. Dropping them leaves a build that\n * succeeds, an image that boots, and a printing feature that throws.\n *\n * ## Copied at `closeBundle`, and only what the target declared\n *\n * The declaration is carried through from the webpack target unchanged, globs and all, so this\n * plugin has nothing to decide: it resolves each `input` against the workspace root, matches the\n * glob, and writes under `output` inside the bundle's directory. `closeBundle` because that is\n * when the output directory exists and Vite has finished emptying it.\n *\n * An entry matching nothing FAILS the build. A glob that has quietly stopped matching is the same\n * silent loss as no glob at all, and the point of this file is that this class of loss is loud.\n */\nimport { cpSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs'\nimport { dirname, join, relative, sep } from 'node:path'\n\nimport type { Plugin } from 'vite'\n\nimport { globToRegExp } from '../../../shared/glob.js'\n\nimport type { AssetDeclaration } from './nx-target.js'\n\nexport interface CopyAssetsOptions {\n /** Absolute path to the workspace root, which `input` is relative to. */\n workspaceRoot: string\n /** Absolute path to the build's output directory. */\n outDir: string\n assets: readonly AssetDeclaration[]\n}\n\n/** Every file under `root`, as paths relative to it and with forward slashes. */\nfunction filesUnder(root: string): string[] {\n const found: string[] = []\n const walk = (dir: string): void => {\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry)\n if (statSync(full).isDirectory()) walk(full)\n else found.push(relative(root, full).split(sep).join('/'))\n }\n }\n walk(root)\n return found\n}\n\n/** What each declaration copies, or the reason it cannot. */\nexport function resolveAssets(options: CopyAssetsOptions): { from: string; to: string }[] {\n const copies: { from: string; to: string }[] = []\n for (const asset of options.assets) {\n const input = join(options.workspaceRoot, asset.input)\n if (!existsSync(input)) {\n throw new Error(\n `sentinel build(nest): the build target declares an asset from \\`${asset.input}\\`, which ` +\n `does not exist. webpack copied it into the output, so the built service would be ` +\n `missing files it reads at runtime.`,\n )\n }\n const matcher = globToRegExp(asset.glob)\n const matched = filesUnder(input).filter((file) => matcher.test(file))\n if (matched.length === 0) {\n throw new Error(\n `sentinel build(nest): the asset glob \\`${asset.glob}\\` under \\`${asset.input}\\` matches ` +\n `no file. An entry that matches nothing is the same silent loss as no entry at all.`,\n )\n }\n for (const file of matched) {\n copies.push({ from: join(input, file), to: join(options.outDir, asset.output, file) })\n }\n }\n return copies\n}\n\nexport const copyAssets = (options: CopyAssetsOptions): Plugin => ({\n name: 'sentinel:copy-assets',\n // After the bundle is written and the output directory has stopped being emptied.\n closeBundle() {\n for (const { from, to } of resolveAssets(options)) {\n mkdirSync(dirname(to), { recursive: true })\n cpSync(from, to)\n }\n },\n})\n","/**\n * Keep a real package external when Vite would inline it only because the service does not\n * declare it.\n *\n * Vite's SSR build decides externalisation from the service's OWN directory. In hublo/monorepo that\n * held because the root manifest declared nearly every package, so the lookup walked up to\n * `<repo>/node_modules`. Once a package moves off the root to the projects that import it, one\n * declared only by a workspace lib (`@nestjs/jwt`, declared by `libs/nest/auth`, not by `counter`)\n * no longer passes that check, and Vite INLINES it, `node_modules/.pnpm` path included. The build\n * stays green and `assertManifestCovers` cannot see it, because an inlined package is no longer an\n * import. Measured on the 9 Vite Nest services with the root drop applied: `@nestjs/jwt` inlined,\n * `@nestjs/core/*` subpaths inlined, and `require` order shuffled in files that did not change.\n *\n * Vite still RESOLVES such a package correctly, from the importer: only the externalise decision\n * is wrong. So the resolution is delegated to Vite (`this.resolve`), and this plugin only turns an\n * inlined package from `node_modules` into the external Vite would have emitted. A first version\n * resolved with `require.resolve` and built the id itself: it changed 38 to 315 files per service on\n * `main`, because Vite keeps an `exports` subpath as written (`rxjs/operators`, not\n * `rxjs/dist/cjs/operators/index.js`) and carries the package's `sideEffects`, without which\n * Rollup kept barrel modules it used to drop.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { isBuiltin } from 'node:module'\nimport { dirname, extname, join, sep } from 'node:path'\n\nimport type { Plugin } from 'vite'\n\nimport type { Alias } from './tsconfig-aliases.js'\n\nexport interface ImporterExternalsOptions {\n /**\n * Packages that must stay INLINED, the same list `ssr.noExternal` receives.\n *\n * Checked before anything else. This plugin runs `enforce: 'pre'`, and a `resolveId` there\n * wins against `ssr.noExternal`: measured, `tslib` then comes out as `require(\"tslib\")`, which\n * is the exact incident `noExternal` exists for. The image does not install `tslib`, because\n * no module declares it, and the service died on its first require.\n */\n noExternal: readonly string[]\n /** The workspace's tsconfig `paths`: those are source, and Vite bundles them. */\n aliases: readonly Alias[]\n}\n\n/** `@scope/name/sub/path` → `@scope/name`, `name/sub` → `name`. */\nexport const packageName = (specifier: string): string => {\n const parts = specifier.split('/')\n return specifier.startsWith('@') ? parts.slice(0, 2).join('/') : (parts[0] ?? specifier)\n}\n\nconst isBareSpecifier = (specifier: string): boolean =>\n !specifier.startsWith('.') &&\n !specifier.startsWith('/') &&\n !specifier.startsWith('\\0') &&\n !isBuiltin(specifier)\n\n/** The package's own manifest, walked up from a file inside it. */\nconst packageManifest = (file: string, name: string): { exports?: unknown } | undefined => {\n let directory = dirname(file)\n while (directory !== dirname(directory)) {\n const manifest = join(directory, 'package.json')\n if (existsSync(manifest)) {\n try {\n const data = JSON.parse(readFileSync(manifest, 'utf8')) as {\n name?: string\n exports?: unknown\n }\n if (data.name === name) return data\n } catch {\n // A manifest that does not parse is not the package's own; keep walking.\n }\n }\n directory = dirname(directory)\n }\n return undefined\n}\n\n/**\n * The id Vite's `tryNodeResolve` gives an external, reproduced so the output does not move: the\n * specifier as written, except a deep import into a package WITHOUT an `exports` map whose\n * extension the resolution changed, which becomes the resolved file\n * (`@nestjs/core/injector/container` → `@nestjs/core/injector/container.js`).\n */\nconst externalId = (specifier: string, name: string, resolved: string): string => {\n if (specifier === name) return specifier\n if (extname(specifier) === extname(resolved)) return specifier\n if (packageManifest(resolved, name)?.exports !== undefined) return specifier\n const index = resolved.split(sep).join('/').indexOf(specifier)\n return index === -1 ? specifier : resolved.split(sep).join('/').slice(index)\n}\n\nexport const importerExternals = (options: ImporterExternalsOptions): Plugin => ({\n name: 'sentinel:importer-externals',\n enforce: 'pre',\n async resolveId(source, importer, resolveOptions) {\n if (!importer || !isBareSpecifier(source)) return null\n const name = packageName(source)\n // FIRST: a package `ssr.noExternal` inlines is never made external here (see the option).\n if (options.noExternal.includes(name)) return null\n if (options.aliases.some((alias) => alias.find.test(source))) return null\n const resolved = await this.resolve(source, importer, { ...resolveOptions, skipSelf: true })\n // Unresolved, or already external: Vite's own decision stands, byte for byte.\n if (!resolved || resolved.external) return null\n const file = resolved.id.split('?')[0] ?? resolved.id\n // A workspace package linked into `node_modules` resolves to its real path, outside it:\n // that is source, and it stays bundled as it is today.\n if (!file.split(sep).includes('node_modules')) return null\n return {\n id: externalId(source, name, file),\n external: true,\n moduleSideEffects: resolved.moduleSideEffects,\n }\n },\n})\n","/**\n * The pruned `package.json` and lockfile a Node image installs from.\n *\n * This is the half that made the nx executor look load-bearing. `--generatePackageJson` was an\n * option of `@nx/webpack:webpack`, and the backend image is built on its two outputs:\n *\n * COPY --from=build /app/dist/apps/nest/<service>/package.json /app/package.json\n * COPY --from=build /app/dist/apps/nest/<service>/pnpm-lock.yaml /app/pnpm-lock.yaml\n * RUN pnpm i --prod=true --frozen-lockfile\n *\n * It is not bound to the executor. The executor supplies one thing, the project graph, and\n * every function that does the work is exported by `@nx/js`. So the same two files can be\n * produced from a Vite plugin, and `--frozen-lockfile` accepting them afterwards is the proof\n * that they agree with each other: pnpm refuses the install otherwise.\n *\n * ## Why the graph, and not something simpler\n *\n * A Nest service here declares almost nothing of its own: measured, one dependency in its\n * manifest against 1463 installed in its image. Everything it uses is declared at the\n * workspace root and reaches it through source imports. So the dependency list can only be\n * computed from what the code imports, which is what the graph knows.\n *\n * Deriving it from the bundle's own externals is not equivalent either: measured on the first\n * service, the bundle statically requires 42 packages while the correct manifest holds 65. The\n * 23 missing ones are transitive or required dynamically at runtime (`fastify` behind\n * `@nestjs/platform-fastify`, `jsonwebtoken`, the LaunchDarkly SDK). Shipping the short list\n * fails in production, not in the build.\n */\nimport { existsSync, writeFileSync } from 'node:fs'\nimport { isBuiltin } from 'node:module'\nimport { join } from 'node:path'\n\nimport type { Plugin, Rollup } from 'vite'\n\nexport interface NodeManifestOptions {\n /** The nx project name, which is how the graph is keyed. */\n project: string\n /** Absolute path to the workspace root. */\n workspaceRoot: string\n /** Where the bundle is written; the two files land beside it. */\n outDir: string\n /** Entry file name, written as `main` so `node .` resolves inside the image. */\n entry?: string\n /**\n * Packages the IMAGE provides, which are therefore allowed to be required without being\n * declared in the manifest.\n *\n * The generated Prisma clients are the case this exists for: the Dockerfile copies\n * `node_modules/@prisma` from its own stage, so they resolve at runtime while no module\n * declares them. Everything else that is required and undeclared is a bug, and the check\n * below refuses the build.\n */\n providedByImage?: readonly string[]\n}\n\n/** What the Dockerfile copies in itself, so requiring it without declaring it is legitimate. */\nconst DEFAULT_PROVIDED_BY_IMAGE = ['@prisma'] as const\n\n/**\n * Every real package the emitted output requires, from Rollup's own record rather than a scan\n * of the text.\n *\n * `chunk.imports` mixes three things, and only the third is a dependency:\n *\n * - other EMITTED chunks. Under `preserveModules` these are relative output paths that do not\n * begin with `.`, so `apps/…`, `libs/…` and `_virtual/…` read exactly like package names.\n * They are recognised by being keys of the bundle itself.\n * - Node BUILTINS, which appear both bare (`crypto`) and prefixed (`node:crypto`).\n * - genuine external packages, which is what the manifest has to cover.\n */\nconst externalPackages = (bundle: Rollup.OutputBundle): Set<string> => {\n const emitted = new Set(Object.keys(bundle))\n const packages = new Set<string>()\n\n for (const emittedFile of Object.values(bundle)) {\n // Assets carry no imports, and the bundle holds both. Narrowing on the discriminant is what\n // replaced a cast through `unknown`, which asserted the same thing without checking it.\n if (emittedFile.type !== 'chunk') continue\n\n for (const imported of emittedFile.imports) {\n if (emitted.has(imported)) continue\n if (imported.startsWith('.') || imported.startsWith('/')) continue\n if (isBuiltin(imported)) continue\n\n const parts = imported.split('/')\n const name = imported.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]\n if (name) packages.add(name)\n }\n }\n return packages\n}\n\n/**\n * Writes the manifest and lockfile once the bundle is on disk.\n *\n * `closeBundle` rather than `writeBundle`, so the files land after Vite has finished with the\n * directory and cannot be cleared by `emptyOutDir`.\n *\n * `closeBundle` also runs after a FAILED build, where there is no directory to write into. Left\n * alone this hook then threw `ENOENT` on the manifest, and since it is the last error raised it\n * became the one Vite printed, hiding the failure that actually stopped the build. It cost two\n * diagnoses before being recognised, so the missing directory is now read as what it is: the\n * bundle was never written, and this plugin has nothing to say about why.\n */\nexport const nodeManifest = (options: NodeManifestOptions): Plugin => {\n /*\n * Captured while the bundle still exists in memory, and read back once the manifest is built.\n *\n * `undefined` until `generateBundle` runs, deliberately: an empty SET would make the coverage\n * check below pass by having seen nothing, which is the worst way for a guard to succeed. The\n * two states have to be told apart.\n */\n let required: Set<string> | undefined\n\n return {\n name: 'sentinel:node-manifest',\n apply: 'build',\n\n generateBundle(_outputOptions, bundle) {\n required = externalPackages(bundle)\n },\n\n async closeBundle() {\n // `closeBundle` runs after a FAILED build too. The ENTRY is what says a bundle was really\n // written: `emptyOutDir` leaves the directory in place, so its existence alone would let a\n // failed build still produce a manifest describing nothing.\n const entry = options.entry ?? 'main.js'\n if (!existsSync(join(options.outDir, entry))) return\n\n // Imported here rather than at module scope: a front config importing this file must not\n // pay for loading the nx graph machinery it will never call.\n const { createProjectGraphAsync } = await import('nx/src/devkit-exports.js')\n const { createPackageJson, createLockFile, getLockFileName } = await import('@nx/js')\n\n const graph = await createProjectGraphAsync({ exitOnError: false })\n const manifest = createPackageJson(options.project, graph, {\n root: options.workspaceRoot,\n isProduction: true,\n })\n manifest.main = entry\n\n if (required === undefined) {\n throw new Error(\n `sentinel build(nest): the bundle was written but never inspected, so the manifest ` +\n `could not be checked against what it requires. Refusing to write one that might ` +\n `not install.`,\n )\n }\n assertManifestCovers(required, manifest, options)\n\n writeFileSync(join(options.outDir, 'package.json'), `${JSON.stringify(manifest, null, 2)}\\n`)\n writeFileSync(\n join(options.outDir, getLockFileName('pnpm')),\n createLockFile(manifest, graph, 'pnpm'),\n )\n },\n }\n}\n\n/**\n * Refuse a bundle that requires something the image will not install.\n *\n * The manifest is derived from the project GRAPH, so it lists what modules declare. The bundle\n * requires what the bundler left external. Those two sets agreeing is an assumption, and it was\n * wrong: `tslib` arrives through `importHelpers` in the workspace tsconfig without any module\n * declaring it, so the manifest omitted it and the service died on its first require.\n *\n * It died only in the image. Locally and in CI the workspace root has the package, so the gap is\n * invisible until a container starts, which is the most expensive place to discover it. Checking\n * here turns that into a build failure naming the package.\n */\nconst assertManifestCovers = (\n required: ReadonlySet<string>,\n manifest: { dependencies?: Record<string, string> },\n options: NodeManifestOptions,\n): void => {\n const declared = new Set(Object.keys(manifest.dependencies ?? {}))\n const provided = options.providedByImage ?? DEFAULT_PROVIDED_BY_IMAGE\n const missing = [...required]\n .filter((name) => !declared.has(name))\n .filter((name) => !provided.some((prefix) => name === prefix || name.startsWith(`${prefix}/`)))\n .sort()\n\n if (missing.length === 0) return\n\n throw new Error(\n `sentinel build(nest): the bundle requires ${missing.length} package(s) the generated ` +\n `manifest does not declare, so the image would not install them: ${missing.join(', ')}. ` +\n `Either declare them in the module's package.json, inline them with ` +\n `\\`ssr.noExternal\\`, or list them in \\`providedByImage\\` if the Dockerfile copies them in.`,\n )\n}\n","/**\n * What `@hublo/sentinel/build/nest` gives an adopted service.\n *\n * The sibling `build/react` entry is a re-export surface: the module keeps its own config and\n * only its import lines move. This one carries a shape as well, because 38 Nest services share\n * one config and have nothing of their own to preserve. See `nest-service.ts` for why the two\n * answers differ, and why that is one rule rather than two.\n *\n * Its own entry point, like the React one, so importing it at build time never drags the CLI\n * and its adapters into a Vite config.\n */\nexport { nestService, type NestServiceOptions } from './nest-service.js'\n\n// The pieces the shape is built from, exported individually so a service that needs something\n// the shape does not give it can compose its own without leaving sentinel.\nexport { decoratorMetadata, type DecoratorMetadataOptions } from './decorator-metadata.js'\nexport { nodeManifest, type NodeManifestOptions } from './node-manifest.js'\nexport { tsconfigAliases, type Alias } from './tsconfig-aliases.js'\n\n// Vite's own, so an adopted config imports everything from one place and the module can drop\n// `vite` from its dependencies.\nexport { defineConfig, loadEnv, mergeConfig } from 'vite'\nexport type { Plugin, PluginOption, UserConfig, UserConfigExport } from 'vite'\n"],"mappings":"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","names":["join","existsSync","dirname","join","sep","existsSync","isBuiltin","join","join","mergeConfig"]}
@@ -7,7 +7,7 @@ import {
7
7
  readTestAdoption,
8
8
  resolveVitest,
9
9
  suiteOf
10
- } from "./chunk-TDNPPNGK.js";
10
+ } from "./chunk-NFGIHMXB.js";
11
11
  import "./chunk-O7REVMOC.js";
12
12
  import "./chunk-QXFCZON7.js";
13
13
  import "./chunk-KMKQDGI6.js";