@hublo/sentinel 1.2.0-alpha.8 → 1.2.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 +28 -2
- package/dist/{chunk-A25QVPXF.js → chunk-Z5I6SEKF.js} +1228 -822
- 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
|
-
import { createRequire } from "module";
|
|
192
|
-
import { delimiter, dirname as dirname2, join as join2 } from "path";
|
|
193
|
-
var require2 = createRequire(import.meta.url);
|
|
194
|
-
function resolveBin(fromDir, name) {
|
|
195
|
-
let dir = fromDir;
|
|
196
|
-
for (; ; ) {
|
|
197
|
-
const candidate = join2(dir, "node_modules", ".bin", name);
|
|
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.`;
|
|
202
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.`;
|
|
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,29 +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
|
-
|
|
498
|
-
|
|
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;
|
|
422
|
+
try {
|
|
423
|
+
return JSON.parse(readFileSync4(path, "utf8")).targets;
|
|
424
|
+
} catch {
|
|
425
|
+
return void 0;
|
|
426
|
+
}
|
|
499
427
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
+
);
|
|
442
|
+
}
|
|
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
|
+
);
|
|
453
|
+
}
|
|
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
|
+
);
|
|
459
|
+
}
|
|
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
|
+
);
|
|
464
|
+
}
|
|
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;
|
|
509
478
|
}
|
|
510
479
|
|
|
511
480
|
// src/roles/build/read-adoption.ts
|
|
512
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
513
|
-
import { join as join6 } from "path";
|
|
514
481
|
var NOT_ADOPTED = (configFile, unreadable = null) => ({
|
|
515
482
|
configFile,
|
|
516
483
|
preset: null,
|
|
@@ -521,7 +488,7 @@ var NOT_ADOPTED = (configFile, unreadable = null) => ({
|
|
|
521
488
|
unreadable
|
|
522
489
|
});
|
|
523
490
|
function buildConfigFile(cwd) {
|
|
524
|
-
return BUILD_CONFIG_FILES.find((name) =>
|
|
491
|
+
return BUILD_CONFIG_FILES.find((name) => existsSync5(join5(cwd, name)));
|
|
525
492
|
}
|
|
526
493
|
function importsSpecifier(source, specifier) {
|
|
527
494
|
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -535,7 +502,10 @@ function importsPreset(source) {
|
|
|
535
502
|
var REACT_PLUGIN_SPECIFIERS = ["@vitejs/plugin-react", "@tanstack/react-start"];
|
|
536
503
|
function declaredBuildPreset(cwd) {
|
|
537
504
|
const source = readBuildConfigSource(cwd);
|
|
538
|
-
if (source === void 0)
|
|
505
|
+
if (source === void 0) {
|
|
506
|
+
if (buildConfigFile(cwd) !== void 0) return void 0;
|
|
507
|
+
return webpackBuildTarget(cwd) === void 0 ? void 0 : "nest";
|
|
508
|
+
}
|
|
539
509
|
if (importsSpecifier(source, BUILD_PRESET_SPECIFIERS.nest)) return "nest";
|
|
540
510
|
if (importsPreset(source)) return "react";
|
|
541
511
|
return REACT_PLUGIN_SPECIFIERS.some((specifier) => importsSpecifier(source, specifier)) ? "react" : void 0;
|
|
@@ -544,7 +514,7 @@ function readBuildConfigSource(cwd) {
|
|
|
544
514
|
const configFile = buildConfigFile(cwd);
|
|
545
515
|
if (!configFile) return void 0;
|
|
546
516
|
try {
|
|
547
|
-
return
|
|
517
|
+
return readFileSync5(join5(cwd, configFile), "utf8");
|
|
548
518
|
} catch {
|
|
549
519
|
return void 0;
|
|
550
520
|
}
|
|
@@ -554,47 +524,501 @@ function readBuildAdoption(cwd) {
|
|
|
554
524
|
if (!configFile) return NOT_ADOPTED(null);
|
|
555
525
|
let source;
|
|
556
526
|
try {
|
|
557
|
-
source =
|
|
527
|
+
source = readFileSync5(join5(cwd, configFile), "utf8");
|
|
558
528
|
} catch (error) {
|
|
559
529
|
return NOT_ADOPTED(configFile, error instanceof Error ? error.message : String(error));
|
|
560
530
|
}
|
|
561
|
-
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
|
+
}
|
|
562
535
|
const manifest = readProjectPackageJson(cwd);
|
|
563
536
|
const declared = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
564
537
|
const ownDeclarations = SENTINEL_OWNED_BUILD_PACKAGES.filter((name) => name in declared).map(
|
|
565
538
|
(name) => ({ name, version: declared[name] })
|
|
566
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
|
+
];
|
|
567
546
|
return {
|
|
568
547
|
configFile,
|
|
569
548
|
// Read from the config rather than assumed: the two presets are adopted the same way and
|
|
570
549
|
// reporting the wrong one would make `--inspect` lie about what a module builds.
|
|
571
550
|
preset: declaredBuildPreset(cwd) ?? "react",
|
|
572
551
|
adopted: true,
|
|
573
|
-
conformant:
|
|
574
|
-
drift
|
|
575
|
-
({ 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`
|
|
576
|
-
),
|
|
552
|
+
conformant: drift.length === 0,
|
|
553
|
+
drift,
|
|
577
554
|
ownDeclarations,
|
|
578
555
|
unreadable: null
|
|
579
556
|
};
|
|
580
557
|
}
|
|
581
558
|
|
|
582
|
-
// src/roles/build/
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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
|
+
|
|
1006
|
+
// src/roles/build/requirements.ts
|
|
1007
|
+
function atLeast(declared, needed) {
|
|
1008
|
+
const parse2 = (value) => value.replace(/^[\^~>=<\s]+/, "").split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
1009
|
+
const left = parse2(declared);
|
|
1010
|
+
const right = parse2(needed);
|
|
1011
|
+
for (let at = 0; at < Math.max(left.length, right.length); at++) {
|
|
1012
|
+
const a = left[at] ?? 0;
|
|
1013
|
+
const b = right[at] ?? 0;
|
|
1014
|
+
if (a !== b) return a > b;
|
|
1015
|
+
}
|
|
1016
|
+
return true;
|
|
1017
|
+
}
|
|
1018
|
+
function unmetRequirements(preset, declared) {
|
|
1019
|
+
const entry = REQUIREMENTS[preset];
|
|
1020
|
+
if (!entry) return [];
|
|
1021
|
+
return Object.entries(entry.requires).filter(([name, needed]) => {
|
|
598
1022
|
const have = declared[name];
|
|
599
1023
|
return have === void 0 || !atLeast(have, needed);
|
|
600
1024
|
}).map(([name, needed]) => ({ name, declared: declared[name], needed }));
|
|
@@ -607,32 +1031,26 @@ function describeUnmet(preset, unmet) {
|
|
|
607
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\`.`;
|
|
608
1032
|
}
|
|
609
1033
|
|
|
610
|
-
// src/
|
|
1034
|
+
// src/core/config/rewrite-imports.ts
|
|
611
1035
|
import { parseSync } from "oxc-parser";
|
|
612
|
-
var
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if (packageType === "module") return void 0;
|
|
616
|
-
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.`;
|
|
617
|
-
}
|
|
618
|
-
var DEFAULT_EXPORT_NAMES = Object.fromEntries(
|
|
619
|
-
OWNED_PACKAGES.filter((entry) => entry.default !== void 0).map((entry) => [
|
|
620
|
-
entry.specifier,
|
|
621
|
-
entry.default
|
|
622
|
-
])
|
|
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])
|
|
623
1039
|
);
|
|
624
|
-
function renderImport(bindings) {
|
|
1040
|
+
function renderImport(bindings, presetSpecifier) {
|
|
625
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(", ");
|
|
626
1042
|
const values = bindings.filter((b) => !b.typeOnly);
|
|
627
1043
|
const types = bindings.filter((b) => b.typeOnly);
|
|
628
1044
|
const lines = [];
|
|
629
|
-
if (values.length > 0) lines.push(`import { ${render(values)} } from '${
|
|
1045
|
+
if (values.length > 0) lines.push(`import { ${render(values)} } from '${presetSpecifier}'`);
|
|
630
1046
|
if (types.length > 0) {
|
|
631
|
-
lines.push(`import type { ${render(types)} } from '${
|
|
1047
|
+
lines.push(`import type { ${render(types)} } from '${presetSpecifier}'`);
|
|
632
1048
|
}
|
|
633
1049
|
return lines.join("\n");
|
|
634
1050
|
}
|
|
635
|
-
function
|
|
1051
|
+
function rewriteToolchainImports(toolchain, source, fileName) {
|
|
1052
|
+
const moved = movedSpecifiers(toolchain);
|
|
1053
|
+
const defaults = defaultExportNames(toolchain);
|
|
636
1054
|
const { program, errors } = parseSync(fileName, source, { sourceType: "module" });
|
|
637
1055
|
if (errors.length > 0) {
|
|
638
1056
|
return {
|
|
@@ -643,10 +1061,10 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
|
643
1061
|
const imports = program.body.filter(
|
|
644
1062
|
(node) => node.type === "ImportDeclaration"
|
|
645
1063
|
);
|
|
646
|
-
if (imports.some((node) => node.source.value ===
|
|
1064
|
+
if (imports.some((node) => node.source.value === toolchain.presetSpecifier)) {
|
|
647
1065
|
return { kind: "unchanged", why: "this config already imports its toolchain from sentinel" };
|
|
648
1066
|
}
|
|
649
|
-
const moving = imports.filter((node) =>
|
|
1067
|
+
const moving = imports.filter((node) => moved.has(node.source.value));
|
|
650
1068
|
if (moving.length === 0) {
|
|
651
1069
|
return {
|
|
652
1070
|
kind: "unchanged",
|
|
@@ -663,7 +1081,7 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
|
663
1081
|
for (const spec of specifiers) {
|
|
664
1082
|
const typeOnly = node.importKind === "type" || spec.importKind === "type";
|
|
665
1083
|
if (spec.type === "ImportDefaultSpecifier") {
|
|
666
|
-
const exported =
|
|
1084
|
+
const exported = defaults[from];
|
|
667
1085
|
if (!exported) {
|
|
668
1086
|
return {
|
|
669
1087
|
kind: "blocked",
|
|
@@ -695,7 +1113,7 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
|
695
1113
|
const after = out.slice(span.end).match(/^\r?\n/)?.[0].length ?? 0;
|
|
696
1114
|
out = out.slice(0, span.start) + out.slice(span.end + after);
|
|
697
1115
|
}
|
|
698
|
-
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);
|
|
699
1117
|
return {
|
|
700
1118
|
kind: "rewritten",
|
|
701
1119
|
source: out,
|
|
@@ -703,7 +1121,19 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
|
703
1121
|
};
|
|
704
1122
|
}
|
|
705
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
|
+
|
|
706
1133
|
// src/roles/build/plan.ts
|
|
1134
|
+
function candidateOutputs() {
|
|
1135
|
+
return BUILD_OUTPUT_CANDIDATES.map((name) => `{projectRoot}/${name}`);
|
|
1136
|
+
}
|
|
707
1137
|
function ownedBuildDependencies(cwd) {
|
|
708
1138
|
const manifest = readProjectPackageJson(cwd);
|
|
709
1139
|
const keys = [];
|
|
@@ -735,9 +1165,9 @@ function buildScripts(cwd) {
|
|
|
735
1165
|
const own2 = moduleName(cwd);
|
|
736
1166
|
scripts[DEV_SCRIPT_NAME] = composeDevScript(
|
|
737
1167
|
existingDev,
|
|
738
|
-
//
|
|
739
|
-
//
|
|
740
|
-
`${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}`
|
|
741
1171
|
);
|
|
742
1172
|
}
|
|
743
1173
|
return scripts;
|
|
@@ -751,54 +1181,73 @@ function plan(context) {
|
|
|
751
1181
|
if (unmet.length > 0) {
|
|
752
1182
|
return { operations: [], skipped: describeUnmet(context.preset, unmet) };
|
|
753
1183
|
}
|
|
754
|
-
|
|
1184
|
+
const declaredPreset2 = declaredBuildPreset(context.cwd);
|
|
1185
|
+
const configFile = buildConfigFile(context.cwd);
|
|
1186
|
+
if (declaredPreset2 === void 0 && configFile !== void 0) {
|
|
1187
|
+
return {
|
|
1188
|
+
operations: [],
|
|
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.`
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
const webpackTarget = webpackBuildTarget(context.cwd);
|
|
1193
|
+
if (declaredBuildPreset(context.cwd) === "nest" || webpackTarget !== void 0) {
|
|
1194
|
+
return planNestService(context, webpackTarget);
|
|
1195
|
+
}
|
|
1196
|
+
if (configFile === void 0) {
|
|
755
1197
|
return {
|
|
756
1198
|
operations: [],
|
|
757
|
-
skipped: `this module
|
|
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.`
|
|
758
1200
|
};
|
|
759
1201
|
}
|
|
1202
|
+
return planReactApp(context, configFile);
|
|
1203
|
+
}
|
|
1204
|
+
function planReactApp(context, configFile) {
|
|
760
1205
|
const notes = [];
|
|
761
1206
|
const operations = [];
|
|
762
|
-
const
|
|
763
|
-
if (
|
|
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") {
|
|
764
1211
|
return {
|
|
765
1212
|
operations: [],
|
|
766
|
-
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.`
|
|
767
1214
|
};
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
}
|
|
781
|
-
for (const name of COMPANION_CONFIG_FILES) {
|
|
782
|
-
const source = readConfigSource(context.cwd, name);
|
|
783
|
-
if (source === "") continue;
|
|
784
|
-
const companion = rewriteBuildImports(source, name);
|
|
785
|
-
if (companion.kind === "rewritten") {
|
|
786
|
-
operations.push({ kind: "write", path: name, contents: companion.source });
|
|
787
|
-
notes.push(`${name} now imports ${companion.moved.join(", ")} from sentinel too.`);
|
|
788
|
-
} else if (companion.kind === "blocked") {
|
|
789
|
-
notes.push(
|
|
790
|
-
`${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.`
|
|
791
|
-
);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
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") {
|
|
795
1227
|
notes.push(
|
|
796
|
-
`${
|
|
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.`
|
|
797
1229
|
);
|
|
798
1230
|
}
|
|
799
1231
|
}
|
|
800
|
-
|
|
801
|
-
|
|
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);
|
|
802
1251
|
const owned = ownedBuildDependencies(context.cwd);
|
|
803
1252
|
if (owned.length > 0) {
|
|
804
1253
|
operations.push({ kind: "remove-json-keys", path: "package.json", keys: owned });
|
|
@@ -806,12 +1255,53 @@ function plan(context) {
|
|
|
806
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.`
|
|
807
1256
|
);
|
|
808
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
|
+
);
|
|
809
1265
|
return { operations, notes };
|
|
810
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
|
+
}
|
|
811
1301
|
function readConfigSource(cwd, configFile) {
|
|
812
|
-
if (!
|
|
1302
|
+
if (!existsSync11(join11(cwd, configFile))) return "";
|
|
813
1303
|
try {
|
|
814
|
-
return
|
|
1304
|
+
return readFileSync9(join11(cwd, configFile), "utf8");
|
|
815
1305
|
} catch {
|
|
816
1306
|
return "";
|
|
817
1307
|
}
|
|
@@ -914,80 +1404,290 @@ var ViteRoleAdapter = class extends BaseAdapter {
|
|
|
914
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.`
|
|
915
1405
|
);
|
|
916
1406
|
}
|
|
917
|
-
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
|
+
function findWorkspaceRoot(startDir) {
|
|
1507
|
+
let dir = startDir;
|
|
1508
|
+
for (; ; ) {
|
|
1509
|
+
if (existsSync12(join12(dir, WORKSPACE_ROOT_MARKER))) return dir;
|
|
1510
|
+
const parent = dirname3(dir);
|
|
1511
|
+
if (parent === dir) return void 0;
|
|
1512
|
+
dir = parent;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
function declaredNativeTs(pkg) {
|
|
1516
|
+
const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS];
|
|
1517
|
+
if (!spec) return void 0;
|
|
1518
|
+
const version = spec.slice(spec.lastIndexOf("@") + 1).replace(/^[\^~>=<\s]+/, "");
|
|
1519
|
+
return version || void 0;
|
|
1520
|
+
}
|
|
1521
|
+
function ensureI18nextSingleton(root, dryRun) {
|
|
1522
|
+
const pkgPath = join12(root, "package.json");
|
|
1523
|
+
if (!existsSync12(pkgPath)) return void 0;
|
|
1524
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
1525
|
+
const want = declaredNativeTs(pkg);
|
|
1526
|
+
if (!want) return void 0;
|
|
1527
|
+
const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
1528
|
+
if (have === want) return void 0;
|
|
1529
|
+
if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} (i18next singleton)`;
|
|
1530
|
+
pkg.pnpm ??= {};
|
|
1531
|
+
pkg.pnpm.overrides ??= {};
|
|
1532
|
+
pkg.pnpm.overrides[OVERRIDE_KEY] = want;
|
|
1533
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1534
|
+
return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
|
|
1535
|
+
}
|
|
1536
|
+
function ensureReleaseAgeAllowList(root, dryRun) {
|
|
1537
|
+
const yamlPath = join12(root, WORKSPACE_YAML);
|
|
1538
|
+
if (!existsSync12(yamlPath)) return void 0;
|
|
1539
|
+
const own = readOwnPackage().name;
|
|
1540
|
+
const lines = readFileSync10(yamlPath, "utf8").split("\n");
|
|
1541
|
+
const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
|
|
1542
|
+
if (keyIdx === -1) return void 0;
|
|
1543
|
+
let lastItemIdx = keyIdx;
|
|
1544
|
+
let indent = " ";
|
|
1545
|
+
for (let i = keyIdx + 1; i < lines.length; i++) {
|
|
1546
|
+
const match = lines[i]?.match(LIST_ITEM);
|
|
1547
|
+
if (!match) break;
|
|
1548
|
+
indent = match[1] ?? indent;
|
|
1549
|
+
lastItemIdx = i;
|
|
1550
|
+
if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === own) return void 0;
|
|
1551
|
+
}
|
|
1552
|
+
if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`;
|
|
1553
|
+
lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`);
|
|
1554
|
+
writeFileSync(yamlPath, lines.join("\n"));
|
|
1555
|
+
return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`;
|
|
1556
|
+
}
|
|
1557
|
+
var PRETTIER_IGNORE = ".prettierignore";
|
|
1558
|
+
var EXCLUSION_HEADER = "# @hublo/sentinel: modules formatted by oxfmt, not by the root Prettier";
|
|
1559
|
+
var ROOT_PRETTIER_CONFIGS = [
|
|
1560
|
+
".prettierrc",
|
|
1561
|
+
".prettierrc.json",
|
|
1562
|
+
".prettierrc.json5",
|
|
1563
|
+
".prettierrc.yml",
|
|
1564
|
+
".prettierrc.yaml",
|
|
1565
|
+
".prettierrc.js",
|
|
1566
|
+
".prettierrc.cjs",
|
|
1567
|
+
".prettierrc.mjs",
|
|
1568
|
+
"prettier.config.js",
|
|
1569
|
+
"prettier.config.cjs",
|
|
1570
|
+
"prettier.config.mjs"
|
|
1571
|
+
];
|
|
1572
|
+
function ensureFormatterExclusion(root, moduleDir, dryRun) {
|
|
1573
|
+
const rel = relative2(root, moduleDir).replaceAll("\\", "/");
|
|
1574
|
+
if (rel === "" || rel.startsWith("..")) return void 0;
|
|
1575
|
+
const ignorePath = join12(root, PRETTIER_IGNORE);
|
|
1576
|
+
const hasPrettier = existsSync12(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync12(join12(root, name)));
|
|
1577
|
+
if (!hasPrettier) return void 0;
|
|
1578
|
+
const pattern = `/${rel}/`;
|
|
1579
|
+
const existing = existsSync12(ignorePath) ? readFileSync10(ignorePath, "utf8") : "";
|
|
1580
|
+
const lines = existing.split("\n");
|
|
1581
|
+
if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
|
|
1582
|
+
return void 0;
|
|
1583
|
+
}
|
|
1584
|
+
if (dryRun) return `would exclude ${rel} from the root Prettier (${PRETTIER_IGNORE})`;
|
|
1585
|
+
const headerIdx = lines.findIndex((line) => line.trim() === EXCLUSION_HEADER);
|
|
1586
|
+
if (headerIdx === -1) {
|
|
1587
|
+
const prefix = existing.trim() === "" ? "" : `${existing.replace(/\n*$/, "")}
|
|
1588
|
+
|
|
1589
|
+
`;
|
|
1590
|
+
writeFileSync(ignorePath, `${prefix}${EXCLUSION_HEADER}
|
|
1591
|
+
${pattern}
|
|
1592
|
+
`);
|
|
1593
|
+
} else {
|
|
1594
|
+
let end = headerIdx + 1;
|
|
1595
|
+
while (end < lines.length && lines[end]?.trim().startsWith("/")) end += 1;
|
|
1596
|
+
const managed = [...lines.slice(headerIdx + 1, end), pattern].sort();
|
|
1597
|
+
writeFileSync(
|
|
1598
|
+
ignorePath,
|
|
1599
|
+
[...lines.slice(0, headerIdx + 1), ...managed, ...lines.slice(end)].join("\n")
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
return `excluded ${rel} from the root Prettier (${PRETTIER_IGNORE}): it formats itself now`;
|
|
1603
|
+
}
|
|
1604
|
+
function ensureWorkspacePrep(opts) {
|
|
1605
|
+
const dryRun = Boolean(opts.dryRun);
|
|
1606
|
+
return [
|
|
1607
|
+
ensureI18nextSingleton(opts.root, dryRun),
|
|
1608
|
+
ensureReleaseAgeAllowList(opts.root, dryRun),
|
|
1609
|
+
opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
|
|
1610
|
+
].filter((message) => message !== void 0);
|
|
1611
|
+
}
|
|
1612
|
+
function inspectWorkspacePrep(root) {
|
|
1613
|
+
const entries = [];
|
|
1614
|
+
let pkg = {};
|
|
1615
|
+
const pkgPath = join12(root, "package.json");
|
|
1616
|
+
if (existsSync12(pkgPath)) {
|
|
1617
|
+
try {
|
|
1618
|
+
pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
1619
|
+
} catch {
|
|
1620
|
+
pkg = {};
|
|
1621
|
+
}
|
|
918
1622
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
*/
|
|
926
|
-
spawnVite(ctx, vite, argv, whenSignalled) {
|
|
927
|
-
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
928
|
-
valueFlags: VITE_VALUE_FLAGS
|
|
929
|
-
});
|
|
930
|
-
const result = spawnSync(vite, [...argv, ...options, ...paths], {
|
|
931
|
-
cwd: ctx.cwd,
|
|
932
|
-
stdio: "inherit"
|
|
1623
|
+
const pinned = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
1624
|
+
if (pinned !== void 0) {
|
|
1625
|
+
const stillForks = declaredNativeTs(pkg);
|
|
1626
|
+
entries.push({
|
|
1627
|
+
rule: `package.json \u2192 pnpm.overrides.${OVERRIDE_KEY}`,
|
|
1628
|
+
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.`
|
|
933
1629
|
});
|
|
934
|
-
if (result.error) {
|
|
935
|
-
this.say(`could not run vite (${result.error.message})`);
|
|
936
|
-
return { ok: false, code: 1 };
|
|
937
|
-
}
|
|
938
|
-
const code = result.status ?? whenSignalled;
|
|
939
|
-
return { ok: code === 0, code };
|
|
940
1630
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1631
|
+
const own = readOwnPackage().name;
|
|
1632
|
+
const yamlPath = join12(root, WORKSPACE_YAML);
|
|
1633
|
+
if (existsSync12(yamlPath)) {
|
|
1634
|
+
const yaml = readFileSync10(yamlPath, "utf8");
|
|
1635
|
+
const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
|
|
1636
|
+
if (listed) {
|
|
1637
|
+
const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
|
|
1638
|
+
entries.push({
|
|
1639
|
+
rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
|
|
1640
|
+
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.`
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
945
1643
|
}
|
|
946
|
-
|
|
1644
|
+
return entries;
|
|
1645
|
+
}
|
|
947
1646
|
|
|
948
|
-
// src/roles/build/
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1647
|
+
// src/roles/build/declared-outputs.ts
|
|
1648
|
+
function declaredBuildTarget(cwd) {
|
|
1649
|
+
return readProjectPackageJson(cwd).nx?.targets?.[BUILD_SCRIPT_NAME];
|
|
1650
|
+
}
|
|
1651
|
+
function resolveOutputToken(token, cwd) {
|
|
1652
|
+
if (token.startsWith("{projectRoot}/")) return join13(cwd, token.slice("{projectRoot}/".length));
|
|
1653
|
+
if (token.startsWith("{workspaceRoot}/")) {
|
|
1654
|
+
const root = findWorkspaceRoot(cwd);
|
|
1655
|
+
return root === void 0 ? void 0 : join13(root, token.slice("{workspaceRoot}/".length));
|
|
953
1656
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
if (planned.operations.length === 0) return planned;
|
|
966
|
-
return {
|
|
967
|
-
...planned,
|
|
968
|
-
notes: [
|
|
969
|
-
...planned.notes ?? [],
|
|
970
|
-
"--init --dev adopts the build too: one plan writes both `build` and `serve`."
|
|
971
|
-
]
|
|
972
|
-
};
|
|
1657
|
+
return join13(cwd, token);
|
|
1658
|
+
}
|
|
1659
|
+
function buildStamp(cwd) {
|
|
1660
|
+
const marker = join13(cwd, `.sentinel-build-stamp-${process.pid}`);
|
|
1661
|
+
try {
|
|
1662
|
+
writeFileSync2(marker, "");
|
|
1663
|
+
const stamp = statSync(marker).mtimeMs;
|
|
1664
|
+
rmSync(marker, { force: true });
|
|
1665
|
+
return stamp;
|
|
1666
|
+
} catch {
|
|
1667
|
+
return Date.now();
|
|
973
1668
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
*
|
|
982
|
-
* No subcommand either. `vite`, `vite dev` and `vite serve` all start the server, and the
|
|
983
|
-
* bare form is the one every version accepts.
|
|
984
|
-
*/
|
|
985
|
-
async run(ctx) {
|
|
986
|
-
const ready = this.prepare(ctx);
|
|
987
|
-
if (ready.kind === "stop") return ready.result;
|
|
988
|
-
return this.spawnVite(ctx, ready.vite, [], 0);
|
|
1669
|
+
}
|
|
1670
|
+
function writtenSince(path, startedAt) {
|
|
1671
|
+
if (!existsSync13(path)) return false;
|
|
1672
|
+
try {
|
|
1673
|
+
return statSync(path).mtimeMs >= startedAt;
|
|
1674
|
+
} catch {
|
|
1675
|
+
return true;
|
|
989
1676
|
}
|
|
990
|
-
}
|
|
1677
|
+
}
|
|
1678
|
+
function outputsProblem(cwd, startedAt) {
|
|
1679
|
+
const target = declaredBuildTarget(cwd);
|
|
1680
|
+
if (target === void 0) return void 0;
|
|
1681
|
+
const declared = target.outputs ?? [];
|
|
1682
|
+
if (declared.length === 0) {
|
|
1683
|
+
if (target.cache !== true) return void 0;
|
|
1684
|
+
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.`;
|
|
1685
|
+
}
|
|
1686
|
+
const resolved = declared.map((token) => ({ token, path: resolveOutputToken(token, cwd) })).filter((entry) => entry.path !== void 0);
|
|
1687
|
+
if (resolved.length === 0) return void 0;
|
|
1688
|
+
if (resolved.some((entry) => writtenSince(entry.path, startedAt))) return void 0;
|
|
1689
|
+
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.`;
|
|
1690
|
+
}
|
|
991
1691
|
|
|
992
1692
|
// src/roles/build/prerequisite.ts
|
|
993
1693
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -1037,7 +1737,15 @@ var ViteAdapter = class extends ViteRoleAdapter {
|
|
|
1037
1737
|
);
|
|
1038
1738
|
return { ok: false, code: prerequisite.code };
|
|
1039
1739
|
}
|
|
1040
|
-
|
|
1740
|
+
const startedAt = buildStamp(ctx.cwd);
|
|
1741
|
+
const result = await this.spawnVite(ctx, ready.vite, ["build"], 1);
|
|
1742
|
+
if (!result.ok) return result;
|
|
1743
|
+
const problem = outputsProblem(ctx.cwd, startedAt);
|
|
1744
|
+
if (problem !== void 0) {
|
|
1745
|
+
this.say(problem);
|
|
1746
|
+
return { ok: false, code: 1 };
|
|
1747
|
+
}
|
|
1748
|
+
return result;
|
|
1041
1749
|
}
|
|
1042
1750
|
/**
|
|
1043
1751
|
* What this module builds with, WITHOUT loading its config.
|
|
@@ -1109,12 +1817,12 @@ function registerBuild() {
|
|
|
1109
1817
|
|
|
1110
1818
|
// src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
|
|
1111
1819
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1112
|
-
import { existsSync as
|
|
1113
|
-
import { join as
|
|
1820
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
1821
|
+
import { join as join18 } from "path";
|
|
1114
1822
|
|
|
1115
1823
|
// src/core/config/has-source.ts
|
|
1116
1824
|
import { readdirSync } from "fs";
|
|
1117
|
-
import { extname, join as
|
|
1825
|
+
import { extname, join as join14, relative as relative3 } from "path";
|
|
1118
1826
|
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1119
1827
|
"node_modules",
|
|
1120
1828
|
"dist",
|
|
@@ -1142,265 +1850,66 @@ var LINTABLE_EXTENSIONS = [
|
|
|
1142
1850
|
var TYPECHECKABLE_EXTENSIONS = [".ts", ".mts", ".cts", ".tsx"];
|
|
1143
1851
|
var FORMATTABLE_EXTENSIONS = [
|
|
1144
1852
|
...LINTABLE_EXTENSIONS,
|
|
1145
|
-
".json",
|
|
1146
|
-
".jsonc",
|
|
1147
|
-
".md",
|
|
1148
|
-
".yaml",
|
|
1149
|
-
".yml",
|
|
1150
|
-
".html",
|
|
1151
|
-
".css",
|
|
1152
|
-
".scss",
|
|
1153
|
-
".hbs"
|
|
1154
|
-
];
|
|
1155
|
-
function hasSourceFiles(cwd, extensions) {
|
|
1156
|
-
const wanted = new Set(extensions);
|
|
1157
|
-
const queue = [cwd];
|
|
1158
|
-
while (queue.length > 0) {
|
|
1159
|
-
const dir = queue.pop();
|
|
1160
|
-
let entries;
|
|
1161
|
-
try {
|
|
1162
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
1163
|
-
} catch {
|
|
1164
|
-
continue;
|
|
1165
|
-
}
|
|
1166
|
-
for (const entry of entries) {
|
|
1167
|
-
if (entry.isDirectory()) {
|
|
1168
|
-
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(
|
|
1169
|
-
continue;
|
|
1170
|
-
}
|
|
1171
|
-
if (wanted.has(extname(entry.name))) return true;
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
return false;
|
|
1175
|
-
}
|
|
1176
|
-
function listSourceFiles(cwd, extensions) {
|
|
1177
|
-
const wanted = new Set(extensions);
|
|
1178
|
-
const queue = [cwd];
|
|
1179
|
-
const found = [];
|
|
1180
|
-
while (queue.length > 0) {
|
|
1181
|
-
const dir = queue.pop();
|
|
1182
|
-
let entries;
|
|
1183
|
-
try {
|
|
1184
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
1185
|
-
} catch {
|
|
1186
|
-
continue;
|
|
1187
|
-
}
|
|
1188
|
-
for (const entry of entries) {
|
|
1189
|
-
const full = join8(dir, entry.name);
|
|
1190
|
-
if (entry.isDirectory()) {
|
|
1191
|
-
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
|
|
1192
|
-
continue;
|
|
1193
|
-
}
|
|
1194
|
-
if (wanted.has(extname(entry.name))) found.push(relative(cwd, full));
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
return found;
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
// src/core/named-scope.ts
|
|
1201
|
-
var TOLERATE_EMPTY_FLAG = "--no-error-on-unmatched-pattern";
|
|
1202
|
-
function scopeFlags(paths, options = []) {
|
|
1203
|
-
if (paths.length === 0 || options.includes(TOLERATE_EMPTY_FLAG)) return [];
|
|
1204
|
-
return [TOLERATE_EMPTY_FLAG];
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
// src/core/settings.ts
|
|
1208
|
-
var WORKSPACE_ROOT_MARKER = "nx.json";
|
|
1209
|
-
var DEFAULT_MAX_DIAGNOSTICS = 100;
|
|
1210
|
-
|
|
1211
|
-
// src/core/workspace-prep.ts
|
|
1212
|
-
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync } from "fs";
|
|
1213
|
-
import { dirname as dirname3, join as join9, relative as relative2 } from "path";
|
|
1214
|
-
var OVERRIDE_KEY = "i18next>typescript";
|
|
1215
|
-
var NATIVE_TS_ALIAS = "@typescript/native";
|
|
1216
|
-
var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
1217
|
-
var RELEASE_AGE_KEY = "minimumReleaseAgeExclude";
|
|
1218
|
-
var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
|
|
1219
|
-
function findWorkspaceRoot(startDir) {
|
|
1220
|
-
let dir = startDir;
|
|
1221
|
-
for (; ; ) {
|
|
1222
|
-
if (existsSync8(join9(dir, WORKSPACE_ROOT_MARKER))) return dir;
|
|
1223
|
-
const parent = dirname3(dir);
|
|
1224
|
-
if (parent === dir) return void 0;
|
|
1225
|
-
dir = parent;
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
function declaredNativeTs(pkg) {
|
|
1229
|
-
const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS];
|
|
1230
|
-
if (!spec) return void 0;
|
|
1231
|
-
const version = spec.slice(spec.lastIndexOf("@") + 1).replace(/^[\^~>=<\s]+/, "");
|
|
1232
|
-
return version || void 0;
|
|
1233
|
-
}
|
|
1234
|
-
function ensureI18nextSingleton(root, dryRun) {
|
|
1235
|
-
const pkgPath = join9(root, "package.json");
|
|
1236
|
-
if (!existsSync8(pkgPath)) return void 0;
|
|
1237
|
-
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
1238
|
-
const want = declaredNativeTs(pkg);
|
|
1239
|
-
if (!want) return void 0;
|
|
1240
|
-
const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
1241
|
-
if (have === want) return void 0;
|
|
1242
|
-
if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} (i18next singleton)`;
|
|
1243
|
-
pkg.pnpm ??= {};
|
|
1244
|
-
pkg.pnpm.overrides ??= {};
|
|
1245
|
-
pkg.pnpm.overrides[OVERRIDE_KEY] = want;
|
|
1246
|
-
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1247
|
-
return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
|
|
1248
|
-
}
|
|
1249
|
-
function ensureReleaseAgeAllowList(root, dryRun) {
|
|
1250
|
-
const yamlPath = join9(root, WORKSPACE_YAML);
|
|
1251
|
-
if (!existsSync8(yamlPath)) return void 0;
|
|
1252
|
-
const own = readOwnPackage().name;
|
|
1253
|
-
const lines = readFileSync6(yamlPath, "utf8").split("\n");
|
|
1254
|
-
const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
|
|
1255
|
-
if (keyIdx === -1) return void 0;
|
|
1256
|
-
let lastItemIdx = keyIdx;
|
|
1257
|
-
let indent = " ";
|
|
1258
|
-
for (let i = keyIdx + 1; i < lines.length; i++) {
|
|
1259
|
-
const match = lines[i]?.match(LIST_ITEM);
|
|
1260
|
-
if (!match) break;
|
|
1261
|
-
indent = match[1] ?? indent;
|
|
1262
|
-
lastItemIdx = i;
|
|
1263
|
-
if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === own) return void 0;
|
|
1264
|
-
}
|
|
1265
|
-
if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`;
|
|
1266
|
-
lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`);
|
|
1267
|
-
writeFileSync(yamlPath, lines.join("\n"));
|
|
1268
|
-
return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`;
|
|
1269
|
-
}
|
|
1270
|
-
var PRETTIER_IGNORE = ".prettierignore";
|
|
1271
|
-
var EXCLUSION_HEADER = "# @hublo/sentinel: modules formatted by oxfmt, not by the root Prettier";
|
|
1272
|
-
var ROOT_PRETTIER_CONFIGS = [
|
|
1273
|
-
".prettierrc",
|
|
1274
|
-
".prettierrc.json",
|
|
1275
|
-
".prettierrc.json5",
|
|
1276
|
-
".prettierrc.yml",
|
|
1277
|
-
".prettierrc.yaml",
|
|
1278
|
-
".prettierrc.js",
|
|
1279
|
-
".prettierrc.cjs",
|
|
1280
|
-
".prettierrc.mjs",
|
|
1281
|
-
"prettier.config.js",
|
|
1282
|
-
"prettier.config.cjs",
|
|
1283
|
-
"prettier.config.mjs"
|
|
1284
|
-
];
|
|
1285
|
-
function ensureFormatterExclusion(root, moduleDir, dryRun) {
|
|
1286
|
-
const rel = relative2(root, moduleDir).replaceAll("\\", "/");
|
|
1287
|
-
if (rel === "" || rel.startsWith("..")) return void 0;
|
|
1288
|
-
const ignorePath = join9(root, PRETTIER_IGNORE);
|
|
1289
|
-
const hasPrettier = existsSync8(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync8(join9(root, name)));
|
|
1290
|
-
if (!hasPrettier) return void 0;
|
|
1291
|
-
const pattern = `/${rel}/`;
|
|
1292
|
-
const existing = existsSync8(ignorePath) ? readFileSync6(ignorePath, "utf8") : "";
|
|
1293
|
-
const lines = existing.split("\n");
|
|
1294
|
-
if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
|
|
1295
|
-
return void 0;
|
|
1296
|
-
}
|
|
1297
|
-
if (dryRun) return `would exclude ${rel} from the root Prettier (${PRETTIER_IGNORE})`;
|
|
1298
|
-
const headerIdx = lines.findIndex((line) => line.trim() === EXCLUSION_HEADER);
|
|
1299
|
-
if (headerIdx === -1) {
|
|
1300
|
-
const prefix = existing.trim() === "" ? "" : `${existing.replace(/\n*$/, "")}
|
|
1301
|
-
|
|
1302
|
-
`;
|
|
1303
|
-
writeFileSync(ignorePath, `${prefix}${EXCLUSION_HEADER}
|
|
1304
|
-
${pattern}
|
|
1305
|
-
`);
|
|
1306
|
-
} else {
|
|
1307
|
-
let end = headerIdx + 1;
|
|
1308
|
-
while (end < lines.length && lines[end]?.trim().startsWith("/")) end += 1;
|
|
1309
|
-
const managed = [...lines.slice(headerIdx + 1, end), pattern].sort();
|
|
1310
|
-
writeFileSync(
|
|
1311
|
-
ignorePath,
|
|
1312
|
-
[...lines.slice(0, headerIdx + 1), ...managed, ...lines.slice(end)].join("\n")
|
|
1313
|
-
);
|
|
1853
|
+
".json",
|
|
1854
|
+
".jsonc",
|
|
1855
|
+
".md",
|
|
1856
|
+
".yaml",
|
|
1857
|
+
".yml",
|
|
1858
|
+
".html",
|
|
1859
|
+
".css",
|
|
1860
|
+
".scss",
|
|
1861
|
+
".hbs"
|
|
1862
|
+
];
|
|
1863
|
+
function hasSourceFiles(cwd, extensions) {
|
|
1864
|
+
const wanted = new Set(extensions);
|
|
1865
|
+
const queue = [cwd];
|
|
1866
|
+
while (queue.length > 0) {
|
|
1867
|
+
const dir = queue.pop();
|
|
1868
|
+
let entries;
|
|
1869
|
+
try {
|
|
1870
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1871
|
+
} catch {
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
for (const entry of entries) {
|
|
1875
|
+
if (entry.isDirectory()) {
|
|
1876
|
+
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join14(dir, entry.name));
|
|
1877
|
+
continue;
|
|
1878
|
+
}
|
|
1879
|
+
if (wanted.has(extname(entry.name))) return true;
|
|
1880
|
+
}
|
|
1314
1881
|
}
|
|
1315
|
-
return
|
|
1316
|
-
}
|
|
1317
|
-
function ensureWorkspacePrep(opts) {
|
|
1318
|
-
const dryRun = Boolean(opts.dryRun);
|
|
1319
|
-
return [
|
|
1320
|
-
ensureI18nextSingleton(opts.root, dryRun),
|
|
1321
|
-
ensureReleaseAgeAllowList(opts.root, dryRun),
|
|
1322
|
-
opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
|
|
1323
|
-
].filter((message) => message !== void 0);
|
|
1882
|
+
return false;
|
|
1324
1883
|
}
|
|
1325
|
-
function
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1884
|
+
function listSourceFiles(cwd, extensions) {
|
|
1885
|
+
const wanted = new Set(extensions);
|
|
1886
|
+
const queue = [cwd];
|
|
1887
|
+
const found = [];
|
|
1888
|
+
while (queue.length > 0) {
|
|
1889
|
+
const dir = queue.pop();
|
|
1890
|
+
let entries;
|
|
1330
1891
|
try {
|
|
1331
|
-
|
|
1892
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1332
1893
|
} catch {
|
|
1333
|
-
|
|
1894
|
+
continue;
|
|
1334
1895
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
});
|
|
1343
|
-
}
|
|
1344
|
-
const own = readOwnPackage().name;
|
|
1345
|
-
const yamlPath = join9(root, WORKSPACE_YAML);
|
|
1346
|
-
if (existsSync8(yamlPath)) {
|
|
1347
|
-
const yaml = readFileSync6(yamlPath, "utf8");
|
|
1348
|
-
const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
|
|
1349
|
-
if (listed) {
|
|
1350
|
-
const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
|
|
1351
|
-
entries.push({
|
|
1352
|
-
rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
|
|
1353
|
-
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.`
|
|
1354
|
-
});
|
|
1896
|
+
for (const entry of entries) {
|
|
1897
|
+
const full = join14(dir, entry.name);
|
|
1898
|
+
if (entry.isDirectory()) {
|
|
1899
|
+
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
|
|
1900
|
+
continue;
|
|
1901
|
+
}
|
|
1902
|
+
if (wanted.has(extname(entry.name))) found.push(relative3(cwd, full));
|
|
1355
1903
|
}
|
|
1356
1904
|
}
|
|
1357
|
-
return
|
|
1905
|
+
return found;
|
|
1358
1906
|
}
|
|
1359
1907
|
|
|
1360
|
-
// src/
|
|
1361
|
-
var
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
"useTabs",
|
|
1366
|
-
"tabWidth",
|
|
1367
|
-
"printWidth",
|
|
1368
|
-
"semi",
|
|
1369
|
-
"singleQuote",
|
|
1370
|
-
"jsxSingleQuote",
|
|
1371
|
-
"trailingComma",
|
|
1372
|
-
"quoteProps",
|
|
1373
|
-
"bracketSpacing",
|
|
1374
|
-
"bracketSameLine",
|
|
1375
|
-
"arrowParens",
|
|
1376
|
-
"endOfLine",
|
|
1377
|
-
"objectWrap",
|
|
1378
|
-
"proseWrap",
|
|
1379
|
-
"experimentalOperatorPosition"
|
|
1380
|
-
];
|
|
1381
|
-
var ALWAYS_LOCAL_KEYS = ["ignorePatterns", "overrides"];
|
|
1382
|
-
var FORMAT_DEFAULT_IGNORES = ["**/*.toml"];
|
|
1383
|
-
var PRETTIER_CONFIG_FILES = [
|
|
1384
|
-
".prettierrc",
|
|
1385
|
-
".prettierrc.json",
|
|
1386
|
-
".prettierrc.json5",
|
|
1387
|
-
".prettierrc.yml",
|
|
1388
|
-
".prettierrc.yaml",
|
|
1389
|
-
".prettierrc.js",
|
|
1390
|
-
".prettierrc.cjs",
|
|
1391
|
-
".prettierrc.mjs",
|
|
1392
|
-
"prettier.config.js",
|
|
1393
|
-
"prettier.config.cjs",
|
|
1394
|
-
"prettier.config.mjs"
|
|
1395
|
-
];
|
|
1396
|
-
function formatTargets() {
|
|
1397
|
-
return {
|
|
1398
|
-
[FORMAT_SCRIPT_NAME]: {
|
|
1399
|
-
cache: true,
|
|
1400
|
-
inputs: ["default", `{projectRoot}/${FORMAT_CONFIG_FILE}`]
|
|
1401
|
-
},
|
|
1402
|
-
[`${FORMAT_SCRIPT_NAME}:fix`]: { cache: false }
|
|
1403
|
-
};
|
|
1908
|
+
// src/core/named-scope.ts
|
|
1909
|
+
var TOLERATE_EMPTY_FLAG = "--no-error-on-unmatched-pattern";
|
|
1910
|
+
function scopeFlags(paths, options = []) {
|
|
1911
|
+
if (paths.length === 0 || options.includes(TOLERATE_EMPTY_FLAG)) return [];
|
|
1912
|
+
return [TOLERATE_EMPTY_FLAG];
|
|
1404
1913
|
}
|
|
1405
1914
|
|
|
1406
1915
|
// src/roles/format/format-script.ts
|
|
@@ -1431,8 +1940,8 @@ function writesWhenRewritten(name, command) {
|
|
|
1431
1940
|
var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
|
|
1432
1941
|
|
|
1433
1942
|
// src/roles/format/inherited-ignores.ts
|
|
1434
|
-
import { existsSync as
|
|
1435
|
-
import { join as
|
|
1943
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
1944
|
+
import { join as join15 } from "path";
|
|
1436
1945
|
var ROOT_IGNORE_FILE = ".prettierignore";
|
|
1437
1946
|
function isPattern(line) {
|
|
1438
1947
|
const trimmed = line.trim();
|
|
@@ -1447,11 +1956,11 @@ function toModulePattern(pattern) {
|
|
|
1447
1956
|
}
|
|
1448
1957
|
function inheritedIgnorePatterns(workspaceRoot) {
|
|
1449
1958
|
if (!workspaceRoot) return [];
|
|
1450
|
-
const path =
|
|
1451
|
-
if (!
|
|
1959
|
+
const path = join15(workspaceRoot, ROOT_IGNORE_FILE);
|
|
1960
|
+
if (!existsSync14(path)) return [];
|
|
1452
1961
|
let contents;
|
|
1453
1962
|
try {
|
|
1454
|
-
contents =
|
|
1963
|
+
contents = readFileSync11(path, "utf8");
|
|
1455
1964
|
} catch {
|
|
1456
1965
|
return [];
|
|
1457
1966
|
}
|
|
@@ -1495,56 +2004,14 @@ function needsSvelteCompiler(output) {
|
|
|
1495
2004
|
return /svelte\/compiler/.test(output);
|
|
1496
2005
|
}
|
|
1497
2006
|
|
|
1498
|
-
// src/roles/format/presets/base.json
|
|
1499
|
-
var base_default = {
|
|
1500
|
-
printWidth: 80,
|
|
1501
|
-
semi: false,
|
|
1502
|
-
singleQuote: true,
|
|
1503
|
-
trailingComma: "all",
|
|
1504
|
-
sortPackageJson: false,
|
|
1505
|
-
sortImports: false
|
|
1506
|
-
};
|
|
1507
|
-
|
|
1508
|
-
// src/roles/format/presets/nest-imports.json
|
|
1509
|
-
var nest_imports_default = {
|
|
1510
|
-
customGroups: [
|
|
1511
|
-
{
|
|
1512
|
-
groupName: "workspace",
|
|
1513
|
-
elementNamePattern: ["@hublo/**", "@shared/**", "@front/**", "@network/**"]
|
|
1514
|
-
}
|
|
1515
|
-
],
|
|
1516
|
-
groups: ["builtin", "external", "workspace", ["parent", "index"], "sibling"],
|
|
1517
|
-
newlinesBetween: true,
|
|
1518
|
-
order: "asc",
|
|
1519
|
-
ignoreCase: false
|
|
1520
|
-
};
|
|
1521
|
-
|
|
1522
|
-
// src/roles/format/preset-data.ts
|
|
1523
|
-
var FORMAT_PRESETS = ["base", "svelte", "nest"];
|
|
1524
|
-
function hasFormatPreset(name) {
|
|
1525
|
-
return FORMAT_PRESETS.includes(name);
|
|
1526
|
-
}
|
|
1527
|
-
function formatPresetName(declared) {
|
|
1528
|
-
if (declared === "svelte") return "svelte";
|
|
1529
|
-
if (declared === "nest") return "nest";
|
|
1530
|
-
return "base";
|
|
1531
|
-
}
|
|
1532
|
-
function formatPresetFor(preset) {
|
|
1533
|
-
const base = base_default;
|
|
1534
|
-
const name = formatPresetName(preset);
|
|
1535
|
-
if (name === "svelte") return { ...base, svelte: true };
|
|
1536
|
-
if (name === "nest") return { ...base, sortImports: nest_imports_default };
|
|
1537
|
-
return base;
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
2007
|
// src/roles/format/prettier-config.ts
|
|
1541
|
-
import { existsSync as
|
|
1542
|
-
import { join as
|
|
2008
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
2009
|
+
import { join as join17 } from "path";
|
|
1543
2010
|
|
|
1544
2011
|
// src/roles/format/resolve-oxfmt.ts
|
|
1545
|
-
import { readFileSync as
|
|
2012
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
1546
2013
|
import { createRequire as createRequire2 } from "module";
|
|
1547
|
-
import { dirname as dirname4, join as
|
|
2014
|
+
import { dirname as dirname4, join as join16 } from "path";
|
|
1548
2015
|
function resolveOxfmt(cwd) {
|
|
1549
2016
|
return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
|
|
1550
2017
|
}
|
|
@@ -1573,8 +2040,8 @@ function configSchema() {
|
|
|
1573
2040
|
function readConfigSchema() {
|
|
1574
2041
|
try {
|
|
1575
2042
|
const manifest = createRequire2(import.meta.url).resolve("oxfmt/package.json");
|
|
1576
|
-
const schemaPath =
|
|
1577
|
-
return JSON.parse(
|
|
2043
|
+
const schemaPath = join16(dirname4(manifest), "configuration_schema.json");
|
|
2044
|
+
return JSON.parse(readFileSync12(schemaPath, "utf8"));
|
|
1578
2045
|
} catch {
|
|
1579
2046
|
return void 0;
|
|
1580
2047
|
}
|
|
@@ -1605,14 +2072,14 @@ function toOxfmtOverrides(value) {
|
|
|
1605
2072
|
return { overrides, unresolved };
|
|
1606
2073
|
}
|
|
1607
2074
|
function readPrettierSettings(cwd) {
|
|
1608
|
-
const file = PRETTIER_CONFIG_FILES.find((name) =>
|
|
2075
|
+
const file = PRETTIER_CONFIG_FILES.find((name) => existsSync15(join17(cwd, name)));
|
|
1609
2076
|
if (!file) return { options: {}, unresolved: [] };
|
|
1610
2077
|
if (/\.(js|cjs|mjs)$/.test(file)) {
|
|
1611
2078
|
return { options: {}, file, unresolved: [`${file} is JavaScript, so it was not executed`] };
|
|
1612
2079
|
}
|
|
1613
2080
|
let parsed;
|
|
1614
2081
|
try {
|
|
1615
|
-
parsed = parseJsonc(
|
|
2082
|
+
parsed = parseJsonc(readFileSync13(join17(cwd, file), "utf8"), file);
|
|
1616
2083
|
} catch {
|
|
1617
2084
|
return { options: {}, file, unresolved: [`${file} could not be parsed`] };
|
|
1618
2085
|
}
|
|
@@ -1641,71 +2108,6 @@ function readPrettierSettings(cwd) {
|
|
|
1641
2108
|
return { options, file, unresolved };
|
|
1642
2109
|
}
|
|
1643
2110
|
|
|
1644
|
-
// src/roles/format/read-adoption.ts
|
|
1645
|
-
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
1646
|
-
import { join as join13 } from "path";
|
|
1647
|
-
var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
|
|
1648
|
-
configFile,
|
|
1649
|
-
preset: null,
|
|
1650
|
-
adopted: false,
|
|
1651
|
-
conformant: false,
|
|
1652
|
-
drift: [],
|
|
1653
|
-
local: {},
|
|
1654
|
-
presetVersion: null,
|
|
1655
|
-
effective: {},
|
|
1656
|
-
unreadable
|
|
1657
|
-
});
|
|
1658
|
-
function sameValue(a, b) {
|
|
1659
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
1660
|
-
}
|
|
1661
|
-
function readFormatAdoption(cwd) {
|
|
1662
|
-
const path = join13(cwd, FORMAT_CONFIG_FILE);
|
|
1663
|
-
if (!existsSync11(path)) return NOT_ADOPTED2(null);
|
|
1664
|
-
let parsed;
|
|
1665
|
-
try {
|
|
1666
|
-
parsed = parseJsonc(readFileSync10(path, "utf8"), FORMAT_CONFIG_FILE);
|
|
1667
|
-
} catch (error) {
|
|
1668
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
1669
|
-
return NOT_ADOPTED2(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
|
|
1670
|
-
}
|
|
1671
|
-
const provenance = parsed[PROVENANCE_KEY];
|
|
1672
|
-
if (!provenance || typeof provenance.preset !== "string") return NOT_ADOPTED2(FORMAT_CONFIG_FILE);
|
|
1673
|
-
if (!hasFormatPreset(provenance.preset)) {
|
|
1674
|
-
return NOT_ADOPTED2(
|
|
1675
|
-
FORMAT_CONFIG_FILE,
|
|
1676
|
-
`${FORMAT_CONFIG_FILE} declares the format preset "${provenance.preset}", which sentinel does not ship (shipped: ${FORMAT_PRESETS.join(", ")}). Re-run \`sentinel --init --format\`.`
|
|
1677
|
-
);
|
|
1678
|
-
}
|
|
1679
|
-
const declaredLocal = Array.isArray(provenance.local) ? provenance.local : [];
|
|
1680
|
-
const preset = formatPresetFor(provenance.preset);
|
|
1681
|
-
const drift = [];
|
|
1682
|
-
const local = {};
|
|
1683
|
-
for (const [key, expected] of Object.entries(preset)) {
|
|
1684
|
-
if (declaredLocal.includes(key)) {
|
|
1685
|
-
local[key] = parsed[key];
|
|
1686
|
-
continue;
|
|
1687
|
-
}
|
|
1688
|
-
if (!(key in parsed) || !sameValue(parsed[key], expected)) drift.push(key);
|
|
1689
|
-
}
|
|
1690
|
-
for (const key of declaredLocal) {
|
|
1691
|
-
if (!(key in preset)) local[key] = parsed[key];
|
|
1692
|
-
}
|
|
1693
|
-
for (const key of ALWAYS_LOCAL_KEYS) {
|
|
1694
|
-
if (key in parsed) local[key] = parsed[key];
|
|
1695
|
-
}
|
|
1696
|
-
return {
|
|
1697
|
-
configFile: FORMAT_CONFIG_FILE,
|
|
1698
|
-
preset: provenance.preset,
|
|
1699
|
-
adopted: true,
|
|
1700
|
-
conformant: drift.length === 0,
|
|
1701
|
-
drift,
|
|
1702
|
-
local,
|
|
1703
|
-
presetVersion: typeof provenance.version === "string" ? provenance.version : null,
|
|
1704
|
-
effective: parsed,
|
|
1705
|
-
unreadable: null
|
|
1706
|
-
};
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
2111
|
// src/roles/format/unenforced.ts
|
|
1710
2112
|
function unenforcedFormatOptions(options) {
|
|
1711
2113
|
const gaps = [];
|
|
@@ -1788,7 +2190,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1788
2190
|
manifestOperation(context.cwd, this.formatScripts(context.cwd))
|
|
1789
2191
|
];
|
|
1790
2192
|
const prettierConfigs = PRETTIER_CONFIG_FILES.filter(
|
|
1791
|
-
(name) =>
|
|
2193
|
+
(name) => existsSync16(join18(context.cwd, name))
|
|
1792
2194
|
);
|
|
1793
2195
|
for (const name of prettierConfigs) operations.push({ kind: "delete", path: name });
|
|
1794
2196
|
const removableDeps = this.modulePrettierDependencies(context.cwd);
|
|
@@ -2028,7 +2430,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
2028
2430
|
modulePrettierDependencies(cwd) {
|
|
2029
2431
|
const isPrettierPackage = (name) => name === "prettier" || name.startsWith("prettier-plugin-") || name.startsWith("@prettier/");
|
|
2030
2432
|
try {
|
|
2031
|
-
const manifest = JSON.parse(
|
|
2433
|
+
const manifest = JSON.parse(readFileSync14(join18(cwd, "package.json"), "utf8"));
|
|
2032
2434
|
return Object.keys(manifest.devDependencies ?? {}).filter(isPrettierPackage).map((name) => ["devDependencies", name]);
|
|
2033
2435
|
} catch {
|
|
2034
2436
|
return [];
|
|
@@ -2110,8 +2512,8 @@ function registerFormat() {
|
|
|
2110
2512
|
|
|
2111
2513
|
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
2112
2514
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
2113
|
-
import { existsSync as
|
|
2114
|
-
import { join as
|
|
2515
|
+
import { existsSync as existsSync24, readFileSync as readFileSync22, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2516
|
+
import { join as join28 } from "path";
|
|
2115
2517
|
|
|
2116
2518
|
// src/core/config/deferred-rules.ts
|
|
2117
2519
|
function deferredRuleNames(rules) {
|
|
@@ -2119,8 +2521,8 @@ function deferredRuleNames(rules) {
|
|
|
2119
2521
|
}
|
|
2120
2522
|
|
|
2121
2523
|
// src/core/fix-report.ts
|
|
2122
|
-
import { statSync } from "fs";
|
|
2123
|
-
import { join as
|
|
2524
|
+
import { statSync as statSync2 } from "fs";
|
|
2525
|
+
import { join as join19 } from "path";
|
|
2124
2526
|
var CONFIG_REFUSED = /failed to parse .*config|invalid config file/i;
|
|
2125
2527
|
function readFailure(output) {
|
|
2126
2528
|
const lines = output.split("\n").map((line2) => line2.trim());
|
|
@@ -2131,7 +2533,7 @@ function readFailure(output) {
|
|
|
2131
2533
|
}
|
|
2132
2534
|
function fingerprint(path) {
|
|
2133
2535
|
try {
|
|
2134
|
-
const stat =
|
|
2536
|
+
const stat = statSync2(path);
|
|
2135
2537
|
return `${stat.size}:${stat.mtimeMs}`;
|
|
2136
2538
|
} catch {
|
|
2137
2539
|
return void 0;
|
|
@@ -2140,7 +2542,7 @@ function fingerprint(path) {
|
|
|
2140
2542
|
function snapshotFiles(cwd, extensions) {
|
|
2141
2543
|
const snapshot = /* @__PURE__ */ new Map();
|
|
2142
2544
|
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2143
|
-
const mark = fingerprint(
|
|
2545
|
+
const mark = fingerprint(join19(cwd, file));
|
|
2144
2546
|
if (mark !== void 0) snapshot.set(file, mark);
|
|
2145
2547
|
}
|
|
2146
2548
|
return snapshot;
|
|
@@ -2148,7 +2550,7 @@ function snapshotFiles(cwd, extensions) {
|
|
|
2148
2550
|
function changedSince(cwd, before, extensions) {
|
|
2149
2551
|
const changed = [];
|
|
2150
2552
|
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2151
|
-
const now = fingerprint(
|
|
2553
|
+
const now = fingerprint(join19(cwd, file));
|
|
2152
2554
|
if (now === void 0) continue;
|
|
2153
2555
|
if (before.get(file) !== now) changed.push(file);
|
|
2154
2556
|
}
|
|
@@ -2167,8 +2569,8 @@ function describeFixOutcome(outcome, toolLabel) {
|
|
|
2167
2569
|
}
|
|
2168
2570
|
|
|
2169
2571
|
// src/roles/lint/module-baseline.ts
|
|
2170
|
-
import { existsSync as
|
|
2171
|
-
import { join as
|
|
2572
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
|
|
2573
|
+
import { join as join20 } from "path";
|
|
2172
2574
|
var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
|
|
2173
2575
|
var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
|
|
2174
2576
|
var LINT_BASELINE_SPECIFIER = `./${LINT_BASELINE_FILE}`;
|
|
@@ -2212,11 +2614,11 @@ function describeHolds(holds) {
|
|
|
2212
2614
|
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`;
|
|
2213
2615
|
}
|
|
2214
2616
|
function heldRules(cwd) {
|
|
2215
|
-
const path =
|
|
2216
|
-
if (!
|
|
2617
|
+
const path = join20(cwd, LINT_BASELINE_FILE);
|
|
2618
|
+
if (!existsSync17(path)) return [];
|
|
2217
2619
|
try {
|
|
2218
2620
|
const parsed = parseJsonc(
|
|
2219
|
-
|
|
2621
|
+
readFileSync15(path, "utf8"),
|
|
2220
2622
|
LINT_BASELINE_FILE
|
|
2221
2623
|
);
|
|
2222
2624
|
return Object.keys(parsed.rules ?? {});
|
|
@@ -2647,7 +3049,8 @@ var react_lib_default = {
|
|
|
2647
3049
|
{
|
|
2648
3050
|
name: "storybook",
|
|
2649
3051
|
specifier: "eslint-plugin-storybook"
|
|
2650
|
-
}
|
|
3052
|
+
},
|
|
3053
|
+
"../plugins/hublo.js"
|
|
2651
3054
|
],
|
|
2652
3055
|
settings: null,
|
|
2653
3056
|
renames: {
|
|
@@ -2656,6 +3059,7 @@ var react_lib_default = {
|
|
|
2656
3059
|
rules: {
|
|
2657
3060
|
curly: ["error", "all"],
|
|
2658
3061
|
"for-direction": "error",
|
|
3062
|
+
"hublo/no-regex-lookbehind": "error",
|
|
2659
3063
|
"jest-dom/prefer-checked": "error",
|
|
2660
3064
|
"jest-dom/prefer-empty": "error",
|
|
2661
3065
|
"jest-dom/prefer-enabled-disabled": "error",
|
|
@@ -3007,7 +3411,7 @@ var react_lib_default = {
|
|
|
3007
3411
|
],
|
|
3008
3412
|
uncovered: {
|
|
3009
3413
|
"no-octal": "no oxlint equivalent",
|
|
3010
|
-
"no-restricted-syntax": "no oxlint equivalent; the house rules it encoded ship as the local `hublo` plugin instead,
|
|
3414
|
+
"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.",
|
|
3011
3415
|
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."
|
|
3012
3416
|
}
|
|
3013
3417
|
};
|
|
@@ -3071,6 +3475,7 @@ var react_default = {
|
|
|
3071
3475
|
severity: "off",
|
|
3072
3476
|
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"
|
|
3073
3477
|
},
|
|
3478
|
+
"hublo/no-regex-lookbehind": "error",
|
|
3074
3479
|
"jest/expect-expect": {
|
|
3075
3480
|
severity: "off",
|
|
3076
3481
|
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"
|
|
@@ -3557,7 +3962,7 @@ var react_default = {
|
|
|
3557
3962
|
uncovered: {
|
|
3558
3963
|
"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.",
|
|
3559
3964
|
"no-octal": "no oxlint equivalent",
|
|
3560
|
-
"no-restricted-syntax": "no oxlint equivalent as configuration, so the
|
|
3965
|
+
"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.",
|
|
3561
3966
|
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."
|
|
3562
3967
|
}
|
|
3563
3968
|
};
|
|
@@ -4039,14 +4444,14 @@ function downgradedRulesFor(preset) {
|
|
|
4039
4444
|
}
|
|
4040
4445
|
|
|
4041
4446
|
// src/roles/lint/extra-layers.ts
|
|
4042
|
-
import { existsSync as
|
|
4043
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4447
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
4448
|
+
import { isAbsolute as isAbsolute2, join as join21, resolve as resolve3 } from "path";
|
|
4044
4449
|
function ruleCount(cwd, specifier) {
|
|
4045
4450
|
const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
|
|
4046
|
-
if (!
|
|
4451
|
+
if (!existsSync18(path)) return void 0;
|
|
4047
4452
|
try {
|
|
4048
4453
|
const parsed = parseJsonc(
|
|
4049
|
-
|
|
4454
|
+
readFileSync16(path, "utf8"),
|
|
4050
4455
|
specifier
|
|
4051
4456
|
);
|
|
4052
4457
|
return Object.keys(parsed.rules ?? {}).length;
|
|
@@ -4066,7 +4471,7 @@ function extraLayers(cwd, extendsList) {
|
|
|
4066
4471
|
function committedExtendsList(cwd, configFile) {
|
|
4067
4472
|
try {
|
|
4068
4473
|
const parsed = parseJsonc(
|
|
4069
|
-
|
|
4474
|
+
readFileSync16(join21(cwd, configFile), "utf8"),
|
|
4070
4475
|
configFile
|
|
4071
4476
|
);
|
|
4072
4477
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4125,8 +4530,8 @@ function errorCountsByConfigRule(stdout) {
|
|
|
4125
4530
|
}
|
|
4126
4531
|
|
|
4127
4532
|
// src/core/config/read-adoption.ts
|
|
4128
|
-
import { existsSync as
|
|
4129
|
-
import { join as
|
|
4533
|
+
import { existsSync as existsSync20, readFileSync as readFileSync18 } from "fs";
|
|
4534
|
+
import { join as join23 } from "path";
|
|
4130
4535
|
|
|
4131
4536
|
// src/core/config/owned-keys.ts
|
|
4132
4537
|
function presetOwnedKeys(config, permitted, presetSets) {
|
|
@@ -4141,12 +4546,12 @@ function localOnlyKeys(config, permitted, presetSets) {
|
|
|
4141
4546
|
}
|
|
4142
4547
|
|
|
4143
4548
|
// src/core/config/resolve-config-target.ts
|
|
4144
|
-
import { existsSync as
|
|
4145
|
-
import { join as
|
|
4549
|
+
import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
|
|
4550
|
+
import { join as join22 } from "path";
|
|
4146
4551
|
function readExtends(absolutePath) {
|
|
4147
4552
|
let parsed;
|
|
4148
4553
|
try {
|
|
4149
|
-
parsed = parseJsonc(
|
|
4554
|
+
parsed = parseJsonc(readFileSync17(absolutePath, "utf8"), absolutePath);
|
|
4150
4555
|
} catch {
|
|
4151
4556
|
return [];
|
|
4152
4557
|
}
|
|
@@ -4160,8 +4565,8 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
|
|
|
4160
4565
|
let existing;
|
|
4161
4566
|
let existingExtendsSomething = false;
|
|
4162
4567
|
for (const candidate of candidates) {
|
|
4163
|
-
const absolutePath =
|
|
4164
|
-
if (!
|
|
4568
|
+
const absolutePath = join22(moduleDir, candidate);
|
|
4569
|
+
if (!existsSync19(absolutePath)) continue;
|
|
4165
4570
|
const chain = readExtends(absolutePath);
|
|
4166
4571
|
if (existing === void 0) {
|
|
4167
4572
|
existing = candidate;
|
|
@@ -4194,12 +4599,12 @@ var NOT_ADOPTED3 = (configFile, unreadable = null) => ({
|
|
|
4194
4599
|
});
|
|
4195
4600
|
function readAdoption(cwd, options) {
|
|
4196
4601
|
const target = resolveConfigTarget(cwd, options);
|
|
4197
|
-
if (target.reason === "none" || !
|
|
4602
|
+
if (target.reason === "none" || !existsSync20(join23(cwd, target.path))) {
|
|
4198
4603
|
return NOT_ADOPTED3(target.reason === "none" ? null : target.path);
|
|
4199
4604
|
}
|
|
4200
4605
|
let parsed;
|
|
4201
4606
|
try {
|
|
4202
|
-
parsed = parseJsonc(
|
|
4607
|
+
parsed = parseJsonc(readFileSync18(join23(cwd, target.path), "utf8"), target.path);
|
|
4203
4608
|
} catch (error) {
|
|
4204
4609
|
const reason = error instanceof Error ? error.message : String(error);
|
|
4205
4610
|
return NOT_ADOPTED3(target.path, `${target.path} could not be parsed (${reason})`);
|
|
@@ -4233,9 +4638,9 @@ function readLintAdoption(cwd) {
|
|
|
4233
4638
|
}
|
|
4234
4639
|
|
|
4235
4640
|
// src/roles/lint/resolve-oxlint.ts
|
|
4236
|
-
import { existsSync as
|
|
4641
|
+
import { existsSync as existsSync21 } from "fs";
|
|
4237
4642
|
import { createRequire as createRequire3 } from "module";
|
|
4238
|
-
import { delimiter as delimiter2, dirname as dirname5, join as
|
|
4643
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join24 } from "path";
|
|
4239
4644
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4240
4645
|
var PACKAGE_OF = {
|
|
4241
4646
|
oxlint: "oxlint",
|
|
@@ -4260,30 +4665,30 @@ function tsgolintShim(cwd) {
|
|
|
4260
4665
|
for (const owner of ["oxlint-tsgolint", "oxlint"]) {
|
|
4261
4666
|
try {
|
|
4262
4667
|
const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
|
|
4263
|
-
candidates.push(
|
|
4264
|
-
candidates.push(
|
|
4668
|
+
candidates.push(join24(packageDir, "node_modules", ".bin", "tsgolint"));
|
|
4669
|
+
candidates.push(join24(packageDir, "..", ".bin", "tsgolint"));
|
|
4265
4670
|
} catch {
|
|
4266
4671
|
}
|
|
4267
4672
|
}
|
|
4268
4673
|
try {
|
|
4269
4674
|
const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
|
|
4270
|
-
candidates.push(
|
|
4675
|
+
candidates.push(join24(ownRoot, "node_modules", ".bin", "tsgolint"));
|
|
4271
4676
|
} catch {
|
|
4272
4677
|
candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
|
|
4273
4678
|
}
|
|
4274
|
-
return candidates.find((candidate) => candidate !== "" &&
|
|
4679
|
+
return candidates.find((candidate) => candidate !== "" && existsSync21(candidate));
|
|
4275
4680
|
}
|
|
4276
4681
|
function oxlintSearchPath(cwd) {
|
|
4277
4682
|
return binSearchPath(cwd);
|
|
4278
4683
|
}
|
|
4279
4684
|
|
|
4280
4685
|
// src/roles/lint/adapters/oxlint/plan.ts
|
|
4281
|
-
import { existsSync as
|
|
4282
|
-
import { join as
|
|
4686
|
+
import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
|
|
4687
|
+
import { join as join27 } from "path";
|
|
4283
4688
|
|
|
4284
4689
|
// src/roles/lint/eslint-ignores.ts
|
|
4285
|
-
import { existsSync as
|
|
4286
|
-
import { join as
|
|
4690
|
+
import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
|
|
4691
|
+
import { join as join25 } from "path";
|
|
4287
4692
|
import { parseSync as parseSync2 } from "oxc-parser";
|
|
4288
4693
|
var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
|
|
4289
4694
|
function toOxlintPattern(pattern) {
|
|
@@ -4295,11 +4700,11 @@ function readRootEslintIgnores(root) {
|
|
|
4295
4700
|
return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
|
|
4296
4701
|
}
|
|
4297
4702
|
function readEslintIgnores(cwd) {
|
|
4298
|
-
const config = ESLINT_CONFIG_FILES.map((name) =>
|
|
4703
|
+
const config = ESLINT_CONFIG_FILES.map((name) => join25(cwd, name)).find((path) => existsSync22(path));
|
|
4299
4704
|
if (!config) return { patterns: [], unresolved: [] };
|
|
4300
4705
|
let source;
|
|
4301
4706
|
try {
|
|
4302
|
-
source =
|
|
4707
|
+
source = readFileSync19(config, "utf8");
|
|
4303
4708
|
} catch {
|
|
4304
4709
|
return { patterns: [], unresolved: [] };
|
|
4305
4710
|
}
|
|
@@ -4400,8 +4805,8 @@ function lintPresetFor(preset) {
|
|
|
4400
4805
|
}
|
|
4401
4806
|
|
|
4402
4807
|
// src/roles/lint/rename-suppressions.ts
|
|
4403
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
4404
|
-
import { join as
|
|
4808
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync20, statSync as statSync3 } from "fs";
|
|
4809
|
+
import { join as join26, relative as relative4 } from "path";
|
|
4405
4810
|
var SOURCE_EXTENSIONS = [
|
|
4406
4811
|
".ts",
|
|
4407
4812
|
".tsx",
|
|
@@ -4448,10 +4853,10 @@ function* sourceFiles(dir) {
|
|
|
4448
4853
|
return;
|
|
4449
4854
|
}
|
|
4450
4855
|
for (const entry of entries) {
|
|
4451
|
-
const full =
|
|
4856
|
+
const full = join26(dir, entry);
|
|
4452
4857
|
let isDirectory;
|
|
4453
4858
|
try {
|
|
4454
|
-
isDirectory =
|
|
4859
|
+
isDirectory = statSync3(full).isDirectory();
|
|
4455
4860
|
} catch {
|
|
4456
4861
|
continue;
|
|
4457
4862
|
}
|
|
@@ -4468,7 +4873,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
4468
4873
|
for (const file of sourceFiles(cwd)) {
|
|
4469
4874
|
let content;
|
|
4470
4875
|
try {
|
|
4471
|
-
content =
|
|
4876
|
+
content = readFileSync20(file, "utf8");
|
|
4472
4877
|
} catch {
|
|
4473
4878
|
continue;
|
|
4474
4879
|
}
|
|
@@ -4492,7 +4897,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
4492
4897
|
}
|
|
4493
4898
|
if (hits.length === 0) return;
|
|
4494
4899
|
found.push({
|
|
4495
|
-
file:
|
|
4900
|
+
file: relative4(cwd, file),
|
|
4496
4901
|
line: index + 1,
|
|
4497
4902
|
from: line,
|
|
4498
4903
|
to: line.replace(rules, renamed.join(", ")),
|
|
@@ -4596,7 +5001,7 @@ function plan2(context) {
|
|
|
4596
5001
|
keys: removableDeps
|
|
4597
5002
|
});
|
|
4598
5003
|
}
|
|
4599
|
-
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) =>
|
|
5004
|
+
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync23(join27(context.cwd, name)));
|
|
4600
5005
|
for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
|
|
4601
5006
|
operations.push(
|
|
4602
5007
|
...nxTargetOperations({
|
|
@@ -4694,7 +5099,7 @@ function lintScripts(cwd) {
|
|
|
4694
5099
|
function committedExtends(cwd) {
|
|
4695
5100
|
try {
|
|
4696
5101
|
const parsed = parseJsonc(
|
|
4697
|
-
|
|
5102
|
+
readFileSync21(join27(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4698
5103
|
LINT_CONFIG_FILE
|
|
4699
5104
|
);
|
|
4700
5105
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4705,7 +5110,7 @@ function committedExtends(cwd) {
|
|
|
4705
5110
|
function committedIgnorePatterns(cwd) {
|
|
4706
5111
|
try {
|
|
4707
5112
|
const parsed = parseJsonc(
|
|
4708
|
-
|
|
5113
|
+
readFileSync21(join27(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4709
5114
|
LINT_CONFIG_FILE
|
|
4710
5115
|
);
|
|
4711
5116
|
return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4717,7 +5122,7 @@ function moduleEslintDependencies(cwd) {
|
|
|
4717
5122
|
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/");
|
|
4718
5123
|
let manifest;
|
|
4719
5124
|
try {
|
|
4720
|
-
manifest = JSON.parse(
|
|
5125
|
+
manifest = JSON.parse(readFileSync21(join27(cwd, "package.json"), "utf8"));
|
|
4721
5126
|
} catch {
|
|
4722
5127
|
return [];
|
|
4723
5128
|
}
|
|
@@ -4851,23 +5256,23 @@ ${result.stderr ?? ""}`);
|
|
|
4851
5256
|
* build the developer can see, rather than a silent half-adoption they cannot.
|
|
4852
5257
|
*/
|
|
4853
5258
|
writeModuleBaseline(ctx, oxlint, env) {
|
|
4854
|
-
const configPath =
|
|
4855
|
-
const baselinePath =
|
|
4856
|
-
if (!
|
|
5259
|
+
const configPath = join28(ctx.cwd, LINT_CONFIG_FILE);
|
|
5260
|
+
const baselinePath = join28(ctx.cwd, LINT_BASELINE_FILE);
|
|
5261
|
+
if (!existsSync24(configPath)) return;
|
|
4857
5262
|
let config;
|
|
4858
5263
|
try {
|
|
4859
5264
|
config = parseJsonc(
|
|
4860
|
-
|
|
5265
|
+
readFileSync22(configPath, "utf8"),
|
|
4861
5266
|
LINT_CONFIG_FILE
|
|
4862
5267
|
);
|
|
4863
5268
|
} catch {
|
|
4864
5269
|
return;
|
|
4865
5270
|
}
|
|
4866
5271
|
const current = Array.isArray(config.extends) ? config.extends : [];
|
|
4867
|
-
const measurePath =
|
|
5272
|
+
const measurePath = join28(ctx.cwd, LINT_MEASURE_FILE);
|
|
4868
5273
|
let measured;
|
|
4869
5274
|
try {
|
|
4870
|
-
|
|
5275
|
+
writeFileSync3(
|
|
4871
5276
|
measurePath,
|
|
4872
5277
|
`${JSON.stringify({ ...config, extends: extendsWithBaseline(current, false) }, null, 2)}
|
|
4873
5278
|
`
|
|
@@ -4885,7 +5290,7 @@ ${result.stderr ?? ""}`);
|
|
|
4885
5290
|
{ cwd: ctx.cwd, encoding: "utf8", env, maxBuffer: CAPTURE_MAX_BUFFER }
|
|
4886
5291
|
);
|
|
4887
5292
|
} finally {
|
|
4888
|
-
|
|
5293
|
+
rmSync2(measurePath, { force: true });
|
|
4889
5294
|
}
|
|
4890
5295
|
if (measured.error || !(measured.stdout ?? "").includes('"diagnostics"')) {
|
|
4891
5296
|
process.stderr.write(
|
|
@@ -4899,20 +5304,20 @@ ${result.stderr ?? ""}`);
|
|
|
4899
5304
|
const holds = holdsFrom(errorCountsByConfigRule(measured.stdout ?? ""));
|
|
4900
5305
|
const next = extendsWithBaseline(current, holds.length > 0);
|
|
4901
5306
|
if (holds.length > 0) {
|
|
4902
|
-
|
|
5307
|
+
writeFileSync3(baselinePath, renderBaseline(holds, readOwnPackage().version));
|
|
4903
5308
|
process.stderr.write(` ${palette(process.stderr).warn(describeHolds(holds))}
|
|
4904
5309
|
`);
|
|
4905
5310
|
} else {
|
|
4906
|
-
|
|
5311
|
+
rmSync2(baselinePath, { force: true });
|
|
4907
5312
|
}
|
|
4908
5313
|
if (next.length !== current.length || next.some((entry, at) => entry !== current[at])) {
|
|
4909
5314
|
config.extends = next;
|
|
4910
|
-
|
|
5315
|
+
writeFileSync3(configPath, `${JSON.stringify(config, null, 2)}
|
|
4911
5316
|
`);
|
|
4912
5317
|
}
|
|
4913
5318
|
}
|
|
4914
5319
|
async run(ctx) {
|
|
4915
|
-
if (!
|
|
5320
|
+
if (!existsSync24(join28(ctx.cwd, LINT_CONFIG_FILE))) {
|
|
4916
5321
|
process.stderr.write(
|
|
4917
5322
|
`sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
|
|
4918
5323
|
`
|
|
@@ -5060,7 +5465,7 @@ ${result.stderr ?? ""}`);
|
|
|
5060
5465
|
let stub;
|
|
5061
5466
|
try {
|
|
5062
5467
|
stub = parseJsonc(
|
|
5063
|
-
|
|
5468
|
+
readFileSync22(join28(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
5064
5469
|
LINT_CONFIG_FILE
|
|
5065
5470
|
);
|
|
5066
5471
|
} catch {
|
|
@@ -5070,7 +5475,7 @@ ${result.stderr ?? ""}`);
|
|
|
5070
5475
|
for (const entry of stub.extends ?? []) {
|
|
5071
5476
|
try {
|
|
5072
5477
|
const preset = parseJsonc(
|
|
5073
|
-
|
|
5478
|
+
readFileSync22(join28(cwd, entry), "utf8"),
|
|
5074
5479
|
entry
|
|
5075
5480
|
);
|
|
5076
5481
|
for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
|
|
@@ -5140,14 +5545,14 @@ ${result.stderr ?? ""}`);
|
|
|
5140
5545
|
let parsed;
|
|
5141
5546
|
try {
|
|
5142
5547
|
parsed = parseJsonc(
|
|
5143
|
-
|
|
5548
|
+
readFileSync22(join28(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
5144
5549
|
LINT_CONFIG_FILE
|
|
5145
5550
|
);
|
|
5146
5551
|
} catch {
|
|
5147
5552
|
return void 0;
|
|
5148
5553
|
}
|
|
5149
5554
|
const targets = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
|
|
5150
|
-
return targets.find((target) => !
|
|
5555
|
+
return targets.find((target) => !existsSync24(join28(cwd, target)));
|
|
5151
5556
|
}
|
|
5152
5557
|
/** Announce what is not enforced, so reduced coverage is never silent. */
|
|
5153
5558
|
announceDisabled(preset) {
|
|
@@ -5179,9 +5584,9 @@ function registerLint() {
|
|
|
5179
5584
|
|
|
5180
5585
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
5181
5586
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5182
|
-
import { existsSync as
|
|
5587
|
+
import { existsSync as existsSync25, readFileSync as readFileSync24 } from "fs";
|
|
5183
5588
|
import { createRequire as createRequire4 } from "module";
|
|
5184
|
-
import { join as
|
|
5589
|
+
import { join as join30 } from "path";
|
|
5185
5590
|
|
|
5186
5591
|
// src/roles/typescript/presets/nest.json
|
|
5187
5592
|
var nest_default2 = {
|
|
@@ -5362,8 +5767,8 @@ function readTsconfigAdoption(cwd) {
|
|
|
5362
5767
|
}
|
|
5363
5768
|
|
|
5364
5769
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
5365
|
-
import { readFileSync as
|
|
5366
|
-
import { join as
|
|
5770
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
5771
|
+
import { join as join29 } from "path";
|
|
5367
5772
|
|
|
5368
5773
|
// src/roles/typescript/typecheck-script.ts
|
|
5369
5774
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
@@ -5456,7 +5861,7 @@ function planAdoption(context) {
|
|
|
5456
5861
|
};
|
|
5457
5862
|
}
|
|
5458
5863
|
const existing = parseJsonc(
|
|
5459
|
-
|
|
5864
|
+
readFileSync23(join29(context.cwd, target.path), "utf8"),
|
|
5460
5865
|
target.path
|
|
5461
5866
|
);
|
|
5462
5867
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
@@ -5594,7 +5999,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5594
5999
|
let chain;
|
|
5595
6000
|
try {
|
|
5596
6001
|
const parsed = parseJsonc(
|
|
5597
|
-
|
|
6002
|
+
readFileSync24(join30(cwd, target.path), "utf8"),
|
|
5598
6003
|
target.path
|
|
5599
6004
|
);
|
|
5600
6005
|
chain = parsed.extends;
|
|
@@ -5607,7 +6012,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5607
6012
|
);
|
|
5608
6013
|
if (preset === void 0) return void 0;
|
|
5609
6014
|
try {
|
|
5610
|
-
createRequire4(
|
|
6015
|
+
createRequire4(join30(cwd, "noop.js")).resolve(preset);
|
|
5611
6016
|
return void 0;
|
|
5612
6017
|
} catch {
|
|
5613
6018
|
return preset;
|
|
@@ -5703,7 +6108,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5703
6108
|
referencedProjects(cwd, config) {
|
|
5704
6109
|
try {
|
|
5705
6110
|
const parsed = parseJsonc(
|
|
5706
|
-
|
|
6111
|
+
readFileSync24(join30(cwd, config), "utf8"),
|
|
5707
6112
|
config
|
|
5708
6113
|
);
|
|
5709
6114
|
const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
|
|
@@ -5719,7 +6124,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5719
6124
|
* check.
|
|
5720
6125
|
*/
|
|
5721
6126
|
typecheckTarget(cwd) {
|
|
5722
|
-
if (
|
|
6127
|
+
if (existsSync25(join30(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
5723
6128
|
const target = resolveTsconfigTarget(cwd);
|
|
5724
6129
|
return target.reason === "none" ? null : target.path;
|
|
5725
6130
|
}
|
|
@@ -5923,7 +6328,7 @@ function replaceLines(current, replacements) {
|
|
|
5923
6328
|
}
|
|
5924
6329
|
|
|
5925
6330
|
// src/core/apply-plan.ts
|
|
5926
|
-
import { existsSync as
|
|
6331
|
+
import { existsSync as existsSync26, readFileSync as readFileSync25, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
5927
6332
|
import { resolve as resolve4, sep } from "path";
|
|
5928
6333
|
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
5929
6334
|
|
|
@@ -5942,7 +6347,7 @@ function resolveWithinRoot(cwd, relativePath) {
|
|
|
5942
6347
|
return absolutePath;
|
|
5943
6348
|
}
|
|
5944
6349
|
function readIfExists(absolutePath) {
|
|
5945
|
-
return
|
|
6350
|
+
return existsSync26(absolutePath) ? readFileSync25(absolutePath, "utf8") : void 0;
|
|
5946
6351
|
}
|
|
5947
6352
|
function* leaves(value, prefix = []) {
|
|
5948
6353
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -6044,14 +6449,14 @@ function preparePlan(cwd, plan3) {
|
|
|
6044
6449
|
}
|
|
6045
6450
|
function writeFileAtomic(absolutePath, contents) {
|
|
6046
6451
|
const tempPath = `${absolutePath}.sentinel-${process.pid}.tmp`;
|
|
6047
|
-
|
|
6452
|
+
writeFileSync4(tempPath, contents);
|
|
6048
6453
|
renameSync(tempPath, absolutePath);
|
|
6049
6454
|
}
|
|
6050
6455
|
function applyPlan(cwd, plan3) {
|
|
6051
6456
|
const changed = preparePlan(cwd, plan3).filter((file) => file.before !== file.after);
|
|
6052
6457
|
for (const file of changed) {
|
|
6053
6458
|
if (file.deleted) {
|
|
6054
|
-
|
|
6459
|
+
rmSync3(file.absolutePath, { force: true });
|
|
6055
6460
|
continue;
|
|
6056
6461
|
}
|
|
6057
6462
|
writeFileAtomic(file.absolutePath, file.after);
|
|
@@ -6060,8 +6465,8 @@ function applyPlan(cwd, plan3) {
|
|
|
6060
6465
|
}
|
|
6061
6466
|
|
|
6062
6467
|
// src/core/config/preset-evidence.ts
|
|
6063
|
-
import { existsSync as
|
|
6064
|
-
import { join as
|
|
6468
|
+
import { existsSync as existsSync27, readdirSync as readdirSync3, readFileSync as readFileSync26 } from "fs";
|
|
6469
|
+
import { join as join31 } from "path";
|
|
6065
6470
|
var PATH_SIGNALS = [
|
|
6066
6471
|
{
|
|
6067
6472
|
preset: "nest",
|
|
@@ -6075,11 +6480,11 @@ var DEPENDENCY_SIGNALS = [
|
|
|
6075
6480
|
{ preset: "nest", pattern: /^@nestjs\// }
|
|
6076
6481
|
];
|
|
6077
6482
|
function dependencyNames(cwd) {
|
|
6078
|
-
const path =
|
|
6079
|
-
if (!
|
|
6483
|
+
const path = join31(cwd, "package.json");
|
|
6484
|
+
if (!existsSync27(path)) return [];
|
|
6080
6485
|
try {
|
|
6081
6486
|
const manifest = parseJsonc(
|
|
6082
|
-
|
|
6487
|
+
readFileSync26(path, "utf8"),
|
|
6083
6488
|
path
|
|
6084
6489
|
);
|
|
6085
6490
|
return [
|
|
@@ -6102,7 +6507,7 @@ function declaresJsx(cwd) {
|
|
|
6102
6507
|
for (const name of entries) {
|
|
6103
6508
|
try {
|
|
6104
6509
|
const config = parseJsonc(
|
|
6105
|
-
|
|
6510
|
+
readFileSync26(join31(cwd, name), "utf8"),
|
|
6106
6511
|
name
|
|
6107
6512
|
);
|
|
6108
6513
|
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
@@ -6261,24 +6666,25 @@ export {
|
|
|
6261
6666
|
declaredPresetFor,
|
|
6262
6667
|
availableTargets,
|
|
6263
6668
|
resolve,
|
|
6264
|
-
BaseAdapter,
|
|
6265
|
-
resolveBin,
|
|
6266
6669
|
readOwnVersion,
|
|
6267
6670
|
readProjectPackageJson,
|
|
6268
6671
|
moduleName,
|
|
6672
|
+
buildConfigFile,
|
|
6673
|
+
BaseAdapter,
|
|
6674
|
+
resolveBin,
|
|
6675
|
+
WORKSPACE_ROOT_MARKER,
|
|
6676
|
+
findWorkspaceRoot,
|
|
6677
|
+
ensureWorkspacePrep,
|
|
6678
|
+
inspectWorkspacePrep,
|
|
6269
6679
|
VERBS,
|
|
6270
6680
|
TARGETS,
|
|
6271
6681
|
LONG_RUNNING_TARGETS,
|
|
6272
6682
|
SWEEPABLE_TARGETS,
|
|
6273
6683
|
PRESET_NAMES,
|
|
6274
|
-
WORKSPACE_ROOT_MARKER,
|
|
6275
|
-
findWorkspaceRoot,
|
|
6276
|
-
ensureWorkspacePrep,
|
|
6277
|
-
inspectWorkspacePrep,
|
|
6278
6684
|
palette,
|
|
6279
6685
|
registerAdapters,
|
|
6280
6686
|
describeFramework,
|
|
6281
6687
|
detectFramework,
|
|
6282
6688
|
dispatch
|
|
6283
6689
|
};
|
|
6284
|
-
//# sourceMappingURL=chunk-
|
|
6690
|
+
//# sourceMappingURL=chunk-Z5I6SEKF.js.map
|