@hublo/sentinel 0.1.0-alpha.8 → 0.1.0-alpha.9
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 +16 -2
- package/dist/bin/sentinel.js +174 -25
- package/dist/bin/sentinel.js.map +1 -1
- package/dist/{chunk-UG74KTIU.js → chunk-NQ7GNHFT.js} +110 -58
- package/dist/chunk-NQ7GNHFT.js.map +1 -0
- package/dist/index.d.ts +11 -10
- package/dist/index.js +1 -1
- package/package.json +4 -6
- package/dist/chunk-UG74KTIU.js.map +0 -1
package/README.md
CHANGED
|
@@ -43,6 +43,8 @@ A large monorepo accumulates:
|
|
|
43
43
|
|
|
44
44
|
sentinel writes **standard config files** into a project (each just `extends` a sentinel preset) and runs the checks. Your editor and the tools read those **normal files natively**, they never call sentinel at runtime, so nothing is coupled to it or brittle.
|
|
45
45
|
|
|
46
|
+
> **Shipped today:** only the **TypeScript** tool, so `--update` writes the `tsconfig` stub, and `--run`/`--report`/`--status` work for `--typescript`. The `eslint.config.js` / `--lint` / `--test` snippets below illustrate the end state; those subpaths (`@hublo/sentinel/lint/*`, …) land with their tool ticket.
|
|
47
|
+
|
|
46
48
|
**Step 1 — put a module on sentinel** (once per module, by a dev; the files are committed):
|
|
47
49
|
|
|
48
50
|
```bash
|
|
@@ -99,6 +101,18 @@ And the app's `package.json` scripts route every check through the one CLI (run
|
|
|
99
101
|
|
|
100
102
|
The per-tool knowledge (eslint → `eslint.config.js`, tsc → `tsconfig`, …) lives **inside sentinel as an adapter**, swappable centrally, but never a runtime dependency of the project.
|
|
101
103
|
|
|
104
|
+
## Requirements & installing
|
|
105
|
+
|
|
106
|
+
**Node.** sentinel needs **Node >= 20.12** (its coloured output uses `util.styleText`, added in 20.12). It fails fast with a clear message on an older runtime rather than crashing. If a project runs on an older Node (e.g. a legacy app on Node 10), run sentinel with a modern Node via `fnm`/`nvm`; you do not need to change the project's own Node.
|
|
107
|
+
|
|
108
|
+
**Try it without installing.** A one-off run needs no auth and touches nothing:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pnpm dlx @hublo/sentinel@<exact-version> --inspect --typescript --module <name>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Installing a pre-release (`minimumReleaseAge`).** The monorepo enforces a 3-day `minimumReleaseAge` supply-chain gate (a freshly published version cannot be installed until it has aged 3 days). A brand-new `alpha` therefore cannot be added yet, so while testing pre-releases you either exclude the package (`pnpm-workspace.yaml` → `minimumReleaseAgeExclude`) or install with `--config.minimumReleaseAge=0`. This is a deliberate protection, not a bug: **always pin the exact version** (`@hublo/sentinel@0.1.0-alpha.9`) rather than `@latest`, so a run is reproducible and the gate stays meaningful.
|
|
115
|
+
|
|
102
116
|
## Architecture: `target → runner → flavour`
|
|
103
117
|
|
|
104
118
|
Every check is described by three layers:
|
|
@@ -260,6 +274,7 @@ OPTIONS --module <name> from the root: scope to one module
|
|
|
260
274
|
--ci from the root: affected only; non-zero exit on failure
|
|
261
275
|
--fix auto-fix where applicable
|
|
262
276
|
--dry-run preview a --update without writing
|
|
277
|
+
--json machine-readable output (report / inspect / status / --dry-run)
|
|
263
278
|
|
|
264
279
|
EXAMPLES sentinel --run --typescript # in a module → that module
|
|
265
280
|
sentinel --report --typescript --module bff-admin # from root → one module
|
|
@@ -309,8 +324,7 @@ src/
|
|
|
309
324
|
runners/ # one runner per sub-tool (duplication, complexity, ...)
|
|
310
325
|
shared/ # reusable utils (package-json, deep-merge, text)
|
|
311
326
|
tests/ # unit tests + tests/e2e (runs the built dist binary)
|
|
312
|
-
.
|
|
313
|
-
.github/workflows/ # ci.yml (PR checks) + release.yml (changesets publish)
|
|
327
|
+
.github/workflows/ # ci.yml (PR checks) + publish.yml (manual, version-input publish)
|
|
314
328
|
```
|
|
315
329
|
|
|
316
330
|
Subpath exports (in `package.json`) expose presets to consumers. Shipped today:
|
package/dist/bin/sentinel.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
availableTargets,
|
|
4
|
+
describeFramework,
|
|
4
5
|
dispatch,
|
|
5
6
|
palette,
|
|
6
7
|
readNxProjectName,
|
|
@@ -9,7 +10,7 @@ import {
|
|
|
9
10
|
registerAdapters,
|
|
10
11
|
resolve,
|
|
11
12
|
resolveBin
|
|
12
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-NQ7GNHFT.js";
|
|
13
14
|
|
|
14
15
|
// bin/sentinel.ts
|
|
15
16
|
import { program } from "commander";
|
|
@@ -124,19 +125,45 @@ async function analyse(params) {
|
|
|
124
125
|
let worstCode = 0;
|
|
125
126
|
let done = 0;
|
|
126
127
|
for (const module of params.modules) {
|
|
127
|
-
|
|
128
|
+
let flavour2;
|
|
129
|
+
if (params.flavour) {
|
|
130
|
+
flavour2 = params.flavour;
|
|
131
|
+
} else {
|
|
132
|
+
const detection = describeFramework(readProjectPackageJson(module.root));
|
|
133
|
+
flavour2 = detection.flavour;
|
|
134
|
+
if (detection.ambiguous) {
|
|
135
|
+
const warn = palette(process.stderr);
|
|
136
|
+
process.stderr.write(
|
|
137
|
+
warn.warn(
|
|
138
|
+
`sentinel: ${module.name}: flavour is ambiguous, detected "${flavour2}" (from ${detection.source}), also found ${detection.conflicts.join(", ")}. Pass --flavour to be explicit.`
|
|
139
|
+
) + "\n"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
128
143
|
const ctx = {
|
|
129
144
|
module: module.name,
|
|
130
145
|
cwd: module.root,
|
|
131
146
|
flavour: flavour2,
|
|
132
147
|
ci: params.ci,
|
|
133
|
-
fix: params.fix
|
|
148
|
+
fix: params.fix,
|
|
149
|
+
maxDiagnostics: params.maxDiagnostics
|
|
134
150
|
};
|
|
135
151
|
for (const target of params.targets) {
|
|
136
152
|
let adapter;
|
|
137
153
|
try {
|
|
138
154
|
adapter = resolve(target, flavour2, params.runner);
|
|
139
|
-
} catch {
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (params.targetsExplicit) {
|
|
157
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
158
|
+
results.push({
|
|
159
|
+
project: module.name,
|
|
160
|
+
target,
|
|
161
|
+
flavour: flavour2,
|
|
162
|
+
ok: false,
|
|
163
|
+
status: "unsupported",
|
|
164
|
+
data: { reason }
|
|
165
|
+
});
|
|
166
|
+
}
|
|
140
167
|
continue;
|
|
141
168
|
}
|
|
142
169
|
try {
|
|
@@ -150,7 +177,14 @@ async function analyse(params) {
|
|
|
150
177
|
);
|
|
151
178
|
}
|
|
152
179
|
const result = await adapter.run(ctx);
|
|
153
|
-
results.push({
|
|
180
|
+
results.push({
|
|
181
|
+
project: module.name,
|
|
182
|
+
target,
|
|
183
|
+
flavour: flavour2,
|
|
184
|
+
ok: result.ok,
|
|
185
|
+
status: result.ok ? "ok" : "failed",
|
|
186
|
+
data: {}
|
|
187
|
+
});
|
|
154
188
|
worstCode = Math.max(worstCode, result.code);
|
|
155
189
|
} else if (params.verb === "report") {
|
|
156
190
|
const result = await adapter.report(ctx);
|
|
@@ -159,21 +193,43 @@ async function analyse(params) {
|
|
|
159
193
|
target,
|
|
160
194
|
flavour: flavour2,
|
|
161
195
|
ok: result.ok,
|
|
196
|
+
status: result.ok ? "ok" : "failed",
|
|
162
197
|
data: result.metrics ?? {}
|
|
163
198
|
});
|
|
164
199
|
worstCode = Math.max(worstCode, result.code);
|
|
165
200
|
} else if (params.verb === "inspect") {
|
|
166
201
|
const config = await adapter.inspect(ctx);
|
|
167
|
-
results.push({
|
|
202
|
+
results.push({
|
|
203
|
+
project: module.name,
|
|
204
|
+
target,
|
|
205
|
+
flavour: flavour2,
|
|
206
|
+
ok: true,
|
|
207
|
+
status: "ok",
|
|
208
|
+
data: config
|
|
209
|
+
});
|
|
168
210
|
} else {
|
|
169
211
|
const status = await adapter.status(ctx);
|
|
170
212
|
const ok = !status.adopted || status.conformant;
|
|
171
|
-
results.push({
|
|
213
|
+
results.push({
|
|
214
|
+
project: module.name,
|
|
215
|
+
target,
|
|
216
|
+
flavour: flavour2,
|
|
217
|
+
ok,
|
|
218
|
+
status: ok ? "ok" : "failed",
|
|
219
|
+
data: status
|
|
220
|
+
});
|
|
172
221
|
if (!ok) worstCode = Math.max(worstCode, 1);
|
|
173
222
|
}
|
|
174
223
|
} catch (error) {
|
|
175
224
|
const message = error instanceof Error ? error.message : String(error);
|
|
176
|
-
results.push({
|
|
225
|
+
results.push({
|
|
226
|
+
project: module.name,
|
|
227
|
+
target,
|
|
228
|
+
flavour: flavour2,
|
|
229
|
+
ok: false,
|
|
230
|
+
status: "failed",
|
|
231
|
+
data: { error: message }
|
|
232
|
+
});
|
|
177
233
|
worstCode = Math.max(worstCode, 1);
|
|
178
234
|
}
|
|
179
235
|
}
|
|
@@ -182,16 +238,34 @@ async function analyse(params) {
|
|
|
182
238
|
return { results, worstCode };
|
|
183
239
|
}
|
|
184
240
|
function generateSummary(result) {
|
|
185
|
-
const { project, target, flavour: flavour2, ok, data } = result;
|
|
241
|
+
const { project, target, flavour: flavour2, ok, status, data } = result;
|
|
186
242
|
const details = data && typeof data === "object" ? data : { value: data };
|
|
187
|
-
return { project, target, flavour: flavour2, ok, ...details };
|
|
243
|
+
return { project, target, flavour: flavour2, ok, status, ...details };
|
|
188
244
|
}
|
|
189
|
-
function generateSummaries(results) {
|
|
190
|
-
|
|
245
|
+
function generateSummaries(results, requestedTargets) {
|
|
246
|
+
const available = new Set(availableTargets());
|
|
247
|
+
return {
|
|
248
|
+
schemaVersion: 2,
|
|
249
|
+
executed: requestedTargets.filter((t) => available.has(t)),
|
|
250
|
+
skipped: requestedTargets.filter((t) => !available.has(t)),
|
|
251
|
+
results: results.map(generateSummary)
|
|
252
|
+
};
|
|
191
253
|
}
|
|
192
254
|
|
|
193
255
|
// src/core/render.ts
|
|
194
|
-
var HEAD_KEYS = /* @__PURE__ */ new Set([
|
|
256
|
+
var HEAD_KEYS = /* @__PURE__ */ new Set([
|
|
257
|
+
"project",
|
|
258
|
+
"module",
|
|
259
|
+
"target",
|
|
260
|
+
"flavour",
|
|
261
|
+
"ok",
|
|
262
|
+
"status",
|
|
263
|
+
"reason",
|
|
264
|
+
// The structured diagnostics are for `--json` consumers; the human row stays the concise
|
|
265
|
+
// `errors=N` line rather than dumping every diagnostic inline.
|
|
266
|
+
"diagnostics",
|
|
267
|
+
"diagnosticsTruncated"
|
|
268
|
+
]);
|
|
195
269
|
function isDeferredRules(value) {
|
|
196
270
|
return Array.isArray(value) && value.every((entry) => typeof entry === "object" && entry !== null && "rule" in entry);
|
|
197
271
|
}
|
|
@@ -199,9 +273,16 @@ function renderValue(key, value, p) {
|
|
|
199
273
|
if ((key === "errors" || key === "implicitAny") && typeof value === "number") {
|
|
200
274
|
return value > 0 ? p.fail(String(value)) : p.ok(String(value));
|
|
201
275
|
}
|
|
276
|
+
if (key === "implicitAny" && value === "deferred") return p.warn("deferred");
|
|
202
277
|
return typeof value === "string" ? value : JSON.stringify(value);
|
|
203
278
|
}
|
|
279
|
+
function renderUnsupportedRow(item, p) {
|
|
280
|
+
const reason = typeof item.reason === "string" ? ` ${p.dim(`(${item.reason})`)}` : "";
|
|
281
|
+
const head = ` ${p.dim("\xB7")} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`;
|
|
282
|
+
return `${head} ${p.dim("\u2014")} ${p.warn("unsupported")}${reason}`;
|
|
283
|
+
}
|
|
204
284
|
function renderSummary(item, p) {
|
|
285
|
+
if (item.status === "unsupported") return renderUnsupportedRow(item, p);
|
|
205
286
|
const ok = item.ok === true;
|
|
206
287
|
const mark = ok ? p.ok("\u2713") : p.fail("\u2717");
|
|
207
288
|
const head = ` ${mark} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`;
|
|
@@ -218,10 +299,11 @@ function renderSummary(item, p) {
|
|
|
218
299
|
return lines.join("\n");
|
|
219
300
|
}
|
|
220
301
|
function statusCoverage(items) {
|
|
221
|
-
const
|
|
302
|
+
const relevant = items.filter((i) => i.status !== "unsupported");
|
|
303
|
+
const adopted = relevant.filter((i) => i.adopted === true);
|
|
222
304
|
const conformant = adopted.filter((i) => i.conformant === true);
|
|
223
305
|
return {
|
|
224
|
-
total:
|
|
306
|
+
total: relevant.length,
|
|
225
307
|
adopted: adopted.length,
|
|
226
308
|
conformant: conformant.length,
|
|
227
309
|
drifted: adopted.length - conformant.length
|
|
@@ -231,6 +313,7 @@ function presetShort(preset) {
|
|
|
231
313
|
return typeof preset === "string" ? preset.replace("@hublo/sentinel/tsconfig/", "") : "?";
|
|
232
314
|
}
|
|
233
315
|
function renderStatusRow(item, p) {
|
|
316
|
+
if (item.status === "unsupported") return renderUnsupportedRow(item, p);
|
|
234
317
|
const adopted = item.adopted === true;
|
|
235
318
|
const conformant = item.conformant === true;
|
|
236
319
|
const mark = !adopted ? p.dim("\xB7") : conformant ? p.ok("\u2713") : p.fail("\u2717");
|
|
@@ -248,7 +331,31 @@ function renderStatusSummary(items, p) {
|
|
|
248
331
|
return ` ${p.strong("coverage:")} ${c.adopted}/${c.total} adopted ${p.dim("\xB7")} ${c.conformant}/${c.adopted} conformant${drift}`;
|
|
249
332
|
}
|
|
250
333
|
|
|
334
|
+
// src/shared/node-version.ts
|
|
335
|
+
var MIN_NODE = "20.12.0";
|
|
336
|
+
function parts(version) {
|
|
337
|
+
const [major = 0, minor = 0, patch = 0] = version.replace(/^v/, "").split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
338
|
+
return [major, minor, patch];
|
|
339
|
+
}
|
|
340
|
+
function checkNodeVersion(current, min = MIN_NODE) {
|
|
341
|
+
const [cMajor, cMinor, cPatch] = parts(current);
|
|
342
|
+
const [mMajor, mMinor, mPatch] = parts(min);
|
|
343
|
+
const ok = cMajor > mMajor || cMajor === mMajor && cMinor > mMinor || cMajor === mMajor && cMinor === mMinor && cPatch >= mPatch;
|
|
344
|
+
if (ok) return { ok: true };
|
|
345
|
+
return {
|
|
346
|
+
ok: false,
|
|
347
|
+
message: `sentinel requires Node >= ${min}, but you are on ${current}. Switch with fnm/nvm (e.g. \`fnm use ${mMajor}\`) and re-run.`
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
251
351
|
// bin/sentinel.ts
|
|
352
|
+
var nodeCheck = checkNodeVersion(process.versions.node);
|
|
353
|
+
if (!nodeCheck.ok) {
|
|
354
|
+
process.stderr.write(`${nodeCheck.message}
|
|
355
|
+
`);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
registerAdapters();
|
|
252
359
|
program.name("sentinel").description("One CLI that guards code health: presets, analysis, and arch checks.").version(readOwnVersion()).configureHelp({ sortOptions: false }).showSuggestionAfterError(true).showHelpAfterError('(run "sentinel --help" for usage)').addHelpText(
|
|
253
360
|
"before",
|
|
254
361
|
[
|
|
@@ -262,7 +369,11 @@ program.name("sentinel").description("One CLI that guards code health: presets,
|
|
|
262
369
|
).option("--run", "execute the target tool").option("--inspect", "show the resolved configuration").option("--update", "generate/apply the config stubs").option("--report", "metrics and health report").option("--status", "adoption + conformity across modules (coverage + drift)").option("--lint", "linting").option("--format", "formatting").option("--typescript", "type checking").option("--build", "build").option("--test", "tests").option("--static-analysis", "cycles, complexity, duplication, centrality").option("--runtime-analysis", "bundle, Lighthouse, web vitals").option("--arch", "architecture boundaries").option("--all", "every target").option(
|
|
263
370
|
"--module <name>",
|
|
264
371
|
"from the workspace root: scope to one module (omit = all; inside a module dir, drop this)"
|
|
265
|
-
).option("--flavour <name>", `override the detected stack preset (${FLAVOURS.join(", ")})`).option("--runner <tool>", "override the default runner (e.g. eslint, biome)").option("--ci", "CI mode: from the root, only the affected modules; non-zero exit on failure").option("--fix", "auto-fix where applicable").option("--dry-run", "preview the changes without writing (--update)").option("--json", "machine-readable JSON output (report/inspect/--dry-run)").
|
|
372
|
+
).option("--flavour <name>", `override the detected stack preset (${FLAVOURS.join(", ")})`).option("--runner <tool>", "override the default runner (e.g. eslint, biome)").option("--ci", "CI mode: from the root, only the affected modules; non-zero exit on failure").option("--fix", "auto-fix where applicable").option("--dry-run", "preview the changes without writing (--update)").option("--json", "machine-readable JSON output (report/inspect/--dry-run)").option(
|
|
373
|
+
"--max-diagnostics <n>",
|
|
374
|
+
"cap the diagnostics embedded per module in --report (0 = no cap)",
|
|
375
|
+
"100"
|
|
376
|
+
).addHelpText(
|
|
266
377
|
"after",
|
|
267
378
|
[
|
|
268
379
|
"",
|
|
@@ -273,7 +384,15 @@ program.name("sentinel").description("One CLI that guards code health: presets,
|
|
|
273
384
|
" sentinel --report --ci # from root \u2192 affected only",
|
|
274
385
|
" sentinel --update --typescript --flavour react # write stubs for the current module"
|
|
275
386
|
].join("\n")
|
|
276
|
-
).
|
|
387
|
+
).addHelpText("after", () => {
|
|
388
|
+
const available = availableTargets();
|
|
389
|
+
const planned = TARGETS.filter((t) => !available.includes(t));
|
|
390
|
+
return [
|
|
391
|
+
"",
|
|
392
|
+
`Available now: ${available.length ? available.map((t) => `--${t}`).join(", ") : "(none yet)"}`,
|
|
393
|
+
planned.length ? `Planned (ship in later tickets): ${planned.map((t) => `--${t}`).join(", ")}` : ""
|
|
394
|
+
].filter(Boolean).join("\n");
|
|
395
|
+
}).parse();
|
|
277
396
|
var opts = program.opts();
|
|
278
397
|
function toCamel(flag) {
|
|
279
398
|
return flag.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -310,6 +429,13 @@ if (opts.all && namedTargets.length > 0) {
|
|
|
310
429
|
);
|
|
311
430
|
}
|
|
312
431
|
var targets = opts.all || namedTargets.length === 0 ? [...TARGETS] : namedTargets;
|
|
432
|
+
var targetsExplicit = !(opts.all || namedTargets.length === 0);
|
|
433
|
+
var maxDiagnostics = Number(opts.maxDiagnostics);
|
|
434
|
+
if (!Number.isInteger(maxDiagnostics) || maxDiagnostics < 0) {
|
|
435
|
+
program.error(
|
|
436
|
+
`--max-diagnostics must be a non-negative integer (0 = no cap); got ${JSON.stringify(opts.maxDiagnostics)}.`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
313
439
|
if (opts.dryRun && verb !== "update") {
|
|
314
440
|
program.error("--dry-run only applies to --update (the read verbs never write).");
|
|
315
441
|
}
|
|
@@ -317,6 +443,13 @@ async function runVerb() {
|
|
|
317
443
|
const { modules, scope } = resolveContext(cwd, { module: opts.module, ci: Boolean(opts.ci) });
|
|
318
444
|
const out = palette(process.stdout);
|
|
319
445
|
const err = palette(process.stderr);
|
|
446
|
+
if (opts.json && verb === "run") {
|
|
447
|
+
process.stderr.write(
|
|
448
|
+
err.warn(
|
|
449
|
+
"note: --json is ignored for --run (it streams the tool output); use --report --json for a machine envelope."
|
|
450
|
+
) + "\n"
|
|
451
|
+
);
|
|
452
|
+
}
|
|
320
453
|
const started = Date.now();
|
|
321
454
|
const { results, worstCode } = await analyse({
|
|
322
455
|
verb,
|
|
@@ -324,12 +457,14 @@ async function runVerb() {
|
|
|
324
457
|
modules,
|
|
325
458
|
runner: opts.runner,
|
|
326
459
|
flavour,
|
|
460
|
+
targetsExplicit,
|
|
461
|
+
maxDiagnostics,
|
|
327
462
|
ci: Boolean(opts.ci),
|
|
328
463
|
fix: Boolean(opts.fix),
|
|
329
464
|
onProgress: (done, total, name) => process.stderr.write(err.dim(` [${done}/${total}] ${name}
|
|
330
465
|
`))
|
|
331
466
|
});
|
|
332
|
-
const summary = generateSummaries(results);
|
|
467
|
+
const summary = generateSummaries(results, targets);
|
|
333
468
|
if (opts.json && verb !== "run") {
|
|
334
469
|
const payload = verb === "status" ? { ...summary, coverage: statusCoverage(summary.results) } : summary;
|
|
335
470
|
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
@@ -345,6 +480,15 @@ async function runVerb() {
|
|
|
345
480
|
err.dim(` ${modules.length} module(s) [${scope}] in ${Date.now() - started}ms
|
|
346
481
|
`)
|
|
347
482
|
);
|
|
483
|
+
if (targetsExplicit && summary.skipped.length > 0) {
|
|
484
|
+
const avail = availableTargets();
|
|
485
|
+
process.stderr.write(
|
|
486
|
+
err.fail(
|
|
487
|
+
`sentinel: target(s) not available yet: ${summary.skipped.map((t) => `--${t}`).join(", ")}. Available now: ${avail.length ? avail.map((t) => `--${t}`).join(", ") : "(none yet)"}.`
|
|
488
|
+
) + "\n"
|
|
489
|
+
);
|
|
490
|
+
return 1;
|
|
491
|
+
}
|
|
348
492
|
return verb === "run" || opts.ci ? worstCode : 0;
|
|
349
493
|
}
|
|
350
494
|
async function runUpdate() {
|
|
@@ -355,18 +499,25 @@ async function runUpdate() {
|
|
|
355
499
|
"--update writes files: target one module (run from its directory, or pass --module). Adopting every module at once is intentionally not allowed \u2014 adopt gradually."
|
|
356
500
|
);
|
|
357
501
|
}
|
|
502
|
+
const available = availableTargets();
|
|
503
|
+
if (targetsExplicit) {
|
|
504
|
+
const unwired = targets.filter((type) => !available.includes(type));
|
|
505
|
+
if (unwired.length > 0) {
|
|
506
|
+
program.error(
|
|
507
|
+
`target(s) not available yet: ${unwired.map((t) => `--${t}`).join(", ")}. Available now: ${available.map((t) => `--${t}`).join(", ") || "(none yet)"}.`
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
const toRun = targets.filter((type) => available.includes(type));
|
|
358
512
|
let worst = 0;
|
|
359
|
-
for (const type of
|
|
513
|
+
for (const type of toRun) {
|
|
360
514
|
try {
|
|
361
515
|
const code = await dispatch({
|
|
362
516
|
verb,
|
|
363
517
|
target: type,
|
|
364
518
|
runner: opts.runner,
|
|
365
|
-
module: module.name,
|
|
366
519
|
cwd: module.root,
|
|
367
520
|
flavour,
|
|
368
|
-
ci: Boolean(opts.ci),
|
|
369
|
-
fix: Boolean(opts.fix),
|
|
370
521
|
dryRun: Boolean(opts.dryRun),
|
|
371
522
|
json: Boolean(opts.json)
|
|
372
523
|
});
|
|
@@ -375,13 +526,11 @@ async function runUpdate() {
|
|
|
375
526
|
process.stderr.write(`
|
|
376
527
|
sentinel (${type}): ${asMessage(err)}
|
|
377
528
|
`);
|
|
378
|
-
if (targets.length === 1) return 1;
|
|
379
529
|
worst = Math.max(worst, 1);
|
|
380
530
|
}
|
|
381
531
|
}
|
|
382
532
|
return worst;
|
|
383
533
|
}
|
|
384
|
-
registerAdapters();
|
|
385
534
|
(verb === "update" ? runUpdate() : runVerb()).then((code) => process.exit(code)).catch((err) => {
|
|
386
535
|
process.stderr.write(`
|
|
387
536
|
sentinel: ${asMessage(err)}
|