deepspace 0.5.1 → 0.5.2
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/dist/cli.js +1274 -1997
- package/dist/cli.js.map +1 -1
- package/dist/index.js +446 -539
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +17 -8
- package/dist/server.js +16 -3
- package/dist/server.js.map +1 -1
- package/dist/testing.d.ts +115 -0
- package/dist/worker.d.ts +13 -6
- package/dist/worker.js +15 -2
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/cli.ts
|
|
4
|
-
import { defineCommand as
|
|
4
|
+
import { defineCommand as defineCommand22, runMain } from "citty";
|
|
5
5
|
import { execSync as execSync4 } from "child_process";
|
|
6
|
-
import { readFileSync as readFileSync14, existsSync as
|
|
7
|
-
import { dirname as dirname4, join as
|
|
6
|
+
import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
|
|
7
|
+
import { dirname as dirname4, join as join16 } from "path";
|
|
8
8
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9
9
|
|
|
10
10
|
// src/cli/lib/cli-errors.ts
|
|
@@ -17,9 +17,10 @@ var API_ERROR_HINTS = {
|
|
|
17
17
|
};
|
|
18
18
|
function formatCliError(err) {
|
|
19
19
|
const message = err instanceof Error ? err.message : String(err);
|
|
20
|
-
const
|
|
20
|
+
const code = err?.code;
|
|
21
|
+
const slug = typeof code === "string" ? code : /^API \S+ \(\d+\): ([a-z0-9_]+)$/.exec(message)?.[1];
|
|
21
22
|
const hint = slug ? API_ERROR_HINTS[slug] : void 0;
|
|
22
|
-
return hint ? `${message}
|
|
23
|
+
return hint && !message.includes(hint) ? `${message}
|
|
23
24
|
${hint}` : message;
|
|
24
25
|
}
|
|
25
26
|
function renderCliError(err) {
|
|
@@ -50,8 +51,8 @@ function wrapCommandErrors(cmd) {
|
|
|
50
51
|
|
|
51
52
|
// src/cli/commands/login.ts
|
|
52
53
|
import { defineCommand } from "citty";
|
|
53
|
-
import { writeFileSync as
|
|
54
|
-
import { join as
|
|
54
|
+
import { writeFileSync as writeFileSync5, mkdirSync } from "fs";
|
|
55
|
+
import { join as join3 } from "path";
|
|
55
56
|
import { homedir } from "os";
|
|
56
57
|
import { randomBytes, createHash } from "crypto";
|
|
57
58
|
import * as p2 from "@clack/prompts";
|
|
@@ -74,12 +75,288 @@ function createSpinner() {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
// src/cli/env.ts
|
|
77
|
-
import { existsSync as
|
|
78
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
79
|
+
|
|
80
|
+
// src/cli/lib/secrets.ts
|
|
81
|
+
var GENERATED_SECRETS_DIVIDER = "# --- DeepSpace secrets cache: generated by `npx deepspace secrets`; do not edit manually ---";
|
|
82
|
+
var SECRET_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
83
|
+
var CONFIG_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
84
|
+
var MAX_SECRET_VALUE_BYTES = 32 * 1024;
|
|
85
|
+
function validateConfigName(configName) {
|
|
86
|
+
if (!CONFIG_NAME_RE.test(configName)) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Invalid config "${configName}". Use letters, numbers, underscores, or hyphens; start with a letter or number.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return configName;
|
|
92
|
+
}
|
|
93
|
+
function validateSecretName(name) {
|
|
94
|
+
if (!SECRET_NAME_RE.test(name)) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Invalid secret name "${name}". Use letters, numbers, and underscores; start with a letter or underscore.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return name;
|
|
100
|
+
}
|
|
101
|
+
function validateSecretValue(key, value) {
|
|
102
|
+
if (new TextEncoder().encode(value).byteLength > MAX_SECRET_VALUE_BYTES) {
|
|
103
|
+
throw new Error(`Secret "${key}" exceeds ${MAX_SECRET_VALUE_BYTES} bytes`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function defaultConfigNameForEnv(wranglerEnv) {
|
|
107
|
+
return wranglerEnv ?? "prd";
|
|
108
|
+
}
|
|
109
|
+
async function secretsApi(deployUrl, token, path, init = {}) {
|
|
110
|
+
const res = await fetch(`${deployUrl}/api/secrets${path}`, {
|
|
111
|
+
...init,
|
|
112
|
+
headers: {
|
|
113
|
+
Authorization: `Bearer ${token}`,
|
|
114
|
+
"Content-Type": "application/json",
|
|
115
|
+
...init.headers ?? {}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const body = await res.json().catch(() => ({}));
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
throw new Error(`Secrets API ${path} (${res.status}): ${body.error ?? "request failed"}`);
|
|
121
|
+
}
|
|
122
|
+
return body;
|
|
123
|
+
}
|
|
124
|
+
function listSecrets(deployUrl, token, appId, configName) {
|
|
125
|
+
return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}`);
|
|
126
|
+
}
|
|
127
|
+
function fetchSecretsValues(deployUrl, token, appId, configName) {
|
|
128
|
+
return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}/values`);
|
|
129
|
+
}
|
|
130
|
+
function getSecretPlain(deployUrl, token, appId, configName, key) {
|
|
131
|
+
return secretsApi(
|
|
132
|
+
deployUrl,
|
|
133
|
+
token,
|
|
134
|
+
`/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}?plain=true`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
function setSecret(deployUrl, token, appId, configName, key, value) {
|
|
138
|
+
return secretsApi(
|
|
139
|
+
deployUrl,
|
|
140
|
+
token,
|
|
141
|
+
`/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}`,
|
|
142
|
+
{ method: "PUT", body: JSON.stringify({ value }) }
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
function deleteSecret(deployUrl, token, appId, configName, key) {
|
|
146
|
+
return secretsApi(
|
|
147
|
+
deployUrl,
|
|
148
|
+
token,
|
|
149
|
+
`/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}`,
|
|
150
|
+
{ method: "DELETE" }
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
function uploadSecrets(deployUrl, token, appId, configName, secrets, replace) {
|
|
154
|
+
return secretsApi(
|
|
155
|
+
deployUrl,
|
|
156
|
+
token,
|
|
157
|
+
`/${appId}/configs/${encodeURIComponent(configName)}/secrets`,
|
|
158
|
+
{ method: "PUT", body: JSON.stringify({ secrets, replace }) }
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
function listConfigs(deployUrl, token, appId) {
|
|
162
|
+
return secretsApi(deployUrl, token, `/${appId}/configs`);
|
|
163
|
+
}
|
|
164
|
+
function createConfig(deployUrl, token, appId, configName, copyFrom) {
|
|
165
|
+
return secretsApi(deployUrl, token, `/${appId}/configs`, {
|
|
166
|
+
method: "POST",
|
|
167
|
+
body: JSON.stringify({ name: configName, ...copyFrom ? { copyFrom } : {} })
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function deleteConfig(deployUrl, token, appId, configName) {
|
|
171
|
+
return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}`, {
|
|
172
|
+
method: "DELETE"
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async function pullAppSecretsCache(deployUrl, token, appId, configName) {
|
|
176
|
+
try {
|
|
177
|
+
const { secrets } = await fetchSecretsValues(deployUrl, token, appId, configName);
|
|
178
|
+
return { appId, configName, values: secrets };
|
|
179
|
+
} catch (err) {
|
|
180
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
181
|
+
if (msg.includes("(404)")) return null;
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function quoteDotenvValue(value) {
|
|
186
|
+
if (/^[A-Za-z0-9_./:@+-]*$/.test(value) && value !== "") return value;
|
|
187
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
188
|
+
return `"${escaped}"`;
|
|
189
|
+
}
|
|
190
|
+
function renderSecretsCache(secrets, source) {
|
|
191
|
+
const keys = Object.keys(secrets).sort();
|
|
192
|
+
const lines = [
|
|
193
|
+
GENERATED_SECRETS_DIVIDER,
|
|
194
|
+
`# app ${source.appId} \xB7 config ${source.configName}`,
|
|
195
|
+
"# Use `npx deepspace secrets` to change these values."
|
|
196
|
+
];
|
|
197
|
+
for (const key of keys) {
|
|
198
|
+
validateSecretValue(key, secrets[key]);
|
|
199
|
+
lines.push(`${key}=${quoteDotenvValue(secrets[key])}`);
|
|
200
|
+
}
|
|
201
|
+
return lines.join("\n");
|
|
202
|
+
}
|
|
203
|
+
function stripGeneratedSecretsCache(content) {
|
|
204
|
+
const idx = content.indexOf(GENERATED_SECRETS_DIVIDER);
|
|
205
|
+
if (idx === -1) return content;
|
|
206
|
+
return content.slice(0, idx).trimEnd() + "\n";
|
|
207
|
+
}
|
|
208
|
+
function parseSecretsUpload(content) {
|
|
209
|
+
const trimmed = content.trim();
|
|
210
|
+
if (!trimmed) return {};
|
|
211
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
212
|
+
const parsed = JSON.parse(trimmed);
|
|
213
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
214
|
+
throw new Error("JSON upload must be an object of KEY=value strings.");
|
|
215
|
+
}
|
|
216
|
+
const out2 = {};
|
|
217
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
218
|
+
validateSecretName(key);
|
|
219
|
+
if (typeof value !== "string") throw new Error(`Secret "${key}" must be a string`);
|
|
220
|
+
validateSecretValue(key, value);
|
|
221
|
+
out2[key] = value;
|
|
222
|
+
}
|
|
223
|
+
return out2;
|
|
224
|
+
}
|
|
225
|
+
const out = {};
|
|
226
|
+
for (const rawLine of trimmed.split("\n")) {
|
|
227
|
+
const line = rawLine.trim();
|
|
228
|
+
if (!line || line.startsWith("#")) continue;
|
|
229
|
+
const eq = line.indexOf("=");
|
|
230
|
+
if (eq <= 0) continue;
|
|
231
|
+
const key = line.slice(0, eq).trim();
|
|
232
|
+
if (!SECRET_NAME_RE.test(key)) continue;
|
|
233
|
+
let value = line.slice(eq + 1).trim();
|
|
234
|
+
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
235
|
+
value = unescapeDotenv(value.slice(1, -1));
|
|
236
|
+
}
|
|
237
|
+
validateSecretValue(key, value);
|
|
238
|
+
out[key] = value;
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
function unescapeDotenv(v) {
|
|
243
|
+
let out = "";
|
|
244
|
+
for (let i = 0; i < v.length; i++) {
|
|
245
|
+
if (v[i] === "\\" && i + 1 < v.length) {
|
|
246
|
+
const c = v[++i];
|
|
247
|
+
out += c === "n" ? "\n" : c === "r" ? "\r" : c;
|
|
248
|
+
} else {
|
|
249
|
+
out += v[i];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
function formatSecretsDownload(secrets, format) {
|
|
255
|
+
const entries = Object.entries(secrets).sort(([a], [b]) => a.localeCompare(b));
|
|
256
|
+
if (format === "json") {
|
|
257
|
+
return JSON.stringify(Object.fromEntries(entries), null, 2) + "\n";
|
|
258
|
+
}
|
|
259
|
+
if (format === "shell") {
|
|
260
|
+
return entries.map(([k, v]) => `export ${k}=${quoteDotenvValue(v)}`).join("\n") + "\n";
|
|
261
|
+
}
|
|
262
|
+
return entries.map(([k, v]) => `${k}=${quoteDotenvValue(v)}`).join("\n") + "\n";
|
|
263
|
+
}
|
|
264
|
+
async function refreshSecretsCache(deployUrl, token, appId, wranglerEnv) {
|
|
265
|
+
const configName = defaultConfigNameForEnv(wranglerEnv);
|
|
266
|
+
const pulled = await pullAppSecretsCache(deployUrl, token, appId, configName);
|
|
267
|
+
if (!pulled) return null;
|
|
268
|
+
const count = Object.keys(pulled.values).length;
|
|
269
|
+
return {
|
|
270
|
+
pulled,
|
|
271
|
+
rendered: renderSecretsCache(pulled.values, pulled),
|
|
272
|
+
summary: `Secrets: ${pulled.configName} (${count} secret${count === 1 ? "" : "s"})`
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/cli/lib/app-identity.ts
|
|
277
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
278
|
+
import { join, resolve } from "path";
|
|
279
|
+
import { parse as parseToml } from "smol-toml";
|
|
280
|
+
var APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
281
|
+
var CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
282
|
+
function mintAppId(now = Date.now()) {
|
|
283
|
+
let ts = "";
|
|
284
|
+
let t = now;
|
|
285
|
+
for (let i = 0; i < 10; i++) {
|
|
286
|
+
ts = CROCKFORD[t % 32] + ts;
|
|
287
|
+
t = Math.floor(t / 32);
|
|
288
|
+
}
|
|
289
|
+
const rand = new Uint8Array(10);
|
|
290
|
+
crypto.getRandomValues(rand);
|
|
291
|
+
let rs = "";
|
|
292
|
+
let acc = 0;
|
|
293
|
+
let bits = 0;
|
|
294
|
+
for (const byte of rand) {
|
|
295
|
+
acc = acc << 8 | byte;
|
|
296
|
+
bits += 8;
|
|
297
|
+
while (bits >= 5) {
|
|
298
|
+
bits -= 5;
|
|
299
|
+
rs += CROCKFORD[acc >> bits & 31];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return `app_${ts}${rs}`.slice(0, 30);
|
|
303
|
+
}
|
|
304
|
+
function readAppId(cwd = process.cwd(), wranglerEnv) {
|
|
305
|
+
const wranglerPath = join(resolve(cwd), "wrangler.toml");
|
|
306
|
+
if (!existsSync(wranglerPath)) return null;
|
|
307
|
+
let cfg;
|
|
308
|
+
try {
|
|
309
|
+
cfg = parseToml(readFileSync(wranglerPath, "utf-8"));
|
|
310
|
+
} catch (err) {
|
|
311
|
+
throw new Error(
|
|
312
|
+
`Could not parse ${wranglerPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
const vars = wranglerEnv ? cfg.env?.[wranglerEnv]?.vars : cfg.vars;
|
|
316
|
+
const id = vars?.DEEPSPACE_APP_ID;
|
|
317
|
+
return typeof id === "string" && APP_ID_RE.test(id) ? id : null;
|
|
318
|
+
}
|
|
319
|
+
function writeAppId(cwd, appId, opts = {}) {
|
|
320
|
+
const wranglerPath = join(resolve(cwd), "wrangler.toml");
|
|
321
|
+
if (!existsSync(wranglerPath)) {
|
|
322
|
+
throw new Error(`No wrangler.toml in ${resolve(cwd)}`);
|
|
323
|
+
}
|
|
324
|
+
const existing = readAppId(cwd, opts.wranglerEnv);
|
|
325
|
+
if (existing && !opts.force) {
|
|
326
|
+
if (existing === appId) return;
|
|
327
|
+
throw new Error(
|
|
328
|
+
`wrangler.toml already carries ${existing}. The app id is immutable \u2014 use --new-id only to fork this repo as a separate app.`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
let src = readFileSync(wranglerPath, "utf-8");
|
|
332
|
+
const header = opts.wranglerEnv ? `[env.${opts.wranglerEnv}.vars]` : "[vars]";
|
|
333
|
+
const line = `DEEPSPACE_APP_ID = "${appId}"`;
|
|
334
|
+
const headerRe = new RegExp(`^\\${header.replace(/\./g, "\\.")}[ \\t]*$`, "m");
|
|
335
|
+
const idLineRe = /^DEEPSPACE_APP_ID\s*=.*$/m;
|
|
336
|
+
const match = headerRe.exec(src);
|
|
337
|
+
if (match) {
|
|
338
|
+
const blockStart = match.index + match[0].length;
|
|
339
|
+
const rest = src.slice(blockStart);
|
|
340
|
+
const nextSection = rest.search(/^\s*\[/m);
|
|
341
|
+
const blockEnd = nextSection === -1 ? src.length : blockStart + nextSection;
|
|
342
|
+
const block = src.slice(blockStart, blockEnd);
|
|
343
|
+
const newBlock = idLineRe.test(block) ? block.replace(idLineRe, line) : `
|
|
344
|
+
${line}${block}`;
|
|
345
|
+
src = src.slice(0, blockStart) + newBlock + src.slice(blockEnd);
|
|
346
|
+
} else {
|
|
347
|
+
src = src.trimEnd() + `
|
|
348
|
+
|
|
349
|
+
${header}
|
|
350
|
+
${line}
|
|
351
|
+
`;
|
|
352
|
+
}
|
|
353
|
+
writeFileSync(wranglerPath, src);
|
|
354
|
+
}
|
|
78
355
|
|
|
79
356
|
// src/cli/lib/secure-file.ts
|
|
80
|
-
import { chmodSync, writeFileSync } from "fs";
|
|
357
|
+
import { chmodSync, writeFileSync as writeFileSync2 } from "fs";
|
|
81
358
|
function writeSecretFileSync(path, content) {
|
|
82
|
-
|
|
359
|
+
writeFileSync2(path, content, { mode: 384 });
|
|
83
360
|
try {
|
|
84
361
|
chmodSync(path, 384);
|
|
85
362
|
} catch {
|
|
@@ -87,9 +364,9 @@ function writeSecretFileSync(path, content) {
|
|
|
87
364
|
}
|
|
88
365
|
|
|
89
366
|
// src/cli/lib/wrangler-env.ts
|
|
90
|
-
import { existsSync, readFileSync, rmSync, writeFileSync as
|
|
91
|
-
import { join } from "path";
|
|
92
|
-
import { parse as
|
|
367
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
368
|
+
import { join as join2 } from "path";
|
|
369
|
+
import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
|
|
93
370
|
|
|
94
371
|
// src/server/rooms/app-name.ts
|
|
95
372
|
var APP_NAME_RULES = {
|
|
@@ -187,35 +464,35 @@ function resolveAppNameForEnv(config, envName) {
|
|
|
187
464
|
}
|
|
188
465
|
return resolveAppName(envBlock.name);
|
|
189
466
|
}
|
|
190
|
-
function devVarsPathFor(
|
|
191
|
-
if (opts.sharedDevVarsCache || !envName) return
|
|
192
|
-
return
|
|
467
|
+
function devVarsPathFor(appDir, envName, opts = {}) {
|
|
468
|
+
if (opts.sharedDevVarsCache || !envName) return join2(appDir, ".dev.vars");
|
|
469
|
+
return join2(appDir, `.dev.vars.${envName}`);
|
|
193
470
|
}
|
|
194
|
-
function legacyEnvDevVarsPathFor(
|
|
195
|
-
return envName ?
|
|
471
|
+
function legacyEnvDevVarsPathFor(appDir, envName) {
|
|
472
|
+
return envName ? join2(appDir, `.dev.vars.${envName}`) : void 0;
|
|
196
473
|
}
|
|
197
|
-
function warnIfLegacyEnvDevVarsExists(
|
|
198
|
-
const legacyPath = legacyEnvDevVarsPathFor(
|
|
199
|
-
if (!legacyPath || !
|
|
474
|
+
function warnIfLegacyEnvDevVarsExists(appDir, envName, warn = console.warn) {
|
|
475
|
+
const legacyPath = legacyEnvDevVarsPathFor(appDir, envName);
|
|
476
|
+
if (!legacyPath || !existsSync2(legacyPath)) return;
|
|
200
477
|
warn(
|
|
201
|
-
`Warning: ${legacyPath} exists, but DeepSpace now uses ${devVarsPathFor(
|
|
478
|
+
`Warning: ${legacyPath} exists, but DeepSpace now uses ${devVarsPathFor(appDir, envName, { sharedDevVarsCache: true })} for every environment. This run will ignore ${legacyPath}; move any secrets you still need into .dev.vars, then remove the env-specific file when you're ready.`
|
|
202
479
|
);
|
|
203
480
|
}
|
|
204
|
-
function prepareWranglerEnvConfig(
|
|
481
|
+
function prepareWranglerEnvConfig(appDir, envName, opts = {}) {
|
|
205
482
|
if (!envName) return { cleanup() {
|
|
206
483
|
} };
|
|
207
484
|
if (!opts.sharedDevVarsCache) return { wranglerEnv: envName, cleanup() {
|
|
208
485
|
} };
|
|
209
|
-
warnIfLegacyEnvDevVarsExists(
|
|
210
|
-
const config = readWranglerConfig(
|
|
486
|
+
warnIfLegacyEnvDevVarsExists(appDir, envName, opts.warn);
|
|
487
|
+
const config = readWranglerConfig(appDir);
|
|
211
488
|
const resolved = resolveAppNameForEnv(config, envName);
|
|
212
489
|
if (!resolved.ok) throw new Error(resolved.reason);
|
|
213
490
|
const flattened = flattenWranglerEnvConfig(config, envName);
|
|
214
|
-
const configPath =
|
|
215
|
-
|
|
491
|
+
const configPath = join2(
|
|
492
|
+
appDir,
|
|
216
493
|
`.wrangler.deepspace.${safeGeneratedConfigSegment(envName)}.${process.pid}.${++generatedConfigSeq}.toml`
|
|
217
494
|
);
|
|
218
|
-
|
|
495
|
+
writeFileSync3(
|
|
219
496
|
configPath,
|
|
220
497
|
[
|
|
221
498
|
"# Generated by DeepSpace for this CLI run.",
|
|
@@ -271,17 +548,17 @@ var WranglerConfigError = class extends Error {
|
|
|
271
548
|
this.name = "WranglerConfigError";
|
|
272
549
|
}
|
|
273
550
|
};
|
|
274
|
-
function readWranglerConfig(
|
|
275
|
-
const path =
|
|
276
|
-
const raw =
|
|
551
|
+
function readWranglerConfig(appDir) {
|
|
552
|
+
const path = join2(appDir, "wrangler.toml");
|
|
553
|
+
const raw = readFileSync2(path, "utf-8");
|
|
277
554
|
try {
|
|
278
|
-
return
|
|
555
|
+
return parseToml2(raw);
|
|
279
556
|
} catch (err) {
|
|
280
557
|
throw new WranglerConfigError(path, err);
|
|
281
558
|
}
|
|
282
559
|
}
|
|
283
|
-
function hasWranglerConfig(
|
|
284
|
-
return
|
|
560
|
+
function hasWranglerConfig(appDir) {
|
|
561
|
+
return existsSync2(join2(appDir, "wrangler.toml"));
|
|
285
562
|
}
|
|
286
563
|
|
|
287
564
|
// src/cli/env.ts
|
|
@@ -303,21 +580,12 @@ async function fetchPublicKey(authUrl) {
|
|
|
303
580
|
}
|
|
304
581
|
return data.publicKey;
|
|
305
582
|
}
|
|
306
|
-
function
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (!resolved.ok) throw new Error(resolved.reason);
|
|
313
|
-
const rawName = wranglerEnv2 ? config.env?.[wranglerEnv2]?.name : config.name;
|
|
314
|
-
if (rawName !== resolved.name) {
|
|
315
|
-
const where = wranglerEnv2 ? `[env.${wranglerEnv2}].name` : "`name`";
|
|
316
|
-
throw new Error(
|
|
317
|
-
`wrangler.toml: ${where} "${rawName}" is not in canonical form. Update it to "${resolved.name}" and re-run.`
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
return resolved.name;
|
|
583
|
+
function requireAppIdFor(appDir, wranglerEnv) {
|
|
584
|
+
const id = readAppId(appDir, wranglerEnv);
|
|
585
|
+
if (id) return id;
|
|
586
|
+
throw new Error(
|
|
587
|
+
"wrangler.toml has no DEEPSPACE_APP_ID" + (wranglerEnv ? ` for [env.${wranglerEnv}]` : "") + ". Run `deepspace init` (or `deepspace init --env <name>`) and retry."
|
|
588
|
+
);
|
|
321
589
|
}
|
|
322
590
|
async function mintAppOwnerJwt(authUrl, callerJwt, appName) {
|
|
323
591
|
const res = await fetch(`${authUrl}/api/auth/mint-app-token`, {
|
|
@@ -326,7 +594,7 @@ async function mintAppOwnerJwt(authUrl, callerJwt, appName) {
|
|
|
326
594
|
"Content-Type": "application/json",
|
|
327
595
|
Authorization: `Bearer ${callerJwt}`
|
|
328
596
|
},
|
|
329
|
-
body: JSON.stringify({ appName })
|
|
597
|
+
body: JSON.stringify({ appId: appName })
|
|
330
598
|
});
|
|
331
599
|
if (!res.ok) {
|
|
332
600
|
const err = await res.text().catch(() => "");
|
|
@@ -340,6 +608,7 @@ async function mintAppOwnerJwt(authUrl, callerJwt, appName) {
|
|
|
340
608
|
}
|
|
341
609
|
async function fetchAppIdentityToken(deployUrl, callerJwt, appName) {
|
|
342
610
|
const res = await fetch(`${deployUrl}/api/apps/${encodeURIComponent(appName)}/identity-token`, {
|
|
611
|
+
// appName carries the appId
|
|
343
612
|
method: "POST",
|
|
344
613
|
headers: { Authorization: `Bearer ${callerJwt}` }
|
|
345
614
|
});
|
|
@@ -368,7 +637,6 @@ var SDK_MANAGED_KEYS = /* @__PURE__ */ new Set([
|
|
|
368
637
|
"ALLOW_DEBUG_ROUTES"
|
|
369
638
|
]);
|
|
370
639
|
var DEV_VARS_DIVIDER = "# --- not managed by the SDK; preserved across dev/test runs ---";
|
|
371
|
-
var GENERATED_SECRETS_DIVIDER = "# --- DeepSpace secrets cache: generated by `npx deepspace secrets`; do not edit manually ---";
|
|
372
640
|
function extractCustomDevVars(content) {
|
|
373
641
|
const lines = content.split("\n");
|
|
374
642
|
const out = [];
|
|
@@ -409,11 +677,6 @@ function extractCustomDevVars(content) {
|
|
|
409
677
|
while (out.length && out[out.length - 1].trim() === "") out.pop();
|
|
410
678
|
return out.join("\n");
|
|
411
679
|
}
|
|
412
|
-
function stripGeneratedSecretsCache(content) {
|
|
413
|
-
const idx = content.indexOf(GENERATED_SECRETS_DIVIDER);
|
|
414
|
-
if (idx < 0) return content;
|
|
415
|
-
return content.slice(0, idx).replace(/\s+$/u, "");
|
|
416
|
-
}
|
|
417
680
|
function removeDevVarsKeys(content, keys) {
|
|
418
681
|
const lines = content.split("\n");
|
|
419
682
|
const out = [];
|
|
@@ -529,12 +792,12 @@ function unescapeQuotedDevVar(value) {
|
|
|
529
792
|
}
|
|
530
793
|
return out;
|
|
531
794
|
}
|
|
532
|
-
async function writeDevVars(
|
|
795
|
+
async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {}) {
|
|
533
796
|
const urls = PLATFORM_URLS;
|
|
534
797
|
const publicKey = await fetchPublicKey(urls.auth);
|
|
535
|
-
const
|
|
536
|
-
const appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt,
|
|
537
|
-
const appIdentityToken = await fetchAppIdentityToken(urls.deploy, callerJwt,
|
|
798
|
+
const appId = opts.appId ?? requireAppIdFor(appDir, wranglerEnv);
|
|
799
|
+
const appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt, appId);
|
|
800
|
+
const appIdentityToken = await fetchAppIdentityToken(urls.deploy, callerJwt, appId);
|
|
538
801
|
const sdkVars = [
|
|
539
802
|
`AUTH_JWT_PUBLIC_KEY="${publicKey}"`,
|
|
540
803
|
`AUTH_JWT_ISSUER=${urls.auth}/api/auth`,
|
|
@@ -549,20 +812,27 @@ async function writeDevVars(appDir2, ownerId, callerJwt, wranglerEnv2, opts = {}
|
|
|
549
812
|
`ALLOW_DEBUG_ROUTES=true`
|
|
550
813
|
].join("\n");
|
|
551
814
|
const useSharedDevVarsCache = opts.sharedDevVarsCache ?? opts.generatedSecretsCache !== void 0;
|
|
552
|
-
const devVarsPath = devVarsPathFor(
|
|
815
|
+
const devVarsPath = devVarsPathFor(appDir, wranglerEnv, {
|
|
553
816
|
sharedDevVarsCache: useSharedDevVarsCache
|
|
554
817
|
});
|
|
555
|
-
const existing =
|
|
556
|
-
const custom = opts.generatedSecretsCache ?? extractCustomDevVars(stripGeneratedSecretsCache(existing));
|
|
557
|
-
|
|
818
|
+
const existing = existsSync3(devVarsPath) ? readFileSync3(devVarsPath, "utf-8") : "";
|
|
819
|
+
const custom = opts.generatedSecretsCache ?? extractCustomDevVars(stripGeneratedSecretsCache(existing).trimEnd());
|
|
820
|
+
let body;
|
|
821
|
+
if (!custom) {
|
|
822
|
+
body = `${sdkVars}
|
|
823
|
+
`;
|
|
824
|
+
} else if (opts.generatedSecretsCache) {
|
|
825
|
+
body = `${sdkVars}
|
|
558
826
|
|
|
559
827
|
${custom}
|
|
560
|
-
|
|
828
|
+
`;
|
|
829
|
+
} else {
|
|
830
|
+
body = `${sdkVars}
|
|
561
831
|
|
|
562
832
|
${DEV_VARS_DIVIDER}
|
|
563
833
|
${custom}
|
|
564
|
-
` : `${sdkVars}
|
|
565
834
|
`;
|
|
835
|
+
}
|
|
566
836
|
writeSecretFileSync(devVarsPath, body);
|
|
567
837
|
}
|
|
568
838
|
|
|
@@ -688,13 +958,13 @@ async function pollForCompletion(sessionId, codeVerifier) {
|
|
|
688
958
|
return null;
|
|
689
959
|
}
|
|
690
960
|
function storeCredentials(sessionToken, jwt) {
|
|
691
|
-
const dir =
|
|
961
|
+
const dir = join3(homedir(), ".deepspace");
|
|
692
962
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
693
|
-
|
|
694
|
-
|
|
963
|
+
writeFileSync5(join3(dir, "session"), sessionToken, { mode: 384 });
|
|
964
|
+
writeFileSync5(join3(dir, "token"), jwt, { mode: 384 });
|
|
695
965
|
}
|
|
696
966
|
function sleep(ms) {
|
|
697
|
-
return new Promise((
|
|
967
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
698
968
|
}
|
|
699
969
|
function base64url(bytes) {
|
|
700
970
|
return bytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
@@ -725,20 +995,20 @@ async function doEmailLogin(email, password) {
|
|
|
725
995
|
|
|
726
996
|
// src/cli/commands/logout.ts
|
|
727
997
|
import { defineCommand as defineCommand2 } from "citty";
|
|
728
|
-
import { existsSync as
|
|
729
|
-
import { join as
|
|
998
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, rmSync as rmSync2 } from "fs";
|
|
999
|
+
import { join as join4 } from "path";
|
|
730
1000
|
import { homedir as homedir2 } from "os";
|
|
731
1001
|
var AUTH_URL2 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
|
|
732
|
-
var DIR =
|
|
733
|
-
var SESSION_PATH =
|
|
734
|
-
var TOKEN_PATH =
|
|
1002
|
+
var DIR = join4(homedir2(), ".deepspace");
|
|
1003
|
+
var SESSION_PATH = join4(DIR, "session");
|
|
1004
|
+
var TOKEN_PATH = join4(DIR, "token");
|
|
735
1005
|
var logout_default = defineCommand2({
|
|
736
1006
|
meta: {
|
|
737
1007
|
name: "logout",
|
|
738
1008
|
description: "Sign out and remove cached credentials"
|
|
739
1009
|
},
|
|
740
1010
|
async run() {
|
|
741
|
-
const sessionToken =
|
|
1011
|
+
const sessionToken = existsSync4(SESSION_PATH) ? readFileSync4(SESSION_PATH, "utf-8").trim() : null;
|
|
742
1012
|
if (sessionToken) {
|
|
743
1013
|
try {
|
|
744
1014
|
await fetch(`${AUTH_URL2}/api/auth/sign-out`, {
|
|
@@ -753,7 +1023,7 @@ var logout_default = defineCommand2({
|
|
|
753
1023
|
}
|
|
754
1024
|
let removed = 0;
|
|
755
1025
|
for (const path of [SESSION_PATH, TOKEN_PATH]) {
|
|
756
|
-
if (
|
|
1026
|
+
if (existsSync4(path)) {
|
|
757
1027
|
rmSync2(path);
|
|
758
1028
|
removed++;
|
|
759
1029
|
}
|
|
@@ -772,8 +1042,8 @@ import { resolve as resolve4, basename, join as join9 } from "path";
|
|
|
772
1042
|
import spawn from "cross-spawn";
|
|
773
1043
|
|
|
774
1044
|
// src/cli/auth.ts
|
|
775
|
-
import { readFileSync as
|
|
776
|
-
import { join as
|
|
1045
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync6, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
1046
|
+
import { join as join5 } from "path";
|
|
777
1047
|
import { homedir as homedir3 } from "os";
|
|
778
1048
|
|
|
779
1049
|
// src/cli/jwt.ts
|
|
@@ -787,26 +1057,26 @@ function decodeJwtPayload(token) {
|
|
|
787
1057
|
|
|
788
1058
|
// src/cli/auth.ts
|
|
789
1059
|
var AUTH_URL3 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
|
|
790
|
-
var DIR2 =
|
|
791
|
-
var SESSION_PATH2 =
|
|
792
|
-
var TOKEN_PATH2 =
|
|
1060
|
+
var DIR2 = join5(homedir3(), ".deepspace");
|
|
1061
|
+
var SESSION_PATH2 = join5(DIR2, "session");
|
|
1062
|
+
var TOKEN_PATH2 = join5(DIR2, "token");
|
|
793
1063
|
async function ensureToken() {
|
|
794
|
-
if (
|
|
795
|
-
const existing =
|
|
1064
|
+
if (existsSync5(TOKEN_PATH2)) {
|
|
1065
|
+
const existing = readFileSync5(TOKEN_PATH2, "utf-8").trim();
|
|
796
1066
|
if (isTokenValid(existing)) {
|
|
797
1067
|
return existing;
|
|
798
1068
|
}
|
|
799
1069
|
}
|
|
800
|
-
if (!
|
|
1070
|
+
if (!existsSync5(SESSION_PATH2)) {
|
|
801
1071
|
throw new Error("Not logged in. Run `deepspace login` first.");
|
|
802
1072
|
}
|
|
803
|
-
const sessionToken =
|
|
1073
|
+
const sessionToken = readFileSync5(SESSION_PATH2, "utf-8").trim();
|
|
804
1074
|
const token = await exchangeSession(AUTH_URL3, sessionToken);
|
|
805
1075
|
if (!token) {
|
|
806
1076
|
throw new Error("Session expired. Run `deepspace login` to re-authenticate.");
|
|
807
1077
|
}
|
|
808
1078
|
mkdirSync2(DIR2, { recursive: true, mode: 448 });
|
|
809
|
-
|
|
1079
|
+
writeFileSync6(TOKEN_PATH2, token, { mode: 384 });
|
|
810
1080
|
return token;
|
|
811
1081
|
}
|
|
812
1082
|
function isTokenValid(jwt) {
|
|
@@ -820,24 +1090,26 @@ function isTokenValid(jwt) {
|
|
|
820
1090
|
|
|
821
1091
|
// src/cli/lib/app-context.ts
|
|
822
1092
|
import {
|
|
823
|
-
existsSync as
|
|
824
|
-
readFileSync as
|
|
1093
|
+
existsSync as existsSync6,
|
|
1094
|
+
readFileSync as readFileSync6,
|
|
825
1095
|
readdirSync,
|
|
826
|
-
writeFileSync as
|
|
1096
|
+
writeFileSync as writeFileSync7,
|
|
827
1097
|
mkdirSync as mkdirSync3,
|
|
828
1098
|
renameSync,
|
|
829
1099
|
unlinkSync
|
|
830
1100
|
} from "fs";
|
|
831
|
-
import { join as
|
|
832
|
-
import { parse as
|
|
1101
|
+
import { join as join6, resolve as resolve2, dirname, isAbsolute, sep } from "path";
|
|
1102
|
+
import { parse as parseToml3 } from "smol-toml";
|
|
833
1103
|
function detectAppName(cwd = process.cwd()) {
|
|
834
|
-
const wranglerPath =
|
|
835
|
-
if (!
|
|
1104
|
+
const wranglerPath = join6(resolve2(cwd), "wrangler.toml");
|
|
1105
|
+
if (!existsSync6(wranglerPath)) return null;
|
|
836
1106
|
try {
|
|
837
|
-
const cfg =
|
|
1107
|
+
const cfg = parseToml3(readFileSync6(wranglerPath, "utf-8"));
|
|
838
1108
|
return typeof cfg.name === "string" && cfg.name.length > 0 ? cfg.name : null;
|
|
839
|
-
} catch {
|
|
840
|
-
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
throw new Error(
|
|
1111
|
+
`Could not parse ${wranglerPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
1112
|
+
);
|
|
841
1113
|
}
|
|
842
1114
|
}
|
|
843
1115
|
function requireAppName(explicit, cwd) {
|
|
@@ -849,9 +1121,9 @@ function requireAppName(explicit, cwd) {
|
|
|
849
1121
|
);
|
|
850
1122
|
}
|
|
851
1123
|
function findAppDir(start = process.cwd()) {
|
|
852
|
-
let dir =
|
|
1124
|
+
let dir = resolve2(start);
|
|
853
1125
|
for (; ; ) {
|
|
854
|
-
if (
|
|
1126
|
+
if (existsSync6(join6(dir, "wrangler.toml"))) return dir;
|
|
855
1127
|
const parent = dirname(dir);
|
|
856
1128
|
if (parent === dir) return null;
|
|
857
1129
|
dir = parent;
|
|
@@ -859,7 +1131,7 @@ function findAppDir(start = process.cwd()) {
|
|
|
859
1131
|
}
|
|
860
1132
|
function findChildApps(dir) {
|
|
861
1133
|
try {
|
|
862
|
-
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() &&
|
|
1134
|
+
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync6(join6(dir, e.name, "wrangler.toml"))).map((e) => e.name).sort();
|
|
863
1135
|
} catch {
|
|
864
1136
|
return [];
|
|
865
1137
|
}
|
|
@@ -867,7 +1139,7 @@ function findChildApps(dir) {
|
|
|
867
1139
|
function writeLaunchFile(launchPath, config) {
|
|
868
1140
|
mkdirSync3(dirname(launchPath), { recursive: true });
|
|
869
1141
|
const tmpPath = `${launchPath}.${process.pid}.tmp`;
|
|
870
|
-
|
|
1142
|
+
writeFileSync7(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
871
1143
|
try {
|
|
872
1144
|
renameSync(tmpPath, launchPath);
|
|
873
1145
|
} catch (err) {
|
|
@@ -880,15 +1152,15 @@ function writeLaunchFile(launchPath, config) {
|
|
|
880
1152
|
}
|
|
881
1153
|
function readLaunchFile(launchPath) {
|
|
882
1154
|
const empty = { version: "0.0.1", configurations: [] };
|
|
883
|
-
if (!
|
|
884
|
-
const raw =
|
|
1155
|
+
if (!existsSync6(launchPath)) return empty;
|
|
1156
|
+
const raw = readFileSync6(launchPath, "utf-8").trim();
|
|
885
1157
|
if (!raw) return empty;
|
|
886
1158
|
const parsed = JSON.parse(raw);
|
|
887
1159
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.configurations)) return null;
|
|
888
1160
|
return parsed;
|
|
889
1161
|
}
|
|
890
|
-
function writeLaunchConfigIfMissing(
|
|
891
|
-
const launchPath =
|
|
1162
|
+
function writeLaunchConfigIfMissing(appDir, appName, port) {
|
|
1163
|
+
const launchPath = join6(appDir, ".claude", "launch.json");
|
|
892
1164
|
try {
|
|
893
1165
|
const config = readLaunchFile(launchPath);
|
|
894
1166
|
if (!config) return;
|
|
@@ -903,8 +1175,8 @@ function writeLaunchConfigIfMissing(appDir2, appName, port) {
|
|
|
903
1175
|
} catch {
|
|
904
1176
|
}
|
|
905
1177
|
}
|
|
906
|
-
function detectClaudeWorktree(
|
|
907
|
-
const segments =
|
|
1178
|
+
function detectClaudeWorktree(appDir) {
|
|
1179
|
+
const segments = resolve2(appDir).split(sep);
|
|
908
1180
|
for (let i = segments.length - 3; i >= 1; i--) {
|
|
909
1181
|
if (segments[i] === ".claude" && segments[i + 1] === "worktrees" && segments[i + 2]) {
|
|
910
1182
|
return {
|
|
@@ -923,16 +1195,16 @@ function deriveWorktreePort(worktreeName) {
|
|
|
923
1195
|
for (const ch of worktreeName) hash = hash * 31 + ch.charCodeAt(0) >>> 0;
|
|
924
1196
|
return WORKTREE_PORT_MIN + hash % WORKTREE_PORT_RANGE;
|
|
925
1197
|
}
|
|
926
|
-
function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName,
|
|
927
|
-
const launchPath =
|
|
1198
|
+
function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
|
|
1199
|
+
const launchPath = join6(mainRepoRoot, ".claude", "launch.json");
|
|
928
1200
|
const entryName = WORKTREE_ENTRY_PREFIX + worktreeName;
|
|
929
1201
|
try {
|
|
930
1202
|
const config = readLaunchFile(launchPath);
|
|
931
1203
|
if (!config) return null;
|
|
932
|
-
const worktreesRoot =
|
|
1204
|
+
const worktreesRoot = join6(mainRepoRoot, ".claude", "worktrees") + sep;
|
|
933
1205
|
config.configurations = config.configurations.filter((c) => {
|
|
934
1206
|
if (c?.name === entryName) return false;
|
|
935
|
-
const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute(c.cwd) && c.cwd.startsWith(worktreesRoot) && !
|
|
1207
|
+
const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute(c.cwd) && c.cwd.startsWith(worktreesRoot) && !existsSync6(c.cwd);
|
|
936
1208
|
return !isStaleWorktreeEntry;
|
|
937
1209
|
});
|
|
938
1210
|
let port = opts.port;
|
|
@@ -953,7 +1225,7 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir2, opts) {
|
|
|
953
1225
|
// the Claude Code desktop launch.json reference alongside `port`) and
|
|
954
1226
|
// verified end-to-end: with it set, the preview server builds from the
|
|
955
1227
|
// worktree instead of the session's main repo.
|
|
956
|
-
cwd:
|
|
1228
|
+
cwd: resolve2(appDir)
|
|
957
1229
|
});
|
|
958
1230
|
writeLaunchFile(launchPath, config);
|
|
959
1231
|
return { entryName, port };
|
|
@@ -961,11 +1233,11 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir2, opts) {
|
|
|
961
1233
|
return null;
|
|
962
1234
|
}
|
|
963
1235
|
}
|
|
964
|
-
function resolveWorktreePort(
|
|
965
|
-
const worktree = detectClaudeWorktree(
|
|
1236
|
+
function resolveWorktreePort(appDir) {
|
|
1237
|
+
const worktree = detectClaudeWorktree(appDir);
|
|
966
1238
|
if (!worktree) return null;
|
|
967
1239
|
try {
|
|
968
|
-
const config = readLaunchFile(
|
|
1240
|
+
const config = readLaunchFile(join6(worktree.mainRepoRoot, ".claude", "launch.json"));
|
|
969
1241
|
const entry = config?.configurations.find(
|
|
970
1242
|
(c) => c?.name === WORKTREE_ENTRY_PREFIX + worktree.worktreeName
|
|
971
1243
|
);
|
|
@@ -975,28 +1247,44 @@ function resolveWorktreePort(appDir2) {
|
|
|
975
1247
|
}
|
|
976
1248
|
return deriveWorktreePort(worktree.worktreeName);
|
|
977
1249
|
}
|
|
1250
|
+
function requireAppIdArg(explicit) {
|
|
1251
|
+
if (explicit) {
|
|
1252
|
+
if (!APP_ID_RE.test(explicit)) {
|
|
1253
|
+
throw new Error(`"${explicit}" is not an app id (app_\u2026). Pass --app <appId>.`);
|
|
1254
|
+
}
|
|
1255
|
+
return explicit;
|
|
1256
|
+
}
|
|
1257
|
+
const appDir = findAppDir();
|
|
1258
|
+
const id = appDir ? readAppId(appDir) : null;
|
|
1259
|
+
if (!id) {
|
|
1260
|
+
throw new Error(
|
|
1261
|
+
"No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID, or pass --app <appId>."
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
return id;
|
|
1265
|
+
}
|
|
978
1266
|
|
|
979
1267
|
// src/cli/lib/install-status.ts
|
|
980
|
-
import { existsSync as
|
|
981
|
-
import { join as
|
|
982
|
-
function ensureInstallReady(
|
|
983
|
-
if (
|
|
984
|
-
const sentinelDir =
|
|
985
|
-
const errPath =
|
|
986
|
-
const startedPath =
|
|
987
|
-
const donePath =
|
|
988
|
-
const logPath =
|
|
989
|
-
if (
|
|
1268
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
1269
|
+
import { join as join7 } from "path";
|
|
1270
|
+
function ensureInstallReady(appDir) {
|
|
1271
|
+
if (existsSync7(join7(appDir, "node_modules", "deepspace", "package.json"))) return;
|
|
1272
|
+
const sentinelDir = join7(appDir, ".deepspace");
|
|
1273
|
+
const errPath = join7(sentinelDir, "install.err");
|
|
1274
|
+
const startedPath = join7(sentinelDir, "install.started");
|
|
1275
|
+
const donePath = join7(sentinelDir, "install.done");
|
|
1276
|
+
const logPath = join7(sentinelDir, "install.log");
|
|
1277
|
+
if (existsSync7(errPath)) {
|
|
990
1278
|
console.error("Background install failed:");
|
|
991
|
-
console.error(
|
|
992
|
-
if (
|
|
1279
|
+
console.error(readFileSync7(errPath, "utf-8").trimEnd());
|
|
1280
|
+
if (existsSync7(logPath)) console.error(`
|
|
993
1281
|
Full log: ${logPath}`);
|
|
994
1282
|
console.error("\nRun `npm install` (or `bun install`) manually, then retry.");
|
|
995
1283
|
process.exit(1);
|
|
996
1284
|
}
|
|
997
|
-
if (
|
|
1285
|
+
if (existsSync7(startedPath) && !existsSync7(donePath)) {
|
|
998
1286
|
console.error("Dependencies are still installing in the background.");
|
|
999
|
-
if (
|
|
1287
|
+
if (existsSync7(logPath)) console.error(`Tail progress: tail -f ${logPath}`);
|
|
1000
1288
|
console.error("Retry once it finishes.");
|
|
1001
1289
|
process.exit(1);
|
|
1002
1290
|
}
|
|
@@ -1006,9 +1294,9 @@ Full log: ${logPath}`);
|
|
|
1006
1294
|
|
|
1007
1295
|
// src/cli/lib/preflight.ts
|
|
1008
1296
|
import { execFileSync } from "child_process";
|
|
1009
|
-
import { existsSync as
|
|
1297
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1010
1298
|
import * as nodeModule from "module";
|
|
1011
|
-
import { resolve as
|
|
1299
|
+
import { resolve as resolve3 } from "path";
|
|
1012
1300
|
function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.registerHooks === "function") {
|
|
1013
1301
|
if (hasRegisterHooks) return;
|
|
1014
1302
|
console.error(
|
|
@@ -1018,23 +1306,23 @@ function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.regi
|
|
|
1018
1306
|
process.exit(1);
|
|
1019
1307
|
}
|
|
1020
1308
|
var DLL_NOT_FOUND = -1073741515;
|
|
1021
|
-
function preflightWindowsWorkerd(
|
|
1309
|
+
function preflightWindowsWorkerd(appDir) {
|
|
1022
1310
|
if (process.platform !== "win32") return;
|
|
1023
|
-
const workerdExe =
|
|
1024
|
-
|
|
1311
|
+
const workerdExe = resolve3(
|
|
1312
|
+
appDir,
|
|
1025
1313
|
"node_modules",
|
|
1026
1314
|
"@cloudflare",
|
|
1027
1315
|
"workerd-windows-64",
|
|
1028
1316
|
"bin",
|
|
1029
1317
|
"workerd.exe"
|
|
1030
1318
|
);
|
|
1031
|
-
if (!
|
|
1319
|
+
if (!existsSync8(workerdExe)) return;
|
|
1032
1320
|
try {
|
|
1033
1321
|
execFileSync(workerdExe, ["--help"], { stdio: "pipe", timeout: 1e4 });
|
|
1034
1322
|
return;
|
|
1035
1323
|
} catch (err) {
|
|
1036
|
-
const
|
|
1037
|
-
if (
|
|
1324
|
+
const status3 = err.status;
|
|
1325
|
+
if (status3 !== DLL_NOT_FOUND) return;
|
|
1038
1326
|
console.error("");
|
|
1039
1327
|
console.error(" workerd (Cloudflare Workers runtime) cannot start on this system.");
|
|
1040
1328
|
console.error(" It needs the Microsoft Visual C++ Redistributable, which is not installed.");
|
|
@@ -1052,7 +1340,7 @@ function preflightWindowsWorkerd(appDir2) {
|
|
|
1052
1340
|
|
|
1053
1341
|
// src/cli/lib/macos-junk.ts
|
|
1054
1342
|
import { readdirSync as readdirSync2, rmSync as rmSync3 } from "fs";
|
|
1055
|
-
import { join as
|
|
1343
|
+
import { join as join8 } from "path";
|
|
1056
1344
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".wrangler", ".vite"]);
|
|
1057
1345
|
function removeMacosJunk(dir) {
|
|
1058
1346
|
let removed = 0;
|
|
@@ -1063,7 +1351,7 @@ function removeMacosJunk(dir) {
|
|
|
1063
1351
|
return removed;
|
|
1064
1352
|
}
|
|
1065
1353
|
for (const entry of entries) {
|
|
1066
|
-
const path =
|
|
1354
|
+
const path = join8(dir, entry.name);
|
|
1067
1355
|
if (entry.isDirectory()) {
|
|
1068
1356
|
if (!SKIP_DIRS.has(entry.name)) removed += removeMacosJunk(path);
|
|
1069
1357
|
continue;
|
|
@@ -1079,741 +1367,17 @@ function removeMacosJunk(dir) {
|
|
|
1079
1367
|
return removed;
|
|
1080
1368
|
}
|
|
1081
1369
|
|
|
1082
|
-
// src/cli/lib/
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
"ai",
|
|
1092
|
-
"r2_bucket",
|
|
1093
|
-
"kv_namespace",
|
|
1094
|
-
"d1",
|
|
1095
|
-
"queue",
|
|
1096
|
-
"browser_rendering",
|
|
1097
|
-
"analytics_engine",
|
|
1098
|
-
"hyperdrive"
|
|
1099
|
-
]);
|
|
1100
|
-
var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
1101
|
-
"ASSETS",
|
|
1102
|
-
"PLATFORM_WORKER",
|
|
1103
|
-
"API_WORKER",
|
|
1104
|
-
"APP_NAME",
|
|
1105
|
-
"OWNER_USER_ID",
|
|
1106
|
-
"AUTH_JWT_PUBLIC_KEY",
|
|
1107
|
-
"AUTH_JWT_ISSUER",
|
|
1108
|
-
"AUTH_WORKER_URL",
|
|
1109
|
-
"APP_IDENTITY_TOKEN",
|
|
1110
|
-
"APP_OWNER_JWT",
|
|
1111
|
-
"USAGE_EVENTS"
|
|
1112
|
-
]);
|
|
1113
|
-
function validateBindingManifest(manifest) {
|
|
1114
|
-
if (!Array.isArray(manifest)) {
|
|
1115
|
-
return { valid: false, errors: [{ reason: "Manifest must be an array" }] };
|
|
1116
|
-
}
|
|
1117
|
-
const errors = [];
|
|
1118
|
-
const seenNames = /* @__PURE__ */ new Set();
|
|
1119
|
-
for (const entry of manifest) {
|
|
1120
|
-
if (!entry || typeof entry !== "object") {
|
|
1121
|
-
errors.push({ reason: `Entry must be an object (got ${entry === null ? "null" : typeof entry})` });
|
|
1122
|
-
continue;
|
|
1123
|
-
}
|
|
1124
|
-
const e = entry;
|
|
1125
|
-
const type = e.type;
|
|
1126
|
-
const name = e.name;
|
|
1127
|
-
if (typeof type !== "string" || !ALLOWED_BINDING_TYPES.has(type)) {
|
|
1128
|
-
errors.push({
|
|
1129
|
-
reason: `Disallowed binding type: ${typeof type === "string" ? type : `(${typeof type})`}. Allowed: ${[...ALLOWED_BINDING_TYPES].sort().join(", ")}`
|
|
1130
|
-
});
|
|
1131
|
-
continue;
|
|
1132
|
-
}
|
|
1133
|
-
if (typeof name !== "string" || !name) {
|
|
1134
|
-
errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
|
|
1135
|
-
continue;
|
|
1136
|
-
}
|
|
1137
|
-
if (RESERVED_BINDING_NAMES.has(name)) {
|
|
1138
|
-
errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
|
|
1139
|
-
continue;
|
|
1140
|
-
}
|
|
1141
|
-
if (seenNames.has(name)) {
|
|
1142
|
-
errors.push({ reason: `Duplicate binding name "${name}"` });
|
|
1143
|
-
continue;
|
|
1144
|
-
}
|
|
1145
|
-
seenNames.add(name);
|
|
1146
|
-
const binding = entry;
|
|
1147
|
-
const fieldErr = requiredFieldError(binding);
|
|
1148
|
-
if (fieldErr) errors.push({ binding, reason: fieldErr });
|
|
1370
|
+
// src/cli/lib/port.ts
|
|
1371
|
+
var DEFAULT_PORT = 5173;
|
|
1372
|
+
function resolvePort(arg) {
|
|
1373
|
+
const raw = arg ?? process.env.DEEPSPACE_PORT;
|
|
1374
|
+
if (!raw) return DEFAULT_PORT;
|
|
1375
|
+
const n = Number(raw);
|
|
1376
|
+
if (!Number.isInteger(n) || n < 1 || n > 65535) {
|
|
1377
|
+
console.error(`Invalid port: ${raw}. Must be an integer between 1 and 65535.`);
|
|
1378
|
+
process.exit(1);
|
|
1149
1379
|
}
|
|
1150
|
-
|
|
1151
|
-
return { valid: true, bindings: manifest };
|
|
1152
|
-
}
|
|
1153
|
-
function requiredFieldError(b) {
|
|
1154
|
-
switch (b.type) {
|
|
1155
|
-
case "vectorize": {
|
|
1156
|
-
if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
|
|
1157
|
-
if (b.index_name === AUTO_PROVISION_SENTINEL) {
|
|
1158
|
-
if (typeof b.dimensions !== "number" || b.dimensions <= 0) {
|
|
1159
|
-
return `vectorize binding "${b.name}" with index_name="auto" requires "dimensions" (positive number)`;
|
|
1160
|
-
}
|
|
1161
|
-
if (!b.metric || !VECTORIZE_METRICS.has(b.metric)) {
|
|
1162
|
-
return `vectorize binding "${b.name}" with index_name="auto" requires "metric" (one of: ${[...VECTORIZE_METRICS].join(", ")})`;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
return null;
|
|
1166
|
-
}
|
|
1167
|
-
case "r2_bucket":
|
|
1168
|
-
return b.bucket_name ? null : `r2_bucket binding "${b.name}" missing bucket_name (or "auto")`;
|
|
1169
|
-
case "kv_namespace": {
|
|
1170
|
-
if (!b.namespace_id) return `kv_namespace binding "${b.name}" missing namespace_id (or "auto")`;
|
|
1171
|
-
if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {
|
|
1172
|
-
return `kv_namespace binding "${b.name}" with namespace_id="auto" requires "title"`;
|
|
1173
|
-
}
|
|
1174
|
-
return null;
|
|
1175
|
-
}
|
|
1176
|
-
case "d1": {
|
|
1177
|
-
if (!b.id) return `d1 binding "${b.name}" missing id (or "auto")`;
|
|
1178
|
-
if (b.id === AUTO_PROVISION_SENTINEL && !b.database_name) {
|
|
1179
|
-
return `d1 binding "${b.name}" with id="auto" requires "database_name"`;
|
|
1180
|
-
}
|
|
1181
|
-
return null;
|
|
1182
|
-
}
|
|
1183
|
-
case "queue":
|
|
1184
|
-
return b.queue_name ? null : `queue binding "${b.name}" missing queue_name (or "auto")`;
|
|
1185
|
-
case "hyperdrive":
|
|
1186
|
-
if (!b.id) return `hyperdrive binding "${b.name}" missing id`;
|
|
1187
|
-
if (b.id === AUTO_PROVISION_SENTINEL) {
|
|
1188
|
-
return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
|
|
1189
|
-
}
|
|
1190
|
-
return null;
|
|
1191
|
-
case "ai":
|
|
1192
|
-
case "browser_rendering":
|
|
1193
|
-
case "analytics_engine":
|
|
1194
|
-
return null;
|
|
1195
|
-
// no required fields beyond `name`
|
|
1196
|
-
default: {
|
|
1197
|
-
const _exhaustive = b;
|
|
1198
|
-
void _exhaustive;
|
|
1199
|
-
return null;
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
function bindingManifestFromOutputConfig(outputConfig) {
|
|
1204
|
-
const out = [];
|
|
1205
|
-
const aiBinding = pluckString(outputConfig.ai, "binding");
|
|
1206
|
-
if (aiBinding) out.push({ type: "ai", name: aiBinding });
|
|
1207
|
-
for (const v of asObjectArray(outputConfig.vectorize)) {
|
|
1208
|
-
const name = pluckString(v, "binding");
|
|
1209
|
-
const index_name = pluckString(v, "index_name");
|
|
1210
|
-
if (!name || !index_name) continue;
|
|
1211
|
-
const dimensions = pluckNumber(v, "dimensions");
|
|
1212
|
-
const metric = pluckString(v, "metric");
|
|
1213
|
-
out.push({
|
|
1214
|
-
type: "vectorize",
|
|
1215
|
-
name,
|
|
1216
|
-
index_name,
|
|
1217
|
-
...dimensions != null && { dimensions },
|
|
1218
|
-
...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
|
|
1219
|
-
});
|
|
1220
|
-
}
|
|
1221
|
-
for (const r of asObjectArray(outputConfig.r2_buckets)) {
|
|
1222
|
-
const name = pluckString(r, "binding");
|
|
1223
|
-
const bucket_name = pluckString(r, "bucket_name");
|
|
1224
|
-
if (name && bucket_name) out.push({ type: "r2_bucket", name, bucket_name });
|
|
1225
|
-
}
|
|
1226
|
-
for (const k of asObjectArray(outputConfig.kv_namespaces)) {
|
|
1227
|
-
const name = pluckString(k, "binding");
|
|
1228
|
-
const namespace_id = pluckString(k, "id");
|
|
1229
|
-
if (!name || !namespace_id) continue;
|
|
1230
|
-
const title = pluckString(k, "title");
|
|
1231
|
-
out.push({
|
|
1232
|
-
type: "kv_namespace",
|
|
1233
|
-
name,
|
|
1234
|
-
namespace_id,
|
|
1235
|
-
...title && { title }
|
|
1236
|
-
});
|
|
1237
|
-
}
|
|
1238
|
-
for (const d of asObjectArray(outputConfig.d1_databases)) {
|
|
1239
|
-
const name = pluckString(d, "binding");
|
|
1240
|
-
const id = pluckString(d, "database_id");
|
|
1241
|
-
if (!name || !id) continue;
|
|
1242
|
-
const database_name = pluckString(d, "database_name");
|
|
1243
|
-
out.push({
|
|
1244
|
-
type: "d1",
|
|
1245
|
-
name,
|
|
1246
|
-
id,
|
|
1247
|
-
...database_name && { database_name }
|
|
1248
|
-
});
|
|
1249
|
-
}
|
|
1250
|
-
const queues = isObject(outputConfig.queues) ? outputConfig.queues : null;
|
|
1251
|
-
for (const q of asObjectArray(queues?.producers)) {
|
|
1252
|
-
const name = pluckString(q, "binding");
|
|
1253
|
-
const queue_name = pluckString(q, "queue");
|
|
1254
|
-
if (name && queue_name) out.push({ type: "queue", name, queue_name });
|
|
1255
|
-
}
|
|
1256
|
-
const browserBinding = pluckString(outputConfig.browser, "binding");
|
|
1257
|
-
if (browserBinding) out.push({ type: "browser_rendering", name: browserBinding });
|
|
1258
|
-
for (const a of asObjectArray(outputConfig.analytics_engine_datasets)) {
|
|
1259
|
-
const name = pluckString(a, "binding");
|
|
1260
|
-
const dataset = pluckString(a, "dataset");
|
|
1261
|
-
if (name) out.push({ type: "analytics_engine", name, dataset: dataset ?? void 0 });
|
|
1262
|
-
}
|
|
1263
|
-
for (const h of asObjectArray(outputConfig.hyperdrive)) {
|
|
1264
|
-
const name = pluckString(h, "binding");
|
|
1265
|
-
const id = pluckString(h, "id");
|
|
1266
|
-
if (name && id) out.push({ type: "hyperdrive", name, id });
|
|
1267
|
-
}
|
|
1268
|
-
return out;
|
|
1269
|
-
}
|
|
1270
|
-
function isObject(v) {
|
|
1271
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
1272
|
-
}
|
|
1273
|
-
function pluckString(v, key) {
|
|
1274
|
-
if (!isObject(v)) return null;
|
|
1275
|
-
const val = v[key];
|
|
1276
|
-
return typeof val === "string" && val.length > 0 ? val : null;
|
|
1277
|
-
}
|
|
1278
|
-
function pluckNumber(v, key) {
|
|
1279
|
-
if (!isObject(v)) return null;
|
|
1280
|
-
const val = v[key];
|
|
1281
|
-
return typeof val === "number" && Number.isFinite(val) ? val : null;
|
|
1282
|
-
}
|
|
1283
|
-
function asObjectArray(v) {
|
|
1284
|
-
if (!Array.isArray(v)) return [];
|
|
1285
|
-
return v.filter(isObject);
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
// src/cli/lib/secrets.ts
|
|
1289
|
-
var DEEPSPACE_SECRETS_PROJECT_VAR = "DEEPSPACE_SECRETS_PROJECT";
|
|
1290
|
-
var DEEPSPACE_SECRETS_CONFIG_VAR = "DEEPSPACE_SECRETS_CONFIG";
|
|
1291
|
-
var GENERATED_SECRETS_DIVIDER2 = "# --- DeepSpace secrets cache: generated by `npx deepspace secrets`; do not edit manually ---";
|
|
1292
|
-
var REQUIRED_SECRETS_BEGIN = "# --- DeepSpace detected secrets (names only; values live in `npx deepspace secrets`) ---";
|
|
1293
|
-
var REQUIRED_SECRETS_END = "# --- end DeepSpace detected secrets ---";
|
|
1294
|
-
var SECRET_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1295
|
-
var PROJECT_NAME_RE = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
1296
|
-
var CONFIG_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
1297
|
-
var MAX_SECRET_VALUE_BYTES = 32 * 1024;
|
|
1298
|
-
function validateProjectName(projectName) {
|
|
1299
|
-
const trimmed = projectName.trim();
|
|
1300
|
-
if (!PROJECT_NAME_RE.test(trimmed)) {
|
|
1301
|
-
throw new Error(
|
|
1302
|
-
`Invalid secrets project "${projectName}". Use lowercase letters, numbers, and hyphens; start with a letter or number.`
|
|
1303
|
-
);
|
|
1304
|
-
}
|
|
1305
|
-
return trimmed;
|
|
1306
|
-
}
|
|
1307
|
-
function validateConfigName(configName) {
|
|
1308
|
-
const trimmed = configName.trim();
|
|
1309
|
-
if (!CONFIG_NAME_RE.test(trimmed)) {
|
|
1310
|
-
throw new Error(
|
|
1311
|
-
`Invalid secrets config "${configName}". Use letters, numbers, underscores, or hyphens; start with a letter or number.`
|
|
1312
|
-
);
|
|
1313
|
-
}
|
|
1314
|
-
return trimmed;
|
|
1315
|
-
}
|
|
1316
|
-
function validateSecretName(name) {
|
|
1317
|
-
const trimmed = name.trim();
|
|
1318
|
-
if (!SECRET_NAME_RE.test(trimmed)) {
|
|
1319
|
-
throw new Error(
|
|
1320
|
-
`Invalid secret name "${name}". Secret names must use letters, numbers, and underscores, and may not start with a number.`
|
|
1321
|
-
);
|
|
1322
|
-
}
|
|
1323
|
-
if (RESERVED_BINDING_NAMES.has(trimmed)) {
|
|
1324
|
-
throw new Error(`"${trimmed}" is reserved by DeepSpace and cannot be used as an app secret.`);
|
|
1325
|
-
}
|
|
1326
|
-
return trimmed;
|
|
1327
|
-
}
|
|
1328
|
-
function validateSecretValue(key, value) {
|
|
1329
|
-
const bytes = new TextEncoder().encode(value).byteLength;
|
|
1330
|
-
if (bytes > MAX_SECRET_VALUE_BYTES) {
|
|
1331
|
-
throw new Error(`Secret "${key}" exceeds ${MAX_SECRET_VALUE_BYTES} bytes.`);
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
function defaultConfigNameForEnv(wranglerEnv2) {
|
|
1335
|
-
return wranglerEnv2 ? validateConfigName(wranglerEnv2) : "prd";
|
|
1336
|
-
}
|
|
1337
|
-
function readTopLevelAppName(appDir2) {
|
|
1338
|
-
const cfg = readWranglerConfig(appDir2);
|
|
1339
|
-
const resolved = resolveAppNameForEnv(cfg, void 0);
|
|
1340
|
-
if (!resolved.ok) throw new Error(resolved.reason);
|
|
1341
|
-
if (cfg.name !== resolved.name) {
|
|
1342
|
-
throw new Error(
|
|
1343
|
-
`wrangler.toml: \`name\` "${cfg.name}" is not in canonical form. Update it to "${resolved.name}" and re-run.`
|
|
1344
|
-
);
|
|
1345
|
-
}
|
|
1346
|
-
return resolved.name;
|
|
1347
|
-
}
|
|
1348
|
-
function resolveSecretsAppName(appDir2, wranglerEnv2) {
|
|
1349
|
-
const cfg = readWranglerConfig(appDir2);
|
|
1350
|
-
const resolved = resolveAppNameForEnv(cfg, wranglerEnv2);
|
|
1351
|
-
if (!resolved.ok) throw new Error(resolved.reason);
|
|
1352
|
-
return resolved.name;
|
|
1353
|
-
}
|
|
1354
|
-
function stringVar(value) {
|
|
1355
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1356
|
-
}
|
|
1357
|
-
function resolveSecretsLink(appDir2, wranglerEnv2) {
|
|
1358
|
-
const cfg = readWranglerConfig(appDir2);
|
|
1359
|
-
const defaultProjectName = readTopLevelAppName(appDir2);
|
|
1360
|
-
const defaultConfigName = defaultConfigNameForEnv(wranglerEnv2);
|
|
1361
|
-
const topVars = cfg.vars ?? {};
|
|
1362
|
-
const envVars = wranglerEnv2 ? cfg.env?.[wranglerEnv2]?.vars ?? {} : {};
|
|
1363
|
-
const projectName = stringVar(envVars[DEEPSPACE_SECRETS_PROJECT_VAR]) ?? stringVar(topVars[DEEPSPACE_SECRETS_PROJECT_VAR]);
|
|
1364
|
-
if (!projectName) {
|
|
1365
|
-
return { linked: false, defaultProjectName, defaultConfigName, wranglerEnv: wranglerEnv2 };
|
|
1366
|
-
}
|
|
1367
|
-
const configName = stringVar(envVars[DEEPSPACE_SECRETS_CONFIG_VAR]) ?? (wranglerEnv2 ? defaultConfigName : stringVar(topVars[DEEPSPACE_SECRETS_CONFIG_VAR]) ?? "prd");
|
|
1368
|
-
return {
|
|
1369
|
-
linked: true,
|
|
1370
|
-
projectName: validateProjectName(projectName),
|
|
1371
|
-
configName: validateConfigName(configName),
|
|
1372
|
-
wranglerEnv: wranglerEnv2
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1375
|
-
function resolveSecretsTarget(appDir2, opts) {
|
|
1376
|
-
const link = resolveSecretsLink(appDir2, opts.wranglerEnv);
|
|
1377
|
-
const projectName = validateProjectName(
|
|
1378
|
-
opts.project ?? (link.linked ? link.projectName : link.defaultProjectName)
|
|
1379
|
-
);
|
|
1380
|
-
const configName = validateConfigName(
|
|
1381
|
-
opts.config ?? (link.linked ? link.configName : link.defaultConfigName)
|
|
1382
|
-
);
|
|
1383
|
-
return { projectName, configName, wranglerEnv: opts.wranglerEnv };
|
|
1384
|
-
}
|
|
1385
|
-
function renderSecretsCache(secrets, source) {
|
|
1386
|
-
const keys = Object.keys(secrets).sort();
|
|
1387
|
-
const lines = [
|
|
1388
|
-
GENERATED_SECRETS_DIVIDER2,
|
|
1389
|
-
`# Source: project=${source.projectName} config=${source.configName}`,
|
|
1390
|
-
`# Pulled: ${source.pulledAt ?? (/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1391
|
-
"# Use `npx deepspace secrets` to change these values."
|
|
1392
|
-
];
|
|
1393
|
-
for (const key of keys) {
|
|
1394
|
-
validateSecretName(key);
|
|
1395
|
-
validateSecretValue(key, secrets[key]);
|
|
1396
|
-
lines.push(`${key}=${quoteDotenvValue(secrets[key])}`);
|
|
1397
|
-
}
|
|
1398
|
-
return lines.join("\n");
|
|
1399
|
-
}
|
|
1400
|
-
function stripGeneratedSecretsCache2(content) {
|
|
1401
|
-
const idx = content.indexOf(GENERATED_SECRETS_DIVIDER2);
|
|
1402
|
-
if (idx < 0) return content.replace(/\s+$/u, "");
|
|
1403
|
-
return content.slice(0, idx).replace(/\s+$/u, "");
|
|
1404
|
-
}
|
|
1405
|
-
function formatSecretsDownload(secrets, format) {
|
|
1406
|
-
const entries = Object.entries(secrets).sort(([a], [b]) => a.localeCompare(b));
|
|
1407
|
-
switch (format) {
|
|
1408
|
-
case "json":
|
|
1409
|
-
case "dotnet-json":
|
|
1410
|
-
return JSON.stringify(Object.fromEntries(entries), null, 2) + "\n";
|
|
1411
|
-
case "env":
|
|
1412
|
-
return entries.map(([key, value]) => `${key}=${quoteDotenvValue(value)}`).join("\n") + "\n";
|
|
1413
|
-
case "env-no-quotes":
|
|
1414
|
-
case "docker":
|
|
1415
|
-
return entries.map(([key, value]) => `${key}=${value}`).join("\n") + "\n";
|
|
1416
|
-
case "yaml":
|
|
1417
|
-
return entries.map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join("\n") + "\n";
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
function writeSecretsCacheFile(appDir2, secrets, source) {
|
|
1421
|
-
const devVarsPath = secretsCacheFilePath(appDir2);
|
|
1422
|
-
const existing = existsSync8(devVarsPath) ? readFileSync7(devVarsPath, "utf-8") : "";
|
|
1423
|
-
const head = stripGeneratedSecretsCache2(existing);
|
|
1424
|
-
const rendered = renderSecretsCache(secrets, source);
|
|
1425
|
-
writeSecretFileSync(devVarsPath, head ? `${head}
|
|
1426
|
-
|
|
1427
|
-
${rendered}
|
|
1428
|
-
` : `${rendered}
|
|
1429
|
-
`);
|
|
1430
|
-
}
|
|
1431
|
-
function clearSecretsCacheFile(appDir2) {
|
|
1432
|
-
const devVarsPath = secretsCacheFilePath(appDir2);
|
|
1433
|
-
if (!existsSync8(devVarsPath)) return;
|
|
1434
|
-
const existing = readFileSync7(devVarsPath, "utf-8");
|
|
1435
|
-
const next = stripGeneratedSecretsCache2(existing);
|
|
1436
|
-
writeSecretFileSync(devVarsPath, next ? `${next}
|
|
1437
|
-
` : "");
|
|
1438
|
-
}
|
|
1439
|
-
function secretsTargetMatchesLink(appDir2, target) {
|
|
1440
|
-
const link = resolveSecretsLink(appDir2, target.wranglerEnv);
|
|
1441
|
-
return link.linked && link.projectName === target.projectName && link.configName === target.configName;
|
|
1442
|
-
}
|
|
1443
|
-
function appScopedSecretsApiOptions(appDir2, target) {
|
|
1444
|
-
const link = resolveSecretsLink(appDir2, target.wranglerEnv);
|
|
1445
|
-
if (!link.linked || link.projectName !== target.projectName) return {};
|
|
1446
|
-
return { appName: resolveSecretsAppName(appDir2, target.wranglerEnv) };
|
|
1447
|
-
}
|
|
1448
|
-
function secretsCacheFilePath(appDir2) {
|
|
1449
|
-
return join8(appDir2, ".dev.vars");
|
|
1450
|
-
}
|
|
1451
|
-
async function pullLinkedSecretsCache(deployUrl, token, appDir2, wranglerEnv2) {
|
|
1452
|
-
const link = resolveSecretsLink(appDir2, wranglerEnv2);
|
|
1453
|
-
if (!link.linked) return null;
|
|
1454
|
-
const values = await fetchSecretsValues(deployUrl, token, link.projectName, link.configName, {
|
|
1455
|
-
appName: resolveSecretsAppName(appDir2, wranglerEnv2)
|
|
1456
|
-
});
|
|
1457
|
-
return {
|
|
1458
|
-
linked: true,
|
|
1459
|
-
projectName: link.projectName,
|
|
1460
|
-
configName: link.configName,
|
|
1461
|
-
wranglerEnv: wranglerEnv2,
|
|
1462
|
-
values
|
|
1463
|
-
};
|
|
1464
|
-
}
|
|
1465
|
-
async function fetchSecretsValues(deployUrl, token, projectName, configName, options = {}) {
|
|
1466
|
-
const body = await secretsApi(
|
|
1467
|
-
deployUrl,
|
|
1468
|
-
token,
|
|
1469
|
-
`/api/secrets/${encodeURIComponent(projectName)}/configs/${encodeURIComponent(configName)}/values`,
|
|
1470
|
-
{},
|
|
1471
|
-
options
|
|
1472
|
-
);
|
|
1473
|
-
return body.secrets;
|
|
1474
|
-
}
|
|
1475
|
-
async function listSecrets(deployUrl, token, projectName, configName, options = {}) {
|
|
1476
|
-
return secretsApi(
|
|
1477
|
-
deployUrl,
|
|
1478
|
-
token,
|
|
1479
|
-
`/api/secrets/${encodeURIComponent(projectName)}/configs/${encodeURIComponent(configName)}`,
|
|
1480
|
-
{},
|
|
1481
|
-
options
|
|
1482
|
-
);
|
|
1483
|
-
}
|
|
1484
|
-
async function getSecretPlain(deployUrl, token, projectName, configName, key, options = {}) {
|
|
1485
|
-
const body = await secretsApi(
|
|
1486
|
-
deployUrl,
|
|
1487
|
-
token,
|
|
1488
|
-
`/api/secrets/${encodeURIComponent(projectName)}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}?plain=true`,
|
|
1489
|
-
{},
|
|
1490
|
-
options
|
|
1491
|
-
);
|
|
1492
|
-
return body.value;
|
|
1493
|
-
}
|
|
1494
|
-
async function deleteSecret(deployUrl, token, projectName, configName, key, options = {}) {
|
|
1495
|
-
await secretsApi(
|
|
1496
|
-
deployUrl,
|
|
1497
|
-
token,
|
|
1498
|
-
`/api/secrets/${encodeURIComponent(projectName)}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}`,
|
|
1499
|
-
{ method: "DELETE" },
|
|
1500
|
-
options
|
|
1501
|
-
);
|
|
1502
|
-
}
|
|
1503
|
-
async function uploadSecrets(deployUrl, token, projectName, configName, secrets, replace, options = {}) {
|
|
1504
|
-
await secretsApi(
|
|
1505
|
-
deployUrl,
|
|
1506
|
-
token,
|
|
1507
|
-
`/api/secrets/${encodeURIComponent(projectName)}/configs/${encodeURIComponent(configName)}/secrets`,
|
|
1508
|
-
{
|
|
1509
|
-
method: "PUT",
|
|
1510
|
-
body: JSON.stringify({ secrets, replace })
|
|
1511
|
-
},
|
|
1512
|
-
options
|
|
1513
|
-
);
|
|
1514
|
-
}
|
|
1515
|
-
async function listConfigs(deployUrl, token, projectName, options = {}) {
|
|
1516
|
-
const body = await secretsApi(
|
|
1517
|
-
deployUrl,
|
|
1518
|
-
token,
|
|
1519
|
-
`/api/secrets/${encodeURIComponent(projectName)}/configs`,
|
|
1520
|
-
{},
|
|
1521
|
-
options
|
|
1522
|
-
);
|
|
1523
|
-
return body.configs;
|
|
1524
|
-
}
|
|
1525
|
-
async function createConfig(deployUrl, token, projectName, configName, copyFromConfigName, options = {}) {
|
|
1526
|
-
return secretsApi(
|
|
1527
|
-
deployUrl,
|
|
1528
|
-
token,
|
|
1529
|
-
`/api/secrets/${encodeURIComponent(projectName)}/configs`,
|
|
1530
|
-
{
|
|
1531
|
-
method: "POST",
|
|
1532
|
-
body: JSON.stringify({
|
|
1533
|
-
name: configName,
|
|
1534
|
-
...copyFromConfigName ? { copyFrom: copyFromConfigName } : {}
|
|
1535
|
-
})
|
|
1536
|
-
},
|
|
1537
|
-
options
|
|
1538
|
-
);
|
|
1539
|
-
}
|
|
1540
|
-
async function deleteConfig(deployUrl, token, projectName, configName, options = {}) {
|
|
1541
|
-
await secretsApi(
|
|
1542
|
-
deployUrl,
|
|
1543
|
-
token,
|
|
1544
|
-
`/api/secrets/${encodeURIComponent(projectName)}/configs/${encodeURIComponent(configName)}`,
|
|
1545
|
-
{ method: "DELETE" },
|
|
1546
|
-
options
|
|
1547
|
-
);
|
|
1548
|
-
}
|
|
1549
|
-
async function setServerSecretsLink(deployUrl, token, appName, projectName) {
|
|
1550
|
-
await secretsApi(deployUrl, token, `/api/secrets-links/${encodeURIComponent(appName)}`, {
|
|
1551
|
-
method: "PUT",
|
|
1552
|
-
body: JSON.stringify({ projectName })
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
async function clearServerSecretsLink(deployUrl, token, appName) {
|
|
1556
|
-
await secretsApi(deployUrl, token, `/api/secrets-links/${encodeURIComponent(appName)}`, {
|
|
1557
|
-
method: "DELETE"
|
|
1558
|
-
});
|
|
1559
|
-
}
|
|
1560
|
-
function parseSecretsUpload(content) {
|
|
1561
|
-
const trimmed = content.trim();
|
|
1562
|
-
if (!trimmed) return {};
|
|
1563
|
-
if (trimmed.startsWith("{")) {
|
|
1564
|
-
const parsed2 = JSON.parse(trimmed);
|
|
1565
|
-
if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
|
|
1566
|
-
throw new Error("JSON upload must be an object of KEY=value strings.");
|
|
1567
|
-
}
|
|
1568
|
-
const out2 = {};
|
|
1569
|
-
for (const [key, value] of Object.entries(parsed2)) {
|
|
1570
|
-
const name = validateSecretName(key);
|
|
1571
|
-
if (typeof value !== "string") {
|
|
1572
|
-
throw new Error(`Secret "${name}" must be a string.`);
|
|
1573
|
-
}
|
|
1574
|
-
validateSecretValue(name, value);
|
|
1575
|
-
out2[name] = value;
|
|
1576
|
-
}
|
|
1577
|
-
return out2;
|
|
1578
|
-
}
|
|
1579
|
-
const parsed = parseDevVars(content);
|
|
1580
|
-
const out = {};
|
|
1581
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
1582
|
-
const name = validateSecretName(key);
|
|
1583
|
-
validateSecretValue(name, value);
|
|
1584
|
-
out[name] = value;
|
|
1585
|
-
}
|
|
1586
|
-
return out;
|
|
1587
|
-
}
|
|
1588
|
-
function writeWranglerSecretsLink(appDir2, target, opts = {}) {
|
|
1589
|
-
const path = join8(resolve3(appDir2), "wrangler.toml");
|
|
1590
|
-
const raw = readFileSync7(path, "utf-8");
|
|
1591
|
-
const table = target.wranglerEnv ? `env.${target.wranglerEnv}.vars` : "vars";
|
|
1592
|
-
const vars = {
|
|
1593
|
-
[DEEPSPACE_SECRETS_CONFIG_VAR]: target.configName
|
|
1594
|
-
};
|
|
1595
|
-
if (!target.wranglerEnv || opts.projectOverride) {
|
|
1596
|
-
vars[DEEPSPACE_SECRETS_PROJECT_VAR] = target.projectName;
|
|
1597
|
-
} else {
|
|
1598
|
-
const link = resolveSecretsLink(appDir2, void 0);
|
|
1599
|
-
if (!link.linked) {
|
|
1600
|
-
vars[DEEPSPACE_SECRETS_PROJECT_VAR] = target.projectName;
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
writeFileSync7(path, setTomlVars(raw, table, vars));
|
|
1604
|
-
}
|
|
1605
|
-
function removeWranglerSecretsLink(appDir2, wranglerEnv2) {
|
|
1606
|
-
removeWranglerSecretsLinkKeys(appDir2, wranglerEnv2, ["project", "config"]);
|
|
1607
|
-
}
|
|
1608
|
-
function removeWranglerSecretsLinkKeys(appDir2, wranglerEnv2, keys) {
|
|
1609
|
-
const path = join8(resolve3(appDir2), "wrangler.toml");
|
|
1610
|
-
const raw = readFileSync7(path, "utf-8");
|
|
1611
|
-
const table = wranglerEnv2 ? `env.${wranglerEnv2}.vars` : "vars";
|
|
1612
|
-
const vars = {};
|
|
1613
|
-
if (keys.includes("project")) vars[DEEPSPACE_SECRETS_PROJECT_VAR] = void 0;
|
|
1614
|
-
if (keys.includes("config")) vars[DEEPSPACE_SECRETS_CONFIG_VAR] = void 0;
|
|
1615
|
-
if (Object.keys(vars).length === 0) return;
|
|
1616
|
-
writeFileSync7(path, setTomlVars(raw, table, vars));
|
|
1617
|
-
}
|
|
1618
|
-
function updateWranglerRequiredSecrets(appDir2, secretNames, opts = {}) {
|
|
1619
|
-
const path = join8(resolve3(appDir2), "wrangler.toml");
|
|
1620
|
-
const raw = readFileSync7(path, "utf-8");
|
|
1621
|
-
const existing = readRequiredSecretNamesFromToml(raw);
|
|
1622
|
-
const incoming = normalizeRequiredSecretNames(secretNames);
|
|
1623
|
-
const next = opts.replace ? incoming : normalizeRequiredSecretNames([...existing, ...incoming]);
|
|
1624
|
-
writeFileSync7(
|
|
1625
|
-
path,
|
|
1626
|
-
setRequiredSecretNamesInToml(raw, next, existing.length > 0 || hasRequiredSecretsBlock(raw))
|
|
1627
|
-
);
|
|
1628
|
-
return next;
|
|
1629
|
-
}
|
|
1630
|
-
function removeWranglerRequiredSecrets(appDir2, secretNames) {
|
|
1631
|
-
const path = join8(resolve3(appDir2), "wrangler.toml");
|
|
1632
|
-
const raw = readFileSync7(path, "utf-8");
|
|
1633
|
-
const remove2 = new Set(normalizeRequiredSecretNames(secretNames));
|
|
1634
|
-
const next = readRequiredSecretNamesFromToml(raw).filter((key) => !remove2.has(key));
|
|
1635
|
-
writeFileSync7(path, setRequiredSecretNamesInToml(raw, next, hasRequiredSecretsBlock(raw)));
|
|
1636
|
-
return next;
|
|
1637
|
-
}
|
|
1638
|
-
function quoteDotenvValue(value) {
|
|
1639
|
-
if (value === "") return '""';
|
|
1640
|
-
if (/^[^\s"'#=]+$/u.test(value)) return value;
|
|
1641
|
-
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1642
|
-
}
|
|
1643
|
-
var SecretsApiError = class extends Error {
|
|
1644
|
-
status;
|
|
1645
|
-
detail;
|
|
1646
|
-
constructor(path, status2, detail) {
|
|
1647
|
-
super(`Secrets API ${path} (${status2}): ${detail}`);
|
|
1648
|
-
this.name = "SecretsApiError";
|
|
1649
|
-
this.status = status2;
|
|
1650
|
-
this.detail = detail;
|
|
1651
|
-
}
|
|
1652
|
-
};
|
|
1653
|
-
async function secretsApi(deployUrl, token, path, init = {}, options = {}) {
|
|
1654
|
-
const scopedPath = withSecretsApiOptions(path, options);
|
|
1655
|
-
let res = await fetch(`${deployUrl}${scopedPath}`, {
|
|
1656
|
-
...init,
|
|
1657
|
-
headers: {
|
|
1658
|
-
Authorization: `Bearer ${token}`,
|
|
1659
|
-
"Content-Type": "application/json",
|
|
1660
|
-
...init.headers ?? {}
|
|
1661
|
-
}
|
|
1662
|
-
});
|
|
1663
|
-
let text2 = await res.text();
|
|
1664
|
-
const parsedError = parseSecretsApiError(text2);
|
|
1665
|
-
if (!res.ok && res.status === 404 && parsedError === "app_not_found" && options.appName) {
|
|
1666
|
-
res = await fetch(`${deployUrl}${path}`, {
|
|
1667
|
-
...init,
|
|
1668
|
-
headers: {
|
|
1669
|
-
Authorization: `Bearer ${token}`,
|
|
1670
|
-
"Content-Type": "application/json",
|
|
1671
|
-
...init.headers ?? {}
|
|
1672
|
-
}
|
|
1673
|
-
});
|
|
1674
|
-
text2 = await res.text();
|
|
1675
|
-
}
|
|
1676
|
-
if (!res.ok) {
|
|
1677
|
-
let detail = text2;
|
|
1678
|
-
try {
|
|
1679
|
-
detail = JSON.parse(text2).error ?? text2;
|
|
1680
|
-
} catch {
|
|
1681
|
-
}
|
|
1682
|
-
throw new SecretsApiError(path, res.status, detail);
|
|
1683
|
-
}
|
|
1684
|
-
if (!text2) return void 0;
|
|
1685
|
-
return JSON.parse(text2);
|
|
1686
|
-
}
|
|
1687
|
-
function withSecretsApiOptions(path, options) {
|
|
1688
|
-
if (!options.appName) return path;
|
|
1689
|
-
const sep2 = path.includes("?") ? "&" : "?";
|
|
1690
|
-
return `${path}${sep2}appName=${encodeURIComponent(options.appName)}`;
|
|
1691
|
-
}
|
|
1692
|
-
function parseSecretsApiError(text2) {
|
|
1693
|
-
try {
|
|
1694
|
-
const parsed = JSON.parse(text2);
|
|
1695
|
-
return typeof parsed.error === "string" ? parsed.error : null;
|
|
1696
|
-
} catch {
|
|
1697
|
-
return null;
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
function setTomlVars(raw, table, vars) {
|
|
1701
|
-
const lines = raw.replace(/\r\n/g, "\n").split("\n");
|
|
1702
|
-
const header = `[${table}]`;
|
|
1703
|
-
const headerRe = new RegExp(`^\\s*\\[${escapeRegExp(table)}\\]\\s*(?:#.*)?$`);
|
|
1704
|
-
let start = lines.findIndex((line) => headerRe.test(line));
|
|
1705
|
-
if (start < 0) {
|
|
1706
|
-
const additions = Object.entries(vars).filter(([, value]) => value !== void 0).map(([key, value]) => `${key} = ${JSON.stringify(value)}`);
|
|
1707
|
-
const prefix = raw.trimEnd();
|
|
1708
|
-
return `${prefix}${prefix ? "\n\n" : ""}${header}
|
|
1709
|
-
${additions.join("\n")}
|
|
1710
|
-
`;
|
|
1711
|
-
}
|
|
1712
|
-
let end = start + 1;
|
|
1713
|
-
while (end < lines.length && !/^\s*\[\[?[^\]]+\]\]?\s*(?:#.*)?$/u.test(lines[end])) end++;
|
|
1714
|
-
const tableLines = lines.slice(start + 1, end);
|
|
1715
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1716
|
-
const nextTableLines = [];
|
|
1717
|
-
for (const line of tableLines) {
|
|
1718
|
-
const key = Object.keys(vars).find(
|
|
1719
|
-
(name) => new RegExp(`^\\s*${escapeRegExp(name)}\\s*=`).test(line)
|
|
1720
|
-
);
|
|
1721
|
-
if (!key) {
|
|
1722
|
-
nextTableLines.push(line);
|
|
1723
|
-
continue;
|
|
1724
|
-
}
|
|
1725
|
-
seen.add(key);
|
|
1726
|
-
const value = vars[key];
|
|
1727
|
-
if (value !== void 0) nextTableLines.push(`${key} = ${JSON.stringify(value)}`);
|
|
1728
|
-
}
|
|
1729
|
-
for (const [key, value] of Object.entries(vars)) {
|
|
1730
|
-
if (!seen.has(key) && value !== void 0) {
|
|
1731
|
-
nextTableLines.push(`${key} = ${JSON.stringify(value)}`);
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
lines.splice(start + 1, end - start - 1, ...nextTableLines);
|
|
1735
|
-
return lines.join("\n").replace(/\s*$/u, "\n");
|
|
1736
|
-
}
|
|
1737
|
-
function readRequiredSecretNamesFromToml(raw) {
|
|
1738
|
-
const block = findRequiredSecretsBlock(raw);
|
|
1739
|
-
if (!block) return [];
|
|
1740
|
-
const names = [];
|
|
1741
|
-
for (const line of block.lines.slice(block.start + 1, block.end)) {
|
|
1742
|
-
const match = /^\s*#\s+([A-Za-z_][A-Za-z0-9_]*)\s*$/u.exec(line);
|
|
1743
|
-
if (match) names.push(validateSecretName(match[1]));
|
|
1744
|
-
}
|
|
1745
|
-
return normalizeRequiredSecretNames(names);
|
|
1746
|
-
}
|
|
1747
|
-
function setRequiredSecretNamesInToml(raw, names, keepEmptyBlock) {
|
|
1748
|
-
const normalized = raw.replace(/\r\n/g, "\n").replace(/\s*$/u, "\n");
|
|
1749
|
-
const block = findRequiredSecretsBlock(normalized);
|
|
1750
|
-
if (names.length === 0 && !block && !keepEmptyBlock) return normalized;
|
|
1751
|
-
const rendered = renderRequiredSecretsBlock(names);
|
|
1752
|
-
if (block) {
|
|
1753
|
-
const lines2 = [...block.lines];
|
|
1754
|
-
lines2.splice(block.start, block.end - block.start + 1, ...rendered);
|
|
1755
|
-
return lines2.join("\n").replace(/\s*$/u, "\n");
|
|
1756
|
-
}
|
|
1757
|
-
const lines = normalized.split("\n");
|
|
1758
|
-
if (lines[lines.length - 1] === "") lines.pop();
|
|
1759
|
-
const insertAt = requiredSecretsInsertIndex(lines);
|
|
1760
|
-
const insertion = insertAt < lines.length ? [...rendered, ""] : lines.length > 0 ? ["", ...rendered] : rendered;
|
|
1761
|
-
lines.splice(insertAt, 0, ...insertion);
|
|
1762
|
-
return lines.join("\n").replace(/\s*$/u, "\n");
|
|
1763
|
-
}
|
|
1764
|
-
function renderRequiredSecretsBlock(names) {
|
|
1765
|
-
return [
|
|
1766
|
-
REQUIRED_SECRETS_BEGIN,
|
|
1767
|
-
...names.length > 0 ? names.map((name) => `# ${name}`) : ["# (none declared)"],
|
|
1768
|
-
REQUIRED_SECRETS_END
|
|
1769
|
-
];
|
|
1770
|
-
}
|
|
1771
|
-
function normalizeRequiredSecretNames(secretNames) {
|
|
1772
|
-
return Array.from(new Set(Array.from(secretNames, (name) => validateSecretName(name)))).sort(
|
|
1773
|
-
(a, b) => a.localeCompare(b)
|
|
1774
|
-
);
|
|
1775
|
-
}
|
|
1776
|
-
function hasRequiredSecretsBlock(raw) {
|
|
1777
|
-
return findRequiredSecretsBlock(raw) !== null;
|
|
1778
|
-
}
|
|
1779
|
-
function findRequiredSecretsBlock(raw) {
|
|
1780
|
-
const lines = raw.replace(/\r\n/g, "\n").split("\n");
|
|
1781
|
-
const start = lines.findIndex((line) => requiredSecretsBeginRe().test(line));
|
|
1782
|
-
if (start < 0) return null;
|
|
1783
|
-
const end = lines.findIndex((line, index) => index > start && requiredSecretsEndRe().test(line));
|
|
1784
|
-
return { lines, start, end: end < 0 ? start : end };
|
|
1785
|
-
}
|
|
1786
|
-
function requiredSecretsInsertIndex(lines) {
|
|
1787
|
-
const varsHeader = /^\s*\[vars\]\s*(?:#.*)?$/u;
|
|
1788
|
-
const tableHeader = /^\s*\[\[?[^\]]+\]\]?\s*(?:#.*)?$/u;
|
|
1789
|
-
const varsStart = lines.findIndex((line) => varsHeader.test(line));
|
|
1790
|
-
if (varsStart < 0) return lines.length;
|
|
1791
|
-
let end = varsStart + 1;
|
|
1792
|
-
while (end < lines.length && !tableHeader.test(lines[end])) end++;
|
|
1793
|
-
while (end > varsStart + 1 && lines[end - 1].trim() === "") end--;
|
|
1794
|
-
return end;
|
|
1795
|
-
}
|
|
1796
|
-
function requiredSecretsBeginRe() {
|
|
1797
|
-
return /^# --- DeepSpace (?:detected|required) secrets\b.*---$/u;
|
|
1798
|
-
}
|
|
1799
|
-
function requiredSecretsEndRe() {
|
|
1800
|
-
return /^# --- end DeepSpace (?:detected|required) secrets ---$/u;
|
|
1801
|
-
}
|
|
1802
|
-
function escapeRegExp(value) {
|
|
1803
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
// src/cli/lib/port.ts
|
|
1807
|
-
var DEFAULT_PORT = 5173;
|
|
1808
|
-
function resolvePort(arg) {
|
|
1809
|
-
const raw = arg ?? process.env.DEEPSPACE_PORT;
|
|
1810
|
-
if (!raw) return DEFAULT_PORT;
|
|
1811
|
-
const n = Number(raw);
|
|
1812
|
-
if (!Number.isInteger(n) || n < 1 || n > 65535) {
|
|
1813
|
-
console.error(`Invalid port: ${raw}. Must be an integer between 1 and 65535.`);
|
|
1814
|
-
process.exit(1);
|
|
1815
|
-
}
|
|
1816
|
-
return n;
|
|
1380
|
+
return n;
|
|
1817
1381
|
}
|
|
1818
1382
|
|
|
1819
1383
|
// src/cli/commands/dev.ts
|
|
@@ -1842,10 +1406,10 @@ var dev_default = defineCommand3({
|
|
|
1842
1406
|
},
|
|
1843
1407
|
async run({ args }) {
|
|
1844
1408
|
preflightNodeVersion("dev");
|
|
1845
|
-
const
|
|
1409
|
+
const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
1846
1410
|
const start = resolve4(args.dir ?? ".");
|
|
1847
|
-
const
|
|
1848
|
-
if (!
|
|
1411
|
+
const appDir = findAppDir(start);
|
|
1412
|
+
if (!appDir) {
|
|
1849
1413
|
console.error(`No wrangler.toml found at or above ${start}.`);
|
|
1850
1414
|
const children = findChildApps(start);
|
|
1851
1415
|
if (children.length > 0) {
|
|
@@ -1856,20 +1420,20 @@ var dev_default = defineCommand3({
|
|
|
1856
1420
|
}
|
|
1857
1421
|
process.exit(1);
|
|
1858
1422
|
}
|
|
1859
|
-
const junk = removeMacosJunk(
|
|
1423
|
+
const junk = removeMacosJunk(appDir);
|
|
1860
1424
|
if (junk > 0) console.log(`Removed ${junk} macOS metadata file(s) (._*, .DS_Store)`);
|
|
1861
|
-
const worktree = detectClaudeWorktree(
|
|
1425
|
+
const worktree = detectClaudeWorktree(appDir);
|
|
1862
1426
|
const explicitPort = Boolean(args.port);
|
|
1863
1427
|
let port = worktree && !explicitPort ? deriveWorktreePort(worktree.worktreeName) : resolvePort(args.port);
|
|
1864
1428
|
if (worktree) {
|
|
1865
1429
|
const upserted = upsertWorktreeLaunchConfig(
|
|
1866
1430
|
worktree.mainRepoRoot,
|
|
1867
1431
|
worktree.worktreeName,
|
|
1868
|
-
|
|
1432
|
+
appDir,
|
|
1869
1433
|
{
|
|
1870
1434
|
port,
|
|
1871
1435
|
probePort: !explicitPort,
|
|
1872
|
-
extraArgs: [...
|
|
1436
|
+
extraArgs: [...wranglerEnv ? ["--env", wranglerEnv] : []]
|
|
1873
1437
|
}
|
|
1874
1438
|
);
|
|
1875
1439
|
if (upserted) {
|
|
@@ -1888,8 +1452,8 @@ var dev_default = defineCommand3({
|
|
|
1888
1452
|
);
|
|
1889
1453
|
}
|
|
1890
1454
|
}
|
|
1891
|
-
writeLaunchConfigIfMissing(
|
|
1892
|
-
ensureInstallReady(
|
|
1455
|
+
writeLaunchConfigIfMissing(appDir, detectAppName(appDir) ?? basename(appDir), port);
|
|
1456
|
+
ensureInstallReady(appDir);
|
|
1893
1457
|
let token;
|
|
1894
1458
|
try {
|
|
1895
1459
|
token = await ensureToken();
|
|
@@ -1905,39 +1469,40 @@ var dev_default = defineCommand3({
|
|
|
1905
1469
|
process.exit(1);
|
|
1906
1470
|
}
|
|
1907
1471
|
console.log(`Logged in as ${payload.name ?? payload.email}`);
|
|
1908
|
-
if (
|
|
1472
|
+
if (wranglerEnv) console.log(`Wrangler env: ${wranglerEnv}`);
|
|
1909
1473
|
console.log(`Port: ${port}`);
|
|
1910
1474
|
let generatedSecretsCache;
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1475
|
+
const appIdForSecrets = readAppId(appDir, wranglerEnv);
|
|
1476
|
+
if (appIdForSecrets) {
|
|
1477
|
+
try {
|
|
1478
|
+
const refreshed = await refreshSecretsCache(DEPLOY_URL, token, appIdForSecrets, wranglerEnv);
|
|
1479
|
+
if (refreshed) {
|
|
1480
|
+
generatedSecretsCache = refreshed.rendered;
|
|
1481
|
+
console.log(refreshed.summary);
|
|
1482
|
+
}
|
|
1483
|
+
} catch (err) {
|
|
1484
|
+
console.error(
|
|
1485
|
+
`Failed to refresh app secrets: ${err instanceof Error ? err.message : String(err)}`
|
|
1917
1486
|
);
|
|
1487
|
+
process.exit(1);
|
|
1918
1488
|
}
|
|
1919
|
-
} catch (err) {
|
|
1920
|
-
console.error(
|
|
1921
|
-
`Failed to refresh linked secrets: ${err instanceof Error ? err.message : String(err)}`
|
|
1922
|
-
);
|
|
1923
|
-
process.exit(1);
|
|
1924
1489
|
}
|
|
1925
1490
|
const sharedDevVarsCache = generatedSecretsCache !== void 0;
|
|
1926
|
-
await writeDevVars(
|
|
1491
|
+
await writeDevVars(appDir, payload.sub, token, wranglerEnv, {
|
|
1927
1492
|
generatedSecretsCache,
|
|
1928
1493
|
sharedDevVarsCache
|
|
1929
1494
|
});
|
|
1930
1495
|
let wranglerConfig;
|
|
1931
1496
|
try {
|
|
1932
|
-
wranglerConfig = prepareWranglerEnvConfig(
|
|
1497
|
+
wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv, { sharedDevVarsCache });
|
|
1933
1498
|
} catch (err) {
|
|
1934
1499
|
console.error(err instanceof Error ? err.message : String(err));
|
|
1935
1500
|
process.exit(1);
|
|
1936
1501
|
}
|
|
1937
|
-
preflightWindowsWorkerd(
|
|
1502
|
+
preflightWindowsWorkerd(appDir);
|
|
1938
1503
|
console.log("Starting dev server...\n");
|
|
1939
1504
|
const vite = spawn("npx", ["vite", "--port", String(port), "--strictPort", "--host"], {
|
|
1940
|
-
cwd:
|
|
1505
|
+
cwd: appDir,
|
|
1941
1506
|
stdio: "inherit",
|
|
1942
1507
|
env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
|
|
1943
1508
|
});
|
|
@@ -2110,18 +1675,18 @@ import { sync as spawnSync3 } from "cross-spawn";
|
|
|
2110
1675
|
// src/cli/lib/playwright.ts
|
|
2111
1676
|
import { execSync } from "child_process";
|
|
2112
1677
|
import { sync as spawnSync2 } from "cross-spawn";
|
|
2113
|
-
function ensurePlaywright(
|
|
1678
|
+
function ensurePlaywright(appDir) {
|
|
2114
1679
|
try {
|
|
2115
|
-
execSync("npx playwright --version", { cwd:
|
|
1680
|
+
execSync("npx playwright --version", { cwd: appDir, stdio: "pipe" });
|
|
2116
1681
|
} catch {
|
|
2117
1682
|
console.log("Installing Playwright...");
|
|
2118
|
-
execSync("npm install -D @playwright/test", { cwd:
|
|
1683
|
+
execSync("npm install -D @playwright/test", { cwd: appDir, stdio: "pipe" });
|
|
2119
1684
|
}
|
|
2120
1685
|
const isLinux = process.platform === "linux";
|
|
2121
1686
|
const isRoot = isLinux && typeof process.getuid === "function" && process.getuid() === 0;
|
|
2122
1687
|
const args = isRoot ? ["install", "--with-deps", "chromium"] : ["install", "chromium"];
|
|
2123
1688
|
const result = spawnSync2("npx", ["playwright", ...args], {
|
|
2124
|
-
cwd:
|
|
1689
|
+
cwd: appDir,
|
|
2125
1690
|
stdio: "inherit"
|
|
2126
1691
|
});
|
|
2127
1692
|
if (result.status !== 0) {
|
|
@@ -2158,10 +1723,10 @@ var test_default = defineCommand5({
|
|
|
2158
1723
|
async run({ args }) {
|
|
2159
1724
|
preflightNodeVersion("test");
|
|
2160
1725
|
const suite = args.suite ?? "default";
|
|
2161
|
-
const
|
|
1726
|
+
const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
2162
1727
|
const start = resolve5(".");
|
|
2163
|
-
const
|
|
2164
|
-
if (!
|
|
1728
|
+
const appDir = findAppDir(start);
|
|
1729
|
+
if (!appDir) {
|
|
2165
1730
|
console.error(`No wrangler.toml found at or above ${start}.`);
|
|
2166
1731
|
const children = findChildApps(start);
|
|
2167
1732
|
if (children.length > 0) {
|
|
@@ -2172,14 +1737,14 @@ var test_default = defineCommand5({
|
|
|
2172
1737
|
}
|
|
2173
1738
|
process.exit(1);
|
|
2174
1739
|
}
|
|
2175
|
-
const worktreePort = args.port ? null : resolveWorktreePort(
|
|
1740
|
+
const worktreePort = args.port ? null : resolveWorktreePort(appDir);
|
|
2176
1741
|
const port = worktreePort ?? resolvePort(args.port);
|
|
2177
1742
|
if (worktreePort && process.env.DEEPSPACE_PORT) {
|
|
2178
1743
|
console.log(
|
|
2179
1744
|
`Ignoring DEEPSPACE_PORT=${process.env.DEEPSPACE_PORT} inside a worktree \u2014 targeting per-worktree port ${port}. Pass --port to override.`
|
|
2180
1745
|
);
|
|
2181
1746
|
}
|
|
2182
|
-
ensureInstallReady(
|
|
1747
|
+
ensureInstallReady(appDir);
|
|
2183
1748
|
let token;
|
|
2184
1749
|
let ownerId;
|
|
2185
1750
|
try {
|
|
@@ -2191,72 +1756,73 @@ var test_default = defineCommand5({
|
|
|
2191
1756
|
process.exit(1);
|
|
2192
1757
|
}
|
|
2193
1758
|
let generatedSecretsCache;
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
1759
|
+
const appIdForSecrets = readAppId(appDir, wranglerEnv);
|
|
1760
|
+
if (appIdForSecrets) {
|
|
1761
|
+
try {
|
|
1762
|
+
const refreshed = await refreshSecretsCache(DEPLOY_URL2, token, appIdForSecrets, wranglerEnv);
|
|
1763
|
+
if (refreshed) {
|
|
1764
|
+
generatedSecretsCache = refreshed.rendered;
|
|
1765
|
+
console.log(refreshed.summary);
|
|
1766
|
+
}
|
|
1767
|
+
} catch (err) {
|
|
1768
|
+
console.error(
|
|
1769
|
+
`Failed to refresh app secrets: ${err instanceof Error ? err.message : String(err)}`
|
|
2200
1770
|
);
|
|
1771
|
+
process.exit(1);
|
|
2201
1772
|
}
|
|
2202
|
-
} catch (err) {
|
|
2203
|
-
console.error(
|
|
2204
|
-
`Failed to refresh linked secrets: ${err instanceof Error ? err.message : String(err)}`
|
|
2205
|
-
);
|
|
2206
|
-
process.exit(1);
|
|
2207
1773
|
}
|
|
2208
1774
|
const sharedDevVarsCache = generatedSecretsCache !== void 0;
|
|
2209
|
-
await writeDevVars(
|
|
1775
|
+
await writeDevVars(appDir, ownerId, token, wranglerEnv, {
|
|
2210
1776
|
generatedSecretsCache,
|
|
2211
1777
|
sharedDevVarsCache
|
|
2212
1778
|
});
|
|
2213
1779
|
if (suite !== "unit") {
|
|
2214
|
-
preflightWindowsWorkerd(
|
|
2215
|
-
ensurePlaywright(
|
|
1780
|
+
preflightWindowsWorkerd(appDir);
|
|
1781
|
+
ensurePlaywright(appDir);
|
|
2216
1782
|
}
|
|
2217
1783
|
let exitCode = 0;
|
|
2218
1784
|
switch (suite) {
|
|
2219
1785
|
case "smoke":
|
|
2220
1786
|
exitCode = runPlaywright(
|
|
2221
|
-
|
|
1787
|
+
appDir,
|
|
2222
1788
|
["tests/smoke.spec.ts"],
|
|
2223
1789
|
port,
|
|
2224
|
-
|
|
1790
|
+
wranglerEnv,
|
|
2225
1791
|
sharedDevVarsCache
|
|
2226
1792
|
);
|
|
2227
1793
|
break;
|
|
2228
1794
|
case "api":
|
|
2229
1795
|
exitCode = runPlaywright(
|
|
2230
|
-
|
|
1796
|
+
appDir,
|
|
2231
1797
|
["tests/api.spec.ts"],
|
|
2232
1798
|
port,
|
|
2233
|
-
|
|
1799
|
+
wranglerEnv,
|
|
2234
1800
|
sharedDevVarsCache
|
|
2235
1801
|
);
|
|
2236
1802
|
break;
|
|
2237
1803
|
case "e2e":
|
|
2238
|
-
exitCode = runPlaywright(
|
|
1804
|
+
exitCode = runPlaywright(appDir, [], port, wranglerEnv, sharedDevVarsCache);
|
|
2239
1805
|
break;
|
|
2240
1806
|
case "unit":
|
|
2241
|
-
exitCode = runVitest(
|
|
1807
|
+
exitCode = runVitest(appDir);
|
|
2242
1808
|
break;
|
|
2243
1809
|
case "all":
|
|
2244
|
-
exitCode = runVitest(
|
|
1810
|
+
exitCode = runVitest(appDir);
|
|
2245
1811
|
if (exitCode === 0)
|
|
2246
|
-
exitCode = runPlaywright(
|
|
1812
|
+
exitCode = runPlaywright(appDir, [], port, wranglerEnv, sharedDevVarsCache);
|
|
2247
1813
|
break;
|
|
2248
1814
|
case "default":
|
|
2249
1815
|
exitCode = runPlaywright(
|
|
2250
|
-
|
|
1816
|
+
appDir,
|
|
2251
1817
|
["tests/smoke.spec.ts", "tests/api.spec.ts"],
|
|
2252
1818
|
port,
|
|
2253
|
-
|
|
1819
|
+
wranglerEnv,
|
|
2254
1820
|
sharedDevVarsCache
|
|
2255
1821
|
);
|
|
2256
1822
|
break;
|
|
2257
1823
|
default:
|
|
2258
1824
|
if (suite.endsWith(".spec.ts")) {
|
|
2259
|
-
exitCode = runPlaywright(
|
|
1825
|
+
exitCode = runPlaywright(appDir, [suite], port, wranglerEnv, sharedDevVarsCache);
|
|
2260
1826
|
} else {
|
|
2261
1827
|
console.error(`Unknown test suite: ${suite}`);
|
|
2262
1828
|
console.error("Available: smoke, api, e2e, unit, all");
|
|
@@ -2266,10 +1832,10 @@ var test_default = defineCommand5({
|
|
|
2266
1832
|
process.exit(exitCode);
|
|
2267
1833
|
}
|
|
2268
1834
|
});
|
|
2269
|
-
function runPlaywright(
|
|
1835
|
+
function runPlaywright(appDir, testFiles, port, wranglerEnv, sharedDevVarsCache = false) {
|
|
2270
1836
|
let wranglerConfig;
|
|
2271
1837
|
try {
|
|
2272
|
-
wranglerConfig = prepareWranglerEnvConfig(
|
|
1838
|
+
wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv, { sharedDevVarsCache });
|
|
2273
1839
|
} catch (err) {
|
|
2274
1840
|
console.error(err instanceof Error ? err.message : String(err));
|
|
2275
1841
|
return 1;
|
|
@@ -2279,7 +1845,7 @@ function runPlaywright(appDir2, testFiles, port, wranglerEnv2, sharedDevVarsCach
|
|
|
2279
1845
|
"npx",
|
|
2280
1846
|
["playwright", "test", "--config", "tests/playwright.config.ts", ...testFiles],
|
|
2281
1847
|
{
|
|
2282
|
-
cwd:
|
|
1848
|
+
cwd: appDir,
|
|
2283
1849
|
stdio: "inherit",
|
|
2284
1850
|
env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
|
|
2285
1851
|
}
|
|
@@ -2289,9 +1855,9 @@ function runPlaywright(appDir2, testFiles, port, wranglerEnv2, sharedDevVarsCach
|
|
|
2289
1855
|
wranglerConfig.cleanup();
|
|
2290
1856
|
}
|
|
2291
1857
|
}
|
|
2292
|
-
function runVitest(
|
|
1858
|
+
function runVitest(appDir) {
|
|
2293
1859
|
const result = spawnSync3("npx", ["vitest", "run", "--passWithNoTests"], {
|
|
2294
|
-
cwd:
|
|
1860
|
+
cwd: appDir,
|
|
2295
1861
|
stdio: "inherit"
|
|
2296
1862
|
});
|
|
2297
1863
|
return result.status ?? 1;
|
|
@@ -2329,15 +1895,15 @@ var screenshot_default = defineCommand6({
|
|
|
2329
1895
|
}
|
|
2330
1896
|
},
|
|
2331
1897
|
run({ args }) {
|
|
2332
|
-
const
|
|
2333
|
-
ensurePlaywright(
|
|
1898
|
+
const appDir = resolve6(".");
|
|
1899
|
+
ensurePlaywright(appDir);
|
|
2334
1900
|
const playwrightArgs = ["playwright", "screenshot", args.url, args.output];
|
|
2335
1901
|
if (args["full-page"]) playwrightArgs.push("--full-page");
|
|
2336
1902
|
if (args["wait-for-timeout"]) {
|
|
2337
1903
|
playwrightArgs.push("--wait-for-timeout", String(args["wait-for-timeout"]));
|
|
2338
1904
|
}
|
|
2339
1905
|
const result = spawnSync4("npx", playwrightArgs, {
|
|
2340
|
-
cwd:
|
|
1906
|
+
cwd: appDir,
|
|
2341
1907
|
stdio: "inherit"
|
|
2342
1908
|
});
|
|
2343
1909
|
process.exit(result.status ?? 1);
|
|
@@ -2631,16 +2197,220 @@ var test_accounts_default = defineCommand7({
|
|
|
2631
2197
|
delete: del,
|
|
2632
2198
|
clear
|
|
2633
2199
|
}
|
|
2634
|
-
});
|
|
2200
|
+
});
|
|
2201
|
+
|
|
2202
|
+
// src/cli/commands/deploy.ts
|
|
2203
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
2204
|
+
import { execSync as execSync2 } from "child_process";
|
|
2205
|
+
import { randomUUID } from "crypto";
|
|
2206
|
+
import { readFileSync as readFileSync9, readdirSync as readdirSync3, existsSync as existsSync10, writeFileSync as writeFileSync9 } from "fs";
|
|
2207
|
+
import { join as join11, resolve as resolve7, dirname as dirname2 } from "path";
|
|
2208
|
+
import { pathToFileURL } from "url";
|
|
2209
|
+
import * as p4 from "@clack/prompts";
|
|
2210
|
+
|
|
2211
|
+
// src/server/rooms/binding-manifest.ts
|
|
2212
|
+
var AUTO_PROVISION_SENTINEL = "auto";
|
|
2213
|
+
var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
|
|
2214
|
+
var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
|
|
2215
|
+
"vectorize",
|
|
2216
|
+
"ai",
|
|
2217
|
+
"r2_bucket",
|
|
2218
|
+
"kv_namespace",
|
|
2219
|
+
"d1",
|
|
2220
|
+
"queue",
|
|
2221
|
+
"browser_rendering",
|
|
2222
|
+
"analytics_engine",
|
|
2223
|
+
"hyperdrive"
|
|
2224
|
+
]);
|
|
2225
|
+
var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
2226
|
+
"ASSETS",
|
|
2227
|
+
"PLATFORM_WORKER",
|
|
2228
|
+
"API_WORKER",
|
|
2229
|
+
"APP_NAME",
|
|
2230
|
+
"OWNER_USER_ID",
|
|
2231
|
+
"AUTH_JWT_PUBLIC_KEY",
|
|
2232
|
+
"AUTH_JWT_ISSUER",
|
|
2233
|
+
"AUTH_WORKER_URL",
|
|
2234
|
+
"APP_IDENTITY_TOKEN",
|
|
2235
|
+
"APP_OWNER_JWT",
|
|
2236
|
+
"USAGE_EVENTS"
|
|
2237
|
+
]);
|
|
2238
|
+
function validateBindingManifest(manifest) {
|
|
2239
|
+
if (!Array.isArray(manifest)) {
|
|
2240
|
+
return { valid: false, errors: [{ reason: "Manifest must be an array" }] };
|
|
2241
|
+
}
|
|
2242
|
+
const errors = [];
|
|
2243
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
2244
|
+
for (const entry of manifest) {
|
|
2245
|
+
if (!entry || typeof entry !== "object") {
|
|
2246
|
+
errors.push({ reason: `Entry must be an object (got ${entry === null ? "null" : typeof entry})` });
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2249
|
+
const e = entry;
|
|
2250
|
+
const type = e.type;
|
|
2251
|
+
const name = e.name;
|
|
2252
|
+
if (typeof type !== "string" || !ALLOWED_BINDING_TYPES.has(type)) {
|
|
2253
|
+
errors.push({
|
|
2254
|
+
reason: `Disallowed binding type: ${typeof type === "string" ? type : `(${typeof type})`}. Allowed: ${[...ALLOWED_BINDING_TYPES].sort().join(", ")}`
|
|
2255
|
+
});
|
|
2256
|
+
continue;
|
|
2257
|
+
}
|
|
2258
|
+
if (typeof name !== "string" || !name) {
|
|
2259
|
+
errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
|
|
2260
|
+
continue;
|
|
2261
|
+
}
|
|
2262
|
+
if (RESERVED_BINDING_NAMES.has(name)) {
|
|
2263
|
+
errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
|
|
2264
|
+
continue;
|
|
2265
|
+
}
|
|
2266
|
+
if (seenNames.has(name)) {
|
|
2267
|
+
errors.push({ reason: `Duplicate binding name "${name}"` });
|
|
2268
|
+
continue;
|
|
2269
|
+
}
|
|
2270
|
+
seenNames.add(name);
|
|
2271
|
+
const binding = entry;
|
|
2272
|
+
const fieldErr = requiredFieldError(binding);
|
|
2273
|
+
if (fieldErr) errors.push({ binding, reason: fieldErr });
|
|
2274
|
+
}
|
|
2275
|
+
if (errors.length) return { valid: false, errors };
|
|
2276
|
+
return { valid: true, bindings: manifest };
|
|
2277
|
+
}
|
|
2278
|
+
function requiredFieldError(b) {
|
|
2279
|
+
switch (b.type) {
|
|
2280
|
+
case "vectorize": {
|
|
2281
|
+
if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
|
|
2282
|
+
if (b.index_name === AUTO_PROVISION_SENTINEL) {
|
|
2283
|
+
if (typeof b.dimensions !== "number" || b.dimensions <= 0) {
|
|
2284
|
+
return `vectorize binding "${b.name}" with index_name="auto" requires "dimensions" (positive number)`;
|
|
2285
|
+
}
|
|
2286
|
+
if (!b.metric || !VECTORIZE_METRICS.has(b.metric)) {
|
|
2287
|
+
return `vectorize binding "${b.name}" with index_name="auto" requires "metric" (one of: ${[...VECTORIZE_METRICS].join(", ")})`;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
return null;
|
|
2291
|
+
}
|
|
2292
|
+
case "r2_bucket":
|
|
2293
|
+
return b.bucket_name ? null : `r2_bucket binding "${b.name}" missing bucket_name (or "auto")`;
|
|
2294
|
+
case "kv_namespace": {
|
|
2295
|
+
if (!b.namespace_id) return `kv_namespace binding "${b.name}" missing namespace_id (or "auto")`;
|
|
2296
|
+
if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {
|
|
2297
|
+
return `kv_namespace binding "${b.name}" with namespace_id="auto" requires "title"`;
|
|
2298
|
+
}
|
|
2299
|
+
return null;
|
|
2300
|
+
}
|
|
2301
|
+
case "d1": {
|
|
2302
|
+
if (!b.id) return `d1 binding "${b.name}" missing id (or "auto")`;
|
|
2303
|
+
if (b.id === AUTO_PROVISION_SENTINEL && !b.database_name) {
|
|
2304
|
+
return `d1 binding "${b.name}" with id="auto" requires "database_name"`;
|
|
2305
|
+
}
|
|
2306
|
+
return null;
|
|
2307
|
+
}
|
|
2308
|
+
case "queue":
|
|
2309
|
+
return b.queue_name ? null : `queue binding "${b.name}" missing queue_name (or "auto")`;
|
|
2310
|
+
case "hyperdrive":
|
|
2311
|
+
if (!b.id) return `hyperdrive binding "${b.name}" missing id`;
|
|
2312
|
+
if (b.id === AUTO_PROVISION_SENTINEL) {
|
|
2313
|
+
return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
|
|
2314
|
+
}
|
|
2315
|
+
return null;
|
|
2316
|
+
case "ai":
|
|
2317
|
+
case "browser_rendering":
|
|
2318
|
+
case "analytics_engine":
|
|
2319
|
+
return null;
|
|
2320
|
+
// no required fields beyond `name`
|
|
2321
|
+
default: {
|
|
2322
|
+
const _exhaustive = b;
|
|
2323
|
+
void _exhaustive;
|
|
2324
|
+
return null;
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
function bindingManifestFromOutputConfig(outputConfig) {
|
|
2329
|
+
const out = [];
|
|
2330
|
+
const aiBinding = pluckString(outputConfig.ai, "binding");
|
|
2331
|
+
if (aiBinding) out.push({ type: "ai", name: aiBinding });
|
|
2332
|
+
for (const v of asObjectArray(outputConfig.vectorize)) {
|
|
2333
|
+
const name = pluckString(v, "binding");
|
|
2334
|
+
const index_name = pluckString(v, "index_name");
|
|
2335
|
+
if (!name || !index_name) continue;
|
|
2336
|
+
const dimensions = pluckNumber(v, "dimensions");
|
|
2337
|
+
const metric = pluckString(v, "metric");
|
|
2338
|
+
out.push({
|
|
2339
|
+
type: "vectorize",
|
|
2340
|
+
name,
|
|
2341
|
+
index_name,
|
|
2342
|
+
...dimensions != null && { dimensions },
|
|
2343
|
+
...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
for (const r of asObjectArray(outputConfig.r2_buckets)) {
|
|
2347
|
+
const name = pluckString(r, "binding");
|
|
2348
|
+
const bucket_name = pluckString(r, "bucket_name");
|
|
2349
|
+
if (name && bucket_name) out.push({ type: "r2_bucket", name, bucket_name });
|
|
2350
|
+
}
|
|
2351
|
+
for (const k of asObjectArray(outputConfig.kv_namespaces)) {
|
|
2352
|
+
const name = pluckString(k, "binding");
|
|
2353
|
+
const namespace_id = pluckString(k, "id");
|
|
2354
|
+
if (!name || !namespace_id) continue;
|
|
2355
|
+
const title = pluckString(k, "title");
|
|
2356
|
+
out.push({
|
|
2357
|
+
type: "kv_namespace",
|
|
2358
|
+
name,
|
|
2359
|
+
namespace_id,
|
|
2360
|
+
...title && { title }
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
for (const d of asObjectArray(outputConfig.d1_databases)) {
|
|
2364
|
+
const name = pluckString(d, "binding");
|
|
2365
|
+
const id = pluckString(d, "database_id");
|
|
2366
|
+
if (!name || !id) continue;
|
|
2367
|
+
const database_name = pluckString(d, "database_name");
|
|
2368
|
+
out.push({
|
|
2369
|
+
type: "d1",
|
|
2370
|
+
name,
|
|
2371
|
+
id,
|
|
2372
|
+
...database_name && { database_name }
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
2375
|
+
const queues = isObject(outputConfig.queues) ? outputConfig.queues : null;
|
|
2376
|
+
for (const q of asObjectArray(queues?.producers)) {
|
|
2377
|
+
const name = pluckString(q, "binding");
|
|
2378
|
+
const queue_name = pluckString(q, "queue");
|
|
2379
|
+
if (name && queue_name) out.push({ type: "queue", name, queue_name });
|
|
2380
|
+
}
|
|
2381
|
+
const browserBinding = pluckString(outputConfig.browser, "binding");
|
|
2382
|
+
if (browserBinding) out.push({ type: "browser_rendering", name: browserBinding });
|
|
2383
|
+
for (const a of asObjectArray(outputConfig.analytics_engine_datasets)) {
|
|
2384
|
+
const name = pluckString(a, "binding");
|
|
2385
|
+
const dataset = pluckString(a, "dataset");
|
|
2386
|
+
if (name) out.push({ type: "analytics_engine", name, dataset: dataset ?? void 0 });
|
|
2387
|
+
}
|
|
2388
|
+
for (const h of asObjectArray(outputConfig.hyperdrive)) {
|
|
2389
|
+
const name = pluckString(h, "binding");
|
|
2390
|
+
const id = pluckString(h, "id");
|
|
2391
|
+
if (name && id) out.push({ type: "hyperdrive", name, id });
|
|
2392
|
+
}
|
|
2393
|
+
return out;
|
|
2394
|
+
}
|
|
2395
|
+
function isObject(v) {
|
|
2396
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
2397
|
+
}
|
|
2398
|
+
function pluckString(v, key) {
|
|
2399
|
+
if (!isObject(v)) return null;
|
|
2400
|
+
const val = v[key];
|
|
2401
|
+
return typeof val === "string" && val.length > 0 ? val : null;
|
|
2402
|
+
}
|
|
2403
|
+
function pluckNumber(v, key) {
|
|
2404
|
+
if (!isObject(v)) return null;
|
|
2405
|
+
const val = v[key];
|
|
2406
|
+
return typeof val === "number" && Number.isFinite(val) ? val : null;
|
|
2407
|
+
}
|
|
2408
|
+
function asObjectArray(v) {
|
|
2409
|
+
if (!Array.isArray(v)) return [];
|
|
2410
|
+
return v.filter(isObject);
|
|
2411
|
+
}
|
|
2635
2412
|
|
|
2636
2413
|
// src/cli/commands/deploy.ts
|
|
2637
|
-
import { defineCommand as defineCommand8 } from "citty";
|
|
2638
|
-
import { execSync as execSync2 } from "child_process";
|
|
2639
|
-
import { randomUUID } from "crypto";
|
|
2640
|
-
import { readFileSync as readFileSync9, readdirSync as readdirSync3, existsSync as existsSync10 } from "fs";
|
|
2641
|
-
import { join as join11, resolve as resolve7, dirname as dirname2 } from "path";
|
|
2642
|
-
import { pathToFileURL } from "url";
|
|
2643
|
-
import * as p4 from "@clack/prompts";
|
|
2644
2414
|
var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
2645
2415
|
var API_URL = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
2646
2416
|
var CLOUDFLARE_DEPLOY_ERROR_HINT = "Deployment reached the DeepSpace deploy worker, but Cloudflare's deployment control-plane API failed. This is often caused by a Cloudflare Dashboard/API incident or Workers for Platforms entitlement outage. Already deployed apps should keep serving; wait for Cloudflare to recover and retry.";
|
|
@@ -2670,8 +2440,8 @@ function extractRunWorkerFirst(cfg) {
|
|
|
2670
2440
|
function errMessage(err) {
|
|
2671
2441
|
return err instanceof Error ? err.message : String(err);
|
|
2672
2442
|
}
|
|
2673
|
-
function isLikelyCloudflareDeployFailure(
|
|
2674
|
-
if (
|
|
2443
|
+
function isLikelyCloudflareDeployFailure(status3, error) {
|
|
2444
|
+
if (status3 >= 500) return true;
|
|
2675
2445
|
if (!error) return false;
|
|
2676
2446
|
return [
|
|
2677
2447
|
"Upload session failed",
|
|
@@ -2681,9 +2451,9 @@ function isLikelyCloudflareDeployFailure(status2, error) {
|
|
|
2681
2451
|
"Internal Server Error"
|
|
2682
2452
|
].some((needle) => error.includes(needle));
|
|
2683
2453
|
}
|
|
2684
|
-
function formatDeployWorkerError(
|
|
2685
|
-
const detail = error ?? `Deployment error (${
|
|
2686
|
-
if (!isLikelyCloudflareDeployFailure(
|
|
2454
|
+
function formatDeployWorkerError(status3, error) {
|
|
2455
|
+
const detail = error ?? `Deployment error (${status3})`;
|
|
2456
|
+
if (!isLikelyCloudflareDeployFailure(status3, error)) return detail;
|
|
2687
2457
|
return `${CLOUDFLARE_DEPLOY_ERROR_HINT}
|
|
2688
2458
|
|
|
2689
2459
|
Underlying error: ${detail}`;
|
|
@@ -2735,10 +2505,10 @@ async function postWithRetry(url, makeInit, {
|
|
|
2735
2505
|
if (lastRes) return lastRes;
|
|
2736
2506
|
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
2737
2507
|
}
|
|
2738
|
-
async function abortStagedUpload(
|
|
2508
|
+
async function abortStagedUpload(appId, uploadId, totalGroups, token) {
|
|
2739
2509
|
try {
|
|
2740
2510
|
await fetch(
|
|
2741
|
-
`${DEPLOY_URL3}/api/deploy/${
|
|
2511
|
+
`${DEPLOY_URL3}/api/deploy/${appId}/assets?uploadId=${uploadId}&totalGroups=${totalGroups}`,
|
|
2742
2512
|
{ method: "DELETE", headers: { Authorization: `Bearer ${token}` } }
|
|
2743
2513
|
);
|
|
2744
2514
|
} catch {
|
|
@@ -2759,19 +2529,24 @@ var deploy_default = defineCommand8({
|
|
|
2759
2529
|
type: "string",
|
|
2760
2530
|
description: "wrangler.toml [env.<name>] block to deploy (e.g. --env staging). Omit to deploy the top-level config.",
|
|
2761
2531
|
required: false
|
|
2532
|
+
},
|
|
2533
|
+
rename: {
|
|
2534
|
+
type: "boolean",
|
|
2535
|
+
description: "Confirm that a changed wrangler `name` renames this app (its URL moves; data, secrets, and collaborators travel). Without this flag an interactive prompt asks.",
|
|
2536
|
+
default: false
|
|
2762
2537
|
}
|
|
2763
2538
|
},
|
|
2764
2539
|
async run({ args }) {
|
|
2765
2540
|
preflightNodeVersion("deploy");
|
|
2766
|
-
const
|
|
2541
|
+
const appDir = resolve7(args.dir ?? ".");
|
|
2767
2542
|
const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
2768
|
-
if (!hasWranglerConfig(
|
|
2543
|
+
if (!hasWranglerConfig(appDir)) {
|
|
2769
2544
|
console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
|
|
2770
2545
|
process.exit(1);
|
|
2771
2546
|
}
|
|
2772
|
-
ensureInstallReady(
|
|
2547
|
+
ensureInstallReady(appDir);
|
|
2773
2548
|
p4.intro("Deploying DeepSpace app");
|
|
2774
|
-
const wranglerConfig = readWranglerConfig(
|
|
2549
|
+
const wranglerConfig = readWranglerConfig(appDir);
|
|
2775
2550
|
const nameRes = resolveAppNameForEnv(wranglerConfig, envName);
|
|
2776
2551
|
if (!nameRes.ok) {
|
|
2777
2552
|
p4.cancel(nameRes.reason);
|
|
@@ -2786,7 +2561,14 @@ var deploy_default = defineCommand8({
|
|
|
2786
2561
|
process.exit(1);
|
|
2787
2562
|
}
|
|
2788
2563
|
const appName = nameRes.name;
|
|
2564
|
+
let appId = readAppId(appDir, envName);
|
|
2565
|
+
if (!appId) {
|
|
2566
|
+
appId = mintAppId();
|
|
2567
|
+
writeAppId(appDir, appId, { wranglerEnv: envName });
|
|
2568
|
+
p4.log.info(`Minted app id ${appId} \u2014 commit wrangler.toml.`);
|
|
2569
|
+
}
|
|
2789
2570
|
p4.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
|
|
2571
|
+
p4.log.info(`Id: ${appId}`);
|
|
2790
2572
|
let token;
|
|
2791
2573
|
let ownerId;
|
|
2792
2574
|
try {
|
|
@@ -2796,41 +2578,33 @@ var deploy_default = defineCommand8({
|
|
|
2796
2578
|
p4.cancel(errMessage(err));
|
|
2797
2579
|
process.exit(1);
|
|
2798
2580
|
}
|
|
2799
|
-
const localSecretsLink = resolveSecretsLink(appDir2, envName);
|
|
2800
|
-
if (localSecretsLink.linked) {
|
|
2801
|
-
try {
|
|
2802
|
-
await setServerSecretsLink(DEPLOY_URL3, token, appName, localSecretsLink.projectName);
|
|
2803
|
-
} catch {
|
|
2804
|
-
}
|
|
2805
|
-
}
|
|
2806
2581
|
let linkedSecrets = null;
|
|
2807
2582
|
try {
|
|
2808
|
-
|
|
2809
|
-
if (
|
|
2810
|
-
|
|
2811
|
-
await writeDevVars(
|
|
2812
|
-
|
|
2583
|
+
const refreshed = await refreshSecretsCache(DEPLOY_URL3, token, appId, envName);
|
|
2584
|
+
if (refreshed) {
|
|
2585
|
+
linkedSecrets = refreshed.pulled;
|
|
2586
|
+
await writeDevVars(appDir, ownerId, token, envName, {
|
|
2587
|
+
appId,
|
|
2588
|
+
generatedSecretsCache: refreshed.rendered,
|
|
2813
2589
|
sharedDevVarsCache: true
|
|
2814
2590
|
});
|
|
2815
|
-
p4.log.info(
|
|
2816
|
-
`Secrets cache: ${linkedSecrets.projectName}/${linkedSecrets.configName} (${Object.keys(linkedSecrets.values).length} secret${Object.keys(linkedSecrets.values).length === 1 ? "" : "s"})`
|
|
2817
|
-
);
|
|
2591
|
+
p4.log.info(refreshed.summary);
|
|
2818
2592
|
}
|
|
2819
2593
|
} catch (err) {
|
|
2820
|
-
p4.cancel(`Failed to refresh
|
|
2594
|
+
p4.cancel(`Failed to refresh app secrets before deploy: ${errMessage(err)}`);
|
|
2821
2595
|
process.exit(1);
|
|
2822
2596
|
}
|
|
2823
|
-
const junk = removeMacosJunk(
|
|
2597
|
+
const junk = removeMacosJunk(appDir);
|
|
2824
2598
|
if (junk > 0) p4.log.info(`Removed ${junk} macOS metadata file(s) (._*, .DS_Store)`);
|
|
2825
2599
|
const s = createSpinner();
|
|
2826
2600
|
s.start("Building...");
|
|
2827
2601
|
let preparedWranglerConfig;
|
|
2828
2602
|
try {
|
|
2829
|
-
preparedWranglerConfig = prepareWranglerEnvConfig(
|
|
2830
|
-
sharedDevVarsCache:
|
|
2603
|
+
preparedWranglerConfig = prepareWranglerEnvConfig(appDir, envName, {
|
|
2604
|
+
sharedDevVarsCache: linkedSecrets !== null
|
|
2831
2605
|
});
|
|
2832
2606
|
execSync2("npx vite build", {
|
|
2833
|
-
cwd:
|
|
2607
|
+
cwd: appDir,
|
|
2834
2608
|
stdio: "pipe",
|
|
2835
2609
|
env: wranglerViteEnv(process.env, preparedWranglerConfig)
|
|
2836
2610
|
});
|
|
@@ -2844,7 +2618,7 @@ var deploy_default = defineCommand8({
|
|
|
2844
2618
|
preparedWranglerConfig?.cleanup();
|
|
2845
2619
|
}
|
|
2846
2620
|
s.stop("Built");
|
|
2847
|
-
const deployConfigPath = join11(
|
|
2621
|
+
const deployConfigPath = join11(appDir, ".wrangler", "deploy", "config.json");
|
|
2848
2622
|
if (!existsSync10(deployConfigPath)) {
|
|
2849
2623
|
p4.cancel("Build output config not found at .wrangler/deploy/config.json");
|
|
2850
2624
|
process.exit(1);
|
|
@@ -2906,15 +2680,13 @@ var deploy_default = defineCommand8({
|
|
|
2906
2680
|
`Custom bindings: ${customBindings.map((b) => `${b.name} (${b.type})`).join(", ")}`
|
|
2907
2681
|
);
|
|
2908
2682
|
}
|
|
2909
|
-
const devVarsPath = devVarsPathFor(
|
|
2910
|
-
sharedDevVarsCache:
|
|
2683
|
+
const devVarsPath = devVarsPathFor(appDir, envName, {
|
|
2684
|
+
sharedDevVarsCache: linkedSecrets !== null
|
|
2911
2685
|
});
|
|
2912
2686
|
let userSecrets = {};
|
|
2913
2687
|
if (existsSync10(devVarsPath)) {
|
|
2914
2688
|
const rawDevVars = readFileSync9(devVarsPath, "utf-8");
|
|
2915
|
-
const customSection = extractCustomDevVars(
|
|
2916
|
-
linkedSecrets ? rawDevVars : stripGeneratedSecretsCache2(rawDevVars)
|
|
2917
|
-
);
|
|
2689
|
+
const customSection = extractCustomDevVars(rawDevVars);
|
|
2918
2690
|
if (customSection) {
|
|
2919
2691
|
try {
|
|
2920
2692
|
userSecrets = parseDevVars(customSection);
|
|
@@ -2945,10 +2717,8 @@ var deploy_default = defineCommand8({
|
|
|
2945
2717
|
}
|
|
2946
2718
|
if (userSecretNames.length) {
|
|
2947
2719
|
p4.log.info(`App secrets (from .dev.vars): ${userSecretNames.join(", ")}`);
|
|
2948
|
-
if (!
|
|
2949
|
-
p4.log.info(
|
|
2950
|
-
"Legacy pass-through; move these to managed secrets with `npx deepspace secrets setup`."
|
|
2951
|
-
);
|
|
2720
|
+
if (!linkedSecrets) {
|
|
2721
|
+
p4.log.info("Not in the app store yet \u2014 migrating after this deploy.");
|
|
2952
2722
|
}
|
|
2953
2723
|
}
|
|
2954
2724
|
s.start(`Deploying to ${appName}.app.space...`);
|
|
@@ -2970,7 +2740,7 @@ var deploy_default = defineCommand8({
|
|
|
2970
2740
|
const groupJson = JSON.stringify(assetGroups[i]);
|
|
2971
2741
|
s.message(`Uploading assets \u2014 group ${i + 1}/${totalGroups}...`);
|
|
2972
2742
|
const groupRes = await postWithRetry(
|
|
2973
|
-
`${DEPLOY_URL3}/api/deploy/${
|
|
2743
|
+
`${DEPLOY_URL3}/api/deploy/${appId}/assets?uploadId=${uploadId}&groupIndex=${i}&totalGroups=${totalGroups}`,
|
|
2974
2744
|
() => ({
|
|
2975
2745
|
method: "POST",
|
|
2976
2746
|
headers: {
|
|
@@ -2982,7 +2752,7 @@ var deploy_default = defineCommand8({
|
|
|
2982
2752
|
);
|
|
2983
2753
|
if (!groupRes.ok) {
|
|
2984
2754
|
const errBody = await groupRes.json().catch(() => ({}));
|
|
2985
|
-
await abortStagedUpload(
|
|
2755
|
+
await abortStagedUpload(appId, uploadId, totalGroups, token);
|
|
2986
2756
|
s.stop("Deploy failed");
|
|
2987
2757
|
p4.cancel(
|
|
2988
2758
|
`Asset upload failed on group ${i + 1}/${totalGroups}: ${errBody.error ?? `HTTP ${groupRes.status}`}`
|
|
@@ -2991,13 +2761,21 @@ var deploy_default = defineCommand8({
|
|
|
2991
2761
|
}
|
|
2992
2762
|
}
|
|
2993
2763
|
} catch (err) {
|
|
2994
|
-
await abortStagedUpload(
|
|
2764
|
+
await abortStagedUpload(appId, uploadId, totalGroups, token);
|
|
2995
2765
|
s.stop("Deploy failed");
|
|
2996
2766
|
p4.cancel(`Asset upload failed (network): ${errMessage(err)}`);
|
|
2997
2767
|
process.exit(1);
|
|
2998
2768
|
}
|
|
2999
2769
|
s.message(`Deploying to ${appName}.app.space...`);
|
|
3000
2770
|
}
|
|
2771
|
+
const commitUrl = `${DEPLOY_URL3}/api/deploy/${appId}`;
|
|
2772
|
+
let confirmRename = args.rename === true;
|
|
2773
|
+
const bailDeploy = async (message, stopLabel = "Deploy failed") => {
|
|
2774
|
+
if (uploadId) await abortStagedUpload(appId, uploadId, totalGroups, token);
|
|
2775
|
+
if (stopLabel !== null) s.stop(stopLabel);
|
|
2776
|
+
p4.cancel(message);
|
|
2777
|
+
process.exit(1);
|
|
2778
|
+
};
|
|
3001
2779
|
const makeForm = () => {
|
|
3002
2780
|
const form = new FormData();
|
|
3003
2781
|
form.append("worker", new Blob([workerJs], { type: "application/javascript" }), "worker.js");
|
|
@@ -3019,38 +2797,45 @@ var deploy_default = defineCommand8({
|
|
|
3019
2797
|
if (extraRoutes.length) {
|
|
3020
2798
|
form.append("extraRunWorkerFirst", JSON.stringify(extraRoutes));
|
|
3021
2799
|
}
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
}
|
|
2800
|
+
form.append("name", appName);
|
|
2801
|
+
if (confirmRename) form.append("confirmRename", "true");
|
|
3025
2802
|
return form;
|
|
3026
2803
|
};
|
|
3027
|
-
const
|
|
2804
|
+
const postCommit = async () => postWithRetry(
|
|
2805
|
+
commitUrl,
|
|
2806
|
+
() => ({
|
|
2807
|
+
method: "POST",
|
|
2808
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
2809
|
+
body: makeForm()
|
|
2810
|
+
}),
|
|
2811
|
+
// Commit runs a real deploy — don't retry its 5xx (could double-deploy);
|
|
2812
|
+
// only a thrown fetch (dropped connection) is retried, and the staged
|
|
2813
|
+
// groups survive on the worker until this request succeeds.
|
|
2814
|
+
{ retryServerErrors: false }
|
|
2815
|
+
);
|
|
3028
2816
|
let res;
|
|
3029
2817
|
try {
|
|
3030
|
-
res = await
|
|
3031
|
-
commitUrl,
|
|
3032
|
-
() => ({
|
|
3033
|
-
method: "POST",
|
|
3034
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
3035
|
-
body: makeForm()
|
|
3036
|
-
}),
|
|
3037
|
-
// Commit runs a real deploy — don't retry its 5xx (could double-deploy);
|
|
3038
|
-
// only a thrown fetch (dropped connection) is retried, and the staged
|
|
3039
|
-
// groups survive on the worker until this request succeeds.
|
|
3040
|
-
{ retryServerErrors: false }
|
|
3041
|
-
);
|
|
2818
|
+
res = await postCommit();
|
|
3042
2819
|
} catch (err) {
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
2820
|
+
await bailDeploy(`Deploy request failed: ${errMessage(err)}`);
|
|
2821
|
+
throw err;
|
|
2822
|
+
}
|
|
2823
|
+
let body = await res.json().catch(() => ({}));
|
|
2824
|
+
if (res.status === 409 && body.code === "rename_required" && !confirmRename) {
|
|
2825
|
+
s.stop("Rename confirmation needed");
|
|
2826
|
+
const yes = await p4.confirm({
|
|
2827
|
+
message: `This deploy renames the app: ${body.fromHost} \u2192 ${body.toHost}. The URL changes and the old one stops serving right away; data, secrets, and collaborators travel with it. (Meant a separate app? Run \`deepspace init --new-id\` instead.) Rename?`
|
|
2828
|
+
});
|
|
2829
|
+
if (p4.isCancel(yes) || !yes) {
|
|
2830
|
+
await bailDeploy("Deploy cancelled.", null);
|
|
2831
|
+
}
|
|
2832
|
+
confirmRename = true;
|
|
2833
|
+
s.start(`Deploying to ${appName}.app.space...`);
|
|
2834
|
+
res = await postCommit();
|
|
2835
|
+
body = await res.json().catch(() => ({}));
|
|
3047
2836
|
}
|
|
3048
|
-
const body = await res.json().catch(() => ({}));
|
|
3049
2837
|
if (!res.ok || !body.success) {
|
|
3050
|
-
|
|
3051
|
-
s.stop("Deploy failed");
|
|
3052
|
-
p4.cancel(formatDeployWorkerError(res.status, body.error));
|
|
3053
|
-
process.exit(1);
|
|
2838
|
+
await bailDeploy(formatDeployWorkerError(res.status, body.error));
|
|
3054
2839
|
}
|
|
3055
2840
|
if (body.onBehalfOfOwner) {
|
|
3056
2841
|
p4.log.warn(`Deployed on behalf of owner ${body.onBehalfOfOwner}`);
|
|
@@ -3064,26 +2849,66 @@ var deploy_default = defineCommand8({
|
|
|
3064
2849
|
"First requests may briefly hit the assets transitional page; retry in a minute."
|
|
3065
2850
|
);
|
|
3066
2851
|
p4.log.success(`Live at: ${body.url}`);
|
|
3067
|
-
await
|
|
3068
|
-
|
|
2852
|
+
await migrateLegacyDevVarsSecrets({
|
|
2853
|
+
deployUrl: DEPLOY_URL3,
|
|
2854
|
+
token,
|
|
2855
|
+
appId,
|
|
2856
|
+
envName,
|
|
2857
|
+
devVarsPath,
|
|
2858
|
+
userSecrets,
|
|
2859
|
+
userSecretNames,
|
|
2860
|
+
hadStore: linkedSecrets !== null
|
|
2861
|
+
});
|
|
2862
|
+
await syncSubscriptionPlans(appDir, appId, token);
|
|
2863
|
+
await syncOneTimeProducts(appDir, appId, token);
|
|
3069
2864
|
p4.outro("Done");
|
|
3070
2865
|
return;
|
|
3071
2866
|
}
|
|
3072
2867
|
}
|
|
3073
2868
|
s.stop("Deployed!");
|
|
3074
2869
|
p4.log.success(`Live at: ${body.url}`);
|
|
3075
|
-
await
|
|
3076
|
-
|
|
2870
|
+
await migrateLegacyDevVarsSecrets({
|
|
2871
|
+
deployUrl: DEPLOY_URL3,
|
|
2872
|
+
token,
|
|
2873
|
+
appId,
|
|
2874
|
+
envName,
|
|
2875
|
+
devVarsPath,
|
|
2876
|
+
userSecrets,
|
|
2877
|
+
userSecretNames,
|
|
2878
|
+
hadStore: linkedSecrets !== null
|
|
2879
|
+
});
|
|
2880
|
+
await syncSubscriptionPlans(appDir, appId, token);
|
|
2881
|
+
await syncOneTimeProducts(appDir, appId, token);
|
|
3077
2882
|
p4.outro("Done");
|
|
3078
2883
|
}
|
|
3079
2884
|
});
|
|
3080
|
-
async function
|
|
3081
|
-
const
|
|
2885
|
+
async function migrateLegacyDevVarsSecrets(opts) {
|
|
2886
|
+
const { deployUrl, token, appId, envName, devVarsPath, userSecrets, userSecretNames, hadStore } = opts;
|
|
2887
|
+
if (hadStore || userSecretNames.length === 0) return;
|
|
2888
|
+
const configName = defaultConfigNameForEnv(envName);
|
|
2889
|
+
try {
|
|
2890
|
+
await uploadSecrets(deployUrl, token, appId, configName, userSecrets, false);
|
|
2891
|
+
if (existsSync10(devVarsPath)) {
|
|
2892
|
+
const cleaned = removeDevVarsKeys(readFileSync9(devVarsPath, "utf-8"), new Set(userSecretNames));
|
|
2893
|
+
writeFileSync9(devVarsPath, cleaned.endsWith("\n") ? cleaned : `${cleaned}
|
|
2894
|
+
`);
|
|
2895
|
+
}
|
|
2896
|
+
p4.log.success(
|
|
2897
|
+
`Migrated ${userSecretNames.length} secret${userSecretNames.length === 1 ? "" : "s"} from .dev.vars into the app store (config ${configName}) \u2014 now managed by \`deepspace secrets\`.`
|
|
2898
|
+
);
|
|
2899
|
+
} catch (err) {
|
|
2900
|
+
p4.log.warn(
|
|
2901
|
+
`Couldn't migrate .dev.vars secrets into the app store (${errMessage(err)}); they deployed as bindings for now and the next deploy will retry.`
|
|
2902
|
+
);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
async function syncSubscriptionPlans(appDir, appName, token) {
|
|
2906
|
+
const subsPath = join11(appDir, "src", "subscriptions.ts");
|
|
3082
2907
|
if (!existsSync10(subsPath)) return;
|
|
3083
2908
|
let plans;
|
|
3084
2909
|
try {
|
|
3085
2910
|
const esbuild = await import("esbuild");
|
|
3086
|
-
const outDir = join11(
|
|
2911
|
+
const outDir = join11(appDir, ".wrangler", "deploy");
|
|
3087
2912
|
const outFile = join11(outDir, "subscriptions.bundle.mjs");
|
|
3088
2913
|
await esbuild.build({
|
|
3089
2914
|
entryPoints: [subsPath],
|
|
@@ -3116,7 +2941,7 @@ async function syncSubscriptionPlans(appDir2, appName, token) {
|
|
|
3116
2941
|
Authorization: `Bearer ${token}`,
|
|
3117
2942
|
"Content-Type": "application/json"
|
|
3118
2943
|
},
|
|
3119
|
-
body: JSON.stringify({ appName, plans })
|
|
2944
|
+
body: JSON.stringify({ appId: appName, plans })
|
|
3120
2945
|
});
|
|
3121
2946
|
} catch (err) {
|
|
3122
2947
|
s.stop("Plan sync skipped");
|
|
@@ -3225,13 +3050,13 @@ current billing period? (Skip to leave them grandfathered.)`
|
|
|
3225
3050
|
}
|
|
3226
3051
|
}
|
|
3227
3052
|
}
|
|
3228
|
-
async function syncOneTimeProducts(
|
|
3229
|
-
const productsPath = join11(
|
|
3053
|
+
async function syncOneTimeProducts(appDir, appName, token) {
|
|
3054
|
+
const productsPath = join11(appDir, "src", "products.ts");
|
|
3230
3055
|
if (!existsSync10(productsPath)) return;
|
|
3231
3056
|
let products;
|
|
3232
3057
|
try {
|
|
3233
3058
|
const esbuild = await import("esbuild");
|
|
3234
|
-
const outDir = join11(
|
|
3059
|
+
const outDir = join11(appDir, ".wrangler", "deploy");
|
|
3235
3060
|
const outFile = join11(outDir, "products.bundle.mjs");
|
|
3236
3061
|
await esbuild.build({
|
|
3237
3062
|
entryPoints: [productsPath],
|
|
@@ -3264,7 +3089,7 @@ async function syncOneTimeProducts(appDir2, appName, token) {
|
|
|
3264
3089
|
Authorization: `Bearer ${token}`,
|
|
3265
3090
|
"Content-Type": "application/json"
|
|
3266
3091
|
},
|
|
3267
|
-
body: JSON.stringify({ appName, products })
|
|
3092
|
+
body: JSON.stringify({ appId: appName, products })
|
|
3268
3093
|
});
|
|
3269
3094
|
} catch (err) {
|
|
3270
3095
|
s?.stop("Product sync skipped");
|
|
@@ -3335,7 +3160,7 @@ var undeploy_default = defineCommand9({
|
|
|
3335
3160
|
args: {
|
|
3336
3161
|
name: {
|
|
3337
3162
|
type: "positional",
|
|
3338
|
-
description: "App
|
|
3163
|
+
description: "App id to undeploy (reads DEEPSPACE_APP_ID from wrangler.toml if omitted)",
|
|
3339
3164
|
required: false
|
|
3340
3165
|
},
|
|
3341
3166
|
env: {
|
|
@@ -3345,21 +3170,17 @@ var undeploy_default = defineCommand9({
|
|
|
3345
3170
|
}
|
|
3346
3171
|
},
|
|
3347
3172
|
async run({ args }) {
|
|
3348
|
-
let appName = args.name;
|
|
3349
3173
|
const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
if (!nameRes.ok) {
|
|
3355
|
-
p5.cancel(nameRes.reason);
|
|
3356
|
-
process.exit(1);
|
|
3357
|
-
}
|
|
3358
|
-
appName = nameRes.name;
|
|
3359
|
-
}
|
|
3174
|
+
let appId = args.name;
|
|
3175
|
+
if (appId && !APP_ID_RE.test(appId)) {
|
|
3176
|
+
p5.cancel(`"${appId}" is not an app id (app_\u2026). Undeploy targets the id \u2014 run from the app directory, or find it in wrangler.toml.`);
|
|
3177
|
+
process.exit(1);
|
|
3360
3178
|
}
|
|
3361
|
-
if (!
|
|
3362
|
-
|
|
3179
|
+
if (!appId && hasWranglerConfig(process.cwd())) {
|
|
3180
|
+
appId = readAppId(process.cwd(), envName) ?? void 0;
|
|
3181
|
+
}
|
|
3182
|
+
if (!appId) {
|
|
3183
|
+
p5.cancel("Provide an app id or run from a DeepSpace app directory with a DEEPSPACE_APP_ID.");
|
|
3363
3184
|
process.exit(1);
|
|
3364
3185
|
}
|
|
3365
3186
|
let token;
|
|
@@ -3369,10 +3190,10 @@ var undeploy_default = defineCommand9({
|
|
|
3369
3190
|
p5.cancel(err instanceof Error ? err.message : String(err));
|
|
3370
3191
|
process.exit(1);
|
|
3371
3192
|
}
|
|
3372
|
-
p5.intro(`Undeploying ${
|
|
3193
|
+
p5.intro(`Undeploying ${appId}`);
|
|
3373
3194
|
const s = createSpinner();
|
|
3374
3195
|
s.start("Removing...");
|
|
3375
|
-
const res = await fetch(`${DEPLOY_URL4}/api/deploy/${
|
|
3196
|
+
const res = await fetch(`${DEPLOY_URL4}/api/deploy/${appId}`, {
|
|
3376
3197
|
method: "DELETE",
|
|
3377
3198
|
headers: { Authorization: `Bearer ${token}` }
|
|
3378
3199
|
});
|
|
@@ -3383,7 +3204,10 @@ var undeploy_default = defineCommand9({
|
|
|
3383
3204
|
process.exit(1);
|
|
3384
3205
|
}
|
|
3385
3206
|
s.stop("Removed");
|
|
3386
|
-
|
|
3207
|
+
const hosts = body.releasedHosts ?? [];
|
|
3208
|
+
p5.outro(
|
|
3209
|
+
hosts.length ? `${hosts.join(", ")} taken down. The app keeps its id \u2014 redeploy to bring it back.` : `App ${appId} taken down.`
|
|
3210
|
+
);
|
|
3387
3211
|
}
|
|
3388
3212
|
});
|
|
3389
3213
|
|
|
@@ -3484,14 +3308,14 @@ var add_default = defineCommand11({
|
|
|
3484
3308
|
}
|
|
3485
3309
|
},
|
|
3486
3310
|
run({ args }) {
|
|
3487
|
-
const
|
|
3488
|
-
ensureInstallReady(
|
|
3489
|
-
const script = findScript(
|
|
3311
|
+
const appDir = resolve8(args.dir ?? ".");
|
|
3312
|
+
ensureInstallReady(appDir);
|
|
3313
|
+
const script = findScript(appDir);
|
|
3490
3314
|
if (!script) {
|
|
3491
3315
|
console.error("Could not find the deepspace feature installer.");
|
|
3492
3316
|
console.error("Looked in:");
|
|
3493
|
-
console.error(` ${join12(
|
|
3494
|
-
console.error(` ${join12(
|
|
3317
|
+
console.error(` ${join12(appDir, "node_modules", "deepspace", "scripts", "add-feature.cjs")}`);
|
|
3318
|
+
console.error(` ${join12(appDir, ".deepspace", "scripts", "add-feature.cjs")}`);
|
|
3495
3319
|
console.error("Are you in a DeepSpace app directory? Try `npm install` first.");
|
|
3496
3320
|
process.exit(1);
|
|
3497
3321
|
}
|
|
@@ -3501,13 +3325,13 @@ var add_default = defineCommand11({
|
|
|
3501
3325
|
} else if (args.info) {
|
|
3502
3326
|
passArgs.push("--info", args.info);
|
|
3503
3327
|
} else if (args.feature) {
|
|
3504
|
-
passArgs.push(args.feature,
|
|
3328
|
+
passArgs.push(args.feature, appDir);
|
|
3505
3329
|
} else {
|
|
3506
3330
|
passArgs.push("--help");
|
|
3507
3331
|
}
|
|
3508
3332
|
execSync3(`node ${JSON.stringify(script)} ${passArgs.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
3509
3333
|
stdio: "inherit",
|
|
3510
|
-
cwd:
|
|
3334
|
+
cwd: appDir
|
|
3511
3335
|
});
|
|
3512
3336
|
}
|
|
3513
3337
|
});
|
|
@@ -3520,17 +3344,17 @@ import { join as join13 } from "path";
|
|
|
3520
3344
|
import { homedir as homedir5 } from "os";
|
|
3521
3345
|
var SKILL_REPO = "deepdotspace/deepspace-skill";
|
|
3522
3346
|
function runSkillsAdd(extraArgs) {
|
|
3523
|
-
return new Promise((
|
|
3347
|
+
return new Promise((resolve10) => {
|
|
3524
3348
|
const child = spawn2(
|
|
3525
3349
|
"npx",
|
|
3526
3350
|
["-y", "skills@latest", "add", SKILL_REPO, ...extraArgs],
|
|
3527
3351
|
{ stdio: "inherit" }
|
|
3528
3352
|
);
|
|
3529
|
-
child.on("close", (code) =>
|
|
3353
|
+
child.on("close", (code) => resolve10(code ?? 1));
|
|
3530
3354
|
child.on("error", (err) => {
|
|
3531
3355
|
console.error(`Failed to invoke npx: ${err.message}`);
|
|
3532
3356
|
console.error("Is Node.js installed and on PATH?");
|
|
3533
|
-
|
|
3357
|
+
resolve10(1);
|
|
3534
3358
|
});
|
|
3535
3359
|
});
|
|
3536
3360
|
}
|
|
@@ -3593,6 +3417,13 @@ var skills_default = defineCommand12({
|
|
|
3593
3417
|
import { defineCommand as defineCommand13 } from "citty";
|
|
3594
3418
|
|
|
3595
3419
|
// src/cli/lib/api.ts
|
|
3420
|
+
var ApiError = class extends Error {
|
|
3421
|
+
constructor(message, status3, code) {
|
|
3422
|
+
super(message);
|
|
3423
|
+
this.status = status3;
|
|
3424
|
+
this.code = code;
|
|
3425
|
+
}
|
|
3426
|
+
};
|
|
3596
3427
|
async function apiFetch(baseUrl, token, path, init) {
|
|
3597
3428
|
const res = await fetch(`${baseUrl}${path}`, {
|
|
3598
3429
|
...init,
|
|
@@ -3605,13 +3436,16 @@ async function apiFetch(baseUrl, token, path, init) {
|
|
|
3605
3436
|
const text2 = await res.text();
|
|
3606
3437
|
if (!res.ok) {
|
|
3607
3438
|
let msg = text2;
|
|
3439
|
+
let code;
|
|
3608
3440
|
try {
|
|
3609
|
-
|
|
3441
|
+
const body = JSON.parse(text2);
|
|
3442
|
+
msg = body.error ?? text2;
|
|
3443
|
+
code = body.code;
|
|
3610
3444
|
} catch {
|
|
3611
3445
|
}
|
|
3612
|
-
throw new
|
|
3446
|
+
throw new ApiError(`API ${path} (${res.status}): ${msg}`, res.status, code);
|
|
3613
3447
|
}
|
|
3614
|
-
return JSON.parse(text2);
|
|
3448
|
+
return text2 ? JSON.parse(text2) : {};
|
|
3615
3449
|
}
|
|
3616
3450
|
|
|
3617
3451
|
// src/cli/commands/domain.ts
|
|
@@ -3642,11 +3476,11 @@ async function confirm3(message) {
|
|
|
3642
3476
|
throw new Error(`${message} \u2014 pass --yes to confirm non-interactively`);
|
|
3643
3477
|
}
|
|
3644
3478
|
process.stdout.write(`${message} [y/N] `);
|
|
3645
|
-
return new Promise((
|
|
3479
|
+
return new Promise((resolve10) => {
|
|
3646
3480
|
process.stdin.setEncoding("utf-8");
|
|
3647
3481
|
process.stdin.once("data", (data) => {
|
|
3648
3482
|
const answer = data.toString().trim().toLowerCase();
|
|
3649
|
-
|
|
3483
|
+
resolve10(answer === "y" || answer === "yes");
|
|
3650
3484
|
});
|
|
3651
3485
|
});
|
|
3652
3486
|
}
|
|
@@ -3723,7 +3557,7 @@ var buy = defineCommand13({
|
|
|
3723
3557
|
}
|
|
3724
3558
|
const checkout = await api(token, "/api/domains/checkout", {
|
|
3725
3559
|
method: "POST",
|
|
3726
|
-
body: JSON.stringify({ domain: args.domain, appName })
|
|
3560
|
+
body: JSON.stringify({ domain: args.domain, appId: appName })
|
|
3727
3561
|
});
|
|
3728
3562
|
if (args.json) {
|
|
3729
3563
|
process.stdout.write(
|
|
@@ -3867,7 +3701,7 @@ var attach = defineCommand13({
|
|
|
3867
3701
|
`/api/domains/${found.id}/reattach`,
|
|
3868
3702
|
{
|
|
3869
3703
|
method: "POST",
|
|
3870
|
-
body: JSON.stringify({
|
|
3704
|
+
body: JSON.stringify({ appId: targetApp })
|
|
3871
3705
|
}
|
|
3872
3706
|
);
|
|
3873
3707
|
if (args.json) {
|
|
@@ -3951,35 +3785,18 @@ function isTestAccountEmail(email) {
|
|
|
3951
3785
|
|
|
3952
3786
|
// src/cli/commands/collaborators.ts
|
|
3953
3787
|
var API_URL3 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
...init,
|
|
3957
|
-
headers: {
|
|
3958
|
-
Authorization: `Bearer ${token}`,
|
|
3959
|
-
"Content-Type": "application/json",
|
|
3960
|
-
...init?.headers ?? {}
|
|
3961
|
-
}
|
|
3962
|
-
});
|
|
3963
|
-
const text2 = await res.text();
|
|
3964
|
-
if (!res.ok) {
|
|
3965
|
-
let msg = text2;
|
|
3966
|
-
try {
|
|
3967
|
-
msg = JSON.parse(text2).error ?? text2;
|
|
3968
|
-
} catch {
|
|
3969
|
-
}
|
|
3970
|
-
throw new Error(`API ${path} (${res.status}): ${msg}`);
|
|
3971
|
-
}
|
|
3972
|
-
return text2 ? JSON.parse(text2) : {};
|
|
3788
|
+
function api2(token, path, init) {
|
|
3789
|
+
return apiFetch(API_URL3, token, path, init);
|
|
3973
3790
|
}
|
|
3974
3791
|
var list3 = defineCommand14({
|
|
3975
3792
|
meta: { name: "list", description: "List collaborators on your app" },
|
|
3976
3793
|
args: {
|
|
3977
|
-
app: { type: "string", description: "App
|
|
3794
|
+
app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
|
|
3978
3795
|
json: { type: "boolean", description: "Emit JSON" }
|
|
3979
3796
|
},
|
|
3980
3797
|
async run({ args }) {
|
|
3981
3798
|
const token = await ensureToken();
|
|
3982
|
-
const app =
|
|
3799
|
+
const app = requireAppIdArg(args.app);
|
|
3983
3800
|
const { collaborators } = await api2(
|
|
3984
3801
|
token,
|
|
3985
3802
|
`/api/app-collaborators/${encodeURIComponent(app)}`
|
|
@@ -4005,12 +3822,12 @@ var add2 = defineCommand14({
|
|
|
4005
3822
|
description: "Collaborator email (must already be a DeepSpace user)",
|
|
4006
3823
|
required: true
|
|
4007
3824
|
},
|
|
4008
|
-
app: { type: "string", description: "App
|
|
3825
|
+
app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
|
|
4009
3826
|
json: { type: "boolean", description: "Emit JSON" }
|
|
4010
3827
|
},
|
|
4011
3828
|
async run({ args }) {
|
|
4012
3829
|
const token = await ensureToken();
|
|
4013
|
-
const app =
|
|
3830
|
+
const app = requireAppIdArg(args.app);
|
|
4014
3831
|
if (isTestAccountEmail(args.email)) {
|
|
4015
3832
|
console.error(
|
|
4016
3833
|
"Test accounts (@deepspace.test) cannot be added as collaborators. Use a real DeepSpace account."
|
|
@@ -4033,12 +3850,12 @@ var remove = defineCommand14({
|
|
|
4033
3850
|
meta: { name: "remove", description: "Remove a collaborator from your app" },
|
|
4034
3851
|
args: {
|
|
4035
3852
|
email: { type: "positional", description: "Collaborator email", required: true },
|
|
4036
|
-
app: { type: "string", description: "App
|
|
3853
|
+
app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
|
|
4037
3854
|
json: { type: "boolean", description: "Emit JSON" }
|
|
4038
3855
|
},
|
|
4039
3856
|
async run({ args }) {
|
|
4040
3857
|
const token = await ensureToken();
|
|
4041
|
-
const app =
|
|
3858
|
+
const app = requireAppIdArg(args.app);
|
|
4042
3859
|
const { collaborators } = await api2(
|
|
4043
3860
|
token,
|
|
4044
3861
|
`/api/app-collaborators/${encodeURIComponent(app)}`
|
|
@@ -4066,12 +3883,86 @@ var collaborators_default = defineCommand14({
|
|
|
4066
3883
|
subCommands: { list: list3, add: add2, remove }
|
|
4067
3884
|
});
|
|
4068
3885
|
|
|
4069
|
-
// src/cli/commands/
|
|
3886
|
+
// src/cli/commands/transfer.ts
|
|
4070
3887
|
import { defineCommand as defineCommand15 } from "citty";
|
|
3888
|
+
var API_URL4 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
3889
|
+
var DEPLOY_URL5 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
3890
|
+
var offer = defineCommand15({
|
|
3891
|
+
meta: { name: "offer", description: "Offer this app to another DeepSpace user" },
|
|
3892
|
+
args: {
|
|
3893
|
+
email: { type: "positional", description: "Recipient email", required: true },
|
|
3894
|
+
app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
|
|
3895
|
+
},
|
|
3896
|
+
async run({ args }) {
|
|
3897
|
+
const token = await ensureToken();
|
|
3898
|
+
const app = requireAppIdArg(args.app);
|
|
3899
|
+
const { expiresAt } = await apiFetch(
|
|
3900
|
+
API_URL4,
|
|
3901
|
+
token,
|
|
3902
|
+
`/api/app-transfers/${encodeURIComponent(app)}`,
|
|
3903
|
+
{ method: "POST", body: JSON.stringify({ email: args.email }) }
|
|
3904
|
+
);
|
|
3905
|
+
console.log(
|
|
3906
|
+
`\u2713 Offered ${app} to ${args.email} (expires ${expiresAt}).
|
|
3907
|
+
They accept with: deepspace transfer accept --app ${app}`
|
|
3908
|
+
);
|
|
3909
|
+
}
|
|
3910
|
+
});
|
|
3911
|
+
var status2 = defineCommand15({
|
|
3912
|
+
meta: { name: "status", description: "Show the pending transfer offer, if any" },
|
|
3913
|
+
args: {
|
|
3914
|
+
app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
|
|
3915
|
+
},
|
|
3916
|
+
async run({ args }) {
|
|
3917
|
+
const token = await ensureToken();
|
|
3918
|
+
const app = requireAppIdArg(args.app);
|
|
3919
|
+
const { transfer } = await apiFetch(API_URL4, token, `/api/app-transfers/${encodeURIComponent(app)}`);
|
|
3920
|
+
if (!transfer) {
|
|
3921
|
+
console.log(`No pending transfer for ${app}.`);
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
console.log(`Pending: ${app} \u2192 ${transfer.toEmailDisplay} (expires ${transfer.expiresAt}).`);
|
|
3925
|
+
}
|
|
3926
|
+
});
|
|
3927
|
+
var accept = defineCommand15({
|
|
3928
|
+
meta: { name: "accept", description: "Accept a transfer offered to you" },
|
|
3929
|
+
args: {
|
|
3930
|
+
app: { type: "string", alias: "a", description: "App id of the offered app", required: true }
|
|
3931
|
+
},
|
|
3932
|
+
async run({ args }) {
|
|
3933
|
+
const token = await ensureToken();
|
|
3934
|
+
const app = requireAppIdArg(args.app);
|
|
3935
|
+
await apiFetch(DEPLOY_URL5, token, `/api/apps/${encodeURIComponent(app)}/transfer/accept`, {
|
|
3936
|
+
method: "POST"
|
|
3937
|
+
});
|
|
3938
|
+
console.log(
|
|
3939
|
+
`\u2713 You now own ${app}. Run \`deepspace init\` in a fresh clone (or set DEEPSPACE_APP_ID = "${app}" in wrangler.toml) and deploy.`
|
|
3940
|
+
);
|
|
3941
|
+
}
|
|
3942
|
+
});
|
|
3943
|
+
var cancel4 = defineCommand15({
|
|
3944
|
+
meta: { name: "cancel", description: "Cancel/decline the pending offer (either party)" },
|
|
3945
|
+
args: {
|
|
3946
|
+
app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
|
|
3947
|
+
},
|
|
3948
|
+
async run({ args }) {
|
|
3949
|
+
const token = await ensureToken();
|
|
3950
|
+
const app = requireAppIdArg(args.app);
|
|
3951
|
+
await apiFetch(API_URL4, token, `/api/app-transfers/${encodeURIComponent(app)}`, { method: "DELETE" });
|
|
3952
|
+
console.log(`\u2713 Transfer offer for ${app} cancelled.`);
|
|
3953
|
+
}
|
|
3954
|
+
});
|
|
3955
|
+
var transfer_default = defineCommand15({
|
|
3956
|
+
meta: { name: "transfer", description: "Transfer app ownership to another user" },
|
|
3957
|
+
subCommands: { offer, status: status2, accept, cancel: cancel4 }
|
|
3958
|
+
});
|
|
3959
|
+
|
|
3960
|
+
// src/cli/commands/integrations.ts
|
|
3961
|
+
import { defineCommand as defineCommand16 } from "citty";
|
|
4071
3962
|
|
|
4072
3963
|
// src/cli/commands/_invoke-impl.ts
|
|
4073
3964
|
import { readFileSync as readFileSync10 } from "fs";
|
|
4074
|
-
var
|
|
3965
|
+
var API_URL5 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
4075
3966
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
4076
3967
|
function parseTarget(target) {
|
|
4077
3968
|
if (!target || typeof target !== "string") {
|
|
@@ -4113,7 +4004,7 @@ function flushAndExit(code) {
|
|
|
4113
4004
|
});
|
|
4114
4005
|
}
|
|
4115
4006
|
async function fetchCatalog(opts = {}) {
|
|
4116
|
-
const url = opts.summary ? `${
|
|
4007
|
+
const url = opts.summary ? `${API_URL5}/api/integrations?summary=1` : `${API_URL5}/api/integrations`;
|
|
4117
4008
|
const res = await fetch(url);
|
|
4118
4009
|
if (!res.ok) {
|
|
4119
4010
|
throw new Error(`Failed to fetch integration catalog (${res.status})`);
|
|
@@ -4201,7 +4092,7 @@ async function runInvoke(args) {
|
|
|
4201
4092
|
const startedAt = Date.now();
|
|
4202
4093
|
let res;
|
|
4203
4094
|
try {
|
|
4204
|
-
res = await fetch(`${
|
|
4095
|
+
res = await fetch(`${API_URL5}/api/integrations/${integration}/${endpoint}`, {
|
|
4205
4096
|
method: "POST",
|
|
4206
4097
|
headers: {
|
|
4207
4098
|
"Content-Type": "application/json",
|
|
@@ -4252,7 +4143,7 @@ async function runInvoke(args) {
|
|
|
4252
4143
|
}
|
|
4253
4144
|
|
|
4254
4145
|
// src/cli/commands/integrations.ts
|
|
4255
|
-
var list4 =
|
|
4146
|
+
var list4 = defineCommand16({
|
|
4256
4147
|
meta: {
|
|
4257
4148
|
name: "list",
|
|
4258
4149
|
description: "List all available integrations"
|
|
@@ -4273,7 +4164,7 @@ var list4 = defineCommand15({
|
|
|
4273
4164
|
}
|
|
4274
4165
|
}
|
|
4275
4166
|
});
|
|
4276
|
-
var info =
|
|
4167
|
+
var info = defineCommand16({
|
|
4277
4168
|
meta: {
|
|
4278
4169
|
name: "info",
|
|
4279
4170
|
description: "Print schema + example body for an integration endpoint"
|
|
@@ -4299,7 +4190,7 @@ var info = defineCommand15({
|
|
|
4299
4190
|
}
|
|
4300
4191
|
}
|
|
4301
4192
|
});
|
|
4302
|
-
var invoke =
|
|
4193
|
+
var invoke = defineCommand16({
|
|
4303
4194
|
meta: {
|
|
4304
4195
|
name: "invoke",
|
|
4305
4196
|
description: "Invoke a platform integration as the logged-in user"
|
|
@@ -4352,7 +4243,7 @@ var invoke = defineCommand15({
|
|
|
4352
4243
|
}
|
|
4353
4244
|
}
|
|
4354
4245
|
});
|
|
4355
|
-
var integrations_default =
|
|
4246
|
+
var integrations_default = defineCommand16({
|
|
4356
4247
|
meta: {
|
|
4357
4248
|
name: "integrations",
|
|
4358
4249
|
description: "List, inspect, and invoke platform integrations"
|
|
@@ -4365,8 +4256,8 @@ var integrations_default = defineCommand15({
|
|
|
4365
4256
|
});
|
|
4366
4257
|
|
|
4367
4258
|
// src/cli/commands/invoke.ts
|
|
4368
|
-
import { defineCommand as
|
|
4369
|
-
var invoke_default =
|
|
4259
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
4260
|
+
var invoke_default = defineCommand17({
|
|
4370
4261
|
meta: {
|
|
4371
4262
|
name: "invoke",
|
|
4372
4263
|
description: "Invoke a platform integration as the logged-in user"
|
|
@@ -4439,13 +4330,13 @@ var invoke_default = defineCommand16({
|
|
|
4439
4330
|
});
|
|
4440
4331
|
|
|
4441
4332
|
// src/cli/commands/library.ts
|
|
4442
|
-
import { defineCommand as
|
|
4333
|
+
import { defineCommand as defineCommand18 } from "citty";
|
|
4443
4334
|
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
4444
4335
|
import { join as join14, resolve as resolve9 } from "path";
|
|
4445
|
-
import { parse as
|
|
4336
|
+
import { parse as parseToml4 } from "smol-toml";
|
|
4446
4337
|
import * as p6 from "@clack/prompts";
|
|
4447
4338
|
var DEFAULT_LIBRARY_APP = "deepdotspace-site";
|
|
4448
|
-
var publish =
|
|
4339
|
+
var publish = defineCommand18({
|
|
4449
4340
|
meta: {
|
|
4450
4341
|
name: "publish",
|
|
4451
4342
|
description: "Publish the current app to the DeepSpace library"
|
|
@@ -4518,12 +4409,12 @@ var publish = defineCommand17({
|
|
|
4518
4409
|
intro6("Publishing to the DeepSpace library");
|
|
4519
4410
|
let appName = typeof args.app === "string" && args.app.trim() ? args.app.trim() : void 0;
|
|
4520
4411
|
if (!appName) {
|
|
4521
|
-
const
|
|
4522
|
-
const wranglerPath = join14(
|
|
4412
|
+
const appDir = resolve9(typeof args.dir === "string" && args.dir ? args.dir : ".");
|
|
4413
|
+
const wranglerPath = join14(appDir, "wrangler.toml");
|
|
4523
4414
|
if (!existsSync12(wranglerPath)) {
|
|
4524
4415
|
return die("No --app provided and no wrangler.toml found. Either pass --app <name> or run from your app's directory.");
|
|
4525
4416
|
}
|
|
4526
|
-
const cfg =
|
|
4417
|
+
const cfg = parseToml4(readFileSync11(wranglerPath, "utf-8"));
|
|
4527
4418
|
const nameRes = resolveAppName(cfg.name);
|
|
4528
4419
|
if (!nameRes.ok) return die(`wrangler.toml: ${nameRes.reason}`);
|
|
4529
4420
|
appName = nameRes.name;
|
|
@@ -4584,7 +4475,7 @@ var publish = defineCommand17({
|
|
|
4584
4475
|
p6.outro("Done");
|
|
4585
4476
|
}
|
|
4586
4477
|
});
|
|
4587
|
-
var unpublish =
|
|
4478
|
+
var unpublish = defineCommand18({
|
|
4588
4479
|
meta: {
|
|
4589
4480
|
name: "unpublish",
|
|
4590
4481
|
description: "Remove a library entry (owner or admin only)"
|
|
@@ -4643,7 +4534,7 @@ var unpublish = defineCommand17({
|
|
|
4643
4534
|
p6.outro("Done");
|
|
4644
4535
|
}
|
|
4645
4536
|
});
|
|
4646
|
-
var library_default =
|
|
4537
|
+
var library_default = defineCommand18({
|
|
4647
4538
|
meta: {
|
|
4648
4539
|
name: "library",
|
|
4649
4540
|
description: "Manage your DeepSpace library entries"
|
|
@@ -4655,13 +4546,13 @@ var library_default = defineCommand17({
|
|
|
4655
4546
|
});
|
|
4656
4547
|
|
|
4657
4548
|
// src/cli/commands/feedback.ts
|
|
4658
|
-
import { defineCommand as
|
|
4549
|
+
import { defineCommand as defineCommand19 } from "citty";
|
|
4659
4550
|
import * as p7 from "@clack/prompts";
|
|
4660
4551
|
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
|
|
4661
4552
|
import { dirname as dirname3, join as join15 } from "path";
|
|
4662
4553
|
import { fileURLToPath } from "url";
|
|
4663
4554
|
import { release } from "os";
|
|
4664
|
-
var
|
|
4555
|
+
var API_URL6 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
4665
4556
|
var TYPES = ["bug", "feature", "other"];
|
|
4666
4557
|
var TYPE_LABELS = {
|
|
4667
4558
|
bug: "Bug report",
|
|
@@ -4671,7 +4562,7 @@ var TYPE_LABELS = {
|
|
|
4671
4562
|
var MAX_TITLE_LEN = 200;
|
|
4672
4563
|
var MAX_BODY_LEN = 1e4;
|
|
4673
4564
|
var MAX_CONTEXT_LEN = 200;
|
|
4674
|
-
var api3 = (token, path, init) => apiFetch(
|
|
4565
|
+
var api3 = (token, path, init) => apiFetch(API_URL6, token, path, init);
|
|
4675
4566
|
function normalizeType(raw) {
|
|
4676
4567
|
const value = (raw ?? "bug").toLowerCase();
|
|
4677
4568
|
if (TYPES.includes(value)) return value;
|
|
@@ -4733,7 +4624,7 @@ function buildFeedbackPayload(input) {
|
|
|
4733
4624
|
...input.context
|
|
4734
4625
|
};
|
|
4735
4626
|
}
|
|
4736
|
-
var feedback_default =
|
|
4627
|
+
var feedback_default = defineCommand19({
|
|
4737
4628
|
meta: {
|
|
4738
4629
|
name: "feedback",
|
|
4739
4630
|
description: "Submit a bug report, feature request, or feedback to DeepSpace"
|
|
@@ -4769,7 +4660,7 @@ var feedback_default = defineCommand18({
|
|
|
4769
4660
|
},
|
|
4770
4661
|
async run({ args }) {
|
|
4771
4662
|
const interactive = process.stdin.isTTY && !args.json;
|
|
4772
|
-
const
|
|
4663
|
+
const fail2 = (message) => {
|
|
4773
4664
|
if (args.json) {
|
|
4774
4665
|
process.stdout.write(JSON.stringify({ ok: false, error: message }) + "\n");
|
|
4775
4666
|
} else {
|
|
@@ -4781,12 +4672,12 @@ var feedback_default = defineCommand18({
|
|
|
4781
4672
|
try {
|
|
4782
4673
|
type = normalizeType(args.type ? String(args.type) : void 0);
|
|
4783
4674
|
} catch (err) {
|
|
4784
|
-
return
|
|
4675
|
+
return fail2(err.message);
|
|
4785
4676
|
}
|
|
4786
4677
|
let title = args.title ? String(args.title) : "";
|
|
4787
4678
|
let body = args.message ? String(args.message) : "";
|
|
4788
4679
|
if ((!title || !body) && !interactive) {
|
|
4789
|
-
return
|
|
4680
|
+
return fail2(
|
|
4790
4681
|
'Provide a title and --message to submit non-interactively, e.g. `deepspace feedback "Title" --message "Details"`.'
|
|
4791
4682
|
);
|
|
4792
4683
|
}
|
|
@@ -4848,7 +4739,7 @@ var feedback_default = defineCommand18({
|
|
|
4848
4739
|
context: collectContext()
|
|
4849
4740
|
});
|
|
4850
4741
|
} catch (err) {
|
|
4851
|
-
return
|
|
4742
|
+
return fail2(err.message);
|
|
4852
4743
|
}
|
|
4853
4744
|
if (interactive && !args.yes) {
|
|
4854
4745
|
p7.note(
|
|
@@ -4861,1011 +4752,395 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
|
|
|
4861
4752
|
"Review"
|
|
4862
4753
|
);
|
|
4863
4754
|
const ok = await p7.confirm({ message: "Send this to the DeepSpace team?" });
|
|
4864
|
-
if (p7.isCancel(ok) || !ok) {
|
|
4865
|
-
p7.cancel("Not sent.");
|
|
4866
|
-
process.exit(0);
|
|
4867
|
-
}
|
|
4868
|
-
}
|
|
4869
|
-
let token;
|
|
4870
|
-
try {
|
|
4871
|
-
token = await ensureToken();
|
|
4872
|
-
} catch (err) {
|
|
4873
|
-
return fail(err.message ?? "Not signed in. Run `deepspace login`.");
|
|
4874
|
-
}
|
|
4875
|
-
let result;
|
|
4876
|
-
try {
|
|
4877
|
-
result = await api3(token, "/api/feedback", {
|
|
4878
|
-
method: "POST",
|
|
4879
|
-
body: JSON.stringify(payload)
|
|
4880
|
-
});
|
|
4881
|
-
} catch (err) {
|
|
4882
|
-
return fail(err.message);
|
|
4883
|
-
}
|
|
4884
|
-
if (args.json) {
|
|
4885
|
-
process.stdout.write(JSON.stringify({ ok: true, report: result.report }) + "\n");
|
|
4886
|
-
return;
|
|
4887
|
-
}
|
|
4888
|
-
if (interactive) {
|
|
4889
|
-
p7.outro(`Thanks! Submitted as ${result.report.id}.`);
|
|
4890
|
-
} else {
|
|
4891
|
-
console.log(`Submitted feedback ${result.report.id} (${result.report.type}).`);
|
|
4892
|
-
}
|
|
4893
|
-
console.log(`Track triage status at ${DASHBOARD_URL}.`);
|
|
4894
|
-
}
|
|
4895
|
-
});
|
|
4896
|
-
|
|
4897
|
-
// src/cli/commands/secrets.ts
|
|
4898
|
-
import { defineCommand as defineCommand19 } from "citty";
|
|
4899
|
-
import { existsSync as existsSync14, readFileSync as readFileSync13, rmSync as rmSync4 } from "fs";
|
|
4900
|
-
import { join as join16, resolve as resolve10 } from "path";
|
|
4901
|
-
var DEPLOY_URL5 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
4902
|
-
function appDir(args) {
|
|
4903
|
-
const dir = typeof args.dir === "string" && args.dir ? args.dir : typeof args.scope === "string" && args.scope ? args.scope : ".";
|
|
4904
|
-
return resolve10(dir);
|
|
4905
|
-
}
|
|
4906
|
-
function wranglerEnv(args) {
|
|
4907
|
-
return typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
4908
|
-
}
|
|
4909
|
-
function targetFromArgs(args) {
|
|
4910
|
-
const dir = appDir(args);
|
|
4911
|
-
const target = resolveSecretsTarget(dir, {
|
|
4912
|
-
project: typeof args.project === "string" && args.project ? args.project : void 0,
|
|
4913
|
-
config: typeof args.config === "string" && args.config ? args.config : void 0,
|
|
4914
|
-
wranglerEnv: wranglerEnv(args)
|
|
4915
|
-
});
|
|
4916
|
-
return { appDir: dir, target };
|
|
4917
|
-
}
|
|
4918
|
-
function scopedArgs() {
|
|
4919
|
-
return {
|
|
4920
|
-
dir: { type: "string", description: "App directory (default: current directory)", required: false },
|
|
4921
|
-
scope: { type: "string", description: "Directory scope; Doppler-compatible alias for --dir", required: false },
|
|
4922
|
-
env: { type: "string", alias: "e", description: "Advanced: wrangler.toml [env.<name>] cache/link slot", required: false },
|
|
4923
|
-
json: { type: "boolean", description: "Emit JSON", default: false }
|
|
4924
|
-
};
|
|
4925
|
-
}
|
|
4926
|
-
function commonArgs() {
|
|
4927
|
-
return {
|
|
4928
|
-
...scopedArgs(),
|
|
4929
|
-
project: { type: "string", alias: "p", description: "Secrets project (default: linked project or wrangler name; does not link)", required: false },
|
|
4930
|
-
config: { type: "string", alias: "c", description: "Secrets config (Doppler-compatible; default: linked config or prd)", required: false }
|
|
4931
|
-
};
|
|
4932
|
-
}
|
|
4933
|
-
function setupArgs() {
|
|
4934
|
-
return {
|
|
4935
|
-
...scopedArgs(),
|
|
4936
|
-
project: { type: "string", alias: "p", description: "Secrets project to link (default: linked project or wrangler name)", required: false },
|
|
4937
|
-
config: { type: "string", alias: "c", description: "Secrets config to link (default: linked config or prd)", required: false },
|
|
4938
|
-
"no-interactive": { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace setup never prompts", default: false },
|
|
4939
|
-
"no-save-token": { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace does not save a token here", default: false }
|
|
4940
|
-
};
|
|
4941
|
-
}
|
|
4942
|
-
async function refreshCache(token, dir, target) {
|
|
4943
|
-
const values = await fetchSecretsValues(
|
|
4944
|
-
DEPLOY_URL5,
|
|
4945
|
-
token,
|
|
4946
|
-
target.projectName,
|
|
4947
|
-
target.configName,
|
|
4948
|
-
appScopedSecretsApiOptions(dir, target)
|
|
4949
|
-
);
|
|
4950
|
-
writeSecretsCacheFile(dir, values, target);
|
|
4951
|
-
return Object.keys(values).length;
|
|
4952
|
-
}
|
|
4953
|
-
async function refreshCacheIfLinked(token, dir, target) {
|
|
4954
|
-
if (!secretsTargetMatchesLink(dir, target)) return null;
|
|
4955
|
-
return refreshCache(token, dir, target);
|
|
4956
|
-
}
|
|
4957
|
-
function writeLinkForTarget(dir, target, projectOverride) {
|
|
4958
|
-
writeWranglerSecretsLink(dir, target, { projectOverride });
|
|
4959
|
-
}
|
|
4960
|
-
function redeployReminder(target) {
|
|
4961
|
-
const envFlag = target.wranglerEnv ? ` --env ${target.wranglerEnv}` : "";
|
|
4962
|
-
console.log(`Redeploy for production to receive these secrets: npx deepspace deploy${envFlag}`);
|
|
4963
|
-
}
|
|
4964
|
-
function unlinkedTargetNotice(target) {
|
|
4965
|
-
const envFlag = target.wranglerEnv ? ` --env ${target.wranglerEnv}` : "";
|
|
4966
|
-
console.log(
|
|
4967
|
-
`Not linked for dev/test/deploy; run npx deepspace secrets setup --project ${target.projectName} --config ${target.configName}${envFlag}.`
|
|
4968
|
-
);
|
|
4969
|
-
}
|
|
4970
|
-
function cacheJson(count) {
|
|
4971
|
-
return { linked: count !== null, cachedSecrets: count };
|
|
4972
|
-
}
|
|
4973
|
-
function updateRequiredSecretsIfTopLevelLinked(dir, target, keys, opts = {}) {
|
|
4974
|
-
if (target.wranglerEnv || !secretsTargetMatchesLink(dir, target)) return null;
|
|
4975
|
-
return updateWranglerRequiredSecrets(dir, keys, opts);
|
|
4976
|
-
}
|
|
4977
|
-
function removeRequiredSecretsIfTopLevelLinked(dir, target, keys) {
|
|
4978
|
-
if (target.wranglerEnv || !secretsTargetMatchesLink(dir, target)) return null;
|
|
4979
|
-
return removeWranglerRequiredSecrets(dir, keys);
|
|
4980
|
-
}
|
|
4981
|
-
function requiredSecretsJson(keys) {
|
|
4982
|
-
return keys ? { requiredSecrets: keys } : {};
|
|
4983
|
-
}
|
|
4984
|
-
function requiredSecretsNotice(keys) {
|
|
4985
|
-
if (!keys) return;
|
|
4986
|
-
console.log(`Updated wrangler.toml detected-secret advisory (${keys.length} name${keys.length === 1 ? "" : "s"}).`);
|
|
4987
|
-
}
|
|
4988
|
-
async function ensureLinkedRemoteTarget(dir, target) {
|
|
4989
|
-
let token;
|
|
4990
|
-
try {
|
|
4991
|
-
token = await ensureToken();
|
|
4992
|
-
} catch (err) {
|
|
4993
|
-
return {
|
|
4994
|
-
status: "unchecked",
|
|
4995
|
-
message: err instanceof Error ? err.message : String(err)
|
|
4996
|
-
};
|
|
4997
|
-
}
|
|
4998
|
-
try {
|
|
4999
|
-
const configs2 = await listConfigs(
|
|
5000
|
-
DEPLOY_URL5,
|
|
5001
|
-
token,
|
|
5002
|
-
target.projectName,
|
|
5003
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5004
|
-
);
|
|
5005
|
-
if (configs2.some((config) => config.name === target.configName)) {
|
|
5006
|
-
return { status: "ok" };
|
|
5007
|
-
}
|
|
5008
|
-
} catch (err) {
|
|
5009
|
-
if (!(err instanceof SecretsApiError && err.status === 404)) {
|
|
5010
|
-
return {
|
|
5011
|
-
status: "unchecked",
|
|
5012
|
-
message: err instanceof Error ? err.message : String(err)
|
|
5013
|
-
};
|
|
5014
|
-
}
|
|
5015
|
-
}
|
|
5016
|
-
try {
|
|
5017
|
-
await createConfig(
|
|
5018
|
-
DEPLOY_URL5,
|
|
5019
|
-
token,
|
|
5020
|
-
target.projectName,
|
|
5021
|
-
target.configName,
|
|
5022
|
-
void 0,
|
|
5023
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5024
|
-
);
|
|
5025
|
-
return { status: "created" };
|
|
5026
|
-
} catch (err) {
|
|
5027
|
-
return {
|
|
5028
|
-
status: "unchecked",
|
|
5029
|
-
message: `Remote config ${target.projectName}/${target.configName} was not found and could not be created: ${err instanceof Error ? err.message : String(err)}`
|
|
5030
|
-
};
|
|
5031
|
-
}
|
|
5032
|
-
}
|
|
5033
|
-
async function bestEffortSetServerSecretsLink(dir, target) {
|
|
5034
|
-
try {
|
|
5035
|
-
const token = await ensureToken();
|
|
5036
|
-
await setServerSecretsLink(
|
|
5037
|
-
DEPLOY_URL5,
|
|
5038
|
-
token,
|
|
5039
|
-
resolveSecretsAppName(dir, target.wranglerEnv),
|
|
5040
|
-
target.projectName
|
|
5041
|
-
);
|
|
5042
|
-
} catch {
|
|
5043
|
-
}
|
|
5044
|
-
}
|
|
5045
|
-
async function bestEffortClearServerSecretsLink(dir, targetEnv) {
|
|
5046
|
-
try {
|
|
5047
|
-
const token = await ensureToken();
|
|
5048
|
-
await clearServerSecretsLink(DEPLOY_URL5, token, resolveSecretsAppName(dir, targetEnv));
|
|
5049
|
-
} catch {
|
|
5050
|
-
}
|
|
5051
|
-
}
|
|
5052
|
-
async function importLegacyDevVarsSecrets(dir, target, remoteCheck) {
|
|
5053
|
-
if (remoteCheck.status === "unchecked") return null;
|
|
5054
|
-
const legacyPath = target.wranglerEnv ? join16(dir, `.dev.vars.${target.wranglerEnv}`) : join16(dir, ".dev.vars");
|
|
5055
|
-
if (!existsSync14(legacyPath)) return null;
|
|
5056
|
-
try {
|
|
5057
|
-
const custom = extractCustomDevVars(
|
|
5058
|
-
stripGeneratedSecretsCache2(readFileSync13(legacyPath, "utf-8"))
|
|
5059
|
-
);
|
|
5060
|
-
if (!custom.trim()) return null;
|
|
5061
|
-
const parsed = parseDevVars(custom);
|
|
5062
|
-
const secrets = {};
|
|
5063
|
-
const skipped = [];
|
|
5064
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
5065
|
-
try {
|
|
5066
|
-
validateSecretName(key);
|
|
5067
|
-
validateSecretValue(key, value);
|
|
5068
|
-
secrets[key] = value;
|
|
5069
|
-
} catch {
|
|
5070
|
-
skipped.push(key);
|
|
5071
|
-
}
|
|
5072
|
-
}
|
|
5073
|
-
const keys = Object.keys(secrets).sort();
|
|
5074
|
-
if (keys.length === 0) return null;
|
|
5075
|
-
const token = await ensureToken();
|
|
5076
|
-
if (remoteCheck.status === "ok") {
|
|
5077
|
-
const existing = await listSecrets(
|
|
5078
|
-
DEPLOY_URL5,
|
|
5079
|
-
token,
|
|
5080
|
-
target.projectName,
|
|
5081
|
-
target.configName,
|
|
5082
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5083
|
-
);
|
|
5084
|
-
if (existing.secrets.length > 0) {
|
|
5085
|
-
return { status: "config-not-empty", existingCount: existing.secrets.length, keys };
|
|
5086
|
-
}
|
|
5087
|
-
}
|
|
5088
|
-
await uploadSecrets(
|
|
5089
|
-
DEPLOY_URL5,
|
|
5090
|
-
token,
|
|
5091
|
-
target.projectName,
|
|
5092
|
-
target.configName,
|
|
5093
|
-
secrets,
|
|
5094
|
-
false,
|
|
5095
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5096
|
-
);
|
|
5097
|
-
let cleanedLegacyFile = false;
|
|
5098
|
-
try {
|
|
5099
|
-
const cleaned = removeDevVarsKeys(
|
|
5100
|
-
stripGeneratedSecretsCache2(readFileSync13(legacyPath, "utf-8")),
|
|
5101
|
-
new Set(keys)
|
|
5102
|
-
);
|
|
5103
|
-
if (target.wranglerEnv && !extractCustomDevVars(cleaned).trim()) {
|
|
5104
|
-
rmSync4(legacyPath, { force: true });
|
|
5105
|
-
} else {
|
|
5106
|
-
writeSecretFileSync(legacyPath, cleaned ? `${cleaned}
|
|
5107
|
-
` : "");
|
|
4755
|
+
if (p7.isCancel(ok) || !ok) {
|
|
4756
|
+
p7.cancel("Not sent.");
|
|
4757
|
+
process.exit(0);
|
|
5108
4758
|
}
|
|
5109
|
-
cleanedLegacyFile = true;
|
|
5110
|
-
} catch {
|
|
5111
4759
|
}
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
}
|
|
5118
|
-
}
|
|
5119
|
-
function printLegacySecretsImport(result, target) {
|
|
5120
|
-
if (!result) return;
|
|
5121
|
-
const configRef = `${target.projectName}/${target.configName}`;
|
|
5122
|
-
if (result.status === "imported") {
|
|
5123
|
-
console.log(
|
|
5124
|
-
`Imported ${result.keys.length} legacy secret${result.keys.length === 1 ? "" : "s"} from .dev.vars into ${configRef}: ${result.keys.join(", ")}`
|
|
5125
|
-
);
|
|
5126
|
-
if (result.cleanedLegacyFile) {
|
|
5127
|
-
console.log("Removed the migrated lines from the local file; the remote store owns them now.");
|
|
4760
|
+
let token;
|
|
4761
|
+
try {
|
|
4762
|
+
token = await ensureToken();
|
|
4763
|
+
} catch (err) {
|
|
4764
|
+
return fail2(err.message ?? "Not signed in. Run `deepspace login`.");
|
|
5128
4765
|
}
|
|
5129
|
-
|
|
5130
|
-
|
|
4766
|
+
let result;
|
|
4767
|
+
try {
|
|
4768
|
+
result = await api3(token, "/api/feedback", {
|
|
4769
|
+
method: "POST",
|
|
4770
|
+
body: JSON.stringify(payload)
|
|
4771
|
+
});
|
|
4772
|
+
} catch (err) {
|
|
4773
|
+
return fail2(err.message);
|
|
5131
4774
|
}
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
if (result.status === "config-not-empty") {
|
|
5136
|
-
console.log(
|
|
5137
|
-
`Found ${result.keys.length} legacy secret${result.keys.length === 1 ? "" : "s"} in .dev.vars, but ${configRef} already has ${result.existingCount} \u2014 not imported. Merge manually with npx deepspace secrets upload.`
|
|
5138
|
-
);
|
|
5139
|
-
return;
|
|
5140
|
-
}
|
|
5141
|
-
console.warn(`Warning: legacy .dev.vars secrets were not imported: ${result.message}`);
|
|
5142
|
-
}
|
|
5143
|
-
function printLinkRemoteWarning(check) {
|
|
5144
|
-
if (check.status !== "unchecked") return;
|
|
5145
|
-
console.warn(`Warning: Linked locally, but the remote config was not checked: ${check.message}`);
|
|
5146
|
-
}
|
|
5147
|
-
function positionals(args, firstName) {
|
|
5148
|
-
const raw = Array.isArray(args._) ? args._ : [];
|
|
5149
|
-
if (raw.length > 0) return raw.map(String);
|
|
5150
|
-
const first = args[firstName];
|
|
5151
|
-
return typeof first === "string" && first ? [first] : [];
|
|
5152
|
-
}
|
|
5153
|
-
function rawPositionals(rawArgs, argsDef) {
|
|
5154
|
-
const camel = (name) => name.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase());
|
|
5155
|
-
const stringFlags = /* @__PURE__ */ new Set();
|
|
5156
|
-
const booleanFlags = /* @__PURE__ */ new Set();
|
|
5157
|
-
for (const [name, def] of Object.entries(argsDef)) {
|
|
5158
|
-
if (def.type === "positional") continue;
|
|
5159
|
-
const flags = def.type === "boolean" ? booleanFlags : stringFlags;
|
|
5160
|
-
flags.add(name);
|
|
5161
|
-
flags.add(camel(name));
|
|
5162
|
-
for (const alias of Array.isArray(def.alias) ? def.alias : def.alias ? [def.alias] : []) {
|
|
5163
|
-
flags.add(alias);
|
|
5164
|
-
flags.add(camel(alias));
|
|
4775
|
+
if (args.json) {
|
|
4776
|
+
process.stdout.write(JSON.stringify({ ok: true, report: result.report }) + "\n");
|
|
4777
|
+
return;
|
|
5165
4778
|
}
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
if (token === "--") {
|
|
5171
|
-
out.push(...rawArgs.slice(i + 1));
|
|
5172
|
-
break;
|
|
5173
|
-
}
|
|
5174
|
-
if (token.length > 1 && token.startsWith("-")) {
|
|
5175
|
-
const body = token.startsWith("--") ? token.slice(2) : token.slice(1);
|
|
5176
|
-
const eq = body.indexOf("=");
|
|
5177
|
-
const name = eq === -1 ? body : body.slice(0, eq);
|
|
5178
|
-
if (booleanFlags.has(name) || name.startsWith("no-") && booleanFlags.has(name.slice(3))) {
|
|
5179
|
-
continue;
|
|
5180
|
-
}
|
|
5181
|
-
if (stringFlags.has(name)) {
|
|
5182
|
-
if (eq === -1) i++;
|
|
5183
|
-
continue;
|
|
5184
|
-
}
|
|
4779
|
+
if (interactive) {
|
|
4780
|
+
p7.outro(`Thanks! Submitted as ${result.report.id}.`);
|
|
4781
|
+
} else {
|
|
4782
|
+
console.log(`Submitted feedback ${result.report.id} (${result.report.type}).`);
|
|
5185
4783
|
}
|
|
5186
|
-
|
|
5187
|
-
}
|
|
5188
|
-
return out;
|
|
5189
|
-
}
|
|
5190
|
-
function hasRawFlag(rawArgs, name) {
|
|
5191
|
-
if (!Array.isArray(rawArgs)) return false;
|
|
5192
|
-
for (const token of rawArgs) {
|
|
5193
|
-
if (token === "--") return false;
|
|
5194
|
-
if (token === `--${name}`) return true;
|
|
4784
|
+
console.log(`Track triage status at ${DASHBOARD_URL}.`);
|
|
5195
4785
|
}
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
function resolveConfigureUnsetArgs(args) {
|
|
5221
|
-
const names = positionals(args, "name");
|
|
5222
|
-
if (names.length === 0) {
|
|
5223
|
-
throw new Error("At least one setting is required. Use `npx deepspace secrets configure unset project config`.");
|
|
4786
|
+
});
|
|
4787
|
+
|
|
4788
|
+
// src/cli/commands/secrets.ts
|
|
4789
|
+
import { defineCommand as defineCommand20 } from "citty";
|
|
4790
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
4791
|
+
var DEPLOY_URL6 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
4792
|
+
var COMMON_ARGS = {
|
|
4793
|
+
app: {
|
|
4794
|
+
type: "string",
|
|
4795
|
+
alias: "a",
|
|
4796
|
+
description: "App id (default: DEEPSPACE_APP_ID from the nearest wrangler.toml)",
|
|
4797
|
+
required: false
|
|
4798
|
+
},
|
|
4799
|
+
config: {
|
|
4800
|
+
type: "string",
|
|
4801
|
+
alias: "c",
|
|
4802
|
+
description: 'Config name (default: the --env name, or "prd")',
|
|
4803
|
+
required: false
|
|
4804
|
+
},
|
|
4805
|
+
env: {
|
|
4806
|
+
type: "string",
|
|
4807
|
+
alias: "e",
|
|
4808
|
+
description: "wrangler.toml [env.<name>] slot \u2014 selects that env\u2019s app id and config",
|
|
4809
|
+
required: false
|
|
5224
4810
|
}
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
4811
|
+
};
|
|
4812
|
+
async function resolveTarget(args) {
|
|
4813
|
+
const wranglerEnv = args.env?.trim() || void 0;
|
|
4814
|
+
let appId = args.app?.trim();
|
|
4815
|
+
if (appId && !APP_ID_RE.test(appId)) {
|
|
4816
|
+
throw new Error(`Invalid app id "${appId}" \u2014 expected app_<26 chars>.`);
|
|
4817
|
+
}
|
|
4818
|
+
if (!appId) {
|
|
4819
|
+
const appDir = findAppDir();
|
|
4820
|
+
appId = appDir && readAppId(appDir, wranglerEnv) || void 0;
|
|
4821
|
+
if (!appId) {
|
|
4822
|
+
throw new Error(
|
|
4823
|
+
"No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID (`deepspace init` mints one), or pass --app <appId>."
|
|
4824
|
+
);
|
|
5229
4825
|
}
|
|
5230
|
-
if (!unset.includes(name)) unset.push(name);
|
|
5231
4826
|
}
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
4827
|
+
const configName = validateConfigName(args.config?.trim() || defaultConfigNameForEnv(wranglerEnv));
|
|
4828
|
+
const token = await ensureToken();
|
|
4829
|
+
return { appId, configName, token };
|
|
4830
|
+
}
|
|
4831
|
+
function fail(err) {
|
|
4832
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
4833
|
+
process.exit(1);
|
|
5237
4834
|
}
|
|
5238
|
-
var list5 =
|
|
4835
|
+
var list5 = defineCommand20({
|
|
5239
4836
|
meta: { name: "list", description: "List masked secrets in a config" },
|
|
5240
4837
|
args: {
|
|
5241
|
-
...
|
|
5242
|
-
"only-names": {
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
4838
|
+
...COMMON_ARGS,
|
|
4839
|
+
"only-names": {
|
|
4840
|
+
type: "boolean",
|
|
4841
|
+
description: "Only print secret names; omit values and metadata",
|
|
4842
|
+
default: false
|
|
4843
|
+
},
|
|
4844
|
+
json: { type: "boolean", description: "Machine-readable output", default: false }
|
|
5246
4845
|
},
|
|
5247
4846
|
async run({ args }) {
|
|
5248
4847
|
try {
|
|
5249
|
-
const
|
|
5250
|
-
const {
|
|
5251
|
-
const result = await listSecrets(
|
|
5252
|
-
DEPLOY_URL5,
|
|
5253
|
-
token,
|
|
5254
|
-
target.projectName,
|
|
5255
|
-
target.configName,
|
|
5256
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5257
|
-
);
|
|
4848
|
+
const t = await resolveTarget(args);
|
|
4849
|
+
const { secrets } = await listSecrets(DEPLOY_URL6, t.token, t.appId, t.configName);
|
|
5258
4850
|
if (args.json) {
|
|
5259
|
-
|
|
4851
|
+
console.log(JSON.stringify({ appId: t.appId, config: t.configName, secrets }, null, 2));
|
|
5260
4852
|
return;
|
|
5261
4853
|
}
|
|
5262
|
-
if (
|
|
5263
|
-
console.log(`No secrets in ${
|
|
4854
|
+
if (secrets.length === 0) {
|
|
4855
|
+
console.log(`No secrets in ${t.configName}.`);
|
|
5264
4856
|
return;
|
|
5265
4857
|
}
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
return;
|
|
5269
|
-
}
|
|
5270
|
-
for (const item of result.secrets) {
|
|
5271
|
-
console.log(`${item.key.padEnd(32)} ${item.masked} v${item.version} ${item.updatedAt}`);
|
|
4858
|
+
for (const s of secrets) {
|
|
4859
|
+
console.log(args["only-names"] ? s.key : `${s.key} (v${s.version}, ${s.valueBytes}B, ${s.updatedAt})`);
|
|
5272
4860
|
}
|
|
5273
4861
|
} catch (err) {
|
|
5274
|
-
|
|
5275
|
-
process.exit(1);
|
|
4862
|
+
fail(err);
|
|
5276
4863
|
}
|
|
5277
4864
|
}
|
|
5278
4865
|
});
|
|
5279
|
-
var
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
};
|
|
5287
|
-
var set = defineCommand19({
|
|
5288
|
-
meta: { name: "set", description: "Set remote secrets from KEY=value or KEY value" },
|
|
5289
|
-
args: SET_ARGS,
|
|
5290
|
-
async run({ args, rawArgs }) {
|
|
4866
|
+
var set = defineCommand20({
|
|
4867
|
+
meta: { name: "set", description: "Set secrets from KEY=value pairs" },
|
|
4868
|
+
args: {
|
|
4869
|
+
...COMMON_ARGS,
|
|
4870
|
+
secret: { type: "positional", description: "KEY=value (repeatable)", required: true }
|
|
4871
|
+
},
|
|
4872
|
+
async run({ args }) {
|
|
5291
4873
|
try {
|
|
5292
|
-
const
|
|
5293
|
-
const
|
|
5294
|
-
|
|
5295
|
-
const secrets = parseSetInputs(inputs);
|
|
5296
|
-
await uploadSecrets(
|
|
5297
|
-
DEPLOY_URL5,
|
|
5298
|
-
token,
|
|
5299
|
-
target.projectName,
|
|
5300
|
-
target.configName,
|
|
5301
|
-
secrets,
|
|
5302
|
-
false,
|
|
5303
|
-
appScopedSecretsApiOptions(dir, target)
|
|
4874
|
+
const t = await resolveTarget(args);
|
|
4875
|
+
const pairs = [args.secret, ...args._ ?? []].filter(
|
|
4876
|
+
(v) => typeof v === "string" && v.length > 0
|
|
5304
4877
|
);
|
|
5305
|
-
const
|
|
5306
|
-
const
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
4878
|
+
const secrets = {};
|
|
4879
|
+
for (const pair of pairs) {
|
|
4880
|
+
const eq = pair.indexOf("=");
|
|
4881
|
+
if (eq <= 0) throw new Error(`Expected KEY=value, got "${pair}"`);
|
|
4882
|
+
const key = validateSecretName(pair.slice(0, eq));
|
|
4883
|
+
const value = pair.slice(eq + 1);
|
|
4884
|
+
validateSecretValue(key, value);
|
|
4885
|
+
secrets[key] = value;
|
|
5311
4886
|
}
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
unlinkedTargetNotice(target);
|
|
4887
|
+
if (Object.keys(secrets).length === 1) {
|
|
4888
|
+
const [[k, v]] = Object.entries(secrets);
|
|
4889
|
+
await setSecret(DEPLOY_URL6, t.token, t.appId, t.configName, k, v);
|
|
5316
4890
|
} else {
|
|
5317
|
-
|
|
5318
|
-
redeployReminder(target);
|
|
4891
|
+
await uploadSecrets(DEPLOY_URL6, t.token, t.appId, t.configName, secrets, false);
|
|
5319
4892
|
}
|
|
4893
|
+
console.log(`Set ${Object.keys(secrets).length} secret${Object.keys(secrets).length === 1 ? "" : "s"} in ${t.configName}.`);
|
|
5320
4894
|
} catch (err) {
|
|
5321
|
-
|
|
5322
|
-
process.exit(1);
|
|
4895
|
+
fail(err);
|
|
5323
4896
|
}
|
|
5324
4897
|
}
|
|
5325
4898
|
});
|
|
5326
|
-
var get =
|
|
5327
|
-
meta: { name: "get", description: "Get
|
|
4899
|
+
var get = defineCommand20({
|
|
4900
|
+
meta: { name: "get", description: "Get a secret; pass --plain to print the value" },
|
|
5328
4901
|
args: {
|
|
5329
|
-
...
|
|
5330
|
-
key: { type: "positional", description: "Secret
|
|
5331
|
-
plain: { type: "boolean", description: "Print plaintext value", default: false }
|
|
5332
|
-
"no-exit-on-missing-secret": { type: "boolean", description: "Do not exit when a requested secret is missing", default: false },
|
|
5333
|
-
raw: { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace has no variable interpolation", default: false },
|
|
5334
|
-
copy: { type: "boolean", description: "Accepted for Doppler CLI compatibility; clipboard copy is not implemented", default: false },
|
|
5335
|
-
type: { type: "boolean", description: "Accepted for Doppler CLI compatibility", default: false },
|
|
5336
|
-
visibility: { type: "boolean", description: "Accepted for Doppler CLI compatibility", default: false }
|
|
4902
|
+
...COMMON_ARGS,
|
|
4903
|
+
key: { type: "positional", description: "Secret name", required: true },
|
|
4904
|
+
plain: { type: "boolean", description: "Print the plaintext value", default: false }
|
|
5337
4905
|
},
|
|
5338
4906
|
async run({ args }) {
|
|
5339
4907
|
try {
|
|
5340
|
-
const
|
|
5341
|
-
const
|
|
5342
|
-
const keys = positionals(args, "key").map((key) => validateSecretName(key));
|
|
4908
|
+
const t = await resolveTarget(args);
|
|
4909
|
+
const key = validateSecretName(args.key);
|
|
5343
4910
|
if (args.plain) {
|
|
5344
|
-
const
|
|
5345
|
-
|
|
5346
|
-
for (const key of keys) {
|
|
5347
|
-
try {
|
|
5348
|
-
values[key] = await getSecretPlain(
|
|
5349
|
-
DEPLOY_URL5,
|
|
5350
|
-
token,
|
|
5351
|
-
target.projectName,
|
|
5352
|
-
target.configName,
|
|
5353
|
-
key,
|
|
5354
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5355
|
-
);
|
|
5356
|
-
} catch (err) {
|
|
5357
|
-
if (!args.noExitOnMissingSecret) throw err;
|
|
5358
|
-
missing2.push(key);
|
|
5359
|
-
}
|
|
5360
|
-
}
|
|
5361
|
-
if (args.json) {
|
|
5362
|
-
process.stdout.write(JSON.stringify({ secrets: values, missing: missing2 }, null, 2) + "\n");
|
|
5363
|
-
return;
|
|
5364
|
-
}
|
|
5365
|
-
for (const key of keys) if (Object.prototype.hasOwnProperty.call(values, key)) process.stdout.write(values[key] + "\n");
|
|
5366
|
-
return;
|
|
5367
|
-
}
|
|
5368
|
-
const result = await listSecrets(
|
|
5369
|
-
DEPLOY_URL5,
|
|
5370
|
-
token,
|
|
5371
|
-
target.projectName,
|
|
5372
|
-
target.configName,
|
|
5373
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5374
|
-
);
|
|
5375
|
-
const found = result.secrets.filter((item) => keys.includes(item.key));
|
|
5376
|
-
const foundKeys = new Set(found.map((item) => item.key));
|
|
5377
|
-
const missing = keys.filter((key) => !foundKeys.has(key));
|
|
5378
|
-
if (missing.length > 0 && !args.noExitOnMissingSecret) {
|
|
5379
|
-
throw new Error(`Secret "${missing[0]}" not found in ${target.projectName}/${target.configName}.`);
|
|
5380
|
-
}
|
|
5381
|
-
if (args.json) {
|
|
5382
|
-
process.stdout.write(JSON.stringify({ secrets: found, missing }, null, 2) + "\n");
|
|
4911
|
+
const { value } = await getSecretPlain(DEPLOY_URL6, t.token, t.appId, t.configName, key);
|
|
4912
|
+
process.stdout.write(value + "\n");
|
|
5383
4913
|
return;
|
|
5384
4914
|
}
|
|
5385
|
-
|
|
5386
|
-
|
|
4915
|
+
const { secrets } = await listSecrets(DEPLOY_URL6, t.token, t.appId, t.configName);
|
|
4916
|
+
const item = secrets.find((s) => s.key === key);
|
|
4917
|
+
if (!item) fail(new Error(`Secret "${key}" not found in ${t.configName}`));
|
|
4918
|
+
console.log(`${item.key} (v${item.version}, ${item.valueBytes}B, ${item.updatedAt})`);
|
|
5387
4919
|
} catch (err) {
|
|
5388
|
-
|
|
5389
|
-
process.exit(1);
|
|
4920
|
+
fail(err);
|
|
5390
4921
|
}
|
|
5391
4922
|
}
|
|
5392
4923
|
});
|
|
5393
|
-
var del2 =
|
|
5394
|
-
meta: { name: "delete", description: "Delete one or more
|
|
4924
|
+
var del2 = defineCommand20({
|
|
4925
|
+
meta: { name: "delete", description: "Delete one or more secrets" },
|
|
5395
4926
|
args: {
|
|
5396
|
-
...
|
|
5397
|
-
key: { type: "positional", description: "Secret
|
|
5398
|
-
yes: { type: "boolean", alias: "y", description: "Proceed without confirmation; accepted for Doppler CLI compatibility", default: false },
|
|
5399
|
-
raw: { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace has no variable interpolation", default: false }
|
|
4927
|
+
...COMMON_ARGS,
|
|
4928
|
+
key: { type: "positional", description: "Secret name (repeatable)", required: true }
|
|
5400
4929
|
},
|
|
5401
4930
|
async run({ args }) {
|
|
5402
4931
|
try {
|
|
5403
|
-
const
|
|
5404
|
-
const
|
|
5405
|
-
|
|
5406
|
-
for (const key of keys) {
|
|
5407
|
-
await deleteSecret(
|
|
5408
|
-
DEPLOY_URL5,
|
|
5409
|
-
token,
|
|
5410
|
-
target.projectName,
|
|
5411
|
-
target.configName,
|
|
5412
|
-
key,
|
|
5413
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5414
|
-
);
|
|
5415
|
-
}
|
|
5416
|
-
const count = await refreshCacheIfLinked(token, dir, target);
|
|
5417
|
-
const requiredSecrets = removeRequiredSecretsIfTopLevelLinked(dir, target, keys);
|
|
5418
|
-
if (args.json) {
|
|
5419
|
-
process.stdout.write(JSON.stringify({ ok: true, deleted: keys, target, ...cacheJson(count), ...requiredSecretsJson(requiredSecrets) }) + "\n");
|
|
5420
|
-
return;
|
|
5421
|
-
}
|
|
5422
|
-
console.log(`Deleted ${keys.length} secret${keys.length === 1 ? "" : "s"} from ${target.projectName}/${target.configName}.`);
|
|
5423
|
-
requiredSecretsNotice(requiredSecrets);
|
|
5424
|
-
if (count === null) {
|
|
5425
|
-
unlinkedTargetNotice(target);
|
|
5426
|
-
} else {
|
|
5427
|
-
console.log(`Refreshed ${count} cached secret${count === 1 ? "" : "s"}.`);
|
|
5428
|
-
redeployReminder(target);
|
|
5429
|
-
}
|
|
5430
|
-
} catch (err) {
|
|
5431
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
5432
|
-
process.exit(1);
|
|
5433
|
-
}
|
|
5434
|
-
}
|
|
5435
|
-
});
|
|
5436
|
-
var pull = defineCommand19({
|
|
5437
|
-
meta: { name: "pull", description: "Refresh .dev.vars* only when the selected config is linked" },
|
|
5438
|
-
args: commonArgs(),
|
|
5439
|
-
async run({ args }) {
|
|
5440
|
-
try {
|
|
5441
|
-
const token = await ensureToken();
|
|
5442
|
-
const { appDir: dir, target } = targetFromArgs(args);
|
|
5443
|
-
const values = await fetchSecretsValues(
|
|
5444
|
-
DEPLOY_URL5,
|
|
5445
|
-
token,
|
|
5446
|
-
target.projectName,
|
|
5447
|
-
target.configName,
|
|
5448
|
-
appScopedSecretsApiOptions(dir, target)
|
|
4932
|
+
const t = await resolveTarget(args);
|
|
4933
|
+
const keys = [args.key, ...args._ ?? []].filter(
|
|
4934
|
+
(v) => typeof v === "string" && v.length > 0
|
|
5449
4935
|
);
|
|
5450
|
-
const
|
|
5451
|
-
|
|
5452
|
-
if (linked) writeSecretsCacheFile(dir, values, target);
|
|
5453
|
-
if (args.json) {
|
|
5454
|
-
process.stdout.write(JSON.stringify({ ok: true, target, linked, cachedSecrets: linked ? count : null }) + "\n");
|
|
5455
|
-
return;
|
|
4936
|
+
for (const key of keys) {
|
|
4937
|
+
await deleteSecret(DEPLOY_URL6, t.token, t.appId, t.configName, validateSecretName(key));
|
|
5456
4938
|
}
|
|
5457
|
-
console.log(
|
|
5458
|
-
if (!linked) unlinkedTargetNotice(target);
|
|
4939
|
+
console.log(`Deleted ${keys.length} secret${keys.length === 1 ? "" : "s"} from ${t.configName}.`);
|
|
5459
4940
|
} catch (err) {
|
|
5460
|
-
|
|
5461
|
-
process.exit(1);
|
|
4941
|
+
fail(err);
|
|
5462
4942
|
}
|
|
5463
4943
|
}
|
|
5464
4944
|
});
|
|
5465
|
-
var
|
|
5466
|
-
meta: { name: "
|
|
4945
|
+
var upload = defineCommand20({
|
|
4946
|
+
meta: { name: "upload", description: "Upload secrets from a dotenv or JSON file" },
|
|
5467
4947
|
args: {
|
|
5468
|
-
...
|
|
5469
|
-
file: { type: "positional", description: "
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
"no-fallback": { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace does not use fallback files", default: false },
|
|
5475
|
-
offline: { type: "boolean", description: "Accepted for Doppler CLI compatibility; offline fallback files are not supported", default: false }
|
|
5476
|
-
},
|
|
5477
|
-
async run({ args, rawArgs }) {
|
|
5478
|
-
try {
|
|
5479
|
-
const token = await ensureToken();
|
|
5480
|
-
const { appDir: dir, target } = targetFromArgs(args);
|
|
5481
|
-
const values = await fetchSecretsValues(
|
|
5482
|
-
DEPLOY_URL5,
|
|
5483
|
-
token,
|
|
5484
|
-
target.projectName,
|
|
5485
|
-
target.configName,
|
|
5486
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5487
|
-
);
|
|
5488
|
-
const output = formatSecretsDownload(values, downloadFormat(args.format));
|
|
5489
|
-
const file = typeof args.file === "string" && args.file ? args.file : void 0;
|
|
5490
|
-
const noFile = Boolean(args.noFile) || hasRawFlag(rawArgs, "no-file");
|
|
5491
|
-
if (noFile || !file) {
|
|
5492
|
-
if (!noFile && !file) {
|
|
5493
|
-
throw new Error("Provide an output filepath or pass --no-file to print to stdout.");
|
|
5494
|
-
}
|
|
5495
|
-
process.stdout.write(output);
|
|
5496
|
-
return;
|
|
5497
|
-
}
|
|
5498
|
-
writeSecretFileSync(resolve10(file), output);
|
|
5499
|
-
if (args.json) {
|
|
5500
|
-
process.stdout.write(JSON.stringify({ ok: true, path: resolve10(file), target, secretCount: Object.keys(values).length }) + "\n");
|
|
5501
|
-
return;
|
|
5502
|
-
}
|
|
5503
|
-
console.log(`Downloaded ${Object.keys(values).length} secret${Object.keys(values).length === 1 ? "" : "s"} to ${file}.`);
|
|
5504
|
-
} catch (err) {
|
|
5505
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
5506
|
-
process.exit(1);
|
|
4948
|
+
...COMMON_ARGS,
|
|
4949
|
+
file: { type: "positional", description: "Path to a dotenv or JSON file (- for stdin)", required: true },
|
|
4950
|
+
replace: {
|
|
4951
|
+
type: "boolean",
|
|
4952
|
+
description: "Replace the whole config (delete keys missing from the file)",
|
|
4953
|
+
default: false
|
|
5507
4954
|
}
|
|
5508
|
-
}
|
|
5509
|
-
});
|
|
5510
|
-
var upload = defineCommand19({
|
|
5511
|
-
meta: { name: "upload", description: "Upload remote secrets from a dotenv or JSON file" },
|
|
5512
|
-
args: {
|
|
5513
|
-
...commonArgs(),
|
|
5514
|
-
file: { type: "positional", description: "Path to dotenv/JSON file", required: true },
|
|
5515
|
-
replace: { type: "boolean", description: "Delete secrets absent from the upload", default: false },
|
|
5516
|
-
raw: { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace has no variable interpolation", default: false }
|
|
5517
4955
|
},
|
|
5518
4956
|
async run({ args }) {
|
|
5519
4957
|
try {
|
|
5520
|
-
const
|
|
5521
|
-
const
|
|
5522
|
-
const file = String(args.file);
|
|
5523
|
-
const content = readExistingFile(file);
|
|
4958
|
+
const t = await resolveTarget(args);
|
|
4959
|
+
const content = args.file === "-" ? readFileSync13(0, "utf-8") : readFileSync13(args.file, "utf-8");
|
|
5524
4960
|
const secrets = parseSecretsUpload(content);
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
target.projectName,
|
|
5529
|
-
target.configName,
|
|
5530
|
-
secrets,
|
|
5531
|
-
!!args.replace,
|
|
5532
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5533
|
-
);
|
|
5534
|
-
const count = await refreshCacheIfLinked(token, dir, target);
|
|
5535
|
-
const requiredSecrets = updateRequiredSecretsIfTopLevelLinked(dir, target, Object.keys(secrets), {
|
|
5536
|
-
replace: !!args.replace
|
|
5537
|
-
});
|
|
5538
|
-
if (args.json) {
|
|
5539
|
-
process.stdout.write(JSON.stringify({ ok: true, uploaded: Object.keys(secrets), target, ...cacheJson(count), ...requiredSecretsJson(requiredSecrets) }) + "\n");
|
|
5540
|
-
return;
|
|
5541
|
-
}
|
|
5542
|
-
console.log(`Uploaded ${Object.keys(secrets).length} secret${Object.keys(secrets).length === 1 ? "" : "s"} to ${target.projectName}/${target.configName}.`);
|
|
5543
|
-
requiredSecretsNotice(requiredSecrets);
|
|
5544
|
-
if (count === null) {
|
|
5545
|
-
unlinkedTargetNotice(target);
|
|
5546
|
-
} else {
|
|
5547
|
-
console.log(`Refreshed ${count} cached secret${count === 1 ? "" : "s"}.`);
|
|
5548
|
-
redeployReminder(target);
|
|
5549
|
-
}
|
|
5550
|
-
} catch (err) {
|
|
5551
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
5552
|
-
process.exit(1);
|
|
5553
|
-
}
|
|
5554
|
-
}
|
|
5555
|
-
});
|
|
5556
|
-
var configsList = defineCommand19({
|
|
5557
|
-
meta: { name: "list", description: "List configs in a secrets project" },
|
|
5558
|
-
args: commonArgs(),
|
|
5559
|
-
async run({ args }) {
|
|
5560
|
-
try {
|
|
5561
|
-
const token = await ensureToken();
|
|
5562
|
-
const { appDir: dir, target } = targetFromArgs(args);
|
|
5563
|
-
const configs2 = await listConfigs(
|
|
5564
|
-
DEPLOY_URL5,
|
|
5565
|
-
token,
|
|
5566
|
-
target.projectName,
|
|
5567
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5568
|
-
);
|
|
5569
|
-
if (args.json) {
|
|
5570
|
-
process.stdout.write(JSON.stringify({ projectName: target.projectName, configs: configs2 }, null, 2) + "\n");
|
|
5571
|
-
return;
|
|
5572
|
-
}
|
|
5573
|
-
if (configs2.length === 0) {
|
|
5574
|
-
console.log(`No configs in ${target.projectName}.`);
|
|
5575
|
-
return;
|
|
5576
|
-
}
|
|
5577
|
-
for (const cfg of configs2) {
|
|
5578
|
-
console.log(`${cfg.name.padEnd(24)} ${cfg.secretCount ?? 0} secret${cfg.secretCount === 1 ? "" : "s"} ${cfg.updatedAt}`);
|
|
5579
|
-
}
|
|
4961
|
+
if (Object.keys(secrets).length === 0) fail(new Error("No secrets found in the input."));
|
|
4962
|
+
await uploadSecrets(DEPLOY_URL6, t.token, t.appId, t.configName, secrets, args.replace);
|
|
4963
|
+
console.log(`Uploaded ${Object.keys(secrets).length} secrets to ${t.configName}.`);
|
|
5580
4964
|
} catch (err) {
|
|
5581
|
-
|
|
5582
|
-
process.exit(1);
|
|
4965
|
+
fail(err);
|
|
5583
4966
|
}
|
|
5584
4967
|
}
|
|
5585
4968
|
});
|
|
5586
|
-
var
|
|
5587
|
-
meta: { name: "
|
|
4969
|
+
var download = defineCommand20({
|
|
4970
|
+
meta: { name: "download", description: "Download a config\u2019s secrets (dotenv/json/shell)" },
|
|
5588
4971
|
args: {
|
|
5589
|
-
...
|
|
5590
|
-
|
|
5591
|
-
|
|
4972
|
+
...COMMON_ARGS,
|
|
4973
|
+
format: {
|
|
4974
|
+
type: "string",
|
|
4975
|
+
description: "dotenv (default) | json | shell",
|
|
4976
|
+
default: "dotenv"
|
|
4977
|
+
}
|
|
5592
4978
|
},
|
|
5593
4979
|
async run({ args }) {
|
|
5594
4980
|
try {
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
const dir = appDir(args);
|
|
5600
|
-
const projectName = validateProjectName(args.project || resolveSecretsTarget(dir, { wranglerEnv: wranglerEnv(args) }).projectName);
|
|
5601
|
-
const rawName = typeof args.config === "string" && args.config ? args.config : typeof args.configName === "string" && args.configName ? args.configName : args.name;
|
|
5602
|
-
if (typeof rawName !== "string" || !rawName) throw new Error("Config name required");
|
|
5603
|
-
const configName = validateConfigName(rawName);
|
|
5604
|
-
const target = { projectName, configName, wranglerEnv: wranglerEnv(args) };
|
|
5605
|
-
const result = await createConfig(
|
|
5606
|
-
DEPLOY_URL5,
|
|
5607
|
-
token,
|
|
5608
|
-
projectName,
|
|
5609
|
-
configName,
|
|
5610
|
-
void 0,
|
|
5611
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5612
|
-
);
|
|
5613
|
-
const count = await refreshCacheIfLinked(token, dir, target);
|
|
5614
|
-
if (args.json) {
|
|
5615
|
-
process.stdout.write(JSON.stringify({ ...result, target, ...cacheJson(count) }) + "\n");
|
|
5616
|
-
return;
|
|
5617
|
-
}
|
|
5618
|
-
if (result.created) {
|
|
5619
|
-
console.log(`Created ${projectName}/${configName}.`);
|
|
5620
|
-
} else {
|
|
5621
|
-
console.log(`Config ${projectName}/${configName} already exists.`);
|
|
5622
|
-
}
|
|
5623
|
-
if (count === null) {
|
|
5624
|
-
unlinkedTargetNotice(target);
|
|
5625
|
-
} else {
|
|
5626
|
-
console.log(`Refreshed ${count} cached secret${count === 1 ? "" : "s"}.`);
|
|
4981
|
+
const t = await resolveTarget(args);
|
|
4982
|
+
const format = args.format;
|
|
4983
|
+
if (!["dotenv", "json", "shell"].includes(format)) {
|
|
4984
|
+
fail(new Error(`Unknown format "${args.format}" \u2014 use dotenv, json, or shell.`));
|
|
5627
4985
|
}
|
|
4986
|
+
const { secrets } = await fetchSecretsValues(DEPLOY_URL6, t.token, t.appId, t.configName);
|
|
4987
|
+
process.stdout.write(formatSecretsDownload(secrets, format));
|
|
5628
4988
|
} catch (err) {
|
|
5629
|
-
|
|
5630
|
-
process.exit(1);
|
|
4989
|
+
fail(err);
|
|
5631
4990
|
}
|
|
5632
4991
|
}
|
|
5633
4992
|
});
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
project: typeof args.project === "string" && args.project ? args.project : void 0,
|
|
5638
|
-
wranglerEnv: wranglerEnv(args)
|
|
5639
|
-
});
|
|
5640
|
-
const sources = positionals(args, "sourceConfig");
|
|
5641
|
-
if (sources.length > 1) throw new Error("Only one source config can be provided.");
|
|
5642
|
-
const sourceConfigName = validateConfigName(
|
|
5643
|
-
sources[0] ?? (typeof args.config === "string" && args.config ? args.config : baseTarget.configName)
|
|
5644
|
-
);
|
|
5645
|
-
if (typeof args.name !== "string" || !args.name) {
|
|
5646
|
-
throw new Error("Destination config name required; pass --name <new-config>.");
|
|
5647
|
-
}
|
|
5648
|
-
const target = {
|
|
5649
|
-
projectName: baseTarget.projectName,
|
|
5650
|
-
configName: validateConfigName(args.name),
|
|
5651
|
-
wranglerEnv: baseTarget.wranglerEnv
|
|
5652
|
-
};
|
|
5653
|
-
return { appDir: dir, sourceConfigName, target };
|
|
5654
|
-
}
|
|
5655
|
-
var configsClone = defineCommand19({
|
|
5656
|
-
meta: { name: "clone", description: "Create a remote config by copying another config server-side" },
|
|
5657
|
-
args: {
|
|
5658
|
-
...commonArgs(),
|
|
5659
|
-
sourceConfig: { type: "positional", description: "Source config name (default: --config or linked/default config)", required: false },
|
|
5660
|
-
name: { type: "string", description: "Destination config name", required: true }
|
|
5661
|
-
},
|
|
4993
|
+
var pull = defineCommand20({
|
|
4994
|
+
meta: { name: "pull", description: "Refresh the .dev.vars cache from the app store" },
|
|
4995
|
+
args: { ...COMMON_ARGS },
|
|
5662
4996
|
async run({ args }) {
|
|
5663
4997
|
try {
|
|
5664
|
-
const
|
|
5665
|
-
const
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5673
|
-
);
|
|
5674
|
-
const count = await refreshCacheIfLinked(token, dir, target);
|
|
5675
|
-
if (args.json) {
|
|
5676
|
-
process.stdout.write(JSON.stringify({ ...result, target, sourceConfigName, ...cacheJson(count) }) + "\n");
|
|
4998
|
+
const wranglerEnv = args.env?.trim() || void 0;
|
|
4999
|
+
const appDir = findAppDir();
|
|
5000
|
+
if (!appDir) fail(new Error("Run from a DeepSpace app directory (one containing wrangler.toml)."));
|
|
5001
|
+
const t = await resolveTarget(args);
|
|
5002
|
+
const ownerId = decodeJwtPayload(t.token).sub;
|
|
5003
|
+
const pulled = await pullAppSecretsCache(DEPLOY_URL6, t.token, t.appId, t.configName);
|
|
5004
|
+
if (!pulled) {
|
|
5005
|
+
console.log(`No secrets in ${t.configName} yet \u2014 nothing to pull.`);
|
|
5677
5006
|
return;
|
|
5678
5007
|
}
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
if (count === null) {
|
|
5688
|
-
unlinkedTargetNotice(target);
|
|
5689
|
-
} else {
|
|
5690
|
-
console.log(`Refreshed ${count} cached secret${count === 1 ? "" : "s"}.`);
|
|
5691
|
-
}
|
|
5008
|
+
await writeDevVars(appDir, ownerId, t.token, wranglerEnv, {
|
|
5009
|
+
appId: t.appId,
|
|
5010
|
+
generatedSecretsCache: renderSecretsCache(pulled.values, pulled),
|
|
5011
|
+
sharedDevVarsCache: true
|
|
5012
|
+
});
|
|
5013
|
+
console.log(
|
|
5014
|
+
`Pulled ${Object.keys(pulled.values).length} secrets (${t.configName}) into .dev.vars.`
|
|
5015
|
+
);
|
|
5692
5016
|
} catch (err) {
|
|
5693
|
-
|
|
5694
|
-
process.exit(1);
|
|
5017
|
+
fail(err);
|
|
5695
5018
|
}
|
|
5696
5019
|
}
|
|
5697
5020
|
});
|
|
5698
|
-
var
|
|
5699
|
-
meta: { name: "
|
|
5700
|
-
args: {
|
|
5701
|
-
...commonArgs(),
|
|
5702
|
-
name: { type: "positional", description: "Config name", required: false }
|
|
5703
|
-
},
|
|
5021
|
+
var configsList = defineCommand20({
|
|
5022
|
+
meta: { name: "list", description: "List the app\u2019s configs" },
|
|
5023
|
+
args: { ...COMMON_ARGS },
|
|
5704
5024
|
async run({ args }) {
|
|
5705
5025
|
try {
|
|
5706
|
-
const
|
|
5707
|
-
const
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
if (typeof rawName !== "string" || !rawName) throw new Error("Config name required");
|
|
5711
|
-
const configName = validateConfigName(rawName);
|
|
5712
|
-
const link = resolveSecretsLink(dir, target.wranglerEnv);
|
|
5713
|
-
const deletingLinked = link.linked && link.projectName === target.projectName && link.configName === configName;
|
|
5714
|
-
await deleteConfig(
|
|
5715
|
-
DEPLOY_URL5,
|
|
5716
|
-
token,
|
|
5717
|
-
target.projectName,
|
|
5718
|
-
configName,
|
|
5719
|
-
appScopedSecretsApiOptions(dir, target)
|
|
5720
|
-
);
|
|
5721
|
-
if (deletingLinked) {
|
|
5722
|
-
removeWranglerSecretsLink(dir, target.wranglerEnv);
|
|
5723
|
-
clearSecretsCacheFile(dir);
|
|
5724
|
-
}
|
|
5725
|
-
if (args.json) {
|
|
5726
|
-
process.stdout.write(JSON.stringify({
|
|
5727
|
-
ok: true,
|
|
5728
|
-
deleted: configName,
|
|
5729
|
-
projectName: target.projectName,
|
|
5730
|
-
unlinked: deletingLinked,
|
|
5731
|
-
cachedSecrets: deletingLinked ? 0 : null
|
|
5732
|
-
}) + "\n");
|
|
5026
|
+
const t = await resolveTarget(args);
|
|
5027
|
+
const { configs: configs2 } = await listConfigs(DEPLOY_URL6, t.token, t.appId);
|
|
5028
|
+
if (configs2.length === 0) {
|
|
5029
|
+
console.log("No configs yet \u2014 the first `secrets set` creates one.");
|
|
5733
5030
|
return;
|
|
5734
5031
|
}
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
console.log("Deleted config was linked; removed the local link and cleared generated cache.");
|
|
5738
|
-
redeployReminder({ ...target, configName });
|
|
5032
|
+
for (const cfg of configs2) {
|
|
5033
|
+
console.log(`${cfg.name} (${cfg.secretCount ?? 0} secrets, updated ${cfg.updatedAt})`);
|
|
5739
5034
|
}
|
|
5740
5035
|
} catch (err) {
|
|
5741
|
-
|
|
5742
|
-
process.exit(1);
|
|
5036
|
+
fail(err);
|
|
5743
5037
|
}
|
|
5744
5038
|
}
|
|
5745
5039
|
});
|
|
5746
|
-
var
|
|
5747
|
-
meta: { name: "
|
|
5748
|
-
args:
|
|
5040
|
+
var configsCreate = defineCommand20({
|
|
5041
|
+
meta: { name: "create", description: "Create a config (optionally copying another)" },
|
|
5042
|
+
args: {
|
|
5043
|
+
...COMMON_ARGS,
|
|
5044
|
+
name: { type: "positional", description: "Config name", required: true },
|
|
5045
|
+
"copy-from": { type: "string", description: "Copy secrets from this config", required: false }
|
|
5046
|
+
},
|
|
5749
5047
|
async run({ args }) {
|
|
5750
5048
|
try {
|
|
5751
|
-
const
|
|
5752
|
-
|
|
5753
|
-
await
|
|
5754
|
-
|
|
5755
|
-
const migration = await importLegacyDevVarsSecrets(dir, target, remoteCheck);
|
|
5756
|
-
if (args.json) {
|
|
5757
|
-
process.stdout.write(
|
|
5758
|
-
JSON.stringify({ ok: true, target, remoteCheck, ...migration && { migration } }) + "\n"
|
|
5759
|
-
);
|
|
5760
|
-
return;
|
|
5761
|
-
}
|
|
5762
|
-
console.log(`Linked ${target.wranglerEnv ? `[env.${target.wranglerEnv}]` : "top-level app"} to ${target.projectName}/${target.configName}.`);
|
|
5763
|
-
if (remoteCheck.status === "created") {
|
|
5764
|
-
console.log(`Created remote config ${target.projectName}/${target.configName}.`);
|
|
5765
|
-
}
|
|
5766
|
-
printLegacySecretsImport(migration, target);
|
|
5767
|
-
printLinkRemoteWarning(remoteCheck);
|
|
5049
|
+
const t = await resolveTarget(args);
|
|
5050
|
+
const name = validateConfigName(args.name);
|
|
5051
|
+
await createConfig(DEPLOY_URL6, t.token, t.appId, name, args["copy-from"] || void 0);
|
|
5052
|
+
console.log(`Created ${name}.`);
|
|
5768
5053
|
} catch (err) {
|
|
5769
|
-
|
|
5770
|
-
process.exit(1);
|
|
5054
|
+
fail(err);
|
|
5771
5055
|
}
|
|
5772
5056
|
}
|
|
5773
5057
|
});
|
|
5774
|
-
var
|
|
5775
|
-
meta: { name: "
|
|
5058
|
+
var configsDelete = defineCommand20({
|
|
5059
|
+
meta: { name: "delete", description: "Delete a config and all of its secrets" },
|
|
5776
5060
|
args: {
|
|
5777
|
-
...
|
|
5778
|
-
name: { type: "positional", description: "
|
|
5061
|
+
...COMMON_ARGS,
|
|
5062
|
+
name: { type: "positional", description: "Config name", required: true }
|
|
5779
5063
|
},
|
|
5780
5064
|
async run({ args }) {
|
|
5781
5065
|
try {
|
|
5782
|
-
const
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
await bestEffortClearServerSecretsLink(dir, targetEnv);
|
|
5787
|
-
}
|
|
5788
|
-
if (args.json) {
|
|
5789
|
-
process.stdout.write(JSON.stringify({ ok: true, unset, env: targetEnv ?? null, cachedSecrets: 0 }) + "\n");
|
|
5790
|
-
return;
|
|
5791
|
-
}
|
|
5792
|
-
console.log(`Unset ${unset.join(", ")} for ${targetEnv ? `[env.${targetEnv}]` : "top-level app"} secrets config.`);
|
|
5793
|
-
console.log("Cleared generated secrets cache for that target.");
|
|
5066
|
+
const t = await resolveTarget(args);
|
|
5067
|
+
const name = validateConfigName(args.name);
|
|
5068
|
+
await deleteConfig(DEPLOY_URL6, t.token, t.appId, name);
|
|
5069
|
+
console.log(`Deleted ${name}.`);
|
|
5794
5070
|
} catch (err) {
|
|
5795
|
-
|
|
5796
|
-
process.exit(1);
|
|
5071
|
+
fail(err);
|
|
5797
5072
|
}
|
|
5798
5073
|
}
|
|
5799
5074
|
});
|
|
5800
|
-
var
|
|
5801
|
-
meta: { name: "
|
|
5802
|
-
subCommands: {
|
|
5803
|
-
unset: configureUnset
|
|
5804
|
-
}
|
|
5805
|
-
});
|
|
5806
|
-
var configs = defineCommand19({
|
|
5807
|
-
meta: { name: "configs", description: "Manage remote secrets configs" },
|
|
5808
|
-
subCommands: {
|
|
5809
|
-
list: configsList,
|
|
5810
|
-
create: configsCreate,
|
|
5811
|
-
clone: configsClone,
|
|
5812
|
-
delete: configsDelete
|
|
5813
|
-
}
|
|
5075
|
+
var configs = defineCommand20({
|
|
5076
|
+
meta: { name: "configs", description: "Manage the app\u2019s secrets configs" },
|
|
5077
|
+
subCommands: { list: configsList, create: configsCreate, delete: configsDelete }
|
|
5814
5078
|
});
|
|
5815
|
-
var secrets_default =
|
|
5816
|
-
meta: { name: "secrets", description: "Manage
|
|
5079
|
+
var secrets_default = defineCommand20({
|
|
5080
|
+
meta: { name: "secrets", description: "Manage the app\u2019s secrets store" },
|
|
5817
5081
|
subCommands: {
|
|
5818
5082
|
list: list5,
|
|
5819
5083
|
set,
|
|
5820
5084
|
get,
|
|
5821
5085
|
delete: del2,
|
|
5822
|
-
pull,
|
|
5823
|
-
download,
|
|
5824
5086
|
upload,
|
|
5825
|
-
|
|
5826
|
-
|
|
5087
|
+
download,
|
|
5088
|
+
pull,
|
|
5827
5089
|
configs
|
|
5828
5090
|
}
|
|
5829
5091
|
});
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5092
|
+
|
|
5093
|
+
// src/cli/commands/init.ts
|
|
5094
|
+
import { defineCommand as defineCommand21 } from "citty";
|
|
5095
|
+
var init_default = defineCommand21({
|
|
5096
|
+
meta: {
|
|
5097
|
+
name: "init",
|
|
5098
|
+
description: "Mint this app\u2019s immutable DEEPSPACE_APP_ID into wrangler.toml"
|
|
5099
|
+
},
|
|
5100
|
+
args: {
|
|
5101
|
+
"new-id": {
|
|
5102
|
+
type: "boolean",
|
|
5103
|
+
description: "Replace the existing id \u2014 forks this repo as a SEPARATE app (new data, new secrets, new registration). The original app keeps running.",
|
|
5104
|
+
default: false
|
|
5105
|
+
},
|
|
5106
|
+
env: {
|
|
5107
|
+
type: "string",
|
|
5108
|
+
alias: "e",
|
|
5109
|
+
description: "wrangler.toml [env.<name>] block to stamp (each env is its own app)",
|
|
5110
|
+
required: false
|
|
5111
|
+
}
|
|
5112
|
+
},
|
|
5113
|
+
async run({ args }) {
|
|
5114
|
+
const appDir = findAppDir();
|
|
5115
|
+
if (!appDir) {
|
|
5116
|
+
console.error("Run from a DeepSpace app directory (one containing wrangler.toml).");
|
|
5117
|
+
process.exit(1);
|
|
5118
|
+
}
|
|
5119
|
+
const envName = args.env || void 0;
|
|
5120
|
+
const existing = readAppId(appDir, envName);
|
|
5121
|
+
if (existing && !args["new-id"]) {
|
|
5122
|
+
console.log(`Already initialized: ${existing}${envName ? ` (env: ${envName})` : ""}`);
|
|
5123
|
+
return;
|
|
5124
|
+
}
|
|
5125
|
+
const appId = mintAppId();
|
|
5126
|
+
writeAppId(appDir, appId, { wranglerEnv: envName, force: args["new-id"] });
|
|
5127
|
+
if (existing) {
|
|
5128
|
+
console.log(`Forked: ${existing} \u2192 ${appId}${envName ? ` (env: ${envName})` : ""}`);
|
|
5129
|
+
console.log("The next deploy registers this as a NEW app; the original is untouched.");
|
|
5130
|
+
} else {
|
|
5131
|
+
console.log(`Minted ${appId}${envName ? ` (env: ${envName})` : ""} \u2014 commit wrangler.toml.`);
|
|
5132
|
+
console.log("The first deploy registers it and claims the `name` subdomain.");
|
|
5840
5133
|
}
|
|
5841
|
-
return out;
|
|
5842
|
-
}
|
|
5843
|
-
if (inputs.length === 1) {
|
|
5844
|
-
const raw = inputs[0];
|
|
5845
|
-
throw new Error(`Secret value required for "${raw}". Use KEY=value or KEY value.`);
|
|
5846
|
-
}
|
|
5847
|
-
if (inputs.length === 2 && !inputs[0].includes("=")) {
|
|
5848
|
-
const key = validateSecretName(inputs[0]);
|
|
5849
|
-
const value = inputs[1];
|
|
5850
|
-
validateSecretValue(key, value);
|
|
5851
|
-
out[key] = value;
|
|
5852
|
-
return out;
|
|
5853
5134
|
}
|
|
5854
|
-
|
|
5855
|
-
}
|
|
5856
|
-
function readExistingFile(path) {
|
|
5857
|
-
const full = resolve10(path);
|
|
5858
|
-
if (!existsSync14(full)) throw new Error(`File not found: ${path}`);
|
|
5859
|
-
return readFileSync13(full, "utf-8");
|
|
5860
|
-
}
|
|
5135
|
+
});
|
|
5861
5136
|
|
|
5862
5137
|
// src/cli/cli.ts
|
|
5863
5138
|
var __dirname = dirname4(fileURLToPath2(import.meta.url));
|
|
5864
|
-
var pkg = JSON.parse(readFileSync14(
|
|
5865
|
-
if (process.platform === "linux" && !process.env.SSL_CERT_FILE &&
|
|
5139
|
+
var pkg = JSON.parse(readFileSync14(join16(__dirname, "..", "package.json"), "utf-8"));
|
|
5140
|
+
if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync14("/etc/ssl/certs/ca-certificates.crt")) {
|
|
5866
5141
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
5867
5142
|
}
|
|
5868
|
-
var create2 =
|
|
5143
|
+
var create2 = defineCommand22({
|
|
5869
5144
|
meta: {
|
|
5870
5145
|
name: "create",
|
|
5871
5146
|
description: "Create a new DeepSpace app"
|
|
@@ -5884,13 +5159,14 @@ var create2 = defineCommand20({
|
|
|
5884
5159
|
execSync4(`npm create deepspace@latest${name}`, { stdio: "inherit" });
|
|
5885
5160
|
}
|
|
5886
5161
|
});
|
|
5887
|
-
var main =
|
|
5162
|
+
var main = defineCommand22({
|
|
5888
5163
|
meta: {
|
|
5889
5164
|
name: "deepspace",
|
|
5890
5165
|
version: pkg.version,
|
|
5891
5166
|
description: "DeepSpace SDK CLI"
|
|
5892
5167
|
},
|
|
5893
5168
|
subCommands: {
|
|
5169
|
+
init: init_default,
|
|
5894
5170
|
create: create2,
|
|
5895
5171
|
login: login_default,
|
|
5896
5172
|
logout: logout_default,
|
|
@@ -5908,6 +5184,7 @@ var main = defineCommand20({
|
|
|
5908
5184
|
undeploy: undeploy_default,
|
|
5909
5185
|
domain: domain_default,
|
|
5910
5186
|
collaborators: collaborators_default,
|
|
5187
|
+
transfer: transfer_default,
|
|
5911
5188
|
library: library_default,
|
|
5912
5189
|
feedback: feedback_default,
|
|
5913
5190
|
secrets: secrets_default
|