@leemour/cli-core 0.8.0 → 0.9.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.
package/README.md CHANGED
@@ -7,8 +7,9 @@ keyring behind a testable seam, and injectable clocks.
7
7
  Extracted from [`brazecli`](https://github.com/leemour/brazecli), where each piece earned its
8
8
  shape, and shared with [`max-cli`](https://github.com/leemour/max-cli).
9
9
 
10
- **Status: 0.5.0.** Published on npm, used by `max-cli`. Both extraction steps have landed: the
11
- files that move unchanged, and the ones that needed a parameter threaded through. 75 tests.
10
+ Published on npm and used by `max-cli`, `cli-messaging`, `tg-cli` and `brazecli`. What changed in
11
+ each version is in [`CHANGELOG.md`](CHANGELOG.md); how the package is put together and released is in
12
+ [`docs/dev/`](docs/dev/ARCHITECTURE.md).
12
13
 
13
14
  ## The rule this package exists to keep
14
15
 
@@ -47,6 +48,7 @@ streams.stderr // []
47
48
  | `/completion` | shell completion over the registry: `suggest`, `formatSuggestions` — see below |
48
49
  | `/update` | keeping an install current: `installerOf`, `updateCommand`, `latestVersion`, `mayNotify`, `updateNotice`, `runUpdate` — see below |
49
50
  | `/codegen` | build time: an official API description → types, Valibot schemas, an operation manifest and a coverage page — see below |
51
+ | `/release` | release time: `releaseCheck` and the checks it runs — changelog shape, links, package contents, version in step — see below |
50
52
 
51
53
  **Nothing in the root export is HTTP.** Status classification, `Retry-After` parsing and the fetch
52
54
  seam live in `@leemour/cli-core/http`, so a CLI that speaks a socket never depends on a stack it
@@ -96,9 +98,10 @@ a throwaway config directory silently overwrites the real secret unless you pass
96
98
  And a secret should never be handed to a logger in the first place — redaction by field name is the
97
99
  second line of defence, not the first.
98
100
 
99
- **Everything the environment knows is passed in.** No `process.env` reads, no config file paths,
100
- no ambient clock. That is what makes a timeout test finish instantly and a keyring test incapable
101
- of reaching a real keychain.
101
+ **Everything the environment knows can be passed in**: the clock, the sleep, the keyring, `env`,
102
+ `fetch` and the streams. The real ones are only defaults — `Credentials` without a `keyring` uses the
103
+ system keychain, `resolvePaths` without `env` reads `process.env`. Passing them is what makes a
104
+ timeout test finish instantly and a keyring test incapable of reaching a real keychain.
102
105
 
103
106
  **Generating an API catalog is `@leemour/cli-core/codegen`**, a build-time tool. It reads a
104
107
  format-neutral `ApiModel` — operations with a transport binding (`http` method and path, or `rpc`
@@ -141,6 +144,30 @@ sentence or `undefined` — it never throws. `runUpdate` starts the package mana
141
144
  stderr (`spawnPlan` is the Windows `.cmd` rule). Nothing here updates or prints by itself — a CLI
142
145
  holding somebody's credentials must not change itself unasked.
143
146
 
147
+ **The release checks are `@leemour/cli-core/release`** — everything about a release that a program
148
+ can decide. A check is `{ name, run }`, and `run` returns the problems it found, one line each; an
149
+ empty list is a pass. `releaseCheck(checks, { version, log })` runs them all, even after a failure,
150
+ prints `ok` or `FAIL` per check and returns how many failed — the caller exits. The checks every CLI
151
+ shares: `command(root, "pnpm", "lint")` for anything that passes by exiting 0, `notOnNpm`,
152
+ `packContents` against a list of what may ship, `changelogProblems` for the fixed headings and the
153
+ dated top section, `docsProblems` for dead links and anchors and the rules of the pages a user reads,
154
+ and `versionScript` — the whole of a `version:check` / `version:sync` script. Each CLI passes its
155
+ own headings, id prefixes and pages; the checks that belong to one messenger stay in that CLI.
156
+
157
+ ```ts
158
+ import { command, notOnNpm, packageVersion, releaseCheck } from "@leemour/cli-core/release"
159
+
160
+ const version = packageVersion(root)
161
+ const failed = releaseCheck(
162
+ [
163
+ { name: "version not on npm", run: notOnNpm(root, "@leemour/tg-cli", version) },
164
+ { name: "lint", run: command(root, "pnpm", "lint") },
165
+ ],
166
+ { version, log: console.log },
167
+ )
168
+ process.exit(failed === 0 ? 0 : 1)
169
+ ```
170
+
144
171
  ## Where secrets actually go, per platform
145
172
 
146
173
  The keyring is real on every desktop and absent on most servers, so the fallback is not an edge
@@ -180,19 +207,23 @@ pnpm typecheck
180
207
  pnpm build
181
208
  ```
182
209
 
210
+ The rest of the checks, and what each is for, are in [`docs/dev/TESTING.md`](docs/dev/TESTING.md).
211
+
183
212
  ## Releasing
184
213
 
185
- Raise `version` in `package.json` through a pull request, merge it, then on `main`:
214
+ Raise `version` in `package.json` and date the `## Unreleased` section of
215
+ [`CHANGELOG.md`](CHANGELOG.md) as that version, through a pull request; merge it, then on `main`:
186
216
 
187
217
  ```sh
188
218
  bin/release
189
219
  ```
190
220
 
191
- It refuses a dirty tree, a branch other than `main`, a `main` that is not pushed and a version npm
192
- already has, then starts [`release.yml`](.github/workflows/release.yml) and follows it. The workflow
193
- runs every check, publishes through npm's
194
- [trusted publishing](https://docs.npmjs.com/trusted-publishers/) — no npm token in GitHub, and npm
195
- attaches provenance — and tags `v<version>` only once npm shows the new version.
221
+ It refuses a dirty tree, a branch other than `main` and a `main` that is not pushed. When npm already
222
+ has the version, or a higher one, it commits the next free version to `main` — the next minor for
223
+ `x.y.0`, the next patch otherwise — renames the changelog heading to match, and publishes that. Then it starts
224
+ [`release.yml`](.github/workflows/release.yml) and follows it. The workflow runs every check,
225
+ publishes through npm's [trusted publishing](https://docs.npmjs.com/trusted-publishers/) — no npm
226
+ token in GitHub, and npm attaches provenance — and tags `v<version>` only once npm shows the new version.
196
227
 
197
228
  npm trusts the workflow **by file name**: the package's Trusted Publisher settings on npmjs.com name
198
229
  `leemour` / `cli-core` / `release.yml`. Rename the file and publishing stops until they are updated.
package/dist/logger.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The only logging contract core knows about. The CLI adapts Pino to it; a Worker can pass
3
- * its own. Core never imports a logging library — see docs/ARCHITECTURE.md.
3
+ * its own. Core never imports a logging library — see docs/dev/ARCHITECTURE.md.
4
4
  */
5
5
  export interface Logger {
6
6
  debug(event: object, message?: string): void;
@@ -0,0 +1,37 @@
1
+ export { type ChangelogRules, changelogProblems, type DocsRules, docsProblems, idPattern, JOURNAL_IDS, markdownFiles, slug, } from "./markdown.js";
2
+ export type Check = {
3
+ name: string;
4
+ run: () => string[];
5
+ };
6
+ /**
7
+ * Runs every check even after one fails, so one pass shows the whole list, and returns how many
8
+ * failed. The caller exits: a library that calls `process.exit` cannot be tested.
9
+ */
10
+ export declare const releaseCheck: (checks: readonly Check[], { version, log }: {
11
+ version: string;
12
+ log: (line: string) => void;
13
+ }) => number;
14
+ export declare const run: (root: string, command: string, args: readonly string[]) => {
15
+ ok: boolean;
16
+ output: string;
17
+ };
18
+ /** A check that passes when the command exits 0, and otherwise shows the last 15 lines it printed. */
19
+ export declare const command: (root: string, name: string, ...args: string[]) => () => string[];
20
+ export declare const packageVersion: (root: string) => string;
21
+ export declare const notOnNpm: (root: string, packageName: string, version: string) => () => string[];
22
+ /**
23
+ * `allowed` holds exact paths, and directories ending in `/`; `said` is how the problem names them.
24
+ */
25
+ export declare const packProblems: (paths: readonly string[], allowed: readonly string[], said: string) => string[];
26
+ export declare const packContents: (root: string, allowed: readonly string[], said: string) => () => string[];
27
+ /**
28
+ * The whole of a `version:check` / `version:sync` script: what to print, and the exit code — 2 when
29
+ * either file cannot be read, 1 on drift. A CLI keeps its version in `package.json` and in a source
30
+ * file read at run time — `--version` must not load `package.json`, which is not there once the
31
+ * command is bundled — and the day they disagree is a release day.
32
+ */
33
+ export declare const versionScript: (root: string, argv: readonly string[], file?: string) => {
34
+ code: 0 | 1 | 2;
35
+ message: string;
36
+ };
37
+ //# 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;AAEtB,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,90 @@
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
+ /**
11
+ * Runs every check even after one fails, so one pass shows the whole list, and returns how many
12
+ * failed. The caller exits: a library that calls `process.exit` cannot be tested.
13
+ */
14
+ export const releaseCheck = (checks, { version, log }) => {
15
+ let failed = 0;
16
+ for (const { name, run } of checks) {
17
+ const problems = run();
18
+ log(`${problems.length === 0 ? "ok " : "FAIL"} ${name}`);
19
+ for (const problem of problems)
20
+ log(problem.replace(/^/gm, " "));
21
+ if (problems.length > 0)
22
+ failed++;
23
+ }
24
+ log(failed === 0 ? `\n${version}: every check passed` : `\n${version}: ${failed} check(s) failed`);
25
+ return failed;
26
+ };
27
+ export const run = (root, command, args) => {
28
+ const done = spawnSync(command, args, { cwd: root, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
29
+ const output = `${done.stdout ?? ""}${done.stderr ?? ""}${done.error ? String(done.error) : ""}`;
30
+ return { ok: done.status === 0, output };
31
+ };
32
+ /** A check that passes when the command exits 0, and otherwise shows the last 15 lines it printed. */
33
+ export const command = (root, name, ...args) => () => {
34
+ const { ok, output } = run(root, name, args);
35
+ return ok ? [] : [output.trim().split("\n").slice(-15).join("\n") || `exit ${name} ${args.join(" ")}`];
36
+ };
37
+ export const packageVersion = (root) => JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version;
38
+ export const notOnNpm = (root, packageName, version) => () => run(root, "npm", ["view", `${packageName}@${version}`, "version", "--prefer-online"]).ok
39
+ ? [`${version} is already on npm — raise the version`]
40
+ : [];
41
+ /**
42
+ * `allowed` holds exact paths, and directories ending in `/`; `said` is how the problem names them.
43
+ */
44
+ export const packProblems = (paths, allowed, said) => paths
45
+ .filter((path) => !allowed.some((entry) => (entry.endsWith("/") ? path.startsWith(entry) : path === entry)))
46
+ .map((path) => `npm pack: ${path} would ship — only ${said} may`);
47
+ export const packContents = (root, allowed, said) => () => {
48
+ const { ok, output } = run(root, "npm", ["pack", "--dry-run", "--json", "--ignore-scripts"]);
49
+ if (!ok)
50
+ return [output.trim()];
51
+ const [packed] = JSON.parse(output.slice(output.indexOf("[")));
52
+ return packProblems((packed?.files ?? []).map(({ path }) => path), allowed, said);
53
+ };
54
+ const VERSION = /export const VERSION = "([^"]*)"/;
55
+ const versionsOf = (root, file) => {
56
+ const declared = packageVersion(root);
57
+ if (typeof declared !== "string" || !/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(declared))
58
+ return `package.json has no usable "version" (got ${JSON.stringify(declared)})`;
59
+ const source = readFileSync(join(root, file), "utf8");
60
+ const current = VERSION.exec(source)?.[1];
61
+ if (!current)
62
+ return `${file} does not export a VERSION string`;
63
+ return { declared, current, source };
64
+ };
65
+ /**
66
+ * The whole of a `version:check` / `version:sync` script: what to print, and the exit code — 2 when
67
+ * either file cannot be read, 1 on drift. A CLI keeps its version in `package.json` and in a source
68
+ * file read at run time — `--version` must not load `package.json`, which is not there once the
69
+ * command is bundled — and the day they disagree is a release day.
70
+ */
71
+ export const versionScript = (root, argv, file = "src/version.ts") => {
72
+ const found = versionsOf(root, file);
73
+ if (typeof found === "string")
74
+ return { code: 2, message: found };
75
+ const { declared, current, source } = found;
76
+ if (argv.includes("--sync")) {
77
+ if (current === declared)
78
+ return { code: 0, message: `version ${declared} — already in step` };
79
+ writeFileSync(join(root, file), source.replace(`"${current}"`, `"${declared}"`));
80
+ return { code: 0, message: `${file}: ${current} → ${declared}` };
81
+ }
82
+ if (current !== declared)
83
+ return {
84
+ code: 1,
85
+ message: `version drift: package.json says ${declared}, ${file} says ${current}.\n` +
86
+ "Run `pnpm version:sync` — package.json is the one that is edited.",
87
+ };
88
+ return { code: 0, message: `version ${declared} — package.json and ${file} agree` };
89
+ };
90
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/release/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,OAAO,EAEL,iBAAiB,EAEjB,YAAY,EACZ,SAAS,EACT,WAAW,EACX,aAAa,EACb,IAAI,GACL,MAAM,eAAe,CAAA;AAItB;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,MAAwB,EACxB,EAAE,OAAO,EAAE,GAAG,EAAoD,EAClE,EAAE;IACF,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,KAAK,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,MAAM,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAA;QACtB,GAAG,CAAC,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC,CAAA;QAC1D,KAAK,MAAM,OAAO,IAAI,QAAQ;YAAE,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAA;QACrE,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,EAAE,CAAA;IACnC,CAAC;IACD,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,sBAAsB,CAAC,CAAC,CAAC,KAAK,OAAO,KAAK,MAAM,kBAAkB,CAAC,CAAA;IAClG,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,IAAuB,EAAE,EAAE;IAC5E,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC,CAAA;IACnG,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;IAChG,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;AAC1C,CAAC,CAAA;AAED,sGAAsG;AACtG,MAAM,CAAC,MAAM,OAAO,GAClB,CAAC,IAAY,EAAE,IAAY,EAAE,GAAG,IAAc,EAAE,EAAE,CAClD,GAAa,EAAE;IACb,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;IAC5C,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;AACxG,CAAC,CAAA;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,IAAY,EAAE,EAAE,CAC5C,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAyB,CAAC,OAAO,CAAA;AAE/F,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,WAAmB,EAAE,OAAe,EAAE,EAAE,CAAC,GAAa,EAAE,CAC7F,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,WAAW,IAAI,OAAO,EAAE,EAAE,SAAS,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE;IACtF,CAAC,CAAC,CAAC,GAAG,OAAO,wCAAwC,CAAC;IACtD,CAAC,CAAC,EAAE,CAAA;AAER;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAwB,EAAE,OAA0B,EAAE,IAAY,EAAE,EAAE,CACjG,KAAK;KACF,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC;KAC3G,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,IAAI,sBAAsB,IAAI,MAAM,CAAC,CAAA;AAErE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,OAA0B,EAAE,IAAY,EAAE,EAAE,CAAC,GAAa,EAAE;IACrG,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAA;IAC5F,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;IAC/B,MAAM,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAoC,CAAA;IACjG,OAAO,YAAY,CACjB,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,EAC7C,OAAO,EACP,IAAI,CACL,CAAA;AACH,CAAC,CAAA;AAED,MAAM,OAAO,GAAG,kCAAkC,CAAA;AAElD,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,IAAY,EAAE,EAAE;IAChD,MAAM,QAAQ,GAAY,cAAc,CAAC,IAAI,CAAC,CAAA;IAC9C,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,mCAAmC,CAAC,IAAI,CAAC,QAAQ,CAAC;QACrF,OAAO,6CAA6C,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAA;IACjF,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAA;IACrD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IACzC,IAAI,CAAC,OAAO;QAAE,OAAO,GAAG,IAAI,mCAAmC,CAAA;IAC/D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,CAAA;AACtC,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,IAAY,EACZ,IAAuB,EACvB,IAAI,GAAG,gBAAgB,EACe,EAAE;IACxC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;IACjE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;IAC3C,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,IAAI,OAAO,KAAK,QAAQ;YAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,WAAW,QAAQ,oBAAoB,EAAE,CAAA;QAC9F,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,EAAE,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAA;QAChF,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,KAAK,OAAO,MAAM,QAAQ,EAAE,EAAE,CAAA;IAClE,CAAC;IACD,IAAI,OAAO,KAAK,QAAQ;QACtB,OAAO;YACL,IAAI,EAAE,CAAC;YACP,OAAO,EACL,oCAAoC,QAAQ,KAAK,IAAI,SAAS,OAAO,KAAK;gBAC1E,mEAAmE;SACtE,CAAA;IACH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,WAAW,QAAQ,uBAAuB,IAAI,QAAQ,EAAE,CAAA;AACrF,CAAC,CAAA"}
@@ -0,0 +1,35 @@
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
+ /** Every `.md` under `directory`, not descending into a directory named in `skip`. */
23
+ export declare const markdownFiles: (directory: string, skip?: ReadonlySet<string>) => string[];
24
+ export type DocsRules = {
25
+ /** Absolute paths; a missing one is passed over. */
26
+ files: readonly string[];
27
+ ids: readonly string[];
28
+ /** A page a user reads, by its path relative to `root`: no correction mark, struck-out text or id. */
29
+ userPage?: (name: string) => boolean;
30
+ correction?: RegExp;
31
+ /** A link not to follow — a private or sibling checkout that CI does not have. */
32
+ skipLink?: (target: string, destination: string) => boolean;
33
+ };
34
+ export declare const docsProblems: (root: string, { files, ids, userPage, correction, skipLink }: DocsRules) => string[];
35
+ //# 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;AA2BvB,sFAAsF;AACtF,eAAO,MAAM,aAAa,cAAe,MAAM,SAAQ,WAAW,CAAC,MAAM,CAAC,KAAe,MAAM,EAQvF,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,150 @@
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
+ 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
+ if (entry.isDirectory())
98
+ return markdownFiles(path, skip);
99
+ return entry.name.endsWith(".md") ? [path] : [];
100
+ })
101
+ : [];
102
+ export const docsProblems = (root, { files, ids, userPage = () => false, correction = /correction \d{4}/i, skipLink = () => false }) => {
103
+ const id = idPattern(ids);
104
+ const cache = new Map();
105
+ const anchors = (path) => {
106
+ let found = cache.get(path);
107
+ if (!found) {
108
+ found = anchorsOf(readFileSync(path, "utf8"));
109
+ cache.set(path, found);
110
+ }
111
+ return found;
112
+ };
113
+ const problems = [];
114
+ for (const path of files.filter((file) => existsSync(file))) {
115
+ const name = relative(root, path);
116
+ const user = userPage(name);
117
+ withoutCode(readFileSync(path, "utf8"))
118
+ .split("\n")
119
+ .forEach((line, index) => {
120
+ const where = `${name}:${index + 1}`;
121
+ for (const [, target] of line.matchAll(/!?\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g)) {
122
+ if (!target || /^[a-z][a-z0-9+.-]*:/i.test(target))
123
+ continue;
124
+ const [file = "", anchor] = decodeURIComponent(target).split("#");
125
+ const destination = file === "" ? path : resolve(dirname(path), file);
126
+ if (skipLink(file, destination))
127
+ continue;
128
+ if (!existsSync(destination)) {
129
+ problems.push(`${where}: link to ${file} — no such file`);
130
+ continue;
131
+ }
132
+ if (anchor &&
133
+ destination.endsWith(".md") &&
134
+ statSync(destination).isFile() &&
135
+ !anchors(destination).has(anchor))
136
+ problems.push(`${where}: link to ${file}#${anchor} — no such heading`);
137
+ }
138
+ if (!user)
139
+ return;
140
+ if (correction.test(line))
141
+ problems.push(`${where}: a correction mark on a user page`);
142
+ if (line.includes("~~"))
143
+ problems.push(`${where}: struck-out text on a user page`);
144
+ for (const found of line.match(id) ?? [])
145
+ problems.push(`${where}: internal id ${found} on a user page`);
146
+ });
147
+ }
148
+ return problems;
149
+ };
150
+ //# 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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leemour/cli-core",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "The parts every command line tool needs: output modes, terminal rendering, error model, exit codes, credentials, clocks",
5
5
  "license": "MIT",
6
6
  "author": "Viacheslav Ptsarev",
@@ -12,6 +12,7 @@
12
12
  "engines": {
13
13
  "node": ">=22"
14
14
  },
15
+ "packageManager": "pnpm@11.20.0",
15
16
  "files": [
16
17
  "dist",
17
18
  "README.md",
@@ -49,8 +50,27 @@
49
50
  "./codegen/runtime": {
50
51
  "types": "./dist/codegen/runtime.d.ts",
51
52
  "default": "./dist/codegen/runtime.js"
53
+ },
54
+ "./release": {
55
+ "types": "./dist/release/index.d.ts",
56
+ "default": "./dist/release/index.js"
52
57
  }
53
58
  },
59
+ "scripts": {
60
+ "build": "tsc --build",
61
+ "typecheck": "tsc --build --force --emitDeclarationOnly && tsc -p tsconfig.test.json && tsc -p tsconfig.scripts.json",
62
+ "lint": "biome check .",
63
+ "lint:fix": "biome check --write .",
64
+ "docs:check": "pnpm build && node --experimental-strip-types scripts/docs-check.ts",
65
+ "test": "vitest run",
66
+ "test:watch": "vitest",
67
+ "test:coverage": "vitest run --coverage",
68
+ "test:slow": "vitest run --reporter=json --outputFile=coverage/tests.json >/dev/null; node --experimental-strip-types scripts/slow-tests.ts",
69
+ "smoke:bun": "bun run scripts/smoke.ts",
70
+ "prepare": "lefthook install || true",
71
+ "secrets:scan": "gitleaks git --no-banner --redact .",
72
+ "prepublishOnly": "pnpm build"
73
+ },
54
74
  "dependencies": {
55
75
  "cli-table3": "^0.6.5",
56
76
  "env-paths": "^4.0.0",
@@ -64,10 +84,11 @@
64
84
  "devDependencies": {
65
85
  "@biomejs/biome": "^2.3.14",
66
86
  "@types/node": "^22.20.4",
87
+ "@vitest/coverage-v8": "^5.0.2",
67
88
  "commander": "^15.0.0",
68
89
  "lefthook": "^2.1.8",
69
90
  "typescript": "^7.0.2",
70
- "vitest": "^5.0.1"
91
+ "vitest": "^5.0.2"
71
92
  },
72
93
  "publishConfig": {
73
94
  "access": "public"
@@ -79,15 +100,5 @@
79
100
  "commander": {
80
101
  "optional": true
81
102
  }
82
- },
83
- "scripts": {
84
- "build": "tsc --build",
85
- "typecheck": "tsc --build --force --emitDeclarationOnly",
86
- "lint": "biome check .",
87
- "lint:fix": "biome check --write .",
88
- "test": "vitest run",
89
- "test:watch": "vitest",
90
- "smoke:bun": "bun run scripts/smoke.ts",
91
- "secrets:scan": "gitleaks git --no-banner --redact ."
92
103
  }
93
- }
104
+ }