@hublo/sentinel 1.2.0-alpha.12 → 1.2.0-alpha.13
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.js +1 -1
- package/dist/{chunk-I4ATYZKK.js → chunk-X675OYUC.js} +293 -287
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/bin/sentinel.js
CHANGED
|
@@ -67,19 +67,6 @@ 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
72
|
import { basename, dirname, join } from "path";
|
|
@@ -132,6 +119,19 @@ function moduleName(dir) {
|
|
|
132
119
|
return readNxProjectName(dir) ?? (declared === void 0 ? basename(dir) : declared);
|
|
133
120
|
}
|
|
134
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
|
+
};
|
|
134
|
+
|
|
135
135
|
// src/core/domain.ts
|
|
136
136
|
var VERBS = ["run", "inspect", "init", "migrate", "report", "status"];
|
|
137
137
|
var TARGETS = [
|
|
@@ -165,11 +165,263 @@ function palette(stream) {
|
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
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 } = 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
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function refusalFor(cwd, target) {
|
|
199
|
+
if (workspaceRootFor(cwd, target) === void 0) {
|
|
200
|
+
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.`;
|
|
201
|
+
}
|
|
202
|
+
if (target.webpackConfig !== TRANSLATABLE_WEBPACK_CONFIG) {
|
|
203
|
+
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.`;
|
|
204
|
+
}
|
|
205
|
+
const live = liveFileReplacements(cwd, target);
|
|
206
|
+
if (live.length > 0) {
|
|
207
|
+
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.`;
|
|
208
|
+
}
|
|
209
|
+
return void 0;
|
|
210
|
+
}
|
|
211
|
+
function liveFileReplacements(cwd, target) {
|
|
212
|
+
const workspaceRoot = workspaceRootFor(cwd, target);
|
|
213
|
+
if (workspaceRoot === void 0) return [];
|
|
214
|
+
const configurations = target.configurations ?? {};
|
|
215
|
+
const live = [];
|
|
216
|
+
for (const configuration of Object.values(configurations)) {
|
|
217
|
+
for (const replacement of configuration.fileReplacements ?? []) {
|
|
218
|
+
const exists = existsSync2(join2(workspaceRoot, replacement.replace)) || existsSync2(join2(workspaceRoot, replacement.with));
|
|
219
|
+
if (exists) live.push(replacement.replace);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return live;
|
|
223
|
+
}
|
|
224
|
+
function workspaceRootFor(cwd, target) {
|
|
225
|
+
const moduleRoot = target.main.replace(/\/src\/.*$/, "");
|
|
226
|
+
if (moduleRoot === target.main || !cwd.endsWith(moduleRoot)) return void 0;
|
|
227
|
+
return cwd.slice(0, cwd.length - moduleRoot.length - 1);
|
|
228
|
+
}
|
|
229
|
+
function workspaceRootHops(cwd, target) {
|
|
230
|
+
const root = workspaceRootFor(cwd, target);
|
|
231
|
+
if (root === void 0) {
|
|
232
|
+
throw new Error(`sentinel build(nest): cannot place ${cwd} in a workspace from ${target.main}`);
|
|
233
|
+
}
|
|
234
|
+
const rel = relative(root, cwd);
|
|
235
|
+
return rel === "" ? 0 : rel.split(/[\\/]/).length;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/roles/build/read-adoption.ts
|
|
239
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
240
|
+
import { join as join3 } from "path";
|
|
241
|
+
|
|
242
|
+
// src/roles/build/presets/requirements.json
|
|
243
|
+
var requirements_default = {
|
|
244
|
+
svelte: {
|
|
245
|
+
why: "sentinel ships Vite 8, and @sveltejs/vite-plugin-svelte caps at Vite 6 until its major 7",
|
|
246
|
+
requires: {
|
|
247
|
+
"@sveltejs/vite-plugin-svelte": "7.0.0",
|
|
248
|
+
svelte: "5.46.4"
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// src/roles/build/presets/toolchain.json
|
|
254
|
+
var toolchain_default = {
|
|
255
|
+
$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.",
|
|
256
|
+
owned: [
|
|
257
|
+
{
|
|
258
|
+
package: "vite",
|
|
259
|
+
specifier: "vite",
|
|
260
|
+
named: ["defineConfig", "loadEnv", "mergeConfig"],
|
|
261
|
+
types: [
|
|
262
|
+
"Plugin",
|
|
263
|
+
"PluginOption",
|
|
264
|
+
"SassPreprocessorOptions",
|
|
265
|
+
"UserConfig",
|
|
266
|
+
"UserConfigExport"
|
|
267
|
+
],
|
|
268
|
+
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."
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
package: "@vitejs/plugin-react",
|
|
272
|
+
specifier: "@vitejs/plugin-react",
|
|
273
|
+
default: "react",
|
|
274
|
+
why: "the React transform. Bound to the Vite that runs it, so it has to come from the same install."
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
package: "@tailwindcss/vite",
|
|
278
|
+
specifier: "@tailwindcss/vite",
|
|
279
|
+
default: "tailwindcss",
|
|
280
|
+
why: "the Tailwind pipeline, identical in all three front apps."
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
package: "vite-plugin-svgr",
|
|
284
|
+
specifier: "vite-plugin-svgr",
|
|
285
|
+
default: "svgr",
|
|
286
|
+
why: "SVG as components, identical in all three front apps."
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
package: "nitro",
|
|
290
|
+
specifier: "nitro/vite",
|
|
291
|
+
named: ["nitro"],
|
|
292
|
+
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."
|
|
293
|
+
}
|
|
294
|
+
],
|
|
295
|
+
add: [
|
|
296
|
+
{
|
|
297
|
+
package: "@hublo/sentinel",
|
|
298
|
+
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."
|
|
299
|
+
}
|
|
300
|
+
],
|
|
301
|
+
$notOwned: {
|
|
302
|
+
"@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.",
|
|
303
|
+
"@locator/babel-jsx": "same, and same owners.",
|
|
304
|
+
"@tanstack/react-start": "a framework the apps code against: 48 source files import it directly.",
|
|
305
|
+
"@tanstack/router-plugin": "generates the app's own route tree.",
|
|
306
|
+
"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."
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// src/roles/build/preset-data.ts
|
|
311
|
+
var REQUIREMENTS = requirements_default;
|
|
312
|
+
var OWNED_PACKAGES = toolchain_default.owned;
|
|
313
|
+
|
|
314
|
+
// src/roles/build/config-policy.ts
|
|
315
|
+
var COMPANION_CONFIG_FILES = [
|
|
316
|
+
"vitest.config.ts",
|
|
317
|
+
"vitest.config.mts",
|
|
318
|
+
"vitest.config.js",
|
|
319
|
+
"vitest.config.mjs"
|
|
320
|
+
];
|
|
321
|
+
var BUILD_CONFIG_FILES = [
|
|
322
|
+
"vite.config.ts",
|
|
323
|
+
"vite.config.mts",
|
|
324
|
+
"vite.config.js",
|
|
325
|
+
"vite.config.mjs"
|
|
326
|
+
];
|
|
327
|
+
var BUILD_PRESET_SPECIFIERS = {
|
|
328
|
+
react: "@hublo/sentinel/build/react",
|
|
329
|
+
nest: "@hublo/sentinel/build/nest"
|
|
330
|
+
};
|
|
331
|
+
var BUILD_PRESET_SPECIFIER = BUILD_PRESET_SPECIFIERS.react;
|
|
332
|
+
var BUILD_SCRIPT_NAME = "build";
|
|
333
|
+
var SENTINEL_BUILD_COMMAND = "sentinel --run --build --";
|
|
334
|
+
var DEV_SCRIPT_NAME = "serve";
|
|
335
|
+
var SENTINEL_DEV_COMMAND = "sentinel --run --dev";
|
|
336
|
+
function buildTarget() {
|
|
337
|
+
return {
|
|
338
|
+
[BUILD_SCRIPT_NAME]: {
|
|
339
|
+
cache: true,
|
|
340
|
+
inputs: ["default", "^default", ...BUILD_CONFIG_FILES.map((name) => `{projectRoot}/${name}`)]
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
var SENTINEL_OWNED_BUILD_PACKAGES = OWNED_PACKAGES.map(
|
|
345
|
+
(entry) => entry.package
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
// src/roles/build/read-adoption.ts
|
|
349
|
+
var NOT_ADOPTED = (configFile, unreadable = null) => ({
|
|
350
|
+
configFile,
|
|
351
|
+
preset: null,
|
|
352
|
+
adopted: false,
|
|
353
|
+
conformant: false,
|
|
354
|
+
drift: [],
|
|
355
|
+
ownDeclarations: [],
|
|
356
|
+
unreadable
|
|
357
|
+
});
|
|
358
|
+
function buildConfigFile(cwd) {
|
|
359
|
+
return BUILD_CONFIG_FILES.find((name) => existsSync3(join3(cwd, name)));
|
|
360
|
+
}
|
|
361
|
+
function importsSpecifier(source, specifier) {
|
|
362
|
+
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
363
|
+
return new RegExp(`(?:from|import)\\s*\\(?\\s*['"\`]${escaped}(?:/[^'"\`]*)?['"\`]`).test(source);
|
|
364
|
+
}
|
|
365
|
+
function importsPreset(source) {
|
|
366
|
+
return Object.values(BUILD_PRESET_SPECIFIERS).some(
|
|
367
|
+
(specifier) => importsSpecifier(source, specifier)
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
var REACT_PLUGIN_SPECIFIERS = ["@vitejs/plugin-react", "@tanstack/react-start"];
|
|
371
|
+
function declaredBuildPreset(cwd) {
|
|
372
|
+
const source = readBuildConfigSource(cwd);
|
|
373
|
+
if (source === void 0) {
|
|
374
|
+
if (buildConfigFile(cwd) !== void 0) return void 0;
|
|
375
|
+
return webpackBuildTarget(cwd) === void 0 ? void 0 : "nest";
|
|
376
|
+
}
|
|
377
|
+
if (importsSpecifier(source, BUILD_PRESET_SPECIFIERS.nest)) return "nest";
|
|
378
|
+
if (importsPreset(source)) return "react";
|
|
379
|
+
return REACT_PLUGIN_SPECIFIERS.some((specifier) => importsSpecifier(source, specifier)) ? "react" : void 0;
|
|
380
|
+
}
|
|
381
|
+
function readBuildConfigSource(cwd) {
|
|
382
|
+
const configFile = buildConfigFile(cwd);
|
|
383
|
+
if (!configFile) return void 0;
|
|
384
|
+
try {
|
|
385
|
+
return readFileSync3(join3(cwd, configFile), "utf8");
|
|
386
|
+
} catch {
|
|
387
|
+
return void 0;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function readBuildAdoption(cwd) {
|
|
391
|
+
const configFile = buildConfigFile(cwd);
|
|
392
|
+
if (!configFile) return NOT_ADOPTED(null);
|
|
393
|
+
let source;
|
|
394
|
+
try {
|
|
395
|
+
source = readFileSync3(join3(cwd, configFile), "utf8");
|
|
396
|
+
} catch (error) {
|
|
397
|
+
return NOT_ADOPTED(configFile, error instanceof Error ? error.message : String(error));
|
|
398
|
+
}
|
|
399
|
+
if (!importsPreset(source)) return NOT_ADOPTED(configFile);
|
|
400
|
+
const manifest = readProjectPackageJson(cwd);
|
|
401
|
+
const declared = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
402
|
+
const ownDeclarations = SENTINEL_OWNED_BUILD_PACKAGES.filter((name) => name in declared).map(
|
|
403
|
+
(name) => ({ name, version: declared[name] })
|
|
404
|
+
);
|
|
405
|
+
return {
|
|
406
|
+
configFile,
|
|
407
|
+
// Read from the config rather than assumed: the two presets are adopted the same way and
|
|
408
|
+
// reporting the wrong one would make `--inspect` lie about what a module builds.
|
|
409
|
+
preset: declaredBuildPreset(cwd) ?? "react",
|
|
410
|
+
adopted: true,
|
|
411
|
+
conformant: ownDeclarations.length === 0,
|
|
412
|
+
drift: ownDeclarations.map(
|
|
413
|
+
({ 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`
|
|
414
|
+
),
|
|
415
|
+
ownDeclarations,
|
|
416
|
+
unreadable: null
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
168
420
|
// src/roles/build/adapters/vite/vite-role.adapter.ts
|
|
169
421
|
import { spawnSync } from "child_process";
|
|
170
422
|
|
|
171
423
|
// src/core/config/tool-args.ts
|
|
172
|
-
import { existsSync as
|
|
424
|
+
import { existsSync as existsSync4 } from "fs";
|
|
173
425
|
import { isAbsolute, resolve as resolve2 } from "path";
|
|
174
426
|
function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
175
427
|
const takesValue = new Set(valueFlags);
|
|
@@ -179,7 +431,7 @@ function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
|
179
431
|
for (const arg of toolArgs) {
|
|
180
432
|
const looksLikeOption = arg.startsWith("-");
|
|
181
433
|
const target = isAbsolute(arg) ? arg : resolve2(cwd, arg);
|
|
182
|
-
if (!previousTakesValue && !looksLikeOption &&
|
|
434
|
+
if (!previousTakesValue && !looksLikeOption && existsSync4(target)) paths.push(arg);
|
|
183
435
|
else options.push(arg);
|
|
184
436
|
previousTakesValue = !arg.includes("=") && takesValue.has(arg);
|
|
185
437
|
}
|
|
@@ -187,15 +439,15 @@ function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
|
187
439
|
}
|
|
188
440
|
|
|
189
441
|
// src/shared/resolve-bin.ts
|
|
190
|
-
import { existsSync as
|
|
442
|
+
import { existsSync as existsSync5 } from "fs";
|
|
191
443
|
import { createRequire } from "module";
|
|
192
|
-
import { delimiter, dirname as dirname2, join as
|
|
444
|
+
import { delimiter, dirname as dirname2, join as join4 } from "path";
|
|
193
445
|
var require2 = createRequire(import.meta.url);
|
|
194
446
|
function resolveBin(fromDir, name) {
|
|
195
447
|
let dir = fromDir;
|
|
196
448
|
for (; ; ) {
|
|
197
|
-
const candidate =
|
|
198
|
-
if (
|
|
449
|
+
const candidate = join4(dir, "node_modules", ".bin", name);
|
|
450
|
+
if (existsSync5(candidate)) return candidate;
|
|
199
451
|
const parent = dirname2(dir);
|
|
200
452
|
if (parent === dir) return void 0;
|
|
201
453
|
dir = parent;
|
|
@@ -207,8 +459,8 @@ function binFromOwnInstall(packageName, binName) {
|
|
|
207
459
|
const bin = require2(manifest).bin;
|
|
208
460
|
const relative6 = typeof bin === "string" ? bin : bin?.[binName];
|
|
209
461
|
if (!relative6) return void 0;
|
|
210
|
-
const executable =
|
|
211
|
-
return
|
|
462
|
+
const executable = join4(dirname2(manifest), relative6);
|
|
463
|
+
return existsSync5(executable) ? executable : void 0;
|
|
212
464
|
} catch {
|
|
213
465
|
return void 0;
|
|
214
466
|
}
|
|
@@ -224,8 +476,8 @@ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
|
224
476
|
import { join as join8 } from "path";
|
|
225
477
|
|
|
226
478
|
// src/core/config/existing-command.ts
|
|
227
|
-
import { readFileSync as
|
|
228
|
-
import { join as
|
|
479
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
480
|
+
import { join as join6 } from "path";
|
|
229
481
|
|
|
230
482
|
// src/shared/jsonc.ts
|
|
231
483
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
@@ -240,11 +492,11 @@ function parseJsonc(text, source = "config") {
|
|
|
240
492
|
}
|
|
241
493
|
|
|
242
494
|
// src/core/config/manifest.ts
|
|
243
|
-
import { existsSync as
|
|
244
|
-
import { basename as basename2, join as
|
|
495
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
496
|
+
import { basename as basename2, join as join5 } from "path";
|
|
245
497
|
function moduleScripts(cwd) {
|
|
246
498
|
try {
|
|
247
|
-
const pkg = JSON.parse(
|
|
499
|
+
const pkg = JSON.parse(readFileSync4(join5(cwd, "package.json"), "utf8"));
|
|
248
500
|
return pkg.scripts ?? {};
|
|
249
501
|
} catch {
|
|
250
502
|
return {};
|
|
@@ -252,7 +504,7 @@ function moduleScripts(cwd) {
|
|
|
252
504
|
}
|
|
253
505
|
function moduleName2(cwd) {
|
|
254
506
|
try {
|
|
255
|
-
const pkg = JSON.parse(
|
|
507
|
+
const pkg = JSON.parse(readFileSync4(join5(cwd, "package.json"), "utf8"));
|
|
256
508
|
return pkg.name;
|
|
257
509
|
} catch {
|
|
258
510
|
return void 0;
|
|
@@ -272,7 +524,7 @@ function manifestOperation(cwd, scripts) {
|
|
|
272
524
|
scripts,
|
|
273
525
|
devDependencies: { [own.name]: isSelfAdoption(cwd) ? "link:." : own.version }
|
|
274
526
|
};
|
|
275
|
-
if (
|
|
527
|
+
if (existsSync6(join5(cwd, "package.json"))) {
|
|
276
528
|
return { kind: "merge-json", path: "package.json", value };
|
|
277
529
|
}
|
|
278
530
|
return {
|
|
@@ -287,7 +539,7 @@ function nxTargetCommand(cwd, target) {
|
|
|
287
539
|
let project;
|
|
288
540
|
try {
|
|
289
541
|
project = parseJsonc(
|
|
290
|
-
|
|
542
|
+
readFileSync5(join6(cwd, "project.json"), "utf8"),
|
|
291
543
|
"project.json"
|
|
292
544
|
);
|
|
293
545
|
} catch {
|
|
@@ -318,14 +570,14 @@ function existingCommand(cwd, target) {
|
|
|
318
570
|
}
|
|
319
571
|
|
|
320
572
|
// src/core/config/nx-target.ts
|
|
321
|
-
import { existsSync as
|
|
322
|
-
import { join as
|
|
573
|
+
import { existsSync as existsSync7 } from "fs";
|
|
574
|
+
import { join as join7 } from "path";
|
|
323
575
|
function nxTargetOperations(options) {
|
|
324
576
|
const { cwd, targets } = options;
|
|
325
577
|
const names = Object.keys(targets);
|
|
326
578
|
if (names.length === 0) return [];
|
|
327
579
|
const operations = [];
|
|
328
|
-
if (
|
|
580
|
+
if (existsSync7(join7(cwd, "project.json"))) {
|
|
329
581
|
operations.push({
|
|
330
582
|
kind: "remove-json-keys",
|
|
331
583
|
path: "project.json",
|
|
@@ -382,112 +634,6 @@ function keepsOtherCommands(script, sentinelCommand) {
|
|
|
382
634
|
return script.split(SEGMENT_SEPARATOR).filter((_, index) => !isSeparatorAt(index)).some((segment) => segment.trim() !== "" && segment.trim() !== sentinelCommand);
|
|
383
635
|
}
|
|
384
636
|
|
|
385
|
-
// src/roles/build/presets/requirements.json
|
|
386
|
-
var requirements_default = {
|
|
387
|
-
svelte: {
|
|
388
|
-
why: "sentinel ships Vite 8, and @sveltejs/vite-plugin-svelte caps at Vite 6 until its major 7",
|
|
389
|
-
requires: {
|
|
390
|
-
"@sveltejs/vite-plugin-svelte": "7.0.0",
|
|
391
|
-
svelte: "5.46.4"
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
// src/roles/build/presets/toolchain.json
|
|
397
|
-
var toolchain_default = {
|
|
398
|
-
$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.",
|
|
399
|
-
owned: [
|
|
400
|
-
{
|
|
401
|
-
package: "vite",
|
|
402
|
-
specifier: "vite",
|
|
403
|
-
named: ["defineConfig", "loadEnv", "mergeConfig"],
|
|
404
|
-
types: [
|
|
405
|
-
"Plugin",
|
|
406
|
-
"PluginOption",
|
|
407
|
-
"SassPreprocessorOptions",
|
|
408
|
-
"UserConfig",
|
|
409
|
-
"UserConfigExport"
|
|
410
|
-
],
|
|
411
|
-
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."
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
package: "@vitejs/plugin-react",
|
|
415
|
-
specifier: "@vitejs/plugin-react",
|
|
416
|
-
default: "react",
|
|
417
|
-
why: "the React transform. Bound to the Vite that runs it, so it has to come from the same install."
|
|
418
|
-
},
|
|
419
|
-
{
|
|
420
|
-
package: "@tailwindcss/vite",
|
|
421
|
-
specifier: "@tailwindcss/vite",
|
|
422
|
-
default: "tailwindcss",
|
|
423
|
-
why: "the Tailwind pipeline, identical in all three front apps."
|
|
424
|
-
},
|
|
425
|
-
{
|
|
426
|
-
package: "vite-plugin-svgr",
|
|
427
|
-
specifier: "vite-plugin-svgr",
|
|
428
|
-
default: "svgr",
|
|
429
|
-
why: "SVG as components, identical in all three front apps."
|
|
430
|
-
},
|
|
431
|
-
{
|
|
432
|
-
package: "nitro",
|
|
433
|
-
specifier: "nitro/vite",
|
|
434
|
-
named: ["nitro"],
|
|
435
|
-
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."
|
|
436
|
-
}
|
|
437
|
-
],
|
|
438
|
-
add: [
|
|
439
|
-
{
|
|
440
|
-
package: "@hublo/sentinel",
|
|
441
|
-
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."
|
|
442
|
-
}
|
|
443
|
-
],
|
|
444
|
-
$notOwned: {
|
|
445
|
-
"@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.",
|
|
446
|
-
"@locator/babel-jsx": "same, and same owners.",
|
|
447
|
-
"@tanstack/react-start": "a framework the apps code against: 48 source files import it directly.",
|
|
448
|
-
"@tanstack/router-plugin": "generates the app's own route tree.",
|
|
449
|
-
"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."
|
|
450
|
-
}
|
|
451
|
-
};
|
|
452
|
-
|
|
453
|
-
// src/roles/build/preset-data.ts
|
|
454
|
-
var REQUIREMENTS = requirements_default;
|
|
455
|
-
var OWNED_PACKAGES = toolchain_default.owned;
|
|
456
|
-
|
|
457
|
-
// src/roles/build/config-policy.ts
|
|
458
|
-
var COMPANION_CONFIG_FILES = [
|
|
459
|
-
"vitest.config.ts",
|
|
460
|
-
"vitest.config.mts",
|
|
461
|
-
"vitest.config.js",
|
|
462
|
-
"vitest.config.mjs"
|
|
463
|
-
];
|
|
464
|
-
var BUILD_CONFIG_FILES = [
|
|
465
|
-
"vite.config.ts",
|
|
466
|
-
"vite.config.mts",
|
|
467
|
-
"vite.config.js",
|
|
468
|
-
"vite.config.mjs"
|
|
469
|
-
];
|
|
470
|
-
var BUILD_PRESET_SPECIFIERS = {
|
|
471
|
-
react: "@hublo/sentinel/build/react",
|
|
472
|
-
nest: "@hublo/sentinel/build/nest"
|
|
473
|
-
};
|
|
474
|
-
var BUILD_PRESET_SPECIFIER = BUILD_PRESET_SPECIFIERS.react;
|
|
475
|
-
var BUILD_SCRIPT_NAME = "build";
|
|
476
|
-
var SENTINEL_BUILD_COMMAND = "sentinel --run --build --";
|
|
477
|
-
var DEV_SCRIPT_NAME = "serve";
|
|
478
|
-
var SENTINEL_DEV_COMMAND = "sentinel --run --dev";
|
|
479
|
-
function buildTarget() {
|
|
480
|
-
return {
|
|
481
|
-
[BUILD_SCRIPT_NAME]: {
|
|
482
|
-
cache: true,
|
|
483
|
-
inputs: ["default", "^default", ...BUILD_CONFIG_FILES.map((name) => `{projectRoot}/${name}`)]
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
var SENTINEL_OWNED_BUILD_PACKAGES = OWNED_PACKAGES.map(
|
|
488
|
-
(entry) => entry.package
|
|
489
|
-
);
|
|
490
|
-
|
|
491
637
|
// src/roles/build/dev-script.ts
|
|
492
638
|
var CHAIN = " -- ";
|
|
493
639
|
var VITE_TOKEN = /(^|\s|\/)vite(\s|$)/;
|
|
@@ -510,78 +656,6 @@ function composeDevScript(existing, command) {
|
|
|
510
656
|
|
|
511
657
|
// src/roles/build/nest/adopt.ts
|
|
512
658
|
import { relative as relative2 } from "path";
|
|
513
|
-
|
|
514
|
-
// src/roles/build/nest/nx-target.ts
|
|
515
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
516
|
-
import { join as join6, relative } from "path";
|
|
517
|
-
var DEPRECATED_NX_BUILDER = "@nx/webpack:webpack";
|
|
518
|
-
var TRANSLATABLE_WEBPACK_CONFIG = "tools/webpack-configs/update.webpack.config.js";
|
|
519
|
-
var readProjectJson = (cwd) => {
|
|
520
|
-
const path = join6(cwd, "project.json");
|
|
521
|
-
if (!existsSync6(path)) return void 0;
|
|
522
|
-
try {
|
|
523
|
-
return JSON.parse(readFileSync4(path, "utf8"));
|
|
524
|
-
} catch {
|
|
525
|
-
return void 0;
|
|
526
|
-
}
|
|
527
|
-
};
|
|
528
|
-
function webpackBuildTarget(cwd) {
|
|
529
|
-
const project = readProjectJson(cwd);
|
|
530
|
-
const target = project?.targets?.build;
|
|
531
|
-
if (target?.executor !== DEPRECATED_NX_BUILDER) return void 0;
|
|
532
|
-
const { outputPath, main, webpackConfig } = target.options ?? {};
|
|
533
|
-
if (typeof project?.name !== "string" || typeof outputPath !== "string" || typeof main !== "string" || typeof webpackConfig !== "string") {
|
|
534
|
-
return void 0;
|
|
535
|
-
}
|
|
536
|
-
return {
|
|
537
|
-
project: project.name,
|
|
538
|
-
outputPath,
|
|
539
|
-
main,
|
|
540
|
-
webpackConfig,
|
|
541
|
-
configurations: target.configurations
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
function refusalFor(cwd, target) {
|
|
545
|
-
if (workspaceRootFor(cwd, target) === void 0) {
|
|
546
|
-
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.`;
|
|
547
|
-
}
|
|
548
|
-
if (target.webpackConfig !== TRANSLATABLE_WEBPACK_CONFIG) {
|
|
549
|
-
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.`;
|
|
550
|
-
}
|
|
551
|
-
const live = liveFileReplacements(cwd, target);
|
|
552
|
-
if (live.length > 0) {
|
|
553
|
-
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.`;
|
|
554
|
-
}
|
|
555
|
-
return void 0;
|
|
556
|
-
}
|
|
557
|
-
function liveFileReplacements(cwd, target) {
|
|
558
|
-
const workspaceRoot = workspaceRootFor(cwd, target);
|
|
559
|
-
if (workspaceRoot === void 0) return [];
|
|
560
|
-
const configurations = target.configurations ?? {};
|
|
561
|
-
const live = [];
|
|
562
|
-
for (const configuration of Object.values(configurations)) {
|
|
563
|
-
for (const replacement of configuration.fileReplacements ?? []) {
|
|
564
|
-
const exists = existsSync6(join6(workspaceRoot, replacement.replace)) || existsSync6(join6(workspaceRoot, replacement.with));
|
|
565
|
-
if (exists) live.push(replacement.replace);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
return live;
|
|
569
|
-
}
|
|
570
|
-
function workspaceRootFor(cwd, target) {
|
|
571
|
-
const moduleRoot = target.main.replace(/\/src\/.*$/, "");
|
|
572
|
-
if (moduleRoot === target.main || !cwd.endsWith(moduleRoot)) return void 0;
|
|
573
|
-
return cwd.slice(0, cwd.length - moduleRoot.length - 1);
|
|
574
|
-
}
|
|
575
|
-
function workspaceRootHops(cwd, target) {
|
|
576
|
-
const root = workspaceRootFor(cwd, target);
|
|
577
|
-
if (root === void 0) {
|
|
578
|
-
throw new Error(`sentinel build(nest): cannot place ${cwd} in a workspace from ${target.main}`);
|
|
579
|
-
}
|
|
580
|
-
const rel = relative(root, cwd);
|
|
581
|
-
return rel === "" ? 0 : rel.split(/[\\/]/).length;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// src/roles/build/nest/adopt.ts
|
|
585
659
|
var NEST_CONFIG_FILE = "vite.config.mts";
|
|
586
660
|
function nestConfigSource(cwd, target) {
|
|
587
661
|
const hops = workspaceRootHops(cwd, target);
|
|
@@ -628,80 +702,6 @@ function nestAdoptionOperations(cwd, target) {
|
|
|
628
702
|
];
|
|
629
703
|
}
|
|
630
704
|
|
|
631
|
-
// src/roles/build/read-adoption.ts
|
|
632
|
-
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
633
|
-
import { join as join7 } from "path";
|
|
634
|
-
var NOT_ADOPTED = (configFile, unreadable = null) => ({
|
|
635
|
-
configFile,
|
|
636
|
-
preset: null,
|
|
637
|
-
adopted: false,
|
|
638
|
-
conformant: false,
|
|
639
|
-
drift: [],
|
|
640
|
-
ownDeclarations: [],
|
|
641
|
-
unreadable
|
|
642
|
-
});
|
|
643
|
-
function buildConfigFile(cwd) {
|
|
644
|
-
return BUILD_CONFIG_FILES.find((name) => existsSync7(join7(cwd, name)));
|
|
645
|
-
}
|
|
646
|
-
function importsSpecifier(source, specifier) {
|
|
647
|
-
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
648
|
-
return new RegExp(`(?:from|import)\\s*\\(?\\s*['"\`]${escaped}(?:/[^'"\`]*)?['"\`]`).test(source);
|
|
649
|
-
}
|
|
650
|
-
function importsPreset(source) {
|
|
651
|
-
return Object.values(BUILD_PRESET_SPECIFIERS).some(
|
|
652
|
-
(specifier) => importsSpecifier(source, specifier)
|
|
653
|
-
);
|
|
654
|
-
}
|
|
655
|
-
var REACT_PLUGIN_SPECIFIERS = ["@vitejs/plugin-react", "@tanstack/react-start"];
|
|
656
|
-
function declaredBuildPreset(cwd) {
|
|
657
|
-
const source = readBuildConfigSource(cwd);
|
|
658
|
-
if (source === void 0) {
|
|
659
|
-
if (buildConfigFile(cwd) !== void 0) return void 0;
|
|
660
|
-
return webpackBuildTarget(cwd) === void 0 ? void 0 : "nest";
|
|
661
|
-
}
|
|
662
|
-
if (importsSpecifier(source, BUILD_PRESET_SPECIFIERS.nest)) return "nest";
|
|
663
|
-
if (importsPreset(source)) return "react";
|
|
664
|
-
return REACT_PLUGIN_SPECIFIERS.some((specifier) => importsSpecifier(source, specifier)) ? "react" : void 0;
|
|
665
|
-
}
|
|
666
|
-
function readBuildConfigSource(cwd) {
|
|
667
|
-
const configFile = buildConfigFile(cwd);
|
|
668
|
-
if (!configFile) return void 0;
|
|
669
|
-
try {
|
|
670
|
-
return readFileSync5(join7(cwd, configFile), "utf8");
|
|
671
|
-
} catch {
|
|
672
|
-
return void 0;
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
function readBuildAdoption(cwd) {
|
|
676
|
-
const configFile = buildConfigFile(cwd);
|
|
677
|
-
if (!configFile) return NOT_ADOPTED(null);
|
|
678
|
-
let source;
|
|
679
|
-
try {
|
|
680
|
-
source = readFileSync5(join7(cwd, configFile), "utf8");
|
|
681
|
-
} catch (error) {
|
|
682
|
-
return NOT_ADOPTED(configFile, error instanceof Error ? error.message : String(error));
|
|
683
|
-
}
|
|
684
|
-
if (!importsPreset(source)) return NOT_ADOPTED(configFile);
|
|
685
|
-
const manifest = readProjectPackageJson(cwd);
|
|
686
|
-
const declared = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
687
|
-
const ownDeclarations = SENTINEL_OWNED_BUILD_PACKAGES.filter((name) => name in declared).map(
|
|
688
|
-
(name) => ({ name, version: declared[name] })
|
|
689
|
-
);
|
|
690
|
-
return {
|
|
691
|
-
configFile,
|
|
692
|
-
// Read from the config rather than assumed: the two presets are adopted the same way and
|
|
693
|
-
// reporting the wrong one would make `--inspect` lie about what a module builds.
|
|
694
|
-
preset: declaredBuildPreset(cwd) ?? "react",
|
|
695
|
-
adopted: true,
|
|
696
|
-
conformant: ownDeclarations.length === 0,
|
|
697
|
-
drift: ownDeclarations.map(
|
|
698
|
-
({ 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`
|
|
699
|
-
),
|
|
700
|
-
ownDeclarations,
|
|
701
|
-
unreadable: null
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
|
|
705
705
|
// src/roles/build/requirements.ts
|
|
706
706
|
function atLeast(declared, needed) {
|
|
707
707
|
const parse2 = (value) => value.replace(/^[\^~>=<\s]+/, "").split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -1112,6 +1112,12 @@ var ViteDevAdapter = class extends ViteRoleAdapter {
|
|
|
1112
1112
|
* wrong correction, it added friction to a command that already produced the right result.
|
|
1113
1113
|
*/
|
|
1114
1114
|
plan(context) {
|
|
1115
|
+
if (declaredBuildPreset(context.cwd) === "nest" || webpackBuildTarget(context.cwd)) {
|
|
1116
|
+
return {
|
|
1117
|
+
operations: [],
|
|
1118
|
+
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.`
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1115
1121
|
const planned = super.plan(context);
|
|
1116
1122
|
if (planned.operations.length === 0) return planned;
|
|
1117
1123
|
return {
|
|
@@ -6412,12 +6418,12 @@ export {
|
|
|
6412
6418
|
declaredPresetFor,
|
|
6413
6419
|
availableTargets,
|
|
6414
6420
|
resolve,
|
|
6415
|
-
BaseAdapter,
|
|
6416
|
-
resolveBin,
|
|
6417
6421
|
readOwnVersion,
|
|
6418
6422
|
readProjectPackageJson,
|
|
6419
6423
|
moduleName,
|
|
6420
6424
|
buildConfigFile,
|
|
6425
|
+
BaseAdapter,
|
|
6426
|
+
resolveBin,
|
|
6421
6427
|
VERBS,
|
|
6422
6428
|
TARGETS,
|
|
6423
6429
|
LONG_RUNNING_TARGETS,
|
|
@@ -6433,4 +6439,4 @@ export {
|
|
|
6433
6439
|
detectFramework,
|
|
6434
6440
|
dispatch
|
|
6435
6441
|
};
|
|
6436
|
-
//# sourceMappingURL=chunk-
|
|
6442
|
+
//# sourceMappingURL=chunk-X675OYUC.js.map
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hublo/sentinel",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.13",
|
|
4
4
|
"description": "One CLI that guards code health across Hublo repos: shared lint/typescript/build/test presets, static & dynamic analysis, and architecture checks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|