@hublo/sentinel 1.2.0-alpha.9 → 1.3.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 +17 -2
- package/dist/bin/sentinel.d.ts +1 -0
- package/dist/bin/sentinel.js +10 -5
- package/dist/{chunk-6RH3LZQZ.js → chunk-676GBPMS.js} +1247 -913
- package/dist/index.js +1 -1
- package/dist/roles/build/nest/toolchain.d.ts +40 -0
- package/dist/roles/build/nest/toolchain.js +217 -54
- package/dist/roles/test/react/toolchain.d.ts +2 -0
- package/dist/roles/test/react/toolchain.js +9 -0
- package/lint/react-lib.json +3 -1
- package/lint/react.json +4 -1
- package/package.json +11 -7
- package/plugins/hublo.js +68 -0
|
@@ -67,19 +67,6 @@ function resolve(target, preset, runner) {
|
|
|
67
67
|
return matching[0];
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// src/core/base-adapter.ts
|
|
71
|
-
var BaseAdapter = class {
|
|
72
|
-
inspect(_ctx) {
|
|
73
|
-
throw new Error(`${this.runner}: --inspect not implemented yet`);
|
|
74
|
-
}
|
|
75
|
-
report(_ctx) {
|
|
76
|
-
throw new Error(`${this.runner}: --report not implemented yet`);
|
|
77
|
-
}
|
|
78
|
-
status(_ctx) {
|
|
79
|
-
throw new Error(`${this.runner}: --status not implemented yet`);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
70
|
// src/shared/package-json.ts
|
|
84
71
|
import { existsSync, readFileSync } from "fs";
|
|
85
72
|
import { basename, dirname, join } from "path";
|
|
@@ -132,6 +119,19 @@ function moduleName(dir) {
|
|
|
132
119
|
return readNxProjectName(dir) ?? (declared === void 0 ? basename(dir) : declared);
|
|
133
120
|
}
|
|
134
121
|
|
|
122
|
+
// src/core/base-adapter.ts
|
|
123
|
+
var BaseAdapter = class {
|
|
124
|
+
inspect(_ctx) {
|
|
125
|
+
throw new Error(`${this.runner}: --inspect not implemented yet`);
|
|
126
|
+
}
|
|
127
|
+
report(_ctx) {
|
|
128
|
+
throw new Error(`${this.runner}: --report not implemented yet`);
|
|
129
|
+
}
|
|
130
|
+
status(_ctx) {
|
|
131
|
+
throw new Error(`${this.runner}: --status not implemented yet`);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
135
|
// src/core/domain.ts
|
|
136
136
|
var VERBS = ["run", "inspect", "init", "migrate", "report", "status"];
|
|
137
137
|
var TARGETS = [
|
|
@@ -165,86 +165,99 @@ function palette(stream) {
|
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
// src/roles/build/
|
|
169
|
-
import {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const looksLikeOption = arg.startsWith("-");
|
|
181
|
-
const target = isAbsolute(arg) ? arg : resolve2(cwd, arg);
|
|
182
|
-
if (!previousTakesValue && !looksLikeOption && existsSync2(target)) paths.push(arg);
|
|
183
|
-
else options.push(arg);
|
|
184
|
-
previousTakesValue = !arg.includes("=") && takesValue.has(arg);
|
|
168
|
+
// src/roles/build/nest/nx-target.ts
|
|
169
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
170
|
+
import { join as join2, relative } from "path";
|
|
171
|
+
var DEPRECATED_NX_BUILDER = "@nx/webpack:webpack";
|
|
172
|
+
var TRANSLATABLE_WEBPACK_CONFIG = "tools/webpack-configs/update.webpack.config.js";
|
|
173
|
+
var readProjectJson = (cwd) => {
|
|
174
|
+
const path = join2(cwd, "project.json");
|
|
175
|
+
if (!existsSync2(path)) return void 0;
|
|
176
|
+
try {
|
|
177
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
178
|
+
} catch {
|
|
179
|
+
return void 0;
|
|
185
180
|
}
|
|
186
|
-
|
|
181
|
+
};
|
|
182
|
+
function webpackBuildTarget(cwd) {
|
|
183
|
+
const project = readProjectJson(cwd);
|
|
184
|
+
const target = project?.targets?.build;
|
|
185
|
+
if (target?.executor !== DEPRECATED_NX_BUILDER) return void 0;
|
|
186
|
+
const { outputPath, main, webpackConfig, transformers, assets } = target.options ?? {};
|
|
187
|
+
if (typeof project?.name !== "string" || typeof outputPath !== "string" || typeof main !== "string" || typeof webpackConfig !== "string") {
|
|
188
|
+
return void 0;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
project: project.name,
|
|
192
|
+
outputPath,
|
|
193
|
+
main,
|
|
194
|
+
webpackConfig,
|
|
195
|
+
configurations: target.configurations,
|
|
196
|
+
transformers,
|
|
197
|
+
assets
|
|
198
|
+
};
|
|
187
199
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (existsSync3(candidate)) return candidate;
|
|
199
|
-
const parent = dirname2(dir);
|
|
200
|
-
if (parent === dir) return void 0;
|
|
201
|
-
dir = parent;
|
|
200
|
+
function refusalFor(cwd, target) {
|
|
201
|
+
if (workspaceRootFor(cwd, target) === void 0) {
|
|
202
|
+
return `this service's build target declares \`main: ${target.main}\`, which does not place this directory inside a workspace. Every value the config needs is derived from that path, so a wrong one would resolve \`workspaceRoot\` to the service itself and leave the alias table resolving nothing. Nothing was written.`;
|
|
203
|
+
}
|
|
204
|
+
if (target.webpackConfig !== TRANSLATABLE_WEBPACK_CONFIG) {
|
|
205
|
+
return `this service builds with ${target.webpackConfig}, not the shared ${TRANSLATABLE_WEBPACK_CONFIG} this preset reproduces. Its own config adds loaders or rules the preset has no equivalent for, and translating it anyway would produce a build that succeeds and a service that fails later. Read that file, decide what the Vite config needs, and write it by hand. Nothing was written.`;
|
|
206
|
+
}
|
|
207
|
+
const live = liveFileReplacements(cwd, target);
|
|
208
|
+
if (live.length > 0) {
|
|
209
|
+
return `this service's production configuration replaces ${live.join(", ")}, and those files exist. The preset has no equivalent, so translating the target would drop a real substitution. Nothing was written.`;
|
|
202
210
|
}
|
|
211
|
+
return void 0;
|
|
203
212
|
}
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
213
|
+
function liveFileReplacements(cwd, target) {
|
|
214
|
+
const workspaceRoot = workspaceRootFor(cwd, target);
|
|
215
|
+
if (workspaceRoot === void 0) return [];
|
|
216
|
+
const configurations = target.configurations ?? {};
|
|
217
|
+
const live = [];
|
|
218
|
+
for (const configuration of Object.values(configurations)) {
|
|
219
|
+
for (const replacement of configuration.fileReplacements ?? []) {
|
|
220
|
+
const exists = existsSync2(join2(workspaceRoot, replacement.replace)) || existsSync2(join2(workspaceRoot, replacement.with));
|
|
221
|
+
if (exists) live.push(replacement.replace);
|
|
222
|
+
}
|
|
214
223
|
}
|
|
224
|
+
return live;
|
|
215
225
|
}
|
|
216
|
-
function
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
);
|
|
226
|
+
function workspaceRootFor(cwd, target) {
|
|
227
|
+
const moduleRoot = target.main.replace(/\/src\/.*$/, "");
|
|
228
|
+
if (moduleRoot === target.main || !cwd.endsWith(moduleRoot)) return void 0;
|
|
229
|
+
return cwd.slice(0, cwd.length - moduleRoot.length - 1);
|
|
230
|
+
}
|
|
231
|
+
function workspaceRootHops(cwd, target) {
|
|
232
|
+
const root = workspaceRootFor(cwd, target);
|
|
233
|
+
if (root === void 0) {
|
|
234
|
+
throw new Error(`sentinel build(nest): cannot place ${cwd} in a workspace from ${target.main}`);
|
|
235
|
+
}
|
|
236
|
+
const rel = relative(root, cwd);
|
|
237
|
+
return rel === "" ? 0 : rel.split(/[\\/]/).length;
|
|
220
238
|
}
|
|
221
239
|
|
|
222
|
-
// src/roles/build/
|
|
223
|
-
import { existsSync as
|
|
224
|
-
import { join as
|
|
225
|
-
|
|
226
|
-
// src/core/config/existing-command.ts
|
|
227
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
228
|
-
import { join as join4 } from "path";
|
|
240
|
+
// src/roles/build/read-adoption.ts
|
|
241
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
242
|
+
import { join as join5 } from "path";
|
|
229
243
|
|
|
230
|
-
// src/
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
return value;
|
|
244
|
+
// src/core/config/module-type.ts
|
|
245
|
+
function esmReadiness(fileName, packageType) {
|
|
246
|
+
if (/\.(mts|mjs)$/.test(fileName)) return { kind: "ready" };
|
|
247
|
+
if (packageType === "module") return { kind: "ready" };
|
|
248
|
+
const rename = fileName.replace(/\.(ts|js)$/, ".mts");
|
|
249
|
+
return {
|
|
250
|
+
kind: "blocked",
|
|
251
|
+
why: `sentinel is ESM only, and this module ${packageType === void 0 ? 'declares no `"type"`' : `declares \`"type": "${packageType}"\``}, so Node loads ${fileName} as CommonJS and the import fails. Two remedies, and adoption takes neither by itself because both reach beyond this build: add \`"type": "module"\` to this module's package.json, which changes how EVERY \`.js\` here loads, or rename the config to \`${rename}\`, which settles it in the filename where nothing can undo it.`
|
|
252
|
+
};
|
|
240
253
|
}
|
|
241
254
|
|
|
242
255
|
// src/core/config/manifest.ts
|
|
243
|
-
import { existsSync as
|
|
256
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
244
257
|
import { basename as basename2, join as join3 } from "path";
|
|
245
258
|
function moduleScripts(cwd) {
|
|
246
259
|
try {
|
|
247
|
-
const pkg = JSON.parse(
|
|
260
|
+
const pkg = JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf8"));
|
|
248
261
|
return pkg.scripts ?? {};
|
|
249
262
|
} catch {
|
|
250
263
|
return {};
|
|
@@ -252,7 +265,7 @@ function moduleScripts(cwd) {
|
|
|
252
265
|
}
|
|
253
266
|
function moduleName2(cwd) {
|
|
254
267
|
try {
|
|
255
|
-
const pkg = JSON.parse(
|
|
268
|
+
const pkg = JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf8"));
|
|
256
269
|
return pkg.name;
|
|
257
270
|
} catch {
|
|
258
271
|
return void 0;
|
|
@@ -266,122 +279,25 @@ function selfCommand(cwd, flags) {
|
|
|
266
279
|
if (!isSelfAdoption(cwd) || !own.bin) return void 0;
|
|
267
280
|
return `node ${own.bin.replace(/^\.\//, "")} ${flags}`;
|
|
268
281
|
}
|
|
282
|
+
function adoptedPackageName(cwd) {
|
|
283
|
+
return readProjectPackageJson(cwd).name ?? readNxProjectName(cwd) ?? basename2(cwd);
|
|
284
|
+
}
|
|
269
285
|
function manifestOperation(cwd, scripts) {
|
|
270
286
|
const own = readOwnPackage();
|
|
271
287
|
const value = {
|
|
272
288
|
scripts,
|
|
273
289
|
devDependencies: { [own.name]: isSelfAdoption(cwd) ? "link:." : own.version }
|
|
274
290
|
};
|
|
275
|
-
if (
|
|
291
|
+
if (existsSync3(join3(cwd, "package.json"))) {
|
|
276
292
|
return { kind: "merge-json", path: "package.json", value };
|
|
277
293
|
}
|
|
278
294
|
return {
|
|
279
295
|
kind: "merge-json",
|
|
280
296
|
path: "package.json",
|
|
281
|
-
value: { name:
|
|
297
|
+
value: { name: adoptedPackageName(cwd), private: true, ...value }
|
|
282
298
|
};
|
|
283
299
|
}
|
|
284
300
|
|
|
285
|
-
// src/core/config/existing-command.ts
|
|
286
|
-
function nxTargetCommand(cwd, target) {
|
|
287
|
-
let project;
|
|
288
|
-
try {
|
|
289
|
-
project = parseJsonc(
|
|
290
|
-
readFileSync3(join4(cwd, "project.json"), "utf8"),
|
|
291
|
-
"project.json"
|
|
292
|
-
);
|
|
293
|
-
} catch {
|
|
294
|
-
return void 0;
|
|
295
|
-
}
|
|
296
|
-
const entry = project.targets?.[target];
|
|
297
|
-
if (!entry || entry.executor !== "nx:run-commands") return void 0;
|
|
298
|
-
const { command, commands, cwd: targetCwd } = entry.options ?? {};
|
|
299
|
-
const ranInModule = typeof targetCwd === "string" && targetCwd.trim() !== "";
|
|
300
|
-
if (typeof command === "string" && command.trim() !== "") {
|
|
301
|
-
return { command: command.trim(), ranInModule };
|
|
302
|
-
}
|
|
303
|
-
if (Array.isArray(commands)) {
|
|
304
|
-
const parts = commands.filter((c) => typeof c === "string" && c.trim() !== "");
|
|
305
|
-
if (parts.length > 0) return { command: parts.map((c) => c.trim()).join(" && "), ranInModule };
|
|
306
|
-
}
|
|
307
|
-
return void 0;
|
|
308
|
-
}
|
|
309
|
-
function rootedForScript(command) {
|
|
310
|
-
return command.replace(/(^|&&\s*)pnpm exec /g, "$1pnpm --workspace-root exec ");
|
|
311
|
-
}
|
|
312
|
-
function existingCommand(cwd, target) {
|
|
313
|
-
const script = moduleScripts(cwd)[target];
|
|
314
|
-
if (script !== void 0 && script.trim() !== "") return script;
|
|
315
|
-
const fromTarget = nxTargetCommand(cwd, target);
|
|
316
|
-
if (fromTarget === void 0) return void 0;
|
|
317
|
-
return fromTarget.ranInModule ? fromTarget.command : rootedForScript(fromTarget.command);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// src/core/config/nx-target.ts
|
|
321
|
-
import { existsSync as existsSync5 } from "fs";
|
|
322
|
-
import { join as join5 } from "path";
|
|
323
|
-
function nxTargetOperations(options) {
|
|
324
|
-
const { cwd, targets } = options;
|
|
325
|
-
const names = Object.keys(targets);
|
|
326
|
-
if (names.length === 0) return [];
|
|
327
|
-
const operations = [];
|
|
328
|
-
if (existsSync5(join5(cwd, "project.json"))) {
|
|
329
|
-
operations.push({
|
|
330
|
-
kind: "remove-json-keys",
|
|
331
|
-
path: "project.json",
|
|
332
|
-
keys: names.map((name) => ["targets", name])
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
operations.push({
|
|
336
|
-
kind: "merge-json",
|
|
337
|
-
path: "package.json",
|
|
338
|
-
value: { nx: { targets } }
|
|
339
|
-
});
|
|
340
|
-
return operations;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// src/core/config/tool-script.ts
|
|
344
|
-
var SEGMENT_SEPARATOR = /(\s*(?:&&|\|\||;|&|\|)\s*)/;
|
|
345
|
-
function isSeparatorAt(index) {
|
|
346
|
-
return index % 2 === 1;
|
|
347
|
-
}
|
|
348
|
-
function invokes(segment, binary) {
|
|
349
|
-
return new RegExp(String.raw`(^|[\s/])${binary}(\s|$)`).test(segment.trim());
|
|
350
|
-
}
|
|
351
|
-
function isSentinelSegment(segment, roleFlag) {
|
|
352
|
-
const runsSentinel = invokes(segment, "sentinel") || segment.includes("bin/sentinel.js");
|
|
353
|
-
return runsSentinel && new RegExp(String.raw`(^|\s)${roleFlag}(\s|$)`).test(segment);
|
|
354
|
-
}
|
|
355
|
-
function composeToolScript(existing, options) {
|
|
356
|
-
const { command } = options;
|
|
357
|
-
if (!existing || existing.trim() === "") return command;
|
|
358
|
-
const { roleFlag } = options;
|
|
359
|
-
const isReplaceable = (segment) => options.replaces(segment) || roleFlag !== void 0 && isSentinelSegment(segment, roleFlag);
|
|
360
|
-
const parts = existing.split(SEGMENT_SEPARATOR);
|
|
361
|
-
const commands = parts.filter((_, index) => !isSeparatorAt(index));
|
|
362
|
-
if (!commands.some(isReplaceable)) return existing;
|
|
363
|
-
let replacedOnce = false;
|
|
364
|
-
const rebuilt = parts.map((part, index) => {
|
|
365
|
-
if (isSeparatorAt(index) || !isReplaceable(part)) return part;
|
|
366
|
-
if (replacedOnce) return null;
|
|
367
|
-
replacedOnce = true;
|
|
368
|
-
return options.keepFix && /(^|\s)--fix(\s|$)/.test(part) ? `${command} --fix` : command;
|
|
369
|
-
});
|
|
370
|
-
const kept = [];
|
|
371
|
-
for (let index = 0; index < rebuilt.length; index += 1) {
|
|
372
|
-
const part = rebuilt[index];
|
|
373
|
-
if (part === null) {
|
|
374
|
-
if (kept.length > 0) kept.pop();
|
|
375
|
-
continue;
|
|
376
|
-
}
|
|
377
|
-
kept.push(part);
|
|
378
|
-
}
|
|
379
|
-
return kept.join("").trim();
|
|
380
|
-
}
|
|
381
|
-
function keepsOtherCommands(script, sentinelCommand) {
|
|
382
|
-
return script.split(SEGMENT_SEPARATOR).filter((_, index) => !isSeparatorAt(index)).some((segment) => segment.trim() !== "" && segment.trim() !== sentinelCommand);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
301
|
// src/roles/build/presets/requirements.json
|
|
386
302
|
var requirements_default = {
|
|
387
303
|
svelte: {
|
|
@@ -408,7 +324,8 @@ var toolchain_default = {
|
|
|
408
324
|
"UserConfig",
|
|
409
325
|
"UserConfigExport"
|
|
410
326
|
],
|
|
411
|
-
why: "the bundler itself. `defineConfig`, `loadEnv` and `mergeConfig` are what every config in this repo imports from it, and the types travel with them: a config importing PluginOption from a package it no longer declares builds but does not typecheck."
|
|
327
|
+
why: "the bundler itself. `defineConfig`, `loadEnv` and `mergeConfig` are what every config in this repo imports from it, and the types travel with them: a config importing PluginOption from a package it no longer declares builds but does not typecheck.",
|
|
328
|
+
floor: "Never below 8.2.2. Vite 8.0.8 resolves rolldown 1.0.0-rc.15, a release candidate whose deferred-module-initialisation codegen is broken: it wrapped a plain re-export barrel in an init helper, then required the module without binding its exports before calling the very function that binding provides. Measured, that crashed 7 of the 8 Nest services with a transformer at their first require (`init_<module> is not defined`), and quietly moved the published contract of 4 others. 8.2.2 resolves rolldown 1.2.6 and all 35 services regenerate their committed OpenAPI document identically. The version is not a preference, it is the floor at which those measurements hold."
|
|
412
329
|
},
|
|
413
330
|
{
|
|
414
331
|
package: "@vitejs/plugin-react",
|
|
@@ -476,11 +393,19 @@ var BUILD_SCRIPT_NAME = "build";
|
|
|
476
393
|
var SENTINEL_BUILD_COMMAND = "sentinel --run --build --";
|
|
477
394
|
var DEV_SCRIPT_NAME = "serve";
|
|
478
395
|
var SENTINEL_DEV_COMMAND = "sentinel --run --dev";
|
|
479
|
-
|
|
396
|
+
var BUILD_OUTPUT_CANDIDATES = ["dist", ".output", "build"];
|
|
397
|
+
function buildTarget(options) {
|
|
398
|
+
const { moduleName: moduleName3, configFiles = BUILD_CONFIG_FILES, outputs } = options;
|
|
480
399
|
return {
|
|
481
400
|
[BUILD_SCRIPT_NAME]: {
|
|
401
|
+
executor: "nx:run-commands",
|
|
402
|
+
options: {
|
|
403
|
+
command: `pnpm --filter ${moduleName3} run ${BUILD_SCRIPT_NAME}`,
|
|
404
|
+
forwardAllArgs: false
|
|
405
|
+
},
|
|
482
406
|
cache: true,
|
|
483
|
-
inputs: ["default", "^default", ...
|
|
407
|
+
inputs: ["default", "^default", ...configFiles.map((name) => `{projectRoot}/${name}`)],
|
|
408
|
+
outputs: [...outputs]
|
|
484
409
|
}
|
|
485
410
|
};
|
|
486
411
|
}
|
|
@@ -488,136 +413,71 @@ var SENTINEL_OWNED_BUILD_PACKAGES = OWNED_PACKAGES.map(
|
|
|
488
413
|
(entry) => entry.package
|
|
489
414
|
);
|
|
490
415
|
|
|
491
|
-
// src/roles/build/
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const kept = tokens.filter((token, at) => !(at === 0 && DEV_SUBCOMMANDS.has(token)));
|
|
498
|
-
return kept.join(" ");
|
|
499
|
-
}
|
|
500
|
-
function composeDevScript(existing, command) {
|
|
501
|
-
if (existing === void 0 || existing.trim() === "") return command;
|
|
502
|
-
const chunks = existing.trim().split(CHAIN);
|
|
503
|
-
const at = chunks.map((chunk) => VITE_TOKEN.test(chunk)).lastIndexOf(true);
|
|
504
|
-
if (at === -1) return existing;
|
|
505
|
-
const rest = (chunks[at] ?? "").replace(/^.*?(^|\s|\/)vite(\s|$)/, "");
|
|
506
|
-
const options = appOptions(rest);
|
|
507
|
-
chunks[at] = options === "" ? command : `${command} -- ${options}`;
|
|
508
|
-
return chunks.join(CHAIN);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// src/roles/build/nest/adopt.ts
|
|
512
|
-
import { relative as relative2 } from "path";
|
|
513
|
-
|
|
514
|
-
// src/roles/build/nest/nx-target.ts
|
|
515
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
516
|
-
import { join as join6, relative } from "path";
|
|
517
|
-
var DEPRECATED_NX_BUILDER = "@nx/webpack:webpack";
|
|
518
|
-
var TRANSLATABLE_WEBPACK_CONFIG = "tools/webpack-configs/update.webpack.config.js";
|
|
519
|
-
var readProjectJson = (cwd) => {
|
|
520
|
-
const path = join6(cwd, "project.json");
|
|
521
|
-
if (!existsSync6(path)) return void 0;
|
|
416
|
+
// src/roles/build/project-json.ts
|
|
417
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
418
|
+
import { join as join4 } from "path";
|
|
419
|
+
function projectTargets(cwd) {
|
|
420
|
+
const path = join4(cwd, "project.json");
|
|
421
|
+
if (!existsSync4(path)) return void 0;
|
|
522
422
|
try {
|
|
523
|
-
return JSON.parse(readFileSync4(path, "utf8"));
|
|
423
|
+
return JSON.parse(readFileSync4(path, "utf8")).targets;
|
|
524
424
|
} catch {
|
|
525
425
|
return void 0;
|
|
526
426
|
}
|
|
527
|
-
};
|
|
528
|
-
function webpackBuildTarget(cwd) {
|
|
529
|
-
const project = readProjectJson(cwd);
|
|
530
|
-
const target = project?.targets?.build;
|
|
531
|
-
if (target?.executor !== DEPRECATED_NX_BUILDER) return void 0;
|
|
532
|
-
const { outputPath, main, webpackConfig } = target.options ?? {};
|
|
533
|
-
if (typeof project?.name !== "string" || typeof outputPath !== "string" || typeof main !== "string" || typeof webpackConfig !== "string") {
|
|
534
|
-
return void 0;
|
|
535
|
-
}
|
|
536
|
-
return {
|
|
537
|
-
project: project.name,
|
|
538
|
-
outputPath,
|
|
539
|
-
main,
|
|
540
|
-
webpackConfig,
|
|
541
|
-
configurations: target.configurations
|
|
542
|
-
};
|
|
543
427
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
428
|
+
|
|
429
|
+
// src/roles/build/adoption-drift.ts
|
|
430
|
+
function buildAdoptionDrift(cwd) {
|
|
431
|
+
const drift = [];
|
|
432
|
+
const manifest = readProjectPackageJson(cwd);
|
|
433
|
+
const script = moduleScripts(cwd)[BUILD_SCRIPT_NAME];
|
|
434
|
+
if (script === void 0) {
|
|
435
|
+
drift.push(
|
|
436
|
+
`has no \`${BUILD_SCRIPT_NAME}\` npm script, so \`pnpm run ${BUILD_SCRIPT_NAME}\` does not build it and only nx can`
|
|
437
|
+
);
|
|
438
|
+
} else if (!script.includes(SENTINEL_BUILD_COMMAND.replace(/ --$/, ""))) {
|
|
439
|
+
drift.push(
|
|
440
|
+
`its \`${BUILD_SCRIPT_NAME}\` script does not invoke sentinel (${script}), so the config points at sentinel while the script runs something else`
|
|
441
|
+
);
|
|
551
442
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
443
|
+
const target = manifest.nx?.targets?.[BUILD_SCRIPT_NAME];
|
|
444
|
+
if (target === void 0) {
|
|
445
|
+
drift.push(
|
|
446
|
+
`declares no \`nx.targets.${BUILD_SCRIPT_NAME}\`, so its build runs on nx's default inputs while every other target it has declares its own`
|
|
447
|
+
);
|
|
448
|
+
} else {
|
|
449
|
+
if (target.cache === true && (target.outputs ?? []).length === 0) {
|
|
450
|
+
drift.push(
|
|
451
|
+
`caches its build and declares no \`outputs\`, so nx stores nothing and a cache hit reports success with no build on disk`
|
|
452
|
+
);
|
|
562
453
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const suffix = cwd.endsWith(moduleRoot) ? moduleRoot : "";
|
|
569
|
-
return suffix === "" ? cwd : cwd.slice(0, cwd.length - suffix.length - 1);
|
|
570
|
-
}
|
|
571
|
-
function workspaceRootHops(cwd, target) {
|
|
572
|
-
const root = workspaceRootFor(cwd, target);
|
|
573
|
-
const rel = relative(root, cwd);
|
|
574
|
-
return rel === "" ? 0 : rel.split(/[\\/]/).length;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// src/roles/build/nest/adopt.ts
|
|
578
|
-
var NEST_CONFIG_FILE = "vite.config.mts";
|
|
579
|
-
function nestConfigSource(cwd, target) {
|
|
580
|
-
const hops = workspaceRootHops(cwd, target);
|
|
581
|
-
const up = Array.from({ length: hops }, () => "..").join("/");
|
|
582
|
-
return `import path from 'node:path'
|
|
583
|
-
|
|
584
|
-
import { nestService } from '${BUILD_PRESET_SPECIFIERS.nest}'
|
|
585
|
-
|
|
586
|
-
export default nestService({
|
|
587
|
-
project: '${target.project}',
|
|
588
|
-
root: __dirname,
|
|
589
|
-
workspaceRoot: path.resolve(__dirname, '${up}'),
|
|
590
|
-
outDir: '${target.outputPath}',
|
|
591
|
-
})
|
|
592
|
-
`;
|
|
593
|
-
}
|
|
594
|
-
function nestBuildTarget(cwd, target) {
|
|
595
|
-
const moduleRoot = relative2(workspaceRootFor(cwd, target), cwd);
|
|
596
|
-
return {
|
|
597
|
-
executor: "nx:run-commands",
|
|
598
|
-
outputs: [`{workspaceRoot}/${target.outputPath}`],
|
|
599
|
-
options: {
|
|
600
|
-
cwd: moduleRoot,
|
|
601
|
-
command: "pnpm exec sentinel --run --build",
|
|
602
|
-
forwardAllArgs: false
|
|
454
|
+
const inputs = target.inputs ?? [];
|
|
455
|
+
if (inputs.length > 0 && !inputs.includes("^default")) {
|
|
456
|
+
drift.push(
|
|
457
|
+
`declares \`inputs\` without \`^default\`, which REPLACES nx's default and stops hashing its dependencies: the build can replay a stale bundle after one changes`
|
|
458
|
+
);
|
|
603
459
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
{ kind: "write", path: NEST_CONFIG_FILE, contents: nestConfigSource(cwd, target) },
|
|
609
|
-
{ kind: "remove-json-keys", path: "project.json", keys: [["targets", "build"]] },
|
|
610
|
-
{
|
|
611
|
-
kind: "merge-json",
|
|
612
|
-
path: "project.json",
|
|
613
|
-
value: { targets: { build: nestBuildTarget(cwd, target) } }
|
|
460
|
+
if (target.options?.forwardAllArgs !== false) {
|
|
461
|
+
drift.push(
|
|
462
|
+
`does not set \`forwardAllArgs: false\`, so a caller's extra flag reaches the bundler, which refuses it (the image passes \`--generatePackageJson\`)`
|
|
463
|
+
);
|
|
614
464
|
}
|
|
615
|
-
|
|
465
|
+
}
|
|
466
|
+
if (projectTargets(cwd)?.[BUILD_SCRIPT_NAME] !== void 0) {
|
|
467
|
+
drift.push(
|
|
468
|
+
`still declares a \`${BUILD_SCRIPT_NAME}\` target in project.json beside the one in package.json, so the module has two and which runs depends on the caller`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
const devScript = moduleScripts(cwd)[DEV_SCRIPT_NAME];
|
|
472
|
+
if (devScript?.includes(SENTINEL_DEV_COMMAND) === true && projectTargets(cwd)?.[DEV_SCRIPT_NAME] !== void 0) {
|
|
473
|
+
drift.push(
|
|
474
|
+
`runs its \`${DEV_SCRIPT_NAME}\` script through sentinel but still declares a \`${DEV_SCRIPT_NAME}\` target in project.json, so one dev server has two declarations and editing the target stops the script from being what runs`
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
return drift;
|
|
616
478
|
}
|
|
617
479
|
|
|
618
480
|
// src/roles/build/read-adoption.ts
|
|
619
|
-
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
620
|
-
import { join as join7 } from "path";
|
|
621
481
|
var NOT_ADOPTED = (configFile, unreadable = null) => ({
|
|
622
482
|
configFile,
|
|
623
483
|
preset: null,
|
|
@@ -628,7 +488,7 @@ var NOT_ADOPTED = (configFile, unreadable = null) => ({
|
|
|
628
488
|
unreadable
|
|
629
489
|
});
|
|
630
490
|
function buildConfigFile(cwd) {
|
|
631
|
-
return BUILD_CONFIG_FILES.find((name) =>
|
|
491
|
+
return BUILD_CONFIG_FILES.find((name) => existsSync5(join5(cwd, name)));
|
|
632
492
|
}
|
|
633
493
|
function importsSpecifier(source, specifier) {
|
|
634
494
|
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -643,6 +503,7 @@ var REACT_PLUGIN_SPECIFIERS = ["@vitejs/plugin-react", "@tanstack/react-start"];
|
|
|
643
503
|
function declaredBuildPreset(cwd) {
|
|
644
504
|
const source = readBuildConfigSource(cwd);
|
|
645
505
|
if (source === void 0) {
|
|
506
|
+
if (buildConfigFile(cwd) !== void 0) return void 0;
|
|
646
507
|
return webpackBuildTarget(cwd) === void 0 ? void 0 : "nest";
|
|
647
508
|
}
|
|
648
509
|
if (importsSpecifier(source, BUILD_PRESET_SPECIFIERS.nest)) return "nest";
|
|
@@ -653,7 +514,7 @@ function readBuildConfigSource(cwd) {
|
|
|
653
514
|
const configFile = buildConfigFile(cwd);
|
|
654
515
|
if (!configFile) return void 0;
|
|
655
516
|
try {
|
|
656
|
-
return readFileSync5(
|
|
517
|
+
return readFileSync5(join5(cwd, configFile), "utf8");
|
|
657
518
|
} catch {
|
|
658
519
|
return void 0;
|
|
659
520
|
}
|
|
@@ -663,31 +524,485 @@ function readBuildAdoption(cwd) {
|
|
|
663
524
|
if (!configFile) return NOT_ADOPTED(null);
|
|
664
525
|
let source;
|
|
665
526
|
try {
|
|
666
|
-
source = readFileSync5(
|
|
527
|
+
source = readFileSync5(join5(cwd, configFile), "utf8");
|
|
667
528
|
} catch (error) {
|
|
668
529
|
return NOT_ADOPTED(configFile, error instanceof Error ? error.message : String(error));
|
|
669
530
|
}
|
|
670
|
-
if (!importsPreset(source))
|
|
531
|
+
if (!importsPreset(source)) {
|
|
532
|
+
const esm = esmReadiness(configFile, readProjectPackageJson(cwd).type);
|
|
533
|
+
return NOT_ADOPTED(configFile, esm.kind === "blocked" ? esm.why : null);
|
|
534
|
+
}
|
|
671
535
|
const manifest = readProjectPackageJson(cwd);
|
|
672
536
|
const declared = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
673
537
|
const ownDeclarations = SENTINEL_OWNED_BUILD_PACKAGES.filter((name) => name in declared).map(
|
|
674
538
|
(name) => ({ name, version: declared[name] })
|
|
675
539
|
);
|
|
540
|
+
const drift = [
|
|
541
|
+
...ownDeclarations.map(
|
|
542
|
+
({ 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`
|
|
543
|
+
),
|
|
544
|
+
...buildAdoptionDrift(cwd)
|
|
545
|
+
];
|
|
676
546
|
return {
|
|
677
547
|
configFile,
|
|
678
548
|
// Read from the config rather than assumed: the two presets are adopted the same way and
|
|
679
549
|
// reporting the wrong one would make `--inspect` lie about what a module builds.
|
|
680
550
|
preset: declaredBuildPreset(cwd) ?? "react",
|
|
681
551
|
adopted: true,
|
|
682
|
-
conformant:
|
|
683
|
-
drift
|
|
684
|
-
({ 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`
|
|
685
|
-
),
|
|
552
|
+
conformant: drift.length === 0,
|
|
553
|
+
drift,
|
|
686
554
|
ownDeclarations,
|
|
687
555
|
unreadable: null
|
|
688
556
|
};
|
|
689
557
|
}
|
|
690
558
|
|
|
559
|
+
// src/roles/build/adapters/vite/vite-role.adapter.ts
|
|
560
|
+
import { spawnSync } from "child_process";
|
|
561
|
+
|
|
562
|
+
// src/core/config/tool-args.ts
|
|
563
|
+
import { existsSync as existsSync6 } from "fs";
|
|
564
|
+
import { isAbsolute, resolve as resolve2 } from "path";
|
|
565
|
+
function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
566
|
+
const takesValue = new Set(valueFlags);
|
|
567
|
+
const options = [];
|
|
568
|
+
const paths = [];
|
|
569
|
+
let previousTakesValue = false;
|
|
570
|
+
for (const arg of toolArgs) {
|
|
571
|
+
const looksLikeOption = arg.startsWith("-");
|
|
572
|
+
const target = isAbsolute(arg) ? arg : resolve2(cwd, arg);
|
|
573
|
+
if (!previousTakesValue && !looksLikeOption && existsSync6(target)) paths.push(arg);
|
|
574
|
+
else options.push(arg);
|
|
575
|
+
previousTakesValue = !arg.includes("=") && takesValue.has(arg);
|
|
576
|
+
}
|
|
577
|
+
return { options, paths };
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/shared/resolve-bin.ts
|
|
581
|
+
import { existsSync as existsSync7 } from "fs";
|
|
582
|
+
import { createRequire } from "module";
|
|
583
|
+
import { delimiter, dirname as dirname2, join as join6 } from "path";
|
|
584
|
+
var require2 = createRequire(import.meta.url);
|
|
585
|
+
function resolveBin(fromDir, name) {
|
|
586
|
+
let dir = fromDir;
|
|
587
|
+
for (; ; ) {
|
|
588
|
+
const candidate = join6(dir, "node_modules", ".bin", name);
|
|
589
|
+
if (existsSync7(candidate)) return candidate;
|
|
590
|
+
const parent = dirname2(dir);
|
|
591
|
+
if (parent === dir) return void 0;
|
|
592
|
+
dir = parent;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function binFromOwnInstall(packageName, binName) {
|
|
596
|
+
try {
|
|
597
|
+
const manifest = require2.resolve(`${packageName}/package.json`);
|
|
598
|
+
const bin = require2(manifest).bin;
|
|
599
|
+
const relative5 = typeof bin === "string" ? bin : bin?.[binName];
|
|
600
|
+
if (!relative5) return void 0;
|
|
601
|
+
const executable = join6(dirname2(manifest), relative5);
|
|
602
|
+
return existsSync7(executable) ? executable : void 0;
|
|
603
|
+
} catch {
|
|
604
|
+
return void 0;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function binSearchPath(cwd) {
|
|
608
|
+
return [`${cwd}${delimiter}node_modules/.bin (and parents)`, "sentinel's own install"].join(
|
|
609
|
+
" then "
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/roles/build/plan.ts
|
|
614
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
615
|
+
import { join as join11 } from "path";
|
|
616
|
+
|
|
617
|
+
// src/core/config/existing-command.ts
|
|
618
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
619
|
+
import { join as join7 } from "path";
|
|
620
|
+
|
|
621
|
+
// src/shared/jsonc.ts
|
|
622
|
+
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
623
|
+
function parseJsonc(text, source = "config") {
|
|
624
|
+
const errors = [];
|
|
625
|
+
const value = parse(text, errors, { allowTrailingComma: true });
|
|
626
|
+
if (errors.length > 0) {
|
|
627
|
+
const details = errors.map((error) => printParseErrorCode(error.error)).join(", ");
|
|
628
|
+
throw new Error(`${source}: malformed JSONC (${details}).`);
|
|
629
|
+
}
|
|
630
|
+
return value;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// src/core/config/existing-command.ts
|
|
634
|
+
function nxTargetCommand(cwd, target) {
|
|
635
|
+
let project;
|
|
636
|
+
try {
|
|
637
|
+
project = parseJsonc(
|
|
638
|
+
readFileSync6(join7(cwd, "project.json"), "utf8"),
|
|
639
|
+
"project.json"
|
|
640
|
+
);
|
|
641
|
+
} catch {
|
|
642
|
+
return void 0;
|
|
643
|
+
}
|
|
644
|
+
const entry = project.targets?.[target];
|
|
645
|
+
if (!entry || entry.executor !== "nx:run-commands") return void 0;
|
|
646
|
+
const { command, commands, cwd: targetCwd } = entry.options ?? {};
|
|
647
|
+
const ranInModule = typeof targetCwd === "string" && targetCwd.trim() !== "";
|
|
648
|
+
if (typeof command === "string" && command.trim() !== "") {
|
|
649
|
+
return { command: command.trim(), ranInModule };
|
|
650
|
+
}
|
|
651
|
+
if (Array.isArray(commands)) {
|
|
652
|
+
const parts = commands.filter((c) => typeof c === "string" && c.trim() !== "");
|
|
653
|
+
if (parts.length > 0) return { command: parts.map((c) => c.trim()).join(" && "), ranInModule };
|
|
654
|
+
}
|
|
655
|
+
return void 0;
|
|
656
|
+
}
|
|
657
|
+
function rootedForScript(command) {
|
|
658
|
+
return command.replace(/(^|&&\s*)pnpm exec /g, "$1pnpm --workspace-root exec ");
|
|
659
|
+
}
|
|
660
|
+
function existingCommand(cwd, target) {
|
|
661
|
+
const script = moduleScripts(cwd)[target];
|
|
662
|
+
if (script !== void 0 && script.trim() !== "") return script;
|
|
663
|
+
const fromTarget = nxTargetCommand(cwd, target);
|
|
664
|
+
if (fromTarget === void 0) return void 0;
|
|
665
|
+
return fromTarget.ranInModule ? fromTarget.command : rootedForScript(fromTarget.command);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// src/core/config/nx-target.ts
|
|
669
|
+
import { existsSync as existsSync8 } from "fs";
|
|
670
|
+
import { join as join8 } from "path";
|
|
671
|
+
function nxTargetMetadataOperations(options) {
|
|
672
|
+
const { targets } = options;
|
|
673
|
+
if (Object.keys(targets).length === 0) return [];
|
|
674
|
+
return [{ kind: "merge-json", path: "package.json", value: { nx: { targets } } }];
|
|
675
|
+
}
|
|
676
|
+
function nxTargetOperations(options) {
|
|
677
|
+
const { cwd, targets } = options;
|
|
678
|
+
const names = Object.keys(targets);
|
|
679
|
+
if (names.length === 0) return [];
|
|
680
|
+
const operations = [];
|
|
681
|
+
if (existsSync8(join8(cwd, "project.json"))) {
|
|
682
|
+
operations.push({
|
|
683
|
+
kind: "remove-json-keys",
|
|
684
|
+
path: "project.json",
|
|
685
|
+
keys: names.map((name) => ["targets", name])
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
operations.push(...nxTargetMetadataOperations(options));
|
|
689
|
+
return operations;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/core/config/tool-script.ts
|
|
693
|
+
var SEGMENT_SEPARATOR = /(\s*(?:&&|\|\||;|&|\|)\s*)/;
|
|
694
|
+
function isSeparatorAt(index) {
|
|
695
|
+
return index % 2 === 1;
|
|
696
|
+
}
|
|
697
|
+
function invokes(segment, binary) {
|
|
698
|
+
return new RegExp(String.raw`(^|[\s/])${binary}(\s|$)`).test(segment.trim());
|
|
699
|
+
}
|
|
700
|
+
function isSentinelSegment(segment, roleFlag) {
|
|
701
|
+
const runsSentinel = invokes(segment, "sentinel") || segment.includes("bin/sentinel.js");
|
|
702
|
+
return runsSentinel && new RegExp(String.raw`(^|\s)${roleFlag}(\s|$)`).test(segment);
|
|
703
|
+
}
|
|
704
|
+
function composeToolScript(existing, options) {
|
|
705
|
+
const { command } = options;
|
|
706
|
+
if (!existing || existing.trim() === "") return command;
|
|
707
|
+
const { roleFlag } = options;
|
|
708
|
+
const isReplaceable = (segment) => options.replaces(segment) || roleFlag !== void 0 && isSentinelSegment(segment, roleFlag);
|
|
709
|
+
const parts = existing.split(SEGMENT_SEPARATOR);
|
|
710
|
+
const commands = parts.filter((_, index) => !isSeparatorAt(index));
|
|
711
|
+
if (!commands.some(isReplaceable)) return existing;
|
|
712
|
+
let replacedOnce = false;
|
|
713
|
+
const rebuilt = parts.map((part, index) => {
|
|
714
|
+
if (isSeparatorAt(index) || !isReplaceable(part)) return part;
|
|
715
|
+
if (replacedOnce) return null;
|
|
716
|
+
replacedOnce = true;
|
|
717
|
+
return options.keepFix && /(^|\s)--fix(\s|$)/.test(part) ? `${command} --fix` : command;
|
|
718
|
+
});
|
|
719
|
+
const kept = [];
|
|
720
|
+
for (let index = 0; index < rebuilt.length; index += 1) {
|
|
721
|
+
const part = rebuilt[index];
|
|
722
|
+
if (part === null) {
|
|
723
|
+
if (kept.length > 0) kept.pop();
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
kept.push(part);
|
|
727
|
+
}
|
|
728
|
+
return kept.join("").trim();
|
|
729
|
+
}
|
|
730
|
+
function keepsOtherCommands(script, sentinelCommand) {
|
|
731
|
+
return script.split(SEGMENT_SEPARATOR).filter((_, index) => !isSeparatorAt(index)).some((segment) => segment.trim() !== "" && segment.trim() !== sentinelCommand);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// src/roles/format/read-adoption.ts
|
|
735
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
736
|
+
import { join as join9 } from "path";
|
|
737
|
+
|
|
738
|
+
// src/roles/format/config-policy.ts
|
|
739
|
+
var FORMAT_CONFIG_FILE = ".oxfmtrc.json";
|
|
740
|
+
var FORMAT_SCRIPT_NAME = "format";
|
|
741
|
+
var PROVENANCE_KEY = "$sentinel";
|
|
742
|
+
var PERMITTED_LOCAL_KEYS = [
|
|
743
|
+
"useTabs",
|
|
744
|
+
"tabWidth",
|
|
745
|
+
"printWidth",
|
|
746
|
+
"semi",
|
|
747
|
+
"singleQuote",
|
|
748
|
+
"jsxSingleQuote",
|
|
749
|
+
"trailingComma",
|
|
750
|
+
"quoteProps",
|
|
751
|
+
"bracketSpacing",
|
|
752
|
+
"bracketSameLine",
|
|
753
|
+
"arrowParens",
|
|
754
|
+
"endOfLine",
|
|
755
|
+
"objectWrap",
|
|
756
|
+
"proseWrap",
|
|
757
|
+
"experimentalOperatorPosition"
|
|
758
|
+
];
|
|
759
|
+
var ALWAYS_LOCAL_KEYS = ["ignorePatterns", "overrides"];
|
|
760
|
+
var FORMAT_DEFAULT_IGNORES = ["**/*.toml"];
|
|
761
|
+
var PRETTIER_CONFIG_FILES = [
|
|
762
|
+
".prettierrc",
|
|
763
|
+
".prettierrc.json",
|
|
764
|
+
".prettierrc.json5",
|
|
765
|
+
".prettierrc.yml",
|
|
766
|
+
".prettierrc.yaml",
|
|
767
|
+
".prettierrc.js",
|
|
768
|
+
".prettierrc.cjs",
|
|
769
|
+
".prettierrc.mjs",
|
|
770
|
+
"prettier.config.js",
|
|
771
|
+
"prettier.config.cjs",
|
|
772
|
+
"prettier.config.mjs"
|
|
773
|
+
];
|
|
774
|
+
function formatTargets() {
|
|
775
|
+
return {
|
|
776
|
+
[FORMAT_SCRIPT_NAME]: {
|
|
777
|
+
cache: true,
|
|
778
|
+
inputs: ["default", `{projectRoot}/${FORMAT_CONFIG_FILE}`]
|
|
779
|
+
},
|
|
780
|
+
[`${FORMAT_SCRIPT_NAME}:fix`]: { cache: false }
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/roles/format/presets/base.json
|
|
785
|
+
var base_default = {
|
|
786
|
+
printWidth: 80,
|
|
787
|
+
semi: false,
|
|
788
|
+
singleQuote: true,
|
|
789
|
+
trailingComma: "all",
|
|
790
|
+
sortPackageJson: false,
|
|
791
|
+
sortImports: false
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
// src/roles/format/presets/nest-imports.json
|
|
795
|
+
var nest_imports_default = {
|
|
796
|
+
customGroups: [
|
|
797
|
+
{
|
|
798
|
+
groupName: "workspace",
|
|
799
|
+
elementNamePattern: ["@hublo/**", "@shared/**", "@front/**", "@network/**"]
|
|
800
|
+
}
|
|
801
|
+
],
|
|
802
|
+
groups: ["builtin", "external", "workspace", ["parent", "index"], "sibling"],
|
|
803
|
+
newlinesBetween: true,
|
|
804
|
+
order: "asc",
|
|
805
|
+
ignoreCase: false
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
// src/roles/format/preset-data.ts
|
|
809
|
+
var FORMAT_PRESETS = ["base", "svelte", "nest"];
|
|
810
|
+
function hasFormatPreset(name) {
|
|
811
|
+
return FORMAT_PRESETS.includes(name);
|
|
812
|
+
}
|
|
813
|
+
function formatPresetName(declared) {
|
|
814
|
+
if (declared === "svelte") return "svelte";
|
|
815
|
+
if (declared === "nest") return "nest";
|
|
816
|
+
return "base";
|
|
817
|
+
}
|
|
818
|
+
function formatPresetFor(preset) {
|
|
819
|
+
const base = base_default;
|
|
820
|
+
const name = formatPresetName(preset);
|
|
821
|
+
if (name === "svelte") return { ...base, svelte: true };
|
|
822
|
+
if (name === "nest") return { ...base, sortImports: nest_imports_default };
|
|
823
|
+
return base;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// src/roles/format/read-adoption.ts
|
|
827
|
+
var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
|
|
828
|
+
configFile,
|
|
829
|
+
preset: null,
|
|
830
|
+
adopted: false,
|
|
831
|
+
conformant: false,
|
|
832
|
+
drift: [],
|
|
833
|
+
local: {},
|
|
834
|
+
presetVersion: null,
|
|
835
|
+
effective: {},
|
|
836
|
+
unreadable
|
|
837
|
+
});
|
|
838
|
+
function sameValue(a, b) {
|
|
839
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
840
|
+
}
|
|
841
|
+
function readFormatAdoption(cwd) {
|
|
842
|
+
const path = join9(cwd, FORMAT_CONFIG_FILE);
|
|
843
|
+
if (!existsSync9(path)) return NOT_ADOPTED2(null);
|
|
844
|
+
let parsed;
|
|
845
|
+
try {
|
|
846
|
+
parsed = parseJsonc(readFileSync7(path, "utf8"), FORMAT_CONFIG_FILE);
|
|
847
|
+
} catch (error) {
|
|
848
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
849
|
+
return NOT_ADOPTED2(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
|
|
850
|
+
}
|
|
851
|
+
const provenance = parsed[PROVENANCE_KEY];
|
|
852
|
+
if (!provenance || typeof provenance.preset !== "string") return NOT_ADOPTED2(FORMAT_CONFIG_FILE);
|
|
853
|
+
if (!hasFormatPreset(provenance.preset)) {
|
|
854
|
+
return NOT_ADOPTED2(
|
|
855
|
+
FORMAT_CONFIG_FILE,
|
|
856
|
+
`${FORMAT_CONFIG_FILE} declares the format preset "${provenance.preset}", which sentinel does not ship (shipped: ${FORMAT_PRESETS.join(", ")}). Re-run \`sentinel --init --format\`.`
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
const declaredLocal = Array.isArray(provenance.local) ? provenance.local : [];
|
|
860
|
+
const preset = formatPresetFor(provenance.preset);
|
|
861
|
+
const drift = [];
|
|
862
|
+
const local = {};
|
|
863
|
+
for (const [key, expected] of Object.entries(preset)) {
|
|
864
|
+
if (declaredLocal.includes(key)) {
|
|
865
|
+
local[key] = parsed[key];
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
if (!(key in parsed) || !sameValue(parsed[key], expected)) drift.push(key);
|
|
869
|
+
}
|
|
870
|
+
for (const key of declaredLocal) {
|
|
871
|
+
if (!(key in preset)) local[key] = parsed[key];
|
|
872
|
+
}
|
|
873
|
+
for (const key of ALWAYS_LOCAL_KEYS) {
|
|
874
|
+
if (key in parsed) local[key] = parsed[key];
|
|
875
|
+
}
|
|
876
|
+
return {
|
|
877
|
+
configFile: FORMAT_CONFIG_FILE,
|
|
878
|
+
preset: provenance.preset,
|
|
879
|
+
adopted: true,
|
|
880
|
+
conformant: drift.length === 0,
|
|
881
|
+
drift,
|
|
882
|
+
local,
|
|
883
|
+
presetVersion: typeof provenance.version === "string" ? provenance.version : null,
|
|
884
|
+
effective: parsed,
|
|
885
|
+
unreadable: null
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/roles/build/dev-script.ts
|
|
890
|
+
var CHAIN = " -- ";
|
|
891
|
+
var VITE_TOKEN = /(^|\s|\/)vite(\s|$)/;
|
|
892
|
+
var DEV_SUBCOMMANDS = /* @__PURE__ */ new Set(["dev", "serve"]);
|
|
893
|
+
function appOptions(rest) {
|
|
894
|
+
const tokens = (rest ?? "").trim().split(/\s+/).filter(Boolean);
|
|
895
|
+
const kept = tokens.filter((token, at) => !(at === 0 && DEV_SUBCOMMANDS.has(token)));
|
|
896
|
+
return kept.join(" ");
|
|
897
|
+
}
|
|
898
|
+
function composeDevScript(existing, command) {
|
|
899
|
+
if (existing === void 0 || existing.trim() === "") return command;
|
|
900
|
+
const chunks = existing.trim().split(CHAIN);
|
|
901
|
+
const at = chunks.map((chunk) => VITE_TOKEN.test(chunk)).lastIndexOf(true);
|
|
902
|
+
if (at === -1) return existing;
|
|
903
|
+
const rest = (chunks[at] ?? "").replace(/^.*?(^|\s|\/)vite(\s|$)/, "");
|
|
904
|
+
const options = appOptions(rest);
|
|
905
|
+
chunks[at] = options === "" ? `${command} --` : `${command} -- ${options}`;
|
|
906
|
+
return chunks.join(CHAIN);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// src/roles/build/dev-target.ts
|
|
910
|
+
var DELEGATION_KEYS = /* @__PURE__ */ new Set(["executor", "options"]);
|
|
911
|
+
var DELEGATION_OPTION_KEYS = /* @__PURE__ */ new Set(["command", "cwd"]);
|
|
912
|
+
function delegatesToScript(target, scriptName) {
|
|
913
|
+
if (typeof target !== "object" || target === null || Array.isArray(target)) return false;
|
|
914
|
+
const entries = target;
|
|
915
|
+
if (!Object.keys(entries).every((key) => DELEGATION_KEYS.has(key))) return false;
|
|
916
|
+
if (entries["executor"] !== "nx:run-commands") return false;
|
|
917
|
+
const options = entries["options"];
|
|
918
|
+
if (typeof options !== "object" || options === null || Array.isArray(options)) return false;
|
|
919
|
+
const declared = options;
|
|
920
|
+
if (!Object.keys(declared).every((key) => DELEGATION_OPTION_KEYS.has(key))) return false;
|
|
921
|
+
const command = declared["command"];
|
|
922
|
+
if (typeof command !== "string") return false;
|
|
923
|
+
return new RegExp(String.raw`(^|\s)run\s+${scriptName}$`).test(command.trim());
|
|
924
|
+
}
|
|
925
|
+
function devScriptIsAdopted(devScript) {
|
|
926
|
+
return devScript !== void 0 && devScript.includes(SENTINEL_DEV_COMMAND);
|
|
927
|
+
}
|
|
928
|
+
function devTargetOperations(cwd, devScript) {
|
|
929
|
+
if (!devScriptIsAdopted(devScript)) return { operations: [] };
|
|
930
|
+
const target = projectTargets(cwd)?.[DEV_SCRIPT_NAME];
|
|
931
|
+
if (target === void 0) return { operations: [] };
|
|
932
|
+
if (!delegatesToScript(target, DEV_SCRIPT_NAME)) {
|
|
933
|
+
return {
|
|
934
|
+
operations: [],
|
|
935
|
+
note: `left the \`${DEV_SCRIPT_NAME}\` target in project.json alone: it does more than call \`pnpm run ${DEV_SCRIPT_NAME}\`, so removing it would drop behaviour this role never declared. Move what it does into the script, or keep it and accept the second home.`
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
return {
|
|
939
|
+
operations: [
|
|
940
|
+
{ kind: "remove-json-keys", path: "project.json", keys: [["targets", DEV_SCRIPT_NAME]] }
|
|
941
|
+
],
|
|
942
|
+
note: `removed the \`${DEV_SCRIPT_NAME}\` target from project.json: it only called \`pnpm run ${DEV_SCRIPT_NAME}\`, which nx infers from the script itself. host-admin has run without that target all along.`
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// src/roles/build/nest/adopt.ts
|
|
947
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
948
|
+
import { join as join10 } from "path";
|
|
949
|
+
var NEST_CONFIG_FILE = "vite.config.mts";
|
|
950
|
+
function asSource(value, depth) {
|
|
951
|
+
const pad = " ".repeat(depth);
|
|
952
|
+
const inner = " ".repeat(depth + 1);
|
|
953
|
+
if (Array.isArray(value)) {
|
|
954
|
+
if (value.length === 0) return "[]";
|
|
955
|
+
return `[
|
|
956
|
+
${value.map((item) => `${inner}${asSource(item, depth + 1)}`).join(",\n")},
|
|
957
|
+
${pad}]`;
|
|
958
|
+
}
|
|
959
|
+
if (value !== null && typeof value === "object") {
|
|
960
|
+
const entries = Object.entries(value);
|
|
961
|
+
if (entries.length === 0) return "{}";
|
|
962
|
+
return `{
|
|
963
|
+
${entries.map(([key, item]) => `${inner}${asKey(key)}: ${asSource(item, depth + 1)}`).join(",\n")},
|
|
964
|
+
${pad}}`;
|
|
965
|
+
}
|
|
966
|
+
if (typeof value === "string") return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
967
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
968
|
+
throw new Error(
|
|
969
|
+
`sentinel build(nest): cannot write ${typeof value} into the generated config. The build target carries a shape this translation was not written for, and it should refuse rather than emit something nobody has read.`
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
function asKey(key) {
|
|
973
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : `'${key}'`;
|
|
974
|
+
}
|
|
975
|
+
function nestConfigSource(cwd, target) {
|
|
976
|
+
const hops = workspaceRootHops(cwd, target);
|
|
977
|
+
const up = Array.from({ length: hops }, () => "..").join("/");
|
|
978
|
+
const carried = (name, value) => value === void 0 || value.length === 0 ? "" : ` ${name}: ${asSource(value, 1)},
|
|
979
|
+
`;
|
|
980
|
+
return `import path from 'node:path'
|
|
981
|
+
|
|
982
|
+
import { nestService } from '${BUILD_PRESET_SPECIFIERS.nest}'
|
|
983
|
+
|
|
984
|
+
export default nestService({
|
|
985
|
+
project: '${target.project}',
|
|
986
|
+
root: __dirname,
|
|
987
|
+
workspaceRoot: path.resolve(__dirname, '${up}'),
|
|
988
|
+
outDir: '${target.outputPath}',
|
|
989
|
+
${carried("transformers", target.transformers)}${carried("assets", target.assets)}})
|
|
990
|
+
`;
|
|
991
|
+
}
|
|
992
|
+
function nestBuildOutputs(target) {
|
|
993
|
+
return [`{workspaceRoot}/${target.outputPath}`];
|
|
994
|
+
}
|
|
995
|
+
var OUT_DIR_IN_CONFIG = /^\s*outDir:\s*'([^']+)'/m;
|
|
996
|
+
function nestOutputsFromConfig(cwd) {
|
|
997
|
+
const configPath = join10(cwd, NEST_CONFIG_FILE);
|
|
998
|
+
if (!existsSync10(configPath)) return void 0;
|
|
999
|
+
const outDir = OUT_DIR_IN_CONFIG.exec(readFileSync8(configPath, "utf8"))?.[1];
|
|
1000
|
+
return outDir === void 0 ? void 0 : [`{workspaceRoot}/${outDir}`];
|
|
1001
|
+
}
|
|
1002
|
+
function nestAdoptionOperations(cwd, target) {
|
|
1003
|
+
return [{ kind: "write", path: NEST_CONFIG_FILE, contents: nestConfigSource(cwd, target) }];
|
|
1004
|
+
}
|
|
1005
|
+
|
|
691
1006
|
// src/roles/build/requirements.ts
|
|
692
1007
|
function atLeast(declared, needed) {
|
|
693
1008
|
const parse2 = (value) => value.replace(/^[\^~>=<\s]+/, "").split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -716,32 +1031,26 @@ function describeUnmet(preset, unmet) {
|
|
|
716
1031
|
return `the build role does not apply to this module yet: ${list}. ${entry?.why ?? ""}. Nothing was written. Raise those versions, check the module still builds and its tests still pass, then re-run \`sentinel --init --build\`.`;
|
|
717
1032
|
}
|
|
718
1033
|
|
|
719
|
-
// src/
|
|
1034
|
+
// src/core/config/rewrite-imports.ts
|
|
720
1035
|
import { parseSync } from "oxc-parser";
|
|
721
|
-
var
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
if (packageType === "module") return void 0;
|
|
725
|
-
return `sentinel is ESM only, and this module does not declare \`"type": "module"\`, so Vite would load ${fileName} as CommonJS and fail to require it. Add \`"type": "module"\` to this module's package.json, or rename the config to \`${fileName.replace(/\.(ts|js)$/, ".m$1")}\`, then run this again.`;
|
|
726
|
-
}
|
|
727
|
-
var DEFAULT_EXPORT_NAMES = Object.fromEntries(
|
|
728
|
-
OWNED_PACKAGES.filter((entry) => entry.default !== void 0).map((entry) => [
|
|
729
|
-
entry.specifier,
|
|
730
|
-
entry.default
|
|
731
|
-
])
|
|
1036
|
+
var movedSpecifiers = (toolchain) => new Set(toolchain.owned.map((entry) => entry.specifier));
|
|
1037
|
+
var defaultExportNames = (toolchain) => Object.fromEntries(
|
|
1038
|
+
toolchain.owned.filter((entry) => entry.default !== void 0).map((entry) => [entry.specifier, entry.default])
|
|
732
1039
|
);
|
|
733
|
-
function renderImport(bindings) {
|
|
1040
|
+
function renderImport(bindings, presetSpecifier) {
|
|
734
1041
|
const render = (list) => list.map((b) => b.exported === b.local ? b.exported : `${b.exported} as ${b.local}`).sort((left, right) => left.localeCompare(right)).join(", ");
|
|
735
1042
|
const values = bindings.filter((b) => !b.typeOnly);
|
|
736
1043
|
const types = bindings.filter((b) => b.typeOnly);
|
|
737
1044
|
const lines = [];
|
|
738
|
-
if (values.length > 0) lines.push(`import { ${render(values)} } from '${
|
|
1045
|
+
if (values.length > 0) lines.push(`import { ${render(values)} } from '${presetSpecifier}'`);
|
|
739
1046
|
if (types.length > 0) {
|
|
740
|
-
lines.push(`import type { ${render(types)} } from '${
|
|
1047
|
+
lines.push(`import type { ${render(types)} } from '${presetSpecifier}'`);
|
|
741
1048
|
}
|
|
742
1049
|
return lines.join("\n");
|
|
743
1050
|
}
|
|
744
|
-
function
|
|
1051
|
+
function rewriteToolchainImports(toolchain, source, fileName) {
|
|
1052
|
+
const moved = movedSpecifiers(toolchain);
|
|
1053
|
+
const defaults = defaultExportNames(toolchain);
|
|
745
1054
|
const { program, errors } = parseSync(fileName, source, { sourceType: "module" });
|
|
746
1055
|
if (errors.length > 0) {
|
|
747
1056
|
return {
|
|
@@ -752,10 +1061,10 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
|
752
1061
|
const imports = program.body.filter(
|
|
753
1062
|
(node) => node.type === "ImportDeclaration"
|
|
754
1063
|
);
|
|
755
|
-
if (imports.some((node) => node.source.value ===
|
|
1064
|
+
if (imports.some((node) => node.source.value === toolchain.presetSpecifier)) {
|
|
756
1065
|
return { kind: "unchanged", why: "this config already imports its toolchain from sentinel" };
|
|
757
1066
|
}
|
|
758
|
-
const moving = imports.filter((node) =>
|
|
1067
|
+
const moving = imports.filter((node) => moved.has(node.source.value));
|
|
759
1068
|
if (moving.length === 0) {
|
|
760
1069
|
return {
|
|
761
1070
|
kind: "unchanged",
|
|
@@ -772,7 +1081,7 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
|
772
1081
|
for (const spec of specifiers) {
|
|
773
1082
|
const typeOnly = node.importKind === "type" || spec.importKind === "type";
|
|
774
1083
|
if (spec.type === "ImportDefaultSpecifier") {
|
|
775
|
-
const exported =
|
|
1084
|
+
const exported = defaults[from];
|
|
776
1085
|
if (!exported) {
|
|
777
1086
|
return {
|
|
778
1087
|
kind: "blocked",
|
|
@@ -804,7 +1113,7 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
|
804
1113
|
const after = out.slice(span.end).match(/^\r?\n/)?.[0].length ?? 0;
|
|
805
1114
|
out = out.slice(0, span.start) + out.slice(span.end + after);
|
|
806
1115
|
}
|
|
807
|
-
out = out.slice(0, first.start) + renderImport(bindings) + out.slice(first.end);
|
|
1116
|
+
out = out.slice(0, first.start) + renderImport(bindings, toolchain.presetSpecifier) + out.slice(first.end);
|
|
808
1117
|
return {
|
|
809
1118
|
kind: "rewritten",
|
|
810
1119
|
source: out,
|
|
@@ -812,7 +1121,19 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
|
812
1121
|
};
|
|
813
1122
|
}
|
|
814
1123
|
|
|
1124
|
+
// src/roles/build/rewrite-imports.ts
|
|
1125
|
+
var BUILD_TOOLCHAIN = {
|
|
1126
|
+
presetSpecifier: BUILD_PRESET_SPECIFIER,
|
|
1127
|
+
owned: OWNED_PACKAGES
|
|
1128
|
+
};
|
|
1129
|
+
function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
1130
|
+
return rewriteToolchainImports(BUILD_TOOLCHAIN, source, fileName);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
815
1133
|
// src/roles/build/plan.ts
|
|
1134
|
+
function candidateOutputs() {
|
|
1135
|
+
return BUILD_OUTPUT_CANDIDATES.map((name) => `{projectRoot}/${name}`);
|
|
1136
|
+
}
|
|
816
1137
|
function ownedBuildDependencies(cwd) {
|
|
817
1138
|
const manifest = readProjectPackageJson(cwd);
|
|
818
1139
|
const keys = [];
|
|
@@ -844,9 +1165,9 @@ function buildScripts(cwd) {
|
|
|
844
1165
|
const own2 = moduleName(cwd);
|
|
845
1166
|
scripts[DEV_SCRIPT_NAME] = composeDevScript(
|
|
846
1167
|
existingDev,
|
|
847
|
-
//
|
|
848
|
-
//
|
|
849
|
-
`${selfCommand(cwd, "--run --dev") ?? SENTINEL_DEV_COMMAND} --module ${own2}
|
|
1168
|
+
// No trailing `--` here: `composeDevScript` owns the separator, and adding one on both
|
|
1169
|
+
// sides produced `-- --`, handing Vite an argument nobody wrote.
|
|
1170
|
+
`${selfCommand(cwd, "--run --dev") ?? SENTINEL_DEV_COMMAND} --module ${own2}`
|
|
850
1171
|
);
|
|
851
1172
|
}
|
|
852
1173
|
return scripts;
|
|
@@ -860,70 +1181,73 @@ function plan(context) {
|
|
|
860
1181
|
if (unmet.length > 0) {
|
|
861
1182
|
return { operations: [], skipped: describeUnmet(context.preset, unmet) };
|
|
862
1183
|
}
|
|
863
|
-
|
|
1184
|
+
const declaredPreset2 = declaredBuildPreset(context.cwd);
|
|
1185
|
+
const configFile = buildConfigFile(context.cwd);
|
|
1186
|
+
if (declaredPreset2 === void 0 && configFile !== void 0) {
|
|
864
1187
|
return {
|
|
865
1188
|
operations: [],
|
|
866
|
-
skipped: `this module has a Vite config, but it
|
|
1189
|
+
skipped: `this module has a Vite config, but it builds neither a React app (no @vitejs/plugin-react or @tanstack/react-start) nor a Nest service (no ${BUILD_PRESET_SPECIFIERS.nest}). Those are the two families this role serves.`
|
|
867
1190
|
};
|
|
868
1191
|
}
|
|
869
|
-
const notes = [];
|
|
870
|
-
const operations = [];
|
|
871
|
-
const configFile = buildConfigFile(context.cwd);
|
|
872
1192
|
const webpackTarget = webpackBuildTarget(context.cwd);
|
|
873
|
-
if (
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1193
|
+
if (declaredBuildPreset(context.cwd) === "nest" || webpackTarget !== void 0) {
|
|
1194
|
+
return planNestService(context, webpackTarget);
|
|
1195
|
+
}
|
|
1196
|
+
if (configFile === void 0) {
|
|
877
1197
|
return {
|
|
878
|
-
operations:
|
|
879
|
-
|
|
880
|
-
`wrote ${NEST_CONFIG_FILE} and pointed the nx build target at sentinel. The target stays \`nx:run-commands\` with \`forwardAllArgs: false\`, so the image's \`--generatePackageJson\` still works and the Dockerfile needs no change.`,
|
|
881
|
-
...dropped.length > 0 ? [
|
|
882
|
-
`dropped the \`${dropped.join("`, `")}\` configuration${dropped.length > 1 ? "s" : ""} from the build target: its fileReplacements point at files that do not exist, so it replaced nothing. A live one would have blocked this instead.`
|
|
883
|
-
] : [],
|
|
884
|
-
`verify with \`nx run ${webpackTarget.project}:generate-swagger-file\` and \`git diff --exit-code\`: it runs the built bundle, so it proves the service boots AND that its published contract did not move.`
|
|
885
|
-
]
|
|
1198
|
+
operations: [],
|
|
1199
|
+
skipped: `no Vite config in this module, so there is nothing to point at sentinel. This role moves an existing config's imports; it does not introduce a bundler. If this module builds another way (38 services here build through \`@nx/webpack\`), the build role does not apply to it.`
|
|
886
1200
|
};
|
|
887
1201
|
}
|
|
888
|
-
|
|
1202
|
+
return planReactApp(context, configFile);
|
|
1203
|
+
}
|
|
1204
|
+
function planReactApp(context, configFile) {
|
|
1205
|
+
const notes = [];
|
|
1206
|
+
const operations = [];
|
|
1207
|
+
const esm = esmReadiness(configFile, readProjectPackageJson(context.cwd).type);
|
|
1208
|
+
if (esm.kind === "blocked") return { operations: [], skipped: esm.why };
|
|
1209
|
+
const rewrite = rewriteBuildImports(readConfigSource(context.cwd, configFile), configFile);
|
|
1210
|
+
if (rewrite.kind === "blocked") {
|
|
889
1211
|
return {
|
|
890
1212
|
operations: [],
|
|
891
|
-
skipped:
|
|
1213
|
+
skipped: `${configFile} could not be pointed at sentinel, so nothing was written: ${rewrite.why}. Nothing here is broken; this needs a look before the role applies.`
|
|
892
1214
|
};
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
}
|
|
906
|
-
for (const name of COMPANION_CONFIG_FILES) {
|
|
907
|
-
const source = readConfigSource(context.cwd, name);
|
|
908
|
-
if (source === "") continue;
|
|
909
|
-
const companion = rewriteBuildImports(source, name);
|
|
910
|
-
if (companion.kind === "rewritten") {
|
|
911
|
-
operations.push({ kind: "write", path: name, contents: companion.source });
|
|
912
|
-
notes.push(`${name} now imports ${companion.moved.join(", ")} from sentinel too.`);
|
|
913
|
-
} else if (companion.kind === "blocked") {
|
|
914
|
-
notes.push(
|
|
915
|
-
`${name} imports the toolchain and could NOT be pointed at sentinel: ${companion.why}. It will stop resolving when the workspace root drops these packages, and it will fail your TESTS rather than your build.`
|
|
916
|
-
);
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
if (rewrite.kind === "rewritten") {
|
|
1215
|
+
}
|
|
1216
|
+
if (rewrite.kind === "rewritten") {
|
|
1217
|
+
operations.push({ kind: "write", path: configFile, contents: rewrite.source });
|
|
1218
|
+
}
|
|
1219
|
+
for (const name of COMPANION_CONFIG_FILES) {
|
|
1220
|
+
const source = readConfigSource(context.cwd, name);
|
|
1221
|
+
if (source === "") continue;
|
|
1222
|
+
const companion = rewriteBuildImports(source, name);
|
|
1223
|
+
if (companion.kind === "rewritten") {
|
|
1224
|
+
operations.push({ kind: "write", path: name, contents: companion.source });
|
|
1225
|
+
notes.push(`${name} now imports ${companion.moved.join(", ")} from sentinel too.`);
|
|
1226
|
+
} else if (companion.kind === "blocked") {
|
|
920
1227
|
notes.push(
|
|
921
|
-
`${
|
|
1228
|
+
`${name} imports the toolchain and could NOT be pointed at sentinel: ${companion.why}. It will stop resolving when the workspace root drops these packages, and it will fail your TESTS rather than your build.`
|
|
922
1229
|
);
|
|
923
1230
|
}
|
|
924
1231
|
}
|
|
925
|
-
|
|
926
|
-
|
|
1232
|
+
if (rewrite.kind === "rewritten") {
|
|
1233
|
+
notes.push(
|
|
1234
|
+
`${configFile} now imports ${rewrite.moved.join(", ")} from \`${BUILD_PRESET_SPECIFIER}\`. Only the import lines changed: every attribute, alias and path is untouched, so this build produces what it produced before.`
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
const scripts = buildScripts(context.cwd);
|
|
1238
|
+
operations.push(manifestOperation(context.cwd, scripts));
|
|
1239
|
+
operations.push(
|
|
1240
|
+
...nxTargetOperations({
|
|
1241
|
+
cwd: context.cwd,
|
|
1242
|
+
targets: buildTarget({
|
|
1243
|
+
moduleName: adoptedPackageName(context.cwd),
|
|
1244
|
+
outputs: candidateOutputs()
|
|
1245
|
+
})
|
|
1246
|
+
})
|
|
1247
|
+
);
|
|
1248
|
+
const dev = devTargetOperations(context.cwd, scripts[DEV_SCRIPT_NAME]);
|
|
1249
|
+
operations.push(...dev.operations);
|
|
1250
|
+
if (dev.note !== void 0) notes.push(dev.note);
|
|
927
1251
|
const owned = ownedBuildDependencies(context.cwd);
|
|
928
1252
|
if (owned.length > 0) {
|
|
929
1253
|
operations.push({ kind: "remove-json-keys", path: "package.json", keys: owned });
|
|
@@ -931,12 +1255,53 @@ function plan(context) {
|
|
|
931
1255
|
`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.`
|
|
932
1256
|
);
|
|
933
1257
|
}
|
|
1258
|
+
const formatted = readFormatAdoption(context.cwd).adopted;
|
|
1259
|
+
notes.push(
|
|
1260
|
+
formatted ? `run \`sentinel --run --format --fix\` on this module: adoption rewrote ${configFile}, and a rewritten import can be longer than your formatter wraps at.` : `this module is formatted by the workspace, not by sentinel, so format ${configFile} with whatever owns it before committing: a rewritten import can be longer than that formatter wraps at, and the check would fail on a file nobody typed.`
|
|
1261
|
+
);
|
|
1262
|
+
notes.push(
|
|
1263
|
+
`this config is code, and it runs before the build does: any environment variable it reads has to be present wherever you build, including a fresh clone, another CI job or an image. Adoption does not change what it reads, but a failure there arrives early and names this role rather than the variable.`
|
|
1264
|
+
);
|
|
934
1265
|
return { operations, notes };
|
|
935
1266
|
}
|
|
1267
|
+
function planNestService(context, webpackTarget) {
|
|
1268
|
+
const outputs = webpackTarget === void 0 ? nestOutputsFromConfig(context.cwd) : nestBuildOutputs(webpackTarget);
|
|
1269
|
+
if (outputs === void 0) {
|
|
1270
|
+
return {
|
|
1271
|
+
operations: [],
|
|
1272
|
+
blocked: `${NEST_CONFIG_FILE} does not declare an \`outDir\`, so sentinel cannot say where this service's build lands. Declaring an nx target without it would cache the build and restore nothing from that cache. Nothing was written.`
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
const metadata = [
|
|
1276
|
+
manifestOperation(context.cwd, { [BUILD_SCRIPT_NAME]: SENTINEL_BUILD_COMMAND }),
|
|
1277
|
+
...nxTargetOperations({
|
|
1278
|
+
cwd: context.cwd,
|
|
1279
|
+
targets: buildTarget({
|
|
1280
|
+
moduleName: adoptedPackageName(context.cwd),
|
|
1281
|
+
configFiles: [NEST_CONFIG_FILE],
|
|
1282
|
+
outputs
|
|
1283
|
+
})
|
|
1284
|
+
})
|
|
1285
|
+
];
|
|
1286
|
+
if (webpackTarget === void 0) return { operations: metadata };
|
|
1287
|
+
const refusal = refusalFor(context.cwd, webpackTarget);
|
|
1288
|
+
if (refusal !== void 0) return { operations: [], blocked: refusal };
|
|
1289
|
+
const dropped = Object.keys(webpackTarget.configurations ?? {});
|
|
1290
|
+
return {
|
|
1291
|
+
operations: [...nestAdoptionOperations(context.cwd, webpackTarget), ...metadata],
|
|
1292
|
+
notes: [
|
|
1293
|
+
`wrote ${NEST_CONFIG_FILE} and pointed the nx build target at sentinel. The target stays \`nx:run-commands\` with \`forwardAllArgs: false\`, so the image's \`--generatePackageJson\` still works and the Dockerfile needs no change.`,
|
|
1294
|
+
...dropped.length > 0 ? [
|
|
1295
|
+
`dropped the \`${dropped.join("`, `")}\` configuration${dropped.length > 1 ? "s" : ""} from the build target: its fileReplacements point at files that do not exist, so it replaced nothing. A live one would have blocked this instead.`
|
|
1296
|
+
] : [],
|
|
1297
|
+
`verify with \`nx run ${webpackTarget.project}:generate-swagger-file\` and \`git diff --exit-code\`: it runs the built bundle, so it proves the service boots AND that its published contract did not move.`
|
|
1298
|
+
]
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
936
1301
|
function readConfigSource(cwd, configFile) {
|
|
937
|
-
if (!
|
|
1302
|
+
if (!existsSync11(join11(cwd, configFile))) return "";
|
|
938
1303
|
try {
|
|
939
|
-
return
|
|
1304
|
+
return readFileSync9(join11(cwd, configFile), "utf8");
|
|
940
1305
|
} catch {
|
|
941
1306
|
return "";
|
|
942
1307
|
}
|
|
@@ -1039,80 +1404,334 @@ var ViteRoleAdapter = class extends BaseAdapter {
|
|
|
1039
1404
|
`using the MODULE's vite, not sentinel's. An adopted config gets its plugins from sentinel, and two copies of Vite fail in ways that never mention a version. Remove vite from this module's package.json.`
|
|
1040
1405
|
);
|
|
1041
1406
|
}
|
|
1042
|
-
return { kind: "ready", vite: bin };
|
|
1407
|
+
return { kind: "ready", vite: bin };
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Run Vite with the caller's own arguments after `--`, and report what happened.
|
|
1411
|
+
*
|
|
1412
|
+
* `whenSignalled` is the only difference between the two targets here, and it is a real one:
|
|
1413
|
+
* a build killed by a signal did not produce a bundle, while a dev server killed by Ctrl-C
|
|
1414
|
+
* did exactly what the developer asked.
|
|
1415
|
+
*/
|
|
1416
|
+
spawnVite(ctx, vite, argv, whenSignalled) {
|
|
1417
|
+
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
1418
|
+
valueFlags: VITE_VALUE_FLAGS
|
|
1419
|
+
});
|
|
1420
|
+
const result = spawnSync(vite, [...argv, ...options, ...paths], {
|
|
1421
|
+
cwd: ctx.cwd,
|
|
1422
|
+
stdio: "inherit"
|
|
1423
|
+
});
|
|
1424
|
+
if (result.error) {
|
|
1425
|
+
this.say(`could not run vite (${result.error.message})`);
|
|
1426
|
+
return { ok: false, code: 1 };
|
|
1427
|
+
}
|
|
1428
|
+
const code = result.status ?? whenSignalled;
|
|
1429
|
+
return { ok: code === 0, code };
|
|
1430
|
+
}
|
|
1431
|
+
/** One prefix for every message this role emits, so they cannot drift apart. */
|
|
1432
|
+
say(message) {
|
|
1433
|
+
process.stderr.write(`sentinel ${this.label}(vite): ${message}
|
|
1434
|
+
`);
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1438
|
+
// src/roles/build/adapters/vite/vite-dev.adapter.ts
|
|
1439
|
+
var ViteDevAdapter = class extends ViteRoleAdapter {
|
|
1440
|
+
target = "dev";
|
|
1441
|
+
get label() {
|
|
1442
|
+
return "dev";
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* `--init --dev` does the same thing as `--init --build`, and now says so.
|
|
1446
|
+
*
|
|
1447
|
+
* The shared plan is deliberate (see `ViteRoleAdapter.plan`): one plan writes both scripts,
|
|
1448
|
+
* because adopting one without the other leaves the module half-migrated. What was missing
|
|
1449
|
+
* was only the telling: the run printed a message about the build and nothing about `serve`,
|
|
1450
|
+
* so a developer could believe the dev server had been adopted on its own. Refusing was the
|
|
1451
|
+
* wrong correction, it added friction to a command that already produced the right result.
|
|
1452
|
+
*/
|
|
1453
|
+
plan(context) {
|
|
1454
|
+
if (declaredBuildPreset(context.cwd) === "nest" || webpackBuildTarget(context.cwd)) {
|
|
1455
|
+
return {
|
|
1456
|
+
operations: [],
|
|
1457
|
+
skipped: `a Nest service has no dev server, so --dev has nothing to adopt here. Its build is \`sentinel --init --build\`, and it runs as a Node process afterwards.`
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
const planned = super.plan(context);
|
|
1461
|
+
if (planned.operations.length === 0) return planned;
|
|
1462
|
+
return {
|
|
1463
|
+
...planned,
|
|
1464
|
+
notes: [
|
|
1465
|
+
...planned.notes ?? [],
|
|
1466
|
+
"--init --dev adopts the build too: one plan writes both `build` and `serve`."
|
|
1467
|
+
]
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Start the dev server. It does not return until stopped, so there is no verdict to report
|
|
1472
|
+
* beyond the exit code the developer's own Ctrl-C produces.
|
|
1473
|
+
*
|
|
1474
|
+
* No `prebuild` here, unlike `--run --build`: the artefact this repo generates before a build
|
|
1475
|
+
* is produced by the watcher that WRAPS this command (`run-with-runtime-artifact-watch`), and
|
|
1476
|
+
* running it again would race the watcher that is about to own the file.
|
|
1477
|
+
*
|
|
1478
|
+
* No subcommand either. `vite`, `vite dev` and `vite serve` all start the server, and the
|
|
1479
|
+
* bare form is the one every version accepts.
|
|
1480
|
+
*/
|
|
1481
|
+
async run(ctx) {
|
|
1482
|
+
const ready = this.prepare(ctx);
|
|
1483
|
+
if (ready.kind === "stop") return ready.result;
|
|
1484
|
+
return this.spawnVite(ctx, ready.vite, [], 0);
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
|
|
1488
|
+
// src/roles/build/declared-outputs.ts
|
|
1489
|
+
import { existsSync as existsSync13, rmSync, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1490
|
+
import { join as join13 } from "path";
|
|
1491
|
+
|
|
1492
|
+
// src/core/workspace-prep.ts
|
|
1493
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync } from "fs";
|
|
1494
|
+
import { dirname as dirname3, join as join12, relative as relative2 } from "path";
|
|
1495
|
+
|
|
1496
|
+
// src/core/settings.ts
|
|
1497
|
+
var WORKSPACE_ROOT_MARKER = "nx.json";
|
|
1498
|
+
var DEFAULT_MAX_DIAGNOSTICS = 100;
|
|
1499
|
+
|
|
1500
|
+
// src/core/workspace-prep.ts
|
|
1501
|
+
var OVERRIDE_KEY = "i18next>typescript";
|
|
1502
|
+
var NATIVE_TS_ALIAS = "@typescript/native";
|
|
1503
|
+
var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
1504
|
+
var RELEASE_AGE_KEY = "minimumReleaseAgeExclude";
|
|
1505
|
+
var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
|
|
1506
|
+
var MAP_ENTRY = /^(\s+)(.*?):\s+(.*?)\s*$/;
|
|
1507
|
+
function findWorkspaceRoot(startDir) {
|
|
1508
|
+
let dir = startDir;
|
|
1509
|
+
for (; ; ) {
|
|
1510
|
+
if (existsSync12(join12(dir, WORKSPACE_ROOT_MARKER))) return dir;
|
|
1511
|
+
const parent = dirname3(dir);
|
|
1512
|
+
if (parent === dir) return void 0;
|
|
1513
|
+
dir = parent;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
function declaredNativeTs(pkg) {
|
|
1517
|
+
const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS];
|
|
1518
|
+
if (!spec) return void 0;
|
|
1519
|
+
const version = spec.slice(spec.lastIndexOf("@") + 1).replace(/^[\^~>=<\s]+/, "");
|
|
1520
|
+
return version || void 0;
|
|
1521
|
+
}
|
|
1522
|
+
function ensureI18nextSingleton(root, dryRun) {
|
|
1523
|
+
const pkgPath = join12(root, "package.json");
|
|
1524
|
+
if (!existsSync12(pkgPath)) return void 0;
|
|
1525
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
1526
|
+
const want = declaredNativeTs(pkg);
|
|
1527
|
+
if (!want) return void 0;
|
|
1528
|
+
const yamlPath = join12(root, WORKSPACE_YAML);
|
|
1529
|
+
if (!existsSync12(yamlPath)) return void 0;
|
|
1530
|
+
const lines = readFileSync10(yamlPath, "utf8").split("\n");
|
|
1531
|
+
const entry = findOverrideEntry(lines);
|
|
1532
|
+
if (entry.valueIdx !== void 0 && entry.value === want) return void 0;
|
|
1533
|
+
if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} in ${WORKSPACE_YAML} (i18next singleton)`;
|
|
1534
|
+
applyOverride(lines, entry, want);
|
|
1535
|
+
writeFileSync(yamlPath, lines.join("\n"));
|
|
1536
|
+
return `pinned ${OVERRIDE_KEY} to ${want} in ${WORKSPACE_YAML} (i18next singleton)`;
|
|
1537
|
+
}
|
|
1538
|
+
function findOverrideEntry(lines) {
|
|
1539
|
+
const blockIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === "overrides:");
|
|
1540
|
+
if (blockIdx === -1) return { indent: " " };
|
|
1541
|
+
let lastEntryIdx = blockIdx;
|
|
1542
|
+
let indent = " ";
|
|
1543
|
+
for (let i = blockIdx + 1; i < lines.length; i++) {
|
|
1544
|
+
const match = lines[i]?.match(MAP_ENTRY);
|
|
1545
|
+
if (!match) break;
|
|
1546
|
+
indent = match[1] ?? indent;
|
|
1547
|
+
lastEntryIdx = i;
|
|
1548
|
+
if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === OVERRIDE_KEY) {
|
|
1549
|
+
return {
|
|
1550
|
+
valueIdx: i,
|
|
1551
|
+
value: (match[3] ?? "").replace(/^['"]|['"]$/g, ""),
|
|
1552
|
+
blockIdx,
|
|
1553
|
+
lastEntryIdx,
|
|
1554
|
+
indent
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return { blockIdx, lastEntryIdx, indent };
|
|
1559
|
+
}
|
|
1560
|
+
function applyOverride(lines, entry, want) {
|
|
1561
|
+
const line = `${entry.indent}'${OVERRIDE_KEY}': ${want}`;
|
|
1562
|
+
if (entry.valueIdx !== void 0) {
|
|
1563
|
+
lines[entry.valueIdx] = line;
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
if (entry.lastEntryIdx !== void 0) {
|
|
1567
|
+
lines.splice(entry.lastEntryIdx + 1, 0, line);
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
1571
|
+
lines.push("overrides:", line, "");
|
|
1572
|
+
}
|
|
1573
|
+
function ensureReleaseAgeAllowList(root, dryRun) {
|
|
1574
|
+
const yamlPath = join12(root, WORKSPACE_YAML);
|
|
1575
|
+
if (!existsSync12(yamlPath)) return void 0;
|
|
1576
|
+
const own = readOwnPackage().name;
|
|
1577
|
+
const lines = readFileSync10(yamlPath, "utf8").split("\n");
|
|
1578
|
+
const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
|
|
1579
|
+
if (keyIdx === -1) return void 0;
|
|
1580
|
+
let lastItemIdx = keyIdx;
|
|
1581
|
+
let indent = " ";
|
|
1582
|
+
for (let i = keyIdx + 1; i < lines.length; i++) {
|
|
1583
|
+
const match = lines[i]?.match(LIST_ITEM);
|
|
1584
|
+
if (!match) break;
|
|
1585
|
+
indent = match[1] ?? indent;
|
|
1586
|
+
lastItemIdx = i;
|
|
1587
|
+
if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === own) return void 0;
|
|
1588
|
+
}
|
|
1589
|
+
if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`;
|
|
1590
|
+
lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`);
|
|
1591
|
+
writeFileSync(yamlPath, lines.join("\n"));
|
|
1592
|
+
return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`;
|
|
1593
|
+
}
|
|
1594
|
+
var PRETTIER_IGNORE = ".prettierignore";
|
|
1595
|
+
var EXCLUSION_HEADER = "# @hublo/sentinel: modules formatted by oxfmt, not by the root Prettier";
|
|
1596
|
+
var ROOT_PRETTIER_CONFIGS = [
|
|
1597
|
+
".prettierrc",
|
|
1598
|
+
".prettierrc.json",
|
|
1599
|
+
".prettierrc.json5",
|
|
1600
|
+
".prettierrc.yml",
|
|
1601
|
+
".prettierrc.yaml",
|
|
1602
|
+
".prettierrc.js",
|
|
1603
|
+
".prettierrc.cjs",
|
|
1604
|
+
".prettierrc.mjs",
|
|
1605
|
+
"prettier.config.js",
|
|
1606
|
+
"prettier.config.cjs",
|
|
1607
|
+
"prettier.config.mjs"
|
|
1608
|
+
];
|
|
1609
|
+
function ensureFormatterExclusion(root, moduleDir, dryRun) {
|
|
1610
|
+
const rel = relative2(root, moduleDir).replaceAll("\\", "/");
|
|
1611
|
+
if (rel === "" || rel.startsWith("..")) return void 0;
|
|
1612
|
+
const ignorePath = join12(root, PRETTIER_IGNORE);
|
|
1613
|
+
const hasPrettier = existsSync12(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync12(join12(root, name)));
|
|
1614
|
+
if (!hasPrettier) return void 0;
|
|
1615
|
+
const pattern = `/${rel}/`;
|
|
1616
|
+
const existing = existsSync12(ignorePath) ? readFileSync10(ignorePath, "utf8") : "";
|
|
1617
|
+
const lines = existing.split("\n");
|
|
1618
|
+
if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
|
|
1619
|
+
return void 0;
|
|
1620
|
+
}
|
|
1621
|
+
if (dryRun) return `would exclude ${rel} from the root Prettier (${PRETTIER_IGNORE})`;
|
|
1622
|
+
const headerIdx = lines.findIndex((line) => line.trim() === EXCLUSION_HEADER);
|
|
1623
|
+
if (headerIdx === -1) {
|
|
1624
|
+
const prefix = existing.trim() === "" ? "" : `${existing.replace(/\n*$/, "")}
|
|
1625
|
+
|
|
1626
|
+
`;
|
|
1627
|
+
writeFileSync(ignorePath, `${prefix}${EXCLUSION_HEADER}
|
|
1628
|
+
${pattern}
|
|
1629
|
+
`);
|
|
1630
|
+
} else {
|
|
1631
|
+
let end = headerIdx + 1;
|
|
1632
|
+
while (end < lines.length && lines[end]?.trim().startsWith("/")) end += 1;
|
|
1633
|
+
const managed = [...lines.slice(headerIdx + 1, end), pattern].sort();
|
|
1634
|
+
writeFileSync(
|
|
1635
|
+
ignorePath,
|
|
1636
|
+
[...lines.slice(0, headerIdx + 1), ...managed, ...lines.slice(end)].join("\n")
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
return `excluded ${rel} from the root Prettier (${PRETTIER_IGNORE}): it formats itself now`;
|
|
1640
|
+
}
|
|
1641
|
+
function ensureWorkspacePrep(opts) {
|
|
1642
|
+
const dryRun = Boolean(opts.dryRun);
|
|
1643
|
+
return [
|
|
1644
|
+
ensureI18nextSingleton(opts.root, dryRun),
|
|
1645
|
+
ensureReleaseAgeAllowList(opts.root, dryRun),
|
|
1646
|
+
opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
|
|
1647
|
+
].filter((message) => message !== void 0);
|
|
1648
|
+
}
|
|
1649
|
+
function inspectWorkspacePrep(root) {
|
|
1650
|
+
const entries = [];
|
|
1651
|
+
let pkg = {};
|
|
1652
|
+
const pkgPath = join12(root, "package.json");
|
|
1653
|
+
if (existsSync12(pkgPath)) {
|
|
1654
|
+
try {
|
|
1655
|
+
pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
1656
|
+
} catch {
|
|
1657
|
+
pkg = {};
|
|
1658
|
+
}
|
|
1043
1659
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
1053
|
-
valueFlags: VITE_VALUE_FLAGS
|
|
1660
|
+
const yamlPath = join12(root, WORKSPACE_YAML);
|
|
1661
|
+
const yaml = existsSync12(yamlPath) ? readFileSync10(yamlPath, "utf8") : void 0;
|
|
1662
|
+
const pinned = yaml === void 0 ? void 0 : findOverrideEntry(yaml.split("\n")).value;
|
|
1663
|
+
if (pinned !== void 0) {
|
|
1664
|
+
const stillForks = declaredNativeTs(pkg);
|
|
1665
|
+
entries.push({
|
|
1666
|
+
rule: `${WORKSPACE_YAML} \u2192 overrides.${OVERRIDE_KEY}`,
|
|
1667
|
+
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.`
|
|
1054
1668
|
});
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1669
|
+
}
|
|
1670
|
+
const legacy = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
1671
|
+
if (legacy !== void 0) {
|
|
1672
|
+
entries.push({
|
|
1673
|
+
rule: `package.json \u2192 pnpm.overrides.${OVERRIDE_KEY}`,
|
|
1674
|
+
reason: `pinned to ${legacy}, but pnpm 11 no longer reads the \`pnpm\` field of package.json and says nothing about it, so this override is not applied. Move it to ${WORKSPACE_YAML} under \`overrides:\`, which pnpm 10 and 11 both read.`
|
|
1058
1675
|
});
|
|
1059
|
-
if (result.error) {
|
|
1060
|
-
this.say(`could not run vite (${result.error.message})`);
|
|
1061
|
-
return { ok: false, code: 1 };
|
|
1062
|
-
}
|
|
1063
|
-
const code = result.status ?? whenSignalled;
|
|
1064
|
-
return { ok: code === 0, code };
|
|
1065
1676
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1677
|
+
const own = readOwnPackage().name;
|
|
1678
|
+
if (yaml !== void 0) {
|
|
1679
|
+
const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
|
|
1680
|
+
if (listed) {
|
|
1681
|
+
const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
|
|
1682
|
+
entries.push({
|
|
1683
|
+
rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
|
|
1684
|
+
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.`
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1070
1687
|
}
|
|
1071
|
-
|
|
1688
|
+
return entries;
|
|
1689
|
+
}
|
|
1072
1690
|
|
|
1073
|
-
// src/roles/build/
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1691
|
+
// src/roles/build/declared-outputs.ts
|
|
1692
|
+
function declaredBuildTarget(cwd) {
|
|
1693
|
+
return readProjectPackageJson(cwd).nx?.targets?.[BUILD_SCRIPT_NAME];
|
|
1694
|
+
}
|
|
1695
|
+
function resolveOutputToken(token, cwd) {
|
|
1696
|
+
if (token.startsWith("{projectRoot}/")) return join13(cwd, token.slice("{projectRoot}/".length));
|
|
1697
|
+
if (token.startsWith("{workspaceRoot}/")) {
|
|
1698
|
+
const root = findWorkspaceRoot(cwd);
|
|
1699
|
+
return root === void 0 ? void 0 : join13(root, token.slice("{workspaceRoot}/".length));
|
|
1078
1700
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
if (planned.operations.length === 0) return planned;
|
|
1091
|
-
return {
|
|
1092
|
-
...planned,
|
|
1093
|
-
notes: [
|
|
1094
|
-
...planned.notes ?? [],
|
|
1095
|
-
"--init --dev adopts the build too: one plan writes both `build` and `serve`."
|
|
1096
|
-
]
|
|
1097
|
-
};
|
|
1701
|
+
return join13(cwd, token);
|
|
1702
|
+
}
|
|
1703
|
+
function buildStamp(cwd) {
|
|
1704
|
+
const marker = join13(cwd, `.sentinel-build-stamp-${process.pid}`);
|
|
1705
|
+
try {
|
|
1706
|
+
writeFileSync2(marker, "");
|
|
1707
|
+
const stamp = statSync(marker).mtimeMs;
|
|
1708
|
+
rmSync(marker, { force: true });
|
|
1709
|
+
return stamp;
|
|
1710
|
+
} catch {
|
|
1711
|
+
return Date.now();
|
|
1098
1712
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
*
|
|
1107
|
-
* No subcommand either. `vite`, `vite dev` and `vite serve` all start the server, and the
|
|
1108
|
-
* bare form is the one every version accepts.
|
|
1109
|
-
*/
|
|
1110
|
-
async run(ctx) {
|
|
1111
|
-
const ready = this.prepare(ctx);
|
|
1112
|
-
if (ready.kind === "stop") return ready.result;
|
|
1113
|
-
return this.spawnVite(ctx, ready.vite, [], 0);
|
|
1713
|
+
}
|
|
1714
|
+
function writtenSince(path, startedAt) {
|
|
1715
|
+
if (!existsSync13(path)) return false;
|
|
1716
|
+
try {
|
|
1717
|
+
return statSync(path).mtimeMs >= startedAt;
|
|
1718
|
+
} catch {
|
|
1719
|
+
return true;
|
|
1114
1720
|
}
|
|
1115
|
-
}
|
|
1721
|
+
}
|
|
1722
|
+
function outputsProblem(cwd, startedAt) {
|
|
1723
|
+
const target = declaredBuildTarget(cwd);
|
|
1724
|
+
if (target === void 0) return void 0;
|
|
1725
|
+
const declared = target.outputs ?? [];
|
|
1726
|
+
if (declared.length === 0) {
|
|
1727
|
+
if (target.cache !== true) return void 0;
|
|
1728
|
+
return `this module's nx build target sets \`cache: true\` and declares no \`outputs\`, so nx will cache this build and restore nothing from it: the next cache hit reports success and leaves no build on disk. Add the directory this build writes to under \`nx.targets.${BUILD_SCRIPT_NAME}.outputs\` in package.json, or re-run \`sentinel --init --build\`, which declares it.`;
|
|
1729
|
+
}
|
|
1730
|
+
const resolved = declared.map((token) => ({ token, path: resolveOutputToken(token, cwd) })).filter((entry) => entry.path !== void 0);
|
|
1731
|
+
if (resolved.length === 0) return void 0;
|
|
1732
|
+
if (resolved.some((entry) => writtenSince(entry.path, startedAt))) return void 0;
|
|
1733
|
+
return `the build wrote nothing to any of the directories this module declares as its nx \`outputs\` (${declared.join(", ")}), so nx would cache this build and restore an empty result from it. Whatever directory the build DID write to belongs in \`nx.targets.${BUILD_SCRIPT_NAME}.outputs\` in package.json. The build itself succeeded; only its cache declaration is wrong.`;
|
|
1734
|
+
}
|
|
1116
1735
|
|
|
1117
1736
|
// src/roles/build/prerequisite.ts
|
|
1118
1737
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -1162,7 +1781,15 @@ var ViteAdapter = class extends ViteRoleAdapter {
|
|
|
1162
1781
|
);
|
|
1163
1782
|
return { ok: false, code: prerequisite.code };
|
|
1164
1783
|
}
|
|
1165
|
-
|
|
1784
|
+
const startedAt = buildStamp(ctx.cwd);
|
|
1785
|
+
const result = await this.spawnVite(ctx, ready.vite, ["build"], 1);
|
|
1786
|
+
if (!result.ok) return result;
|
|
1787
|
+
const problem = outputsProblem(ctx.cwd, startedAt);
|
|
1788
|
+
if (problem !== void 0) {
|
|
1789
|
+
this.say(problem);
|
|
1790
|
+
return { ok: false, code: 1 };
|
|
1791
|
+
}
|
|
1792
|
+
return result;
|
|
1166
1793
|
}
|
|
1167
1794
|
/**
|
|
1168
1795
|
* What this module builds with, WITHOUT loading its config.
|
|
@@ -1234,12 +1861,12 @@ function registerBuild() {
|
|
|
1234
1861
|
|
|
1235
1862
|
// src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
|
|
1236
1863
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1237
|
-
import { existsSync as
|
|
1238
|
-
import { join as
|
|
1864
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
1865
|
+
import { join as join18 } from "path";
|
|
1239
1866
|
|
|
1240
1867
|
// src/core/config/has-source.ts
|
|
1241
1868
|
import { readdirSync } from "fs";
|
|
1242
|
-
import { extname, join as
|
|
1869
|
+
import { extname, join as join14, relative as relative3 } from "path";
|
|
1243
1870
|
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1244
1871
|
"node_modules",
|
|
1245
1872
|
"dist",
|
|
@@ -1274,258 +1901,59 @@ var FORMATTABLE_EXTENSIONS = [
|
|
|
1274
1901
|
".yml",
|
|
1275
1902
|
".html",
|
|
1276
1903
|
".css",
|
|
1277
|
-
".scss",
|
|
1278
|
-
".hbs"
|
|
1279
|
-
];
|
|
1280
|
-
function hasSourceFiles(cwd, extensions) {
|
|
1281
|
-
const wanted = new Set(extensions);
|
|
1282
|
-
const queue = [cwd];
|
|
1283
|
-
while (queue.length > 0) {
|
|
1284
|
-
const dir = queue.pop();
|
|
1285
|
-
let entries;
|
|
1286
|
-
try {
|
|
1287
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
1288
|
-
} catch {
|
|
1289
|
-
continue;
|
|
1290
|
-
}
|
|
1291
|
-
for (const entry of entries) {
|
|
1292
|
-
if (entry.isDirectory()) {
|
|
1293
|
-
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(
|
|
1294
|
-
continue;
|
|
1295
|
-
}
|
|
1296
|
-
if (wanted.has(extname(entry.name))) return true;
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
return false;
|
|
1300
|
-
}
|
|
1301
|
-
function listSourceFiles(cwd, extensions) {
|
|
1302
|
-
const wanted = new Set(extensions);
|
|
1303
|
-
const queue = [cwd];
|
|
1304
|
-
const found = [];
|
|
1305
|
-
while (queue.length > 0) {
|
|
1306
|
-
const dir = queue.pop();
|
|
1307
|
-
let entries;
|
|
1308
|
-
try {
|
|
1309
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
1310
|
-
} catch {
|
|
1311
|
-
continue;
|
|
1312
|
-
}
|
|
1313
|
-
for (const entry of entries) {
|
|
1314
|
-
const full = join9(dir, entry.name);
|
|
1315
|
-
if (entry.isDirectory()) {
|
|
1316
|
-
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
|
|
1317
|
-
continue;
|
|
1318
|
-
}
|
|
1319
|
-
if (wanted.has(extname(entry.name))) found.push(relative3(cwd, full));
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
return found;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
// src/core/named-scope.ts
|
|
1326
|
-
var TOLERATE_EMPTY_FLAG = "--no-error-on-unmatched-pattern";
|
|
1327
|
-
function scopeFlags(paths, options = []) {
|
|
1328
|
-
if (paths.length === 0 || options.includes(TOLERATE_EMPTY_FLAG)) return [];
|
|
1329
|
-
return [TOLERATE_EMPTY_FLAG];
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
// src/core/settings.ts
|
|
1333
|
-
var WORKSPACE_ROOT_MARKER = "nx.json";
|
|
1334
|
-
var DEFAULT_MAX_DIAGNOSTICS = 100;
|
|
1335
|
-
|
|
1336
|
-
// src/core/workspace-prep.ts
|
|
1337
|
-
import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync } from "fs";
|
|
1338
|
-
import { dirname as dirname3, join as join10, relative as relative4 } from "path";
|
|
1339
|
-
var OVERRIDE_KEY = "i18next>typescript";
|
|
1340
|
-
var NATIVE_TS_ALIAS = "@typescript/native";
|
|
1341
|
-
var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
1342
|
-
var RELEASE_AGE_KEY = "minimumReleaseAgeExclude";
|
|
1343
|
-
var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
|
|
1344
|
-
function findWorkspaceRoot(startDir) {
|
|
1345
|
-
let dir = startDir;
|
|
1346
|
-
for (; ; ) {
|
|
1347
|
-
if (existsSync9(join10(dir, WORKSPACE_ROOT_MARKER))) return dir;
|
|
1348
|
-
const parent = dirname3(dir);
|
|
1349
|
-
if (parent === dir) return void 0;
|
|
1350
|
-
dir = parent;
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
function declaredNativeTs(pkg) {
|
|
1354
|
-
const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS];
|
|
1355
|
-
if (!spec) return void 0;
|
|
1356
|
-
const version = spec.slice(spec.lastIndexOf("@") + 1).replace(/^[\^~>=<\s]+/, "");
|
|
1357
|
-
return version || void 0;
|
|
1358
|
-
}
|
|
1359
|
-
function ensureI18nextSingleton(root, dryRun) {
|
|
1360
|
-
const pkgPath = join10(root, "package.json");
|
|
1361
|
-
if (!existsSync9(pkgPath)) return void 0;
|
|
1362
|
-
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
1363
|
-
const want = declaredNativeTs(pkg);
|
|
1364
|
-
if (!want) return void 0;
|
|
1365
|
-
const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
1366
|
-
if (have === want) return void 0;
|
|
1367
|
-
if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} (i18next singleton)`;
|
|
1368
|
-
pkg.pnpm ??= {};
|
|
1369
|
-
pkg.pnpm.overrides ??= {};
|
|
1370
|
-
pkg.pnpm.overrides[OVERRIDE_KEY] = want;
|
|
1371
|
-
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1372
|
-
return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
|
|
1373
|
-
}
|
|
1374
|
-
function ensureReleaseAgeAllowList(root, dryRun) {
|
|
1375
|
-
const yamlPath = join10(root, WORKSPACE_YAML);
|
|
1376
|
-
if (!existsSync9(yamlPath)) return void 0;
|
|
1377
|
-
const own = readOwnPackage().name;
|
|
1378
|
-
const lines = readFileSync7(yamlPath, "utf8").split("\n");
|
|
1379
|
-
const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
|
|
1380
|
-
if (keyIdx === -1) return void 0;
|
|
1381
|
-
let lastItemIdx = keyIdx;
|
|
1382
|
-
let indent = " ";
|
|
1383
|
-
for (let i = keyIdx + 1; i < lines.length; i++) {
|
|
1384
|
-
const match = lines[i]?.match(LIST_ITEM);
|
|
1385
|
-
if (!match) break;
|
|
1386
|
-
indent = match[1] ?? indent;
|
|
1387
|
-
lastItemIdx = i;
|
|
1388
|
-
if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === own) return void 0;
|
|
1389
|
-
}
|
|
1390
|
-
if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`;
|
|
1391
|
-
lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`);
|
|
1392
|
-
writeFileSync(yamlPath, lines.join("\n"));
|
|
1393
|
-
return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`;
|
|
1394
|
-
}
|
|
1395
|
-
var PRETTIER_IGNORE = ".prettierignore";
|
|
1396
|
-
var EXCLUSION_HEADER = "# @hublo/sentinel: modules formatted by oxfmt, not by the root Prettier";
|
|
1397
|
-
var ROOT_PRETTIER_CONFIGS = [
|
|
1398
|
-
".prettierrc",
|
|
1399
|
-
".prettierrc.json",
|
|
1400
|
-
".prettierrc.json5",
|
|
1401
|
-
".prettierrc.yml",
|
|
1402
|
-
".prettierrc.yaml",
|
|
1403
|
-
".prettierrc.js",
|
|
1404
|
-
".prettierrc.cjs",
|
|
1405
|
-
".prettierrc.mjs",
|
|
1406
|
-
"prettier.config.js",
|
|
1407
|
-
"prettier.config.cjs",
|
|
1408
|
-
"prettier.config.mjs"
|
|
1409
|
-
];
|
|
1410
|
-
function ensureFormatterExclusion(root, moduleDir, dryRun) {
|
|
1411
|
-
const rel = relative4(root, moduleDir).replaceAll("\\", "/");
|
|
1412
|
-
if (rel === "" || rel.startsWith("..")) return void 0;
|
|
1413
|
-
const ignorePath = join10(root, PRETTIER_IGNORE);
|
|
1414
|
-
const hasPrettier = existsSync9(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync9(join10(root, name)));
|
|
1415
|
-
if (!hasPrettier) return void 0;
|
|
1416
|
-
const pattern = `/${rel}/`;
|
|
1417
|
-
const existing = existsSync9(ignorePath) ? readFileSync7(ignorePath, "utf8") : "";
|
|
1418
|
-
const lines = existing.split("\n");
|
|
1419
|
-
if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
|
|
1420
|
-
return void 0;
|
|
1421
|
-
}
|
|
1422
|
-
if (dryRun) return `would exclude ${rel} from the root Prettier (${PRETTIER_IGNORE})`;
|
|
1423
|
-
const headerIdx = lines.findIndex((line) => line.trim() === EXCLUSION_HEADER);
|
|
1424
|
-
if (headerIdx === -1) {
|
|
1425
|
-
const prefix = existing.trim() === "" ? "" : `${existing.replace(/\n*$/, "")}
|
|
1426
|
-
|
|
1427
|
-
`;
|
|
1428
|
-
writeFileSync(ignorePath, `${prefix}${EXCLUSION_HEADER}
|
|
1429
|
-
${pattern}
|
|
1430
|
-
`);
|
|
1431
|
-
} else {
|
|
1432
|
-
let end = headerIdx + 1;
|
|
1433
|
-
while (end < lines.length && lines[end]?.trim().startsWith("/")) end += 1;
|
|
1434
|
-
const managed = [...lines.slice(headerIdx + 1, end), pattern].sort();
|
|
1435
|
-
writeFileSync(
|
|
1436
|
-
ignorePath,
|
|
1437
|
-
[...lines.slice(0, headerIdx + 1), ...managed, ...lines.slice(end)].join("\n")
|
|
1438
|
-
);
|
|
1904
|
+
".scss",
|
|
1905
|
+
".hbs"
|
|
1906
|
+
];
|
|
1907
|
+
function hasSourceFiles(cwd, extensions) {
|
|
1908
|
+
const wanted = new Set(extensions);
|
|
1909
|
+
const queue = [cwd];
|
|
1910
|
+
while (queue.length > 0) {
|
|
1911
|
+
const dir = queue.pop();
|
|
1912
|
+
let entries;
|
|
1913
|
+
try {
|
|
1914
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1915
|
+
} catch {
|
|
1916
|
+
continue;
|
|
1917
|
+
}
|
|
1918
|
+
for (const entry of entries) {
|
|
1919
|
+
if (entry.isDirectory()) {
|
|
1920
|
+
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join14(dir, entry.name));
|
|
1921
|
+
continue;
|
|
1922
|
+
}
|
|
1923
|
+
if (wanted.has(extname(entry.name))) return true;
|
|
1924
|
+
}
|
|
1439
1925
|
}
|
|
1440
|
-
return
|
|
1441
|
-
}
|
|
1442
|
-
function ensureWorkspacePrep(opts) {
|
|
1443
|
-
const dryRun = Boolean(opts.dryRun);
|
|
1444
|
-
return [
|
|
1445
|
-
ensureI18nextSingleton(opts.root, dryRun),
|
|
1446
|
-
ensureReleaseAgeAllowList(opts.root, dryRun),
|
|
1447
|
-
opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
|
|
1448
|
-
].filter((message) => message !== void 0);
|
|
1926
|
+
return false;
|
|
1449
1927
|
}
|
|
1450
|
-
function
|
|
1451
|
-
const
|
|
1452
|
-
|
|
1453
|
-
const
|
|
1454
|
-
|
|
1928
|
+
function listSourceFiles(cwd, extensions) {
|
|
1929
|
+
const wanted = new Set(extensions);
|
|
1930
|
+
const queue = [cwd];
|
|
1931
|
+
const found = [];
|
|
1932
|
+
while (queue.length > 0) {
|
|
1933
|
+
const dir = queue.pop();
|
|
1934
|
+
let entries;
|
|
1455
1935
|
try {
|
|
1456
|
-
|
|
1936
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1457
1937
|
} catch {
|
|
1458
|
-
|
|
1938
|
+
continue;
|
|
1459
1939
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
});
|
|
1468
|
-
}
|
|
1469
|
-
const own = readOwnPackage().name;
|
|
1470
|
-
const yamlPath = join10(root, WORKSPACE_YAML);
|
|
1471
|
-
if (existsSync9(yamlPath)) {
|
|
1472
|
-
const yaml = readFileSync7(yamlPath, "utf8");
|
|
1473
|
-
const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
|
|
1474
|
-
if (listed) {
|
|
1475
|
-
const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
|
|
1476
|
-
entries.push({
|
|
1477
|
-
rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
|
|
1478
|
-
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.`
|
|
1479
|
-
});
|
|
1940
|
+
for (const entry of entries) {
|
|
1941
|
+
const full = join14(dir, entry.name);
|
|
1942
|
+
if (entry.isDirectory()) {
|
|
1943
|
+
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
|
|
1944
|
+
continue;
|
|
1945
|
+
}
|
|
1946
|
+
if (wanted.has(extname(entry.name))) found.push(relative3(cwd, full));
|
|
1480
1947
|
}
|
|
1481
1948
|
}
|
|
1482
|
-
return
|
|
1949
|
+
return found;
|
|
1483
1950
|
}
|
|
1484
1951
|
|
|
1485
|
-
// src/
|
|
1486
|
-
var
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
"useTabs",
|
|
1491
|
-
"tabWidth",
|
|
1492
|
-
"printWidth",
|
|
1493
|
-
"semi",
|
|
1494
|
-
"singleQuote",
|
|
1495
|
-
"jsxSingleQuote",
|
|
1496
|
-
"trailingComma",
|
|
1497
|
-
"quoteProps",
|
|
1498
|
-
"bracketSpacing",
|
|
1499
|
-
"bracketSameLine",
|
|
1500
|
-
"arrowParens",
|
|
1501
|
-
"endOfLine",
|
|
1502
|
-
"objectWrap",
|
|
1503
|
-
"proseWrap",
|
|
1504
|
-
"experimentalOperatorPosition"
|
|
1505
|
-
];
|
|
1506
|
-
var ALWAYS_LOCAL_KEYS = ["ignorePatterns", "overrides"];
|
|
1507
|
-
var FORMAT_DEFAULT_IGNORES = ["**/*.toml"];
|
|
1508
|
-
var PRETTIER_CONFIG_FILES = [
|
|
1509
|
-
".prettierrc",
|
|
1510
|
-
".prettierrc.json",
|
|
1511
|
-
".prettierrc.json5",
|
|
1512
|
-
".prettierrc.yml",
|
|
1513
|
-
".prettierrc.yaml",
|
|
1514
|
-
".prettierrc.js",
|
|
1515
|
-
".prettierrc.cjs",
|
|
1516
|
-
".prettierrc.mjs",
|
|
1517
|
-
"prettier.config.js",
|
|
1518
|
-
"prettier.config.cjs",
|
|
1519
|
-
"prettier.config.mjs"
|
|
1520
|
-
];
|
|
1521
|
-
function formatTargets() {
|
|
1522
|
-
return {
|
|
1523
|
-
[FORMAT_SCRIPT_NAME]: {
|
|
1524
|
-
cache: true,
|
|
1525
|
-
inputs: ["default", `{projectRoot}/${FORMAT_CONFIG_FILE}`]
|
|
1526
|
-
},
|
|
1527
|
-
[`${FORMAT_SCRIPT_NAME}:fix`]: { cache: false }
|
|
1528
|
-
};
|
|
1952
|
+
// src/core/named-scope.ts
|
|
1953
|
+
var TOLERATE_EMPTY_FLAG = "--no-error-on-unmatched-pattern";
|
|
1954
|
+
function scopeFlags(paths, options = []) {
|
|
1955
|
+
if (paths.length === 0 || options.includes(TOLERATE_EMPTY_FLAG)) return [];
|
|
1956
|
+
return [TOLERATE_EMPTY_FLAG];
|
|
1529
1957
|
}
|
|
1530
1958
|
|
|
1531
1959
|
// src/roles/format/format-script.ts
|
|
@@ -1556,8 +1984,8 @@ function writesWhenRewritten(name, command) {
|
|
|
1556
1984
|
var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
|
|
1557
1985
|
|
|
1558
1986
|
// src/roles/format/inherited-ignores.ts
|
|
1559
|
-
import { existsSync as
|
|
1560
|
-
import { join as
|
|
1987
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
1988
|
+
import { join as join15 } from "path";
|
|
1561
1989
|
var ROOT_IGNORE_FILE = ".prettierignore";
|
|
1562
1990
|
function isPattern(line) {
|
|
1563
1991
|
const trimmed = line.trim();
|
|
@@ -1567,16 +1995,17 @@ function toModulePattern(pattern) {
|
|
|
1567
1995
|
const trimmed = pattern.trim();
|
|
1568
1996
|
if (trimmed.startsWith("!")) return void 0;
|
|
1569
1997
|
if (trimmed.startsWith("/**/")) return trimmed.slice(1);
|
|
1998
|
+
if (trimmed.startsWith("**/")) return trimmed;
|
|
1570
1999
|
if (trimmed.startsWith("/")) return void 0;
|
|
1571
2000
|
return trimmed.replace(/\/$/, "").includes("/") ? void 0 : trimmed;
|
|
1572
2001
|
}
|
|
1573
2002
|
function inheritedIgnorePatterns(workspaceRoot) {
|
|
1574
2003
|
if (!workspaceRoot) return [];
|
|
1575
|
-
const path =
|
|
1576
|
-
if (!
|
|
2004
|
+
const path = join15(workspaceRoot, ROOT_IGNORE_FILE);
|
|
2005
|
+
if (!existsSync14(path)) return [];
|
|
1577
2006
|
let contents;
|
|
1578
2007
|
try {
|
|
1579
|
-
contents =
|
|
2008
|
+
contents = readFileSync11(path, "utf8");
|
|
1580
2009
|
} catch {
|
|
1581
2010
|
return [];
|
|
1582
2011
|
}
|
|
@@ -1620,56 +2049,14 @@ function needsSvelteCompiler(output) {
|
|
|
1620
2049
|
return /svelte\/compiler/.test(output);
|
|
1621
2050
|
}
|
|
1622
2051
|
|
|
1623
|
-
// src/roles/format/presets/base.json
|
|
1624
|
-
var base_default = {
|
|
1625
|
-
printWidth: 80,
|
|
1626
|
-
semi: false,
|
|
1627
|
-
singleQuote: true,
|
|
1628
|
-
trailingComma: "all",
|
|
1629
|
-
sortPackageJson: false,
|
|
1630
|
-
sortImports: false
|
|
1631
|
-
};
|
|
1632
|
-
|
|
1633
|
-
// src/roles/format/presets/nest-imports.json
|
|
1634
|
-
var nest_imports_default = {
|
|
1635
|
-
customGroups: [
|
|
1636
|
-
{
|
|
1637
|
-
groupName: "workspace",
|
|
1638
|
-
elementNamePattern: ["@hublo/**", "@shared/**", "@front/**", "@network/**"]
|
|
1639
|
-
}
|
|
1640
|
-
],
|
|
1641
|
-
groups: ["builtin", "external", "workspace", ["parent", "index"], "sibling"],
|
|
1642
|
-
newlinesBetween: true,
|
|
1643
|
-
order: "asc",
|
|
1644
|
-
ignoreCase: false
|
|
1645
|
-
};
|
|
1646
|
-
|
|
1647
|
-
// src/roles/format/preset-data.ts
|
|
1648
|
-
var FORMAT_PRESETS = ["base", "svelte", "nest"];
|
|
1649
|
-
function hasFormatPreset(name) {
|
|
1650
|
-
return FORMAT_PRESETS.includes(name);
|
|
1651
|
-
}
|
|
1652
|
-
function formatPresetName(declared) {
|
|
1653
|
-
if (declared === "svelte") return "svelte";
|
|
1654
|
-
if (declared === "nest") return "nest";
|
|
1655
|
-
return "base";
|
|
1656
|
-
}
|
|
1657
|
-
function formatPresetFor(preset) {
|
|
1658
|
-
const base = base_default;
|
|
1659
|
-
const name = formatPresetName(preset);
|
|
1660
|
-
if (name === "svelte") return { ...base, svelte: true };
|
|
1661
|
-
if (name === "nest") return { ...base, sortImports: nest_imports_default };
|
|
1662
|
-
return base;
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
2052
|
// src/roles/format/prettier-config.ts
|
|
1666
|
-
import { existsSync as
|
|
1667
|
-
import { join as
|
|
2053
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
2054
|
+
import { join as join17 } from "path";
|
|
1668
2055
|
|
|
1669
2056
|
// src/roles/format/resolve-oxfmt.ts
|
|
1670
|
-
import { readFileSync as
|
|
2057
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
1671
2058
|
import { createRequire as createRequire2 } from "module";
|
|
1672
|
-
import { dirname as dirname4, join as
|
|
2059
|
+
import { dirname as dirname4, join as join16 } from "path";
|
|
1673
2060
|
function resolveOxfmt(cwd) {
|
|
1674
2061
|
return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
|
|
1675
2062
|
}
|
|
@@ -1698,8 +2085,8 @@ function configSchema() {
|
|
|
1698
2085
|
function readConfigSchema() {
|
|
1699
2086
|
try {
|
|
1700
2087
|
const manifest = createRequire2(import.meta.url).resolve("oxfmt/package.json");
|
|
1701
|
-
const schemaPath =
|
|
1702
|
-
return JSON.parse(
|
|
2088
|
+
const schemaPath = join16(dirname4(manifest), "configuration_schema.json");
|
|
2089
|
+
return JSON.parse(readFileSync12(schemaPath, "utf8"));
|
|
1703
2090
|
} catch {
|
|
1704
2091
|
return void 0;
|
|
1705
2092
|
}
|
|
@@ -1730,14 +2117,14 @@ function toOxfmtOverrides(value) {
|
|
|
1730
2117
|
return { overrides, unresolved };
|
|
1731
2118
|
}
|
|
1732
2119
|
function readPrettierSettings(cwd) {
|
|
1733
|
-
const file = PRETTIER_CONFIG_FILES.find((name) =>
|
|
2120
|
+
const file = PRETTIER_CONFIG_FILES.find((name) => existsSync15(join17(cwd, name)));
|
|
1734
2121
|
if (!file) return { options: {}, unresolved: [] };
|
|
1735
2122
|
if (/\.(js|cjs|mjs)$/.test(file)) {
|
|
1736
2123
|
return { options: {}, file, unresolved: [`${file} is JavaScript, so it was not executed`] };
|
|
1737
2124
|
}
|
|
1738
2125
|
let parsed;
|
|
1739
2126
|
try {
|
|
1740
|
-
parsed = parseJsonc(
|
|
2127
|
+
parsed = parseJsonc(readFileSync13(join17(cwd, file), "utf8"), file);
|
|
1741
2128
|
} catch {
|
|
1742
2129
|
return { options: {}, file, unresolved: [`${file} could not be parsed`] };
|
|
1743
2130
|
}
|
|
@@ -1766,71 +2153,6 @@ function readPrettierSettings(cwd) {
|
|
|
1766
2153
|
return { options, file, unresolved };
|
|
1767
2154
|
}
|
|
1768
2155
|
|
|
1769
|
-
// src/roles/format/read-adoption.ts
|
|
1770
|
-
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
1771
|
-
import { join as join14 } from "path";
|
|
1772
|
-
var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
|
|
1773
|
-
configFile,
|
|
1774
|
-
preset: null,
|
|
1775
|
-
adopted: false,
|
|
1776
|
-
conformant: false,
|
|
1777
|
-
drift: [],
|
|
1778
|
-
local: {},
|
|
1779
|
-
presetVersion: null,
|
|
1780
|
-
effective: {},
|
|
1781
|
-
unreadable
|
|
1782
|
-
});
|
|
1783
|
-
function sameValue(a, b) {
|
|
1784
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
1785
|
-
}
|
|
1786
|
-
function readFormatAdoption(cwd) {
|
|
1787
|
-
const path = join14(cwd, FORMAT_CONFIG_FILE);
|
|
1788
|
-
if (!existsSync12(path)) return NOT_ADOPTED2(null);
|
|
1789
|
-
let parsed;
|
|
1790
|
-
try {
|
|
1791
|
-
parsed = parseJsonc(readFileSync11(path, "utf8"), FORMAT_CONFIG_FILE);
|
|
1792
|
-
} catch (error) {
|
|
1793
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
1794
|
-
return NOT_ADOPTED2(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
|
|
1795
|
-
}
|
|
1796
|
-
const provenance = parsed[PROVENANCE_KEY];
|
|
1797
|
-
if (!provenance || typeof provenance.preset !== "string") return NOT_ADOPTED2(FORMAT_CONFIG_FILE);
|
|
1798
|
-
if (!hasFormatPreset(provenance.preset)) {
|
|
1799
|
-
return NOT_ADOPTED2(
|
|
1800
|
-
FORMAT_CONFIG_FILE,
|
|
1801
|
-
`${FORMAT_CONFIG_FILE} declares the format preset "${provenance.preset}", which sentinel does not ship (shipped: ${FORMAT_PRESETS.join(", ")}). Re-run \`sentinel --init --format\`.`
|
|
1802
|
-
);
|
|
1803
|
-
}
|
|
1804
|
-
const declaredLocal = Array.isArray(provenance.local) ? provenance.local : [];
|
|
1805
|
-
const preset = formatPresetFor(provenance.preset);
|
|
1806
|
-
const drift = [];
|
|
1807
|
-
const local = {};
|
|
1808
|
-
for (const [key, expected] of Object.entries(preset)) {
|
|
1809
|
-
if (declaredLocal.includes(key)) {
|
|
1810
|
-
local[key] = parsed[key];
|
|
1811
|
-
continue;
|
|
1812
|
-
}
|
|
1813
|
-
if (!(key in parsed) || !sameValue(parsed[key], expected)) drift.push(key);
|
|
1814
|
-
}
|
|
1815
|
-
for (const key of declaredLocal) {
|
|
1816
|
-
if (!(key in preset)) local[key] = parsed[key];
|
|
1817
|
-
}
|
|
1818
|
-
for (const key of ALWAYS_LOCAL_KEYS) {
|
|
1819
|
-
if (key in parsed) local[key] = parsed[key];
|
|
1820
|
-
}
|
|
1821
|
-
return {
|
|
1822
|
-
configFile: FORMAT_CONFIG_FILE,
|
|
1823
|
-
preset: provenance.preset,
|
|
1824
|
-
adopted: true,
|
|
1825
|
-
conformant: drift.length === 0,
|
|
1826
|
-
drift,
|
|
1827
|
-
local,
|
|
1828
|
-
presetVersion: typeof provenance.version === "string" ? provenance.version : null,
|
|
1829
|
-
effective: parsed,
|
|
1830
|
-
unreadable: null
|
|
1831
|
-
};
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
2156
|
// src/roles/format/unenforced.ts
|
|
1835
2157
|
function unenforcedFormatOptions(options) {
|
|
1836
2158
|
const gaps = [];
|
|
@@ -1913,7 +2235,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1913
2235
|
manifestOperation(context.cwd, this.formatScripts(context.cwd))
|
|
1914
2236
|
];
|
|
1915
2237
|
const prettierConfigs = PRETTIER_CONFIG_FILES.filter(
|
|
1916
|
-
(name) =>
|
|
2238
|
+
(name) => existsSync16(join18(context.cwd, name))
|
|
1917
2239
|
);
|
|
1918
2240
|
for (const name of prettierConfigs) operations.push({ kind: "delete", path: name });
|
|
1919
2241
|
const removableDeps = this.modulePrettierDependencies(context.cwd);
|
|
@@ -2153,7 +2475,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
2153
2475
|
modulePrettierDependencies(cwd) {
|
|
2154
2476
|
const isPrettierPackage = (name) => name === "prettier" || name.startsWith("prettier-plugin-") || name.startsWith("@prettier/");
|
|
2155
2477
|
try {
|
|
2156
|
-
const manifest = JSON.parse(
|
|
2478
|
+
const manifest = JSON.parse(readFileSync14(join18(cwd, "package.json"), "utf8"));
|
|
2157
2479
|
return Object.keys(manifest.devDependencies ?? {}).filter(isPrettierPackage).map((name) => ["devDependencies", name]);
|
|
2158
2480
|
} catch {
|
|
2159
2481
|
return [];
|
|
@@ -2235,8 +2557,8 @@ function registerFormat() {
|
|
|
2235
2557
|
|
|
2236
2558
|
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
2237
2559
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
2238
|
-
import { existsSync as
|
|
2239
|
-
import { join as
|
|
2560
|
+
import { existsSync as existsSync24, readFileSync as readFileSync22, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2561
|
+
import { join as join28 } from "path";
|
|
2240
2562
|
|
|
2241
2563
|
// src/core/config/deferred-rules.ts
|
|
2242
2564
|
function deferredRuleNames(rules) {
|
|
@@ -2244,8 +2566,8 @@ function deferredRuleNames(rules) {
|
|
|
2244
2566
|
}
|
|
2245
2567
|
|
|
2246
2568
|
// src/core/fix-report.ts
|
|
2247
|
-
import { statSync } from "fs";
|
|
2248
|
-
import { join as
|
|
2569
|
+
import { statSync as statSync2 } from "fs";
|
|
2570
|
+
import { join as join19 } from "path";
|
|
2249
2571
|
var CONFIG_REFUSED = /failed to parse .*config|invalid config file/i;
|
|
2250
2572
|
function readFailure(output) {
|
|
2251
2573
|
const lines = output.split("\n").map((line2) => line2.trim());
|
|
@@ -2256,7 +2578,7 @@ function readFailure(output) {
|
|
|
2256
2578
|
}
|
|
2257
2579
|
function fingerprint(path) {
|
|
2258
2580
|
try {
|
|
2259
|
-
const stat =
|
|
2581
|
+
const stat = statSync2(path);
|
|
2260
2582
|
return `${stat.size}:${stat.mtimeMs}`;
|
|
2261
2583
|
} catch {
|
|
2262
2584
|
return void 0;
|
|
@@ -2265,7 +2587,7 @@ function fingerprint(path) {
|
|
|
2265
2587
|
function snapshotFiles(cwd, extensions) {
|
|
2266
2588
|
const snapshot = /* @__PURE__ */ new Map();
|
|
2267
2589
|
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2268
|
-
const mark = fingerprint(
|
|
2590
|
+
const mark = fingerprint(join19(cwd, file));
|
|
2269
2591
|
if (mark !== void 0) snapshot.set(file, mark);
|
|
2270
2592
|
}
|
|
2271
2593
|
return snapshot;
|
|
@@ -2273,7 +2595,7 @@ function snapshotFiles(cwd, extensions) {
|
|
|
2273
2595
|
function changedSince(cwd, before, extensions) {
|
|
2274
2596
|
const changed = [];
|
|
2275
2597
|
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2276
|
-
const now = fingerprint(
|
|
2598
|
+
const now = fingerprint(join19(cwd, file));
|
|
2277
2599
|
if (now === void 0) continue;
|
|
2278
2600
|
if (before.get(file) !== now) changed.push(file);
|
|
2279
2601
|
}
|
|
@@ -2292,8 +2614,8 @@ function describeFixOutcome(outcome, toolLabel) {
|
|
|
2292
2614
|
}
|
|
2293
2615
|
|
|
2294
2616
|
// src/roles/lint/module-baseline.ts
|
|
2295
|
-
import { existsSync as
|
|
2296
|
-
import { join as
|
|
2617
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
|
|
2618
|
+
import { join as join20 } from "path";
|
|
2297
2619
|
var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
|
|
2298
2620
|
var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
|
|
2299
2621
|
var LINT_BASELINE_SPECIFIER = `./${LINT_BASELINE_FILE}`;
|
|
@@ -2337,11 +2659,11 @@ function describeHolds(holds) {
|
|
|
2337
2659
|
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`;
|
|
2338
2660
|
}
|
|
2339
2661
|
function heldRules(cwd) {
|
|
2340
|
-
const path =
|
|
2341
|
-
if (!
|
|
2662
|
+
const path = join20(cwd, LINT_BASELINE_FILE);
|
|
2663
|
+
if (!existsSync17(path)) return [];
|
|
2342
2664
|
try {
|
|
2343
2665
|
const parsed = parseJsonc(
|
|
2344
|
-
|
|
2666
|
+
readFileSync15(path, "utf8"),
|
|
2345
2667
|
LINT_BASELINE_FILE
|
|
2346
2668
|
);
|
|
2347
2669
|
return Object.keys(parsed.rules ?? {});
|
|
@@ -2772,7 +3094,8 @@ var react_lib_default = {
|
|
|
2772
3094
|
{
|
|
2773
3095
|
name: "storybook",
|
|
2774
3096
|
specifier: "eslint-plugin-storybook"
|
|
2775
|
-
}
|
|
3097
|
+
},
|
|
3098
|
+
"../plugins/hublo.js"
|
|
2776
3099
|
],
|
|
2777
3100
|
settings: null,
|
|
2778
3101
|
renames: {
|
|
@@ -2781,6 +3104,7 @@ var react_lib_default = {
|
|
|
2781
3104
|
rules: {
|
|
2782
3105
|
curly: ["error", "all"],
|
|
2783
3106
|
"for-direction": "error",
|
|
3107
|
+
"hublo/no-regex-lookbehind": "error",
|
|
2784
3108
|
"jest-dom/prefer-checked": "error",
|
|
2785
3109
|
"jest-dom/prefer-empty": "error",
|
|
2786
3110
|
"jest-dom/prefer-enabled-disabled": "error",
|
|
@@ -3132,7 +3456,7 @@ var react_lib_default = {
|
|
|
3132
3456
|
],
|
|
3133
3457
|
uncovered: {
|
|
3134
3458
|
"no-octal": "no oxlint equivalent",
|
|
3135
|
-
"no-restricted-syntax": "no oxlint equivalent; the house rules it encoded ship as the local `hublo` plugin instead,
|
|
3459
|
+
"no-restricted-syntax": "no oxlint equivalent; the house rules it encoded ship as the local `hublo` plugin instead, so this tier hosts `../plugins/hublo.js` too. It enforces exactly one of them: `hublo/no-regex-lookbehind`, because a library is where that bug is WRITTEN and it reaches an app tier only by being imported (HUB-43431 shipped from libs/front/components into the worker app). The palette selectors stay app-tier only.",
|
|
3136
3460
|
semi: "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between."
|
|
3137
3461
|
}
|
|
3138
3462
|
};
|
|
@@ -3196,6 +3520,7 @@ var react_default = {
|
|
|
3196
3520
|
severity: "off",
|
|
3197
3521
|
reason: "the house rule finds 3 real violations; they are fixed as their own change rather than inside a linter swap, so the rule ships visible-but-off and can be switched on once they are"
|
|
3198
3522
|
},
|
|
3523
|
+
"hublo/no-regex-lookbehind": "error",
|
|
3199
3524
|
"jest/expect-expect": {
|
|
3200
3525
|
severity: "off",
|
|
3201
3526
|
reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
|
|
@@ -3682,7 +4007,7 @@ var react_default = {
|
|
|
3682
4007
|
uncovered: {
|
|
3683
4008
|
"import/order": "import ordering is FORMATTING, and the format role owns it: oxfmt's `sortImports` reproduces exactly these groups (builtin, external, internal, parent/index, sibling, alphabetical, blank line between) and sorts a file completely in ONE pass, because it rewrites the whole import block. A lint fixer cannot: it emits text edits over overlapping ranges, so this rule needed several runs to finish (five imports took four passes, measured) and sentinel deliberately runs autofix once. Enforced by `pnpm run format`, not by the linter, which is also why `eslint-plugin-import` is no longer hosted at all: its only other rule, no-duplicates, is native in oxlint.",
|
|
3684
4009
|
"no-octal": "no oxlint equivalent",
|
|
3685
|
-
"no-restricted-syntax": "no oxlint equivalent as configuration, so the
|
|
4010
|
+
"no-restricted-syntax": "no oxlint equivalent as configuration, so the AST selectors this repo used are shipped as real rules in the local `hublo` plugin instead, which this tier hosts (`../plugins/hublo.js`). Two are hosted today: the eight palette selectors as `hublo/no-palette-common`, and the lookbehind selector as `hublo/no-regex-lookbehind`. Any selector NOT in that plugin is not enforced here; propose it and it joins the plugin rather than a per-module copy.",
|
|
3686
4011
|
semi: "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between."
|
|
3687
4012
|
}
|
|
3688
4013
|
};
|
|
@@ -4164,14 +4489,14 @@ function downgradedRulesFor(preset) {
|
|
|
4164
4489
|
}
|
|
4165
4490
|
|
|
4166
4491
|
// src/roles/lint/extra-layers.ts
|
|
4167
|
-
import { existsSync as
|
|
4168
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4492
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
4493
|
+
import { isAbsolute as isAbsolute2, join as join21, resolve as resolve3 } from "path";
|
|
4169
4494
|
function ruleCount(cwd, specifier) {
|
|
4170
4495
|
const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
|
|
4171
|
-
if (!
|
|
4496
|
+
if (!existsSync18(path)) return void 0;
|
|
4172
4497
|
try {
|
|
4173
4498
|
const parsed = parseJsonc(
|
|
4174
|
-
|
|
4499
|
+
readFileSync16(path, "utf8"),
|
|
4175
4500
|
specifier
|
|
4176
4501
|
);
|
|
4177
4502
|
return Object.keys(parsed.rules ?? {}).length;
|
|
@@ -4191,7 +4516,7 @@ function extraLayers(cwd, extendsList) {
|
|
|
4191
4516
|
function committedExtendsList(cwd, configFile) {
|
|
4192
4517
|
try {
|
|
4193
4518
|
const parsed = parseJsonc(
|
|
4194
|
-
|
|
4519
|
+
readFileSync16(join21(cwd, configFile), "utf8"),
|
|
4195
4520
|
configFile
|
|
4196
4521
|
);
|
|
4197
4522
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4250,8 +4575,8 @@ function errorCountsByConfigRule(stdout) {
|
|
|
4250
4575
|
}
|
|
4251
4576
|
|
|
4252
4577
|
// src/core/config/read-adoption.ts
|
|
4253
|
-
import { existsSync as
|
|
4254
|
-
import { join as
|
|
4578
|
+
import { existsSync as existsSync20, readFileSync as readFileSync18 } from "fs";
|
|
4579
|
+
import { join as join23 } from "path";
|
|
4255
4580
|
|
|
4256
4581
|
// src/core/config/owned-keys.ts
|
|
4257
4582
|
function presetOwnedKeys(config, permitted, presetSets) {
|
|
@@ -4266,12 +4591,12 @@ function localOnlyKeys(config, permitted, presetSets) {
|
|
|
4266
4591
|
}
|
|
4267
4592
|
|
|
4268
4593
|
// src/core/config/resolve-config-target.ts
|
|
4269
|
-
import { existsSync as
|
|
4270
|
-
import { join as
|
|
4594
|
+
import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
|
|
4595
|
+
import { join as join22 } from "path";
|
|
4271
4596
|
function readExtends(absolutePath) {
|
|
4272
4597
|
let parsed;
|
|
4273
4598
|
try {
|
|
4274
|
-
parsed = parseJsonc(
|
|
4599
|
+
parsed = parseJsonc(readFileSync17(absolutePath, "utf8"), absolutePath);
|
|
4275
4600
|
} catch {
|
|
4276
4601
|
return [];
|
|
4277
4602
|
}
|
|
@@ -4285,8 +4610,8 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
|
|
|
4285
4610
|
let existing;
|
|
4286
4611
|
let existingExtendsSomething = false;
|
|
4287
4612
|
for (const candidate of candidates) {
|
|
4288
|
-
const absolutePath =
|
|
4289
|
-
if (!
|
|
4613
|
+
const absolutePath = join22(moduleDir, candidate);
|
|
4614
|
+
if (!existsSync19(absolutePath)) continue;
|
|
4290
4615
|
const chain = readExtends(absolutePath);
|
|
4291
4616
|
if (existing === void 0) {
|
|
4292
4617
|
existing = candidate;
|
|
@@ -4319,12 +4644,12 @@ var NOT_ADOPTED3 = (configFile, unreadable = null) => ({
|
|
|
4319
4644
|
});
|
|
4320
4645
|
function readAdoption(cwd, options) {
|
|
4321
4646
|
const target = resolveConfigTarget(cwd, options);
|
|
4322
|
-
if (target.reason === "none" || !
|
|
4647
|
+
if (target.reason === "none" || !existsSync20(join23(cwd, target.path))) {
|
|
4323
4648
|
return NOT_ADOPTED3(target.reason === "none" ? null : target.path);
|
|
4324
4649
|
}
|
|
4325
4650
|
let parsed;
|
|
4326
4651
|
try {
|
|
4327
|
-
parsed = parseJsonc(
|
|
4652
|
+
parsed = parseJsonc(readFileSync18(join23(cwd, target.path), "utf8"), target.path);
|
|
4328
4653
|
} catch (error) {
|
|
4329
4654
|
const reason = error instanceof Error ? error.message : String(error);
|
|
4330
4655
|
return NOT_ADOPTED3(target.path, `${target.path} could not be parsed (${reason})`);
|
|
@@ -4358,9 +4683,9 @@ function readLintAdoption(cwd) {
|
|
|
4358
4683
|
}
|
|
4359
4684
|
|
|
4360
4685
|
// src/roles/lint/resolve-oxlint.ts
|
|
4361
|
-
import { existsSync as
|
|
4686
|
+
import { existsSync as existsSync21 } from "fs";
|
|
4362
4687
|
import { createRequire as createRequire3 } from "module";
|
|
4363
|
-
import { delimiter as delimiter2, dirname as dirname5, join as
|
|
4688
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join24 } from "path";
|
|
4364
4689
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4365
4690
|
var PACKAGE_OF = {
|
|
4366
4691
|
oxlint: "oxlint",
|
|
@@ -4385,30 +4710,30 @@ function tsgolintShim(cwd) {
|
|
|
4385
4710
|
for (const owner of ["oxlint-tsgolint", "oxlint"]) {
|
|
4386
4711
|
try {
|
|
4387
4712
|
const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
|
|
4388
|
-
candidates.push(
|
|
4389
|
-
candidates.push(
|
|
4713
|
+
candidates.push(join24(packageDir, "node_modules", ".bin", "tsgolint"));
|
|
4714
|
+
candidates.push(join24(packageDir, "..", ".bin", "tsgolint"));
|
|
4390
4715
|
} catch {
|
|
4391
4716
|
}
|
|
4392
4717
|
}
|
|
4393
4718
|
try {
|
|
4394
4719
|
const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
|
|
4395
|
-
candidates.push(
|
|
4720
|
+
candidates.push(join24(ownRoot, "node_modules", ".bin", "tsgolint"));
|
|
4396
4721
|
} catch {
|
|
4397
4722
|
candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
|
|
4398
4723
|
}
|
|
4399
|
-
return candidates.find((candidate) => candidate !== "" &&
|
|
4724
|
+
return candidates.find((candidate) => candidate !== "" && existsSync21(candidate));
|
|
4400
4725
|
}
|
|
4401
4726
|
function oxlintSearchPath(cwd) {
|
|
4402
4727
|
return binSearchPath(cwd);
|
|
4403
4728
|
}
|
|
4404
4729
|
|
|
4405
4730
|
// src/roles/lint/adapters/oxlint/plan.ts
|
|
4406
|
-
import { existsSync as
|
|
4407
|
-
import { join as
|
|
4731
|
+
import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
|
|
4732
|
+
import { join as join27 } from "path";
|
|
4408
4733
|
|
|
4409
4734
|
// src/roles/lint/eslint-ignores.ts
|
|
4410
|
-
import { existsSync as
|
|
4411
|
-
import { join as
|
|
4735
|
+
import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
|
|
4736
|
+
import { join as join25 } from "path";
|
|
4412
4737
|
import { parseSync as parseSync2 } from "oxc-parser";
|
|
4413
4738
|
var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
|
|
4414
4739
|
function toOxlintPattern(pattern) {
|
|
@@ -4420,11 +4745,11 @@ function readRootEslintIgnores(root) {
|
|
|
4420
4745
|
return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
|
|
4421
4746
|
}
|
|
4422
4747
|
function readEslintIgnores(cwd) {
|
|
4423
|
-
const config = ESLINT_CONFIG_FILES.map((name) =>
|
|
4748
|
+
const config = ESLINT_CONFIG_FILES.map((name) => join25(cwd, name)).find((path) => existsSync22(path));
|
|
4424
4749
|
if (!config) return { patterns: [], unresolved: [] };
|
|
4425
4750
|
let source;
|
|
4426
4751
|
try {
|
|
4427
|
-
source =
|
|
4752
|
+
source = readFileSync19(config, "utf8");
|
|
4428
4753
|
} catch {
|
|
4429
4754
|
return { patterns: [], unresolved: [] };
|
|
4430
4755
|
}
|
|
@@ -4525,8 +4850,8 @@ function lintPresetFor(preset) {
|
|
|
4525
4850
|
}
|
|
4526
4851
|
|
|
4527
4852
|
// src/roles/lint/rename-suppressions.ts
|
|
4528
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
4529
|
-
import { join as
|
|
4853
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync20, statSync as statSync3 } from "fs";
|
|
4854
|
+
import { join as join26, relative as relative4 } from "path";
|
|
4530
4855
|
var SOURCE_EXTENSIONS = [
|
|
4531
4856
|
".ts",
|
|
4532
4857
|
".tsx",
|
|
@@ -4573,10 +4898,10 @@ function* sourceFiles(dir) {
|
|
|
4573
4898
|
return;
|
|
4574
4899
|
}
|
|
4575
4900
|
for (const entry of entries) {
|
|
4576
|
-
const full =
|
|
4901
|
+
const full = join26(dir, entry);
|
|
4577
4902
|
let isDirectory;
|
|
4578
4903
|
try {
|
|
4579
|
-
isDirectory =
|
|
4904
|
+
isDirectory = statSync3(full).isDirectory();
|
|
4580
4905
|
} catch {
|
|
4581
4906
|
continue;
|
|
4582
4907
|
}
|
|
@@ -4593,7 +4918,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
4593
4918
|
for (const file of sourceFiles(cwd)) {
|
|
4594
4919
|
let content;
|
|
4595
4920
|
try {
|
|
4596
|
-
content =
|
|
4921
|
+
content = readFileSync20(file, "utf8");
|
|
4597
4922
|
} catch {
|
|
4598
4923
|
continue;
|
|
4599
4924
|
}
|
|
@@ -4617,7 +4942,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
4617
4942
|
}
|
|
4618
4943
|
if (hits.length === 0) return;
|
|
4619
4944
|
found.push({
|
|
4620
|
-
file:
|
|
4945
|
+
file: relative4(cwd, file),
|
|
4621
4946
|
line: index + 1,
|
|
4622
4947
|
from: line,
|
|
4623
4948
|
to: line.replace(rules, renamed.join(", ")),
|
|
@@ -4721,7 +5046,7 @@ function plan2(context) {
|
|
|
4721
5046
|
keys: removableDeps
|
|
4722
5047
|
});
|
|
4723
5048
|
}
|
|
4724
|
-
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) =>
|
|
5049
|
+
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync23(join27(context.cwd, name)));
|
|
4725
5050
|
for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
|
|
4726
5051
|
operations.push(
|
|
4727
5052
|
...nxTargetOperations({
|
|
@@ -4819,7 +5144,7 @@ function lintScripts(cwd) {
|
|
|
4819
5144
|
function committedExtends(cwd) {
|
|
4820
5145
|
try {
|
|
4821
5146
|
const parsed = parseJsonc(
|
|
4822
|
-
|
|
5147
|
+
readFileSync21(join27(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4823
5148
|
LINT_CONFIG_FILE
|
|
4824
5149
|
);
|
|
4825
5150
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4830,7 +5155,7 @@ function committedExtends(cwd) {
|
|
|
4830
5155
|
function committedIgnorePatterns(cwd) {
|
|
4831
5156
|
try {
|
|
4832
5157
|
const parsed = parseJsonc(
|
|
4833
|
-
|
|
5158
|
+
readFileSync21(join27(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4834
5159
|
LINT_CONFIG_FILE
|
|
4835
5160
|
);
|
|
4836
5161
|
return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4842,7 +5167,7 @@ function moduleEslintDependencies(cwd) {
|
|
|
4842
5167
|
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/");
|
|
4843
5168
|
let manifest;
|
|
4844
5169
|
try {
|
|
4845
|
-
manifest = JSON.parse(
|
|
5170
|
+
manifest = JSON.parse(readFileSync21(join27(cwd, "package.json"), "utf8"));
|
|
4846
5171
|
} catch {
|
|
4847
5172
|
return [];
|
|
4848
5173
|
}
|
|
@@ -4976,23 +5301,23 @@ ${result.stderr ?? ""}`);
|
|
|
4976
5301
|
* build the developer can see, rather than a silent half-adoption they cannot.
|
|
4977
5302
|
*/
|
|
4978
5303
|
writeModuleBaseline(ctx, oxlint, env) {
|
|
4979
|
-
const configPath =
|
|
4980
|
-
const baselinePath =
|
|
4981
|
-
if (!
|
|
5304
|
+
const configPath = join28(ctx.cwd, LINT_CONFIG_FILE);
|
|
5305
|
+
const baselinePath = join28(ctx.cwd, LINT_BASELINE_FILE);
|
|
5306
|
+
if (!existsSync24(configPath)) return;
|
|
4982
5307
|
let config;
|
|
4983
5308
|
try {
|
|
4984
5309
|
config = parseJsonc(
|
|
4985
|
-
|
|
5310
|
+
readFileSync22(configPath, "utf8"),
|
|
4986
5311
|
LINT_CONFIG_FILE
|
|
4987
5312
|
);
|
|
4988
5313
|
} catch {
|
|
4989
5314
|
return;
|
|
4990
5315
|
}
|
|
4991
5316
|
const current = Array.isArray(config.extends) ? config.extends : [];
|
|
4992
|
-
const measurePath =
|
|
5317
|
+
const measurePath = join28(ctx.cwd, LINT_MEASURE_FILE);
|
|
4993
5318
|
let measured;
|
|
4994
5319
|
try {
|
|
4995
|
-
|
|
5320
|
+
writeFileSync3(
|
|
4996
5321
|
measurePath,
|
|
4997
5322
|
`${JSON.stringify({ ...config, extends: extendsWithBaseline(current, false) }, null, 2)}
|
|
4998
5323
|
`
|
|
@@ -5010,7 +5335,7 @@ ${result.stderr ?? ""}`);
|
|
|
5010
5335
|
{ cwd: ctx.cwd, encoding: "utf8", env, maxBuffer: CAPTURE_MAX_BUFFER }
|
|
5011
5336
|
);
|
|
5012
5337
|
} finally {
|
|
5013
|
-
|
|
5338
|
+
rmSync2(measurePath, { force: true });
|
|
5014
5339
|
}
|
|
5015
5340
|
if (measured.error || !(measured.stdout ?? "").includes('"diagnostics"')) {
|
|
5016
5341
|
process.stderr.write(
|
|
@@ -5024,20 +5349,20 @@ ${result.stderr ?? ""}`);
|
|
|
5024
5349
|
const holds = holdsFrom(errorCountsByConfigRule(measured.stdout ?? ""));
|
|
5025
5350
|
const next = extendsWithBaseline(current, holds.length > 0);
|
|
5026
5351
|
if (holds.length > 0) {
|
|
5027
|
-
|
|
5352
|
+
writeFileSync3(baselinePath, renderBaseline(holds, readOwnPackage().version));
|
|
5028
5353
|
process.stderr.write(` ${palette(process.stderr).warn(describeHolds(holds))}
|
|
5029
5354
|
`);
|
|
5030
5355
|
} else {
|
|
5031
|
-
|
|
5356
|
+
rmSync2(baselinePath, { force: true });
|
|
5032
5357
|
}
|
|
5033
5358
|
if (next.length !== current.length || next.some((entry, at) => entry !== current[at])) {
|
|
5034
5359
|
config.extends = next;
|
|
5035
|
-
|
|
5360
|
+
writeFileSync3(configPath, `${JSON.stringify(config, null, 2)}
|
|
5036
5361
|
`);
|
|
5037
5362
|
}
|
|
5038
5363
|
}
|
|
5039
5364
|
async run(ctx) {
|
|
5040
|
-
if (!
|
|
5365
|
+
if (!existsSync24(join28(ctx.cwd, LINT_CONFIG_FILE))) {
|
|
5041
5366
|
process.stderr.write(
|
|
5042
5367
|
`sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
|
|
5043
5368
|
`
|
|
@@ -5185,7 +5510,7 @@ ${result.stderr ?? ""}`);
|
|
|
5185
5510
|
let stub;
|
|
5186
5511
|
try {
|
|
5187
5512
|
stub = parseJsonc(
|
|
5188
|
-
|
|
5513
|
+
readFileSync22(join28(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
5189
5514
|
LINT_CONFIG_FILE
|
|
5190
5515
|
);
|
|
5191
5516
|
} catch {
|
|
@@ -5195,7 +5520,7 @@ ${result.stderr ?? ""}`);
|
|
|
5195
5520
|
for (const entry of stub.extends ?? []) {
|
|
5196
5521
|
try {
|
|
5197
5522
|
const preset = parseJsonc(
|
|
5198
|
-
|
|
5523
|
+
readFileSync22(join28(cwd, entry), "utf8"),
|
|
5199
5524
|
entry
|
|
5200
5525
|
);
|
|
5201
5526
|
for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
|
|
@@ -5265,14 +5590,14 @@ ${result.stderr ?? ""}`);
|
|
|
5265
5590
|
let parsed;
|
|
5266
5591
|
try {
|
|
5267
5592
|
parsed = parseJsonc(
|
|
5268
|
-
|
|
5593
|
+
readFileSync22(join28(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
5269
5594
|
LINT_CONFIG_FILE
|
|
5270
5595
|
);
|
|
5271
5596
|
} catch {
|
|
5272
5597
|
return void 0;
|
|
5273
5598
|
}
|
|
5274
5599
|
const targets = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
|
|
5275
|
-
return targets.find((target) => !
|
|
5600
|
+
return targets.find((target) => !existsSync24(join28(cwd, target)));
|
|
5276
5601
|
}
|
|
5277
5602
|
/** Announce what is not enforced, so reduced coverage is never silent. */
|
|
5278
5603
|
announceDisabled(preset) {
|
|
@@ -5304,9 +5629,9 @@ function registerLint() {
|
|
|
5304
5629
|
|
|
5305
5630
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
5306
5631
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5307
|
-
import { existsSync as
|
|
5632
|
+
import { existsSync as existsSync25, readFileSync as readFileSync24 } from "fs";
|
|
5308
5633
|
import { createRequire as createRequire4 } from "module";
|
|
5309
|
-
import { join as
|
|
5634
|
+
import { join as join30 } from "path";
|
|
5310
5635
|
|
|
5311
5636
|
// src/roles/typescript/presets/nest.json
|
|
5312
5637
|
var nest_default2 = {
|
|
@@ -5487,8 +5812,8 @@ function readTsconfigAdoption(cwd) {
|
|
|
5487
5812
|
}
|
|
5488
5813
|
|
|
5489
5814
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
5490
|
-
import { readFileSync as
|
|
5491
|
-
import { join as
|
|
5815
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
5816
|
+
import { join as join29 } from "path";
|
|
5492
5817
|
|
|
5493
5818
|
// src/roles/typescript/typecheck-script.ts
|
|
5494
5819
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
@@ -5581,7 +5906,7 @@ function planAdoption(context) {
|
|
|
5581
5906
|
};
|
|
5582
5907
|
}
|
|
5583
5908
|
const existing = parseJsonc(
|
|
5584
|
-
|
|
5909
|
+
readFileSync23(join29(context.cwd, target.path), "utf8"),
|
|
5585
5910
|
target.path
|
|
5586
5911
|
);
|
|
5587
5912
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
@@ -5719,7 +6044,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5719
6044
|
let chain;
|
|
5720
6045
|
try {
|
|
5721
6046
|
const parsed = parseJsonc(
|
|
5722
|
-
|
|
6047
|
+
readFileSync24(join30(cwd, target.path), "utf8"),
|
|
5723
6048
|
target.path
|
|
5724
6049
|
);
|
|
5725
6050
|
chain = parsed.extends;
|
|
@@ -5732,7 +6057,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5732
6057
|
);
|
|
5733
6058
|
if (preset === void 0) return void 0;
|
|
5734
6059
|
try {
|
|
5735
|
-
createRequire4(
|
|
6060
|
+
createRequire4(join30(cwd, "noop.js")).resolve(preset);
|
|
5736
6061
|
return void 0;
|
|
5737
6062
|
} catch {
|
|
5738
6063
|
return preset;
|
|
@@ -5828,7 +6153,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5828
6153
|
referencedProjects(cwd, config) {
|
|
5829
6154
|
try {
|
|
5830
6155
|
const parsed = parseJsonc(
|
|
5831
|
-
|
|
6156
|
+
readFileSync24(join30(cwd, config), "utf8"),
|
|
5832
6157
|
config
|
|
5833
6158
|
);
|
|
5834
6159
|
const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
|
|
@@ -5844,7 +6169,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5844
6169
|
* check.
|
|
5845
6170
|
*/
|
|
5846
6171
|
typecheckTarget(cwd) {
|
|
5847
|
-
if (
|
|
6172
|
+
if (existsSync25(join30(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
5848
6173
|
const target = resolveTsconfigTarget(cwd);
|
|
5849
6174
|
return target.reason === "none" ? null : target.path;
|
|
5850
6175
|
}
|
|
@@ -6048,7 +6373,7 @@ function replaceLines(current, replacements) {
|
|
|
6048
6373
|
}
|
|
6049
6374
|
|
|
6050
6375
|
// src/core/apply-plan.ts
|
|
6051
|
-
import { existsSync as
|
|
6376
|
+
import { existsSync as existsSync26, readFileSync as readFileSync25, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
6052
6377
|
import { resolve as resolve4, sep } from "path";
|
|
6053
6378
|
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
6054
6379
|
|
|
@@ -6067,7 +6392,7 @@ function resolveWithinRoot(cwd, relativePath) {
|
|
|
6067
6392
|
return absolutePath;
|
|
6068
6393
|
}
|
|
6069
6394
|
function readIfExists(absolutePath) {
|
|
6070
|
-
return
|
|
6395
|
+
return existsSync26(absolutePath) ? readFileSync25(absolutePath, "utf8") : void 0;
|
|
6071
6396
|
}
|
|
6072
6397
|
function* leaves(value, prefix = []) {
|
|
6073
6398
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -6169,14 +6494,14 @@ function preparePlan(cwd, plan3) {
|
|
|
6169
6494
|
}
|
|
6170
6495
|
function writeFileAtomic(absolutePath, contents) {
|
|
6171
6496
|
const tempPath = `${absolutePath}.sentinel-${process.pid}.tmp`;
|
|
6172
|
-
|
|
6497
|
+
writeFileSync4(tempPath, contents);
|
|
6173
6498
|
renameSync(tempPath, absolutePath);
|
|
6174
6499
|
}
|
|
6175
6500
|
function applyPlan(cwd, plan3) {
|
|
6176
6501
|
const changed = preparePlan(cwd, plan3).filter((file) => file.before !== file.after);
|
|
6177
6502
|
for (const file of changed) {
|
|
6178
6503
|
if (file.deleted) {
|
|
6179
|
-
|
|
6504
|
+
rmSync3(file.absolutePath, { force: true });
|
|
6180
6505
|
continue;
|
|
6181
6506
|
}
|
|
6182
6507
|
writeFileAtomic(file.absolutePath, file.after);
|
|
@@ -6185,8 +6510,8 @@ function applyPlan(cwd, plan3) {
|
|
|
6185
6510
|
}
|
|
6186
6511
|
|
|
6187
6512
|
// src/core/config/preset-evidence.ts
|
|
6188
|
-
import { existsSync as
|
|
6189
|
-
import { join as
|
|
6513
|
+
import { existsSync as existsSync27, readdirSync as readdirSync3, readFileSync as readFileSync26 } from "fs";
|
|
6514
|
+
import { join as join31 } from "path";
|
|
6190
6515
|
var PATH_SIGNALS = [
|
|
6191
6516
|
{
|
|
6192
6517
|
preset: "nest",
|
|
@@ -6195,16 +6520,20 @@ var PATH_SIGNALS = [
|
|
|
6195
6520
|
}
|
|
6196
6521
|
];
|
|
6197
6522
|
var DEPENDENCY_SIGNALS = [
|
|
6198
|
-
|
|
6523
|
+
// Runtime dependencies only. `eslint-plugin-react*` used to count here, but a lint plugin is
|
|
6524
|
+
// TOOLING: this very repo depends on `eslint-plugin-react-refresh` to ship React rules to
|
|
6525
|
+
// other projects, and was therefore read as a React app itself. No module in the monorepo
|
|
6526
|
+
// carries an `eslint-plugin-react*` without also carrying react, so nothing loses detection.
|
|
6527
|
+
{ preset: "react", pattern: /^(react|react-dom|@types\/react)$/ },
|
|
6199
6528
|
{ preset: "svelte", pattern: /^svelte$|^@sveltejs\// },
|
|
6200
6529
|
{ preset: "nest", pattern: /^@nestjs\// }
|
|
6201
6530
|
];
|
|
6202
6531
|
function dependencyNames(cwd) {
|
|
6203
|
-
const path =
|
|
6204
|
-
if (!
|
|
6532
|
+
const path = join31(cwd, "package.json");
|
|
6533
|
+
if (!existsSync27(path)) return [];
|
|
6205
6534
|
try {
|
|
6206
6535
|
const manifest = parseJsonc(
|
|
6207
|
-
|
|
6536
|
+
readFileSync26(path, "utf8"),
|
|
6208
6537
|
path
|
|
6209
6538
|
);
|
|
6210
6539
|
return [
|
|
@@ -6227,7 +6556,7 @@ function declaresJsx(cwd) {
|
|
|
6227
6556
|
for (const name of entries) {
|
|
6228
6557
|
try {
|
|
6229
6558
|
const config = parseJsonc(
|
|
6230
|
-
|
|
6559
|
+
readFileSync26(join31(cwd, name), "utf8"),
|
|
6231
6560
|
name
|
|
6232
6561
|
);
|
|
6233
6562
|
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
@@ -6254,6 +6583,10 @@ function presetSignals(cwd) {
|
|
|
6254
6583
|
}
|
|
6255
6584
|
return signals;
|
|
6256
6585
|
}
|
|
6586
|
+
function correctingSignal(detected, cwd) {
|
|
6587
|
+
const [signal] = presetSignals(cwd);
|
|
6588
|
+
return signal !== void 0 && signal.preset !== detected ? signal : void 0;
|
|
6589
|
+
}
|
|
6257
6590
|
function presetContradiction(declared, cwd) {
|
|
6258
6591
|
const signals = presetSignals(cwd);
|
|
6259
6592
|
if (signals.length === 0) return void 0;
|
|
@@ -6334,8 +6667,8 @@ async function dispatch(opts) {
|
|
|
6334
6667
|
}
|
|
6335
6668
|
let effective = preset;
|
|
6336
6669
|
if (opts.preset === void 0 && adapter.declaredPreset?.(opts.cwd) === void 0) {
|
|
6337
|
-
const
|
|
6338
|
-
if (signal !== void 0
|
|
6670
|
+
const signal = correctingSignal(preset, opts.cwd);
|
|
6671
|
+
if (signal !== void 0) {
|
|
6339
6672
|
effective = signal.preset;
|
|
6340
6673
|
process.stderr.write(
|
|
6341
6674
|
` detected "${preset}" from dependencies, but ${signal.evidence}, so adopting as "${signal.preset}". Pass --preset to override.
|
|
@@ -6386,25 +6719,26 @@ export {
|
|
|
6386
6719
|
declaredPresetFor,
|
|
6387
6720
|
availableTargets,
|
|
6388
6721
|
resolve,
|
|
6389
|
-
BaseAdapter,
|
|
6390
|
-
resolveBin,
|
|
6391
6722
|
readOwnVersion,
|
|
6392
6723
|
readProjectPackageJson,
|
|
6393
6724
|
moduleName,
|
|
6394
6725
|
buildConfigFile,
|
|
6726
|
+
BaseAdapter,
|
|
6727
|
+
resolveBin,
|
|
6728
|
+
WORKSPACE_ROOT_MARKER,
|
|
6729
|
+
findWorkspaceRoot,
|
|
6730
|
+
ensureWorkspacePrep,
|
|
6731
|
+
inspectWorkspacePrep,
|
|
6395
6732
|
VERBS,
|
|
6396
6733
|
TARGETS,
|
|
6397
6734
|
LONG_RUNNING_TARGETS,
|
|
6398
6735
|
SWEEPABLE_TARGETS,
|
|
6399
6736
|
PRESET_NAMES,
|
|
6400
|
-
WORKSPACE_ROOT_MARKER,
|
|
6401
|
-
findWorkspaceRoot,
|
|
6402
|
-
ensureWorkspacePrep,
|
|
6403
|
-
inspectWorkspacePrep,
|
|
6404
6737
|
palette,
|
|
6405
6738
|
registerAdapters,
|
|
6739
|
+
correctingSignal,
|
|
6406
6740
|
describeFramework,
|
|
6407
6741
|
detectFramework,
|
|
6408
6742
|
dispatch
|
|
6409
6743
|
};
|
|
6410
|
-
//# sourceMappingURL=chunk-
|
|
6744
|
+
//# sourceMappingURL=chunk-676GBPMS.js.map
|