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.
- package/README.md +77 -34
- package/package.json +12 -4
- package/src/cli.js +305 -1
- package/src/commands/apps.js +128 -0
- package/src/commands/auth.js +75 -4
- package/src/commands/config.js +282 -0
- package/src/commands/cost.js +593 -0
- package/src/commands/db.js +531 -0
- package/src/commands/deploy.js +298 -0
- package/src/commands/doctor.js +41 -9
- 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/lib/clouds/aws.js +309 -35
- package/src/lib/clouds/cf.js +401 -2
- package/src/lib/clouds/gcp.js +234 -3
- package/src/lib/clouds/slicervm.js +139 -0
- package/src/lib/config.js +40 -0
- package/src/lib/docker.js +34 -0
- package/src/lib/link.js +20 -5
- package/src/lib/providers/aws/app.js +481 -0
- package/src/lib/providers/aws/db.js +513 -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 +279 -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 +457 -0
- package/src/lib/providers/gcp/dns.js +166 -0
- package/src/lib/providers/gcp/registry.js +30 -0
- package/src/lib/providers/resolve.js +49 -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,279 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createD1Database,
|
|
3
|
+
deleteD1Database,
|
|
4
|
+
getD1Database,
|
|
5
|
+
queryD1,
|
|
6
|
+
exportD1,
|
|
7
|
+
importD1,
|
|
8
|
+
getWorkersSubdomain,
|
|
9
|
+
} from "../../clouds/cf.js";
|
|
10
|
+
import { getAppConfig, pushAppConfig } from "./app.js";
|
|
11
|
+
import { randomBytes } from "crypto";
|
|
12
|
+
|
|
13
|
+
export async function createDatabase(cfg, appName, opts = {}) {
|
|
14
|
+
if (!opts.skipAppConfig) {
|
|
15
|
+
var appConfig = await getAppConfig(cfg, appName);
|
|
16
|
+
if (!appConfig) {
|
|
17
|
+
throw new Error(`App ${appName} not found.`);
|
|
18
|
+
}
|
|
19
|
+
if (appConfig.dbId) {
|
|
20
|
+
throw new Error(`App ${appName} already has a database: ${appConfig.dbName}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
var dbName = `relight-${appName}`;
|
|
25
|
+
var result = await createD1Database(cfg.accountId, cfg.apiToken, dbName, {
|
|
26
|
+
locationHint: opts.location,
|
|
27
|
+
jurisdiction: opts.jurisdiction,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
var subdomain = await getWorkersSubdomain(cfg.accountId, cfg.apiToken);
|
|
31
|
+
var connectionUrl = subdomain
|
|
32
|
+
? `https://relight-${appName}.${subdomain}.workers.dev`
|
|
33
|
+
: null;
|
|
34
|
+
|
|
35
|
+
var dbToken = randomBytes(32).toString("hex");
|
|
36
|
+
|
|
37
|
+
if (!opts.skipAppConfig) {
|
|
38
|
+
appConfig.dbId = result.uuid;
|
|
39
|
+
appConfig.dbName = dbName;
|
|
40
|
+
|
|
41
|
+
if (!appConfig.envKeys) appConfig.envKeys = [];
|
|
42
|
+
if (!appConfig.secretKeys) appConfig.secretKeys = [];
|
|
43
|
+
if (!appConfig.env) appConfig.env = {};
|
|
44
|
+
|
|
45
|
+
if (connectionUrl) {
|
|
46
|
+
appConfig.env["DB_URL"] = connectionUrl;
|
|
47
|
+
if (!appConfig.envKeys.includes("DB_URL")) appConfig.envKeys.push("DB_URL");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
appConfig.env["DB_TOKEN"] = "[hidden]";
|
|
51
|
+
appConfig.secretKeys = appConfig.secretKeys.filter((k) => k !== "DB_TOKEN");
|
|
52
|
+
appConfig.secretKeys.push("DB_TOKEN");
|
|
53
|
+
appConfig.envKeys = appConfig.envKeys.filter((k) => k !== "DB_TOKEN");
|
|
54
|
+
|
|
55
|
+
var newSecrets = { DB_TOKEN: dbToken };
|
|
56
|
+
|
|
57
|
+
await pushAppConfig(cfg, appName, appConfig, { newSecrets });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
dbId: result.uuid,
|
|
62
|
+
dbName,
|
|
63
|
+
dbToken,
|
|
64
|
+
connectionUrl,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function destroyDatabase(cfg, appName, opts = {}) {
|
|
69
|
+
var dbId = opts.dbId;
|
|
70
|
+
if (!dbId) {
|
|
71
|
+
var appConfig = await getAppConfig(cfg, appName);
|
|
72
|
+
if (!appConfig || !appConfig.dbId) {
|
|
73
|
+
throw new Error(`App ${appName} does not have a database.`);
|
|
74
|
+
}
|
|
75
|
+
dbId = appConfig.dbId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await deleteD1Database(cfg.accountId, cfg.apiToken, dbId);
|
|
79
|
+
|
|
80
|
+
if (!opts.dbId) {
|
|
81
|
+
delete appConfig.dbId;
|
|
82
|
+
delete appConfig.dbName;
|
|
83
|
+
|
|
84
|
+
if (appConfig.env) {
|
|
85
|
+
delete appConfig.env["DB_URL"];
|
|
86
|
+
delete appConfig.env["DB_TOKEN"];
|
|
87
|
+
}
|
|
88
|
+
if (appConfig.envKeys) appConfig.envKeys = appConfig.envKeys.filter((k) => k !== "DB_URL");
|
|
89
|
+
if (appConfig.secretKeys) appConfig.secretKeys = appConfig.secretKeys.filter((k) => k !== "DB_TOKEN");
|
|
90
|
+
|
|
91
|
+
await pushAppConfig(cfg, appName, appConfig);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function getDatabaseInfo(cfg, appName, opts = {}) {
|
|
96
|
+
var dbId = opts.dbId;
|
|
97
|
+
if (!dbId) {
|
|
98
|
+
var appConfig = await getAppConfig(cfg, appName);
|
|
99
|
+
if (!appConfig || !appConfig.dbId) {
|
|
100
|
+
throw new Error(`App ${appName} does not have a database.`);
|
|
101
|
+
}
|
|
102
|
+
dbId = appConfig.dbId;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
var dbDetails = await getD1Database(cfg.accountId, cfg.apiToken, dbId);
|
|
106
|
+
var subdomain = await getWorkersSubdomain(cfg.accountId, cfg.apiToken);
|
|
107
|
+
var connectionUrl = subdomain
|
|
108
|
+
? `https://relight-${appName}.${subdomain}.workers.dev`
|
|
109
|
+
: null;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
dbId,
|
|
113
|
+
dbName: dbDetails.name || `relight-${appName}`,
|
|
114
|
+
connectionUrl,
|
|
115
|
+
size: dbDetails.file_size,
|
|
116
|
+
numTables: dbDetails.num_tables,
|
|
117
|
+
createdAt: dbDetails.created_at,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function queryDatabase(cfg, appName, sql, params, opts = {}) {
|
|
122
|
+
var dbId = opts.dbId;
|
|
123
|
+
if (!dbId) {
|
|
124
|
+
var appConfig = await getAppConfig(cfg, appName);
|
|
125
|
+
if (!appConfig || !appConfig.dbId) {
|
|
126
|
+
throw new Error(`App ${appName} does not have a database.`);
|
|
127
|
+
}
|
|
128
|
+
dbId = appConfig.dbId;
|
|
129
|
+
}
|
|
130
|
+
return queryD1(cfg.accountId, cfg.apiToken, dbId, sql, params);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function importDatabase(cfg, appName, sqlContent, opts = {}) {
|
|
134
|
+
var dbId = opts.dbId;
|
|
135
|
+
if (!dbId) {
|
|
136
|
+
var appConfig = await getAppConfig(cfg, appName);
|
|
137
|
+
if (!appConfig || !appConfig.dbId) {
|
|
138
|
+
throw new Error(`App ${appName} does not have a database.`);
|
|
139
|
+
}
|
|
140
|
+
dbId = appConfig.dbId;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Step 1: Init import
|
|
144
|
+
var initRes = await importD1(cfg.accountId, cfg.apiToken, dbId, {
|
|
145
|
+
action: "init",
|
|
146
|
+
});
|
|
147
|
+
var initResult = initRes.result || initRes;
|
|
148
|
+
|
|
149
|
+
if (!initResult.filename || !initResult.upload_url) {
|
|
150
|
+
throw new Error("Import init failed - no upload URL returned.");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Step 2: Upload to signed URL
|
|
154
|
+
var uploadRes = await fetch(initResult.upload_url, {
|
|
155
|
+
method: "PUT",
|
|
156
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
157
|
+
body: sqlContent,
|
|
158
|
+
});
|
|
159
|
+
if (!uploadRes.ok) {
|
|
160
|
+
throw new Error(`Upload failed: ${uploadRes.status} ${await uploadRes.text()}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Step 3: Ingest
|
|
164
|
+
var ingestRes = await importD1(cfg.accountId, cfg.apiToken, dbId, {
|
|
165
|
+
action: "ingest",
|
|
166
|
+
filename: initResult.filename,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Step 4: Poll until complete
|
|
170
|
+
var polling = true;
|
|
171
|
+
while (polling) {
|
|
172
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
173
|
+
var pollRes = await importD1(cfg.accountId, cfg.apiToken, dbId, {
|
|
174
|
+
action: "poll",
|
|
175
|
+
current_bookmark: (ingestRes.result || ingestRes).at_bookmark,
|
|
176
|
+
});
|
|
177
|
+
var pollResult = pollRes.result || pollRes;
|
|
178
|
+
if (pollResult.status === "complete" || pollResult.type === "done") {
|
|
179
|
+
polling = false;
|
|
180
|
+
} else if (pollResult.status === "error" || pollResult.error) {
|
|
181
|
+
throw new Error(pollResult.error || "Unknown error during ingest.");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function exportDatabase(cfg, appName, opts = {}) {
|
|
187
|
+
var dbId = opts.dbId;
|
|
188
|
+
if (!dbId) {
|
|
189
|
+
var appConfig = await getAppConfig(cfg, appName);
|
|
190
|
+
if (!appConfig || !appConfig.dbId) {
|
|
191
|
+
throw new Error(`App ${appName} does not have a database.`);
|
|
192
|
+
}
|
|
193
|
+
dbId = appConfig.dbId;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
var exportRes = await exportD1(cfg.accountId, cfg.apiToken, dbId, {
|
|
197
|
+
output_format: "polling",
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
var signedUrl = null;
|
|
201
|
+
while (!signedUrl) {
|
|
202
|
+
var exportResult = exportRes.result || exportRes;
|
|
203
|
+
if (exportResult.status === "complete" && exportResult.signed_url) {
|
|
204
|
+
signedUrl = exportResult.signed_url;
|
|
205
|
+
} else if (exportResult.status === "error") {
|
|
206
|
+
throw new Error(exportResult.error || "Unknown error.");
|
|
207
|
+
} else {
|
|
208
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
209
|
+
exportRes = await exportD1(cfg.accountId, cfg.apiToken, dbId, {
|
|
210
|
+
output_format: "polling",
|
|
211
|
+
current_bookmark: exportResult.at_bookmark,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Download
|
|
217
|
+
var dumpRes = await fetch(signedUrl);
|
|
218
|
+
if (!dumpRes.ok) {
|
|
219
|
+
throw new Error(`Download failed: ${dumpRes.status}`);
|
|
220
|
+
}
|
|
221
|
+
return dumpRes.text();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function rotateToken(cfg, appName, opts = {}) {
|
|
225
|
+
var subdomain = await getWorkersSubdomain(cfg.accountId, cfg.apiToken);
|
|
226
|
+
var connectionUrl = subdomain
|
|
227
|
+
? `https://relight-${appName}.${subdomain}.workers.dev`
|
|
228
|
+
: null;
|
|
229
|
+
|
|
230
|
+
var dbToken = randomBytes(32).toString("hex");
|
|
231
|
+
|
|
232
|
+
if (!opts.skipAppConfig) {
|
|
233
|
+
var appConfig = await getAppConfig(cfg, appName);
|
|
234
|
+
if (!appConfig || !appConfig.dbId) {
|
|
235
|
+
throw new Error(`App ${appName} does not have a database.`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!appConfig.envKeys) appConfig.envKeys = [];
|
|
239
|
+
if (!appConfig.secretKeys) appConfig.secretKeys = [];
|
|
240
|
+
if (!appConfig.env) appConfig.env = {};
|
|
241
|
+
|
|
242
|
+
appConfig.env["DB_TOKEN"] = "[hidden]";
|
|
243
|
+
if (!appConfig.secretKeys.includes("DB_TOKEN")) appConfig.secretKeys.push("DB_TOKEN");
|
|
244
|
+
appConfig.envKeys = appConfig.envKeys.filter((k) => k !== "DB_TOKEN");
|
|
245
|
+
|
|
246
|
+
if (connectionUrl) {
|
|
247
|
+
appConfig.env["DB_URL"] = connectionUrl;
|
|
248
|
+
if (!appConfig.envKeys.includes("DB_URL")) appConfig.envKeys.push("DB_URL");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
await pushAppConfig(cfg, appName, appConfig, { newSecrets: { DB_TOKEN: dbToken } });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { dbToken, connectionUrl };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function resetDatabase(cfg, appName, opts = {}) {
|
|
258
|
+
var dbId = opts.dbId;
|
|
259
|
+
if (!dbId) {
|
|
260
|
+
var appConfig = await getAppConfig(cfg, appName);
|
|
261
|
+
if (!appConfig || !appConfig.dbId) {
|
|
262
|
+
throw new Error(`App ${appName} does not have a database.`);
|
|
263
|
+
}
|
|
264
|
+
dbId = appConfig.dbId;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
var results = await queryD1(
|
|
268
|
+
cfg.accountId, cfg.apiToken, dbId,
|
|
269
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_cf_%'"
|
|
270
|
+
);
|
|
271
|
+
var result = Array.isArray(results) ? results[0] : results;
|
|
272
|
+
var tables = (result && result.results) ? result.results.map((r) => r.name) : [];
|
|
273
|
+
|
|
274
|
+
for (var t of tables) {
|
|
275
|
+
await queryD1(cfg.accountId, cfg.apiToken, dbId, `DROP TABLE IF EXISTS "${t}"`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return tables;
|
|
279
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listZones,
|
|
3
|
+
findZoneForHostname,
|
|
4
|
+
listDnsRecords,
|
|
5
|
+
createDnsRecord,
|
|
6
|
+
deleteDnsRecord,
|
|
7
|
+
addWorkerDomain,
|
|
8
|
+
removeWorkerDomain,
|
|
9
|
+
listWorkerDomainsForService,
|
|
10
|
+
getWorkersSubdomain,
|
|
11
|
+
} from "../../clouds/cf.js";
|
|
12
|
+
import { getAppConfig, pushAppConfig } from "./app.js";
|
|
13
|
+
|
|
14
|
+
export async function listDomains(cfg, appName) {
|
|
15
|
+
var scriptName = `relight-${appName}`;
|
|
16
|
+
var subdomain = await getWorkersSubdomain(cfg.accountId, cfg.apiToken);
|
|
17
|
+
var defaultDomain = subdomain
|
|
18
|
+
? `relight-${appName}.${subdomain}.workers.dev`
|
|
19
|
+
: null;
|
|
20
|
+
|
|
21
|
+
var domains = await listWorkerDomainsForService(cfg.accountId, cfg.apiToken, scriptName);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
default: defaultDomain,
|
|
25
|
+
custom: domains.map((d) => d.hostname),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function addDomain(cfg, appName, domain, { zone, zones }) {
|
|
30
|
+
var scriptName = `relight-${appName}`;
|
|
31
|
+
|
|
32
|
+
// Check for existing DNS records
|
|
33
|
+
var existing = await listDnsRecords(cfg.accountId, cfg.apiToken, zone.id, { name: domain });
|
|
34
|
+
if (existing.length > 0) {
|
|
35
|
+
var types = existing.map((r) => `${r.type} -> ${r.content}`).join("\n ");
|
|
36
|
+
throw new Error(
|
|
37
|
+
`DNS record already exists for ${domain}.\nExisting records:\n ${types}\n\nRemove the existing record first, or choose a different domain.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Attach domain to worker
|
|
42
|
+
try {
|
|
43
|
+
await addWorkerDomain(cfg.accountId, cfg.apiToken, scriptName, domain, zone.id);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
if (e.message.includes("already has externally managed DNS records")) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`DNS record already exists for ${domain} (externally managed). Remove the existing DNS record first, or choose a different domain.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Create CNAME record
|
|
54
|
+
var subdomain = await getWorkersSubdomain(cfg.accountId, cfg.apiToken);
|
|
55
|
+
var target = subdomain ? `relight-${appName}.${subdomain}.workers.dev` : null;
|
|
56
|
+
|
|
57
|
+
if (target) {
|
|
58
|
+
try {
|
|
59
|
+
await createDnsRecord(cfg.accountId, cfg.apiToken, zone.id, {
|
|
60
|
+
type: "CNAME",
|
|
61
|
+
name: domain,
|
|
62
|
+
content: target,
|
|
63
|
+
proxied: true,
|
|
64
|
+
});
|
|
65
|
+
} catch (e) {
|
|
66
|
+
if (!e.message.includes("already exists")) {
|
|
67
|
+
// Not fatal - worker domain is already attached
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Update app config metadata
|
|
73
|
+
var appConfig = await getAppConfig(cfg, appName);
|
|
74
|
+
if (appConfig) {
|
|
75
|
+
if (!appConfig.domains) appConfig.domains = [];
|
|
76
|
+
if (!appConfig.domains.includes(domain)) {
|
|
77
|
+
appConfig.domains.push(domain);
|
|
78
|
+
await pushAppConfig(cfg, appName, appConfig);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function removeDomain(cfg, appName, domain) {
|
|
84
|
+
// Remove Worker Domain route
|
|
85
|
+
await removeWorkerDomain(cfg.accountId, cfg.apiToken, domain);
|
|
86
|
+
|
|
87
|
+
// Remove CNAME record if it exists
|
|
88
|
+
var cfZones = await listZones(cfg.accountId, cfg.apiToken);
|
|
89
|
+
var zone = findZoneForHostname(cfZones, domain);
|
|
90
|
+
if (zone) {
|
|
91
|
+
var records = await listDnsRecords(cfg.accountId, cfg.apiToken, zone.id, {
|
|
92
|
+
type: "CNAME",
|
|
93
|
+
name: domain,
|
|
94
|
+
});
|
|
95
|
+
for (var record of records) {
|
|
96
|
+
await deleteDnsRecord(cfg.accountId, cfg.apiToken, zone.id, record.id);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Update app config metadata
|
|
101
|
+
var appConfig = await getAppConfig(cfg, appName);
|
|
102
|
+
if (appConfig) {
|
|
103
|
+
appConfig.domains = (appConfig.domains || []).filter((d) => d !== domain);
|
|
104
|
+
await pushAppConfig(cfg, appName, appConfig);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// --- Pure DNS record operations (for cross-cloud use) ---
|
|
109
|
+
|
|
110
|
+
export async function addDnsRecord(cfg, domain, target, zone) {
|
|
111
|
+
// Check for existing records
|
|
112
|
+
var existing = await listDnsRecords(cfg.accountId, cfg.apiToken, zone.id, { name: domain });
|
|
113
|
+
if (existing.length > 0) {
|
|
114
|
+
var types = existing.map((r) => `${r.type} -> ${r.content}`).join("\n ");
|
|
115
|
+
throw new Error(
|
|
116
|
+
`DNS record already exists for ${domain}.\nExisting records:\n ${types}\n\nRemove the existing record first, or choose a different domain.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Create CNAME: domain -> target, proxied
|
|
121
|
+
await createDnsRecord(cfg.accountId, cfg.apiToken, zone.id, {
|
|
122
|
+
type: "CNAME",
|
|
123
|
+
name: domain,
|
|
124
|
+
content: target,
|
|
125
|
+
proxied: true,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function removeDnsRecord(cfg, domain) {
|
|
130
|
+
var cfZones = await listZones(cfg.accountId, cfg.apiToken);
|
|
131
|
+
var zone = findZoneForHostname(cfZones, domain);
|
|
132
|
+
if (!zone) return;
|
|
133
|
+
|
|
134
|
+
var records = await listDnsRecords(cfg.accountId, cfg.apiToken, zone.id, {
|
|
135
|
+
type: "CNAME",
|
|
136
|
+
name: domain,
|
|
137
|
+
});
|
|
138
|
+
for (var record of records) {
|
|
139
|
+
await deleteDnsRecord(cfg.accountId, cfg.apiToken, zone.id, record.id);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Re-export zone utilities for the domains command's interactive flow
|
|
144
|
+
export async function getZones(cfg) {
|
|
145
|
+
return listZones(cfg.accountId, cfg.apiToken);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { findZoneForHostname };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CF_REGISTRY,
|
|
3
|
+
getRegistryCredentials,
|
|
4
|
+
} from "../../clouds/cf.js";
|
|
5
|
+
|
|
6
|
+
export async function getCredentials(cfg) {
|
|
7
|
+
var creds = await getRegistryCredentials(cfg.accountId, cfg.apiToken);
|
|
8
|
+
return {
|
|
9
|
+
registry: CF_REGISTRY,
|
|
10
|
+
username: creds.username,
|
|
11
|
+
password: creds.password,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getImageTag(cfg, appName, tag) {
|
|
16
|
+
return `${CF_REGISTRY}/${cfg.accountId}/relight-${appName}:${tag}`;
|
|
17
|
+
}
|