relight-cli 0.1.0 → 0.2.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.
Files changed (42) hide show
  1. package/README.md +77 -34
  2. package/package.json +12 -4
  3. package/src/cli.js +305 -1
  4. package/src/commands/apps.js +128 -0
  5. package/src/commands/auth.js +75 -4
  6. package/src/commands/config.js +282 -0
  7. package/src/commands/cost.js +593 -0
  8. package/src/commands/db.js +531 -0
  9. package/src/commands/deploy.js +298 -0
  10. package/src/commands/doctor.js +41 -9
  11. package/src/commands/domains.js +223 -0
  12. package/src/commands/logs.js +111 -0
  13. package/src/commands/open.js +42 -0
  14. package/src/commands/ps.js +121 -0
  15. package/src/commands/scale.js +132 -0
  16. package/src/lib/clouds/aws.js +309 -35
  17. package/src/lib/clouds/cf.js +401 -2
  18. package/src/lib/clouds/gcp.js +234 -3
  19. package/src/lib/clouds/slicervm.js +139 -0
  20. package/src/lib/config.js +40 -0
  21. package/src/lib/docker.js +34 -0
  22. package/src/lib/link.js +20 -5
  23. package/src/lib/providers/aws/app.js +481 -0
  24. package/src/lib/providers/aws/db.js +513 -0
  25. package/src/lib/providers/aws/dns.js +232 -0
  26. package/src/lib/providers/aws/registry.js +59 -0
  27. package/src/lib/providers/cf/app.js +596 -0
  28. package/src/lib/providers/cf/bundle.js +70 -0
  29. package/src/lib/providers/cf/db.js +279 -0
  30. package/src/lib/providers/cf/dns.js +148 -0
  31. package/src/lib/providers/cf/registry.js +17 -0
  32. package/src/lib/providers/gcp/app.js +429 -0
  33. package/src/lib/providers/gcp/db.js +457 -0
  34. package/src/lib/providers/gcp/dns.js +166 -0
  35. package/src/lib/providers/gcp/registry.js +30 -0
  36. package/src/lib/providers/resolve.js +49 -0
  37. package/src/lib/providers/slicervm/app.js +396 -0
  38. package/src/lib/providers/slicervm/db.js +33 -0
  39. package/src/lib/providers/slicervm/dns.js +58 -0
  40. package/src/lib/providers/slicervm/registry.js +7 -0
  41. package/worker-template/package.json +10 -0
  42. package/worker-template/src/index.js +260 -0
@@ -0,0 +1,128 @@
1
+ import { success, fatal, hint, fmt, table } from "../lib/output.js";
2
+ import { resolveAppName, readLink, unlinkApp } from "../lib/link.js";
3
+ import { resolveCloudId, getCloudCfg, getProvider } from "../lib/providers/resolve.js";
4
+ import { createInterface } from "readline";
5
+
6
+ export async function appsList(options) {
7
+ var cloud = resolveCloudId(options.cloud);
8
+ var cfg = getCloudCfg(cloud);
9
+ var appProvider = await getProvider(cloud, "app");
10
+
11
+ var apps = await appProvider.listApps(cfg);
12
+
13
+ if (apps.length === 0) {
14
+ if (options.json) {
15
+ console.log("[]");
16
+ } else {
17
+ process.stderr.write("No apps deployed.\n");
18
+ hint("Next", "relight deploy");
19
+ }
20
+ return;
21
+ }
22
+
23
+ if (options.json) {
24
+ console.log(JSON.stringify(apps, null, 2));
25
+ return;
26
+ }
27
+
28
+ var rows = apps.map((a) => [
29
+ fmt.app(a.name),
30
+ a.modified ? new Date(a.modified).toISOString() : "-",
31
+ ]);
32
+
33
+ console.log(table(["NAME", "LAST MODIFIED"], rows));
34
+ }
35
+
36
+ export async function appsInfo(name, options) {
37
+ name = resolveAppName(name);
38
+ var cloud = resolveCloudId(options.cloud);
39
+ var cfg = getCloudCfg(cloud);
40
+ var appProvider = await getProvider(cloud, "app");
41
+
42
+ var info = await appProvider.getAppInfo(cfg, name);
43
+
44
+ if (!info) {
45
+ fatal(
46
+ `App ${fmt.app(name)} not found.`,
47
+ `Run ${fmt.cmd(`relight deploy ${name} .`)} first.`
48
+ );
49
+ }
50
+
51
+ var appConfig = info.appConfig;
52
+
53
+ if (options.json) {
54
+ console.log(JSON.stringify(appConfig, null, 2));
55
+ return;
56
+ }
57
+
58
+ console.log("");
59
+ console.log(`${fmt.bold("App:")} ${fmt.app(name)}`);
60
+ if (info.url) console.log(`${fmt.bold("URL:")} ${fmt.url(info.url)}`);
61
+ console.log(
62
+ `${fmt.bold("Image:")} ${appConfig.image || fmt.dim("(not deployed)")}`
63
+ );
64
+ console.log(`${fmt.bold("Regions:")} ${appConfig.regions.join(", ")}`);
65
+ console.log(`${fmt.bold("Instances:")} ${appConfig.instances} per region`);
66
+ console.log(`${fmt.bold("Port:")} ${appConfig.port}`);
67
+ console.log(
68
+ `${fmt.bold("Domains:")} ${(appConfig.domains || []).join(", ") || fmt.dim("(none)")}`
69
+ );
70
+ var envCount = (appConfig.envKeys || []).length;
71
+ var secretCount = (appConfig.secretKeys || []).length;
72
+ var totalCount = envCount + secretCount;
73
+ if (!appConfig.envKeys && appConfig.env) totalCount = Object.keys(appConfig.env).length;
74
+ var envDisplay = secretCount > 0 ? `${totalCount} (${secretCount} secret)` : `${totalCount}`;
75
+ console.log(
76
+ `${fmt.bold("Env vars:")} ${envDisplay}`
77
+ );
78
+ if (appConfig.dbId) {
79
+ console.log(`${fmt.bold("Database:")} ${appConfig.dbName || appConfig.dbId}`);
80
+ }
81
+ if (appConfig.deployedAt) {
82
+ console.log(`${fmt.bold("Deployed:")} ${appConfig.deployedAt}`);
83
+ }
84
+ if (appConfig.createdAt) {
85
+ console.log(`${fmt.bold("Created:")} ${appConfig.createdAt}`);
86
+ }
87
+ }
88
+
89
+ export async function appsDestroy(name, options) {
90
+ name = resolveAppName(name);
91
+ var cloud = resolveCloudId(options.cloud);
92
+ var cfg = getCloudCfg(cloud);
93
+ var appProvider = await getProvider(cloud, "app");
94
+
95
+ if (options.confirm !== name) {
96
+ if (process.stdin.isTTY) {
97
+ var rl = createInterface({ input: process.stdin, output: process.stderr });
98
+ var answer = await new Promise((resolve) =>
99
+ rl.question(`Type "${name}" to confirm destruction: `, resolve)
100
+ );
101
+ rl.close();
102
+ if (answer.trim() !== name) {
103
+ fatal("Confirmation did not match. Aborting.");
104
+ }
105
+ } else {
106
+ fatal(
107
+ `Destroying ${fmt.app(name)} requires confirmation.`,
108
+ `Run: relight apps destroy ${name} --confirm ${name}`
109
+ );
110
+ }
111
+ }
112
+
113
+ process.stderr.write(`Destroying ${fmt.app(name)}...\n`);
114
+
115
+ try {
116
+ await appProvider.destroyApp(cfg, name);
117
+ } catch (e) {
118
+ fatal(`Could not destroy ${fmt.app(name)}.`, e.message);
119
+ }
120
+
121
+ // Remove .relight if it points to this app
122
+ var linked = readLink();
123
+ if (linked && linked.app === name) {
124
+ unlinkApp();
125
+ }
126
+
127
+ success(`App ${fmt.app(name)} destroyed.`);
128
+ }
@@ -16,6 +16,7 @@ import {
16
16
  verifyProject,
17
17
  } from "../lib/clouds/gcp.js";
18
18
  import { verifyCredentials as awsVerify } from "../lib/clouds/aws.js";
19
+ import { verifyConnection as slicerVerify } from "../lib/clouds/slicervm.js";
19
20
  import kleur from "kleur";
20
21
 
21
22
  function prompt(rl, question) {
@@ -72,6 +73,9 @@ export async function auth(options) {
72
73
  case "aws":
73
74
  cloudConfig = await authAWS(rl);
74
75
  break;
76
+ case "slicervm":
77
+ cloudConfig = await authSlicerVM(rl);
78
+ break;
75
79
  }
76
80
 
77
81
  rl.close();
@@ -105,6 +109,8 @@ function normalizeCompute(input) {
105
109
  aws: "aws",
106
110
  amazon: "aws",
107
111
  "app-runner": "aws",
112
+ slicervm: "slicervm",
113
+ slicer: "slicervm",
108
114
  };
109
115
  return aliases[input.toLowerCase()] || input.toLowerCase();
110
116
  }
@@ -168,17 +174,21 @@ async function authGCP(rl) {
168
174
  var SA_URL =
169
175
  "https://console.cloud.google.com/iam-admin/serviceaccounts/create";
170
176
  var ENABLE_APIS =
171
- "https://console.cloud.google.com/apis/enableflow?apiid=run.googleapis.com,artifactregistry.googleapis.com";
177
+ "https://console.cloud.google.com/apis/enableflow?apiid=run.googleapis.com,artifactregistry.googleapis.com,sqladmin.googleapis.com,dns.googleapis.com,logging.googleapis.com,monitoring.googleapis.com";
172
178
 
173
179
  process.stderr.write(`\n ${kleur.bold("Setup")}\n\n`);
174
- process.stderr.write(` 1. Enable the Cloud Run and Artifact Registry APIs:\n`);
180
+ process.stderr.write(` 1. Enable the required APIs:\n`);
175
181
  process.stderr.write(` ${fmt.url(ENABLE_APIS)}\n\n`);
176
182
  process.stderr.write(` 2. Create a service account:\n`);
177
183
  process.stderr.write(` ${fmt.url(SA_URL)}\n\n`);
178
184
  process.stderr.write(` Name it ${fmt.val("relight")} and grant these roles:\n`);
179
185
  process.stderr.write(` ${fmt.val("Cloud Run Admin")}\n`);
180
186
  process.stderr.write(` ${fmt.val("Artifact Registry Admin")}\n`);
181
- process.stderr.write(` ${fmt.val("Service Account User")}\n\n`);
187
+ process.stderr.write(` ${fmt.val("Service Account User")}\n`);
188
+ process.stderr.write(` ${fmt.val("Cloud SQL Admin")}\n`);
189
+ process.stderr.write(` ${fmt.val("DNS Administrator")}\n`);
190
+ process.stderr.write(` ${fmt.val("Logs Viewer")}\n`);
191
+ process.stderr.write(` ${fmt.val("Monitoring Viewer")}\n\n`);
182
192
  process.stderr.write(` 3. Go to the service account → ${kleur.bold("Keys")} tab\n`);
183
193
  process.stderr.write(` 4. ${kleur.bold("Add Key")} → ${kleur.bold("Create new key")} → ${kleur.bold("JSON")}\n`);
184
194
  process.stderr.write(` 5. Save the downloaded file\n\n`);
@@ -237,7 +247,12 @@ async function authAWS(rl) {
237
247
  process.stderr.write(` ${fmt.url(IAM_CONSOLE)}\n\n`);
238
248
  process.stderr.write(` 2. Create a user and attach these policies:\n`);
239
249
  process.stderr.write(` ${fmt.val("AWSAppRunnerFullAccess")}\n`);
240
- process.stderr.write(` ${fmt.val("AmazonEC2ContainerRegistryFullAccess")}\n\n`);
250
+ process.stderr.write(` ${fmt.val("AmazonEC2ContainerRegistryFullAccess")}\n`);
251
+ process.stderr.write(` ${fmt.val("AmazonRDSFullAccess")}\n`);
252
+ process.stderr.write(` ${fmt.val("AmazonRoute53FullAccess")}\n`);
253
+ process.stderr.write(` ${fmt.val("AmazonEC2ReadOnlyAccess")}\n`);
254
+ process.stderr.write(` ${fmt.val("CloudWatchLogsReadOnlyAccess")}\n`);
255
+ process.stderr.write(` ${fmt.val("IAMFullAccess")}\n\n`);
241
256
  process.stderr.write(` 3. Go to the user's ${kleur.bold("Security credentials")} tab\n`);
242
257
  process.stderr.write(` 4. Click ${kleur.bold("Create access key")} → choose ${kleur.bold("Command Line Interface")}\n`);
243
258
  process.stderr.write(` 5. Copy the Access Key ID and Secret Access Key\n\n`);
@@ -287,6 +302,62 @@ async function authAWS(rl) {
287
302
  return { accessKeyId, secretAccessKey, region };
288
303
  }
289
304
 
305
+ // --- SlicerVM ---
306
+
307
+ async function authSlicerVM(rl) {
308
+ process.stderr.write(`\n ${kleur.bold("Connection mode")}\n\n`);
309
+ process.stderr.write(` ${kleur.bold("[1]")} Unix socket (local dev)\n`);
310
+ process.stderr.write(` ${kleur.bold("[2]")} HTTP API (remote)\n\n`);
311
+
312
+ var modeChoice = await prompt(rl, "Select [1-2]: ");
313
+ var useSocket = modeChoice.trim() === "1";
314
+
315
+ var config = {};
316
+
317
+ if (useSocket) {
318
+ var defaultSocket = "/var/run/slicer/slicer.sock";
319
+ var socketPath = await prompt(rl, `Socket path [${defaultSocket}]: `);
320
+ socketPath = (socketPath || "").trim() || defaultSocket;
321
+ config.socketPath = socketPath;
322
+ } else {
323
+ var apiUrl = await prompt(rl, "Slicer API URL (e.g. https://slicer.example.com:8080): ");
324
+ apiUrl = (apiUrl || "").trim().replace(/\/+$/, "");
325
+ if (!apiUrl) fatal("No API URL provided.");
326
+ config.apiUrl = apiUrl;
327
+
328
+ var token = await prompt(rl, "API token: ");
329
+ token = (token || "").trim();
330
+ if (!token) fatal("No token provided.");
331
+ config.token = token;
332
+ }
333
+
334
+ var hostGroup = await prompt(rl, "Host group [apps]: ");
335
+ config.hostGroup = (hostGroup || "").trim() || "apps";
336
+
337
+ var baseDomain = await prompt(rl, "Base domain (e.g. apps.example.com) [localhost]: ");
338
+ config.baseDomain = (baseDomain || "").trim() || "localhost";
339
+
340
+ process.stderr.write("\nVerifying...\n");
341
+ var verifyCfg = useSocket
342
+ ? { socketPath: config.socketPath }
343
+ : { apiUrl: config.apiUrl, apiToken: config.token };
344
+ try {
345
+ await slicerVerify(verifyCfg);
346
+ } catch (e) {
347
+ fatal("Connection failed.", e.message);
348
+ }
349
+
350
+ if (useSocket) {
351
+ process.stderr.write(` Socket: ${fmt.bold(config.socketPath)}\n`);
352
+ } else {
353
+ process.stderr.write(` API: ${fmt.bold(config.apiUrl)}\n`);
354
+ }
355
+ process.stderr.write(` Host group: ${fmt.dim(config.hostGroup)}\n`);
356
+ process.stderr.write(` Base domain: ${fmt.dim(config.baseDomain)}\n`);
357
+
358
+ return config;
359
+ }
360
+
290
361
  // --- Helpers ---
291
362
 
292
363
  function tryExec(cmd) {
@@ -0,0 +1,282 @@
1
+ import { readFileSync } from "fs";
2
+ import { status, success, fatal, hint, fmt } from "../lib/output.js";
3
+ import { resolveAppName } from "../lib/link.js";
4
+ import { resolveCloudId, getCloudCfg, getProvider } from "../lib/providers/resolve.js";
5
+
6
+ var RESERVED_NAMES = ["RELIGHT_APP_CONFIG", "APP_CONTAINER", "DB", "DB_URL", "DB_TOKEN"];
7
+
8
+ function validateKeyName(key) {
9
+ if (RESERVED_NAMES.includes(key)) {
10
+ fatal(`${fmt.key(key)} is a reserved binding name and cannot be used as an env var.`);
11
+ }
12
+ }
13
+
14
+ function ensureKeyLists(appConfig) {
15
+ if (!appConfig.envKeys) appConfig.envKeys = [];
16
+ if (!appConfig.secretKeys) appConfig.secretKeys = [];
17
+ if (!appConfig.env) appConfig.env = {};
18
+ }
19
+
20
+ export async function configShow(name, options) {
21
+ name = resolveAppName(name);
22
+ var cloud = resolveCloudId(options.cloud);
23
+ var cfg = getCloudCfg(cloud);
24
+ var appProvider = await getProvider(cloud, "app");
25
+ var appConfig = await appProvider.getAppConfig(cfg, name);
26
+
27
+ if (!appConfig) {
28
+ fatal(
29
+ `App ${fmt.app(name)} not found.`,
30
+ `Run ${fmt.cmd(`relight deploy ${name} .`)} first.`
31
+ );
32
+ }
33
+
34
+ var env = appConfig.env || {};
35
+ var keys = Object.keys(env);
36
+
37
+ if (keys.length === 0) {
38
+ if (options.json) {
39
+ console.log("{}");
40
+ } else {
41
+ process.stderr.write(`No config vars set for ${fmt.app(name)}.\n`);
42
+ }
43
+ return;
44
+ }
45
+
46
+ if (options.json) {
47
+ console.log(JSON.stringify(env, null, 2));
48
+ return;
49
+ }
50
+
51
+ for (var key of keys) {
52
+ console.log(`${fmt.key(key)}=${env[key]}`);
53
+ }
54
+ }
55
+
56
+ export async function configSet(args, options) {
57
+ var name, vars;
58
+ if (args.length === 0) {
59
+ fatal("No env vars provided.", "Usage: relight config set [name] KEY=VALUE ...");
60
+ }
61
+ if (args[0].includes("=")) {
62
+ name = resolveAppName(null);
63
+ vars = args;
64
+ } else {
65
+ name = args[0];
66
+ vars = args.slice(1);
67
+ }
68
+
69
+ if (vars.length === 0) {
70
+ fatal("No env vars provided.", "Usage: relight config set [name] KEY=VALUE ...");
71
+ }
72
+
73
+ var cloud = resolveCloudId(options.cloud);
74
+ var cfg = getCloudCfg(cloud);
75
+ var appProvider = await getProvider(cloud, "app");
76
+ var appConfig = await appProvider.getAppConfig(cfg, name);
77
+
78
+ if (!appConfig) {
79
+ fatal(
80
+ `App ${fmt.app(name)} not found.`,
81
+ `Run ${fmt.cmd(`relight deploy ${name} .`)} first.`
82
+ );
83
+ }
84
+
85
+ ensureKeyLists(appConfig);
86
+
87
+ var isSecret = options && options.secret;
88
+ var newSecrets = {};
89
+
90
+ for (var v of vars) {
91
+ var eq = v.indexOf("=");
92
+ if (eq === -1) {
93
+ fatal(`Invalid format: ${v}`, "Use KEY=VALUE format.");
94
+ }
95
+ var key = v.substring(0, eq);
96
+ var value = v.substring(eq + 1);
97
+ validateKeyName(key);
98
+
99
+ if (isSecret) {
100
+ appConfig.envKeys = appConfig.envKeys.filter((k) => k !== key);
101
+ if (!appConfig.secretKeys.includes(key)) appConfig.secretKeys.push(key);
102
+ newSecrets[key] = value;
103
+ appConfig.env[key] = "[hidden]";
104
+ status(`${fmt.key(key)} set ${fmt.dim("(secret)")}`);
105
+ } else {
106
+ appConfig.secretKeys = appConfig.secretKeys.filter((k) => k !== key);
107
+ if (!appConfig.envKeys.includes(key)) appConfig.envKeys.push(key);
108
+ appConfig.env[key] = value;
109
+ status(`${fmt.key(key)} set`);
110
+ }
111
+ }
112
+
113
+ await appProvider.pushAppConfig(cfg, name, appConfig, { newSecrets: Object.keys(newSecrets).length > 0 ? newSecrets : undefined });
114
+ success("Config updated (live).");
115
+ }
116
+
117
+ export async function configGet(args, options) {
118
+ var name, key;
119
+ if (args.length === 2) {
120
+ name = args[0];
121
+ key = args[1];
122
+ } else if (args.length === 1) {
123
+ name = resolveAppName(null);
124
+ key = args[0];
125
+ } else {
126
+ fatal("Usage: relight config get [name] <key>");
127
+ }
128
+
129
+ var cloud = resolveCloudId(options.cloud);
130
+ var cfg = getCloudCfg(cloud);
131
+ var appProvider = await getProvider(cloud, "app");
132
+ var appConfig = await appProvider.getAppConfig(cfg, name);
133
+
134
+ if (!appConfig) {
135
+ fatal(
136
+ `App ${fmt.app(name)} not found.`,
137
+ `Run ${fmt.cmd(`relight deploy ${name} .`)} first.`
138
+ );
139
+ }
140
+
141
+ var env = appConfig.env || {};
142
+
143
+ if (!(key in env)) {
144
+ fatal(`Key ${fmt.key(key)} is not set on ${fmt.app(name)}.`);
145
+ }
146
+
147
+ if ((appConfig.secretKeys || []).includes(key)) {
148
+ console.log("[hidden]");
149
+ hint("Secret values are write-only", "they cannot be read back after being set.");
150
+ return;
151
+ }
152
+
153
+ console.log(env[key]);
154
+ }
155
+
156
+ export async function configUnset(args, options) {
157
+ var name, keys;
158
+ if (args.length === 0) {
159
+ fatal("No keys provided.", "Usage: relight config unset [name] KEY ...");
160
+ }
161
+ if (args.length === 1) {
162
+ name = resolveAppName(null);
163
+ keys = args;
164
+ } else if (/^[A-Z_][A-Z0-9_]*$/.test(args[0])) {
165
+ name = resolveAppName(null);
166
+ keys = args;
167
+ } else {
168
+ name = args[0];
169
+ keys = args.slice(1);
170
+ }
171
+
172
+ if (keys.length === 0) {
173
+ fatal("No keys provided.", "Usage: relight config unset [name] KEY ...");
174
+ }
175
+
176
+ var cloud = resolveCloudId(options.cloud);
177
+ var cfg = getCloudCfg(cloud);
178
+ var appProvider = await getProvider(cloud, "app");
179
+ var appConfig = await appProvider.getAppConfig(cfg, name);
180
+
181
+ if (!appConfig) {
182
+ fatal(
183
+ `App ${fmt.app(name)} not found.`,
184
+ `Run ${fmt.cmd(`relight deploy ${name} .`)} first.`
185
+ );
186
+ }
187
+
188
+ ensureKeyLists(appConfig);
189
+
190
+ for (var key of keys) {
191
+ var wasEnv = appConfig.envKeys.includes(key);
192
+ var wasSecret = appConfig.secretKeys.includes(key);
193
+ if (!wasEnv && !wasSecret) {
194
+ status(`${fmt.key(key)} ${fmt.dim("(not set, skipping)")}`);
195
+ continue;
196
+ }
197
+ appConfig.envKeys = appConfig.envKeys.filter((k) => k !== key);
198
+ appConfig.secretKeys = appConfig.secretKeys.filter((k) => k !== key);
199
+ delete appConfig.env[key];
200
+ status(`${fmt.key(key)} removed`);
201
+ }
202
+
203
+ await appProvider.pushAppConfig(cfg, name, appConfig);
204
+ success("Config updated (live).");
205
+ }
206
+
207
+ export async function configImport(name, options) {
208
+ name = resolveAppName(name);
209
+ var cloud = resolveCloudId(options.cloud);
210
+ var cfg = getCloudCfg(cloud);
211
+ var appProvider = await getProvider(cloud, "app");
212
+ var appConfig = await appProvider.getAppConfig(cfg, name);
213
+
214
+ if (!appConfig) {
215
+ fatal(
216
+ `App ${fmt.app(name)} not found.`,
217
+ `Run ${fmt.cmd(`relight deploy ${name} .`)} first.`
218
+ );
219
+ }
220
+
221
+ ensureKeyLists(appConfig);
222
+
223
+ var input;
224
+ if (options.file) {
225
+ try {
226
+ input = readFileSync(options.file, "utf-8");
227
+ } catch (e) {
228
+ fatal(`Could not read file: ${options.file}`, e.message);
229
+ }
230
+ } else if (process.stdin.isTTY) {
231
+ fatal(
232
+ "No input provided.",
233
+ "Pipe a .env file: cat .env | relight config import\n Or use: relight config import --file .env"
234
+ );
235
+ } else {
236
+ var chunks = [];
237
+ for await (var chunk of process.stdin) {
238
+ chunks.push(chunk);
239
+ }
240
+ input = Buffer.concat(chunks).toString("utf-8");
241
+ }
242
+
243
+ var isSecret = options && options.secret;
244
+ var newSecrets = {};
245
+ var count = 0;
246
+ for (var line of input.split("\n")) {
247
+ line = line.trim();
248
+ if (!line || line.startsWith("#")) continue;
249
+ var eq = line.indexOf("=");
250
+ if (eq === -1) continue;
251
+ var key = line.substring(0, eq).trim();
252
+ var value = line.substring(eq + 1).trim();
253
+ if (
254
+ (value.startsWith('"') && value.endsWith('"')) ||
255
+ (value.startsWith("'") && value.endsWith("'"))
256
+ ) {
257
+ value = value.slice(1, -1);
258
+ }
259
+ validateKeyName(key);
260
+
261
+ if (isSecret) {
262
+ appConfig.envKeys = appConfig.envKeys.filter((k) => k !== key);
263
+ if (!appConfig.secretKeys.includes(key)) appConfig.secretKeys.push(key);
264
+ newSecrets[key] = value;
265
+ appConfig.env[key] = "[hidden]";
266
+ status(`${fmt.key(key)} set ${fmt.dim("(secret)")}`);
267
+ } else {
268
+ appConfig.secretKeys = appConfig.secretKeys.filter((k) => k !== key);
269
+ if (!appConfig.envKeys.includes(key)) appConfig.envKeys.push(key);
270
+ appConfig.env[key] = value;
271
+ status(`${fmt.key(key)} set`);
272
+ }
273
+ count++;
274
+ }
275
+
276
+ if (count === 0) {
277
+ fatal("No variables found in input.", "Use KEY=VALUE format, one per line.");
278
+ }
279
+
280
+ await appProvider.pushAppConfig(cfg, name, appConfig, { newSecrets: Object.keys(newSecrets).length > 0 ? newSecrets : undefined });
281
+ success(`${count} variable${count !== 1 ? "s" : ""} imported (live).`);
282
+ }