@hardfin/cli 0.0.2-dev.6 → 0.0.2-dev.8
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 +68 -8
- package/dist/cli.js +315 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,7 @@ Most commands are generated from the published API document rather than written
|
|
|
31
31
|
| `hardfin api` | A call to any endpoint, written by hand, and the escape hatch when no generated command fits |
|
|
32
32
|
| `src/command/surface.generated.ts` | Every endpoint as a command, rewritten by the generator |
|
|
33
33
|
| `surface-overrides.json` | The operations whose generated name is wrong |
|
|
34
|
+
| `spec/core.openapi.yaml` | The API document the generator reads, vendored here |
|
|
34
35
|
| `scripts/generate-surface.mjs` | The generator |
|
|
35
36
|
|
|
36
37
|
### How an endpoint becomes a command
|
|
@@ -70,20 +71,79 @@ Settle it in `surface-overrides.json`, keyed by `operationId`:
|
|
|
70
71
|
An override for an `operationId` the document no longer publishes fails the generator. That
|
|
71
72
|
is deliberate, because a silently dropped override renames a command nobody meant to rename.
|
|
72
73
|
|
|
73
|
-
###
|
|
74
|
+
### The vendored document
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
the generated file changes. It needs the `API_SPEC_READ_DEPLOY_KEY` secret.
|
|
76
|
+
`spec/core.openapi.yaml` is the API document this repository carries, and the generator
|
|
77
|
+
reads it. Nothing fetches a document during a build, a release, or CI.
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
Refresh it with the script, which then rewrites the generated commands:
|
|
80
80
|
|
|
81
81
|
```sh
|
|
82
|
-
|
|
82
|
+
scripts/update-spec.sh # reads hardfinhq/api-spec over your own git access
|
|
83
|
+
scripts/update-spec.sh ../hardfin # bundles the fragmented source in a monorepo checkout
|
|
83
84
|
```
|
|
84
85
|
|
|
85
|
-
The
|
|
86
|
-
|
|
86
|
+
The monorepo form runs the same bundler at the same version CI uses, so the result matches
|
|
87
|
+
what the api-spec bridge publishes. Commit the document and the generated commands together.
|
|
88
|
+
|
|
89
|
+
Two checks keep the pair honest.
|
|
90
|
+
|
|
91
|
+
| Check | Refuses |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| The generator | a document whose `info.version` is not a date, which means an earlier release |
|
|
94
|
+
| CI | a vendored document that was updated without regenerating the commands |
|
|
95
|
+
|
|
96
|
+
The first one matters because `hardfinhq/api-spec` can sit a release behind the monorepo
|
|
97
|
+
while its bridge pull request is open. Generating from that document would replace the
|
|
98
|
+
current commands with an earlier API's.
|
|
99
|
+
|
|
100
|
+
## Local configuration
|
|
101
|
+
|
|
102
|
+
A local build reaches a local server without editing code. Four layers supply the same
|
|
103
|
+
settings, and the one nearest the top wins.
|
|
104
|
+
|
|
105
|
+
| Layer | Where | Beats |
|
|
106
|
+
| --- | --- | --- |
|
|
107
|
+
| Flag | `--api-url` | everything below |
|
|
108
|
+
| Environment | an exported `HARDFIN_*` variable | the files below |
|
|
109
|
+
| Env file | `.env` in the working directory, or the file `HARDFIN_ENV_FILE` names | the config file |
|
|
110
|
+
| Config file | `config.local.json` in the working directory | the defaults |
|
|
111
|
+
| Default | the published API | nothing |
|
|
112
|
+
|
|
113
|
+
An exported variable beats `.env` because Node leaves a variable that is already set alone.
|
|
114
|
+
|
|
115
|
+
### What a local build writes
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"apiUrl": "http://localhost:8080/v2",
|
|
120
|
+
"auth": { "tokenUrl": "http://localhost:9000/oauth/token" }
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The authentication endpoints follow `apiUrl`, so pointing at a local server moves the whole
|
|
125
|
+
flow. Name one under `auth` to move only that one. The keys are `apiUrl`, `apiKey`,
|
|
126
|
+
`clientId`, and `auth` holding `authorizeUrl`, `tokenUrl`, `deviceUrl`, and `revokeUrl`.
|
|
127
|
+
|
|
128
|
+
A key the file does not define fails the command with exit code 2. A typo that was silently
|
|
129
|
+
ignored would look like a setting that never applied.
|
|
130
|
+
|
|
131
|
+
`config.local.json` and `.env` are both gitignored.
|
|
132
|
+
|
|
133
|
+
### Seeing what won
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
hardfin config
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
It prints each setting, its value, and the layer that supplied it. The API key is reported
|
|
140
|
+
as set or not set, never printed.
|
|
141
|
+
|
|
142
|
+
### Both files are read from the working directory
|
|
143
|
+
|
|
144
|
+
The CLI reads whatever `config.local.json` and `.env` sit in the directory you run it from.
|
|
145
|
+
A directory you do not control can therefore point the CLI at a server you do not expect, so
|
|
146
|
+
run `hardfin config` when a command reaches somewhere surprising.
|
|
87
147
|
|
|
88
148
|
## Releasing
|
|
89
149
|
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { Command, Option } from "commander";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
6
7
|
//#region src/command/registry.ts
|
|
7
8
|
/** ExitCode is what the process returns, and what an agent branches on. */
|
|
8
9
|
const ExitCode = {
|
|
@@ -19,14 +20,93 @@ function defineCommand(command) {
|
|
|
19
20
|
//#region src/config/settings.ts
|
|
20
21
|
/** The API version this build was written against, sent on every request. */
|
|
21
22
|
const API_VERSION = "2026-09-17";
|
|
23
|
+
/** The file a local build reads its overrides from, in the working directory. */
|
|
24
|
+
const CONFIG_FILE = "config.local.json";
|
|
22
25
|
const DEFAULT_API_URL = "https://api.hardfin.com/v2";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
const DEFAULT_ENV_FILE = ".env";
|
|
27
|
+
const FileSettings = z.strictObject({
|
|
28
|
+
apiUrl: z.string().optional(),
|
|
29
|
+
apiKey: z.string().optional(),
|
|
30
|
+
clientId: z.string().optional(),
|
|
31
|
+
auth: z.strictObject({
|
|
32
|
+
authorizeUrl: z.string().optional(),
|
|
33
|
+
tokenUrl: z.string().optional(),
|
|
34
|
+
deviceUrl: z.string().optional(),
|
|
35
|
+
revokeUrl: z.string().optional()
|
|
36
|
+
}).optional()
|
|
37
|
+
});
|
|
38
|
+
/** ConfigFailure is a config file that cannot be read or does not match the schema. */
|
|
39
|
+
var ConfigFailure = class extends Error {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "ConfigFailure";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/** toSettings resolves what this invocation talks to, and where each value came from. */
|
|
46
|
+
function toSettings(flags = {}, directory = process.cwd()) {
|
|
47
|
+
const fromEnvFile = loadEnvFile(directory);
|
|
48
|
+
const file = toFileSettings(directory);
|
|
49
|
+
const sources = {};
|
|
50
|
+
const pick = (key, flag, variable, fromFile, fallback) => {
|
|
51
|
+
const [value, source] = toLayer(flag, process.env[variable], fromEnvFile.has(variable), fromFile, fallback);
|
|
52
|
+
sources[key] = source;
|
|
53
|
+
return value;
|
|
54
|
+
};
|
|
55
|
+
const apiUrl = toTrimmedUrl(pick("apiUrl", flags.apiUrl, "HARDFIN_API_URL", file.apiUrl, DEFAULT_API_URL) ?? DEFAULT_API_URL);
|
|
56
|
+
return {
|
|
57
|
+
settings: {
|
|
58
|
+
apiUrl,
|
|
59
|
+
apiKey: pick("apiKey", flags.apiKey, "HARDFIN_API_KEY", file.apiKey),
|
|
60
|
+
clientId: pick("clientId", flags.clientId, "HARDFIN_CLIENT_ID", file.clientId),
|
|
61
|
+
authorizeUrl: pick("authorizeUrl", flags.authorizeUrl, "HARDFIN_AUTHORIZE_URL", file.auth?.authorizeUrl, `${apiUrl}/auth/authorize`) ?? "",
|
|
62
|
+
tokenUrl: pick("tokenUrl", flags.tokenUrl, "HARDFIN_TOKEN_URL", file.auth?.tokenUrl, `${apiUrl}/auth/token`) ?? "",
|
|
63
|
+
deviceUrl: pick("deviceUrl", flags.deviceUrl, "HARDFIN_DEVICE_URL", file.auth?.deviceUrl, `${apiUrl}/auth/device`) ?? "",
|
|
64
|
+
revokeUrl: pick("revokeUrl", flags.revokeUrl, "HARDFIN_REVOKE_URL", file.auth?.revokeUrl, `${apiUrl}/auth/revoke`) ?? ""
|
|
65
|
+
},
|
|
66
|
+
sources
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function toLayer(flag, environment, isFromEnvFile, file, fallback) {
|
|
70
|
+
if (flag) return [flag, "flag"];
|
|
71
|
+
if (environment) return [environment, isFromEnvFile ? "env file" : "environment"];
|
|
72
|
+
if (file) return [file, "config file"];
|
|
73
|
+
return [fallback, "default"];
|
|
74
|
+
}
|
|
75
|
+
/** toFileSettings reads the local override file, which a local build is expected to have. */
|
|
76
|
+
function toFileSettings(directory) {
|
|
77
|
+
const path = resolve(directory, CONFIG_FILE);
|
|
78
|
+
if (!existsSync(path)) return {};
|
|
79
|
+
let parsed;
|
|
80
|
+
try {
|
|
81
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
82
|
+
} catch {
|
|
83
|
+
throw new ConfigFailure(`${CONFIG_FILE} does not hold JSON`);
|
|
84
|
+
}
|
|
85
|
+
const result = FileSettings.safeParse(parsed);
|
|
86
|
+
if (!result.success) {
|
|
87
|
+
const issue = result.error.issues[0];
|
|
88
|
+
throw new ConfigFailure(`${CONFIG_FILE} is not valid: ${issue?.path.join(".") || "root"} ${issue?.message ?? ""}`.trim());
|
|
89
|
+
}
|
|
90
|
+
return result.data;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* loadEnvFile reads a .env beside the command, so a local build needs no exports, and
|
|
94
|
+
* answers which variables it supplied. Node leaves an exported variable alone, so a
|
|
95
|
+
* shell export still wins over the file.
|
|
96
|
+
*/
|
|
97
|
+
function loadEnvFile(directory) {
|
|
98
|
+
const path = process.env["HARDFIN_ENV_FILE"] ?? resolve(directory, DEFAULT_ENV_FILE);
|
|
99
|
+
if (!existsSync(path)) return /* @__PURE__ */ new Set();
|
|
100
|
+
const before = new Set(Object.keys(process.env));
|
|
101
|
+
try {
|
|
102
|
+
process.loadEnvFile(path);
|
|
103
|
+
} catch {
|
|
104
|
+
throw new ConfigFailure(`${path} cannot be read as an env file`);
|
|
105
|
+
}
|
|
106
|
+
return new Set(Object.keys(process.env).filter((name) => !before.has(name)));
|
|
26
107
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return process.env["HARDFIN_API_KEY"] || void 0;
|
|
108
|
+
function toTrimmedUrl(url) {
|
|
109
|
+
return url.replace(/\/+$/, "");
|
|
30
110
|
}
|
|
31
111
|
//#endregion
|
|
32
112
|
//#region src/output/writer.ts
|
|
@@ -272,7 +352,7 @@ const apiCommand = defineCommand({
|
|
|
272
352
|
run: runApi
|
|
273
353
|
});
|
|
274
354
|
async function runApi(input) {
|
|
275
|
-
const apiKey =
|
|
355
|
+
const apiKey = input.resolved.settings.apiKey;
|
|
276
356
|
if (!apiKey) {
|
|
277
357
|
writeFailure("not authenticated. Set HARDFIN_API_KEY to an API key for your organization", input.isJSON);
|
|
278
358
|
return ExitCode.NOT_AUTHENTICATED;
|
|
@@ -297,7 +377,7 @@ async function runApi(input) {
|
|
|
297
377
|
}
|
|
298
378
|
try {
|
|
299
379
|
writeData((await request({
|
|
300
|
-
apiUrl:
|
|
380
|
+
apiUrl: input.resolved.settings.apiUrl,
|
|
301
381
|
apiKey,
|
|
302
382
|
method: String(input.flags["method"] ?? "GET").toUpperCase(),
|
|
303
383
|
path,
|
|
@@ -333,6 +413,56 @@ function toBody$1(source) {
|
|
|
333
413
|
}
|
|
334
414
|
}
|
|
335
415
|
//#endregion
|
|
416
|
+
//#region src/command/config.ts
|
|
417
|
+
const configCommand = defineCommand({
|
|
418
|
+
name: "config",
|
|
419
|
+
summary: "Print what this invocation talks to, and where each value came from",
|
|
420
|
+
description: `Resolves the API and authentication endpoints from the flags, the environment, a .env file, and ${CONFIG_FILE} in the working directory. Use it when a local build reaches the wrong server.`,
|
|
421
|
+
arguments: [],
|
|
422
|
+
flags: [{
|
|
423
|
+
name: "json",
|
|
424
|
+
description: "Print machine-readable output, which is the default when stdout is not a terminal",
|
|
425
|
+
schema: z.boolean()
|
|
426
|
+
}],
|
|
427
|
+
examples: [{
|
|
428
|
+
description: "See what a local build is pointed at",
|
|
429
|
+
command: "hardfin config"
|
|
430
|
+
}, {
|
|
431
|
+
description: "Read one value from a script",
|
|
432
|
+
command: "hardfin config --json | jq -r .apiUrl"
|
|
433
|
+
}],
|
|
434
|
+
run: runConfig
|
|
435
|
+
});
|
|
436
|
+
/** toReport pairs each setting with the layer that supplied it. */
|
|
437
|
+
function toReport(resolved) {
|
|
438
|
+
const report = { apiVersion: API_VERSION };
|
|
439
|
+
for (const [key, value] of Object.entries(resolved.settings)) {
|
|
440
|
+
const source = resolved.sources[key];
|
|
441
|
+
report[key] = key === "apiKey" ? {
|
|
442
|
+
set: value !== void 0,
|
|
443
|
+
from: source
|
|
444
|
+
} : {
|
|
445
|
+
value: value ?? null,
|
|
446
|
+
from: source
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
return report;
|
|
450
|
+
}
|
|
451
|
+
async function runConfig(input) {
|
|
452
|
+
const report = toReport(input.resolved);
|
|
453
|
+
if (input.isJSON) {
|
|
454
|
+
writeData(report);
|
|
455
|
+
return ExitCode.OK;
|
|
456
|
+
}
|
|
457
|
+
writeData(Object.entries(report).map(([key, entry]) => {
|
|
458
|
+
if (typeof entry !== "object" || entry === null) return `${key.padEnd(14)} ${String(entry)}`;
|
|
459
|
+
const holder = entry;
|
|
460
|
+
const shown = holder.value ?? (holder.set ? "set" : "not set");
|
|
461
|
+
return `${key.padEnd(14)} ${shown} (${holder.from})`;
|
|
462
|
+
}).join("\n"));
|
|
463
|
+
return ExitCode.OK;
|
|
464
|
+
}
|
|
465
|
+
//#endregion
|
|
336
466
|
//#region src/command/operation.ts
|
|
337
467
|
const INPUT_FLAG = {
|
|
338
468
|
name: "input",
|
|
@@ -360,7 +490,7 @@ function defineOperation(operation) {
|
|
|
360
490
|
};
|
|
361
491
|
}
|
|
362
492
|
async function runOperation(operation, input) {
|
|
363
|
-
const apiKey =
|
|
493
|
+
const apiKey = input.resolved.settings.apiKey;
|
|
364
494
|
if (!apiKey) {
|
|
365
495
|
writeFailure("not authenticated. Set HARDFIN_API_KEY to an API key for your organization", input.isJSON);
|
|
366
496
|
return ExitCode.NOT_AUTHENTICATED;
|
|
@@ -380,7 +510,7 @@ async function runOperation(operation, input) {
|
|
|
380
510
|
}
|
|
381
511
|
try {
|
|
382
512
|
writeData((await request({
|
|
383
|
-
apiUrl:
|
|
513
|
+
apiUrl: input.resolved.settings.apiUrl,
|
|
384
514
|
apiKey,
|
|
385
515
|
method: operation.method,
|
|
386
516
|
path,
|
|
@@ -635,6 +765,44 @@ const commands = [
|
|
|
635
765
|
queryFlags: [],
|
|
636
766
|
takesBody: true
|
|
637
767
|
}),
|
|
768
|
+
{
|
|
769
|
+
name: "move",
|
|
770
|
+
summary: "Move commands",
|
|
771
|
+
arguments: [],
|
|
772
|
+
flags: [],
|
|
773
|
+
examples: [],
|
|
774
|
+
subcommands: [{
|
|
775
|
+
name: "execute",
|
|
776
|
+
summary: "Execute commands",
|
|
777
|
+
arguments: [],
|
|
778
|
+
flags: [],
|
|
779
|
+
examples: [],
|
|
780
|
+
subcommands: [defineOperation({
|
|
781
|
+
name: "create",
|
|
782
|
+
summary: "Execute asset move",
|
|
783
|
+
method: "POST",
|
|
784
|
+
path: "/asset/move/execute",
|
|
785
|
+
pathParameters: [],
|
|
786
|
+
queryFlags: [],
|
|
787
|
+
takesBody: true
|
|
788
|
+
})]
|
|
789
|
+
}, {
|
|
790
|
+
name: "plan",
|
|
791
|
+
summary: "Plan commands",
|
|
792
|
+
arguments: [],
|
|
793
|
+
flags: [],
|
|
794
|
+
examples: [],
|
|
795
|
+
subcommands: [defineOperation({
|
|
796
|
+
name: "create",
|
|
797
|
+
summary: "Plan asset move",
|
|
798
|
+
method: "POST",
|
|
799
|
+
path: "/asset/move/plan",
|
|
800
|
+
pathParameters: [],
|
|
801
|
+
queryFlags: [],
|
|
802
|
+
takesBody: true
|
|
803
|
+
})]
|
|
804
|
+
}]
|
|
805
|
+
},
|
|
638
806
|
defineOperation({
|
|
639
807
|
name: "get",
|
|
640
808
|
summary: "Get asset",
|
|
@@ -720,6 +888,74 @@ const commands = [
|
|
|
720
888
|
takesBody: true
|
|
721
889
|
})]
|
|
722
890
|
},
|
|
891
|
+
{
|
|
892
|
+
name: "event",
|
|
893
|
+
summary: "Event commands",
|
|
894
|
+
arguments: [],
|
|
895
|
+
flags: [],
|
|
896
|
+
examples: [],
|
|
897
|
+
subcommands: [defineOperation({
|
|
898
|
+
name: "list",
|
|
899
|
+
summary: "Get asset event list",
|
|
900
|
+
method: "GET",
|
|
901
|
+
path: "/asset/{assetKey}/event",
|
|
902
|
+
pathParameters: [{
|
|
903
|
+
name: "assetKey",
|
|
904
|
+
description: "The asset's key",
|
|
905
|
+
required: true
|
|
906
|
+
}],
|
|
907
|
+
queryFlags: [],
|
|
908
|
+
takesBody: false
|
|
909
|
+
})]
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
name: "event-group",
|
|
913
|
+
summary: "Event group commands",
|
|
914
|
+
arguments: [],
|
|
915
|
+
flags: [],
|
|
916
|
+
examples: [],
|
|
917
|
+
subcommands: [defineOperation({
|
|
918
|
+
name: "list",
|
|
919
|
+
summary: "Get asset event group listing",
|
|
920
|
+
method: "GET",
|
|
921
|
+
path: "/asset/{assetKey}/event-group",
|
|
922
|
+
pathParameters: [{
|
|
923
|
+
name: "assetKey",
|
|
924
|
+
description: "The asset's key",
|
|
925
|
+
required: true
|
|
926
|
+
}],
|
|
927
|
+
queryFlags: [{
|
|
928
|
+
name: "start",
|
|
929
|
+
queryName: "start",
|
|
930
|
+
description: "The earliest an event group may have happened, or null to read from the first",
|
|
931
|
+
valueName: "value",
|
|
932
|
+
schema: z.string()
|
|
933
|
+
}, {
|
|
934
|
+
name: "end",
|
|
935
|
+
queryName: "end",
|
|
936
|
+
description: "The latest an event group may have happened, or null to read through the last",
|
|
937
|
+
valueName: "value",
|
|
938
|
+
schema: z.string()
|
|
939
|
+
}],
|
|
940
|
+
takesBody: false
|
|
941
|
+
}), defineOperation({
|
|
942
|
+
name: "get",
|
|
943
|
+
summary: "Get asset event group",
|
|
944
|
+
method: "GET",
|
|
945
|
+
path: "/asset/{assetKey}/event-group/{eventGroupKey}",
|
|
946
|
+
pathParameters: [{
|
|
947
|
+
name: "assetKey",
|
|
948
|
+
description: "The asset's key",
|
|
949
|
+
required: true
|
|
950
|
+
}, {
|
|
951
|
+
name: "eventGroupKey",
|
|
952
|
+
description: "The event group's key",
|
|
953
|
+
required: true
|
|
954
|
+
}],
|
|
955
|
+
queryFlags: [],
|
|
956
|
+
takesBody: false
|
|
957
|
+
})]
|
|
958
|
+
},
|
|
723
959
|
{
|
|
724
960
|
name: "file",
|
|
725
961
|
summary: "File commands",
|
|
@@ -756,6 +992,26 @@ const commands = [
|
|
|
756
992
|
takesBody: false
|
|
757
993
|
})]
|
|
758
994
|
},
|
|
995
|
+
{
|
|
996
|
+
name: "functional-status",
|
|
997
|
+
summary: "Functional status commands",
|
|
998
|
+
arguments: [],
|
|
999
|
+
flags: [],
|
|
1000
|
+
examples: [],
|
|
1001
|
+
subcommands: [defineOperation({
|
|
1002
|
+
name: "list",
|
|
1003
|
+
summary: "Get asset functional status history",
|
|
1004
|
+
method: "GET",
|
|
1005
|
+
path: "/asset/{assetKey}/functional-status",
|
|
1006
|
+
pathParameters: [{
|
|
1007
|
+
name: "assetKey",
|
|
1008
|
+
description: "The asset's key",
|
|
1009
|
+
required: true
|
|
1010
|
+
}],
|
|
1011
|
+
queryFlags: [],
|
|
1012
|
+
takesBody: false
|
|
1013
|
+
})]
|
|
1014
|
+
},
|
|
759
1015
|
{
|
|
760
1016
|
name: "ownership",
|
|
761
1017
|
summary: "Ownership commands",
|
|
@@ -855,6 +1111,46 @@ const commands = [
|
|
|
855
1111
|
})
|
|
856
1112
|
]
|
|
857
1113
|
},
|
|
1114
|
+
{
|
|
1115
|
+
name: "scrap",
|
|
1116
|
+
summary: "Scrap commands",
|
|
1117
|
+
arguments: [],
|
|
1118
|
+
flags: [],
|
|
1119
|
+
examples: [],
|
|
1120
|
+
subcommands: [defineOperation({
|
|
1121
|
+
name: "create",
|
|
1122
|
+
summary: "Scrap asset",
|
|
1123
|
+
method: "POST",
|
|
1124
|
+
path: "/asset/{assetKey}/scrap",
|
|
1125
|
+
pathParameters: [{
|
|
1126
|
+
name: "assetKey",
|
|
1127
|
+
description: "The asset's key",
|
|
1128
|
+
required: true
|
|
1129
|
+
}],
|
|
1130
|
+
queryFlags: [],
|
|
1131
|
+
takesBody: true
|
|
1132
|
+
})]
|
|
1133
|
+
},
|
|
1134
|
+
{
|
|
1135
|
+
name: "unscrap",
|
|
1136
|
+
summary: "Unscrap commands",
|
|
1137
|
+
arguments: [],
|
|
1138
|
+
flags: [],
|
|
1139
|
+
examples: [],
|
|
1140
|
+
subcommands: [defineOperation({
|
|
1141
|
+
name: "create",
|
|
1142
|
+
summary: "Unscrap asset",
|
|
1143
|
+
method: "POST",
|
|
1144
|
+
path: "/asset/{assetKey}/unscrap",
|
|
1145
|
+
pathParameters: [{
|
|
1146
|
+
name: "assetKey",
|
|
1147
|
+
description: "The asset's key",
|
|
1148
|
+
required: true
|
|
1149
|
+
}],
|
|
1150
|
+
queryFlags: [],
|
|
1151
|
+
takesBody: false
|
|
1152
|
+
})]
|
|
1153
|
+
},
|
|
858
1154
|
{
|
|
859
1155
|
name: "url-link",
|
|
860
1156
|
summary: "URL link commands",
|
|
@@ -1056,9 +1352,8 @@ const commands = [
|
|
|
1056
1352
|
queryFlags: [{
|
|
1057
1353
|
name: "attachment",
|
|
1058
1354
|
queryName: "attachment",
|
|
1059
|
-
description: "
|
|
1060
|
-
|
|
1061
|
-
schema: z.string()
|
|
1355
|
+
description: "True when the file downloads as an attachment rather than opening inline",
|
|
1356
|
+
schema: z.boolean()
|
|
1062
1357
|
}],
|
|
1063
1358
|
takesBody: false
|
|
1064
1359
|
})]
|
|
@@ -1479,6 +1774,7 @@ const commands = [
|
|
|
1479
1774
|
}
|
|
1480
1775
|
],
|
|
1481
1776
|
apiCommand,
|
|
1777
|
+
configCommand,
|
|
1482
1778
|
agentGuideCommand
|
|
1483
1779
|
];
|
|
1484
1780
|
//#endregion
|
|
@@ -1494,7 +1790,7 @@ function toRejectedFlag(command, flags) {
|
|
|
1494
1790
|
//#endregion
|
|
1495
1791
|
//#region src/cli.ts
|
|
1496
1792
|
const program = new Command();
|
|
1497
|
-
program.name("hardfin").description("Call the Hardfin API from a terminal or an agent").version(version, "-v, --version").showHelpAfterError().enablePositionalOptions();
|
|
1793
|
+
program.name("hardfin").description("Call the Hardfin API from a terminal or an agent").version(version, "-v, --version").option("--api-url <url>", "The API to call, which also moves the authentication endpoints").showHelpAfterError().enablePositionalOptions();
|
|
1498
1794
|
for (const command of commands) program.addCommand(toProgram(command));
|
|
1499
1795
|
await program.parseAsync(process.argv);
|
|
1500
1796
|
/** toProgram wires one registry command into the parser. */
|
|
@@ -1530,15 +1826,17 @@ async function toExitCode(command, args, flags) {
|
|
|
1530
1826
|
return ExitCode.USAGE;
|
|
1531
1827
|
}
|
|
1532
1828
|
try {
|
|
1829
|
+
const resolved = toSettings({ apiUrl: program.opts()["apiUrl"] });
|
|
1533
1830
|
return await command.run?.({
|
|
1534
1831
|
args,
|
|
1535
1832
|
flags,
|
|
1536
1833
|
isJSON,
|
|
1537
|
-
commands
|
|
1834
|
+
commands,
|
|
1835
|
+
resolved
|
|
1538
1836
|
}) ?? ExitCode.OK;
|
|
1539
1837
|
} catch (error) {
|
|
1540
1838
|
writeFailure(error instanceof Error ? error.message : String(error), isJSON);
|
|
1541
|
-
return ExitCode.ERROR;
|
|
1839
|
+
return error instanceof ConfigFailure ? ExitCode.USAGE : ExitCode.ERROR;
|
|
1542
1840
|
}
|
|
1543
1841
|
}
|
|
1544
1842
|
function toArgumentList(value) {
|