@leemour/cli-core 0.1.0 → 0.1.2

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,8 @@ 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.1.0, not published yet.** Both extraction steps have landed: the files that move
11
- unchanged, and the ones that needed a parameter threaded through. 73 tests.
10
+ **Status: 0.1.2.** 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.
12
12
 
13
13
  ## The rule this package exists to keep
14
14
 
@@ -40,7 +40,7 @@ streams.stderr // []
40
40
  | `paths` | `env-paths` for config, state and cache, each overridable by environment variable |
41
41
  | `config` | JSON config loading that names the bad field, and an atomic write that locks the directory down |
42
42
  | `credentials` | environment → keyring → file, warning once and falling through when the keyring refuses |
43
- | `logging` | a Pino adapter writing JSON lines with secrets redacted by field name |
43
+ | `logging` | a Pino adapter writing JSON lines with secrets redacted by field name; a file that cannot be written is reported to `onError`, never thrown |
44
44
  | `retry` | full-jitter backoff, and the distinction between "no answer came" and "safe to repeat" |
45
45
  | `/testing` | `captureStreams`, `memoryKeyring`, `brokenKeyring`, `fakeClock` |
46
46
 
@@ -101,6 +101,27 @@ pnpm typecheck
101
101
  pnpm build
102
102
  ```
103
103
 
104
+ ## Releasing
105
+
106
+ Raise `version` in `package.json` through a pull request, merge it, then on `main`:
107
+
108
+ ```sh
109
+ bin/release
110
+ ```
111
+
112
+ It refuses a dirty tree, a branch other than `main`, a `main` that is not pushed and a version npm
113
+ already has, then starts [`release.yml`](.github/workflows/release.yml) and follows it. The workflow
114
+ runs every check, publishes through npm's
115
+ [trusted publishing](https://docs.npmjs.com/trusted-publishers/) — no npm token in GitHub, and npm
116
+ attaches provenance — and tags `v<version>` only once npm shows the new version.
117
+
118
+ npm trusts the workflow **by file name**: the package's Trusted Publisher settings on npmjs.com name
119
+ `leemour` / `cli-core` / `release.yml`. Rename the file and publishing stops until they are updated.
120
+
121
+ `bin/release --local` is the fallback: it runs the same checks here and publishes with the npm token
122
+ from the keyring (`secret-tool`, service `npm`, account `leemour`) without printing it. The token
123
+ must be allowed to write `@leemour/cli-core`, not only `@leemour/max-cli`.
124
+
104
125
  ## Licence
105
126
 
106
127
  MIT.
package/dist/logging.d.ts CHANGED
@@ -16,11 +16,16 @@ export interface FileLoggerOptions {
16
16
  base?: Record<string, unknown>;
17
17
  /** Added to `REDACTED_FIELDS` — the names only this program knows about. */
18
18
  redact?: string[];
19
+ /**
20
+ * Told once when the file cannot be opened or written; every record after that is dropped.
21
+ * Without it the failure is silent, so a host that promised a log should say it is gone.
22
+ */
23
+ onError?: (error: Error) => void;
19
24
  }
20
25
  /**
21
26
  * Structured records for machines: JSON lines, no ANSI ever, secrets redacted. The terminal is a
22
27
  * different concern and goes through the renderer — a spinner frame must never reach a log file,
23
28
  * and a log record must never reach stdout.
24
29
  */
25
- export declare const createFileLogger: ({ path, level, base, redact, }?: FileLoggerOptions) => FileLogger;
30
+ export declare const createFileLogger: ({ path, level, base, redact, onError, }?: FileLoggerOptions) => FileLogger;
26
31
  //# sourceMappingURL=logging.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC;;;;GAIG;AACH,eAAO,MAAM,eAAe,UAa3B,CAAA;AAED,MAAM,WAAW,UAAW,SAAQ,MAAM;IACxC,iFAAiF;IACjF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAClB;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,iCAK9B,iBAAsB,KAAG,UA2B3B,CAAA"}
1
+ {"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC;;;;GAIG;AACH,eAAO,MAAM,eAAe,UAa3B,CAAA;AAED,MAAM,WAAW,UAAW,SAAQ,MAAM;IACxC,iFAAiF;IACjF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,0CAM9B,iBAAsB,KAAG,UA8B3B,CAAA"}
package/dist/logging.js CHANGED
@@ -24,13 +24,16 @@ export const REDACTED_FIELDS = [
24
24
  * different concern and goes through the renderer — a spinner frame must never reach a log file,
25
25
  * and a log record must never reach stdout.
26
26
  */
27
- export const createFileLogger = ({ path, level = "info", base = {}, redact = [], } = {}) => {
27
+ export const createFileLogger = ({ path, level = "info", base = {}, redact = [], onError = () => { }, } = {}) => {
28
28
  if (!path)
29
29
  return { ...silent, close: async () => { } };
30
30
  // A plain append stream, not a worker transport: logging must not need a second process, and
31
31
  // `sync: false` drops the tail of the file when a command exits — which is exactly the failure
32
32
  // the log exists to explain.
33
33
  const file = createWriteStream(path, { flags: "a" });
34
+ // The open is asynchronous, so a directory removed a moment ago surfaces here as an `error`
35
+ // event — and one with no listener kills the process the log was only meant to describe.
36
+ file.on("error", onError);
34
37
  const fields = [...new Set([...REDACTED_FIELDS, ...redact])];
35
38
  const paths = fields.flatMap((field) => [field, `*.${field}`, `*.*.${field}`]);
36
39
  const logger = pino({ level, base, redact: { paths, censor: "[redacted]" }, timestamp: pino.stdTimeFunctions.isoTime }, file);
@@ -1 +1 @@
1
- {"version":3,"file":"logging.js","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,eAAe;IACf,eAAe;IACf,QAAQ;IACR,SAAS;IACT,OAAO;IACP,aAAa;IACb,cAAc;IACd,cAAc;IACd,UAAU;IACV,QAAQ;IACR,YAAY;IACZ,OAAO;CACR,CAAA;AAgBD;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAC/B,IAAI,EACJ,KAAK,GAAG,MAAM,EACd,IAAI,GAAG,EAAE,EACT,MAAM,GAAG,EAAE,MACU,EAAE,EAAc,EAAE;IACvC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC,EAAE,CAAA;IAEtD,6FAA6F;IAC7F,+FAA+F;IAC/F,6BAA6B;IAC7B,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;IACpD,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,eAAe,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;IAC5D,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,EAAE,EAAE,OAAO,KAAK,EAAE,CAAC,CAAC,CAAA;IAE9E,MAAM,MAAM,GAAG,IAAI,CACjB,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,EAClG,IAAI,CACL,CAAA;IAED,OAAO;QACL,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC;QACvD,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC;QACrD,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC;QACrD,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC;QACvD,KAAK,EAAE,GAAG,EAAE,CACV,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE;gBACZ,OAAO,EAAE,CAAA;YACX,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC;KACL,CAAA;AACH,CAAC,CAAA;AAED,MAAM,MAAM,GAAW;IACrB,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;IACf,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;CAChB,CAAA"}
1
+ {"version":3,"file":"logging.js","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,eAAe;IACf,eAAe;IACf,QAAQ;IACR,SAAS;IACT,OAAO;IACP,aAAa;IACb,cAAc;IACd,cAAc;IACd,UAAU;IACV,QAAQ;IACR,YAAY;IACZ,OAAO;CACR,CAAA;AAqBD;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAC/B,IAAI,EACJ,KAAK,GAAG,MAAM,EACd,IAAI,GAAG,EAAE,EACT,MAAM,GAAG,EAAE,EACX,OAAO,GAAG,GAAG,EAAE,GAAE,CAAC,MACG,EAAE,EAAc,EAAE;IACvC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC,EAAE,CAAA;IAEtD,6FAA6F;IAC7F,+FAA+F;IAC/F,6BAA6B;IAC7B,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;IACpD,4FAA4F;IAC5F,yFAAyF;IACzF,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IACzB,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,eAAe,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;IAC5D,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,EAAE,EAAE,OAAO,KAAK,EAAE,CAAC,CAAC,CAAA;IAE9E,MAAM,MAAM,GAAG,IAAI,CACjB,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,EAClG,IAAI,CACL,CAAA;IAED,OAAO;QACL,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC;QACvD,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC;QACrD,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC;QACrD,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC;QACvD,KAAK,EAAE,GAAG,EAAE,CACV,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE;gBACZ,OAAO,EAAE,CAAA;YACX,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC;KACL,CAAA;AACH,CAAC,CAAA;AAED,MAAM,MAAM,GAAW;IACrB,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;IACf,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;CAChB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leemour/cli-core",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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",
@@ -31,6 +32,18 @@
31
32
  "default": "./dist/http/index.js"
32
33
  }
33
34
  },
35
+ "scripts": {
36
+ "build": "tsc --build",
37
+ "typecheck": "tsc --build --force --emitDeclarationOnly",
38
+ "lint": "biome check .",
39
+ "lint:fix": "biome check --write .",
40
+ "test": "vitest run",
41
+ "test:watch": "vitest",
42
+ "smoke:bun": "bun run scripts/smoke.ts",
43
+ "prepare": "lefthook install || true",
44
+ "secrets:scan": "gitleaks git --no-banner --redact .",
45
+ "prepublishOnly": "pnpm build"
46
+ },
34
47
  "dependencies": {
35
48
  "cli-table3": "^0.6.5",
36
49
  "picocolors": "^1.1.1",
@@ -50,15 +63,5 @@
50
63
  },
51
64
  "publishConfig": {
52
65
  "access": "public"
53
- },
54
- "scripts": {
55
- "build": "tsc --build",
56
- "typecheck": "tsc --build --force --emitDeclarationOnly",
57
- "lint": "biome check .",
58
- "lint:fix": "biome check --write .",
59
- "test": "vitest run",
60
- "test:watch": "vitest",
61
- "smoke:bun": "bun run scripts/smoke.ts",
62
- "secrets:scan": "gitleaks git --no-banner --redact ."
63
66
  }
64
- }
67
+ }