@antelopejs/tooling-configs 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/bin/api-diff.mjs +203 -8
- package/dist/bin/check-size.mjs +2 -2
- package/dist/bin/checks.mjs +2 -1
- package/dist/bin/comment-proof.mjs +7 -3
- package/dist/bin/ratchet.mjs +10 -5
- package/dist/bin/sync.mjs +96 -6
- package/dist/files-jKlueTMd.mjs +298 -0
- package/dist/oxc/fmt.mjs +1 -1
- package/dist/oxc/lint.mjs +4 -3
- package/dist/{ratchet-B2_QUucT.mjs → ratchet-CNbpU42F.mjs} +7 -6
- package/dist/renames-BGO4dWJ8.mjs +59 -0
- package/dist/{shared-NC0T8ykm.mjs → shared-CsXIMK3I.mjs} +4 -2
- package/dist/{size-D7JJp_W4.mjs → size-BdYmv57u.mjs} +4 -3
- package/dist/{typescript-CHOlkIXa.mjs → typescript-aLCrMi2y.mjs} +26 -108
- package/package.json +1 -1
- package/dist/files-DAD5OtfV.mjs +0 -129
package/README.md
CHANGED
|
@@ -173,7 +173,7 @@ pnpm install
|
|
|
173
173
|
```
|
|
174
174
|
|
|
175
175
|
- A rule listed under `rules` is off for `oxlint`, so editors, hooks and `lint` stay green. `antelopejs-ratchet` counts it with every rule on, and fails when a count rises. It prints the findings in the files the change touched.
|
|
176
|
-
- A file listed under `oversizeFiles` may exceed 20,000 bytes and `max-lines`, up to its recorded size. Any other file over either limit fails.
|
|
176
|
+
- A file listed under `oversizeFiles` may exceed 20,000 bytes and `max-lines`, up to its recorded size. Any other file over either limit fails. When git sees a listed file renamed (`git mv`, or a staged delete and add), the allowance moves to the new path and never exceeds the old one; a copy or a new file inherits nothing. `antelopejs-ratchet --base <ref>` detects renames against that revision instead of `HEAD`, for a rename that was committed before the file was updated.
|
|
177
177
|
- `antelopejs-ratchet --update` lowers each number to the current count and removes what reached zero. It never raises a number or adds an entry. Commit the lowered file.
|
|
178
178
|
|
|
179
179
|
When every rule count is zero and `oversizeFiles` is empty, the ratchet is retired. `antelopejs-ratchet --update` deletes the file and asks you to run `antelopejs-sync`. Sync then removes `ratchet`, `ratchet:update`, the ratchet hook and the Debt instructions. Commit those changes together. An empty baseline on first adoption creates no file. Later syncs never recreate it because the `sync:check` script marks adoption.
|
|
@@ -182,7 +182,7 @@ When every rule count is zero and `oversizeFiles` is empty, the ratchet is retir
|
|
|
182
182
|
|
|
183
183
|
### Size
|
|
184
184
|
|
|
185
|
-
`antelopejs-check-size` fails on an authored file over 20,000 bytes, tests included. Generated code, fixtures, lockfiles, `CHANGELOG.md`, Markdown and binary assets are excluded. Data JSON under `locales/` or `i18n/`, `blocks.json`, `schema.json`, and `evals/**/cases.json` are excluded too. Files over the limit with a NUL byte in their first 8,000 bytes are binary even without a known extension. Staged checks inspect the index blob. Excluded files are also removed from recorded size allowances on ratchet update.
|
|
185
|
+
`antelopejs-check-size` fails on an authored file over 20,000 bytes, tests included. Generated code (`dist`, `coverage`, `.nuxt`, `.output`, and `build` or `output` at the repository root or a package root, never under `src/` or `test/`), fixtures, lockfiles, `CHANGELOG.md`, Markdown and binary assets are excluded. Data JSON under `locales/` or `i18n/`, `blocks.json`, `schema.json`, and `evals/**/cases.json` are excluded too. Files over the limit with a NUL byte in their first 8,000 bytes are binary even without a known extension. Staged checks inspect the index blob. Excluded files are also removed from recorded size allowances on ratchet update.
|
|
186
186
|
|
|
187
187
|
Pass paths to check only those files, as the pre-commit hook does. The command also fails on an `oxlint-disable` or `eslint-disable` directive (line, next-line or file form) that names no rule: oxlint applies such a directive before any rule runs, so `no-comments` cannot report it.
|
|
188
188
|
|
package/dist/bin/api-diff.mjs
CHANGED
|
@@ -1,9 +1,190 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { i as
|
|
4
|
-
import {
|
|
2
|
+
import { c as revisionCounts, i as repositoryRoot, o as DIRECTORY_LINK, s as compareRevisions, u as runCli } from "../files-jKlueTMd.mjs";
|
|
3
|
+
import { i as parseManifest, r as packageManager } from "../package-Ckd7e4tA.mjs";
|
|
4
|
+
import { i as packagesAt, n as isTestSource, r as outputRoot, t as emitPackage } from "../typescript-aLCrMi2y.mjs";
|
|
5
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
5
6
|
import { dirname, join, relative, resolve } from "node:path";
|
|
7
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
6
8
|
import { generateDtsBundle } from "dts-bundle-generator";
|
|
9
|
+
//#region src/guardrails/runtime-diff.ts
|
|
10
|
+
const DESCRIBE = `
|
|
11
|
+
const { readFileSync, existsSync } = require("node:fs");
|
|
12
|
+
const { dirname, join, extname } = require("node:path");
|
|
13
|
+
const { pathToFileURL } = require("node:url");
|
|
14
|
+
const file = process.argv[1];
|
|
15
|
+
let directory = dirname(file);
|
|
16
|
+
while (!existsSync(join(directory, "package.json")) && dirname(directory) !== directory) directory = dirname(directory);
|
|
17
|
+
const manifest = existsSync(join(directory, "package.json")) ? JSON.parse(readFileSync(join(directory, "package.json"), "utf8")) : {};
|
|
18
|
+
const esm = extname(file) === ".mjs" || (extname(file) === ".js" && manifest.type === "module");
|
|
19
|
+
const seen = new Map();
|
|
20
|
+
function functionInfo(fn) {
|
|
21
|
+
let constructible = true;
|
|
22
|
+
try { Reflect.construct(Object, [], fn); } catch { constructible = false; }
|
|
23
|
+
return { name: fn.name, length: fn.length, async: Object.getPrototypeOf(fn) === Object.getPrototypeOf(async function() {}), constructible };
|
|
24
|
+
}
|
|
25
|
+
function valueInfo(value) {
|
|
26
|
+
if (typeof value !== "function") return { kind: value === null ? "null" : typeof value };
|
|
27
|
+
if (seen.has(value)) return { kind: "reference", id: seen.get(value) };
|
|
28
|
+
const id = seen.size;
|
|
29
|
+
seen.set(value, id);
|
|
30
|
+
return { kind: "function", id, function: functionInfo(value), static: properties(value, false), prototype: value.prototype ? properties(value.prototype, false) : [] };
|
|
31
|
+
}
|
|
32
|
+
function properties(object, observe) {
|
|
33
|
+
if (object === null || !["object", "function"].includes(typeof object)) return [];
|
|
34
|
+
return Reflect.ownKeys(object).map(key => {
|
|
35
|
+
const d = Object.getOwnPropertyDescriptor(object, key);
|
|
36
|
+
const common = { key: typeof key === "symbol" ? { symbol: String(key) } : key, enumerable: d.enumerable, configurable: d.configurable };
|
|
37
|
+
return "value" in d
|
|
38
|
+
? { ...common, kind: "data", writable: d.writable, value: valueInfo(d.value) }
|
|
39
|
+
: { ...common, kind: "accessor", get: d.get ? functionInfo(d.get) : null, set: d.set ? functionInfo(d.set) : null, observed: observe ? valueInfo(Reflect.get(object, key)) : null };
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
(async () => {
|
|
43
|
+
const value = esm ? await import(pathToFileURL(file).href) : require(file);
|
|
44
|
+
process.stdout.write(JSON.stringify({ kind: esm ? "esm" : "cjs", value: valueInfo(value), properties: typeof value === "function" ? [] : properties(value, true) }));
|
|
45
|
+
})().catch(error => { process.stderr.write(String(error.stack)); process.exitCode = 1; });
|
|
46
|
+
`;
|
|
47
|
+
function runtimeTargets(target, entry = "exports") {
|
|
48
|
+
if (target == null) return [];
|
|
49
|
+
if (Array.isArray(target)) return target.flatMap((value, index) => runtimeTargets(value, `${entry}/${index}`));
|
|
50
|
+
if (target.constructor === String) return [{
|
|
51
|
+
entry,
|
|
52
|
+
target: String(target)
|
|
53
|
+
}];
|
|
54
|
+
return Object.entries(target).flatMap(([key, value]) => {
|
|
55
|
+
if (key === "types") return [];
|
|
56
|
+
if ([
|
|
57
|
+
"browser",
|
|
58
|
+
"deno",
|
|
59
|
+
"react-native"
|
|
60
|
+
].includes(key)) throw new Error(`${entry}/${key}: unsupported runtime environment.`);
|
|
61
|
+
return runtimeTargets(value, `${entry}/${key}`);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function builtEntries(directory, manifest) {
|
|
65
|
+
const targets = runtimeTargets(manifest.exports);
|
|
66
|
+
if (manifest.main) targets.push({
|
|
67
|
+
entry: "main",
|
|
68
|
+
target: manifest.main
|
|
69
|
+
});
|
|
70
|
+
if (!targets.length) throw new Error(`${directory}: no published runtime entry.`);
|
|
71
|
+
return targets.filter(({ target }) => !target.endsWith(".json")).flatMap(({ entry, target }) => {
|
|
72
|
+
if (!target.includes("*")) {
|
|
73
|
+
const file = resolve(directory, target);
|
|
74
|
+
if (!existsSync(file)) throw new Error(`${directory}: missing built runtime entry ${target}.`);
|
|
75
|
+
return [{
|
|
76
|
+
entry,
|
|
77
|
+
file
|
|
78
|
+
}];
|
|
79
|
+
}
|
|
80
|
+
const prefix = resolve(directory, target.slice(0, target.indexOf("*")));
|
|
81
|
+
const parent = dirname(prefix);
|
|
82
|
+
const pattern = new RegExp(`^${resolve(directory, target).split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join("(.*)")}$`, "u");
|
|
83
|
+
if (!existsSync(parent)) throw new Error(`${directory}: missing build output for ${target}.`);
|
|
84
|
+
const output = outputRoot(directory);
|
|
85
|
+
const matches = readdirSync(parent, {
|
|
86
|
+
recursive: true,
|
|
87
|
+
withFileTypes: true
|
|
88
|
+
}).filter((file) => file.isFile()).map((file) => join(file.parentPath, file.name)).filter((file) => pattern.test(file) && !isTestSource(output, file));
|
|
89
|
+
if (!matches.length) throw new Error(`${directory}: missing built runtime entry ${target}.`);
|
|
90
|
+
return matches.sort().map((file) => {
|
|
91
|
+
const capture = pattern.exec(file)?.[1] ?? relative(directory, file);
|
|
92
|
+
return {
|
|
93
|
+
entry: entry.replaceAll("*", capture),
|
|
94
|
+
file
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function childJson(args, cwd) {
|
|
100
|
+
const result = spawnSync(process.execPath, args, {
|
|
101
|
+
cwd,
|
|
102
|
+
encoding: "utf8",
|
|
103
|
+
timeout: 1e4,
|
|
104
|
+
maxBuffer: 16777216,
|
|
105
|
+
stdio: [
|
|
106
|
+
"ignore",
|
|
107
|
+
"pipe",
|
|
108
|
+
"pipe"
|
|
109
|
+
]
|
|
110
|
+
});
|
|
111
|
+
if (result.error) throw result.error;
|
|
112
|
+
if (result.status !== 0) throw new Error(result.stderr || `Runtime child exited with ${result.status ?? result.signal}.`);
|
|
113
|
+
return `${JSON.stringify(JSON.parse(result.stdout), null, 2)}\n`;
|
|
114
|
+
}
|
|
115
|
+
function buildPackage(directory, output, manifest) {
|
|
116
|
+
const buildManifest = parseManifest(readFileSync(join(directory, "package.json"), "utf8"));
|
|
117
|
+
if (buildManifest.scripts?.build) execFileSync(packageManager(buildManifest), ["run", "build"], {
|
|
118
|
+
cwd: directory,
|
|
119
|
+
encoding: "utf8",
|
|
120
|
+
shell: process.platform === "win32",
|
|
121
|
+
env: {
|
|
122
|
+
...process.env,
|
|
123
|
+
pnpm_config_verify_deps_before_run: "false"
|
|
124
|
+
},
|
|
125
|
+
timeout: 12e4,
|
|
126
|
+
stdio: [
|
|
127
|
+
"ignore",
|
|
128
|
+
"pipe",
|
|
129
|
+
"pipe"
|
|
130
|
+
]
|
|
131
|
+
});
|
|
132
|
+
let entries;
|
|
133
|
+
try {
|
|
134
|
+
entries = builtEntries(directory, manifest);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (buildManifest.scripts?.build) throw error;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
for (const { entry, file } of entries) {
|
|
140
|
+
const destination = join(output, `${encodeURIComponent(entry)}.json`);
|
|
141
|
+
const metadata = childJson([
|
|
142
|
+
"-e",
|
|
143
|
+
DESCRIBE,
|
|
144
|
+
file
|
|
145
|
+
], directory);
|
|
146
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
147
|
+
writeFileSync(destination, metadata);
|
|
148
|
+
}
|
|
149
|
+
return entries.length;
|
|
150
|
+
}
|
|
151
|
+
/** Builds both committed revisions and compares published runtime metadata and one captured optional probe. */
|
|
152
|
+
function runtimeDiff(root, baseRef, probe) {
|
|
153
|
+
const errors = [];
|
|
154
|
+
const failedPackages = /* @__PURE__ */ new Set();
|
|
155
|
+
const skippedPackages = /* @__PURE__ */ new Set();
|
|
156
|
+
const result = compareRevisions(root, baseRef, "Runtime API", (worktree, output, scratch) => {
|
|
157
|
+
const packages = packagesAt(worktree, true);
|
|
158
|
+
let entries = 0;
|
|
159
|
+
for (const { directory, manifest } of packages) try {
|
|
160
|
+
const built = buildPackage(join(worktree, directory), join(output, encodeURIComponent(directory).replaceAll(".", "%2E")), manifest);
|
|
161
|
+
if (built === void 0) skippedPackages.add(directory);
|
|
162
|
+
else entries += built;
|
|
163
|
+
} catch (error) {
|
|
164
|
+
failedPackages.add(directory);
|
|
165
|
+
errors.push(`Execution error: ${directory}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
166
|
+
}
|
|
167
|
+
for (const directory of failedPackages) for (const side of ["base", "head"]) rmSync(join(dirname(output), side, encodeURIComponent(directory).replaceAll(".", "%2E")), {
|
|
168
|
+
recursive: true,
|
|
169
|
+
force: true
|
|
170
|
+
});
|
|
171
|
+
if (probe !== void 0) {
|
|
172
|
+
mkdirSync(scratch, { recursive: true });
|
|
173
|
+
const script = join(scratch, "probe.cjs");
|
|
174
|
+
writeFileSync(script, probe);
|
|
175
|
+
writeFileSync(join(output, "probe.json"), childJson([script, worktree], worktree));
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
packages: packages.map(({ directory }) => directory),
|
|
179
|
+
entries
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
return {
|
|
183
|
+
different: result.different || errors.length > 0,
|
|
184
|
+
output: (errors.length && !result.different ? result.output.replace(/^Runtime API:[^\n]*\n/u, "Runtime API: comparison incomplete due to execution errors.\n") : result.output) + [...skippedPackages].map((directory) => `Runtime API: skipped ${directory}, which has no build script and no committed runtime entry.\n`).join("") + errors.join("")
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
7
188
|
//#region src/guardrails/api-diff.ts
|
|
8
189
|
function entrySegment(value) {
|
|
9
190
|
return encodeURIComponent(value).replaceAll(".", "%2E").replaceAll("*", "%2A");
|
|
@@ -99,7 +280,7 @@ function bundleDependencies(directory, scratch) {
|
|
|
99
280
|
if (parent === current) return;
|
|
100
281
|
current = parent;
|
|
101
282
|
}
|
|
102
|
-
symlinkSync(join(current, "node_modules"), join(scratch, "node_modules"),
|
|
283
|
+
symlinkSync(join(current, "node_modules"), join(scratch, "node_modules"), DIRECTORY_LINK);
|
|
103
284
|
}
|
|
104
285
|
/** dts-bundle-generator 9 remaps a .d.mts/.d.cts root twice; a source-named copy preserves its declaration text. */
|
|
105
286
|
function generatorEntry(file) {
|
|
@@ -140,8 +321,7 @@ function bundlePackage(directory, output, scratch, manifest) {
|
|
|
140
321
|
}
|
|
141
322
|
return entries.length;
|
|
142
323
|
}
|
|
143
|
-
|
|
144
|
-
function apiDiff(root, baseRef) {
|
|
324
|
+
function declarationDiff(root, baseRef) {
|
|
145
325
|
return compareRevisions(root, baseRef, "API", (worktree, output, scratch) => {
|
|
146
326
|
const packages = packagesAt(worktree, true);
|
|
147
327
|
let entries = 0;
|
|
@@ -152,12 +332,27 @@ function apiDiff(root, baseRef) {
|
|
|
152
332
|
};
|
|
153
333
|
});
|
|
154
334
|
}
|
|
335
|
+
/** Compares declarations and built runtime exports, with an optional behavioral probe captured before either build. */
|
|
336
|
+
function apiDiff(root, baseRef, probeFile) {
|
|
337
|
+
const probe = probeFile === void 0 ? void 0 : readFileSync(resolve(root, probeFile));
|
|
338
|
+
const results = [];
|
|
339
|
+
const errors = [];
|
|
340
|
+
for (const compare of [() => declarationDiff(root, baseRef), () => runtimeDiff(root, baseRef, probe)]) try {
|
|
341
|
+
results.push(compare());
|
|
342
|
+
} catch (error) {
|
|
343
|
+
errors.push(`Execution error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
different: errors.length > 0 || results.some((result) => result.different),
|
|
347
|
+
output: results.map((result) => result.output).join("") + errors.join("") + (probe === void 0 ? "No behavioural probe ran.\n" : "Behavioural probe supplied.\n") + revisionCounts(root, baseRef)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
155
350
|
//#endregion
|
|
156
351
|
//#region src/bin/api-diff.ts
|
|
157
352
|
runCli(() => {
|
|
158
353
|
const args = process.argv.slice(2);
|
|
159
|
-
if (args.length !== 1) throw new Error("Usage: antelopejs-api-diff <base-ref>");
|
|
160
|
-
const result = apiDiff(repositoryRoot(), args[0]);
|
|
354
|
+
if (args.length !== 1 && !(args.length === 3 && args[1] === "--probe")) throw new Error("Usage: antelopejs-api-diff <base-ref> [--probe <file.cjs>]");
|
|
355
|
+
const result = apiDiff(repositoryRoot(), args[0], args[2]);
|
|
161
356
|
process.stdout.write(result.output);
|
|
162
357
|
return result.different ? 1 : 0;
|
|
163
358
|
});
|
package/dist/bin/check-size.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { t as checkSize } from "../size-
|
|
2
|
+
import { i as repositoryRoot, r as repositoryPath, t as authoredSizes, u as runCli } from "../files-jKlueTMd.mjs";
|
|
3
|
+
import { t as checkSize } from "../size-BdYmv57u.mjs";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
package/dist/bin/checks.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "../ceilings-BSw9p4YB.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { i as repositoryRoot, u as runCli } from "../files-jKlueTMd.mjs";
|
|
4
4
|
import { i as parseManifest, r as packageManager } from "../package-Ckd7e4tA.mjs";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
@@ -29,6 +29,7 @@ function runChecks(root) {
|
|
|
29
29
|
const result = spawnSync(manager, ["run", step], {
|
|
30
30
|
cwd: root,
|
|
31
31
|
encoding: "utf8",
|
|
32
|
+
shell: process.platform === "win32",
|
|
32
33
|
env: {
|
|
33
34
|
...process.env,
|
|
34
35
|
CI: "1"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { i as
|
|
2
|
+
import { c as revisionCounts, i as repositoryRoot, s as compareRevisions, u as runCli } from "../files-jKlueTMd.mjs";
|
|
3
|
+
import { i as packagesAt, t as emitPackage } from "../typescript-aLCrMi2y.mjs";
|
|
4
4
|
import { copyFileSync, mkdirSync } from "node:fs";
|
|
5
5
|
import { dirname, join, relative } from "node:path";
|
|
6
6
|
//#region src/guardrails/comment-proof.ts
|
|
7
7
|
/** Proves comment-free JavaScript emit matches, including private packages and excluding tests. */
|
|
8
8
|
function commentProof(root, baseRef) {
|
|
9
|
-
|
|
9
|
+
const result = compareRevisions(root, baseRef, "Comment proof", (worktree, output, scratch) => {
|
|
10
10
|
const packages = packagesAt(worktree, false);
|
|
11
11
|
let entries = 0;
|
|
12
12
|
for (const { directory } of packages) {
|
|
@@ -25,6 +25,10 @@ function commentProof(root, baseRef) {
|
|
|
25
25
|
entries
|
|
26
26
|
};
|
|
27
27
|
});
|
|
28
|
+
return {
|
|
29
|
+
...result,
|
|
30
|
+
output: result.output + revisionCounts(root, baseRef)
|
|
31
|
+
};
|
|
28
32
|
}
|
|
29
33
|
//#endregion
|
|
30
34
|
//#region src/bin/comment-proof.ts
|
package/dist/bin/ratchet.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { s as readCeilings, t as CEILINGS_FILE } from "../ceilings-BSw9p4YB.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { n as renamedPaths } from "../renames-BGO4dWJ8.mjs";
|
|
4
|
+
import { a as resolveRevision, i as repositoryRoot, r as repositoryPath, u as runCli } from "../files-jKlueTMd.mjs";
|
|
5
|
+
import { n as ratchet, r as lintDiagnostics } from "../ratchet-CNbpU42F.mjs";
|
|
5
6
|
import { resolve } from "node:path";
|
|
6
7
|
import { execFileSync } from "node:child_process";
|
|
7
8
|
import { parseArgs } from "node:util";
|
|
@@ -18,6 +19,7 @@ function stagedRatchet(root, files) {
|
|
|
18
19
|
encoding: "utf8"
|
|
19
20
|
}).split("\0").filter(Boolean);
|
|
20
21
|
if (!targets.length) return [];
|
|
22
|
+
const renames = renamedPaths(root, true);
|
|
21
23
|
const additions = /* @__PURE__ */ new Map();
|
|
22
24
|
for (const file of targets) {
|
|
23
25
|
const diff = execFileSync("git", [
|
|
@@ -26,8 +28,9 @@ function stagedRatchet(root, files) {
|
|
|
26
28
|
"--no-ext-diff",
|
|
27
29
|
"--no-color",
|
|
28
30
|
"--unified=0",
|
|
31
|
+
"--find-renames",
|
|
29
32
|
"--",
|
|
30
|
-
file
|
|
33
|
+
...[renames.get(file), file].filter((path) => path !== void 0)
|
|
31
34
|
], {
|
|
32
35
|
cwd: root,
|
|
33
36
|
encoding: "utf8"
|
|
@@ -46,18 +49,20 @@ runCli(() => {
|
|
|
46
49
|
const { values, positionals } = parseArgs({
|
|
47
50
|
options: {
|
|
48
51
|
update: { type: "boolean" },
|
|
49
|
-
staged: { type: "boolean" }
|
|
52
|
+
staged: { type: "boolean" },
|
|
53
|
+
base: { type: "string" }
|
|
50
54
|
},
|
|
51
55
|
allowPositionals: true
|
|
52
56
|
});
|
|
53
57
|
const root = repositoryRoot();
|
|
58
|
+
if (values.base !== void 0 && values.staged) throw new Error("--base cannot be combined with --staged.");
|
|
54
59
|
if (values.update && values.staged) throw new Error("--staged cannot be combined with --update.");
|
|
55
60
|
if (positionals.length && !values.staged) throw new Error("File arguments require --staged.");
|
|
56
61
|
const result = values.staged ? {
|
|
57
62
|
failures: stagedRatchet(root, positionals.length ? positionals.map((file) => repositoryPath(root, resolve(file))) : void 0),
|
|
58
63
|
lowered: [],
|
|
59
64
|
retired: false
|
|
60
|
-
} : ratchet(root, values.update);
|
|
65
|
+
} : ratchet(root, values.update, values.base === void 0 ? void 0 : resolveRevision(root, values.base));
|
|
61
66
|
if (result.retired) process.stdout.write(`Deleted ${CEILINGS_FILE}: no debt left. Run antelopejs-sync.\n`);
|
|
62
67
|
for (const message of result.lowered) process.stdout.write(`Lowered ${message}\n`);
|
|
63
68
|
for (const message of result.failures) process.stderr.write(`${message}\n`);
|
package/dist/bin/sync.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { a as formatCeilings, o as isRetired, s as readCeilings, t as CEILINGS_FILE } from "../ceilings-BSw9p4YB.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { i as repositoryRoot, u as runCli } from "../files-jKlueTMd.mjs";
|
|
4
4
|
import { a as repositoryTools, i as parseManifest, n as managedPackage, o as toolingVersions, r as packageManager, t as execCommand } from "../package-Ckd7e4tA.mjs";
|
|
5
|
-
import { t as measureBaseline } from "../ratchet-
|
|
5
|
+
import { t as measureBaseline } from "../ratchet-CNbpU42F.mjs";
|
|
6
6
|
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { dirname, join, resolve } from "node:path";
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
8
9
|
import { parseArgs } from "node:util";
|
|
9
10
|
import { isMap, isScalar, isSeq, parseDocument } from "yaml";
|
|
10
11
|
//#region src/guardrails/agents-block.ts
|
|
@@ -12,7 +13,11 @@ const AGENTS_BLOCK_START = "<!-- antelopejs-guardrails:start -->";
|
|
|
12
13
|
const AGENTS_BLOCK_END = "<!-- antelopejs-guardrails:end -->";
|
|
13
14
|
const COMMENTS = `### Comments
|
|
14
15
|
|
|
15
|
-
Say it in code. A constraint belongs in a type, a lint rule, or a test. A public item's contract belongs in TSDoc (\`/** */\`) on that item. The only other comments are lint directives that name their rules, \`@ts-expect-error <reason>\`, \`SAFETY: <reason>\` above a type assertion, \`/*!\` license headers, and bundler or formatter directives
|
|
16
|
+
Say it in code. A constraint belongs in a type, a lint rule, or a test. A public item's contract belongs in TSDoc (\`/** */\`) on that item. The only other comments are lint directives that name their rules, \`@ts-expect-error <reason>\`, \`SAFETY: <reason>\` above a type assertion, \`/*!\` license headers, and bundler or formatter directives.
|
|
17
|
+
|
|
18
|
+
Before suppressing a type rule, try inference or a type derived from its owner with \`Parameters\`, \`ReturnType\`, or indexed access. A private alias can still appear in a published declaration. Keep a suppression only after checking the published contract and the unchecked boundary it protects.
|
|
19
|
+
|
|
20
|
+
Check each directive reason against the current line and its callers. Returning a value is not the same as awaiting and discarding it. A reason must describe what the code does now.`;
|
|
16
21
|
const TESTS = `### Tests
|
|
17
22
|
|
|
18
23
|
Hold every test you write or keep to this checklist:
|
|
@@ -22,7 +27,13 @@ Hold every test you write or keep to this checklist:
|
|
|
22
27
|
- An invariant over many files is a lint rule, not a test.`;
|
|
23
28
|
const CLEANUP_PROOF = `### Cleanup proof
|
|
24
29
|
|
|
25
|
-
A behaviour-preserving cleanup PR shows \`antelopejs-api-diff <base-ref>\` passing. Published declarations stay unchanged. A comment-only PR shows \`antelopejs-comment-proof <base-ref>\` passing instead of a verifier. Both compare committed HEAD
|
|
30
|
+
A behaviour-preserving cleanup PR shows \`antelopejs-api-diff <base-ref>\` passing. Published declarations and built runtime exports stay unchanged. A comment-only PR shows \`antelopejs-comment-proof <base-ref>\` passing instead of a verifier. Both compare committed HEAD.
|
|
31
|
+
|
|
32
|
+
Copy counts from the proof's base and HEAD table. Never type counts by hand. Keep the command and the committed head beside the copied output. Directive lines are not individual suppressed rules.
|
|
33
|
+
|
|
34
|
+
Pass \`--probe <file.cjs>\` to api-diff when changing receivers, positional calls, or modules that no published entry reaches. The same captured script runs against both worktree roots, supplied as \`argv[2]\`, and prints JSON observations. Matching metadata alone does not prove those behaviours.
|
|
35
|
+
|
|
36
|
+
An entry-only rule exempts only its own module. Add a second module entry rather than exempting another importer. A no-upward rule must reject peers in the same tier as well as higher tiers. Probe the rank decision with an import, not just a reading of the configuration.`;
|
|
26
37
|
const SHARED_RULES = [
|
|
27
38
|
["Formatting", "oxfmt"],
|
|
28
39
|
["Lint findings are errors; no rule warns", "oxlint preset"],
|
|
@@ -32,14 +43,19 @@ const SHARED_RULES = [
|
|
|
32
43
|
["No assertion that restates a constant", "`antelopejs/no-constant-value-assertion`"],
|
|
33
44
|
["No test whose only check is \"is a function\"", "`antelopejs/no-typeof-function-only-test`"],
|
|
34
45
|
["Params ≤ 5, function ≤ 120 lines, file ≤ 500 lines, depth ≤ 4, complexity ≤ 20", "`eslint/max-*`, `eslint/complexity`"],
|
|
35
|
-
["Cleanup preserves
|
|
36
|
-
["Comment-only changes preserve JavaScript emit", "`antelopejs-comment-proof <base-ref>`"]
|
|
46
|
+
["Cleanup preserves declarations and built runtime export metadata", "`antelopejs-api-diff <base-ref>`"],
|
|
47
|
+
["Comment-only changes preserve JavaScript emit", "`antelopejs-comment-proof <base-ref>`"],
|
|
48
|
+
["PR-body counts come from tracked code files", "api-diff and comment-proof tables"],
|
|
49
|
+
["Receiver, positional and unpublished-module behaviour", "`antelopejs-api-diff <base-ref> --probe <file.cjs>`"],
|
|
50
|
+
["Type suppressions and directive reasons are justified", "review; no tool"],
|
|
51
|
+
["No-upward rules reject same-tier peers", "review and import probes"]
|
|
37
52
|
];
|
|
38
53
|
const FULL_RULES = [
|
|
39
54
|
["Files ≤ 20,000 bytes, tests included", "`antelopejs-check-size` (pre-commit)"],
|
|
40
55
|
["Types check", "`tsc` (pre-push)"],
|
|
41
56
|
["No dead files, exports, or dependencies", "knip"],
|
|
42
57
|
["Managed files match the pack", "`antelopejs-sync --check`"],
|
|
58
|
+
["Entry-only rules exempt no tracked outsider", "`antelopejs-sync --check`"],
|
|
43
59
|
["A test names the wrong implementation it catches", "review; no tool"]
|
|
44
60
|
];
|
|
45
61
|
/** Pads each column the way oxfmt does, so formatting AGENTS.md never reports sync drift. */
|
|
@@ -258,6 +274,76 @@ function hasChecksWorkflow(root) {
|
|
|
258
274
|
return false;
|
|
259
275
|
}
|
|
260
276
|
//#endregion
|
|
277
|
+
//#region src/guardrails/entry-only.ts
|
|
278
|
+
const LOAD_RULES = `
|
|
279
|
+
const advisories = [];
|
|
280
|
+
const rules = [];
|
|
281
|
+
const SUPPORTED = { from: ["pathNot"], to: ["path", "pathNot"] };
|
|
282
|
+
function patterns(value) {
|
|
283
|
+
if (value === undefined) return [];
|
|
284
|
+
const list = Array.isArray(value) ? value : [value];
|
|
285
|
+
if (list.some(pattern => typeof pattern !== "string")) return null;
|
|
286
|
+
try { list.forEach(pattern => new RegExp(pattern)); } catch { return null; }
|
|
287
|
+
return list;
|
|
288
|
+
}
|
|
289
|
+
function problem(rule) {
|
|
290
|
+
if (!rule.from || typeof rule.from !== "object" || !rule.to || typeof rule.to !== "object")
|
|
291
|
+
return "Warning: entry-only rule " + rule.name + " has no from and to selectors, so its exemptions cannot be checked.";
|
|
292
|
+
const unsupported = [
|
|
293
|
+
...(rule.severity === "error" ? [] : ["severity " + String(rule.severity)]),
|
|
294
|
+
...Object.keys(rule.from).filter(key => !SUPPORTED.from.includes(key)).map(key => "from." + key),
|
|
295
|
+
...Object.keys(rule.to).filter(key => !SUPPORTED.to.includes(key)).map(key => "to." + key),
|
|
296
|
+
];
|
|
297
|
+
if (unsupported.length)
|
|
298
|
+
return "Note: entry-only rule " + rule.name + " skipped, because its selectors are not supported (" + unsupported.join(", ") + "); only error rules built from from.pathNot and to.path or to.pathNot patterns are checked.";
|
|
299
|
+
if (rule.to.path === undefined)
|
|
300
|
+
return "Warning: entry-only rule " + rule.name + " has no to.path, so there is no module to exempt against.";
|
|
301
|
+
if (patterns(rule.from.pathNot) === null || !patterns(rule.to.path)?.length || patterns(rule.to.pathNot) === null)
|
|
302
|
+
return "Warning: entry-only rule " + rule.name + " has selectors that are not valid regular expression strings, so its exemptions cannot be checked.";
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
const forbidden = require(process.cwd()+"/.dependency-cruiser.cjs").forbidden;
|
|
307
|
+
if (!Array.isArray(forbidden)) advisories.push("Warning: .dependency-cruiser.cjs has no forbidden rule array, so entry-only exemptions are not checked.");
|
|
308
|
+
else for (const rule of forbidden) {
|
|
309
|
+
if (!rule || typeof rule.name !== "string" || !rule.name.endsWith("-entry-only")) continue;
|
|
310
|
+
const advisory = problem(rule);
|
|
311
|
+
if (advisory === undefined) rules.push({ name: rule.name, exempt: patterns(rule.from.pathNot), domain: patterns(rule.to.path) });
|
|
312
|
+
else advisories.push(advisory);
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
advisories.push("Warning: .dependency-cruiser.cjs could not be loaded, so entry-only exemptions are not checked (" + String(error && error.message) + ").");
|
|
316
|
+
}
|
|
317
|
+
process.stdout.write(JSON.stringify({ rules, advisories }));
|
|
318
|
+
`;
|
|
319
|
+
function matcher(patterns) {
|
|
320
|
+
return patterns.map((pattern) => new RegExp(pattern));
|
|
321
|
+
}
|
|
322
|
+
/** Rejects tracked importers exempted from another module's entry-only boundary. A rule it cannot interpret yields a printed note or warning in `advisories` and never a failure. */
|
|
323
|
+
function checkEntryOnlyConfiguration(root) {
|
|
324
|
+
if (!existsSync(join(root, ".dependency-cruiser.cjs"))) return {
|
|
325
|
+
errors: [],
|
|
326
|
+
advisories: []
|
|
327
|
+
};
|
|
328
|
+
const { rules, advisories } = JSON.parse(execFileSync(process.execPath, ["-e", LOAD_RULES], {
|
|
329
|
+
cwd: root,
|
|
330
|
+
encoding: "utf8",
|
|
331
|
+
timeout: 1e4
|
|
332
|
+
}));
|
|
333
|
+
const paths = execFileSync("git", ["ls-files", "-z"], {
|
|
334
|
+
cwd: root,
|
|
335
|
+
encoding: "utf8"
|
|
336
|
+
}).split("\0").filter(Boolean);
|
|
337
|
+
return {
|
|
338
|
+
errors: rules.flatMap((rule) => {
|
|
339
|
+
const domain = matcher(rule.domain);
|
|
340
|
+
const exemption = matcher(rule.exempt);
|
|
341
|
+
return paths.filter((path) => exemption.some((pattern) => pattern.test(path)) && !domain.some((pattern) => pattern.test(path))).map((path) => `${rule.name} exempts ${path} outside its module; give the module a second entry instead`);
|
|
342
|
+
}),
|
|
343
|
+
advisories
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
//#endregion
|
|
261
347
|
//#region src/guardrails/sync.ts
|
|
262
348
|
const LEGACY_COMMENT_INSTRUCTION = "- Explain non-obvious constraints and decisions in comments; avoid restating the code. Document public contracts with TSDoc.";
|
|
263
349
|
const RETIRED_RATCHET = `${CEILINGS_FILE} has no debt left. Run ratchet:update to delete it, then run antelopejs-sync.`;
|
|
@@ -463,6 +549,8 @@ function syncRepository(root, options = {}) {
|
|
|
463
549
|
const context = repositoryContext(root, current, profile);
|
|
464
550
|
let plan = planSync(current, modes, context);
|
|
465
551
|
const errors = plan.filter((artifact) => artifact.kind === "error").map(({ message }) => message);
|
|
552
|
+
const entryOnly = checkEntryOnlyConfiguration(root);
|
|
553
|
+
errors.push(...entryOnly.errors);
|
|
466
554
|
const notes = plan.flatMap((artifact) => artifact.kind === "migrated" ? artifact.notes : []);
|
|
467
555
|
(options.check ? errors : notes).push(...repositoryNotices(root, context));
|
|
468
556
|
if (!errors.length && profile === "full" && !options.check && !current.has("guardrails.ratchet.json") && manifest.scripts?.["sync:check"] === void 0) {
|
|
@@ -483,6 +571,7 @@ function syncRepository(root, options = {}) {
|
|
|
483
571
|
paths: errors.length ? [] : writes.map(({ path }) => path),
|
|
484
572
|
errors,
|
|
485
573
|
notes,
|
|
574
|
+
advisories: entryOnly.advisories,
|
|
486
575
|
dependenciesAdded
|
|
487
576
|
};
|
|
488
577
|
}
|
|
@@ -502,6 +591,7 @@ runCli(() => {
|
|
|
502
591
|
profile: values.profile
|
|
503
592
|
});
|
|
504
593
|
for (const note of result.notes) process.stdout.write(`Note: ${values.check || result.errors.length ? "Planned migration. " : ""}${note}\n`);
|
|
594
|
+
for (const advisory of result.advisories) process.stdout.write(`${advisory}\n`);
|
|
505
595
|
for (const error of result.errors) process.stderr.write(`${error}\n`);
|
|
506
596
|
if (result.errors.length) return 1;
|
|
507
597
|
for (const path of result.paths) process.stdout.write(`${values.check ? "Drift" : "Updated"}: ${path}${values.check ? "; run antelopejs-sync" : ""}\n`);
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import "./ceilings-BSw9p4YB.mjs";
|
|
2
|
+
import { n as IGNORE_PATTERNS, t as AGENT_IGNORE_PATTERNS } from "./shared-CsXIMK3I.mjs";
|
|
3
|
+
import { closeSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readSync, rmSync, symlinkSync } from "node:fs";
|
|
4
|
+
import { dirname, join, matchesGlob, relative, resolve } from "node:path";
|
|
5
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
//#region src/guardrails/cli.ts
|
|
8
|
+
function runCli(main) {
|
|
9
|
+
try {
|
|
10
|
+
process.exitCode = main();
|
|
11
|
+
} catch (error) {
|
|
12
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/guardrails/revisions.ts
|
|
18
|
+
function trackedManifests(root) {
|
|
19
|
+
return execFileSync("git", ["ls-files", "-z"], {
|
|
20
|
+
cwd: root,
|
|
21
|
+
encoding: "utf8"
|
|
22
|
+
}).split("\0").filter((file) => file === "package.json" || file.endsWith("/package.json"));
|
|
23
|
+
}
|
|
24
|
+
const DIRECTORY_LINK = process.platform === "win32" ? "junction" : "dir";
|
|
25
|
+
function linkDependencies(root, worktree) {
|
|
26
|
+
const directories = /* @__PURE__ */ new Set([".", ...trackedManifests(root).map(dirname)]);
|
|
27
|
+
for (const directory of directories) {
|
|
28
|
+
const source = join(root, directory, "node_modules");
|
|
29
|
+
const target = join(worktree, directory, "node_modules");
|
|
30
|
+
if (existsSync(source) && existsSync(dirname(target)) && !existsSync(target)) symlinkSync(source, target, DIRECTORY_LINK);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Compares isolated committed revisions and removes both worktrees even when compilation fails. */
|
|
34
|
+
function compareRevisions(root, baseRef, label, emit) {
|
|
35
|
+
const temporary = mkdtempSync(join(tmpdir(), "antelope-proof-"));
|
|
36
|
+
const worktrees = [];
|
|
37
|
+
const results = [];
|
|
38
|
+
try {
|
|
39
|
+
for (const [side, ref] of [["base", baseRef], ["head", "HEAD"]]) {
|
|
40
|
+
const revision = execFileSync("git", [
|
|
41
|
+
"rev-parse",
|
|
42
|
+
"--verify",
|
|
43
|
+
"--end-of-options",
|
|
44
|
+
`${ref}^{commit}`
|
|
45
|
+
], {
|
|
46
|
+
cwd: root,
|
|
47
|
+
encoding: "utf8",
|
|
48
|
+
stdio: [
|
|
49
|
+
"ignore",
|
|
50
|
+
"pipe",
|
|
51
|
+
"pipe"
|
|
52
|
+
]
|
|
53
|
+
}).trim();
|
|
54
|
+
const worktree = join(temporary, "worktrees", side);
|
|
55
|
+
execFileSync("git", [
|
|
56
|
+
"worktree",
|
|
57
|
+
"add",
|
|
58
|
+
"--quiet",
|
|
59
|
+
"--detach",
|
|
60
|
+
worktree,
|
|
61
|
+
revision
|
|
62
|
+
], {
|
|
63
|
+
cwd: root,
|
|
64
|
+
stdio: [
|
|
65
|
+
"ignore",
|
|
66
|
+
"pipe",
|
|
67
|
+
"pipe"
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
worktrees.push(worktree);
|
|
71
|
+
linkDependencies(root, worktree);
|
|
72
|
+
const output = join(temporary, side);
|
|
73
|
+
mkdirSync(output, { recursive: true });
|
|
74
|
+
results.push(emit(worktree, output, join(temporary, "emit", side)));
|
|
75
|
+
}
|
|
76
|
+
const diff = spawnSync("git", [
|
|
77
|
+
"diff",
|
|
78
|
+
"--no-index",
|
|
79
|
+
"--no-color",
|
|
80
|
+
"--no-renames",
|
|
81
|
+
"--no-ext-diff",
|
|
82
|
+
"--no-textconv",
|
|
83
|
+
"--",
|
|
84
|
+
"base",
|
|
85
|
+
"head"
|
|
86
|
+
], {
|
|
87
|
+
cwd: temporary,
|
|
88
|
+
encoding: "utf8",
|
|
89
|
+
maxBuffer: 67108864
|
|
90
|
+
});
|
|
91
|
+
if (diff.error) throw diff.error;
|
|
92
|
+
if (diff.status !== 0 && diff.status !== 1) throw new Error(diff.stderr || "Could not compare revision outputs.");
|
|
93
|
+
const packages = new Set(results.flatMap((result) => result.packages)).size;
|
|
94
|
+
return {
|
|
95
|
+
different: diff.status === 1,
|
|
96
|
+
output: diff.status === 1 ? `${label}: differences against ${baseRef} (committed HEAD).\n${diff.stdout}` : `${label}: ${packages} packages, ${results[1].entries} entries unchanged against ${baseRef} (committed HEAD).\n`
|
|
97
|
+
};
|
|
98
|
+
} finally {
|
|
99
|
+
for (const worktree of worktrees.reverse()) execFileSync("git", [
|
|
100
|
+
"worktree",
|
|
101
|
+
"remove",
|
|
102
|
+
"--force",
|
|
103
|
+
worktree
|
|
104
|
+
], {
|
|
105
|
+
cwd: root,
|
|
106
|
+
stdio: "pipe"
|
|
107
|
+
});
|
|
108
|
+
rmSync(temporary, {
|
|
109
|
+
recursive: true,
|
|
110
|
+
force: true
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const COUNTS = [
|
|
115
|
+
["oxlint-disable / eslint-disable directive lines", /^\s*(?:\/\/|\/\*|\*)\s*(?:oxlint|eslint)-disable\b/u],
|
|
116
|
+
["SAFETY: lines", /^\s*(?:\/\/|\/\*|\*)\s*SAFETY:/u],
|
|
117
|
+
["@ts-expect-error lines", /^\s*(?:\/\/|\/\*|\*)\s*@ts-expect-error\b/u]
|
|
118
|
+
];
|
|
119
|
+
const CODE_PATHSPECS = [
|
|
120
|
+
"*.ts",
|
|
121
|
+
"*.tsx",
|
|
122
|
+
"*.mts",
|
|
123
|
+
"*.cts",
|
|
124
|
+
"*.js",
|
|
125
|
+
"*.jsx",
|
|
126
|
+
"*.mjs",
|
|
127
|
+
"*.cjs",
|
|
128
|
+
"*.vue"
|
|
129
|
+
];
|
|
130
|
+
function countsAt(root, ref) {
|
|
131
|
+
const result = spawnSync("git", [
|
|
132
|
+
"grep",
|
|
133
|
+
"-h",
|
|
134
|
+
"-I",
|
|
135
|
+
"-E",
|
|
136
|
+
"-e",
|
|
137
|
+
"(oxlint|eslint)-disable|SAFETY:|@ts-expect-error",
|
|
138
|
+
ref,
|
|
139
|
+
"--",
|
|
140
|
+
...CODE_PATHSPECS
|
|
141
|
+
], {
|
|
142
|
+
cwd: root,
|
|
143
|
+
encoding: "utf8",
|
|
144
|
+
maxBuffer: 67108864
|
|
145
|
+
});
|
|
146
|
+
if (result.error) throw result.error;
|
|
147
|
+
if (result.status !== 0 && result.status !== 1) throw new Error(result.stderr);
|
|
148
|
+
const lines = result.stdout.split("\n");
|
|
149
|
+
return COUNTS.map(([, pattern]) => lines.filter((line) => pattern.test(line)).length);
|
|
150
|
+
}
|
|
151
|
+
/** Counts directive, `SAFETY:` and `@ts-expect-error` lines in every tracked code file at the base and at committed HEAD, so a PR body copies them instead of typing them. */
|
|
152
|
+
function revisionCounts(root, baseRef) {
|
|
153
|
+
const base = countsAt(root, baseRef);
|
|
154
|
+
const head = countsAt(root, "HEAD");
|
|
155
|
+
return `\nCounts in tracked code files (${baseRef} and committed HEAD).\n| Lines | base | HEAD |\n| --- | ---: | ---: |\n${COUNTS.map(([label], index) => `| ${label} | ${base[index]} | ${head[index]} |`).join("\n")}\n`;
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/guardrails/files.ts
|
|
159
|
+
const SIZE_EXCLUSIONS = [
|
|
160
|
+
{
|
|
161
|
+
kind: "generated",
|
|
162
|
+
patterns: [
|
|
163
|
+
...IGNORE_PATTERNS,
|
|
164
|
+
...AGENT_IGNORE_PATTERNS,
|
|
165
|
+
"**/*.generated.*",
|
|
166
|
+
"**/generated/**",
|
|
167
|
+
"**/__snapshots__/**"
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
kind: "fixtures",
|
|
172
|
+
patterns: ["**/fixtures/**", "**/__fixtures__/**"]
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
kind: "data",
|
|
176
|
+
patterns: [
|
|
177
|
+
"**/locales/**/*.json",
|
|
178
|
+
"**/i18n/**/*.json",
|
|
179
|
+
"**/blocks.json",
|
|
180
|
+
"**/schema.json",
|
|
181
|
+
"**/evals/**/cases.json"
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
kind: "lockfiles",
|
|
186
|
+
patterns: [
|
|
187
|
+
"**/{pnpm-lock.yaml,package-lock.json,npm-shrinkwrap.json,yarn.lock,bun.lock,bun.lockb,Cargo.lock,poetry.lock,uv.lock,Gemfile.lock,composer.lock,go.sum}",
|
|
188
|
+
"**/*.lock",
|
|
189
|
+
"**/*.lockb"
|
|
190
|
+
]
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
kind: "documentation",
|
|
194
|
+
patterns: ["**/CHANGELOG.md", "**/*.{md,mdx,markdown,MD}"]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
kind: "assets",
|
|
198
|
+
patterns: ["**/*.{png,jpg,jpeg,gif,webp,avif,ico,svg,bmp,tiff,pdf,mp3,wav,ogg,flac,mp4,webm,mov,avi,woff,woff2,ttf,otf,eot,zip,gz,tgz,bz2,xz,7z,tar,rar,wasm,bin,exe,dll,so,dylib,db,sqlite,sqlite3,icns}"]
|
|
199
|
+
}
|
|
200
|
+
];
|
|
201
|
+
function gitFiles(root) {
|
|
202
|
+
return [...new Set(execFileSync("git", [
|
|
203
|
+
"ls-files",
|
|
204
|
+
"--cached",
|
|
205
|
+
"--others",
|
|
206
|
+
"--exclude-standard",
|
|
207
|
+
"-z"
|
|
208
|
+
], {
|
|
209
|
+
cwd: root,
|
|
210
|
+
encoding: "utf8"
|
|
211
|
+
}).split("\0").filter(Boolean))];
|
|
212
|
+
}
|
|
213
|
+
function repositoryRoot(cwd = process.cwd()) {
|
|
214
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
215
|
+
cwd,
|
|
216
|
+
encoding: "utf8"
|
|
217
|
+
}).trim();
|
|
218
|
+
}
|
|
219
|
+
/** Resolves a user-supplied revision to a commit id, so it can never read as an option. */
|
|
220
|
+
function resolveRevision(root, revision) {
|
|
221
|
+
return execFileSync("git", [
|
|
222
|
+
"rev-parse",
|
|
223
|
+
"--verify",
|
|
224
|
+
"--end-of-options",
|
|
225
|
+
`${revision}^{commit}`
|
|
226
|
+
], {
|
|
227
|
+
cwd: root,
|
|
228
|
+
encoding: "utf8",
|
|
229
|
+
stdio: [
|
|
230
|
+
"ignore",
|
|
231
|
+
"pipe",
|
|
232
|
+
"pipe"
|
|
233
|
+
]
|
|
234
|
+
}).trim();
|
|
235
|
+
}
|
|
236
|
+
function repositoryPath(root, file) {
|
|
237
|
+
return relative(root, resolve(root, file)).split("\\").join("/");
|
|
238
|
+
}
|
|
239
|
+
function fileBytes(root, file, staged = false) {
|
|
240
|
+
if (staged) {
|
|
241
|
+
const result = spawnSync("git", [
|
|
242
|
+
"cat-file",
|
|
243
|
+
"-s",
|
|
244
|
+
`:${file}`
|
|
245
|
+
], {
|
|
246
|
+
cwd: root,
|
|
247
|
+
encoding: "utf8"
|
|
248
|
+
});
|
|
249
|
+
if (result.error) throw result.error;
|
|
250
|
+
return result.status === 0 ? Number(result.stdout.trim()) : void 0;
|
|
251
|
+
}
|
|
252
|
+
const path = join(root, file);
|
|
253
|
+
if (!existsSync(path)) return void 0;
|
|
254
|
+
const stat = lstatSync(path);
|
|
255
|
+
return stat.isFile() ? stat.size : void 0;
|
|
256
|
+
}
|
|
257
|
+
/** A package root is the directory of a tracked manifest outside every `src` directory; its top-level `build` and `output` are generated output. */
|
|
258
|
+
function packageOutputPatterns(root) {
|
|
259
|
+
return trackedManifests(root).flatMap((manifest) => {
|
|
260
|
+
const directory = dirname(manifest);
|
|
261
|
+
if (directory === "." || directory.split("/").includes("src")) return [];
|
|
262
|
+
return [`${directory}/build/**`, `${directory}/output/**`];
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/** `path.matchesGlob` never lets `*` or `**` cross a segment starting with a dot, so each path is also tried with those dots removed. */
|
|
266
|
+
function isExcluded(file, packageOutputs) {
|
|
267
|
+
const undotted = file.replace(/(^|\/)\./gu, "$1");
|
|
268
|
+
const matches = (pattern) => matchesGlob(file, pattern) || matchesGlob(undotted, pattern);
|
|
269
|
+
return SIZE_EXCLUSIONS.some(({ patterns }) => patterns.some(matches)) || packageOutputs.some(matches);
|
|
270
|
+
}
|
|
271
|
+
function isBinary(root, file, staged) {
|
|
272
|
+
if (staged) return execFileSync("git", ["show", `:${file}`], {
|
|
273
|
+
cwd: root,
|
|
274
|
+
maxBuffer: fileBytes(root, file, true)
|
|
275
|
+
}).subarray(0, 8e3).includes(0);
|
|
276
|
+
const descriptor = openSync(join(root, file), "r");
|
|
277
|
+
try {
|
|
278
|
+
const prefix = Buffer.alloc(8e3);
|
|
279
|
+
const length = readSync(descriptor, prefix, 0, prefix.length, 0);
|
|
280
|
+
return prefix.subarray(0, length).includes(0);
|
|
281
|
+
} finally {
|
|
282
|
+
closeSync(descriptor);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function authoredSizes(root, files = gitFiles(root), staged = false) {
|
|
286
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
287
|
+
const packageOutputs = packageOutputPatterns(root);
|
|
288
|
+
for (const input of files) {
|
|
289
|
+
const file = repositoryPath(root, input);
|
|
290
|
+
if (isExcluded(file, packageOutputs)) continue;
|
|
291
|
+
const size = fileBytes(root, file, staged);
|
|
292
|
+
if (size !== void 0 && size > 2e4 && isBinary(root, file, staged)) continue;
|
|
293
|
+
if (size !== void 0) sizes.set(file, size);
|
|
294
|
+
}
|
|
295
|
+
return sizes;
|
|
296
|
+
}
|
|
297
|
+
//#endregion
|
|
298
|
+
export { resolveRevision as a, revisionCounts as c, repositoryRoot as i, trackedManifests as l, gitFiles as n, DIRECTORY_LINK as o, repositoryPath as r, compareRevisions as s, authoredSizes as t, runCli as u };
|
package/dist/oxc/fmt.mjs
CHANGED
package/dist/oxc/lint.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { i as findGitRoot
|
|
1
|
+
import { i as findGitRoot } from "../ceilings-BSw9p4YB.mjs";
|
|
2
|
+
import { t as readCarriedCeilings } from "../renames-BGO4dWJ8.mjs";
|
|
2
3
|
import { n as TEST_FILE_GLOBS, t as ASSERTION_FUNCTION_NAMES } from "../shared-WC0z3y9F.mjs";
|
|
3
|
-
import { n as IGNORE_PATTERNS, t as AGENT_IGNORE_PATTERNS } from "../shared-
|
|
4
|
+
import { n as IGNORE_PATTERNS, t as AGENT_IGNORE_PATTERNS } from "../shared-CsXIMK3I.mjs";
|
|
4
5
|
import { defineConfig } from "oxlint";
|
|
5
6
|
//#region src/oxc/lint.ts
|
|
6
7
|
/**
|
|
@@ -179,7 +180,7 @@ function importSortingPreset() {
|
|
|
179
180
|
/** Overrides run after rules, including test-specific rules; measurement bypasses all ceilings. */
|
|
180
181
|
function ratchetOverrides() {
|
|
181
182
|
if (process.env["ANTELOPEJS_RATCHET_MEASURE"] !== void 0) return [];
|
|
182
|
-
const ceilings =
|
|
183
|
+
const ceilings = readCarriedCeilings(findGitRoot());
|
|
183
184
|
const rules = Object.fromEntries(Object.keys(ceilings.rules).map((rule) => [rule, "off"]));
|
|
184
185
|
return [...Object.keys(ceilings.oversizeFiles).map((path) => ({
|
|
185
186
|
files: [path.replaceAll(/([*?[\]{}])/gu, "\\$1")],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { a as formatCeilings, n as MAX_FILE_BYTES, o as isRetired, r as MEASURE_ENV,
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
1
|
+
import { a as formatCeilings, n as MAX_FILE_BYTES, o as isRetired, r as MEASURE_ENV, t as CEILINGS_FILE } from "./ceilings-BSw9p4YB.mjs";
|
|
2
|
+
import { t as readCarriedCeilings } from "./renames-BGO4dWJ8.mjs";
|
|
3
|
+
import { n as gitFiles, r as repositoryPath, t as authoredSizes } from "./files-jKlueTMd.mjs";
|
|
4
|
+
import { n as sizeFailure } from "./size-BdYmv57u.mjs";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
6
|
import { existsSync, mkdtempSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
7
|
import { basename, dirname, join } from "node:path";
|
|
@@ -50,7 +51,7 @@ function lintDiagnostics(root, config, files) {
|
|
|
50
51
|
writeFileSync(path, config ?? "");
|
|
51
52
|
args.push("--config", path, "--ignore-pattern", `${basename(temporary)}/**`);
|
|
52
53
|
}
|
|
53
|
-
if (files) args.push("--", ...files);
|
|
54
|
+
if (files) args.push("--no-error-on-unmatched-pattern", "--", ...files);
|
|
54
55
|
const result = spawnSync(process.execPath, args, {
|
|
55
56
|
cwd: root,
|
|
56
57
|
encoding: "utf8",
|
|
@@ -137,9 +138,9 @@ function lowerCeilings(ceilings, current) {
|
|
|
137
138
|
}
|
|
138
139
|
return lowered;
|
|
139
140
|
}
|
|
140
|
-
function ratchet(root, update = false) {
|
|
141
|
+
function ratchet(root, update = false, base = "HEAD") {
|
|
141
142
|
if (!existsSync(join(root, "guardrails.ratchet.json"))) throw new Error(`${CEILINGS_FILE} is missing. Run antelopejs-sync first.`);
|
|
142
|
-
const ceilings =
|
|
143
|
+
const ceilings = readCarriedCeilings(root, false, [base]);
|
|
143
144
|
const diagnostics = lintDiagnostics(root);
|
|
144
145
|
const current = baseline(root, diagnostics);
|
|
145
146
|
const changed = changedFiles(root);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { s as readCeilings } from "./ceilings-BSw9p4YB.mjs";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
//#region src/guardrails/renames.ts
|
|
4
|
+
/**
|
|
5
|
+
* Renames git detects, as new path to old path. Without `revisions` it
|
|
6
|
+
* compares the index with HEAD when `staged`, otherwise the working tree with
|
|
7
|
+
* HEAD; with revisions it compares those. Copies are never reported, and a
|
|
8
|
+
* repository without HEAD has no renames.
|
|
9
|
+
*/
|
|
10
|
+
function renamedPaths(root, staged = false, revisions = staged ? [] : ["HEAD"]) {
|
|
11
|
+
const result = spawnSync("git", [
|
|
12
|
+
"diff",
|
|
13
|
+
"--find-renames",
|
|
14
|
+
"--diff-filter=R",
|
|
15
|
+
"--name-status",
|
|
16
|
+
"-z",
|
|
17
|
+
"--no-ext-diff",
|
|
18
|
+
...staged ? ["--cached"] : [],
|
|
19
|
+
...revisions,
|
|
20
|
+
"--"
|
|
21
|
+
], {
|
|
22
|
+
cwd: root,
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
maxBuffer: 67108864
|
|
25
|
+
});
|
|
26
|
+
const renames = /* @__PURE__ */ new Map();
|
|
27
|
+
if (result.error || result.status !== 0) return renames;
|
|
28
|
+
const fields = result.stdout.split("\0");
|
|
29
|
+
for (let index = 0; index + 2 < fields.length; index += 3) renames.set(fields[index + 2], fields[index + 1]);
|
|
30
|
+
return renames;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Moves the oversize allowance of each renamed file to its new path. The new
|
|
34
|
+
* path never gets more than the old allowance; a copy or a new file has no
|
|
35
|
+
* rename entry and so inherits nothing.
|
|
36
|
+
*/
|
|
37
|
+
function carryAllowances(ceilings, renames) {
|
|
38
|
+
const oversizeFiles = { ...ceilings.oversizeFiles };
|
|
39
|
+
const moved = [];
|
|
40
|
+
for (const [file, previous] of renames) {
|
|
41
|
+
const allowance = ceilings.oversizeFiles[previous];
|
|
42
|
+
if (allowance === void 0) continue;
|
|
43
|
+
delete oversizeFiles[previous];
|
|
44
|
+
moved.push([file, Math.min(allowance, ceilings.oversizeFiles[file] ?? allowance)]);
|
|
45
|
+
}
|
|
46
|
+
for (const [file, allowance] of moved) oversizeFiles[file] = allowance;
|
|
47
|
+
return {
|
|
48
|
+
rules: ceilings.rules,
|
|
49
|
+
oversizeFiles
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** The ceilings at `root` with allowances carried across the renames in `staged` or working-tree scope. */
|
|
53
|
+
function readCarriedCeilings(root, staged = false, revisions) {
|
|
54
|
+
const ceilings = readCeilings(root);
|
|
55
|
+
if (Object.keys(ceilings.oversizeFiles).length === 0) return ceilings;
|
|
56
|
+
return carryAllowances(ceilings, renamedPaths(root, staged, revisions));
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { renamedPaths as n, readCarriedCeilings as t };
|
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
* Paths every AntelopeJS project generates or vendors, and that no linter should read.
|
|
4
4
|
* `.github/ISSUE_TEMPLATE/**` holds GitHub's legacy issue templates: Markdown with
|
|
5
5
|
* YAML frontmatter saved under a .yml extension, which a YAML formatter cannot parse.
|
|
6
|
+
* `build/**` and `output/**` match only at the repository root: a directory with
|
|
7
|
+
* either name under `src/` or another authored root is source, not build output.
|
|
6
8
|
*/
|
|
7
9
|
const IGNORE_PATTERNS = [
|
|
8
10
|
"**/node_modules/**",
|
|
9
11
|
"**/dist/**",
|
|
10
|
-
"**/build/**",
|
|
11
12
|
"**/coverage/**",
|
|
12
13
|
"**/.antelope/**",
|
|
13
14
|
"**/.nuxt/**",
|
|
14
15
|
"**/.output/**",
|
|
15
|
-
"
|
|
16
|
+
"build/**",
|
|
17
|
+
"output/**",
|
|
16
18
|
"**/i18n-registry.generated.ts",
|
|
17
19
|
".github/ISSUE_TEMPLATE/**"
|
|
18
20
|
];
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { t as
|
|
1
|
+
import "./ceilings-BSw9p4YB.mjs";
|
|
2
|
+
import { t as readCarriedCeilings } from "./renames-BGO4dWJ8.mjs";
|
|
3
|
+
import { t as authoredSizes } from "./files-jKlueTMd.mjs";
|
|
3
4
|
//#region src/guardrails/size.ts
|
|
4
5
|
function sizeFailure(file, size, limit) {
|
|
5
6
|
return `${file}: ${size} bytes, ${size - limit} bytes over the ${limit}-byte ceiling. Split by responsibility into sibling modules, one cohesive group of exports each; keep the original path re-exporting only if it is a public entry point. Split a test file by behaviour under test, one file per describe. Do not minify, strip whitespace, or move code under an excluded path to pass.`;
|
|
6
7
|
}
|
|
7
8
|
function checkSize(root, files, staged = false) {
|
|
8
|
-
const ceilings =
|
|
9
|
+
const ceilings = readCarriedCeilings(root, staged);
|
|
9
10
|
const failures = [];
|
|
10
11
|
for (const [file, size] of authoredSizes(root, files, staged)) {
|
|
11
12
|
const limit = ceilings.oversizeFiles[file] ?? 2e4;
|
|
@@ -1,106 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { l as trackedManifests } from "./files-jKlueTMd.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { existsSync, mkdirSync,
|
|
4
|
-
import { dirname, join, matchesGlob, relative, sep } from "node:path";
|
|
5
|
-
import { execFileSync, spawnSync } from "node:child_process";
|
|
6
|
-
import { tmpdir } from "node:os";
|
|
7
|
-
//#region src/guardrails/revisions.ts
|
|
8
|
-
function trackedManifests(root) {
|
|
9
|
-
return execFileSync("git", ["ls-files", "-z"], {
|
|
10
|
-
cwd: root,
|
|
11
|
-
encoding: "utf8"
|
|
12
|
-
}).split("\0").filter((file) => file === "package.json" || file.endsWith("/package.json"));
|
|
13
|
-
}
|
|
14
|
-
function linkDependencies(root, worktree) {
|
|
15
|
-
const directories = /* @__PURE__ */ new Set([".", ...trackedManifests(root).map(dirname)]);
|
|
16
|
-
for (const directory of directories) {
|
|
17
|
-
const source = join(root, directory, "node_modules");
|
|
18
|
-
const target = join(worktree, directory, "node_modules");
|
|
19
|
-
if (existsSync(source) && existsSync(dirname(target)) && !existsSync(target)) symlinkSync(source, target, "dir");
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
/** Compares isolated committed revisions and removes both worktrees even when compilation fails. */
|
|
23
|
-
function compareRevisions(root, baseRef, label, emit) {
|
|
24
|
-
const temporary = mkdtempSync(join(tmpdir(), "antelope-proof-"));
|
|
25
|
-
const worktrees = [];
|
|
26
|
-
const results = [];
|
|
27
|
-
try {
|
|
28
|
-
for (const [side, ref] of [["base", baseRef], ["head", "HEAD"]]) {
|
|
29
|
-
const revision = execFileSync("git", [
|
|
30
|
-
"rev-parse",
|
|
31
|
-
"--verify",
|
|
32
|
-
"--end-of-options",
|
|
33
|
-
`${ref}^{commit}`
|
|
34
|
-
], {
|
|
35
|
-
cwd: root,
|
|
36
|
-
encoding: "utf8",
|
|
37
|
-
stdio: [
|
|
38
|
-
"ignore",
|
|
39
|
-
"pipe",
|
|
40
|
-
"pipe"
|
|
41
|
-
]
|
|
42
|
-
}).trim();
|
|
43
|
-
const worktree = join(temporary, "worktrees", side);
|
|
44
|
-
execFileSync("git", [
|
|
45
|
-
"worktree",
|
|
46
|
-
"add",
|
|
47
|
-
"--quiet",
|
|
48
|
-
"--detach",
|
|
49
|
-
worktree,
|
|
50
|
-
revision
|
|
51
|
-
], {
|
|
52
|
-
cwd: root,
|
|
53
|
-
stdio: [
|
|
54
|
-
"ignore",
|
|
55
|
-
"pipe",
|
|
56
|
-
"pipe"
|
|
57
|
-
]
|
|
58
|
-
});
|
|
59
|
-
worktrees.push(worktree);
|
|
60
|
-
linkDependencies(root, worktree);
|
|
61
|
-
const output = join(temporary, side);
|
|
62
|
-
mkdirSync(output, { recursive: true });
|
|
63
|
-
results.push(emit(worktree, output, join(temporary, "emit", side)));
|
|
64
|
-
}
|
|
65
|
-
const diff = spawnSync("git", [
|
|
66
|
-
"diff",
|
|
67
|
-
"--no-index",
|
|
68
|
-
"--no-color",
|
|
69
|
-
"--no-renames",
|
|
70
|
-
"--no-ext-diff",
|
|
71
|
-
"--no-textconv",
|
|
72
|
-
"--",
|
|
73
|
-
"base",
|
|
74
|
-
"head"
|
|
75
|
-
], {
|
|
76
|
-
cwd: temporary,
|
|
77
|
-
encoding: "utf8",
|
|
78
|
-
maxBuffer: 67108864
|
|
79
|
-
});
|
|
80
|
-
if (diff.error) throw diff.error;
|
|
81
|
-
if (diff.status !== 0 && diff.status !== 1) throw new Error(diff.stderr || "Could not compare revision outputs.");
|
|
82
|
-
const packages = new Set(results.flatMap((result) => result.packages)).size;
|
|
83
|
-
return {
|
|
84
|
-
different: diff.status === 1,
|
|
85
|
-
output: diff.status === 1 ? `${label}: differences against ${baseRef} (committed HEAD).\n${diff.stdout}` : `${label}: ${packages} packages, ${results[1].entries} entries unchanged against ${baseRef} (committed HEAD).\n`
|
|
86
|
-
};
|
|
87
|
-
} finally {
|
|
88
|
-
for (const worktree of worktrees.reverse()) execFileSync("git", [
|
|
89
|
-
"worktree",
|
|
90
|
-
"remove",
|
|
91
|
-
"--force",
|
|
92
|
-
worktree
|
|
93
|
-
], {
|
|
94
|
-
cwd: root,
|
|
95
|
-
stdio: "pipe"
|
|
96
|
-
});
|
|
97
|
-
rmSync(temporary, {
|
|
98
|
-
recursive: true,
|
|
99
|
-
force: true
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
//#endregion
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join, matchesGlob, relative, resolve, sep } from "node:path";
|
|
104
5
|
//#region src/guardrails/typescript.ts
|
|
105
6
|
function packagesAt(root, publishedOnly) {
|
|
106
7
|
return trackedManifests(root).flatMap((file) => {
|
|
@@ -118,9 +19,16 @@ function packagesAt(root, publishedOnly) {
|
|
|
118
19
|
function loadTypeScript(directory) {
|
|
119
20
|
return createRequire(join(directory, "package.json"))("typescript");
|
|
120
21
|
}
|
|
22
|
+
const TEST_SOURCE_GLOBS = [
|
|
23
|
+
"**/*.test.*",
|
|
24
|
+
"**/*.spec.*",
|
|
25
|
+
"**/__tests__/**",
|
|
26
|
+
"test/**",
|
|
27
|
+
"tests/**"
|
|
28
|
+
];
|
|
121
29
|
function isTestSource(root, file) {
|
|
122
30
|
const path = relative(root, file).split("\\").join("/");
|
|
123
|
-
return
|
|
31
|
+
return TEST_SOURCE_GLOBS.some((pattern) => matchesGlob(path, pattern));
|
|
124
32
|
}
|
|
125
33
|
function failDiagnostics(compiler, diagnostics) {
|
|
126
34
|
const errors = diagnostics.filter((item) => item.category === compiler.DiagnosticCategory.Error);
|
|
@@ -149,14 +57,24 @@ function copyDeclarations(program, directory, output) {
|
|
|
149
57
|
}
|
|
150
58
|
return files;
|
|
151
59
|
}
|
|
152
|
-
|
|
153
|
-
function emitPackage(directory, output, declarations) {
|
|
60
|
+
function parseConfig(directory) {
|
|
154
61
|
const compiler = loadTypeScript(directory);
|
|
155
|
-
const
|
|
156
|
-
const config = compiler.readConfigFile(configPath, compiler.sys.readFile);
|
|
62
|
+
const config = compiler.readConfigFile(join(directory, "tsconfig.json"), compiler.sys.readFile);
|
|
157
63
|
if (config.error) failDiagnostics(compiler, [config.error]);
|
|
158
64
|
const parsed = compiler.parseJsonConfigFileContent(config.config, compiler.sys, directory);
|
|
159
65
|
failDiagnostics(compiler, parsed.errors);
|
|
66
|
+
return {
|
|
67
|
+
compiler,
|
|
68
|
+
parsed
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** The configured `outDir`, the root under which the declaration half already judges a built path as test output. */
|
|
72
|
+
function outputRoot(directory) {
|
|
73
|
+
return resolve(directory, parseConfig(directory).parsed.options.outDir ?? ".");
|
|
74
|
+
}
|
|
75
|
+
/** Emits with the package's compiler and config; test roots and outputs never enter either proof. */
|
|
76
|
+
function emitPackage(directory, output, declarations) {
|
|
77
|
+
const { compiler, parsed } = parseConfig(directory);
|
|
160
78
|
const options = {
|
|
161
79
|
...parsed.options,
|
|
162
80
|
rootDir: parsed.options.rootDir ?? (parsed.options.outDir ? void 0 : directory),
|
|
@@ -199,4 +117,4 @@ function emitPackage(directory, output, declarations) {
|
|
|
199
117
|
};
|
|
200
118
|
}
|
|
201
119
|
//#endregion
|
|
202
|
-
export {
|
|
120
|
+
export { packagesAt as i, isTestSource as n, outputRoot as r, emitPackage as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/tooling-configs",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Shared tooling presets for AntelopeJS repositories: oxlint (with the vendored anti-slop rules), oxfmt, ESLint for Nuxt layers, Knip, and an interface range check",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelopejs",
|
package/dist/files-DAD5OtfV.mjs
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import "./ceilings-BSw9p4YB.mjs";
|
|
2
|
-
import { n as IGNORE_PATTERNS, t as AGENT_IGNORE_PATTERNS } from "./shared-NC0T8ykm.mjs";
|
|
3
|
-
import { closeSync, existsSync, lstatSync, openSync, readSync } from "node:fs";
|
|
4
|
-
import { join, matchesGlob, relative, resolve } from "node:path";
|
|
5
|
-
import { execFileSync, spawnSync } from "node:child_process";
|
|
6
|
-
//#region src/guardrails/cli.ts
|
|
7
|
-
function runCli(main) {
|
|
8
|
-
try {
|
|
9
|
-
process.exitCode = main();
|
|
10
|
-
} catch (error) {
|
|
11
|
-
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
12
|
-
process.exitCode = 1;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
//#endregion
|
|
16
|
-
//#region src/guardrails/files.ts
|
|
17
|
-
const SIZE_EXCLUSIONS = [
|
|
18
|
-
{
|
|
19
|
-
kind: "generated",
|
|
20
|
-
patterns: [
|
|
21
|
-
...IGNORE_PATTERNS,
|
|
22
|
-
...AGENT_IGNORE_PATTERNS,
|
|
23
|
-
"**/*.generated.*",
|
|
24
|
-
"**/generated/**",
|
|
25
|
-
"**/__snapshots__/**"
|
|
26
|
-
]
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
kind: "fixtures",
|
|
30
|
-
patterns: ["**/fixtures/**", "**/__fixtures__/**"]
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
kind: "data",
|
|
34
|
-
patterns: [
|
|
35
|
-
"**/locales/**/*.json",
|
|
36
|
-
"**/i18n/**/*.json",
|
|
37
|
-
"**/blocks.json",
|
|
38
|
-
"**/schema.json",
|
|
39
|
-
"**/evals/**/cases.json"
|
|
40
|
-
]
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
kind: "lockfiles",
|
|
44
|
-
patterns: [
|
|
45
|
-
"**/{pnpm-lock.yaml,package-lock.json,npm-shrinkwrap.json,yarn.lock,bun.lock,bun.lockb,Cargo.lock,poetry.lock,uv.lock,Gemfile.lock,composer.lock,go.sum}",
|
|
46
|
-
"**/*.lock",
|
|
47
|
-
"**/*.lockb"
|
|
48
|
-
]
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
kind: "documentation",
|
|
52
|
-
patterns: ["**/CHANGELOG.md", "**/*.{md,mdx,markdown,MD}"]
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
kind: "assets",
|
|
56
|
-
patterns: ["**/*.{png,jpg,jpeg,gif,webp,avif,ico,svg,bmp,tiff,pdf,mp3,wav,ogg,flac,mp4,webm,mov,avi,woff,woff2,ttf,otf,eot,zip,gz,tgz,bz2,xz,7z,tar,rar,wasm,bin,exe,dll,so,dylib,db,sqlite,sqlite3,icns}"]
|
|
57
|
-
}
|
|
58
|
-
];
|
|
59
|
-
function gitFiles(root) {
|
|
60
|
-
return [...new Set(execFileSync("git", [
|
|
61
|
-
"ls-files",
|
|
62
|
-
"--cached",
|
|
63
|
-
"--others",
|
|
64
|
-
"--exclude-standard",
|
|
65
|
-
"-z"
|
|
66
|
-
], {
|
|
67
|
-
cwd: root,
|
|
68
|
-
encoding: "utf8"
|
|
69
|
-
}).split("\0").filter(Boolean))];
|
|
70
|
-
}
|
|
71
|
-
function repositoryRoot(cwd = process.cwd()) {
|
|
72
|
-
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
73
|
-
cwd,
|
|
74
|
-
encoding: "utf8"
|
|
75
|
-
}).trim();
|
|
76
|
-
}
|
|
77
|
-
function repositoryPath(root, file) {
|
|
78
|
-
return relative(root, resolve(root, file)).split("\\").join("/");
|
|
79
|
-
}
|
|
80
|
-
function fileBytes(root, file, staged = false) {
|
|
81
|
-
if (staged) {
|
|
82
|
-
const result = spawnSync("git", [
|
|
83
|
-
"cat-file",
|
|
84
|
-
"-s",
|
|
85
|
-
`:${file}`
|
|
86
|
-
], {
|
|
87
|
-
cwd: root,
|
|
88
|
-
encoding: "utf8"
|
|
89
|
-
});
|
|
90
|
-
if (result.error) throw result.error;
|
|
91
|
-
return result.status === 0 ? Number(result.stdout.trim()) : void 0;
|
|
92
|
-
}
|
|
93
|
-
const path = join(root, file);
|
|
94
|
-
if (!existsSync(path)) return void 0;
|
|
95
|
-
const stat = lstatSync(path);
|
|
96
|
-
return stat.isFile() ? stat.size : void 0;
|
|
97
|
-
}
|
|
98
|
-
/** `path.matchesGlob` never lets `*` or `**` cross a segment starting with a dot, so each path is also tried with those dots removed. */
|
|
99
|
-
function isExcluded(file) {
|
|
100
|
-
const undotted = file.replace(/(^|\/)\./gu, "$1");
|
|
101
|
-
return SIZE_EXCLUSIONS.some(({ patterns }) => patterns.some((pattern) => matchesGlob(file, pattern) || matchesGlob(undotted, pattern)));
|
|
102
|
-
}
|
|
103
|
-
function isBinary(root, file, staged) {
|
|
104
|
-
if (staged) return execFileSync("git", ["show", `:${file}`], {
|
|
105
|
-
cwd: root,
|
|
106
|
-
maxBuffer: fileBytes(root, file, true)
|
|
107
|
-
}).subarray(0, 8e3).includes(0);
|
|
108
|
-
const descriptor = openSync(join(root, file), "r");
|
|
109
|
-
try {
|
|
110
|
-
const prefix = Buffer.alloc(8e3);
|
|
111
|
-
const length = readSync(descriptor, prefix, 0, prefix.length, 0);
|
|
112
|
-
return prefix.subarray(0, length).includes(0);
|
|
113
|
-
} finally {
|
|
114
|
-
closeSync(descriptor);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
function authoredSizes(root, files = gitFiles(root), staged = false) {
|
|
118
|
-
const sizes = /* @__PURE__ */ new Map();
|
|
119
|
-
for (const input of files) {
|
|
120
|
-
const file = repositoryPath(root, input);
|
|
121
|
-
if (isExcluded(file)) continue;
|
|
122
|
-
const size = fileBytes(root, file, staged);
|
|
123
|
-
if (size !== void 0 && size > 2e4 && isBinary(root, file, staged)) continue;
|
|
124
|
-
if (size !== void 0) sizes.set(file, size);
|
|
125
|
-
}
|
|
126
|
-
return sizes;
|
|
127
|
-
}
|
|
128
|
-
//#endregion
|
|
129
|
-
export { runCli as a, repositoryRoot as i, gitFiles as n, repositoryPath as r, authoredSizes as t };
|