@hublo/sentinel 1.2.0-alpha.1 → 1.2.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/bin/sentinel.d.ts +5 -0
- package/dist/bin/sentinel.js +30 -7
- package/dist/{chunk-FLJ2QNFF.js → chunk-YZ4EKBK7.js} +336 -198
- package/dist/index.js +1 -2
- package/dist/roles/build/toolchain.d.ts +5 -0
- package/dist/roles/build/toolchain.js +16 -0
- package/package.json +4 -3
- package/dist/chunk-XVDOQ3G3.js +0 -39
- package/dist/roles/build/react-app.d.ts +0 -112
- package/dist/roles/build/react-app.js +0 -126
package/README.md
CHANGED
|
@@ -151,7 +151,7 @@ pnpm dlx @hublo/sentinel@<exact-version> --inspect --typescript --module <name>
|
|
|
151
151
|
- [`docs/using-sentinel.md`](docs/using-sentinel.md) — every verb for every role, scoping a run to some files, what a fully adopted module looks like, and how versions move between modules.
|
|
152
152
|
- [`docs/lint-adoption.md`](docs/lint-adoption.md) — migrating a module from ESLint to Oxlint: the two steps, what `--init` writes and removes, the two react tiers, and the failures worth recognising.
|
|
153
153
|
- [`docs/format-adoption.md`](docs/format-adoption.md) — migrating a module from Prettier to oxfmt: why this config is materialized rather than a stub, how a module keeps its own formatting, and what did not survive the move.
|
|
154
|
-
- [`docs/build-adoption.md`](docs/build-adoption.md) — moving a React app's Vite toolchain into sentinel:
|
|
154
|
+
- [`docs/build-adoption.md`](docs/build-adoption.md) — moving a React app's Vite toolchain into sentinel: adoption changes the config's IMPORTS and nothing else, what sentinel owns versus what stays the app's, and why reading nothing is what makes losing nothing a guarantee.
|
|
155
155
|
- [`docs/typescript-adoption.md`](docs/typescript-adoption.md) — the adoption cheat sheet: the two adoption steps, the command model (verb x type x location), options, reading a report, and troubleshooting.
|
|
156
156
|
- [`docs/typescript-traces.md`](docs/typescript-traces.md) — a **generated, versioned** reference of live command + output traces (every verb, option, config result and edge case) against the mock monorepo. Regenerate after CLI changes with `pnpm docs:traces`.
|
|
157
157
|
|
|
@@ -161,7 +161,7 @@ Every check is described by three layers:
|
|
|
161
161
|
|
|
162
162
|
| Layer | Flag | What it is | Examples |
|
|
163
163
|
| -------------------- | --------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
164
|
-
| **target** (role) | `--lint`, `--typescript`, … | the _kind_ of check, stable | `lint` `format` `typescript` `build` `test` `static-analysis` `runtime-analysis`
|
|
164
|
+
| **target** (role) | `--lint`, `--typescript`, … | the _kind_ of check, stable | `lint` `format` `typescript` `build` `dev` `test` `static-analysis` `runtime-analysis` |
|
|
165
165
|
| **runner** (adapter) | `--runner=<tool>` | the _tool_ behind the target, swappable | lint: `eslint`/`biome`/`oxlint` · types: `tsc`/`tsgo` · build: `vite` · test: `vitest` |
|
|
166
166
|
| **preset** (preset) | detected / `--preset` | the _variant_ per stack (strict by default) | `react` `nest` `node` `svelte` (svelte: lint + format; no tsconfig preset yet, so the TypeScript role stands aside on those modules and the other two adopt) |
|
|
167
167
|
|
package/dist/bin/sentinel.d.ts
CHANGED
package/dist/bin/sentinel.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
LONG_RUNNING_TARGETS,
|
|
3
4
|
PRESET_NAMES,
|
|
4
5
|
PresetUnsupportedError,
|
|
5
6
|
SWEEPABLE_TARGETS,
|
|
@@ -13,22 +14,21 @@ import {
|
|
|
13
14
|
ensureWorkspacePrep,
|
|
14
15
|
findWorkspaceRoot,
|
|
15
16
|
inspectWorkspacePrep,
|
|
17
|
+
moduleName,
|
|
16
18
|
palette,
|
|
17
|
-
readNxProjectName,
|
|
18
19
|
readOwnVersion,
|
|
19
20
|
readProjectPackageJson,
|
|
20
21
|
registerAdapters,
|
|
21
22
|
resolve,
|
|
22
23
|
resolveBin
|
|
23
|
-
} from "../chunk-
|
|
24
|
-
import "../chunk-XVDOQ3G3.js";
|
|
24
|
+
} from "../chunk-YZ4EKBK7.js";
|
|
25
25
|
|
|
26
26
|
// bin/sentinel.ts
|
|
27
27
|
import { program } from "commander";
|
|
28
28
|
|
|
29
29
|
// src/core/context.ts
|
|
30
30
|
import { existsSync } from "fs";
|
|
31
|
-
import {
|
|
31
|
+
import { join as join2 } from "path";
|
|
32
32
|
|
|
33
33
|
// src/core/discover-modules.ts
|
|
34
34
|
import { execFileSync } from "child_process";
|
|
@@ -96,7 +96,7 @@ function resolveContext(cwd2, opts2) {
|
|
|
96
96
|
if (opts2.ci) {
|
|
97
97
|
throw new Error("--ci selects the affected set from the workspace root; run it there.");
|
|
98
98
|
}
|
|
99
|
-
const name =
|
|
99
|
+
const name = moduleName(cwd2);
|
|
100
100
|
return { modules: [{ name, root: cwd2 }], scope: "cwd-module" };
|
|
101
101
|
}
|
|
102
102
|
if (atRoot) {
|
|
@@ -463,6 +463,15 @@ async function runVerb(ctx) {
|
|
|
463
463
|
const { modules, scope } = resolveContext(ctx.cwd, { module: ctx.module, ci: ctx.ci });
|
|
464
464
|
const out = palette(process.stdout);
|
|
465
465
|
const err = palette(process.stderr);
|
|
466
|
+
const longRunning = ctx.targets.filter((target) => LONG_RUNNING_TARGETS.includes(target));
|
|
467
|
+
if (longRunning.length > 0 && modules.length > 1) {
|
|
468
|
+
process.stderr.write(
|
|
469
|
+
err.fail(
|
|
470
|
+
`sentinel: ${longRunning.map((t) => `--${t}`).join(", ")} starts a server that does not return, so it runs against ONE module. This resolved to ${modules.length} [${scope}]. Run it from inside the module's directory, or pass --module <name>.`
|
|
471
|
+
) + "\n"
|
|
472
|
+
);
|
|
473
|
+
return 1;
|
|
474
|
+
}
|
|
466
475
|
const shaped = ctx.verb === "run" && ctx.json ? "report" : ctx.verb;
|
|
467
476
|
const replacement = {
|
|
468
477
|
report: "--run --json",
|
|
@@ -579,6 +588,17 @@ registerAdapters();
|
|
|
579
588
|
var separator = process.argv.indexOf("--");
|
|
580
589
|
var toolArgs = separator === -1 ? [] : process.argv.slice(separator + 1);
|
|
581
590
|
var cliArgv = separator === -1 ? process.argv : process.argv.slice(0, separator);
|
|
591
|
+
var TARGET_HELP = {
|
|
592
|
+
lint: "linting",
|
|
593
|
+
format: "formatting",
|
|
594
|
+
typescript: "type checking",
|
|
595
|
+
build: "build",
|
|
596
|
+
dev: "dev server (long-running: never swept by an unqualified --run)",
|
|
597
|
+
test: "tests",
|
|
598
|
+
"static-analysis": "cycles, complexity, duplication, centrality",
|
|
599
|
+
"runtime-analysis": "bundle, Lighthouse, web vitals",
|
|
600
|
+
arch: "architecture boundaries"
|
|
601
|
+
};
|
|
582
602
|
program.name("sentinel").description("One CLI that guards code health: presets, analysis, and arch checks.").version(readOwnVersion()).configureHelp({ sortOptions: false }).showSuggestionAfterError(true).showHelpAfterError('(run "sentinel --help" for usage)').addHelpText(
|
|
583
603
|
"before",
|
|
584
604
|
[
|
|
@@ -589,7 +609,7 @@ program.name("sentinel").description("One CLI that guards code health: presets,
|
|
|
589
609
|
" --ci (affected), or all modules. --init targets one module only.",
|
|
590
610
|
""
|
|
591
611
|
].join("\n")
|
|
592
|
-
).option("--run", "execute the target tool").option("--inspect", "show the resolved configuration").option("--init", "set up a module: write its config stubs + the workspace prep it needs").option("--migrate", "planned: change an already-initialized setup (not available yet)").option("--report", "deprecated: use --run --json").option("--status", "deprecated: use --inspect").option(
|
|
612
|
+
).option("--run", "execute the target tool").option("--inspect", "show the resolved configuration").option("--init", "set up a module: write its config stubs + the workspace prep it needs").option("--migrate", "planned: change an already-initialized setup (not available yet)").option("--report", "deprecated: use --run --json").option("--status", "deprecated: use --inspect").option(
|
|
593
613
|
"--module <name>",
|
|
594
614
|
"from the workspace root: scope to one module (omit = all; inside a module dir, drop this)"
|
|
595
615
|
).option("--preset <name>", `the stack preset to apply (${PRESET_NAMES.join(", ")})`).option("--flavour <name>", "deprecated: use --preset").option("--runner <tool>", "override the default runner (e.g. eslint, biome)").option("--ci", "CI mode: from the root, only the affected modules; non-zero exit on failure").option("--fix", "auto-fix where applicable").option("--dry-run", "preview the changes without writing (--init)").option("--json", "machine-readable output, for every verb").option(
|
|
@@ -621,7 +641,10 @@ program.name("sentinel").description("One CLI that guards code health: presets,
|
|
|
621
641
|
`Available now: ${available.length ? available.map((t) => `--${t}`).join(", ") : "(none yet)"}`,
|
|
622
642
|
planned.length ? `Planned (ship in later tickets): ${planned.map((t) => `--${t}`).join(", ")}` : ""
|
|
623
643
|
].filter(Boolean).join("\n");
|
|
624
|
-
})
|
|
644
|
+
});
|
|
645
|
+
for (const target of TARGETS) program.option(`--${target}`, TARGET_HELP[target]);
|
|
646
|
+
program.option("--all", "every target");
|
|
647
|
+
program.parse(cliArgv);
|
|
625
648
|
var opts = program.opts();
|
|
626
649
|
function toCamel(flag) {
|
|
627
650
|
return flag.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -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"];
|
|
@@ -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
|
|
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
|
|
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) ??
|
|
281
|
+
value: { name: readNxProjectName(cwd) ?? basename2(cwd), private: true, ...value }
|
|
284
282
|
};
|
|
285
283
|
}
|
|
286
284
|
|
|
@@ -384,7 +382,85 @@ 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",
|
|
@@ -404,13 +480,9 @@ function buildTarget() {
|
|
|
404
480
|
}
|
|
405
481
|
};
|
|
406
482
|
}
|
|
407
|
-
var SENTINEL_OWNED_BUILD_PACKAGES =
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
"@tailwindcss/vite",
|
|
411
|
-
"vite-plugin-svgr",
|
|
412
|
-
"nitro"
|
|
413
|
-
];
|
|
483
|
+
var SENTINEL_OWNED_BUILD_PACKAGES = OWNED_PACKAGES.map(
|
|
484
|
+
(entry) => entry.package
|
|
485
|
+
);
|
|
414
486
|
|
|
415
487
|
// src/roles/build/dev-script.ts
|
|
416
488
|
var CHAIN = " -- ";
|
|
@@ -418,17 +490,17 @@ var VITE_TOKEN = /(^|\s|\/)vite(\s|$)/;
|
|
|
418
490
|
var DEV_SUBCOMMANDS = /* @__PURE__ */ new Set(["dev", "serve"]);
|
|
419
491
|
function appOptions(rest) {
|
|
420
492
|
const tokens = (rest ?? "").trim().split(/\s+/).filter(Boolean);
|
|
421
|
-
const kept = tokens.filter((token,
|
|
493
|
+
const kept = tokens.filter((token, at) => !(at === 0 && DEV_SUBCOMMANDS.has(token)));
|
|
422
494
|
return kept.join(" ");
|
|
423
495
|
}
|
|
424
496
|
function composeDevScript(existing, command) {
|
|
425
497
|
if (existing === void 0 || existing.trim() === "") return command;
|
|
426
498
|
const chunks = existing.trim().split(CHAIN);
|
|
427
|
-
const
|
|
428
|
-
if (
|
|
429
|
-
const rest = (chunks[
|
|
499
|
+
const at = chunks.map((chunk) => VITE_TOKEN.test(chunk)).lastIndexOf(true);
|
|
500
|
+
if (at === -1) return existing;
|
|
501
|
+
const rest = (chunks[at] ?? "").replace(/^.*?(^|\s|\/)vite(\s|$)/, "");
|
|
430
502
|
const options = appOptions(rest);
|
|
431
|
-
chunks[
|
|
503
|
+
chunks[at] = options === "" ? command : `${command} -- ${options}`;
|
|
432
504
|
return chunks.join(CHAIN);
|
|
433
505
|
}
|
|
434
506
|
|
|
@@ -458,6 +530,7 @@ var REACT_PLUGIN_SPECIFIERS = ["@vitejs/plugin-react", "@tanstack/react-start"];
|
|
|
458
530
|
function declaredBuildPreset(cwd) {
|
|
459
531
|
const source = readBuildConfigSource(cwd);
|
|
460
532
|
if (source === void 0) return void 0;
|
|
533
|
+
if (importsPreset(source)) return "react";
|
|
461
534
|
return REACT_PLUGIN_SPECIFIERS.some((specifier) => importsSpecifier(source, specifier)) ? "react" : void 0;
|
|
462
535
|
}
|
|
463
536
|
function readBuildConfigSource(cwd) {
|
|
@@ -502,9 +575,9 @@ function atLeast(declared, needed) {
|
|
|
502
575
|
const parse2 = (value) => value.replace(/^[\^~>=<\s]+/, "").split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
503
576
|
const left = parse2(declared);
|
|
504
577
|
const right = parse2(needed);
|
|
505
|
-
for (let
|
|
506
|
-
const a = left[
|
|
507
|
-
const b = right[
|
|
578
|
+
for (let at = 0; at < Math.max(left.length, right.length); at++) {
|
|
579
|
+
const a = left[at] ?? 0;
|
|
580
|
+
const b = right[at] ?? 0;
|
|
508
581
|
if (a !== b) return a > b;
|
|
509
582
|
}
|
|
510
583
|
return true;
|
|
@@ -525,43 +598,103 @@ function describeUnmet(preset, unmet) {
|
|
|
525
598
|
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
599
|
}
|
|
527
600
|
|
|
528
|
-
// src/roles/build/
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
601
|
+
// src/roles/build/rewrite-imports.ts
|
|
602
|
+
import { parseSync } from "oxc-parser";
|
|
603
|
+
var MOVED_SPECIFIERS = new Set(OWNED_PACKAGES.map((entry) => entry.specifier));
|
|
604
|
+
function esmBlocker(fileName, packageType) {
|
|
605
|
+
if (/\.(mts|mjs)$/.test(fileName)) return void 0;
|
|
606
|
+
if (packageType === "module") return void 0;
|
|
607
|
+
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.`;
|
|
608
|
+
}
|
|
609
|
+
var DEFAULT_EXPORT_NAMES = Object.fromEntries(
|
|
610
|
+
OWNED_PACKAGES.filter((entry) => entry.default !== void 0).map((entry) => [
|
|
611
|
+
entry.specifier,
|
|
612
|
+
entry.default
|
|
613
|
+
])
|
|
614
|
+
);
|
|
615
|
+
function renderImport(bindings) {
|
|
616
|
+
const render = (list) => list.map((b) => b.exported === b.local ? b.exported : `${b.exported} as ${b.local}`).sort((left, right) => left.localeCompare(right)).join(", ");
|
|
617
|
+
const values = bindings.filter((b) => !b.typeOnly);
|
|
618
|
+
const types = bindings.filter((b) => b.typeOnly);
|
|
619
|
+
const lines = [];
|
|
620
|
+
if (values.length > 0) lines.push(`import { ${render(values)} } from '${BUILD_PRESET_SPECIFIER}'`);
|
|
621
|
+
if (types.length > 0) {
|
|
622
|
+
lines.push(`import type { ${render(types)} } from '${BUILD_PRESET_SPECIFIER}'`);
|
|
623
|
+
}
|
|
624
|
+
return lines.join("\n");
|
|
625
|
+
}
|
|
626
|
+
function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
627
|
+
const { program, errors } = parseSync(fileName, source, { sourceType: "module" });
|
|
628
|
+
if (errors.length > 0) {
|
|
629
|
+
return {
|
|
630
|
+
kind: "blocked",
|
|
631
|
+
why: `${fileName} does not parse: ${errors[0]?.message ?? "unknown"}`
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
const imports = program.body.filter(
|
|
635
|
+
(node) => node.type === "ImportDeclaration"
|
|
636
|
+
);
|
|
637
|
+
if (imports.some((node) => node.source.value === BUILD_PRESET_SPECIFIER)) {
|
|
638
|
+
return { kind: "unchanged", why: "this config already imports its toolchain from sentinel" };
|
|
639
|
+
}
|
|
640
|
+
const moving = imports.filter((node) => MOVED_SPECIFIERS.has(node.source.value));
|
|
641
|
+
if (moving.length === 0) {
|
|
642
|
+
return {
|
|
643
|
+
kind: "unchanged",
|
|
644
|
+
why: "this config imports none of the packages sentinel owns, so there is nothing to move"
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
const bindings = [];
|
|
648
|
+
for (const node of moving) {
|
|
649
|
+
const from = node.source.value;
|
|
650
|
+
const specifiers = node.specifiers ?? [];
|
|
651
|
+
if (specifiers.length === 0) {
|
|
652
|
+
return { kind: "blocked", why: `\`import '${from}'\` has no bindings to move` };
|
|
653
|
+
}
|
|
654
|
+
for (const spec of specifiers) {
|
|
655
|
+
const typeOnly = node.importKind === "type" || spec.importKind === "type";
|
|
656
|
+
if (spec.type === "ImportDefaultSpecifier") {
|
|
657
|
+
const exported = DEFAULT_EXPORT_NAMES[from];
|
|
658
|
+
if (!exported) {
|
|
659
|
+
return {
|
|
660
|
+
kind: "blocked",
|
|
661
|
+
why: `no sentinel export for the default import of \`${from}\``
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
bindings.push({ exported, local: spec.local.name, typeOnly });
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (spec.type === "ImportSpecifier") {
|
|
668
|
+
bindings.push({
|
|
669
|
+
exported: spec.imported?.name ?? spec.imported?.value ?? spec.local.name,
|
|
670
|
+
local: spec.local.name,
|
|
671
|
+
typeOnly
|
|
672
|
+
});
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
return {
|
|
676
|
+
kind: "blocked",
|
|
677
|
+
why: `\`import * as ${spec.local.name} from '${from}'\` cannot be mapped onto sentinel's named exports`
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const spans = moving.map((node) => ({ start: node.start, end: node.end })).sort((left, right) => left.start - right.start);
|
|
682
|
+
const first = spans[0];
|
|
683
|
+
const rest = spans.slice(1);
|
|
684
|
+
let out = source;
|
|
685
|
+
for (const span of rest.slice().reverse()) {
|
|
686
|
+
const after = out.slice(span.end).match(/^\r?\n/)?.[0].length ?? 0;
|
|
687
|
+
out = out.slice(0, span.start) + out.slice(span.end + after);
|
|
688
|
+
}
|
|
689
|
+
out = out.slice(0, first.start) + renderImport(bindings) + out.slice(first.end);
|
|
690
|
+
return {
|
|
691
|
+
kind: "rewritten",
|
|
692
|
+
source: out,
|
|
693
|
+
moved: [...new Set(moving.map((node) => node.source.value))].sort()
|
|
694
|
+
};
|
|
564
695
|
}
|
|
696
|
+
|
|
697
|
+
// src/roles/build/plan.ts
|
|
565
698
|
function ownedBuildDependencies(cwd) {
|
|
566
699
|
const manifest = readProjectPackageJson(cwd);
|
|
567
700
|
const keys = [];
|
|
@@ -590,9 +723,10 @@ function buildScripts(cwd) {
|
|
|
590
723
|
};
|
|
591
724
|
const existingDev = existingCommand(cwd, DEV_SCRIPT_NAME);
|
|
592
725
|
if (existingDev !== void 0) {
|
|
726
|
+
const own2 = moduleName(cwd);
|
|
593
727
|
scripts[DEV_SCRIPT_NAME] = composeDevScript(
|
|
594
728
|
existingDev,
|
|
595
|
-
selfCommand(cwd, "--run --dev") ?? SENTINEL_DEV_COMMAND
|
|
729
|
+
`${selfCommand(cwd, "--run --dev") ?? SENTINEL_DEV_COMMAND} --module ${own2}`
|
|
596
730
|
);
|
|
597
731
|
}
|
|
598
732
|
return scripts;
|
|
@@ -616,14 +750,41 @@ function plan(context) {
|
|
|
616
750
|
const operations = [];
|
|
617
751
|
const configFile = buildConfigFile(context.cwd);
|
|
618
752
|
if (configFile === void 0) {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
`
|
|
622
|
-
|
|
623
|
-
} else
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
);
|
|
753
|
+
return {
|
|
754
|
+
operations: [],
|
|
755
|
+
skipped: `no Vite config in this module, so there is nothing to point at sentinel. This role moves an existing config's imports; it does not introduce a bundler. If this module builds another way (38 projects here build through \`@nx/webpack\`), the build role does not apply to it.`
|
|
756
|
+
};
|
|
757
|
+
} else {
|
|
758
|
+
const notEsm = esmBlocker(configFile, readProjectPackageJson(context.cwd).type);
|
|
759
|
+
if (notEsm !== void 0) return { operations: [], skipped: notEsm };
|
|
760
|
+
const rewrite = rewriteBuildImports(readConfigSource(context.cwd, configFile), configFile);
|
|
761
|
+
if (rewrite.kind === "blocked") {
|
|
762
|
+
return {
|
|
763
|
+
operations: [],
|
|
764
|
+
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.`
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
if (rewrite.kind === "rewritten") {
|
|
768
|
+
operations.push({ kind: "write", path: configFile, contents: rewrite.source });
|
|
769
|
+
}
|
|
770
|
+
for (const name of COMPANION_CONFIG_FILES) {
|
|
771
|
+
const source = readConfigSource(context.cwd, name);
|
|
772
|
+
if (source === "") continue;
|
|
773
|
+
const companion = rewriteBuildImports(source, name);
|
|
774
|
+
if (companion.kind === "rewritten") {
|
|
775
|
+
operations.push({ kind: "write", path: name, contents: companion.source });
|
|
776
|
+
notes.push(`${name} now imports ${companion.moved.join(", ")} from sentinel too.`);
|
|
777
|
+
} else if (companion.kind === "blocked") {
|
|
778
|
+
notes.push(
|
|
779
|
+
`${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.`
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (rewrite.kind === "rewritten") {
|
|
784
|
+
notes.push(
|
|
785
|
+
`${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.`
|
|
786
|
+
);
|
|
787
|
+
}
|
|
627
788
|
}
|
|
628
789
|
operations.push(manifestOperation(context.cwd, buildScripts(context.cwd)));
|
|
629
790
|
operations.push(...nxTargetOperations({ cwd: context.cwd, targets: buildTarget() }));
|
|
@@ -636,12 +797,12 @@ function plan(context) {
|
|
|
636
797
|
}
|
|
637
798
|
return { operations, notes };
|
|
638
799
|
}
|
|
639
|
-
function
|
|
640
|
-
if (!existsSync7(join7(cwd, configFile))) return
|
|
800
|
+
function readConfigSource(cwd, configFile) {
|
|
801
|
+
if (!existsSync7(join7(cwd, configFile))) return "";
|
|
641
802
|
try {
|
|
642
|
-
return readFileSync5(join7(cwd, configFile), "utf8")
|
|
803
|
+
return readFileSync5(join7(cwd, configFile), "utf8");
|
|
643
804
|
} catch {
|
|
644
|
-
return
|
|
805
|
+
return "";
|
|
645
806
|
}
|
|
646
807
|
}
|
|
647
808
|
|
|
@@ -773,6 +934,24 @@ var ViteDevAdapter = class extends ViteRoleAdapter {
|
|
|
773
934
|
get label() {
|
|
774
935
|
return "dev";
|
|
775
936
|
}
|
|
937
|
+
/**
|
|
938
|
+
* There is no such thing as adopting the dev server on its own.
|
|
939
|
+
*
|
|
940
|
+
* `ViteRoleAdapter.plan()` is shared with `--build` because everything else about the two
|
|
941
|
+
* targets is shared, and inheriting it here made `--init --dev` perform the FULL build
|
|
942
|
+
* adoption, silently, under a message that only ever mentioned the build. Exiting 0 on that
|
|
943
|
+
* is the worst of the options: it teaches that dev can be adopted by itself, and that belief
|
|
944
|
+
* is what produces the half-migrated module this role exists to prevent (see the class
|
|
945
|
+
* comment above, and `registerBuild`).
|
|
946
|
+
*
|
|
947
|
+
* A single plan writes both scripts, so the honest answer is to name the command that does
|
|
948
|
+
* it rather than to do it under a different name.
|
|
949
|
+
*/
|
|
950
|
+
plan() {
|
|
951
|
+
throw new Error(
|
|
952
|
+
"the dev server is adopted together with the build, never on its own. Run --init --build: one plan writes both the `build` and the `serve` scripts."
|
|
953
|
+
);
|
|
954
|
+
}
|
|
776
955
|
/**
|
|
777
956
|
* Start the dev server. It does not return until stopped, so there is no verdict to report
|
|
778
957
|
* beyond the exit code the developer's own Ctrl-C produces.
|
|
@@ -791,48 +970,6 @@ var ViteDevAdapter = class extends ViteRoleAdapter {
|
|
|
791
970
|
}
|
|
792
971
|
};
|
|
793
972
|
|
|
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
973
|
// src/roles/build/prerequisite.ts
|
|
837
974
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
838
975
|
var PREBUILD_SCRIPT_NAME = `pre${BUILD_SCRIPT_NAME}`;
|
|
@@ -859,23 +996,6 @@ function runPrerequisite(cwd, env = process.env) {
|
|
|
859
996
|
return { kind: "ran" };
|
|
860
997
|
}
|
|
861
998
|
|
|
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
999
|
// src/roles/build/adapters/vite/vite.adapter.ts
|
|
880
1000
|
var ViteAdapter = class extends ViteRoleAdapter {
|
|
881
1001
|
target = "build";
|
|
@@ -901,25 +1021,30 @@ var ViteAdapter = class extends ViteRoleAdapter {
|
|
|
901
1021
|
return this.spawnVite(ctx, ready.vite, ["build"], 1);
|
|
902
1022
|
}
|
|
903
1023
|
/**
|
|
904
|
-
* What this module builds with,
|
|
1024
|
+
* What this module builds with, WITHOUT loading its config.
|
|
905
1025
|
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
1026
|
+
* Everything reported here is read from the manifest and from the config's text: whether the
|
|
1027
|
+
* module imports its toolchain from sentinel, which Vite install would answer, what it still
|
|
1028
|
+
* declares of its own, and whether it has a `prebuild` step.
|
|
909
1029
|
*
|
|
910
|
-
*
|
|
911
|
-
*
|
|
912
|
-
*
|
|
1030
|
+
* It used to also load the config through Vite and report where it departed from a preset
|
|
1031
|
+
* and what it added on top. Both were dropped with the preset itself. Adoption changes a
|
|
1032
|
+
* config's IMPORTS and nothing else, so there is no preset the app signed up to and no
|
|
1033
|
+
* distance from it to measure; and computing it meant EXECUTING an app's config, which is
|
|
1034
|
+
* expensive and can fail on its own terms (career throws without `VITE_BRAND_ID`). Reading
|
|
1035
|
+
* a file to report on it must not be able to break.
|
|
913
1036
|
*
|
|
914
|
-
*
|
|
915
|
-
*
|
|
1037
|
+
* They come back with the shared base, when there is again something an app has chosen to
|
|
1038
|
+
* adopt and can therefore depart from.
|
|
916
1039
|
*/
|
|
917
1040
|
async inspect(ctx) {
|
|
918
1041
|
const adoption = readBuildAdoption(ctx.cwd);
|
|
919
|
-
|
|
1042
|
+
return {
|
|
920
1043
|
runner: "vite",
|
|
921
1044
|
configFile: adoption.configFile,
|
|
922
1045
|
...adoption.unreadable ? { unreadable: adoption.unreadable } : {},
|
|
1046
|
+
// The field nothing else reports: an adopted config takes its plugins from sentinel, so
|
|
1047
|
+
// a module answering `module` here is one build away from the two-copies failure.
|
|
923
1048
|
vite: viteOrigin(ctx.cwd),
|
|
924
1049
|
// Rendered as a labelled block by the shared renderer, the same shape the lint role's
|
|
925
1050
|
// parked rules and the format role's overrides already use.
|
|
@@ -929,36 +1054,6 @@ var ViteAdapter = class extends ViteRoleAdapter {
|
|
|
929
1054
|
})),
|
|
930
1055
|
prerequisite: moduleScripts(ctx.cwd)[PREBUILD_SCRIPT_NAME] ?? null
|
|
931
1056
|
};
|
|
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
1057
|
}
|
|
963
1058
|
/**
|
|
964
1059
|
* Adoption as data, WITHOUT building.
|
|
@@ -984,17 +1079,6 @@ var ViteAdapter = class extends ViteRoleAdapter {
|
|
|
984
1079
|
};
|
|
985
1080
|
}
|
|
986
1081
|
};
|
|
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
1082
|
|
|
999
1083
|
// src/roles/build/register.ts
|
|
1000
1084
|
function registerBuild() {
|
|
@@ -4180,8 +4264,7 @@ import { join as join23 } from "path";
|
|
|
4180
4264
|
// src/roles/lint/eslint-ignores.ts
|
|
4181
4265
|
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
4182
4266
|
import { join as join21 } from "path";
|
|
4183
|
-
|
|
4184
|
-
var STRING_LITERAL = /['"`]([^'"`]+)['"`]/g;
|
|
4267
|
+
import { parseSync as parseSync2 } from "oxc-parser";
|
|
4185
4268
|
var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
|
|
4186
4269
|
function toOxlintPattern(pattern) {
|
|
4187
4270
|
return pattern.replace(/^\.\//, "");
|
|
@@ -4201,15 +4284,62 @@ function readEslintIgnores(cwd) {
|
|
|
4201
4284
|
return { patterns: [], unresolved: [] };
|
|
4202
4285
|
}
|
|
4203
4286
|
const patterns = /* @__PURE__ */ new Set();
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4287
|
+
const unresolved = new Set([...source.matchAll(OPAQUE_SOURCE)].map((match) => match[0]));
|
|
4288
|
+
const { program, errors } = parseSync2(config, source, { sourceType: "module" });
|
|
4289
|
+
if (errors.length > 0) {
|
|
4290
|
+
return { patterns: [], unresolved: [`${config} could not be parsed`] };
|
|
4291
|
+
}
|
|
4292
|
+
walk(program, (node) => {
|
|
4293
|
+
if (node.type === "CallExpression" && calleeName(node) === "globalIgnores") {
|
|
4294
|
+
for (const literal of stringLiterals(firstArrayArgument(node))) {
|
|
4295
|
+
patterns.add(toOxlintPattern(literal));
|
|
4208
4296
|
}
|
|
4297
|
+
return;
|
|
4298
|
+
}
|
|
4299
|
+
if (node.type !== "ObjectExpression") return;
|
|
4300
|
+
const keys = objectKeys(node);
|
|
4301
|
+
if (!keys.includes("ignores")) return;
|
|
4302
|
+
const values = arrayValues(node, "ignores");
|
|
4303
|
+
if (keys.length > 1) {
|
|
4304
|
+
unresolved.add(
|
|
4305
|
+
`ignores scoped to one config block (with ${keys.filter((k) => k !== "ignores").join(", ")}): ${values.join(", ")} \u2014 exempt from those RULES, not excluded from linting`
|
|
4306
|
+
);
|
|
4307
|
+
return;
|
|
4209
4308
|
}
|
|
4309
|
+
for (const literal of values) patterns.add(toOxlintPattern(literal));
|
|
4310
|
+
});
|
|
4311
|
+
return { patterns: [...patterns], unresolved: [...unresolved] };
|
|
4312
|
+
}
|
|
4313
|
+
function walk(node, visit) {
|
|
4314
|
+
if (!node || typeof node !== "object") return;
|
|
4315
|
+
const current = node;
|
|
4316
|
+
if (typeof current.type === "string") visit(current);
|
|
4317
|
+
for (const key of Object.keys(current)) {
|
|
4318
|
+
const value = current[key];
|
|
4319
|
+
if (Array.isArray(value)) for (const entry of value) walk(entry, visit);
|
|
4320
|
+
else if (value && typeof value === "object") walk(value, visit);
|
|
4210
4321
|
}
|
|
4211
|
-
|
|
4212
|
-
|
|
4322
|
+
}
|
|
4323
|
+
function calleeName(node) {
|
|
4324
|
+
const callee = node.callee;
|
|
4325
|
+
return callee?.type === "Identifier" ? callee.name : void 0;
|
|
4326
|
+
}
|
|
4327
|
+
function firstArrayArgument(node) {
|
|
4328
|
+
const args = node.arguments ?? [];
|
|
4329
|
+
return args.find((argument) => argument?.type === "ArrayExpression");
|
|
4330
|
+
}
|
|
4331
|
+
function stringLiterals(array) {
|
|
4332
|
+
const elements = array?.elements ?? [];
|
|
4333
|
+
return elements.filter((element) => element?.type === "Literal" && typeof element.value === "string").map((element) => element.value);
|
|
4334
|
+
}
|
|
4335
|
+
function objectKeys(node) {
|
|
4336
|
+
const properties = node.properties ?? [];
|
|
4337
|
+
return properties.filter((property) => property?.type === "Property" && !property.computed).map((property) => property.key?.name ?? String(property.key?.value ?? "")).filter(Boolean);
|
|
4338
|
+
}
|
|
4339
|
+
function arrayValues(node, name) {
|
|
4340
|
+
const properties = node.properties ?? [];
|
|
4341
|
+
const found = properties.find((property) => property.key?.name === name);
|
|
4342
|
+
return stringLiterals(found?.value);
|
|
4213
4343
|
}
|
|
4214
4344
|
|
|
4215
4345
|
// src/roles/lint/lint-script.ts
|
|
@@ -4755,7 +4885,7 @@ ${result.stderr ?? ""}`);
|
|
|
4755
4885
|
} else {
|
|
4756
4886
|
rmSync(baselinePath, { force: true });
|
|
4757
4887
|
}
|
|
4758
|
-
if (next.length !== current.length || next.some((entry,
|
|
4888
|
+
if (next.length !== current.length || next.some((entry, at) => entry !== current[at])) {
|
|
4759
4889
|
config.extends = next;
|
|
4760
4890
|
writeFileSync2(configPath, `${JSON.stringify(config, null, 2)}
|
|
4761
4891
|
`);
|
|
@@ -5717,7 +5847,7 @@ function detectFramework(packageJson) {
|
|
|
5717
5847
|
}
|
|
5718
5848
|
|
|
5719
5849
|
// src/core/dispatch.ts
|
|
5720
|
-
import { basename as
|
|
5850
|
+
import { basename as basename3 } from "path";
|
|
5721
5851
|
|
|
5722
5852
|
// src/shared/text.ts
|
|
5723
5853
|
function ensureLines(current, lines) {
|
|
@@ -5776,6 +5906,13 @@ function replaceLines(current, replacements) {
|
|
|
5776
5906
|
import { existsSync as existsSync22, readFileSync as readFileSync22, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
5777
5907
|
import { resolve as resolve4, sep } from "path";
|
|
5778
5908
|
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
5909
|
+
|
|
5910
|
+
// src/shared/deep-merge.ts
|
|
5911
|
+
function isPlainObject(value) {
|
|
5912
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5913
|
+
}
|
|
5914
|
+
|
|
5915
|
+
// src/core/apply-plan.ts
|
|
5779
5916
|
function resolveWithinRoot(cwd, relativePath) {
|
|
5780
5917
|
const root = resolve4(cwd);
|
|
5781
5918
|
const absolutePath = resolve4(root, relativePath);
|
|
@@ -6086,7 +6223,7 @@ async function dispatch(opts) {
|
|
|
6086
6223
|
process.stderr.write(` fixing what ${opts.target} can fix automatically...
|
|
6087
6224
|
`);
|
|
6088
6225
|
await adapter.afterInit({
|
|
6089
|
-
module:
|
|
6226
|
+
module: basename3(opts.cwd),
|
|
6090
6227
|
cwd: opts.cwd,
|
|
6091
6228
|
preset,
|
|
6092
6229
|
ci: false,
|
|
@@ -6108,9 +6245,10 @@ export {
|
|
|
6108
6245
|
resolveBin,
|
|
6109
6246
|
readOwnVersion,
|
|
6110
6247
|
readProjectPackageJson,
|
|
6111
|
-
|
|
6248
|
+
moduleName,
|
|
6112
6249
|
VERBS,
|
|
6113
6250
|
TARGETS,
|
|
6251
|
+
LONG_RUNNING_TARGETS,
|
|
6114
6252
|
SWEEPABLE_TARGETS,
|
|
6115
6253
|
PRESET_NAMES,
|
|
6116
6254
|
WORKSPACE_ROOT_MARKER,
|
|
@@ -6123,4 +6261,4 @@ export {
|
|
|
6123
6261
|
detectFramework,
|
|
6124
6262
|
dispatch
|
|
6125
6263
|
};
|
|
6126
|
-
//# sourceMappingURL=chunk-
|
|
6264
|
+
//# sourceMappingURL=chunk-YZ4EKBK7.js.map
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as tailwindcss } from '@tailwindcss/vite';
|
|
2
|
+
export { default as react } from '@vitejs/plugin-react';
|
|
3
|
+
export { nitro } from 'nitro/vite';
|
|
4
|
+
export { default as svgr } from 'vite-plugin-svgr';
|
|
5
|
+
export { Plugin, PluginOption, SassPreprocessorOptions, UserConfig, UserConfigExport, defineConfig, loadEnv, mergeConfig } from 'vite';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/roles/build/toolchain.ts
|
|
2
|
+
import { default as default2 } from "@tailwindcss/vite";
|
|
3
|
+
import { default as default3 } from "@vitejs/plugin-react";
|
|
4
|
+
import { nitro } from "nitro/vite";
|
|
5
|
+
import { default as default4 } from "vite-plugin-svgr";
|
|
6
|
+
import { defineConfig, loadEnv, mergeConfig } from "vite";
|
|
7
|
+
export {
|
|
8
|
+
defineConfig,
|
|
9
|
+
loadEnv,
|
|
10
|
+
mergeConfig,
|
|
11
|
+
nitro,
|
|
12
|
+
default3 as react,
|
|
13
|
+
default4 as svgr,
|
|
14
|
+
default2 as tailwindcss
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=toolchain.js.map
|
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.3",
|
|
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",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"default": "./dist/tsconfig/node.json"
|
|
40
40
|
},
|
|
41
41
|
"./build/react": {
|
|
42
|
-
"types": "./dist/roles/build/
|
|
43
|
-
"import": "./dist/roles/build/
|
|
42
|
+
"types": "./dist/roles/build/toolchain.d.ts",
|
|
43
|
+
"import": "./dist/roles/build/toolchain.js"
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"files": [
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"eslint-plugin-styled-components-a11y": "2.2.0",
|
|
63
63
|
"jsonc-parser": "^3.3.1",
|
|
64
64
|
"nitro": "3.0.260415-beta",
|
|
65
|
+
"oxc-parser": "0.147.0",
|
|
65
66
|
"oxfmt": "0.63.0",
|
|
66
67
|
"oxlint": "1.77.0",
|
|
67
68
|
"oxlint-tsgolint": "7.0.2001",
|
package/dist/chunk-XVDOQ3G3.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// src/roles/build/presets/requirements.json
|
|
2
|
-
var requirements_default = {
|
|
3
|
-
svelte: {
|
|
4
|
-
why: "sentinel ships Vite 8, and @sveltejs/vite-plugin-svelte caps at Vite 6 until its major 7",
|
|
5
|
-
requires: {
|
|
6
|
-
"@sveltejs/vite-plugin-svelte": "7.0.0",
|
|
7
|
-
svelte: "5.46.4"
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
// src/roles/build/presets/shared.json
|
|
13
|
-
var shared_default = {
|
|
14
|
-
target: "chrome89",
|
|
15
|
-
allowedHosts: [".hubpreprod.com", ".playground-hublo.com"],
|
|
16
|
-
publicAssets: [
|
|
17
|
-
{
|
|
18
|
-
dir: "public/branding",
|
|
19
|
-
baseURL: "/branding",
|
|
20
|
-
maxAge: 31536e3
|
|
21
|
-
}
|
|
22
|
-
]
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// src/roles/build/preset-data.ts
|
|
26
|
-
var REACT_APP_DEFAULTS = shared_default;
|
|
27
|
-
var REQUIREMENTS = requirements_default;
|
|
28
|
-
|
|
29
|
-
// src/shared/deep-merge.ts
|
|
30
|
-
function isPlainObject(value) {
|
|
31
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export {
|
|
35
|
-
REACT_APP_DEFAULTS,
|
|
36
|
-
REQUIREMENTS,
|
|
37
|
-
isPlainObject
|
|
38
|
-
};
|
|
39
|
-
//# sourceMappingURL=chunk-XVDOQ3G3.js.map
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { PluginOption, UserConfig } from 'vite';
|
|
2
|
-
export { defineConfig, loadEnv } from 'vite';
|
|
3
|
-
|
|
4
|
-
/** The convention values, in one place so `--inspect` can report what an app departs from. */
|
|
5
|
-
declare const REACT_APP_DEFAULTS: {
|
|
6
|
-
target: string;
|
|
7
|
-
allowedHosts: string[];
|
|
8
|
-
publicAssets: {
|
|
9
|
-
dir: string;
|
|
10
|
-
baseURL: string;
|
|
11
|
-
maxAge: number;
|
|
12
|
-
}[];
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
/** Where the router's routes live and where it writes its generated tree. */
|
|
16
|
-
interface RouterPaths {
|
|
17
|
-
routesDirectory: string;
|
|
18
|
-
generatedRouteTree: string;
|
|
19
|
-
/** Extra lines appended to the generated tree. Two of the three apps use it. */
|
|
20
|
-
routeTreeFileFooter?: string[];
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* The TanStack factories, supplied by the app.
|
|
24
|
-
*
|
|
25
|
-
* Typed loosely on purpose: sentinel decides WHEN to call them and with what, and has no
|
|
26
|
-
* business asserting their return shape. Narrowing this would only couple sentinel to a
|
|
27
|
-
* version of a package it does not own.
|
|
28
|
-
*/
|
|
29
|
-
interface TanstackFactories {
|
|
30
|
-
start: (options: {
|
|
31
|
-
router: RouterPaths;
|
|
32
|
-
}) => PluginOption[];
|
|
33
|
-
router: (options: RouterPaths & {
|
|
34
|
-
target: string;
|
|
35
|
-
autoCodeSplitting: boolean;
|
|
36
|
-
}) => PluginOption;
|
|
37
|
-
}
|
|
38
|
-
/** Everything an app declares. */
|
|
39
|
-
interface ReactAppOptions {
|
|
40
|
-
/** The app's own directory. Always `__dirname`. */
|
|
41
|
-
root: string;
|
|
42
|
-
/** Vite's `mode`, from the config callback. */
|
|
43
|
-
mode: string;
|
|
44
|
-
/** Served under, e.g. `/console/`. */
|
|
45
|
-
base: string;
|
|
46
|
-
/** Dev server port. */
|
|
47
|
-
port: number;
|
|
48
|
-
/** HMR port. Explicit, NOT `port + 1`: career goes DOWN to 9998 where the others go up. */
|
|
49
|
-
hmrPort: number;
|
|
50
|
-
router: RouterPaths;
|
|
51
|
-
tanstack: TanstackFactories;
|
|
52
|
-
/**
|
|
53
|
-
* Sourcemaps: a boolean, however the app decided it.
|
|
54
|
-
*
|
|
55
|
-
* The three apps disagree on how — two use a mode allowlist, one reads an env var. That is
|
|
56
|
-
* an inconsistency in the repo rather than a requirement, and encoding both would make
|
|
57
|
-
* sentinel the owner of a decision it should not hold.
|
|
58
|
-
*/
|
|
59
|
-
sourcemap?: boolean;
|
|
60
|
-
/** Passed through verbatim, never read. See the note above. */
|
|
61
|
-
alias?: unknown[];
|
|
62
|
-
/** The app's own plugins (branding, babel). Appended AFTER the base set. */
|
|
63
|
-
plugins?: PluginOption[];
|
|
64
|
-
/** Conventions the app may override. */
|
|
65
|
-
target?: string;
|
|
66
|
-
allowedHosts?: string[];
|
|
67
|
-
/**
|
|
68
|
-
* Anything else this app needs, merged over the preset LAST.
|
|
69
|
-
*
|
|
70
|
-
* The preset is a starting point, not a cage. Two things an app must be able to do and
|
|
71
|
-
* could not before: override a NESTED attribute without losing its siblings, and add an
|
|
72
|
-
* attribute the preset knows nothing about.
|
|
73
|
-
*
|
|
74
|
-
* Objects merge, everything else replaces — see `merge-config` for why arrays replace
|
|
75
|
-
* rather than concatenate. Whatever lands here is reported by `--inspect --build`, so an
|
|
76
|
-
* override is a visible, countable decision instead of something found by reading a config.
|
|
77
|
-
*
|
|
78
|
-
* Real cases today: console needs `ssr`, `optimizeDeps` and `preview`; career needs
|
|
79
|
-
* `css.preprocessorOptions` for Sass; all three need their own `nitro.publicAssets`.
|
|
80
|
-
*/
|
|
81
|
-
overrides?: Record<string, unknown>;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* The environment, loaded the way all three apps already load it.
|
|
86
|
-
*
|
|
87
|
-
* `production` is normalised to `prd` because that is this repository's deployment tag: it is
|
|
88
|
-
* the `.env.prd` the build reads and the SSM path segment the values come from. Not a
|
|
89
|
-
* preference, a fact about the repo — which is exactly why it belongs in the preset rather
|
|
90
|
-
* than being retyped in every config.
|
|
91
|
-
*/
|
|
92
|
-
declare function appEnv(root: string, mode: string): Record<string, string>;
|
|
93
|
-
/** True when this run is a test rather than a real build. */
|
|
94
|
-
declare function isTestMode(mode: string, env?: NodeJS.ProcessEnv): boolean;
|
|
95
|
-
/**
|
|
96
|
-
* The plugin names in the order they run, which is what `--inspect` reports.
|
|
97
|
-
*
|
|
98
|
-
* `extras` is how many plugins the app appends of its own; they are named positionally
|
|
99
|
-
* because sentinel has no way to name a function someone else passed in.
|
|
100
|
-
*/
|
|
101
|
-
declare function pluginPlan(isTest: boolean, extras?: number): string[];
|
|
102
|
-
/**
|
|
103
|
-
* The Vite config for a React app, minus the plugins.
|
|
104
|
-
*
|
|
105
|
-
* Pure and separately testable: everything here is data in, data out, so the composition
|
|
106
|
-
* rules can be asserted without a bundler anywhere near the test.
|
|
107
|
-
*/
|
|
108
|
-
declare function reactAppConfig(options: ReactAppOptions): Omit<UserConfig, 'plugins'>;
|
|
109
|
-
/** The config an app spreads into `defineConfig`. */
|
|
110
|
-
declare function reactApp(options: ReactAppOptions): UserConfig;
|
|
111
|
-
|
|
112
|
-
export { REACT_APP_DEFAULTS, type ReactAppOptions, type RouterPaths, type TanstackFactories, appEnv, isTestMode, pluginPlan, reactApp, reactAppConfig };
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
REACT_APP_DEFAULTS,
|
|
3
|
-
isPlainObject
|
|
4
|
-
} from "../../chunk-XVDOQ3G3.js";
|
|
5
|
-
|
|
6
|
-
// src/roles/build/react-app.ts
|
|
7
|
-
import tailwindcss from "@tailwindcss/vite";
|
|
8
|
-
import react from "@vitejs/plugin-react";
|
|
9
|
-
import { nitro } from "nitro/vite";
|
|
10
|
-
import { loadEnv } from "vite";
|
|
11
|
-
import svgr from "vite-plugin-svgr";
|
|
12
|
-
|
|
13
|
-
// src/roles/build/merge-config.ts
|
|
14
|
-
function mergeConfig(base, overrides) {
|
|
15
|
-
if (!overrides) return base;
|
|
16
|
-
const out = { ...base };
|
|
17
|
-
for (const [key, value] of Object.entries(overrides)) {
|
|
18
|
-
if (value === void 0) {
|
|
19
|
-
delete out[key];
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
const current = out[key];
|
|
23
|
-
out[key] = isPlainObject(current) && isPlainObject(value) ? mergeConfig(current, value) : value;
|
|
24
|
-
}
|
|
25
|
-
return out;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// src/roles/build/react-app.ts
|
|
29
|
-
import { defineConfig, loadEnv as loadEnv2 } from "vite";
|
|
30
|
-
function appEnv(root, mode) {
|
|
31
|
-
return loadEnv(mode === "production" ? "prd" : mode, root, "");
|
|
32
|
-
}
|
|
33
|
-
function isTestMode(mode, env = process.env) {
|
|
34
|
-
return mode === "test" || env.VITEST === "true";
|
|
35
|
-
}
|
|
36
|
-
function pluginEntries(options, isTest) {
|
|
37
|
-
const { tanstack, router } = options;
|
|
38
|
-
return [
|
|
39
|
-
{ name: "tailwindcss", build: () => tailwindcss() },
|
|
40
|
-
...isTest ? [
|
|
41
|
-
{
|
|
42
|
-
name: "tanstackRouter",
|
|
43
|
-
build: () => tanstack.router({ ...router, target: "react", autoCodeSplitting: true })
|
|
44
|
-
}
|
|
45
|
-
] : [
|
|
46
|
-
{ name: "tanstackStart", build: () => tanstack.start({ router }) },
|
|
47
|
-
{ name: "nitro", build: () => nitro() }
|
|
48
|
-
],
|
|
49
|
-
{ name: "react", build: () => react() },
|
|
50
|
-
{ name: "svgr", build: () => svgr() }
|
|
51
|
-
];
|
|
52
|
-
}
|
|
53
|
-
function pluginPlan(isTest, extras = 0) {
|
|
54
|
-
const entries = pluginEntries(
|
|
55
|
-
{ tanstack: { start: () => [], router: () => null }, router: STUB_ROUTER },
|
|
56
|
-
isTest
|
|
57
|
-
);
|
|
58
|
-
return [
|
|
59
|
-
...entries.map((entry) => entry.name),
|
|
60
|
-
...Array.from({ length: extras }, (_, at) => `app:${at}`)
|
|
61
|
-
];
|
|
62
|
-
}
|
|
63
|
-
var STUB_ROUTER = { routesDirectory: "", generatedRouteTree: "" };
|
|
64
|
-
function basePlugins(options, isTest) {
|
|
65
|
-
return [
|
|
66
|
-
...pluginEntries(options, isTest).flatMap((entry) => entry.build()),
|
|
67
|
-
...options.plugins ?? []
|
|
68
|
-
];
|
|
69
|
-
}
|
|
70
|
-
function reactAppConfig(options) {
|
|
71
|
-
const preset = {
|
|
72
|
-
base: options.base,
|
|
73
|
-
root: options.root,
|
|
74
|
-
define: {
|
|
75
|
-
// Development only. In a build this would leak a browser global into SSR output.
|
|
76
|
-
...options.mode === "development" ? { global: "window" } : {}
|
|
77
|
-
},
|
|
78
|
-
server: {
|
|
79
|
-
port: options.port,
|
|
80
|
-
allowedHosts: options.allowedHosts ?? [...REACT_APP_DEFAULTS.allowedHosts],
|
|
81
|
-
hmr: { host: "localhost", protocol: "ws", port: options.hmrPort }
|
|
82
|
-
},
|
|
83
|
-
build: {
|
|
84
|
-
target: options.target ?? REACT_APP_DEFAULTS.target,
|
|
85
|
-
sourcemap: options.sourcemap ?? false
|
|
86
|
-
},
|
|
87
|
-
/*
|
|
88
|
-
* Nitro's two settings are sentinel's, and both were measured across the three apps rather
|
|
89
|
-
* than assumed.
|
|
90
|
-
*
|
|
91
|
-
* `baseURL` is always the app's `base`, without exception: `/console/`, `/` and `/admin/`.
|
|
92
|
-
* It is derived, so an app cannot get them out of step.
|
|
93
|
-
*
|
|
94
|
-
* `publicAssets` is byte-identical in all three — the branding directory, its URL and a
|
|
95
|
-
* one-year max-age. That is a convention about where branding lives, not data about an
|
|
96
|
-
* app, and it was sitting in three `overrides` blocks saying the same thing.
|
|
97
|
-
*
|
|
98
|
-
* An app that needs MORE from nitro overrides the key: career adds `externals` to keep the
|
|
99
|
-
* SSR sanitisation chain out of the server bundle. `mergeConfig` merges objects, so adding
|
|
100
|
-
* one key does not take these two away.
|
|
101
|
-
*/
|
|
102
|
-
nitro: {
|
|
103
|
-
baseURL: options.base,
|
|
104
|
-
publicAssets: [...REACT_APP_DEFAULTS.publicAssets]
|
|
105
|
-
},
|
|
106
|
-
...options.alias ? { resolve: { alias: options.alias } } : {}
|
|
107
|
-
};
|
|
108
|
-
return mergeConfig(preset, options.overrides);
|
|
109
|
-
}
|
|
110
|
-
function reactApp(options) {
|
|
111
|
-
return {
|
|
112
|
-
...reactAppConfig(options),
|
|
113
|
-
plugins: basePlugins(options, isTestMode(options.mode))
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
export {
|
|
117
|
-
REACT_APP_DEFAULTS,
|
|
118
|
-
appEnv,
|
|
119
|
-
defineConfig,
|
|
120
|
-
isTestMode,
|
|
121
|
-
loadEnv2 as loadEnv,
|
|
122
|
-
pluginPlan,
|
|
123
|
-
reactApp,
|
|
124
|
-
reactAppConfig
|
|
125
|
-
};
|
|
126
|
-
//# sourceMappingURL=react-app.js.map
|