@hublo/sentinel 1.1.0-alpha.6 → 1.1.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/dist/bin/sentinel.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
registerAdapters,
|
|
17
17
|
resolve,
|
|
18
18
|
resolveBin
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-FA6N4XCP.js";
|
|
20
20
|
|
|
21
21
|
// bin/sentinel.ts
|
|
22
22
|
import { program } from "commander";
|
|
@@ -223,6 +223,14 @@ async function analyse(params) {
|
|
|
223
223
|
toolArgs: params.toolArgs
|
|
224
224
|
};
|
|
225
225
|
try {
|
|
226
|
+
let adoption;
|
|
227
|
+
if (params.verb === "run" || params.verb === "report") {
|
|
228
|
+
try {
|
|
229
|
+
adoption = await adapter.status(ctx);
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const adoptionFields = adoption ? { adopted: adoption.adopted, preset: adoption.preset, conformant: adoption.conformant } : {};
|
|
226
234
|
if (params.verb === "run") {
|
|
227
235
|
if (params.modules.length > 1) {
|
|
228
236
|
const err = palette(process.stderr);
|
|
@@ -239,7 +247,7 @@ async function analyse(params) {
|
|
|
239
247
|
flavour: effectiveFlavour,
|
|
240
248
|
ok: result.ok,
|
|
241
249
|
status: result.ok ? "ok" : "failed",
|
|
242
|
-
data: {}
|
|
250
|
+
data: { ...adoptionFields }
|
|
243
251
|
});
|
|
244
252
|
worstCode = Math.max(worstCode, result.code);
|
|
245
253
|
} else if (params.verb === "report") {
|
|
@@ -250,12 +258,15 @@ async function analyse(params) {
|
|
|
250
258
|
flavour: effectiveFlavour,
|
|
251
259
|
ok: result.ok,
|
|
252
260
|
status: result.ok ? "ok" : "failed",
|
|
253
|
-
|
|
261
|
+
// Engine fields LAST, so they WIN. Spreading them first let a role's own metrics
|
|
262
|
+
// override them, which is the disagreement this was written to remove: the point
|
|
263
|
+
// is that adoption has exactly one source, not that it has a default.
|
|
264
|
+
data: { ...result.metrics ?? {}, ...adoptionFields }
|
|
254
265
|
});
|
|
255
266
|
worstCode = Math.max(worstCode, result.code);
|
|
256
267
|
} else if (params.verb === "inspect") {
|
|
257
|
-
const [config,
|
|
258
|
-
const ok = !
|
|
268
|
+
const [config, adoption2] = await Promise.all([adapter.inspect(ctx), adapter.status(ctx)]);
|
|
269
|
+
const ok = !adoption2.unreadable && (!adoption2.adopted || adoption2.conformant);
|
|
259
270
|
results.push({
|
|
260
271
|
project: module.name,
|
|
261
272
|
target,
|
|
@@ -265,7 +276,7 @@ async function analyse(params) {
|
|
|
265
276
|
// `inspect` is typed as `unknown` (each role shapes its own detail), so the
|
|
266
277
|
// merge is guarded rather than assumed: a role returning a non-object still
|
|
267
278
|
// gets its adoption fields instead of throwing here.
|
|
268
|
-
data: config && typeof config === "object" ? { ...config, ...
|
|
279
|
+
data: config && typeof config === "object" ? { ...config, ...adoption2 } : { ...adoption2 }
|
|
269
280
|
});
|
|
270
281
|
if (!ok) worstCode = Math.max(worstCode, 1);
|
|
271
282
|
} else {
|
|
@@ -1057,7 +1057,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1057
1057
|
*/
|
|
1058
1058
|
async report(ctx) {
|
|
1059
1059
|
const adoption = readFormatAdoption(ctx.cwd);
|
|
1060
|
-
const base = {
|
|
1060
|
+
const base = {};
|
|
1061
1061
|
if (!adoption.adopted) return { ok: true, code: 0, metrics: { ...base, unformatted: 0 } };
|
|
1062
1062
|
const oxfmt = resolveOxfmt(ctx.cwd);
|
|
1063
1063
|
if (!oxfmt) {
|
|
@@ -3023,9 +3023,15 @@ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
|
3023
3023
|
import { join as join13 } from "path";
|
|
3024
3024
|
|
|
3025
3025
|
// src/core/config/owned-keys.ts
|
|
3026
|
-
function presetOwnedKeys(config, permitted) {
|
|
3026
|
+
function presetOwnedKeys(config, permitted, presetSets) {
|
|
3027
3027
|
if (!config) return [];
|
|
3028
|
-
return Object.keys(config).filter(
|
|
3028
|
+
return Object.keys(config).filter(
|
|
3029
|
+
(key) => !permitted.includes(key) && (presetSets === void 0 || presetSets.includes(key))
|
|
3030
|
+
);
|
|
3031
|
+
}
|
|
3032
|
+
function localOnlyKeys(config, permitted, presetSets) {
|
|
3033
|
+
if (!config) return [];
|
|
3034
|
+
return Object.keys(config).filter((key) => !permitted.includes(key) && !presetSets.includes(key));
|
|
3029
3035
|
}
|
|
3030
3036
|
|
|
3031
3037
|
// src/core/config/resolve-config-target.ts
|
|
@@ -3095,7 +3101,7 @@ function readAdoption(cwd, options) {
|
|
|
3095
3101
|
const preset = normaliseExtends(parsed.extends).find((entry) => options.presetPattern.test(entry)) ?? null;
|
|
3096
3102
|
if (preset === null) return NOT_ADOPTED2(target.path);
|
|
3097
3103
|
const settings = options.settingsKey === null ? parsed : parsed[options.settingsKey];
|
|
3098
|
-
const drift = presetOwnedKeys(settings, options.permitted);
|
|
3104
|
+
const drift = presetOwnedKeys(settings, options.permitted, options.presetOwns?.(preset));
|
|
3099
3105
|
return {
|
|
3100
3106
|
configFile: target.path,
|
|
3101
3107
|
preset,
|
|
@@ -3740,10 +3746,9 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3740
3746
|
/** Lint health for `--report`. Deepened in a later step; today it is the run outcome. */
|
|
3741
3747
|
async report(ctx) {
|
|
3742
3748
|
const adoption = readLintAdoption(ctx.cwd);
|
|
3743
|
-
if (!adoption.adopted) return { ok: true, code: 0, metrics: {
|
|
3749
|
+
if (!adoption.adopted) return { ok: true, code: 0, metrics: {} };
|
|
3744
3750
|
const oxlint = resolveOxlint(ctx.cwd);
|
|
3745
|
-
if (!oxlint)
|
|
3746
|
-
return { ok: false, code: 1, metrics: { adopted: true, error: "oxlint not found" } };
|
|
3751
|
+
if (!oxlint) return { ok: false, code: 1, metrics: { error: "oxlint not found" } };
|
|
3747
3752
|
const typeAware = canRunTypeAware(ctx.cwd) ? ["--type-aware"] : [];
|
|
3748
3753
|
const result = spawnSync2(oxlint, ["-c", LINT_CONFIG_FILE, ...typeAware, "-f", "json", "."], {
|
|
3749
3754
|
cwd: ctx.cwd,
|
|
@@ -3751,7 +3756,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3751
3756
|
env: { ...process.env, PATH: oxlintPath(ctx.cwd, process.env) }
|
|
3752
3757
|
});
|
|
3753
3758
|
if (result.error) {
|
|
3754
|
-
return { ok: false, code: 1, metrics: {
|
|
3759
|
+
return { ok: false, code: 1, metrics: { error: result.error.message } };
|
|
3755
3760
|
}
|
|
3756
3761
|
const diagnostics = parseOxlintDiagnostics(result.stdout ?? "");
|
|
3757
3762
|
const errors = diagnostics.filter((entry) => entry.severity === "error").length;
|
|
@@ -3769,7 +3774,6 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3769
3774
|
ok: errors === 0,
|
|
3770
3775
|
code: errors === 0 ? 0 : 1,
|
|
3771
3776
|
metrics: {
|
|
3772
|
-
adopted: true,
|
|
3773
3777
|
errors,
|
|
3774
3778
|
warnings,
|
|
3775
3779
|
rules,
|
|
@@ -3952,6 +3956,14 @@ validate2();
|
|
|
3952
3956
|
function hasShippedPreset(preset) {
|
|
3953
3957
|
return preset in LAYERS;
|
|
3954
3958
|
}
|
|
3959
|
+
function tsPresetFor(preset) {
|
|
3960
|
+
const layer = LAYERS[preset];
|
|
3961
|
+
if (!layer) throw new Error(`no TypeScript preset ships for preset "${preset}"`);
|
|
3962
|
+
return {
|
|
3963
|
+
...BASE.$schema === void 0 ? {} : { $schema: BASE.$schema },
|
|
3964
|
+
compilerOptions: { ...BASE.compilerOptions, ...layer.compilerOptions }
|
|
3965
|
+
};
|
|
3966
|
+
}
|
|
3955
3967
|
|
|
3956
3968
|
// src/roles/typescript/phased-rules.ts
|
|
3957
3969
|
function deferredRuleNames2() {
|
|
@@ -3966,8 +3978,17 @@ var PERMITTED_COMPILER_OPTIONS = [
|
|
|
3966
3978
|
"outDir",
|
|
3967
3979
|
"tsBuildInfoFile"
|
|
3968
3980
|
];
|
|
3969
|
-
function
|
|
3970
|
-
|
|
3981
|
+
function presetCompilerOptionKeys(preset) {
|
|
3982
|
+
if (preset === void 0 || !hasShippedPreset(preset)) return void 0;
|
|
3983
|
+
return Object.keys(tsPresetFor(preset).compilerOptions);
|
|
3984
|
+
}
|
|
3985
|
+
function presetOwnedKeys2(compilerOptions, preset) {
|
|
3986
|
+
return presetOwnedKeys(compilerOptions, PERMITTED_COMPILER_OPTIONS, presetCompilerOptionKeys(preset));
|
|
3987
|
+
}
|
|
3988
|
+
function localOnlyCompilerOptions(compilerOptions, preset) {
|
|
3989
|
+
const sets = presetCompilerOptionKeys(preset);
|
|
3990
|
+
if (sets === void 0) return [];
|
|
3991
|
+
return localOnlyKeys(compilerOptions, PERMITTED_COMPILER_OPTIONS, sets);
|
|
3971
3992
|
}
|
|
3972
3993
|
|
|
3973
3994
|
// src/roles/typescript/resolve-tsconfig-target.ts
|
|
@@ -3991,7 +4012,11 @@ function readTsconfigAdoption(cwd) {
|
|
|
3991
4012
|
fallback: TSCONFIG_FALLBACK,
|
|
3992
4013
|
presetPattern: SENTINEL_PRESET,
|
|
3993
4014
|
settingsKey: "compilerOptions",
|
|
3994
|
-
permitted: PERMITTED_COMPILER_OPTIONS
|
|
4015
|
+
permitted: PERMITTED_COMPILER_OPTIONS,
|
|
4016
|
+
// Drift is measured against what the preset SETS, not against the allowlist alone: see
|
|
4017
|
+
// `presetOwnedKeys`. Without this a module overriding the workspace base reads as
|
|
4018
|
+
// non-conformant forever, since re-running `--init` would not (and should not) fix it.
|
|
4019
|
+
presetOwns: (path) => presetCompilerOptionKeys(path.slice(path.lastIndexOf("/") + 1)) ?? []
|
|
3995
4020
|
});
|
|
3996
4021
|
}
|
|
3997
4022
|
|
|
@@ -4086,7 +4111,8 @@ function planAdoption(context) {
|
|
|
4086
4111
|
target.path
|
|
4087
4112
|
);
|
|
4088
4113
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
4089
|
-
const drift = presetOwnedKeys2(existing.compilerOptions);
|
|
4114
|
+
const drift = presetOwnedKeys2(existing.compilerOptions, context.preset);
|
|
4115
|
+
const keptLocal = localOnlyCompilerOptions(existing.compilerOptions, context.preset);
|
|
4090
4116
|
const operations = [
|
|
4091
4117
|
{ kind: "merge-json", path: target.path, value: { extends: extendsChain } }
|
|
4092
4118
|
];
|
|
@@ -4101,6 +4127,11 @@ function planAdoption(context) {
|
|
|
4101
4127
|
});
|
|
4102
4128
|
notes.push(`stripped preset-owned compilerOptions: ${drift.join(", ")}`);
|
|
4103
4129
|
}
|
|
4130
|
+
if (keptLocal.length > 0) {
|
|
4131
|
+
notes.push(
|
|
4132
|
+
`KEPT this module's own compilerOptions (${keptLocal.join(", ")}): the preset does not set them, so they are yours rather than drift. Removing them would not hand the decision to the preset, it would hand it to the workspace tsconfig this module extends`
|
|
4133
|
+
);
|
|
4134
|
+
}
|
|
4104
4135
|
operations.push(addScript, ...nxTargets);
|
|
4105
4136
|
const existingCheck = moduleScripts(context.cwd)[TYPECHECK_SCRIPT_NAME];
|
|
4106
4137
|
if (keepsForeignChecker(existingCheck)) {
|
|
@@ -4133,7 +4164,7 @@ function parseDiagnostics(output) {
|
|
|
4133
4164
|
return diagnostics;
|
|
4134
4165
|
}
|
|
4135
4166
|
var PHASED_STRICTNESS_WARNING = `sentinel typescript: phase 1 (non-breaking) \u2014 deferred: ${deferredRuleNames2().join(", ")}. Enabled centrally in a later wave; run \`sentinel --inspect --typescript\` for the list, or \`sentinel --run --typescript -- --noImplicitAny\` to measure what one would cost today.`;
|
|
4136
|
-
var TscAdapter = class extends BaseAdapter {
|
|
4167
|
+
var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
4137
4168
|
target = "typescript";
|
|
4138
4169
|
runner = "tsc";
|
|
4139
4170
|
/**
|
|
@@ -4241,6 +4272,13 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
4241
4272
|
);
|
|
4242
4273
|
return { ok: true, code: 0 };
|
|
4243
4274
|
}
|
|
4275
|
+
if (!readTsconfigAdoption(ctx.cwd).adopted) {
|
|
4276
|
+
process.stderr.write(
|
|
4277
|
+
`sentinel typescript(tsc): no sentinel preset in this module's tsconfig; run \`sentinel --init --typescript\` to adopt.
|
|
4278
|
+
`
|
|
4279
|
+
);
|
|
4280
|
+
return { ok: true, code: 0 };
|
|
4281
|
+
}
|
|
4244
4282
|
const missing = this.missingPresetPackage(ctx.cwd);
|
|
4245
4283
|
if (missing) {
|
|
4246
4284
|
process.stderr.write(
|
|
@@ -4366,10 +4404,23 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
4366
4404
|
* 7006/7031/7053/… ), the signal that drives the noImplicitAny migration. No
|
|
4367
4405
|
* tsconfig is a clean, empty report.
|
|
4368
4406
|
*/
|
|
4407
|
+
/**
|
|
4408
|
+
* The metrics for a module this role has nothing to say about. Every KEY the full path
|
|
4409
|
+
* emits, so a consumer never sees a row whose shape depends on whether the module adopted.
|
|
4410
|
+
*/
|
|
4411
|
+
static NOTHING_TO_REPORT = {
|
|
4412
|
+
errors: 0,
|
|
4413
|
+
implicitAny: "deferred",
|
|
4414
|
+
diagnostics: [],
|
|
4415
|
+
diagnosticsTruncated: false
|
|
4416
|
+
};
|
|
4369
4417
|
async report(ctx) {
|
|
4370
4418
|
const config = this.typecheckTarget(ctx.cwd);
|
|
4371
4419
|
if (!config) {
|
|
4372
|
-
return { ok: true, code: 0, metrics:
|
|
4420
|
+
return { ok: true, code: 0, metrics: _TscAdapter.NOTHING_TO_REPORT };
|
|
4421
|
+
}
|
|
4422
|
+
if (!readTsconfigAdoption(ctx.cwd).adopted) {
|
|
4423
|
+
return { ok: true, code: 0, metrics: _TscAdapter.NOTHING_TO_REPORT };
|
|
4373
4424
|
}
|
|
4374
4425
|
const tsc = resolveBin(ctx.cwd, "tsc") ?? "tsc";
|
|
4375
4426
|
const result = spawnSync3(tsc, ["-b", config], { cwd: ctx.cwd, encoding: "utf8" });
|
|
@@ -4861,4 +4912,4 @@ export {
|
|
|
4861
4912
|
detectFramework,
|
|
4862
4913
|
dispatch
|
|
4863
4914
|
};
|
|
4864
|
-
//# sourceMappingURL=chunk-
|
|
4915
|
+
//# sourceMappingURL=chunk-FA6N4XCP.js.map
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hublo/sentinel",
|
|
3
|
-
"version": "1.1.0
|
|
3
|
+
"version": "1.1.0",
|
|
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",
|