@hublo/sentinel 1.1.5 → 1.2.0-alpha.0
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 +5 -4
- package/dist/bin/sentinel.js +47 -5
- package/dist/{chunk-RLVLDC5H.js → chunk-TWL6T237.js} +1033 -330
- package/dist/chunk-XSCDJZDY.js +26 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/roles/build/react-app.d.ts +116 -0
- package/dist/roles/build/react-app.js +111 -0
- package/lint/nest.json +358 -0
- package/lint/node.json +52 -0
- package/lint/react-lib.json +376 -0
- package/lint/react.json +474 -0
- package/lint/svelte.json +209 -0
- package/lint/tools.json +197 -0
- package/oxlint/nest.json +2 -355
- package/oxlint/node.json +3 -50
- package/oxlint/react-lib.json +2 -373
- package/oxlint/react.json +2 -471
- package/oxlint/svelte.json +3 -207
- package/oxlint/tools.json +3 -195
- package/package.json +21 -2
|
@@ -1,5 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
REACT_APP_DEFAULTS,
|
|
3
|
+
isPlainObject
|
|
4
|
+
} from "./chunk-XSCDJZDY.js";
|
|
5
|
+
|
|
1
6
|
// src/core/registry.ts
|
|
2
7
|
var adapters = [];
|
|
8
|
+
var PresetUnsupportedError = class extends Error {
|
|
9
|
+
constructor(target, preset) {
|
|
10
|
+
super(`No adapter for target "${target}" handles preset "${preset}".`);
|
|
11
|
+
this.target = target;
|
|
12
|
+
this.preset = preset;
|
|
13
|
+
this.name = "PresetUnsupportedError";
|
|
14
|
+
}
|
|
15
|
+
target;
|
|
16
|
+
preset;
|
|
17
|
+
};
|
|
3
18
|
var defaultRunner = {
|
|
4
19
|
// Filled in by tool branches, e.g. lint: 'eslint', typescript: 'tsc'.
|
|
5
20
|
};
|
|
@@ -12,6 +27,14 @@ function setDefaultRunner(target, runner) {
|
|
|
12
27
|
function all() {
|
|
13
28
|
return adapters;
|
|
14
29
|
}
|
|
30
|
+
function declaredPresetFor(target, cwd) {
|
|
31
|
+
for (const adapter of adapters) {
|
|
32
|
+
if (adapter.target !== target) continue;
|
|
33
|
+
const declared = adapter.declaredPreset?.(cwd);
|
|
34
|
+
if (declared !== void 0) return declared;
|
|
35
|
+
}
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
15
38
|
function availableTargets() {
|
|
16
39
|
return [...new Set(adapters.map((a) => a.target))];
|
|
17
40
|
}
|
|
@@ -24,7 +47,7 @@ function resolve(target, preset, runner) {
|
|
|
24
47
|
}
|
|
25
48
|
const candidates = preset ? forTarget.filter((a) => a.appliesTo(preset)) : forTarget;
|
|
26
49
|
if (candidates.length === 0) {
|
|
27
|
-
throw new
|
|
50
|
+
throw new PresetUnsupportedError(target, preset);
|
|
28
51
|
}
|
|
29
52
|
const wanted = runner ?? defaultRunner[target];
|
|
30
53
|
const available = candidates.map((a) => a.runner).join(", ");
|
|
@@ -131,21 +154,79 @@ var TARGETS = [
|
|
|
131
154
|
"format",
|
|
132
155
|
"typescript",
|
|
133
156
|
"build",
|
|
157
|
+
"dev",
|
|
134
158
|
"test",
|
|
135
159
|
"static-analysis",
|
|
136
160
|
"runtime-analysis",
|
|
137
161
|
"arch"
|
|
138
162
|
];
|
|
163
|
+
var LONG_RUNNING_TARGETS = ["dev"];
|
|
164
|
+
var SWEEPABLE_TARGETS = TARGETS.filter(
|
|
165
|
+
(target) => !LONG_RUNNING_TARGETS.includes(target)
|
|
166
|
+
);
|
|
139
167
|
var PRESET_NAMES = ["react", "nest", "svelte", "node", "tools"];
|
|
140
168
|
|
|
141
|
-
// src/roles/
|
|
169
|
+
// src/roles/build/adapters/vite/vite-dev.adapter.ts
|
|
142
170
|
import { spawnSync } from "child_process";
|
|
143
|
-
|
|
144
|
-
|
|
171
|
+
|
|
172
|
+
// src/core/config/tool-args.ts
|
|
173
|
+
import { existsSync as existsSync2 } from "fs";
|
|
174
|
+
import { isAbsolute, resolve as resolve2 } from "path";
|
|
175
|
+
function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
176
|
+
const takesValue = new Set(valueFlags);
|
|
177
|
+
const options = [];
|
|
178
|
+
const paths = [];
|
|
179
|
+
let previousTakesValue = false;
|
|
180
|
+
for (const arg of toolArgs) {
|
|
181
|
+
const looksLikeOption = arg.startsWith("-");
|
|
182
|
+
const target = isAbsolute(arg) ? arg : resolve2(cwd, arg);
|
|
183
|
+
if (!previousTakesValue && !looksLikeOption && existsSync2(target)) paths.push(arg);
|
|
184
|
+
else options.push(arg);
|
|
185
|
+
previousTakesValue = !arg.includes("=") && takesValue.has(arg);
|
|
186
|
+
}
|
|
187
|
+
return { options, paths };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/shared/resolve-bin.ts
|
|
191
|
+
import { existsSync as existsSync3 } from "fs";
|
|
192
|
+
import { createRequire } from "module";
|
|
193
|
+
import { delimiter, dirname as dirname2, join as join2 } from "path";
|
|
194
|
+
var require2 = createRequire(import.meta.url);
|
|
195
|
+
function resolveBin(fromDir, name) {
|
|
196
|
+
let dir = fromDir;
|
|
197
|
+
for (; ; ) {
|
|
198
|
+
const candidate = join2(dir, "node_modules", ".bin", name);
|
|
199
|
+
if (existsSync3(candidate)) return candidate;
|
|
200
|
+
const parent = dirname2(dir);
|
|
201
|
+
if (parent === dir) return void 0;
|
|
202
|
+
dir = parent;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function binFromOwnInstall(packageName, binName) {
|
|
206
|
+
try {
|
|
207
|
+
const manifest = require2.resolve(`${packageName}/package.json`);
|
|
208
|
+
const bin = require2(manifest).bin;
|
|
209
|
+
const relative3 = typeof bin === "string" ? bin : bin?.[binName];
|
|
210
|
+
if (!relative3) return void 0;
|
|
211
|
+
const executable = join2(dirname2(manifest), relative3);
|
|
212
|
+
return existsSync3(executable) ? executable : void 0;
|
|
213
|
+
} catch {
|
|
214
|
+
return void 0;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function binSearchPath(cwd) {
|
|
218
|
+
return [`${cwd}${delimiter}node_modules/.bin (and parents)`, "sentinel's own install"].join(
|
|
219
|
+
" then "
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/roles/build/plan.ts
|
|
224
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
225
|
+
import { join as join7 } from "path";
|
|
145
226
|
|
|
146
227
|
// src/core/config/existing-command.ts
|
|
147
228
|
import { readFileSync as readFileSync3 } from "fs";
|
|
148
|
-
import { join as
|
|
229
|
+
import { join as join4 } from "path";
|
|
149
230
|
|
|
150
231
|
// src/shared/jsonc.ts
|
|
151
232
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
@@ -160,11 +241,11 @@ function parseJsonc(text, source = "config") {
|
|
|
160
241
|
}
|
|
161
242
|
|
|
162
243
|
// src/core/config/manifest.ts
|
|
163
|
-
import { existsSync as
|
|
164
|
-
import { basename, join as
|
|
244
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
245
|
+
import { basename, join as join3 } from "path";
|
|
165
246
|
function moduleScripts(cwd) {
|
|
166
247
|
try {
|
|
167
|
-
const pkg = JSON.parse(readFileSync2(
|
|
248
|
+
const pkg = JSON.parse(readFileSync2(join3(cwd, "package.json"), "utf8"));
|
|
168
249
|
return pkg.scripts ?? {};
|
|
169
250
|
} catch {
|
|
170
251
|
return {};
|
|
@@ -172,7 +253,7 @@ function moduleScripts(cwd) {
|
|
|
172
253
|
}
|
|
173
254
|
function moduleName(cwd) {
|
|
174
255
|
try {
|
|
175
|
-
const pkg = JSON.parse(readFileSync2(
|
|
256
|
+
const pkg = JSON.parse(readFileSync2(join3(cwd, "package.json"), "utf8"));
|
|
176
257
|
return pkg.name;
|
|
177
258
|
} catch {
|
|
178
259
|
return void 0;
|
|
@@ -192,54 +273,675 @@ function manifestOperation(cwd, scripts) {
|
|
|
192
273
|
scripts,
|
|
193
274
|
devDependencies: { [own.name]: isSelfAdoption(cwd) ? "link:." : own.version }
|
|
194
275
|
};
|
|
195
|
-
if (
|
|
276
|
+
if (existsSync4(join3(cwd, "package.json"))) {
|
|
196
277
|
return { kind: "merge-json", path: "package.json", value };
|
|
197
278
|
}
|
|
198
|
-
return {
|
|
199
|
-
kind: "merge-json",
|
|
200
|
-
path: "package.json",
|
|
201
|
-
value: { name: readNxProjectName(cwd) ?? basename(cwd), private: true, ...value }
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// src/core/config/existing-command.ts
|
|
206
|
-
function nxTargetCommand(cwd, target) {
|
|
207
|
-
let project;
|
|
208
|
-
try {
|
|
209
|
-
project = parseJsonc(
|
|
210
|
-
readFileSync3(
|
|
211
|
-
"project.json"
|
|
212
|
-
);
|
|
213
|
-
} catch {
|
|
214
|
-
return void 0;
|
|
279
|
+
return {
|
|
280
|
+
kind: "merge-json",
|
|
281
|
+
path: "package.json",
|
|
282
|
+
value: { name: readNxProjectName(cwd) ?? basename(cwd), private: true, ...value }
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/core/config/existing-command.ts
|
|
287
|
+
function nxTargetCommand(cwd, target) {
|
|
288
|
+
let project;
|
|
289
|
+
try {
|
|
290
|
+
project = parseJsonc(
|
|
291
|
+
readFileSync3(join4(cwd, "project.json"), "utf8"),
|
|
292
|
+
"project.json"
|
|
293
|
+
);
|
|
294
|
+
} catch {
|
|
295
|
+
return void 0;
|
|
296
|
+
}
|
|
297
|
+
const entry = project.targets?.[target];
|
|
298
|
+
if (!entry || entry.executor !== "nx:run-commands") return void 0;
|
|
299
|
+
const { command, commands, cwd: targetCwd } = entry.options ?? {};
|
|
300
|
+
const ranInModule = typeof targetCwd === "string" && targetCwd.trim() !== "";
|
|
301
|
+
if (typeof command === "string" && command.trim() !== "") {
|
|
302
|
+
return { command: command.trim(), ranInModule };
|
|
303
|
+
}
|
|
304
|
+
if (Array.isArray(commands)) {
|
|
305
|
+
const parts = commands.filter((c) => typeof c === "string" && c.trim() !== "");
|
|
306
|
+
if (parts.length > 0) return { command: parts.map((c) => c.trim()).join(" && "), ranInModule };
|
|
307
|
+
}
|
|
308
|
+
return void 0;
|
|
309
|
+
}
|
|
310
|
+
function rootedForScript(command) {
|
|
311
|
+
return command.replace(/(^|&&\s*)pnpm exec /g, "$1pnpm --workspace-root exec ");
|
|
312
|
+
}
|
|
313
|
+
function existingCommand(cwd, target) {
|
|
314
|
+
const script = moduleScripts(cwd)[target];
|
|
315
|
+
if (script !== void 0 && script.trim() !== "") return script;
|
|
316
|
+
const fromTarget = nxTargetCommand(cwd, target);
|
|
317
|
+
if (fromTarget === void 0) return void 0;
|
|
318
|
+
return fromTarget.ranInModule ? fromTarget.command : rootedForScript(fromTarget.command);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/core/config/nx-target.ts
|
|
322
|
+
import { existsSync as existsSync5 } from "fs";
|
|
323
|
+
import { join as join5 } from "path";
|
|
324
|
+
function nxTargetOperations(options) {
|
|
325
|
+
const { cwd, targets } = options;
|
|
326
|
+
const names = Object.keys(targets);
|
|
327
|
+
if (names.length === 0) return [];
|
|
328
|
+
const operations = [];
|
|
329
|
+
if (existsSync5(join5(cwd, "project.json"))) {
|
|
330
|
+
operations.push({
|
|
331
|
+
kind: "remove-json-keys",
|
|
332
|
+
path: "project.json",
|
|
333
|
+
keys: names.map((name) => ["targets", name])
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
operations.push({
|
|
337
|
+
kind: "merge-json",
|
|
338
|
+
path: "package.json",
|
|
339
|
+
value: { nx: { targets } }
|
|
340
|
+
});
|
|
341
|
+
return operations;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/core/config/tool-script.ts
|
|
345
|
+
var SEGMENT_SEPARATOR = /(\s*(?:&&|\|\||;|&|\|)\s*)/;
|
|
346
|
+
function isSeparatorAt(index) {
|
|
347
|
+
return index % 2 === 1;
|
|
348
|
+
}
|
|
349
|
+
function invokes(segment, binary) {
|
|
350
|
+
return new RegExp(String.raw`(^|[\s/])${binary}(\s|$)`).test(segment.trim());
|
|
351
|
+
}
|
|
352
|
+
function isSentinelSegment(segment, roleFlag) {
|
|
353
|
+
const runsSentinel = invokes(segment, "sentinel") || segment.includes("bin/sentinel.js");
|
|
354
|
+
return runsSentinel && new RegExp(String.raw`(^|\s)${roleFlag}(\s|$)`).test(segment);
|
|
355
|
+
}
|
|
356
|
+
function composeToolScript(existing, options) {
|
|
357
|
+
const { command } = options;
|
|
358
|
+
if (!existing || existing.trim() === "") return command;
|
|
359
|
+
const { roleFlag } = options;
|
|
360
|
+
const isReplaceable = (segment) => options.replaces(segment) || roleFlag !== void 0 && isSentinelSegment(segment, roleFlag);
|
|
361
|
+
const parts = existing.split(SEGMENT_SEPARATOR);
|
|
362
|
+
const commands = parts.filter((_, index) => !isSeparatorAt(index));
|
|
363
|
+
if (!commands.some(isReplaceable)) return existing;
|
|
364
|
+
let replacedOnce = false;
|
|
365
|
+
const rebuilt = parts.map((part, index) => {
|
|
366
|
+
if (isSeparatorAt(index) || !isReplaceable(part)) return part;
|
|
367
|
+
if (replacedOnce) return null;
|
|
368
|
+
replacedOnce = true;
|
|
369
|
+
return options.keepFix && /(^|\s)--fix(\s|$)/.test(part) ? `${command} --fix` : command;
|
|
370
|
+
});
|
|
371
|
+
const kept = [];
|
|
372
|
+
for (let index = 0; index < rebuilt.length; index += 1) {
|
|
373
|
+
const part = rebuilt[index];
|
|
374
|
+
if (part === null) {
|
|
375
|
+
if (kept.length > 0) kept.pop();
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
kept.push(part);
|
|
379
|
+
}
|
|
380
|
+
return kept.join("").trim();
|
|
381
|
+
}
|
|
382
|
+
function keepsOtherCommands(script, sentinelCommand) {
|
|
383
|
+
return script.split(SEGMENT_SEPARATOR).filter((_, index) => !isSeparatorAt(index)).some((segment) => segment.trim() !== "" && segment.trim() !== sentinelCommand);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/roles/build/config-policy.ts
|
|
387
|
+
var BUILD_CONFIG_FILES = [
|
|
388
|
+
"vite.config.ts",
|
|
389
|
+
"vite.config.mts",
|
|
390
|
+
"vite.config.js",
|
|
391
|
+
"vite.config.mjs"
|
|
392
|
+
];
|
|
393
|
+
var BUILD_PRESET_SPECIFIER = "@hublo/sentinel/build/react";
|
|
394
|
+
var BUILD_SCRIPT_NAME = "build";
|
|
395
|
+
var SENTINEL_BUILD_COMMAND = "sentinel --run --build";
|
|
396
|
+
var DEV_SCRIPT_NAME = "serve";
|
|
397
|
+
var SENTINEL_DEV_COMMAND = "sentinel --run --dev";
|
|
398
|
+
function buildTarget() {
|
|
399
|
+
return {
|
|
400
|
+
[BUILD_SCRIPT_NAME]: {
|
|
401
|
+
cache: true,
|
|
402
|
+
inputs: ["default", "^default", `{projectRoot}/${BUILD_CONFIG_FILES[0]}`]
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
var SENTINEL_OWNED_BUILD_PACKAGES = [
|
|
407
|
+
"vite",
|
|
408
|
+
"@vitejs/plugin-react",
|
|
409
|
+
"@tailwindcss/vite",
|
|
410
|
+
"vite-plugin-svgr",
|
|
411
|
+
"nitro"
|
|
412
|
+
];
|
|
413
|
+
|
|
414
|
+
// src/roles/build/dev-script.ts
|
|
415
|
+
var CHAIN = " -- ";
|
|
416
|
+
var VITE_TOKEN = /(^|\s|\/)vite(\s|$)/;
|
|
417
|
+
var DEV_SUBCOMMANDS = /* @__PURE__ */ new Set(["dev", "serve"]);
|
|
418
|
+
function appOptions(rest) {
|
|
419
|
+
const tokens = (rest ?? "").trim().split(/\s+/).filter(Boolean);
|
|
420
|
+
const kept = tokens.filter((token, at) => !(at === 0 && DEV_SUBCOMMANDS.has(token)));
|
|
421
|
+
return kept.join(" ");
|
|
422
|
+
}
|
|
423
|
+
function composeDevScript(existing, command) {
|
|
424
|
+
if (existing === void 0 || existing.trim() === "") return command;
|
|
425
|
+
const chunks = existing.trim().split(CHAIN);
|
|
426
|
+
const at = chunks.map((chunk) => VITE_TOKEN.test(chunk)).lastIndexOf(true);
|
|
427
|
+
if (at === -1) return existing;
|
|
428
|
+
const rest = (chunks[at] ?? "").replace(/^.*?(^|\s|\/)vite(\s|$)/, "");
|
|
429
|
+
const options = appOptions(rest);
|
|
430
|
+
chunks[at] = options === "" ? command : `${command} -- ${options}`;
|
|
431
|
+
return chunks.join(CHAIN);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// src/roles/build/read-adoption.ts
|
|
435
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
436
|
+
import { join as join6 } from "path";
|
|
437
|
+
var NOT_ADOPTED = (configFile, unreadable = null) => ({
|
|
438
|
+
configFile,
|
|
439
|
+
preset: null,
|
|
440
|
+
adopted: false,
|
|
441
|
+
conformant: false,
|
|
442
|
+
drift: [],
|
|
443
|
+
ownDeclarations: [],
|
|
444
|
+
unreadable
|
|
445
|
+
});
|
|
446
|
+
function buildConfigFile(cwd) {
|
|
447
|
+
return BUILD_CONFIG_FILES.find((name) => existsSync6(join6(cwd, name)));
|
|
448
|
+
}
|
|
449
|
+
function importsPreset(source) {
|
|
450
|
+
const specifier = BUILD_PRESET_SPECIFIER.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
451
|
+
return new RegExp(`(?:from|import)\\s*\\(?\\s*['"\`]${specifier}['"\`]`).test(source);
|
|
452
|
+
}
|
|
453
|
+
var REACT_PLUGIN_SPECIFIERS = ["@vitejs/plugin-react", "@tanstack/react-start"];
|
|
454
|
+
function declaredBuildPreset(cwd) {
|
|
455
|
+
const configFile = buildConfigFile(cwd);
|
|
456
|
+
if (!configFile) return void 0;
|
|
457
|
+
try {
|
|
458
|
+
const source = readFileSync4(join6(cwd, configFile), "utf8");
|
|
459
|
+
return REACT_PLUGIN_SPECIFIERS.some((specifier) => source.includes(specifier)) ? "react" : void 0;
|
|
460
|
+
} catch {
|
|
461
|
+
return void 0;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function readBuildAdoption(cwd) {
|
|
465
|
+
const configFile = buildConfigFile(cwd);
|
|
466
|
+
if (!configFile) return NOT_ADOPTED(null);
|
|
467
|
+
let source;
|
|
468
|
+
try {
|
|
469
|
+
source = readFileSync4(join6(cwd, configFile), "utf8");
|
|
470
|
+
} catch (error) {
|
|
471
|
+
return NOT_ADOPTED(configFile, error instanceof Error ? error.message : String(error));
|
|
472
|
+
}
|
|
473
|
+
if (!importsPreset(source)) return NOT_ADOPTED(configFile);
|
|
474
|
+
const manifest = readProjectPackageJson(cwd);
|
|
475
|
+
const declared = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
476
|
+
const ownDeclarations = SENTINEL_OWNED_BUILD_PACKAGES.filter((name) => name in declared).map(
|
|
477
|
+
(name) => ({ name, version: declared[name] })
|
|
478
|
+
);
|
|
479
|
+
return {
|
|
480
|
+
configFile,
|
|
481
|
+
preset: "react",
|
|
482
|
+
adopted: true,
|
|
483
|
+
conformant: ownDeclarations.length === 0,
|
|
484
|
+
drift: ownDeclarations.map(
|
|
485
|
+
({ name, version }) => `declares ${name}@${version} of its own; sentinel owns it, and two copies in one build give the plugins a different Vite than the one running them`
|
|
486
|
+
),
|
|
487
|
+
ownDeclarations,
|
|
488
|
+
unreadable: null
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/roles/build/plan.ts
|
|
493
|
+
function scaffold() {
|
|
494
|
+
return `import { defineConfig, reactApp } from '${BUILD_PRESET_SPECIFIER}'
|
|
495
|
+
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
|
496
|
+
import { tanstackRouter } from '@tanstack/router-plugin/vite'
|
|
497
|
+
import path from 'node:path'
|
|
498
|
+
|
|
499
|
+
/*
|
|
500
|
+
* Composition comes from sentinel: how the env is loaded, which plugins run under test and
|
|
501
|
+
* which under a real build, the order they run in. The values below are this app's own.
|
|
502
|
+
*
|
|
503
|
+
* TanStack is passed IN rather than imported by sentinel, because the app codes against it
|
|
504
|
+
* directly. \`alias\` is passed through verbatim and never read, so nothing in it can be lost.
|
|
505
|
+
* Anything sentinel does not set goes in \`overrides\`, which merges over the preset last.
|
|
506
|
+
*/
|
|
507
|
+
export default defineConfig(({ mode }) =>
|
|
508
|
+
reactApp({
|
|
509
|
+
root: __dirname,
|
|
510
|
+
mode,
|
|
511
|
+
base: '/',
|
|
512
|
+
port: 3000,
|
|
513
|
+
// Explicit, NOT port + 1: the three apps in this repo disagree, one of them goes down.
|
|
514
|
+
hmrPort: 3001,
|
|
515
|
+
router: {
|
|
516
|
+
routesDirectory: path.resolve(__dirname, 'src/routes'),
|
|
517
|
+
generatedRouteTree: path.resolve(__dirname, 'src/routeTree.gen.ts'),
|
|
518
|
+
},
|
|
519
|
+
tanstack: { start: tanstackStart, router: tanstackRouter },
|
|
520
|
+
alias: [],
|
|
521
|
+
}),
|
|
522
|
+
)
|
|
523
|
+
`;
|
|
524
|
+
}
|
|
525
|
+
function ownedBuildDependencies(cwd) {
|
|
526
|
+
const manifest = readProjectPackageJson(cwd);
|
|
527
|
+
const keys = [];
|
|
528
|
+
for (const section of ["dependencies", "devDependencies"]) {
|
|
529
|
+
const declared = manifest[section];
|
|
530
|
+
if (!declared) continue;
|
|
531
|
+
for (const name of SENTINEL_OWNED_BUILD_PACKAGES) {
|
|
532
|
+
if (name in declared) keys.push([section, name]);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return keys;
|
|
536
|
+
}
|
|
537
|
+
function invokesVite(segment) {
|
|
538
|
+
return /(^|\s|\/)vite(\s|$)/.test(segment.trim());
|
|
539
|
+
}
|
|
540
|
+
function buildScripts(cwd) {
|
|
541
|
+
const own = selfCommand(cwd, "--run --build");
|
|
542
|
+
const scripts = {
|
|
543
|
+
[BUILD_SCRIPT_NAME]: composeToolScript(existingCommand(cwd, BUILD_SCRIPT_NAME), {
|
|
544
|
+
command: own ?? SENTINEL_BUILD_COMMAND,
|
|
545
|
+
replaces: invokesVite,
|
|
546
|
+
// Only meaningful for the self case: it lets a re-init correct a sentinel invocation
|
|
547
|
+
// whose FORM is wrong, without matching another role's script.
|
|
548
|
+
roleFlag: own === void 0 ? void 0 : "--build"
|
|
549
|
+
})
|
|
550
|
+
};
|
|
551
|
+
const existingDev = existingCommand(cwd, DEV_SCRIPT_NAME);
|
|
552
|
+
if (existingDev !== void 0) {
|
|
553
|
+
scripts[DEV_SCRIPT_NAME] = composeDevScript(
|
|
554
|
+
existingDev,
|
|
555
|
+
selfCommand(cwd, "--run --dev") ?? SENTINEL_DEV_COMMAND
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
return scripts;
|
|
559
|
+
}
|
|
560
|
+
function plan(context) {
|
|
561
|
+
if (declaredBuildPreset(context.cwd) === void 0 && buildConfigFile(context.cwd) !== void 0) {
|
|
562
|
+
return {
|
|
563
|
+
operations: [],
|
|
564
|
+
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; the two SvelteKit configs in this repo share nothing with it.`
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const notes = [];
|
|
568
|
+
const operations = [];
|
|
569
|
+
const configFile = buildConfigFile(context.cwd);
|
|
570
|
+
if (configFile === void 0) {
|
|
571
|
+
operations.push({ kind: "write", path: BUILD_CONFIG_FILES[0], contents: scaffold() });
|
|
572
|
+
notes.push(
|
|
573
|
+
`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.`
|
|
574
|
+
);
|
|
575
|
+
} else if (!readsPreset(context.cwd, configFile)) {
|
|
576
|
+
notes.push(
|
|
577
|
+
`${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.`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
operations.push(manifestOperation(context.cwd, buildScripts(context.cwd)));
|
|
581
|
+
operations.push(...nxTargetOperations({ cwd: context.cwd, targets: buildTarget() }));
|
|
582
|
+
const owned = ownedBuildDependencies(context.cwd);
|
|
583
|
+
if (owned.length > 0) {
|
|
584
|
+
operations.push({ kind: "remove-json-keys", path: "package.json", keys: owned });
|
|
585
|
+
notes.push(
|
|
586
|
+
`removed ${owned.map(([, name]) => name).join(", ")} from this module: sentinel owns them now, and two copies of Vite in one build give the plugins a different Vite than the one running them.`
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
return { operations, notes };
|
|
590
|
+
}
|
|
591
|
+
function readsPreset(cwd, configFile) {
|
|
592
|
+
if (!existsSync7(join7(cwd, configFile))) return false;
|
|
593
|
+
try {
|
|
594
|
+
return readFileSync5(join7(cwd, configFile), "utf8").includes(BUILD_PRESET_SPECIFIER);
|
|
595
|
+
} catch {
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/roles/build/resolve-vite.ts
|
|
601
|
+
function resolveVite(cwd) {
|
|
602
|
+
return binFromOwnInstall("vite", "vite") ?? resolveBin(cwd, "vite");
|
|
603
|
+
}
|
|
604
|
+
function viteOrigin(cwd) {
|
|
605
|
+
if (binFromOwnInstall("vite", "vite")) return "sentinel";
|
|
606
|
+
return resolveBin(cwd, "vite") ? "module" : "none";
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// src/roles/build/adapters/vite/vite-dev.adapter.ts
|
|
610
|
+
var ViteDevAdapter = class extends BaseAdapter {
|
|
611
|
+
target = "dev";
|
|
612
|
+
runner = "vite";
|
|
613
|
+
appliesTo(preset) {
|
|
614
|
+
return preset === "react";
|
|
615
|
+
}
|
|
616
|
+
declaredPreset(cwd) {
|
|
617
|
+
return declaredBuildPreset(cwd);
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* The same plan as `--build`, deliberately.
|
|
621
|
+
*
|
|
622
|
+
* `build` and `serve` are one app's two calls into one toolchain, so adopting one without
|
|
623
|
+
* the other leaves the module half-migrated in a way nothing reports. The operations are
|
|
624
|
+
* idempotent, so `--init --build` and `--init --dev` are interchangeable rather than
|
|
625
|
+
* additive.
|
|
626
|
+
*/
|
|
627
|
+
plan(context) {
|
|
628
|
+
return plan(context);
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Start the dev server. It does not return until stopped, so there is no verdict to report
|
|
632
|
+
* beyond the exit code the developer's own Ctrl-C produces.
|
|
633
|
+
*
|
|
634
|
+
* No `prebuild` here, unlike `--run --build`: the artefact this repo generates before a build
|
|
635
|
+
* is produced by the watcher that WRAPS this command (`run-with-runtime-artifact-watch`), and
|
|
636
|
+
* running it again would race the watcher that is about to own the file.
|
|
637
|
+
*/
|
|
638
|
+
async run(ctx) {
|
|
639
|
+
if (!readBuildAdoption(ctx.cwd).adopted) {
|
|
640
|
+
process.stderr.write(
|
|
641
|
+
`sentinel dev(vite): no sentinel preset in this module's Vite config; run \`sentinel --init --build\` to adopt.
|
|
642
|
+
`
|
|
643
|
+
);
|
|
644
|
+
return { ok: true, code: 0 };
|
|
645
|
+
}
|
|
646
|
+
const vite = resolveVite(ctx.cwd);
|
|
647
|
+
if (!vite) {
|
|
648
|
+
process.stderr.write(
|
|
649
|
+
`sentinel dev(vite): could not find the vite binary (looked in ${binSearchPath(ctx.cwd)}). Run \`pnpm install\` in the module.
|
|
650
|
+
`
|
|
651
|
+
);
|
|
652
|
+
return { ok: false, code: 1 };
|
|
653
|
+
}
|
|
654
|
+
if (viteOrigin(ctx.cwd) === "module") {
|
|
655
|
+
process.stderr.write(
|
|
656
|
+
`sentinel dev(vite): serving with the MODULE's vite, not sentinel's. An adopted config gets its plugins from sentinel, and two copies of Vite in one process fail in ways that never mention a version. Remove vite from this module's package.json.
|
|
657
|
+
`
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, { valueFlags: [] });
|
|
661
|
+
const result = spawnSync(vite, [...options, ...paths], { cwd: ctx.cwd, stdio: "inherit" });
|
|
662
|
+
if (result.error) {
|
|
663
|
+
process.stderr.write(`sentinel dev(vite): could not run vite (${result.error.message})
|
|
664
|
+
`);
|
|
665
|
+
return { ok: false, code: 1 };
|
|
666
|
+
}
|
|
667
|
+
const code = result.status ?? 0;
|
|
668
|
+
return { ok: code === 0, code };
|
|
669
|
+
}
|
|
670
|
+
async status(ctx) {
|
|
671
|
+
const { adopted, preset, conformant, drift, unreadable } = readBuildAdoption(ctx.cwd);
|
|
672
|
+
return { adopted, preset, conformant, drift, unreadable };
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
// src/roles/build/adapters/vite/vite.adapter.ts
|
|
677
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
678
|
+
|
|
679
|
+
// src/roles/build/owned-paths.ts
|
|
680
|
+
var PRESET_OWNED_KEYS = [
|
|
681
|
+
"base",
|
|
682
|
+
"root",
|
|
683
|
+
"define",
|
|
684
|
+
"server",
|
|
685
|
+
"build",
|
|
686
|
+
"resolve",
|
|
687
|
+
"plugins"
|
|
688
|
+
];
|
|
689
|
+
function describeDepartures(config, defaults) {
|
|
690
|
+
const departures = [];
|
|
691
|
+
const build = asRecord(config.build);
|
|
692
|
+
const target = build?.target;
|
|
693
|
+
if (typeof target === "string" && target !== defaults.target) {
|
|
694
|
+
departures.push({
|
|
695
|
+
rule: "build.target",
|
|
696
|
+
reason: `${target}, where the convention is ${defaults.target}`
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
const server = asRecord(config.server);
|
|
700
|
+
const hosts = server?.allowedHosts;
|
|
701
|
+
if (Array.isArray(hosts) && !sameStrings(hosts, defaults.allowedHosts)) {
|
|
702
|
+
departures.push({
|
|
703
|
+
rule: "server.allowedHosts",
|
|
704
|
+
reason: `${JSON.stringify(hosts)}, where the convention is ${JSON.stringify(defaults.allowedHosts)}`
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
return departures;
|
|
708
|
+
}
|
|
709
|
+
function appAdditions(config) {
|
|
710
|
+
const owned = new Set(PRESET_OWNED_KEYS);
|
|
711
|
+
return Object.keys(config).filter((key) => !owned.has(key)).sort();
|
|
712
|
+
}
|
|
713
|
+
function asRecord(value) {
|
|
714
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
715
|
+
}
|
|
716
|
+
function sameStrings(a, b) {
|
|
717
|
+
return a.length === b.length && a.every((value, at) => value === b[at]);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// src/roles/build/prerequisite.ts
|
|
721
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
722
|
+
var PREBUILD_SCRIPT_NAME = `pre${BUILD_SCRIPT_NAME}`;
|
|
723
|
+
function insideBuildScript(env = process.env) {
|
|
724
|
+
return env.npm_lifecycle_event === BUILD_SCRIPT_NAME;
|
|
725
|
+
}
|
|
726
|
+
function runPrerequisite(cwd, env = process.env) {
|
|
727
|
+
const scripts = moduleScripts(cwd);
|
|
728
|
+
const script = scripts[PREBUILD_SCRIPT_NAME];
|
|
729
|
+
if (script === void 0 || script.trim() === "") return { kind: "none" };
|
|
730
|
+
if (insideBuildScript(env)) return { kind: "already-run" };
|
|
731
|
+
const result = spawnSync2("pnpm", ["run", PREBUILD_SCRIPT_NAME], { cwd, stdio: "inherit" });
|
|
732
|
+
if (result.error) {
|
|
733
|
+
return { kind: "failed", code: 1, reason: result.error.message };
|
|
734
|
+
}
|
|
735
|
+
const code = result.status ?? 1;
|
|
736
|
+
if (code !== 0) {
|
|
737
|
+
return {
|
|
738
|
+
kind: "failed",
|
|
739
|
+
code,
|
|
740
|
+
reason: `\`pnpm run ${PREBUILD_SCRIPT_NAME}\` exited ${code}`
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
return { kind: "ran" };
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// src/roles/build/resolve-config.ts
|
|
747
|
+
async function resolveBuildConfig(cwd) {
|
|
748
|
+
try {
|
|
749
|
+
const { loadConfigFromFile } = await import("vite");
|
|
750
|
+
const loaded = await loadConfigFromFile(
|
|
751
|
+
{ command: "build", mode: "production" },
|
|
752
|
+
void 0,
|
|
753
|
+
cwd,
|
|
754
|
+
"silent"
|
|
755
|
+
);
|
|
756
|
+
if (!loaded) return { kind: "failed", reason: "Vite found no config file in this module" };
|
|
757
|
+
return { kind: "loaded", config: loaded.config, from: loaded.path };
|
|
758
|
+
} catch (error) {
|
|
759
|
+
return { kind: "failed", reason: error instanceof Error ? error.message : String(error) };
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// src/roles/build/adapters/vite/vite.adapter.ts
|
|
764
|
+
var VITE_VALUE_FLAGS = ["--config", "-c", "--mode", "-m", "--outDir", "--logLevel", "--base"];
|
|
765
|
+
var ViteAdapter = class extends BaseAdapter {
|
|
766
|
+
target = "build";
|
|
767
|
+
runner = "vite";
|
|
768
|
+
/**
|
|
769
|
+
* React only. The two SvelteKit configs in this repo are 34 and 21 lines and share nothing
|
|
770
|
+
* with the React three; claiming them would mean a preset that fits neither.
|
|
771
|
+
*/
|
|
772
|
+
appliesTo(preset) {
|
|
773
|
+
return preset === "react";
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* `react`, read from the module's Vite config rather than from its dependencies.
|
|
777
|
+
*
|
|
778
|
+
* Without this the role is unreachable in practice: the toolchain is declared at the ROOT,
|
|
779
|
+
* so every front app detects as `node` and `appliesTo` filters the adapter out — including
|
|
780
|
+
* during `sentinel --inspect` with no target, which never passes `--preset`.
|
|
781
|
+
*/
|
|
782
|
+
declaredPreset(cwd) {
|
|
783
|
+
return declaredBuildPreset(cwd);
|
|
784
|
+
}
|
|
785
|
+
plan(context) {
|
|
786
|
+
return plan(context);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Build the module.
|
|
790
|
+
*
|
|
791
|
+
* A module that has not adopted is reported and passes, the same stance every other role
|
|
792
|
+
* takes: `--run` with no named target sweeps every wired target across the workspace, so
|
|
793
|
+
* failing here would exit non-zero on every module that has not migrated, which is most of
|
|
794
|
+
* them.
|
|
795
|
+
*/
|
|
796
|
+
async run(ctx) {
|
|
797
|
+
const adoption = readBuildAdoption(ctx.cwd);
|
|
798
|
+
if (!adoption.adopted) {
|
|
799
|
+
process.stderr.write(
|
|
800
|
+
`sentinel build(vite): no sentinel preset in this module's Vite config; run \`sentinel --init --build\` to adopt.
|
|
801
|
+
`
|
|
802
|
+
);
|
|
803
|
+
return { ok: true, code: 0 };
|
|
804
|
+
}
|
|
805
|
+
const prerequisite = runPrerequisite(ctx.cwd);
|
|
806
|
+
if (prerequisite.kind === "failed") {
|
|
807
|
+
process.stderr.write(
|
|
808
|
+
`sentinel build(vite): the ${PREBUILD_SCRIPT_NAME} step failed, so the build was not started: ${prerequisite.reason}
|
|
809
|
+
`
|
|
810
|
+
);
|
|
811
|
+
return { ok: false, code: prerequisite.code };
|
|
812
|
+
}
|
|
813
|
+
const vite = resolveVite(ctx.cwd);
|
|
814
|
+
if (!vite) {
|
|
815
|
+
process.stderr.write(
|
|
816
|
+
`sentinel build(vite): could not find the vite binary (looked in ${binSearchPath(ctx.cwd)}). Run \`pnpm install\` in the module.
|
|
817
|
+
`
|
|
818
|
+
);
|
|
819
|
+
return { ok: false, code: 1 };
|
|
820
|
+
}
|
|
821
|
+
if (viteOrigin(ctx.cwd) === "module") {
|
|
822
|
+
process.stderr.write(
|
|
823
|
+
`sentinel build(vite): building with the MODULE's vite, not sentinel's. An adopted config gets its plugins from sentinel, and two copies of Vite in one build fail in ways that never mention a version. Remove vite from this module's package.json.
|
|
824
|
+
`
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
828
|
+
valueFlags: VITE_VALUE_FLAGS
|
|
829
|
+
});
|
|
830
|
+
const result = spawnSync3(vite, ["build", ...options, ...paths], {
|
|
831
|
+
cwd: ctx.cwd,
|
|
832
|
+
stdio: "inherit"
|
|
833
|
+
});
|
|
834
|
+
if (result.error) {
|
|
835
|
+
process.stderr.write(`sentinel build(vite): could not run vite (${result.error.message})
|
|
836
|
+
`);
|
|
837
|
+
return { ok: false, code: 1 };
|
|
838
|
+
}
|
|
839
|
+
const code = result.status ?? 1;
|
|
840
|
+
return { ok: code === 0, code };
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* What this module builds with, without building it.
|
|
844
|
+
*
|
|
845
|
+
* `--inspect` reads the committed config for every role, and the moment `--build` was wired
|
|
846
|
+
* it joined the sweep — so leaving this to throw would have broken `sentinel --inspect` on
|
|
847
|
+
* every React module, for a role that had only just arrived. Cheap and honest is the bar.
|
|
848
|
+
*
|
|
849
|
+
* `vite` is the interesting field and the one nothing else reports: an adopted config takes
|
|
850
|
+
* its plugins from sentinel, so a module answering `module` here is one build away from the
|
|
851
|
+
* two-copies failure, and this is where that is visible before it happens.
|
|
852
|
+
*
|
|
853
|
+
* The resolved config comes from Vite's own loader rather than from parsing the file. See
|
|
854
|
+
* `resolve-config` for why that is the only honest answer here, and what it costs.
|
|
855
|
+
*/
|
|
856
|
+
async inspect(ctx) {
|
|
857
|
+
const adoption = readBuildAdoption(ctx.cwd);
|
|
858
|
+
const base = {
|
|
859
|
+
runner: "vite",
|
|
860
|
+
configFile: adoption.configFile,
|
|
861
|
+
...adoption.unreadable ? { unreadable: adoption.unreadable } : {},
|
|
862
|
+
vite: viteOrigin(ctx.cwd),
|
|
863
|
+
// Rendered as a labelled block by the shared renderer, the same shape the lint role's
|
|
864
|
+
// parked rules and the format role's overrides already use.
|
|
865
|
+
overrides: adoption.ownDeclarations.map(({ name, version }) => ({
|
|
866
|
+
rule: name,
|
|
867
|
+
reason: `declared by this module at ${version}, where sentinel owns it`
|
|
868
|
+
})),
|
|
869
|
+
prerequisite: moduleScripts(ctx.cwd)[PREBUILD_SCRIPT_NAME] ?? null
|
|
870
|
+
};
|
|
871
|
+
if (adoption.configFile === null) return base;
|
|
872
|
+
const resolved = await resolveBuildConfig(ctx.cwd);
|
|
873
|
+
if (resolved.kind === "failed") {
|
|
874
|
+
return { ...base, configError: resolved.reason };
|
|
875
|
+
}
|
|
876
|
+
const config = resolved.config;
|
|
877
|
+
return {
|
|
878
|
+
...base,
|
|
879
|
+
// What the app departs from, and what it adds, are different facts. A DEPARTURE is a
|
|
880
|
+
// disagreement with an opinion the preset holds; an ADDITION is the app needing
|
|
881
|
+
// something the preset never claimed, which is the preset working rather than being
|
|
882
|
+
// worked around.
|
|
883
|
+
departures: describeDepartures(config, REACT_APP_DEFAULTS),
|
|
884
|
+
additions: appAdditions(config),
|
|
885
|
+
resolved: {
|
|
886
|
+
base: config.base ?? null,
|
|
887
|
+
target: config.build?.target ?? null,
|
|
888
|
+
sourcemap: config.build?.sourcemap ?? null,
|
|
889
|
+
port: config.server?.port ?? null,
|
|
890
|
+
// The one number nothing else reports and that no formula predicts: career goes DOWN
|
|
891
|
+
// to 9998 where the others go up, which is why it is data rather than `port + 1`.
|
|
892
|
+
hmrPort: config.server?.hmr?.port ?? null,
|
|
893
|
+
// Counted, not listed. It is 56-67% of every config today, and printing 296 entries
|
|
894
|
+
// would bury everything above it. The count is what tells you whether it moved.
|
|
895
|
+
aliases: Array.isArray(config.resolve?.alias) ? config.resolve.alias.length : 0,
|
|
896
|
+
plugins: Array.isArray(config.plugins) ? config.plugins.flat(9).length : 0
|
|
897
|
+
}
|
|
898
|
+
};
|
|
215
899
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
900
|
+
/**
|
|
901
|
+
* Adoption as data, WITHOUT building.
|
|
902
|
+
*
|
|
903
|
+
* Deliberately not "run the build and attach metrics". `--report` is what a migration
|
|
904
|
+
* dashboard calls across the whole workspace, and a bundler is the one tool here where doing
|
|
905
|
+
* the real work costs minutes per module rather than seconds. A report that nobody can
|
|
906
|
+
* afford to run is a report nobody runs.
|
|
907
|
+
*
|
|
908
|
+
* Bundle size is the metric this will eventually want, and it needs a build to produce. It
|
|
909
|
+
* belongs behind an explicit opt-in rather than in the default sweep, and the ticket parks
|
|
910
|
+
* it for exactly that reason.
|
|
911
|
+
*/
|
|
912
|
+
async report(ctx) {
|
|
913
|
+
const adoption = readBuildAdoption(ctx.cwd);
|
|
914
|
+
return {
|
|
915
|
+
ok: true,
|
|
916
|
+
code: 0,
|
|
917
|
+
metrics: {
|
|
918
|
+
vite: viteOrigin(ctx.cwd),
|
|
919
|
+
ownBuildDependencies: adoption.ownDeclarations.length
|
|
920
|
+
}
|
|
921
|
+
};
|
|
222
922
|
}
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
923
|
+
async status(ctx) {
|
|
924
|
+
const { adopted, preset, conformant, drift, unreadable } = readBuildAdoption(ctx.cwd);
|
|
925
|
+
return { adopted, preset, conformant, drift, unreadable };
|
|
226
926
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const fromTarget = nxTargetCommand(cwd, target);
|
|
236
|
-
if (fromTarget === void 0) return void 0;
|
|
237
|
-
return fromTarget.ranInModule ? fromTarget.command : rootedForScript(fromTarget.command);
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
// src/roles/build/register.ts
|
|
930
|
+
function registerBuild() {
|
|
931
|
+
register(new ViteAdapter());
|
|
932
|
+
setDefaultRunner("build", "vite");
|
|
933
|
+
register(new ViteDevAdapter());
|
|
934
|
+
setDefaultRunner("dev", "vite");
|
|
238
935
|
}
|
|
239
936
|
|
|
937
|
+
// src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
|
|
938
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
939
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
940
|
+
import { join as join14 } from "path";
|
|
941
|
+
|
|
240
942
|
// src/core/config/has-source.ts
|
|
241
943
|
import { readdirSync } from "fs";
|
|
242
|
-
import { extname, join as
|
|
944
|
+
import { extname, join as join8 } from "path";
|
|
243
945
|
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
244
946
|
"node_modules",
|
|
245
947
|
"dist",
|
|
@@ -290,7 +992,7 @@ function hasSourceFiles(cwd, extensions) {
|
|
|
290
992
|
}
|
|
291
993
|
for (const entry of entries) {
|
|
292
994
|
if (entry.isDirectory()) {
|
|
293
|
-
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(
|
|
995
|
+
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join8(dir, entry.name));
|
|
294
996
|
continue;
|
|
295
997
|
}
|
|
296
998
|
if (wanted.has(extname(entry.name))) return true;
|
|
@@ -299,54 +1001,13 @@ function hasSourceFiles(cwd, extensions) {
|
|
|
299
1001
|
return false;
|
|
300
1002
|
}
|
|
301
1003
|
|
|
302
|
-
// src/core/config/nx-target.ts
|
|
303
|
-
import { existsSync as existsSync3 } from "fs";
|
|
304
|
-
import { join as join5 } from "path";
|
|
305
|
-
function nxTargetOperations(options) {
|
|
306
|
-
const { cwd, targets } = options;
|
|
307
|
-
const names = Object.keys(targets);
|
|
308
|
-
if (names.length === 0) return [];
|
|
309
|
-
const operations = [];
|
|
310
|
-
if (existsSync3(join5(cwd, "project.json"))) {
|
|
311
|
-
operations.push({
|
|
312
|
-
kind: "remove-json-keys",
|
|
313
|
-
path: "project.json",
|
|
314
|
-
keys: names.map((name) => ["targets", name])
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
operations.push({
|
|
318
|
-
kind: "merge-json",
|
|
319
|
-
path: "package.json",
|
|
320
|
-
value: { nx: { targets } }
|
|
321
|
-
});
|
|
322
|
-
return operations;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// src/core/config/tool-args.ts
|
|
326
|
-
import { existsSync as existsSync4 } from "fs";
|
|
327
|
-
import { isAbsolute, resolve as resolve2 } from "path";
|
|
328
|
-
function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
329
|
-
const takesValue = new Set(valueFlags);
|
|
330
|
-
const options = [];
|
|
331
|
-
const paths = [];
|
|
332
|
-
let previousTakesValue = false;
|
|
333
|
-
for (const arg of toolArgs) {
|
|
334
|
-
const looksLikeOption = arg.startsWith("-");
|
|
335
|
-
const target = isAbsolute(arg) ? arg : resolve2(cwd, arg);
|
|
336
|
-
if (!previousTakesValue && !looksLikeOption && existsSync4(target)) paths.push(arg);
|
|
337
|
-
else options.push(arg);
|
|
338
|
-
previousTakesValue = !arg.includes("=") && takesValue.has(arg);
|
|
339
|
-
}
|
|
340
|
-
return { options, paths };
|
|
341
|
-
}
|
|
342
|
-
|
|
343
1004
|
// src/core/settings.ts
|
|
344
1005
|
var WORKSPACE_ROOT_MARKER = "nx.json";
|
|
345
1006
|
var DEFAULT_MAX_DIAGNOSTICS = 100;
|
|
346
1007
|
|
|
347
1008
|
// src/core/workspace-prep.ts
|
|
348
|
-
import { existsSync as
|
|
349
|
-
import { dirname as
|
|
1009
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync } from "fs";
|
|
1010
|
+
import { dirname as dirname3, join as join9, relative } from "path";
|
|
350
1011
|
var OVERRIDE_KEY = "i18next>typescript";
|
|
351
1012
|
var NATIVE_TS_ALIAS = "@typescript/native";
|
|
352
1013
|
var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
@@ -355,8 +1016,8 @@ var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
|
|
|
355
1016
|
function findWorkspaceRoot(startDir) {
|
|
356
1017
|
let dir = startDir;
|
|
357
1018
|
for (; ; ) {
|
|
358
|
-
if (
|
|
359
|
-
const parent =
|
|
1019
|
+
if (existsSync8(join9(dir, WORKSPACE_ROOT_MARKER))) return dir;
|
|
1020
|
+
const parent = dirname3(dir);
|
|
360
1021
|
if (parent === dir) return void 0;
|
|
361
1022
|
dir = parent;
|
|
362
1023
|
}
|
|
@@ -368,9 +1029,9 @@ function declaredNativeTs(pkg) {
|
|
|
368
1029
|
return version || void 0;
|
|
369
1030
|
}
|
|
370
1031
|
function ensureI18nextSingleton(root, dryRun) {
|
|
371
|
-
const pkgPath =
|
|
372
|
-
if (!
|
|
373
|
-
const pkg = JSON.parse(
|
|
1032
|
+
const pkgPath = join9(root, "package.json");
|
|
1033
|
+
if (!existsSync8(pkgPath)) return void 0;
|
|
1034
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
374
1035
|
const want = declaredNativeTs(pkg);
|
|
375
1036
|
if (!want) return void 0;
|
|
376
1037
|
const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
@@ -383,10 +1044,10 @@ function ensureI18nextSingleton(root, dryRun) {
|
|
|
383
1044
|
return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
|
|
384
1045
|
}
|
|
385
1046
|
function ensureReleaseAgeAllowList(root, dryRun) {
|
|
386
|
-
const yamlPath =
|
|
387
|
-
if (!
|
|
1047
|
+
const yamlPath = join9(root, WORKSPACE_YAML);
|
|
1048
|
+
if (!existsSync8(yamlPath)) return void 0;
|
|
388
1049
|
const own = readOwnPackage().name;
|
|
389
|
-
const lines =
|
|
1050
|
+
const lines = readFileSync6(yamlPath, "utf8").split("\n");
|
|
390
1051
|
const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
|
|
391
1052
|
if (keyIdx === -1) return void 0;
|
|
392
1053
|
let lastItemIdx = keyIdx;
|
|
@@ -421,11 +1082,11 @@ var ROOT_PRETTIER_CONFIGS = [
|
|
|
421
1082
|
function ensureFormatterExclusion(root, moduleDir, dryRun) {
|
|
422
1083
|
const rel = relative(root, moduleDir).replaceAll("\\", "/");
|
|
423
1084
|
if (rel === "" || rel.startsWith("..")) return void 0;
|
|
424
|
-
const ignorePath =
|
|
425
|
-
const hasPrettier =
|
|
1085
|
+
const ignorePath = join9(root, PRETTIER_IGNORE);
|
|
1086
|
+
const hasPrettier = existsSync8(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync8(join9(root, name)));
|
|
426
1087
|
if (!hasPrettier) return void 0;
|
|
427
1088
|
const pattern = `/${rel}/`;
|
|
428
|
-
const existing =
|
|
1089
|
+
const existing = existsSync8(ignorePath) ? readFileSync6(ignorePath, "utf8") : "";
|
|
429
1090
|
const lines = existing.split("\n");
|
|
430
1091
|
if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
|
|
431
1092
|
return void 0;
|
|
@@ -458,38 +1119,39 @@ function ensureWorkspacePrep(opts) {
|
|
|
458
1119
|
opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
|
|
459
1120
|
].filter((message) => message !== void 0);
|
|
460
1121
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
if (existsSync6(candidate)) return candidate;
|
|
472
|
-
const parent = dirname3(dir);
|
|
473
|
-
if (parent === dir) return void 0;
|
|
474
|
-
dir = parent;
|
|
1122
|
+
function inspectWorkspacePrep(root) {
|
|
1123
|
+
const entries = [];
|
|
1124
|
+
let pkg = {};
|
|
1125
|
+
const pkgPath = join9(root, "package.json");
|
|
1126
|
+
if (existsSync8(pkgPath)) {
|
|
1127
|
+
try {
|
|
1128
|
+
pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
1129
|
+
} catch {
|
|
1130
|
+
pkg = {};
|
|
1131
|
+
}
|
|
475
1132
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const executable = join7(dirname3(manifest), relative3);
|
|
484
|
-
return existsSync6(executable) ? executable : void 0;
|
|
485
|
-
} catch {
|
|
486
|
-
return void 0;
|
|
1133
|
+
const pinned = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
1134
|
+
if (pinned !== void 0) {
|
|
1135
|
+
const stillForks = declaredNativeTs(pkg);
|
|
1136
|
+
entries.push({
|
|
1137
|
+
rule: `package.json \u2192 pnpm.overrides.${OVERRIDE_KEY}`,
|
|
1138
|
+
reason: stillForks === void 0 ? `pinned to ${pinned}, but the root no longer declares ${NATIVE_TS_ALIAS}, so nothing forks any more. This override is now dead config and can be removed.` : `pinned to ${pinned}, matching the second TypeScript the root declares (${NATIVE_TS_ALIAS}). It re-resolves i18next for EVERY module that depends on it, not only adopted ones. It retires with the second TypeScript.`
|
|
1139
|
+
});
|
|
487
1140
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
1141
|
+
const own = readOwnPackage().name;
|
|
1142
|
+
const yamlPath = join9(root, WORKSPACE_YAML);
|
|
1143
|
+
if (existsSync8(yamlPath)) {
|
|
1144
|
+
const yaml = readFileSync6(yamlPath, "utf8");
|
|
1145
|
+
const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
|
|
1146
|
+
if (listed) {
|
|
1147
|
+
const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
|
|
1148
|
+
entries.push({
|
|
1149
|
+
rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
|
|
1150
|
+
reason: gated ? `${own} is exempt from this workspace's release-age gate, so a freshly published version installs instead of being held. That is a standing exemption from a supply-chain control: the exposure is one bump wide, since the version is pinned and the lockfile carries its integrity, so a re-publish of the same version does not pass. Retire it once a bump can simply wait out the window.` : `${own} is allow-listed, but this workspace no longer sets minimumReleaseAge, so there is no gate to be exempt from. This entry is now dead config and can be removed.`
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return entries;
|
|
493
1155
|
}
|
|
494
1156
|
|
|
495
1157
|
// src/roles/format/config-policy.ts
|
|
@@ -514,6 +1176,7 @@ var PERMITTED_LOCAL_KEYS = [
|
|
|
514
1176
|
"experimentalOperatorPosition"
|
|
515
1177
|
];
|
|
516
1178
|
var ALWAYS_LOCAL_KEYS = ["ignorePatterns", "overrides"];
|
|
1179
|
+
var FORMAT_DEFAULT_IGNORES = ["**/*.toml"];
|
|
517
1180
|
var PRETTIER_CONFIG_FILES = [
|
|
518
1181
|
".prettierrc",
|
|
519
1182
|
".prettierrc.json",
|
|
@@ -537,48 +1200,6 @@ function formatTargets() {
|
|
|
537
1200
|
};
|
|
538
1201
|
}
|
|
539
1202
|
|
|
540
|
-
// src/core/config/tool-script.ts
|
|
541
|
-
var SEGMENT_SEPARATOR = /(\s*(?:&&|\|\||;|&|\|)\s*)/;
|
|
542
|
-
function isSeparatorAt(index) {
|
|
543
|
-
return index % 2 === 1;
|
|
544
|
-
}
|
|
545
|
-
function invokes(segment, binary) {
|
|
546
|
-
return new RegExp(String.raw`(^|[\s/])${binary}(\s|$)`).test(segment.trim());
|
|
547
|
-
}
|
|
548
|
-
function isSentinelSegment(segment, roleFlag) {
|
|
549
|
-
const runsSentinel = invokes(segment, "sentinel") || segment.includes("bin/sentinel.js");
|
|
550
|
-
return runsSentinel && new RegExp(String.raw`(^|\s)${roleFlag}(\s|$)`).test(segment);
|
|
551
|
-
}
|
|
552
|
-
function composeToolScript(existing, options) {
|
|
553
|
-
const { command } = options;
|
|
554
|
-
if (!existing || existing.trim() === "") return command;
|
|
555
|
-
const { roleFlag } = options;
|
|
556
|
-
const isReplaceable = (segment) => options.replaces(segment) || roleFlag !== void 0 && isSentinelSegment(segment, roleFlag);
|
|
557
|
-
const parts = existing.split(SEGMENT_SEPARATOR);
|
|
558
|
-
const commands = parts.filter((_, index) => !isSeparatorAt(index));
|
|
559
|
-
if (!commands.some(isReplaceable)) return existing;
|
|
560
|
-
let replacedOnce = false;
|
|
561
|
-
const rebuilt = parts.map((part, index) => {
|
|
562
|
-
if (isSeparatorAt(index) || !isReplaceable(part)) return part;
|
|
563
|
-
if (replacedOnce) return null;
|
|
564
|
-
replacedOnce = true;
|
|
565
|
-
return options.keepFix && /(^|\s)--fix(\s|$)/.test(part) ? `${command} --fix` : command;
|
|
566
|
-
});
|
|
567
|
-
const kept = [];
|
|
568
|
-
for (let index = 0; index < rebuilt.length; index += 1) {
|
|
569
|
-
const part = rebuilt[index];
|
|
570
|
-
if (part === null) {
|
|
571
|
-
if (kept.length > 0) kept.pop();
|
|
572
|
-
continue;
|
|
573
|
-
}
|
|
574
|
-
kept.push(part);
|
|
575
|
-
}
|
|
576
|
-
return kept.join("").trim();
|
|
577
|
-
}
|
|
578
|
-
function keepsOtherCommands(script, sentinelCommand) {
|
|
579
|
-
return script.split(SEGMENT_SEPARATOR).filter((_, index) => !isSeparatorAt(index)).some((segment) => segment.trim() !== "" && segment.trim() !== sentinelCommand);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
1203
|
// src/roles/format/format-script.ts
|
|
583
1204
|
var SENTINEL_FORMAT_COMMAND = "sentinel --run --format";
|
|
584
1205
|
function isPrettierSegment(segment) {
|
|
@@ -607,8 +1228,8 @@ function writesWhenRewritten(name, command) {
|
|
|
607
1228
|
var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
|
|
608
1229
|
|
|
609
1230
|
// src/roles/format/inherited-ignores.ts
|
|
610
|
-
import { existsSync as
|
|
611
|
-
import { join as
|
|
1231
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
1232
|
+
import { join as join10 } from "path";
|
|
612
1233
|
var ROOT_IGNORE_FILE = ".prettierignore";
|
|
613
1234
|
function isPattern(line) {
|
|
614
1235
|
const trimmed = line.trim();
|
|
@@ -623,11 +1244,11 @@ function toModulePattern(pattern) {
|
|
|
623
1244
|
}
|
|
624
1245
|
function inheritedIgnorePatterns(workspaceRoot) {
|
|
625
1246
|
if (!workspaceRoot) return [];
|
|
626
|
-
const path =
|
|
627
|
-
if (!
|
|
1247
|
+
const path = join10(workspaceRoot, ROOT_IGNORE_FILE);
|
|
1248
|
+
if (!existsSync9(path)) return [];
|
|
628
1249
|
let contents;
|
|
629
1250
|
try {
|
|
630
|
-
contents =
|
|
1251
|
+
contents = readFileSync7(path, "utf8");
|
|
631
1252
|
} catch {
|
|
632
1253
|
return [];
|
|
633
1254
|
}
|
|
@@ -714,13 +1335,13 @@ function formatPresetFor(preset) {
|
|
|
714
1335
|
}
|
|
715
1336
|
|
|
716
1337
|
// src/roles/format/prettier-config.ts
|
|
717
|
-
import { existsSync as
|
|
718
|
-
import { join as
|
|
1338
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
1339
|
+
import { join as join12 } from "path";
|
|
719
1340
|
|
|
720
1341
|
// src/roles/format/resolve-oxfmt.ts
|
|
721
|
-
import { readFileSync as
|
|
1342
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
722
1343
|
import { createRequire as createRequire2 } from "module";
|
|
723
|
-
import { dirname as dirname4, join as
|
|
1344
|
+
import { dirname as dirname4, join as join11 } from "path";
|
|
724
1345
|
function resolveOxfmt(cwd) {
|
|
725
1346
|
return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
|
|
726
1347
|
}
|
|
@@ -749,8 +1370,8 @@ function configSchema() {
|
|
|
749
1370
|
function readConfigSchema() {
|
|
750
1371
|
try {
|
|
751
1372
|
const manifest = createRequire2(import.meta.url).resolve("oxfmt/package.json");
|
|
752
|
-
const schemaPath =
|
|
753
|
-
return JSON.parse(
|
|
1373
|
+
const schemaPath = join11(dirname4(manifest), "configuration_schema.json");
|
|
1374
|
+
return JSON.parse(readFileSync8(schemaPath, "utf8"));
|
|
754
1375
|
} catch {
|
|
755
1376
|
return void 0;
|
|
756
1377
|
}
|
|
@@ -781,14 +1402,14 @@ function toOxfmtOverrides(value) {
|
|
|
781
1402
|
return { overrides, unresolved };
|
|
782
1403
|
}
|
|
783
1404
|
function readPrettierSettings(cwd) {
|
|
784
|
-
const file = PRETTIER_CONFIG_FILES.find((name) =>
|
|
1405
|
+
const file = PRETTIER_CONFIG_FILES.find((name) => existsSync10(join12(cwd, name)));
|
|
785
1406
|
if (!file) return { options: {}, unresolved: [] };
|
|
786
1407
|
if (/\.(js|cjs|mjs)$/.test(file)) {
|
|
787
1408
|
return { options: {}, file, unresolved: [`${file} is JavaScript, so it was not executed`] };
|
|
788
1409
|
}
|
|
789
1410
|
let parsed;
|
|
790
1411
|
try {
|
|
791
|
-
parsed = parseJsonc(
|
|
1412
|
+
parsed = parseJsonc(readFileSync9(join12(cwd, file), "utf8"), file);
|
|
792
1413
|
} catch {
|
|
793
1414
|
return { options: {}, file, unresolved: [`${file} could not be parsed`] };
|
|
794
1415
|
}
|
|
@@ -818,9 +1439,9 @@ function readPrettierSettings(cwd) {
|
|
|
818
1439
|
}
|
|
819
1440
|
|
|
820
1441
|
// src/roles/format/read-adoption.ts
|
|
821
|
-
import { existsSync as
|
|
822
|
-
import { join as
|
|
823
|
-
var
|
|
1442
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
1443
|
+
import { join as join13 } from "path";
|
|
1444
|
+
var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
|
|
824
1445
|
configFile,
|
|
825
1446
|
preset: null,
|
|
826
1447
|
adopted: false,
|
|
@@ -835,19 +1456,19 @@ function sameValue(a, b) {
|
|
|
835
1456
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
836
1457
|
}
|
|
837
1458
|
function readFormatAdoption(cwd) {
|
|
838
|
-
const path =
|
|
839
|
-
if (!
|
|
1459
|
+
const path = join13(cwd, FORMAT_CONFIG_FILE);
|
|
1460
|
+
if (!existsSync11(path)) return NOT_ADOPTED2(null);
|
|
840
1461
|
let parsed;
|
|
841
1462
|
try {
|
|
842
|
-
parsed = parseJsonc(
|
|
1463
|
+
parsed = parseJsonc(readFileSync10(path, "utf8"), FORMAT_CONFIG_FILE);
|
|
843
1464
|
} catch (error) {
|
|
844
1465
|
const reason = error instanceof Error ? error.message : String(error);
|
|
845
|
-
return
|
|
1466
|
+
return NOT_ADOPTED2(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
|
|
846
1467
|
}
|
|
847
1468
|
const provenance = parsed[PROVENANCE_KEY];
|
|
848
|
-
if (!provenance || typeof provenance.preset !== "string") return
|
|
1469
|
+
if (!provenance || typeof provenance.preset !== "string") return NOT_ADOPTED2(FORMAT_CONFIG_FILE);
|
|
849
1470
|
if (!hasFormatPreset(provenance.preset)) {
|
|
850
|
-
return
|
|
1471
|
+
return NOT_ADOPTED2(
|
|
851
1472
|
FORMAT_CONFIG_FILE,
|
|
852
1473
|
`${FORMAT_CONFIG_FILE} declares the format preset "${provenance.preset}", which sentinel does not ship (shipped: ${FORMAT_PRESETS.join(", ")}). Re-run \`sentinel --init --format\`.`
|
|
853
1474
|
);
|
|
@@ -926,7 +1547,9 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
926
1547
|
const localKeys = Object.keys(local).filter((key) => !ALWAYS_LOCAL_KEYS.includes(key)).sort();
|
|
927
1548
|
const inherited = inheritedIgnorePatterns(findWorkspaceRoot(context.cwd));
|
|
928
1549
|
const declaredIgnores = Array.isArray(local.ignorePatterns) ? local.ignorePatterns.filter((v) => typeof v === "string") : [];
|
|
929
|
-
const ignorePatterns = [
|
|
1550
|
+
const ignorePatterns = [
|
|
1551
|
+
.../* @__PURE__ */ new Set([...declaredIgnores, ...inherited, ...FORMAT_DEFAULT_IGNORES])
|
|
1552
|
+
].sort();
|
|
930
1553
|
if (ignorePatterns.length > 0) local.ignorePatterns = ignorePatterns;
|
|
931
1554
|
const unchanged = existing.adopted && existing.conformant;
|
|
932
1555
|
const version = unchanged && existing.presetVersion ? existing.presetVersion : own.version;
|
|
@@ -946,7 +1569,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
946
1569
|
manifestOperation(context.cwd, this.formatScripts(context.cwd))
|
|
947
1570
|
];
|
|
948
1571
|
const prettierConfigs = PRETTIER_CONFIG_FILES.filter(
|
|
949
|
-
(name) =>
|
|
1572
|
+
(name) => existsSync12(join14(context.cwd, name))
|
|
950
1573
|
);
|
|
951
1574
|
for (const name of prettierConfigs) operations.push({ kind: "delete", path: name });
|
|
952
1575
|
const removableDeps = this.modulePrettierDependencies(context.cwd);
|
|
@@ -1005,7 +1628,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1005
1628
|
async afterInit(ctx) {
|
|
1006
1629
|
const oxfmt = resolveOxfmt(ctx.cwd);
|
|
1007
1630
|
if (!oxfmt) return;
|
|
1008
|
-
const pass =
|
|
1631
|
+
const pass = spawnSync4(oxfmt, ["--write", "."], { cwd: ctx.cwd, encoding: "utf8" });
|
|
1009
1632
|
process.stderr.write(` ${palette(process.stderr).dim(FORMATTER_DIFFERENCES)}
|
|
1010
1633
|
`);
|
|
1011
1634
|
if (pass.status === 0) return;
|
|
@@ -1068,7 +1691,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1068
1691
|
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
1069
1692
|
valueFlags: OXFMT_VALUE_FLAGS
|
|
1070
1693
|
});
|
|
1071
|
-
const result =
|
|
1694
|
+
const result = spawnSync4(oxfmt, [mode, ...options, ...paths.length > 0 ? paths : ["."]], {
|
|
1072
1695
|
cwd: ctx.cwd,
|
|
1073
1696
|
stdio: "inherit"
|
|
1074
1697
|
});
|
|
@@ -1146,7 +1769,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1146
1769
|
);
|
|
1147
1770
|
return { ok: false, code: 1, metrics: { ...base, unformatted: null } };
|
|
1148
1771
|
}
|
|
1149
|
-
const result =
|
|
1772
|
+
const result = spawnSync4(oxfmt, ["--list-different", "."], { cwd: ctx.cwd, encoding: "utf8" });
|
|
1150
1773
|
if (result.error) {
|
|
1151
1774
|
process.stderr.write(
|
|
1152
1775
|
`sentinel format(oxfmt): could not run oxfmt (${result.error.message})
|
|
@@ -1182,7 +1805,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1182
1805
|
modulePrettierDependencies(cwd) {
|
|
1183
1806
|
const isPrettierPackage = (name) => name === "prettier" || name.startsWith("prettier-plugin-") || name.startsWith("@prettier/");
|
|
1184
1807
|
try {
|
|
1185
|
-
const manifest = JSON.parse(
|
|
1808
|
+
const manifest = JSON.parse(readFileSync11(join14(cwd, "package.json"), "utf8"));
|
|
1186
1809
|
return Object.keys(manifest.devDependencies ?? {}).filter(isPrettierPackage).map((name) => ["devDependencies", name]);
|
|
1187
1810
|
} catch {
|
|
1188
1811
|
return [];
|
|
@@ -1263,9 +1886,9 @@ function registerFormat() {
|
|
|
1263
1886
|
}
|
|
1264
1887
|
|
|
1265
1888
|
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
1266
|
-
import { spawnSync as
|
|
1267
|
-
import { existsSync as
|
|
1268
|
-
import { join as
|
|
1889
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
1890
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1891
|
+
import { join as join22 } from "path";
|
|
1269
1892
|
|
|
1270
1893
|
// src/core/config/deferred-rules.ts
|
|
1271
1894
|
function deferredRuleNames(rules) {
|
|
@@ -1275,10 +1898,18 @@ function deferredRuleNames(rules) {
|
|
|
1275
1898
|
// src/roles/lint/config-policy.ts
|
|
1276
1899
|
var LINT_CONFIG_FILE = ".oxlintrc.json";
|
|
1277
1900
|
var LINT_SCRIPT_NAME = "lint";
|
|
1278
|
-
var PRESET_DIR = "./node_modules/@hublo/sentinel/
|
|
1901
|
+
var PRESET_DIR = "./node_modules/@hublo/sentinel/lint";
|
|
1279
1902
|
function presetPath(preset) {
|
|
1280
1903
|
return `${PRESET_DIR}/${preset}.json`;
|
|
1281
1904
|
}
|
|
1905
|
+
function isSentinelPreset(entry) {
|
|
1906
|
+
return /@hublo\/sentinel\/(lint|oxlint)\//.test(entry);
|
|
1907
|
+
}
|
|
1908
|
+
function extendsWithPreset(current, variant) {
|
|
1909
|
+
const own = presetPath(variant);
|
|
1910
|
+
const others = current.filter((entry) => entry !== own && !isSentinelPreset(entry));
|
|
1911
|
+
return [own, ...new Set(others)];
|
|
1912
|
+
}
|
|
1282
1913
|
function presetVariant(preset, cwd) {
|
|
1283
1914
|
const normalised = cwd.replaceAll("\\", "/");
|
|
1284
1915
|
return preset === "react" && /(^|\/)libs\//.test(normalised) ? "react-lib" : preset;
|
|
@@ -1286,7 +1917,7 @@ function presetVariant(preset, cwd) {
|
|
|
1286
1917
|
function presetOfVariant(variant) {
|
|
1287
1918
|
return variant === "react-lib" ? "react" : variant;
|
|
1288
1919
|
}
|
|
1289
|
-
var SENTINEL_LINT_PRESET = /@hublo\/sentinel\/oxlint\/([a-z-]+)\.json$/;
|
|
1920
|
+
var SENTINEL_LINT_PRESET = /@hublo\/sentinel\/(?:lint|oxlint)\/([a-z-]+)\.json$/;
|
|
1290
1921
|
function presetNameFromPath(preset) {
|
|
1291
1922
|
return preset ? SENTINEL_LINT_PRESET.exec(preset)?.[1] ?? void 0 : void 0;
|
|
1292
1923
|
}
|
|
@@ -1298,7 +1929,7 @@ var ESLINT_CONFIG_FILES = [
|
|
|
1298
1929
|
"eslint.config.ts"
|
|
1299
1930
|
];
|
|
1300
1931
|
function lintTarget() {
|
|
1301
|
-
return { cache: true, inputs: ["default", `{projectRoot}/${LINT_CONFIG_FILE}`] };
|
|
1932
|
+
return { cache: true, inputs: ["default", "^default", `{projectRoot}/${LINT_CONFIG_FILE}`] };
|
|
1302
1933
|
}
|
|
1303
1934
|
|
|
1304
1935
|
// src/roles/lint/presets/base.json
|
|
@@ -1642,8 +2273,8 @@ var nest_default = {
|
|
|
1642
2273
|
},
|
|
1643
2274
|
"typescript/return-await": {
|
|
1644
2275
|
severity: "warn",
|
|
1645
|
-
reason: "replaces the deprecated core `no-return-await`, which both modules set to error but which was never actually reporting: the nx lint target expanded to 2 files of 2888.
|
|
1646
|
-
options: ["
|
|
2276
|
+
reason: "replaces the deprecated core `no-return-await`, which both modules set to error but which was never actually reporting: the nx lint target expanded to 2 files of 2888. `in-try-catch` rather than `never`, and the planning adoption is what settled it. `never` forces `return await f()` to become `const x = await f(); return x` even inside a `try`, where the await is REQUIRED for the catch to fire: it produced 10 such edits across 15 modules, every one of them inside a try/catch, and every one of them a rewrite that made the code longer without making it safer. Worse, the obvious reading of the rule is to delete the `await`, which silently stops the catch from running. `in-try-catch` is what typescript-eslint recommends and flags none of those 10. Held at warn so adoption does not turn a green module red, and raised to error once the remaining findings are fixed.",
|
|
2277
|
+
options: ["in-try-catch"]
|
|
1647
2278
|
},
|
|
1648
2279
|
"typescript/switch-exhaustiveness-check": [
|
|
1649
2280
|
"error",
|
|
@@ -2034,8 +2665,8 @@ var react_lib_default = {
|
|
|
2034
2665
|
},
|
|
2035
2666
|
"typescript/return-await": {
|
|
2036
2667
|
severity: "warn",
|
|
2037
|
-
reason: "replaces the deprecated core `no-return-await
|
|
2038
|
-
options: ["
|
|
2668
|
+
reason: "replaces the deprecated core `no-return-await`, which both modules set to error but which was never actually reporting: the nx lint target expanded to 2 files of 2888. `in-try-catch` rather than `never`, and the planning adoption is what settled it. `never` forces `return await f()` to become `const x = await f(); return x` even inside a `try`, where the await is REQUIRED for the catch to fire: it produced 10 such edits across 15 modules, every one of them inside a try/catch, and every one of them a rewrite that made the code longer without making it safer. Worse, the obvious reading of the rule is to delete the `await`, which silently stops the catch from running. `in-try-catch` is what typescript-eslint recommends and flags none of those 10. Held at warn so adoption does not turn a green module red, and raised to error once the remaining findings are fixed.",
|
|
2669
|
+
options: ["in-try-catch"]
|
|
2039
2670
|
},
|
|
2040
2671
|
"use-isnan": [
|
|
2041
2672
|
"error",
|
|
@@ -2608,8 +3239,8 @@ var react_default = {
|
|
|
2608
3239
|
"typescript/restrict-template-expressions": "error",
|
|
2609
3240
|
"typescript/return-await": {
|
|
2610
3241
|
severity: "warn",
|
|
2611
|
-
reason: "replaces the deprecated core `no-return-await`, which both modules set to error but which was never actually reporting: the nx lint target expanded to 2 files of 2888.
|
|
2612
|
-
options: ["
|
|
3242
|
+
reason: "replaces the deprecated core `no-return-await`, which both modules set to error but which was never actually reporting: the nx lint target expanded to 2 files of 2888. `in-try-catch` rather than `never`, and the planning adoption is what settled it. `never` forces `return await f()` to become `const x = await f(); return x` even inside a `try`, where the await is REQUIRED for the catch to fire: it produced 10 such edits across 15 modules, every one of them inside a try/catch, and every one of them a rewrite that made the code longer without making it safer. Worse, the obvious reading of the rule is to delete the `await`, which silently stops the catch from running. `in-try-catch` is what typescript-eslint recommends and flags none of those 10. Held at warn so adoption does not turn a green module red, and raised to error once the remaining findings are fixed.",
|
|
3243
|
+
options: ["in-try-catch"]
|
|
2613
3244
|
},
|
|
2614
3245
|
"typescript/switch-exhaustiveness-check": {
|
|
2615
3246
|
severity: "warn",
|
|
@@ -3070,6 +3701,10 @@ function downgradedRulesFor(preset) {
|
|
|
3070
3701
|
return downgradedFor(preset);
|
|
3071
3702
|
}
|
|
3072
3703
|
|
|
3704
|
+
// src/roles/lint/extra-layers.ts
|
|
3705
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
|
|
3706
|
+
import { isAbsolute as isAbsolute2, join as join15, resolve as resolve3 } from "path";
|
|
3707
|
+
|
|
3073
3708
|
// src/roles/lint/module-baseline.ts
|
|
3074
3709
|
var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
|
|
3075
3710
|
var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
|
|
@@ -3078,10 +3713,7 @@ function holdsFrom(errorCounts) {
|
|
|
3078
3713
|
return [...errorCounts].map(([rule, count]) => ({ rule, count })).sort((left, right) => right.count - left.count || left.rule.localeCompare(right.rule));
|
|
3079
3714
|
}
|
|
3080
3715
|
function renderBaseline(holds, version) {
|
|
3081
|
-
const entries = holds.map(
|
|
3082
|
-
({ rule, count }) => ` // ${count} violation${count === 1 ? "" : "s"} when this module adopted
|
|
3083
|
-
${JSON.stringify(rule)}: "warn"`
|
|
3084
|
-
).join(",\n");
|
|
3716
|
+
const entries = holds.map(({ rule }) => ` ${JSON.stringify(rule)}: "warn"`).join(",\n");
|
|
3085
3717
|
return [
|
|
3086
3718
|
"{",
|
|
3087
3719
|
` // Written by @hublo/sentinel@${version}. Do not edit by hand: \`sentinel --init --lint\``,
|
|
@@ -3089,9 +3721,15 @@ function renderBaseline(holds, version) {
|
|
|
3089
3721
|
" // and returns to `error` on its own.",
|
|
3090
3722
|
" //",
|
|
3091
3723
|
" // These rules are held at `warn` because this module ALREADY violated them when it",
|
|
3092
|
-
" // adopted. They still run and still report
|
|
3093
|
-
" //
|
|
3094
|
-
" //
|
|
3724
|
+
" // adopted. They still run and still report, so the build cannot fail on them.",
|
|
3725
|
+
" //",
|
|
3726
|
+
" // The hold is per RULE, not per occurrence: oxlint cannot freeze a specific list of",
|
|
3727
|
+
" // violations, so a NEW violation of one of these rules is covered too, and will only",
|
|
3728
|
+
" // warn. That is the cost of adopting without a red build, and the reason to clear this",
|
|
3729
|
+
" // file rather than live with it.",
|
|
3730
|
+
" //",
|
|
3731
|
+
" // `sentinel --inspect --lint` counts what is left. Fix them and re-run `--init --lint`",
|
|
3732
|
+
" // to get the preset severity back.",
|
|
3095
3733
|
' "rules": {',
|
|
3096
3734
|
entries,
|
|
3097
3735
|
" }",
|
|
@@ -3111,6 +3749,41 @@ function describeHolds(holds) {
|
|
|
3111
3749
|
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`;
|
|
3112
3750
|
}
|
|
3113
3751
|
|
|
3752
|
+
// src/roles/lint/extra-layers.ts
|
|
3753
|
+
function ruleCount(cwd, specifier) {
|
|
3754
|
+
const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
|
|
3755
|
+
if (!existsSync13(path)) return void 0;
|
|
3756
|
+
try {
|
|
3757
|
+
const parsed = parseJsonc(
|
|
3758
|
+
readFileSync12(path, "utf8"),
|
|
3759
|
+
specifier
|
|
3760
|
+
);
|
|
3761
|
+
return Object.keys(parsed.rules ?? {}).length;
|
|
3762
|
+
} catch {
|
|
3763
|
+
return void 0;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
function extraLayers(cwd, extendsList) {
|
|
3767
|
+
return extendsList.filter((entry) => !isSentinelPreset(entry) && entry !== LINT_BASELINE_SPECIFIER).map((entry) => {
|
|
3768
|
+
const count = ruleCount(cwd, entry);
|
|
3769
|
+
return {
|
|
3770
|
+
rule: entry,
|
|
3771
|
+
reason: count === void 0 ? "this module extends it, but it could not be read from here" : `${count} rule(s) on top of the preset, applied after it`
|
|
3772
|
+
};
|
|
3773
|
+
});
|
|
3774
|
+
}
|
|
3775
|
+
function committedExtendsList(cwd, configFile) {
|
|
3776
|
+
try {
|
|
3777
|
+
const parsed = parseJsonc(
|
|
3778
|
+
readFileSync12(join15(cwd, configFile), "utf8"),
|
|
3779
|
+
configFile
|
|
3780
|
+
);
|
|
3781
|
+
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
3782
|
+
} catch {
|
|
3783
|
+
return [];
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3114
3787
|
// src/roles/lint/parse-diagnostics.ts
|
|
3115
3788
|
function ruleId(code) {
|
|
3116
3789
|
const inner = /\(([^)]+)\)/.exec(code);
|
|
@@ -3161,8 +3834,8 @@ function errorCountsByConfigRule(stdout) {
|
|
|
3161
3834
|
}
|
|
3162
3835
|
|
|
3163
3836
|
// src/core/config/read-adoption.ts
|
|
3164
|
-
import { existsSync as
|
|
3165
|
-
import { join as
|
|
3837
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
|
|
3838
|
+
import { join as join17 } from "path";
|
|
3166
3839
|
|
|
3167
3840
|
// src/core/config/owned-keys.ts
|
|
3168
3841
|
function presetOwnedKeys(config, permitted, presetSets) {
|
|
@@ -3177,12 +3850,12 @@ function localOnlyKeys(config, permitted, presetSets) {
|
|
|
3177
3850
|
}
|
|
3178
3851
|
|
|
3179
3852
|
// src/core/config/resolve-config-target.ts
|
|
3180
|
-
import { existsSync as
|
|
3181
|
-
import { join as
|
|
3853
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
|
|
3854
|
+
import { join as join16 } from "path";
|
|
3182
3855
|
function readExtends(absolutePath) {
|
|
3183
3856
|
let parsed;
|
|
3184
3857
|
try {
|
|
3185
|
-
parsed = parseJsonc(
|
|
3858
|
+
parsed = parseJsonc(readFileSync13(absolutePath, "utf8"), absolutePath);
|
|
3186
3859
|
} catch {
|
|
3187
3860
|
return [];
|
|
3188
3861
|
}
|
|
@@ -3196,8 +3869,8 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
|
|
|
3196
3869
|
let existing;
|
|
3197
3870
|
let existingExtendsSomething = false;
|
|
3198
3871
|
for (const candidate of candidates) {
|
|
3199
|
-
const absolutePath =
|
|
3200
|
-
if (!
|
|
3872
|
+
const absolutePath = join16(moduleDir, candidate);
|
|
3873
|
+
if (!existsSync14(absolutePath)) continue;
|
|
3201
3874
|
const chain = readExtends(absolutePath);
|
|
3202
3875
|
if (existing === void 0) {
|
|
3203
3876
|
existing = candidate;
|
|
@@ -3220,7 +3893,7 @@ function normaliseExtends(value) {
|
|
|
3220
3893
|
}
|
|
3221
3894
|
return [];
|
|
3222
3895
|
}
|
|
3223
|
-
var
|
|
3896
|
+
var NOT_ADOPTED3 = (configFile, unreadable = null) => ({
|
|
3224
3897
|
configFile,
|
|
3225
3898
|
preset: null,
|
|
3226
3899
|
adopted: false,
|
|
@@ -3230,18 +3903,18 @@ var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
|
|
|
3230
3903
|
});
|
|
3231
3904
|
function readAdoption(cwd, options) {
|
|
3232
3905
|
const target = resolveConfigTarget(cwd, options);
|
|
3233
|
-
if (target.reason === "none" || !
|
|
3234
|
-
return
|
|
3906
|
+
if (target.reason === "none" || !existsSync15(join17(cwd, target.path))) {
|
|
3907
|
+
return NOT_ADOPTED3(target.reason === "none" ? null : target.path);
|
|
3235
3908
|
}
|
|
3236
3909
|
let parsed;
|
|
3237
3910
|
try {
|
|
3238
|
-
parsed = parseJsonc(
|
|
3911
|
+
parsed = parseJsonc(readFileSync14(join17(cwd, target.path), "utf8"), target.path);
|
|
3239
3912
|
} catch (error) {
|
|
3240
3913
|
const reason = error instanceof Error ? error.message : String(error);
|
|
3241
|
-
return
|
|
3914
|
+
return NOT_ADOPTED3(target.path, `${target.path} could not be parsed (${reason})`);
|
|
3242
3915
|
}
|
|
3243
3916
|
const preset = normaliseExtends(parsed.extends).find((entry) => options.presetPattern.test(entry)) ?? null;
|
|
3244
|
-
if (preset === null) return
|
|
3917
|
+
if (preset === null) return NOT_ADOPTED3(target.path);
|
|
3245
3918
|
const settings = options.settingsKey === null ? parsed : parsed[options.settingsKey];
|
|
3246
3919
|
const drift = presetOwnedKeys(settings, options.permitted, options.presetOwns?.(preset));
|
|
3247
3920
|
return {
|
|
@@ -3269,9 +3942,9 @@ function readLintAdoption(cwd) {
|
|
|
3269
3942
|
}
|
|
3270
3943
|
|
|
3271
3944
|
// src/roles/lint/resolve-oxlint.ts
|
|
3272
|
-
import { existsSync as
|
|
3945
|
+
import { existsSync as existsSync16 } from "fs";
|
|
3273
3946
|
import { createRequire as createRequire3 } from "module";
|
|
3274
|
-
import { delimiter as delimiter2, dirname as dirname5, join as
|
|
3947
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join18 } from "path";
|
|
3275
3948
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3276
3949
|
var PACKAGE_OF = {
|
|
3277
3950
|
oxlint: "oxlint",
|
|
@@ -3296,30 +3969,30 @@ function tsgolintShim(cwd) {
|
|
|
3296
3969
|
for (const owner of ["oxlint-tsgolint", "oxlint"]) {
|
|
3297
3970
|
try {
|
|
3298
3971
|
const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
|
|
3299
|
-
candidates.push(
|
|
3300
|
-
candidates.push(
|
|
3972
|
+
candidates.push(join18(packageDir, "node_modules", ".bin", "tsgolint"));
|
|
3973
|
+
candidates.push(join18(packageDir, "..", ".bin", "tsgolint"));
|
|
3301
3974
|
} catch {
|
|
3302
3975
|
}
|
|
3303
3976
|
}
|
|
3304
3977
|
try {
|
|
3305
3978
|
const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
|
|
3306
|
-
candidates.push(
|
|
3979
|
+
candidates.push(join18(ownRoot, "node_modules", ".bin", "tsgolint"));
|
|
3307
3980
|
} catch {
|
|
3308
3981
|
candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
|
|
3309
3982
|
}
|
|
3310
|
-
return candidates.find((candidate) => candidate !== "" &&
|
|
3983
|
+
return candidates.find((candidate) => candidate !== "" && existsSync16(candidate));
|
|
3311
3984
|
}
|
|
3312
3985
|
function oxlintSearchPath(cwd) {
|
|
3313
3986
|
return binSearchPath(cwd);
|
|
3314
3987
|
}
|
|
3315
3988
|
|
|
3316
3989
|
// src/roles/lint/adapters/oxlint/plan.ts
|
|
3317
|
-
import { existsSync as
|
|
3318
|
-
import { join as
|
|
3990
|
+
import { existsSync as existsSync18, readFileSync as readFileSync17 } from "fs";
|
|
3991
|
+
import { join as join21 } from "path";
|
|
3319
3992
|
|
|
3320
3993
|
// src/roles/lint/eslint-ignores.ts
|
|
3321
|
-
import { existsSync as
|
|
3322
|
-
import { join as
|
|
3994
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
|
|
3995
|
+
import { join as join19 } from "path";
|
|
3323
3996
|
var IGNORE_BLOCKS = [/\bignores\s*:\s*\[([^\]]*)\]/g, /\bglobalIgnores\s*\(\s*\[([^\]]*)\]/g];
|
|
3324
3997
|
var STRING_LITERAL = /['"`]([^'"`]+)['"`]/g;
|
|
3325
3998
|
var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
|
|
@@ -3332,11 +4005,11 @@ function readRootEslintIgnores(root) {
|
|
|
3332
4005
|
return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
|
|
3333
4006
|
}
|
|
3334
4007
|
function readEslintIgnores(cwd) {
|
|
3335
|
-
const config = ESLINT_CONFIG_FILES.map((name) =>
|
|
4008
|
+
const config = ESLINT_CONFIG_FILES.map((name) => join19(cwd, name)).find((path) => existsSync17(path));
|
|
3336
4009
|
if (!config) return { patterns: [], unresolved: [] };
|
|
3337
4010
|
let source;
|
|
3338
4011
|
try {
|
|
3339
|
-
source =
|
|
4012
|
+
source = readFileSync15(config, "utf8");
|
|
3340
4013
|
} catch {
|
|
3341
4014
|
return { patterns: [], unresolved: [] };
|
|
3342
4015
|
}
|
|
@@ -3390,8 +4063,8 @@ function lintPresetFor(preset) {
|
|
|
3390
4063
|
}
|
|
3391
4064
|
|
|
3392
4065
|
// src/roles/lint/rename-suppressions.ts
|
|
3393
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
3394
|
-
import { join as
|
|
4066
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync16, statSync } from "fs";
|
|
4067
|
+
import { join as join20, relative as relative2 } from "path";
|
|
3395
4068
|
var SOURCE_EXTENSIONS = [
|
|
3396
4069
|
".ts",
|
|
3397
4070
|
".tsx",
|
|
@@ -3438,7 +4111,7 @@ function* sourceFiles(dir) {
|
|
|
3438
4111
|
return;
|
|
3439
4112
|
}
|
|
3440
4113
|
for (const entry of entries) {
|
|
3441
|
-
const full =
|
|
4114
|
+
const full = join20(dir, entry);
|
|
3442
4115
|
let isDirectory;
|
|
3443
4116
|
try {
|
|
3444
4117
|
isDirectory = statSync(full).isDirectory();
|
|
@@ -3458,7 +4131,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
3458
4131
|
for (const file of sourceFiles(cwd)) {
|
|
3459
4132
|
let content;
|
|
3460
4133
|
try {
|
|
3461
|
-
content =
|
|
4134
|
+
content = readFileSync16(file, "utf8");
|
|
3462
4135
|
} catch {
|
|
3463
4136
|
continue;
|
|
3464
4137
|
}
|
|
@@ -3498,7 +4171,17 @@ var DEFAULT_IGNORE_PATTERNS = [
|
|
|
3498
4171
|
"**/dist/**",
|
|
3499
4172
|
"**/node_modules/**",
|
|
3500
4173
|
"**/coverage/**",
|
|
3501
|
-
|
|
4174
|
+
// `**/*.mock.ts` used to sit here and has been removed. It was the only entry in this list
|
|
4175
|
+
// with no reason written next to it, and it does not meet the bar the paragraph above sets:
|
|
4176
|
+
// a mock is not build output, not vendored and not generated. It is hand-written TypeScript
|
|
4177
|
+
// that ships with the tests, and lint rules have as much to say about it as about any other
|
|
4178
|
+
// source file. Ignoring it quietly narrowed what an adopted module checks — career lost
|
|
4179
|
+
// `src/test/router.mock.ts`, which its previous ESLint config did lint.
|
|
4180
|
+
//
|
|
4181
|
+
// Measured before removing, because this is the one change that can turn a green module red:
|
|
4182
|
+
// career has 1 such file and it reports nothing; the 15 planning modules have none. 222 exist
|
|
4183
|
+
// repo-wide, and modules adopting later meet them through the baseline, which is exactly what
|
|
4184
|
+
// the baseline is for.
|
|
3502
4185
|
// Tool configuration is not source. A repo's shared ESLint config ignores these, and every
|
|
3503
4186
|
// module inherits that; adoption replaces the config chain, so without them the first thing
|
|
3504
4187
|
// an adopted module lints is its own jest setup file. Found on libs/front/components, where
|
|
@@ -3527,7 +4210,7 @@ function summariseByPlugin(rules) {
|
|
|
3527
4210
|
}
|
|
3528
4211
|
return [...counts.entries()].sort((left, right) => right[1] - left[1]).map(([plugin, count]) => `${count} ${plugin}`).join(", ");
|
|
3529
4212
|
}
|
|
3530
|
-
function
|
|
4213
|
+
function plan2(context) {
|
|
3531
4214
|
if (!hasLintPreset(context.preset)) {
|
|
3532
4215
|
return {
|
|
3533
4216
|
operations: [],
|
|
@@ -3561,7 +4244,7 @@ function plan(context) {
|
|
|
3561
4244
|
].filter((pattern) => !defaults.has(bare(pattern)));
|
|
3562
4245
|
const carried = [.../* @__PURE__ */ new Set([...DEFAULT_IGNORE_PATTERNS, ...moduleOwn])];
|
|
3563
4246
|
const stub = {
|
|
3564
|
-
extends:
|
|
4247
|
+
extends: extendsWithPreset(committedExtends(context.cwd), variant),
|
|
3565
4248
|
...carried.length > 0 ? { ignorePatterns: carried } : {}
|
|
3566
4249
|
};
|
|
3567
4250
|
const operations = [
|
|
@@ -3576,7 +4259,7 @@ function plan(context) {
|
|
|
3576
4259
|
keys: removableDeps
|
|
3577
4260
|
});
|
|
3578
4261
|
}
|
|
3579
|
-
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) =>
|
|
4262
|
+
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync18(join21(context.cwd, name)));
|
|
3580
4263
|
for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
|
|
3581
4264
|
operations.push(
|
|
3582
4265
|
...nxTargetOperations({
|
|
@@ -3671,10 +4354,21 @@ function lintScripts(cwd) {
|
|
|
3671
4354
|
}
|
|
3672
4355
|
return rewritten;
|
|
3673
4356
|
}
|
|
4357
|
+
function committedExtends(cwd) {
|
|
4358
|
+
try {
|
|
4359
|
+
const parsed = parseJsonc(
|
|
4360
|
+
readFileSync17(join21(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4361
|
+
LINT_CONFIG_FILE
|
|
4362
|
+
);
|
|
4363
|
+
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
4364
|
+
} catch {
|
|
4365
|
+
return [];
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
3674
4368
|
function committedIgnorePatterns(cwd) {
|
|
3675
4369
|
try {
|
|
3676
4370
|
const parsed = parseJsonc(
|
|
3677
|
-
|
|
4371
|
+
readFileSync17(join21(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
3678
4372
|
LINT_CONFIG_FILE
|
|
3679
4373
|
);
|
|
3680
4374
|
return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
|
|
@@ -3686,7 +4380,7 @@ function moduleEslintDependencies(cwd) {
|
|
|
3686
4380
|
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/");
|
|
3687
4381
|
let manifest;
|
|
3688
4382
|
try {
|
|
3689
|
-
manifest = JSON.parse(
|
|
4383
|
+
manifest = JSON.parse(readFileSync17(join21(cwd, "package.json"), "utf8"));
|
|
3690
4384
|
} catch {
|
|
3691
4385
|
return [];
|
|
3692
4386
|
}
|
|
@@ -3745,7 +4439,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3745
4439
|
* lines. The adapter stays the contract with the engine.
|
|
3746
4440
|
*/
|
|
3747
4441
|
plan(context) {
|
|
3748
|
-
return
|
|
4442
|
+
return plan2(context);
|
|
3749
4443
|
}
|
|
3750
4444
|
declaredPreset(cwd) {
|
|
3751
4445
|
return presetOfVariant(presetNameFromPath(readLintAdoption(cwd).preset));
|
|
@@ -3755,7 +4449,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3755
4449
|
* verification run that follows is the one a developer reads.
|
|
3756
4450
|
*/
|
|
3757
4451
|
fixPass(ctx, oxlint, env) {
|
|
3758
|
-
const result =
|
|
4452
|
+
const result = spawnSync5(oxlint, ["-c", LINT_CONFIG_FILE, "--fix", "."], {
|
|
3759
4453
|
cwd: ctx.cwd,
|
|
3760
4454
|
encoding: "utf8",
|
|
3761
4455
|
env,
|
|
@@ -3805,20 +4499,20 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3805
4499
|
* build the developer can see, rather than a silent half-adoption they cannot.
|
|
3806
4500
|
*/
|
|
3807
4501
|
writeModuleBaseline(ctx, oxlint, env) {
|
|
3808
|
-
const configPath =
|
|
3809
|
-
const baselinePath =
|
|
3810
|
-
if (!
|
|
4502
|
+
const configPath = join22(ctx.cwd, LINT_CONFIG_FILE);
|
|
4503
|
+
const baselinePath = join22(ctx.cwd, LINT_BASELINE_FILE);
|
|
4504
|
+
if (!existsSync19(configPath)) return;
|
|
3811
4505
|
let config;
|
|
3812
4506
|
try {
|
|
3813
4507
|
config = parseJsonc(
|
|
3814
|
-
|
|
4508
|
+
readFileSync18(configPath, "utf8"),
|
|
3815
4509
|
LINT_CONFIG_FILE
|
|
3816
4510
|
);
|
|
3817
4511
|
} catch {
|
|
3818
4512
|
return;
|
|
3819
4513
|
}
|
|
3820
4514
|
const current = Array.isArray(config.extends) ? config.extends : [];
|
|
3821
|
-
const measurePath =
|
|
4515
|
+
const measurePath = join22(ctx.cwd, LINT_MEASURE_FILE);
|
|
3822
4516
|
let measured;
|
|
3823
4517
|
try {
|
|
3824
4518
|
writeFileSync2(
|
|
@@ -3826,7 +4520,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3826
4520
|
`${JSON.stringify({ ...config, extends: extendsWithBaseline(current, false) }, null, 2)}
|
|
3827
4521
|
`
|
|
3828
4522
|
);
|
|
3829
|
-
measured =
|
|
4523
|
+
measured = spawnSync5(
|
|
3830
4524
|
oxlint,
|
|
3831
4525
|
[
|
|
3832
4526
|
"-c",
|
|
@@ -3866,7 +4560,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3866
4560
|
}
|
|
3867
4561
|
}
|
|
3868
4562
|
async run(ctx) {
|
|
3869
|
-
if (!
|
|
4563
|
+
if (!existsSync19(join22(ctx.cwd, LINT_CONFIG_FILE))) {
|
|
3870
4564
|
process.stderr.write(
|
|
3871
4565
|
`sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
|
|
3872
4566
|
`
|
|
@@ -3913,7 +4607,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3913
4607
|
const targets = paths.length > 0 ? paths : ["."];
|
|
3914
4608
|
const lint = (extra) => {
|
|
3915
4609
|
const passed = [...extra, ...passedOptions];
|
|
3916
|
-
const result =
|
|
4610
|
+
const result = spawnSync5(oxlint, ["-c", LINT_CONFIG_FILE, ...passed, ...targets], {
|
|
3917
4611
|
cwd: ctx.cwd,
|
|
3918
4612
|
stdio: "inherit",
|
|
3919
4613
|
env
|
|
@@ -3950,7 +4644,12 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3950
4644
|
downgraded: downgradedRulesFor(presetVariant(ctx.preset, ctx.cwd)).map((entry) => ({
|
|
3951
4645
|
rule: entry.rule,
|
|
3952
4646
|
reason: entry.reason
|
|
3953
|
-
}))
|
|
4647
|
+
})),
|
|
4648
|
+
// What this module enforces BEYOND the preset. Not drift, and not covered by anything
|
|
4649
|
+
// else here: the stub still holds only `extends` and `ignorePatterns`, so a module with
|
|
4650
|
+
// a team layer reports `conformant=true drift=[]` and used to say nothing at all about
|
|
4651
|
+
// the extra rules it runs. See `extra-layers` for why that state is legitimate.
|
|
4652
|
+
layers: extraLayers(ctx.cwd, committedExtendsList(ctx.cwd, LINT_CONFIG_FILE))
|
|
3954
4653
|
};
|
|
3955
4654
|
}
|
|
3956
4655
|
/** How many rules the extended preset enforces, read from the preset on disk. */
|
|
@@ -3981,7 +4680,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3981
4680
|
let stub;
|
|
3982
4681
|
try {
|
|
3983
4682
|
stub = parseJsonc(
|
|
3984
|
-
|
|
4683
|
+
readFileSync18(join22(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
3985
4684
|
LINT_CONFIG_FILE
|
|
3986
4685
|
);
|
|
3987
4686
|
} catch {
|
|
@@ -3991,7 +4690,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3991
4690
|
for (const entry of stub.extends ?? []) {
|
|
3992
4691
|
try {
|
|
3993
4692
|
const preset = parseJsonc(
|
|
3994
|
-
|
|
4693
|
+
readFileSync18(join22(cwd, entry), "utf8"),
|
|
3995
4694
|
entry
|
|
3996
4695
|
);
|
|
3997
4696
|
for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
|
|
@@ -4008,7 +4707,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4008
4707
|
const oxlint = resolveOxlint(ctx.cwd);
|
|
4009
4708
|
if (!oxlint) return { ok: false, code: 1, metrics: { error: "oxlint not found" } };
|
|
4010
4709
|
const typeAware = canRunTypeAware(ctx.cwd) ? ["--type-aware"] : [];
|
|
4011
|
-
const result =
|
|
4710
|
+
const result = spawnSync5(oxlint, ["-c", LINT_CONFIG_FILE, ...typeAware, "-f", "json", "."], {
|
|
4012
4711
|
cwd: ctx.cwd,
|
|
4013
4712
|
encoding: "utf8",
|
|
4014
4713
|
env: { ...process.env, PATH: oxlintPath(ctx.cwd, process.env) },
|
|
@@ -4061,14 +4760,14 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4061
4760
|
let parsed;
|
|
4062
4761
|
try {
|
|
4063
4762
|
parsed = parseJsonc(
|
|
4064
|
-
|
|
4763
|
+
readFileSync18(join22(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4065
4764
|
LINT_CONFIG_FILE
|
|
4066
4765
|
);
|
|
4067
4766
|
} catch {
|
|
4068
4767
|
return void 0;
|
|
4069
4768
|
}
|
|
4070
4769
|
const targets = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
|
|
4071
|
-
return targets.find((target) => !
|
|
4770
|
+
return targets.find((target) => !existsSync19(join22(cwd, target)));
|
|
4072
4771
|
}
|
|
4073
4772
|
/** Announce what is not enforced, so reduced coverage is never silent. */
|
|
4074
4773
|
announceDisabled(preset) {
|
|
@@ -4099,10 +4798,10 @@ function registerLint() {
|
|
|
4099
4798
|
}
|
|
4100
4799
|
|
|
4101
4800
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
4102
|
-
import { spawnSync as
|
|
4103
|
-
import { existsSync as
|
|
4801
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
4802
|
+
import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
|
|
4104
4803
|
import { createRequire as createRequire4 } from "module";
|
|
4105
|
-
import { join as
|
|
4804
|
+
import { join as join24 } from "path";
|
|
4106
4805
|
|
|
4107
4806
|
// src/roles/typescript/presets/base.json
|
|
4108
4807
|
var base_default3 = {
|
|
@@ -4266,7 +4965,7 @@ function resolveTsconfigTarget(moduleDir) {
|
|
|
4266
4965
|
}
|
|
4267
4966
|
|
|
4268
4967
|
// src/roles/typescript/read-adoption.ts
|
|
4269
|
-
var SENTINEL_PRESET = /^@hublo\/sentinel\/tsconfig\/[a-z-]+$/;
|
|
4968
|
+
var SENTINEL_PRESET = /^@hublo\/sentinel\/(?:typescript|tsconfig)\/[a-z-]+$/;
|
|
4270
4969
|
function readTsconfigAdoption(cwd) {
|
|
4271
4970
|
return readAdoption(cwd, {
|
|
4272
4971
|
candidates: TSCONFIG_CANDIDATES,
|
|
@@ -4283,8 +4982,8 @@ function readTsconfigAdoption(cwd) {
|
|
|
4283
4982
|
}
|
|
4284
4983
|
|
|
4285
4984
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
4286
|
-
import { readFileSync as
|
|
4287
|
-
import { join as
|
|
4985
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
4986
|
+
import { join as join23 } from "path";
|
|
4288
4987
|
|
|
4289
4988
|
// src/roles/typescript/typecheck-script.ts
|
|
4290
4989
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
@@ -4306,7 +5005,10 @@ function keepsForeignChecker(existing) {
|
|
|
4306
5005
|
|
|
4307
5006
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
4308
5007
|
var INSTALL_NOTE = "run `pnpm install` to fetch @hublo/sentinel (added to the module devDependencies) so `extends` and the typecheck script resolve";
|
|
4309
|
-
var TYPECHECK_TARGET = {
|
|
5008
|
+
var TYPECHECK_TARGET = {
|
|
5009
|
+
cache: true,
|
|
5010
|
+
inputs: ["default", "^default", "{projectRoot}/tsconfig.json"]
|
|
5011
|
+
};
|
|
4310
5012
|
function typecheckScripts(cwd) {
|
|
4311
5013
|
return {
|
|
4312
5014
|
// Reads the nx target too, not only the npm script: five modules in this repo keep a
|
|
@@ -4315,9 +5017,11 @@ function typecheckScripts(cwd) {
|
|
|
4315
5017
|
[TYPECHECK_SCRIPT_NAME]: composeTypecheckScript(existingCommand(cwd, TYPECHECK_SCRIPT_NAME))
|
|
4316
5018
|
};
|
|
4317
5019
|
}
|
|
5020
|
+
var SENTINEL_TS_PRESET = /@hublo\/sentinel\/(?:typescript|tsconfig)\//;
|
|
4318
5021
|
function composeExtends(current, preset) {
|
|
4319
5022
|
const chain = typeof current === "string" ? [current] : Array.isArray(current) ? current.filter((entry) => typeof entry === "string") : [];
|
|
4320
|
-
|
|
5023
|
+
const withoutOwn = chain.filter((entry) => entry !== preset && !SENTINEL_TS_PRESET.test(entry));
|
|
5024
|
+
return [...withoutOwn, preset];
|
|
4321
5025
|
}
|
|
4322
5026
|
function declaredPreset(cwd) {
|
|
4323
5027
|
const { preset } = readTsconfigAdoption(cwd);
|
|
@@ -4346,7 +5050,7 @@ function planAdoption(context) {
|
|
|
4346
5050
|
};
|
|
4347
5051
|
}
|
|
4348
5052
|
const target = resolveTsconfigTarget(context.cwd);
|
|
4349
|
-
const preset = `@hublo/sentinel/
|
|
5053
|
+
const preset = `@hublo/sentinel/typescript/${context.preset}`;
|
|
4350
5054
|
const addScript = manifestOperation(context.cwd, typecheckScripts(context.cwd));
|
|
4351
5055
|
const nxTargets = nxTargetOperations({
|
|
4352
5056
|
cwd: context.cwd,
|
|
@@ -4372,7 +5076,7 @@ function planAdoption(context) {
|
|
|
4372
5076
|
};
|
|
4373
5077
|
}
|
|
4374
5078
|
const existing = parseJsonc(
|
|
4375
|
-
|
|
5079
|
+
readFileSync19(join23(context.cwd, target.path), "utf8"),
|
|
4376
5080
|
target.path
|
|
4377
5081
|
);
|
|
4378
5082
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
@@ -4410,7 +5114,7 @@ function planAdoption(context) {
|
|
|
4410
5114
|
|
|
4411
5115
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
4412
5116
|
var SENTINEL_PACKAGE = "@hublo/sentinel";
|
|
4413
|
-
var
|
|
5117
|
+
var SENTINEL_PRESET_SCOPES = [`${SENTINEL_PACKAGE}/typescript/`, `${SENTINEL_PACKAGE}/tsconfig/`];
|
|
4414
5118
|
var DIAGNOSTIC_RE = /^(.+?)\((\d+),(\d+)\): error (TS\d+): (.+)$/;
|
|
4415
5119
|
function parseDiagnostics(output) {
|
|
4416
5120
|
const diagnostics = [];
|
|
@@ -4510,7 +5214,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4510
5214
|
let chain;
|
|
4511
5215
|
try {
|
|
4512
5216
|
const parsed = parseJsonc(
|
|
4513
|
-
|
|
5217
|
+
readFileSync20(join24(cwd, target.path), "utf8"),
|
|
4514
5218
|
target.path
|
|
4515
5219
|
);
|
|
4516
5220
|
chain = parsed.extends;
|
|
@@ -4519,11 +5223,11 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4519
5223
|
}
|
|
4520
5224
|
const entries = typeof chain === "string" ? [chain] : Array.isArray(chain) ? chain : [];
|
|
4521
5225
|
const preset = entries.find(
|
|
4522
|
-
(entry) => typeof entry === "string" && entry.startsWith(
|
|
5226
|
+
(entry) => typeof entry === "string" && SENTINEL_PRESET_SCOPES.some((scope) => entry.startsWith(scope))
|
|
4523
5227
|
);
|
|
4524
5228
|
if (preset === void 0) return void 0;
|
|
4525
5229
|
try {
|
|
4526
|
-
createRequire4(
|
|
5230
|
+
createRequire4(join24(cwd, "noop.js")).resolve(preset);
|
|
4527
5231
|
return void 0;
|
|
4528
5232
|
} catch {
|
|
4529
5233
|
return preset;
|
|
@@ -4564,7 +5268,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4564
5268
|
return { ok: false, code: 1 };
|
|
4565
5269
|
}
|
|
4566
5270
|
if (options.length > 0) return this.runWithOptions(ctx, tsc, config);
|
|
4567
|
-
const result =
|
|
5271
|
+
const result = spawnSync6(tsc, ["-b", config], { cwd: ctx.cwd, stdio: "inherit" });
|
|
4568
5272
|
if (result.error) {
|
|
4569
5273
|
process.stderr.write(
|
|
4570
5274
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -4599,7 +5303,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4599
5303
|
let worst = 0;
|
|
4600
5304
|
for (const project of this.referencedProjects(ctx.cwd, config)) {
|
|
4601
5305
|
const args = ["-p", project, "--noEmit", "--composite", "false", ...ctx.toolArgs ?? []];
|
|
4602
|
-
const result =
|
|
5306
|
+
const result = spawnSync6(tsc, args, { cwd: ctx.cwd, stdio: "inherit" });
|
|
4603
5307
|
if (result.error) {
|
|
4604
5308
|
process.stderr.write(
|
|
4605
5309
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -4619,7 +5323,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4619
5323
|
referencedProjects(cwd, config) {
|
|
4620
5324
|
try {
|
|
4621
5325
|
const parsed = parseJsonc(
|
|
4622
|
-
|
|
5326
|
+
readFileSync20(join24(cwd, config), "utf8"),
|
|
4623
5327
|
config
|
|
4624
5328
|
);
|
|
4625
5329
|
const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
|
|
@@ -4635,7 +5339,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4635
5339
|
* check.
|
|
4636
5340
|
*/
|
|
4637
5341
|
typecheckTarget(cwd) {
|
|
4638
|
-
if (
|
|
5342
|
+
if (existsSync20(join24(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
4639
5343
|
const target = resolveTsconfigTarget(cwd);
|
|
4640
5344
|
return target.reason === "none" ? null : target.path;
|
|
4641
5345
|
}
|
|
@@ -4688,7 +5392,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4688
5392
|
return { ok: true, code: 0, metrics: _TscAdapter.NOTHING_TO_REPORT };
|
|
4689
5393
|
}
|
|
4690
5394
|
const tsc = resolveBin(ctx.cwd, "tsc") ?? "tsc";
|
|
4691
|
-
const result =
|
|
5395
|
+
const result = spawnSync6(tsc, ["-b", config], { cwd: ctx.cwd, encoding: "utf8" });
|
|
4692
5396
|
if (result.error) {
|
|
4693
5397
|
process.stderr.write(
|
|
4694
5398
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -4731,7 +5435,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4731
5435
|
* errors in the output mean the rule must be on.
|
|
4732
5436
|
*/
|
|
4733
5437
|
noImplicitAnyEnabled(cwd, tsc, config, mainOutput) {
|
|
4734
|
-
const shown =
|
|
5438
|
+
const shown = spawnSync6(tsc, ["-p", config, "--showConfig"], { cwd, encoding: "utf8" });
|
|
4735
5439
|
if (shown.status === 0 && shown.stdout) {
|
|
4736
5440
|
try {
|
|
4737
5441
|
const co = parseJsonc(
|
|
@@ -4757,6 +5461,7 @@ function registerAdapters() {
|
|
|
4757
5461
|
registerTypescript();
|
|
4758
5462
|
registerLint();
|
|
4759
5463
|
registerFormat();
|
|
5464
|
+
registerBuild();
|
|
4760
5465
|
}
|
|
4761
5466
|
|
|
4762
5467
|
// src/core/detect-framework.ts
|
|
@@ -4838,26 +5543,19 @@ function replaceLines(current, replacements) {
|
|
|
4838
5543
|
}
|
|
4839
5544
|
|
|
4840
5545
|
// src/core/apply-plan.ts
|
|
4841
|
-
import { existsSync as
|
|
4842
|
-
import { resolve as
|
|
5546
|
+
import { existsSync as existsSync21, readFileSync as readFileSync21, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
5547
|
+
import { resolve as resolve4, sep } from "path";
|
|
4843
5548
|
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
4844
|
-
|
|
4845
|
-
// src/shared/deep-merge.ts
|
|
4846
|
-
function isPlainObject(value) {
|
|
4847
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4848
|
-
}
|
|
4849
|
-
|
|
4850
|
-
// src/core/apply-plan.ts
|
|
4851
5549
|
function resolveWithinRoot(cwd, relativePath) {
|
|
4852
|
-
const root =
|
|
4853
|
-
const absolutePath =
|
|
5550
|
+
const root = resolve4(cwd);
|
|
5551
|
+
const absolutePath = resolve4(root, relativePath);
|
|
4854
5552
|
if (absolutePath !== root && !absolutePath.startsWith(root + sep)) {
|
|
4855
5553
|
throw new Error(`Refusing to write outside the module root: "${relativePath}".`);
|
|
4856
5554
|
}
|
|
4857
5555
|
return absolutePath;
|
|
4858
5556
|
}
|
|
4859
5557
|
function readIfExists(absolutePath) {
|
|
4860
|
-
return
|
|
5558
|
+
return existsSync21(absolutePath) ? readFileSync21(absolutePath, "utf8") : void 0;
|
|
4861
5559
|
}
|
|
4862
5560
|
function* leaves(value, prefix = []) {
|
|
4863
5561
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -4938,9 +5636,9 @@ function applyOperationTo(current, operation) {
|
|
|
4938
5636
|
}
|
|
4939
5637
|
}
|
|
4940
5638
|
}
|
|
4941
|
-
function preparePlan(cwd,
|
|
5639
|
+
function preparePlan(cwd, plan3) {
|
|
4942
5640
|
const prepared = /* @__PURE__ */ new Map();
|
|
4943
|
-
for (const operation of
|
|
5641
|
+
for (const operation of plan3.operations) {
|
|
4944
5642
|
const absolutePath = resolveWithinRoot(cwd, operation.path);
|
|
4945
5643
|
const existing = prepared.get(operation.path);
|
|
4946
5644
|
const before = existing?.before ?? readIfExists(absolutePath) ?? "";
|
|
@@ -4962,8 +5660,8 @@ function writeFileAtomic(absolutePath, contents) {
|
|
|
4962
5660
|
writeFileSync3(tempPath, contents);
|
|
4963
5661
|
renameSync(tempPath, absolutePath);
|
|
4964
5662
|
}
|
|
4965
|
-
function applyPlan(cwd,
|
|
4966
|
-
const changed = preparePlan(cwd,
|
|
5663
|
+
function applyPlan(cwd, plan3) {
|
|
5664
|
+
const changed = preparePlan(cwd, plan3).filter((file) => file.before !== file.after);
|
|
4967
5665
|
for (const file of changed) {
|
|
4968
5666
|
if (file.deleted) {
|
|
4969
5667
|
rmSync2(file.absolutePath, { force: true });
|
|
@@ -4975,8 +5673,8 @@ function applyPlan(cwd, plan2) {
|
|
|
4975
5673
|
}
|
|
4976
5674
|
|
|
4977
5675
|
// src/core/config/preset-evidence.ts
|
|
4978
|
-
import { existsSync as
|
|
4979
|
-
import { join as
|
|
5676
|
+
import { existsSync as existsSync22, readdirSync as readdirSync3, readFileSync as readFileSync22 } from "fs";
|
|
5677
|
+
import { join as join25 } from "path";
|
|
4980
5678
|
var PATH_SIGNALS = [
|
|
4981
5679
|
{
|
|
4982
5680
|
preset: "nest",
|
|
@@ -4990,11 +5688,11 @@ var DEPENDENCY_SIGNALS = [
|
|
|
4990
5688
|
{ preset: "nest", pattern: /^@nestjs\// }
|
|
4991
5689
|
];
|
|
4992
5690
|
function dependencyNames(cwd) {
|
|
4993
|
-
const path =
|
|
4994
|
-
if (!
|
|
5691
|
+
const path = join25(cwd, "package.json");
|
|
5692
|
+
if (!existsSync22(path)) return [];
|
|
4995
5693
|
try {
|
|
4996
5694
|
const manifest = parseJsonc(
|
|
4997
|
-
|
|
5695
|
+
readFileSync22(path, "utf8"),
|
|
4998
5696
|
path
|
|
4999
5697
|
);
|
|
5000
5698
|
return [
|
|
@@ -5017,7 +5715,7 @@ function declaresJsx(cwd) {
|
|
|
5017
5715
|
for (const name of entries) {
|
|
5018
5716
|
try {
|
|
5019
5717
|
const config = parseJsonc(
|
|
5020
|
-
|
|
5718
|
+
readFileSync22(join25(cwd, name), "utf8"),
|
|
5021
5719
|
name
|
|
5022
5720
|
);
|
|
5023
5721
|
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
@@ -5068,14 +5766,14 @@ function resolveFlavour(opts) {
|
|
|
5068
5766
|
}
|
|
5069
5767
|
return detection.preset;
|
|
5070
5768
|
}
|
|
5071
|
-
function previewPlan(opts,
|
|
5072
|
-
const changed = preparePlan(opts.cwd,
|
|
5769
|
+
function previewPlan(opts, plan3) {
|
|
5770
|
+
const changed = preparePlan(opts.cwd, plan3).filter((file) => file.before !== file.after);
|
|
5073
5771
|
if (opts.json) {
|
|
5074
5772
|
process.stdout.write(
|
|
5075
5773
|
JSON.stringify(
|
|
5076
5774
|
{
|
|
5077
5775
|
dryRun: true,
|
|
5078
|
-
notes:
|
|
5776
|
+
notes: plan3.notes ?? [],
|
|
5079
5777
|
files: changed.map(({ path, before, after, deleted }) => ({
|
|
5080
5778
|
path,
|
|
5081
5779
|
action: deleted ? "delete" : before.length === 0 ? "create" : "update",
|
|
@@ -5090,7 +5788,7 @@ function previewPlan(opts, plan2) {
|
|
|
5090
5788
|
return 0;
|
|
5091
5789
|
}
|
|
5092
5790
|
process.stderr.write(" dry run: no files written\n");
|
|
5093
|
-
for (const note of
|
|
5791
|
+
for (const note of plan3.notes ?? []) process.stderr.write(` ${note}
|
|
5094
5792
|
`);
|
|
5095
5793
|
if (changed.length === 0) {
|
|
5096
5794
|
process.stderr.write(" nothing to change\n");
|
|
@@ -5111,7 +5809,8 @@ async function dispatch(opts) {
|
|
|
5111
5809
|
throw new Error(`dispatch handles --init only; --${opts.verb} routes through analyse()`);
|
|
5112
5810
|
}
|
|
5113
5811
|
const detected = resolveFlavour(opts);
|
|
5114
|
-
const
|
|
5812
|
+
const resolutionPreset = opts.preset ?? declaredPresetFor(opts.target, opts.cwd) ?? detected;
|
|
5813
|
+
const adapter = resolve(opts.target, resolutionPreset, opts.runner);
|
|
5115
5814
|
const preset = opts.preset ?? adapter.declaredPreset?.(opts.cwd) ?? detected;
|
|
5116
5815
|
const contradiction = presetContradiction(preset, opts.cwd);
|
|
5117
5816
|
if (contradiction !== void 0) {
|
|
@@ -5120,25 +5819,25 @@ async function dispatch(opts) {
|
|
|
5120
5819
|
return 1;
|
|
5121
5820
|
}
|
|
5122
5821
|
const context = { cwd: opts.cwd, preset };
|
|
5123
|
-
const
|
|
5124
|
-
if (
|
|
5125
|
-
process.stderr.write(`sentinel (${opts.target}): ${
|
|
5822
|
+
const plan3 = await adapter.plan(context);
|
|
5823
|
+
if (plan3.blocked) {
|
|
5824
|
+
process.stderr.write(`sentinel (${opts.target}): ${plan3.blocked}
|
|
5126
5825
|
`);
|
|
5127
5826
|
return 1;
|
|
5128
5827
|
}
|
|
5129
|
-
if (
|
|
5130
|
-
process.stderr.write(` ${palette(process.stderr).dim(`${opts.target}: ${
|
|
5828
|
+
if (plan3.skipped) {
|
|
5829
|
+
process.stderr.write(` ${palette(process.stderr).dim(`${opts.target}: ${plan3.skipped}`)}
|
|
5131
5830
|
`);
|
|
5132
5831
|
return 0;
|
|
5133
5832
|
}
|
|
5134
5833
|
if (opts.dryRun) {
|
|
5135
|
-
return previewPlan(opts,
|
|
5834
|
+
return previewPlan(opts, plan3);
|
|
5136
5835
|
}
|
|
5137
|
-
for (const change of applyPlan(opts.cwd,
|
|
5836
|
+
for (const change of applyPlan(opts.cwd, plan3)) {
|
|
5138
5837
|
process.stderr.write(` ${change.deleted ? "removed" : "wrote"} ${change.path}
|
|
5139
5838
|
`);
|
|
5140
5839
|
}
|
|
5141
|
-
for (const note of
|
|
5840
|
+
for (const note of plan3.notes ?? []) process.stderr.write(` ${note}
|
|
5142
5841
|
`);
|
|
5143
5842
|
if (adapter.afterInit) {
|
|
5144
5843
|
process.stderr.write(` fixing what ${opts.target} can fix automatically...
|
|
@@ -5155,26 +5854,30 @@ async function dispatch(opts) {
|
|
|
5155
5854
|
}
|
|
5156
5855
|
|
|
5157
5856
|
export {
|
|
5857
|
+
PresetUnsupportedError,
|
|
5158
5858
|
register,
|
|
5159
5859
|
setDefaultRunner,
|
|
5160
5860
|
all,
|
|
5861
|
+
declaredPresetFor,
|
|
5161
5862
|
availableTargets,
|
|
5162
5863
|
resolve,
|
|
5163
5864
|
BaseAdapter,
|
|
5865
|
+
resolveBin,
|
|
5164
5866
|
readOwnVersion,
|
|
5165
5867
|
readProjectPackageJson,
|
|
5166
5868
|
readNxProjectName,
|
|
5167
5869
|
WORKSPACE_ROOT_MARKER,
|
|
5168
5870
|
findWorkspaceRoot,
|
|
5169
5871
|
ensureWorkspacePrep,
|
|
5872
|
+
inspectWorkspacePrep,
|
|
5170
5873
|
palette,
|
|
5171
|
-
resolveBin,
|
|
5172
5874
|
VERBS,
|
|
5173
5875
|
TARGETS,
|
|
5876
|
+
SWEEPABLE_TARGETS,
|
|
5174
5877
|
PRESET_NAMES,
|
|
5175
5878
|
registerAdapters,
|
|
5176
5879
|
describeFramework,
|
|
5177
5880
|
detectFramework,
|
|
5178
5881
|
dispatch
|
|
5179
5882
|
};
|
|
5180
|
-
//# sourceMappingURL=chunk-
|
|
5883
|
+
//# sourceMappingURL=chunk-TWL6T237.js.map
|