@hardfin/cli 0.1.0-dev.30 → 0.1.0-dev.32

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
@@ -77,6 +77,15 @@ hardfin file get file_7hq2mx9pkr4stz8w --output photo.jpg
77
77
  hardfin file get file_7hq2mx9pkr4stz8w --output - | wc -c
78
78
  ```
79
79
 
80
+ Send a whole body as JSON with `--input`, from a file or from standard input. A flag given as
81
+ well replaces its field, and a required field the body carries needs no flag. A value with a
82
+ comma in it, which a `key=value` flag cannot carry, goes through `--input`.
83
+
84
+ ```sh
85
+ hardfin location create --input location.json
86
+ echo '{"name": "Dock 4, north side"}' | hardfin location create --input -
87
+ ```
88
+
80
89
  Clear a field with `--unset`, naming the flag:
81
90
 
82
91
  ```sh
@@ -159,8 +168,18 @@ hardfin status # what the CLI is configured with and signed in as
159
168
  hardfin logout # forget this machine, and ask Hardfin to revoke it
160
169
  ```
161
170
 
162
- An API key is still the way to run unattended. `HARDFIN_API_KEY` wins over a stored sign in,
163
- so CI and an agent sandbox need no browser.
171
+ An API key is still the way to run unattended. `--api-key`, or `HARDFIN_API_KEY`, wins over
172
+ a stored sign in, so CI and an agent sandbox need no browser. The flag beats the variable.
173
+
174
+ An API key is held to two rules before it is sent:
175
+
176
+ | Rule | Why |
177
+ | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
178
+ | It goes only over https, or to this machine | Anything between here and the host could read it |
179
+ | A key you set, by flag or exported variable, never goes to an API URL that `config.local.json` or `.env` in the working directory chose | A repository you cloned could otherwise send your key to its own server |
180
+
181
+ A key and an API URL that come from the same folder's files still work together, which is
182
+ how a local build is set up. Pass `--api-url` to send a key you set to that folder's address.
164
183
 
165
184
  ### What login does
166
185
 
@@ -373,7 +392,7 @@ settings, and the one nearest the top wins.
373
392
 
374
393
  | Layer | Where | Beats |
375
394
  | ----------- | --------------------------------------------------------------------- | ---------------- |
376
- | Flag | `--api-url` | everything below |
395
+ | Flag | `--api-url`, `--api-key`, `--issuer-url` | everything below |
377
396
  | Environment | an exported `HARDFIN_*` variable | the files below |
378
397
  | Env file | `.env` in the working directory, or the file `HARDFIN_ENV_FILE` names | the config file |
379
398
  | Config file | `config.local.json` in the working directory | the defaults |
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]="29560915-e7ae-5a74-b5f6-f4a6988e1b43")}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]="62437fed-ab84-53c5-9984-9bbadc0f2e15")}catch(e){}}();
4
4
  import module from "node:module";
5
5
  //#region src/cli.ts
6
6
  module.enableCompileCache?.();
@@ -14,7 +14,7 @@ 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-BZW_50HP.js");
17
+ const { toCli, toParseExitCode, wasReportAttempted } = await import("./program-B02z-kNH.js");
18
18
  try {
19
19
  await toCli().parseAsync(process.argv);
20
20
  } catch (error) {
@@ -33,7 +33,7 @@ async function toCrash(error) {
33
33
  isCrashing = true;
34
34
  process.stderr.write(`error: ${error instanceof Error ? error.message : String(error)}\n`);
35
35
  try {
36
- const { toReported } = await import("./program-BZW_50HP.js");
36
+ const { toReported } = await import("./program-B02z-kNH.js");
37
37
  await toReported(error, false, void 0, "fatal");
38
38
  } finally {
39
39
  process.exit(1);
@@ -51,4 +51,4 @@ async function toQuickAnswer(args) {
51
51
  export {};
52
52
 
53
53
  //# sourceMappingURL=cli.js.map
54
- //# debugId=29560915-e7ae-5a74-b5f6-f4a6988e1b43
54
+ //# debugId=62437fed-ab84-53c5-9984-9bbadc0f2e15
@@ -5,6 +5,11 @@
5
5
  "description": "The API to call, whose host also holds the authorization server",
6
6
  "valueName": "url"
7
7
  },
8
+ {
9
+ "name": "api-key",
10
+ "description": "An API key to call with, in place of HARDFIN_API_KEY or a sign in",
11
+ "valueName": "key"
12
+ },
8
13
  {
9
14
  "name": "issuer-url",
10
15
  "description": "The authorization server, when it does not sit at the API's host",
@@ -346,6 +351,11 @@
346
351
  "valueName": "field",
347
352
  "repeatable": true
348
353
  },
354
+ {
355
+ "name": "input",
356
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
357
+ "valueName": "path"
358
+ },
349
359
  {
350
360
  "name": "json",
351
361
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -379,6 +389,11 @@
379
389
  "valueName": "assetId=,deliverAt=,deliverAtTimezone=,destinationId=,originId=,shipAt=,shipAtTimezone=",
380
390
  "repeatable": true
381
391
  },
392
+ {
393
+ "name": "input",
394
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
395
+ "valueName": "path"
396
+ },
382
397
  {
383
398
  "name": "json",
384
399
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -407,6 +422,11 @@
407
422
  "valueName": "assetId=,deliverAt=,id=,shipAt=",
408
423
  "repeatable": true
409
424
  },
425
+ {
426
+ "name": "input",
427
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
428
+ "valueName": "path"
429
+ },
410
430
  {
411
431
  "name": "json",
412
432
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -482,6 +502,11 @@
482
502
  "valueName": "field",
483
503
  "repeatable": true
484
504
  },
505
+ {
506
+ "name": "input",
507
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
508
+ "valueName": "path"
509
+ },
485
510
  {
486
511
  "name": "json",
487
512
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -591,6 +616,11 @@
591
616
  "valueName": "field",
592
617
  "repeatable": true
593
618
  },
619
+ {
620
+ "name": "input",
621
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
622
+ "valueName": "path"
623
+ },
594
624
  {
595
625
  "name": "json",
596
626
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -618,6 +648,11 @@
618
648
  "description": "Whether Hardfin sets the asset's in-service date itself",
619
649
  "negatable": true
620
650
  },
651
+ {
652
+ "name": "input",
653
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
654
+ "valueName": "path"
655
+ },
621
656
  {
622
657
  "name": "json",
623
658
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -690,6 +725,11 @@
690
725
  "valueName": "field",
691
726
  "repeatable": true
692
727
  },
728
+ {
729
+ "name": "input",
730
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
731
+ "valueName": "path"
732
+ },
693
733
  {
694
734
  "name": "json",
695
735
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -888,6 +928,11 @@
888
928
  "valueName": "field",
889
929
  "repeatable": true
890
930
  },
931
+ {
932
+ "name": "input",
933
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
934
+ "valueName": "path"
935
+ },
891
936
  {
892
937
  "name": "json",
893
938
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -908,6 +953,11 @@
908
953
  "description": "The day your organization takes the asset back, which cannot be in the future",
909
954
  "valueName": "value"
910
955
  },
956
+ {
957
+ "name": "input",
958
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
959
+ "valueName": "path"
960
+ },
911
961
  {
912
962
  "name": "json",
913
963
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -960,6 +1010,11 @@
960
1010
  "valueName": "field",
961
1011
  "repeatable": true
962
1012
  },
1013
+ {
1014
+ "name": "input",
1015
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
1016
+ "valueName": "path"
1017
+ },
963
1018
  {
964
1019
  "name": "json",
965
1020
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -1022,6 +1077,11 @@
1022
1077
  "valueName": "field",
1023
1078
  "repeatable": true
1024
1079
  },
1080
+ {
1081
+ "name": "input",
1082
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
1083
+ "valueName": "path"
1084
+ },
1025
1085
  {
1026
1086
  "name": "json",
1027
1087
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -1101,6 +1161,11 @@
1101
1161
  "valueName": "field",
1102
1162
  "repeatable": true
1103
1163
  },
1164
+ {
1165
+ "name": "input",
1166
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
1167
+ "valueName": "path"
1168
+ },
1104
1169
  {
1105
1170
  "name": "json",
1106
1171
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -1160,6 +1225,11 @@
1160
1225
  "valueName": "field",
1161
1226
  "repeatable": true
1162
1227
  },
1228
+ {
1229
+ "name": "input",
1230
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
1231
+ "valueName": "path"
1232
+ },
1163
1233
  {
1164
1234
  "name": "json",
1165
1235
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -1308,6 +1378,11 @@
1308
1378
  "valueName": "field",
1309
1379
  "repeatable": true
1310
1380
  },
1381
+ {
1382
+ "name": "input",
1383
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
1384
+ "valueName": "path"
1385
+ },
1311
1386
  {
1312
1387
  "name": "json",
1313
1388
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -1397,6 +1472,11 @@
1397
1472
  "valueName": "field",
1398
1473
  "repeatable": true
1399
1474
  },
1475
+ {
1476
+ "name": "input",
1477
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
1478
+ "valueName": "path"
1479
+ },
1400
1480
  {
1401
1481
  "name": "json",
1402
1482
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -1659,6 +1739,11 @@
1659
1739
  "valueName": "field",
1660
1740
  "repeatable": true
1661
1741
  },
1742
+ {
1743
+ "name": "input",
1744
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
1745
+ "valueName": "path"
1746
+ },
1662
1747
  {
1663
1748
  "name": "json",
1664
1749
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -1734,6 +1819,11 @@
1734
1819
  "valueName": "field",
1735
1820
  "repeatable": true
1736
1821
  },
1822
+ {
1823
+ "name": "input",
1824
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
1825
+ "valueName": "path"
1826
+ },
1737
1827
  {
1738
1828
  "name": "json",
1739
1829
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -1833,6 +1923,11 @@
1833
1923
  "valueName": "field",
1834
1924
  "repeatable": true
1835
1925
  },
1926
+ {
1927
+ "name": "input",
1928
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
1929
+ "valueName": "path"
1930
+ },
1836
1931
  {
1837
1932
  "name": "json",
1838
1933
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -1893,6 +1988,11 @@
1893
1988
  "valueName": "field",
1894
1989
  "repeatable": true
1895
1990
  },
1991
+ {
1992
+ "name": "input",
1993
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
1994
+ "valueName": "path"
1995
+ },
1896
1996
  {
1897
1997
  "name": "json",
1898
1998
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -1919,6 +2019,11 @@
1919
2019
  "valueName": "field",
1920
2020
  "repeatable": true
1921
2021
  },
2022
+ {
2023
+ "name": "input",
2024
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
2025
+ "valueName": "path"
2026
+ },
1922
2027
  {
1923
2028
  "name": "json",
1924
2029
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -2136,6 +2241,11 @@
2136
2241
  "valueName": "field",
2137
2242
  "repeatable": true
2138
2243
  },
2244
+ {
2245
+ "name": "input",
2246
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
2247
+ "valueName": "path"
2248
+ },
2139
2249
  {
2140
2250
  "name": "json",
2141
2251
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -2255,6 +2365,11 @@
2255
2365
  "valueName": "field",
2256
2366
  "repeatable": true
2257
2367
  },
2368
+ {
2369
+ "name": "input",
2370
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
2371
+ "valueName": "path"
2372
+ },
2258
2373
  {
2259
2374
  "name": "json",
2260
2375
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -2366,6 +2481,11 @@
2366
2481
  "valueName": "field",
2367
2482
  "repeatable": true
2368
2483
  },
2484
+ {
2485
+ "name": "input",
2486
+ "description": "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
2487
+ "valueName": "path"
2488
+ },
2369
2489
  {
2370
2490
  "name": "json",
2371
2491
  "description": "Print machine-readable output, which is the default when stdout is not a terminal"
@@ -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]="5c189832-33ce-5032-b0e0-fc3a33cf5b1d")}catch(e){}}();
4
- import { a as defineCommand, i as GLOBAL_FLAGS, r as ExitCode, t as toInvocation } from "./scrub-VtQmxap1.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]="b1928379-e7e3-5a7e-9ade-d45695e05814")}catch(e){}}();
4
+ import { a as defineCommand, i as GLOBAL_FLAGS, r as ExitCode, t as toInvocation } from "./scrub-BN3gyBpR.js";
5
5
  import { n as toCompletion, t as toAnswer$1 } from "./completion-tree-rR_Rmeij.js";
6
6
  import { createRequire } from "node:module";
7
7
  import { Command, CommanderError, Option } from "commander";
@@ -694,12 +694,15 @@ const held = /* @__PURE__ */ new Map();
694
694
  * unattended caller sets it deliberately, and otherwise the stored refresh token buys an
695
695
  * access token that lives only in memory.
696
696
  */
697
- async function toRequestCredential(settings) {
698
- if (settings.apiKey) return {
699
- header: "X-API-Key",
700
- value: settings.apiKey,
701
- kind: "api key"
702
- };
697
+ async function toRequestCredential(settings, sources) {
698
+ if (settings.apiKey) {
699
+ refuseForeignKey(settings, sources);
700
+ return {
701
+ header: "X-API-Key",
702
+ value: settings.apiKey,
703
+ kind: "api key"
704
+ };
705
+ }
703
706
  refuseForeignHost(settings);
704
707
  const stored = toCredential(settings.issuerUrl);
705
708
  if (!stored) throw new NoCredential("not authenticated. Run hardfin login, or set HARDFIN_API_KEY");
@@ -775,6 +778,20 @@ function refuseForeignHost(settings) {
775
778
  if (api.host !== issuer.host) throw new NoCredential(`this sign in belongs to ${issuer.host}, so it is not sent to ${api.host}. Set HARDFIN_API_KEY to call another host, or point --issuer-url at the server that signs you in`);
776
779
  if (api.protocol !== "https:" && !isLoopback(api.hostname)) throw new NoCredential(`${settings.apiUrl} is not https, so a sign in is not sent to it`);
777
780
  }
781
+ /** The layers a file in the working directory sets, which a cloned repository can carry. */
782
+ const DIRECTORY_SOURCES = /* @__PURE__ */ new Set(["config file", "env file"]);
783
+ /** refuseForeignKey keeps an API key off plain connections and off hosts a stray file chose. */
784
+ function refuseForeignKey(settings, sources) {
785
+ const api = toHost$1(settings.apiUrl);
786
+ if (api === void 0) throw new NoCredential(`${settings.apiUrl} is not a URL this CLI can call`);
787
+ if (api.protocol !== "https:" && !isLoopback(api.hostname)) throw new NoCredential(`Your API key was not sent, because ${api.host} does not use a secure connection.`);
788
+ const isKeyChosen = sources?.apiKey !== void 0 && !DIRECTORY_SOURCES.has(sources.apiKey);
789
+ const isUrlFromFolder = sources?.apiUrl !== void 0 && DIRECTORY_SOURCES.has(sources.apiUrl);
790
+ if (isKeyChosen && isUrlFromFolder) {
791
+ const file = sources.apiUrl === "config file" ? CONFIG_FILE : ".env";
792
+ throw new NoCredential(`Your API key was not sent to ${api.host}, because that address comes from a file in this folder, not from you.\nDetails: the API URL is set in ${file}. Pass --api-url to use it anyway`);
793
+ }
794
+ }
778
795
  function toHost$1(url) {
779
796
  try {
780
797
  return new URL(url);
@@ -790,12 +807,15 @@ function isLoopback(hostname) {
790
807
  * an access token this process already holds. An error report uses it, because a refresh
791
808
  * cut short by the process ending would spend the refresh token and sign the person out.
792
809
  */
793
- function toHeldCredential(settings) {
794
- if (settings.apiKey) return {
795
- header: "X-API-Key",
796
- value: settings.apiKey,
797
- kind: "api key"
798
- };
810
+ function toHeldCredential(settings, sources) {
811
+ if (settings.apiKey) {
812
+ refuseForeignKey(settings, sources);
813
+ return {
814
+ header: "X-API-Key",
815
+ value: settings.apiKey,
816
+ kind: "api key"
817
+ };
818
+ }
799
819
  const current = held.get(settings.issuerUrl);
800
820
  if (!current || (current.expiresAt ?? 0) - EXPIRY_MARGIN_MS <= Date.now()) return;
801
821
  return {
@@ -966,7 +986,7 @@ function toGuide(commands, version) {
966
986
  "",
967
987
  "## Before calling",
968
988
  "",
969
- "- Authenticate by setting `HARDFIN_API_KEY`, or by running `hardfin login`",
989
+ "- Authenticate with `--api-key`, by setting `HARDFIN_API_KEY`, or by running `hardfin login`",
970
990
  "- Every command prints JSON on stdout and diagnostics on stderr",
971
991
  "- Output is JSON whenever stdout is not a terminal, so no flag is needed when calling from code",
972
992
  "",
@@ -1045,6 +1065,31 @@ function toSummary(command) {
1045
1065
  subcommands: command.subcommands?.filter((entry) => !entry.hidden).map(toSummary)
1046
1066
  };
1047
1067
  }
1068
+ //#endregion
1069
+ //#region src/command/input.ts
1070
+ /** InputFailure is a body given with --input that cannot be used, and says why. */
1071
+ var InputFailure = class extends Error {
1072
+ constructor(message) {
1073
+ super(message);
1074
+ this.name = "InputFailure";
1075
+ }
1076
+ };
1077
+ /** toJSONInput reads the JSON a command was given with --input, from a file or from standard input. */
1078
+ function toJSONInput(source) {
1079
+ const name = source === "-" ? "standard input" : source;
1080
+ let text;
1081
+ try {
1082
+ text = readFileSync(source === "-" ? 0 : source, "utf8");
1083
+ } catch {
1084
+ return new InputFailure(`${name} cannot be read`);
1085
+ }
1086
+ if (text.trim() === "") return new InputFailure(`${name} is empty, and a body needs JSON`);
1087
+ try {
1088
+ return JSON.parse(text);
1089
+ } catch {
1090
+ return new InputFailure(`${name} does not hold JSON`);
1091
+ }
1092
+ }
1048
1093
  const apiCommand = defineCommand({
1049
1094
  name: "api",
1050
1095
  summary: "Call a Hardfin API endpoint and print what it answers",
@@ -1122,16 +1167,16 @@ async function runApi(input) {
1122
1167
  }
1123
1168
  let body;
1124
1169
  if (typeof input.flags["input"] === "string") {
1125
- body = toBody$1(input.flags["input"]);
1126
- if (body === void 0) {
1127
- writeFailure("invalid_input", `${input.flags["input"]} does not hold JSON`, input.isJSON);
1170
+ body = toJSONInput(input.flags["input"]);
1171
+ if (body instanceof InputFailure) {
1172
+ writeFailure("invalid_input", body.message, input.isJSON);
1128
1173
  return ExitCode.USAGE;
1129
1174
  }
1130
1175
  }
1131
1176
  try {
1132
1177
  writeData((await request$1({
1133
1178
  apiUrl: input.resolved.settings.apiUrl,
1134
- credential: await toRequestCredential(input.resolved.settings),
1179
+ credential: await toRequestCredential(input.resolved.settings, input.resolved.sources),
1135
1180
  method: toText(input.flags["method"] ?? "GET").toUpperCase(),
1136
1181
  path,
1137
1182
  query,
@@ -1160,14 +1205,6 @@ function toQuery$1(fields) {
1160
1205
  }
1161
1206
  return query;
1162
1207
  }
1163
- function toBody$1(source) {
1164
- const text = source === "-" ? readFileSync(0, "utf8") : readFileSync(source, "utf8");
1165
- try {
1166
- return JSON.parse(text);
1167
- } catch {
1168
- return;
1169
- }
1170
- }
1171
1208
  //#endregion
1172
1209
  //#region src/auth/browser.ts
1173
1210
  /** isWSL reports whether this Linux is running under Windows. */
@@ -2543,7 +2580,7 @@ function toServerReport(metadata) {
2543
2580
  async function toApiReport(resolved, options) {
2544
2581
  const settings = resolved.settings;
2545
2582
  try {
2546
- const credential = options.isHeldOnly ? toHeldCredential(settings) : await toRequestCredential(settings);
2583
+ const credential = options.isHeldOnly ? toHeldCredential(settings, resolved.sources) : await toRequestCredential(settings, resolved.sources);
2547
2584
  if (!credential) return {
2548
2585
  checked: false,
2549
2586
  url: settings.apiUrl,
@@ -2675,7 +2712,7 @@ async function report(error, occasion, resolved) {
2675
2712
  const settings = resolved ?? toResolved();
2676
2713
  const status = settings ? await toStatus(settings) : void 0;
2677
2714
  const secrets = [settings?.settings.apiKey, settings ? toCredential(settings.settings.issuerUrl)?.refreshToken : void 0];
2678
- return await (await import("./sentry-D5lKx0nm.js")).send({
2715
+ return await (await import("./sentry-CLaeAWNa.js")).send({
2679
2716
  error,
2680
2717
  occasion,
2681
2718
  telemetry,
@@ -2867,7 +2904,7 @@ const REFUSED_COMMANDS = /* @__PURE__ */ new Set([
2867
2904
  "mcp"
2868
2905
  ]);
2869
2906
  /** The global options, which sit before a command and take a value of their own. */
2870
- const GLOBAL_OPTIONS_WITH_VALUES = /* @__PURE__ */ new Set(["--api-url", "--issuer-url"]);
2907
+ const GLOBAL_OPTIONS_WITH_VALUES = new Set(GLOBAL_FLAGS.filter((flag) => flag.valueName).map((flag) => `--${flag.name}`));
2871
2908
  /**
2872
2909
  * toCommandName reads which command a call names. A global option sits before it, so
2873
2910
  * reading the first argument alone would let --issuer-url x logout through.
@@ -3077,6 +3114,12 @@ const UNSET_FLAG = {
3077
3114
  repeatable: true,
3078
3115
  schema: z.array(z.string())
3079
3116
  };
3117
+ const INPUT_FLAG = {
3118
+ name: "input",
3119
+ description: "A file holding the JSON request body, or - for standard input. A flag given as well replaces its field",
3120
+ valueName: "path",
3121
+ schema: z.string()
3122
+ };
3080
3123
  const FILE_FLAG = {
3081
3124
  name: "file",
3082
3125
  description: "The file to upload",
@@ -3100,6 +3143,7 @@ function defineOperation(operation) {
3100
3143
  ...operation.queryFlags,
3101
3144
  ...operation.bodyFlags,
3102
3145
  ...operation.bodyFlags.some((flag) => flag.nullable) ? [UNSET_FLAG] : [],
3146
+ ...operation.bodyFlags.length > 0 ? [INPUT_FLAG] : [],
3103
3147
  ...operation.upload ? [...operation.upload.fields, FILE_FLAG] : [],
3104
3148
  ...operation.downloads ? [OUTPUT_FLAG] : [],
3105
3149
  JSON_FLAG
@@ -3127,12 +3171,17 @@ async function runOperation(operation, input) {
3127
3171
  writeFailure("invalid_arguments", `this command takes ${operation.pathParameters.length} argument(s)`, input.isJSON);
3128
3172
  return ExitCode.USAGE;
3129
3173
  }
3130
- const missing = operation.bodyFlags.concat(operation.upload?.fields ?? []).filter((flag) => flag.required && input.flags[toOptionKey(flag.name)] === void 0);
3174
+ const given = toGivenBody(input.flags["input"]);
3175
+ if (given instanceof InputFailure) {
3176
+ writeFailure("invalid_input", given.message, input.isJSON);
3177
+ return ExitCode.USAGE;
3178
+ }
3179
+ const missing = operation.bodyFlags.concat(operation.upload?.fields ?? []).filter((flag) => flag.required && input.flags[toOptionKey(flag.name)] === void 0 && !hasPath(given, flag.jsonPath));
3131
3180
  if (missing.length > 0) {
3132
3181
  writeFailure("missing_flag", `this command needs ${missing.map((flag) => `--${flag.name}`).join(", ")}`, input.isJSON);
3133
3182
  return ExitCode.USAGE;
3134
3183
  }
3135
- const body = toBody(operation, input.flags);
3184
+ const body = toBody(operation, input.flags, given);
3136
3185
  if (body instanceof Error) {
3137
3186
  writeFailure("invalid_flag", body.message, input.isJSON);
3138
3187
  return ExitCode.USAGE;
@@ -3153,7 +3202,7 @@ async function runOperation(operation, input) {
3153
3202
  try {
3154
3203
  const envelope = await request$1({
3155
3204
  apiUrl: input.resolved.settings.apiUrl,
3156
- credential: await toRequestCredential(input.resolved.settings),
3205
+ credential: await toRequestCredential(input.resolved.settings, input.resolved.sources),
3157
3206
  method: operation.method,
3158
3207
  path,
3159
3208
  query: toQuery(operation, input.flags),
@@ -3241,10 +3290,27 @@ async function toForm(upload, flags) {
3241
3290
  }
3242
3291
  return form;
3243
3292
  }
3244
- /** toBody builds the request body from the flags, one field at a time. */
3245
- function toBody(operation, flags) {
3246
- const body = {};
3247
- let hasField = false;
3293
+ /** toGivenBody reads the --input body, which a generated command takes only as a JSON object. */
3294
+ function toGivenBody(source) {
3295
+ if (typeof source !== "string") return;
3296
+ const read = toJSONInput(source);
3297
+ if (read instanceof InputFailure) return read;
3298
+ if (typeof read !== "object" || read === null || Array.isArray(read)) return new InputFailure(`the body in ${source === "-" ? "standard input" : source} must be a JSON object, such as {"name": "Acme"}`);
3299
+ return read;
3300
+ }
3301
+ /** hasPath reports whether a body already holds a value at a field's path. */
3302
+ function hasPath(body, path) {
3303
+ let holder = body;
3304
+ for (const name of path) {
3305
+ if (typeof holder !== "object" || holder === null || !(name in holder)) return false;
3306
+ holder = holder[name];
3307
+ }
3308
+ return true;
3309
+ }
3310
+ /** toBody builds the request body from the --input body and the flags, a flag winning over its field. */
3311
+ function toBody(operation, flags, given) {
3312
+ const body = given ? structuredClone(given) : {};
3313
+ let hasField = given !== void 0;
3248
3314
  for (const flag of operation.bodyFlags) {
3249
3315
  const value = flags[toOptionKey(flag.name)];
3250
3316
  if (value === void 0) continue;
@@ -6060,6 +6126,7 @@ async function toExitCode(root, command, args, flags) {
6060
6126
  try {
6061
6127
  resolved = toSettings({
6062
6128
  apiUrl: root.opts()["apiUrl"],
6129
+ apiKey: root.opts()["apiKey"],
6063
6130
  issuerUrl: root.opts()["issuerUrl"]
6064
6131
  });
6065
6132
  return await command.run?.({
@@ -6101,5 +6168,5 @@ async function toReported(error, isJSON, resolved, level = "error") {
6101
6168
  //#endregion
6102
6169
  export { toCli, toParseExitCode, toReported, wasReportAttempted };
6103
6170
 
6104
- //# sourceMappingURL=program-BZW_50HP.js.map
6105
- //# debugId=5c189832-33ce-5032-b0e0-fc3a33cf5b1d
6171
+ //# sourceMappingURL=program-B02z-kNH.js.map
6172
+ //# debugId=b1928379-e7e3-5a7e-9ade-d45695e05814
@@ -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]="9fab3688-8d6c-599c-94b2-64daa75cd233")}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]="3af5060a-b6fe-5f2e-9661-01f4460a7b04")}catch(e){}}();
4
4
  import { z } from "zod";
5
5
  import { homedir } from "node:os";
6
6
  //#region src/command/registry.ts
@@ -17,17 +17,26 @@ function defineCommand(command) {
17
17
  return command;
18
18
  }
19
19
  /** GLOBAL_FLAGS are the options the CLI takes before any command. */
20
- const GLOBAL_FLAGS = [{
21
- name: "api-url",
22
- description: "The API to call, whose host also holds the authorization server",
23
- valueName: "url",
24
- schema: z.string()
25
- }, {
26
- name: "issuer-url",
27
- description: "The authorization server, when it does not sit at the API's host",
28
- valueName: "url",
29
- schema: z.string()
30
- }];
20
+ const GLOBAL_FLAGS = [
21
+ {
22
+ name: "api-url",
23
+ description: "The API to call, whose host also holds the authorization server",
24
+ valueName: "url",
25
+ schema: z.string()
26
+ },
27
+ {
28
+ name: "api-key",
29
+ description: "An API key to call with, in place of HARDFIN_API_KEY or a sign in",
30
+ valueName: "key",
31
+ schema: z.string()
32
+ },
33
+ {
34
+ name: "issuer-url",
35
+ description: "The authorization server, when it does not sit at the API's host",
36
+ valueName: "url",
37
+ schema: z.string()
38
+ }
39
+ ];
31
40
  //#endregion
32
41
  //#region src/report/scrub.ts
33
42
  const REDACTED = "[redacted]";
@@ -109,5 +118,5 @@ function toFlag(flags, word) {
109
118
  //#endregion
110
119
  export { defineCommand as a, GLOBAL_FLAGS as i, toScrubbed as n, ExitCode as r, toInvocation as t };
111
120
 
112
- //# sourceMappingURL=scrub-VtQmxap1.js.map
113
- //# debugId=9fab3688-8d6c-599c-94b2-64daa75cd233
121
+ //# sourceMappingURL=scrub-BN3gyBpR.js.map
122
+ //# debugId=3af5060a-b6fe-5f2e-9661-01f4460a7b04
@@ -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]="a251c173-a448-5d16-93ea-bc9547e82365")}catch(e){}}();
4
- import { n as toScrubbed } from "./scrub-VtQmxap1.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]="853940c3-9e72-566b-ad70-9f043e6aba8d")}catch(e){}}();
4
+ import { n as toScrubbed } from "./scrub-BN3gyBpR.js";
5
5
  import { dirname } from "node:path";
6
6
  import { request } from "node:http";
7
7
  import { fileURLToPath } from "node:url";
@@ -4385,5 +4385,5 @@ function toTags(delivery) {
4385
4385
  //#endregion
4386
4386
  export { send };
4387
4387
 
4388
- //# sourceMappingURL=sentry-D5lKx0nm.js.map
4389
- //# debugId=a251c173-a448-5d16-93ea-bc9547e82365
4388
+ //# sourceMappingURL=sentry-CLaeAWNa.js.map
4389
+ //# debugId=853940c3-9e72-566b-ad70-9f043e6aba8d
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardfin/cli",
3
- "version": "0.1.0-dev.30",
3
+ "version": "0.1.0-dev.32",
4
4
  "description": "Command line interface for the Hardfin API",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hardfin, Inc.",