@venizia/dev-configs 0.4.2 → 0.4.4
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/LICENSE.md +2 -5
- package/README.md +20 -5
- package/dist/checks/catalog.d.ts +39 -0
- package/dist/checks/catalog.d.ts.map +1 -0
- package/dist/checks/catalog.js +94 -0
- package/dist/checks/catalog.js.map +1 -0
- package/dist/checks/index.d.ts +11 -0
- package/dist/checks/index.d.ts.map +1 -0
- package/dist/checks/index.js +27 -0
- package/dist/checks/index.js.map +1 -0
- package/dist/checks/module-cycles.d.ts +22 -0
- package/dist/checks/module-cycles.d.ts.map +1 -0
- package/dist/checks/module-cycles.js +149 -0
- package/dist/checks/module-cycles.js.map +1 -0
- package/dist/checks/purity/manifest.d.ts +94 -0
- package/dist/checks/purity/manifest.d.ts.map +1 -0
- package/dist/checks/purity/manifest.js +177 -0
- package/dist/checks/purity/manifest.js.map +1 -0
- package/dist/checks/purity/probe.d.ts +68 -0
- package/dist/checks/purity/probe.d.ts.map +1 -0
- package/dist/checks/purity/probe.js +295 -0
- package/dist/checks/purity/probe.js.map +1 -0
- package/dist/checks/purity/run.d.ts +47 -0
- package/dist/checks/purity/run.d.ts.map +1 -0
- package/dist/checks/purity/run.js +121 -0
- package/dist/checks/purity/run.js.map +1 -0
- package/dist/checks/split-report.d.ts +19 -0
- package/dist/checks/split-report.d.ts.map +1 -0
- package/dist/checks/split-report.js +123 -0
- package/dist/checks/split-report.js.map +1 -0
- package/dist/cli/check.d.ts +10 -0
- package/dist/cli/check.d.ts.map +1 -0
- package/dist/cli/check.js +206 -0
- package/dist/cli/check.js.map +1 -0
- package/dist/cli/clean.d.ts +2 -1
- package/dist/cli/clean.d.ts.map +1 -1
- package/dist/cli/clean.js +18 -1
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/codemod/const-classes.d.ts +80 -0
- package/dist/cli/codemod/const-classes.d.ts.map +1 -0
- package/dist/cli/codemod/const-classes.js +603 -0
- package/dist/cli/codemod/const-classes.js.map +1 -0
- package/dist/cli/codemod/drop-redundant-casts.d.ts +112 -0
- package/dist/cli/codemod/drop-redundant-casts.d.ts.map +1 -0
- package/dist/cli/codemod/drop-redundant-casts.js +780 -0
- package/dist/cli/codemod/drop-redundant-casts.js.map +1 -0
- package/dist/cli/codemod/files.d.ts +22 -0
- package/dist/cli/codemod/files.d.ts.map +1 -0
- package/dist/cli/codemod/files.js +68 -0
- package/dist/cli/codemod/files.js.map +1 -0
- package/dist/cli/codemod/index.d.ts +5 -4
- package/dist/cli/codemod/index.d.ts.map +1 -1
- package/dist/cli/codemod/index.js +77 -36
- package/dist/cli/codemod/index.js.map +1 -1
- package/dist/cli/codemod/model-settings.d.ts +2 -1
- package/dist/cli/codemod/model-settings.d.ts.map +1 -1
- package/dist/cli/codemod/model-settings.js +77 -3
- package/dist/cli/codemod/model-settings.js.map +1 -1
- package/dist/cli/index.js +21 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/linters/house/common.d.ts +1 -0
- package/dist/linters/house/common.d.ts.map +1 -1
- package/dist/linters/house/common.js +1 -0
- package/dist/linters/house/common.js.map +1 -1
- package/dist/linters/house/configs.d.ts.map +1 -1
- package/dist/linters/house/configs.js +20 -1
- package/dist/linters/house/configs.js.map +1 -1
- package/dist/linters/house/const-class-values.rule.d.ts +7 -4
- package/dist/linters/house/const-class-values.rule.d.ts.map +1 -1
- package/dist/linters/house/const-class-values.rule.js +73 -21
- package/dist/linters/house/const-class-values.rule.js.map +1 -1
- package/dist/linters/rules/common.d.ts +48 -1
- package/dist/linters/rules/common.d.ts.map +1 -1
- package/dist/linters/rules/common.js +53 -0
- package/dist/linters/rules/common.js.map +1 -1
- package/dist/linters/rules/eqeqeq.rule.d.ts +2 -2
- package/dist/linters/rules/eqeqeq.rule.d.ts.map +1 -1
- package/dist/linters/rules/eqeqeq.rule.js +2 -1
- package/dist/linters/rules/eqeqeq.rule.js.map +1 -1
- package/dist/linters/rules/no-unused-vars.rule.d.ts +3 -3
- package/dist/linters/rules/no-unused-vars.rule.d.ts.map +1 -1
- package/dist/linters/rules/no-unused-vars.rule.js +2 -1
- package/dist/linters/rules/no-unused-vars.rule.js.map +1 -1
- package/dist/linters/rules/switch-case-braces.rule.d.ts +2 -2
- package/dist/linters/rules/switch-case-braces.rule.d.ts.map +1 -1
- package/dist/linters/rules/switch-case-braces.rule.js +2 -1
- package/dist/linters/rules/switch-case-braces.rule.js.map +1 -1
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/repo-tmp.d.ts +12 -0
- package/dist/testing/repo-tmp.d.ts.map +1 -0
- package/dist/testing/repo-tmp.js +67 -0
- package/dist/testing/repo-tmp.js.map +1 -0
- package/dist/testing/spawn.helper.d.ts +13 -0
- package/dist/testing/spawn.helper.d.ts.map +1 -0
- package/dist/testing/spawn.helper.js +22 -0
- package/dist/testing/spawn.helper.js.map +1 -0
- package/dist/testing/tmpdir-preload.d.ts +2 -0
- package/dist/testing/tmpdir-preload.d.ts.map +1 -0
- package/dist/testing/tmpdir-preload.js +9 -0
- package/dist/testing/tmpdir-preload.js.map +1 -0
- package/package.json +9 -1
- package/src/tsconfig/tsconfig.base.json +5 -2
- package/src/tsconfig/tsconfig.common.json +2 -2
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { IPurityClaim, IPurityOptions } from './manifest';
|
|
2
|
+
/** The outcome of one row, as the text report and the JSON verdict both name it. */
|
|
3
|
+
export declare class PurityVerdicts {
|
|
4
|
+
static readonly PASS = "pass";
|
|
5
|
+
static readonly FAIL = "fail";
|
|
6
|
+
static readonly WAIVED = "waived";
|
|
7
|
+
static readonly MISSING = "missing";
|
|
8
|
+
static readonly STALE_WAIVER = "stale-waiver";
|
|
9
|
+
static readonly SCHEME_SET: Set<string>;
|
|
10
|
+
static isValid(value: string): value is TPurityVerdict;
|
|
11
|
+
}
|
|
12
|
+
export type TPurityVerdict = Extract<(typeof PurityVerdicts)[keyof typeof PurityVerdicts], string>;
|
|
13
|
+
/** One row of the `--json` verdict: nothing machine- or time-specific, so two runs on one commit diff empty. */
|
|
14
|
+
export interface IPurityRowVerdict {
|
|
15
|
+
label: string;
|
|
16
|
+
entry: string;
|
|
17
|
+
verdict: TPurityVerdict;
|
|
18
|
+
sizeBytes?: number;
|
|
19
|
+
builtins?: string[];
|
|
20
|
+
globals?: string[];
|
|
21
|
+
guardedGlobals?: string[];
|
|
22
|
+
external?: string[];
|
|
23
|
+
buildError?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface IPurityReport {
|
|
26
|
+
rows: IPurityRowVerdict[];
|
|
27
|
+
failed: number;
|
|
28
|
+
waived: number;
|
|
29
|
+
checked: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Probes every row of the claims and returns the report. `print` receives the human report line by
|
|
33
|
+
* line, in the exact shape the repository scripts printed before the engine moved here.
|
|
34
|
+
*/
|
|
35
|
+
export declare const checkPurity: (opts: {
|
|
36
|
+
root: string;
|
|
37
|
+
claims: IPurityClaim[];
|
|
38
|
+
options?: IPurityOptions;
|
|
39
|
+
/** Only the rows of this package; a token that matches nothing is a broken gate and returns `undefined`. */
|
|
40
|
+
filter?: string;
|
|
41
|
+
/** The claims file, repository-relative, named in the hint printed for a stale waiver. */
|
|
42
|
+
claimsFile: string;
|
|
43
|
+
print: (line: string) => void;
|
|
44
|
+
/** Where the no-match message goes (stderr for the command): it is an error, not a report line. */
|
|
45
|
+
printError: (line: string) => void;
|
|
46
|
+
}) => Promise<IPurityReport | undefined>;
|
|
47
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/checks/purity/run.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAgB,cAAc,EAAE,MAAM,YAAY,CAAC;AAI7E,oFAAoF;AACpF,qBAAa,cAAc;IACzB,MAAM,CAAC,QAAQ,CAAC,IAAI,UAAU;IAC9B,MAAM,CAAC,QAAQ,CAAC,IAAI,UAAU;IAC9B,MAAM,CAAC,QAAQ,CAAC,MAAM,YAAY;IAClC,MAAM,CAAC,QAAQ,CAAC,OAAO,aAAa;IACpC,MAAM,CAAC,QAAQ,CAAC,YAAY,kBAAkB;IAE9C,MAAM,CAAC,QAAQ,CAAC,UAAU,cAMvB;IAEH,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,cAAc;CAGvD;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;AAEnG,gHAAgH;AAChH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,iBAAiB,EAAE,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAID;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAU,MAAM;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,4GAA4G;IAC5G,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,mGAAmG;IACnG,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,KAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAgHpC,CAAC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkPurity = exports.PurityVerdicts = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const manifest_1 = require("./manifest");
|
|
7
|
+
const probe_1 = require("./probe");
|
|
8
|
+
/** The outcome of one row, as the text report and the JSON verdict both name it. */
|
|
9
|
+
class PurityVerdicts {
|
|
10
|
+
static { this.PASS = 'pass'; }
|
|
11
|
+
static { this.FAIL = 'fail'; }
|
|
12
|
+
static { this.WAIVED = 'waived'; }
|
|
13
|
+
static { this.MISSING = 'missing'; }
|
|
14
|
+
static { this.STALE_WAIVER = 'stale-waiver'; }
|
|
15
|
+
static { this.SCHEME_SET = new Set([
|
|
16
|
+
this.PASS,
|
|
17
|
+
this.FAIL,
|
|
18
|
+
this.WAIVED,
|
|
19
|
+
this.MISSING,
|
|
20
|
+
this.STALE_WAIVER,
|
|
21
|
+
]); }
|
|
22
|
+
static isValid(value) {
|
|
23
|
+
return this.SCHEME_SET.has(value);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.PurityVerdicts = PurityVerdicts;
|
|
27
|
+
const formatList = (values) => values.join(', ') || 'none';
|
|
28
|
+
/**
|
|
29
|
+
* Probes every row of the claims and returns the report. `print` receives the human report line by
|
|
30
|
+
* line, in the exact shape the repository scripts printed before the engine moved here.
|
|
31
|
+
*/
|
|
32
|
+
const checkPurity = async (opts) => {
|
|
33
|
+
const { root, claims, options, filter, claimsFile, print, printError } = opts;
|
|
34
|
+
const manifest = (0, manifest_1.buildManifest)({ claims, root, options });
|
|
35
|
+
const rows = filter ? manifest.filter((row) => row.package === filter) : manifest;
|
|
36
|
+
// Non-zero, not a friendly note: a mistyped `package` or a renamed directory would otherwise turn
|
|
37
|
+
// a release-blocking gate into a silent no-op that still reports success.
|
|
38
|
+
if (rows.length === 0) {
|
|
39
|
+
const known = [...new Set(manifest.map((row) => row.package))].sort().join(', ');
|
|
40
|
+
printError(`[purity] No manifest entry matches "${filter}" | Known packages: ${known} - a filter that ` +
|
|
41
|
+
`matches nothing is a broken gate, not an empty one.`);
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const labelWidth = Math.max(...rows.map((row) => row.label.length));
|
|
45
|
+
const verdicts = [];
|
|
46
|
+
let failed = 0;
|
|
47
|
+
let waived = 0;
|
|
48
|
+
for (const row of rows) {
|
|
49
|
+
const entry = (0, node_path_1.join)(root, row.entry);
|
|
50
|
+
// The `exports` map is the source of these rows, so a missing file is either an unbuilt package
|
|
51
|
+
// or a manifest pointing at something the build never emits - both fatal, and neither is worth
|
|
52
|
+
// reporting as a bundler error twenty lines long.
|
|
53
|
+
if (!(0, node_fs_1.existsSync)(entry)) {
|
|
54
|
+
failed += 1;
|
|
55
|
+
print(` ✗ ${row.label.padEnd(labelWidth)} MISSING ${row.entry}`);
|
|
56
|
+
print(` The package's \`exports\` map points here and nothing is built - run \`make ${row.package}\``);
|
|
57
|
+
verdicts.push({ label: row.label, entry: row.entry, verdict: PurityVerdicts.MISSING });
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const result = await (0, probe_1.probeEntry)({
|
|
61
|
+
entry,
|
|
62
|
+
cwd: root,
|
|
63
|
+
external: row.external,
|
|
64
|
+
});
|
|
65
|
+
const detail = {
|
|
66
|
+
label: row.label,
|
|
67
|
+
entry: row.entry,
|
|
68
|
+
sizeBytes: result.sizeBytes,
|
|
69
|
+
builtins: result.builtins,
|
|
70
|
+
globals: result.globals,
|
|
71
|
+
guardedGlobals: result.guardedGlobals,
|
|
72
|
+
external: result.external,
|
|
73
|
+
// The bundler's message names absolute paths; the verdict must read the same on any machine.
|
|
74
|
+
...(result.buildError ? { buildError: result.buildError.replaceAll(root, '<root>') } : {}),
|
|
75
|
+
};
|
|
76
|
+
// Guarded reads are printed on the passing path too: they are safe, but a reviewer needs to
|
|
77
|
+
// see that the entry is clean by construction and not merely by the shape of one expression.
|
|
78
|
+
const guarded = result.guardedGlobals.length > 0 ? ` | guarded: ${result.guardedGlobals.join(', ')}` : '';
|
|
79
|
+
// Same reasoning as `guarded`, printed on both paths: an entry that externalises a dependency
|
|
80
|
+
// measures less than one that does not, and that must stay visible next to the verdict.
|
|
81
|
+
const external = result.external.length > 0 ? ` | external: ${result.external.join(', ')}` : '';
|
|
82
|
+
// Checked before the normal verdict, because for a waived row the two outcomes swap meaning.
|
|
83
|
+
if (row.expectImpure) {
|
|
84
|
+
// A waived entry that comes back pure fails: the waiver now describes something untrue, and
|
|
85
|
+
// the reader who trusts it next will be trusting it about a different entry.
|
|
86
|
+
if (result.ok) {
|
|
87
|
+
failed += 1;
|
|
88
|
+
print(` ✗ ${row.label.padEnd(labelWidth)} STALE WAIVER - this entry is browser-pure now`);
|
|
89
|
+
print(` Drop it from \`impure\` in ${claimsFile} so the gate measures it again`);
|
|
90
|
+
verdicts.push({ ...detail, verdict: PurityVerdicts.STALE_WAIVER });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
waived += 1;
|
|
94
|
+
print(` ~ ${row.label.padEnd(labelWidth)} waived | builtins: ${formatList(result.builtins)} | globals: ${formatList(result.globals)}${external}`);
|
|
95
|
+
verdicts.push({ ...detail, verdict: PurityVerdicts.WAIVED });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (result.ok) {
|
|
99
|
+
print(` ✓ ${row.label.padEnd(labelWidth)} ${(result.sizeBytes / 1024).toFixed(1)} KB${guarded}${external}`);
|
|
100
|
+
verdicts.push({ ...detail, verdict: PurityVerdicts.PASS });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
failed += 1;
|
|
104
|
+
print(` ✗ ${row.label.padEnd(labelWidth)} builtins: ${formatList(result.builtins)} | globals: ${formatList(result.globals)}${guarded}${external}`);
|
|
105
|
+
if (result.buildError) {
|
|
106
|
+
print(` ${result.buildError.split('\n')[0]}`);
|
|
107
|
+
}
|
|
108
|
+
verdicts.push({ ...detail, verdict: PurityVerdicts.FAIL });
|
|
109
|
+
}
|
|
110
|
+
const note = waived > 0 ? ` (${waived} waived as known-impure)` : '';
|
|
111
|
+
if (failed > 0) {
|
|
112
|
+
print(`\n[purity] ${failed}/${rows.length} entries are not browser-pure${note}.`);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
const checked = rows.length - waived;
|
|
116
|
+
print(`\n[purity] ${checked}/${checked} entries are browser-pure${note}.`);
|
|
117
|
+
}
|
|
118
|
+
return { rows: verdicts, failed, waived, checked: rows.length - waived };
|
|
119
|
+
};
|
|
120
|
+
exports.checkPurity = checkPurity;
|
|
121
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/checks/purity/run.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { buildManifest } from './manifest';\nimport type { IPurityClaim, IPurityEntry, IPurityOptions } from './manifest';\nimport { probeEntry } from './probe';\nimport type { IPurityResult } from './probe';\n\n/** The outcome of one row, as the text report and the JSON verdict both name it. */\nexport class PurityVerdicts {\n static readonly PASS = 'pass';\n static readonly FAIL = 'fail';\n static readonly WAIVED = 'waived';\n static readonly MISSING = 'missing';\n static readonly STALE_WAIVER = 'stale-waiver';\n\n static readonly SCHEME_SET = new Set<string>([\n this.PASS,\n this.FAIL,\n this.WAIVED,\n this.MISSING,\n this.STALE_WAIVER,\n ]);\n\n static isValid(value: string): value is TPurityVerdict {\n return this.SCHEME_SET.has(value);\n }\n}\n\nexport type TPurityVerdict = Extract<(typeof PurityVerdicts)[keyof typeof PurityVerdicts], string>;\n\n/** One row of the `--json` verdict: nothing machine- or time-specific, so two runs on one commit diff empty. */\nexport interface IPurityRowVerdict {\n label: string;\n entry: string;\n verdict: TPurityVerdict;\n sizeBytes?: number;\n builtins?: string[];\n globals?: string[];\n guardedGlobals?: string[];\n external?: string[];\n buildError?: string;\n}\n\nexport interface IPurityReport {\n rows: IPurityRowVerdict[];\n failed: number;\n waived: number;\n checked: number;\n}\n\nconst formatList = (values: string[]): string => values.join(', ') || 'none';\n\n/**\n * Probes every row of the claims and returns the report. `print` receives the human report line by\n * line, in the exact shape the repository scripts printed before the engine moved here.\n */\nexport const checkPurity = async (opts: {\n root: string;\n claims: IPurityClaim[];\n options?: IPurityOptions;\n /** Only the rows of this package; a token that matches nothing is a broken gate and returns `undefined`. */\n filter?: string;\n /** The claims file, repository-relative, named in the hint printed for a stale waiver. */\n claimsFile: string;\n print: (line: string) => void;\n /** Where the no-match message goes (stderr for the command): it is an error, not a report line. */\n printError: (line: string) => void;\n}): Promise<IPurityReport | undefined> => {\n const { root, claims, options, filter, claimsFile, print, printError } = opts;\n const manifest = buildManifest({ claims, root, options });\n const rows: IPurityEntry[] = filter ? manifest.filter((row) => row.package === filter) : manifest;\n\n // Non-zero, not a friendly note: a mistyped `package` or a renamed directory would otherwise turn\n // a release-blocking gate into a silent no-op that still reports success.\n if (rows.length === 0) {\n const known = [...new Set(manifest.map((row) => row.package))].sort().join(', ');\n printError(\n `[purity] No manifest entry matches \"${filter}\" | Known packages: ${known} - a filter that ` +\n `matches nothing is a broken gate, not an empty one.`,\n );\n return undefined;\n }\n\n const labelWidth = Math.max(...rows.map((row) => row.label.length));\n const verdicts: IPurityRowVerdict[] = [];\n let failed = 0;\n let waived = 0;\n\n for (const row of rows) {\n const entry = join(root, row.entry);\n\n // The `exports` map is the source of these rows, so a missing file is either an unbuilt package\n // or a manifest pointing at something the build never emits - both fatal, and neither is worth\n // reporting as a bundler error twenty lines long.\n if (!existsSync(entry)) {\n failed += 1;\n print(` ✗ ${row.label.padEnd(labelWidth)} MISSING ${row.entry}`);\n print(\n ` The package's \\`exports\\` map points here and nothing is built - run \\`make ${row.package}\\``,\n );\n verdicts.push({ label: row.label, entry: row.entry, verdict: PurityVerdicts.MISSING });\n continue;\n }\n\n const result: IPurityResult = await probeEntry({\n entry,\n cwd: root,\n external: row.external,\n });\n\n const detail = {\n label: row.label,\n entry: row.entry,\n sizeBytes: result.sizeBytes,\n builtins: result.builtins,\n globals: result.globals,\n guardedGlobals: result.guardedGlobals,\n external: result.external,\n // The bundler's message names absolute paths; the verdict must read the same on any machine.\n ...(result.buildError ? { buildError: result.buildError.replaceAll(root, '<root>') } : {}),\n };\n\n // Guarded reads are printed on the passing path too: they are safe, but a reviewer needs to\n // see that the entry is clean by construction and not merely by the shape of one expression.\n const guarded =\n result.guardedGlobals.length > 0 ? ` | guarded: ${result.guardedGlobals.join(', ')}` : '';\n\n // Same reasoning as `guarded`, printed on both paths: an entry that externalises a dependency\n // measures less than one that does not, and that must stay visible next to the verdict.\n const external = result.external.length > 0 ? ` | external: ${result.external.join(', ')}` : '';\n\n // Checked before the normal verdict, because for a waived row the two outcomes swap meaning.\n if (row.expectImpure) {\n // A waived entry that comes back pure fails: the waiver now describes something untrue, and\n // the reader who trusts it next will be trusting it about a different entry.\n if (result.ok) {\n failed += 1;\n print(` ✗ ${row.label.padEnd(labelWidth)} STALE WAIVER - this entry is browser-pure now`);\n print(` Drop it from \\`impure\\` in ${claimsFile} so the gate measures it again`);\n verdicts.push({ ...detail, verdict: PurityVerdicts.STALE_WAIVER });\n continue;\n }\n\n waived += 1;\n print(\n ` ~ ${row.label.padEnd(labelWidth)} waived | builtins: ${formatList(result.builtins)} | globals: ${formatList(result.globals)}${external}`,\n );\n verdicts.push({ ...detail, verdict: PurityVerdicts.WAIVED });\n continue;\n }\n\n if (result.ok) {\n print(\n ` ✓ ${row.label.padEnd(labelWidth)} ${(result.sizeBytes / 1024).toFixed(1)} KB${guarded}${external}`,\n );\n verdicts.push({ ...detail, verdict: PurityVerdicts.PASS });\n continue;\n }\n\n failed += 1;\n print(\n ` ✗ ${row.label.padEnd(labelWidth)} builtins: ${formatList(result.builtins)} | globals: ${formatList(result.globals)}${guarded}${external}`,\n );\n if (result.buildError) {\n print(` ${result.buildError.split('\\n')[0]}`);\n }\n verdicts.push({ ...detail, verdict: PurityVerdicts.FAIL });\n }\n\n const note = waived > 0 ? ` (${waived} waived as known-impure)` : '';\n\n if (failed > 0) {\n print(`\\n[purity] ${failed}/${rows.length} entries are not browser-pure${note}.`);\n } else {\n const checked = rows.length - waived;\n print(`\\n[purity] ${checked}/${checked} entries are browser-pure${note}.`);\n }\n\n return { rows: verdicts, failed, waived, checked: rows.length - waived };\n};\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare class SplitReport {
|
|
2
|
+
/** `distDir` is the built ESM folder inside a package (`dist/esm`, or `dist` where one build is published); `print` takes the report line by line. */
|
|
3
|
+
static run(opts: {
|
|
4
|
+
packageDir: string;
|
|
5
|
+
distDir?: string;
|
|
6
|
+
print?: (line: string) => void;
|
|
7
|
+
}): void;
|
|
8
|
+
/** Every packages/* and examples/* directory that has a src/ folder. */
|
|
9
|
+
static defaultTargets(opts?: {
|
|
10
|
+
root?: string;
|
|
11
|
+
}): string[];
|
|
12
|
+
private static section;
|
|
13
|
+
private static facts;
|
|
14
|
+
/** abstract.ts + base.ts pairs are the format; exactly one of each is one concept. */
|
|
15
|
+
private static isAbstractBasePair;
|
|
16
|
+
private static walk;
|
|
17
|
+
private static foldersWithoutIndex;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=split-report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split-report.d.ts","sourceRoot":"","sources":["../../src/checks/split-report.ts"],"names":[],"mappings":"AA2BA,qBAAa,WAAW;IACtB,sJAAsJ;IACtJ,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,GAAG,IAAI;IA4ChG,wEAAwE;IACxE,MAAM,CAAC,cAAc,CAAC,IAAI,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,MAAM,EAAE;IAY7D,OAAO,CAAC,MAAM,CAAC,OAAO;IAWtB,OAAO,CAAC,MAAM,CAAC,KAAK;IAUpB,sFAAsF;IACtF,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAKjC,OAAO,CAAC,MAAM,CAAC,IAAI;IAiBnB,OAAO,CAAC,MAAM,CAAC,mBAAmB;CAqBnC"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SplitReport = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Informational report behind the file-splitting convention (conventions/file-splitting.md):
|
|
6
|
+
* hub candidates, stray types files, scope folders without a barrel, files over the soft mark,
|
|
7
|
+
* and import cycles per package. Never a gate. Usage: devz check split-report [--dist <dir>] [packages/kernel]
|
|
8
|
+
* - with no argument, scans every packages/* and examples/* directory that has a src/.
|
|
9
|
+
*/
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_path_1 = require("node:path");
|
|
12
|
+
const module_cycles_1 = require("./module-cycles");
|
|
13
|
+
const SKIP = new Set(['node_modules', 'dist', '__tests__', 'generated', '.turbo']);
|
|
14
|
+
const SOFT_LINE_MARK = 500;
|
|
15
|
+
const HUB_EXPORT_MARK = 10;
|
|
16
|
+
const DECLARATION_KEYWORDS = ['class', 'const', 'type', 'interface', 'function', 'enum', 'let'];
|
|
17
|
+
const DECLARATION = new RegExp(`^export\\s+(?:default\\s+)?(?:abstract\\s+)?(${DECLARATION_KEYWORDS.join('|')})\\s+(\\w+)`, 'gm');
|
|
18
|
+
const EXPORTED_CLASS = /^export\s+(?:abstract\s+)?class\s+(\w+)/gm;
|
|
19
|
+
class SplitReport {
|
|
20
|
+
/** `distDir` is the built ESM folder inside a package (`dist/esm`, or `dist` where one build is published); `print` takes the report line by line. */
|
|
21
|
+
static run(opts) {
|
|
22
|
+
const { distDir = 'dist', print = console.log } = opts;
|
|
23
|
+
const src = (0, node_path_1.join)(opts.packageDir, 'src');
|
|
24
|
+
if (!(0, node_fs_1.existsSync)(src)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const facts = SplitReport.walk({ dir: src }).map((file) => SplitReport.facts({ file }));
|
|
28
|
+
const rel = (file) => (0, node_path_1.relative)(opts.packageDir, file);
|
|
29
|
+
const hubs = facts.filter((f) => ['types.ts', 'constants.ts'].includes((0, node_path_1.basename)(f.file)) && f.exports > HUB_EXPORT_MARK);
|
|
30
|
+
const multiClass = facts.filter((f) => f.classes.length >= 2 && !SplitReport.isAbstractBasePair({ facts: f }));
|
|
31
|
+
const strayTypes = facts.filter((f) => ['types.ts', 'constants.ts'].includes((0, node_path_1.basename)(f.file)) && !f.file.includes('/common/'));
|
|
32
|
+
const long = facts.filter((f) => f.lines > SOFT_LINE_MARK);
|
|
33
|
+
const noBarrel = SplitReport.foldersWithoutIndex({ dir: src });
|
|
34
|
+
const esm = (0, node_path_1.join)(opts.packageDir, distDir);
|
|
35
|
+
const cycles = (0, node_fs_1.existsSync)(esm) ? module_cycles_1.ModuleGraph.fromDirectory({ dir: esm }).cycles().length : -1;
|
|
36
|
+
print(`\n## ${(0, node_path_1.basename)(opts.packageDir)} - ${facts.length} files`);
|
|
37
|
+
const hubTitle = `hub candidates (> ${HUB_EXPORT_MARK} exports)`;
|
|
38
|
+
const hubRows = hubs.map((f) => `${rel(f.file)} (${f.exports})`);
|
|
39
|
+
SplitReport.section({ title: hubTitle, rows: hubRows, print });
|
|
40
|
+
const classTitle = 'files with 2+ exported classes';
|
|
41
|
+
const classRows = multiClass.map((f) => `${rel(f.file)} (${f.classes.join(', ')})`);
|
|
42
|
+
SplitReport.section({ title: classTitle, rows: classRows, print });
|
|
43
|
+
const strayTitle = 'types/constants outside common/';
|
|
44
|
+
const strayRows = strayTypes.map((f) => rel(f.file));
|
|
45
|
+
SplitReport.section({ title: strayTitle, rows: strayRows, print });
|
|
46
|
+
const barrelTitle = 'scope folders without index.ts';
|
|
47
|
+
const barrelRows = noBarrel.map((d) => (0, node_path_1.relative)(opts.packageDir, d));
|
|
48
|
+
SplitReport.section({ title: barrelTitle, rows: barrelRows, print });
|
|
49
|
+
const longTitle = `over ${SOFT_LINE_MARK} lines (a prompt to explain, not a defect)`;
|
|
50
|
+
const longRows = long.map((f) => `${rel(f.file)} (${f.lines})`);
|
|
51
|
+
SplitReport.section({ title: longTitle, rows: longRows, print });
|
|
52
|
+
const cycleMsg = cycles < 0 ? 'no dist' : cycles;
|
|
53
|
+
print(`- import cycles in ${distDir}: ${cycleMsg}`);
|
|
54
|
+
}
|
|
55
|
+
/** Every packages/* and examples/* directory that has a src/ folder. */
|
|
56
|
+
static defaultTargets(opts = {}) {
|
|
57
|
+
const root = opts.root ?? process.cwd();
|
|
58
|
+
const withSource = (folder) => (0, node_fs_1.existsSync)((0, node_path_1.resolve)(root, folder))
|
|
59
|
+
? (0, node_fs_1.readdirSync)((0, node_path_1.resolve)(root, folder))
|
|
60
|
+
.map((name) => (0, node_path_1.resolve)(root, folder, name))
|
|
61
|
+
.filter((dir) => (0, node_fs_1.existsSync)((0, node_path_1.join)(dir, 'src')))
|
|
62
|
+
: [];
|
|
63
|
+
return [...withSource('packages'), ...withSource('examples')];
|
|
64
|
+
}
|
|
65
|
+
static section(opts) {
|
|
66
|
+
opts.print(`- ${opts.title}: ${opts.rows.length}`);
|
|
67
|
+
for (const row of opts.rows) {
|
|
68
|
+
opts.print(` ${row}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
static facts(opts) {
|
|
72
|
+
const text = (0, node_fs_1.readFileSync)(opts.file, 'utf8');
|
|
73
|
+
return {
|
|
74
|
+
file: opts.file,
|
|
75
|
+
lines: text.split('\n').length,
|
|
76
|
+
exports: [...text.matchAll(DECLARATION)].length,
|
|
77
|
+
classes: [...text.matchAll(EXPORTED_CLASS)].map((match) => match[1]),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** abstract.ts + base.ts pairs are the format; exactly one of each is one concept. */
|
|
81
|
+
static isAbstractBasePair(opts) {
|
|
82
|
+
const [first, second] = opts.facts.classes;
|
|
83
|
+
return opts.facts.classes.length === 2 && /Abstract/.test(first) && /Base/.test(second);
|
|
84
|
+
}
|
|
85
|
+
static walk(opts) {
|
|
86
|
+
const out = [];
|
|
87
|
+
const dirEntries = (0, node_fs_1.readdirSync)(opts.dir);
|
|
88
|
+
for (const name of dirEntries) {
|
|
89
|
+
if (SKIP.has(name)) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const full = (0, node_path_1.join)(opts.dir, name);
|
|
93
|
+
if ((0, node_fs_1.statSync)(full).isDirectory()) {
|
|
94
|
+
out.push(...SplitReport.walk({ dir: full }));
|
|
95
|
+
}
|
|
96
|
+
else if (full.endsWith('.ts') && !full.endsWith('.d.ts')) {
|
|
97
|
+
out.push(full);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
static foldersWithoutIndex(opts) {
|
|
103
|
+
const out = [];
|
|
104
|
+
const dirEntries = (0, node_fs_1.readdirSync)(opts.dir);
|
|
105
|
+
for (const name of dirEntries) {
|
|
106
|
+
if (SKIP.has(name)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const full = (0, node_path_1.join)(opts.dir, name);
|
|
110
|
+
if (!(0, node_fs_1.statSync)(full).isDirectory()) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const hasTs = (0, node_fs_1.readdirSync)(full).some((entry) => entry.endsWith('.ts') && entry !== 'index.ts');
|
|
114
|
+
if (hasTs && !(0, node_fs_1.existsSync)((0, node_path_1.join)(full, 'index.ts'))) {
|
|
115
|
+
out.push(full);
|
|
116
|
+
}
|
|
117
|
+
out.push(...SplitReport.foldersWithoutIndex({ dir: full }));
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
exports.SplitReport = SplitReport;
|
|
123
|
+
//# sourceMappingURL=split-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split-report.js","sourceRoot":"","sources":["../../src/checks/split-report.ts"],"names":[],"mappings":";;;AAAA;;;;;GAKG;AACH,qCAA0E;AAC1E,yCAA8D;AAC9D,mDAA8C;AAE9C,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;AACnF,MAAM,cAAc,GAAG,GAAG,CAAC;AAC3B,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAChG,MAAM,WAAW,GAAG,IAAI,MAAM,CAC5B,gDAAgD,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,EAC3F,IAAI,CACL,CAAC;AACF,MAAM,cAAc,GAAG,2CAA2C,CAAC;AASnE,MAAa,WAAW;IACtB,sJAAsJ;IACtJ,MAAM,CAAC,GAAG,CAAC,IAA8E;QACvF,MAAM,EAAE,OAAO,GAAG,MAAM,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QACvD,MAAM,GAAG,GAAG,IAAA,gBAAI,EAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACzC,IAAI,CAAC,IAAA,oBAAU,EAAC,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACxF,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAA,oBAAQ,EAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAE9D,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CACvB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC,QAAQ,CAAC,IAAA,oBAAQ,EAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,GAAG,eAAe,CAC9F,CAAC;QACF,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAC7B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAC9E,CAAC;QACF,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAC7B,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC,QAAQ,CAAC,IAAA,oBAAQ,EAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAC1F,CAAC;QACF,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,cAAc,CAAC,CAAC;QAC3D,MAAM,QAAQ,GAAG,WAAW,CAAC,mBAAmB,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QAC/D,MAAM,GAAG,GAAG,IAAA,gBAAI,EAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAA,oBAAU,EAAC,GAAG,CAAC,CAAC,CAAC,CAAC,2BAAW,CAAC,aAAa,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAE9F,KAAK,CAAC,QAAQ,IAAA,oBAAQ,EAAC,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC;QACnE,MAAM,QAAQ,GAAG,qBAAqB,eAAe,WAAW,CAAC;QACjE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;QACjE,WAAW,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/D,MAAM,UAAU,GAAG,gCAAgC,CAAC;QACpD,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpF,WAAW,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QACnE,MAAM,UAAU,GAAG,iCAAiC,CAAC;QACrD,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACrD,WAAW,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QACnE,MAAM,WAAW,GAAG,gCAAgC,CAAC;QACrD,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAA,oBAAQ,EAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;QACrE,WAAW,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;QACrE,MAAM,SAAS,GAAG,QAAQ,cAAc,4CAA4C,CAAC;QACrF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;QAChE,WAAW,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QACjE,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;QACjD,KAAK,CAAC,sBAAsB,OAAO,KAAK,QAAQ,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,wEAAwE;IACxE,MAAM,CAAC,cAAc,CAAC,OAA0B,EAAE;QAChD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACxC,MAAM,UAAU,GAAG,CAAC,MAAc,EAAY,EAAE,CAC9C,IAAA,oBAAU,EAAC,IAAA,mBAAO,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC/B,CAAC,CAAC,IAAA,qBAAW,EAAC,IAAA,mBAAO,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;iBAC/B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAA,mBAAO,EAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;iBAC1C,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAA,oBAAU,EAAC,IAAA,gBAAI,EAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;YAClD,CAAC,CAAC,EAAE,CAAC;QAET,OAAO,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,EAAE,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;IAChE,CAAC;IAEO,MAAM,CAAC,OAAO,CAAC,IAItB;QACC,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACnD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAEO,MAAM,CAAC,KAAK,CAAC,IAAsB;QACzC,MAAM,IAAI,GAAG,IAAA,sBAAY,EAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7C,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM;YAC9B,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM;YAC/C,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SACrE,CAAC;IACJ,CAAC;IAED,sFAAsF;IAC9E,MAAM,CAAC,kBAAkB,CAAC,IAA2B;QAC3D,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;QAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1F,CAAC;IAEO,MAAM,CAAC,IAAI,CAAC,IAAqB;QACvC,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,IAAA,qBAAW,EAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC9B,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnB,SAAS;YACX,CAAC;YACD,MAAM,IAAI,GAAG,IAAA,gBAAI,EAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAClC,IAAI,IAAA,kBAAQ,EAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;gBACjC,GAAG,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YAC/C,CAAC;iBAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAEO,MAAM,CAAC,mBAAmB,CAAC,IAAqB;QACtD,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,IAAA,qBAAW,EAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC9B,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnB,SAAS;YACX,CAAC;YACD,MAAM,IAAI,GAAG,IAAA,gBAAI,EAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAClC,IAAI,CAAC,IAAA,kBAAQ,EAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;gBAClC,SAAS;YACX,CAAC;YACD,MAAM,KAAK,GAAG,IAAA,qBAAW,EAAC,IAAI,CAAC,CAAC,IAAI,CAClC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,UAAU,CACzD,CAAC;YACF,IAAI,KAAK,IAAI,CAAC,IAAA,oBAAU,EAAC,IAAA,gBAAI,EAAC,IAAI,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC;gBACjD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,mBAAmB,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF;AA5HD,kCA4HC","sourcesContent":["/**\n * Informational report behind the file-splitting convention (conventions/file-splitting.md):\n * hub candidates, stray types files, scope folders without a barrel, files over the soft mark,\n * and import cycles per package. Never a gate. Usage: devz check split-report [--dist <dir>] [packages/kernel]\n * - with no argument, scans every packages/* and examples/* directory that has a src/.\n */\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';\nimport { basename, join, relative, resolve } from 'node:path';\nimport { ModuleGraph } from './module-cycles';\n\nconst SKIP = new Set(['node_modules', 'dist', '__tests__', 'generated', '.turbo']);\nconst SOFT_LINE_MARK = 500;\nconst HUB_EXPORT_MARK = 10;\nconst DECLARATION_KEYWORDS = ['class', 'const', 'type', 'interface', 'function', 'enum', 'let'];\nconst DECLARATION = new RegExp(\n `^export\\\\s+(?:default\\\\s+)?(?:abstract\\\\s+)?(${DECLARATION_KEYWORDS.join('|')})\\\\s+(\\\\w+)`,\n 'gm',\n);\nconst EXPORTED_CLASS = /^export\\s+(?:abstract\\s+)?class\\s+(\\w+)/gm;\n\ninterface IFileFacts {\n file: string;\n lines: number;\n exports: number;\n classes: string[];\n}\n\nexport class SplitReport {\n /** `distDir` is the built ESM folder inside a package (`dist/esm`, or `dist` where one build is published); `print` takes the report line by line. */\n static run(opts: { packageDir: string; distDir?: string; print?: (line: string) => void }): void {\n const { distDir = 'dist', print = console.log } = opts;\n const src = join(opts.packageDir, 'src');\n if (!existsSync(src)) {\n return;\n }\n const facts = SplitReport.walk({ dir: src }).map((file) => SplitReport.facts({ file }));\n const rel = (file: string) => relative(opts.packageDir, file);\n\n const hubs = facts.filter(\n (f) => ['types.ts', 'constants.ts'].includes(basename(f.file)) && f.exports > HUB_EXPORT_MARK,\n );\n const multiClass = facts.filter(\n (f) => f.classes.length >= 2 && !SplitReport.isAbstractBasePair({ facts: f }),\n );\n const strayTypes = facts.filter(\n (f) =>\n ['types.ts', 'constants.ts'].includes(basename(f.file)) && !f.file.includes('/common/'),\n );\n const long = facts.filter((f) => f.lines > SOFT_LINE_MARK);\n const noBarrel = SplitReport.foldersWithoutIndex({ dir: src });\n const esm = join(opts.packageDir, distDir);\n const cycles = existsSync(esm) ? ModuleGraph.fromDirectory({ dir: esm }).cycles().length : -1;\n\n print(`\\n## ${basename(opts.packageDir)} - ${facts.length} files`);\n const hubTitle = `hub candidates (> ${HUB_EXPORT_MARK} exports)`;\n const hubRows = hubs.map((f) => `${rel(f.file)} (${f.exports})`);\n SplitReport.section({ title: hubTitle, rows: hubRows, print });\n const classTitle = 'files with 2+ exported classes';\n const classRows = multiClass.map((f) => `${rel(f.file)} (${f.classes.join(', ')})`);\n SplitReport.section({ title: classTitle, rows: classRows, print });\n const strayTitle = 'types/constants outside common/';\n const strayRows = strayTypes.map((f) => rel(f.file));\n SplitReport.section({ title: strayTitle, rows: strayRows, print });\n const barrelTitle = 'scope folders without index.ts';\n const barrelRows = noBarrel.map((d) => relative(opts.packageDir, d));\n SplitReport.section({ title: barrelTitle, rows: barrelRows, print });\n const longTitle = `over ${SOFT_LINE_MARK} lines (a prompt to explain, not a defect)`;\n const longRows = long.map((f) => `${rel(f.file)} (${f.lines})`);\n SplitReport.section({ title: longTitle, rows: longRows, print });\n const cycleMsg = cycles < 0 ? 'no dist' : cycles;\n print(`- import cycles in ${distDir}: ${cycleMsg}`);\n }\n\n /** Every packages/* and examples/* directory that has a src/ folder. */\n static defaultTargets(opts: { root?: string } = {}): string[] {\n const root = opts.root ?? process.cwd();\n const withSource = (folder: string): string[] =>\n existsSync(resolve(root, folder))\n ? readdirSync(resolve(root, folder))\n .map((name) => resolve(root, folder, name))\n .filter((dir) => existsSync(join(dir, 'src')))\n : [];\n\n return [...withSource('packages'), ...withSource('examples')];\n }\n\n private static section(opts: {\n title: string;\n rows: string[];\n print: (line: string) => void;\n }): void {\n opts.print(`- ${opts.title}: ${opts.rows.length}`);\n for (const row of opts.rows) {\n opts.print(` ${row}`);\n }\n }\n\n private static facts(opts: { file: string }): IFileFacts {\n const text = readFileSync(opts.file, 'utf8');\n return {\n file: opts.file,\n lines: text.split('\\n').length,\n exports: [...text.matchAll(DECLARATION)].length,\n classes: [...text.matchAll(EXPORTED_CLASS)].map((match) => match[1]),\n };\n }\n\n /** abstract.ts + base.ts pairs are the format; exactly one of each is one concept. */\n private static isAbstractBasePair(opts: { facts: IFileFacts }): boolean {\n const [first, second] = opts.facts.classes;\n return opts.facts.classes.length === 2 && /Abstract/.test(first) && /Base/.test(second);\n }\n\n private static walk(opts: { dir: string }): string[] {\n const out: string[] = [];\n const dirEntries = readdirSync(opts.dir);\n for (const name of dirEntries) {\n if (SKIP.has(name)) {\n continue;\n }\n const full = join(opts.dir, name);\n if (statSync(full).isDirectory()) {\n out.push(...SplitReport.walk({ dir: full }));\n } else if (full.endsWith('.ts') && !full.endsWith('.d.ts')) {\n out.push(full);\n }\n }\n return out;\n }\n\n private static foldersWithoutIndex(opts: { dir: string }): string[] {\n const out: string[] = [];\n const dirEntries = readdirSync(opts.dir);\n for (const name of dirEntries) {\n if (SKIP.has(name)) {\n continue;\n }\n const full = join(opts.dir, name);\n if (!statSync(full).isDirectory()) {\n continue;\n }\n const hasTs = readdirSync(full).some(\n (entry) => entry.endsWith('.ts') && entry !== 'index.ts',\n );\n if (hasTs && !existsSync(join(full, 'index.ts'))) {\n out.push(full);\n }\n out.push(...SplitReport.foldersWithoutIndex({ dir: full }));\n }\n return out;\n }\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** `devz check <name>`: runs one of the shared repository gates in the current folder. */
|
|
2
|
+
export declare class CheckCommand {
|
|
3
|
+
private static runSplitReport;
|
|
4
|
+
private static runCatalog;
|
|
5
|
+
static run(opts: {
|
|
6
|
+
root: string;
|
|
7
|
+
args?: string[];
|
|
8
|
+
}): Promise<number>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/cli/check.ts"],"names":[],"mappings":"AA0HA,0FAA0F;AAC1F,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,cAAc;mBAkBR,UAAU;WAsClB,GAAG,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAiF3E"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CheckCommand = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const catalog_1 = require("../checks/catalog");
|
|
7
|
+
const module_cycles_1 = require("../checks/module-cycles");
|
|
8
|
+
const run_1 = require("../checks/purity/run");
|
|
9
|
+
const split_report_1 = require("../checks/split-report");
|
|
10
|
+
const USAGE = `Usage: devz check <name> [options]
|
|
11
|
+
|
|
12
|
+
Checks:
|
|
13
|
+
purity [package] --claims <file> [--json] probe every entry claimed browser-pure (needs a build)
|
|
14
|
+
module-cycles <dir> [--max <n>] list the import cycles of a built ESM folder
|
|
15
|
+
split-report [--dist <dir>] [folder...] report hub files, stray types, missing barrels, long files, cycles (never a gate)
|
|
16
|
+
catalog [--glob <pattern>]... [--pinned <file>]... [--preset-prefix <prefix>]
|
|
17
|
+
guard dependency ranges against the root workspaces.catalog
|
|
18
|
+
|
|
19
|
+
purity
|
|
20
|
+
--claims <file> a module that exports PURITY_CLAIMS (the claims) and, optionally, PURITY_OPTIONS
|
|
21
|
+
[package] probe only the rows of this package; a token that matches nothing fails
|
|
22
|
+
--json print the verdict as JSON (no paths of this machine, no timing) instead of the report
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
module-cycles
|
|
26
|
+
<dir>, or --dist <dir> the built folder (dist/esm); exits 1 when more cycles than --max, 2 on a usage error
|
|
27
|
+
|
|
28
|
+
split-report
|
|
29
|
+
--dist <dir> the built ESM folder inside a package: dist/esm, or dist where one build is published (default dist)
|
|
30
|
+
[folder...] package folders to report; none scans every packages/* and examples/* that has a src/
|
|
31
|
+
|
|
32
|
+
catalog
|
|
33
|
+
--glob <pattern> a manifest glob beside packages/*/package.json (repeatable)
|
|
34
|
+
--pinned <file> a manifest outside the workspace: its catalogued ranges must equal the catalog's (repeatable)
|
|
35
|
+
--preset-prefix <p> files under this prefix are presets: catalog: is allowed in their peerDependencies
|
|
36
|
+
|
|
37
|
+
--help print this text and exit`;
|
|
38
|
+
/** A usage error: nothing was probed. */
|
|
39
|
+
const USAGE_ERROR_CODE = 2;
|
|
40
|
+
const parsePurityArguments = (args) => {
|
|
41
|
+
const parsed = { json: false };
|
|
42
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
43
|
+
const arg = args[index];
|
|
44
|
+
if (arg === '--json') {
|
|
45
|
+
parsed.json = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (arg === '--claims') {
|
|
49
|
+
const value = args[index + 1];
|
|
50
|
+
if (value === undefined || value.startsWith('--')) {
|
|
51
|
+
return '--claims needs a file';
|
|
52
|
+
}
|
|
53
|
+
parsed.claimsFile = value;
|
|
54
|
+
index += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (arg.startsWith('--')) {
|
|
58
|
+
return `unknown option "${arg}"`;
|
|
59
|
+
}
|
|
60
|
+
if (parsed.filter !== undefined) {
|
|
61
|
+
return `unexpected argument "${arg}"`;
|
|
62
|
+
}
|
|
63
|
+
parsed.filter = arg;
|
|
64
|
+
}
|
|
65
|
+
return parsed;
|
|
66
|
+
};
|
|
67
|
+
/** Splits repeatable `--name value` options and positionals; `undefined` for an option that lacks its value. */
|
|
68
|
+
const parseRepeatable = (opts) => {
|
|
69
|
+
const { args, repeatable, single } = opts;
|
|
70
|
+
const values = {};
|
|
71
|
+
const positionals = [];
|
|
72
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
73
|
+
const arg = args[index];
|
|
74
|
+
if (arg.startsWith('--')) {
|
|
75
|
+
const name = arg.slice(2);
|
|
76
|
+
if (!repeatable.includes(name) && !single.includes(name)) {
|
|
77
|
+
return `unknown option "${arg}"`;
|
|
78
|
+
}
|
|
79
|
+
const value = args[index + 1];
|
|
80
|
+
if (value === undefined || value.startsWith('--')) {
|
|
81
|
+
return `${arg} needs a value`;
|
|
82
|
+
}
|
|
83
|
+
values[name] = [...(values[name] ?? []), value];
|
|
84
|
+
index += 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
positionals.push(arg);
|
|
88
|
+
}
|
|
89
|
+
return { positionals, values };
|
|
90
|
+
};
|
|
91
|
+
/** `devz check <name>`: runs one of the shared repository gates in the current folder. */
|
|
92
|
+
class CheckCommand {
|
|
93
|
+
static runSplitReport(opts) {
|
|
94
|
+
const parsed = parseRepeatable({ args: opts.args, repeatable: [], single: ['dist'] });
|
|
95
|
+
if (typeof parsed === 'string') {
|
|
96
|
+
console.error(`devz check split-report: ${parsed}\n${USAGE}`);
|
|
97
|
+
return USAGE_ERROR_CODE;
|
|
98
|
+
}
|
|
99
|
+
const targets = parsed.positionals.length
|
|
100
|
+
? parsed.positionals.map((target) => (0, node_path_1.resolve)(opts.root, target))
|
|
101
|
+
: split_report_1.SplitReport.defaultTargets({ root: opts.root });
|
|
102
|
+
for (const packageDir of targets) {
|
|
103
|
+
split_report_1.SplitReport.run({ packageDir, distDir: parsed.values.dist?.[0] });
|
|
104
|
+
}
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
static async runCatalog(opts) {
|
|
108
|
+
const parsed = parseRepeatable({
|
|
109
|
+
args: opts.args,
|
|
110
|
+
repeatable: ['glob', 'pinned'],
|
|
111
|
+
single: ['preset-prefix'],
|
|
112
|
+
});
|
|
113
|
+
if (typeof parsed === 'string' || parsed.positionals.length > 0) {
|
|
114
|
+
console.error(`devz check catalog: ${typeof parsed === 'string' ? parsed : `unexpected argument "${parsed.positionals[0]}"`}\n${USAGE}`);
|
|
115
|
+
return USAGE_ERROR_CODE;
|
|
116
|
+
}
|
|
117
|
+
const report = await (0, catalog_1.findCatalogProblems)({
|
|
118
|
+
root: opts.root,
|
|
119
|
+
globs: parsed.values.glob,
|
|
120
|
+
pinned: parsed.values.pinned,
|
|
121
|
+
presetPrefix: parsed.values['preset-prefix']?.[0],
|
|
122
|
+
});
|
|
123
|
+
if (report.noCatalog) {
|
|
124
|
+
console.error('check-catalog: no `workspaces.catalog` in the root package.json');
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
if (report.problems.length === 0) {
|
|
128
|
+
console.log(`check-catalog: OK - ${report.entries} entries, ${report.referenced} referenced`);
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
console.error(`check-catalog: ${report.problems.length} problem(s):`);
|
|
132
|
+
for (const problem of report.problems) {
|
|
133
|
+
console.error(` ${problem.file}: ${problem.message}`);
|
|
134
|
+
}
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
static async run(opts) {
|
|
138
|
+
const { root, args = [] } = opts;
|
|
139
|
+
const [name, ...rest] = args;
|
|
140
|
+
if (name === undefined || name === '--help' || name === '-h') {
|
|
141
|
+
console.log(USAGE);
|
|
142
|
+
return name === undefined ? USAGE_ERROR_CODE : 0;
|
|
143
|
+
}
|
|
144
|
+
if (name === 'module-cycles') {
|
|
145
|
+
return (0, module_cycles_1.runModuleCycles)({
|
|
146
|
+
args: rest,
|
|
147
|
+
print: (line) => console.log(line),
|
|
148
|
+
printError: (line) => console.error(line),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (name === 'split-report') {
|
|
152
|
+
return CheckCommand.runSplitReport({ root, args: rest });
|
|
153
|
+
}
|
|
154
|
+
if (name === 'catalog') {
|
|
155
|
+
return CheckCommand.runCatalog({ root, args: rest });
|
|
156
|
+
}
|
|
157
|
+
if (name !== 'purity') {
|
|
158
|
+
console.error(`devz check: unknown check "${name}"\n${USAGE}`);
|
|
159
|
+
return USAGE_ERROR_CODE;
|
|
160
|
+
}
|
|
161
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
162
|
+
console.log(USAGE);
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
const parsed = parsePurityArguments(rest);
|
|
166
|
+
if (typeof parsed === 'string') {
|
|
167
|
+
console.error(`devz check purity: ${parsed}\n${USAGE}`);
|
|
168
|
+
return USAGE_ERROR_CODE;
|
|
169
|
+
}
|
|
170
|
+
if (parsed.claimsFile === undefined) {
|
|
171
|
+
console.error(`devz check purity: --claims <file> is required\n${USAGE}`);
|
|
172
|
+
return USAGE_ERROR_CODE;
|
|
173
|
+
}
|
|
174
|
+
const claimsPath = (0, node_path_1.isAbsolute)(parsed.claimsFile)
|
|
175
|
+
? parsed.claimsFile
|
|
176
|
+
: (0, node_path_1.resolve)(root, parsed.claimsFile);
|
|
177
|
+
if (!(0, node_fs_1.existsSync)(claimsPath)) {
|
|
178
|
+
console.error(`devz check purity: claims file not found: ${parsed.claimsFile}`);
|
|
179
|
+
return USAGE_ERROR_CODE;
|
|
180
|
+
}
|
|
181
|
+
const claimsModule = await import(claimsPath);
|
|
182
|
+
if (!Array.isArray(claimsModule.PURITY_CLAIMS)) {
|
|
183
|
+
console.error(`devz check purity: ${parsed.claimsFile} does not export PURITY_CLAIMS`);
|
|
184
|
+
return USAGE_ERROR_CODE;
|
|
185
|
+
}
|
|
186
|
+
const lines = [];
|
|
187
|
+
const report = await (0, run_1.checkPurity)({
|
|
188
|
+
root,
|
|
189
|
+
claims: claimsModule.PURITY_CLAIMS,
|
|
190
|
+
options: claimsModule.PURITY_OPTIONS,
|
|
191
|
+
filter: parsed.filter,
|
|
192
|
+
claimsFile: (0, node_path_1.relative)(root, claimsPath),
|
|
193
|
+
print: (line) => (parsed.json ? lines.push(line) : console.log(line)),
|
|
194
|
+
printError: (line) => console.error(line),
|
|
195
|
+
});
|
|
196
|
+
if (report === undefined) {
|
|
197
|
+
return 1;
|
|
198
|
+
}
|
|
199
|
+
if (parsed.json) {
|
|
200
|
+
console.log(JSON.stringify(report, null, 2));
|
|
201
|
+
}
|
|
202
|
+
return report.failed > 0 ? 1 : 0;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
exports.CheckCommand = CheckCommand;
|
|
206
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/cli/check.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from 'node:fs';\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { findCatalogProblems } from '../checks/catalog';\nimport { runModuleCycles } from '../checks/module-cycles';\nimport { checkPurity } from '../checks/purity/run';\nimport { SplitReport } from '../checks/split-report';\nimport type { IPurityClaim, IPurityOptions } from '../checks/purity/manifest';\n\nconst USAGE = `Usage: devz check <name> [options]\n\nChecks:\n purity [package] --claims <file> [--json] probe every entry claimed browser-pure (needs a build)\n module-cycles <dir> [--max <n>] list the import cycles of a built ESM folder\n split-report [--dist <dir>] [folder...] report hub files, stray types, missing barrels, long files, cycles (never a gate)\n catalog [--glob <pattern>]... [--pinned <file>]... [--preset-prefix <prefix>]\n guard dependency ranges against the root workspaces.catalog\n\npurity\n --claims <file> a module that exports PURITY_CLAIMS (the claims) and, optionally, PURITY_OPTIONS\n [package] probe only the rows of this package; a token that matches nothing fails\n --json print the verdict as JSON (no paths of this machine, no timing) instead of the report\n\n\nmodule-cycles\n <dir>, or --dist <dir> the built folder (dist/esm); exits 1 when more cycles than --max, 2 on a usage error\n\nsplit-report\n --dist <dir> the built ESM folder inside a package: dist/esm, or dist where one build is published (default dist)\n [folder...] package folders to report; none scans every packages/* and examples/* that has a src/\n\ncatalog\n --glob <pattern> a manifest glob beside packages/*/package.json (repeatable)\n --pinned <file> a manifest outside the workspace: its catalogued ranges must equal the catalog's (repeatable)\n --preset-prefix <p> files under this prefix are presets: catalog: is allowed in their peerDependencies\n\n --help print this text and exit`;\n\n/** A usage error: nothing was probed. */\nconst USAGE_ERROR_CODE = 2;\n\ninterface IClaimsModule {\n PURITY_CLAIMS?: IPurityClaim[];\n PURITY_OPTIONS?: IPurityOptions;\n}\n\ninterface IPurityArguments {\n claimsFile?: string;\n filter?: string;\n json: boolean;\n}\n\nconst parsePurityArguments = (args: string[]): IPurityArguments | string => {\n const parsed: IPurityArguments = { json: false };\n\n for (let index = 0; index < args.length; index += 1) {\n const arg = args[index];\n\n if (arg === '--json') {\n parsed.json = true;\n continue;\n }\n\n if (arg === '--claims') {\n const value = args[index + 1];\n if (value === undefined || value.startsWith('--')) {\n return '--claims needs a file';\n }\n\n parsed.claimsFile = value;\n index += 1;\n continue;\n }\n\n if (arg.startsWith('--')) {\n return `unknown option \"${arg}\"`;\n }\n\n if (parsed.filter !== undefined) {\n return `unexpected argument \"${arg}\"`;\n }\n\n parsed.filter = arg;\n }\n\n return parsed;\n};\n\n/** Splits repeatable `--name value` options and positionals; `undefined` for an option that lacks its value. */\nconst parseRepeatable = (opts: {\n args: string[];\n repeatable: string[];\n single: string[];\n}): { positionals: string[]; values: Record<string, string[]> } | string => {\n const { args, repeatable, single } = opts;\n const values: Record<string, string[]> = {};\n const positionals: string[] = [];\n\n for (let index = 0; index < args.length; index += 1) {\n const arg = args[index];\n\n if (arg.startsWith('--')) {\n const name = arg.slice(2);\n if (!repeatable.includes(name) && !single.includes(name)) {\n return `unknown option \"${arg}\"`;\n }\n\n const value = args[index + 1];\n if (value === undefined || value.startsWith('--')) {\n return `${arg} needs a value`;\n }\n\n values[name] = [...(values[name] ?? []), value];\n index += 1;\n continue;\n }\n\n positionals.push(arg);\n }\n\n return { positionals, values };\n};\n\n/** `devz check <name>`: runs one of the shared repository gates in the current folder. */\nexport class CheckCommand {\n private static runSplitReport(opts: { root: string; args: string[] }): number {\n const parsed = parseRepeatable({ args: opts.args, repeatable: [], single: ['dist'] });\n if (typeof parsed === 'string') {\n console.error(`devz check split-report: ${parsed}\\n${USAGE}`);\n return USAGE_ERROR_CODE;\n }\n\n const targets = parsed.positionals.length\n ? parsed.positionals.map((target) => resolve(opts.root, target))\n : SplitReport.defaultTargets({ root: opts.root });\n\n for (const packageDir of targets) {\n SplitReport.run({ packageDir, distDir: parsed.values.dist?.[0] });\n }\n\n return 0;\n }\n\n private static async runCatalog(opts: { root: string; args: string[] }): Promise<number> {\n const parsed = parseRepeatable({\n args: opts.args,\n repeatable: ['glob', 'pinned'],\n single: ['preset-prefix'],\n });\n if (typeof parsed === 'string' || parsed.positionals.length > 0) {\n console.error(\n `devz check catalog: ${typeof parsed === 'string' ? parsed : `unexpected argument \"${parsed.positionals[0]}\"`}\\n${USAGE}`,\n );\n return USAGE_ERROR_CODE;\n }\n\n const report = await findCatalogProblems({\n root: opts.root,\n globs: parsed.values.glob,\n pinned: parsed.values.pinned,\n presetPrefix: parsed.values['preset-prefix']?.[0],\n });\n\n if (report.noCatalog) {\n console.error('check-catalog: no `workspaces.catalog` in the root package.json');\n return 1;\n }\n\n if (report.problems.length === 0) {\n console.log(`check-catalog: OK - ${report.entries} entries, ${report.referenced} referenced`);\n return 0;\n }\n\n console.error(`check-catalog: ${report.problems.length} problem(s):`);\n for (const problem of report.problems) {\n console.error(` ${problem.file}: ${problem.message}`);\n }\n\n return 1;\n }\n\n static async run(opts: { root: string; args?: string[] }): Promise<number> {\n const { root, args = [] } = opts;\n const [name, ...rest] = args;\n\n if (name === undefined || name === '--help' || name === '-h') {\n console.log(USAGE);\n return name === undefined ? USAGE_ERROR_CODE : 0;\n }\n\n if (name === 'module-cycles') {\n return runModuleCycles({\n args: rest,\n print: (line) => console.log(line),\n printError: (line) => console.error(line),\n });\n }\n\n if (name === 'split-report') {\n return CheckCommand.runSplitReport({ root, args: rest });\n }\n\n if (name === 'catalog') {\n return CheckCommand.runCatalog({ root, args: rest });\n }\n\n if (name !== 'purity') {\n console.error(`devz check: unknown check \"${name}\"\\n${USAGE}`);\n return USAGE_ERROR_CODE;\n }\n\n if (rest.includes('--help') || rest.includes('-h')) {\n console.log(USAGE);\n return 0;\n }\n\n const parsed = parsePurityArguments(rest);\n if (typeof parsed === 'string') {\n console.error(`devz check purity: ${parsed}\\n${USAGE}`);\n return USAGE_ERROR_CODE;\n }\n\n if (parsed.claimsFile === undefined) {\n console.error(`devz check purity: --claims <file> is required\\n${USAGE}`);\n return USAGE_ERROR_CODE;\n }\n\n const claimsPath = isAbsolute(parsed.claimsFile)\n ? parsed.claimsFile\n : resolve(root, parsed.claimsFile);\n if (!existsSync(claimsPath)) {\n console.error(`devz check purity: claims file not found: ${parsed.claimsFile}`);\n return USAGE_ERROR_CODE;\n }\n\n const claimsModule: IClaimsModule = await import(claimsPath);\n if (!Array.isArray(claimsModule.PURITY_CLAIMS)) {\n console.error(`devz check purity: ${parsed.claimsFile} does not export PURITY_CLAIMS`);\n return USAGE_ERROR_CODE;\n }\n\n const lines: string[] = [];\n const report = await checkPurity({\n root,\n claims: claimsModule.PURITY_CLAIMS,\n options: claimsModule.PURITY_OPTIONS,\n filter: parsed.filter,\n claimsFile: relative(root, claimsPath),\n print: (line) => (parsed.json ? lines.push(line) : console.log(line)),\n printError: (line) => console.error(line),\n });\n\n if (report === undefined) {\n return 1;\n }\n\n if (parsed.json) {\n console.log(JSON.stringify(report, null, 2));\n }\n\n return report.failed > 0 ? 1 : 0;\n }\n}\n"]}
|