@lizard-build/cli 0.1.0 → 0.3.30
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/.github/workflows/release.yml +90 -0
- package/AGENTS.md +113 -0
- package/README.md +41 -0
- package/dist/commands/add.js +318 -45
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +68 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/docs.d.ts +2 -0
- package/dist/commands/docs.js +13 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/commands/domain.d.ts +9 -0
- package/dist/commands/domain.js +195 -0
- package/dist/commands/domain.js.map +1 -0
- package/dist/commands/git.js +175 -36
- package/dist/commands/git.js.map +1 -1
- package/dist/commands/init.d.ts +24 -0
- package/dist/commands/init.js +128 -86
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/link.d.ts +7 -0
- package/dist/commands/link.js +104 -33
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/login.js +4 -3
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logs.js +223 -30
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/open.js +3 -2
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/port.d.ts +7 -0
- package/dist/commands/port.js +49 -0
- package/dist/commands/port.js.map +1 -0
- package/dist/commands/projects.js +36 -6
- package/dist/commands/projects.js.map +1 -1
- package/dist/commands/ps.js +32 -39
- package/dist/commands/ps.js.map +1 -1
- package/dist/commands/redeploy.js +48 -8
- package/dist/commands/redeploy.js.map +1 -1
- package/dist/commands/regions.js +2 -5
- package/dist/commands/regions.js.map +1 -1
- package/dist/commands/restart.js +84 -10
- package/dist/commands/restart.js.map +1 -1
- package/dist/commands/run.d.ts +9 -0
- package/dist/commands/run.js +61 -22
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/scale.d.ts +10 -0
- package/dist/commands/scale.js +166 -0
- package/dist/commands/scale.js.map +1 -0
- package/dist/commands/secrets.js +200 -89
- package/dist/commands/secrets.js.map +1 -1
- package/dist/commands/service-set.d.ts +49 -0
- package/dist/commands/service-set.js +552 -0
- package/dist/commands/service-set.js.map +1 -0
- package/dist/commands/service-show.d.ts +11 -0
- package/dist/commands/service-show.js +44 -0
- package/dist/commands/service-show.js.map +1 -0
- package/dist/commands/service.d.ts +8 -0
- package/dist/commands/service.js +262 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/commands/skill.d.ts +2 -0
- package/dist/commands/skill.js +146 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/commands/ssh.d.ts +2 -0
- package/dist/commands/ssh.js +161 -0
- package/dist/commands/ssh.js.map +1 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +49 -38
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/unlink.d.ts +5 -0
- package/dist/commands/unlink.js +18 -0
- package/dist/commands/unlink.js.map +1 -0
- package/dist/commands/up.d.ts +9 -0
- package/dist/commands/up.js +417 -0
- package/dist/commands/up.js.map +1 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +79 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/whoami.js +26 -6
- package/dist/commands/whoami.js.map +1 -1
- package/dist/commands/workspace.d.ts +8 -0
- package/dist/commands/workspace.js +36 -0
- package/dist/commands/workspace.js.map +1 -0
- package/dist/index.js +209 -82
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +17 -2
- package/dist/lib/api.js +85 -51
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/auth.d.ts +3 -11
- package/dist/lib/auth.js +16 -36
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/config.d.ts +36 -15
- package/dist/lib/config.js +71 -58
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/format.d.ts +1 -0
- package/dist/lib/format.js +17 -4
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/name.d.ts +11 -0
- package/dist/lib/name.js +26 -0
- package/dist/lib/name.js.map +1 -0
- package/dist/lib/picker.d.ts +32 -0
- package/dist/lib/picker.js +91 -0
- package/dist/lib/picker.js.map +1 -0
- package/dist/lib/resolve.d.ts +85 -0
- package/dist/lib/resolve.js +203 -0
- package/dist/lib/resolve.js.map +1 -0
- package/dist/lib/updater.d.ts +16 -0
- package/dist/lib/updater.js +102 -0
- package/dist/lib/updater.js.map +1 -0
- package/lizard-wrapper.sh +2 -0
- package/package.json +11 -3
- package/skill-data/core/SKILL.md +239 -0
- package/src/commands/add.ts +388 -56
- package/src/commands/config.ts +80 -0
- package/src/commands/docs.ts +15 -0
- package/src/commands/domain.ts +248 -0
- package/src/commands/git.ts +201 -40
- package/src/commands/init.ts +149 -100
- package/src/commands/link.ts +127 -35
- package/src/commands/login.ts +4 -3
- package/src/commands/logs.ts +283 -27
- package/src/commands/open.ts +3 -2
- package/src/commands/port.ts +57 -0
- package/src/commands/projects.ts +43 -6
- package/src/commands/ps.ts +39 -60
- package/src/commands/redeploy.ts +51 -10
- package/src/commands/regions.ts +2 -6
- package/src/commands/restart.ts +84 -10
- package/src/commands/run.ts +68 -24
- package/src/commands/scale.ts +216 -0
- package/src/commands/secrets.ts +277 -100
- package/src/commands/service-set.ts +669 -0
- package/src/commands/service-show.ts +52 -0
- package/src/commands/service.ts +298 -0
- package/src/commands/skill.ts +157 -0
- package/src/commands/ssh.ts +176 -0
- package/src/commands/status.ts +51 -46
- package/src/commands/unlink.ts +17 -0
- package/src/commands/up.ts +461 -0
- package/src/commands/upgrade.ts +87 -0
- package/src/commands/whoami.ts +34 -6
- package/src/commands/workspace.ts +44 -0
- package/src/index.ts +219 -85
- package/src/lib/api.ts +114 -51
- package/src/lib/auth.ts +22 -46
- package/src/lib/config.ts +100 -65
- package/src/lib/format.ts +18 -4
- package/src/lib/name.ts +27 -0
- package/src/lib/picker.ts +133 -0
- package/src/lib/resolve.ts +285 -0
- package/src/lib/updater.ts +106 -0
- package/test/cli.test.ts +491 -0
- package/test/fixtures/hello-app/Dockerfile +5 -0
- package/test/fixtures/hello-app/index.js +5 -0
- package/test/unit/api.test.ts +66 -0
- package/test/unit/config.test.ts +94 -0
- package/test/unit/init.test.ts +211 -0
- package/test/unit/json.test.ts +208 -0
- package/test/unit/picker.test.ts +161 -0
- package/test/unit/resolve.test.ts +124 -0
- package/test/unit/service-set.test.ts +355 -0
- package/vitest.config.ts +10 -0
- package/dist/commands/connect.d.ts +0 -2
- package/dist/commands/connect.js +0 -117
- package/dist/commands/connect.js.map +0 -1
- package/dist/commands/context.d.ts +0 -2
- package/dist/commands/context.js +0 -71
- package/dist/commands/context.js.map +0 -1
- package/dist/commands/deploy.d.ts +0 -2
- package/dist/commands/deploy.js +0 -120
- package/dist/commands/deploy.js.map +0 -1
- package/dist/commands/destroy.d.ts +0 -2
- package/dist/commands/destroy.js +0 -51
- package/dist/commands/destroy.js.map +0 -1
- package/dist/commands/update.d.ts +0 -2
- package/dist/commands/update.js +0 -41
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/version.d.ts +0 -2
- package/dist/commands/version.js +0 -37
- package/dist/commands/version.js.map +0 -1
- package/src/commands/connect.ts +0 -145
- package/src/commands/context.ts +0 -93
- package/src/commands/deploy.ts +0 -153
- package/src/commands/destroy.ts +0 -51
- package/src/commands/update.ts +0 -44
- package/src/commands/version.ts +0 -37
package/src/commands/secrets.ts
CHANGED
|
@@ -1,28 +1,132 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import { Command, Option } from "commander";
|
|
4
|
+
import { api, withScope, type ResourceScope } from "../lib/api.js";
|
|
5
|
+
import { getProjectLink } from "../lib/config.js";
|
|
6
|
+
import { getActiveService, resolveProjectScope } from "../lib/resolve.js";
|
|
7
|
+
import { success, isJSONMode, printJSON, table, isTTY } from "../lib/format.js";
|
|
6
8
|
|
|
7
9
|
interface Secret {
|
|
8
10
|
key: string;
|
|
9
11
|
value: string;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* `lizard secrets` — secret management. Defaults to the linked service
|
|
16
|
+
* scope, with --global for project-wide.
|
|
17
|
+
*
|
|
18
|
+
* Bare command without subcommand prints the secret list.
|
|
19
|
+
* `--refs` lists `${{...}}` templates available in this scope.
|
|
20
|
+
*/
|
|
21
|
+
interface Scope {
|
|
22
|
+
path: string;
|
|
23
|
+
label: "project" | "service";
|
|
24
|
+
projectId: string;
|
|
25
|
+
serviceId?: string;
|
|
26
|
+
serviceName?: string;
|
|
27
|
+
scope: ResourceScope;
|
|
28
|
+
}
|
|
16
29
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
async function resolveScope(
|
|
31
|
+
projectFlag: string | undefined,
|
|
32
|
+
serviceFlag: string | undefined,
|
|
33
|
+
global: boolean,
|
|
34
|
+
): Promise<Scope> {
|
|
35
|
+
const { projectId, scope: rs } = await resolveProjectScope(projectFlag);
|
|
36
|
+
|
|
37
|
+
if (global) {
|
|
38
|
+
return {
|
|
39
|
+
path: withScope(`/api/projects/${projectId}/secrets`, rs),
|
|
40
|
+
label: "project",
|
|
41
|
+
projectId,
|
|
42
|
+
scope: rs,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (serviceFlag) {
|
|
47
|
+
const svc = await getActiveService(serviceFlag, projectId);
|
|
48
|
+
return {
|
|
49
|
+
path: withScope(`/api/apps/${svc.id}/secrets`, rs),
|
|
50
|
+
label: "service",
|
|
51
|
+
projectId,
|
|
52
|
+
serviceId: svc.id,
|
|
53
|
+
serviceName: svc.name,
|
|
54
|
+
scope: rs,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const link = getProjectLink();
|
|
59
|
+
if (!link?.serviceId) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
"No service linked. Pass --service <name>, run `lizard service link <name>`, or use --global.",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
path: withScope(`/api/apps/${link.serviceId}/secrets`, rs),
|
|
66
|
+
label: "service",
|
|
67
|
+
projectId,
|
|
68
|
+
serviceId: link.serviceId,
|
|
69
|
+
serviceName: link.serviceName || link.serviceId,
|
|
70
|
+
scope: rs,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function configApplySecrets(
|
|
75
|
+
scope: Scope,
|
|
76
|
+
secrets: Record<string, string | null>,
|
|
77
|
+
): Promise<void> {
|
|
78
|
+
const payload =
|
|
79
|
+
scope.label === "project"
|
|
80
|
+
? { secrets: { shared: secrets } }
|
|
81
|
+
: { secrets: { services: { [scope.serviceName!]: secrets } } };
|
|
82
|
+
await api.post(
|
|
83
|
+
withScope(`/api/projects/${scope.projectId}/config:apply`, scope.scope),
|
|
84
|
+
payload,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parsePairs(pairs: string[]): Record<string, string> {
|
|
89
|
+
const out: Record<string, string> = {};
|
|
90
|
+
for (const pair of pairs) {
|
|
91
|
+
const eq = pair.indexOf("=");
|
|
92
|
+
if (eq < 1) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Invalid format: "${pair}". Use KEY=value (e.g. \`lizard secrets set DATABASE_URL=postgres://...\`).`,
|
|
25
95
|
);
|
|
96
|
+
}
|
|
97
|
+
out[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function registerSecrets(program: Command) {
|
|
103
|
+
const cmd = program
|
|
104
|
+
.command("secrets")
|
|
105
|
+
.alias("secret")
|
|
106
|
+
.description("Manage secrets (default scope: service; --global for project)")
|
|
107
|
+
.option("--global", "Target the whole project")
|
|
108
|
+
.option("--show", "Reveal values")
|
|
109
|
+
.option("--ref", "List reference templates available in this scope")
|
|
110
|
+
.addOption(new Option("--refs").hideHelp().implies({ ref: true }))
|
|
111
|
+
.option("-s, --service <name>", "Service to scope to (overrides linked)")
|
|
112
|
+
.option("-p, --project <id>", "Project to scope to")
|
|
113
|
+
.addHelpText(
|
|
114
|
+
"after",
|
|
115
|
+
`
|
|
116
|
+
Notes:
|
|
117
|
+
Secrets apply live to running VMs. Keys prefixed with NEXT_PUBLIC_* or VITE_*
|
|
118
|
+
trigger a redeploy because they're baked into the client bundle at build time.`,
|
|
119
|
+
)
|
|
120
|
+
.action(async (opts) => {
|
|
121
|
+
const scope = await resolveScope(opts.project, opts.service, opts.global);
|
|
122
|
+
|
|
123
|
+
// --ref → list reference templates exposed by the platform
|
|
124
|
+
if (opts.ref) {
|
|
125
|
+
await printRefs(scope);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const secrets = await api.get<Secret[]>(scope.path);
|
|
26
130
|
|
|
27
131
|
if (isJSONMode()) {
|
|
28
132
|
printJSON(
|
|
@@ -34,7 +138,9 @@ export function registerSecrets(program: Command) {
|
|
|
34
138
|
}
|
|
35
139
|
|
|
36
140
|
if (secrets.length === 0) {
|
|
37
|
-
console.log(
|
|
141
|
+
console.log(
|
|
142
|
+
`No ${scope.label} secrets. Use \`lizard secrets set KEY=value${opts.global ? " --global" : ""}\`.`,
|
|
143
|
+
);
|
|
38
144
|
return;
|
|
39
145
|
}
|
|
40
146
|
|
|
@@ -42,134 +148,205 @@ export function registerSecrets(program: Command) {
|
|
|
42
148
|
["Key", "Value"],
|
|
43
149
|
secrets.map((s) => [
|
|
44
150
|
s.key,
|
|
45
|
-
opts.show
|
|
151
|
+
opts.show
|
|
152
|
+
? s.value
|
|
153
|
+
: chalk.dim("•".repeat(Math.min(s.value.length, 20))),
|
|
46
154
|
]),
|
|
47
155
|
);
|
|
48
156
|
});
|
|
49
157
|
|
|
50
|
-
|
|
51
|
-
.command("
|
|
52
|
-
.
|
|
53
|
-
.
|
|
54
|
-
.option("--
|
|
55
|
-
.
|
|
56
|
-
|
|
158
|
+
cmd
|
|
159
|
+
.command("list")
|
|
160
|
+
.description("List secrets")
|
|
161
|
+
.option("--global", "Target the whole project")
|
|
162
|
+
.option("--show", "Reveal values")
|
|
163
|
+
.option("--ref", "List reference templates available in this scope")
|
|
164
|
+
.addOption(new Option("--refs").hideHelp().implies({ ref: true }))
|
|
165
|
+
.option("-s, --service <name>", "Service to scope to (overrides linked)")
|
|
166
|
+
.option("-p, --project <id>", "Project to scope to")
|
|
167
|
+
.action(async (opts, sub) => {
|
|
168
|
+
const inherited = sub.parent?.opts() || {};
|
|
169
|
+
const scope = await resolveScope(
|
|
170
|
+
opts.project ?? inherited.project,
|
|
171
|
+
opts.service ?? inherited.service,
|
|
172
|
+
opts.global || inherited.global,
|
|
173
|
+
);
|
|
57
174
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const eqIdx = pair.indexOf("=");
|
|
62
|
-
if (eqIdx < 1) {
|
|
63
|
-
throw new Error(`Invalid format: "${pair}". Use KEY=value`);
|
|
64
|
-
}
|
|
65
|
-
newSecrets[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1);
|
|
175
|
+
if (opts.ref) {
|
|
176
|
+
await printRefs(scope);
|
|
177
|
+
return;
|
|
66
178
|
}
|
|
67
179
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
merged.push({ key: s.key, value: newSecrets[s.key] });
|
|
78
|
-
existingKeys.add(s.key);
|
|
79
|
-
} else {
|
|
80
|
-
merged.push(s);
|
|
81
|
-
}
|
|
180
|
+
const secrets = await api.get<Secret[]>(scope.path);
|
|
181
|
+
|
|
182
|
+
if (isJSONMode()) {
|
|
183
|
+
printJSON(
|
|
184
|
+
opts.show
|
|
185
|
+
? secrets
|
|
186
|
+
: secrets.map((s) => ({ key: s.key, value: "***" })),
|
|
187
|
+
);
|
|
188
|
+
return;
|
|
82
189
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
190
|
+
|
|
191
|
+
if (secrets.length === 0) {
|
|
192
|
+
console.log(`No ${scope.label} secrets.`);
|
|
193
|
+
return;
|
|
88
194
|
}
|
|
89
195
|
|
|
90
|
-
|
|
196
|
+
table(
|
|
197
|
+
["Key", "Value"],
|
|
198
|
+
secrets.map((s) => [
|
|
199
|
+
s.key,
|
|
200
|
+
opts.show
|
|
201
|
+
? s.value
|
|
202
|
+
: chalk.dim("•".repeat(Math.min(s.value.length, 20))),
|
|
203
|
+
]),
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
cmd
|
|
208
|
+
.command("set")
|
|
209
|
+
.argument("<pairs...>", "KEY=value pairs")
|
|
210
|
+
.description("Set one or more secrets")
|
|
211
|
+
.option("--global", "Target the whole project")
|
|
212
|
+
.option("-s, --service <name>", "Service to scope to (overrides linked)")
|
|
213
|
+
.option("-p, --project <id>", "Project to scope to")
|
|
214
|
+
.action(async (pairs: string[], opts, sub) => {
|
|
215
|
+
const inherited = sub.parent?.opts() || {};
|
|
216
|
+
const scope = await resolveScope(
|
|
217
|
+
opts.project ?? inherited.project,
|
|
218
|
+
opts.service ?? inherited.service,
|
|
219
|
+
opts.global || inherited.global,
|
|
220
|
+
);
|
|
221
|
+
const newSecrets = parsePairs(pairs);
|
|
222
|
+
await configApplySecrets(scope, newSecrets);
|
|
91
223
|
|
|
92
224
|
if (isJSONMode()) {
|
|
93
|
-
printJSON({ updated: Object.keys(newSecrets) });
|
|
225
|
+
printJSON({ updated: Object.keys(newSecrets), scope: scope.label });
|
|
94
226
|
} else {
|
|
95
|
-
success(
|
|
96
|
-
`${Object.keys(newSecrets).length} secret(s) updated`,
|
|
97
|
-
);
|
|
227
|
+
success(`${Object.keys(newSecrets).length} ${scope.label} secret(s) updated`);
|
|
98
228
|
}
|
|
99
229
|
});
|
|
100
230
|
|
|
101
|
-
|
|
231
|
+
cmd
|
|
102
232
|
.command("delete")
|
|
233
|
+
.alias("rm")
|
|
103
234
|
.argument("<keys...>", "Secret keys to delete")
|
|
104
235
|
.description("Delete one or more secrets")
|
|
105
|
-
.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
236
|
+
.option("--global", "Target the whole project")
|
|
237
|
+
.option("-y, --yes", "Skip confirmation")
|
|
238
|
+
.option("-s, --service <name>", "Service to scope to (overrides linked)")
|
|
239
|
+
.option("-p, --project <id>", "Project to scope to")
|
|
240
|
+
.action(async (keys: string[], opts, sub) => {
|
|
241
|
+
const inherited = sub.parent?.opts() || {};
|
|
242
|
+
const scope = await resolveScope(
|
|
243
|
+
opts.project ?? inherited.project,
|
|
244
|
+
opts.service ?? inherited.service,
|
|
245
|
+
opts.global || inherited.global,
|
|
109
246
|
);
|
|
247
|
+
const existing = await api.get<Secret[]>(scope.path);
|
|
248
|
+
const existingKeys = new Set(existing.map((s) => s.key));
|
|
249
|
+
const notFound = keys.filter((k) => !existingKeys.has(k));
|
|
250
|
+
if (notFound.length > 0) {
|
|
251
|
+
throw new Error(`Secret(s) not found: ${notFound.join(", ")}`);
|
|
252
|
+
}
|
|
110
253
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
254
|
+
if (!opts.yes) {
|
|
255
|
+
if (!isTTY()) throw new Error("Use -y to confirm in non-interactive mode");
|
|
256
|
+
const summary = keys.length === 1 ? chalk.bold(keys[0]) : `${keys.length} keys`;
|
|
257
|
+
const confirm = await p.confirm({
|
|
258
|
+
message: `Delete ${summary} from ${chalk.bold(scope.label)} scope?`,
|
|
259
|
+
});
|
|
260
|
+
if (p.isCancel(confirm) || !confirm) process.exit(5);
|
|
116
261
|
}
|
|
117
262
|
|
|
118
|
-
|
|
263
|
+
const deletePayload: Record<string, null> = {};
|
|
264
|
+
keys.forEach((k) => { deletePayload[k] = null; });
|
|
265
|
+
await configApplySecrets(scope, deletePayload);
|
|
119
266
|
|
|
120
267
|
if (isJSONMode()) {
|
|
121
|
-
printJSON({ deleted: keys });
|
|
268
|
+
printJSON({ deleted: keys, scope: scope.label });
|
|
122
269
|
} else {
|
|
123
|
-
success(`${keys.length} secret(s) deleted`);
|
|
270
|
+
success(`${keys.length} ${scope.label} secret(s) deleted`);
|
|
124
271
|
}
|
|
125
272
|
});
|
|
126
273
|
|
|
127
|
-
|
|
274
|
+
cmd
|
|
128
275
|
.command("import")
|
|
129
|
-
.description("Import secrets from stdin (KEY=value
|
|
130
|
-
.option("--
|
|
131
|
-
.
|
|
132
|
-
|
|
276
|
+
.description("Import secrets from stdin (KEY=value, one per line)")
|
|
277
|
+
.option("--global", "Target the whole project")
|
|
278
|
+
.option("-s, --service <name>", "Service to scope to (overrides linked)")
|
|
279
|
+
.option("-p, --project <id>", "Project to scope to")
|
|
280
|
+
.action(async (opts, sub) => {
|
|
281
|
+
const inherited = sub.parent?.opts() || {};
|
|
282
|
+
const scope = await resolveScope(
|
|
283
|
+
opts.project ?? inherited.project,
|
|
284
|
+
opts.service ?? inherited.service,
|
|
285
|
+
opts.global || inherited.global,
|
|
286
|
+
);
|
|
133
287
|
|
|
134
|
-
// Read stdin
|
|
135
288
|
const chunks: Buffer[] = [];
|
|
136
|
-
for await (const chunk of process.stdin)
|
|
137
|
-
chunks.push(chunk as Buffer);
|
|
138
|
-
}
|
|
289
|
+
for await (const chunk of process.stdin) chunks.push(chunk as Buffer);
|
|
139
290
|
const input = Buffer.concat(chunks).toString("utf-8");
|
|
140
291
|
|
|
141
292
|
const newSecrets: Record<string, string> = {};
|
|
142
293
|
for (const line of input.split("\n")) {
|
|
143
294
|
const trimmed = line.trim();
|
|
144
295
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
147
|
-
newSecrets[trimmed.slice(0,
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (Object.keys(newSecrets).length === 0) {
|
|
151
|
-
throw new Error("No valid KEY=value pairs found in input");
|
|
296
|
+
const eq = trimmed.indexOf("=");
|
|
297
|
+
if (eq < 1) continue;
|
|
298
|
+
newSecrets[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
|
|
152
299
|
}
|
|
153
300
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
`/api/projects/${projectId}/secrets`,
|
|
157
|
-
);
|
|
158
|
-
const existingMap = new Map(existing.map((s) => [s.key, s.value]));
|
|
159
|
-
for (const [k, v] of Object.entries(newSecrets)) {
|
|
160
|
-
existingMap.set(k, v);
|
|
301
|
+
if (!Object.keys(newSecrets).length) {
|
|
302
|
+
throw new Error("No valid KEY=value pairs in input");
|
|
161
303
|
}
|
|
162
|
-
const merged = Array.from(existingMap.entries()).map(([key, value]) => ({
|
|
163
|
-
key,
|
|
164
|
-
value,
|
|
165
|
-
}));
|
|
166
304
|
|
|
167
|
-
await
|
|
305
|
+
await configApplySecrets(scope, newSecrets);
|
|
168
306
|
|
|
169
307
|
if (isJSONMode()) {
|
|
170
|
-
printJSON({ imported: Object.keys(newSecrets) });
|
|
308
|
+
printJSON({ imported: Object.keys(newSecrets), scope: scope.label });
|
|
171
309
|
} else {
|
|
172
|
-
success(`${Object.keys(newSecrets).length} secret(s) imported`);
|
|
310
|
+
success(`${Object.keys(newSecrets).length} ${scope.label} secret(s) imported`);
|
|
173
311
|
}
|
|
174
312
|
});
|
|
175
313
|
}
|
|
314
|
+
|
|
315
|
+
interface VarRef {
|
|
316
|
+
template: string;
|
|
317
|
+
description?: string;
|
|
318
|
+
source?: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Fetch the reference manifest from the backend so users know which
|
|
323
|
+
* `${{...}}` templates are valid (e.g. `${{Postgres.DATABASE_URL}}`,
|
|
324
|
+
* `${{api.LIZARD_PUBLIC_DOMAIN}}`). Backend endpoint:
|
|
325
|
+
* GET /api/projects/<id>/variables:refs
|
|
326
|
+
* GET /api/apps/<id>/variables:refs (for service-scope)
|
|
327
|
+
*
|
|
328
|
+
* Returns a flat list of templates ready to copy-paste into secret values.
|
|
329
|
+
*/
|
|
330
|
+
async function printRefs(scope: Scope): Promise<void> {
|
|
331
|
+
const endpoint =
|
|
332
|
+
scope.label === "service" && scope.serviceId
|
|
333
|
+
? withScope(`/api/apps/${scope.serviceId}/variables:refs`, scope.scope)
|
|
334
|
+
: withScope(`/api/projects/${scope.projectId}/variables:refs`, scope.scope);
|
|
335
|
+
|
|
336
|
+
const refs = await api.get<VarRef[]>(endpoint);
|
|
337
|
+
|
|
338
|
+
if (isJSONMode()) {
|
|
339
|
+
printJSON(refs);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!refs.length) {
|
|
344
|
+
console.log("No reference variables exposed in this scope.");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
table(
|
|
349
|
+
["Template", "Source", "Description"],
|
|
350
|
+
refs.map((r) => [chalk.cyan(r.template), r.source || "—", r.description || ""]),
|
|
351
|
+
);
|
|
352
|
+
}
|