@antelopejs/tooling-configs 0.2.1 → 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 +26 -5
- package/dist/bin/api-diff.d.mts +1 -0
- package/dist/bin/api-diff.mjs +360 -0
- package/dist/bin/check-size.mjs +2 -2
- package/dist/bin/checks.mjs +8 -5
- package/dist/bin/comment-proof.d.mts +1 -0
- package/dist/bin/comment-proof.mjs +43 -0
- package/dist/bin/ratchet.mjs +14 -7
- package/dist/bin/sync.mjs +138 -20
- package/dist/{ceilings--pJmhSde.mjs → ceilings-BSw9p4YB.mjs} +5 -1
- package/dist/files-jKlueTMd.mjs +298 -0
- package/dist/oxc/fmt.mjs +1 -1
- package/dist/oxc/lint.mjs +4 -3
- package/dist/{package-B1aipTbn.mjs → package-Ckd7e4tA.mjs} +4 -0
- package/dist/{ratchet-B2-4XxAl.mjs → ratchet-CNbpU42F.mjs} +18 -12
- package/dist/renames-BGO4dWJ8.mjs +59 -0
- package/dist/{shared-NC0T8ykm.mjs → shared-CsXIMK3I.mjs} +4 -2
- package/dist/{size-DJ7YSry_.mjs → size-BdYmv57u.mjs} +4 -3
- package/dist/typescript-aLCrMi2y.mjs +120 -0
- package/package.json +4 -3
- package/dist/files-Bocv-qA8.mjs +0 -103
package/README.md
CHANGED
|
@@ -29,6 +29,8 @@ The AntelopeJS guardrail pack: shared lint, format and dead-code presets, the co
|
|
|
29
29
|
| bin | `antelopejs-checks` | Runs every check and prints only the failures |
|
|
30
30
|
| bin | `antelopejs-ratchet` | Fails when recorded debt grows; `--update` lowers the record |
|
|
31
31
|
| bin | `antelopejs-check-size` | Fails on authored files over 20,000 bytes and on blanket disable directives |
|
|
32
|
+
| bin | `antelopejs-api-diff <base-ref>` | Compares published declaration bundles against committed HEAD |
|
|
33
|
+
| bin | `antelopejs-comment-proof <base-ref>` | Compares comment-free JavaScript emit against committed HEAD |
|
|
32
34
|
| bin | `antelopejs-check-interface-ranges` | Fails on interface ranges that break module startup |
|
|
33
35
|
|
|
34
36
|
## Install
|
|
@@ -153,11 +155,11 @@ pnpm install
|
|
|
153
155
|
| `.agents/setup` | Orb setup: switches to the `.nvmrc` Node, activates `packageManager` through corepack, installs with the frozen lockfile |
|
|
154
156
|
| `AGENTS.md` | Writes the managed block between `<!-- antelopejs-guardrails:start -->` and `:end -->`; text outside the markers stays |
|
|
155
157
|
| `CLAUDE.md` | `@AGENTS.md` |
|
|
156
|
-
| `guardrails.ratchet.json` | Records
|
|
158
|
+
| `guardrails.ratchet.json` | Records non-empty debt on first adoption, before `sync:check` exists |
|
|
157
159
|
|
|
158
160
|
`--profile template` writes only the three preset configs and a user-facing `AGENTS.md` block, for template repositories. `antelopejs-sync --check` writes nothing and exits 1 on any drift; `pnpm run checks` runs it.
|
|
159
161
|
|
|
160
|
-
`pnpm run checks` runs `typecheck`, `lint`, `format:check`, `knip`, `test`, `size
|
|
162
|
+
`pnpm run checks` runs `typecheck`, `lint`, `format:check`, `knip`, `test`, `size` and `sync:check`, plus `ratchet` when `guardrails.ratchet.json` exists. It prints only the output of the steps that failed. Sync adds the ratchet scripts and pre-commit hook only while that file exists.
|
|
161
163
|
|
|
162
164
|
### Ratchet
|
|
163
165
|
|
|
@@ -171,12 +173,31 @@ pnpm install
|
|
|
171
173
|
```
|
|
172
174
|
|
|
173
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.
|
|
174
|
-
- 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.
|
|
175
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.
|
|
176
178
|
|
|
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.
|
|
180
|
+
|
|
181
|
+
`antelopejs-sync --check` rejects an existing retired file and tells you to run `ratchet:update`. Plain sync reports the same note but leaves deletion to the update command.
|
|
182
|
+
|
|
177
183
|
### Size
|
|
178
184
|
|
|
179
|
-
`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.
|
|
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
|
+
|
|
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
|
+
|
|
189
|
+
### Cleanup proof
|
|
190
|
+
|
|
191
|
+
A behaviour-preserving cleanup PR shows `antelopejs-api-diff <base-ref>` passing. It checks each tracked, non-private package with a `tsconfig.json`, using that package's installed TypeScript. It emits declarations without comments, resolves published entries from `exports` (preferring `types`), `main`, `types`/`typings` and `typesVersions`, and bundles each entry's local declaration closure. External packages stay external. Stable top-level ordering ignores declaration moves between internal files while preserving overload order. Compiler errors fail the proof.
|
|
192
|
+
|
|
193
|
+
A comment-only PR shows `antelopejs-comment-proof <base-ref>` passing instead of a verifier. It includes private packages, ignores type diagnostics and compares comment-free JavaScript emit byte for byte. Both commands exclude test sources, fail on added or removed outputs, and print unified diffs for changes.
|
|
194
|
+
|
|
195
|
+
Both commands compare **committed HEAD**, not staged or working-tree edits. They check out the base and HEAD in temporary detached worktrees, reuse the current checkout's root and package `node_modules`, and remove the worktrees afterwards. Install the consumer's dependencies before running them.
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
pnpm exec antelopejs-api-diff origin/main
|
|
199
|
+
pnpm exec antelopejs-comment-proof origin/main
|
|
200
|
+
```
|
|
180
201
|
|
|
181
202
|
## oxfmt
|
|
182
203
|
|
|
@@ -279,7 +300,7 @@ checks that remain the author's responsibility.
|
|
|
279
300
|
|
|
280
301
|
## Contributing
|
|
281
302
|
|
|
282
|
-
See [CONTRIBUTING.md](./CONTRIBUTING.md). `pnpm install`, then `pnpm checks` runs the typecheck, lint, format check, tests, size
|
|
303
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md). `pnpm install`, then `pnpm checks` runs the typecheck, lint, format check, Knip, tests, size and sync checks. Repositories with recorded debt also run the ratchet.
|
|
283
304
|
|
|
284
305
|
## License
|
|
285
306
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
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";
|
|
6
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
7
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
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
|
|
188
|
+
//#region src/guardrails/api-diff.ts
|
|
189
|
+
function entrySegment(value) {
|
|
190
|
+
return encodeURIComponent(value).replaceAll(".", "%2E").replaceAll("*", "%2A");
|
|
191
|
+
}
|
|
192
|
+
function entryPattern(value) {
|
|
193
|
+
return value.split("*").map(entrySegment).join("*");
|
|
194
|
+
}
|
|
195
|
+
function exportTargets(target, entry = "exports/root") {
|
|
196
|
+
if (typeof target === "string") return [{
|
|
197
|
+
entry,
|
|
198
|
+
target
|
|
199
|
+
}];
|
|
200
|
+
if (target == null) return [];
|
|
201
|
+
if (Array.isArray(target)) return target.flatMap((value, index) => exportTargets(value, `${entry}/fallback/${index}`));
|
|
202
|
+
if (target.types !== void 0) return exportTargets(target.types, entry);
|
|
203
|
+
return Object.entries(target).flatMap(([key, value]) => {
|
|
204
|
+
return exportTargets(value, key.startsWith(".") ? key === "." ? "exports/root" : `exports/subpath/${entryPattern(key.slice(2))}` : `${entry}/condition/${entrySegment(key)}`);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function declarationTarget(target) {
|
|
208
|
+
return target.replace(/(?<!\.d)\.(mjs|cjs|js|mts|cts|tsx?|jsx)$/u, (extension) => {
|
|
209
|
+
if (extension === ".mjs" || extension === ".mts") return ".d.mts";
|
|
210
|
+
if (extension === ".cjs" || extension === ".cts") return ".d.cts";
|
|
211
|
+
return ".d.ts";
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
function entryPatterns(manifest) {
|
|
215
|
+
const targets = exportTargets(manifest.exports);
|
|
216
|
+
for (const key of [
|
|
217
|
+
"main",
|
|
218
|
+
"types",
|
|
219
|
+
"typings"
|
|
220
|
+
]) {
|
|
221
|
+
const target = manifest[key];
|
|
222
|
+
if (target !== void 0) targets.push({
|
|
223
|
+
entry: key,
|
|
224
|
+
target
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
for (const [version, mappings] of Object.entries(manifest.typesVersions ?? {})) for (const [entry, paths] of Object.entries(mappings)) for (const [index, target] of paths.entries()) targets.push({
|
|
228
|
+
entry: `typesVersions/${entrySegment(version)}/${entryPattern(entry)}/${index}`,
|
|
229
|
+
target
|
|
230
|
+
});
|
|
231
|
+
return targets.map(({ entry, target }) => ({
|
|
232
|
+
entry,
|
|
233
|
+
target: declarationTarget(target)
|
|
234
|
+
})).filter(({ target }) => /\.d\.[cm]?ts$/u.test(target) || target.includes("*"));
|
|
235
|
+
}
|
|
236
|
+
/** Export stars are substitutions rather than path globs and can span directory separators. */
|
|
237
|
+
function targetPattern(target) {
|
|
238
|
+
return new RegExp(`^${target.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join("(.*)")}$`, "u");
|
|
239
|
+
}
|
|
240
|
+
function publishedEntries(directory, manifest, emitted, scratch) {
|
|
241
|
+
const outputRoot = resolve(directory, emitted.options.outDir ?? ".");
|
|
242
|
+
const files = emitted.files.map((file) => relative(scratch, file).split("\\").join("/"));
|
|
243
|
+
const entries = [];
|
|
244
|
+
for (const { entry, target } of entryPatterns(manifest)) {
|
|
245
|
+
if (isTestSource(outputRoot, resolve(directory, target))) continue;
|
|
246
|
+
const path = relative(outputRoot, resolve(directory, target)).split("\\").join("/");
|
|
247
|
+
const pattern = targetPattern(path);
|
|
248
|
+
const matches = files.filter((file) => pattern.test(file));
|
|
249
|
+
if (!matches.length && !path.includes("*")) throw new Error(`${directory}: published target ${target} has no emitted declaration.`);
|
|
250
|
+
for (const file of matches) {
|
|
251
|
+
const captures = pattern.exec(file)?.slice(1) ?? [];
|
|
252
|
+
let index = 0;
|
|
253
|
+
const name = entry.includes("*") ? entry.replaceAll("*", () => entrySegment(captures[index++] ?? captures[0] ?? "")) : `${entry}${captures.length ? `/file/${entrySegment(file)}` : ""}`;
|
|
254
|
+
entries.push({
|
|
255
|
+
entry: `${name}.d.ts`,
|
|
256
|
+
file
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return entries.sort((a, b) => a.entry.localeCompare(b.entry));
|
|
261
|
+
}
|
|
262
|
+
function statementName(compiler, statement, source) {
|
|
263
|
+
if (compiler.isVariableStatement(statement)) return statement.declarationList.declarations[0]?.name.getText(source) ?? "";
|
|
264
|
+
if (compiler.isFunctionDeclaration(statement) || compiler.isClassDeclaration(statement) || compiler.isInterfaceDeclaration(statement) || compiler.isTypeAliasDeclaration(statement) || compiler.isEnumDeclaration(statement) || compiler.isModuleDeclaration(statement) || compiler.isImportEqualsDeclaration(statement)) return statement.name?.getText(source) ?? "default";
|
|
265
|
+
return statement.getText(source);
|
|
266
|
+
}
|
|
267
|
+
function normalizeBundle(compiler, text) {
|
|
268
|
+
const source = compiler.createSourceFile("bundle.d.ts", text, compiler.ScriptTarget.Latest, true);
|
|
269
|
+
const statements = [...source.statements].sort((a, b) => statementName(compiler, a, source).localeCompare(statementName(compiler, b, source)));
|
|
270
|
+
const normalized = compiler.factory.updateSourceFile(source, statements);
|
|
271
|
+
return compiler.createPrinter({
|
|
272
|
+
removeComments: true,
|
|
273
|
+
newLine: compiler.NewLineKind.LineFeed
|
|
274
|
+
}).printFile(normalized);
|
|
275
|
+
}
|
|
276
|
+
function bundleDependencies(directory, scratch) {
|
|
277
|
+
let current = directory;
|
|
278
|
+
while (!existsSync(join(current, "node_modules"))) {
|
|
279
|
+
const parent = dirname(current);
|
|
280
|
+
if (parent === current) return;
|
|
281
|
+
current = parent;
|
|
282
|
+
}
|
|
283
|
+
symlinkSync(join(current, "node_modules"), join(scratch, "node_modules"), DIRECTORY_LINK);
|
|
284
|
+
}
|
|
285
|
+
/** dts-bundle-generator 9 remaps a .d.mts/.d.cts root twice; a source-named copy preserves its declaration text. */
|
|
286
|
+
function generatorEntry(file) {
|
|
287
|
+
const alias = file.replace(/\.d\.([cm]ts)$/u, ".$1");
|
|
288
|
+
if (alias !== file) copyFileSync(file, alias);
|
|
289
|
+
return alias;
|
|
290
|
+
}
|
|
291
|
+
function bundlePackage(directory, output, scratch, manifest) {
|
|
292
|
+
const emitted = emitPackage(directory, scratch, true);
|
|
293
|
+
const entries = publishedEntries(directory, manifest, emitted, scratch);
|
|
294
|
+
if (!entries.length) return 0;
|
|
295
|
+
bundleDependencies(directory, scratch);
|
|
296
|
+
writeFileSync(join(scratch, "package.json"), readFileSync(join(directory, "package.json")));
|
|
297
|
+
const config = join(scratch, "tsconfig.json");
|
|
298
|
+
writeFileSync(config, JSON.stringify({
|
|
299
|
+
extends: join(directory, "tsconfig.json"),
|
|
300
|
+
compilerOptions: {
|
|
301
|
+
composite: false,
|
|
302
|
+
incremental: false,
|
|
303
|
+
noEmit: false,
|
|
304
|
+
rootDir: scratch
|
|
305
|
+
},
|
|
306
|
+
files: entries.map(({ file }) => join(scratch, file)),
|
|
307
|
+
include: []
|
|
308
|
+
}));
|
|
309
|
+
const bundles = generateDtsBundle(entries.map(({ file }) => ({
|
|
310
|
+
filePath: generatorEntry(join(scratch, file)),
|
|
311
|
+
output: {
|
|
312
|
+
exportReferencedTypes: false,
|
|
313
|
+
noBanner: true
|
|
314
|
+
},
|
|
315
|
+
libraries: { inlinedLibraries: [] }
|
|
316
|
+
})), { preferredConfigPath: config });
|
|
317
|
+
for (const [index, { entry }] of entries.entries()) {
|
|
318
|
+
const path = join(output, entry);
|
|
319
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
320
|
+
writeFileSync(path, normalizeBundle(emitted.compiler, bundles[index]));
|
|
321
|
+
}
|
|
322
|
+
return entries.length;
|
|
323
|
+
}
|
|
324
|
+
function declarationDiff(root, baseRef) {
|
|
325
|
+
return compareRevisions(root, baseRef, "API", (worktree, output, scratch) => {
|
|
326
|
+
const packages = packagesAt(worktree, true);
|
|
327
|
+
let entries = 0;
|
|
328
|
+
for (const { directory, manifest } of packages) entries += bundlePackage(join(worktree, directory), join(output, directory === "." ? "_root" : directory), join(scratch, directory === "." ? "_root" : directory), manifest);
|
|
329
|
+
return {
|
|
330
|
+
packages: packages.map(({ directory }) => directory),
|
|
331
|
+
entries
|
|
332
|
+
};
|
|
333
|
+
});
|
|
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
|
+
}
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/bin/api-diff.ts
|
|
352
|
+
runCli(() => {
|
|
353
|
+
const args = process.argv.slice(2);
|
|
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]);
|
|
356
|
+
process.stdout.write(result.output);
|
|
357
|
+
return result.different ? 1 : 0;
|
|
358
|
+
});
|
|
359
|
+
//#endregion
|
|
360
|
+
export {};
|
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,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import { i as
|
|
4
|
-
import {
|
|
2
|
+
import "../ceilings-BSw9p4YB.mjs";
|
|
3
|
+
import { i as repositoryRoot, u as runCli } from "../files-jKlueTMd.mjs";
|
|
4
|
+
import { i as parseManifest, r as packageManager } from "../package-Ckd7e4tA.mjs";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
6
|
import { join } from "node:path";
|
|
6
7
|
import { spawnSync } from "node:child_process";
|
|
7
8
|
//#region src/guardrails/checks.ts
|
|
@@ -19,7 +20,8 @@ function runChecks(root) {
|
|
|
19
20
|
const manifest = parseManifest(readFileSync(join(root, "package.json"), "utf8"));
|
|
20
21
|
const manager = packageManager(manifest);
|
|
21
22
|
const failures = [];
|
|
22
|
-
|
|
23
|
+
const steps = CHECK_STEPS.filter((step) => step !== "ratchet" || existsSync(join(root, "guardrails.ratchet.json")));
|
|
24
|
+
for (const step of steps) {
|
|
23
25
|
if (!manifest.scripts?.[step]) {
|
|
24
26
|
failures.push(`[${step}] Missing package.json script "${step}". Run antelopejs-sync.\n`);
|
|
25
27
|
continue;
|
|
@@ -27,6 +29,7 @@ function runChecks(root) {
|
|
|
27
29
|
const result = spawnSync(manager, ["run", step], {
|
|
28
30
|
cwd: root,
|
|
29
31
|
encoding: "utf8",
|
|
32
|
+
shell: process.platform === "win32",
|
|
30
33
|
env: {
|
|
31
34
|
...process.env,
|
|
32
35
|
CI: "1"
|
|
@@ -37,7 +40,7 @@ function runChecks(root) {
|
|
|
37
40
|
}
|
|
38
41
|
return {
|
|
39
42
|
failed: failures.length,
|
|
40
|
-
output: `${failures.join("\n")}Checks: ${
|
|
43
|
+
output: `${failures.join("\n")}Checks: ${steps.length - failures.length} passed, ${failures.length} failed.\n`
|
|
41
44
|
};
|
|
42
45
|
}
|
|
43
46
|
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
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
|
+
import { copyFileSync, mkdirSync } from "node:fs";
|
|
5
|
+
import { dirname, join, relative } from "node:path";
|
|
6
|
+
//#region src/guardrails/comment-proof.ts
|
|
7
|
+
/** Proves comment-free JavaScript emit matches, including private packages and excluding tests. */
|
|
8
|
+
function commentProof(root, baseRef) {
|
|
9
|
+
const result = compareRevisions(root, baseRef, "Comment proof", (worktree, output, scratch) => {
|
|
10
|
+
const packages = packagesAt(worktree, false);
|
|
11
|
+
let entries = 0;
|
|
12
|
+
for (const { directory } of packages) {
|
|
13
|
+
const key = directory === "." ? "_root" : directory;
|
|
14
|
+
const emittedRoot = join(scratch, key);
|
|
15
|
+
const emitted = emitPackage(join(worktree, directory), emittedRoot, false);
|
|
16
|
+
for (const file of emitted.files) {
|
|
17
|
+
const path = join(output, key, relative(emittedRoot, file));
|
|
18
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
19
|
+
copyFileSync(file, path);
|
|
20
|
+
entries++;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
packages: packages.map(({ directory }) => directory),
|
|
25
|
+
entries
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
...result,
|
|
30
|
+
output: result.output + revisionCounts(root, baseRef)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/bin/comment-proof.ts
|
|
35
|
+
runCli(() => {
|
|
36
|
+
const args = process.argv.slice(2);
|
|
37
|
+
if (args.length !== 1) throw new Error("Usage: antelopejs-comment-proof <base-ref>");
|
|
38
|
+
const result = commentProof(repositoryRoot(), args[0]);
|
|
39
|
+
process.stdout.write(result.output);
|
|
40
|
+
return result.different ? 1 : 0;
|
|
41
|
+
});
|
|
42
|
+
//#endregion
|
|
43
|
+
export {};
|
package/dist/bin/ratchet.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { s as readCeilings, t as CEILINGS_FILE } from "../ceilings-BSw9p4YB.mjs";
|
|
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,17 +49,21 @@ 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
|
-
lowered: []
|
|
59
|
-
|
|
63
|
+
lowered: [],
|
|
64
|
+
retired: false
|
|
65
|
+
} : ratchet(root, values.update, values.base === void 0 ? void 0 : resolveRevision(root, values.base));
|
|
66
|
+
if (result.retired) process.stdout.write(`Deleted ${CEILINGS_FILE}: no debt left. Run antelopejs-sync.\n`);
|
|
60
67
|
for (const message of result.lowered) process.stdout.write(`Lowered ${message}\n`);
|
|
61
68
|
for (const message of result.failures) process.stderr.write(`${message}\n`);
|
|
62
69
|
if (!result.failures.length) process.stdout.write("Ratchet: within ceilings.\n");
|