@greatwait/cli 0.1.0-alpha.5 → 0.1.0-alpha.6
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/dist/catalog.d.ts +4 -5
- package/dist/catalog.js +489 -174
- package/dist/commands.js +198 -56
- package/dist/member-catalog.d.ts +1 -0
- package/dist/member-catalog.js +1 -0
- package/dist/member-commands.d.ts +6 -0
- package/dist/member-commands.js +192 -0
- package/dist/operate.d.ts +21 -0
- package/dist/operate.js +79 -6
- package/dist/remediation.d.ts +1 -1
- package/dist/support.d.ts +2 -0
- package/dist/support.js +7 -0
- package/package.json +3 -3
package/dist/operate.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { billingAutoTopUpApplyRequestSchema, billingAutoTopUpPreviewRequestSchema, billingAutoTopUpPreviewSchema, billingCeilingUpdateRequestSchema, billingFundingQuoteRequestSchema, billingFundingQuoteSchema, billingNotificationSettingsUpdateRequestSchema, billingRefundRequestCreateSchema, billingRefundResolutionRequestSchema, projectBillingQuoteSchema, projectBillingQuoteRequestSchema, } from "@greatwait/contracts";
|
|
1
|
+
import { httpRequestSchema, endpointHeaderChangesSchema, billingAutoTopUpApplyRequestSchema, billingAutoTopUpPreviewRequestSchema, billingAutoTopUpPreviewSchema, billingCeilingUpdateRequestSchema, billingFundingQuoteRequestSchema, billingFundingQuoteSchema, billingNotificationSettingsUpdateRequestSchema, billingRefundRequestCreateSchema, billingRefundResolutionRequestSchema, projectBillingQuoteSchema, projectBillingQuoteRequestSchema, } from "@greatwait/contracts";
|
|
2
2
|
import { parseDuration } from "./args.js";
|
|
3
3
|
import { confirmChange } from "./confirm.js";
|
|
4
4
|
import { redactSecret } from "./output.js";
|
|
5
5
|
import { CliError } from "./runtime.js";
|
|
6
6
|
import { openSession } from "./session.js";
|
|
7
|
-
import { apiUrlOption, conditionalHeaders, continuationNote, drainPages, freshnessNote, pageRequest, parseInstant, parseResponse, positionals, readJsonOption, table, } from "./support.js";
|
|
7
|
+
import { apiUrlOption, conditionalHeaders, continuationNote, drainPages, freshnessNote, pageRequest, parseInstant, parseResponse, positionals, readJsonOption, readPrivateJsonOption, table, } from "./support.js";
|
|
8
8
|
/* -------------------------------------------------------------------------- */
|
|
9
9
|
/* Shared plumbing */
|
|
10
10
|
/* -------------------------------------------------------------------------- */
|
|
@@ -102,18 +102,78 @@ async function scheduleRequest(runtime, args, usage) {
|
|
|
102
102
|
? { everySeconds: parseDuration(value) }
|
|
103
103
|
: { cron: value };
|
|
104
104
|
const endpoint = args.options["--endpoint"];
|
|
105
|
+
const url = args.options["--url"];
|
|
106
|
+
if (endpoint !== undefined && url !== undefined) {
|
|
107
|
+
// Refused here as well as by the service, because "you gave both" is a
|
|
108
|
+
// sentence about the line somebody typed. The service's own refusal says
|
|
109
|
+
// the same thing a round trip later.
|
|
110
|
+
throw new CliError("give --endpoint or --url, not both", "usage", "--url uses this project's destination at that address, or creates one. --endpoint names a destination that already exists.");
|
|
111
|
+
}
|
|
105
112
|
const data = await readJsonOption(runtime, "--data", args.options["--data"]);
|
|
113
|
+
const httpInput = await readPrivateJsonOption(runtime, "--http", args.options["--http"]);
|
|
114
|
+
const http = httpInput === undefined
|
|
115
|
+
? undefined
|
|
116
|
+
: httpRequestSchema.safeParse(httpInput);
|
|
117
|
+
if (http !== undefined && !http.success)
|
|
118
|
+
throw new CliError("--http settings are invalid", "usage", http.error.issues.map((issue) => issue.message).join("; "));
|
|
106
119
|
const latencyClass = args.options["--latency-class"];
|
|
107
120
|
const partitionKey = args.options["--partition-key"];
|
|
108
121
|
return {
|
|
109
122
|
...(data === undefined ? {} : { data }),
|
|
123
|
+
...(http?.success ? { http: http.data } : {}),
|
|
110
124
|
...(endpoint === undefined ? {} : { endpoint }),
|
|
125
|
+
...(url === undefined ? {} : { url }),
|
|
111
126
|
...(latencyClass === undefined ? {} : { latencyClass }),
|
|
112
127
|
...(partitionKey === undefined ? {} : { partitionKey }),
|
|
113
128
|
...(timeZone === undefined ? {} : { timeZone }),
|
|
114
129
|
...recurrence,
|
|
115
130
|
};
|
|
116
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Where a schedule written with `--url` delivers, and where its signing
|
|
134
|
+
* secret is.
|
|
135
|
+
*
|
|
136
|
+
* **Only the API's notice, never a secret.** A schedule write that names a
|
|
137
|
+
* `url` answers with `endpointSigningSecret` — `returned: false` and the
|
|
138
|
+
* dashboard page where a member reveals the secret — and nothing else about
|
|
139
|
+
* it, so there is nothing here that could print one. Absent when the write
|
|
140
|
+
* named `--endpoint`: that caller already holds the destination it chose.
|
|
141
|
+
*/
|
|
142
|
+
export function destinationLines(schedule, notice,
|
|
143
|
+
/**
|
|
144
|
+
* The `--url` that was sent. A schedule write answers without
|
|
145
|
+
* `endpointUrl` (only reads carry it), and the address is the one the
|
|
146
|
+
* destination was found or made at.
|
|
147
|
+
*/
|
|
148
|
+
requestedUrl) {
|
|
149
|
+
if (notice === undefined)
|
|
150
|
+
return [];
|
|
151
|
+
const address = schedule.endpointUrl ?? canonical(requestedUrl);
|
|
152
|
+
return [
|
|
153
|
+
...(schedule.endpoint === undefined
|
|
154
|
+
? []
|
|
155
|
+
: [
|
|
156
|
+
`Destination ${schedule.endpoint}${address === undefined ? "" : ` at ${address}`}`,
|
|
157
|
+
]),
|
|
158
|
+
``,
|
|
159
|
+
`GreatWait signs every call to this address. Your receiver checks the`,
|
|
160
|
+
`signature with the destination's signing secret, which isn't shown here.`,
|
|
161
|
+
notice.revealUrl === undefined
|
|
162
|
+
? `A project member can reveal it on the destination's page in the dashboard.`
|
|
163
|
+
: `Reveal it on the destination's page: ${notice.revealUrl}`,
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
/** An address as the service stores it, or as typed if it will not parse. */
|
|
167
|
+
function canonical(url) {
|
|
168
|
+
if (url === undefined)
|
|
169
|
+
return undefined;
|
|
170
|
+
try {
|
|
171
|
+
return new URL(url).href;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return url;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
117
177
|
/** How a schedule's recurrence reads in one column. */
|
|
118
178
|
function recurrenceOf(schedule) {
|
|
119
179
|
return schedule.recurrence.kind;
|
|
@@ -176,6 +236,12 @@ const destinationsUpdate = async (runtime, args) => {
|
|
|
176
236
|
if (url === undefined) {
|
|
177
237
|
throw new CliError(`${runtime.programName} destinations update needs --url`, "usage", `Usage: ${runtime.programName} destinations update <destination> --url https://your-receiver.example.com/greatwait`);
|
|
178
238
|
}
|
|
239
|
+
const headerInput = await readPrivateJsonOption(runtime, "--headers", args.options["--headers"]);
|
|
240
|
+
const headers = headerInput === undefined
|
|
241
|
+
? undefined
|
|
242
|
+
: endpointHeaderChangesSchema.safeParse(headerInput);
|
|
243
|
+
if (headers !== undefined && !headers.success)
|
|
244
|
+
throw new CliError("--headers settings are invalid", "usage", headers.error.issues.map((issue) => issue.message).join("; "));
|
|
179
245
|
const active = await session(runtime, args);
|
|
180
246
|
// Read first, so the preview is the destination as it stands rather than the
|
|
181
247
|
// arguments read back.
|
|
@@ -188,7 +254,7 @@ const destinationsUpdate = async (runtime, args) => {
|
|
|
188
254
|
`Deliveries in flight keep going to the receiver they started for.`,
|
|
189
255
|
],
|
|
190
256
|
});
|
|
191
|
-
const updated = parseResponse("endpoints.update", await active.client.request("endpoints_update", id, { url }, conditionalHeaders(args)));
|
|
257
|
+
const updated = parseResponse("endpoints.update", await active.client.request("endpoints_update", id, { url, ...(headers?.success ? { headers: headers.data } : {}) }, conditionalHeaders(args)));
|
|
192
258
|
return {
|
|
193
259
|
data: { confirmation, endpoint: updated },
|
|
194
260
|
human: endpointBlock(updated).join("\n"),
|
|
@@ -279,6 +345,9 @@ async function scheduleChange(runtime, args, options) {
|
|
|
279
345
|
return {
|
|
280
346
|
data: {
|
|
281
347
|
...(confirmation === undefined ? {} : { confirmation }),
|
|
348
|
+
...(view.endpointSigningSecret === undefined
|
|
349
|
+
? {}
|
|
350
|
+
: { endpointSigningSecret: view.endpointSigningSecret }),
|
|
282
351
|
...(view.occurrence === undefined ? {} : { occurrence: view.occurrence }),
|
|
283
352
|
schedule: view.schedule,
|
|
284
353
|
},
|
|
@@ -292,12 +361,13 @@ async function scheduleChange(runtime, args, options) {
|
|
|
292
361
|
: [
|
|
293
362
|
`Next ${view.occurrence.id} planned for ${view.occurrence.plannedFor} (${view.occurrence.state})`,
|
|
294
363
|
]),
|
|
364
|
+
...destinationLines(view.schedule, view.endpointSigningSecret, args.options["--url"]),
|
|
295
365
|
].join("\n"),
|
|
296
366
|
...(nextMove === undefined ? {} : { nextMove }),
|
|
297
367
|
};
|
|
298
368
|
}
|
|
299
369
|
const schedulesReplace = async (runtime, args) => {
|
|
300
|
-
const usage = `${runtime.programName} schedules replace <schedule> --endpoint <destination> (--in <duration> | --at <instant> | --every <duration> | --cron <expression>)`;
|
|
370
|
+
const usage = `${runtime.programName} schedules replace <schedule> (--endpoint <destination> | --url <https url>) (--in <duration> | --at <instant> | --every <duration> | --cron <expression>)`;
|
|
301
371
|
const [id] = positionals(args, ["<schedule>"], usage);
|
|
302
372
|
const request = await scheduleRequest(runtime, args, usage);
|
|
303
373
|
return scheduleChange(runtime, args, {
|
|
@@ -306,7 +376,8 @@ const schedulesReplace = async (runtime, args) => {
|
|
|
306
376
|
action: `Replace the definition of schedule ${current.id}.`,
|
|
307
377
|
effect: [
|
|
308
378
|
`now ${recurrenceOf(current)}, ${current.status}, version ${String(current.version)}`,
|
|
309
|
-
`after the
|
|
379
|
+
`after the timing on this command line. Destination, data and HTTP`,
|
|
380
|
+
` settings are kept unless this command line gives new ones.`,
|
|
310
381
|
`Occurrences already planned under the old definition are discarded.`,
|
|
311
382
|
],
|
|
312
383
|
}),
|
|
@@ -1387,7 +1458,9 @@ const sinksCreate = async (runtime, args) => {
|
|
|
1387
1458
|
const program = await readJsonOption(runtime, "--program", args.options["--program"]);
|
|
1388
1459
|
const sink = parseResponse("testsinks.create", await active.client.request("testsinks_create", {
|
|
1389
1460
|
endpoint,
|
|
1390
|
-
|
|
1461
|
+
// The program is passed as typed and validated by the service, which
|
|
1462
|
+
// names the field it refuses; the client's type cannot know it yet.
|
|
1463
|
+
...(program === undefined ? {} : { program: program }),
|
|
1391
1464
|
}));
|
|
1392
1465
|
return {
|
|
1393
1466
|
data: { sink },
|
package/dist/remediation.d.ts
CHANGED
|
@@ -30,6 +30,6 @@ export declare function remediationFor(problem: Problem | undefined, retryable:
|
|
|
30
30
|
* A case asserting what the advice *says* wants one spelling; a case asserting
|
|
31
31
|
* that it follows the invoked name passes its own.
|
|
32
32
|
*/
|
|
33
|
-
export declare const REMEDIATION: Readonly<Record<"
|
|
33
|
+
export declare const REMEDIATION: Readonly<Record<"authentication_required" | "billing_not_ready" | "capability_not_configured" | "credential_expired" | "credential_revoked" | "idempotency_key_reused" | "invalid_request" | "not_found" | "payload_too_large" | "plan_limit_reached" | "project_archived" | "rate_limited" | "revision_conflict" | "scope_missing" | "service_unavailable" | "state_conflict" | "test_to_live_escalation" | "version_conflict", string>>;
|
|
34
34
|
/** Exposed so a case can assert the records cover the published vocabularies. */
|
|
35
35
|
export { isRetryable, remediationTable };
|
package/dist/support.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ export declare function expectNoExtraArguments(args: ParsedArguments, used: numb
|
|
|
31
31
|
* "unexpected argument" rule is applied from the same count.
|
|
32
32
|
*/
|
|
33
33
|
export declare function positionals(args: ParsedArguments, names: readonly string[], usage: string): readonly string[];
|
|
34
|
+
/** Secret-bearing option documents are accepted through files/stdin only. */
|
|
35
|
+
export declare function readPrivateJsonOption(runtime: CliRuntime, flag: string, value: string | undefined): Promise<unknown>;
|
|
34
36
|
/** The payload, from an argument, a file, or standard input. */
|
|
35
37
|
export declare function readJsonOption(runtime: CliRuntime, flag: string, value: string | undefined): Promise<unknown>;
|
|
36
38
|
/**
|
package/dist/support.js
CHANGED
|
@@ -76,6 +76,13 @@ export function positionals(args, names, usage) {
|
|
|
76
76
|
expectNoExtraArguments(args, names.length, usage);
|
|
77
77
|
return found;
|
|
78
78
|
}
|
|
79
|
+
/** Secret-bearing option documents are accepted through files/stdin only. */
|
|
80
|
+
export async function readPrivateJsonOption(runtime, flag, value) {
|
|
81
|
+
if (value !== undefined && value !== "-" && !value.startsWith("@")) {
|
|
82
|
+
throw new CliError(`${flag} must be @file.json or -`, "usage", "Read secret-bearing settings from a private file or standard input, never a command-line argument.");
|
|
83
|
+
}
|
|
84
|
+
return readJsonOption(runtime, flag, value);
|
|
85
|
+
}
|
|
79
86
|
/** The payload, from an argument, a file, or standard input. */
|
|
80
87
|
export async function readJsonOption(runtime, flag, value) {
|
|
81
88
|
if (value === undefined)
|
package/package.json
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"url": "https://github.com/flowxo/greatwait/issues"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@greatwait/api-client": "0.1.0-alpha.
|
|
11
|
-
"@greatwait/contracts": "0.1.0-alpha.
|
|
10
|
+
"@greatwait/api-client": "0.1.0-alpha.6",
|
|
11
|
+
"@greatwait/contracts": "0.1.0-alpha.6"
|
|
12
12
|
},
|
|
13
13
|
"description": "The GreatWait command line: sign in, create a destination and a one-shot schedule, and wait for the callback, over the public API.",
|
|
14
14
|
"engines": {
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
},
|
|
49
49
|
"sideEffects": false,
|
|
50
50
|
"type": "module",
|
|
51
|
-
"version": "0.1.0-alpha.
|
|
51
|
+
"version": "0.1.0-alpha.6"
|
|
52
52
|
}
|