@hardfin/cli 0.1.1-dev.69 → 0.1.1-dev.71

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
@@ -85,6 +85,7 @@ Most commands are generated from the published API document rather than written
85
85
  | `src/command/surface.generated.ts` | Every endpoint as a command, rewritten by the generator |
86
86
  | `surface-overrides.json` | The operations whose generated name is wrong |
87
87
  | `spec/core.openapi.yaml` | The API document the generator reads, vendored here |
88
+ | `spec/source.json` | Where the vendored document came from, and whether it is a preview |
88
89
  | `scripts/generate-surface.mjs` | The generator |
89
90
 
90
91
  ### How an endpoint becomes a command
@@ -199,9 +200,10 @@ A field the API does not take null for is sent the word as text. The same holds
199
200
  hardfin asset accounting update ast_4f9xk2mq7plr8stz --unset useful-life
200
201
  ```
201
202
 
202
- Three things fail locally with exit code 2, before any request is made: a value outside an
203
- enum, a required field that is missing, and `--unset` on a field the API will not take null
204
- for.
203
+ Four things fail locally with exit code 2, before any request is made: a value outside an
204
+ enum, a required field that is missing, a list element missing a field every element needs,
205
+ and `--unset` on a field the API will not take null for. `--help` starts a required flag's
206
+ description with `Required.`, and names the fields every element of a repeated flag needs.
205
207
 
206
208
  ### When a command fails
207
209
 
@@ -277,16 +279,17 @@ hardfin agent-guide --json customer create | jq -r '.commands[0].flags[] | selec
277
279
  The JSON holds `cli`, `apiVersion`, `exitCodes`, `errorCodes`, `globalFlags` and `commands`.
278
280
  Each command names its `path`, its `endpoint`, whether it `runs`, and its flags.
279
281
 
280
- | A flag's | Holds |
281
- | ------------ | --------------------------------------------------------------------------- |
282
- | `required` | Present when the command fails without it |
283
- | `schema` | The `type`, and the `enum`, `format`, `minimum` or `maximum` that narrow it |
284
- | `sends` | The query parameter, or the path of the body field, the value goes to |
285
- | `clearable` | Present when `--unset` can clear the field |
286
- | `repeatable` | Present when the flag is given once per value |
287
- | `negatable` | Present when a boolean also takes a `--no-` form |
288
- | `element` | The fields one value carries as `key=value`, for an array of objects |
289
- | `default` | The value used when the flag is not given |
282
+ | A flag's | Holds |
283
+ | ----------------- | --------------------------------------------------------------------------- |
284
+ | `required` | Present when the command fails without it |
285
+ | `schema` | The `type`, and the `enum`, `format`, `minimum` or `maximum` that narrow it |
286
+ | `sends` | The query parameter, or the path of the body field, the value goes to |
287
+ | `clearable` | Present when `--unset` can clear the field |
288
+ | `repeatable` | Present when the flag is given once per value |
289
+ | `negatable` | Present when a boolean also takes a `--no-` form |
290
+ | `element` | The fields one value carries as `key=value`, for an array of objects |
291
+ | `elementRequired` | The fields of `element` every value must carry |
292
+ | `default` | The value used when the flag is not given |
290
293
 
291
294
  A part that is absent means no. The description is read from the same declarations the parser
292
295
  enforces, so it cannot say one thing while the command does another.
@@ -322,24 +325,61 @@ reads it. Nothing fetches a document during a build, a release, or CI.
322
325
  Refresh it with the script, which then rewrites the generated commands:
323
326
 
324
327
  ```sh
325
- scripts/update-spec.sh # reads hardfinhq/api-spec over your own git access
328
+ scripts/update-spec.sh # the newer of api-spec's main and active-preview
329
+ scripts/update-spec.sh --main # api-spec's main, the published contract
330
+ scripts/update-spec.sh --preview # api-spec's active-preview, and fails when there is none
326
331
  scripts/update-spec.sh ../hardfin # bundles the fragmented source in a monorepo checkout
327
332
  ```
328
333
 
329
- The monorepo form runs the same bundler at the same version CI uses, so the result matches
330
- what the api-spec bridge publishes. Commit the document and the generated commands together.
334
+ The first three read `hardfinhq/api-spec` over your own git access. The monorepo form runs the
335
+ same bundler at the same version CI uses, so the result matches what the api-spec bridge
336
+ publishes. Commit the document, `spec/source.json` and the generated files together.
331
337
 
332
- Two checks keep the pair honest.
338
+ #### Previews
333
339
 
334
- | Check | Refuses |
335
- | ------------- | ----------------------------------------------------------------------------- |
336
- | The generator | a document whose `info.version` is not a date, which means an earlier release |
337
- | CI | a vendored document that was updated without regenerating the commands |
340
+ `hardfinhq/api-spec` has two branches the script reads.
341
+
342
+ | Branch | Holds | Exists |
343
+ | ---------------- | ------------------------------------------------------- | --------------------------------------------- |
344
+ | `main` | The published contract, which the production API serves | always |
345
+ | `active-preview` | The next contract, before it publishes | while the monorepo's `api-preview` tag exists |
346
+
347
+ Pushing or moving the monorepo's `api-preview` tag rebuilds `active-preview` from `main` with
348
+ the tagged commit's document. Deleting the tag deletes the branch. With no option, the script
349
+ reads `active-preview` while it exists and its last commit is newer than `main`'s. Once `main`
350
+ publishes past it, `main` holds everything the preview did, and the script reads `main`.
351
+
352
+ A preview lets a dev build carry the next contract before it publishes. Merging a preview
353
+ document to `dev` publishes it as a `-dev` build like any other change.
354
+
355
+ The script records where the document came from in `spec/source.json`, including whether it is
356
+ a preview. A preview names an API version the production API does not serve yet, so it never
357
+ reaches a release.
358
+
359
+ | Check | Refuses |
360
+ | ------------------------------------------------ | ------------------------------------------------------- |
361
+ | The version check, on a pull request into `main` | a `spec/source.json` that names a preview |
362
+ | The release workflow, on a push to `main` | the same, for a preview that reached `main` another way |
363
+
364
+ Run `scripts/update-spec.sh --main` and commit the result before releasing a `dev` that carries a preview.
365
+
366
+ #### Checks on the document
367
+
368
+ Three checks keep the document and the generated files honest.
369
+
370
+ | Check | Refuses |
371
+ | ------------- | -------------------------------------------------------------------------------------------------------------------------- |
372
+ | The generator | a document whose `info.version` is not a date, which means an earlier release |
373
+ | CI | a vendored document that was updated without regenerating the commands |
374
+ | CI | an API version that differs from the document's, because the generator writes it to `src/command/api-version.generated.ts` |
338
375
 
339
376
  The first one matters because `hardfinhq/api-spec` can sit a release behind the monorepo
340
377
  while its bridge pull request is open. Generating from that document would replace the
341
378
  current commands with an earlier API's.
342
379
 
380
+ The API version every request sends is the document's `info.version`. Nothing sets it by hand,
381
+ so a new document cannot leave requests naming the version before it.
382
+
343
383
  ## Signing in
344
384
 
345
385
  ```sh
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e9a5da76-6485-5cc1-9d09-55076e5c9b22")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4a31fc43-cc3a-59be-a501-eb86f4115b3f")}catch(e){}}();
4
4
  import { createRequire } from "node:module";
5
5
  import { z } from "zod";
6
6
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
@@ -8,79 +8,6 @@ import { dirname, join, resolve, sep } from "node:path";
8
8
  import { parseEnv } from "node:util";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { homedir } from "node:os";
11
- //#region src/output/writer.ts
12
- /**
13
- * toText reads a value a caller supplied, which arrives typed as unknown. An object would
14
- * otherwise reach a request as the text "[object Object]".
15
- */
16
- function toText(value) {
17
- if (typeof value === "string") return value;
18
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
19
- if (value === void 0 || value === null) return "";
20
- return JSON.stringify(value);
21
- }
22
- /** Every control character but a newline and a tab, which are the two a line of text needs. */
23
- const CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
24
- /**
25
- * toPrintable makes text safe to show at a terminal. A name or a message from the API can
26
- * hold an escape sequence, which a terminal would act on. Each such character is written
27
- * as its code, so the text reads as what it holds.
28
- */
29
- function toPrintable(text) {
30
- return text.replace(CONTROL, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
31
- }
32
- /**
33
- * toJSON writes a value for whoever is reading the stream. A person at a terminal gets it
34
- * indented. A program gets it on one line, because the indentation of a page of records is
35
- * close to half of what an agent would otherwise have to read.
36
- */
37
- function toJSON(value, stream) {
38
- return stream.isTTY ? JSON.stringify(value, null, 2) : JSON.stringify(value);
39
- }
40
- /** writeData prints a command's result on stdout. */
41
- function writeData(value) {
42
- if (typeof value === "string") {
43
- const text = toPrintable(value);
44
- process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
45
- return;
46
- }
47
- process.stdout.write(`${toJSON(value ?? null, process.stdout)}\n`);
48
- }
49
- /** The code of the last failure written, which decides whether the CLI checks for an update. */
50
- let lastFailureCode;
51
- /** toLastFailureCode answers the code of the last failure this run wrote. */
52
- function toLastFailureCode() {
53
- return lastFailureCode;
54
- }
55
- /** writeFailure prints why a command failed on stderr, with a code only a machine reading JSON sees. */
56
- function writeFailure(code, message, isJSON, errors, requestId, status) {
57
- lastFailureCode = code;
58
- if (!isJSON) {
59
- process.stderr.write(`error: ${toPrintable(message)}${status === void 0 ? "" : ` (HTTP ${status})`}\n`);
60
- for (const entry of errors?.slice(1) ?? []) process.stderr.write(` ${toPrintable(entry.error)} (${entry.statusCode})\n`);
61
- if (requestId) process.stderr.write(` request ${toPrintable(requestId)}\n`);
62
- return;
63
- }
64
- const payload = {
65
- code,
66
- message,
67
- errors: errors ?? [{
68
- error: message,
69
- statusCode: 0
70
- }],
71
- requestId: requestId ?? null,
72
- status: status ?? null
73
- };
74
- process.stderr.write(`${toJSON(payload, process.stderr)}\n`);
75
- }
76
- /** isJSONOutput decides whether this invocation prints machine-readable output. */
77
- function isJSONOutput(flags) {
78
- if (flags["json"] === true) return true;
79
- return !process.stdout.isTTY;
80
- }
81
- /** version is what this build reports, read from the published package manifest. */
82
- const version$1 = createRequire(import.meta.url)("../package.json").version;
83
- //#endregion
84
11
  //#region src/report/expected.ts
85
12
  /**
86
13
  * ExpectedFailure is a failure the CLI anticipated: the person's input, their network, their
@@ -112,6 +39,8 @@ var TimedOut = class extends ExpectedFailure {
112
39
  this.name = "TimedOut";
113
40
  }
114
41
  };
42
+ /** version is what this build reports, read from the published package manifest. */
43
+ const version$1 = createRequire(import.meta.url)("../package.json").version;
115
44
  /** The values that mean no, for a variable that turns something on or off. */
116
45
  const OFF = /* @__PURE__ */ new Set([
117
46
  "0",
@@ -184,9 +113,11 @@ function toRelease(of) {
184
113
  return `hardfin-cli@${of}`;
185
114
  }
186
115
  //#endregion
116
+ //#region src/command/api-version.generated.ts
117
+ /** API_VERSION is the version of the API document the commands were generated from. */
118
+ const API_VERSION = "2026-10-22";
119
+ //#endregion
187
120
  //#region src/config/settings.ts
188
- /** The API version this build was written against, sent on every request. */
189
- const API_VERSION = "2026-10-21";
190
121
  /** The file a local build reads its overrides from, in the working directory. */
191
122
  const CONFIG_FILE = "config.local.json";
192
123
  const DEFAULT_API_URL = "https://api.hardfin.com/v2";
@@ -304,6 +235,77 @@ function toOrigin(apiUrl) {
304
235
  }
305
236
  }
306
237
  //#endregion
238
+ //#region src/output/writer.ts
239
+ /**
240
+ * toText reads a value a caller supplied, which arrives typed as unknown. An object would
241
+ * otherwise reach a request as the text "[object Object]".
242
+ */
243
+ function toText(value) {
244
+ if (typeof value === "string") return value;
245
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
246
+ if (value === void 0 || value === null) return "";
247
+ return JSON.stringify(value);
248
+ }
249
+ /** Every control character but a newline and a tab, which are the two a line of text needs. */
250
+ const CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
251
+ /**
252
+ * toPrintable makes text safe to show at a terminal. A name or a message from the API can
253
+ * hold an escape sequence, which a terminal would act on. Each such character is written
254
+ * as its code, so the text reads as what it holds.
255
+ */
256
+ function toPrintable(text) {
257
+ return text.replace(CONTROL, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
258
+ }
259
+ /**
260
+ * toJSON writes a value for whoever is reading the stream. A person at a terminal gets it
261
+ * indented. A program gets it on one line, because the indentation of a page of records is
262
+ * close to half of what an agent would otherwise have to read.
263
+ */
264
+ function toJSON(value, stream) {
265
+ return stream.isTTY ? JSON.stringify(value, null, 2) : JSON.stringify(value);
266
+ }
267
+ /** writeData prints a command's result on stdout. */
268
+ function writeData(value) {
269
+ if (typeof value === "string") {
270
+ const text = toPrintable(value);
271
+ process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
272
+ return;
273
+ }
274
+ process.stdout.write(`${toJSON(value ?? null, process.stdout)}\n`);
275
+ }
276
+ /** The code of the last failure written, which decides whether the CLI checks for an update. */
277
+ let lastFailureCode;
278
+ /** toLastFailureCode answers the code of the last failure this run wrote. */
279
+ function toLastFailureCode() {
280
+ return lastFailureCode;
281
+ }
282
+ /** writeFailure prints why a command failed on stderr, with a code only a machine reading JSON sees. */
283
+ function writeFailure(code, message, isJSON, errors, requestId, status) {
284
+ lastFailureCode = code;
285
+ if (!isJSON) {
286
+ process.stderr.write(`error: ${toPrintable(message)}${status === void 0 ? "" : ` (HTTP ${status})`}\n`);
287
+ for (const entry of errors?.slice(1) ?? []) process.stderr.write(` ${toPrintable(entry.error)} (${entry.statusCode})\n`);
288
+ if (requestId) process.stderr.write(` request ${toPrintable(requestId)}\n`);
289
+ return;
290
+ }
291
+ const payload = {
292
+ code,
293
+ message,
294
+ errors: errors ?? [{
295
+ error: message,
296
+ statusCode: 0
297
+ }],
298
+ requestId: requestId ?? null,
299
+ status: status ?? null
300
+ };
301
+ process.stderr.write(`${toJSON(payload, process.stderr)}\n`);
302
+ }
303
+ /** isJSONOutput decides whether this invocation prints machine-readable output. */
304
+ function isJSONOutput(flags) {
305
+ if (flags["json"] === true) return true;
306
+ return !process.stdout.isTTY;
307
+ }
308
+ //#endregion
307
309
  //#region src/system/state.ts
308
310
  /** toStateDirectory names the folder the CLI keeps what it remembers between runs in. */
309
311
  function toStateDirectory() {
@@ -494,7 +496,7 @@ function writeCache(cache) {
494
496
  } catch {}
495
497
  }
496
498
  //#endregion
497
- export { toPrintable as C, writeFailure as E, toLastFailureCode as S, writeData as T, LockHeld as _, isShipped as a, isJSONOutput as b, toOutdatedTo as c, CONFIG_FILE as d, ConfigFailure as f, Interrupted as g, ExpectedFailure as h, toUpdateCheckRefusal as i, toStateDirectory as l, toTelemetry as m, isReminderDue as n, toPackageDirectory as o, toSettings as p, toCheck as r, noteNotices as s, PACKAGE as t, API_VERSION as u, TimedOut as v, toText as w, toJSON as x, version$1 as y };
499
+ export { ExpectedFailure as C, TimedOut as E, version$1 as S, LockHeld as T, CONFIG_FILE as _, isShipped as a, API_VERSION as b, toOutdatedTo as c, toJSON as d, toLastFailureCode as f, writeFailure as g, writeData as h, toUpdateCheckRefusal as i, toStateDirectory as l, toText as m, isReminderDue as n, toPackageDirectory as o, toPrintable as p, toCheck as r, noteNotices as s, PACKAGE as t, isJSONOutput as u, ConfigFailure as v, Interrupted as w, toTelemetry as x, toSettings as y };
498
500
 
499
- //# sourceMappingURL=check-CDkHR_Qt.js.map
500
- //# debugId=e9a5da76-6485-5cc1-9d09-55076e5c9b22
501
+ //# sourceMappingURL=check-CvjTUx-K.js.map
502
+ //# debugId=4a31fc43-cc3a-59be-a501-eb86f4115b3f
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="6ad4927e-4221-5401-ac3e-392b1fcb2a19")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3bf6cdf0-d851-549b-9a43-bb3c8520e8ff")}catch(e){}}();
4
4
  import module from "node:module";
5
5
  //#region src/cli.ts
6
6
  module.enableCompileCache?.();
@@ -14,8 +14,8 @@ process.on("unhandledRejection", (error) => void toCrash(error));
14
14
  try {
15
15
  const answer = await toQuickAnswer(process.argv.slice(2));
16
16
  if (answer === void 0) {
17
- const { toCli, toParseExitCode, wasReportAttempted } = await import("./program-eT-hgKX0.js");
18
- const { startUpdateCheck, writeUpdateNotice } = await import("./notice-Bq9ifPV6.js");
17
+ const { toCli, toParseExitCode, wasReportAttempted } = await import("./program-CC8Iqb14.js");
18
+ const { startUpdateCheck, writeUpdateNotice } = await import("./notice-D6mbHF9O.js");
19
19
  const pending = startUpdateCheck(process.argv.slice(2));
20
20
  try {
21
21
  await toCli().parseAsync(process.argv);
@@ -36,7 +36,7 @@ async function toCrash(error) {
36
36
  isCrashing = true;
37
37
  const isJSON = process.argv.includes("--json") || !process.stdout.isTTY;
38
38
  try {
39
- const { toReported, writeCrash } = await import("./program-eT-hgKX0.js");
39
+ const { toReported, writeCrash } = await import("./program-CC8Iqb14.js");
40
40
  writeCrash(error, isJSON);
41
41
  await toReported(error, isJSON, void 0, "fatal");
42
42
  } catch {
@@ -57,4 +57,4 @@ async function toQuickAnswer(args) {
57
57
  export {};
58
58
 
59
59
  //# sourceMappingURL=cli.js.map
60
- //# debugId=6ad4927e-4221-5401-ac3e-392b1fcb2a19
60
+ //# debugId=3bf6cdf0-d851-549b-9a43-bb3c8520e8ff