@statuser/cli 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +448 -2
- package/dist/client.js +163 -0
- package/dist/client.js.map +1 -0
- package/dist/commander-i18n.js +52 -0
- package/dist/commander-i18n.js.map +1 -0
- package/dist/commands/api.js +104 -0
- package/dist/commands/api.js.map +1 -0
- package/dist/commands/auth.js +104 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/catalog.js +72 -0
- package/dist/commands/catalog.js.map +1 -0
- package/dist/commands/config.js +302 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/heartbeat.js +199 -0
- package/dist/commands/heartbeat.js.map +1 -0
- package/dist/commands/incidents.js +265 -0
- package/dist/commands/incidents.js.map +1 -0
- package/dist/commands/maintenance.js +211 -0
- package/dist/commands/maintenance.js.map +1 -0
- package/dist/commands/reports.js +170 -0
- package/dist/commands/reports.js.map +1 -0
- package/dist/commands/scenarios.js +361 -0
- package/dist/commands/scenarios.js.map +1 -0
- package/dist/commands/servers-add.js +152 -0
- package/dist/commands/servers-add.js.map +1 -0
- package/dist/commands/servers-checks.js +56 -0
- package/dist/commands/servers-checks.js.map +1 -0
- package/dist/commands/servers.js +424 -0
- package/dist/commands/servers.js.map +1 -0
- package/dist/config.js +107 -0
- package/dist/config.js.map +1 -0
- package/dist/confirm.js +34 -0
- package/dist/confirm.js.map +1 -0
- package/dist/context.js +27 -0
- package/dist/context.js.map +1 -0
- package/dist/duration.js +20 -0
- package/dist/duration.js.map +1 -0
- package/dist/errors.js +73 -0
- package/dist/errors.js.map +1 -0
- package/dist/files.js +36 -0
- package/dist/files.js.map +1 -0
- package/dist/generated/openapi.js +6 -0
- package/dist/generated/openapi.js.map +1 -0
- package/dist/i18n.js +757 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/json-error.js +10 -0
- package/dist/json-error.js.map +1 -0
- package/dist/output.js +43 -0
- package/dist/output.js.map +1 -0
- package/dist/scenarios/config.js +228 -0
- package/dist/scenarios/config.js.map +1 -0
- package/dist/scenarios/plan.js +159 -0
- package/dist/scenarios/plan.js.map +1 -0
- package/dist/servers-config/export.js +72 -0
- package/dist/servers-config/export.js.map +1 -0
- package/dist/servers-config/fields.js +172 -0
- package/dist/servers-config/fields.js.map +1 -0
- package/dist/servers-config/forbidden-host.js +75 -0
- package/dist/servers-config/forbidden-host.js.map +1 -0
- package/dist/servers-config/host-pattern.js +6 -0
- package/dist/servers-config/host-pattern.js.map +1 -0
- package/dist/servers-config/parse.js +260 -0
- package/dist/servers-config/parse.js.map +1 -0
- package/dist/servers-config/plan.js +492 -0
- package/dist/servers-config/plan.js.map +1 -0
- package/dist/servers-update.js +157 -0
- package/dist/servers-update.js.map +1 -0
- package/dist/status-page.js +15 -0
- package/dist/status-page.js.map +1 -0
- package/dist/text-length.js +8 -0
- package/dist/text-length.js.map +1 -0
- package/dist/write-failure.js +37 -0
- package/dist/write-failure.js.map +1 -0
- package/package.json +70 -4
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Help } from 'commander';
|
|
2
|
+
import { t } from './i18n.js';
|
|
3
|
+
// Commander prints its help headings and parse errors in English only. The
|
|
4
|
+
// help text is post-processed and the errors are rewritten through the
|
|
5
|
+
// dictionary, so a Russian terminal does not get half of the CLI in English.
|
|
6
|
+
const HEADINGS = [
|
|
7
|
+
[/^Usage:/gm, () => t.helpUsage],
|
|
8
|
+
[/^Arguments:/gm, () => t.helpArguments],
|
|
9
|
+
[/^Global Options:/gm, () => t.helpGlobalOptions],
|
|
10
|
+
[/^Options:/gm, () => t.helpOptions],
|
|
11
|
+
[/^Commands:/gm, () => t.helpCommands],
|
|
12
|
+
// A wrapped line puts a newline after the colon, not a space.
|
|
13
|
+
[/\(default:(?=\s)/g, () => `(${t.helpDefault}:`],
|
|
14
|
+
];
|
|
15
|
+
const ERRORS = [
|
|
16
|
+
[/^error: unknown option '([^']+)'(.*)$/s, (o) => t.errUnknownOption(o)],
|
|
17
|
+
[/^error: unknown command '([^']+)'(.*)$/s, (c) => t.errUnknownCommand(c)],
|
|
18
|
+
[
|
|
19
|
+
/^error: required option '([^']+)' not specified/,
|
|
20
|
+
(o) => t.errMissingOption(o),
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
/^error: missing required argument '([^']+)'/,
|
|
24
|
+
(a) => t.errMissingArgument(a),
|
|
25
|
+
],
|
|
26
|
+
[/^error: option '([^']+)' argument missing/, (o) => t.errOptionValue(o)],
|
|
27
|
+
[/^error: too many arguments/, () => t.errTooManyArguments],
|
|
28
|
+
];
|
|
29
|
+
export function localizeHelp(text) {
|
|
30
|
+
return HEADINGS.reduce((acc, [pattern, label]) => acc.replace(pattern, label()), text);
|
|
31
|
+
}
|
|
32
|
+
export function localizeError(text) {
|
|
33
|
+
for (const [pattern, message] of ERRORS) {
|
|
34
|
+
const match = pattern.exec(text.trim());
|
|
35
|
+
if (match)
|
|
36
|
+
return `${message(...match.slice(1))}\n`;
|
|
37
|
+
}
|
|
38
|
+
return text;
|
|
39
|
+
}
|
|
40
|
+
/** Call before subcommands are added: they inherit these settings. */
|
|
41
|
+
export function localizeCommander(program) {
|
|
42
|
+
program
|
|
43
|
+
.configureHelp({
|
|
44
|
+
formatHelp: (cmd, helper) => localizeHelp(Help.prototype.formatHelp.call(helper, cmd, helper)),
|
|
45
|
+
})
|
|
46
|
+
.configureOutput({
|
|
47
|
+
outputError: (text, write) => write(localizeError(text)),
|
|
48
|
+
})
|
|
49
|
+
.helpOption('-h, --help', t.helpOption)
|
|
50
|
+
.helpCommand('help [command]', t.helpCommand);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=commander-i18n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commander-i18n.js","sourceRoot":"","sources":["../src/commander-i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgB,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,CAAC,EAAE,MAAM,WAAW,CAAC;AAE9B,2EAA2E;AAC3E,uEAAuE;AACvE,6EAA6E;AAC7E,MAAM,QAAQ,GAAkC;IAC9C,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAChC,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;IACxC,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACjD,CAAC,aAAa,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC;IACpC,CAAC,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;IACtC,8DAA8D;IAC9D,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,WAAW,GAAG,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,GAAqD;IAC/D,CAAC,wCAAwC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACxE,CAAC,yCAAyC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC1E;QACE,iDAAiD;QACjD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC;KAC7B;IACD;QACE,6CAA6C;QAC7C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC;KAC/B;IACD,CAAC,2CAA2C,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;IACzE,CAAC,4BAA4B,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,mBAAmB,CAAC;CAC5D,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,QAAQ,CAAC,MAAM,CACpB,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,EACxD,IAAI,CACL,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,MAAM,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACxC,IAAI,KAAK;YAAE,OAAO,GAAG,OAAO,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,iBAAiB,CAAC,OAAgB;IAChD,OAAO;SACJ,aAAa,CAAC;QACb,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAC1B,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;KACpE,CAAC;SACD,eAAe,CAAC;QACf,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;KACzD,CAAC;SACD,UAAU,CAAC,YAAY,EAAE,CAAC,CAAC,UAAU,CAAC;SACtC,WAAW,CAAC,gBAAgB,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { authedClient, globalOptions } from '../context.js';
|
|
3
|
+
import { CliError, ExitCode } from '../errors.js';
|
|
4
|
+
import { t } from '../i18n.js';
|
|
5
|
+
import { jsonErrorPosition } from '../json-error.js';
|
|
6
|
+
import { printJson, printLine } from '../output.js';
|
|
7
|
+
const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
8
|
+
const collect = (value, previous) => [...previous, value];
|
|
9
|
+
export function registerApiCommand(program) {
|
|
10
|
+
program
|
|
11
|
+
.command('api')
|
|
12
|
+
.description(t.cmdApi)
|
|
13
|
+
.argument('<path>', '/v1/…')
|
|
14
|
+
.option('-X, --method <method>', 'GET, POST, PUT, PATCH, DELETE', 'GET')
|
|
15
|
+
.option('-F, --field <key=value>', t.optField, collect, [])
|
|
16
|
+
.option('-f, --raw-field <key=value>', t.optRawField, collect, [])
|
|
17
|
+
.option('--input <file>', t.optInput)
|
|
18
|
+
.action(async (path, opts, command) => {
|
|
19
|
+
const method = opts.method.toUpperCase();
|
|
20
|
+
if (!METHODS.includes(method)) {
|
|
21
|
+
throw new CliError(t.apiMethodInvalid(opts.method), ExitCode.Usage);
|
|
22
|
+
}
|
|
23
|
+
const fields = {};
|
|
24
|
+
for (const [position, raw] of opts.field.entries()) {
|
|
25
|
+
const [key, value] = splitField(raw, '-F', position);
|
|
26
|
+
fields[key] = parseTyped(value);
|
|
27
|
+
}
|
|
28
|
+
for (const [position, raw] of opts.rawField.entries()) {
|
|
29
|
+
const [key, value] = splitField(raw, '-f', position);
|
|
30
|
+
fields[key] = value;
|
|
31
|
+
}
|
|
32
|
+
const hasFields = Object.keys(fields).length > 0;
|
|
33
|
+
if (hasFields && opts.input) {
|
|
34
|
+
throw new CliError(t.apiFieldsWithInput, ExitCode.Usage);
|
|
35
|
+
}
|
|
36
|
+
// fetch refuses a body on GET before sending, which would surface as a
|
|
37
|
+
// network error with exit code 1.
|
|
38
|
+
if (opts.input && method === 'GET') {
|
|
39
|
+
throw new CliError(t.apiInputWithGet, ExitCode.Usage);
|
|
40
|
+
}
|
|
41
|
+
// Like `gh api`: fields of a read request are query parameters.
|
|
42
|
+
const fieldsAsQuery = method === 'GET' || method === 'DELETE';
|
|
43
|
+
let body;
|
|
44
|
+
let query;
|
|
45
|
+
if (opts.input) {
|
|
46
|
+
const text = await readInput(opts.input).catch(() => {
|
|
47
|
+
throw new CliError(t.configNotFound(opts.input), ExitCode.Usage);
|
|
48
|
+
});
|
|
49
|
+
try {
|
|
50
|
+
body = JSON.parse(text);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw new CliError(t.configInvalid(opts.input, t.cfgJson(jsonErrorPosition(error))), ExitCode.Usage);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (hasFields && fieldsAsQuery) {
|
|
57
|
+
query = Object.fromEntries(Object.entries(fields).map(([k, v]) => [
|
|
58
|
+
k,
|
|
59
|
+
typeof v === 'object' ? JSON.stringify(v) : v,
|
|
60
|
+
]));
|
|
61
|
+
}
|
|
62
|
+
else if (hasFields) {
|
|
63
|
+
body = fields;
|
|
64
|
+
}
|
|
65
|
+
const { client } = await authedClient(command);
|
|
66
|
+
const result = await client.call({ method, path, query, body });
|
|
67
|
+
// With --json stdout is always one JSON value: text answers (CSV
|
|
68
|
+
// exports) become a JSON string, an empty one becomes null.
|
|
69
|
+
if (globalOptions(command).json)
|
|
70
|
+
printJson(result);
|
|
71
|
+
else if (result === null)
|
|
72
|
+
return;
|
|
73
|
+
else if (typeof result === 'string')
|
|
74
|
+
printLine(result);
|
|
75
|
+
else
|
|
76
|
+
printJson(result);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function splitField(raw, flag, position) {
|
|
80
|
+
const index = raw.indexOf('=');
|
|
81
|
+
// The argument may be a bare secret: it is never quoted back.
|
|
82
|
+
if (index <= 0) {
|
|
83
|
+
throw new CliError(t.argInvalid(flag, position + 1), ExitCode.Usage);
|
|
84
|
+
}
|
|
85
|
+
return [raw.slice(0, index), raw.slice(index + 1)];
|
|
86
|
+
}
|
|
87
|
+
/** `-F count=3` sends a number, `-F on=true` a boolean, `-F ids=[1,2]` an array. */
|
|
88
|
+
function parseTyped(value) {
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(value);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function readInput(source) {
|
|
97
|
+
if (source !== '-')
|
|
98
|
+
return readFile(source, 'utf8');
|
|
99
|
+
const chunks = [];
|
|
100
|
+
for await (const chunk of process.stdin)
|
|
101
|
+
chunks.push(chunk);
|
|
102
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/commands/api.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,OAAO,GAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;AASxE,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,QAAkB,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,CAAC;AAE5E,MAAM,UAAU,kBAAkB,CAAC,OAAgB;IACjD,OAAO;SACJ,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC;SACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;SAC3B,MAAM,CAAC,uBAAuB,EAAE,+BAA+B,EAAE,KAAK,CAAC;SACvE,MAAM,CAAC,yBAAyB,EAAE,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC;SAC1D,MAAM,CAAC,6BAA6B,EAAE,CAAC,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;SACjE,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC,QAAQ,CAAC;SACpC,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,IAAgB,EAAE,OAAgB,EAAE,EAAE;QACjE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,EAAgB,CAAC;QACvD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;QACtE,CAAC;QACD,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;YACnD,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;YACrD,MAAM,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;QACD,KAAK,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;YACtD,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;YACrD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtB,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QACjD,IAAI,SAAS,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC5B,MAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,kBAAkB,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC3D,CAAC;QACD,uEAAuE;QACvE,kCAAkC;QAClC,IAAI,IAAI,CAAC,KAAK,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;YACnC,MAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,eAAe,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;QACxD,CAAC;QAED,gEAAgE;QAChE,MAAM,aAAa,GAAG,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,QAAQ,CAAC;QAC9D,IAAI,IAAa,CAAC;QAClB,IAAI,KAA6C,CAAC;QAClD,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;gBAClD,MAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,KAAM,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;YACpE,CAAC,CAAC,CAAC;YACH,IAAI,CAAC;gBACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,IAAI,QAAQ,CAChB,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,EAChE,QAAQ,CAAC,KAAK,CACf,CAAC;YACJ,CAAC;QACH,CAAC;aAAM,IAAI,SAAS,IAAI,aAAa,EAAE,CAAC;YACtC,KAAK,GAAG,MAAM,CAAC,WAAW,CACxB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;gBACrC,CAAC;gBACD,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAgB;aAC9D,CAAC,CACH,CAAC;QACJ,CAAC;aAAM,IAAI,SAAS,EAAE,CAAC;YACrB,IAAI,GAAG,MAAM,CAAC;QAChB,CAAC;QAED,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAU,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACzE,iEAAiE;QACjE,4DAA4D;QAC5D,IAAI,aAAa,CAAC,OAAO,CAAC,CAAC,IAAI;YAAE,SAAS,CAAC,MAAM,CAAC,CAAC;aAC9C,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO;aAC5B,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,SAAS,CAAC,MAAM,CAAC,CAAC;;YAClD,SAAS,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,UAAU,CACjB,GAAW,EACX,IAAY,EACZ,QAAgB;IAEhB,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/B,8DAA8D;IAC9D,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACf,MAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,oFAAoF;AACpF,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,MAAc;IACrC,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK;QAAE,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;IACtE,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { Writable } from 'node:stream';
|
|
3
|
+
import { StatuserClient } from '../client.js';
|
|
4
|
+
import { removeConfig, resolveApiUrl, saveConfig } from '../config.js';
|
|
5
|
+
import { authedClient, globalOptions } from '../context.js';
|
|
6
|
+
import { CliError, ExitCode } from '../errors.js';
|
|
7
|
+
import { t } from '../i18n.js';
|
|
8
|
+
import { printJson, printLine } from '../output.js';
|
|
9
|
+
export function registerAuthCommands(program) {
|
|
10
|
+
program
|
|
11
|
+
.command('login')
|
|
12
|
+
.description(t.cmdLogin)
|
|
13
|
+
.option('--with-token', t.optWithToken)
|
|
14
|
+
.action(async (opts, command) => {
|
|
15
|
+
const apiKey = (opts.withToken ? await readStdin() : await promptHidden(t.promptApiKey)).trim();
|
|
16
|
+
if (!apiKey)
|
|
17
|
+
throw new CliError(t.loginEmptyKey, ExitCode.Usage);
|
|
18
|
+
const global = globalOptions(command);
|
|
19
|
+
const apiUrl = await resolveApiUrl(global.apiUrl);
|
|
20
|
+
const client = new StatuserClient({
|
|
21
|
+
apiUrl,
|
|
22
|
+
apiKey,
|
|
23
|
+
debug: global.debug,
|
|
24
|
+
});
|
|
25
|
+
// Checked before saving: a typo should not be stored as a working key.
|
|
26
|
+
const account = await client.call({
|
|
27
|
+
method: 'GET',
|
|
28
|
+
path: '/v1/account',
|
|
29
|
+
});
|
|
30
|
+
const path = await saveConfig({ api_url: apiUrl, api_key: apiKey });
|
|
31
|
+
if (global.json)
|
|
32
|
+
printJson({ email: account.email, config: path });
|
|
33
|
+
else
|
|
34
|
+
printLine(t.loginSaved(account.email, path));
|
|
35
|
+
});
|
|
36
|
+
program
|
|
37
|
+
.command('logout')
|
|
38
|
+
.description(t.cmdLogout)
|
|
39
|
+
.action(async (_opts, command) => {
|
|
40
|
+
const removed = await removeConfig();
|
|
41
|
+
if (globalOptions(command).json)
|
|
42
|
+
printJson({ removed });
|
|
43
|
+
else
|
|
44
|
+
printLine(removed ? t.logoutDone : t.logoutNothing);
|
|
45
|
+
});
|
|
46
|
+
program
|
|
47
|
+
.command('whoami')
|
|
48
|
+
.description(t.cmdWhoami)
|
|
49
|
+
.action(async (_opts, command) => {
|
|
50
|
+
const { client, auth } = await authedClient(command);
|
|
51
|
+
const account = await client.call({
|
|
52
|
+
method: 'GET',
|
|
53
|
+
path: '/v1/account',
|
|
54
|
+
});
|
|
55
|
+
if (globalOptions(command).json) {
|
|
56
|
+
printJson(account);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const source = auth.source.kind === 'env'
|
|
60
|
+
? t.keySourceEnv
|
|
61
|
+
: t.keySourceFile(auth.source.path);
|
|
62
|
+
printLine(t.whoami(account.email, source));
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async function readStdin() {
|
|
66
|
+
const chunks = [];
|
|
67
|
+
for await (const chunk of process.stdin)
|
|
68
|
+
chunks.push(chunk);
|
|
69
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
70
|
+
}
|
|
71
|
+
/** Prompts on the terminal without echoing the key. */
|
|
72
|
+
function promptHidden(question) {
|
|
73
|
+
if (!process.stdin.isTTY) {
|
|
74
|
+
return Promise.reject(new CliError(t.loginNoTty, ExitCode.Usage));
|
|
75
|
+
}
|
|
76
|
+
let muted = false;
|
|
77
|
+
const output = new Writable({
|
|
78
|
+
write(chunk, _encoding, callback) {
|
|
79
|
+
if (!muted)
|
|
80
|
+
process.stderr.write(chunk);
|
|
81
|
+
callback();
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
const rl = createInterface({ input: process.stdin, output, terminal: true });
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
let answered = false;
|
|
87
|
+
rl.question(question, (answer) => {
|
|
88
|
+
answered = true;
|
|
89
|
+
rl.close();
|
|
90
|
+
process.stderr.write('\n');
|
|
91
|
+
resolve(answer);
|
|
92
|
+
});
|
|
93
|
+
// Ctrl+D closes the prompt without an answer; without this the promise
|
|
94
|
+
// never settles and Node exits with its own code 13.
|
|
95
|
+
rl.on('close', () => {
|
|
96
|
+
if (answered)
|
|
97
|
+
return;
|
|
98
|
+
process.stderr.write('\n');
|
|
99
|
+
reject(new CliError(t.loginEmptyKey, ExitCode.Usage));
|
|
100
|
+
});
|
|
101
|
+
muted = true;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { authedClient, globalOptions } from '../context.js';
|
|
2
|
+
import { t } from '../i18n.js';
|
|
3
|
+
import { printJson, printLine, printTable } from '../output.js';
|
|
4
|
+
import { parseId } from './servers.js';
|
|
5
|
+
/**
|
|
6
|
+
* Where the ids for `--project` and `--page` come from. The lists are
|
|
7
|
+
* read-only here: projects and pages are set up in the panel.
|
|
8
|
+
*/
|
|
9
|
+
export function registerCatalogCommands(program) {
|
|
10
|
+
program
|
|
11
|
+
.command('projects')
|
|
12
|
+
.description(t.cmdProjects)
|
|
13
|
+
.command('list')
|
|
14
|
+
.description(t.cmdProjectsList)
|
|
15
|
+
.action(async (_opts, command) => {
|
|
16
|
+
const { client } = await authedClient(command);
|
|
17
|
+
const projects = await client.call({
|
|
18
|
+
method: 'GET',
|
|
19
|
+
path: '/v1/projects',
|
|
20
|
+
});
|
|
21
|
+
if (globalOptions(command).json) {
|
|
22
|
+
printJson(projects);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!projects.length) {
|
|
26
|
+
printLine(t.projectsEmpty);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
printTable([t.colId, t.colName], projects.map((project) => [project.id, project.name]));
|
|
30
|
+
});
|
|
31
|
+
program
|
|
32
|
+
.command('pages')
|
|
33
|
+
.description(t.cmdPages)
|
|
34
|
+
.command('list')
|
|
35
|
+
.description(t.cmdPagesList)
|
|
36
|
+
.option('--project <id>', t.optProject, parseId)
|
|
37
|
+
.action(async (opts, command) => {
|
|
38
|
+
const { client } = await authedClient(command);
|
|
39
|
+
let pages = await client.call({
|
|
40
|
+
method: 'GET',
|
|
41
|
+
path: '/v1/status-pages',
|
|
42
|
+
});
|
|
43
|
+
if (opts.project !== undefined) {
|
|
44
|
+
pages = pages.filter((page) => page.project_id === opts.project);
|
|
45
|
+
}
|
|
46
|
+
if (globalOptions(command).json) {
|
|
47
|
+
// The full page carries its reports, maintenance and groups; the
|
|
48
|
+
// list keeps what identifies it.
|
|
49
|
+
printJson(pages.map((page) => ({
|
|
50
|
+
id: page.id,
|
|
51
|
+
name: page.name,
|
|
52
|
+
slug: page.slug,
|
|
53
|
+
domains: page.domains,
|
|
54
|
+
is_published: page.is_published,
|
|
55
|
+
project_id: page.project_id,
|
|
56
|
+
})));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!pages.length) {
|
|
60
|
+
printLine(t.pagesEmpty);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
printTable([t.colId, t.colName, t.colSlug, t.colPublished, t.colProject], pages.map((page) => [
|
|
64
|
+
page.id,
|
|
65
|
+
page.name,
|
|
66
|
+
page.slug,
|
|
67
|
+
page.is_published ? t.yes : t.no,
|
|
68
|
+
page.project_id,
|
|
69
|
+
]));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/commands/catalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { authedClient, globalOptions } from '../context.js';
|
|
3
|
+
import { CliError, ExitCode } from '../errors.js';
|
|
4
|
+
import { writeOwnFile } from '../files.js';
|
|
5
|
+
import { describeWriteFailure } from '../write-failure.js';
|
|
6
|
+
import { t } from '../i18n.js';
|
|
7
|
+
import { jsonErrorPosition } from '../json-error.js';
|
|
8
|
+
import { printErr, printJson, printLine } from '../output.js';
|
|
9
|
+
import { loadScenarioConfig } from '../scenarios/config.js';
|
|
10
|
+
import { buildDeployPlan } from '../scenarios/plan.js';
|
|
11
|
+
import { exportServers } from '../servers-config/export.js';
|
|
12
|
+
import { parseServersSection } from '../servers-config/parse.js';
|
|
13
|
+
import { buildServersPlan, checkScenarioLimits, } from '../servers-config/plan.js';
|
|
14
|
+
import { describeStep } from './scenarios.js';
|
|
15
|
+
import { listServers, parseId } from './servers.js';
|
|
16
|
+
const DEFAULT_CONFIG = 'statuser.json';
|
|
17
|
+
/**
|
|
18
|
+
* Configuration as a file: `export` writes the project's servers to
|
|
19
|
+
* statuser.json, `diff` shows what `apply` would change, `apply` brings the
|
|
20
|
+
* project in line. Nothing is ever deleted; servers the file does not
|
|
21
|
+
* mention are only listed.
|
|
22
|
+
*/
|
|
23
|
+
export function registerConfigCommands(program) {
|
|
24
|
+
program
|
|
25
|
+
.command('export')
|
|
26
|
+
.description(t.cmdExport)
|
|
27
|
+
.requiredOption('--project <id>', t.optProject, parseId)
|
|
28
|
+
.option('-o, --output <file>', t.optExportOutput)
|
|
29
|
+
.option('--force', t.optForce)
|
|
30
|
+
.action(async (opts, command) => {
|
|
31
|
+
const { client } = await authedClient(command);
|
|
32
|
+
const result = exportServers(await listServers(client, opts.project));
|
|
33
|
+
let document = {
|
|
34
|
+
project_id: opts.project,
|
|
35
|
+
servers: result.entries,
|
|
36
|
+
};
|
|
37
|
+
if (opts.output && opts.force) {
|
|
38
|
+
// Re-exporting into an existing file keeps its scenarios section:
|
|
39
|
+
// those entries point at local test files the export cannot know.
|
|
40
|
+
// Only a missing file is "nothing to keep"; a broken or unreadable
|
|
41
|
+
// one stops the export before it overwrites the scenarios inside.
|
|
42
|
+
const existing = await readJson(opts.output).catch((error) => {
|
|
43
|
+
if (error.code === 'ENOENT') {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
throw error instanceof CliError
|
|
47
|
+
? error
|
|
48
|
+
: new CliError(t.configInvalid(opts.output, String(error)), ExitCode.Usage);
|
|
49
|
+
});
|
|
50
|
+
// Scenarios of another project must not move into this one.
|
|
51
|
+
if (existing &&
|
|
52
|
+
existing.scenarios !== undefined &&
|
|
53
|
+
existing.project_id !== opts.project) {
|
|
54
|
+
throw new CliError(t.exportOtherProject(opts.output, Number(existing.project_id)), ExitCode.Usage);
|
|
55
|
+
}
|
|
56
|
+
if (existing)
|
|
57
|
+
document = { ...existing, ...document };
|
|
58
|
+
}
|
|
59
|
+
const text = `${JSON.stringify(document, null, 2)}\n`;
|
|
60
|
+
if (opts.output) {
|
|
61
|
+
await writeOwnFile(opts.output, text, !!opts.force);
|
|
62
|
+
if (globalOptions(command).json) {
|
|
63
|
+
printJson({ file: opts.output, servers: result.entries.length });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
printErr(t.fileSaved(opts.output));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
process.stdout.write(text);
|
|
71
|
+
}
|
|
72
|
+
if (result.headerVariables.length) {
|
|
73
|
+
printErr(t.exportHeaderVariables(result.headerVariables.join(', ')));
|
|
74
|
+
}
|
|
75
|
+
if (result.withSecrets.length) {
|
|
76
|
+
printErr(t.exportSecretsOmitted(result.withSecrets.map((s) => s.name ?? `#${s.id}`).join(', ')));
|
|
77
|
+
}
|
|
78
|
+
if (result.pausedCount)
|
|
79
|
+
printErr(t.exportPaused(result.pausedCount));
|
|
80
|
+
if (result.scenarioCount) {
|
|
81
|
+
printErr(t.exportScenarios(result.scenarioCount));
|
|
82
|
+
}
|
|
83
|
+
printErr(t.exportReview);
|
|
84
|
+
});
|
|
85
|
+
program
|
|
86
|
+
.command('diff')
|
|
87
|
+
.description(t.cmdDiff)
|
|
88
|
+
.option('-c, --config <path>', t.optConfig, DEFAULT_CONFIG)
|
|
89
|
+
.action(async (opts, command) => {
|
|
90
|
+
const { client } = await authedClient(command);
|
|
91
|
+
const plan = await buildFullPlan(client, opts.config);
|
|
92
|
+
if (globalOptions(command).json)
|
|
93
|
+
printJson(summarize(plan, true, []));
|
|
94
|
+
else {
|
|
95
|
+
printPlan(plan);
|
|
96
|
+
printLine(t.deployDryRunDone);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
program
|
|
100
|
+
.command('apply')
|
|
101
|
+
.description(t.cmdApply)
|
|
102
|
+
.option('-c, --config <path>', t.optConfig, DEFAULT_CONFIG)
|
|
103
|
+
.action(async (opts, command) => {
|
|
104
|
+
const { client } = await authedClient(command);
|
|
105
|
+
const plan = await buildFullPlan(client, opts.config);
|
|
106
|
+
const json = globalOptions(command).json;
|
|
107
|
+
if (!json)
|
|
108
|
+
printPlan(plan);
|
|
109
|
+
const { applied, failure } = await execute(client, plan, !!json);
|
|
110
|
+
// With --json the run always ends with one JSON document, including
|
|
111
|
+
// what was applied before a failure.
|
|
112
|
+
if (json) {
|
|
113
|
+
printJson({
|
|
114
|
+
...summarize(plan, false, applied),
|
|
115
|
+
error: failure ?? null,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
else if (!applied.length && !failure) {
|
|
119
|
+
printLine(t.deployNothing);
|
|
120
|
+
}
|
|
121
|
+
if (failure)
|
|
122
|
+
throw new CliError(failure.message, failure.exitCode);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async function buildFullPlan(client, path) {
|
|
126
|
+
const raw = await readJson(path).catch((error) => {
|
|
127
|
+
if (error instanceof CliError)
|
|
128
|
+
throw error;
|
|
129
|
+
throw new CliError(t.configNotFound(path), ExitCode.Usage);
|
|
130
|
+
});
|
|
131
|
+
const fail = (problem) => {
|
|
132
|
+
throw new CliError(t.configInvalid(path, problem), ExitCode.Usage);
|
|
133
|
+
};
|
|
134
|
+
if (raw.servers === undefined && raw.scenarios === undefined) {
|
|
135
|
+
fail(t.applyNothingToManage);
|
|
136
|
+
}
|
|
137
|
+
// All validation and both plans come before the first write.
|
|
138
|
+
const scenarioConfig = await loadScenarioConfig(path, process.env, {
|
|
139
|
+
required: false,
|
|
140
|
+
});
|
|
141
|
+
const desired = raw.servers === undefined
|
|
142
|
+
? []
|
|
143
|
+
: parseServersSection(raw.servers, process.env, fail);
|
|
144
|
+
const [account, current] = await Promise.all([
|
|
145
|
+
listServers(client),
|
|
146
|
+
client.call({
|
|
147
|
+
method: 'GET',
|
|
148
|
+
path: '/v1/billing/current-plan',
|
|
149
|
+
}),
|
|
150
|
+
]);
|
|
151
|
+
const scenarios = scenarioConfig.scenarios.length
|
|
152
|
+
? buildDeployPlan(scenarioConfig, account)
|
|
153
|
+
: null;
|
|
154
|
+
const scenarioCreates = scenarios?.steps.filter((s) => s.kind === 'create').length ?? 0;
|
|
155
|
+
if (scenarios) {
|
|
156
|
+
checkScenarioLimits(scenarios.steps, account.filter((s) => s.protocol === 'scenario').length, current.features);
|
|
157
|
+
}
|
|
158
|
+
const servers = buildServersPlan(desired, scenarioConfig.projectId, account, current.features, scenarioCreates);
|
|
159
|
+
return { projectId: scenarioConfig.projectId, servers, scenarios };
|
|
160
|
+
}
|
|
161
|
+
function printPlan(plan) {
|
|
162
|
+
printLine(t.deployPlanTitle(plan.projectId));
|
|
163
|
+
for (const step of plan.servers.steps) {
|
|
164
|
+
const name = step.entry.name ?? step.entry.at;
|
|
165
|
+
if (step.kind === 'create') {
|
|
166
|
+
printLine(t.deployCreate(name));
|
|
167
|
+
for (const warning of step.warnings)
|
|
168
|
+
printErr(warning);
|
|
169
|
+
}
|
|
170
|
+
else if (step.kind === 'update') {
|
|
171
|
+
printLine(t.deployUpdate(name, step.serverId, step.fields.join(', ')));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
printLine(t.deployUnchanged(name, step.serverId));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (const item of plan.servers.unmanaged) {
|
|
178
|
+
printLine(t.deployUnmanaged(item.name ?? '', item.id));
|
|
179
|
+
}
|
|
180
|
+
for (const step of plan.scenarios?.steps ?? []) {
|
|
181
|
+
printLine(describeStep(step));
|
|
182
|
+
}
|
|
183
|
+
for (const item of plan.scenarios?.unmanaged ?? []) {
|
|
184
|
+
printLine(t.deployUnmanaged(item.name ?? '', item.id));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function execute(client, plan, quiet) {
|
|
188
|
+
const applied = [];
|
|
189
|
+
const serverSteps = [
|
|
190
|
+
...plan.servers.steps.filter((s) => s.kind === 'update'),
|
|
191
|
+
...plan.servers.steps.filter((s) => s.kind === 'create'),
|
|
192
|
+
];
|
|
193
|
+
const writes = [];
|
|
194
|
+
for (const step of serverSteps) {
|
|
195
|
+
const name = step.entry.name ?? step.entry.at;
|
|
196
|
+
if (step.kind === 'update') {
|
|
197
|
+
writes.push({
|
|
198
|
+
name,
|
|
199
|
+
run: async () => {
|
|
200
|
+
await client.call({
|
|
201
|
+
method: 'PATCH',
|
|
202
|
+
path: `/v1/servers/${step.serverId}`,
|
|
203
|
+
body: step.body,
|
|
204
|
+
});
|
|
205
|
+
return { name, action: 'update', id: step.serverId };
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
else if (step.kind === 'create') {
|
|
210
|
+
writes.push({
|
|
211
|
+
name,
|
|
212
|
+
run: async () => {
|
|
213
|
+
const created = await client.call({
|
|
214
|
+
method: 'POST',
|
|
215
|
+
path: '/v1/servers',
|
|
216
|
+
body: step.body,
|
|
217
|
+
});
|
|
218
|
+
return { name, action: 'create', id: created.id };
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
for (const step of plan.scenarios?.steps ?? []) {
|
|
224
|
+
const name = step.scenario.name;
|
|
225
|
+
if (step.kind === 'update') {
|
|
226
|
+
writes.push({
|
|
227
|
+
name,
|
|
228
|
+
run: async () => {
|
|
229
|
+
await client.call({
|
|
230
|
+
method: 'PATCH',
|
|
231
|
+
path: `/v1/servers/${step.serverId}`,
|
|
232
|
+
body: step.body,
|
|
233
|
+
});
|
|
234
|
+
return { name, action: 'update', id: step.serverId };
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
else if (step.kind === 'create') {
|
|
239
|
+
writes.push({
|
|
240
|
+
name,
|
|
241
|
+
run: async () => {
|
|
242
|
+
const created = await client.call({
|
|
243
|
+
method: 'POST',
|
|
244
|
+
path: '/v1/servers',
|
|
245
|
+
body: step.body,
|
|
246
|
+
});
|
|
247
|
+
return { name, action: 'create', id: created.id };
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
for (const write of writes) {
|
|
253
|
+
try {
|
|
254
|
+
applied.push(await write.run());
|
|
255
|
+
if (!quiet)
|
|
256
|
+
printLine(t.deployApplied(write.name));
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
return { applied, failure: describeWriteFailure(write.name, error) };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return { applied };
|
|
263
|
+
}
|
|
264
|
+
function summarize(plan, dryRun, applied) {
|
|
265
|
+
return {
|
|
266
|
+
project_id: plan.projectId,
|
|
267
|
+
dry_run: dryRun,
|
|
268
|
+
// Request bodies are left out: they may carry passwords and env values.
|
|
269
|
+
servers: plan.servers.steps.map((step) => ({
|
|
270
|
+
name: step.entry.name,
|
|
271
|
+
action: step.kind,
|
|
272
|
+
id: step.kind === 'create' ? null : step.serverId,
|
|
273
|
+
fields: step.kind === 'update' ? step.fields : undefined,
|
|
274
|
+
})),
|
|
275
|
+
scenarios: (plan.scenarios?.steps ?? []).map((step) => ({
|
|
276
|
+
name: step.scenario.name,
|
|
277
|
+
action: step.kind,
|
|
278
|
+
id: step.kind === 'create' ? null : step.serverId,
|
|
279
|
+
fields: step.kind === 'update' ? step.fields : undefined,
|
|
280
|
+
})),
|
|
281
|
+
unmanaged: [
|
|
282
|
+
...plan.servers.unmanaged,
|
|
283
|
+
...(plan.scenarios?.unmanaged ?? []),
|
|
284
|
+
],
|
|
285
|
+
applied,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
async function readJson(path) {
|
|
289
|
+
const text = await readFile(path, 'utf8');
|
|
290
|
+
let parsed;
|
|
291
|
+
try {
|
|
292
|
+
parsed = JSON.parse(text);
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
throw new CliError(t.configInvalid(path, t.cfgJson(jsonErrorPosition(error))), ExitCode.Usage);
|
|
296
|
+
}
|
|
297
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
298
|
+
throw new CliError(t.configInvalid(path, t.cfgObject), ExitCode.Usage);
|
|
299
|
+
}
|
|
300
|
+
return parsed;
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=config.js.map
|