@hublo/sentinel 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/sentinel.js +34 -33
- package/dist/{chunk-HKGCWPRT.js → chunk-BN3TB6TF.js} +162 -25
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/bin/sentinel.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
FLAVOURS,
|
|
4
|
+
TARGETS,
|
|
5
|
+
VERBS,
|
|
3
6
|
availableTargets,
|
|
4
7
|
describeFramework,
|
|
5
8
|
dispatch,
|
|
@@ -11,7 +14,7 @@ import {
|
|
|
11
14
|
registerAdapters,
|
|
12
15
|
resolve,
|
|
13
16
|
resolveBin
|
|
14
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-BN3TB6TF.js";
|
|
15
18
|
|
|
16
19
|
// bin/sentinel.ts
|
|
17
20
|
import { program } from "commander";
|
|
@@ -106,20 +109,6 @@ function resolveContext(cwd2, opts2) {
|
|
|
106
109
|
);
|
|
107
110
|
}
|
|
108
111
|
|
|
109
|
-
// src/core/domain.ts
|
|
110
|
-
var VERBS = ["run", "inspect", "init", "migrate", "report", "status"];
|
|
111
|
-
var TARGETS = [
|
|
112
|
-
"lint",
|
|
113
|
-
"format",
|
|
114
|
-
"typescript",
|
|
115
|
-
"build",
|
|
116
|
-
"test",
|
|
117
|
-
"static-analysis",
|
|
118
|
-
"runtime-analysis",
|
|
119
|
-
"arch"
|
|
120
|
-
];
|
|
121
|
-
var FLAVOURS = ["react", "nest", "svelte", "node"];
|
|
122
|
-
|
|
123
112
|
// src/core/orchestrate.ts
|
|
124
113
|
async function analyse(params) {
|
|
125
114
|
const results = [];
|
|
@@ -141,14 +130,6 @@ async function analyse(params) {
|
|
|
141
130
|
);
|
|
142
131
|
}
|
|
143
132
|
}
|
|
144
|
-
const ctx = {
|
|
145
|
-
module: module.name,
|
|
146
|
-
cwd: module.root,
|
|
147
|
-
flavour: flavour2,
|
|
148
|
-
ci: params.ci,
|
|
149
|
-
fix: params.fix,
|
|
150
|
-
maxDiagnostics: params.maxDiagnostics
|
|
151
|
-
};
|
|
152
133
|
for (const target of params.targets) {
|
|
153
134
|
let adapter;
|
|
154
135
|
try {
|
|
@@ -167,13 +148,22 @@ async function analyse(params) {
|
|
|
167
148
|
}
|
|
168
149
|
continue;
|
|
169
150
|
}
|
|
151
|
+
const effectiveFlavour = params.flavour ?? adapter.declaredFlavour?.(module.root) ?? flavour2;
|
|
152
|
+
const ctx = {
|
|
153
|
+
module: module.name,
|
|
154
|
+
cwd: module.root,
|
|
155
|
+
flavour: effectiveFlavour,
|
|
156
|
+
ci: params.ci,
|
|
157
|
+
fix: params.fix,
|
|
158
|
+
maxDiagnostics: params.maxDiagnostics
|
|
159
|
+
};
|
|
170
160
|
try {
|
|
171
161
|
if (params.verb === "run") {
|
|
172
162
|
if (params.modules.length > 1) {
|
|
173
163
|
const err = palette(process.stderr);
|
|
174
164
|
process.stderr.write(
|
|
175
165
|
`
|
|
176
|
-
${err.accent("\u25B6")} ${err.strong(module.name)} ${err.dim(`(${
|
|
166
|
+
${err.accent("\u25B6")} ${err.strong(module.name)} ${err.dim(`(${effectiveFlavour})`)} ${target}
|
|
177
167
|
`
|
|
178
168
|
);
|
|
179
169
|
}
|
|
@@ -181,7 +171,7 @@ async function analyse(params) {
|
|
|
181
171
|
results.push({
|
|
182
172
|
project: module.name,
|
|
183
173
|
target,
|
|
184
|
-
flavour:
|
|
174
|
+
flavour: effectiveFlavour,
|
|
185
175
|
ok: result.ok,
|
|
186
176
|
status: result.ok ? "ok" : "failed",
|
|
187
177
|
data: {}
|
|
@@ -192,7 +182,7 @@ async function analyse(params) {
|
|
|
192
182
|
results.push({
|
|
193
183
|
project: module.name,
|
|
194
184
|
target,
|
|
195
|
-
flavour:
|
|
185
|
+
flavour: effectiveFlavour,
|
|
196
186
|
ok: result.ok,
|
|
197
187
|
status: result.ok ? "ok" : "failed",
|
|
198
188
|
data: result.metrics ?? {}
|
|
@@ -203,7 +193,7 @@ async function analyse(params) {
|
|
|
203
193
|
results.push({
|
|
204
194
|
project: module.name,
|
|
205
195
|
target,
|
|
206
|
-
flavour:
|
|
196
|
+
flavour: effectiveFlavour,
|
|
207
197
|
ok: true,
|
|
208
198
|
status: "ok",
|
|
209
199
|
data: config
|
|
@@ -214,7 +204,7 @@ async function analyse(params) {
|
|
|
214
204
|
results.push({
|
|
215
205
|
project: module.name,
|
|
216
206
|
target,
|
|
217
|
-
flavour:
|
|
207
|
+
flavour: effectiveFlavour,
|
|
218
208
|
ok,
|
|
219
209
|
status: ok ? "ok" : "failed",
|
|
220
210
|
data: status
|
|
@@ -226,7 +216,7 @@ async function analyse(params) {
|
|
|
226
216
|
results.push({
|
|
227
217
|
project: module.name,
|
|
228
218
|
target,
|
|
229
|
-
flavour:
|
|
219
|
+
flavour: effectiveFlavour,
|
|
230
220
|
ok: false,
|
|
231
221
|
status: "failed",
|
|
232
222
|
data: { error: message }
|
|
@@ -529,8 +519,13 @@ async function runVerb() {
|
|
|
529
519
|
maxDiagnostics,
|
|
530
520
|
ci: Boolean(opts.ci),
|
|
531
521
|
fix: Boolean(opts.fix),
|
|
532
|
-
|
|
533
|
-
|
|
522
|
+
// Per-module progress is a HUMAN affordance: it tells someone staring at a slow sweep
|
|
523
|
+
// that it is alive. On this monorepo's 441 projects it is 441 lines that bury the four
|
|
524
|
+
// that matter, and it is pure noise in a captured log. So emit it only for an
|
|
525
|
+
// interactive terminal; a pipe, CI, or a generated trace gets the results and the
|
|
526
|
+
// one-line summary, which is all any of them can use.
|
|
527
|
+
onProgress: process.stderr.isTTY ? (done, total, name) => process.stderr.write(err.dim(` [${done}/${total}] ${name}
|
|
528
|
+
`)) : void 0
|
|
534
529
|
});
|
|
535
530
|
const summary = generateSummaries(results, targets);
|
|
536
531
|
if (opts.json && verb !== "run") {
|
|
@@ -613,18 +608,24 @@ sentinel (${type}): ${asMessage(err)}
|
|
|
613
608
|
}
|
|
614
609
|
return worst;
|
|
615
610
|
}
|
|
611
|
+
async function exitWithoutTruncating(code) {
|
|
612
|
+
process.exitCode = code;
|
|
613
|
+
const stream = process.stdout;
|
|
614
|
+
if (stream.write("")) return;
|
|
615
|
+
await new Promise((resolve2) => stream.once("drain", () => resolve2()));
|
|
616
|
+
}
|
|
616
617
|
async function main() {
|
|
617
618
|
if (verb === "migrate") {
|
|
618
619
|
program.error("--migrate is planned and not available yet; use --init to set a module up.");
|
|
619
620
|
}
|
|
620
621
|
const runSelectedVerb = verb === "init" ? runInit : runVerb;
|
|
621
622
|
const exitCode = await runSelectedVerb();
|
|
622
|
-
|
|
623
|
+
await exitWithoutTruncating(exitCode);
|
|
623
624
|
}
|
|
624
625
|
main().catch((error) => {
|
|
625
626
|
process.stderr.write(`
|
|
626
627
|
sentinel: ${asMessage(error)}
|
|
627
628
|
`);
|
|
628
|
-
|
|
629
|
+
void exitWithoutTruncating(1);
|
|
629
630
|
});
|
|
630
631
|
//# sourceMappingURL=sentinel.js.map
|
|
@@ -62,6 +62,20 @@ var BaseAdapter = class {
|
|
|
62
62
|
}
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
+
// src/core/domain.ts
|
|
66
|
+
var VERBS = ["run", "inspect", "init", "migrate", "report", "status"];
|
|
67
|
+
var TARGETS = [
|
|
68
|
+
"lint",
|
|
69
|
+
"format",
|
|
70
|
+
"typescript",
|
|
71
|
+
"build",
|
|
72
|
+
"test",
|
|
73
|
+
"static-analysis",
|
|
74
|
+
"runtime-analysis",
|
|
75
|
+
"arch"
|
|
76
|
+
];
|
|
77
|
+
var FLAVOURS = ["react", "nest", "svelte", "node"];
|
|
78
|
+
|
|
65
79
|
// src/shared/color.ts
|
|
66
80
|
import { styleText } from "util";
|
|
67
81
|
function palette(stream) {
|
|
@@ -125,8 +139,12 @@ function readNxProjectName(dir) {
|
|
|
125
139
|
|
|
126
140
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
127
141
|
import { spawnSync } from "child_process";
|
|
128
|
-
import { existsSync as
|
|
129
|
-
import { basename, join as
|
|
142
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
143
|
+
import { basename, join as join6 } from "path";
|
|
144
|
+
|
|
145
|
+
// src/core/config/flavour-evidence.ts
|
|
146
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
147
|
+
import { join as join2 } from "path";
|
|
130
148
|
|
|
131
149
|
// src/shared/jsonc.ts
|
|
132
150
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
@@ -140,14 +158,92 @@ function parseJsonc(text, source = "config") {
|
|
|
140
158
|
return value;
|
|
141
159
|
}
|
|
142
160
|
|
|
161
|
+
// src/core/config/flavour-evidence.ts
|
|
162
|
+
var PATH_SIGNALS = [
|
|
163
|
+
{
|
|
164
|
+
flavour: "nest",
|
|
165
|
+
pattern: /(^|\/)apps\/nest\//,
|
|
166
|
+
evidence: "it lives under apps/nest"
|
|
167
|
+
}
|
|
168
|
+
];
|
|
169
|
+
var DEPENDENCY_SIGNALS = [
|
|
170
|
+
{ flavour: "react", pattern: /^(react|react-dom|@types\/react)$|^eslint-plugin-react/ },
|
|
171
|
+
{ flavour: "svelte", pattern: /^svelte$|^@sveltejs\// },
|
|
172
|
+
{ flavour: "nest", pattern: /^@nestjs\// }
|
|
173
|
+
];
|
|
174
|
+
function dependencyNames(cwd) {
|
|
175
|
+
const path = join2(cwd, "package.json");
|
|
176
|
+
if (!existsSync2(path)) return [];
|
|
177
|
+
try {
|
|
178
|
+
const manifest = parseJsonc(
|
|
179
|
+
readFileSync2(path, "utf8"),
|
|
180
|
+
path
|
|
181
|
+
);
|
|
182
|
+
return [
|
|
183
|
+
...Object.keys(manifest.dependencies ?? {}),
|
|
184
|
+
...Object.keys(manifest.devDependencies ?? {})
|
|
185
|
+
];
|
|
186
|
+
} catch {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function declaresJsx(cwd) {
|
|
191
|
+
let entries;
|
|
192
|
+
try {
|
|
193
|
+
entries = readdirSync(cwd).filter(
|
|
194
|
+
(name) => name.startsWith("tsconfig") && name.endsWith(".json")
|
|
195
|
+
);
|
|
196
|
+
} catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
for (const name of entries) {
|
|
200
|
+
try {
|
|
201
|
+
const config = parseJsonc(
|
|
202
|
+
readFileSync2(join2(cwd, name), "utf8"),
|
|
203
|
+
name
|
|
204
|
+
);
|
|
205
|
+
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
206
|
+
} catch {
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
function flavourSignals(cwd) {
|
|
212
|
+
const signals = [];
|
|
213
|
+
const dependencies = dependencyNames(cwd);
|
|
214
|
+
for (const { flavour, pattern } of DEPENDENCY_SIGNALS) {
|
|
215
|
+
const hit = dependencies.find((name) => pattern.test(name));
|
|
216
|
+
if (hit) signals.push({ flavour, evidence: `it depends on "${hit}"` });
|
|
217
|
+
}
|
|
218
|
+
if (declaresJsx(cwd) && !signals.some((signal) => signal.flavour === "react")) {
|
|
219
|
+
signals.push({ flavour: "react", evidence: 'its tsconfig sets "jsx"' });
|
|
220
|
+
}
|
|
221
|
+
const location = cwd.replaceAll("\\", "/");
|
|
222
|
+
for (const { flavour, pattern, evidence } of PATH_SIGNALS) {
|
|
223
|
+
if (pattern.test(location) && !signals.some((signal) => signal.flavour === flavour)) {
|
|
224
|
+
signals.push({ flavour, evidence });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return signals;
|
|
228
|
+
}
|
|
229
|
+
function flavourContradiction(declared, cwd) {
|
|
230
|
+
const signals = flavourSignals(cwd);
|
|
231
|
+
if (signals.length === 0) return void 0;
|
|
232
|
+
if (signals.some((signal) => signal.flavour === declared)) return void 0;
|
|
233
|
+
const [first] = signals;
|
|
234
|
+
if (!first) return void 0;
|
|
235
|
+
const evidence = signals.map((signal) => signal.evidence).join(", and ");
|
|
236
|
+
return `--flavour ${declared} does not match this module: ${evidence}, which makes it "${first.flavour}". Nothing was written. Re-run with --flavour ${first.flavour}, or adopt from the module you actually meant.`;
|
|
237
|
+
}
|
|
238
|
+
|
|
143
239
|
// src/shared/resolve-bin.ts
|
|
144
|
-
import { existsSync as
|
|
145
|
-
import { dirname as dirname2, join as
|
|
240
|
+
import { existsSync as existsSync3 } from "fs";
|
|
241
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
146
242
|
function resolveBin(fromDir, name) {
|
|
147
243
|
let dir = fromDir;
|
|
148
244
|
for (; ; ) {
|
|
149
|
-
const candidate =
|
|
150
|
-
if (
|
|
245
|
+
const candidate = join3(dir, "node_modules", ".bin", name);
|
|
246
|
+
if (existsSync3(candidate)) return candidate;
|
|
151
247
|
const parent = dirname2(dir);
|
|
152
248
|
if (parent === dir) return void 0;
|
|
153
249
|
dir = parent;
|
|
@@ -184,18 +280,18 @@ function hasShippedPreset(flavour) {
|
|
|
184
280
|
}
|
|
185
281
|
|
|
186
282
|
// src/roles/typescript/read-adoption.ts
|
|
187
|
-
import { existsSync as
|
|
188
|
-
import { join as
|
|
283
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
284
|
+
import { join as join5 } from "path";
|
|
189
285
|
|
|
190
286
|
// src/roles/typescript/resolve-tsconfig-target.ts
|
|
191
|
-
import { existsSync as
|
|
192
|
-
import { join as
|
|
287
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
288
|
+
import { join as join4 } from "path";
|
|
193
289
|
var TARGET_EXTENDS_MARKERS = ["tsconfig.base.json", "@hublo/sentinel/tsconfig/"];
|
|
194
290
|
var CANDIDATES = ["tsconfig.app.json", "tsconfig.json"];
|
|
195
291
|
function readExtends(absolutePath) {
|
|
196
292
|
let parsed;
|
|
197
293
|
try {
|
|
198
|
-
parsed = parseJsonc(
|
|
294
|
+
parsed = parseJsonc(readFileSync3(absolutePath, "utf8"), absolutePath);
|
|
199
295
|
} catch {
|
|
200
296
|
return [];
|
|
201
297
|
}
|
|
@@ -207,11 +303,15 @@ function readExtends(absolutePath) {
|
|
|
207
303
|
}
|
|
208
304
|
function resolveTsconfigTarget(moduleDir) {
|
|
209
305
|
let existing;
|
|
306
|
+
let existingExtendsSomething = false;
|
|
210
307
|
for (const candidate of CANDIDATES) {
|
|
211
|
-
const absolutePath =
|
|
212
|
-
if (!
|
|
213
|
-
existing ??= candidate;
|
|
308
|
+
const absolutePath = join4(moduleDir, candidate);
|
|
309
|
+
if (!existsSync4(absolutePath)) continue;
|
|
214
310
|
const extendsValues = readExtends(absolutePath);
|
|
311
|
+
if (existing === void 0) {
|
|
312
|
+
existing = candidate;
|
|
313
|
+
existingExtendsSomething = extendsValues.length > 0;
|
|
314
|
+
}
|
|
215
315
|
const extendsBase = extendsValues.some(
|
|
216
316
|
(value) => TARGET_EXTENDS_MARKERS.some((marker) => value.includes(marker))
|
|
217
317
|
);
|
|
@@ -219,7 +319,9 @@ function resolveTsconfigTarget(moduleDir) {
|
|
|
219
319
|
return { path: candidate, reason: "extends-base" };
|
|
220
320
|
}
|
|
221
321
|
}
|
|
222
|
-
if (existing)
|
|
322
|
+
if (existing) {
|
|
323
|
+
return { path: existing, reason: existingExtendsSomething ? "other-chain" : "no-extends" };
|
|
324
|
+
}
|
|
223
325
|
return { path: "tsconfig.json", reason: "none" };
|
|
224
326
|
}
|
|
225
327
|
|
|
@@ -240,12 +342,12 @@ var NOT_ADOPTED = (configFile) => ({
|
|
|
240
342
|
});
|
|
241
343
|
function readTsconfigAdoption(cwd) {
|
|
242
344
|
const target = resolveTsconfigTarget(cwd);
|
|
243
|
-
if (target.reason === "none" || !
|
|
345
|
+
if (target.reason === "none" || !existsSync5(join5(cwd, target.path))) {
|
|
244
346
|
return NOT_ADOPTED(target.reason === "none" ? null : target.path);
|
|
245
347
|
}
|
|
246
348
|
let parsed;
|
|
247
349
|
try {
|
|
248
|
-
parsed = parseJsonc(
|
|
350
|
+
parsed = parseJsonc(readFileSync4(join5(cwd, target.path), "utf8"), target.path);
|
|
249
351
|
} catch {
|
|
250
352
|
return NOT_ADOPTED(target.path);
|
|
251
353
|
}
|
|
@@ -294,6 +396,20 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
294
396
|
appliesTo(_flavour) {
|
|
295
397
|
return true;
|
|
296
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* The flavour read from the committed `extends` chain
|
|
401
|
+
* (`@hublo/sentinel/tsconfig/nest` -> `nest`), or undefined when the module has not
|
|
402
|
+
* adopted a preset, so the engine falls back to dependency detection.
|
|
403
|
+
*
|
|
404
|
+
* This is the same detection-free read `--status` uses, and it is why an adopted React app
|
|
405
|
+
* reports `react` even in a monorepo that hoists `react` to the root.
|
|
406
|
+
*/
|
|
407
|
+
declaredFlavour(cwd) {
|
|
408
|
+
const { preset } = readTsconfigAdoption(cwd);
|
|
409
|
+
if (!preset) return void 0;
|
|
410
|
+
const name = preset.slice(preset.lastIndexOf("/") + 1);
|
|
411
|
+
return FLAVOURS.includes(name) ? name : void 0;
|
|
412
|
+
}
|
|
297
413
|
/**
|
|
298
414
|
* Plan `--init`: make the module extend the sentinel preset with a THIN,
|
|
299
415
|
* conformant stub, route type-checking through the CLI, and pin the
|
|
@@ -314,6 +430,17 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
314
430
|
blocked: `no TypeScript preset for flavour "${context.flavour}" yet (shipped: ${SHIPPED_FLAVOURS.join(", ")}). Nothing was written; this module cannot adopt the TypeScript preset until that flavour ships.`
|
|
315
431
|
};
|
|
316
432
|
}
|
|
433
|
+
const current = this.declaredFlavour(context.cwd);
|
|
434
|
+
if (current !== void 0 && current !== context.flavour) {
|
|
435
|
+
return {
|
|
436
|
+
operations: [],
|
|
437
|
+
blocked: `this module is already adopted as "${current}", and --flavour says "${context.flavour}". Nothing was written. If the change is deliberate, remove the sentinel preset from the tsconfig \`extends\` chain and re-run; otherwise re-run with --flavour ${current}.`
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const contradiction = flavourContradiction(context.flavour, context.cwd);
|
|
441
|
+
if (contradiction !== void 0) {
|
|
442
|
+
return { operations: [], blocked: contradiction };
|
|
443
|
+
}
|
|
317
444
|
const target = resolveTsconfigTarget(context.cwd);
|
|
318
445
|
const preset = `@hublo/sentinel/tsconfig/${context.flavour}`;
|
|
319
446
|
const addScript = this.packageJsonOperation(context.cwd);
|
|
@@ -322,6 +449,12 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
322
449
|
operations: [],
|
|
323
450
|
blocked: `${target.path} extends a config chain sentinel does not handle, so nothing was written. Point it at the workspace base (or a plain config) and re-run.`
|
|
324
451
|
};
|
|
452
|
+
if (target.reason === "no-extends") {
|
|
453
|
+
return {
|
|
454
|
+
operations: [],
|
|
455
|
+
blocked: `${target.path} extends nothing, so it defines its own complete set of compilerOptions and sentinel will not silently take ownership of them. Nothing was written. Make it extend the workspace base and re-run, and --init will then move the shared options into the preset.`
|
|
456
|
+
};
|
|
457
|
+
}
|
|
325
458
|
}
|
|
326
459
|
if (target.reason === "none") {
|
|
327
460
|
const contents = JSON.stringify({ extends: preset, include: ["src"] }, null, 2) + "\n";
|
|
@@ -331,7 +464,7 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
331
464
|
};
|
|
332
465
|
}
|
|
333
466
|
const existing = parseJsonc(
|
|
334
|
-
|
|
467
|
+
readFileSync5(join6(context.cwd, target.path), "utf8"),
|
|
335
468
|
target.path
|
|
336
469
|
);
|
|
337
470
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
@@ -366,7 +499,7 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
366
499
|
packageJsonOperation(cwd) {
|
|
367
500
|
const own = readOwnPackage();
|
|
368
501
|
const devDependencies = { [own.name]: own.version };
|
|
369
|
-
if (
|
|
502
|
+
if (existsSync6(join6(cwd, "package.json"))) {
|
|
370
503
|
return {
|
|
371
504
|
kind: "merge-json",
|
|
372
505
|
path: "package.json",
|
|
@@ -414,7 +547,7 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
414
547
|
* check.
|
|
415
548
|
*/
|
|
416
549
|
typecheckTarget(cwd) {
|
|
417
|
-
if (
|
|
550
|
+
if (existsSync6(join6(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
418
551
|
const target = resolveTsconfigTarget(cwd);
|
|
419
552
|
return target.reason === "none" ? null : target.path;
|
|
420
553
|
}
|
|
@@ -598,7 +731,7 @@ function diffLines(before, after) {
|
|
|
598
731
|
}
|
|
599
732
|
|
|
600
733
|
// src/core/apply-plan.ts
|
|
601
|
-
import { existsSync as
|
|
734
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, renameSync, writeFileSync } from "fs";
|
|
602
735
|
import { resolve as resolve2, sep } from "path";
|
|
603
736
|
import { applyEdits, modify } from "jsonc-parser";
|
|
604
737
|
|
|
@@ -617,7 +750,7 @@ function resolveWithinRoot(cwd, relativePath) {
|
|
|
617
750
|
return absolutePath;
|
|
618
751
|
}
|
|
619
752
|
function readIfExists(absolutePath) {
|
|
620
|
-
return
|
|
753
|
+
return existsSync7(absolutePath) ? readFileSync6(absolutePath, "utf8") : void 0;
|
|
621
754
|
}
|
|
622
755
|
function* leaves(value, prefix = []) {
|
|
623
756
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -745,8 +878,9 @@ async function dispatch(opts) {
|
|
|
745
878
|
if (opts.verb !== "init") {
|
|
746
879
|
throw new Error(`dispatch handles --init only; --${opts.verb} routes through analyse()`);
|
|
747
880
|
}
|
|
748
|
-
const
|
|
749
|
-
const adapter = resolve(opts.target,
|
|
881
|
+
const detected = resolveFlavour(opts);
|
|
882
|
+
const adapter = resolve(opts.target, detected, opts.runner);
|
|
883
|
+
const flavour = opts.flavour ?? adapter.declaredFlavour?.(opts.cwd) ?? detected;
|
|
750
884
|
const context = { cwd: opts.cwd, flavour };
|
|
751
885
|
const plan = await adapter.plan(context);
|
|
752
886
|
if (plan.blocked) {
|
|
@@ -772,6 +906,9 @@ export {
|
|
|
772
906
|
availableTargets,
|
|
773
907
|
resolve,
|
|
774
908
|
BaseAdapter,
|
|
909
|
+
VERBS,
|
|
910
|
+
TARGETS,
|
|
911
|
+
FLAVOURS,
|
|
775
912
|
palette,
|
|
776
913
|
readOwnPackage,
|
|
777
914
|
readOwnVersion,
|
|
@@ -783,4 +920,4 @@ export {
|
|
|
783
920
|
detectFramework,
|
|
784
921
|
dispatch
|
|
785
922
|
};
|
|
786
|
-
//# sourceMappingURL=chunk-
|
|
923
|
+
//# sourceMappingURL=chunk-BN3TB6TF.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -169,6 +169,17 @@ interface Adapter {
|
|
|
169
169
|
* adapters can share a (target, runner) if they specialise different flavours.
|
|
170
170
|
*/
|
|
171
171
|
appliesTo(flavour: Flavour): boolean;
|
|
172
|
+
/**
|
|
173
|
+
* The flavour this module has COMMITTED to, read from its own config, or undefined when
|
|
174
|
+
* it has not adopted a preset yet. Optional: an adapter implements it only when its tool's
|
|
175
|
+
* config states the flavour unambiguously.
|
|
176
|
+
*
|
|
177
|
+
* The engine prefers this over dependency detection, because in a monorepo dependencies are
|
|
178
|
+
* hoisted to the root: `host-admin` declares no `react`, so detection honestly answers
|
|
179
|
+
* `node` while the committed `extends` says `.../tsconfig/react`. Reporting the detected
|
|
180
|
+
* value there would show every adopted module as `node`, which is simply untrue.
|
|
181
|
+
*/
|
|
182
|
+
declaredFlavour?(cwd: string): Flavour | undefined;
|
|
172
183
|
/**
|
|
173
184
|
* Plan what `--init` should write, as declarative operations (see
|
|
174
185
|
* `FileOperation`). Given a normalized `UpdateContext`, the adapter may READ the
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hublo/sentinel",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "One CLI that guards code health across Hublo repos: shared lint/typescript/build/test presets, static & dynamic analysis, and architecture checks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|