@hublo/sentinel 1.2.0-alpha.1 → 1.2.0-alpha.11

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.
@@ -1,9 +1,3 @@
1
- import {
2
- REACT_APP_DEFAULTS,
3
- REQUIREMENTS,
4
- isPlainObject
5
- } from "./chunk-XVDOQ3G3.js";
6
-
7
1
  // src/core/registry.ts
8
2
  var adapters = [];
9
3
  var PresetUnsupportedError = class extends Error {
@@ -88,7 +82,7 @@ var BaseAdapter = class {
88
82
 
89
83
  // src/shared/package-json.ts
90
84
  import { existsSync, readFileSync } from "fs";
91
- import { dirname, join } from "path";
85
+ import { basename, dirname, join } from "path";
92
86
  import { fileURLToPath } from "url";
93
87
  function readOwnPackage() {
94
88
  let dir = dirname(fileURLToPath(import.meta.url));
@@ -133,6 +127,10 @@ function readNxProjectName(dir) {
133
127
  return void 0;
134
128
  }
135
129
  }
130
+ function moduleName(dir) {
131
+ const declared = readProjectPackageJson(dir).name;
132
+ return readNxProjectName(dir) ?? (declared === void 0 ? basename(dir) : declared);
133
+ }
136
134
 
137
135
  // src/core/domain.ts
138
136
  var VERBS = ["run", "inspect", "init", "migrate", "report", "status"];
@@ -207,9 +205,9 @@ function binFromOwnInstall(packageName, binName) {
207
205
  try {
208
206
  const manifest = require2.resolve(`${packageName}/package.json`);
209
207
  const bin = require2(manifest).bin;
210
- const relative4 = typeof bin === "string" ? bin : bin?.[binName];
211
- if (!relative4) return void 0;
212
- const executable = join2(dirname2(manifest), relative4);
208
+ const relative6 = typeof bin === "string" ? bin : bin?.[binName];
209
+ if (!relative6) return void 0;
210
+ const executable = join2(dirname2(manifest), relative6);
213
211
  return existsSync3(executable) ? executable : void 0;
214
212
  } catch {
215
213
  return void 0;
@@ -222,8 +220,8 @@ function binSearchPath(cwd) {
222
220
  }
223
221
 
224
222
  // src/roles/build/plan.ts
225
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
226
- import { join as join7 } from "path";
223
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
224
+ import { join as join8 } from "path";
227
225
 
228
226
  // src/core/config/existing-command.ts
229
227
  import { readFileSync as readFileSync3 } from "fs";
@@ -243,7 +241,7 @@ function parseJsonc(text, source = "config") {
243
241
 
244
242
  // src/core/config/manifest.ts
245
243
  import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
246
- import { basename, join as join3 } from "path";
244
+ import { basename as basename2, join as join3 } from "path";
247
245
  function moduleScripts(cwd) {
248
246
  try {
249
247
  const pkg = JSON.parse(readFileSync2(join3(cwd, "package.json"), "utf8"));
@@ -252,7 +250,7 @@ function moduleScripts(cwd) {
252
250
  return {};
253
251
  }
254
252
  }
255
- function moduleName(cwd) {
253
+ function moduleName2(cwd) {
256
254
  try {
257
255
  const pkg = JSON.parse(readFileSync2(join3(cwd, "package.json"), "utf8"));
258
256
  return pkg.name;
@@ -261,7 +259,7 @@ function moduleName(cwd) {
261
259
  }
262
260
  }
263
261
  function isSelfAdoption(cwd) {
264
- return moduleName(cwd) === readOwnPackage().name;
262
+ return moduleName2(cwd) === readOwnPackage().name;
265
263
  }
266
264
  function selfCommand(cwd, flags) {
267
265
  const own = readOwnPackage();
@@ -280,7 +278,7 @@ function manifestOperation(cwd, scripts) {
280
278
  return {
281
279
  kind: "merge-json",
282
280
  path: "package.json",
283
- value: { name: readNxProjectName(cwd) ?? basename(cwd), private: true, ...value }
281
+ value: { name: readNxProjectName(cwd) ?? basename2(cwd), private: true, ...value }
284
282
  };
285
283
  }
286
284
 
@@ -384,16 +382,98 @@ function keepsOtherCommands(script, sentinelCommand) {
384
382
  return script.split(SEGMENT_SEPARATOR).filter((_, index) => !isSeparatorAt(index)).some((segment) => segment.trim() !== "" && segment.trim() !== sentinelCommand);
385
383
  }
386
384
 
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
+
387
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
+ ];
388
464
  var BUILD_CONFIG_FILES = [
389
465
  "vite.config.ts",
390
466
  "vite.config.mts",
391
467
  "vite.config.js",
392
468
  "vite.config.mjs"
393
469
  ];
394
- var BUILD_PRESET_SPECIFIER = "@hublo/sentinel/build/react";
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;
395
475
  var BUILD_SCRIPT_NAME = "build";
396
- var SENTINEL_BUILD_COMMAND = "sentinel --run --build";
476
+ var SENTINEL_BUILD_COMMAND = "sentinel --run --build --";
397
477
  var DEV_SCRIPT_NAME = "serve";
398
478
  var SENTINEL_DEV_COMMAND = "sentinel --run --dev";
399
479
  function buildTarget() {
@@ -404,13 +484,9 @@ function buildTarget() {
404
484
  }
405
485
  };
406
486
  }
407
- var SENTINEL_OWNED_BUILD_PACKAGES = [
408
- "vite",
409
- "@vitejs/plugin-react",
410
- "@tailwindcss/vite",
411
- "vite-plugin-svgr",
412
- "nitro"
413
- ];
487
+ var SENTINEL_OWNED_BUILD_PACKAGES = OWNED_PACKAGES.map(
488
+ (entry) => entry.package
489
+ );
414
490
 
415
491
  // src/roles/build/dev-script.ts
416
492
  var CHAIN = " -- ";
@@ -418,23 +494,143 @@ var VITE_TOKEN = /(^|\s|\/)vite(\s|$)/;
418
494
  var DEV_SUBCOMMANDS = /* @__PURE__ */ new Set(["dev", "serve"]);
419
495
  function appOptions(rest) {
420
496
  const tokens = (rest ?? "").trim().split(/\s+/).filter(Boolean);
421
- const kept = tokens.filter((token, at2) => !(at2 === 0 && DEV_SUBCOMMANDS.has(token)));
497
+ const kept = tokens.filter((token, at) => !(at === 0 && DEV_SUBCOMMANDS.has(token)));
422
498
  return kept.join(" ");
423
499
  }
424
500
  function composeDevScript(existing, command) {
425
501
  if (existing === void 0 || existing.trim() === "") return command;
426
502
  const chunks = existing.trim().split(CHAIN);
427
- const at2 = chunks.map((chunk) => VITE_TOKEN.test(chunk)).lastIndexOf(true);
428
- if (at2 === -1) return existing;
429
- const rest = (chunks[at2] ?? "").replace(/^.*?(^|\s|\/)vite(\s|$)/, "");
503
+ const at = chunks.map((chunk) => VITE_TOKEN.test(chunk)).lastIndexOf(true);
504
+ if (at === -1) return existing;
505
+ const rest = (chunks[at] ?? "").replace(/^.*?(^|\s|\/)vite(\s|$)/, "");
430
506
  const options = appOptions(rest);
431
- chunks[at2] = options === "" ? command : `${command} -- ${options}`;
507
+ chunks[at] = options === "" ? command : `${command} -- ${options}`;
432
508
  return chunks.join(CHAIN);
433
509
  }
434
510
 
435
- // src/roles/build/read-adoption.ts
511
+ // src/roles/build/nest/adopt.ts
512
+ import { relative as relative2 } from "path";
513
+
514
+ // src/roles/build/nest/nx-target.ts
436
515
  import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
437
- import { join as join6 } from "path";
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
+ var NEST_CONFIG_FILE = "vite.config.mts";
586
+ function nestConfigSource(cwd, target) {
587
+ const hops = workspaceRootHops(cwd, target);
588
+ const up = Array.from({ length: hops }, () => "..").join("/");
589
+ return `import path from 'node:path'
590
+
591
+ import { nestService } from '${BUILD_PRESET_SPECIFIERS.nest}'
592
+
593
+ export default nestService({
594
+ project: '${target.project}',
595
+ root: __dirname,
596
+ workspaceRoot: path.resolve(__dirname, '${up}'),
597
+ outDir: '${target.outputPath}',
598
+ })
599
+ `;
600
+ }
601
+ function nestBuildTarget(cwd, target) {
602
+ const workspaceRoot = workspaceRootFor(cwd, target);
603
+ if (workspaceRoot === void 0) {
604
+ throw new Error(
605
+ `sentinel build(nest): cannot place ${cwd} inside a workspace from ${target.main}`
606
+ );
607
+ }
608
+ const moduleRoot = relative2(workspaceRoot, cwd);
609
+ return {
610
+ executor: "nx:run-commands",
611
+ outputs: [`{workspaceRoot}/${target.outputPath}`],
612
+ options: {
613
+ cwd: moduleRoot,
614
+ command: "pnpm exec sentinel --run --build",
615
+ forwardAllArgs: false
616
+ }
617
+ };
618
+ }
619
+ function nestAdoptionOperations(cwd, target) {
620
+ return [
621
+ { kind: "write", path: NEST_CONFIG_FILE, contents: nestConfigSource(cwd, target) },
622
+ { kind: "remove-json-keys", path: "project.json", keys: [["targets", "build"]] },
623
+ {
624
+ kind: "merge-json",
625
+ path: "project.json",
626
+ value: { targets: { build: nestBuildTarget(cwd, target) } }
627
+ }
628
+ ];
629
+ }
630
+
631
+ // src/roles/build/read-adoption.ts
632
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
633
+ import { join as join7 } from "path";
438
634
  var NOT_ADOPTED = (configFile, unreadable = null) => ({
439
635
  configFile,
440
636
  preset: null,
@@ -445,26 +641,33 @@ var NOT_ADOPTED = (configFile, unreadable = null) => ({
445
641
  unreadable
446
642
  });
447
643
  function buildConfigFile(cwd) {
448
- return BUILD_CONFIG_FILES.find((name) => existsSync6(join6(cwd, name)));
644
+ return BUILD_CONFIG_FILES.find((name) => existsSync7(join7(cwd, name)));
449
645
  }
450
646
  function importsSpecifier(source, specifier) {
451
647
  const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
452
648
  return new RegExp(`(?:from|import)\\s*\\(?\\s*['"\`]${escaped}(?:/[^'"\`]*)?['"\`]`).test(source);
453
649
  }
454
650
  function importsPreset(source) {
455
- return importsSpecifier(source, BUILD_PRESET_SPECIFIER);
651
+ return Object.values(BUILD_PRESET_SPECIFIERS).some(
652
+ (specifier) => importsSpecifier(source, specifier)
653
+ );
456
654
  }
457
655
  var REACT_PLUGIN_SPECIFIERS = ["@vitejs/plugin-react", "@tanstack/react-start"];
458
656
  function declaredBuildPreset(cwd) {
459
657
  const source = readBuildConfigSource(cwd);
460
- if (source === void 0) return void 0;
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";
461
664
  return REACT_PLUGIN_SPECIFIERS.some((specifier) => importsSpecifier(source, specifier)) ? "react" : void 0;
462
665
  }
463
666
  function readBuildConfigSource(cwd) {
464
667
  const configFile = buildConfigFile(cwd);
465
668
  if (!configFile) return void 0;
466
669
  try {
467
- return readFileSync4(join6(cwd, configFile), "utf8");
670
+ return readFileSync5(join7(cwd, configFile), "utf8");
468
671
  } catch {
469
672
  return void 0;
470
673
  }
@@ -474,7 +677,7 @@ function readBuildAdoption(cwd) {
474
677
  if (!configFile) return NOT_ADOPTED(null);
475
678
  let source;
476
679
  try {
477
- source = readFileSync4(join6(cwd, configFile), "utf8");
680
+ source = readFileSync5(join7(cwd, configFile), "utf8");
478
681
  } catch (error) {
479
682
  return NOT_ADOPTED(configFile, error instanceof Error ? error.message : String(error));
480
683
  }
@@ -486,7 +689,9 @@ function readBuildAdoption(cwd) {
486
689
  );
487
690
  return {
488
691
  configFile,
489
- preset: "react",
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",
490
695
  adopted: true,
491
696
  conformant: ownDeclarations.length === 0,
492
697
  drift: ownDeclarations.map(
@@ -502,9 +707,9 @@ function atLeast(declared, needed) {
502
707
  const parse2 = (value) => value.replace(/^[\^~>=<\s]+/, "").split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
503
708
  const left = parse2(declared);
504
709
  const right = parse2(needed);
505
- for (let at2 = 0; at2 < Math.max(left.length, right.length); at2++) {
506
- const a = left[at2] ?? 0;
507
- const b = right[at2] ?? 0;
710
+ for (let at = 0; at < Math.max(left.length, right.length); at++) {
711
+ const a = left[at] ?? 0;
712
+ const b = right[at] ?? 0;
508
713
  if (a !== b) return a > b;
509
714
  }
510
715
  return true;
@@ -525,43 +730,103 @@ function describeUnmet(preset, unmet) {
525
730
  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\`.`;
526
731
  }
527
732
 
528
- // src/roles/build/plan.ts
529
- function scaffold() {
530
- return `import { defineConfig, reactApp } from '${BUILD_PRESET_SPECIFIER}'
531
- import { tanstackStart } from '@tanstack/react-start/plugin/vite'
532
- import { tanstackRouter } from '@tanstack/router-plugin/vite'
533
- import path from 'node:path'
534
-
535
- /*
536
- * Composition comes from sentinel: which plugins run under test and which under a real build,
537
- * the order they run in, and how the env is read. The values below are this app's own.
538
- *
539
- * TanStack is passed IN rather than imported by sentinel, because the app codes against it
540
- * directly. \`alias\` is passed through verbatim and never read, so nothing in it can be lost.
541
- * Anything sentinel does not set goes in \`overrides\`, which merges over the preset last.
542
- */
543
- export default defineConfig(({ mode }) =>
544
- reactApp({
545
- root: __dirname,
546
- mode,
547
- base: '/',
548
- port: 3000,
549
- // Explicit, NOT port + 1: the three apps in this repo disagree, one of them goes down.
550
- hmrPort: 3001,
551
- router: {
552
- routesDirectory: path.resolve(__dirname, 'src/routes'),
553
- generatedRouteTree: path.resolve(__dirname, 'src/routeTree.gen.ts'),
554
- },
555
- tanstack: { start: tanstackStart, router: tanstackRouter },
556
- alias: [],
557
- }),
558
- )
559
-
560
- // If this app reads environment variables, use \`appEnv(__dirname, mode)\` rather than Vite's
561
- // \`loadEnv\`: it applies the production -> prd normalisation this repo deploys under, which
562
- // every app was otherwise retyping.
563
- `;
733
+ // src/roles/build/rewrite-imports.ts
734
+ import { parseSync } from "oxc-parser";
735
+ var MOVED_SPECIFIERS = new Set(OWNED_PACKAGES.map((entry) => entry.specifier));
736
+ function esmBlocker(fileName, packageType) {
737
+ if (/\.(mts|mjs)$/.test(fileName)) return void 0;
738
+ if (packageType === "module") return void 0;
739
+ 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.`;
740
+ }
741
+ var DEFAULT_EXPORT_NAMES = Object.fromEntries(
742
+ OWNED_PACKAGES.filter((entry) => entry.default !== void 0).map((entry) => [
743
+ entry.specifier,
744
+ entry.default
745
+ ])
746
+ );
747
+ function renderImport(bindings) {
748
+ const render = (list) => list.map((b) => b.exported === b.local ? b.exported : `${b.exported} as ${b.local}`).sort((left, right) => left.localeCompare(right)).join(", ");
749
+ const values = bindings.filter((b) => !b.typeOnly);
750
+ const types = bindings.filter((b) => b.typeOnly);
751
+ const lines = [];
752
+ if (values.length > 0) lines.push(`import { ${render(values)} } from '${BUILD_PRESET_SPECIFIER}'`);
753
+ if (types.length > 0) {
754
+ lines.push(`import type { ${render(types)} } from '${BUILD_PRESET_SPECIFIER}'`);
755
+ }
756
+ return lines.join("\n");
757
+ }
758
+ function rewriteBuildImports(source, fileName = "vite.config.ts") {
759
+ const { program, errors } = parseSync(fileName, source, { sourceType: "module" });
760
+ if (errors.length > 0) {
761
+ return {
762
+ kind: "blocked",
763
+ why: `${fileName} does not parse: ${errors[0]?.message ?? "unknown"}`
764
+ };
765
+ }
766
+ const imports = program.body.filter(
767
+ (node) => node.type === "ImportDeclaration"
768
+ );
769
+ if (imports.some((node) => node.source.value === BUILD_PRESET_SPECIFIER)) {
770
+ return { kind: "unchanged", why: "this config already imports its toolchain from sentinel" };
771
+ }
772
+ const moving = imports.filter((node) => MOVED_SPECIFIERS.has(node.source.value));
773
+ if (moving.length === 0) {
774
+ return {
775
+ kind: "unchanged",
776
+ why: "this config imports none of the packages sentinel owns, so there is nothing to move"
777
+ };
778
+ }
779
+ const bindings = [];
780
+ for (const node of moving) {
781
+ const from = node.source.value;
782
+ const specifiers = node.specifiers ?? [];
783
+ if (specifiers.length === 0) {
784
+ return { kind: "blocked", why: `\`import '${from}'\` has no bindings to move` };
785
+ }
786
+ for (const spec of specifiers) {
787
+ const typeOnly = node.importKind === "type" || spec.importKind === "type";
788
+ if (spec.type === "ImportDefaultSpecifier") {
789
+ const exported = DEFAULT_EXPORT_NAMES[from];
790
+ if (!exported) {
791
+ return {
792
+ kind: "blocked",
793
+ why: `no sentinel export for the default import of \`${from}\``
794
+ };
795
+ }
796
+ bindings.push({ exported, local: spec.local.name, typeOnly });
797
+ continue;
798
+ }
799
+ if (spec.type === "ImportSpecifier") {
800
+ bindings.push({
801
+ exported: spec.imported?.name ?? spec.imported?.value ?? spec.local.name,
802
+ local: spec.local.name,
803
+ typeOnly
804
+ });
805
+ continue;
806
+ }
807
+ return {
808
+ kind: "blocked",
809
+ why: `\`import * as ${spec.local.name} from '${from}'\` cannot be mapped onto sentinel's named exports`
810
+ };
811
+ }
812
+ }
813
+ const spans = moving.map((node) => ({ start: node.start, end: node.end })).sort((left, right) => left.start - right.start);
814
+ const first = spans[0];
815
+ const rest = spans.slice(1);
816
+ let out = source;
817
+ for (const span of rest.slice().reverse()) {
818
+ const after = out.slice(span.end).match(/^\r?\n/)?.[0].length ?? 0;
819
+ out = out.slice(0, span.start) + out.slice(span.end + after);
820
+ }
821
+ out = out.slice(0, first.start) + renderImport(bindings) + out.slice(first.end);
822
+ return {
823
+ kind: "rewritten",
824
+ source: out,
825
+ moved: [...new Set(moving.map((node) => node.source.value))].sort()
826
+ };
564
827
  }
828
+
829
+ // src/roles/build/plan.ts
565
830
  function ownedBuildDependencies(cwd) {
566
831
  const manifest = readProjectPackageJson(cwd);
567
832
  const keys = [];
@@ -590,9 +855,12 @@ function buildScripts(cwd) {
590
855
  };
591
856
  const existingDev = existingCommand(cwd, DEV_SCRIPT_NAME);
592
857
  if (existingDev !== void 0) {
858
+ const own2 = moduleName(cwd);
593
859
  scripts[DEV_SCRIPT_NAME] = composeDevScript(
594
860
  existingDev,
595
- selfCommand(cwd, "--run --dev") ?? SENTINEL_DEV_COMMAND
861
+ // Trailing `--` for the same reason as the build script: whatever the caller appends
862
+ // reaches Vite, not sentinel's parser. It goes after `--module`, which is sentinel's own.
863
+ `${selfCommand(cwd, "--run --dev") ?? SENTINEL_DEV_COMMAND} --module ${own2} --`
596
864
  );
597
865
  }
598
866
  return scripts;
@@ -606,23 +874,57 @@ function plan(context) {
606
874
  if (unmet.length > 0) {
607
875
  return { operations: [], skipped: describeUnmet(context.preset, unmet) };
608
876
  }
609
- if (declaredBuildPreset(context.cwd) === void 0 && buildConfigFile(context.cwd) !== void 0) {
877
+ const declaredPreset2 = declaredBuildPreset(context.cwd);
878
+ const configFile = buildConfigFile(context.cwd);
879
+ if (declaredPreset2 === void 0 && configFile !== void 0) {
610
880
  return {
611
881
  operations: [],
612
- skipped: `this module has a Vite config, but it does not build a React app (no @vitejs/plugin-react or @tanstack/react-start in it). The build role ships a React preset only.`
882
+ 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.`
613
883
  };
614
884
  }
885
+ const webpackTarget = webpackBuildTarget(context.cwd);
886
+ if (declaredBuildPreset(context.cwd) === "nest" || webpackTarget !== void 0) {
887
+ return planNestService(context, configFile, webpackTarget);
888
+ }
889
+ if (configFile === void 0) {
890
+ return {
891
+ operations: [],
892
+ 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 (36 services here build through \`@nx/webpack\`), the build role does not apply to it.`
893
+ };
894
+ }
895
+ return planReactApp(context, configFile);
896
+ }
897
+ function planReactApp(context, configFile) {
615
898
  const notes = [];
616
899
  const operations = [];
617
- const configFile = buildConfigFile(context.cwd);
618
- if (configFile === void 0) {
619
- operations.push({ kind: "write", path: BUILD_CONFIG_FILES[0], contents: scaffold() });
620
- notes.push(
621
- `wrote ${BUILD_CONFIG_FILES[0]} as a starting point. Fill in this app's own values (base, ports, routes); sentinel owns the composition, not the data.`
622
- );
623
- } else if (!readsPreset(context.cwd, configFile)) {
900
+ const notEsm = esmBlocker(configFile, readProjectPackageJson(context.cwd).type);
901
+ if (notEsm !== void 0) return { operations: [], skipped: notEsm };
902
+ const rewrite = rewriteBuildImports(readConfigSource(context.cwd, configFile), configFile);
903
+ if (rewrite.kind === "blocked") {
904
+ return {
905
+ operations: [],
906
+ 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.`
907
+ };
908
+ }
909
+ if (rewrite.kind === "rewritten") {
910
+ operations.push({ kind: "write", path: configFile, contents: rewrite.source });
911
+ }
912
+ for (const name of COMPANION_CONFIG_FILES) {
913
+ const source = readConfigSource(context.cwd, name);
914
+ if (source === "") continue;
915
+ const companion = rewriteBuildImports(source, name);
916
+ if (companion.kind === "rewritten") {
917
+ operations.push({ kind: "write", path: name, contents: companion.source });
918
+ notes.push(`${name} now imports ${companion.moved.join(", ")} from sentinel too.`);
919
+ } else if (companion.kind === "blocked") {
920
+ notes.push(
921
+ `${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.`
922
+ );
923
+ }
924
+ }
925
+ if (rewrite.kind === "rewritten") {
624
926
  notes.push(
625
- `${configFile} is this app's own, so it was left alone. To adopt: import \`reactApp\` from \`${BUILD_PRESET_SPECIFIER}\` and pass it this app's values, keeping \`alias\` and your own plugins verbatim. \`sentinel --inspect --build\` reports what the resolved config departs from and what it adds, before and after.`
927
+ `${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.`
626
928
  );
627
929
  }
628
930
  operations.push(manifestOperation(context.cwd, buildScripts(context.cwd)));
@@ -636,12 +938,33 @@ function plan(context) {
636
938
  }
637
939
  return { operations, notes };
638
940
  }
639
- function readsPreset(cwd, configFile) {
640
- if (!existsSync7(join7(cwd, configFile))) return false;
941
+ function planNestService(context, configFile, webpackTarget) {
942
+ if (webpackTarget === void 0) {
943
+ return {
944
+ operations: [],
945
+ skipped: `already building through sentinel: ${configFile ?? NEST_CONFIG_FILE} imports the nest preset and the nx target runs it. Nothing to change.`
946
+ };
947
+ }
948
+ const refusal = refusalFor(context.cwd, webpackTarget);
949
+ if (refusal !== void 0) return { operations: [], blocked: refusal };
950
+ const dropped = Object.keys(webpackTarget.configurations ?? {});
951
+ return {
952
+ operations: nestAdoptionOperations(context.cwd, webpackTarget),
953
+ notes: [
954
+ `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.`,
955
+ ...dropped.length > 0 ? [
956
+ `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.`
957
+ ] : [],
958
+ `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.`
959
+ ]
960
+ };
961
+ }
962
+ function readConfigSource(cwd, configFile) {
963
+ if (!existsSync8(join8(cwd, configFile))) return "";
641
964
  try {
642
- return readFileSync5(join7(cwd, configFile), "utf8").includes(BUILD_PRESET_SPECIFIER);
965
+ return readFileSync6(join8(cwd, configFile), "utf8");
643
966
  } catch {
644
- return false;
967
+ return "";
645
968
  }
646
969
  }
647
970
 
@@ -679,11 +1002,17 @@ var ViteRoleAdapter = class extends BaseAdapter {
679
1002
  * resolution let it run at all — filtering here would give "no adapter for preset svelte",
680
1003
  * which tells nobody what to do about it.
681
1004
  *
682
- * Nest, node and tools are genuinely declined: there is nothing to bundle, and no version
683
- * would change that.
1005
+ * Nest it now serves too, through `@hublo/sentinel/build/nest`. It used to be declined on the
1006
+ * grounds that "there is nothing to bundle", which was never true: those 36 services bundle
1007
+ * with webpack. What changed is that Nx v24 removes the `@nx/webpack:webpack` executor and
1008
+ * the `composePlugins` / `withNx` helpers their shared config is built on, and nx's own
1009
+ * migration generator refuses every one of them because they use `@nx/js:node`
1010
+ * (nrwl/nx#36389). So the way out runs through here.
1011
+ *
1012
+ * `node` and `tools` remain genuinely declined: nothing under them bundles at all.
684
1013
  */
685
1014
  appliesTo(preset) {
686
- return preset === "react" || preset === "svelte";
1015
+ return preset === "react" || preset === "svelte" || preset === "nest";
687
1016
  }
688
1017
  /**
689
1018
  * `react`, read from the module's Vite config rather than from its dependencies.
@@ -773,6 +1102,26 @@ var ViteDevAdapter = class extends ViteRoleAdapter {
773
1102
  get label() {
774
1103
  return "dev";
775
1104
  }
1105
+ /**
1106
+ * `--init --dev` does the same thing as `--init --build`, and now says so.
1107
+ *
1108
+ * The shared plan is deliberate (see `ViteRoleAdapter.plan`): one plan writes both scripts,
1109
+ * because adopting one without the other leaves the module half-migrated. What was missing
1110
+ * was only the telling: the run printed a message about the build and nothing about `serve`,
1111
+ * so a developer could believe the dev server had been adopted on its own. Refusing was the
1112
+ * wrong correction, it added friction to a command that already produced the right result.
1113
+ */
1114
+ plan(context) {
1115
+ const planned = super.plan(context);
1116
+ if (planned.operations.length === 0) return planned;
1117
+ return {
1118
+ ...planned,
1119
+ notes: [
1120
+ ...planned.notes ?? [],
1121
+ "--init --dev adopts the build too: one plan writes both `build` and `serve`."
1122
+ ]
1123
+ };
1124
+ }
776
1125
  /**
777
1126
  * Start the dev server. It does not return until stopped, so there is no verdict to report
778
1127
  * beyond the exit code the developer's own Ctrl-C produces.
@@ -791,48 +1140,6 @@ var ViteDevAdapter = class extends ViteRoleAdapter {
791
1140
  }
792
1141
  };
793
1142
 
794
- // src/roles/build/owned-paths.ts
795
- var PRESET_OWNED_KEYS = [
796
- "base",
797
- "root",
798
- "define",
799
- "server",
800
- "build",
801
- "nitro",
802
- "resolve",
803
- "plugins"
804
- ];
805
- function describeDepartures(config, defaults) {
806
- const departures = [];
807
- const build = asRecord(config.build);
808
- const target = build?.target;
809
- if (typeof target === "string" && target !== defaults.target) {
810
- departures.push({
811
- rule: "build.target",
812
- reason: `${target}, where the convention is ${defaults.target}`
813
- });
814
- }
815
- const server = asRecord(config.server);
816
- const hosts = server?.allowedHosts;
817
- if (Array.isArray(hosts) && !sameStrings(hosts, defaults.allowedHosts)) {
818
- departures.push({
819
- rule: "server.allowedHosts",
820
- reason: `${JSON.stringify(hosts)}, where the convention is ${JSON.stringify(defaults.allowedHosts)}`
821
- });
822
- }
823
- return departures;
824
- }
825
- function appAdditions(config) {
826
- const owned = new Set(PRESET_OWNED_KEYS);
827
- return Object.keys(config).filter((key) => !owned.has(key)).sort();
828
- }
829
- function asRecord(value) {
830
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
831
- }
832
- function sameStrings(a, b) {
833
- return a.length === b.length && a.every((value, at2) => value === b[at2]);
834
- }
835
-
836
1143
  // src/roles/build/prerequisite.ts
837
1144
  import { spawnSync as spawnSync2 } from "child_process";
838
1145
  var PREBUILD_SCRIPT_NAME = `pre${BUILD_SCRIPT_NAME}`;
@@ -859,23 +1166,6 @@ function runPrerequisite(cwd, env = process.env) {
859
1166
  return { kind: "ran" };
860
1167
  }
861
1168
 
862
- // src/roles/build/resolve-config.ts
863
- async function resolveBuildConfig(cwd) {
864
- try {
865
- const { loadConfigFromFile } = await import("vite");
866
- const loaded = await loadConfigFromFile(
867
- { command: "build", mode: "production" },
868
- void 0,
869
- cwd,
870
- "silent"
871
- );
872
- if (!loaded) return { kind: "failed", reason: "Vite found no config file in this module" };
873
- return { kind: "loaded", config: loaded.config, from: loaded.path };
874
- } catch (error) {
875
- return { kind: "failed", reason: error instanceof Error ? error.message : String(error) };
876
- }
877
- }
878
-
879
1169
  // src/roles/build/adapters/vite/vite.adapter.ts
880
1170
  var ViteAdapter = class extends ViteRoleAdapter {
881
1171
  target = "build";
@@ -901,25 +1191,30 @@ var ViteAdapter = class extends ViteRoleAdapter {
901
1191
  return this.spawnVite(ctx, ready.vite, ["build"], 1);
902
1192
  }
903
1193
  /**
904
- * What this module builds with, without building it.
1194
+ * What this module builds with, WITHOUT loading its config.
905
1195
  *
906
- * `--inspect` reads the committed config for every role, and the moment `--build` was wired
907
- * it joined the sweep — so leaving this to throw would have broken `sentinel --inspect` on
908
- * every React module, for a role that had only just arrived. Cheap and honest is the bar.
1196
+ * Everything reported here is read from the manifest and from the config's text: whether the
1197
+ * module imports its toolchain from sentinel, which Vite install would answer, what it still
1198
+ * declares of its own, and whether it has a `prebuild` step.
909
1199
  *
910
- * `vite` is the interesting field and the one nothing else reports: an adopted config takes
911
- * its plugins from sentinel, so a module answering `module` here is one build away from the
912
- * two-copies failure, and this is where that is visible before it happens.
1200
+ * It used to also load the config through Vite and report where it departed from a preset
1201
+ * and what it added on top. Both were dropped with the preset itself. Adoption changes a
1202
+ * config's IMPORTS and nothing else, so there is no preset the app signed up to and no
1203
+ * distance from it to measure; and computing it meant EXECUTING an app's config, which is
1204
+ * expensive and can fail on its own terms (career throws without `VITE_BRAND_ID`). Reading
1205
+ * a file to report on it must not be able to break.
913
1206
  *
914
- * The resolved config comes from Vite's own loader rather than from parsing the file. See
915
- * `resolve-config` for why that is the only honest answer here, and what it costs.
1207
+ * They come back with the shared base, when there is again something an app has chosen to
1208
+ * adopt and can therefore depart from.
916
1209
  */
917
1210
  async inspect(ctx) {
918
1211
  const adoption = readBuildAdoption(ctx.cwd);
919
- const base = {
1212
+ return {
920
1213
  runner: "vite",
921
1214
  configFile: adoption.configFile,
922
1215
  ...adoption.unreadable ? { unreadable: adoption.unreadable } : {},
1216
+ // The field nothing else reports: an adopted config takes its plugins from sentinel, so
1217
+ // a module answering `module` here is one build away from the two-copies failure.
923
1218
  vite: viteOrigin(ctx.cwd),
924
1219
  // Rendered as a labelled block by the shared renderer, the same shape the lint role's
925
1220
  // parked rules and the format role's overrides already use.
@@ -929,36 +1224,6 @@ var ViteAdapter = class extends ViteRoleAdapter {
929
1224
  })),
930
1225
  prerequisite: moduleScripts(ctx.cwd)[PREBUILD_SCRIPT_NAME] ?? null
931
1226
  };
932
- if (adoption.configFile === null) return base;
933
- const resolved = await resolveBuildConfig(ctx.cwd);
934
- if (resolved.kind === "failed") {
935
- return { ...base, configError: resolved.reason };
936
- }
937
- const config = resolved.config;
938
- return {
939
- ...base,
940
- // What the app departs from, and what it adds, are different facts. A DEPARTURE is a
941
- // disagreement with an opinion the preset holds; an ADDITION is the app needing
942
- // something the preset never claimed, which is the preset working rather than being
943
- // worked around.
944
- departures: describeDepartures(config, REACT_APP_DEFAULTS),
945
- additions: appAdditions(config),
946
- resolved: {
947
- base: at(config, "base") ?? null,
948
- target: at(config, "build", "target") ?? null,
949
- sourcemap: at(config, "build", "sourcemap") ?? null,
950
- port: at(config, "server", "port") ?? null,
951
- // The one number nothing else reports and that no formula predicts: career goes DOWN
952
- // to 9998 where the others go up, which is why it is data rather than `port + 1`.
953
- hmrPort: at(config, "server", "hmr", "port") ?? null,
954
- // Counted, not listed. It is 56-67% of every config today, and printing 296 entries
955
- // would bury everything above it. The count is what tells you whether it moved.
956
- aliases: countOf(at(config, "resolve", "alias")),
957
- // Flattened without a depth limit: Vite lets a plugin be an arbitrarily nested array,
958
- // and a magic number here would silently undercount the day someone nests one deeper.
959
- plugins: countOf(at(config, "plugins"))
960
- }
961
- };
962
1227
  }
963
1228
  /**
964
1229
  * Adoption as data, WITHOUT building.
@@ -984,17 +1249,6 @@ var ViteAdapter = class extends ViteRoleAdapter {
984
1249
  };
985
1250
  }
986
1251
  };
987
- function at(config, ...path) {
988
- let value = config;
989
- for (const key of path) {
990
- if (typeof value !== "object" || value === null) return void 0;
991
- value = value[key];
992
- }
993
- return value;
994
- }
995
- function countOf(value) {
996
- return Array.isArray(value) ? value.flat(Number.POSITIVE_INFINITY).length : 0;
997
- }
998
1252
 
999
1253
  // src/roles/build/register.ts
1000
1254
  function registerBuild() {
@@ -1006,12 +1260,12 @@ function registerBuild() {
1006
1260
 
1007
1261
  // src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
1008
1262
  import { spawnSync as spawnSync3 } from "child_process";
1009
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
1010
- import { join as join14 } from "path";
1263
+ import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
1264
+ import { join as join15 } from "path";
1011
1265
 
1012
1266
  // src/core/config/has-source.ts
1013
1267
  import { readdirSync } from "fs";
1014
- import { extname, join as join8, relative } from "path";
1268
+ import { extname, join as join9, relative as relative3 } from "path";
1015
1269
  var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
1016
1270
  "node_modules",
1017
1271
  "dist",
@@ -1062,7 +1316,7 @@ function hasSourceFiles(cwd, extensions) {
1062
1316
  }
1063
1317
  for (const entry of entries) {
1064
1318
  if (entry.isDirectory()) {
1065
- if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join8(dir, entry.name));
1319
+ if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join9(dir, entry.name));
1066
1320
  continue;
1067
1321
  }
1068
1322
  if (wanted.has(extname(entry.name))) return true;
@@ -1083,12 +1337,12 @@ function listSourceFiles(cwd, extensions) {
1083
1337
  continue;
1084
1338
  }
1085
1339
  for (const entry of entries) {
1086
- const full = join8(dir, entry.name);
1340
+ const full = join9(dir, entry.name);
1087
1341
  if (entry.isDirectory()) {
1088
1342
  if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
1089
1343
  continue;
1090
1344
  }
1091
- if (wanted.has(extname(entry.name))) found.push(relative(cwd, full));
1345
+ if (wanted.has(extname(entry.name))) found.push(relative3(cwd, full));
1092
1346
  }
1093
1347
  }
1094
1348
  return found;
@@ -1096,8 +1350,9 @@ function listSourceFiles(cwd, extensions) {
1096
1350
 
1097
1351
  // src/core/named-scope.ts
1098
1352
  var TOLERATE_EMPTY_FLAG = "--no-error-on-unmatched-pattern";
1099
- function scopeFlags(paths) {
1100
- return paths.length > 0 ? [TOLERATE_EMPTY_FLAG] : [];
1353
+ function scopeFlags(paths, options = []) {
1354
+ if (paths.length === 0 || options.includes(TOLERATE_EMPTY_FLAG)) return [];
1355
+ return [TOLERATE_EMPTY_FLAG];
1101
1356
  }
1102
1357
 
1103
1358
  // src/core/settings.ts
@@ -1105,8 +1360,8 @@ var WORKSPACE_ROOT_MARKER = "nx.json";
1105
1360
  var DEFAULT_MAX_DIAGNOSTICS = 100;
1106
1361
 
1107
1362
  // src/core/workspace-prep.ts
1108
- import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync } from "fs";
1109
- import { dirname as dirname3, join as join9, relative as relative2 } from "path";
1363
+ import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync } from "fs";
1364
+ import { dirname as dirname3, join as join10, relative as relative4 } from "path";
1110
1365
  var OVERRIDE_KEY = "i18next>typescript";
1111
1366
  var NATIVE_TS_ALIAS = "@typescript/native";
1112
1367
  var WORKSPACE_YAML = "pnpm-workspace.yaml";
@@ -1115,7 +1370,7 @@ var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
1115
1370
  function findWorkspaceRoot(startDir) {
1116
1371
  let dir = startDir;
1117
1372
  for (; ; ) {
1118
- if (existsSync8(join9(dir, WORKSPACE_ROOT_MARKER))) return dir;
1373
+ if (existsSync9(join10(dir, WORKSPACE_ROOT_MARKER))) return dir;
1119
1374
  const parent = dirname3(dir);
1120
1375
  if (parent === dir) return void 0;
1121
1376
  dir = parent;
@@ -1128,9 +1383,9 @@ function declaredNativeTs(pkg) {
1128
1383
  return version || void 0;
1129
1384
  }
1130
1385
  function ensureI18nextSingleton(root, dryRun) {
1131
- const pkgPath = join9(root, "package.json");
1132
- if (!existsSync8(pkgPath)) return void 0;
1133
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
1386
+ const pkgPath = join10(root, "package.json");
1387
+ if (!existsSync9(pkgPath)) return void 0;
1388
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
1134
1389
  const want = declaredNativeTs(pkg);
1135
1390
  if (!want) return void 0;
1136
1391
  const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
@@ -1143,10 +1398,10 @@ function ensureI18nextSingleton(root, dryRun) {
1143
1398
  return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
1144
1399
  }
1145
1400
  function ensureReleaseAgeAllowList(root, dryRun) {
1146
- const yamlPath = join9(root, WORKSPACE_YAML);
1147
- if (!existsSync8(yamlPath)) return void 0;
1401
+ const yamlPath = join10(root, WORKSPACE_YAML);
1402
+ if (!existsSync9(yamlPath)) return void 0;
1148
1403
  const own = readOwnPackage().name;
1149
- const lines = readFileSync6(yamlPath, "utf8").split("\n");
1404
+ const lines = readFileSync7(yamlPath, "utf8").split("\n");
1150
1405
  const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
1151
1406
  if (keyIdx === -1) return void 0;
1152
1407
  let lastItemIdx = keyIdx;
@@ -1179,13 +1434,13 @@ var ROOT_PRETTIER_CONFIGS = [
1179
1434
  "prettier.config.mjs"
1180
1435
  ];
1181
1436
  function ensureFormatterExclusion(root, moduleDir, dryRun) {
1182
- const rel = relative2(root, moduleDir).replaceAll("\\", "/");
1437
+ const rel = relative4(root, moduleDir).replaceAll("\\", "/");
1183
1438
  if (rel === "" || rel.startsWith("..")) return void 0;
1184
- const ignorePath = join9(root, PRETTIER_IGNORE);
1185
- const hasPrettier = existsSync8(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync8(join9(root, name)));
1439
+ const ignorePath = join10(root, PRETTIER_IGNORE);
1440
+ const hasPrettier = existsSync9(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync9(join10(root, name)));
1186
1441
  if (!hasPrettier) return void 0;
1187
1442
  const pattern = `/${rel}/`;
1188
- const existing = existsSync8(ignorePath) ? readFileSync6(ignorePath, "utf8") : "";
1443
+ const existing = existsSync9(ignorePath) ? readFileSync7(ignorePath, "utf8") : "";
1189
1444
  const lines = existing.split("\n");
1190
1445
  if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
1191
1446
  return void 0;
@@ -1221,10 +1476,10 @@ function ensureWorkspacePrep(opts) {
1221
1476
  function inspectWorkspacePrep(root) {
1222
1477
  const entries = [];
1223
1478
  let pkg = {};
1224
- const pkgPath = join9(root, "package.json");
1225
- if (existsSync8(pkgPath)) {
1479
+ const pkgPath = join10(root, "package.json");
1480
+ if (existsSync9(pkgPath)) {
1226
1481
  try {
1227
- pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
1482
+ pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
1228
1483
  } catch {
1229
1484
  pkg = {};
1230
1485
  }
@@ -1238,9 +1493,9 @@ function inspectWorkspacePrep(root) {
1238
1493
  });
1239
1494
  }
1240
1495
  const own = readOwnPackage().name;
1241
- const yamlPath = join9(root, WORKSPACE_YAML);
1242
- if (existsSync8(yamlPath)) {
1243
- const yaml = readFileSync6(yamlPath, "utf8");
1496
+ const yamlPath = join10(root, WORKSPACE_YAML);
1497
+ if (existsSync9(yamlPath)) {
1498
+ const yaml = readFileSync7(yamlPath, "utf8");
1244
1499
  const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
1245
1500
  if (listed) {
1246
1501
  const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
@@ -1327,8 +1582,8 @@ function writesWhenRewritten(name, command) {
1327
1582
  var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
1328
1583
 
1329
1584
  // src/roles/format/inherited-ignores.ts
1330
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
1331
- import { join as join10 } from "path";
1585
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
1586
+ import { join as join11 } from "path";
1332
1587
  var ROOT_IGNORE_FILE = ".prettierignore";
1333
1588
  function isPattern(line) {
1334
1589
  const trimmed = line.trim();
@@ -1343,11 +1598,11 @@ function toModulePattern(pattern) {
1343
1598
  }
1344
1599
  function inheritedIgnorePatterns(workspaceRoot) {
1345
1600
  if (!workspaceRoot) return [];
1346
- const path = join10(workspaceRoot, ROOT_IGNORE_FILE);
1347
- if (!existsSync9(path)) return [];
1601
+ const path = join11(workspaceRoot, ROOT_IGNORE_FILE);
1602
+ if (!existsSync10(path)) return [];
1348
1603
  let contents;
1349
1604
  try {
1350
- contents = readFileSync7(path, "utf8");
1605
+ contents = readFileSync8(path, "utf8");
1351
1606
  } catch {
1352
1607
  return [];
1353
1608
  }
@@ -1434,13 +1689,13 @@ function formatPresetFor(preset) {
1434
1689
  }
1435
1690
 
1436
1691
  // src/roles/format/prettier-config.ts
1437
- import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
1438
- import { join as join12 } from "path";
1692
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
1693
+ import { join as join13 } from "path";
1439
1694
 
1440
1695
  // src/roles/format/resolve-oxfmt.ts
1441
- import { readFileSync as readFileSync8 } from "fs";
1696
+ import { readFileSync as readFileSync9 } from "fs";
1442
1697
  import { createRequire as createRequire2 } from "module";
1443
- import { dirname as dirname4, join as join11 } from "path";
1698
+ import { dirname as dirname4, join as join12 } from "path";
1444
1699
  function resolveOxfmt(cwd) {
1445
1700
  return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
1446
1701
  }
@@ -1469,8 +1724,8 @@ function configSchema() {
1469
1724
  function readConfigSchema() {
1470
1725
  try {
1471
1726
  const manifest = createRequire2(import.meta.url).resolve("oxfmt/package.json");
1472
- const schemaPath = join11(dirname4(manifest), "configuration_schema.json");
1473
- return JSON.parse(readFileSync8(schemaPath, "utf8"));
1727
+ const schemaPath = join12(dirname4(manifest), "configuration_schema.json");
1728
+ return JSON.parse(readFileSync9(schemaPath, "utf8"));
1474
1729
  } catch {
1475
1730
  return void 0;
1476
1731
  }
@@ -1501,14 +1756,14 @@ function toOxfmtOverrides(value) {
1501
1756
  return { overrides, unresolved };
1502
1757
  }
1503
1758
  function readPrettierSettings(cwd) {
1504
- const file = PRETTIER_CONFIG_FILES.find((name) => existsSync10(join12(cwd, name)));
1759
+ const file = PRETTIER_CONFIG_FILES.find((name) => existsSync11(join13(cwd, name)));
1505
1760
  if (!file) return { options: {}, unresolved: [] };
1506
1761
  if (/\.(js|cjs|mjs)$/.test(file)) {
1507
1762
  return { options: {}, file, unresolved: [`${file} is JavaScript, so it was not executed`] };
1508
1763
  }
1509
1764
  let parsed;
1510
1765
  try {
1511
- parsed = parseJsonc(readFileSync9(join12(cwd, file), "utf8"), file);
1766
+ parsed = parseJsonc(readFileSync10(join13(cwd, file), "utf8"), file);
1512
1767
  } catch {
1513
1768
  return { options: {}, file, unresolved: [`${file} could not be parsed`] };
1514
1769
  }
@@ -1538,8 +1793,8 @@ function readPrettierSettings(cwd) {
1538
1793
  }
1539
1794
 
1540
1795
  // src/roles/format/read-adoption.ts
1541
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
1542
- import { join as join13 } from "path";
1796
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
1797
+ import { join as join14 } from "path";
1543
1798
  var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
1544
1799
  configFile,
1545
1800
  preset: null,
@@ -1555,11 +1810,11 @@ function sameValue(a, b) {
1555
1810
  return JSON.stringify(a) === JSON.stringify(b);
1556
1811
  }
1557
1812
  function readFormatAdoption(cwd) {
1558
- const path = join13(cwd, FORMAT_CONFIG_FILE);
1559
- if (!existsSync11(path)) return NOT_ADOPTED2(null);
1813
+ const path = join14(cwd, FORMAT_CONFIG_FILE);
1814
+ if (!existsSync12(path)) return NOT_ADOPTED2(null);
1560
1815
  let parsed;
1561
1816
  try {
1562
- parsed = parseJsonc(readFileSync10(path, "utf8"), FORMAT_CONFIG_FILE);
1817
+ parsed = parseJsonc(readFileSync11(path, "utf8"), FORMAT_CONFIG_FILE);
1563
1818
  } catch (error) {
1564
1819
  const reason = error instanceof Error ? error.message : String(error);
1565
1820
  return NOT_ADOPTED2(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
@@ -1684,7 +1939,7 @@ var OxfmtAdapter = class extends BaseAdapter {
1684
1939
  manifestOperation(context.cwd, this.formatScripts(context.cwd))
1685
1940
  ];
1686
1941
  const prettierConfigs = PRETTIER_CONFIG_FILES.filter(
1687
- (name) => existsSync12(join14(context.cwd, name))
1942
+ (name) => existsSync13(join15(context.cwd, name))
1688
1943
  );
1689
1944
  for (const name of prettierConfigs) operations.push({ kind: "delete", path: name });
1690
1945
  const removableDeps = this.modulePrettierDependencies(context.cwd);
@@ -1808,7 +2063,7 @@ var OxfmtAdapter = class extends BaseAdapter {
1808
2063
  });
1809
2064
  const result = spawnSync3(
1810
2065
  oxfmt,
1811
- [mode, ...scopeFlags(paths), ...options, ...paths.length > 0 ? paths : ["."]],
2066
+ [mode, ...scopeFlags(paths, options), ...options, ...paths.length > 0 ? paths : ["."]],
1812
2067
  {
1813
2068
  cwd: ctx.cwd,
1814
2069
  stdio: "inherit"
@@ -1924,7 +2179,7 @@ var OxfmtAdapter = class extends BaseAdapter {
1924
2179
  modulePrettierDependencies(cwd) {
1925
2180
  const isPrettierPackage = (name) => name === "prettier" || name.startsWith("prettier-plugin-") || name.startsWith("@prettier/");
1926
2181
  try {
1927
- const manifest = JSON.parse(readFileSync11(join14(cwd, "package.json"), "utf8"));
2182
+ const manifest = JSON.parse(readFileSync12(join15(cwd, "package.json"), "utf8"));
1928
2183
  return Object.keys(manifest.devDependencies ?? {}).filter(isPrettierPackage).map((name) => ["devDependencies", name]);
1929
2184
  } catch {
1930
2185
  return [];
@@ -2006,8 +2261,8 @@ function registerFormat() {
2006
2261
 
2007
2262
  // src/roles/lint/adapters/oxlint/oxlint.adapter.ts
2008
2263
  import { spawnSync as spawnSync4 } from "child_process";
2009
- import { existsSync as existsSync20, readFileSync as readFileSync19, rmSync, writeFileSync as writeFileSync2 } from "fs";
2010
- import { join as join24 } from "path";
2264
+ import { existsSync as existsSync21, readFileSync as readFileSync20, rmSync, writeFileSync as writeFileSync2 } from "fs";
2265
+ import { join as join25 } from "path";
2011
2266
 
2012
2267
  // src/core/config/deferred-rules.ts
2013
2268
  function deferredRuleNames(rules) {
@@ -2016,7 +2271,7 @@ function deferredRuleNames(rules) {
2016
2271
 
2017
2272
  // src/core/fix-report.ts
2018
2273
  import { statSync } from "fs";
2019
- import { join as join15 } from "path";
2274
+ import { join as join16 } from "path";
2020
2275
  var CONFIG_REFUSED = /failed to parse .*config|invalid config file/i;
2021
2276
  function readFailure(output) {
2022
2277
  const lines = output.split("\n").map((line2) => line2.trim());
@@ -2036,7 +2291,7 @@ function fingerprint(path) {
2036
2291
  function snapshotFiles(cwd, extensions) {
2037
2292
  const snapshot = /* @__PURE__ */ new Map();
2038
2293
  for (const file of listSourceFiles(cwd, extensions)) {
2039
- const mark = fingerprint(join15(cwd, file));
2294
+ const mark = fingerprint(join16(cwd, file));
2040
2295
  if (mark !== void 0) snapshot.set(file, mark);
2041
2296
  }
2042
2297
  return snapshot;
@@ -2044,7 +2299,7 @@ function snapshotFiles(cwd, extensions) {
2044
2299
  function changedSince(cwd, before, extensions) {
2045
2300
  const changed = [];
2046
2301
  for (const file of listSourceFiles(cwd, extensions)) {
2047
- const now = fingerprint(join15(cwd, file));
2302
+ const now = fingerprint(join16(cwd, file));
2048
2303
  if (now === void 0) continue;
2049
2304
  if (before.get(file) !== now) changed.push(file);
2050
2305
  }
@@ -2063,8 +2318,8 @@ function describeFixOutcome(outcome, toolLabel) {
2063
2318
  }
2064
2319
 
2065
2320
  // src/roles/lint/module-baseline.ts
2066
- import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
2067
- import { join as join16 } from "path";
2321
+ import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
2322
+ import { join as join17 } from "path";
2068
2323
  var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
2069
2324
  var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
2070
2325
  var LINT_BASELINE_SPECIFIER = `./${LINT_BASELINE_FILE}`;
@@ -2108,11 +2363,11 @@ function describeHolds(holds) {
2108
2363
  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`;
2109
2364
  }
2110
2365
  function heldRules(cwd) {
2111
- const path = join16(cwd, LINT_BASELINE_FILE);
2112
- if (!existsSync13(path)) return [];
2366
+ const path = join17(cwd, LINT_BASELINE_FILE);
2367
+ if (!existsSync14(path)) return [];
2113
2368
  try {
2114
2369
  const parsed = parseJsonc(
2115
- readFileSync12(path, "utf8"),
2370
+ readFileSync13(path, "utf8"),
2116
2371
  LINT_BASELINE_FILE
2117
2372
  );
2118
2373
  return Object.keys(parsed.rules ?? {});
@@ -3935,14 +4190,14 @@ function downgradedRulesFor(preset) {
3935
4190
  }
3936
4191
 
3937
4192
  // src/roles/lint/extra-layers.ts
3938
- import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
3939
- import { isAbsolute as isAbsolute2, join as join17, resolve as resolve3 } from "path";
4193
+ import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
4194
+ import { isAbsolute as isAbsolute2, join as join18, resolve as resolve3 } from "path";
3940
4195
  function ruleCount(cwd, specifier) {
3941
4196
  const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
3942
- if (!existsSync14(path)) return void 0;
4197
+ if (!existsSync15(path)) return void 0;
3943
4198
  try {
3944
4199
  const parsed = parseJsonc(
3945
- readFileSync13(path, "utf8"),
4200
+ readFileSync14(path, "utf8"),
3946
4201
  specifier
3947
4202
  );
3948
4203
  return Object.keys(parsed.rules ?? {}).length;
@@ -3962,7 +4217,7 @@ function extraLayers(cwd, extendsList) {
3962
4217
  function committedExtendsList(cwd, configFile) {
3963
4218
  try {
3964
4219
  const parsed = parseJsonc(
3965
- readFileSync13(join17(cwd, configFile), "utf8"),
4220
+ readFileSync14(join18(cwd, configFile), "utf8"),
3966
4221
  configFile
3967
4222
  );
3968
4223
  return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
@@ -4021,8 +4276,8 @@ function errorCountsByConfigRule(stdout) {
4021
4276
  }
4022
4277
 
4023
4278
  // src/core/config/read-adoption.ts
4024
- import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
4025
- import { join as join19 } from "path";
4279
+ import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
4280
+ import { join as join20 } from "path";
4026
4281
 
4027
4282
  // src/core/config/owned-keys.ts
4028
4283
  function presetOwnedKeys(config, permitted, presetSets) {
@@ -4037,12 +4292,12 @@ function localOnlyKeys(config, permitted, presetSets) {
4037
4292
  }
4038
4293
 
4039
4294
  // src/core/config/resolve-config-target.ts
4040
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
4041
- import { join as join18 } from "path";
4295
+ import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
4296
+ import { join as join19 } from "path";
4042
4297
  function readExtends(absolutePath) {
4043
4298
  let parsed;
4044
4299
  try {
4045
- parsed = parseJsonc(readFileSync14(absolutePath, "utf8"), absolutePath);
4300
+ parsed = parseJsonc(readFileSync15(absolutePath, "utf8"), absolutePath);
4046
4301
  } catch {
4047
4302
  return [];
4048
4303
  }
@@ -4056,8 +4311,8 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
4056
4311
  let existing;
4057
4312
  let existingExtendsSomething = false;
4058
4313
  for (const candidate of candidates) {
4059
- const absolutePath = join18(moduleDir, candidate);
4060
- if (!existsSync15(absolutePath)) continue;
4314
+ const absolutePath = join19(moduleDir, candidate);
4315
+ if (!existsSync16(absolutePath)) continue;
4061
4316
  const chain = readExtends(absolutePath);
4062
4317
  if (existing === void 0) {
4063
4318
  existing = candidate;
@@ -4090,12 +4345,12 @@ var NOT_ADOPTED3 = (configFile, unreadable = null) => ({
4090
4345
  });
4091
4346
  function readAdoption(cwd, options) {
4092
4347
  const target = resolveConfigTarget(cwd, options);
4093
- if (target.reason === "none" || !existsSync16(join19(cwd, target.path))) {
4348
+ if (target.reason === "none" || !existsSync17(join20(cwd, target.path))) {
4094
4349
  return NOT_ADOPTED3(target.reason === "none" ? null : target.path);
4095
4350
  }
4096
4351
  let parsed;
4097
4352
  try {
4098
- parsed = parseJsonc(readFileSync15(join19(cwd, target.path), "utf8"), target.path);
4353
+ parsed = parseJsonc(readFileSync16(join20(cwd, target.path), "utf8"), target.path);
4099
4354
  } catch (error) {
4100
4355
  const reason = error instanceof Error ? error.message : String(error);
4101
4356
  return NOT_ADOPTED3(target.path, `${target.path} could not be parsed (${reason})`);
@@ -4129,9 +4384,9 @@ function readLintAdoption(cwd) {
4129
4384
  }
4130
4385
 
4131
4386
  // src/roles/lint/resolve-oxlint.ts
4132
- import { existsSync as existsSync17 } from "fs";
4387
+ import { existsSync as existsSync18 } from "fs";
4133
4388
  import { createRequire as createRequire3 } from "module";
4134
- import { delimiter as delimiter2, dirname as dirname5, join as join20 } from "path";
4389
+ import { delimiter as delimiter2, dirname as dirname5, join as join21 } from "path";
4135
4390
  import { fileURLToPath as fileURLToPath2 } from "url";
4136
4391
  var PACKAGE_OF = {
4137
4392
  oxlint: "oxlint",
@@ -4156,32 +4411,31 @@ function tsgolintShim(cwd) {
4156
4411
  for (const owner of ["oxlint-tsgolint", "oxlint"]) {
4157
4412
  try {
4158
4413
  const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
4159
- candidates.push(join20(packageDir, "node_modules", ".bin", "tsgolint"));
4160
- candidates.push(join20(packageDir, "..", ".bin", "tsgolint"));
4414
+ candidates.push(join21(packageDir, "node_modules", ".bin", "tsgolint"));
4415
+ candidates.push(join21(packageDir, "..", ".bin", "tsgolint"));
4161
4416
  } catch {
4162
4417
  }
4163
4418
  }
4164
4419
  try {
4165
4420
  const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
4166
- candidates.push(join20(ownRoot, "node_modules", ".bin", "tsgolint"));
4421
+ candidates.push(join21(ownRoot, "node_modules", ".bin", "tsgolint"));
4167
4422
  } catch {
4168
4423
  candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
4169
4424
  }
4170
- return candidates.find((candidate) => candidate !== "" && existsSync17(candidate));
4425
+ return candidates.find((candidate) => candidate !== "" && existsSync18(candidate));
4171
4426
  }
4172
4427
  function oxlintSearchPath(cwd) {
4173
4428
  return binSearchPath(cwd);
4174
4429
  }
4175
4430
 
4176
4431
  // src/roles/lint/adapters/oxlint/plan.ts
4177
- import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
4178
- import { join as join23 } from "path";
4432
+ import { existsSync as existsSync20, readFileSync as readFileSync19 } from "fs";
4433
+ import { join as join24 } from "path";
4179
4434
 
4180
4435
  // src/roles/lint/eslint-ignores.ts
4181
- import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
4182
- import { join as join21 } from "path";
4183
- var IGNORE_BLOCKS = [/\bignores\s*:\s*\[([^\]]*)\]/g, /\bglobalIgnores\s*\(\s*\[([^\]]*)\]/g];
4184
- var STRING_LITERAL = /['"`]([^'"`]+)['"`]/g;
4436
+ import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
4437
+ import { join as join22 } from "path";
4438
+ import { parseSync as parseSync2 } from "oxc-parser";
4185
4439
  var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
4186
4440
  function toOxlintPattern(pattern) {
4187
4441
  return pattern.replace(/^\.\//, "");
@@ -4192,24 +4446,71 @@ function readRootEslintIgnores(root) {
4192
4446
  return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
4193
4447
  }
4194
4448
  function readEslintIgnores(cwd) {
4195
- const config = ESLINT_CONFIG_FILES.map((name) => join21(cwd, name)).find((path) => existsSync18(path));
4449
+ const config = ESLINT_CONFIG_FILES.map((name) => join22(cwd, name)).find((path) => existsSync19(path));
4196
4450
  if (!config) return { patterns: [], unresolved: [] };
4197
4451
  let source;
4198
4452
  try {
4199
- source = readFileSync16(config, "utf8");
4453
+ source = readFileSync17(config, "utf8");
4200
4454
  } catch {
4201
4455
  return { patterns: [], unresolved: [] };
4202
4456
  }
4203
4457
  const patterns = /* @__PURE__ */ new Set();
4204
- for (const block of IGNORE_BLOCKS) {
4205
- for (const [, body] of source.matchAll(block)) {
4206
- for (const [, literal] of (body ?? "").matchAll(STRING_LITERAL)) {
4207
- if (literal) patterns.add(toOxlintPattern(literal));
4458
+ const unresolved = new Set([...source.matchAll(OPAQUE_SOURCE)].map((match) => match[0]));
4459
+ const { program, errors } = parseSync2(config, source, { sourceType: "module" });
4460
+ if (errors.length > 0) {
4461
+ return { patterns: [], unresolved: [`${config} could not be parsed`] };
4462
+ }
4463
+ walk(program, (node) => {
4464
+ if (node.type === "CallExpression" && calleeName(node) === "globalIgnores") {
4465
+ for (const literal of stringLiterals(firstArrayArgument(node))) {
4466
+ patterns.add(toOxlintPattern(literal));
4208
4467
  }
4468
+ return;
4209
4469
  }
4470
+ if (node.type !== "ObjectExpression") return;
4471
+ const keys = objectKeys(node);
4472
+ if (!keys.includes("ignores")) return;
4473
+ const values = arrayValues(node, "ignores");
4474
+ if (keys.length > 1) {
4475
+ unresolved.add(
4476
+ `ignores scoped to one config block (with ${keys.filter((k) => k !== "ignores").join(", ")}): ${values.join(", ")} \u2014 exempt from those RULES, not excluded from linting`
4477
+ );
4478
+ return;
4479
+ }
4480
+ for (const literal of values) patterns.add(toOxlintPattern(literal));
4481
+ });
4482
+ return { patterns: [...patterns], unresolved: [...unresolved] };
4483
+ }
4484
+ function walk(node, visit) {
4485
+ if (!node || typeof node !== "object") return;
4486
+ const current = node;
4487
+ if (typeof current.type === "string") visit(current);
4488
+ for (const key of Object.keys(current)) {
4489
+ const value = current[key];
4490
+ if (Array.isArray(value)) for (const entry of value) walk(entry, visit);
4491
+ else if (value && typeof value === "object") walk(value, visit);
4210
4492
  }
4211
- const unresolved = [...source.matchAll(OPAQUE_SOURCE)].map((match) => match[0]);
4212
- return { patterns: [...patterns], unresolved: [...new Set(unresolved)] };
4493
+ }
4494
+ function calleeName(node) {
4495
+ const callee = node.callee;
4496
+ return callee?.type === "Identifier" ? callee.name : void 0;
4497
+ }
4498
+ function firstArrayArgument(node) {
4499
+ const args = node.arguments ?? [];
4500
+ return args.find((argument) => argument?.type === "ArrayExpression");
4501
+ }
4502
+ function stringLiterals(array) {
4503
+ const elements = array?.elements ?? [];
4504
+ return elements.filter((element) => element?.type === "Literal" && typeof element.value === "string").map((element) => element.value);
4505
+ }
4506
+ function objectKeys(node) {
4507
+ const properties = node.properties ?? [];
4508
+ return properties.filter((property) => property?.type === "Property" && !property.computed).map((property) => property.key?.name ?? String(property.key?.value ?? "")).filter(Boolean);
4509
+ }
4510
+ function arrayValues(node, name) {
4511
+ const properties = node.properties ?? [];
4512
+ const found = properties.find((property) => property.key?.name === name);
4513
+ return stringLiterals(found?.value);
4213
4514
  }
4214
4515
 
4215
4516
  // src/roles/lint/lint-script.ts
@@ -4250,8 +4551,8 @@ function lintPresetFor(preset) {
4250
4551
  }
4251
4552
 
4252
4553
  // src/roles/lint/rename-suppressions.ts
4253
- import { readdirSync as readdirSync2, readFileSync as readFileSync17, statSync as statSync2 } from "fs";
4254
- import { join as join22, relative as relative3 } from "path";
4554
+ import { readdirSync as readdirSync2, readFileSync as readFileSync18, statSync as statSync2 } from "fs";
4555
+ import { join as join23, relative as relative5 } from "path";
4255
4556
  var SOURCE_EXTENSIONS = [
4256
4557
  ".ts",
4257
4558
  ".tsx",
@@ -4298,7 +4599,7 @@ function* sourceFiles(dir) {
4298
4599
  return;
4299
4600
  }
4300
4601
  for (const entry of entries) {
4301
- const full = join22(dir, entry);
4602
+ const full = join23(dir, entry);
4302
4603
  let isDirectory;
4303
4604
  try {
4304
4605
  isDirectory = statSync2(full).isDirectory();
@@ -4318,7 +4619,7 @@ function findSuppressionRenames(cwd, renames) {
4318
4619
  for (const file of sourceFiles(cwd)) {
4319
4620
  let content;
4320
4621
  try {
4321
- content = readFileSync17(file, "utf8");
4622
+ content = readFileSync18(file, "utf8");
4322
4623
  } catch {
4323
4624
  continue;
4324
4625
  }
@@ -4342,7 +4643,7 @@ function findSuppressionRenames(cwd, renames) {
4342
4643
  }
4343
4644
  if (hits.length === 0) return;
4344
4645
  found.push({
4345
- file: relative3(cwd, file),
4646
+ file: relative5(cwd, file),
4346
4647
  line: index + 1,
4347
4648
  from: line,
4348
4649
  to: line.replace(rules, renamed.join(", ")),
@@ -4446,7 +4747,7 @@ function plan2(context) {
4446
4747
  keys: removableDeps
4447
4748
  });
4448
4749
  }
4449
- const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync19(join23(context.cwd, name)));
4750
+ const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync20(join24(context.cwd, name)));
4450
4751
  for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
4451
4752
  operations.push(
4452
4753
  ...nxTargetOperations({
@@ -4544,7 +4845,7 @@ function lintScripts(cwd) {
4544
4845
  function committedExtends(cwd) {
4545
4846
  try {
4546
4847
  const parsed = parseJsonc(
4547
- readFileSync18(join23(cwd, LINT_CONFIG_FILE), "utf8"),
4848
+ readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
4548
4849
  LINT_CONFIG_FILE
4549
4850
  );
4550
4851
  return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
@@ -4555,7 +4856,7 @@ function committedExtends(cwd) {
4555
4856
  function committedIgnorePatterns(cwd) {
4556
4857
  try {
4557
4858
  const parsed = parseJsonc(
4558
- readFileSync18(join23(cwd, LINT_CONFIG_FILE), "utf8"),
4859
+ readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
4559
4860
  LINT_CONFIG_FILE
4560
4861
  );
4561
4862
  return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
@@ -4567,7 +4868,7 @@ function moduleEslintDependencies(cwd) {
4567
4868
  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/");
4568
4869
  let manifest;
4569
4870
  try {
4570
- manifest = JSON.parse(readFileSync18(join23(cwd, "package.json"), "utf8"));
4871
+ manifest = JSON.parse(readFileSync19(join24(cwd, "package.json"), "utf8"));
4571
4872
  } catch {
4572
4873
  return [];
4573
4874
  }
@@ -4701,20 +5002,20 @@ ${result.stderr ?? ""}`);
4701
5002
  * build the developer can see, rather than a silent half-adoption they cannot.
4702
5003
  */
4703
5004
  writeModuleBaseline(ctx, oxlint, env) {
4704
- const configPath = join24(ctx.cwd, LINT_CONFIG_FILE);
4705
- const baselinePath = join24(ctx.cwd, LINT_BASELINE_FILE);
4706
- if (!existsSync20(configPath)) return;
5005
+ const configPath = join25(ctx.cwd, LINT_CONFIG_FILE);
5006
+ const baselinePath = join25(ctx.cwd, LINT_BASELINE_FILE);
5007
+ if (!existsSync21(configPath)) return;
4707
5008
  let config;
4708
5009
  try {
4709
5010
  config = parseJsonc(
4710
- readFileSync19(configPath, "utf8"),
5011
+ readFileSync20(configPath, "utf8"),
4711
5012
  LINT_CONFIG_FILE
4712
5013
  );
4713
5014
  } catch {
4714
5015
  return;
4715
5016
  }
4716
5017
  const current = Array.isArray(config.extends) ? config.extends : [];
4717
- const measurePath = join24(ctx.cwd, LINT_MEASURE_FILE);
5018
+ const measurePath = join25(ctx.cwd, LINT_MEASURE_FILE);
4718
5019
  let measured;
4719
5020
  try {
4720
5021
  writeFileSync2(
@@ -4755,14 +5056,14 @@ ${result.stderr ?? ""}`);
4755
5056
  } else {
4756
5057
  rmSync(baselinePath, { force: true });
4757
5058
  }
4758
- if (next.length !== current.length || next.some((entry, at2) => entry !== current[at2])) {
5059
+ if (next.length !== current.length || next.some((entry, at) => entry !== current[at])) {
4759
5060
  config.extends = next;
4760
5061
  writeFileSync2(configPath, `${JSON.stringify(config, null, 2)}
4761
5062
  `);
4762
5063
  }
4763
5064
  }
4764
5065
  async run(ctx) {
4765
- if (!existsSync20(join24(ctx.cwd, LINT_CONFIG_FILE))) {
5066
+ if (!existsSync21(join25(ctx.cwd, LINT_CONFIG_FILE))) {
4766
5067
  process.stderr.write(
4767
5068
  `sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
4768
5069
  `
@@ -4807,7 +5108,7 @@ ${result.stderr ?? ""}`);
4807
5108
  valueFlags: OXLINT_VALUE_FLAGS
4808
5109
  });
4809
5110
  const targets = paths.length > 0 ? paths : ["."];
4810
- const emptyScope = scopeFlags(paths);
5111
+ const emptyScope = scopeFlags(paths, passedOptions);
4811
5112
  const lint = (extra) => {
4812
5113
  const passed = [...extra, ...emptyScope, ...passedOptions];
4813
5114
  const result = spawnSync4(oxlint, ["-c", LINT_CONFIG_FILE, ...passed, ...targets], {
@@ -4910,7 +5211,7 @@ ${result.stderr ?? ""}`);
4910
5211
  let stub;
4911
5212
  try {
4912
5213
  stub = parseJsonc(
4913
- readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
5214
+ readFileSync20(join25(cwd, LINT_CONFIG_FILE), "utf8"),
4914
5215
  LINT_CONFIG_FILE
4915
5216
  );
4916
5217
  } catch {
@@ -4920,7 +5221,7 @@ ${result.stderr ?? ""}`);
4920
5221
  for (const entry of stub.extends ?? []) {
4921
5222
  try {
4922
5223
  const preset = parseJsonc(
4923
- readFileSync19(join24(cwd, entry), "utf8"),
5224
+ readFileSync20(join25(cwd, entry), "utf8"),
4924
5225
  entry
4925
5226
  );
4926
5227
  for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
@@ -4990,14 +5291,14 @@ ${result.stderr ?? ""}`);
4990
5291
  let parsed;
4991
5292
  try {
4992
5293
  parsed = parseJsonc(
4993
- readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
5294
+ readFileSync20(join25(cwd, LINT_CONFIG_FILE), "utf8"),
4994
5295
  LINT_CONFIG_FILE
4995
5296
  );
4996
5297
  } catch {
4997
5298
  return void 0;
4998
5299
  }
4999
5300
  const targets = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
5000
- return targets.find((target) => !existsSync20(join24(cwd, target)));
5301
+ return targets.find((target) => !existsSync21(join25(cwd, target)));
5001
5302
  }
5002
5303
  /** Announce what is not enforced, so reduced coverage is never silent. */
5003
5304
  announceDisabled(preset) {
@@ -5029,9 +5330,9 @@ function registerLint() {
5029
5330
 
5030
5331
  // src/roles/typescript/adapters/tsc/tsc.adapter.ts
5031
5332
  import { spawnSync as spawnSync5 } from "child_process";
5032
- import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
5333
+ import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
5033
5334
  import { createRequire as createRequire4 } from "module";
5034
- import { join as join26 } from "path";
5335
+ import { join as join27 } from "path";
5035
5336
 
5036
5337
  // src/roles/typescript/presets/nest.json
5037
5338
  var nest_default2 = {
@@ -5212,8 +5513,8 @@ function readTsconfigAdoption(cwd) {
5212
5513
  }
5213
5514
 
5214
5515
  // src/roles/typescript/adapters/tsc/plan.ts
5215
- import { readFileSync as readFileSync20 } from "fs";
5216
- import { join as join25 } from "path";
5516
+ import { readFileSync as readFileSync21 } from "fs";
5517
+ import { join as join26 } from "path";
5217
5518
 
5218
5519
  // src/roles/typescript/typecheck-script.ts
5219
5520
  var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
@@ -5306,7 +5607,7 @@ function planAdoption(context) {
5306
5607
  };
5307
5608
  }
5308
5609
  const existing = parseJsonc(
5309
- readFileSync20(join25(context.cwd, target.path), "utf8"),
5610
+ readFileSync21(join26(context.cwd, target.path), "utf8"),
5310
5611
  target.path
5311
5612
  );
5312
5613
  const extendsChain = composeExtends(existing.extends, preset);
@@ -5444,7 +5745,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
5444
5745
  let chain;
5445
5746
  try {
5446
5747
  const parsed = parseJsonc(
5447
- readFileSync21(join26(cwd, target.path), "utf8"),
5748
+ readFileSync22(join27(cwd, target.path), "utf8"),
5448
5749
  target.path
5449
5750
  );
5450
5751
  chain = parsed.extends;
@@ -5457,7 +5758,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
5457
5758
  );
5458
5759
  if (preset === void 0) return void 0;
5459
5760
  try {
5460
- createRequire4(join26(cwd, "noop.js")).resolve(preset);
5761
+ createRequire4(join27(cwd, "noop.js")).resolve(preset);
5461
5762
  return void 0;
5462
5763
  } catch {
5463
5764
  return preset;
@@ -5553,7 +5854,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
5553
5854
  referencedProjects(cwd, config) {
5554
5855
  try {
5555
5856
  const parsed = parseJsonc(
5556
- readFileSync21(join26(cwd, config), "utf8"),
5857
+ readFileSync22(join27(cwd, config), "utf8"),
5557
5858
  config
5558
5859
  );
5559
5860
  const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
@@ -5569,7 +5870,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
5569
5870
  * check.
5570
5871
  */
5571
5872
  typecheckTarget(cwd) {
5572
- if (existsSync21(join26(cwd, "tsconfig.json"))) return "tsconfig.json";
5873
+ if (existsSync22(join27(cwd, "tsconfig.json"))) return "tsconfig.json";
5573
5874
  const target = resolveTsconfigTarget(cwd);
5574
5875
  return target.reason === "none" ? null : target.path;
5575
5876
  }
@@ -5717,7 +6018,7 @@ function detectFramework(packageJson) {
5717
6018
  }
5718
6019
 
5719
6020
  // src/core/dispatch.ts
5720
- import { basename as basename2 } from "path";
6021
+ import { basename as basename3 } from "path";
5721
6022
 
5722
6023
  // src/shared/text.ts
5723
6024
  function ensureLines(current, lines) {
@@ -5773,9 +6074,16 @@ function replaceLines(current, replacements) {
5773
6074
  }
5774
6075
 
5775
6076
  // src/core/apply-plan.ts
5776
- import { existsSync as existsSync22, readFileSync as readFileSync22, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
6077
+ import { existsSync as existsSync23, readFileSync as readFileSync23, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
5777
6078
  import { resolve as resolve4, sep } from "path";
5778
6079
  import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
6080
+
6081
+ // src/shared/deep-merge.ts
6082
+ function isPlainObject(value) {
6083
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6084
+ }
6085
+
6086
+ // src/core/apply-plan.ts
5779
6087
  function resolveWithinRoot(cwd, relativePath) {
5780
6088
  const root = resolve4(cwd);
5781
6089
  const absolutePath = resolve4(root, relativePath);
@@ -5785,7 +6093,7 @@ function resolveWithinRoot(cwd, relativePath) {
5785
6093
  return absolutePath;
5786
6094
  }
5787
6095
  function readIfExists(absolutePath) {
5788
- return existsSync22(absolutePath) ? readFileSync22(absolutePath, "utf8") : void 0;
6096
+ return existsSync23(absolutePath) ? readFileSync23(absolutePath, "utf8") : void 0;
5789
6097
  }
5790
6098
  function* leaves(value, prefix = []) {
5791
6099
  for (const [key, keyValue] of Object.entries(value)) {
@@ -5903,8 +6211,8 @@ function applyPlan(cwd, plan3) {
5903
6211
  }
5904
6212
 
5905
6213
  // src/core/config/preset-evidence.ts
5906
- import { existsSync as existsSync23, readdirSync as readdirSync3, readFileSync as readFileSync23 } from "fs";
5907
- import { join as join27 } from "path";
6214
+ import { existsSync as existsSync24, readdirSync as readdirSync3, readFileSync as readFileSync24 } from "fs";
6215
+ import { join as join28 } from "path";
5908
6216
  var PATH_SIGNALS = [
5909
6217
  {
5910
6218
  preset: "nest",
@@ -5918,11 +6226,11 @@ var DEPENDENCY_SIGNALS = [
5918
6226
  { preset: "nest", pattern: /^@nestjs\// }
5919
6227
  ];
5920
6228
  function dependencyNames(cwd) {
5921
- const path = join27(cwd, "package.json");
5922
- if (!existsSync23(path)) return [];
6229
+ const path = join28(cwd, "package.json");
6230
+ if (!existsSync24(path)) return [];
5923
6231
  try {
5924
6232
  const manifest = parseJsonc(
5925
- readFileSync23(path, "utf8"),
6233
+ readFileSync24(path, "utf8"),
5926
6234
  path
5927
6235
  );
5928
6236
  return [
@@ -5945,7 +6253,7 @@ function declaresJsx(cwd) {
5945
6253
  for (const name of entries) {
5946
6254
  try {
5947
6255
  const config = parseJsonc(
5948
- readFileSync23(join27(cwd, name), "utf8"),
6256
+ readFileSync24(join28(cwd, name), "utf8"),
5949
6257
  name
5950
6258
  );
5951
6259
  if (config.compilerOptions?.jsx !== void 0) return true;
@@ -6086,7 +6394,7 @@ async function dispatch(opts) {
6086
6394
  process.stderr.write(` fixing what ${opts.target} can fix automatically...
6087
6395
  `);
6088
6396
  await adapter.afterInit({
6089
- module: basename2(opts.cwd),
6397
+ module: basename3(opts.cwd),
6090
6398
  cwd: opts.cwd,
6091
6399
  preset,
6092
6400
  ci: false,
@@ -6108,9 +6416,11 @@ export {
6108
6416
  resolveBin,
6109
6417
  readOwnVersion,
6110
6418
  readProjectPackageJson,
6111
- readNxProjectName,
6419
+ moduleName,
6420
+ buildConfigFile,
6112
6421
  VERBS,
6113
6422
  TARGETS,
6423
+ LONG_RUNNING_TARGETS,
6114
6424
  SWEEPABLE_TARGETS,
6115
6425
  PRESET_NAMES,
6116
6426
  WORKSPACE_ROOT_MARKER,
@@ -6123,4 +6433,4 @@ export {
6123
6433
  detectFramework,
6124
6434
  dispatch
6125
6435
  };
6126
- //# sourceMappingURL=chunk-FLJ2QNFF.js.map
6436
+ //# sourceMappingURL=chunk-6VOQDDZD.js.map