@hublo/sentinel 1.3.0 → 1.4.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/bin/sentinel.d.ts +1 -1
- package/dist/bin/sentinel.js +22 -9
- package/dist/chunk-2XLX6PFR.js +132 -0
- package/dist/chunk-2XLX6PFR.js.map +1 -0
- package/dist/chunk-3TDUIKVQ.js +178 -0
- package/dist/chunk-3TDUIKVQ.js.map +1 -0
- package/dist/chunk-CPCUPK4J.js +70 -0
- package/dist/chunk-CPCUPK4J.js.map +1 -0
- package/dist/chunk-CRKUEP4J.js +427 -0
- package/dist/chunk-CRKUEP4J.js.map +1 -0
- package/dist/{chunk-676GBPMS.js → chunk-L7WS36XV.js} +3921 -556
- package/dist/chunk-PWV3BMDA.js +15 -0
- package/dist/chunk-PWV3BMDA.js.map +1 -0
- package/dist/chunk-WLFE5RUU.js +264 -0
- package/dist/chunk-WLFE5RUU.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +2 -2
- package/dist/roles/build/nest/toolchain.d.ts +4 -36
- package/dist/roles/build/nest/toolchain.js +10 -178
- package/dist/roles/build/nest/toolchain.js.map +1 -0
- package/dist/roles/build/toolchain.js.map +1 -0
- package/dist/roles/test/nest/toolchain.d.ts +9 -0
- package/dist/roles/test/nest/toolchain.js +25 -0
- package/dist/roles/test/nest/toolchain.js.map +1 -0
- package/dist/roles/test/react/toolchain.d.ts +66 -0
- package/dist/roles/test/react/toolchain.js +14 -1
- package/dist/roles/test/react/toolchain.js.map +1 -0
- package/dist/roles/test/setup/jest-parity.d.ts +2 -0
- package/dist/roles/test/setup/jest-parity.js +159 -0
- package/dist/roles/test/setup/jest-parity.js.map +1 -0
- package/dist/roles/test/setup/mock-extended.d.ts +46 -0
- package/dist/roles/test/setup/mock-extended.js +65 -0
- package/dist/roles/test/setup/mock-extended.js.map +1 -0
- package/dist/roles/test/setup/msw-lifecycle.d.ts +16 -0
- package/dist/roles/test/setup/msw-lifecycle.js +12 -0
- package/dist/roles/test/setup/msw-lifecycle.js.map +1 -0
- package/dist/roles/test/setup/msw-server.d.ts +3 -0
- package/dist/roles/test/setup/msw-server.js +10 -0
- package/dist/roles/test/setup/msw-server.js.map +1 -0
- package/dist/roles/test/setup/workspace-entry.d.ts +2 -0
- package/dist/roles/test/setup/workspace-entry.js +8 -0
- package/dist/roles/test/setup/workspace-entry.js.map +1 -0
- package/dist/roles/test/shared-test-config.d.ts +32 -0
- package/dist/roles/test/shared-test-config.js +8 -0
- package/dist/roles/test/shared-test-config.js.map +1 -0
- package/dist/roles/test/tools/msw.d.ts +1 -0
- package/dist/roles/test/tools/msw.js +3 -0
- package/dist/roles/test/tools/msw.js.map +1 -0
- package/dist/tsconfig-aliases-Ce6axdJ4.d.ts +36 -0
- package/docs/.gitkeep +0 -0
- package/docs/build-adoption.md +521 -0
- package/docs/format-adoption.md +321 -0
- package/docs/lint-adoption.md +290 -0
- package/docs/performance.md +49 -0
- package/docs/test-adoption.md +219 -0
- package/docs/typescript-adoption.md +184 -0
- package/docs/typescript-traces.md +798 -0
- package/docs/using-sentinel.md +195 -0
- package/docs/validating-a-change.md +101 -0
- package/package.json +35 -6
|
@@ -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
|
|
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
|
|
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 (!
|
|
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 =
|
|
320
|
-
const entry =
|
|
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({
|
|
@@ -0,0 +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 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/**\n * `@nx/webpack`'s glob dialect, reduced to what it means for a file name.\n *\n * Only the forms the declarations here use are supported, and anything else is rejected at build\n * time rather than silently matching nothing: `**` for any depth, `*` within a segment, and a\n * `{a,b}` alternation. Written out rather than pulled from a glob library because the whole\n * dependency would exist to read one line of configuration.\n */\nfunction globToRegExp(glob: string): RegExp {\n let pattern = ''\n for (let index = 0; index < glob.length; index++) {\n const char = glob[index] ?? ''\n if (char === '*') {\n if (glob[index + 1] === '*') {\n // `**/` matches any number of directories, including none.\n pattern += glob[index + 2] === '/' ? '(?:.*/)?' : '.*'\n index += glob[index + 2] === '/' ? 2 : 1\n } else {\n pattern += '[^/]*'\n }\n continue\n }\n if (char === '{') {\n const close = glob.indexOf('}', index)\n if (close === -1)\n throw new Error(`sentinel build(nest): unbalanced \\`{\\` in asset glob ${glob}`)\n const alternatives = glob.slice(index + 1, close).split(',')\n pattern += `(?:${alternatives.map((one) => one.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')).join('|')})`\n index = close\n continue\n }\n pattern += char.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n }\n return new RegExp(`^${pattern}$`)\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":"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","names":["join","existsSync","join","join","mergeConfig"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/roles/build/toolchain.ts"],"sourcesContent":["/**\n * The build toolchain, re-exported so an app imports it from sentinel instead of from the\n * workspace root.\n *\n * This is what makes adoption a REFACTOR rather than a rewrite, and it is the whole point.\n * Today `vite`, `@vitejs/plugin-react`, `@tailwindcss/vite`, `vite-plugin-svgr` and `nitro`\n * sit in the monorepo's root `package.json`, which is what stops any front app from being\n * built or moved on its own. An app adopts by changing WHERE its tools come from, and nothing\n * else: every attribute of its config, every alias, every path stays exactly as it was.\n *\n * The alternative was to read the app's config and rebuild it around `reactApp`. That was\n * designed and rejected, correctly: it means understanding a file that is code rather than\n * configuration, and its failure mode is a build that SUCCEEDS and ships the wrong bundle.\n * Here there is nothing to understand, so there is nothing to lose. The two configs are\n * equivalent by construction, because only the import specifiers changed.\n *\n * `reactApp` remains, and stays optional: an app that wants the shared composition adopts it\n * as a second, separate step, with `--inspect --build` to check the result. Nobody has to do\n * that to unblock the root.\n *\n * TanStack is deliberately NOT here. 48 app source files import `@tanstack/react-start`\n * directly, so it is a framework the app codes against rather than a tool that builds it, and\n * the router plugin generates the app's own route tree. Same for `@rolldown/plugin-babel` and\n * `@locator/babel-jsx`, which the apps declare themselves: verified, neither is at the root,\n * so neither is sentinel's to take.\n */\nexport { default as tailwindcss } from '@tailwindcss/vite'\nexport { default as react } from '@vitejs/plugin-react'\nexport { nitro } from 'nitro/vite'\nexport { default as svgr } from 'vite-plugin-svgr'\n\n/**\n * Vite's own API, re-exported for the same reason.\n *\n * `defineConfig`, `loadEnv` and `mergeConfig` are what the configs in this repo import from\n * `vite`; once the root drops the package, those imports are the ones that stop resolving.\n * `mergeConfig` is here because of a file the build role nearly missed: `vitest.config.ts`.\n * Three of them import from `vite` (career and host-admin take `loadEnv`, `libs/front/ui`\n * takes `mergeConfig`), and they are a SECOND door into the same toolchain. Note that\n * `vitest/config` exports a `mergeConfig` of its own, which is vitest's and stays put: the\n * rewriter matches on the specifier, never on the name. The types travel\n * with them, because a config that imports `PluginOption` from a package it no longer declares\n * fails to typecheck even though it builds.\n */\nexport { defineConfig, loadEnv, mergeConfig } from 'vite'\nexport type {\n Plugin,\n PluginOption,\n SassPreprocessorOptions,\n UserConfig,\n UserConfigExport,\n} from 'vite'\n"],"mappings":";AA0BA,SAAoB,WAAXA,gBAA8B;AACvC,SAAoB,WAAXA,gBAAwB;AACjC,SAAS,aAAa;AACtB,SAAoB,WAAXA,gBAAuB;AAehC,SAAS,cAAc,SAAS,mBAAmB;","names":["default"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ViteUserConfig } from 'vitest/config';
|
|
2
|
+
export { ConfigEnv, TestUserConfig, ViteUserConfig, defineConfig, mergeConfig } from 'vitest/config';
|
|
3
|
+
export { loadEnv } from 'vite';
|
|
4
|
+
import { SharedTestOptions } from '../shared-test-config.js';
|
|
5
|
+
export { A as Alias, D as DecoratorMetadataOptions, d as decoratorMetadata, t as tsconfigAliases } from '../../../tsconfig-aliases-Ce6axdJ4.js';
|
|
6
|
+
|
|
7
|
+
declare function nestTestConfig(options: SharedTestOptions): ViteUserConfig;
|
|
8
|
+
|
|
9
|
+
export { SharedTestOptions as NestTestOptions, nestTestConfig };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sharedTestConfig
|
|
3
|
+
} from "../../../chunk-CRKUEP4J.js";
|
|
4
|
+
import {
|
|
5
|
+
decoratorMetadata,
|
|
6
|
+
tsconfigAliases
|
|
7
|
+
} from "../../../chunk-3TDUIKVQ.js";
|
|
8
|
+
|
|
9
|
+
// src/roles/test/nest/toolchain.ts
|
|
10
|
+
import { defineConfig, mergeConfig } from "vitest/config";
|
|
11
|
+
import { loadEnv } from "vite";
|
|
12
|
+
|
|
13
|
+
// src/roles/test/nest/nest-test-config.ts
|
|
14
|
+
function nestTestConfig(options) {
|
|
15
|
+
return sharedTestConfig({ ...options, flavour: "nest" });
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
decoratorMetadata,
|
|
19
|
+
defineConfig,
|
|
20
|
+
loadEnv,
|
|
21
|
+
mergeConfig,
|
|
22
|
+
nestTestConfig,
|
|
23
|
+
tsconfigAliases
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=toolchain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/roles/test/nest/toolchain.ts","../../../../src/roles/test/nest/nest-test-config.ts"],"sourcesContent":["/**\n * What `@hublo/sentinel/test/nest` gives an adopted module.\n *\n * Its own entry point, like the build ones, so importing it from a config never drags the CLI and\n * its adapters into a test run.\n *\n * It differs from `test/react` in one way that matters, and the difference is not decoration.\n * React modules already had a Vitest config carrying decisions their owners made and justified, so\n * that preset is a pure re-export surface and moves nothing. Nest modules have NO Vitest config:\n * one has to be written, and four of its lines are load-bearing in ways nobody would guess from a\n * migration guide. Those four live in `nestTestConfig` rather than in 133 copies.\n */\n\n/**\n * From `vitest/config`, and NOT from `vite`, which also exports a `defineConfig`.\n *\n * They are different functions: `vitest/config`'s knows the `test` key, vite's does not. A config\n * built with vite's would load and quietly drop its entire test block, which is the kind of\n * failure that reads as \"vitest found no tests\" and sends someone hunting through globs.\n *\n * The BUILD role's nest entry point re-exports vite's, correctly for its purpose. Importing that\n * one here would be the exact mistake this comment exists to prevent.\n */\nexport { defineConfig, mergeConfig } from 'vitest/config'\n\n/**\n * From `vite`, because `vitest/config` does not export it. Two of the six already-Vitest configs\n * build their test env with it, and Nest services read their env the same way.\n *\n * Re-exported rather than claimed: the `vite` package belongs to the build role, and a module\n * adopting only this one keeps its own.\n */\nexport { loadEnv } from 'vite'\n\n/** The whole config, as one call. See `nest-test-config.ts` for why each line is there. */\nexport { nestTestConfig, type NestTestOptions } from './nest-test-config.js'\n\n/**\n * Re-exported so a module that needs to compose rather than override can reach them without\n * importing the BUILD role's entry point from a test config, which would pull vite's\n * `defineConfig` into scope beside vitest's and invite the mistake above.\n */\nexport {\n decoratorMetadata,\n type DecoratorMetadataOptions,\n} from '../../build/nest/decorator-metadata.js'\nexport { tsconfigAliases, type Alias } from '../../build/nest/tsconfig-aliases.js'\n\nexport type { ConfigEnv, TestUserConfig, ViteUserConfig } from 'vitest/config'\n","import type { ViteUserConfig } from 'vitest/config'\n\n/**\n * The Vitest config a NEST module gets: the shared one, plus the three things that are not shared.\n *\n * Everything load-bearing lives in `shared-test-config.ts`, because almost everything IS shared.\n * Measured across this repo before splitting, so the line falls where the code does and not where\n * the names suggest:\n *\n * jest-mock-extended front 21 nest 2542 shared\n * msw front 848 nest 517 shared\n * axios front 692 nest 863 shared\n * @prisma/ front 0 nest 1186 NEST ONLY\n *\n * So the nest side is three things: the generated Prisma client's runtime alias, `isolate` (a Nest\n * module registers metadata as an import side effect, so a shared module registry lets one suite\n * see what another registered), and the decorator plugin for the one module that needs it.\n */\nimport { sharedTestConfig, type SharedTestOptions } from '../shared-test-config.js'\n\nexport type { SharedTestOptions as NestTestOptions }\n\nexport function nestTestConfig(options: SharedTestOptions): ViteUserConfig {\n return sharedTestConfig({ ...options, flavour: 'nest' })\n}\n"],"mappings":";;;;;;;;;AAuBA,SAAS,cAAc,mBAAmB;AAS1C,SAAS,eAAe;;;ACVjB,SAAS,eAAe,SAA4C;AACzE,SAAO,iBAAiB,EAAE,GAAG,SAAS,SAAS,OAAO,CAAC;AACzD;","names":[]}
|
|
@@ -1,2 +1,68 @@
|
|
|
1
|
+
import { ViteUserConfig } from 'vitest/config';
|
|
1
2
|
export { ConfigEnv, TestUserConfig, ViteUserConfig, defineConfig, mergeConfig } from 'vitest/config';
|
|
3
|
+
import { Plugin } from 'vite';
|
|
2
4
|
export { loadEnv } from 'vite';
|
|
5
|
+
import { SharedTestOptions } from '../shared-test-config.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolve packages the way jest did, for a suite that was written against jest's resolution.
|
|
9
|
+
*
|
|
10
|
+
* ## What jest resolved, measured rather than remembered
|
|
11
|
+
*
|
|
12
|
+
* Every suite in this repo ran through `@nx/jest/preset`, whose
|
|
13
|
+
* `testEnvironmentOptions.customExportConditions` is exactly `['node', 'require', 'default']`
|
|
14
|
+
* (read off the installed preset, not off its documentation). `development` was never in that
|
|
15
|
+
* list, so a package shipping a separate development build behind that condition loaded its
|
|
16
|
+
* PRODUCTION file under jest.
|
|
17
|
+
*
|
|
18
|
+
* Vitest resolves with Vite's conditions instead, and its default carries the
|
|
19
|
+
* `development|production` token. Measured on a bare config, the resolved list is
|
|
20
|
+
* `['node', 'development|production']`. `@emotion/cache` then loads
|
|
21
|
+
* `emotion-cache.development.cjs.js`, whose extra stylis plugin calls
|
|
22
|
+
* `console.error(':first-child is potentially unsafe...')`.
|
|
23
|
+
*
|
|
24
|
+
* With `jest-fail-on-console` in the setup, that console call IS a failure: **100 tests over 26
|
|
25
|
+
* files on `apps/front/front-legacy`**, every one of them green under jest. `libs/front/components`
|
|
26
|
+
* hits the same root cause on one test.
|
|
27
|
+
*
|
|
28
|
+
* ## Why a plugin and not a config line
|
|
29
|
+
*
|
|
30
|
+
* Declaring `ssr.resolve.conditions: ['node']` does nothing: Vite merges config arrays by
|
|
31
|
+
* CONCATENATION, so Vitest's default is appended straight back. The token has to come off the
|
|
32
|
+
* RESOLVED config, which is what `configResolved` is for.
|
|
33
|
+
*
|
|
34
|
+
* This is the same shape as the other thing that cannot be declared: a module cannot reproduce
|
|
35
|
+
* `customExportConditions` either, because the list it asks for is PREFIXED to Vite's defaults
|
|
36
|
+
* rather than substituted for them.
|
|
37
|
+
*
|
|
38
|
+
* ## The three lists are one array
|
|
39
|
+
*
|
|
40
|
+
* Measured on Vite 8: `config.resolve.conditions`, `config.ssr.resolve.conditions` and
|
|
41
|
+
* `config.environments.ssr.resolve.conditions` are the SAME array object, so stripping one strips
|
|
42
|
+
* all three. All three are stripped anyway. Relying on an aliasing that nothing promises is how a
|
|
43
|
+
* silent regression arrives on a Vite upgrade, and the cost of being explicit is two lines.
|
|
44
|
+
*
|
|
45
|
+
* ## ⚠️ What this deliberately hides, said out loud
|
|
46
|
+
*
|
|
47
|
+
* emotion's warning is REAL: `libs/front/components` is consumed by a Next app, and a
|
|
48
|
+
* `:first-child` selector is genuinely unsafe when the markup is rendered server-side. Restoring
|
|
49
|
+
* jest's resolution puts that warning back out of sight.
|
|
50
|
+
*
|
|
51
|
+
* It is hidden here on purpose all the same, because a migration that also turns 100 green tests
|
|
52
|
+
* red cannot be told apart from a migration that broke something. The baseline gate compares test
|
|
53
|
+
* names, and it has no way to know which reds are progress. The selector belongs to the module's
|
|
54
|
+
* owners as its own piece of work, with its own ticket, not as a side effect of changing runner.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Add to `plugins` in a module whose suite was written against jest's resolution.
|
|
59
|
+
*
|
|
60
|
+
* Not applied by the preset for everyone: it is a MIGRATION aid, and a module that was always on
|
|
61
|
+
* Vitest never had jest's resolution to go back to. A module adopts it the day it migrates, and
|
|
62
|
+
* can drop it the day its suite no longer depends on the production build.
|
|
63
|
+
*/
|
|
64
|
+
declare function jestExportConditions(): Plugin;
|
|
65
|
+
|
|
66
|
+
declare function reactTestConfig(options: SharedTestOptions): ViteUserConfig;
|
|
67
|
+
|
|
68
|
+
export { SharedTestOptions as ReactTestOptions, jestExportConditions, reactTestConfig };
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
jestExportConditions,
|
|
3
|
+
sharedTestConfig
|
|
4
|
+
} from "../../../chunk-CRKUEP4J.js";
|
|
5
|
+
import "../../../chunk-3TDUIKVQ.js";
|
|
6
|
+
|
|
1
7
|
// src/roles/test/react/toolchain.ts
|
|
2
8
|
import { defineConfig, mergeConfig } from "vitest/config";
|
|
3
9
|
import { loadEnv } from "vite";
|
|
10
|
+
|
|
11
|
+
// src/roles/test/react/react-test-config.ts
|
|
12
|
+
function reactTestConfig(options) {
|
|
13
|
+
return sharedTestConfig({ ...options, flavour: "react" });
|
|
14
|
+
}
|
|
4
15
|
export {
|
|
5
16
|
defineConfig,
|
|
17
|
+
jestExportConditions,
|
|
6
18
|
loadEnv,
|
|
7
|
-
mergeConfig
|
|
19
|
+
mergeConfig,
|
|
20
|
+
reactTestConfig
|
|
8
21
|
};
|
|
9
22
|
//# sourceMappingURL=toolchain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/roles/test/react/toolchain.ts","../../../../src/roles/test/react/react-test-config.ts"],"sourcesContent":["/**\n * What `@hublo/sentinel/test/react` gives an adopted module.\n *\n * A re-export surface, not a shape, and that is the measured answer rather than a preference.\n * The six configs already on Vitest fall into two groups: three React apps that converge on one\n * skeleton, and three modules of twelve to twenty lines that share almost nothing. A shared form\n * would serve half of them.\n *\n * More decisive: those configs carry decisions their owners made and justified in comments, a\n * `pool: 'threads'` measured on 1100 test files, an `experimental.fsModuleCache` measured against\n * transform and import time. Moving them is not a tooling migration, so the role moves the\n * imports and leaves every line of judgement where it is.\n *\n * Its own entry point, like the build ones, so importing it from a config never drags the CLI\n * and its adapters into a test run.\n */\n\n/**\n * From `vitest/config`, and NOT from `vite`, which also exports a `defineConfig`.\n *\n * They are different functions: measured, `vitest/config`'s `defineConfig` is not the same object\n * as vite's, and only it knows the `test` key. A config built with vite's would load and quietly\n * drop its entire test block. `mergeConfig` IS the same function in both, so its source does not\n * matter and it is taken from here for consistency.\n */\nexport { defineConfig, mergeConfig } from 'vitest/config'\n\n/**\n * From `vite`, because `vitest/config` does not export it. Measured, not assumed.\n *\n * Two of the six configs build their test env with it. Re-exported rather than claimed: the\n * `vite` package belongs to the build role, and a module adopting only this one keeps its own.\n */\nexport { loadEnv } from 'vite'\n\n/**\n * The names `vitest/config` actually exports, checked against its own declarations rather than\n * guessed: vite's `UserConfig` is re-exported there AS `ViteUserConfig`, and the test-side one is\n * `TestUserConfig`. Importing `UserConfig` from it compiles nowhere.\n */\nexport type { ConfigEnv, TestUserConfig, ViteUserConfig } from 'vitest/config'\n\n/**\n * The one thing this surface ADDS rather than re-exports, and only because a module cannot write\n * it for itself: Vite appends its own export conditions back over anything a config declares, so\n * restoring jest's resolution takes a plugin. Its own file carries the measurement.\n */\nexport { jestExportConditions } from './jest-export-conditions.js'\n\n/**\n * The config a MIGRATED React module gets, written by `--init --test`.\n *\n * Added on 23/09, because until then a front module being migrated received `nestTestConfig` while\n * the report announced `preset=react`. The re-export surface above is for the six modules that\n * already had a Vitest config and keep their own decisions; this is for the ones that had none and\n * need one written.\n */\nexport { reactTestConfig, type ReactTestOptions } from './react-test-config.js'\n","import type { ViteUserConfig } from 'vitest/config'\n\n/**\n * The Vitest config a REACT module gets: the shared one, without the Nest-only parts.\n *\n * ⚠️ This exists because a front module was being migrated with `nestTestConfig`, and the report\n * then said `preset=react` while the file it had just written imported `test/nest`. A tool that\n * contradicts itself in the same run is worse than one that is simply incomplete.\n *\n * What it does NOT carry, each measured rather than assumed: the `@prisma/*` runtime alias (1186\n * files mention it under nest, ZERO under front) and `isolate` (Nest registers metadata as an\n * import side effect; React has no such catalog, and isolation is not free).\n *\n * What it DOES carry is everything else, which is most of it: the workspace tsconfig path aliases,\n * jest's export conditions, the four test-file extensions, the concurrency this repo runs with, and\n * the msw, axios and jest-mock-extended aliases, all three of which the front uses heavily.\n *\n * Deliberately NOT here: a React plugin. Vite's own transform handles JSX in a test run, and the\n * front suites pass without one, so adding it would be paying for something nothing asked for.\n */\nimport { sharedTestConfig, type SharedTestOptions } from '../shared-test-config.js'\n\nexport type { SharedTestOptions as ReactTestOptions }\n\nexport function reactTestConfig(options: SharedTestOptions): ViteUserConfig {\n return sharedTestConfig({ ...options, flavour: 'react' })\n}\n"],"mappings":";;;;;;;AAyBA,SAAS,cAAc,mBAAmB;AAQ1C,SAAS,eAAe;;;ACTjB,SAAS,gBAAgB,SAA4C;AAC1E,SAAO,iBAAiB,EAAE,GAAG,SAAS,SAAS,QAAQ,CAAC;AAC1D;","names":[]}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
installWorkspaceSetup
|
|
3
|
+
} from "../../../chunk-CPCUPK4J.js";
|
|
4
|
+
import {
|
|
5
|
+
clearDeepMocks,
|
|
6
|
+
resetDeepMocks
|
|
7
|
+
} from "../../../chunk-2XLX6PFR.js";
|
|
8
|
+
import "../../../chunk-WLFE5RUU.js";
|
|
9
|
+
|
|
10
|
+
// src/roles/test/setup/jest-parity.ts
|
|
11
|
+
import { expect, vi } from "vitest";
|
|
12
|
+
|
|
13
|
+
// src/roles/test/setup/constructor-semantics.ts
|
|
14
|
+
function constructable(value) {
|
|
15
|
+
return typeof value === "function" && Object.getOwnPropertyDescriptor(value, "prototype") !== void 0;
|
|
16
|
+
}
|
|
17
|
+
function asConstructable(implementation) {
|
|
18
|
+
if (constructable(implementation)) return implementation;
|
|
19
|
+
return function forwarded(...args) {
|
|
20
|
+
return implementation.apply(this, args);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function installJestConstructorSemantics(vi2) {
|
|
24
|
+
const patch = (mock) => {
|
|
25
|
+
const { mockImplementation, mockImplementationOnce } = mock;
|
|
26
|
+
mock.mockImplementation = function(implementation) {
|
|
27
|
+
return mockImplementation.call(this, asConstructable(implementation));
|
|
28
|
+
};
|
|
29
|
+
mock.mockImplementationOnce = function(implementation) {
|
|
30
|
+
return mockImplementationOnce.call(this, asConstructable(implementation));
|
|
31
|
+
};
|
|
32
|
+
mock.mockReturnValue = function(value) {
|
|
33
|
+
return this.mockImplementation(() => value);
|
|
34
|
+
};
|
|
35
|
+
mock.mockReturnValueOnce = function(value) {
|
|
36
|
+
return this.mockImplementationOnce(() => value);
|
|
37
|
+
};
|
|
38
|
+
return mock;
|
|
39
|
+
};
|
|
40
|
+
const { fn, spyOn } = vi2;
|
|
41
|
+
vi2.fn = function(implementation) {
|
|
42
|
+
return patch(fn.call(this, implementation && asConstructable(implementation)));
|
|
43
|
+
};
|
|
44
|
+
vi2.spyOn = function(target, ...rest) {
|
|
45
|
+
return patch(spyOn.call(this, target, ...rest));
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/roles/test/setup/deep-mock-reset.ts
|
|
50
|
+
function installDeepMockReset(vi2) {
|
|
51
|
+
const { resetAllMocks, clearAllMocks } = vi2;
|
|
52
|
+
vi2.resetAllMocks = function extended() {
|
|
53
|
+
const answer = resetAllMocks.call(this);
|
|
54
|
+
resetDeepMocks();
|
|
55
|
+
return answer;
|
|
56
|
+
};
|
|
57
|
+
vi2.clearAllMocks = function extended() {
|
|
58
|
+
const answer = clearAllMocks.call(this);
|
|
59
|
+
clearDeepMocks();
|
|
60
|
+
return answer;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/roles/test/setup/error-equality.ts
|
|
65
|
+
function errorsCompareByMessage(left, right) {
|
|
66
|
+
if (left instanceof Error && right instanceof Error) return left.message === right.message;
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
function installJestErrorEquality(expect2) {
|
|
70
|
+
expect2.addEqualityTesters([errorsCompareByMessage]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/roles/test/setup/fake-timers.ts
|
|
74
|
+
var FAKED_BY_DEFAULT = [
|
|
75
|
+
"setTimeout",
|
|
76
|
+
"clearTimeout",
|
|
77
|
+
"setInterval",
|
|
78
|
+
"clearInterval",
|
|
79
|
+
"setImmediate",
|
|
80
|
+
"clearImmediate",
|
|
81
|
+
"Date",
|
|
82
|
+
"performance",
|
|
83
|
+
"hrtime"
|
|
84
|
+
];
|
|
85
|
+
function withoutJestOnlyOptions(options) {
|
|
86
|
+
const given = options;
|
|
87
|
+
if (given?.doNotFake === void 0) return options;
|
|
88
|
+
const { doNotFake, ...rest } = given;
|
|
89
|
+
const base = rest.toFake ?? [...FAKED_BY_DEFAULT];
|
|
90
|
+
return { ...rest, toFake: base.filter((timer) => !doNotFake.includes(timer)) };
|
|
91
|
+
}
|
|
92
|
+
function installJestFakeTimerOptions(vi2) {
|
|
93
|
+
const inherited = vi2.useFakeTimers.bind(vi2);
|
|
94
|
+
vi2.useFakeTimers = (options) => inherited(withoutJestOnlyOptions(options));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/roles/test/setup/jest-global.ts
|
|
98
|
+
function installJestGlobal(vi2) {
|
|
99
|
+
;
|
|
100
|
+
globalThis.jest = vi2;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/roles/test/setup/mock-reset.ts
|
|
104
|
+
function isResettable(value) {
|
|
105
|
+
return typeof value === "function" && typeof value.mockReset === "function" && typeof value.mockImplementation === "function";
|
|
106
|
+
}
|
|
107
|
+
function resetLikeJest(mock) {
|
|
108
|
+
if (!isResettable(mock)) return mock;
|
|
109
|
+
const inherited = mock.mockReset.bind(mock);
|
|
110
|
+
mock.mockReset = () => {
|
|
111
|
+
inherited();
|
|
112
|
+
mock.mockImplementation(() => void 0);
|
|
113
|
+
return mock;
|
|
114
|
+
};
|
|
115
|
+
return mock;
|
|
116
|
+
}
|
|
117
|
+
function installJestMockReset(vi2) {
|
|
118
|
+
for (const name of ["fn", "spyOn"]) {
|
|
119
|
+
const factory = vi2[name].bind(vi2);
|
|
120
|
+
vi2[name] = ((...args) => resetLikeJest(factory(...args)));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/roles/test/setup/rejected-function.ts
|
|
125
|
+
import { chai } from "vitest";
|
|
126
|
+
function thrownByCalling(candidate) {
|
|
127
|
+
try {
|
|
128
|
+
candidate();
|
|
129
|
+
} catch (thrown) {
|
|
130
|
+
return thrown;
|
|
131
|
+
}
|
|
132
|
+
return candidate;
|
|
133
|
+
}
|
|
134
|
+
function installJestRejectedFunction() {
|
|
135
|
+
const { Assertion, util } = chai;
|
|
136
|
+
for (const name of ["toThrow", "toThrowError"]) {
|
|
137
|
+
const original = Assertion.prototype[name];
|
|
138
|
+
if (typeof original !== "function") continue;
|
|
139
|
+
Assertion.prototype[name] = function patched(...args) {
|
|
140
|
+
const reason = this._obj;
|
|
141
|
+
const rejected = util.flag(this, "promise") === "rejects";
|
|
142
|
+
if (rejected && typeof reason === "function" && !(reason instanceof Error)) {
|
|
143
|
+
this._obj = thrownByCalling(reason);
|
|
144
|
+
}
|
|
145
|
+
return original.apply(this, args);
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/roles/test/setup/jest-parity.ts
|
|
151
|
+
installJestErrorEquality(expect);
|
|
152
|
+
installJestMockReset(vi);
|
|
153
|
+
installJestConstructorSemantics(vi);
|
|
154
|
+
installDeepMockReset(vi);
|
|
155
|
+
installJestFakeTimerOptions(vi);
|
|
156
|
+
installJestRejectedFunction();
|
|
157
|
+
installJestGlobal(vi);
|
|
158
|
+
await installWorkspaceSetup();
|
|
159
|
+
//# sourceMappingURL=jest-parity.js.map
|