@wirecat/cli-core 0.0.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +344 -0
  3. package/config/biome.base.json +56 -0
  4. package/config/cspell.json +56 -0
  5. package/config/lefthook.yml +34 -0
  6. package/config/rumdl.toml +9 -0
  7. package/config/tsconfig.base.json +21 -0
  8. package/config/tsconfig.scripts.json +13 -0
  9. package/config/tsconfig.test.json +11 -0
  10. package/config/vale/CliDocs/Filler.yml +9 -0
  11. package/config/vale/CliDocs/SentenceLength.yml +6 -0
  12. package/config/vale/Tg/Tone.yml +7 -0
  13. package/dist/codegen/index.d.ts +12 -0
  14. package/dist/codegen/index.d.ts.map +1 -0
  15. package/dist/codegen/index.js +11 -0
  16. package/dist/codegen/index.js.map +1 -0
  17. package/dist/codegen/manifest.d.ts +47 -0
  18. package/dist/codegen/manifest.d.ts.map +1 -0
  19. package/dist/codegen/manifest.js +73 -0
  20. package/dist/codegen/manifest.js.map +1 -0
  21. package/dist/codegen/model.d.ts +146 -0
  22. package/dist/codegen/model.d.ts.map +1 -0
  23. package/dist/codegen/model.js +7 -0
  24. package/dist/codegen/model.js.map +1 -0
  25. package/dist/codegen/names.d.ts +4 -0
  26. package/dist/codegen/names.d.ts.map +1 -0
  27. package/dist/codegen/names.js +46 -0
  28. package/dist/codegen/names.js.map +1 -0
  29. package/dist/codegen/pipeline.d.ts +24 -0
  30. package/dist/codegen/pipeline.d.ts.map +1 -0
  31. package/dist/codegen/pipeline.js +193 -0
  32. package/dist/codegen/pipeline.js.map +1 -0
  33. package/dist/codegen/runtime.d.ts +24 -0
  34. package/dist/codegen/runtime.d.ts.map +1 -0
  35. package/dist/codegen/runtime.js +38 -0
  36. package/dist/codegen/runtime.js.map +1 -0
  37. package/dist/codegen/tree.d.ts +29 -0
  38. package/dist/codegen/tree.d.ts.map +1 -0
  39. package/dist/codegen/tree.js +87 -0
  40. package/dist/codegen/tree.js.map +1 -0
  41. package/dist/codegen/typescript.d.ts +6 -0
  42. package/dist/codegen/typescript.d.ts.map +1 -0
  43. package/dist/codegen/typescript.js +78 -0
  44. package/dist/codegen/typescript.js.map +1 -0
  45. package/dist/codegen/valibot.d.ts +10 -0
  46. package/dist/codegen/valibot.d.ts.map +1 -0
  47. package/dist/codegen/valibot.js +132 -0
  48. package/dist/codegen/valibot.js.map +1 -0
  49. package/dist/commands/index.d.ts +76 -0
  50. package/dist/commands/index.d.ts.map +1 -0
  51. package/dist/commands/index.js +62 -0
  52. package/dist/commands/index.js.map +1 -0
  53. package/dist/commands/page.d.ts +80 -0
  54. package/dist/commands/page.d.ts.map +1 -0
  55. package/dist/commands/page.js +100 -0
  56. package/dist/commands/page.js.map +1 -0
  57. package/dist/completion/index.d.ts +37 -0
  58. package/dist/completion/index.d.ts.map +1 -0
  59. package/dist/completion/index.js +87 -0
  60. package/dist/completion/index.js.map +1 -0
  61. package/dist/config.d.ts +20 -0
  62. package/dist/config.d.ts.map +1 -0
  63. package/dist/config.js +61 -0
  64. package/dist/config.js.map +1 -0
  65. package/dist/credentials.d.ts +48 -0
  66. package/dist/credentials.d.ts.map +1 -0
  67. package/dist/credentials.js +120 -0
  68. package/dist/credentials.js.map +1 -0
  69. package/dist/dev/bin.d.ts +3 -0
  70. package/dist/dev/bin.d.ts.map +1 -0
  71. package/dist/dev/bin.js +8 -0
  72. package/dist/dev/bin.js.map +1 -0
  73. package/dist/dev/main.d.ts +7 -0
  74. package/dist/dev/main.d.ts.map +1 -0
  75. package/dist/dev/main.js +149 -0
  76. package/dist/dev/main.js.map +1 -0
  77. package/dist/dev/next-version.d.ts +11 -0
  78. package/dist/dev/next-version.d.ts.map +1 -0
  79. package/dist/dev/next-version.js +33 -0
  80. package/dist/dev/next-version.js.map +1 -0
  81. package/dist/dev/slow-tests.d.ts +14 -0
  82. package/dist/dev/slow-tests.d.ts.map +1 -0
  83. package/dist/dev/slow-tests.js +17 -0
  84. package/dist/dev/slow-tests.js.map +1 -0
  85. package/dist/dev/test-matrix.d.ts +35 -0
  86. package/dist/dev/test-matrix.d.ts.map +1 -0
  87. package/dist/dev/test-matrix.js +93 -0
  88. package/dist/dev/test-matrix.js.map +1 -0
  89. package/dist/errors.d.ts +34 -0
  90. package/dist/errors.d.ts.map +1 -0
  91. package/dist/errors.js +36 -0
  92. package/dist/errors.js.map +1 -0
  93. package/dist/exit-codes.d.ts +11 -0
  94. package/dist/exit-codes.d.ts.map +1 -0
  95. package/dist/exit-codes.js +25 -0
  96. package/dist/exit-codes.js.map +1 -0
  97. package/dist/http/index.d.ts +20 -0
  98. package/dist/http/index.d.ts.map +1 -0
  99. package/dist/http/index.js +46 -0
  100. package/dist/http/index.js.map +1 -0
  101. package/dist/index.d.ts +16 -0
  102. package/dist/index.d.ts.map +1 -0
  103. package/dist/index.js +15 -0
  104. package/dist/index.js.map +1 -0
  105. package/dist/keyring.d.ts +27 -0
  106. package/dist/keyring.d.ts.map +1 -0
  107. package/dist/keyring.js +45 -0
  108. package/dist/keyring.js.map +1 -0
  109. package/dist/logger.d.ts +12 -0
  110. package/dist/logger.d.ts.map +1 -0
  111. package/dist/logger.js +7 -0
  112. package/dist/logger.js.map +1 -0
  113. package/dist/logging.d.ts +31 -0
  114. package/dist/logging.d.ts.map +1 -0
  115. package/dist/logging.js +58 -0
  116. package/dist/logging.js.map +1 -0
  117. package/dist/mcp/index.d.ts +32 -0
  118. package/dist/mcp/index.d.ts.map +1 -0
  119. package/dist/mcp/index.js +105 -0
  120. package/dist/mcp/index.js.map +1 -0
  121. package/dist/paths.d.ts +28 -0
  122. package/dist/paths.d.ts.map +1 -0
  123. package/dist/paths.js +23 -0
  124. package/dist/paths.js.map +1 -0
  125. package/dist/pretty.d.ts +12 -0
  126. package/dist/pretty.d.ts.map +1 -0
  127. package/dist/pretty.js +83 -0
  128. package/dist/pretty.js.map +1 -0
  129. package/dist/release/index.d.ts +38 -0
  130. package/dist/release/index.d.ts.map +1 -0
  131. package/dist/release/index.js +91 -0
  132. package/dist/release/index.js.map +1 -0
  133. package/dist/release/markdown.d.ts +36 -0
  134. package/dist/release/markdown.d.ts.map +1 -0
  135. package/dist/release/markdown.js +153 -0
  136. package/dist/release/markdown.js.map +1 -0
  137. package/dist/release/structure.d.ts +16 -0
  138. package/dist/release/structure.d.ts.map +1 -0
  139. package/dist/release/structure.js +74 -0
  140. package/dist/release/structure.js.map +1 -0
  141. package/dist/renderer.d.ts +34 -0
  142. package/dist/renderer.d.ts.map +1 -0
  143. package/dist/renderer.js +55 -0
  144. package/dist/renderer.js.map +1 -0
  145. package/dist/retry.d.ts +27 -0
  146. package/dist/retry.d.ts.map +1 -0
  147. package/dist/retry.js +26 -0
  148. package/dist/retry.js.map +1 -0
  149. package/dist/sanitize.d.ts +25 -0
  150. package/dist/sanitize.d.ts.map +1 -0
  151. package/dist/sanitize.js +52 -0
  152. package/dist/sanitize.js.map +1 -0
  153. package/dist/skill/index.d.ts +78 -0
  154. package/dist/skill/index.d.ts.map +1 -0
  155. package/dist/skill/index.js +190 -0
  156. package/dist/skill/index.js.map +1 -0
  157. package/dist/streams.d.ts +24 -0
  158. package/dist/streams.d.ts.map +1 -0
  159. package/dist/streams.js +26 -0
  160. package/dist/streams.js.map +1 -0
  161. package/dist/testing/index.d.ts +24 -0
  162. package/dist/testing/index.d.ts.map +1 -0
  163. package/dist/testing/index.js +29 -0
  164. package/dist/testing/index.js.map +1 -0
  165. package/dist/time.d.ts +21 -0
  166. package/dist/time.d.ts.map +1 -0
  167. package/dist/time.js +19 -0
  168. package/dist/time.js.map +1 -0
  169. package/dist/update/index.d.ts +80 -0
  170. package/dist/update/index.d.ts.map +1 -0
  171. package/dist/update/index.js +159 -0
  172. package/dist/update/index.js.map +1 -0
  173. package/package.json +116 -2
@@ -0,0 +1,38 @@
1
+ export { type ChangelogRules, changelogProblems, type DocsRules, docsProblems, idPattern, JOURNAL_IDS, markdownFiles, slug, } from "./markdown.js";
2
+ export { REQUIRED_PAGES, type StructureRules, structureProblems } from "./structure.js";
3
+ export type Check = {
4
+ name: string;
5
+ run: () => string[];
6
+ };
7
+ /**
8
+ * Runs every check even after one fails, so one pass shows the whole list, and returns how many
9
+ * failed. The caller exits: a library that calls `process.exit` cannot be tested.
10
+ */
11
+ export declare const releaseCheck: (checks: readonly Check[], { version, log }: {
12
+ version: string;
13
+ log: (line: string) => void;
14
+ }) => number;
15
+ export declare const run: (root: string, command: string, args: readonly string[]) => {
16
+ ok: boolean;
17
+ output: string;
18
+ };
19
+ /** A check that passes when the command exits 0, and otherwise shows the last 15 lines it printed. */
20
+ export declare const command: (root: string, name: string, ...args: string[]) => () => string[];
21
+ export declare const packageVersion: (root: string) => string;
22
+ export declare const notOnNpm: (root: string, packageName: string, version: string) => () => string[];
23
+ /**
24
+ * `allowed` holds exact paths, and directories ending in `/`; `said` is how the problem names them.
25
+ */
26
+ export declare const packProblems: (paths: readonly string[], allowed: readonly string[], said: string) => string[];
27
+ export declare const packContents: (root: string, allowed: readonly string[], said: string) => () => string[];
28
+ /**
29
+ * The whole of a `version:check` / `version:sync` script: what to print, and the exit code — 2 when
30
+ * either file cannot be read, 1 on drift. A CLI keeps its version in `package.json` and in a source
31
+ * file read at run time — `--version` must not load `package.json`, which is not there once the
32
+ * command is bundled — and the day they disagree is a release day.
33
+ */
34
+ export declare const versionScript: (root: string, argv: readonly string[], file?: string) => {
35
+ code: 0 | 1 | 2;
36
+ message: string;
37
+ };
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/release/index.ts"],"names":[],"mappings":"AASA,OAAO,EACL,KAAK,cAAc,EACnB,iBAAiB,EACjB,KAAK,SAAS,EACd,YAAY,EACZ,SAAS,EACT,WAAW,EACX,aAAa,EACb,IAAI,GACL,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAEvF,MAAM,MAAM,KAAK,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,MAAM,EAAE,CAAA;CAAE,CAAA;AAEzD;;;GAGG;AACH,eAAO,MAAM,YAAY,WACf,SAAS,KAAK,EAAE,oBACN;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,WAWnE,CAAA;AAED,eAAO,MAAM,GAAG,SAAU,MAAM,WAAW,MAAM,QAAQ,SAAS,MAAM,EAAE;;;CAIzE,CAAA;AAED,sGAAsG;AACtG,eAAO,MAAM,OAAO,SACX,MAAM,QAAQ,MAAM,WAAW,MAAM,EAAE,WAC1C,MAAM,EAGT,CAAA;AAEH,eAAO,MAAM,cAAc,SAAU,MAAM,WACoD,CAAA;AAE/F,eAAO,MAAM,QAAQ,SAAU,MAAM,eAAe,MAAM,WAAW,MAAM,WAAS,MAAM,EAGlF,CAAA;AAER;;GAEG;AACH,eAAO,MAAM,YAAY,UAAW,SAAS,MAAM,EAAE,WAAW,SAAS,MAAM,EAAE,QAAQ,MAAM,aAG1B,CAAA;AAErE,eAAO,MAAM,YAAY,SAAU,MAAM,WAAW,SAAS,MAAM,EAAE,QAAQ,MAAM,WAAS,MAAM,EASjG,CAAA;AAcD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,SAClB,MAAM,QACN,SAAS,MAAM,EAAE,oBAEtB;IAAE,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAiBpC,CAAA"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Everything about a release that a program can decide. Each check returns the problems it found,
3
+ * one line each, saying what and where; an empty list is a pass. The judgement half — changelog
4
+ * wording, docs against the diff, live checks — is each CLI's release skill.
5
+ */
6
+ import { spawnSync } from "node:child_process";
7
+ import { readFileSync, writeFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ export { changelogProblems, docsProblems, idPattern, JOURNAL_IDS, markdownFiles, slug, } from "./markdown.js";
10
+ export { REQUIRED_PAGES, structureProblems } from "./structure.js";
11
+ /**
12
+ * Runs every check even after one fails, so one pass shows the whole list, and returns how many
13
+ * failed. The caller exits: a library that calls `process.exit` cannot be tested.
14
+ */
15
+ export const releaseCheck = (checks, { version, log }) => {
16
+ let failed = 0;
17
+ for (const { name, run } of checks) {
18
+ const problems = run();
19
+ log(`${problems.length === 0 ? "ok " : "FAIL"} ${name}`);
20
+ for (const problem of problems)
21
+ log(problem.replace(/^/gm, " "));
22
+ if (problems.length > 0)
23
+ failed++;
24
+ }
25
+ log(failed === 0 ? `\n${version}: every check passed` : `\n${version}: ${failed} check(s) failed`);
26
+ return failed;
27
+ };
28
+ export const run = (root, command, args) => {
29
+ const done = spawnSync(command, args, { cwd: root, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
30
+ const output = `${done.stdout ?? ""}${done.stderr ?? ""}${done.error ? String(done.error) : ""}`;
31
+ return { ok: done.status === 0, output };
32
+ };
33
+ /** A check that passes when the command exits 0, and otherwise shows the last 15 lines it printed. */
34
+ export const command = (root, name, ...args) => () => {
35
+ const { ok, output } = run(root, name, args);
36
+ return ok ? [] : [output.trim().split("\n").slice(-15).join("\n") || `exit ${name} ${args.join(" ")}`];
37
+ };
38
+ export const packageVersion = (root) => JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version;
39
+ export const notOnNpm = (root, packageName, version) => () => run(root, "npm", ["view", `${packageName}@${version}`, "version", "--prefer-online"]).ok
40
+ ? [`${version} is already on npm — raise the version`]
41
+ : [];
42
+ /**
43
+ * `allowed` holds exact paths, and directories ending in `/`; `said` is how the problem names them.
44
+ */
45
+ export const packProblems = (paths, allowed, said) => paths
46
+ .filter((path) => !allowed.some((entry) => (entry.endsWith("/") ? path.startsWith(entry) : path === entry)))
47
+ .map((path) => `npm pack: ${path} would ship — only ${said} may`);
48
+ export const packContents = (root, allowed, said) => () => {
49
+ const { ok, output } = run(root, "npm", ["pack", "--dry-run", "--json", "--ignore-scripts"]);
50
+ if (!ok)
51
+ return [output.trim()];
52
+ const [packed] = JSON.parse(output.slice(output.indexOf("[")));
53
+ return packProblems((packed?.files ?? []).map(({ path }) => path), allowed, said);
54
+ };
55
+ const VERSION = /export const VERSION = "([^"]*)"/;
56
+ const versionsOf = (root, file) => {
57
+ const declared = packageVersion(root);
58
+ if (typeof declared !== "string" || !/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(declared))
59
+ return `package.json has no usable "version" (got ${JSON.stringify(declared)})`;
60
+ const source = readFileSync(join(root, file), "utf8");
61
+ const current = VERSION.exec(source)?.[1];
62
+ if (!current)
63
+ return `${file} does not export a VERSION string`;
64
+ return { declared, current, source };
65
+ };
66
+ /**
67
+ * The whole of a `version:check` / `version:sync` script: what to print, and the exit code — 2 when
68
+ * either file cannot be read, 1 on drift. A CLI keeps its version in `package.json` and in a source
69
+ * file read at run time — `--version` must not load `package.json`, which is not there once the
70
+ * command is bundled — and the day they disagree is a release day.
71
+ */
72
+ export const versionScript = (root, argv, file = "src/version.ts") => {
73
+ const found = versionsOf(root, file);
74
+ if (typeof found === "string")
75
+ return { code: 2, message: found };
76
+ const { declared, current, source } = found;
77
+ if (argv.includes("--sync")) {
78
+ if (current === declared)
79
+ return { code: 0, message: `version ${declared} — already in step` };
80
+ writeFileSync(join(root, file), source.replace(`"${current}"`, `"${declared}"`));
81
+ return { code: 0, message: `${file}: ${current} → ${declared}` };
82
+ }
83
+ if (current !== declared)
84
+ return {
85
+ code: 1,
86
+ message: `version drift: package.json says ${declared}, ${file} says ${current}.\n` +
87
+ "Run `pnpm version:sync` — package.json is the one that is edited.",
88
+ };
89
+ return { code: 0, message: `version ${declared} — package.json and ${file} agree` };
90
+ };
91
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/release/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /** The prefixes of the owner's journal ids; a project adds its own (`CLI`, `MAX`, …). */
2
+ export declare const JOURNAL_IDS: readonly ["NEED", "FIND", "BUG", "SEC", "PERF", "UX", "IDEA", "RISK", "DEBT", "ASK", "TASK"];
3
+ export declare const idPattern: (prefixes: readonly string[]) => RegExp;
4
+ export type ChangelogRules = {
5
+ /** The only `###` headings a section may have. */
6
+ headings: readonly string[];
7
+ /** The top section's title until it is dated, `Unreleased` or `Не выпущено`. */
8
+ unreleased: string;
9
+ ids: readonly string[];
10
+ };
11
+ /**
12
+ * `release`: the top section must be `version`, and nothing may be left unreleased — a release
13
+ * workflow builds its notes by matching `## <version> `, so a heading slightly off publishes empty
14
+ * notes without an error.
15
+ */
16
+ export declare const changelogProblems: (text: string, { version, release, headings, unreleased, ids }: ChangelogRules & {
17
+ version?: string;
18
+ release: boolean;
19
+ }) => string[];
20
+ /** GitHub's heading anchor: lower case, only letters, digits, `-`, `_` and spaces kept, spaces to `-`. */
21
+ export declare const slug: (heading: string) => string;
22
+ export declare const withoutCode: (text: string) => string;
23
+ /** Every `.md` under `directory`, not descending into a directory named in `skip`. */
24
+ export declare const markdownFiles: (directory: string, skip?: ReadonlySet<string>) => string[];
25
+ export type DocsRules = {
26
+ /** Absolute paths; a missing one is passed over. */
27
+ files: readonly string[];
28
+ ids: readonly string[];
29
+ /** A page a user reads, by its path relative to `root`: no correction mark, struck-out text or id. */
30
+ userPage?: (name: string) => boolean;
31
+ correction?: RegExp;
32
+ /** A link not to follow — a private or sibling checkout that CI does not have. */
33
+ skipLink?: (target: string, destination: string) => boolean;
34
+ };
35
+ export declare const docsProblems: (root: string, { files, ids, userPage, correction, skipLink }: DocsRules) => string[];
36
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/release/markdown.ts"],"names":[],"mappings":"AAGA,yFAAyF;AACzF,eAAO,MAAM,WAAW,YAAI,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAU,CAAA;AAEvH,eAAO,MAAM,SAAS,aAAc,SAAS,MAAM,EAAE,WAA4D,CAAA;AAIjH,MAAM,MAAM,cAAc,GAAG;IAC3B,kDAAkD;IAClD,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAA;IAClB,GAAG,EAAE,SAAS,MAAM,EAAE,CAAA;CACvB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,SACtB,MAAM,mDACqC,cAAc,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,aAyCzG,CAAA;AAED,0GAA0G;AAC1G,eAAO,MAAM,IAAI,YAAa,MAAM,WAMb,CAAA;AAEvB,eAAO,MAAM,WAAW,SAAU,MAAM,WAGqB,CAAA;AAsB7D,sFAAsF;AACtF,eAAO,MAAM,aAAa,cAAe,MAAM,SAAQ,WAAW,CAAC,MAAM,CAAC,KAAe,MAAM,EAWvF,CAAA;AAER,MAAM,MAAM,SAAS,GAAG;IACtB,oDAAoD;IACpD,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,GAAG,EAAE,SAAS,MAAM,EAAE,CAAA;IACtB,sGAAsG;IACtG,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IACpC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAA;CAC5D,CAAA;AAED,eAAO,MAAM,YAAY,SACjB,MAAM,kDACsF,SAAS,aA8C5G,CAAA"}
@@ -0,0 +1,153 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { dirname, join, relative, resolve } from "node:path";
3
+ /** The prefixes of the owner's journal ids; a project adds its own (`CLI`, `MAX`, …). */
4
+ export const JOURNAL_IDS = ["NEED", "FIND", "BUG", "SEC", "PERF", "UX", "IDEA", "RISK", "DEBT", "ASK", "TASK"];
5
+ export const idPattern = (prefixes) => new RegExp(`\\b(?:${prefixes.join("|")})-\\d+\\b`, "g");
6
+ const VERSION_HEADING = /^## (\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?) — (\d{2})\.(\d{2})\.(\d{4})$/;
7
+ /**
8
+ * `release`: the top section must be `version`, and nothing may be left unreleased — a release
9
+ * workflow builds its notes by matching `## <version> `, so a heading slightly off publishes empty
10
+ * notes without an error.
11
+ */
12
+ export const changelogProblems = (text, { version, release, headings, unreleased, ids }) => {
13
+ const problems = [];
14
+ const id = idPattern(ids);
15
+ let section;
16
+ let subheadings = new Set();
17
+ let first = true;
18
+ text.split("\n").forEach((line, index) => {
19
+ const where = `CHANGELOG.md:${index + 1}`;
20
+ if (line.startsWith("## ")) {
21
+ section = line.slice(3);
22
+ subheadings = new Set();
23
+ if (section === unreleased) {
24
+ if (release)
25
+ problems.push(`${where}: "${unreleased}" is left — date it as ${version} before releasing`);
26
+ else if (!first)
27
+ problems.push(`${where}: "${unreleased}" must be the top section`);
28
+ }
29
+ else {
30
+ const found = VERSION_HEADING.exec(line);
31
+ if (!found)
32
+ problems.push(`${where}: "${line}" is not "## <version> — DD.MM.YYYY"`);
33
+ else {
34
+ const [, heading, day, month] = found;
35
+ if (Number(day) < 1 || Number(day) > 31 || Number(month) < 1 || Number(month) > 12)
36
+ problems.push(`${where}: "${line}" has no such date`);
37
+ if (release && first && heading !== version)
38
+ problems.push(`${where}: the top section is ${heading}, package.json says ${version}`);
39
+ }
40
+ }
41
+ first = false;
42
+ return;
43
+ }
44
+ if (line.startsWith("### ")) {
45
+ const heading = line.slice(4);
46
+ if (!headings.includes(heading))
47
+ problems.push(`${where}: "${heading}" is not one of: ${headings.join(", ")}`);
48
+ if (subheadings.has(heading))
49
+ problems.push(`${where}: "${heading}" twice in "${section}"`);
50
+ subheadings.add(heading);
51
+ }
52
+ for (const found of line.match(id) ?? [])
53
+ problems.push(`${where}: internal id ${found} — say what changed instead`);
54
+ });
55
+ if (release && first)
56
+ problems.push(`CHANGELOG.md: no section for ${version}`);
57
+ return problems;
58
+ };
59
+ /** GitHub's heading anchor: lower case, only letters, digits, `-`, `_` and spaces kept, spaces to `-`. */
60
+ export const slug = (heading) => heading
61
+ .replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1")
62
+ .replace(/<[^>]+>/g, "")
63
+ .toLowerCase()
64
+ .replace(/[^\p{L}\p{M}\p{N}\-_ ]/gu, "")
65
+ .replace(/ /g, "-");
66
+ export const withoutCode = (text) => text
67
+ .replace(/^(```|~~~)[^\n]*\n[\s\S]*?^\1[^\n]*$/gm, (block) => block.replace(/[^\n]/g, " "))
68
+ .replace(/`[^`\n]*`/g, (span) => " ".repeat(span.length));
69
+ // Headings are read from the raw text, so code inside a heading still counts toward its anchor.
70
+ const anchorsOf = (text) => {
71
+ const raw = text.split("\n");
72
+ const anchors = new Set();
73
+ const counts = new Map();
74
+ withoutCode(text)
75
+ .split("\n")
76
+ .forEach((masked, index) => {
77
+ if (!/^#{1,6} /.test(masked))
78
+ return;
79
+ const heading = /^#{1,6} (.+?)\s*#*\s*$/.exec(raw[index] ?? "")?.[1];
80
+ if (heading === undefined)
81
+ return;
82
+ const base = slug(heading);
83
+ const seen = counts.get(base) ?? 0;
84
+ counts.set(base, seen + 1);
85
+ anchors.add(seen === 0 ? base : `${base}-${seen}`);
86
+ });
87
+ for (const [, name = ""] of text.matchAll(/<a\s+(?:name|id)="([^"]+)"/g))
88
+ anchors.add(name);
89
+ return anchors;
90
+ };
91
+ /** Every `.md` under `directory`, not descending into a directory named in `skip`. */
92
+ export const markdownFiles = (directory, skip = new Set()) => existsSync(directory)
93
+ ? readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
94
+ if (skip.has(entry.name))
95
+ return [];
96
+ const path = join(directory, entry.name);
97
+ // stat, not the entry's own type: Claude Code's sandbox mounts character devices over names
98
+ // like .claude/loop.md, and the directory listing still reports what lies underneath.
99
+ const stats = statSync(path, { throwIfNoEntry: false });
100
+ if (entry.isDirectory() && stats?.isDirectory())
101
+ return markdownFiles(path, skip);
102
+ return stats?.isFile() && entry.name.endsWith(".md") ? [path] : [];
103
+ })
104
+ : [];
105
+ export const docsProblems = (root, { files, ids, userPage = () => false, correction = /correction \d{4}/i, skipLink = () => false }) => {
106
+ const id = idPattern(ids);
107
+ const cache = new Map();
108
+ const anchors = (path) => {
109
+ let found = cache.get(path);
110
+ if (!found) {
111
+ found = anchorsOf(readFileSync(path, "utf8"));
112
+ cache.set(path, found);
113
+ }
114
+ return found;
115
+ };
116
+ const problems = [];
117
+ for (const path of files.filter((file) => existsSync(file))) {
118
+ const name = relative(root, path);
119
+ const user = userPage(name);
120
+ withoutCode(readFileSync(path, "utf8"))
121
+ .split("\n")
122
+ .forEach((line, index) => {
123
+ const where = `${name}:${index + 1}`;
124
+ for (const [, target] of line.matchAll(/!?\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g)) {
125
+ if (!target || /^[a-z][a-z0-9+.-]*:/i.test(target))
126
+ continue;
127
+ const [file = "", anchor] = decodeURIComponent(target).split("#");
128
+ const destination = file === "" ? path : resolve(dirname(path), file);
129
+ if (skipLink(file, destination))
130
+ continue;
131
+ if (!existsSync(destination)) {
132
+ problems.push(`${where}: link to ${file} — no such file`);
133
+ continue;
134
+ }
135
+ if (anchor &&
136
+ destination.endsWith(".md") &&
137
+ statSync(destination).isFile() &&
138
+ !anchors(destination).has(anchor))
139
+ problems.push(`${where}: link to ${file}#${anchor} — no such heading`);
140
+ }
141
+ if (!user)
142
+ return;
143
+ if (correction.test(line))
144
+ problems.push(`${where}: a correction mark on a user page`);
145
+ if (line.includes("~~"))
146
+ problems.push(`${where}: struck-out text on a user page`);
147
+ for (const found of line.match(id) ?? [])
148
+ problems.push(`${where}: internal id ${found} on a user page`);
149
+ });
150
+ }
151
+ return problems;
152
+ };
153
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/release/markdown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /** The pages every tool has — the shared page set in cli-docs' STRUCTURE.md. */
2
+ export declare const REQUIRED_PAGES: readonly ["index", "installation", "usage", "commands", "configuration", "troubleshooting", "security"];
3
+ export type StructureRules = {
4
+ /** The docs folder, relative to `root`. */
5
+ docs?: string;
6
+ required?: readonly string[];
7
+ /** Pages in `meta.json` that are not files in the folder: `changelog` comes from the root. */
8
+ external?: readonly string[];
9
+ };
10
+ /**
11
+ * The docs folder against its `meta.json`: every listed page exists, every page is listed once,
12
+ * the required ones are there, and each page has exactly one `# heading`, its title. `README.md`
13
+ * is the contributors' index and stays out of the sidebar.
14
+ */
15
+ export declare const structureProblems: (root: string, { docs, required, external }?: StructureRules) => string[];
16
+ //# sourceMappingURL=structure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../../src/release/structure.ts"],"names":[],"mappings":"AAIA,gFAAgF;AAChF,eAAO,MAAM,cAAc,YACzB,OAAO,EACP,cAAc,EACd,OAAO,EACP,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,UAAU,CACF,CAAA;AAKV,MAAM,MAAM,cAAc,GAAG;IAC3B,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC7B,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,SACtB,MAAM,iCAC4D,cAAc,aA+CvF,CAAA"}
@@ -0,0 +1,74 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { withoutCode } from "./markdown.js";
4
+ /** The pages every tool has — the shared page set in cli-docs' STRUCTURE.md. */
5
+ export const REQUIRED_PAGES = [
6
+ "index",
7
+ "installation",
8
+ "usage",
9
+ "commands",
10
+ "configuration",
11
+ "troubleshooting",
12
+ "security",
13
+ ];
14
+ // Fumadocs' own keys (fumadocs.dev/docs/page-conventions); the portal reads meta.json as it is.
15
+ const META_KEYS = new Set(["title", "icon", "pages", "defaultOpen", "collapsible", "pagesIndex", "root"]);
16
+ /**
17
+ * The docs folder against its `meta.json`: every listed page exists, every page is listed once,
18
+ * the required ones are there, and each page has exactly one `# heading`, its title. `README.md`
19
+ * is the contributors' index and stays out of the sidebar.
20
+ */
21
+ export const structureProblems = (root, { docs = "docs", required = REQUIRED_PAGES, external = ["changelog"] } = {}) => {
22
+ const folder = join(root, docs);
23
+ const metaPath = join(folder, "meta.json");
24
+ if (!existsSync(metaPath))
25
+ return [`${docs}/meta.json: missing — it lists the pages in sidebar order`];
26
+ let meta;
27
+ try {
28
+ meta = JSON.parse(readFileSync(metaPath, "utf8"));
29
+ }
30
+ catch (error) {
31
+ return [`${docs}/meta.json: not JSON — ${error.message}`];
32
+ }
33
+ const problems = [];
34
+ for (const key of Object.keys(meta))
35
+ if (!META_KEYS.has(key))
36
+ problems.push(`${docs}/meta.json: "${key}" is not a Fumadocs key`);
37
+ const listed = Array.isArray(meta.pages) ? meta.pages.filter((page) => typeof page === "string") : [];
38
+ if (!Array.isArray(meta.pages))
39
+ problems.push(`${docs}/meta.json: "pages" must be a list`);
40
+ const pages = listed.filter((page) => !/^---.*---$/.test(page) && !/^\[.*\]\(.*\)$/.test(page));
41
+ const seen = new Set();
42
+ for (const page of pages) {
43
+ if (seen.has(page))
44
+ problems.push(`${docs}/meta.json: "${page}" is listed twice`);
45
+ seen.add(page);
46
+ if (external.includes(page))
47
+ continue;
48
+ if (!existsSync(join(folder, `${page}.md`)))
49
+ problems.push(`${docs}/meta.json: "${page}" — no ${docs}/${page}.md`);
50
+ }
51
+ for (const page of required)
52
+ if (!seen.has(page))
53
+ problems.push(`${docs}/meta.json: "${page}" is required and not listed`);
54
+ const files = readdirSync(folder).filter((name) => name.endsWith(".md") && name !== "README.md");
55
+ for (const file of files) {
56
+ const page = file.slice(0, -3);
57
+ if (!seen.has(page))
58
+ problems.push(`${docs}/${file}: not in meta.json — listed nowhere, so the portal never shows it`);
59
+ const text = readFileSync(join(folder, file), "utf8");
60
+ const titles = withoutCode(text)
61
+ .split("\n")
62
+ .filter((line) => /^# /.test(line)).length;
63
+ if (titles !== 1)
64
+ problems.push(`${docs}/${file}: ${titles} "# " headings — a page has exactly one, its title`);
65
+ const first = text
66
+ .replace(/^(\s*<!--[\s\S]*?-->)*/, "")
67
+ .split("\n")
68
+ .find((line) => line.trim() !== "");
69
+ if (titles === 1 && !first?.startsWith("# "))
70
+ problems.push(`${docs}/${file}: the "# " heading must come first`);
71
+ }
72
+ return problems;
73
+ };
74
+ //# sourceMappingURL=structure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structure.js","sourceRoot":"","sources":["../../src/release/structure.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { type Streams } from "./streams.js";
2
+ /**
3
+ * What a renderer can be asked for. `auto` is not here on purpose: choosing between a person and a
4
+ * pipe is the caller's job and happens once, so a renderer can never be handed a mode it has to
5
+ * resolve.
6
+ */
7
+ export declare const RENDER_FORMATS: readonly ["pretty", "json", "jsonl"];
8
+ export type RenderFormat = (typeof RENDER_FORMATS)[number];
9
+ export interface Renderer {
10
+ /** The one thing a caller asked for. In a machine mode this is the whole of stdout. */
11
+ result(value: unknown): void;
12
+ /** Each item on its own line. Only meaningful in `jsonl`; elsewhere it renders the whole list. */
13
+ stream(items: Iterable<unknown>): void;
14
+ note(message: string): void;
15
+ success(message: string): void;
16
+ warn(message: string): void;
17
+ failure(message: string): void;
18
+ }
19
+ export interface RendererOptions {
20
+ format: RenderFormat;
21
+ color: boolean;
22
+ streams?: Streams;
23
+ /** Drops `note`, `success` and `warn`. A failure is still said, and the result is untouched. */
24
+ quiet?: boolean;
25
+ }
26
+ /**
27
+ * **Every diagnostic goes to stderr, in every mode — pretty included.**
28
+ *
29
+ * The brief only requires that of the machine modes, but making it unconditional means §44 holds
30
+ * by construction instead of by a mode check somebody can forget. It also means piping human
31
+ * output still gives you the content and not the commentary.
32
+ */
33
+ export declare const createRenderer: ({ format, color, streams, quiet, }: RendererOptions) => Renderer;
34
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3D;;;;GAIG;AACH,eAAO,MAAM,cAAc,YAAI,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAU,CAAA;AAClE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAE1D,MAAM,WAAW,QAAQ;IACvB,uFAAuF;IACvF,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;IAC5B,kGAAkG;IAClG,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;IACtC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,YAAY,CAAA;IACpB,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gGAAgG;IAChG,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,uCAKxB,eAAe,KAAG,QAyCpB,CAAA"}
@@ -0,0 +1,55 @@
1
+ import { renderPretty } from "./pretty.js";
2
+ import { visibleControls } from "./sanitize.js";
3
+ import { processStreams } from "./streams.js";
4
+ /**
5
+ * What a renderer can be asked for. `auto` is not here on purpose: choosing between a person and a
6
+ * pipe is the caller's job and happens once, so a renderer can never be handed a mode it has to
7
+ * resolve.
8
+ */
9
+ export const RENDER_FORMATS = ["pretty", "json", "jsonl"];
10
+ /**
11
+ * **Every diagnostic goes to stderr, in every mode — pretty included.**
12
+ *
13
+ * The brief only requires that of the machine modes, but making it unconditional means §44 holds
14
+ * by construction instead of by a mode check somebody can forget. It also means piping human
15
+ * output still gives you the content and not the commentary.
16
+ */
17
+ export const createRenderer = ({ format, color, streams = processStreams, quiet = false, }) => {
18
+ // Sanitised in every mode, not just `pretty`: a diagnostic goes to stderr, and stderr is read
19
+ // by a person whatever stdout was asked to be. The message is often the other side's own words.
20
+ const mark = (symbol, message) => format === "pretty" ? `${symbol} ${visibleControls(message)}` : visibleControls(message);
21
+ const say = (symbol) => (message) => {
22
+ if (!quiet)
23
+ streams.diagnostic(mark(symbol, message));
24
+ };
25
+ const diagnostics = {
26
+ note: say("·"),
27
+ success: say("✓"),
28
+ warn: say("!"),
29
+ failure: (message) => streams.diagnostic(mark("✗", message)),
30
+ };
31
+ if (format === "jsonl") {
32
+ return {
33
+ ...diagnostics,
34
+ result: (value) => streams.data(JSON.stringify(value)),
35
+ stream: (items) => {
36
+ for (const item of items)
37
+ streams.data(JSON.stringify(item));
38
+ },
39
+ };
40
+ }
41
+ if (format === "pretty") {
42
+ return {
43
+ ...diagnostics,
44
+ result: (value) => streams.data(renderPretty(value, { color })),
45
+ stream: (items) => streams.data(renderPretty([...items], { color })),
46
+ };
47
+ }
48
+ // json — exactly one deterministic value, and nothing else, ever.
49
+ return {
50
+ ...diagnostics,
51
+ result: (value) => streams.data(JSON.stringify(value)),
52
+ stream: (items) => streams.data(JSON.stringify([...items])),
53
+ };
54
+ };
55
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAgB,MAAM,cAAc,CAAA;AAE3D;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAU,CAAA;AAsBlE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,EAC7B,MAAM,EACN,KAAK,EACL,OAAO,GAAG,cAAc,EACxB,KAAK,GAAG,KAAK,GACG,EAAY,EAAE;IAC9B,8FAA8F;IAC9F,gGAAgG;IAChG,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,OAAe,EAAE,EAAE,CAC/C,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;IAE1F,MAAM,GAAG,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,CAAC,OAAe,EAAE,EAAE;QAClD,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACvD,CAAC,CAAA;IAED,MAAM,WAAW,GAAG;QAClB,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC;QACd,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC;QACjB,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC;QACd,OAAO,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;KACrE,CAAA;IAED,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO;YACL,GAAG,WAAW;YACd,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACtD,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;gBAChB,KAAK,MAAM,IAAI,IAAI,KAAK;oBAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;YAC9D,CAAC;SACF,CAAA;IACH,CAAC;IAED,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,OAAO;YACL,GAAG,WAAW;YACd,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YAC/D,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;SACrE,CAAA;IACH,CAAC;IAED,kEAAkE;IAClE,OAAO;QACL,GAAG,WAAW;QACd,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACtD,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;KAC5D,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,27 @@
1
+ import type { ErrorCode } from "./errors.js";
2
+ export interface RetryConfig {
3
+ /** Attempts after the first. One means two attempts in total. */
4
+ retries: number;
5
+ baseDelayMs: number;
6
+ maxDelayMs: number;
7
+ /** Beyond this we stop waiting and hand back a structured error instead of blocking. */
8
+ maxRetryAfterMs: number;
9
+ }
10
+ export declare const DEFAULT_RETRY: RetryConfig;
11
+ /**
12
+ * Full jitter: the wait is uniform over `[0, min(cap, base · 2^n))`. Picking the exponential value
13
+ * itself synchronizes every client that failed at the same moment, which is the stampede the
14
+ * backoff exists to avoid.
15
+ *
16
+ * `attempt` is the attempt that just failed, counting from 1.
17
+ */
18
+ export declare const backoffMs: (attempt: number, config: RetryConfig, random: () => number) => number;
19
+ /**
20
+ * The three outcomes where nothing came back, and so there is nothing to classify.
21
+ *
22
+ * **This says the attempt failed without an answer — never that the operation is safe to repeat.**
23
+ * A request that died after leaving may well have been carried out, which is why a write gets
24
+ * `outcome_unknown` rather than a retry.
25
+ */
26
+ export declare const isTransportFailure: (code: ErrorCode) => boolean;
27
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,MAAM,WAAW,WAAW;IAC1B,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,wFAAwF;IACxF,eAAe,EAAE,MAAM,CAAA;CACxB;AAED,eAAO,MAAM,aAAa,EAAE,WAK3B,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,YAAa,MAAM,UAAU,WAAW,UAAU,MAAM,MAAM,KAAG,MAGtF,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,SAAU,SAAS,KAAG,OACmB,CAAA"}
package/dist/retry.js ADDED
@@ -0,0 +1,26 @@
1
+ export const DEFAULT_RETRY = {
2
+ retries: 1,
3
+ baseDelayMs: 250,
4
+ maxDelayMs: 10_000,
5
+ maxRetryAfterMs: 30_000,
6
+ };
7
+ /**
8
+ * Full jitter: the wait is uniform over `[0, min(cap, base · 2^n))`. Picking the exponential value
9
+ * itself synchronizes every client that failed at the same moment, which is the stampede the
10
+ * backoff exists to avoid.
11
+ *
12
+ * `attempt` is the attempt that just failed, counting from 1.
13
+ */
14
+ export const backoffMs = (attempt, config, random) => {
15
+ const ceiling = Math.min(config.maxDelayMs, config.baseDelayMs * 2 ** (attempt - 1));
16
+ return Math.round(random() * ceiling);
17
+ };
18
+ /**
19
+ * The three outcomes where nothing came back, and so there is nothing to classify.
20
+ *
21
+ * **This says the attempt failed without an answer — never that the operation is safe to repeat.**
22
+ * A request that died after leaving may well have been carried out, which is why a write gets
23
+ * `outcome_unknown` rather than a retry.
24
+ */
25
+ export const isTransportFailure = (code) => code === "timeout" || code === "network_error" || code === "cancelled";
26
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,aAAa,GAAgB;IACxC,OAAO,EAAE,CAAC;IACV,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,MAAM;IAClB,eAAe,EAAE,MAAM;CACxB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,OAAe,EAAE,MAAmB,EAAE,MAAoB,EAAU,EAAE;IAC9F,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,WAAW,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAA;IACpF,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,CAAA;AACvC,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAe,EAAW,EAAE,CAC7D,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,eAAe,IAAI,IAAI,KAAK,WAAW,CAAA"}