@hublo/sentinel 1.2.0-alpha.2 → 1.2.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/sentinel.d.ts +1 -0
- package/dist/bin/sentinel.js +31 -5
- package/dist/{chunk-SA2RMTVI.js → chunk-RKVGZNJ7.js} +859 -577
- package/dist/index.js +1 -1
- package/dist/roles/build/nest/toolchain.d.ts +130 -0
- package/dist/roles/build/nest/toolchain.js +403 -0
- package/dist/roles/test/react/toolchain.d.ts +2 -0
- package/dist/roles/test/react/toolchain.js +9 -0
- package/package.json +25 -3
|
@@ -67,22 +67,9 @@ function resolve(target, preset, runner) {
|
|
|
67
67
|
return matching[0];
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// src/core/base-adapter.ts
|
|
71
|
-
var BaseAdapter = class {
|
|
72
|
-
inspect(_ctx) {
|
|
73
|
-
throw new Error(`${this.runner}: --inspect not implemented yet`);
|
|
74
|
-
}
|
|
75
|
-
report(_ctx) {
|
|
76
|
-
throw new Error(`${this.runner}: --report not implemented yet`);
|
|
77
|
-
}
|
|
78
|
-
status(_ctx) {
|
|
79
|
-
throw new Error(`${this.runner}: --status not implemented yet`);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
70
|
// src/shared/package-json.ts
|
|
84
71
|
import { existsSync, readFileSync } from "fs";
|
|
85
|
-
import { dirname, join } from "path";
|
|
72
|
+
import { basename, dirname, join } from "path";
|
|
86
73
|
import { fileURLToPath } from "url";
|
|
87
74
|
function readOwnPackage() {
|
|
88
75
|
let dir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -127,6 +114,23 @@ function readNxProjectName(dir) {
|
|
|
127
114
|
return void 0;
|
|
128
115
|
}
|
|
129
116
|
}
|
|
117
|
+
function moduleName(dir) {
|
|
118
|
+
const declared = readProjectPackageJson(dir).name;
|
|
119
|
+
return readNxProjectName(dir) ?? (declared === void 0 ? basename(dir) : declared);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/core/base-adapter.ts
|
|
123
|
+
var BaseAdapter = class {
|
|
124
|
+
inspect(_ctx) {
|
|
125
|
+
throw new Error(`${this.runner}: --inspect not implemented yet`);
|
|
126
|
+
}
|
|
127
|
+
report(_ctx) {
|
|
128
|
+
throw new Error(`${this.runner}: --report not implemented yet`);
|
|
129
|
+
}
|
|
130
|
+
status(_ctx) {
|
|
131
|
+
throw new Error(`${this.runner}: --status not implemented yet`);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
130
134
|
|
|
131
135
|
// src/core/domain.ts
|
|
132
136
|
var VERBS = ["run", "inspect", "init", "migrate", "report", "status"];
|
|
@@ -161,11 +165,273 @@ function palette(stream) {
|
|
|
161
165
|
};
|
|
162
166
|
}
|
|
163
167
|
|
|
168
|
+
// src/roles/build/nest/nx-target.ts
|
|
169
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
170
|
+
import { join as join2, relative } from "path";
|
|
171
|
+
var DEPRECATED_NX_BUILDER = "@nx/webpack:webpack";
|
|
172
|
+
var TRANSLATABLE_WEBPACK_CONFIG = "tools/webpack-configs/update.webpack.config.js";
|
|
173
|
+
var readProjectJson = (cwd) => {
|
|
174
|
+
const path = join2(cwd, "project.json");
|
|
175
|
+
if (!existsSync2(path)) return void 0;
|
|
176
|
+
try {
|
|
177
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
178
|
+
} catch {
|
|
179
|
+
return void 0;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
function webpackBuildTarget(cwd) {
|
|
183
|
+
const project = readProjectJson(cwd);
|
|
184
|
+
const target = project?.targets?.build;
|
|
185
|
+
if (target?.executor !== DEPRECATED_NX_BUILDER) return void 0;
|
|
186
|
+
const { outputPath, main, webpackConfig, transformers, assets } = target.options ?? {};
|
|
187
|
+
if (typeof project?.name !== "string" || typeof outputPath !== "string" || typeof main !== "string" || typeof webpackConfig !== "string") {
|
|
188
|
+
return void 0;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
project: project.name,
|
|
192
|
+
outputPath,
|
|
193
|
+
main,
|
|
194
|
+
webpackConfig,
|
|
195
|
+
configurations: target.configurations,
|
|
196
|
+
transformers,
|
|
197
|
+
assets
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function refusalFor(cwd, target) {
|
|
201
|
+
if (workspaceRootFor(cwd, target) === void 0) {
|
|
202
|
+
return `this service's build target declares \`main: ${target.main}\`, which does not place this directory inside a workspace. Every value the config needs is derived from that path, so a wrong one would resolve \`workspaceRoot\` to the service itself and leave the alias table resolving nothing. Nothing was written.`;
|
|
203
|
+
}
|
|
204
|
+
if (target.webpackConfig !== TRANSLATABLE_WEBPACK_CONFIG) {
|
|
205
|
+
return `this service builds with ${target.webpackConfig}, not the shared ${TRANSLATABLE_WEBPACK_CONFIG} this preset reproduces. Its own config adds loaders or rules the preset has no equivalent for, and translating it anyway would produce a build that succeeds and a service that fails later. Read that file, decide what the Vite config needs, and write it by hand. Nothing was written.`;
|
|
206
|
+
}
|
|
207
|
+
const live = liveFileReplacements(cwd, target);
|
|
208
|
+
if (live.length > 0) {
|
|
209
|
+
return `this service's production configuration replaces ${live.join(", ")}, and those files exist. The preset has no equivalent, so translating the target would drop a real substitution. Nothing was written.`;
|
|
210
|
+
}
|
|
211
|
+
return void 0;
|
|
212
|
+
}
|
|
213
|
+
function liveFileReplacements(cwd, target) {
|
|
214
|
+
const workspaceRoot = workspaceRootFor(cwd, target);
|
|
215
|
+
if (workspaceRoot === void 0) return [];
|
|
216
|
+
const configurations = target.configurations ?? {};
|
|
217
|
+
const live = [];
|
|
218
|
+
for (const configuration of Object.values(configurations)) {
|
|
219
|
+
for (const replacement of configuration.fileReplacements ?? []) {
|
|
220
|
+
const exists = existsSync2(join2(workspaceRoot, replacement.replace)) || existsSync2(join2(workspaceRoot, replacement.with));
|
|
221
|
+
if (exists) live.push(replacement.replace);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return live;
|
|
225
|
+
}
|
|
226
|
+
function workspaceRootFor(cwd, target) {
|
|
227
|
+
const moduleRoot = target.main.replace(/\/src\/.*$/, "");
|
|
228
|
+
if (moduleRoot === target.main || !cwd.endsWith(moduleRoot)) return void 0;
|
|
229
|
+
return cwd.slice(0, cwd.length - moduleRoot.length - 1);
|
|
230
|
+
}
|
|
231
|
+
function workspaceRootHops(cwd, target) {
|
|
232
|
+
const root = workspaceRootFor(cwd, target);
|
|
233
|
+
if (root === void 0) {
|
|
234
|
+
throw new Error(`sentinel build(nest): cannot place ${cwd} in a workspace from ${target.main}`);
|
|
235
|
+
}
|
|
236
|
+
const rel = relative(root, cwd);
|
|
237
|
+
return rel === "" ? 0 : rel.split(/[\\/]/).length;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/roles/build/read-adoption.ts
|
|
241
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
242
|
+
import { join as join3 } from "path";
|
|
243
|
+
|
|
244
|
+
// src/roles/build/presets/requirements.json
|
|
245
|
+
var requirements_default = {
|
|
246
|
+
svelte: {
|
|
247
|
+
why: "sentinel ships Vite 8, and @sveltejs/vite-plugin-svelte caps at Vite 6 until its major 7",
|
|
248
|
+
requires: {
|
|
249
|
+
"@sveltejs/vite-plugin-svelte": "7.0.0",
|
|
250
|
+
svelte: "5.46.4"
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// src/roles/build/presets/toolchain.json
|
|
256
|
+
var toolchain_default = {
|
|
257
|
+
$comment: "The build toolchain sentinel owns. One table, because three places used to hold parts of it: the list to remove from a module's package.json, the list of import specifiers to rewrite, and the map from a default import to the name sentinel re-exports it under. A package added to one and not the others gives a removal with no rewrite, or an import pointing at a package the module no longer declares.",
|
|
258
|
+
owned: [
|
|
259
|
+
{
|
|
260
|
+
package: "vite",
|
|
261
|
+
specifier: "vite",
|
|
262
|
+
named: ["defineConfig", "loadEnv", "mergeConfig"],
|
|
263
|
+
types: [
|
|
264
|
+
"Plugin",
|
|
265
|
+
"PluginOption",
|
|
266
|
+
"SassPreprocessorOptions",
|
|
267
|
+
"UserConfig",
|
|
268
|
+
"UserConfigExport"
|
|
269
|
+
],
|
|
270
|
+
why: "the bundler itself. `defineConfig`, `loadEnv` and `mergeConfig` are what every config in this repo imports from it, and the types travel with them: a config importing PluginOption from a package it no longer declares builds but does not typecheck."
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
package: "@vitejs/plugin-react",
|
|
274
|
+
specifier: "@vitejs/plugin-react",
|
|
275
|
+
default: "react",
|
|
276
|
+
why: "the React transform. Bound to the Vite that runs it, so it has to come from the same install."
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
package: "@tailwindcss/vite",
|
|
280
|
+
specifier: "@tailwindcss/vite",
|
|
281
|
+
default: "tailwindcss",
|
|
282
|
+
why: "the Tailwind pipeline, identical in all three front apps."
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
package: "vite-plugin-svgr",
|
|
286
|
+
specifier: "vite-plugin-svgr",
|
|
287
|
+
default: "svgr",
|
|
288
|
+
why: "SVG as components, identical in all three front apps."
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
package: "nitro",
|
|
292
|
+
specifier: "nitro/vite",
|
|
293
|
+
named: ["nitro"],
|
|
294
|
+
why: "the SSR server. Note the package and the import specifier differ, which is exactly the kind of split that made three separate lists disagree."
|
|
295
|
+
}
|
|
296
|
+
],
|
|
297
|
+
add: [
|
|
298
|
+
{
|
|
299
|
+
package: "@hublo/sentinel",
|
|
300
|
+
why: "the module now gets its toolchain from here, so it declares it. Pinned by the installer rather than by us: `--init` writes the dependency, `pnpm install` resolves it."
|
|
301
|
+
}
|
|
302
|
+
],
|
|
303
|
+
$notOwned: {
|
|
304
|
+
"@rolldown/plugin-babel": "declared by career and host-admin themselves, not at the root. It supports LocatorJS, a development convenience gated on LOCATORJS=true, not shared build infrastructure. Removing it would remove a feature from two teams, which is their call and must not ride along inside an adoption.",
|
|
305
|
+
"@locator/babel-jsx": "same, and same owners.",
|
|
306
|
+
"@tanstack/react-start": "a framework the apps code against: 48 source files import it directly.",
|
|
307
|
+
"@tanstack/router-plugin": "generates the app's own route tree.",
|
|
308
|
+
"vite-bundle-analyzer": "at the root at 1.3.7 and imported nowhere, the only mention being a commented-out line in host-admin. Not a candidate to re-export, a candidate to delete from the root."
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// src/roles/build/preset-data.ts
|
|
313
|
+
var REQUIREMENTS = requirements_default;
|
|
314
|
+
var OWNED_PACKAGES = toolchain_default.owned;
|
|
315
|
+
|
|
316
|
+
// src/roles/build/config-policy.ts
|
|
317
|
+
var COMPANION_CONFIG_FILES = [
|
|
318
|
+
"vitest.config.ts",
|
|
319
|
+
"vitest.config.mts",
|
|
320
|
+
"vitest.config.js",
|
|
321
|
+
"vitest.config.mjs"
|
|
322
|
+
];
|
|
323
|
+
var BUILD_CONFIG_FILES = [
|
|
324
|
+
"vite.config.ts",
|
|
325
|
+
"vite.config.mts",
|
|
326
|
+
"vite.config.js",
|
|
327
|
+
"vite.config.mjs"
|
|
328
|
+
];
|
|
329
|
+
var BUILD_PRESET_SPECIFIERS = {
|
|
330
|
+
react: "@hublo/sentinel/build/react",
|
|
331
|
+
nest: "@hublo/sentinel/build/nest"
|
|
332
|
+
};
|
|
333
|
+
var BUILD_PRESET_SPECIFIER = BUILD_PRESET_SPECIFIERS.react;
|
|
334
|
+
var BUILD_SCRIPT_NAME = "build";
|
|
335
|
+
var SENTINEL_BUILD_COMMAND = "sentinel --run --build --";
|
|
336
|
+
var DEV_SCRIPT_NAME = "serve";
|
|
337
|
+
var SENTINEL_DEV_COMMAND = "sentinel --run --dev";
|
|
338
|
+
var BUILD_OUTPUT_CANDIDATES = ["dist", ".output", "build"];
|
|
339
|
+
function buildTarget(options) {
|
|
340
|
+
const { moduleName: moduleName3, configFiles = BUILD_CONFIG_FILES, outputs } = options;
|
|
341
|
+
return {
|
|
342
|
+
[BUILD_SCRIPT_NAME]: {
|
|
343
|
+
executor: "nx:run-commands",
|
|
344
|
+
options: {
|
|
345
|
+
command: `pnpm --filter ${moduleName3} run ${BUILD_SCRIPT_NAME}`,
|
|
346
|
+
forwardAllArgs: false
|
|
347
|
+
},
|
|
348
|
+
cache: true,
|
|
349
|
+
inputs: ["default", "^default", ...configFiles.map((name) => `{projectRoot}/${name}`)],
|
|
350
|
+
outputs: [...outputs]
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
var SENTINEL_OWNED_BUILD_PACKAGES = OWNED_PACKAGES.map(
|
|
355
|
+
(entry) => entry.package
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
// src/roles/build/read-adoption.ts
|
|
359
|
+
var NOT_ADOPTED = (configFile, unreadable = null) => ({
|
|
360
|
+
configFile,
|
|
361
|
+
preset: null,
|
|
362
|
+
adopted: false,
|
|
363
|
+
conformant: false,
|
|
364
|
+
drift: [],
|
|
365
|
+
ownDeclarations: [],
|
|
366
|
+
unreadable
|
|
367
|
+
});
|
|
368
|
+
function buildConfigFile(cwd) {
|
|
369
|
+
return BUILD_CONFIG_FILES.find((name) => existsSync3(join3(cwd, name)));
|
|
370
|
+
}
|
|
371
|
+
function importsSpecifier(source, specifier) {
|
|
372
|
+
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
373
|
+
return new RegExp(`(?:from|import)\\s*\\(?\\s*['"\`]${escaped}(?:/[^'"\`]*)?['"\`]`).test(source);
|
|
374
|
+
}
|
|
375
|
+
function importsPreset(source) {
|
|
376
|
+
return Object.values(BUILD_PRESET_SPECIFIERS).some(
|
|
377
|
+
(specifier) => importsSpecifier(source, specifier)
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
var REACT_PLUGIN_SPECIFIERS = ["@vitejs/plugin-react", "@tanstack/react-start"];
|
|
381
|
+
function declaredBuildPreset(cwd) {
|
|
382
|
+
const source = readBuildConfigSource(cwd);
|
|
383
|
+
if (source === void 0) {
|
|
384
|
+
if (buildConfigFile(cwd) !== void 0) return void 0;
|
|
385
|
+
return webpackBuildTarget(cwd) === void 0 ? void 0 : "nest";
|
|
386
|
+
}
|
|
387
|
+
if (importsSpecifier(source, BUILD_PRESET_SPECIFIERS.nest)) return "nest";
|
|
388
|
+
if (importsPreset(source)) return "react";
|
|
389
|
+
return REACT_PLUGIN_SPECIFIERS.some((specifier) => importsSpecifier(source, specifier)) ? "react" : void 0;
|
|
390
|
+
}
|
|
391
|
+
function readBuildConfigSource(cwd) {
|
|
392
|
+
const configFile = buildConfigFile(cwd);
|
|
393
|
+
if (!configFile) return void 0;
|
|
394
|
+
try {
|
|
395
|
+
return readFileSync3(join3(cwd, configFile), "utf8");
|
|
396
|
+
} catch {
|
|
397
|
+
return void 0;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function readBuildAdoption(cwd) {
|
|
401
|
+
const configFile = buildConfigFile(cwd);
|
|
402
|
+
if (!configFile) return NOT_ADOPTED(null);
|
|
403
|
+
let source;
|
|
404
|
+
try {
|
|
405
|
+
source = readFileSync3(join3(cwd, configFile), "utf8");
|
|
406
|
+
} catch (error) {
|
|
407
|
+
return NOT_ADOPTED(configFile, error instanceof Error ? error.message : String(error));
|
|
408
|
+
}
|
|
409
|
+
if (!importsPreset(source)) return NOT_ADOPTED(configFile);
|
|
410
|
+
const manifest = readProjectPackageJson(cwd);
|
|
411
|
+
const declared = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
412
|
+
const ownDeclarations = SENTINEL_OWNED_BUILD_PACKAGES.filter((name) => name in declared).map(
|
|
413
|
+
(name) => ({ name, version: declared[name] })
|
|
414
|
+
);
|
|
415
|
+
return {
|
|
416
|
+
configFile,
|
|
417
|
+
// Read from the config rather than assumed: the two presets are adopted the same way and
|
|
418
|
+
// reporting the wrong one would make `--inspect` lie about what a module builds.
|
|
419
|
+
preset: declaredBuildPreset(cwd) ?? "react",
|
|
420
|
+
adopted: true,
|
|
421
|
+
conformant: ownDeclarations.length === 0,
|
|
422
|
+
drift: ownDeclarations.map(
|
|
423
|
+
({ name, version }) => `declares ${name}@${version} of its own; sentinel owns it, and two copies in one build give the plugins a different Vite than the one running them`
|
|
424
|
+
),
|
|
425
|
+
ownDeclarations,
|
|
426
|
+
unreadable: null
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
164
430
|
// src/roles/build/adapters/vite/vite-role.adapter.ts
|
|
165
431
|
import { spawnSync } from "child_process";
|
|
166
432
|
|
|
167
433
|
// src/core/config/tool-args.ts
|
|
168
|
-
import { existsSync as
|
|
434
|
+
import { existsSync as existsSync4 } from "fs";
|
|
169
435
|
import { isAbsolute, resolve as resolve2 } from "path";
|
|
170
436
|
function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
171
437
|
const takesValue = new Set(valueFlags);
|
|
@@ -175,7 +441,7 @@ function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
|
175
441
|
for (const arg of toolArgs) {
|
|
176
442
|
const looksLikeOption = arg.startsWith("-");
|
|
177
443
|
const target = isAbsolute(arg) ? arg : resolve2(cwd, arg);
|
|
178
|
-
if (!previousTakesValue && !looksLikeOption &&
|
|
444
|
+
if (!previousTakesValue && !looksLikeOption && existsSync4(target)) paths.push(arg);
|
|
179
445
|
else options.push(arg);
|
|
180
446
|
previousTakesValue = !arg.includes("=") && takesValue.has(arg);
|
|
181
447
|
}
|
|
@@ -183,15 +449,15 @@ function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
|
183
449
|
}
|
|
184
450
|
|
|
185
451
|
// src/shared/resolve-bin.ts
|
|
186
|
-
import { existsSync as
|
|
452
|
+
import { existsSync as existsSync5 } from "fs";
|
|
187
453
|
import { createRequire } from "module";
|
|
188
|
-
import { delimiter, dirname as dirname2, join as
|
|
454
|
+
import { delimiter, dirname as dirname2, join as join4 } from "path";
|
|
189
455
|
var require2 = createRequire(import.meta.url);
|
|
190
456
|
function resolveBin(fromDir, name) {
|
|
191
457
|
let dir = fromDir;
|
|
192
458
|
for (; ; ) {
|
|
193
|
-
const candidate =
|
|
194
|
-
if (
|
|
459
|
+
const candidate = join4(dir, "node_modules", ".bin", name);
|
|
460
|
+
if (existsSync5(candidate)) return candidate;
|
|
195
461
|
const parent = dirname2(dir);
|
|
196
462
|
if (parent === dir) return void 0;
|
|
197
463
|
dir = parent;
|
|
@@ -201,10 +467,10 @@ function binFromOwnInstall(packageName, binName) {
|
|
|
201
467
|
try {
|
|
202
468
|
const manifest = require2.resolve(`${packageName}/package.json`);
|
|
203
469
|
const bin = require2(manifest).bin;
|
|
204
|
-
const
|
|
205
|
-
if (!
|
|
206
|
-
const executable =
|
|
207
|
-
return
|
|
470
|
+
const relative5 = typeof bin === "string" ? bin : bin?.[binName];
|
|
471
|
+
if (!relative5) return void 0;
|
|
472
|
+
const executable = join4(dirname2(manifest), relative5);
|
|
473
|
+
return existsSync5(executable) ? executable : void 0;
|
|
208
474
|
} catch {
|
|
209
475
|
return void 0;
|
|
210
476
|
}
|
|
@@ -216,12 +482,12 @@ function binSearchPath(cwd) {
|
|
|
216
482
|
}
|
|
217
483
|
|
|
218
484
|
// src/roles/build/plan.ts
|
|
219
|
-
import { existsSync as
|
|
220
|
-
import {
|
|
485
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
486
|
+
import { join as join9 } from "path";
|
|
221
487
|
|
|
222
488
|
// src/core/config/existing-command.ts
|
|
223
|
-
import { readFileSync as
|
|
224
|
-
import { join as
|
|
489
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
490
|
+
import { join as join6 } from "path";
|
|
225
491
|
|
|
226
492
|
// src/shared/jsonc.ts
|
|
227
493
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
@@ -236,45 +502,48 @@ function parseJsonc(text, source = "config") {
|
|
|
236
502
|
}
|
|
237
503
|
|
|
238
504
|
// src/core/config/manifest.ts
|
|
239
|
-
import { existsSync as
|
|
240
|
-
import { basename, join as
|
|
505
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
506
|
+
import { basename as basename2, join as join5 } from "path";
|
|
241
507
|
function moduleScripts(cwd) {
|
|
242
508
|
try {
|
|
243
|
-
const pkg = JSON.parse(
|
|
509
|
+
const pkg = JSON.parse(readFileSync4(join5(cwd, "package.json"), "utf8"));
|
|
244
510
|
return pkg.scripts ?? {};
|
|
245
511
|
} catch {
|
|
246
512
|
return {};
|
|
247
513
|
}
|
|
248
514
|
}
|
|
249
|
-
function
|
|
515
|
+
function moduleName2(cwd) {
|
|
250
516
|
try {
|
|
251
|
-
const pkg = JSON.parse(
|
|
517
|
+
const pkg = JSON.parse(readFileSync4(join5(cwd, "package.json"), "utf8"));
|
|
252
518
|
return pkg.name;
|
|
253
519
|
} catch {
|
|
254
520
|
return void 0;
|
|
255
521
|
}
|
|
256
522
|
}
|
|
257
523
|
function isSelfAdoption(cwd) {
|
|
258
|
-
return
|
|
524
|
+
return moduleName2(cwd) === readOwnPackage().name;
|
|
259
525
|
}
|
|
260
526
|
function selfCommand(cwd, flags) {
|
|
261
527
|
const own = readOwnPackage();
|
|
262
528
|
if (!isSelfAdoption(cwd) || !own.bin) return void 0;
|
|
263
529
|
return `node ${own.bin.replace(/^\.\//, "")} ${flags}`;
|
|
264
530
|
}
|
|
531
|
+
function adoptedPackageName(cwd) {
|
|
532
|
+
return readProjectPackageJson(cwd).name ?? readNxProjectName(cwd) ?? basename2(cwd);
|
|
533
|
+
}
|
|
265
534
|
function manifestOperation(cwd, scripts) {
|
|
266
535
|
const own = readOwnPackage();
|
|
267
536
|
const value = {
|
|
268
537
|
scripts,
|
|
269
538
|
devDependencies: { [own.name]: isSelfAdoption(cwd) ? "link:." : own.version }
|
|
270
539
|
};
|
|
271
|
-
if (
|
|
540
|
+
if (existsSync6(join5(cwd, "package.json"))) {
|
|
272
541
|
return { kind: "merge-json", path: "package.json", value };
|
|
273
542
|
}
|
|
274
543
|
return {
|
|
275
544
|
kind: "merge-json",
|
|
276
545
|
path: "package.json",
|
|
277
|
-
value: { name:
|
|
546
|
+
value: { name: adoptedPackageName(cwd), private: true, ...value }
|
|
278
547
|
};
|
|
279
548
|
}
|
|
280
549
|
|
|
@@ -283,7 +552,7 @@ function nxTargetCommand(cwd, target) {
|
|
|
283
552
|
let project;
|
|
284
553
|
try {
|
|
285
554
|
project = parseJsonc(
|
|
286
|
-
|
|
555
|
+
readFileSync5(join6(cwd, "project.json"), "utf8"),
|
|
287
556
|
"project.json"
|
|
288
557
|
);
|
|
289
558
|
} catch {
|
|
@@ -314,25 +583,26 @@ function existingCommand(cwd, target) {
|
|
|
314
583
|
}
|
|
315
584
|
|
|
316
585
|
// src/core/config/nx-target.ts
|
|
317
|
-
import { existsSync as
|
|
318
|
-
import { join as
|
|
586
|
+
import { existsSync as existsSync7 } from "fs";
|
|
587
|
+
import { join as join7 } from "path";
|
|
588
|
+
function nxTargetMetadataOperations(options) {
|
|
589
|
+
const { targets } = options;
|
|
590
|
+
if (Object.keys(targets).length === 0) return [];
|
|
591
|
+
return [{ kind: "merge-json", path: "package.json", value: { nx: { targets } } }];
|
|
592
|
+
}
|
|
319
593
|
function nxTargetOperations(options) {
|
|
320
594
|
const { cwd, targets } = options;
|
|
321
595
|
const names = Object.keys(targets);
|
|
322
596
|
if (names.length === 0) return [];
|
|
323
597
|
const operations = [];
|
|
324
|
-
if (
|
|
598
|
+
if (existsSync7(join7(cwd, "project.json"))) {
|
|
325
599
|
operations.push({
|
|
326
600
|
kind: "remove-json-keys",
|
|
327
601
|
path: "project.json",
|
|
328
602
|
keys: names.map((name) => ["targets", name])
|
|
329
603
|
});
|
|
330
604
|
}
|
|
331
|
-
operations.push(
|
|
332
|
-
kind: "merge-json",
|
|
333
|
-
path: "package.json",
|
|
334
|
-
value: { nx: { targets } }
|
|
335
|
-
});
|
|
605
|
+
operations.push(...nxTargetMetadataOperations(options));
|
|
336
606
|
return operations;
|
|
337
607
|
}
|
|
338
608
|
|
|
@@ -378,108 +648,6 @@ function keepsOtherCommands(script, sentinelCommand) {
|
|
|
378
648
|
return script.split(SEGMENT_SEPARATOR).filter((_, index) => !isSeparatorAt(index)).some((segment) => segment.trim() !== "" && segment.trim() !== sentinelCommand);
|
|
379
649
|
}
|
|
380
650
|
|
|
381
|
-
// src/roles/build/presets/requirements.json
|
|
382
|
-
var requirements_default = {
|
|
383
|
-
svelte: {
|
|
384
|
-
why: "sentinel ships Vite 8, and @sveltejs/vite-plugin-svelte caps at Vite 6 until its major 7",
|
|
385
|
-
requires: {
|
|
386
|
-
"@sveltejs/vite-plugin-svelte": "7.0.0",
|
|
387
|
-
svelte: "5.46.4"
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
// src/roles/build/presets/toolchain.json
|
|
393
|
-
var toolchain_default = {
|
|
394
|
-
$comment: "The build toolchain sentinel owns. One table, because three places used to hold parts of it: the list to remove from a module's package.json, the list of import specifiers to rewrite, and the map from a default import to the name sentinel re-exports it under. A package added to one and not the others gives a removal with no rewrite, or an import pointing at a package the module no longer declares.",
|
|
395
|
-
owned: [
|
|
396
|
-
{
|
|
397
|
-
package: "vite",
|
|
398
|
-
specifier: "vite",
|
|
399
|
-
named: ["defineConfig", "loadEnv", "mergeConfig"],
|
|
400
|
-
types: [
|
|
401
|
-
"Plugin",
|
|
402
|
-
"PluginOption",
|
|
403
|
-
"SassPreprocessorOptions",
|
|
404
|
-
"UserConfig",
|
|
405
|
-
"UserConfigExport"
|
|
406
|
-
],
|
|
407
|
-
why: "the bundler itself. `defineConfig`, `loadEnv` and `mergeConfig` are what every config in this repo imports from it, and the types travel with them: a config importing PluginOption from a package it no longer declares builds but does not typecheck."
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
package: "@vitejs/plugin-react",
|
|
411
|
-
specifier: "@vitejs/plugin-react",
|
|
412
|
-
default: "react",
|
|
413
|
-
why: "the React transform. Bound to the Vite that runs it, so it has to come from the same install."
|
|
414
|
-
},
|
|
415
|
-
{
|
|
416
|
-
package: "@tailwindcss/vite",
|
|
417
|
-
specifier: "@tailwindcss/vite",
|
|
418
|
-
default: "tailwindcss",
|
|
419
|
-
why: "the Tailwind pipeline, identical in all three front apps."
|
|
420
|
-
},
|
|
421
|
-
{
|
|
422
|
-
package: "vite-plugin-svgr",
|
|
423
|
-
specifier: "vite-plugin-svgr",
|
|
424
|
-
default: "svgr",
|
|
425
|
-
why: "SVG as components, identical in all three front apps."
|
|
426
|
-
},
|
|
427
|
-
{
|
|
428
|
-
package: "nitro",
|
|
429
|
-
specifier: "nitro/vite",
|
|
430
|
-
named: ["nitro"],
|
|
431
|
-
why: "the SSR server. Note the package and the import specifier differ, which is exactly the kind of split that made three separate lists disagree."
|
|
432
|
-
}
|
|
433
|
-
],
|
|
434
|
-
add: [
|
|
435
|
-
{
|
|
436
|
-
package: "@hublo/sentinel",
|
|
437
|
-
why: "the module now gets its toolchain from here, so it declares it. Pinned by the installer rather than by us: `--init` writes the dependency, `pnpm install` resolves it."
|
|
438
|
-
}
|
|
439
|
-
],
|
|
440
|
-
$notOwned: {
|
|
441
|
-
"@rolldown/plugin-babel": "declared by career and host-admin themselves, not at the root. It supports LocatorJS, a development convenience gated on LOCATORJS=true, not shared build infrastructure. Removing it would remove a feature from two teams, which is their call and must not ride along inside an adoption.",
|
|
442
|
-
"@locator/babel-jsx": "same, and same owners.",
|
|
443
|
-
"@tanstack/react-start": "a framework the apps code against: 48 source files import it directly.",
|
|
444
|
-
"@tanstack/router-plugin": "generates the app's own route tree.",
|
|
445
|
-
"vite-bundle-analyzer": "at the root at 1.3.7 and imported nowhere, the only mention being a commented-out line in host-admin. Not a candidate to re-export, a candidate to delete from the root."
|
|
446
|
-
}
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
// src/roles/build/preset-data.ts
|
|
450
|
-
var REQUIREMENTS = requirements_default;
|
|
451
|
-
var OWNED_PACKAGES = toolchain_default.owned;
|
|
452
|
-
|
|
453
|
-
// src/roles/build/config-policy.ts
|
|
454
|
-
var COMPANION_CONFIG_FILES = [
|
|
455
|
-
"vitest.config.ts",
|
|
456
|
-
"vitest.config.mts",
|
|
457
|
-
"vitest.config.js",
|
|
458
|
-
"vitest.config.mjs"
|
|
459
|
-
];
|
|
460
|
-
var BUILD_CONFIG_FILES = [
|
|
461
|
-
"vite.config.ts",
|
|
462
|
-
"vite.config.mts",
|
|
463
|
-
"vite.config.js",
|
|
464
|
-
"vite.config.mjs"
|
|
465
|
-
];
|
|
466
|
-
var BUILD_PRESET_SPECIFIER = "@hublo/sentinel/build/react";
|
|
467
|
-
var BUILD_SCRIPT_NAME = "build";
|
|
468
|
-
var SENTINEL_BUILD_COMMAND = "sentinel --run --build";
|
|
469
|
-
var DEV_SCRIPT_NAME = "serve";
|
|
470
|
-
var SENTINEL_DEV_COMMAND = "sentinel --run --dev";
|
|
471
|
-
function buildTarget() {
|
|
472
|
-
return {
|
|
473
|
-
[BUILD_SCRIPT_NAME]: {
|
|
474
|
-
cache: true,
|
|
475
|
-
inputs: ["default", "^default", ...BUILD_CONFIG_FILES.map((name) => `{projectRoot}/${name}`)]
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
var SENTINEL_OWNED_BUILD_PACKAGES = OWNED_PACKAGES.map(
|
|
480
|
-
(entry) => entry.package
|
|
481
|
-
);
|
|
482
|
-
|
|
483
651
|
// src/roles/build/dev-script.ts
|
|
484
652
|
var CHAIN = " -- ";
|
|
485
653
|
var VITE_TOKEN = /(^|\s|\/)vite(\s|$)/;
|
|
@@ -496,73 +664,43 @@ function composeDevScript(existing, command) {
|
|
|
496
664
|
if (at === -1) return existing;
|
|
497
665
|
const rest = (chunks[at] ?? "").replace(/^.*?(^|\s|\/)vite(\s|$)/, "");
|
|
498
666
|
const options = appOptions(rest);
|
|
499
|
-
chunks[at] = options === "" ? command : `${command} -- ${options}`;
|
|
667
|
+
chunks[at] = options === "" ? `${command} --` : `${command} -- ${options}`;
|
|
500
668
|
return chunks.join(CHAIN);
|
|
501
669
|
}
|
|
502
670
|
|
|
503
|
-
// src/roles/build/
|
|
504
|
-
import { existsSync as
|
|
505
|
-
import { join as
|
|
506
|
-
var
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
return importsSpecifier(source, BUILD_PRESET_SPECIFIER);
|
|
671
|
+
// src/roles/build/nest/adopt.ts
|
|
672
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
673
|
+
import { join as join8 } from "path";
|
|
674
|
+
var NEST_CONFIG_FILE = "vite.config.mts";
|
|
675
|
+
function nestConfigSource(cwd, target) {
|
|
676
|
+
const hops = workspaceRootHops(cwd, target);
|
|
677
|
+
const up = Array.from({ length: hops }, () => "..").join("/");
|
|
678
|
+
const carried = (name, value) => value === void 0 || value.length === 0 ? "" : ` ${name}: ${JSON.stringify(value)},
|
|
679
|
+
`;
|
|
680
|
+
return `import path from 'node:path'
|
|
681
|
+
|
|
682
|
+
import { nestService } from '${BUILD_PRESET_SPECIFIERS.nest}'
|
|
683
|
+
|
|
684
|
+
export default nestService({
|
|
685
|
+
project: '${target.project}',
|
|
686
|
+
root: __dirname,
|
|
687
|
+
workspaceRoot: path.resolve(__dirname, '${up}'),
|
|
688
|
+
outDir: '${target.outputPath}',
|
|
689
|
+
${carried("transformers", target.transformers)}${carried("assets", target.assets)}})
|
|
690
|
+
`;
|
|
524
691
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const source = readBuildConfigSource(cwd);
|
|
528
|
-
if (source === void 0) return void 0;
|
|
529
|
-
return REACT_PLUGIN_SPECIFIERS.some((specifier) => importsSpecifier(source, specifier)) ? "react" : void 0;
|
|
692
|
+
function nestBuildOutputs(target) {
|
|
693
|
+
return [`{workspaceRoot}/${target.outputPath}`];
|
|
530
694
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
return void 0;
|
|
538
|
-
}
|
|
695
|
+
var OUT_DIR_IN_CONFIG = /^\s*outDir:\s*'([^']+)'/m;
|
|
696
|
+
function nestOutputsFromConfig(cwd) {
|
|
697
|
+
const configPath = join8(cwd, NEST_CONFIG_FILE);
|
|
698
|
+
if (!existsSync8(configPath)) return void 0;
|
|
699
|
+
const outDir = OUT_DIR_IN_CONFIG.exec(readFileSync6(configPath, "utf8"))?.[1];
|
|
700
|
+
return outDir === void 0 ? void 0 : [`{workspaceRoot}/${outDir}`];
|
|
539
701
|
}
|
|
540
|
-
function
|
|
541
|
-
|
|
542
|
-
if (!configFile) return NOT_ADOPTED(null);
|
|
543
|
-
let source;
|
|
544
|
-
try {
|
|
545
|
-
source = readFileSync4(join6(cwd, configFile), "utf8");
|
|
546
|
-
} catch (error) {
|
|
547
|
-
return NOT_ADOPTED(configFile, error instanceof Error ? error.message : String(error));
|
|
548
|
-
}
|
|
549
|
-
if (!importsPreset(source)) return NOT_ADOPTED(configFile);
|
|
550
|
-
const manifest = readProjectPackageJson(cwd);
|
|
551
|
-
const declared = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
552
|
-
const ownDeclarations = SENTINEL_OWNED_BUILD_PACKAGES.filter((name) => name in declared).map(
|
|
553
|
-
(name) => ({ name, version: declared[name] })
|
|
554
|
-
);
|
|
555
|
-
return {
|
|
556
|
-
configFile,
|
|
557
|
-
preset: "react",
|
|
558
|
-
adopted: true,
|
|
559
|
-
conformant: ownDeclarations.length === 0,
|
|
560
|
-
drift: ownDeclarations.map(
|
|
561
|
-
({ name, version }) => `declares ${name}@${version} of its own; sentinel owns it, and two copies in one build give the plugins a different Vite than the one running them`
|
|
562
|
-
),
|
|
563
|
-
ownDeclarations,
|
|
564
|
-
unreadable: null
|
|
565
|
-
};
|
|
702
|
+
function nestAdoptionOperations(cwd, target) {
|
|
703
|
+
return [{ kind: "write", path: NEST_CONFIG_FILE, contents: nestConfigSource(cwd, target) }];
|
|
566
704
|
}
|
|
567
705
|
|
|
568
706
|
// src/roles/build/requirements.ts
|
|
@@ -593,32 +731,31 @@ function describeUnmet(preset, unmet) {
|
|
|
593
731
|
return `the build role does not apply to this module yet: ${list}. ${entry?.why ?? ""}. Nothing was written. Raise those versions, check the module still builds and its tests still pass, then re-run \`sentinel --init --build\`.`;
|
|
594
732
|
}
|
|
595
733
|
|
|
596
|
-
// src/
|
|
734
|
+
// src/core/config/rewrite-imports.ts
|
|
597
735
|
import { parseSync } from "oxc-parser";
|
|
598
|
-
var
|
|
736
|
+
var movedSpecifiers = (toolchain) => new Set(toolchain.owned.map((entry) => entry.specifier));
|
|
599
737
|
function esmBlocker(fileName, packageType) {
|
|
600
738
|
if (/\.(mts|mjs)$/.test(fileName)) return void 0;
|
|
601
739
|
if (packageType === "module") return void 0;
|
|
602
740
|
return `sentinel is ESM only, and this module does not declare \`"type": "module"\`, so Vite would load ${fileName} as CommonJS and fail to require it. Add \`"type": "module"\` to this module's package.json, or rename the config to \`${fileName.replace(/\.(ts|js)$/, ".m$1")}\`, then run this again.`;
|
|
603
741
|
}
|
|
604
|
-
var
|
|
605
|
-
|
|
606
|
-
entry.specifier,
|
|
607
|
-
entry.default
|
|
608
|
-
])
|
|
742
|
+
var defaultExportNames = (toolchain) => Object.fromEntries(
|
|
743
|
+
toolchain.owned.filter((entry) => entry.default !== void 0).map((entry) => [entry.specifier, entry.default])
|
|
609
744
|
);
|
|
610
|
-
function renderImport(bindings) {
|
|
745
|
+
function renderImport(bindings, presetSpecifier) {
|
|
611
746
|
const render = (list) => list.map((b) => b.exported === b.local ? b.exported : `${b.exported} as ${b.local}`).sort((left, right) => left.localeCompare(right)).join(", ");
|
|
612
747
|
const values = bindings.filter((b) => !b.typeOnly);
|
|
613
748
|
const types = bindings.filter((b) => b.typeOnly);
|
|
614
749
|
const lines = [];
|
|
615
|
-
if (values.length > 0) lines.push(`import { ${render(values)} } from '${
|
|
750
|
+
if (values.length > 0) lines.push(`import { ${render(values)} } from '${presetSpecifier}'`);
|
|
616
751
|
if (types.length > 0) {
|
|
617
|
-
lines.push(`import type { ${render(types)} } from '${
|
|
752
|
+
lines.push(`import type { ${render(types)} } from '${presetSpecifier}'`);
|
|
618
753
|
}
|
|
619
754
|
return lines.join("\n");
|
|
620
755
|
}
|
|
621
|
-
function
|
|
756
|
+
function rewriteToolchainImports(toolchain, source, fileName) {
|
|
757
|
+
const moved = movedSpecifiers(toolchain);
|
|
758
|
+
const defaults = defaultExportNames(toolchain);
|
|
622
759
|
const { program, errors } = parseSync(fileName, source, { sourceType: "module" });
|
|
623
760
|
if (errors.length > 0) {
|
|
624
761
|
return {
|
|
@@ -629,10 +766,10 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
|
629
766
|
const imports = program.body.filter(
|
|
630
767
|
(node) => node.type === "ImportDeclaration"
|
|
631
768
|
);
|
|
632
|
-
if (imports.some((node) => node.source.value ===
|
|
769
|
+
if (imports.some((node) => node.source.value === toolchain.presetSpecifier)) {
|
|
633
770
|
return { kind: "unchanged", why: "this config already imports its toolchain from sentinel" };
|
|
634
771
|
}
|
|
635
|
-
const moving = imports.filter((node) =>
|
|
772
|
+
const moving = imports.filter((node) => moved.has(node.source.value));
|
|
636
773
|
if (moving.length === 0) {
|
|
637
774
|
return {
|
|
638
775
|
kind: "unchanged",
|
|
@@ -649,7 +786,7 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
|
649
786
|
for (const spec of specifiers) {
|
|
650
787
|
const typeOnly = node.importKind === "type" || spec.importKind === "type";
|
|
651
788
|
if (spec.type === "ImportDefaultSpecifier") {
|
|
652
|
-
const exported =
|
|
789
|
+
const exported = defaults[from];
|
|
653
790
|
if (!exported) {
|
|
654
791
|
return {
|
|
655
792
|
kind: "blocked",
|
|
@@ -681,7 +818,7 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
|
681
818
|
const after = out.slice(span.end).match(/^\r?\n/)?.[0].length ?? 0;
|
|
682
819
|
out = out.slice(0, span.start) + out.slice(span.end + after);
|
|
683
820
|
}
|
|
684
|
-
out = out.slice(0, first.start) + renderImport(bindings) + out.slice(first.end);
|
|
821
|
+
out = out.slice(0, first.start) + renderImport(bindings, toolchain.presetSpecifier) + out.slice(first.end);
|
|
685
822
|
return {
|
|
686
823
|
kind: "rewritten",
|
|
687
824
|
source: out,
|
|
@@ -689,7 +826,19 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
|
689
826
|
};
|
|
690
827
|
}
|
|
691
828
|
|
|
829
|
+
// src/roles/build/rewrite-imports.ts
|
|
830
|
+
var BUILD_TOOLCHAIN = {
|
|
831
|
+
presetSpecifier: BUILD_PRESET_SPECIFIER,
|
|
832
|
+
owned: OWNED_PACKAGES
|
|
833
|
+
};
|
|
834
|
+
function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
835
|
+
return rewriteToolchainImports(BUILD_TOOLCHAIN, source, fileName);
|
|
836
|
+
}
|
|
837
|
+
|
|
692
838
|
// src/roles/build/plan.ts
|
|
839
|
+
function candidateOutputs() {
|
|
840
|
+
return BUILD_OUTPUT_CANDIDATES.map((name) => `{projectRoot}/${name}`);
|
|
841
|
+
}
|
|
693
842
|
function ownedBuildDependencies(cwd) {
|
|
694
843
|
const manifest = readProjectPackageJson(cwd);
|
|
695
844
|
const keys = [];
|
|
@@ -718,9 +867,11 @@ function buildScripts(cwd) {
|
|
|
718
867
|
};
|
|
719
868
|
const existingDev = existingCommand(cwd, DEV_SCRIPT_NAME);
|
|
720
869
|
if (existingDev !== void 0) {
|
|
721
|
-
const own2 =
|
|
870
|
+
const own2 = moduleName(cwd);
|
|
722
871
|
scripts[DEV_SCRIPT_NAME] = composeDevScript(
|
|
723
872
|
existingDev,
|
|
873
|
+
// No trailing `--` here: `composeDevScript` owns the separator, and adding one on both
|
|
874
|
+
// sides produced `-- --`, handing Vite an argument nobody wrote.
|
|
724
875
|
`${selfCommand(cwd, "--run --dev") ?? SENTINEL_DEV_COMMAND} --module ${own2}`
|
|
725
876
|
);
|
|
726
877
|
}
|
|
@@ -735,54 +886,69 @@ function plan(context) {
|
|
|
735
886
|
if (unmet.length > 0) {
|
|
736
887
|
return { operations: [], skipped: describeUnmet(context.preset, unmet) };
|
|
737
888
|
}
|
|
738
|
-
|
|
889
|
+
const declaredPreset2 = declaredBuildPreset(context.cwd);
|
|
890
|
+
const configFile = buildConfigFile(context.cwd);
|
|
891
|
+
if (declaredPreset2 === void 0 && configFile !== void 0) {
|
|
892
|
+
return {
|
|
893
|
+
operations: [],
|
|
894
|
+
skipped: `this module has a Vite config, but it builds neither a React app (no @vitejs/plugin-react or @tanstack/react-start) nor a Nest service (no ${BUILD_PRESET_SPECIFIERS.nest}). Those are the two families this role serves.`
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
const webpackTarget = webpackBuildTarget(context.cwd);
|
|
898
|
+
if (declaredBuildPreset(context.cwd) === "nest" || webpackTarget !== void 0) {
|
|
899
|
+
return planNestService(context, webpackTarget);
|
|
900
|
+
}
|
|
901
|
+
if (configFile === void 0) {
|
|
739
902
|
return {
|
|
740
903
|
operations: [],
|
|
741
|
-
skipped: `this module
|
|
904
|
+
skipped: `no Vite config in this module, so there is nothing to point at sentinel. This role moves an existing config's imports; it does not introduce a bundler. If this module builds another way (38 services here build through \`@nx/webpack\`), the build role does not apply to it.`
|
|
742
905
|
};
|
|
743
906
|
}
|
|
907
|
+
return planReactApp(context, configFile);
|
|
908
|
+
}
|
|
909
|
+
function planReactApp(context, configFile) {
|
|
744
910
|
const notes = [];
|
|
745
911
|
const operations = [];
|
|
746
|
-
const
|
|
747
|
-
if (
|
|
912
|
+
const notEsm = esmBlocker(configFile, readProjectPackageJson(context.cwd).type);
|
|
913
|
+
if (notEsm !== void 0) return { operations: [], skipped: notEsm };
|
|
914
|
+
const rewrite = rewriteBuildImports(readConfigSource(context.cwd, configFile), configFile);
|
|
915
|
+
if (rewrite.kind === "blocked") {
|
|
748
916
|
return {
|
|
749
917
|
operations: [],
|
|
750
|
-
skipped:
|
|
918
|
+
skipped: `${configFile} could not be pointed at sentinel, so nothing was written: ${rewrite.why}. Nothing here is broken; this needs a look before the role applies.`
|
|
751
919
|
};
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
}
|
|
765
|
-
for (const name of COMPANION_CONFIG_FILES) {
|
|
766
|
-
const source = readConfigSource(context.cwd, name);
|
|
767
|
-
if (source === "") continue;
|
|
768
|
-
const companion = rewriteBuildImports(source, name);
|
|
769
|
-
if (companion.kind === "rewritten") {
|
|
770
|
-
operations.push({ kind: "write", path: name, contents: companion.source });
|
|
771
|
-
notes.push(`${name} now imports ${companion.moved.join(", ")} from sentinel too.`);
|
|
772
|
-
} else if (companion.kind === "blocked") {
|
|
773
|
-
notes.push(
|
|
774
|
-
`${name} imports the toolchain and could NOT be pointed at sentinel: ${companion.why}. It will stop resolving when the workspace root drops these packages, and it will fail your TESTS rather than your build.`
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
if (rewrite.kind === "rewritten") {
|
|
920
|
+
}
|
|
921
|
+
if (rewrite.kind === "rewritten") {
|
|
922
|
+
operations.push({ kind: "write", path: configFile, contents: rewrite.source });
|
|
923
|
+
}
|
|
924
|
+
for (const name of COMPANION_CONFIG_FILES) {
|
|
925
|
+
const source = readConfigSource(context.cwd, name);
|
|
926
|
+
if (source === "") continue;
|
|
927
|
+
const companion = rewriteBuildImports(source, name);
|
|
928
|
+
if (companion.kind === "rewritten") {
|
|
929
|
+
operations.push({ kind: "write", path: name, contents: companion.source });
|
|
930
|
+
notes.push(`${name} now imports ${companion.moved.join(", ")} from sentinel too.`);
|
|
931
|
+
} else if (companion.kind === "blocked") {
|
|
779
932
|
notes.push(
|
|
780
|
-
`${
|
|
933
|
+
`${name} imports the toolchain and could NOT be pointed at sentinel: ${companion.why}. It will stop resolving when the workspace root drops these packages, and it will fail your TESTS rather than your build.`
|
|
781
934
|
);
|
|
782
935
|
}
|
|
783
936
|
}
|
|
937
|
+
if (rewrite.kind === "rewritten") {
|
|
938
|
+
notes.push(
|
|
939
|
+
`${configFile} now imports ${rewrite.moved.join(", ")} from \`${BUILD_PRESET_SPECIFIER}\`. Only the import lines changed: every attribute, alias and path is untouched, so this build produces what it produced before.`
|
|
940
|
+
);
|
|
941
|
+
}
|
|
784
942
|
operations.push(manifestOperation(context.cwd, buildScripts(context.cwd)));
|
|
785
|
-
operations.push(
|
|
943
|
+
operations.push(
|
|
944
|
+
...nxTargetOperations({
|
|
945
|
+
cwd: context.cwd,
|
|
946
|
+
targets: buildTarget({
|
|
947
|
+
moduleName: adoptedPackageName(context.cwd),
|
|
948
|
+
outputs: candidateOutputs()
|
|
949
|
+
})
|
|
950
|
+
})
|
|
951
|
+
);
|
|
786
952
|
const owned = ownedBuildDependencies(context.cwd);
|
|
787
953
|
if (owned.length > 0) {
|
|
788
954
|
operations.push({ kind: "remove-json-keys", path: "package.json", keys: owned });
|
|
@@ -792,10 +958,44 @@ function plan(context) {
|
|
|
792
958
|
}
|
|
793
959
|
return { operations, notes };
|
|
794
960
|
}
|
|
961
|
+
function planNestService(context, webpackTarget) {
|
|
962
|
+
const outputs = webpackTarget === void 0 ? nestOutputsFromConfig(context.cwd) : nestBuildOutputs(webpackTarget);
|
|
963
|
+
if (outputs === void 0) {
|
|
964
|
+
return {
|
|
965
|
+
operations: [],
|
|
966
|
+
blocked: `${NEST_CONFIG_FILE} does not declare an \`outDir\`, so sentinel cannot say where this service's build lands. Declaring an nx target without it would cache the build and restore nothing from that cache. Nothing was written.`
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
const metadata = [
|
|
970
|
+
...nxTargetOperations({
|
|
971
|
+
cwd: context.cwd,
|
|
972
|
+
targets: buildTarget({
|
|
973
|
+
moduleName: adoptedPackageName(context.cwd),
|
|
974
|
+
configFiles: [NEST_CONFIG_FILE],
|
|
975
|
+
outputs
|
|
976
|
+
})
|
|
977
|
+
}),
|
|
978
|
+
manifestOperation(context.cwd, { [BUILD_SCRIPT_NAME]: SENTINEL_BUILD_COMMAND })
|
|
979
|
+
];
|
|
980
|
+
if (webpackTarget === void 0) return { operations: metadata };
|
|
981
|
+
const refusal = refusalFor(context.cwd, webpackTarget);
|
|
982
|
+
if (refusal !== void 0) return { operations: [], blocked: refusal };
|
|
983
|
+
const dropped = Object.keys(webpackTarget.configurations ?? {});
|
|
984
|
+
return {
|
|
985
|
+
operations: [...nestAdoptionOperations(context.cwd, webpackTarget), ...metadata],
|
|
986
|
+
notes: [
|
|
987
|
+
`wrote ${NEST_CONFIG_FILE} and pointed the nx build target at sentinel. The target stays \`nx:run-commands\` with \`forwardAllArgs: false\`, so the image's \`--generatePackageJson\` still works and the Dockerfile needs no change.`,
|
|
988
|
+
...dropped.length > 0 ? [
|
|
989
|
+
`dropped the \`${dropped.join("`, `")}\` configuration${dropped.length > 1 ? "s" : ""} from the build target: its fileReplacements point at files that do not exist, so it replaced nothing. A live one would have blocked this instead.`
|
|
990
|
+
] : [],
|
|
991
|
+
`verify with \`nx run ${webpackTarget.project}:generate-swagger-file\` and \`git diff --exit-code\`: it runs the built bundle, so it proves the service boots AND that its published contract did not move.`
|
|
992
|
+
]
|
|
993
|
+
};
|
|
994
|
+
}
|
|
795
995
|
function readConfigSource(cwd, configFile) {
|
|
796
|
-
if (!
|
|
996
|
+
if (!existsSync9(join9(cwd, configFile))) return "";
|
|
797
997
|
try {
|
|
798
|
-
return
|
|
998
|
+
return readFileSync7(join9(cwd, configFile), "utf8");
|
|
799
999
|
} catch {
|
|
800
1000
|
return "";
|
|
801
1001
|
}
|
|
@@ -835,11 +1035,17 @@ var ViteRoleAdapter = class extends BaseAdapter {
|
|
|
835
1035
|
* resolution let it run at all — filtering here would give "no adapter for preset svelte",
|
|
836
1036
|
* which tells nobody what to do about it.
|
|
837
1037
|
*
|
|
838
|
-
* Nest
|
|
839
|
-
*
|
|
1038
|
+
* Nest it now serves too, through `@hublo/sentinel/build/nest`. It used to be declined on the
|
|
1039
|
+
* grounds that "there is nothing to bundle", which was never true: those 38 services bundle
|
|
1040
|
+
* with webpack. What changed is that Nx v24 removes the `@nx/webpack:webpack` executor and
|
|
1041
|
+
* the `composePlugins` / `withNx` helpers their shared config is built on, and nx's own
|
|
1042
|
+
* migration generator refuses every one of them because they use `@nx/js:node`
|
|
1043
|
+
* (nrwl/nx#36389). So the way out runs through here.
|
|
1044
|
+
*
|
|
1045
|
+
* `node` and `tools` remain genuinely declined: nothing under them bundles at all.
|
|
840
1046
|
*/
|
|
841
1047
|
appliesTo(preset) {
|
|
842
|
-
return preset === "react" || preset === "svelte";
|
|
1048
|
+
return preset === "react" || preset === "svelte" || preset === "nest";
|
|
843
1049
|
}
|
|
844
1050
|
/**
|
|
845
1051
|
* `react`, read from the module's Vite config rather than from its dependencies.
|
|
@@ -892,60 +1098,279 @@ var ViteRoleAdapter = class extends BaseAdapter {
|
|
|
892
1098
|
`using the MODULE's vite, not sentinel's. An adopted config gets its plugins from sentinel, and two copies of Vite fail in ways that never mention a version. Remove vite from this module's package.json.`
|
|
893
1099
|
);
|
|
894
1100
|
}
|
|
895
|
-
return { kind: "ready", vite: bin };
|
|
1101
|
+
return { kind: "ready", vite: bin };
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Run Vite with the caller's own arguments after `--`, and report what happened.
|
|
1105
|
+
*
|
|
1106
|
+
* `whenSignalled` is the only difference between the two targets here, and it is a real one:
|
|
1107
|
+
* a build killed by a signal did not produce a bundle, while a dev server killed by Ctrl-C
|
|
1108
|
+
* did exactly what the developer asked.
|
|
1109
|
+
*/
|
|
1110
|
+
spawnVite(ctx, vite, argv, whenSignalled) {
|
|
1111
|
+
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
1112
|
+
valueFlags: VITE_VALUE_FLAGS
|
|
1113
|
+
});
|
|
1114
|
+
const result = spawnSync(vite, [...argv, ...options, ...paths], {
|
|
1115
|
+
cwd: ctx.cwd,
|
|
1116
|
+
stdio: "inherit"
|
|
1117
|
+
});
|
|
1118
|
+
if (result.error) {
|
|
1119
|
+
this.say(`could not run vite (${result.error.message})`);
|
|
1120
|
+
return { ok: false, code: 1 };
|
|
1121
|
+
}
|
|
1122
|
+
const code = result.status ?? whenSignalled;
|
|
1123
|
+
return { ok: code === 0, code };
|
|
1124
|
+
}
|
|
1125
|
+
/** One prefix for every message this role emits, so they cannot drift apart. */
|
|
1126
|
+
say(message) {
|
|
1127
|
+
process.stderr.write(`sentinel ${this.label}(vite): ${message}
|
|
1128
|
+
`);
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
// src/roles/build/adapters/vite/vite-dev.adapter.ts
|
|
1133
|
+
var ViteDevAdapter = class extends ViteRoleAdapter {
|
|
1134
|
+
target = "dev";
|
|
1135
|
+
get label() {
|
|
1136
|
+
return "dev";
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* `--init --dev` does the same thing as `--init --build`, and now says so.
|
|
1140
|
+
*
|
|
1141
|
+
* The shared plan is deliberate (see `ViteRoleAdapter.plan`): one plan writes both scripts,
|
|
1142
|
+
* because adopting one without the other leaves the module half-migrated. What was missing
|
|
1143
|
+
* was only the telling: the run printed a message about the build and nothing about `serve`,
|
|
1144
|
+
* so a developer could believe the dev server had been adopted on its own. Refusing was the
|
|
1145
|
+
* wrong correction, it added friction to a command that already produced the right result.
|
|
1146
|
+
*/
|
|
1147
|
+
plan(context) {
|
|
1148
|
+
if (declaredBuildPreset(context.cwd) === "nest" || webpackBuildTarget(context.cwd)) {
|
|
1149
|
+
return {
|
|
1150
|
+
operations: [],
|
|
1151
|
+
skipped: `a Nest service has no dev server, so --dev has nothing to adopt here. Its build is \`sentinel --init --build\`, and it runs as a Node process afterwards.`
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
const planned = super.plan(context);
|
|
1155
|
+
if (planned.operations.length === 0) return planned;
|
|
1156
|
+
return {
|
|
1157
|
+
...planned,
|
|
1158
|
+
notes: [
|
|
1159
|
+
...planned.notes ?? [],
|
|
1160
|
+
"--init --dev adopts the build too: one plan writes both `build` and `serve`."
|
|
1161
|
+
]
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Start the dev server. It does not return until stopped, so there is no verdict to report
|
|
1166
|
+
* beyond the exit code the developer's own Ctrl-C produces.
|
|
1167
|
+
*
|
|
1168
|
+
* No `prebuild` here, unlike `--run --build`: the artefact this repo generates before a build
|
|
1169
|
+
* is produced by the watcher that WRAPS this command (`run-with-runtime-artifact-watch`), and
|
|
1170
|
+
* running it again would race the watcher that is about to own the file.
|
|
1171
|
+
*
|
|
1172
|
+
* No subcommand either. `vite`, `vite dev` and `vite serve` all start the server, and the
|
|
1173
|
+
* bare form is the one every version accepts.
|
|
1174
|
+
*/
|
|
1175
|
+
async run(ctx) {
|
|
1176
|
+
const ready = this.prepare(ctx);
|
|
1177
|
+
if (ready.kind === "stop") return ready.result;
|
|
1178
|
+
return this.spawnVite(ctx, ready.vite, [], 0);
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
// src/roles/build/declared-outputs.ts
|
|
1183
|
+
import { existsSync as existsSync11, statSync } from "fs";
|
|
1184
|
+
import { join as join11 } from "path";
|
|
1185
|
+
|
|
1186
|
+
// src/core/workspace-prep.ts
|
|
1187
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync } from "fs";
|
|
1188
|
+
import { dirname as dirname3, join as join10, relative as relative2 } from "path";
|
|
1189
|
+
|
|
1190
|
+
// src/core/settings.ts
|
|
1191
|
+
var WORKSPACE_ROOT_MARKER = "nx.json";
|
|
1192
|
+
var DEFAULT_MAX_DIAGNOSTICS = 100;
|
|
1193
|
+
|
|
1194
|
+
// src/core/workspace-prep.ts
|
|
1195
|
+
var OVERRIDE_KEY = "i18next>typescript";
|
|
1196
|
+
var NATIVE_TS_ALIAS = "@typescript/native";
|
|
1197
|
+
var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
1198
|
+
var RELEASE_AGE_KEY = "minimumReleaseAgeExclude";
|
|
1199
|
+
var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
|
|
1200
|
+
function findWorkspaceRoot(startDir) {
|
|
1201
|
+
let dir = startDir;
|
|
1202
|
+
for (; ; ) {
|
|
1203
|
+
if (existsSync10(join10(dir, WORKSPACE_ROOT_MARKER))) return dir;
|
|
1204
|
+
const parent = dirname3(dir);
|
|
1205
|
+
if (parent === dir) return void 0;
|
|
1206
|
+
dir = parent;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
function declaredNativeTs(pkg) {
|
|
1210
|
+
const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS];
|
|
1211
|
+
if (!spec) return void 0;
|
|
1212
|
+
const version = spec.slice(spec.lastIndexOf("@") + 1).replace(/^[\^~>=<\s]+/, "");
|
|
1213
|
+
return version || void 0;
|
|
1214
|
+
}
|
|
1215
|
+
function ensureI18nextSingleton(root, dryRun) {
|
|
1216
|
+
const pkgPath = join10(root, "package.json");
|
|
1217
|
+
if (!existsSync10(pkgPath)) return void 0;
|
|
1218
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
1219
|
+
const want = declaredNativeTs(pkg);
|
|
1220
|
+
if (!want) return void 0;
|
|
1221
|
+
const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
1222
|
+
if (have === want) return void 0;
|
|
1223
|
+
if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} (i18next singleton)`;
|
|
1224
|
+
pkg.pnpm ??= {};
|
|
1225
|
+
pkg.pnpm.overrides ??= {};
|
|
1226
|
+
pkg.pnpm.overrides[OVERRIDE_KEY] = want;
|
|
1227
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1228
|
+
return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
|
|
1229
|
+
}
|
|
1230
|
+
function ensureReleaseAgeAllowList(root, dryRun) {
|
|
1231
|
+
const yamlPath = join10(root, WORKSPACE_YAML);
|
|
1232
|
+
if (!existsSync10(yamlPath)) return void 0;
|
|
1233
|
+
const own = readOwnPackage().name;
|
|
1234
|
+
const lines = readFileSync8(yamlPath, "utf8").split("\n");
|
|
1235
|
+
const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
|
|
1236
|
+
if (keyIdx === -1) return void 0;
|
|
1237
|
+
let lastItemIdx = keyIdx;
|
|
1238
|
+
let indent = " ";
|
|
1239
|
+
for (let i = keyIdx + 1; i < lines.length; i++) {
|
|
1240
|
+
const match = lines[i]?.match(LIST_ITEM);
|
|
1241
|
+
if (!match) break;
|
|
1242
|
+
indent = match[1] ?? indent;
|
|
1243
|
+
lastItemIdx = i;
|
|
1244
|
+
if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === own) return void 0;
|
|
1245
|
+
}
|
|
1246
|
+
if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`;
|
|
1247
|
+
lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`);
|
|
1248
|
+
writeFileSync(yamlPath, lines.join("\n"));
|
|
1249
|
+
return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`;
|
|
1250
|
+
}
|
|
1251
|
+
var PRETTIER_IGNORE = ".prettierignore";
|
|
1252
|
+
var EXCLUSION_HEADER = "# @hublo/sentinel: modules formatted by oxfmt, not by the root Prettier";
|
|
1253
|
+
var ROOT_PRETTIER_CONFIGS = [
|
|
1254
|
+
".prettierrc",
|
|
1255
|
+
".prettierrc.json",
|
|
1256
|
+
".prettierrc.json5",
|
|
1257
|
+
".prettierrc.yml",
|
|
1258
|
+
".prettierrc.yaml",
|
|
1259
|
+
".prettierrc.js",
|
|
1260
|
+
".prettierrc.cjs",
|
|
1261
|
+
".prettierrc.mjs",
|
|
1262
|
+
"prettier.config.js",
|
|
1263
|
+
"prettier.config.cjs",
|
|
1264
|
+
"prettier.config.mjs"
|
|
1265
|
+
];
|
|
1266
|
+
function ensureFormatterExclusion(root, moduleDir, dryRun) {
|
|
1267
|
+
const rel = relative2(root, moduleDir).replaceAll("\\", "/");
|
|
1268
|
+
if (rel === "" || rel.startsWith("..")) return void 0;
|
|
1269
|
+
const ignorePath = join10(root, PRETTIER_IGNORE);
|
|
1270
|
+
const hasPrettier = existsSync10(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync10(join10(root, name)));
|
|
1271
|
+
if (!hasPrettier) return void 0;
|
|
1272
|
+
const pattern = `/${rel}/`;
|
|
1273
|
+
const existing = existsSync10(ignorePath) ? readFileSync8(ignorePath, "utf8") : "";
|
|
1274
|
+
const lines = existing.split("\n");
|
|
1275
|
+
if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
|
|
1276
|
+
return void 0;
|
|
1277
|
+
}
|
|
1278
|
+
if (dryRun) return `would exclude ${rel} from the root Prettier (${PRETTIER_IGNORE})`;
|
|
1279
|
+
const headerIdx = lines.findIndex((line) => line.trim() === EXCLUSION_HEADER);
|
|
1280
|
+
if (headerIdx === -1) {
|
|
1281
|
+
const prefix = existing.trim() === "" ? "" : `${existing.replace(/\n*$/, "")}
|
|
1282
|
+
|
|
1283
|
+
`;
|
|
1284
|
+
writeFileSync(ignorePath, `${prefix}${EXCLUSION_HEADER}
|
|
1285
|
+
${pattern}
|
|
1286
|
+
`);
|
|
1287
|
+
} else {
|
|
1288
|
+
let end = headerIdx + 1;
|
|
1289
|
+
while (end < lines.length && lines[end]?.trim().startsWith("/")) end += 1;
|
|
1290
|
+
const managed = [...lines.slice(headerIdx + 1, end), pattern].sort();
|
|
1291
|
+
writeFileSync(
|
|
1292
|
+
ignorePath,
|
|
1293
|
+
[...lines.slice(0, headerIdx + 1), ...managed, ...lines.slice(end)].join("\n")
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
return `excluded ${rel} from the root Prettier (${PRETTIER_IGNORE}): it formats itself now`;
|
|
1297
|
+
}
|
|
1298
|
+
function ensureWorkspacePrep(opts) {
|
|
1299
|
+
const dryRun = Boolean(opts.dryRun);
|
|
1300
|
+
return [
|
|
1301
|
+
ensureI18nextSingleton(opts.root, dryRun),
|
|
1302
|
+
ensureReleaseAgeAllowList(opts.root, dryRun),
|
|
1303
|
+
opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
|
|
1304
|
+
].filter((message) => message !== void 0);
|
|
1305
|
+
}
|
|
1306
|
+
function inspectWorkspacePrep(root) {
|
|
1307
|
+
const entries = [];
|
|
1308
|
+
let pkg = {};
|
|
1309
|
+
const pkgPath = join10(root, "package.json");
|
|
1310
|
+
if (existsSync10(pkgPath)) {
|
|
1311
|
+
try {
|
|
1312
|
+
pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
1313
|
+
} catch {
|
|
1314
|
+
pkg = {};
|
|
1315
|
+
}
|
|
896
1316
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
*/
|
|
904
|
-
spawnVite(ctx, vite, argv, whenSignalled) {
|
|
905
|
-
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
906
|
-
valueFlags: VITE_VALUE_FLAGS
|
|
907
|
-
});
|
|
908
|
-
const result = spawnSync(vite, [...argv, ...options, ...paths], {
|
|
909
|
-
cwd: ctx.cwd,
|
|
910
|
-
stdio: "inherit"
|
|
1317
|
+
const pinned = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
1318
|
+
if (pinned !== void 0) {
|
|
1319
|
+
const stillForks = declaredNativeTs(pkg);
|
|
1320
|
+
entries.push({
|
|
1321
|
+
rule: `package.json \u2192 pnpm.overrides.${OVERRIDE_KEY}`,
|
|
1322
|
+
reason: stillForks === void 0 ? `pinned to ${pinned}, but the root no longer declares ${NATIVE_TS_ALIAS}, so nothing forks any more. This override is now dead config and can be removed.` : `pinned to ${pinned}, matching the second TypeScript the root declares (${NATIVE_TS_ALIAS}). It re-resolves i18next for EVERY module that depends on it, not only adopted ones. It retires with the second TypeScript.`
|
|
911
1323
|
});
|
|
912
|
-
if (result.error) {
|
|
913
|
-
this.say(`could not run vite (${result.error.message})`);
|
|
914
|
-
return { ok: false, code: 1 };
|
|
915
|
-
}
|
|
916
|
-
const code = result.status ?? whenSignalled;
|
|
917
|
-
return { ok: code === 0, code };
|
|
918
1324
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1325
|
+
const own = readOwnPackage().name;
|
|
1326
|
+
const yamlPath = join10(root, WORKSPACE_YAML);
|
|
1327
|
+
if (existsSync10(yamlPath)) {
|
|
1328
|
+
const yaml = readFileSync8(yamlPath, "utf8");
|
|
1329
|
+
const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
|
|
1330
|
+
if (listed) {
|
|
1331
|
+
const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
|
|
1332
|
+
entries.push({
|
|
1333
|
+
rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
|
|
1334
|
+
reason: gated ? `${own} is exempt from this workspace's release-age gate, so a freshly published version installs instead of being held. That is a standing exemption from a supply-chain control: the exposure is one bump wide, since the version is pinned and the lockfile carries its integrity, so a re-publish of the same version does not pass. Retire it once a bump can simply wait out the window.` : `${own} is allow-listed, but this workspace no longer sets minimumReleaseAge, so there is no gate to be exempt from. This entry is now dead config and can be removed.`
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
923
1337
|
}
|
|
924
|
-
|
|
1338
|
+
return entries;
|
|
1339
|
+
}
|
|
925
1340
|
|
|
926
|
-
// src/roles/build/
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1341
|
+
// src/roles/build/declared-outputs.ts
|
|
1342
|
+
function declaredBuildTarget(cwd) {
|
|
1343
|
+
return readProjectPackageJson(cwd).nx?.targets?.[BUILD_SCRIPT_NAME];
|
|
1344
|
+
}
|
|
1345
|
+
function resolveOutputToken(token, cwd) {
|
|
1346
|
+
if (token.startsWith("{projectRoot}/")) return join11(cwd, token.slice("{projectRoot}/".length));
|
|
1347
|
+
if (token.startsWith("{workspaceRoot}/")) {
|
|
1348
|
+
const root = findWorkspaceRoot(cwd);
|
|
1349
|
+
return root === void 0 ? void 0 : join11(root, token.slice("{workspaceRoot}/".length));
|
|
931
1350
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
* No subcommand either. `vite`, `vite dev` and `vite serve` all start the server, and the
|
|
941
|
-
* bare form is the one every version accepts.
|
|
942
|
-
*/
|
|
943
|
-
async run(ctx) {
|
|
944
|
-
const ready = this.prepare(ctx);
|
|
945
|
-
if (ready.kind === "stop") return ready.result;
|
|
946
|
-
return this.spawnVite(ctx, ready.vite, [], 0);
|
|
1351
|
+
return join11(cwd, token);
|
|
1352
|
+
}
|
|
1353
|
+
function writtenSince(path, startedAt) {
|
|
1354
|
+
if (!existsSync11(path)) return false;
|
|
1355
|
+
try {
|
|
1356
|
+
return statSync(path).mtimeMs >= startedAt;
|
|
1357
|
+
} catch {
|
|
1358
|
+
return true;
|
|
947
1359
|
}
|
|
948
|
-
}
|
|
1360
|
+
}
|
|
1361
|
+
function outputsProblem(cwd, startedAt) {
|
|
1362
|
+
const target = declaredBuildTarget(cwd);
|
|
1363
|
+
if (target === void 0) return void 0;
|
|
1364
|
+
const declared = target.outputs ?? [];
|
|
1365
|
+
if (declared.length === 0) {
|
|
1366
|
+
if (target.cache !== true) return void 0;
|
|
1367
|
+
return `this module's nx build target sets \`cache: true\` and declares no \`outputs\`, so nx will cache this build and restore nothing from it: the next cache hit reports success and leaves no build on disk. Add the directory this build writes to under \`nx.targets.${BUILD_SCRIPT_NAME}.outputs\` in package.json, or re-run \`sentinel --init --build\`, which declares it.`;
|
|
1368
|
+
}
|
|
1369
|
+
const resolved = declared.map((token) => ({ token, path: resolveOutputToken(token, cwd) })).filter((entry) => entry.path !== void 0);
|
|
1370
|
+
if (resolved.length === 0) return void 0;
|
|
1371
|
+
if (resolved.some((entry) => writtenSince(entry.path, startedAt))) return void 0;
|
|
1372
|
+
return `the build wrote nothing to any of the directories this module declares as its nx \`outputs\` (${declared.join(", ")}), so nx would cache this build and restore an empty result from it. Whatever directory the build DID write to belongs in \`nx.targets.${BUILD_SCRIPT_NAME}.outputs\` in package.json. The build itself succeeded; only its cache declaration is wrong.`;
|
|
1373
|
+
}
|
|
949
1374
|
|
|
950
1375
|
// src/roles/build/prerequisite.ts
|
|
951
1376
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -995,7 +1420,15 @@ var ViteAdapter = class extends ViteRoleAdapter {
|
|
|
995
1420
|
);
|
|
996
1421
|
return { ok: false, code: prerequisite.code };
|
|
997
1422
|
}
|
|
998
|
-
|
|
1423
|
+
const startedAt = Date.now();
|
|
1424
|
+
const result = await this.spawnVite(ctx, ready.vite, ["build"], 1);
|
|
1425
|
+
if (!result.ok) return result;
|
|
1426
|
+
const problem = outputsProblem(ctx.cwd, startedAt);
|
|
1427
|
+
if (problem !== void 0) {
|
|
1428
|
+
this.say(problem);
|
|
1429
|
+
return { ok: false, code: 1 };
|
|
1430
|
+
}
|
|
1431
|
+
return result;
|
|
999
1432
|
}
|
|
1000
1433
|
/**
|
|
1001
1434
|
* What this module builds with, WITHOUT loading its config.
|
|
@@ -1067,12 +1500,12 @@ function registerBuild() {
|
|
|
1067
1500
|
|
|
1068
1501
|
// src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
|
|
1069
1502
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1070
|
-
import { existsSync as
|
|
1071
|
-
import { join as
|
|
1503
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
1504
|
+
import { join as join17 } from "path";
|
|
1072
1505
|
|
|
1073
1506
|
// src/core/config/has-source.ts
|
|
1074
1507
|
import { readdirSync } from "fs";
|
|
1075
|
-
import { extname, join as
|
|
1508
|
+
import { extname, join as join12, relative as relative3 } from "path";
|
|
1076
1509
|
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1077
1510
|
"node_modules",
|
|
1078
1511
|
"dist",
|
|
@@ -1123,7 +1556,7 @@ function hasSourceFiles(cwd, extensions) {
|
|
|
1123
1556
|
}
|
|
1124
1557
|
for (const entry of entries) {
|
|
1125
1558
|
if (entry.isDirectory()) {
|
|
1126
|
-
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(
|
|
1559
|
+
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join12(dir, entry.name));
|
|
1127
1560
|
continue;
|
|
1128
1561
|
}
|
|
1129
1562
|
if (wanted.has(extname(entry.name))) return true;
|
|
@@ -1144,12 +1577,12 @@ function listSourceFiles(cwd, extensions) {
|
|
|
1144
1577
|
continue;
|
|
1145
1578
|
}
|
|
1146
1579
|
for (const entry of entries) {
|
|
1147
|
-
const full =
|
|
1580
|
+
const full = join12(dir, entry.name);
|
|
1148
1581
|
if (entry.isDirectory()) {
|
|
1149
1582
|
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
|
|
1150
1583
|
continue;
|
|
1151
1584
|
}
|
|
1152
|
-
if (wanted.has(extname(entry.name))) found.push(
|
|
1585
|
+
if (wanted.has(extname(entry.name))) found.push(relative3(cwd, full));
|
|
1153
1586
|
}
|
|
1154
1587
|
}
|
|
1155
1588
|
return found;
|
|
@@ -1157,161 +1590,9 @@ function listSourceFiles(cwd, extensions) {
|
|
|
1157
1590
|
|
|
1158
1591
|
// src/core/named-scope.ts
|
|
1159
1592
|
var TOLERATE_EMPTY_FLAG = "--no-error-on-unmatched-pattern";
|
|
1160
|
-
function scopeFlags(paths) {
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
// src/core/settings.ts
|
|
1165
|
-
var WORKSPACE_ROOT_MARKER = "nx.json";
|
|
1166
|
-
var DEFAULT_MAX_DIAGNOSTICS = 100;
|
|
1167
|
-
|
|
1168
|
-
// src/core/workspace-prep.ts
|
|
1169
|
-
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync } from "fs";
|
|
1170
|
-
import { dirname as dirname3, join as join9, relative as relative2 } from "path";
|
|
1171
|
-
var OVERRIDE_KEY = "i18next>typescript";
|
|
1172
|
-
var NATIVE_TS_ALIAS = "@typescript/native";
|
|
1173
|
-
var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
1174
|
-
var RELEASE_AGE_KEY = "minimumReleaseAgeExclude";
|
|
1175
|
-
var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
|
|
1176
|
-
function findWorkspaceRoot(startDir) {
|
|
1177
|
-
let dir = startDir;
|
|
1178
|
-
for (; ; ) {
|
|
1179
|
-
if (existsSync8(join9(dir, WORKSPACE_ROOT_MARKER))) return dir;
|
|
1180
|
-
const parent = dirname3(dir);
|
|
1181
|
-
if (parent === dir) return void 0;
|
|
1182
|
-
dir = parent;
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
function declaredNativeTs(pkg) {
|
|
1186
|
-
const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS];
|
|
1187
|
-
if (!spec) return void 0;
|
|
1188
|
-
const version = spec.slice(spec.lastIndexOf("@") + 1).replace(/^[\^~>=<\s]+/, "");
|
|
1189
|
-
return version || void 0;
|
|
1190
|
-
}
|
|
1191
|
-
function ensureI18nextSingleton(root, dryRun) {
|
|
1192
|
-
const pkgPath = join9(root, "package.json");
|
|
1193
|
-
if (!existsSync8(pkgPath)) return void 0;
|
|
1194
|
-
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
1195
|
-
const want = declaredNativeTs(pkg);
|
|
1196
|
-
if (!want) return void 0;
|
|
1197
|
-
const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
1198
|
-
if (have === want) return void 0;
|
|
1199
|
-
if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} (i18next singleton)`;
|
|
1200
|
-
pkg.pnpm ??= {};
|
|
1201
|
-
pkg.pnpm.overrides ??= {};
|
|
1202
|
-
pkg.pnpm.overrides[OVERRIDE_KEY] = want;
|
|
1203
|
-
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1204
|
-
return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
|
|
1205
|
-
}
|
|
1206
|
-
function ensureReleaseAgeAllowList(root, dryRun) {
|
|
1207
|
-
const yamlPath = join9(root, WORKSPACE_YAML);
|
|
1208
|
-
if (!existsSync8(yamlPath)) return void 0;
|
|
1209
|
-
const own = readOwnPackage().name;
|
|
1210
|
-
const lines = readFileSync6(yamlPath, "utf8").split("\n");
|
|
1211
|
-
const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
|
|
1212
|
-
if (keyIdx === -1) return void 0;
|
|
1213
|
-
let lastItemIdx = keyIdx;
|
|
1214
|
-
let indent = " ";
|
|
1215
|
-
for (let i = keyIdx + 1; i < lines.length; i++) {
|
|
1216
|
-
const match = lines[i]?.match(LIST_ITEM);
|
|
1217
|
-
if (!match) break;
|
|
1218
|
-
indent = match[1] ?? indent;
|
|
1219
|
-
lastItemIdx = i;
|
|
1220
|
-
if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === own) return void 0;
|
|
1221
|
-
}
|
|
1222
|
-
if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`;
|
|
1223
|
-
lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`);
|
|
1224
|
-
writeFileSync(yamlPath, lines.join("\n"));
|
|
1225
|
-
return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`;
|
|
1226
|
-
}
|
|
1227
|
-
var PRETTIER_IGNORE = ".prettierignore";
|
|
1228
|
-
var EXCLUSION_HEADER = "# @hublo/sentinel: modules formatted by oxfmt, not by the root Prettier";
|
|
1229
|
-
var ROOT_PRETTIER_CONFIGS = [
|
|
1230
|
-
".prettierrc",
|
|
1231
|
-
".prettierrc.json",
|
|
1232
|
-
".prettierrc.json5",
|
|
1233
|
-
".prettierrc.yml",
|
|
1234
|
-
".prettierrc.yaml",
|
|
1235
|
-
".prettierrc.js",
|
|
1236
|
-
".prettierrc.cjs",
|
|
1237
|
-
".prettierrc.mjs",
|
|
1238
|
-
"prettier.config.js",
|
|
1239
|
-
"prettier.config.cjs",
|
|
1240
|
-
"prettier.config.mjs"
|
|
1241
|
-
];
|
|
1242
|
-
function ensureFormatterExclusion(root, moduleDir, dryRun) {
|
|
1243
|
-
const rel = relative2(root, moduleDir).replaceAll("\\", "/");
|
|
1244
|
-
if (rel === "" || rel.startsWith("..")) return void 0;
|
|
1245
|
-
const ignorePath = join9(root, PRETTIER_IGNORE);
|
|
1246
|
-
const hasPrettier = existsSync8(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync8(join9(root, name)));
|
|
1247
|
-
if (!hasPrettier) return void 0;
|
|
1248
|
-
const pattern = `/${rel}/`;
|
|
1249
|
-
const existing = existsSync8(ignorePath) ? readFileSync6(ignorePath, "utf8") : "";
|
|
1250
|
-
const lines = existing.split("\n");
|
|
1251
|
-
if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
|
|
1252
|
-
return void 0;
|
|
1253
|
-
}
|
|
1254
|
-
if (dryRun) return `would exclude ${rel} from the root Prettier (${PRETTIER_IGNORE})`;
|
|
1255
|
-
const headerIdx = lines.findIndex((line) => line.trim() === EXCLUSION_HEADER);
|
|
1256
|
-
if (headerIdx === -1) {
|
|
1257
|
-
const prefix = existing.trim() === "" ? "" : `${existing.replace(/\n*$/, "")}
|
|
1258
|
-
|
|
1259
|
-
`;
|
|
1260
|
-
writeFileSync(ignorePath, `${prefix}${EXCLUSION_HEADER}
|
|
1261
|
-
${pattern}
|
|
1262
|
-
`);
|
|
1263
|
-
} else {
|
|
1264
|
-
let end = headerIdx + 1;
|
|
1265
|
-
while (end < lines.length && lines[end]?.trim().startsWith("/")) end += 1;
|
|
1266
|
-
const managed = [...lines.slice(headerIdx + 1, end), pattern].sort();
|
|
1267
|
-
writeFileSync(
|
|
1268
|
-
ignorePath,
|
|
1269
|
-
[...lines.slice(0, headerIdx + 1), ...managed, ...lines.slice(end)].join("\n")
|
|
1270
|
-
);
|
|
1271
|
-
}
|
|
1272
|
-
return `excluded ${rel} from the root Prettier (${PRETTIER_IGNORE}): it formats itself now`;
|
|
1273
|
-
}
|
|
1274
|
-
function ensureWorkspacePrep(opts) {
|
|
1275
|
-
const dryRun = Boolean(opts.dryRun);
|
|
1276
|
-
return [
|
|
1277
|
-
ensureI18nextSingleton(opts.root, dryRun),
|
|
1278
|
-
ensureReleaseAgeAllowList(opts.root, dryRun),
|
|
1279
|
-
opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
|
|
1280
|
-
].filter((message) => message !== void 0);
|
|
1281
|
-
}
|
|
1282
|
-
function inspectWorkspacePrep(root) {
|
|
1283
|
-
const entries = [];
|
|
1284
|
-
let pkg = {};
|
|
1285
|
-
const pkgPath = join9(root, "package.json");
|
|
1286
|
-
if (existsSync8(pkgPath)) {
|
|
1287
|
-
try {
|
|
1288
|
-
pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
1289
|
-
} catch {
|
|
1290
|
-
pkg = {};
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
const pinned = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
1294
|
-
if (pinned !== void 0) {
|
|
1295
|
-
const stillForks = declaredNativeTs(pkg);
|
|
1296
|
-
entries.push({
|
|
1297
|
-
rule: `package.json \u2192 pnpm.overrides.${OVERRIDE_KEY}`,
|
|
1298
|
-
reason: stillForks === void 0 ? `pinned to ${pinned}, but the root no longer declares ${NATIVE_TS_ALIAS}, so nothing forks any more. This override is now dead config and can be removed.` : `pinned to ${pinned}, matching the second TypeScript the root declares (${NATIVE_TS_ALIAS}). It re-resolves i18next for EVERY module that depends on it, not only adopted ones. It retires with the second TypeScript.`
|
|
1299
|
-
});
|
|
1300
|
-
}
|
|
1301
|
-
const own = readOwnPackage().name;
|
|
1302
|
-
const yamlPath = join9(root, WORKSPACE_YAML);
|
|
1303
|
-
if (existsSync8(yamlPath)) {
|
|
1304
|
-
const yaml = readFileSync6(yamlPath, "utf8");
|
|
1305
|
-
const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
|
|
1306
|
-
if (listed) {
|
|
1307
|
-
const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
|
|
1308
|
-
entries.push({
|
|
1309
|
-
rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
|
|
1310
|
-
reason: gated ? `${own} is exempt from this workspace's release-age gate, so a freshly published version installs instead of being held. That is a standing exemption from a supply-chain control: the exposure is one bump wide, since the version is pinned and the lockfile carries its integrity, so a re-publish of the same version does not pass. Retire it once a bump can simply wait out the window.` : `${own} is allow-listed, but this workspace no longer sets minimumReleaseAge, so there is no gate to be exempt from. This entry is now dead config and can be removed.`
|
|
1311
|
-
});
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
return entries;
|
|
1593
|
+
function scopeFlags(paths, options = []) {
|
|
1594
|
+
if (paths.length === 0 || options.includes(TOLERATE_EMPTY_FLAG)) return [];
|
|
1595
|
+
return [TOLERATE_EMPTY_FLAG];
|
|
1315
1596
|
}
|
|
1316
1597
|
|
|
1317
1598
|
// src/roles/format/config-policy.ts
|
|
@@ -1388,8 +1669,8 @@ function writesWhenRewritten(name, command) {
|
|
|
1388
1669
|
var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
|
|
1389
1670
|
|
|
1390
1671
|
// src/roles/format/inherited-ignores.ts
|
|
1391
|
-
import { existsSync as
|
|
1392
|
-
import { join as
|
|
1672
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
1673
|
+
import { join as join13 } from "path";
|
|
1393
1674
|
var ROOT_IGNORE_FILE = ".prettierignore";
|
|
1394
1675
|
function isPattern(line) {
|
|
1395
1676
|
const trimmed = line.trim();
|
|
@@ -1404,11 +1685,11 @@ function toModulePattern(pattern) {
|
|
|
1404
1685
|
}
|
|
1405
1686
|
function inheritedIgnorePatterns(workspaceRoot) {
|
|
1406
1687
|
if (!workspaceRoot) return [];
|
|
1407
|
-
const path =
|
|
1408
|
-
if (!
|
|
1688
|
+
const path = join13(workspaceRoot, ROOT_IGNORE_FILE);
|
|
1689
|
+
if (!existsSync12(path)) return [];
|
|
1409
1690
|
let contents;
|
|
1410
1691
|
try {
|
|
1411
|
-
contents =
|
|
1692
|
+
contents = readFileSync9(path, "utf8");
|
|
1412
1693
|
} catch {
|
|
1413
1694
|
return [];
|
|
1414
1695
|
}
|
|
@@ -1495,13 +1776,13 @@ function formatPresetFor(preset) {
|
|
|
1495
1776
|
}
|
|
1496
1777
|
|
|
1497
1778
|
// src/roles/format/prettier-config.ts
|
|
1498
|
-
import { existsSync as
|
|
1499
|
-
import { join as
|
|
1779
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
1780
|
+
import { join as join15 } from "path";
|
|
1500
1781
|
|
|
1501
1782
|
// src/roles/format/resolve-oxfmt.ts
|
|
1502
|
-
import { readFileSync as
|
|
1783
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
1503
1784
|
import { createRequire as createRequire2 } from "module";
|
|
1504
|
-
import { dirname as dirname4, join as
|
|
1785
|
+
import { dirname as dirname4, join as join14 } from "path";
|
|
1505
1786
|
function resolveOxfmt(cwd) {
|
|
1506
1787
|
return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
|
|
1507
1788
|
}
|
|
@@ -1530,8 +1811,8 @@ function configSchema() {
|
|
|
1530
1811
|
function readConfigSchema() {
|
|
1531
1812
|
try {
|
|
1532
1813
|
const manifest = createRequire2(import.meta.url).resolve("oxfmt/package.json");
|
|
1533
|
-
const schemaPath =
|
|
1534
|
-
return JSON.parse(
|
|
1814
|
+
const schemaPath = join14(dirname4(manifest), "configuration_schema.json");
|
|
1815
|
+
return JSON.parse(readFileSync10(schemaPath, "utf8"));
|
|
1535
1816
|
} catch {
|
|
1536
1817
|
return void 0;
|
|
1537
1818
|
}
|
|
@@ -1562,14 +1843,14 @@ function toOxfmtOverrides(value) {
|
|
|
1562
1843
|
return { overrides, unresolved };
|
|
1563
1844
|
}
|
|
1564
1845
|
function readPrettierSettings(cwd) {
|
|
1565
|
-
const file = PRETTIER_CONFIG_FILES.find((name) =>
|
|
1846
|
+
const file = PRETTIER_CONFIG_FILES.find((name) => existsSync13(join15(cwd, name)));
|
|
1566
1847
|
if (!file) return { options: {}, unresolved: [] };
|
|
1567
1848
|
if (/\.(js|cjs|mjs)$/.test(file)) {
|
|
1568
1849
|
return { options: {}, file, unresolved: [`${file} is JavaScript, so it was not executed`] };
|
|
1569
1850
|
}
|
|
1570
1851
|
let parsed;
|
|
1571
1852
|
try {
|
|
1572
|
-
parsed = parseJsonc(
|
|
1853
|
+
parsed = parseJsonc(readFileSync11(join15(cwd, file), "utf8"), file);
|
|
1573
1854
|
} catch {
|
|
1574
1855
|
return { options: {}, file, unresolved: [`${file} could not be parsed`] };
|
|
1575
1856
|
}
|
|
@@ -1599,8 +1880,8 @@ function readPrettierSettings(cwd) {
|
|
|
1599
1880
|
}
|
|
1600
1881
|
|
|
1601
1882
|
// src/roles/format/read-adoption.ts
|
|
1602
|
-
import { existsSync as
|
|
1603
|
-
import { join as
|
|
1883
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
1884
|
+
import { join as join16 } from "path";
|
|
1604
1885
|
var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
|
|
1605
1886
|
configFile,
|
|
1606
1887
|
preset: null,
|
|
@@ -1616,11 +1897,11 @@ function sameValue(a, b) {
|
|
|
1616
1897
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
1617
1898
|
}
|
|
1618
1899
|
function readFormatAdoption(cwd) {
|
|
1619
|
-
const path =
|
|
1620
|
-
if (!
|
|
1900
|
+
const path = join16(cwd, FORMAT_CONFIG_FILE);
|
|
1901
|
+
if (!existsSync14(path)) return NOT_ADOPTED2(null);
|
|
1621
1902
|
let parsed;
|
|
1622
1903
|
try {
|
|
1623
|
-
parsed = parseJsonc(
|
|
1904
|
+
parsed = parseJsonc(readFileSync12(path, "utf8"), FORMAT_CONFIG_FILE);
|
|
1624
1905
|
} catch (error) {
|
|
1625
1906
|
const reason = error instanceof Error ? error.message : String(error);
|
|
1626
1907
|
return NOT_ADOPTED2(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
|
|
@@ -1745,7 +2026,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1745
2026
|
manifestOperation(context.cwd, this.formatScripts(context.cwd))
|
|
1746
2027
|
];
|
|
1747
2028
|
const prettierConfigs = PRETTIER_CONFIG_FILES.filter(
|
|
1748
|
-
(name) =>
|
|
2029
|
+
(name) => existsSync15(join17(context.cwd, name))
|
|
1749
2030
|
);
|
|
1750
2031
|
for (const name of prettierConfigs) operations.push({ kind: "delete", path: name });
|
|
1751
2032
|
const removableDeps = this.modulePrettierDependencies(context.cwd);
|
|
@@ -1869,7 +2150,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1869
2150
|
});
|
|
1870
2151
|
const result = spawnSync3(
|
|
1871
2152
|
oxfmt,
|
|
1872
|
-
[mode, ...scopeFlags(paths), ...options, ...paths.length > 0 ? paths : ["."]],
|
|
2153
|
+
[mode, ...scopeFlags(paths, options), ...options, ...paths.length > 0 ? paths : ["."]],
|
|
1873
2154
|
{
|
|
1874
2155
|
cwd: ctx.cwd,
|
|
1875
2156
|
stdio: "inherit"
|
|
@@ -1985,7 +2266,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1985
2266
|
modulePrettierDependencies(cwd) {
|
|
1986
2267
|
const isPrettierPackage = (name) => name === "prettier" || name.startsWith("prettier-plugin-") || name.startsWith("@prettier/");
|
|
1987
2268
|
try {
|
|
1988
|
-
const manifest = JSON.parse(
|
|
2269
|
+
const manifest = JSON.parse(readFileSync13(join17(cwd, "package.json"), "utf8"));
|
|
1989
2270
|
return Object.keys(manifest.devDependencies ?? {}).filter(isPrettierPackage).map((name) => ["devDependencies", name]);
|
|
1990
2271
|
} catch {
|
|
1991
2272
|
return [];
|
|
@@ -2067,8 +2348,8 @@ function registerFormat() {
|
|
|
2067
2348
|
|
|
2068
2349
|
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
2069
2350
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
2070
|
-
import { existsSync as
|
|
2071
|
-
import { join as
|
|
2351
|
+
import { existsSync as existsSync23, readFileSync as readFileSync21, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
2352
|
+
import { join as join27 } from "path";
|
|
2072
2353
|
|
|
2073
2354
|
// src/core/config/deferred-rules.ts
|
|
2074
2355
|
function deferredRuleNames(rules) {
|
|
@@ -2076,8 +2357,8 @@ function deferredRuleNames(rules) {
|
|
|
2076
2357
|
}
|
|
2077
2358
|
|
|
2078
2359
|
// src/core/fix-report.ts
|
|
2079
|
-
import { statSync } from "fs";
|
|
2080
|
-
import { join as
|
|
2360
|
+
import { statSync as statSync2 } from "fs";
|
|
2361
|
+
import { join as join18 } from "path";
|
|
2081
2362
|
var CONFIG_REFUSED = /failed to parse .*config|invalid config file/i;
|
|
2082
2363
|
function readFailure(output) {
|
|
2083
2364
|
const lines = output.split("\n").map((line2) => line2.trim());
|
|
@@ -2088,7 +2369,7 @@ function readFailure(output) {
|
|
|
2088
2369
|
}
|
|
2089
2370
|
function fingerprint(path) {
|
|
2090
2371
|
try {
|
|
2091
|
-
const stat =
|
|
2372
|
+
const stat = statSync2(path);
|
|
2092
2373
|
return `${stat.size}:${stat.mtimeMs}`;
|
|
2093
2374
|
} catch {
|
|
2094
2375
|
return void 0;
|
|
@@ -2097,7 +2378,7 @@ function fingerprint(path) {
|
|
|
2097
2378
|
function snapshotFiles(cwd, extensions) {
|
|
2098
2379
|
const snapshot = /* @__PURE__ */ new Map();
|
|
2099
2380
|
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2100
|
-
const mark = fingerprint(
|
|
2381
|
+
const mark = fingerprint(join18(cwd, file));
|
|
2101
2382
|
if (mark !== void 0) snapshot.set(file, mark);
|
|
2102
2383
|
}
|
|
2103
2384
|
return snapshot;
|
|
@@ -2105,7 +2386,7 @@ function snapshotFiles(cwd, extensions) {
|
|
|
2105
2386
|
function changedSince(cwd, before, extensions) {
|
|
2106
2387
|
const changed = [];
|
|
2107
2388
|
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2108
|
-
const now = fingerprint(
|
|
2389
|
+
const now = fingerprint(join18(cwd, file));
|
|
2109
2390
|
if (now === void 0) continue;
|
|
2110
2391
|
if (before.get(file) !== now) changed.push(file);
|
|
2111
2392
|
}
|
|
@@ -2124,8 +2405,8 @@ function describeFixOutcome(outcome, toolLabel) {
|
|
|
2124
2405
|
}
|
|
2125
2406
|
|
|
2126
2407
|
// src/roles/lint/module-baseline.ts
|
|
2127
|
-
import { existsSync as
|
|
2128
|
-
import { join as
|
|
2408
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
2409
|
+
import { join as join19 } from "path";
|
|
2129
2410
|
var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
|
|
2130
2411
|
var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
|
|
2131
2412
|
var LINT_BASELINE_SPECIFIER = `./${LINT_BASELINE_FILE}`;
|
|
@@ -2169,11 +2450,11 @@ function describeHolds(holds) {
|
|
|
2169
2450
|
return `held ${holds.length} rule(s) at \`warn\` for this module, covering ${total} violation(s) that were already there: ${listed}${rest}. They are written to ${LINT_BASELINE_FILE}, they still report, and they return to \`error\` once fixed and re-initialized`;
|
|
2170
2451
|
}
|
|
2171
2452
|
function heldRules(cwd) {
|
|
2172
|
-
const path =
|
|
2173
|
-
if (!
|
|
2453
|
+
const path = join19(cwd, LINT_BASELINE_FILE);
|
|
2454
|
+
if (!existsSync16(path)) return [];
|
|
2174
2455
|
try {
|
|
2175
2456
|
const parsed = parseJsonc(
|
|
2176
|
-
|
|
2457
|
+
readFileSync14(path, "utf8"),
|
|
2177
2458
|
LINT_BASELINE_FILE
|
|
2178
2459
|
);
|
|
2179
2460
|
return Object.keys(parsed.rules ?? {});
|
|
@@ -3996,14 +4277,14 @@ function downgradedRulesFor(preset) {
|
|
|
3996
4277
|
}
|
|
3997
4278
|
|
|
3998
4279
|
// src/roles/lint/extra-layers.ts
|
|
3999
|
-
import { existsSync as
|
|
4000
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4280
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
|
|
4281
|
+
import { isAbsolute as isAbsolute2, join as join20, resolve as resolve3 } from "path";
|
|
4001
4282
|
function ruleCount(cwd, specifier) {
|
|
4002
4283
|
const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
|
|
4003
|
-
if (!
|
|
4284
|
+
if (!existsSync17(path)) return void 0;
|
|
4004
4285
|
try {
|
|
4005
4286
|
const parsed = parseJsonc(
|
|
4006
|
-
|
|
4287
|
+
readFileSync15(path, "utf8"),
|
|
4007
4288
|
specifier
|
|
4008
4289
|
);
|
|
4009
4290
|
return Object.keys(parsed.rules ?? {}).length;
|
|
@@ -4023,7 +4304,7 @@ function extraLayers(cwd, extendsList) {
|
|
|
4023
4304
|
function committedExtendsList(cwd, configFile) {
|
|
4024
4305
|
try {
|
|
4025
4306
|
const parsed = parseJsonc(
|
|
4026
|
-
|
|
4307
|
+
readFileSync15(join20(cwd, configFile), "utf8"),
|
|
4027
4308
|
configFile
|
|
4028
4309
|
);
|
|
4029
4310
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4082,8 +4363,8 @@ function errorCountsByConfigRule(stdout) {
|
|
|
4082
4363
|
}
|
|
4083
4364
|
|
|
4084
4365
|
// src/core/config/read-adoption.ts
|
|
4085
|
-
import { existsSync as
|
|
4086
|
-
import { join as
|
|
4366
|
+
import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
|
|
4367
|
+
import { join as join22 } from "path";
|
|
4087
4368
|
|
|
4088
4369
|
// src/core/config/owned-keys.ts
|
|
4089
4370
|
function presetOwnedKeys(config, permitted, presetSets) {
|
|
@@ -4098,12 +4379,12 @@ function localOnlyKeys(config, permitted, presetSets) {
|
|
|
4098
4379
|
}
|
|
4099
4380
|
|
|
4100
4381
|
// src/core/config/resolve-config-target.ts
|
|
4101
|
-
import { existsSync as
|
|
4102
|
-
import { join as
|
|
4382
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
4383
|
+
import { join as join21 } from "path";
|
|
4103
4384
|
function readExtends(absolutePath) {
|
|
4104
4385
|
let parsed;
|
|
4105
4386
|
try {
|
|
4106
|
-
parsed = parseJsonc(
|
|
4387
|
+
parsed = parseJsonc(readFileSync16(absolutePath, "utf8"), absolutePath);
|
|
4107
4388
|
} catch {
|
|
4108
4389
|
return [];
|
|
4109
4390
|
}
|
|
@@ -4117,8 +4398,8 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
|
|
|
4117
4398
|
let existing;
|
|
4118
4399
|
let existingExtendsSomething = false;
|
|
4119
4400
|
for (const candidate of candidates) {
|
|
4120
|
-
const absolutePath =
|
|
4121
|
-
if (!
|
|
4401
|
+
const absolutePath = join21(moduleDir, candidate);
|
|
4402
|
+
if (!existsSync18(absolutePath)) continue;
|
|
4122
4403
|
const chain = readExtends(absolutePath);
|
|
4123
4404
|
if (existing === void 0) {
|
|
4124
4405
|
existing = candidate;
|
|
@@ -4151,12 +4432,12 @@ var NOT_ADOPTED3 = (configFile, unreadable = null) => ({
|
|
|
4151
4432
|
});
|
|
4152
4433
|
function readAdoption(cwd, options) {
|
|
4153
4434
|
const target = resolveConfigTarget(cwd, options);
|
|
4154
|
-
if (target.reason === "none" || !
|
|
4435
|
+
if (target.reason === "none" || !existsSync19(join22(cwd, target.path))) {
|
|
4155
4436
|
return NOT_ADOPTED3(target.reason === "none" ? null : target.path);
|
|
4156
4437
|
}
|
|
4157
4438
|
let parsed;
|
|
4158
4439
|
try {
|
|
4159
|
-
parsed = parseJsonc(
|
|
4440
|
+
parsed = parseJsonc(readFileSync17(join22(cwd, target.path), "utf8"), target.path);
|
|
4160
4441
|
} catch (error) {
|
|
4161
4442
|
const reason = error instanceof Error ? error.message : String(error);
|
|
4162
4443
|
return NOT_ADOPTED3(target.path, `${target.path} could not be parsed (${reason})`);
|
|
@@ -4190,9 +4471,9 @@ function readLintAdoption(cwd) {
|
|
|
4190
4471
|
}
|
|
4191
4472
|
|
|
4192
4473
|
// src/roles/lint/resolve-oxlint.ts
|
|
4193
|
-
import { existsSync as
|
|
4474
|
+
import { existsSync as existsSync20 } from "fs";
|
|
4194
4475
|
import { createRequire as createRequire3 } from "module";
|
|
4195
|
-
import { delimiter as delimiter2, dirname as dirname5, join as
|
|
4476
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join23 } from "path";
|
|
4196
4477
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4197
4478
|
var PACKAGE_OF = {
|
|
4198
4479
|
oxlint: "oxlint",
|
|
@@ -4217,30 +4498,30 @@ function tsgolintShim(cwd) {
|
|
|
4217
4498
|
for (const owner of ["oxlint-tsgolint", "oxlint"]) {
|
|
4218
4499
|
try {
|
|
4219
4500
|
const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
|
|
4220
|
-
candidates.push(
|
|
4221
|
-
candidates.push(
|
|
4501
|
+
candidates.push(join23(packageDir, "node_modules", ".bin", "tsgolint"));
|
|
4502
|
+
candidates.push(join23(packageDir, "..", ".bin", "tsgolint"));
|
|
4222
4503
|
} catch {
|
|
4223
4504
|
}
|
|
4224
4505
|
}
|
|
4225
4506
|
try {
|
|
4226
4507
|
const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
|
|
4227
|
-
candidates.push(
|
|
4508
|
+
candidates.push(join23(ownRoot, "node_modules", ".bin", "tsgolint"));
|
|
4228
4509
|
} catch {
|
|
4229
4510
|
candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
|
|
4230
4511
|
}
|
|
4231
|
-
return candidates.find((candidate) => candidate !== "" &&
|
|
4512
|
+
return candidates.find((candidate) => candidate !== "" && existsSync20(candidate));
|
|
4232
4513
|
}
|
|
4233
4514
|
function oxlintSearchPath(cwd) {
|
|
4234
4515
|
return binSearchPath(cwd);
|
|
4235
4516
|
}
|
|
4236
4517
|
|
|
4237
4518
|
// src/roles/lint/adapters/oxlint/plan.ts
|
|
4238
|
-
import { existsSync as
|
|
4239
|
-
import { join as
|
|
4519
|
+
import { existsSync as existsSync22, readFileSync as readFileSync20 } from "fs";
|
|
4520
|
+
import { join as join26 } from "path";
|
|
4240
4521
|
|
|
4241
4522
|
// src/roles/lint/eslint-ignores.ts
|
|
4242
|
-
import { existsSync as
|
|
4243
|
-
import { join as
|
|
4523
|
+
import { existsSync as existsSync21, readFileSync as readFileSync18 } from "fs";
|
|
4524
|
+
import { join as join24 } from "path";
|
|
4244
4525
|
import { parseSync as parseSync2 } from "oxc-parser";
|
|
4245
4526
|
var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
|
|
4246
4527
|
function toOxlintPattern(pattern) {
|
|
@@ -4252,11 +4533,11 @@ function readRootEslintIgnores(root) {
|
|
|
4252
4533
|
return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
|
|
4253
4534
|
}
|
|
4254
4535
|
function readEslintIgnores(cwd) {
|
|
4255
|
-
const config = ESLINT_CONFIG_FILES.map((name) =>
|
|
4536
|
+
const config = ESLINT_CONFIG_FILES.map((name) => join24(cwd, name)).find((path) => existsSync21(path));
|
|
4256
4537
|
if (!config) return { patterns: [], unresolved: [] };
|
|
4257
4538
|
let source;
|
|
4258
4539
|
try {
|
|
4259
|
-
source =
|
|
4540
|
+
source = readFileSync18(config, "utf8");
|
|
4260
4541
|
} catch {
|
|
4261
4542
|
return { patterns: [], unresolved: [] };
|
|
4262
4543
|
}
|
|
@@ -4357,8 +4638,8 @@ function lintPresetFor(preset) {
|
|
|
4357
4638
|
}
|
|
4358
4639
|
|
|
4359
4640
|
// src/roles/lint/rename-suppressions.ts
|
|
4360
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
4361
|
-
import { join as
|
|
4641
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync19, statSync as statSync3 } from "fs";
|
|
4642
|
+
import { join as join25, relative as relative4 } from "path";
|
|
4362
4643
|
var SOURCE_EXTENSIONS = [
|
|
4363
4644
|
".ts",
|
|
4364
4645
|
".tsx",
|
|
@@ -4405,10 +4686,10 @@ function* sourceFiles(dir) {
|
|
|
4405
4686
|
return;
|
|
4406
4687
|
}
|
|
4407
4688
|
for (const entry of entries) {
|
|
4408
|
-
const full =
|
|
4689
|
+
const full = join25(dir, entry);
|
|
4409
4690
|
let isDirectory;
|
|
4410
4691
|
try {
|
|
4411
|
-
isDirectory =
|
|
4692
|
+
isDirectory = statSync3(full).isDirectory();
|
|
4412
4693
|
} catch {
|
|
4413
4694
|
continue;
|
|
4414
4695
|
}
|
|
@@ -4425,7 +4706,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
4425
4706
|
for (const file of sourceFiles(cwd)) {
|
|
4426
4707
|
let content;
|
|
4427
4708
|
try {
|
|
4428
|
-
content =
|
|
4709
|
+
content = readFileSync19(file, "utf8");
|
|
4429
4710
|
} catch {
|
|
4430
4711
|
continue;
|
|
4431
4712
|
}
|
|
@@ -4449,7 +4730,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
4449
4730
|
}
|
|
4450
4731
|
if (hits.length === 0) return;
|
|
4451
4732
|
found.push({
|
|
4452
|
-
file:
|
|
4733
|
+
file: relative4(cwd, file),
|
|
4453
4734
|
line: index + 1,
|
|
4454
4735
|
from: line,
|
|
4455
4736
|
to: line.replace(rules, renamed.join(", ")),
|
|
@@ -4553,7 +4834,7 @@ function plan2(context) {
|
|
|
4553
4834
|
keys: removableDeps
|
|
4554
4835
|
});
|
|
4555
4836
|
}
|
|
4556
|
-
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) =>
|
|
4837
|
+
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync22(join26(context.cwd, name)));
|
|
4557
4838
|
for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
|
|
4558
4839
|
operations.push(
|
|
4559
4840
|
...nxTargetOperations({
|
|
@@ -4651,7 +4932,7 @@ function lintScripts(cwd) {
|
|
|
4651
4932
|
function committedExtends(cwd) {
|
|
4652
4933
|
try {
|
|
4653
4934
|
const parsed = parseJsonc(
|
|
4654
|
-
|
|
4935
|
+
readFileSync20(join26(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4655
4936
|
LINT_CONFIG_FILE
|
|
4656
4937
|
);
|
|
4657
4938
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4662,7 +4943,7 @@ function committedExtends(cwd) {
|
|
|
4662
4943
|
function committedIgnorePatterns(cwd) {
|
|
4663
4944
|
try {
|
|
4664
4945
|
const parsed = parseJsonc(
|
|
4665
|
-
|
|
4946
|
+
readFileSync20(join26(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4666
4947
|
LINT_CONFIG_FILE
|
|
4667
4948
|
);
|
|
4668
4949
|
return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4674,7 +4955,7 @@ function moduleEslintDependencies(cwd) {
|
|
|
4674
4955
|
const isEslintPackage = (name) => name === "eslint" || name === "@types/eslint" || name === "typescript-eslint" || name.startsWith("@typescript-eslint/") || name.startsWith("eslint-plugin-") || name.startsWith("eslint-config-") || name.startsWith("@eslint/");
|
|
4675
4956
|
let manifest;
|
|
4676
4957
|
try {
|
|
4677
|
-
manifest = JSON.parse(
|
|
4958
|
+
manifest = JSON.parse(readFileSync20(join26(cwd, "package.json"), "utf8"));
|
|
4678
4959
|
} catch {
|
|
4679
4960
|
return [];
|
|
4680
4961
|
}
|
|
@@ -4808,20 +5089,20 @@ ${result.stderr ?? ""}`);
|
|
|
4808
5089
|
* build the developer can see, rather than a silent half-adoption they cannot.
|
|
4809
5090
|
*/
|
|
4810
5091
|
writeModuleBaseline(ctx, oxlint, env) {
|
|
4811
|
-
const configPath =
|
|
4812
|
-
const baselinePath =
|
|
4813
|
-
if (!
|
|
5092
|
+
const configPath = join27(ctx.cwd, LINT_CONFIG_FILE);
|
|
5093
|
+
const baselinePath = join27(ctx.cwd, LINT_BASELINE_FILE);
|
|
5094
|
+
if (!existsSync23(configPath)) return;
|
|
4814
5095
|
let config;
|
|
4815
5096
|
try {
|
|
4816
5097
|
config = parseJsonc(
|
|
4817
|
-
|
|
5098
|
+
readFileSync21(configPath, "utf8"),
|
|
4818
5099
|
LINT_CONFIG_FILE
|
|
4819
5100
|
);
|
|
4820
5101
|
} catch {
|
|
4821
5102
|
return;
|
|
4822
5103
|
}
|
|
4823
5104
|
const current = Array.isArray(config.extends) ? config.extends : [];
|
|
4824
|
-
const measurePath =
|
|
5105
|
+
const measurePath = join27(ctx.cwd, LINT_MEASURE_FILE);
|
|
4825
5106
|
let measured;
|
|
4826
5107
|
try {
|
|
4827
5108
|
writeFileSync2(
|
|
@@ -4869,7 +5150,7 @@ ${result.stderr ?? ""}`);
|
|
|
4869
5150
|
}
|
|
4870
5151
|
}
|
|
4871
5152
|
async run(ctx) {
|
|
4872
|
-
if (!
|
|
5153
|
+
if (!existsSync23(join27(ctx.cwd, LINT_CONFIG_FILE))) {
|
|
4873
5154
|
process.stderr.write(
|
|
4874
5155
|
`sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
|
|
4875
5156
|
`
|
|
@@ -4914,7 +5195,7 @@ ${result.stderr ?? ""}`);
|
|
|
4914
5195
|
valueFlags: OXLINT_VALUE_FLAGS
|
|
4915
5196
|
});
|
|
4916
5197
|
const targets = paths.length > 0 ? paths : ["."];
|
|
4917
|
-
const emptyScope = scopeFlags(paths);
|
|
5198
|
+
const emptyScope = scopeFlags(paths, passedOptions);
|
|
4918
5199
|
const lint = (extra) => {
|
|
4919
5200
|
const passed = [...extra, ...emptyScope, ...passedOptions];
|
|
4920
5201
|
const result = spawnSync4(oxlint, ["-c", LINT_CONFIG_FILE, ...passed, ...targets], {
|
|
@@ -5017,7 +5298,7 @@ ${result.stderr ?? ""}`);
|
|
|
5017
5298
|
let stub;
|
|
5018
5299
|
try {
|
|
5019
5300
|
stub = parseJsonc(
|
|
5020
|
-
|
|
5301
|
+
readFileSync21(join27(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
5021
5302
|
LINT_CONFIG_FILE
|
|
5022
5303
|
);
|
|
5023
5304
|
} catch {
|
|
@@ -5027,7 +5308,7 @@ ${result.stderr ?? ""}`);
|
|
|
5027
5308
|
for (const entry of stub.extends ?? []) {
|
|
5028
5309
|
try {
|
|
5029
5310
|
const preset = parseJsonc(
|
|
5030
|
-
|
|
5311
|
+
readFileSync21(join27(cwd, entry), "utf8"),
|
|
5031
5312
|
entry
|
|
5032
5313
|
);
|
|
5033
5314
|
for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
|
|
@@ -5097,14 +5378,14 @@ ${result.stderr ?? ""}`);
|
|
|
5097
5378
|
let parsed;
|
|
5098
5379
|
try {
|
|
5099
5380
|
parsed = parseJsonc(
|
|
5100
|
-
|
|
5381
|
+
readFileSync21(join27(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
5101
5382
|
LINT_CONFIG_FILE
|
|
5102
5383
|
);
|
|
5103
5384
|
} catch {
|
|
5104
5385
|
return void 0;
|
|
5105
5386
|
}
|
|
5106
5387
|
const targets = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
|
|
5107
|
-
return targets.find((target) => !
|
|
5388
|
+
return targets.find((target) => !existsSync23(join27(cwd, target)));
|
|
5108
5389
|
}
|
|
5109
5390
|
/** Announce what is not enforced, so reduced coverage is never silent. */
|
|
5110
5391
|
announceDisabled(preset) {
|
|
@@ -5136,9 +5417,9 @@ function registerLint() {
|
|
|
5136
5417
|
|
|
5137
5418
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
5138
5419
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5139
|
-
import { existsSync as
|
|
5420
|
+
import { existsSync as existsSync24, readFileSync as readFileSync23 } from "fs";
|
|
5140
5421
|
import { createRequire as createRequire4 } from "module";
|
|
5141
|
-
import { join as
|
|
5422
|
+
import { join as join29 } from "path";
|
|
5142
5423
|
|
|
5143
5424
|
// src/roles/typescript/presets/nest.json
|
|
5144
5425
|
var nest_default2 = {
|
|
@@ -5319,8 +5600,8 @@ function readTsconfigAdoption(cwd) {
|
|
|
5319
5600
|
}
|
|
5320
5601
|
|
|
5321
5602
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
5322
|
-
import { readFileSync as
|
|
5323
|
-
import { join as
|
|
5603
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
5604
|
+
import { join as join28 } from "path";
|
|
5324
5605
|
|
|
5325
5606
|
// src/roles/typescript/typecheck-script.ts
|
|
5326
5607
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
@@ -5413,7 +5694,7 @@ function planAdoption(context) {
|
|
|
5413
5694
|
};
|
|
5414
5695
|
}
|
|
5415
5696
|
const existing = parseJsonc(
|
|
5416
|
-
|
|
5697
|
+
readFileSync22(join28(context.cwd, target.path), "utf8"),
|
|
5417
5698
|
target.path
|
|
5418
5699
|
);
|
|
5419
5700
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
@@ -5551,7 +5832,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5551
5832
|
let chain;
|
|
5552
5833
|
try {
|
|
5553
5834
|
const parsed = parseJsonc(
|
|
5554
|
-
|
|
5835
|
+
readFileSync23(join29(cwd, target.path), "utf8"),
|
|
5555
5836
|
target.path
|
|
5556
5837
|
);
|
|
5557
5838
|
chain = parsed.extends;
|
|
@@ -5564,7 +5845,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5564
5845
|
);
|
|
5565
5846
|
if (preset === void 0) return void 0;
|
|
5566
5847
|
try {
|
|
5567
|
-
createRequire4(
|
|
5848
|
+
createRequire4(join29(cwd, "noop.js")).resolve(preset);
|
|
5568
5849
|
return void 0;
|
|
5569
5850
|
} catch {
|
|
5570
5851
|
return preset;
|
|
@@ -5660,7 +5941,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5660
5941
|
referencedProjects(cwd, config) {
|
|
5661
5942
|
try {
|
|
5662
5943
|
const parsed = parseJsonc(
|
|
5663
|
-
|
|
5944
|
+
readFileSync23(join29(cwd, config), "utf8"),
|
|
5664
5945
|
config
|
|
5665
5946
|
);
|
|
5666
5947
|
const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
|
|
@@ -5676,7 +5957,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5676
5957
|
* check.
|
|
5677
5958
|
*/
|
|
5678
5959
|
typecheckTarget(cwd) {
|
|
5679
|
-
if (
|
|
5960
|
+
if (existsSync24(join29(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
5680
5961
|
const target = resolveTsconfigTarget(cwd);
|
|
5681
5962
|
return target.reason === "none" ? null : target.path;
|
|
5682
5963
|
}
|
|
@@ -5880,7 +6161,7 @@ function replaceLines(current, replacements) {
|
|
|
5880
6161
|
}
|
|
5881
6162
|
|
|
5882
6163
|
// src/core/apply-plan.ts
|
|
5883
|
-
import { existsSync as
|
|
6164
|
+
import { existsSync as existsSync25, readFileSync as readFileSync24, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
5884
6165
|
import { resolve as resolve4, sep } from "path";
|
|
5885
6166
|
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
5886
6167
|
|
|
@@ -5899,7 +6180,7 @@ function resolveWithinRoot(cwd, relativePath) {
|
|
|
5899
6180
|
return absolutePath;
|
|
5900
6181
|
}
|
|
5901
6182
|
function readIfExists(absolutePath) {
|
|
5902
|
-
return
|
|
6183
|
+
return existsSync25(absolutePath) ? readFileSync24(absolutePath, "utf8") : void 0;
|
|
5903
6184
|
}
|
|
5904
6185
|
function* leaves(value, prefix = []) {
|
|
5905
6186
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -6017,8 +6298,8 @@ function applyPlan(cwd, plan3) {
|
|
|
6017
6298
|
}
|
|
6018
6299
|
|
|
6019
6300
|
// src/core/config/preset-evidence.ts
|
|
6020
|
-
import { existsSync as
|
|
6021
|
-
import { join as
|
|
6301
|
+
import { existsSync as existsSync26, readdirSync as readdirSync3, readFileSync as readFileSync25 } from "fs";
|
|
6302
|
+
import { join as join30 } from "path";
|
|
6022
6303
|
var PATH_SIGNALS = [
|
|
6023
6304
|
{
|
|
6024
6305
|
preset: "nest",
|
|
@@ -6032,11 +6313,11 @@ var DEPENDENCY_SIGNALS = [
|
|
|
6032
6313
|
{ preset: "nest", pattern: /^@nestjs\// }
|
|
6033
6314
|
];
|
|
6034
6315
|
function dependencyNames(cwd) {
|
|
6035
|
-
const path =
|
|
6036
|
-
if (!
|
|
6316
|
+
const path = join30(cwd, "package.json");
|
|
6317
|
+
if (!existsSync26(path)) return [];
|
|
6037
6318
|
try {
|
|
6038
6319
|
const manifest = parseJsonc(
|
|
6039
|
-
|
|
6320
|
+
readFileSync25(path, "utf8"),
|
|
6040
6321
|
path
|
|
6041
6322
|
);
|
|
6042
6323
|
return [
|
|
@@ -6059,7 +6340,7 @@ function declaresJsx(cwd) {
|
|
|
6059
6340
|
for (const name of entries) {
|
|
6060
6341
|
try {
|
|
6061
6342
|
const config = parseJsonc(
|
|
6062
|
-
|
|
6343
|
+
readFileSync25(join30(cwd, name), "utf8"),
|
|
6063
6344
|
name
|
|
6064
6345
|
);
|
|
6065
6346
|
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
@@ -6218,24 +6499,25 @@ export {
|
|
|
6218
6499
|
declaredPresetFor,
|
|
6219
6500
|
availableTargets,
|
|
6220
6501
|
resolve,
|
|
6221
|
-
BaseAdapter,
|
|
6222
|
-
resolveBin,
|
|
6223
6502
|
readOwnVersion,
|
|
6224
6503
|
readProjectPackageJson,
|
|
6225
|
-
|
|
6504
|
+
moduleName,
|
|
6505
|
+
buildConfigFile,
|
|
6506
|
+
BaseAdapter,
|
|
6507
|
+
resolveBin,
|
|
6508
|
+
WORKSPACE_ROOT_MARKER,
|
|
6509
|
+
findWorkspaceRoot,
|
|
6510
|
+
ensureWorkspacePrep,
|
|
6511
|
+
inspectWorkspacePrep,
|
|
6226
6512
|
VERBS,
|
|
6227
6513
|
TARGETS,
|
|
6228
6514
|
LONG_RUNNING_TARGETS,
|
|
6229
6515
|
SWEEPABLE_TARGETS,
|
|
6230
6516
|
PRESET_NAMES,
|
|
6231
|
-
WORKSPACE_ROOT_MARKER,
|
|
6232
|
-
findWorkspaceRoot,
|
|
6233
|
-
ensureWorkspacePrep,
|
|
6234
|
-
inspectWorkspacePrep,
|
|
6235
6517
|
palette,
|
|
6236
6518
|
registerAdapters,
|
|
6237
6519
|
describeFramework,
|
|
6238
6520
|
detectFramework,
|
|
6239
6521
|
dispatch
|
|
6240
6522
|
};
|
|
6241
|
-
//# sourceMappingURL=chunk-
|
|
6523
|
+
//# sourceMappingURL=chunk-RKVGZNJ7.js.map
|