@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.
@@ -1,8 +1,9 @@
1
- import { a as formatCeilings, n as MAX_FILE_BYTES, o as readCeilings, r as MEASURE_ENV, t as CEILINGS_FILE } from "./ceilings--pJmhSde.mjs";
2
- import { i as repositoryPath, n as fileBytes, r as gitFiles, t as authoredSizes } from "./files-Bocv-qA8.mjs";
3
- import { n as sizeFailure } from "./size-DJ7YSry_.mjs";
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
- import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
6
+ import { existsSync, mkdtempSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
6
7
  import { basename, dirname, join } from "node:path";
7
8
  import { execFileSync, spawnSync } from "node:child_process";
8
9
  //#region src/guardrails/oxlint.ts
@@ -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",
@@ -77,11 +78,12 @@ function lintDiagnostics(root, config, files) {
77
78
  function baseline(root, diagnostics) {
78
79
  const rules = {};
79
80
  const oversizeFiles = {};
81
+ const sizes = authoredSizes(root);
80
82
  for (const diagnostic of diagnostics) if (diagnostic.rule === "eslint/max-lines") {
81
- const size = fileBytes(root, diagnostic.file);
83
+ const size = sizes.get(diagnostic.file);
82
84
  if (size !== void 0) oversizeFiles[diagnostic.file] = size;
83
85
  } else rules[diagnostic.rule] = (rules[diagnostic.rule] ?? 0) + 1;
84
- for (const [file, size] of authoredSizes(root)) if (size > 2e4) oversizeFiles[file] = size;
86
+ for (const [file, size] of sizes) if (size > 2e4) oversizeFiles[file] = size;
85
87
  return {
86
88
  rules,
87
89
  oversizeFiles
@@ -136,9 +138,9 @@ function lowerCeilings(ceilings, current) {
136
138
  }
137
139
  return lowered;
138
140
  }
139
- function ratchet(root, update = false) {
141
+ function ratchet(root, update = false, base = "HEAD") {
140
142
  if (!existsSync(join(root, "guardrails.ratchet.json"))) throw new Error(`${CEILINGS_FILE} is missing. Run antelopejs-sync first.`);
141
- const ceilings = readCeilings(root);
143
+ const ceilings = readCarriedCeilings(root, false, [base]);
142
144
  const diagnostics = lintDiagnostics(root);
143
145
  const current = baseline(root, diagnostics);
144
146
  const changed = changedFiles(root);
@@ -154,18 +156,22 @@ function ratchet(root, update = false) {
154
156
  if (shown.length < matches.length) failures.push(` ...and ${matches.length - shown.length} recorded in files this change does not touch.`);
155
157
  }
156
158
  for (const [file, size] of Object.entries(current.oversizeFiles)) if (ceilings.oversizeFiles[file] === void 0) failures.push(size > 2e4 ? sizeFailure(file, size, MAX_FILE_BYTES) : `${file}: ${size} bytes and an eslint/max-lines diagnostic; this file has no oversize allowance. Split by responsibility into sibling modules, or split tests by behaviour under test, one file per describe.`);
159
+ const sizes = authoredSizes(root, Object.keys(ceilings.oversizeFiles));
157
160
  for (const [file, limit] of Object.entries(ceilings.oversizeFiles)) {
158
- const size = fileBytes(root, file);
161
+ const size = sizes.get(file);
159
162
  if (size !== void 0 && size > limit) failures.push(sizeFailure(file, size, limit));
160
163
  }
161
164
  const lowered = update ? lowerCeilings(ceilings, current) : [];
165
+ const retired = update && isRetired(ceilings);
162
166
  if (update) {
163
167
  const path = join(root, CEILINGS_FILE);
164
- writeFileSync(path, formatCeilings(ceilings));
168
+ if (retired) unlinkSync(path);
169
+ else writeFileSync(path, formatCeilings(ceilings));
165
170
  }
166
171
  return {
167
172
  failures,
168
- lowered
173
+ lowered,
174
+ retired
169
175
  };
170
176
  }
171
177
  //#endregion
@@ -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
- "**/output/**",
16
+ "build/**",
17
+ "output/**",
16
18
  "**/i18n-registry.generated.ts",
17
19
  ".github/ISSUE_TEMPLATE/**"
18
20
  ];
@@ -1,11 +1,12 @@
1
- import { o as readCeilings } from "./ceilings--pJmhSde.mjs";
2
- import { t as authoredSizes } from "./files-Bocv-qA8.mjs";
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 = readCeilings(root);
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;
@@ -0,0 +1,120 @@
1
+ import { l as trackedManifests } from "./files-jKlueTMd.mjs";
2
+ import { createRequire } from "node:module";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { dirname, join, matchesGlob, relative, resolve, sep } from "node:path";
5
+ //#region src/guardrails/typescript.ts
6
+ function packagesAt(root, publishedOnly) {
7
+ return trackedManifests(root).flatMap((file) => {
8
+ const directory = dirname(file);
9
+ if (!existsSync(join(root, directory, "tsconfig.json"))) return [];
10
+ const manifest = JSON.parse(readFileSync(join(root, file), "utf8"));
11
+ if (publishedOnly && manifest.private === true) return [];
12
+ return [{
13
+ directory,
14
+ manifest
15
+ }];
16
+ });
17
+ }
18
+ /** Loads the consumer's compiler, including a package-local installation in a workspace. */
19
+ function loadTypeScript(directory) {
20
+ return createRequire(join(directory, "package.json"))("typescript");
21
+ }
22
+ const TEST_SOURCE_GLOBS = [
23
+ "**/*.test.*",
24
+ "**/*.spec.*",
25
+ "**/__tests__/**",
26
+ "test/**",
27
+ "tests/**"
28
+ ];
29
+ function isTestSource(root, file) {
30
+ const path = relative(root, file).split("\\").join("/");
31
+ return TEST_SOURCE_GLOBS.some((pattern) => matchesGlob(path, pattern));
32
+ }
33
+ function failDiagnostics(compiler, diagnostics) {
34
+ const errors = diagnostics.filter((item) => item.category === compiler.DiagnosticCategory.Error);
35
+ if (errors.length) throw new Error(compiler.formatDiagnostics(errors, {
36
+ getCanonicalFileName: (file) => file,
37
+ getCurrentDirectory: () => process.cwd(),
38
+ getNewLine: () => "\n"
39
+ }));
40
+ }
41
+ /** Declaration inputs are part of the local closure but TypeScript does not emit copies of them. */
42
+ function copyDeclarations(program, directory, output) {
43
+ const sources = program.getSourceFiles().filter((source) => !program.isSourceFileDefaultLibrary(source) && !program.isSourceFileFromExternalLibrary(source) && !relative(directory, source.fileName).startsWith(`..${sep}`) && !isTestSource(directory, source.fileName));
44
+ const authored = sources.filter((source) => !source.isDeclarationFile);
45
+ let root = dirname((authored[0] ?? sources[0])?.fileName ?? join(directory, "index.ts"));
46
+ for (const source of authored.length ? authored : sources) while (relative(root, source.fileName).startsWith(`..${sep}`)) root = dirname(root);
47
+ root = program.getCompilerOptions().rootDir ?? root;
48
+ const files = [];
49
+ for (const source of sources) {
50
+ if (!source.isDeclarationFile) continue;
51
+ const path = relative(root, source.fileName);
52
+ if (path.startsWith(`..${sep}`)) continue;
53
+ const target = join(output, path);
54
+ mkdirSync(dirname(target), { recursive: true });
55
+ writeFileSync(target, source.text);
56
+ files.push(target);
57
+ }
58
+ return files;
59
+ }
60
+ function parseConfig(directory) {
61
+ const compiler = loadTypeScript(directory);
62
+ const config = compiler.readConfigFile(join(directory, "tsconfig.json"), compiler.sys.readFile);
63
+ if (config.error) failDiagnostics(compiler, [config.error]);
64
+ const parsed = compiler.parseJsonConfigFileContent(config.config, compiler.sys, directory);
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);
78
+ const options = {
79
+ ...parsed.options,
80
+ rootDir: parsed.options.rootDir ?? (parsed.options.outDir ? void 0 : directory),
81
+ outDir: output,
82
+ declarationDir: declarations ? output : void 0,
83
+ declaration: declarations,
84
+ emitDeclarationOnly: declarations,
85
+ removeComments: true,
86
+ declarationMap: false,
87
+ sourceMap: false,
88
+ inlineSourceMap: false,
89
+ noEmit: false,
90
+ noEmitOnError: false,
91
+ composite: false,
92
+ incremental: false,
93
+ tsBuildInfoFile: void 0,
94
+ outFile: void 0,
95
+ allowImportingTsExtensions: declarations ? parsed.options.allowImportingTsExtensions : false
96
+ };
97
+ const program = compiler.createProgram({
98
+ rootNames: parsed.fileNames.filter((file) => !isTestSource(directory, file)),
99
+ options,
100
+ projectReferences: parsed.projectReferences
101
+ });
102
+ if (declarations) failDiagnostics(compiler, compiler.getPreEmitDiagnostics(program));
103
+ const files = [];
104
+ const result = program.emit(void 0, (file, text, _bom, _onError, sources) => {
105
+ if (sources?.some((source) => isTestSource(directory, source.fileName))) return;
106
+ if (declarations ? !/\.d\.[cm]?ts$/.test(file) : !/\.[cm]?jsx?$/.test(file)) return;
107
+ mkdirSync(dirname(file), { recursive: true });
108
+ writeFileSync(file, text);
109
+ files.push(file);
110
+ });
111
+ if (declarations) failDiagnostics(compiler, result.diagnostics);
112
+ if (declarations) files.push(...copyDeclarations(program, directory, output));
113
+ return {
114
+ compiler,
115
+ files,
116
+ options: parsed.options
117
+ };
118
+ }
119
+ //#endregion
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.1",
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",
@@ -21,9 +21,11 @@
21
21
  "url": "git+https://github.com/AntelopeJS/tooling-configs.git"
22
22
  },
23
23
  "bin": {
24
+ "antelopejs-api-diff": "./dist/bin/api-diff.mjs",
24
25
  "antelopejs-check-interface-ranges": "./dist/bin/check-interface-ranges.mjs",
25
26
  "antelopejs-check-size": "./dist/bin/check-size.mjs",
26
27
  "antelopejs-checks": "./dist/bin/checks.mjs",
28
+ "antelopejs-comment-proof": "./dist/bin/comment-proof.mjs",
27
29
  "antelopejs-ratchet": "./dist/bin/ratchet.mjs",
28
30
  "antelopejs-sync": "./dist/bin/sync.mjs"
29
31
  },
@@ -55,15 +57,14 @@
55
57
  "checks": "node src/bin/checks.ts",
56
58
  "format:check": "oxfmt --check .",
57
59
  "size": "node src/bin/check-size.ts",
58
- "ratchet": "node src/bin/ratchet.ts",
59
60
  "prepare": "if git rev-parse --is-inside-work-tree >/dev/null 2>&1 && command -v lefthook >/dev/null 2>&1; then lefthook install; fi",
60
61
  "sync": "node src/bin/sync.ts",
61
- "ratchet:update": "node src/bin/ratchet.ts --update",
62
62
  "sync:check": "node src/bin/sync.ts --check",
63
63
  "knip": "knip"
64
64
  },
65
65
  "dependencies": {
66
66
  "@oxlint/plugins": "1.81.0",
67
+ "dts-bundle-generator": "^9.5.1",
67
68
  "yaml": "^2.9.1"
68
69
  },
69
70
  "devDependencies": {
@@ -1,103 +0,0 @@
1
- import { n as IGNORE_PATTERNS, t as AGENT_IGNORE_PATTERNS } from "./shared-NC0T8ykm.mjs";
2
- import { existsSync, lstatSync } from "node:fs";
3
- import { join, matchesGlob, relative, resolve } from "node:path";
4
- import { execFileSync, spawnSync } from "node:child_process";
5
- //#region src/guardrails/cli.ts
6
- function runCli(main) {
7
- try {
8
- process.exitCode = main();
9
- } catch (error) {
10
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
11
- process.exitCode = 1;
12
- }
13
- }
14
- //#endregion
15
- //#region src/guardrails/files.ts
16
- const SIZE_EXCLUSIONS = [
17
- {
18
- kind: "generated",
19
- patterns: [
20
- ...IGNORE_PATTERNS,
21
- ...AGENT_IGNORE_PATTERNS,
22
- "**/*.generated.*",
23
- "**/generated/**",
24
- "**/__snapshots__/**"
25
- ]
26
- },
27
- {
28
- kind: "fixtures",
29
- patterns: ["**/fixtures/**", "**/__fixtures__/**"]
30
- },
31
- {
32
- kind: "lockfiles",
33
- patterns: [
34
- "**/{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}",
35
- "**/*.lock",
36
- "**/*.lockb"
37
- ]
38
- },
39
- {
40
- kind: "documentation",
41
- patterns: ["**/CHANGELOG.md", "**/*.{md,mdx,markdown,MD}"]
42
- },
43
- {
44
- kind: "assets",
45
- 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}"]
46
- }
47
- ];
48
- function gitFiles(root) {
49
- return [...new Set(execFileSync("git", [
50
- "ls-files",
51
- "--cached",
52
- "--others",
53
- "--exclude-standard",
54
- "-z"
55
- ], {
56
- cwd: root,
57
- encoding: "utf8"
58
- }).split("\0").filter(Boolean))];
59
- }
60
- function repositoryRoot(cwd = process.cwd()) {
61
- return execFileSync("git", ["rev-parse", "--show-toplevel"], {
62
- cwd,
63
- encoding: "utf8"
64
- }).trim();
65
- }
66
- function repositoryPath(root, file) {
67
- return relative(root, resolve(root, file)).split("\\").join("/");
68
- }
69
- function fileBytes(root, file, staged = false) {
70
- if (staged) {
71
- const result = spawnSync("git", [
72
- "cat-file",
73
- "-s",
74
- `:${file}`
75
- ], {
76
- cwd: root,
77
- encoding: "utf8"
78
- });
79
- if (result.error) throw result.error;
80
- return result.status === 0 ? Number(result.stdout.trim()) : void 0;
81
- }
82
- const path = join(root, file);
83
- if (!existsSync(path)) return void 0;
84
- const stat = lstatSync(path);
85
- return stat.isFile() ? stat.size : void 0;
86
- }
87
- /** `path.matchesGlob` never lets `*` or `**` cross a segment starting with a dot, so each path is also tried with those dots removed. */
88
- function isExcluded(file) {
89
- const undotted = file.replace(/(^|\/)\./gu, "$1");
90
- return SIZE_EXCLUSIONS.some(({ patterns }) => patterns.some((pattern) => matchesGlob(file, pattern) || matchesGlob(undotted, pattern)));
91
- }
92
- function authoredSizes(root, files = gitFiles(root), staged = false) {
93
- const sizes = /* @__PURE__ */ new Map();
94
- for (const input of files) {
95
- const file = repositoryPath(root, input);
96
- if (isExcluded(file)) continue;
97
- const size = fileBytes(root, file, staged);
98
- if (size !== void 0) sizes.set(file, size);
99
- }
100
- return sizes;
101
- }
102
- //#endregion
103
- export { repositoryRoot as a, repositoryPath as i, fileBytes as n, runCli as o, gitFiles as r, authoredSizes as t };