@lamalibre/create-lamaste 4.3.1 → 4.4.0
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/package.json +2 -2
- package/src/lib/service-config.js +6 -0
- package/vendor/docs/00-introduction/how-it-works.md +2 -2
- package/vendor/docs/00-introduction/quickstart.md +5 -5
- package/vendor/docs/00-introduction/what-is-lamaste.md +5 -5
- package/vendor/docs/01-concepts/certificates.md +3 -3
- package/vendor/docs/01-concepts/mtls.md +2 -2
- package/vendor/docs/01-concepts/security-model.md +38 -1
- package/vendor/docs/01-concepts/tickets.md +2 -2
- package/vendor/docs/01-concepts/tunneling.md +4 -4
- package/vendor/docs/02-guides/agent-setup.md +6 -6
- package/vendor/docs/02-guides/certificate-management.md +2 -2
- package/vendor/docs/02-guides/cloud-provisioning.md +3 -3
- package/vendor/docs/02-guides/desktop-app-setup.md +11 -11
- package/vendor/docs/02-guides/installation.md +3 -3
- package/vendor/docs/02-guides/onboarding.md +2 -2
- package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +2 -2
- package/vendor/docs/03-architecture/installer-flow.md +2 -2
- package/vendor/docs/03-architecture/installer.md +4 -4
- package/vendor/docs/03-architecture/management-flow.md +1 -1
- package/vendor/docs/03-architecture/overview.md +5 -5
- package/vendor/docs/03-architecture/panel-client.md +7 -7
- package/vendor/docs/03-architecture/panel-server.md +2 -2
- package/vendor/docs/03-architecture/state-management.md +2 -2
- package/vendor/docs/03-architecture/system-overview.md +1 -1
- package/vendor/docs/04-api-reference/agent-hosted-plugins.md +393 -33
- package/vendor/docs/04-api-reference/app-host.md +87 -38
- package/vendor/docs/04-api-reference/audit-and-notifications.md +10 -10
- package/vendor/docs/04-api-reference/certificates.md +22 -19
- package/vendor/docs/04-api-reference/lifecycle-hooks.md +33 -11
- package/vendor/docs/04-api-reference/onboarding.md +1 -1
- package/vendor/docs/04-api-reference/overview.md +137 -135
- package/vendor/docs/04-api-reference/tunnels.md +14 -10
- package/vendor/docs/05-operations/backup-and-restore.md +2 -1
- package/vendor/docs/05-operations/uninstalling.md +1 -1
- package/vendor/docs/05-operations/upgrades.md +6 -6
- package/vendor/docs/06-reference/config-files.md +89 -84
- package/vendor/docs/06-reference/glossary.md +3 -3
- package/vendor/docs/06-reference/installer-flags.md +13 -13
- package/vendor/docs/06-reference/ports-and-services.md +1 -1
- package/vendor/docs/06-reference/troubleshooting.md +8 -8
- package/vendor/docs/gatekeeper-design.md +8 -8
- package/vendor/docs/identity-directory-design.md +2 -2
- package/vendor/docs/package.json +1 -1
- package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
- package/vendor/gatekeeper/package.json +2 -2
- package/vendor/server/bin/lamaste-server.js +4 -1
- package/vendor/server/package.json +4 -3
- package/vendor/server/src/commands/plugin-settings.js +541 -0
- package/vendor/server/src/commands/plugins.js +94 -27
- package/vendor/server/src/panel-api.js +36 -4
- package/vendor/server/src/plugin-approval.js +78 -0
- package/vendor/server/src/plugin-settings.js +699 -0
- package/vendor/server-ui/dist/assets/index-25wo_8av.css +1 -0
- package/vendor/server-ui/dist/assets/index-CESyyK8V.js +84 -0
- package/vendor/server-ui/dist/index.html +2 -2
- package/vendor/serverd/package.json +2 -2
- package/vendor/serverd/src/index.js +9 -0
- package/vendor/serverd/src/lib/agent-presence.js +106 -0
- package/vendor/serverd/src/lib/hook-pipelines.js +20 -4
- package/vendor/serverd/src/lib/migrations/state/0012_plugin_settings_and_presence.sql +62 -0
- package/vendor/serverd/src/lib/mtls.js +44 -4
- package/vendor/serverd/src/lib/plugin-hooks.js +120 -20
- package/vendor/serverd/src/lib/plugin-hosting.js +35 -14
- package/vendor/serverd/src/lib/plugin-lifecycle.js +267 -83
- package/vendor/serverd/src/lib/plugin-settings.js +1101 -0
- package/vendor/serverd/src/lib/plugins.js +73 -22
- package/vendor/serverd/src/lib/tunnel-hooks.js +11 -0
- package/vendor/serverd/src/routes/management/plugins.js +166 -19
- package/vendor/serverd/src/routes/management/tunnels.js +21 -1
- package/vendor/server-ui/dist/assets/index-BTlBt_cD.js +0 -84
- package/vendor/server-ui/dist/assets/index-Bc5NCMSv.css +0 -1
|
@@ -27,8 +27,9 @@
|
|
|
27
27
|
* hooks <name> The plugin's lifecycle hooks and what needs approval: the relay
|
|
28
28
|
* hook set (stages, routes, fail-open hooks, effects), the agent
|
|
29
29
|
* half Lamaste installs, the approval digest; agent hooks
|
|
30
|
-
* approve-hooks <name> --digest <sha256>
|
|
31
|
-
* Approve with the approval digest `hooks` shows
|
|
30
|
+
* approve-hooks <name> --digest <sha256> [--version <v>]
|
|
31
|
+
* Approve with the approval digest `hooks` shows; a pending update with
|
|
32
|
+
* its version too (`--version` alone when it has no digest)
|
|
32
33
|
* auto-approve <name> on|off
|
|
33
34
|
* Approve what this plugin's later versions bring automatically (audited)
|
|
34
35
|
* update <name> [--version <v>]
|
|
@@ -45,6 +46,12 @@
|
|
|
45
46
|
* followed to its end
|
|
46
47
|
* forget-data <name> --agent <label>
|
|
47
48
|
* Drop only the relay's record of it; the data stays on the agent
|
|
49
|
+
* settings <name> An agent-hosted plugin's settings: each field's value (a secret
|
|
50
|
+
* only as set or not), whether it still needs configuration,
|
|
51
|
+
* where the values come from, the hosting
|
|
52
|
+
* configure <name> [--set key=value]... [--clear key]... [--secrets-stdin]
|
|
53
|
+
* Change them (kept by its agent half); in a terminal without
|
|
54
|
+
* flags, field by field — a secret never on the command line
|
|
48
55
|
*
|
|
49
56
|
* Every subcommand goes through the running panel (`/api/plugins/*`, admin
|
|
50
57
|
* certificate): the panel owns the plugin registry (SQLite `state.db`), runs
|
|
@@ -61,6 +68,14 @@ import { AGENT_LABEL_REGEX } from '@lamalibre/lamaste';
|
|
|
61
68
|
import { PANEL_SERVICE } from '../config.js';
|
|
62
69
|
import { panelRequest } from '../panel-api.js';
|
|
63
70
|
import { emit, emitStep, emitError, emitComplete, emitLog } from '../ndjson.js';
|
|
71
|
+
import { configureCmd, settingsCmd } from './plugin-settings.js';
|
|
72
|
+
import { settingsDiffLines, terminalText } from '../plugin-settings.js';
|
|
73
|
+
import {
|
|
74
|
+
APPROVE_HOOKS_USAGE,
|
|
75
|
+
approveHooksCommand,
|
|
76
|
+
parseApproveArgs,
|
|
77
|
+
pendingUpdateReasons,
|
|
78
|
+
} from '../plugin-approval.js';
|
|
64
79
|
|
|
65
80
|
/** npm runs inside the panel for install and uninstall (its own limit is 120 s). */
|
|
66
81
|
const NPM_CALL_TIMEOUT_MS = 180_000;
|
|
@@ -115,6 +130,10 @@ export async function runPlugins(args, { json }) {
|
|
|
115
130
|
return purgeDataCmd(args.slice(1), { json });
|
|
116
131
|
case 'forget-data':
|
|
117
132
|
return forgetDataCmd(args.slice(1), { json });
|
|
133
|
+
case 'settings':
|
|
134
|
+
return settingsCmd(args.slice(1), { json });
|
|
135
|
+
case 'configure':
|
|
136
|
+
return configureCmd(args.slice(1), { json });
|
|
118
137
|
default:
|
|
119
138
|
printPluginUsage();
|
|
120
139
|
process.exit(sub ? 1 : 0);
|
|
@@ -148,8 +167,9 @@ ${b('Subcommands:')}
|
|
|
148
167
|
${c('rotate-hosting-secret <name>')}
|
|
149
168
|
New installation secret (the agent fetches it on its next sync)
|
|
150
169
|
${c('hooks <name>')} Lifecycle hooks and what needs approval: relay hooks, agent half
|
|
151
|
-
${c('approve-hooks <name> --digest <sha256>')}
|
|
152
|
-
Approve with the approval digest "hooks" showed
|
|
170
|
+
${c('approve-hooks <name> --digest <sha256> [--version <v>]')}
|
|
171
|
+
Approve with the approval digest "hooks" showed; a pending update
|
|
172
|
+
with its version too (--version alone when it has no digest)
|
|
153
173
|
${c('auto-approve <name> on|off')}
|
|
154
174
|
Approve what this plugin's later versions bring automatically
|
|
155
175
|
${c('update <name> [--version <v>]')}
|
|
@@ -164,6 +184,13 @@ ${b('Subcommands:')}
|
|
|
164
184
|
Delete it on the agent — its data directory and account (a push)
|
|
165
185
|
${c('forget-data <name> --agent <label>')}
|
|
166
186
|
Drop only the relay's record; the data stays on the agent
|
|
187
|
+
${c('settings <name>')} Settings of an agent-hosted plugin: values (secrets only as set or
|
|
188
|
+
not), what is missing, where they come from, the hosting
|
|
189
|
+
${c('configure <name> [--set key=value]... [--clear key]... [--secrets-stdin]')}
|
|
190
|
+
Change them: --set parses by the field's type (never a secret),
|
|
191
|
+
--clear puts a field back to its default or clears a secret,
|
|
192
|
+
--secrets-stdin reads {"key": "value" | null} from stdin;
|
|
193
|
+
in a terminal without flags, asked field by field
|
|
167
194
|
`);
|
|
168
195
|
}
|
|
169
196
|
|
|
@@ -732,11 +759,14 @@ function printAgentHalf(agentHalf, title) {
|
|
|
732
759
|
|
|
733
760
|
/**
|
|
734
761
|
* Print what a version brings that needs approval — the approval digest
|
|
735
|
-
* (
|
|
736
|
-
* half Lamaste installs
|
|
762
|
+
* (what `approve-hooks` takes; none for an update that brings neither
|
|
763
|
+
* relay hooks nor an agent half Lamaste installs), its relay hook set and
|
|
764
|
+
* the agent half Lamaste installs.
|
|
737
765
|
*/
|
|
738
766
|
function printApprovalContents({ digest, relay, agentHalf }) {
|
|
739
|
-
console.log(
|
|
767
|
+
console.log(
|
|
768
|
+
` approval digest: ${digest ? chalk.cyan(digest) : chalk.dim('none — no relay hooks, no agent half Lamaste installs')}`,
|
|
769
|
+
);
|
|
740
770
|
if (relay) {
|
|
741
771
|
console.log('');
|
|
742
772
|
printRelayHookSet(relay, 'Relay hooks — run in a sandbox on the relay');
|
|
@@ -747,9 +777,43 @@ function printApprovalContents({ digest, relay, agentHalf }) {
|
|
|
747
777
|
}
|
|
748
778
|
}
|
|
749
779
|
|
|
750
|
-
/**
|
|
751
|
-
|
|
752
|
-
|
|
780
|
+
/**
|
|
781
|
+
* Print an update that waits for approval under `title`: why it waits
|
|
782
|
+
* (`approvalRequired` — a secret turned plain in red), what it brings
|
|
783
|
+
* (its digest, relay hook set and agent half), how it changes the settings
|
|
784
|
+
* schema (outside the digest) — fields added and removed with their types,
|
|
785
|
+
* retyped ones, newly required ones, and a warning for secrets that become
|
|
786
|
+
* plain values — what auto-approve could not cover beyond the reasons, and
|
|
787
|
+
* the command that approves it.
|
|
788
|
+
*/
|
|
789
|
+
function printPendingUpdate(name, update, title) {
|
|
790
|
+
console.log('');
|
|
791
|
+
console.log(chalk.bold(` ${title}`));
|
|
792
|
+
const { required, notCovered } = pendingUpdateReasons(update);
|
|
793
|
+
for (const line of required) {
|
|
794
|
+
console.log(line.warning ? chalk.red(` - ${line.text}`) : ` - ${line.text}`);
|
|
795
|
+
}
|
|
796
|
+
console.log('');
|
|
797
|
+
printApprovalContents(update);
|
|
798
|
+
const lines = settingsDiffLines(update.settings);
|
|
799
|
+
if (lines.length > 0) {
|
|
800
|
+
console.log('');
|
|
801
|
+
console.log(chalk.bold(' Settings — what this version changes (not part of the digest)'));
|
|
802
|
+
for (const line of lines) {
|
|
803
|
+
console.log(` ${line.warning ? chalk.red(line.text) : line.text}`);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (notCovered.length > 0) {
|
|
807
|
+
console.log('');
|
|
808
|
+
console.log(chalk.bold(' Auto-approve does not cover it'));
|
|
809
|
+
for (const reason of notCovered) console.log(chalk.yellow(` ${reason}`));
|
|
810
|
+
}
|
|
811
|
+
console.log(`\n Approve and update with: ${approveCommand(name, update)}`);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/** The command that approves the installed version (`{ digest }`) or a pending update (`{ digest, version }`). */
|
|
815
|
+
function approveCommand(name, target) {
|
|
816
|
+
return chalk.cyan(approveHooksCommand(name, target));
|
|
753
817
|
}
|
|
754
818
|
|
|
755
819
|
/**
|
|
@@ -794,7 +858,7 @@ async function hooksCmd(name, { json }) {
|
|
|
794
858
|
`\n auto-approve for later versions: ${hooks.autoApprove ? chalk.yellow('on') : 'off'}`,
|
|
795
859
|
);
|
|
796
860
|
if (approval.state !== 'approved') {
|
|
797
|
-
console.log(`\n Approve with: ${approveCommand(name, approval.digest)}`);
|
|
861
|
+
console.log(`\n Approve with: ${approveCommand(name, { digest: approval.digest })}`);
|
|
798
862
|
}
|
|
799
863
|
}
|
|
800
864
|
if (hooks.agent) {
|
|
@@ -805,10 +869,11 @@ async function hooksCmd(name, { json }) {
|
|
|
805
869
|
}
|
|
806
870
|
if (hooks.pendingUpdate) {
|
|
807
871
|
const update = hooks.pendingUpdate;
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
872
|
+
printPendingUpdate(
|
|
873
|
+
name,
|
|
874
|
+
update,
|
|
875
|
+
`Update to ${terminalText(update.version)} — waits for your approval:`,
|
|
876
|
+
);
|
|
812
877
|
}
|
|
813
878
|
console.log('');
|
|
814
879
|
}
|
|
@@ -819,18 +884,16 @@ async function hooksCmd(name, { json }) {
|
|
|
819
884
|
* @param {{ json: boolean }} options
|
|
820
885
|
*/
|
|
821
886
|
async function approveHooksCmd(name, rest, { json }) {
|
|
822
|
-
|
|
823
|
-
const
|
|
824
|
-
if (
|
|
825
|
-
|
|
826
|
-
'Usage: lamaste-server plugins approve-hooks <name> --digest <sha256> (the approval digest `hooks <name>` shows)',
|
|
827
|
-
json,
|
|
828
|
-
);
|
|
829
|
-
}
|
|
887
|
+
if (!name) fail(APPROVE_HOOKS_USAGE, json);
|
|
888
|
+
const args = parseApproveArgs(rest);
|
|
889
|
+
if (args.error) fail(args.error, json);
|
|
890
|
+
const { digest, version } = args;
|
|
830
891
|
let result;
|
|
831
892
|
try {
|
|
893
|
+
// A version names the pending update; its digest, when it has one, goes with it.
|
|
832
894
|
result = await panelRequest('POST', `/api/plugins/${encodeURIComponent(name)}/hooks/approve`, {
|
|
833
895
|
digest,
|
|
896
|
+
...(version ? { version } : {}),
|
|
834
897
|
});
|
|
835
898
|
} catch (err) {
|
|
836
899
|
// Approving a pending update starts it: its agent half is a push.
|
|
@@ -899,10 +962,12 @@ async function updateCmd(name, rest, { json }) {
|
|
|
899
962
|
return;
|
|
900
963
|
}
|
|
901
964
|
const update = result.pendingUpdate;
|
|
965
|
+
printPendingUpdate(
|
|
966
|
+
name,
|
|
967
|
+
update,
|
|
968
|
+
`${name} ${terminalText(update.version)} waits for your approval:`,
|
|
969
|
+
);
|
|
902
970
|
console.log('');
|
|
903
|
-
console.log(chalk.bold(` ${name} ${update.version} waits for approval`));
|
|
904
|
-
printApprovalContents(update);
|
|
905
|
-
console.log(`\n Approve and update with: ${approveCommand(name, update.digest)}\n`);
|
|
906
971
|
return;
|
|
907
972
|
}
|
|
908
973
|
if (!json) console.log(`\n Updating ${chalk.cyan(name)}`);
|
|
@@ -1111,7 +1176,9 @@ async function installPluginCmd(packageName, { json }) {
|
|
|
1111
1176
|
agentHalf: result.hooks.agentHalf,
|
|
1112
1177
|
});
|
|
1113
1178
|
if (approval.state === 'pending') {
|
|
1114
|
-
console.log(
|
|
1179
|
+
console.log(
|
|
1180
|
+
`\n Approve with: ${approveCommand(plugin.name, { digest: approval.digest })}`,
|
|
1181
|
+
);
|
|
1115
1182
|
}
|
|
1116
1183
|
console.log('');
|
|
1117
1184
|
}
|
|
@@ -22,6 +22,7 @@ import { mkdtemp, open, readFile, rm } from 'node:fs/promises';
|
|
|
22
22
|
import { tmpdir } from 'node:os';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
24
|
import { execa } from 'execa';
|
|
25
|
+
import { SETTINGS_KEY_RE } from '@lamalibre/lamaste';
|
|
25
26
|
import { PKI_DIR, readConfig } from './config.js';
|
|
26
27
|
import { PANEL_LOOPBACK, assertPanelListener, readPanelIdentity } from './panel-identity.js';
|
|
27
28
|
|
|
@@ -244,12 +245,31 @@ function detailsText(details) {
|
|
|
244
245
|
return [...new Set(found)].join('; ');
|
|
245
246
|
}
|
|
246
247
|
|
|
248
|
+
/**
|
|
249
|
+
* The per-field messages of a refused settings change (`errors`: a setting
|
|
250
|
+
* key, or `_` for the change as a whole, to a message), or null when the
|
|
251
|
+
* panel sent none. Only string messages under a key a setting can have are
|
|
252
|
+
* kept — the panel's and the agent half's messages never contain a value.
|
|
253
|
+
*
|
|
254
|
+
* @param {unknown} errors
|
|
255
|
+
* @returns {Record<string, string> | null}
|
|
256
|
+
*/
|
|
257
|
+
function settingsErrors(errors) {
|
|
258
|
+
if (errors === null || typeof errors !== 'object' || Array.isArray(errors)) return null;
|
|
259
|
+
const kept = Object.entries(errors).filter(
|
|
260
|
+
([key, message]) => (key === '_' || SETTINGS_KEY_RE.test(key)) && typeof message === 'string',
|
|
261
|
+
);
|
|
262
|
+
return kept.length > 0 ? Object.fromEntries(kept) : null;
|
|
263
|
+
}
|
|
264
|
+
|
|
247
265
|
/**
|
|
248
266
|
* Split curl's output (`<payload>\n<status>`) and turn a non-2xx status
|
|
249
267
|
* into an error carrying the panel's `error` (and `details`) in its message,
|
|
250
268
|
* the status as `statusCode`, and what a caller acts on beyond the message
|
|
251
|
-
* when the panel sent it: `code` (e.g. `agent_not_eligible`), `reasons
|
|
252
|
-
*
|
|
269
|
+
* when the panel sent it: `code` (e.g. `agent_not_eligible`), `reasons`, a
|
|
270
|
+
* running operation's `operationId`, and for a refused settings change its
|
|
271
|
+
* per-field `errors`, the current `revision` (`revision_mismatch`) and the
|
|
272
|
+
* `hosting` (`hosting_offline`). Pure, exported for tests.
|
|
253
273
|
*
|
|
254
274
|
* @param {string} stdout
|
|
255
275
|
* @param {string} method
|
|
@@ -282,6 +302,17 @@ export function parsePanelResponse(stdout, method, path) {
|
|
|
282
302
|
err.reasons = parsed.reasons.filter((r) => typeof r === 'string');
|
|
283
303
|
}
|
|
284
304
|
if (typeof parsed.operationId === 'string') err.operationId = parsed.operationId;
|
|
305
|
+
const errors = settingsErrors(parsed.errors);
|
|
306
|
+
if (errors) err.errors = errors;
|
|
307
|
+
if (Number.isSafeInteger(parsed.revision) && parsed.revision >= 0) {
|
|
308
|
+
err.revision = parsed.revision;
|
|
309
|
+
}
|
|
310
|
+
if (
|
|
311
|
+
parsed.hosting === null ||
|
|
312
|
+
(typeof parsed.hosting === 'object' && !Array.isArray(parsed.hosting))
|
|
313
|
+
) {
|
|
314
|
+
err.hosting = parsed.hosting;
|
|
315
|
+
}
|
|
285
316
|
throw err;
|
|
286
317
|
}
|
|
287
318
|
|
|
@@ -337,8 +368,9 @@ export async function curlPanelRequest({
|
|
|
337
368
|
* client certificate (mTLS); neither the P12 password nor the body is ever
|
|
338
369
|
* in curl's arguments. Resolves with the JSON body; rejects with an Error
|
|
339
370
|
* (`statusCode`, the panel's `error` in the message, a refusal's `code`,
|
|
340
|
-
* `reasons` and `operationId
|
|
341
|
-
*
|
|
371
|
+
* `reasons` and `operationId`, a refused settings change's `errors`,
|
|
372
|
+
* `revision` and `hosting`) for any non-2xx answer, and with one that says
|
|
373
|
+
* why when the request could not be made.
|
|
342
374
|
*
|
|
343
375
|
* @param {string} method
|
|
344
376
|
* @param {string} path e.g. '/api/tunnels'
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `lamaste-server plugins hooks|update|approve-hooks`: why an update waits
|
|
3
|
+
* for approval, the command that approves it, and the arguments that
|
|
4
|
+
* command takes. Pure — no output — so they are tested as they are.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { terminalText } from './plugin-settings.js';
|
|
8
|
+
|
|
9
|
+
const DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
10
|
+
const VERSION_RE = /^[0-9A-Za-z.+-]{1,64}$/;
|
|
11
|
+
/** The reason an update turns secret settings into plain ones. */
|
|
12
|
+
const SETTINGS_SECRET_TO_PLAIN = 'settings.secret-to-plain';
|
|
13
|
+
|
|
14
|
+
/** How `approve-hooks` is called. */
|
|
15
|
+
export const APPROVE_HOOKS_USAGE =
|
|
16
|
+
'Usage: lamaste-server plugins approve-hooks <name> --digest <sha256> [--version <v>] (what `hooks <name>` shows)';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Why a pending update waits, as the terminal shows it: each reason of
|
|
20
|
+
* `approvalRequired` (`[{ code, reason }]`; a secret turned plain is a
|
|
21
|
+
* warning), and the sentences of `autoApproveWithheld` that do not repeat
|
|
22
|
+
* one of them — what auto-approve, when on, could not cover beyond that.
|
|
23
|
+
* Every text as {@link terminalText}.
|
|
24
|
+
*
|
|
25
|
+
* @param {{ approvalRequired?: unknown, autoApproveWithheld?: unknown }} update
|
|
26
|
+
* @returns {{ required: Array<{ text: string, warning: boolean }>, notCovered: string[] }}
|
|
27
|
+
*/
|
|
28
|
+
export function pendingUpdateReasons(update) {
|
|
29
|
+
const required = (Array.isArray(update?.approvalRequired) ? update.approvalRequired : [])
|
|
30
|
+
.filter((entry) => entry !== null && typeof entry === 'object')
|
|
31
|
+
.map((entry) => ({
|
|
32
|
+
text: terminalText(entry.reason),
|
|
33
|
+
warning: entry.code === SETTINGS_SECRET_TO_PLAIN,
|
|
34
|
+
}));
|
|
35
|
+
const said = new Set(required.map((line) => line.text));
|
|
36
|
+
const notCovered = (Array.isArray(update?.autoApproveWithheld) ? update.autoApproveWithheld : [])
|
|
37
|
+
.map((reason) => terminalText(reason))
|
|
38
|
+
.filter((text) => !said.has(text));
|
|
39
|
+
return { required, notCovered };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The command that approves what plugin `name` brings: the installed
|
|
44
|
+
* version by its digest (`{ digest }`), or a pending update by its version
|
|
45
|
+
* and — when it has one — its digest (`{ digest, version }`).
|
|
46
|
+
*
|
|
47
|
+
* @param {string} name
|
|
48
|
+
* @param {{ digest?: string | null, version?: string | null }} target
|
|
49
|
+
*/
|
|
50
|
+
export function approveHooksCommand(name, { digest = null, version = null }) {
|
|
51
|
+
return [
|
|
52
|
+
`lamaste-server plugins approve-hooks ${terminalText(name)}`,
|
|
53
|
+
...(digest ? [`--digest ${terminalText(digest)}`] : []),
|
|
54
|
+
...(version ? [`--version ${terminalText(version)}`] : []),
|
|
55
|
+
].join(' ');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The arguments of `approve-hooks <name>` after the name: `--digest
|
|
60
|
+
* <sha256>` (the installed version's, or a pending update's) and
|
|
61
|
+
* `--version <v>` (a pending update's) — at least one; `--version` alone
|
|
62
|
+
* approves an update that brings neither relay hooks nor an agent half.
|
|
63
|
+
*
|
|
64
|
+
* @param {string[]} rest
|
|
65
|
+
* @returns {{ digest: string | null, version: string | null } | { error: string }}
|
|
66
|
+
*/
|
|
67
|
+
export function parseApproveArgs(rest) {
|
|
68
|
+
const valueOf = (flag) => {
|
|
69
|
+
const idx = rest.indexOf(flag);
|
|
70
|
+
return idx === -1 ? null : (rest[idx + 1] ?? '');
|
|
71
|
+
};
|
|
72
|
+
const digest = valueOf('--digest');
|
|
73
|
+
const version = valueOf('--version');
|
|
74
|
+
if (digest === null && version === null) return { error: APPROVE_HOOKS_USAGE };
|
|
75
|
+
if (digest !== null && !DIGEST_RE.test(digest)) return { error: APPROVE_HOOKS_USAGE };
|
|
76
|
+
if (version !== null && !VERSION_RE.test(version)) return { error: APPROVE_HOOKS_USAGE };
|
|
77
|
+
return { digest, version };
|
|
78
|
+
}
|