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

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
@@ -199,9 +199,10 @@ A field the API does not take null for is sent the word as text. The same holds
199
199
  hardfin asset accounting update ast_4f9xk2mq7plr8stz --unset useful-life
200
200
  ```
201
201
 
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.
202
+ Four things fail locally with exit code 2, before any request is made: a value outside an
203
+ enum, a required field that is missing, a list element missing a field every element needs,
204
+ and `--unset` on a field the API will not take null for. `--help` starts a required flag's
205
+ description with `Required.`, and names the fields every element of a repeated flag needs.
205
206
 
206
207
  ### When a command fails
207
208
 
@@ -277,16 +278,17 @@ hardfin agent-guide --json customer create | jq -r '.commands[0].flags[] | selec
277
278
  The JSON holds `cli`, `apiVersion`, `exitCodes`, `errorCodes`, `globalFlags` and `commands`.
278
279
  Each command names its `path`, its `endpoint`, whether it `runs`, and its flags.
279
280
 
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 |
281
+ | A flag's | Holds |
282
+ | ----------------- | --------------------------------------------------------------------------- |
283
+ | `required` | Present when the command fails without it |
284
+ | `schema` | The `type`, and the `enum`, `format`, `minimum` or `maximum` that narrow it |
285
+ | `sends` | The query parameter, or the path of the body field, the value goes to |
286
+ | `clearable` | Present when `--unset` can clear the field |
287
+ | `repeatable` | Present when the flag is given once per value |
288
+ | `negatable` | Present when a boolean also takes a `--no-` form |
289
+ | `element` | The fields one value carries as `key=value`, for an array of objects |
290
+ | `elementRequired` | The fields of `element` every value must carry |
291
+ | `default` | The value used when the flag is not given |
290
292
 
291
293
  A part that is absent means no. The description is read from the same declarations the parser
292
294
  enforces, so it cannot say one thing while the command does another.
@@ -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]="9d637f5e-0cda-5582-80ff-432edc41bf8c")}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",
@@ -304,6 +233,77 @@ function toOrigin(apiUrl) {
304
233
  }
305
234
  }
306
235
  //#endregion
236
+ //#region src/output/writer.ts
237
+ /**
238
+ * toText reads a value a caller supplied, which arrives typed as unknown. An object would
239
+ * otherwise reach a request as the text "[object Object]".
240
+ */
241
+ function toText(value) {
242
+ if (typeof value === "string") return value;
243
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
244
+ if (value === void 0 || value === null) return "";
245
+ return JSON.stringify(value);
246
+ }
247
+ /** Every control character but a newline and a tab, which are the two a line of text needs. */
248
+ const CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
249
+ /**
250
+ * toPrintable makes text safe to show at a terminal. A name or a message from the API can
251
+ * hold an escape sequence, which a terminal would act on. Each such character is written
252
+ * as its code, so the text reads as what it holds.
253
+ */
254
+ function toPrintable(text) {
255
+ return text.replace(CONTROL, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
256
+ }
257
+ /**
258
+ * toJSON writes a value for whoever is reading the stream. A person at a terminal gets it
259
+ * indented. A program gets it on one line, because the indentation of a page of records is
260
+ * close to half of what an agent would otherwise have to read.
261
+ */
262
+ function toJSON(value, stream) {
263
+ return stream.isTTY ? JSON.stringify(value, null, 2) : JSON.stringify(value);
264
+ }
265
+ /** writeData prints a command's result on stdout. */
266
+ function writeData(value) {
267
+ if (typeof value === "string") {
268
+ const text = toPrintable(value);
269
+ process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
270
+ return;
271
+ }
272
+ process.stdout.write(`${toJSON(value ?? null, process.stdout)}\n`);
273
+ }
274
+ /** The code of the last failure written, which decides whether the CLI checks for an update. */
275
+ let lastFailureCode;
276
+ /** toLastFailureCode answers the code of the last failure this run wrote. */
277
+ function toLastFailureCode() {
278
+ return lastFailureCode;
279
+ }
280
+ /** writeFailure prints why a command failed on stderr, with a code only a machine reading JSON sees. */
281
+ function writeFailure(code, message, isJSON, errors, requestId, status) {
282
+ lastFailureCode = code;
283
+ if (!isJSON) {
284
+ process.stderr.write(`error: ${toPrintable(message)}${status === void 0 ? "" : ` (HTTP ${status})`}\n`);
285
+ for (const entry of errors?.slice(1) ?? []) process.stderr.write(` ${toPrintable(entry.error)} (${entry.statusCode})\n`);
286
+ if (requestId) process.stderr.write(` request ${toPrintable(requestId)}\n`);
287
+ return;
288
+ }
289
+ const payload = {
290
+ code,
291
+ message,
292
+ errors: errors ?? [{
293
+ error: message,
294
+ statusCode: 0
295
+ }],
296
+ requestId: requestId ?? null,
297
+ status: status ?? null
298
+ };
299
+ process.stderr.write(`${toJSON(payload, process.stderr)}\n`);
300
+ }
301
+ /** isJSONOutput decides whether this invocation prints machine-readable output. */
302
+ function isJSONOutput(flags) {
303
+ if (flags["json"] === true) return true;
304
+ return !process.stdout.isTTY;
305
+ }
306
+ //#endregion
307
307
  //#region src/system/state.ts
308
308
  /** toStateDirectory names the folder the CLI keeps what it remembers between runs in. */
309
309
  function toStateDirectory() {
@@ -494,7 +494,7 @@ function writeCache(cache) {
494
494
  } catch {}
495
495
  }
496
496
  //#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 };
497
+ export { ExpectedFailure as C, TimedOut as E, version$1 as S, LockHeld as T, API_VERSION as _, isShipped as a, toSettings 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, CONFIG_FILE as v, Interrupted as w, toTelemetry as x, ConfigFailure as y };
498
498
 
499
- //# sourceMappingURL=check-CDkHR_Qt.js.map
500
- //# debugId=e9a5da76-6485-5cc1-9d09-55076e5c9b22
499
+ //# sourceMappingURL=check-wgnggJ45.js.map
500
+ //# debugId=9d637f5e-0cda-5582-80ff-432edc41bf8c
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]="e43cc9b0-2707-553a-973d-9a707e329000")}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-CYp6XF8D.js");
18
+ const { startUpdateCheck, writeUpdateNotice } = await import("./notice-CQ5Nlp80.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-CYp6XF8D.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=e43cc9b0-2707-553a-973d-9a707e329000
@@ -1,7 +1,7 @@
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]="85e43a08-810a-5f4e-9d70-a869c9a65ee1")}catch(e){}}();
4
- import { S as toLastFailureCode, c as toOutdatedTo, i as toUpdateCheckRefusal, n as isReminderDue, o as toPackageDirectory, r as toCheck, t as PACKAGE, u as API_VERSION, x as toJSON } from "./check-CDkHR_Qt.js";
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]="46b26b33-c4ba-5ddd-9436-b5f91120d1da")}catch(e){}}();
4
+ import { _ as API_VERSION, c as toOutdatedTo, d as toJSON, f as toLastFailureCode, i as toUpdateCheckRefusal, n as isReminderDue, o as toPackageDirectory, r as toCheck, t as PACKAGE } from "./check-wgnggJ45.js";
5
5
  //#region src/update/notice.ts
6
6
  /**
7
7
  * The failures a newer CLI may not have, because each can mean the API changed under this one:
@@ -130,5 +130,5 @@ function toCommandName(args) {
130
130
  //#endregion
131
131
  export { startUpdateCheck, writeUpdateNotice };
132
132
 
133
- //# sourceMappingURL=notice-Bq9ifPV6.js.map
134
- //# debugId=85e43a08-810a-5f4e-9d70-a869c9a65ee1
133
+ //# sourceMappingURL=notice-CQ5Nlp80.js.map
134
+ //# debugId=46b26b33-c4ba-5ddd-9436-b5f91120d1da
@@ -1,8 +1,8 @@
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]="ef42eac6-02c0-5ed0-ad0b-c3d60bfb51f8")}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]="2061590a-8bec-56a7-864d-cb1435ccb994")}catch(e){}}();
4
4
  import { a as defineCommand, i as GLOBAL_FLAGS, r as ExitCode, t as toInvocation } from "./scrub-BZ0egWxC.js";
5
- import { C as toPrintable, E as writeFailure, T as writeData, _ as LockHeld, a as isShipped, b as isJSONOutput, d as CONFIG_FILE, f as ConfigFailure, g as Interrupted, h as ExpectedFailure, i as toUpdateCheckRefusal, l as toStateDirectory, m as toTelemetry, o as toPackageDirectory, p as toSettings, r as toCheck, s as noteNotices, t as PACKAGE, u as API_VERSION, v as TimedOut, w as toText, y as version$1 } from "./check-CDkHR_Qt.js";
5
+ import { C as ExpectedFailure, E as TimedOut, S as version$1, T as LockHeld, _ as API_VERSION, a as isShipped, b as toSettings, g as writeFailure, h as writeData, i as toUpdateCheckRefusal, l as toStateDirectory, m as toText, o as toPackageDirectory, p as toPrintable, r as toCheck, s as noteNotices, t as PACKAGE, u as isJSONOutput, v as CONFIG_FILE, w as Interrupted, x as toTelemetry, y as ConfigFailure } from "./check-wgnggJ45.js";
6
6
  import { n as toAnswer$1, r as toCompletion, t as PATH_VALUES } from "./completion-tree-DLC6ARLR.js";
7
7
  import { createRequire } from "node:module";
8
8
  import { Command, CommanderError, InvalidArgumentError, Option } from "commander";
@@ -15,77 +15,6 @@ import { spawn, spawnSync } from "node:child_process";
15
15
  import { createInterface } from "node:readline/promises";
16
16
  import { createServer } from "node:http";
17
17
  import { createInterface as createInterface$1 } from "node:readline";
18
- //#region src/cli/parser.ts
19
- /** toParser builds the parser from the registry, and hands whatever it parses to onRun. */
20
- function toParser(entries, onRun, isSilent = false) {
21
- const program = new Command();
22
- program.name("hardfin").description("Call the Hardfin API from a terminal or an agent").version(version$1, "-v, --version").enablePositionalOptions();
23
- toQuietParser(program, isSilent);
24
- for (const flag of GLOBAL_FLAGS) program.option(`--${flag.name} <${flag.valueName}>`, flag.description);
25
- for (const command of entries) program.addCommand(toProgram(program, command, onRun, isSilent), { hidden: command.hidden });
26
- return program;
27
- }
28
- /** toProgram wires one registry command into the parser. */
29
- function toProgram(root, command, onRun, isSilent) {
30
- const program = new Command(command.name).summary(command.summary).description(command.description ?? command.summary);
31
- toQuietParser(program, isSilent);
32
- for (const argument of command.arguments) {
33
- const name = argument.variadic ? `${argument.name}...` : argument.name;
34
- program.argument(argument.required ? `<${name}>` : `[${name}]`, argument.description);
35
- }
36
- for (const flag of command.flags) {
37
- const short = flag.short ? `-${flag.short}, ` : "";
38
- const value = flag.valueName ? ` <${flag.valueName}>` : "";
39
- const option = new Option(`${short}--${flag.name}${value}`, flag.description);
40
- if (flag.negatable) program.addOption(new Option(`--no-${flag.name}`, `${flag.description}, turned off`));
41
- if (flag.repeatable) option.argParser(collect);
42
- if (!flag.repeatable && flag.valueName) option.argParser(toOnce(flag.name));
43
- if (flag.defaultValue !== void 0) option.default(flag.defaultValue);
44
- program.addOption(option);
45
- }
46
- for (const example of command.examples) program.addHelpText("after", `\n${example.description}:\n $ ${example.command}`);
47
- for (const subcommand of command.subcommands ?? []) program.addCommand(toProgram(root, subcommand, onRun, isSilent), { hidden: subcommand.hidden });
48
- if (!command.run) return program;
49
- program.action((...parsed) => {
50
- const flags = parsed[parsed.length - 2] ?? {};
51
- return onRun({
52
- command,
53
- args: parsed.slice(0, parsed.length - 2).flatMap(toArgumentList),
54
- flags,
55
- globals: root.opts()
56
- });
57
- });
58
- return program;
59
- }
60
- /**
61
- * toQuietParser has the parser hand back its mistakes, so they are answered like any other
62
- * failure. A silent parser also keeps its help and version to itself, for a caller that only
63
- * wants to know what a command line means.
64
- */
65
- function toQuietParser(program, isSilent) {
66
- program.exitOverride().configureOutput({ outputError: () => void 0 });
67
- if (isSilent) program.configureOutput({
68
- writeOut: () => void 0,
69
- writeErr: () => void 0
70
- });
71
- }
72
- function toArgumentList(value) {
73
- if (Array.isArray(value)) return value.map(String);
74
- return value === void 0 ? [] : [toText(value)];
75
- }
76
- /** toOnce refuses a flag given a second time. */
77
- function toOnce(name) {
78
- let isGiven = false;
79
- return (value) => {
80
- if (isGiven) throw new InvalidArgumentError(`--${name} takes one value and is given more than once`);
81
- isGiven = true;
82
- return value;
83
- };
84
- }
85
- function collect(value, previous) {
86
- return [...previous ?? [], value];
87
- }
88
- //#endregion
89
18
  //#region src/auth/jwt.ts
90
19
  /**
91
20
  * toClaims reads an access token's payload for display. Nothing here verifies the
@@ -1137,7 +1066,10 @@ function toCommandLines(command, parents) {
1137
1066
  function toTaken(flag) {
1138
1067
  const values = flag.schema.enum ?? flag.schema.items?.enum;
1139
1068
  if (values) return `one of ${values.map((value) => `\`${value}\``).join(", ")}`;
1140
- if (flag.element) return flag.element.map((field) => `${field}=`).join(",");
1069
+ if (flag.element) {
1070
+ const taken = flag.element.map((field) => `${field}=`).join(",");
1071
+ return flag.elementRequired ? `${taken}, each needing ${flag.elementRequired.map((field) => `${field}=`).join(",")}` : taken;
1072
+ }
1141
1073
  return flag.valueName ?? "nothing";
1142
1074
  }
1143
1075
  async function runAgentGuide(input) {
@@ -1191,6 +1123,7 @@ function toDescribed(flag) {
1191
1123
  elementMoney: sent.elementMoney,
1192
1124
  elementEnums: sent.elementEnums,
1193
1125
  elementPatterns: sent.elementPatterns,
1126
+ elementRequired: sent.elementRequired,
1194
1127
  together: sent.together
1195
1128
  };
1196
1129
  return Object.fromEntries(Object.entries(described).filter(([, value]) => value !== void 0 && value !== false));
@@ -1247,6 +1180,84 @@ function toSummary(command, parents) {
1247
1180
  };
1248
1181
  }
1249
1182
  //#endregion
1183
+ //#region src/cli/parser.ts
1184
+ /** toParser builds the parser from the registry, and hands whatever it parses to onRun. */
1185
+ function toParser(entries, onRun, isSilent = false) {
1186
+ const program = new Command();
1187
+ program.name("hardfin").description("Call the Hardfin API from a terminal or an agent").version(version$1, "-v, --version").enablePositionalOptions();
1188
+ toQuietParser(program, isSilent);
1189
+ for (const flag of GLOBAL_FLAGS) program.option(`--${flag.name} <${flag.valueName}>`, flag.description);
1190
+ for (const command of entries) program.addCommand(toProgram(program, command, onRun, isSilent), { hidden: command.hidden });
1191
+ return program;
1192
+ }
1193
+ /** toHelpDescription says in help whether a flag, or each element it carries, must be given. */
1194
+ function toHelpDescription(flag) {
1195
+ const described = toDescribed(flag);
1196
+ const required = described.required ? `Required. ${flag.description}` : flag.description;
1197
+ if (!described.elementRequired) return required;
1198
+ return `${required}. Each one needs ${described.elementRequired.map((field) => `${field}=`).join(", ")}`;
1199
+ }
1200
+ /** toProgram wires one registry command into the parser. */
1201
+ function toProgram(root, command, onRun, isSilent) {
1202
+ const program = new Command(command.name).summary(command.summary).description(command.description ?? command.summary);
1203
+ toQuietParser(program, isSilent);
1204
+ for (const argument of command.arguments) {
1205
+ const name = argument.variadic ? `${argument.name}...` : argument.name;
1206
+ program.argument(argument.required ? `<${name}>` : `[${name}]`, argument.description);
1207
+ }
1208
+ for (const flag of command.flags) {
1209
+ const short = flag.short ? `-${flag.short}, ` : "";
1210
+ const value = flag.valueName ? ` <${flag.valueName}>` : "";
1211
+ const option = new Option(`${short}--${flag.name}${value}`, toHelpDescription(flag));
1212
+ if (flag.negatable) program.addOption(new Option(`--no-${flag.name}`, `${flag.description}, turned off`));
1213
+ if (flag.repeatable) option.argParser(collect);
1214
+ if (!flag.repeatable && flag.valueName) option.argParser(toOnce(flag.name));
1215
+ if (flag.defaultValue !== void 0) option.default(flag.defaultValue);
1216
+ program.addOption(option);
1217
+ }
1218
+ for (const example of command.examples) program.addHelpText("after", `\n${example.description}:\n $ ${example.command}`);
1219
+ for (const subcommand of command.subcommands ?? []) program.addCommand(toProgram(root, subcommand, onRun, isSilent), { hidden: subcommand.hidden });
1220
+ if (!command.run) return program;
1221
+ program.action((...parsed) => {
1222
+ const flags = parsed[parsed.length - 2] ?? {};
1223
+ return onRun({
1224
+ command,
1225
+ args: parsed.slice(0, parsed.length - 2).flatMap(toArgumentList),
1226
+ flags,
1227
+ globals: root.opts()
1228
+ });
1229
+ });
1230
+ return program;
1231
+ }
1232
+ /**
1233
+ * toQuietParser has the parser hand back its mistakes, so they are answered like any other
1234
+ * failure. A silent parser also keeps its help and version to itself, for a caller that only
1235
+ * wants to know what a command line means.
1236
+ */
1237
+ function toQuietParser(program, isSilent) {
1238
+ program.exitOverride().configureOutput({ outputError: () => void 0 });
1239
+ if (isSilent) program.configureOutput({
1240
+ writeOut: () => void 0,
1241
+ writeErr: () => void 0
1242
+ });
1243
+ }
1244
+ function toArgumentList(value) {
1245
+ if (Array.isArray(value)) return value.map(String);
1246
+ return value === void 0 ? [] : [toText(value)];
1247
+ }
1248
+ /** toOnce refuses a flag given a second time. */
1249
+ function toOnce(name) {
1250
+ let isGiven = false;
1251
+ return (value) => {
1252
+ if (isGiven) throw new InvalidArgumentError(`--${name} takes one value and is given more than once`);
1253
+ isGiven = true;
1254
+ return value;
1255
+ };
1256
+ }
1257
+ function collect(value, previous) {
1258
+ return [...previous ?? [], value];
1259
+ }
1260
+ //#endregion
1250
1261
  //#region src/command/input.ts
1251
1262
  /** InputFailure is a body given with --input that cannot be used, and says why. */
1252
1263
  var InputFailure = class extends Error {
@@ -1421,6 +1432,11 @@ async function runOperation(operation, input) {
1421
1432
  writeFailure("invalid_flag", built.message, input.isJSON);
1422
1433
  return ExitCode.USAGE;
1423
1434
  }
1435
+ const missingElementField = toMissingElementField(operation, built);
1436
+ if (missingElementField) {
1437
+ writeFailure("missing_flag", missingElementField, input.isJSON);
1438
+ return ExitCode.USAGE;
1439
+ }
1424
1440
  const isBareAction = operation.method === "POST" && operation.pathParameters.length > 0 && !operation.upload;
1425
1441
  const body = built === void 0 && isBareAction && operation.bodyFlags.length > 0 ? {} : built;
1426
1442
  if (operation.bodyFlags.length > 0 && body === void 0) {
@@ -1581,6 +1597,29 @@ function toUnknownField(shape, value, path) {
1581
1597
  if (unknown) return unknown;
1582
1598
  }
1583
1599
  }
1600
+ /**
1601
+ * toMissingElementField names the first element that lacks a field every element must carry,
1602
+ * whether the element came from its flag or from the --input body.
1603
+ */
1604
+ function toMissingElementField(operation, body) {
1605
+ for (const flag of operation.bodyFlags) {
1606
+ const elements = toValueAt(body, flag.jsonPath);
1607
+ if (!flag.elementRequired || !Array.isArray(elements)) continue;
1608
+ for (const [index, element] of elements.entries()) {
1609
+ const missing = flag.elementRequired.filter((field) => !hasPath(element, [field]));
1610
+ if (missing.length > 0) return `${flag.jsonPath.join(".")}[${index}] needs ${missing.join(", ")}. Every --${flag.name} needs ${flag.elementRequired.map((field) => `${field}=`).join(", ")}`;
1611
+ }
1612
+ }
1613
+ }
1614
+ /** toValueAt reads the value a body holds at a field's path. */
1615
+ function toValueAt(body, path) {
1616
+ let holder = body;
1617
+ for (const name of path) {
1618
+ if (typeof holder !== "object" || holder === null) return;
1619
+ holder = holder[name];
1620
+ }
1621
+ return holder;
1622
+ }
1584
1623
  /** hasPath reports whether a body already holds a value at a field's path. */
1585
1624
  function hasPath(body, path) {
1586
1625
  let holder = body;
@@ -4784,7 +4823,7 @@ const surfaceCommands = [
4784
4823
  defineOperation({
4785
4824
  name: "create",
4786
4825
  summary: "Create asset",
4787
- example: "hardfin asset create --in-inventory-date <value> --item-id <uuid> --location-id <uuid>",
4826
+ example: "hardfin asset create --in-inventory-date <value> --item-id <uuid> --location-id <uuid> --serial <value>",
4788
4827
  method: "POST",
4789
4828
  path: "/asset",
4790
4829
  pathParameters: [],
@@ -5307,6 +5346,11 @@ const surfaceCommands = [
5307
5346
  "shipAt": ["string", "null"],
5308
5347
  "shipAtTimezone": ["string", "null"]
5309
5348
  },
5349
+ elementRequired: [
5350
+ "assetId",
5351
+ "destinationId",
5352
+ "originId"
5353
+ ],
5310
5354
  schema: z.array(z.string())
5311
5355
  }],
5312
5356
  body: { "fields": { "moves": { "items": { "fields": {
@@ -5350,6 +5394,7 @@ const surfaceCommands = [
5350
5394
  "deliverAt": ["string", "null"],
5351
5395
  "shipAt": ["string", "null"]
5352
5396
  },
5397
+ elementRequired: ["assetId", "id"],
5353
5398
  schema: z.array(z.string())
5354
5399
  }],
5355
5400
  body: { "fields": { "moves": { "items": { "fields": {
@@ -5441,6 +5486,7 @@ const surfaceCommands = [
5441
5486
  "string",
5442
5487
  "null"
5443
5488
  ] },
5489
+ elementRequired: ["fieldId"],
5444
5490
  schema: z.array(z.string())
5445
5491
  },
5446
5492
  {
@@ -5941,7 +5987,7 @@ const surfaceCommands = [
5941
5987
  subcommands: [defineOperation({
5942
5988
  name: "create",
5943
5989
  summary: "Create asset cost adjustment",
5944
- example: "hardfin asset cost-adjustment create ast_4f9xk2mq7plr8stz --adjustment-type <value> --amount-value <value> --amount-iso-code <value>",
5990
+ example: "hardfin asset cost-adjustment create ast_4f9xk2mq7plr8stz --adjustment-type <value> --amount-value <value> --amount-iso-code <value> --effective-date <value> --reason <value>",
5945
5991
  method: "POST",
5946
5992
  path: "/asset/{assetKey}/cost-adjustment",
5947
5993
  pathParameters: [{
@@ -7509,6 +7555,7 @@ const surfaceCommands = [
7509
7555
  element: ["itemId", "requestedQuantity"],
7510
7556
  elementUuids: ["itemId"],
7511
7557
  elementTypes: { "requestedQuantity": ["integer"] },
7558
+ elementRequired: ["itemId", "requestedQuantity"],
7512
7559
  schema: z.array(z.string())
7513
7560
  },
7514
7561
  {
@@ -7619,6 +7666,7 @@ const surfaceCommands = [
7619
7666
  element: ["itemId", "requestedQuantity"],
7620
7667
  elementUuids: ["itemId"],
7621
7668
  elementTypes: { "requestedQuantity": ["integer"] },
7669
+ elementRequired: ["itemId", "requestedQuantity"],
7622
7670
  schema: z.array(z.string())
7623
7671
  },
7624
7672
  {
@@ -8111,6 +8159,7 @@ const surfaceCommands = [
8111
8159
  "order": ["integer"],
8112
8160
  "section": ["integer"]
8113
8161
  },
8162
+ elementRequired: ["fieldId"],
8114
8163
  schema: z.array(z.string())
8115
8164
  },
8116
8165
  {
@@ -8594,7 +8643,7 @@ const surfaceCommands = [
8594
8643
  defineOperation({
8595
8644
  name: "create",
8596
8645
  summary: "Create item field",
8597
- example: "hardfin item field create item_7Hq2Lm9XcR4tWz8K --field-type <value> --label <value> --order <number>",
8646
+ example: "hardfin item field create item_7Hq2Lm9XcR4tWz8K --field-type <value> --label <value> --order <number> --section <number>",
8598
8647
  method: "POST",
8599
8648
  path: "/item/{itemKey}/field",
8600
8649
  pathParameters: [{
@@ -9344,7 +9393,7 @@ const surfaceCommands = [
9344
9393
  defineOperation({
9345
9394
  name: "create",
9346
9395
  summary: "Create project",
9347
- example: "hardfin project create --line-item aggregationKey=<value>,itemId=<value>,quantity=<value>,transactionType=<value> --name <value>",
9396
+ example: "hardfin project create --line-item itemId=<value>,transactionType=<value>,aggregationKey=<value>,quantity=<value> --name <value>",
9348
9397
  method: "POST",
9349
9398
  path: "/project",
9350
9399
  pathParameters: [],
@@ -9557,6 +9606,7 @@ const surfaceCommands = [
9557
9606
  "totalOverride.amount": "^[^-]",
9558
9607
  "unitPrice.amount": "^[^-]"
9559
9608
  },
9609
+ elementRequired: ["itemId", "transactionType"],
9560
9610
  schema: z.array(z.string())
9561
9611
  },
9562
9612
  {
@@ -9695,7 +9745,7 @@ const surfaceCommands = [
9695
9745
  defineOperation({
9696
9746
  name: "update",
9697
9747
  summary: "Patch project",
9698
- example: "hardfin project update proj_7hq2lm9xcr4twz8k --line-item aggregationKey=<value>,itemId=<value>,quantity=<value>,transactionType=<value>",
9748
+ example: "hardfin project update proj_7hq2lm9xcr4twz8k --line-item itemId=<value>,transactionType=<value>,aggregationKey=<value>,quantity=<value>",
9699
9749
  method: "PATCH",
9700
9750
  path: "/project/{projectKey}",
9701
9751
  pathParameters: [{
@@ -9973,6 +10023,7 @@ const surfaceCommands = [
9973
10023
  "totalOverride.amount": "^[^-]",
9974
10024
  "unitPrice.amount": "^[^-]"
9975
10025
  },
10026
+ elementRequired: ["itemId", "transactionType"],
9976
10027
  schema: z.array(z.string())
9977
10028
  },
9978
10029
  {
@@ -10896,6 +10947,7 @@ const surfaceCommands = [
10896
10947
  element: ["itemId", "requestedQuantity"],
10897
10948
  elementUuids: ["itemId"],
10898
10949
  elementTypes: { "requestedQuantity": ["integer"] },
10950
+ elementRequired: ["itemId", "requestedQuantity"],
10899
10951
  schema: z.array(z.string())
10900
10952
  },
10901
10953
  {
@@ -11112,6 +11164,7 @@ const surfaceCommands = [
11112
11164
  element: ["itemId", "requestedQuantity"],
11113
11165
  elementUuids: ["itemId"],
11114
11166
  elementTypes: { "requestedQuantity": ["integer"] },
11167
+ elementRequired: ["itemId", "requestedQuantity"],
11115
11168
  schema: z.array(z.string())
11116
11169
  },
11117
11170
  {
@@ -11510,7 +11563,7 @@ const surfaceCommands = [
11510
11563
  defineOperation({
11511
11564
  name: "create",
11512
11565
  summary: "Create return order",
11513
- example: "hardfin return-order create --from-location-id <uuid> --item itemId=<value>,requestedQuantity=<value> --return-type <value>",
11566
+ example: "hardfin return-order create --from-location-id <uuid> --item itemId=<value>,requestedQuantity=<value> --return-type <value> --to-location-id <uuid>",
11514
11567
  method: "POST",
11515
11568
  path: "/return-order",
11516
11569
  pathParameters: [],
@@ -11636,6 +11689,7 @@ const surfaceCommands = [
11636
11689
  element: ["itemId", "requestedQuantity"],
11637
11690
  elementUuids: ["itemId"],
11638
11691
  elementTypes: { "requestedQuantity": ["integer"] },
11692
+ elementRequired: ["itemId", "requestedQuantity"],
11639
11693
  schema: z.array(z.string())
11640
11694
  },
11641
11695
  {
@@ -11863,6 +11917,7 @@ const surfaceCommands = [
11863
11917
  element: ["itemId", "requestedQuantity"],
11864
11918
  elementUuids: ["itemId"],
11865
11919
  elementTypes: { "requestedQuantity": ["integer"] },
11920
+ elementRequired: ["itemId", "requestedQuantity"],
11866
11921
  schema: z.array(z.string())
11867
11922
  },
11868
11923
  {
@@ -13679,5 +13734,5 @@ async function toReported(error, isJSON, resolved, level = "error") {
13679
13734
  //#endregion
13680
13735
  export { toCli, toParseExitCode, toReported, wasReportAttempted, writeCrash };
13681
13736
 
13682
- //# sourceMappingURL=program-eT-hgKX0.js.map
13683
- //# debugId=ef42eac6-02c0-5ed0-ad0b-c3d60bfb51f8
13737
+ //# sourceMappingURL=program-CYp6XF8D.js.map
13738
+ //# debugId=2061590a-8bec-56a7-864d-cb1435ccb994
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardfin/cli",
3
- "version": "0.1.1-dev.69",
3
+ "version": "0.1.1-dev.70",
4
4
  "description": "Command line interface for the Hardfin API",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hardfin, Inc.",