relight-cli 0.1.0 → 0.3.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/README.md +77 -34
- package/package.json +12 -4
- package/src/cli.js +350 -1
- package/src/commands/apps.js +128 -0
- package/src/commands/auth.js +13 -4
- package/src/commands/config.js +282 -0
- package/src/commands/cost.js +593 -0
- package/src/commands/db.js +775 -0
- package/src/commands/deploy.js +264 -0
- package/src/commands/doctor.js +69 -13
- package/src/commands/domains.js +223 -0
- package/src/commands/logs.js +111 -0
- package/src/commands/open.js +42 -0
- package/src/commands/ps.js +121 -0
- package/src/commands/scale.js +132 -0
- package/src/commands/service.js +227 -0
- package/src/lib/clouds/aws.js +309 -35
- package/src/lib/clouds/cf.js +401 -2
- package/src/lib/clouds/gcp.js +255 -4
- package/src/lib/clouds/neon.js +147 -0
- package/src/lib/clouds/slicervm.js +139 -0
- package/src/lib/config.js +200 -2
- package/src/lib/docker.js +34 -0
- package/src/lib/link.js +31 -5
- package/src/lib/providers/aws/app.js +481 -0
- package/src/lib/providers/aws/db.js +504 -0
- package/src/lib/providers/aws/dns.js +232 -0
- package/src/lib/providers/aws/registry.js +59 -0
- package/src/lib/providers/cf/app.js +596 -0
- package/src/lib/providers/cf/bundle.js +70 -0
- package/src/lib/providers/cf/db.js +181 -0
- package/src/lib/providers/cf/dns.js +148 -0
- package/src/lib/providers/cf/registry.js +17 -0
- package/src/lib/providers/gcp/app.js +429 -0
- package/src/lib/providers/gcp/db.js +372 -0
- package/src/lib/providers/gcp/dns.js +166 -0
- package/src/lib/providers/gcp/registry.js +30 -0
- package/src/lib/providers/neon/db.js +306 -0
- package/src/lib/providers/resolve.js +79 -0
- package/src/lib/providers/slicervm/app.js +396 -0
- package/src/lib/providers/slicervm/db.js +33 -0
- package/src/lib/providers/slicervm/dns.js +58 -0
- package/src/lib/providers/slicervm/registry.js +7 -0
- package/worker-template/package.json +10 -0
- 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 { resolveTarget } from "../lib/providers/resolve.js";
|
|
4
|
+
import { createInterface } from "readline";
|
|
5
|
+
|
|
6
|
+
export async function appsList(options) {
|
|
7
|
+
var target = await resolveTarget(options);
|
|
8
|
+
var cfg = target.cfg;
|
|
9
|
+
var appProvider = await target.provider("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 target = await resolveTarget(options);
|
|
39
|
+
var cfg = target.cfg;
|
|
40
|
+
var appProvider = await target.provider("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 target = await resolveTarget(options);
|
|
92
|
+
var cfg = target.cfg;
|
|
93
|
+
var appProvider = await target.provider("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
|
+
}
|
package/src/commands/auth.js
CHANGED
|
@@ -168,17 +168,21 @@ async function authGCP(rl) {
|
|
|
168
168
|
var SA_URL =
|
|
169
169
|
"https://console.cloud.google.com/iam-admin/serviceaccounts/create";
|
|
170
170
|
var ENABLE_APIS =
|
|
171
|
-
"https://console.cloud.google.com/apis/enableflow?apiid=run.googleapis.com,artifactregistry.googleapis.com";
|
|
171
|
+
"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
172
|
|
|
173
173
|
process.stderr.write(`\n ${kleur.bold("Setup")}\n\n`);
|
|
174
|
-
process.stderr.write(` 1. Enable the
|
|
174
|
+
process.stderr.write(` 1. Enable the required APIs:\n`);
|
|
175
175
|
process.stderr.write(` ${fmt.url(ENABLE_APIS)}\n\n`);
|
|
176
176
|
process.stderr.write(` 2. Create a service account:\n`);
|
|
177
177
|
process.stderr.write(` ${fmt.url(SA_URL)}\n\n`);
|
|
178
178
|
process.stderr.write(` Name it ${fmt.val("relight")} and grant these roles:\n`);
|
|
179
179
|
process.stderr.write(` ${fmt.val("Cloud Run Admin")}\n`);
|
|
180
180
|
process.stderr.write(` ${fmt.val("Artifact Registry Admin")}\n`);
|
|
181
|
-
process.stderr.write(` ${fmt.val("Service Account User")}\n
|
|
181
|
+
process.stderr.write(` ${fmt.val("Service Account User")}\n`);
|
|
182
|
+
process.stderr.write(` ${fmt.val("Cloud SQL Admin")}\n`);
|
|
183
|
+
process.stderr.write(` ${fmt.val("DNS Administrator")}\n`);
|
|
184
|
+
process.stderr.write(` ${fmt.val("Logs Viewer")}\n`);
|
|
185
|
+
process.stderr.write(` ${fmt.val("Monitoring Viewer")}\n\n`);
|
|
182
186
|
process.stderr.write(` 3. Go to the service account → ${kleur.bold("Keys")} tab\n`);
|
|
183
187
|
process.stderr.write(` 4. ${kleur.bold("Add Key")} → ${kleur.bold("Create new key")} → ${kleur.bold("JSON")}\n`);
|
|
184
188
|
process.stderr.write(` 5. Save the downloaded file\n\n`);
|
|
@@ -237,7 +241,12 @@ async function authAWS(rl) {
|
|
|
237
241
|
process.stderr.write(` ${fmt.url(IAM_CONSOLE)}\n\n`);
|
|
238
242
|
process.stderr.write(` 2. Create a user and attach these policies:\n`);
|
|
239
243
|
process.stderr.write(` ${fmt.val("AWSAppRunnerFullAccess")}\n`);
|
|
240
|
-
process.stderr.write(` ${fmt.val("AmazonEC2ContainerRegistryFullAccess")}\n
|
|
244
|
+
process.stderr.write(` ${fmt.val("AmazonEC2ContainerRegistryFullAccess")}\n`);
|
|
245
|
+
process.stderr.write(` ${fmt.val("AmazonRDSFullAccess")}\n`);
|
|
246
|
+
process.stderr.write(` ${fmt.val("AmazonRoute53FullAccess")}\n`);
|
|
247
|
+
process.stderr.write(` ${fmt.val("AmazonEC2ReadOnlyAccess")}\n`);
|
|
248
|
+
process.stderr.write(` ${fmt.val("CloudWatchLogsReadOnlyAccess")}\n`);
|
|
249
|
+
process.stderr.write(` ${fmt.val("IAMFullAccess")}\n\n`);
|
|
241
250
|
process.stderr.write(` 3. Go to the user's ${kleur.bold("Security credentials")} tab\n`);
|
|
242
251
|
process.stderr.write(` 4. Click ${kleur.bold("Create access key")} → choose ${kleur.bold("Command Line Interface")}\n`);
|
|
243
252
|
process.stderr.write(` 5. Copy the Access Key ID and Secret Access Key\n\n`);
|
|
@@ -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 { resolveTarget } 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 target = await resolveTarget(options);
|
|
23
|
+
var cfg = target.cfg;
|
|
24
|
+
var appProvider = await target.provider("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 target = await resolveTarget(options);
|
|
74
|
+
var cfg = target.cfg;
|
|
75
|
+
var appProvider = await target.provider("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 target = await resolveTarget(options);
|
|
130
|
+
var cfg = target.cfg;
|
|
131
|
+
var appProvider = await target.provider("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 target = await resolveTarget(options);
|
|
177
|
+
var cfg = target.cfg;
|
|
178
|
+
var appProvider = await target.provider("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 target = await resolveTarget(options);
|
|
210
|
+
var cfg = target.cfg;
|
|
211
|
+
var appProvider = await target.provider("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
|
+
}
|