deepspace 0.5.4 → 0.5.6
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 +1068 -612
- package/dist/cli.js.map +1 -1
- package/features/admin-page/feature.json +3 -1
- package/features/cron/feature.json +1 -2
- package/features/file-attachments/feature.json +3 -1
- package/features/landing/feature.json +1 -1
- package/features/messaging/feature.json +1 -1
- package/features/search-bar/feature.json +6 -5
- package/package.json +1 -1
- package/scripts/add-feature.cjs +174 -36
package/dist/cli.js
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/cli.ts
|
|
4
4
|
import { defineCommand as defineCommand22, runMain } from "citty";
|
|
5
|
-
import {
|
|
6
|
-
import { readFileSync as
|
|
7
|
-
import { dirname as
|
|
8
|
-
import { fileURLToPath as
|
|
5
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
6
|
+
import { readFileSync as readFileSync17, existsSync as existsSync14 } from "fs";
|
|
7
|
+
import { dirname as dirname5, join as join15 } from "path";
|
|
8
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9
9
|
|
|
10
10
|
// src/cli/lib/cli-errors.ts
|
|
11
11
|
var API_ERROR_HINTS = {
|
|
12
12
|
not_app_owner: "Only the app owner can do this.",
|
|
13
|
-
app_not_found: "App not found. Check the app
|
|
13
|
+
app_not_found: "App not found. Check the app id \u2014 the DEEPSPACE_APP_ID value in wrangler.toml, usually `app_\u2026` (a legacy app's id is its name). List your apps with `deepspace apps`. (`domain` commands take the deployed app *name* instead.)",
|
|
14
14
|
not_app_owner_or_collaborator: "You must be the app owner or a collaborator to do this.",
|
|
15
15
|
test_account_cannot_be_collaborator: "Test accounts cannot be added as collaborators. Use a real DeepSpace account.",
|
|
16
16
|
user_not_found: "No DeepSpace user with that email. They need to log in to DeepSpace at least once (`deepspace login`, or sign in to any app) before they can be referenced by email."
|
|
@@ -25,8 +25,11 @@ ${hint}` : message;
|
|
|
25
25
|
}
|
|
26
26
|
function renderCliError(err) {
|
|
27
27
|
console.error(formatCliError(err));
|
|
28
|
-
if (process.env.DEBUG
|
|
29
|
-
|
|
28
|
+
if (process.env.DEBUG) {
|
|
29
|
+
const { apiPath, status: status3 } = err;
|
|
30
|
+
if (apiPath) console.error(`
|
|
31
|
+
API ${apiPath}${status3 ? ` (${status3})` : ""}`);
|
|
32
|
+
if (err instanceof Error && err.stack) console.error("\n" + err.stack);
|
|
30
33
|
}
|
|
31
34
|
process.exit(1);
|
|
32
35
|
}
|
|
@@ -51,7 +54,7 @@ function wrapCommandErrors(cmd) {
|
|
|
51
54
|
|
|
52
55
|
// src/cli/commands/login.ts
|
|
53
56
|
import { defineCommand } from "citty";
|
|
54
|
-
import { writeFileSync as writeFileSync5, mkdirSync } from "fs";
|
|
57
|
+
import { writeFileSync as writeFileSync5, mkdirSync, readFileSync as readFileSync4 } from "fs";
|
|
55
58
|
import { join as join3 } from "path";
|
|
56
59
|
import { homedir } from "os";
|
|
57
60
|
import { randomBytes, createHash } from "crypto";
|
|
@@ -77,8 +80,215 @@ function createSpinner() {
|
|
|
77
80
|
// src/cli/env.ts
|
|
78
81
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
79
82
|
|
|
83
|
+
// src/server/rooms/binding-manifest.ts
|
|
84
|
+
var AUTO_PROVISION_SENTINEL = "auto";
|
|
85
|
+
var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
|
|
86
|
+
var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
|
|
87
|
+
"vectorize",
|
|
88
|
+
"ai",
|
|
89
|
+
"r2_bucket",
|
|
90
|
+
"kv_namespace",
|
|
91
|
+
"d1",
|
|
92
|
+
"queue",
|
|
93
|
+
"browser_rendering",
|
|
94
|
+
"analytics_engine",
|
|
95
|
+
"hyperdrive"
|
|
96
|
+
]);
|
|
97
|
+
var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
98
|
+
"ASSETS",
|
|
99
|
+
"PLATFORM_WORKER",
|
|
100
|
+
"API_WORKER",
|
|
101
|
+
"APP_NAME",
|
|
102
|
+
"OWNER_USER_ID",
|
|
103
|
+
"AUTH_JWT_PUBLIC_KEY",
|
|
104
|
+
"AUTH_JWT_ISSUER",
|
|
105
|
+
"AUTH_WORKER_URL",
|
|
106
|
+
"APP_IDENTITY_TOKEN",
|
|
107
|
+
"APP_OWNER_JWT",
|
|
108
|
+
"USAGE_EVENTS"
|
|
109
|
+
]);
|
|
110
|
+
function validateBindingManifest(manifest) {
|
|
111
|
+
if (!Array.isArray(manifest)) {
|
|
112
|
+
return { valid: false, errors: [{ reason: "Manifest must be an array" }] };
|
|
113
|
+
}
|
|
114
|
+
const errors = [];
|
|
115
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
116
|
+
for (const entry of manifest) {
|
|
117
|
+
if (!entry || typeof entry !== "object") {
|
|
118
|
+
errors.push({ reason: `Entry must be an object (got ${entry === null ? "null" : typeof entry})` });
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const e = entry;
|
|
122
|
+
const type = e.type;
|
|
123
|
+
const name = e.name;
|
|
124
|
+
if (typeof type !== "string" || !ALLOWED_BINDING_TYPES.has(type)) {
|
|
125
|
+
errors.push({
|
|
126
|
+
reason: `Disallowed binding type: ${typeof type === "string" ? type : `(${typeof type})`}. Allowed: ${[...ALLOWED_BINDING_TYPES].sort().join(", ")}`
|
|
127
|
+
});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (typeof name !== "string" || !name) {
|
|
131
|
+
errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (RESERVED_BINDING_NAMES.has(name)) {
|
|
135
|
+
errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (seenNames.has(name)) {
|
|
139
|
+
errors.push({ reason: `Duplicate binding name "${name}"` });
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
seenNames.add(name);
|
|
143
|
+
const binding = entry;
|
|
144
|
+
const fieldErr = requiredFieldError(binding);
|
|
145
|
+
if (fieldErr) errors.push({ binding, reason: fieldErr });
|
|
146
|
+
}
|
|
147
|
+
if (errors.length) return { valid: false, errors };
|
|
148
|
+
return { valid: true, bindings: manifest };
|
|
149
|
+
}
|
|
150
|
+
function requiredFieldError(b) {
|
|
151
|
+
switch (b.type) {
|
|
152
|
+
case "vectorize": {
|
|
153
|
+
if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
|
|
154
|
+
if (b.index_name === AUTO_PROVISION_SENTINEL) {
|
|
155
|
+
if (typeof b.dimensions !== "number" || b.dimensions <= 0) {
|
|
156
|
+
return `vectorize binding "${b.name}" with index_name="auto" requires "dimensions" (positive number)`;
|
|
157
|
+
}
|
|
158
|
+
if (!b.metric || !VECTORIZE_METRICS.has(b.metric)) {
|
|
159
|
+
return `vectorize binding "${b.name}" with index_name="auto" requires "metric" (one of: ${[...VECTORIZE_METRICS].join(", ")})`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
case "r2_bucket":
|
|
165
|
+
return b.bucket_name ? null : `r2_bucket binding "${b.name}" missing bucket_name (or "auto")`;
|
|
166
|
+
case "kv_namespace": {
|
|
167
|
+
if (!b.namespace_id) return `kv_namespace binding "${b.name}" missing namespace_id (or "auto")`;
|
|
168
|
+
if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {
|
|
169
|
+
return `kv_namespace binding "${b.name}" with namespace_id="auto" requires "title"`;
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
case "d1": {
|
|
174
|
+
if (!b.id) return `d1 binding "${b.name}" missing id (or "auto")`;
|
|
175
|
+
if (b.id === AUTO_PROVISION_SENTINEL && !b.database_name) {
|
|
176
|
+
return `d1 binding "${b.name}" with id="auto" requires "database_name"`;
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
case "queue":
|
|
181
|
+
return b.queue_name ? null : `queue binding "${b.name}" missing queue_name (or "auto")`;
|
|
182
|
+
case "hyperdrive":
|
|
183
|
+
if (!b.id) return `hyperdrive binding "${b.name}" missing id`;
|
|
184
|
+
if (b.id === AUTO_PROVISION_SENTINEL) {
|
|
185
|
+
return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
case "ai":
|
|
189
|
+
case "browser_rendering":
|
|
190
|
+
case "analytics_engine":
|
|
191
|
+
return null;
|
|
192
|
+
// no required fields beyond `name`
|
|
193
|
+
default: {
|
|
194
|
+
const _exhaustive = b;
|
|
195
|
+
void _exhaustive;
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function bindingManifestFromOutputConfig(outputConfig) {
|
|
201
|
+
const out = [];
|
|
202
|
+
const aiBinding = pluckString(outputConfig.ai, "binding");
|
|
203
|
+
if (aiBinding) out.push({ type: "ai", name: aiBinding });
|
|
204
|
+
for (const v of asObjectArray(outputConfig.vectorize)) {
|
|
205
|
+
const name = pluckString(v, "binding");
|
|
206
|
+
const index_name = pluckString(v, "index_name");
|
|
207
|
+
if (!name || !index_name) continue;
|
|
208
|
+
const dimensions = pluckNumber(v, "dimensions");
|
|
209
|
+
const metric = pluckString(v, "metric");
|
|
210
|
+
out.push({
|
|
211
|
+
type: "vectorize",
|
|
212
|
+
name,
|
|
213
|
+
index_name,
|
|
214
|
+
...dimensions != null && { dimensions },
|
|
215
|
+
...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
for (const r of asObjectArray(outputConfig.r2_buckets)) {
|
|
219
|
+
const name = pluckString(r, "binding");
|
|
220
|
+
const bucket_name = pluckString(r, "bucket_name");
|
|
221
|
+
if (name && bucket_name) out.push({ type: "r2_bucket", name, bucket_name });
|
|
222
|
+
}
|
|
223
|
+
for (const k of asObjectArray(outputConfig.kv_namespaces)) {
|
|
224
|
+
const name = pluckString(k, "binding");
|
|
225
|
+
const namespace_id = pluckString(k, "id");
|
|
226
|
+
if (!name || !namespace_id) continue;
|
|
227
|
+
const title = pluckString(k, "title");
|
|
228
|
+
out.push({
|
|
229
|
+
type: "kv_namespace",
|
|
230
|
+
name,
|
|
231
|
+
namespace_id,
|
|
232
|
+
...title && { title }
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
for (const d of asObjectArray(outputConfig.d1_databases)) {
|
|
236
|
+
const name = pluckString(d, "binding");
|
|
237
|
+
const id = pluckString(d, "database_id");
|
|
238
|
+
if (!name || !id) continue;
|
|
239
|
+
const database_name = pluckString(d, "database_name");
|
|
240
|
+
out.push({
|
|
241
|
+
type: "d1",
|
|
242
|
+
name,
|
|
243
|
+
id,
|
|
244
|
+
...database_name && { database_name }
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const queues = isObject(outputConfig.queues) ? outputConfig.queues : null;
|
|
248
|
+
for (const q of asObjectArray(queues?.producers)) {
|
|
249
|
+
const name = pluckString(q, "binding");
|
|
250
|
+
const queue_name = pluckString(q, "queue");
|
|
251
|
+
if (name && queue_name) out.push({ type: "queue", name, queue_name });
|
|
252
|
+
}
|
|
253
|
+
const browserBinding = pluckString(outputConfig.browser, "binding");
|
|
254
|
+
if (browserBinding) out.push({ type: "browser_rendering", name: browserBinding });
|
|
255
|
+
for (const a of asObjectArray(outputConfig.analytics_engine_datasets)) {
|
|
256
|
+
const name = pluckString(a, "binding");
|
|
257
|
+
const dataset = pluckString(a, "dataset");
|
|
258
|
+
if (name) out.push({ type: "analytics_engine", name, dataset: dataset ?? void 0 });
|
|
259
|
+
}
|
|
260
|
+
for (const h of asObjectArray(outputConfig.hyperdrive)) {
|
|
261
|
+
const name = pluckString(h, "binding");
|
|
262
|
+
const id = pluckString(h, "id");
|
|
263
|
+
if (name && id) out.push({ type: "hyperdrive", name, id });
|
|
264
|
+
}
|
|
265
|
+
return out;
|
|
266
|
+
}
|
|
267
|
+
function isObject(v) {
|
|
268
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
269
|
+
}
|
|
270
|
+
function pluckString(v, key) {
|
|
271
|
+
if (!isObject(v)) return null;
|
|
272
|
+
const val = v[key];
|
|
273
|
+
return typeof val === "string" && val.length > 0 ? val : null;
|
|
274
|
+
}
|
|
275
|
+
function pluckNumber(v, key) {
|
|
276
|
+
if (!isObject(v)) return null;
|
|
277
|
+
const val = v[key];
|
|
278
|
+
return typeof val === "number" && Number.isFinite(val) ? val : null;
|
|
279
|
+
}
|
|
280
|
+
function asObjectArray(v) {
|
|
281
|
+
if (!Array.isArray(v)) return [];
|
|
282
|
+
return v.filter(isObject);
|
|
283
|
+
}
|
|
284
|
+
|
|
80
285
|
// src/cli/lib/secrets.ts
|
|
81
286
|
var GENERATED_SECRETS_DIVIDER = "# --- DeepSpace secrets cache: generated by `npx deepspace secrets`; do not edit manually ---";
|
|
287
|
+
var RESERVED_SECRET_NAMES = /* @__PURE__ */ new Set([
|
|
288
|
+
...RESERVED_BINDING_NAMES,
|
|
289
|
+
"API_WORKER_URL",
|
|
290
|
+
"PLATFORM_WORKER_URL"
|
|
291
|
+
]);
|
|
82
292
|
var SECRET_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
83
293
|
var CONFIG_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
84
294
|
var MAX_SECRET_VALUE_BYTES = 32 * 1024;
|
|
@@ -96,6 +306,11 @@ function validateSecretName(name) {
|
|
|
96
306
|
`Invalid secret name "${name}". Use letters, numbers, and underscores; start with a letter or underscore.`
|
|
97
307
|
);
|
|
98
308
|
}
|
|
309
|
+
if (RESERVED_SECRET_NAMES.has(name)) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
`"${name}" is managed by the DeepSpace SDK and can't be set as an app secret \u2014 the platform injects it at deploy time.`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
99
314
|
return name;
|
|
100
315
|
}
|
|
101
316
|
function validateSecretValue(key, value) {
|
|
@@ -117,7 +332,10 @@ async function secretsApi(deployUrl, token, path, init = {}) {
|
|
|
117
332
|
});
|
|
118
333
|
const body = await res.json().catch(() => ({}));
|
|
119
334
|
if (!res.ok) {
|
|
120
|
-
|
|
335
|
+
const err = new Error(body.error ?? `Secrets request failed (${res.status})`);
|
|
336
|
+
err.status = res.status;
|
|
337
|
+
err.apiPath = path;
|
|
338
|
+
throw err;
|
|
121
339
|
}
|
|
122
340
|
return body;
|
|
123
341
|
}
|
|
@@ -177,8 +395,7 @@ async function pullAppSecretsCache(deployUrl, token, appId, configName) {
|
|
|
177
395
|
const { secrets } = await fetchSecretsValues(deployUrl, token, appId, configName);
|
|
178
396
|
return { appId, configName, values: secrets };
|
|
179
397
|
} catch (err) {
|
|
180
|
-
|
|
181
|
-
if (msg.includes("(404)")) return null;
|
|
398
|
+
if (err?.status === 404) return null;
|
|
182
399
|
throw err;
|
|
183
400
|
}
|
|
184
401
|
}
|
|
@@ -223,22 +440,56 @@ function parseSecretsUpload(content) {
|
|
|
223
440
|
return out2;
|
|
224
441
|
}
|
|
225
442
|
const out = {};
|
|
226
|
-
|
|
227
|
-
|
|
443
|
+
const lines = trimmed.split(/\r?\n/);
|
|
444
|
+
for (let i = 0; i < lines.length; i++) {
|
|
445
|
+
const line = lines[i].trim();
|
|
228
446
|
if (!line || line.startsWith("#")) continue;
|
|
229
447
|
const eq = line.indexOf("=");
|
|
230
448
|
if (eq <= 0) continue;
|
|
231
449
|
const key = line.slice(0, eq).trim();
|
|
232
450
|
if (!SECRET_NAME_RE.test(key)) continue;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
451
|
+
validateSecretName(key);
|
|
452
|
+
const raw = line.slice(eq + 1).trim();
|
|
453
|
+
const quote = raw[0] === '"' || raw[0] === "'" ? raw[0] : null;
|
|
454
|
+
let value;
|
|
455
|
+
if (!quote) {
|
|
456
|
+
value = raw;
|
|
457
|
+
} else if (endsWithUnescapedQuote(raw.slice(1), quote)) {
|
|
458
|
+
const inner = raw.slice(1, -1);
|
|
459
|
+
value = quote === '"' ? unescapeDotenv(inner) : inner;
|
|
460
|
+
} else {
|
|
461
|
+
const startLine = i + 1;
|
|
462
|
+
const parts = [raw.slice(1)];
|
|
463
|
+
let closed = false;
|
|
464
|
+
while (++i < lines.length) {
|
|
465
|
+
const cont = lines[i].trimEnd();
|
|
466
|
+
if (endsWithUnescapedQuote(cont, quote)) {
|
|
467
|
+
parts.push(cont.slice(0, -1));
|
|
468
|
+
closed = true;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
parts.push(lines[i]);
|
|
472
|
+
}
|
|
473
|
+
if (!closed) {
|
|
474
|
+
throw new Error(
|
|
475
|
+
`Unterminated quoted value for "${key}" (opens on line ${startLine}). Close the ${quote === '"' ? "double" : "single"} quote, or escape newlines as \\n on one line.`
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
value = parts.join("\n");
|
|
479
|
+
if (quote === '"') value = unescapeDotenv(value);
|
|
236
480
|
}
|
|
237
481
|
validateSecretValue(key, value);
|
|
238
482
|
out[key] = value;
|
|
239
483
|
}
|
|
240
484
|
return out;
|
|
241
485
|
}
|
|
486
|
+
function endsWithUnescapedQuote(s, q) {
|
|
487
|
+
if (!s.endsWith(q)) return false;
|
|
488
|
+
if (q === "'") return true;
|
|
489
|
+
let backslashes = 0;
|
|
490
|
+
for (let j = s.length - 2; j >= 0 && s[j] === "\\"; j--) backslashes++;
|
|
491
|
+
return backslashes % 2 === 0;
|
|
492
|
+
}
|
|
242
493
|
function unescapeDotenv(v) {
|
|
243
494
|
let out = "";
|
|
244
495
|
for (let i = 0; i < v.length; i++) {
|
|
@@ -251,13 +502,16 @@ function unescapeDotenv(v) {
|
|
|
251
502
|
}
|
|
252
503
|
return out;
|
|
253
504
|
}
|
|
505
|
+
function shellSingleQuote(value) {
|
|
506
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
507
|
+
}
|
|
254
508
|
function formatSecretsDownload(secrets, format) {
|
|
255
509
|
const entries = Object.entries(secrets).sort(([a], [b]) => a.localeCompare(b));
|
|
256
510
|
if (format === "json") {
|
|
257
511
|
return JSON.stringify(Object.fromEntries(entries), null, 2) + "\n";
|
|
258
512
|
}
|
|
259
513
|
if (format === "shell") {
|
|
260
|
-
return entries.map(([k, v]) => `export ${k}=${
|
|
514
|
+
return entries.map(([k, v]) => `export ${k}=${shellSingleQuote(v)}`).join("\n") + "\n";
|
|
261
515
|
}
|
|
262
516
|
return entries.map(([k, v]) => `${k}=${quoteDotenvValue(v)}`).join("\n") + "\n";
|
|
263
517
|
}
|
|
@@ -690,38 +944,6 @@ function extractCustomDevVars(content) {
|
|
|
690
944
|
while (out.length && out[out.length - 1].trim() === "") out.pop();
|
|
691
945
|
return out.join("\n");
|
|
692
946
|
}
|
|
693
|
-
function removeDevVarsKeys(content, keys) {
|
|
694
|
-
const lines = content.split("\n");
|
|
695
|
-
const out = [];
|
|
696
|
-
let i = 0;
|
|
697
|
-
while (i < lines.length) {
|
|
698
|
-
const line = lines[i];
|
|
699
|
-
const eq = line.indexOf("=");
|
|
700
|
-
const keyMatch = eq >= 0 ? line.slice(0, eq).match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*$/) : null;
|
|
701
|
-
if (!keyMatch) {
|
|
702
|
-
out.push(line);
|
|
703
|
-
i++;
|
|
704
|
-
continue;
|
|
705
|
-
}
|
|
706
|
-
const key = keyMatch[1];
|
|
707
|
-
const valStart = line.slice(eq + 1);
|
|
708
|
-
const block = [line];
|
|
709
|
-
if (valStart.startsWith('"') && !hasUnescapedQuote(valStart.slice(1))) {
|
|
710
|
-
i++;
|
|
711
|
-
while (i < lines.length) {
|
|
712
|
-
block.push(lines[i]);
|
|
713
|
-
const closed = hasUnescapedQuote(lines[i]);
|
|
714
|
-
i++;
|
|
715
|
-
if (closed) break;
|
|
716
|
-
}
|
|
717
|
-
} else {
|
|
718
|
-
i++;
|
|
719
|
-
}
|
|
720
|
-
if (!keys.has(key)) out.push(...block);
|
|
721
|
-
}
|
|
722
|
-
while (out.length && out[out.length - 1].trim() === "") out.pop();
|
|
723
|
-
return out.join("\n");
|
|
724
|
-
}
|
|
725
947
|
function hasUnescapedQuote(s) {
|
|
726
948
|
let escaped = false;
|
|
727
949
|
for (const ch of s) {
|
|
@@ -820,8 +1042,10 @@ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {})
|
|
|
820
1042
|
`OWNER_USER_ID=${ownerId}`,
|
|
821
1043
|
`APP_OWNER_JWT=${appOwnerJwt}`,
|
|
822
1044
|
...appIdentityToken ? [`APP_IDENTITY_TOKEN=${appIdentityToken}`] : [],
|
|
823
|
-
// Open /api/debug/* (proxied to RecordRoom)
|
|
824
|
-
//
|
|
1045
|
+
// Open /api/debug/* (proxied to RecordRoom) locally. This is the SDK's
|
|
1046
|
+
// dev-only auto-value; the deploy ships the remote store, not this
|
|
1047
|
+
// `.dev.vars` section, so it never reaches prod. To enable it on a deployed
|
|
1048
|
+
// env, `deepspace secrets set ALLOW_DEBUG_ROUTES=true`.
|
|
825
1049
|
`ALLOW_DEBUG_ROUTES=true`
|
|
826
1050
|
].join("\n");
|
|
827
1051
|
const useSharedDevVarsCache = opts.sharedDevVarsCache ?? opts.generatedSecretsCache !== void 0;
|
|
@@ -884,19 +1108,38 @@ var login_default = defineCommand({
|
|
|
884
1108
|
args: {
|
|
885
1109
|
email: {
|
|
886
1110
|
type: "string",
|
|
887
|
-
description: "Email address (non-interactive mode)",
|
|
1111
|
+
description: "Email address (non-interactive mode; or $DEEPSPACE_EMAIL)",
|
|
888
1112
|
required: false
|
|
889
1113
|
},
|
|
890
1114
|
password: {
|
|
891
1115
|
type: "string",
|
|
892
|
-
description: "Password (
|
|
1116
|
+
description: "Password (discouraged \u2014 visible in ps/history; prefer --password-stdin or $DEEPSPACE_PASSWORD)",
|
|
893
1117
|
required: false
|
|
1118
|
+
},
|
|
1119
|
+
"password-stdin": {
|
|
1120
|
+
type: "boolean",
|
|
1121
|
+
description: "Read the password from stdin instead of --password",
|
|
1122
|
+
default: false
|
|
894
1123
|
}
|
|
895
1124
|
},
|
|
896
1125
|
async run({ args }) {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1126
|
+
const interactive = Boolean(process.stdin.isTTY);
|
|
1127
|
+
const { email, password } = resolveLoginCredentials({
|
|
1128
|
+
emailArg: args.email,
|
|
1129
|
+
passwordArg: args.password,
|
|
1130
|
+
envEmail: interactive ? void 0 : process.env.DEEPSPACE_EMAIL,
|
|
1131
|
+
envPassword: interactive ? void 0 : process.env.DEEPSPACE_PASSWORD,
|
|
1132
|
+
passwordStdin: args["password-stdin"] ? readPasswordFromStdin() : void 0
|
|
1133
|
+
});
|
|
1134
|
+
const passwordIntent = Boolean(args["password-stdin"]) || args.password !== void 0 || password !== void 0;
|
|
1135
|
+
const decision = loginModeDecision({ email, password, passwordIntent });
|
|
1136
|
+
if (decision.mode === "error") {
|
|
1137
|
+
console.error(decision.message);
|
|
1138
|
+
process.exit(1);
|
|
1139
|
+
}
|
|
1140
|
+
if (decision.mode === "password") {
|
|
1141
|
+
console.log(`Signing in as ${email}...`);
|
|
1142
|
+
await doEmailLogin(email, password);
|
|
900
1143
|
console.log("Logged in");
|
|
901
1144
|
return;
|
|
902
1145
|
}
|
|
@@ -947,6 +1190,34 @@ plans on the web dashboard:
|
|
|
947
1190
|
p2.outro("Done");
|
|
948
1191
|
}
|
|
949
1192
|
});
|
|
1193
|
+
function resolveLoginCredentials(opts) {
|
|
1194
|
+
const email = opts.emailArg || opts.envEmail || void 0;
|
|
1195
|
+
const password = opts.passwordStdin !== void 0 ? opts.passwordStdin : opts.passwordArg || opts.envPassword || void 0;
|
|
1196
|
+
return { email, password };
|
|
1197
|
+
}
|
|
1198
|
+
function loginModeDecision(opts) {
|
|
1199
|
+
if (opts.email && opts.password) return { mode: "password" };
|
|
1200
|
+
if (!opts.passwordIntent) return { mode: "oauth" };
|
|
1201
|
+
if (!opts.email) {
|
|
1202
|
+
return {
|
|
1203
|
+
mode: "error",
|
|
1204
|
+
message: "Non-interactive login needs an email: --email <you@example.com> (or $DEEPSPACE_EMAIL)."
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
return {
|
|
1208
|
+
mode: "error",
|
|
1209
|
+
message: "Non-interactive login needs a non-empty password: --password-stdin, $DEEPSPACE_PASSWORD, or --password."
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
function readPasswordFromStdin() {
|
|
1213
|
+
if (process.stdin.isTTY) {
|
|
1214
|
+
console.error(
|
|
1215
|
+
'--password-stdin expects the password piped on stdin, e.g. `printf %s "$PW" | deepspace login --email you@x.com --password-stdin`.'
|
|
1216
|
+
);
|
|
1217
|
+
process.exit(1);
|
|
1218
|
+
}
|
|
1219
|
+
return readFileSync4(0, "utf-8").replace(/\r?\n$/, "");
|
|
1220
|
+
}
|
|
950
1221
|
async function pollForCompletion(sessionId, codeVerifier) {
|
|
951
1222
|
const maxAttempts = 120;
|
|
952
1223
|
const interval = 5e3;
|
|
@@ -1017,7 +1288,7 @@ async function doEmailLogin(email, password) {
|
|
|
1017
1288
|
|
|
1018
1289
|
// src/cli/commands/logout.ts
|
|
1019
1290
|
import { defineCommand as defineCommand2 } from "citty";
|
|
1020
|
-
import { existsSync as existsSync4, readFileSync as
|
|
1291
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, rmSync as rmSync2 } from "fs";
|
|
1021
1292
|
import { join as join4 } from "path";
|
|
1022
1293
|
import { homedir as homedir2 } from "os";
|
|
1023
1294
|
var AUTH_URL2 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
|
|
@@ -1030,7 +1301,7 @@ var logout_default = defineCommand2({
|
|
|
1030
1301
|
description: "Sign out and remove cached credentials"
|
|
1031
1302
|
},
|
|
1032
1303
|
async run() {
|
|
1033
|
-
const sessionToken = existsSync4(SESSION_PATH) ?
|
|
1304
|
+
const sessionToken = existsSync4(SESSION_PATH) ? readFileSync5(SESSION_PATH, "utf-8").trim() : null;
|
|
1034
1305
|
if (sessionToken) {
|
|
1035
1306
|
try {
|
|
1036
1307
|
await fetch(`${AUTH_URL2}/api/auth/sign-out`, {
|
|
@@ -1060,11 +1331,11 @@ var logout_default = defineCommand2({
|
|
|
1060
1331
|
|
|
1061
1332
|
// src/cli/commands/dev.ts
|
|
1062
1333
|
import { defineCommand as defineCommand3 } from "citty";
|
|
1063
|
-
import { resolve as resolve4, basename, join as join9 } from "path";
|
|
1334
|
+
import { resolve as resolve4, basename as basename2, join as join9 } from "path";
|
|
1064
1335
|
import spawn from "cross-spawn";
|
|
1065
1336
|
|
|
1066
1337
|
// src/cli/auth.ts
|
|
1067
|
-
import { readFileSync as
|
|
1338
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
1068
1339
|
import { join as join5 } from "path";
|
|
1069
1340
|
import { homedir as homedir3 } from "os";
|
|
1070
1341
|
|
|
@@ -1084,7 +1355,7 @@ var SESSION_PATH2 = join5(DIR2, "session");
|
|
|
1084
1355
|
var TOKEN_PATH2 = join5(DIR2, "token");
|
|
1085
1356
|
async function ensureToken() {
|
|
1086
1357
|
if (existsSync5(TOKEN_PATH2)) {
|
|
1087
|
-
const existing =
|
|
1358
|
+
const existing = readFileSync6(TOKEN_PATH2, "utf-8").trim();
|
|
1088
1359
|
if (isTokenValid(existing)) {
|
|
1089
1360
|
return existing;
|
|
1090
1361
|
}
|
|
@@ -1092,7 +1363,7 @@ async function ensureToken() {
|
|
|
1092
1363
|
if (!existsSync5(SESSION_PATH2)) {
|
|
1093
1364
|
throw new Error("Not logged in. Run `deepspace login` first.");
|
|
1094
1365
|
}
|
|
1095
|
-
const sessionToken =
|
|
1366
|
+
const sessionToken = readFileSync6(SESSION_PATH2, "utf-8").trim();
|
|
1096
1367
|
const token = await exchangeSession(AUTH_URL3, sessionToken);
|
|
1097
1368
|
if (!token) {
|
|
1098
1369
|
throw new Error("Session expired. Run `deepspace login` to re-authenticate.");
|
|
@@ -1113,20 +1384,56 @@ function isTokenValid(jwt) {
|
|
|
1113
1384
|
// src/cli/lib/app-context.ts
|
|
1114
1385
|
import {
|
|
1115
1386
|
existsSync as existsSync6,
|
|
1116
|
-
readFileSync as
|
|
1387
|
+
readFileSync as readFileSync7,
|
|
1117
1388
|
readdirSync,
|
|
1118
1389
|
writeFileSync as writeFileSync7,
|
|
1119
1390
|
mkdirSync as mkdirSync3,
|
|
1120
1391
|
renameSync,
|
|
1121
1392
|
unlinkSync
|
|
1122
1393
|
} from "fs";
|
|
1123
|
-
import { join as join6, resolve as resolve2, dirname, isAbsolute, sep } from "path";
|
|
1394
|
+
import { join as join6, resolve as resolve2, dirname, basename, isAbsolute, sep } from "path";
|
|
1124
1395
|
import { parse as parseToml3 } from "smol-toml";
|
|
1396
|
+
|
|
1397
|
+
// src/cli/lib/api.ts
|
|
1398
|
+
var ApiError = class extends Error {
|
|
1399
|
+
constructor(message, status3, code, apiPath) {
|
|
1400
|
+
super(message);
|
|
1401
|
+
this.status = status3;
|
|
1402
|
+
this.code = code;
|
|
1403
|
+
this.apiPath = apiPath;
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
async function apiFetch(baseUrl, token, path, init) {
|
|
1407
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
1408
|
+
...init,
|
|
1409
|
+
headers: {
|
|
1410
|
+
Authorization: `Bearer ${token}`,
|
|
1411
|
+
"Content-Type": "application/json",
|
|
1412
|
+
...init?.headers ?? {}
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
const text2 = await res.text();
|
|
1416
|
+
if (!res.ok) {
|
|
1417
|
+
let msg = text2;
|
|
1418
|
+
let code;
|
|
1419
|
+
try {
|
|
1420
|
+
const body = JSON.parse(text2);
|
|
1421
|
+
msg = body.error ?? text2;
|
|
1422
|
+
code = body.code;
|
|
1423
|
+
} catch {
|
|
1424
|
+
}
|
|
1425
|
+
throw new ApiError(msg || `Request failed (${res.status})`, res.status, code, path);
|
|
1426
|
+
}
|
|
1427
|
+
return text2 ? JSON.parse(text2) : {};
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// src/cli/lib/app-context.ts
|
|
1431
|
+
var STRICT_APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
1125
1432
|
function detectAppName(cwd = process.cwd()) {
|
|
1126
1433
|
const wranglerPath = join6(resolve2(cwd), "wrangler.toml");
|
|
1127
1434
|
if (!existsSync6(wranglerPath)) return null;
|
|
1128
1435
|
try {
|
|
1129
|
-
const cfg = parseToml3(
|
|
1436
|
+
const cfg = parseToml3(readFileSync7(wranglerPath, "utf-8"));
|
|
1130
1437
|
return typeof cfg.name === "string" && cfg.name.length > 0 ? cfg.name : null;
|
|
1131
1438
|
} catch (err) {
|
|
1132
1439
|
throw new Error(
|
|
@@ -1175,18 +1482,42 @@ function writeLaunchFile(launchPath, config) {
|
|
|
1175
1482
|
function readLaunchFile(launchPath) {
|
|
1176
1483
|
const empty = { version: "0.0.1", configurations: [] };
|
|
1177
1484
|
if (!existsSync6(launchPath)) return empty;
|
|
1178
|
-
const raw =
|
|
1485
|
+
const raw = readFileSync7(launchPath, "utf-8").trim();
|
|
1179
1486
|
if (!raw) return empty;
|
|
1180
1487
|
const parsed = JSON.parse(raw);
|
|
1181
1488
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.configurations)) return null;
|
|
1182
1489
|
return parsed;
|
|
1183
1490
|
}
|
|
1184
|
-
function writeLaunchConfigIfMissing(appDir, appName, port) {
|
|
1491
|
+
function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
|
|
1185
1492
|
const launchPath = join6(appDir, ".claude", "launch.json");
|
|
1186
1493
|
try {
|
|
1187
1494
|
const config = readLaunchFile(launchPath);
|
|
1188
1495
|
if (!config) return;
|
|
1189
|
-
|
|
1496
|
+
const existing = config.configurations.find((c) => c?.name === appName);
|
|
1497
|
+
if (existing) {
|
|
1498
|
+
if (!opts.updatePort) return;
|
|
1499
|
+
let changed = false;
|
|
1500
|
+
if (Number(existing.port) !== port) {
|
|
1501
|
+
existing.port = port;
|
|
1502
|
+
changed = true;
|
|
1503
|
+
}
|
|
1504
|
+
if (Array.isArray(existing.runtimeArgs)) {
|
|
1505
|
+
const ra = existing.runtimeArgs.map(String);
|
|
1506
|
+
const pi = ra.indexOf("--port");
|
|
1507
|
+
if (pi >= 0 && pi + 1 < ra.length) {
|
|
1508
|
+
if (ra[pi + 1] !== String(port)) {
|
|
1509
|
+
ra[pi + 1] = String(port);
|
|
1510
|
+
changed = true;
|
|
1511
|
+
}
|
|
1512
|
+
} else {
|
|
1513
|
+
ra.push("--port", String(port));
|
|
1514
|
+
changed = true;
|
|
1515
|
+
}
|
|
1516
|
+
if (changed) existing.runtimeArgs = ra;
|
|
1517
|
+
}
|
|
1518
|
+
if (changed) writeLaunchFile(launchPath, config);
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1190
1521
|
config.configurations.push({
|
|
1191
1522
|
name: appName,
|
|
1192
1523
|
runtimeExecutable: "npx",
|
|
@@ -1197,6 +1528,18 @@ function writeLaunchConfigIfMissing(appDir, appName, port) {
|
|
|
1197
1528
|
} catch {
|
|
1198
1529
|
}
|
|
1199
1530
|
}
|
|
1531
|
+
function resolveAppLaunchPort(appDir) {
|
|
1532
|
+
try {
|
|
1533
|
+
const config = readLaunchFile(join6(appDir, ".claude", "launch.json"));
|
|
1534
|
+
if (!config) return null;
|
|
1535
|
+
const appName = detectAppName(appDir) ?? basename(appDir);
|
|
1536
|
+
const entry = config.configurations.find((c) => c?.name === appName) ?? (config.configurations.length === 1 ? config.configurations[0] : void 0);
|
|
1537
|
+
const port = Number(entry?.port);
|
|
1538
|
+
return Number.isInteger(port) && port > 0 ? port : null;
|
|
1539
|
+
} catch {
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1200
1543
|
function detectClaudeWorktree(appDir) {
|
|
1201
1544
|
const segments = resolve2(appDir).split(sep);
|
|
1202
1545
|
for (let i = segments.length - 3; i >= 1; i--) {
|
|
@@ -1232,7 +1575,7 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
|
|
|
1232
1575
|
let port = opts.port;
|
|
1233
1576
|
if (opts.probePort) {
|
|
1234
1577
|
const taken = new Set(
|
|
1235
|
-
config.configurations.map((c) => Number(c?.port)).filter((
|
|
1578
|
+
config.configurations.map((c) => Number(c?.port)).filter((p10) => Number.isInteger(p10) && p10 > 0)
|
|
1236
1579
|
);
|
|
1237
1580
|
for (let i = 0; i < WORKTREE_PORT_RANGE && taken.has(port); i++) {
|
|
1238
1581
|
port = WORKTREE_PORT_MIN + (port - WORKTREE_PORT_MIN + 1) % WORKTREE_PORT_RANGE;
|
|
@@ -1285,9 +1628,36 @@ function requireAppIdArg(explicit) {
|
|
|
1285
1628
|
}
|
|
1286
1629
|
return id;
|
|
1287
1630
|
}
|
|
1631
|
+
function matchAppSelector(apps, selector) {
|
|
1632
|
+
const byId = apps.find((a) => a.appId === selector);
|
|
1633
|
+
if (byId) return byId.appId;
|
|
1634
|
+
const byName = apps.find((a) => a.name === selector);
|
|
1635
|
+
if (byName) return byName.appId;
|
|
1636
|
+
return STRICT_APP_ID_RE.test(selector) ? selector : null;
|
|
1637
|
+
}
|
|
1638
|
+
async function resolveAppSelector(deployUrl, token, selector) {
|
|
1639
|
+
if (STRICT_APP_ID_RE.test(selector)) return selector;
|
|
1640
|
+
const { apps } = await apiFetch(deployUrl, token, "/api/apps");
|
|
1641
|
+
const id = matchAppSelector(apps, selector);
|
|
1642
|
+
if (!id) {
|
|
1643
|
+
throw new Error(`No app "${selector}" in your account. Run \`deepspace apps\` to list your apps.`);
|
|
1644
|
+
}
|
|
1645
|
+
return id;
|
|
1646
|
+
}
|
|
1647
|
+
async function resolveAppTarget(deployUrl, token, explicit) {
|
|
1648
|
+
if (explicit) return resolveAppSelector(deployUrl, token, explicit);
|
|
1649
|
+
const appDir = findAppDir();
|
|
1650
|
+
const id = appDir ? readAppId(appDir) : null;
|
|
1651
|
+
if (!id) {
|
|
1652
|
+
throw new Error(
|
|
1653
|
+
"No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID, or pass --app <id or name>."
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
return id;
|
|
1657
|
+
}
|
|
1288
1658
|
|
|
1289
1659
|
// src/cli/lib/install-status.ts
|
|
1290
|
-
import { existsSync as existsSync7, readFileSync as
|
|
1660
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
1291
1661
|
import { join as join7 } from "path";
|
|
1292
1662
|
function ensureInstallReady(appDir) {
|
|
1293
1663
|
if (existsSync7(join7(appDir, "node_modules", "deepspace", "package.json"))) return;
|
|
@@ -1298,13 +1668,19 @@ function ensureInstallReady(appDir) {
|
|
|
1298
1668
|
const logPath = join7(sentinelDir, "install.log");
|
|
1299
1669
|
if (existsSync7(errPath)) {
|
|
1300
1670
|
console.error("Background install failed:");
|
|
1301
|
-
console.error(
|
|
1671
|
+
console.error(readFileSync8(errPath, "utf-8").trimEnd());
|
|
1302
1672
|
if (existsSync7(logPath)) console.error(`
|
|
1303
1673
|
Full log: ${logPath}`);
|
|
1304
1674
|
console.error("\nRun `npm install` (or `bun install`) manually, then retry.");
|
|
1305
1675
|
process.exit(1);
|
|
1306
1676
|
}
|
|
1307
1677
|
if (existsSync7(startedPath) && !existsSync7(donePath)) {
|
|
1678
|
+
if (!installWorkerAlive(sentinelDir)) {
|
|
1679
|
+
console.error("The background install is no longer running and never finished.");
|
|
1680
|
+
if (existsSync7(logPath)) console.error(`See what happened: ${logPath}`);
|
|
1681
|
+
console.error("Run `npm install` (or `bun install`) manually, then retry.");
|
|
1682
|
+
process.exit(1);
|
|
1683
|
+
}
|
|
1308
1684
|
console.error("Dependencies are still installing in the background.");
|
|
1309
1685
|
if (existsSync7(logPath)) console.error(`Tail progress: tail -f ${logPath}`);
|
|
1310
1686
|
console.error("Retry once it finishes.");
|
|
@@ -1313,6 +1689,18 @@ Full log: ${logPath}`);
|
|
|
1313
1689
|
console.error("Dependencies not installed. Run `npm install` (or `bun install`) first.");
|
|
1314
1690
|
process.exit(1);
|
|
1315
1691
|
}
|
|
1692
|
+
function installWorkerAlive(sentinelDir) {
|
|
1693
|
+
const pidPath = join7(sentinelDir, "install.pid");
|
|
1694
|
+
if (!existsSync7(pidPath)) return true;
|
|
1695
|
+
const pid = Number.parseInt(readFileSync8(pidPath, "utf-8").trim(), 10);
|
|
1696
|
+
if (!Number.isFinite(pid) || pid <= 0) return true;
|
|
1697
|
+
try {
|
|
1698
|
+
process.kill(pid, 0);
|
|
1699
|
+
return true;
|
|
1700
|
+
} catch (err) {
|
|
1701
|
+
return err.code === "EPERM";
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1316
1704
|
|
|
1317
1705
|
// src/cli/lib/preflight.ts
|
|
1318
1706
|
import { execFileSync } from "child_process";
|
|
@@ -1390,7 +1778,16 @@ function removeMacosJunk(dir) {
|
|
|
1390
1778
|
}
|
|
1391
1779
|
|
|
1392
1780
|
// src/cli/lib/port.ts
|
|
1781
|
+
import { createServer } from "net";
|
|
1393
1782
|
var DEFAULT_PORT = 5173;
|
|
1783
|
+
function checkPortAvailable(port, host = "0.0.0.0") {
|
|
1784
|
+
return new Promise((resolve10) => {
|
|
1785
|
+
const srv = createServer();
|
|
1786
|
+
srv.once("error", () => resolve10(false));
|
|
1787
|
+
srv.once("listening", () => srv.close(() => resolve10(true)));
|
|
1788
|
+
srv.listen(port, host);
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1394
1791
|
function resolvePort(arg) {
|
|
1395
1792
|
const raw = arg ?? process.env.DEEPSPACE_PORT;
|
|
1396
1793
|
if (!raw) return DEFAULT_PORT;
|
|
@@ -1417,6 +1814,7 @@ var dev_default = defineCommand3({
|
|
|
1417
1814
|
},
|
|
1418
1815
|
env: {
|
|
1419
1816
|
type: "string",
|
|
1817
|
+
alias: "e",
|
|
1420
1818
|
description: "wrangler.toml [env.<name>] block to run (e.g. --env staging). Applies the env's overrides at build time.",
|
|
1421
1819
|
required: false
|
|
1422
1820
|
},
|
|
@@ -1474,7 +1872,9 @@ var dev_default = defineCommand3({
|
|
|
1474
1872
|
);
|
|
1475
1873
|
}
|
|
1476
1874
|
}
|
|
1477
|
-
writeLaunchConfigIfMissing(appDir, detectAppName(appDir) ??
|
|
1875
|
+
writeLaunchConfigIfMissing(appDir, detectAppName(appDir) ?? basename2(appDir), port, {
|
|
1876
|
+
updatePort: explicitPort
|
|
1877
|
+
});
|
|
1478
1878
|
ensureInstallReady(appDir);
|
|
1479
1879
|
let token;
|
|
1480
1880
|
try {
|
|
@@ -1522,6 +1922,12 @@ var dev_default = defineCommand3({
|
|
|
1522
1922
|
process.exit(1);
|
|
1523
1923
|
}
|
|
1524
1924
|
preflightWindowsWorkerd(appDir);
|
|
1925
|
+
if (!await checkPortAvailable(port)) {
|
|
1926
|
+
console.error(`Port ${port} is already in use.`);
|
|
1927
|
+
const killCmd = port === DEFAULT_PORT ? "deepspace kill" : `deepspace kill --port ${port}`;
|
|
1928
|
+
console.error(`Free it with \`${killCmd}\`, or start on another port: \`deepspace dev --port <other>\`.`);
|
|
1929
|
+
process.exit(1);
|
|
1930
|
+
}
|
|
1525
1931
|
console.log("Starting dev server...\n");
|
|
1526
1932
|
const vite = spawn("npx", ["vite", "--port", String(port), "--strictPort", "--host"], {
|
|
1527
1933
|
cwd: appDir,
|
|
@@ -1544,9 +1950,16 @@ var dev_default = defineCommand3({
|
|
|
1544
1950
|
// src/cli/commands/kill.ts
|
|
1545
1951
|
import { defineCommand as defineCommand4 } from "citty";
|
|
1546
1952
|
import { spawnSync } from "child_process";
|
|
1953
|
+
import { readFileSync as readFileSync9, readdirSync as readdirSync3, readlinkSync } from "fs";
|
|
1547
1954
|
import { setTimeout as wait } from "timers/promises";
|
|
1548
1955
|
var SIGTERM_GRACE_MS = 1500;
|
|
1549
1956
|
var IS_WIN = process.platform === "win32";
|
|
1957
|
+
function pickKillPort(opts) {
|
|
1958
|
+
if (opts.explicit != null) return opts.explicit;
|
|
1959
|
+
if (opts.worktree != null) return opts.worktree;
|
|
1960
|
+
if (opts.env != null) return opts.env;
|
|
1961
|
+
return opts.appLaunch ?? DEFAULT_PORT;
|
|
1962
|
+
}
|
|
1550
1963
|
var kill_default = defineCommand4({
|
|
1551
1964
|
meta: {
|
|
1552
1965
|
name: "kill",
|
|
@@ -1565,21 +1978,38 @@ var kill_default = defineCommand4({
|
|
|
1565
1978
|
}
|
|
1566
1979
|
},
|
|
1567
1980
|
async run({ args }) {
|
|
1568
|
-
const
|
|
1981
|
+
const cwd = process.cwd();
|
|
1982
|
+
const port = pickKillPort({
|
|
1983
|
+
explicit: args.port ? resolvePort(args.port) : null,
|
|
1984
|
+
worktree: resolveWorktreePort(cwd),
|
|
1985
|
+
// resolvePort() reads + validates $DEEPSPACE_PORT; only consult it when set.
|
|
1986
|
+
env: process.env.DEEPSPACE_PORT ? resolvePort() : null,
|
|
1987
|
+
appLaunch: resolveAppLaunchPort(findAppDir(cwd) ?? cwd)
|
|
1988
|
+
});
|
|
1569
1989
|
const targets = /* @__PURE__ */ new Set();
|
|
1570
|
-
|
|
1990
|
+
const listeners = enumerateListeners(port);
|
|
1991
|
+
for (const pid of listeners.pids) targets.add(pid);
|
|
1571
1992
|
for (const pid of [...targets]) {
|
|
1572
1993
|
for (const child of childPids(pid)) targets.add(child);
|
|
1573
1994
|
}
|
|
1995
|
+
let swept = false;
|
|
1574
1996
|
if (args.all) {
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1997
|
+
const sweep = sweepByName(["workerd", "wrangler", "vite"]);
|
|
1998
|
+
swept = sweep.swept;
|
|
1999
|
+
for (const pid of sweep.pids) targets.add(pid);
|
|
1578
2000
|
}
|
|
1579
2001
|
if (targets.size === 0) {
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
2002
|
+
const outcome = noTargetsMessage({
|
|
2003
|
+
enumerated: listeners.enumerated,
|
|
2004
|
+
swept,
|
|
2005
|
+
all: args.all,
|
|
2006
|
+
port
|
|
2007
|
+
});
|
|
2008
|
+
if (!outcome.ok) {
|
|
2009
|
+
console.error(outcome.message);
|
|
2010
|
+
process.exit(1);
|
|
2011
|
+
}
|
|
2012
|
+
console.log(outcome.message);
|
|
1583
2013
|
return;
|
|
1584
2014
|
}
|
|
1585
2015
|
for (const pid of targets) {
|
|
@@ -1605,16 +2035,101 @@ var kill_default = defineCommand4({
|
|
|
1605
2035
|
);
|
|
1606
2036
|
process.exit(1);
|
|
1607
2037
|
}
|
|
1608
|
-
|
|
2038
|
+
if (args.all) {
|
|
2039
|
+
console.log(`Killed ${targets.size} process(es) (workerd/wrangler/vite sweep). :${port} is free.`);
|
|
2040
|
+
} else {
|
|
2041
|
+
console.log(`Port ${port} is free.`);
|
|
2042
|
+
}
|
|
1609
2043
|
}
|
|
1610
2044
|
});
|
|
1611
|
-
function
|
|
2045
|
+
function noTargetsMessage(opts) {
|
|
2046
|
+
const { enumerated, swept, all, port } = opts;
|
|
2047
|
+
const couldObserve = enumerated || all && swept;
|
|
2048
|
+
if (!couldObserve) {
|
|
2049
|
+
return {
|
|
2050
|
+
ok: false,
|
|
2051
|
+
message: `Couldn't determine what's running on :${port}: no lsof/pgrep and /proc is unavailable, so nothing was inspected or killed. Install lsof (or procps for pgrep), or find and kill the process manually.`
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
return {
|
|
2055
|
+
ok: true,
|
|
2056
|
+
message: all ? `Nothing listening on :${port} and no stray workerd/wrangler/vite processes found.` : `Nothing listening on :${port}. (Pass --all to sweep stray workerd/wrangler.)`
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
function enumerateListeners(port) {
|
|
1612
2060
|
if (IS_WIN) {
|
|
1613
|
-
|
|
2061
|
+
const r = runPowershellChecked(
|
|
1614
2062
|
`Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess`
|
|
1615
2063
|
);
|
|
2064
|
+
return { pids: r.pids, enumerated: r.ran };
|
|
2065
|
+
}
|
|
2066
|
+
const lsof = runLsof(port);
|
|
2067
|
+
if (lsof.ran) return { pids: lsof.pids, enumerated: true };
|
|
2068
|
+
const proc = listenerPidsViaProc(port);
|
|
2069
|
+
return { pids: proc.pids, enumerated: proc.available };
|
|
2070
|
+
}
|
|
2071
|
+
function listenerPids(port) {
|
|
2072
|
+
return enumerateListeners(port).pids;
|
|
2073
|
+
}
|
|
2074
|
+
function runLsof(port) {
|
|
2075
|
+
try {
|
|
2076
|
+
const r = spawnSync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
|
|
2077
|
+
encoding: "utf-8"
|
|
2078
|
+
});
|
|
2079
|
+
if (r.error) return { pids: [], ran: false };
|
|
2080
|
+
return { pids: r.status === 0 ? parsePidLines(r.stdout) : [], ran: true };
|
|
2081
|
+
} catch {
|
|
2082
|
+
return { pids: [], ran: false };
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
function listenerPidsViaProc(port) {
|
|
2086
|
+
let available = false;
|
|
2087
|
+
try {
|
|
2088
|
+
const hexPort = port.toString(16).toUpperCase().padStart(4, "0");
|
|
2089
|
+
const inodes = /* @__PURE__ */ new Set();
|
|
2090
|
+
for (const f of ["/proc/net/tcp", "/proc/net/tcp6"]) {
|
|
2091
|
+
let data;
|
|
2092
|
+
try {
|
|
2093
|
+
data = readFileSync9(f, "utf-8");
|
|
2094
|
+
} catch {
|
|
2095
|
+
continue;
|
|
2096
|
+
}
|
|
2097
|
+
available = true;
|
|
2098
|
+
for (const line of data.split("\n").slice(1)) {
|
|
2099
|
+
const cols = line.trim().split(/\s+/);
|
|
2100
|
+
if (cols[3] !== "0A") continue;
|
|
2101
|
+
if (cols[1]?.split(":")[1] === hexPort && cols[9]) inodes.add(cols[9]);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
if (!available) return { pids: [], available: false };
|
|
2105
|
+
if (!inodes.size) return { pids: [], available: true };
|
|
2106
|
+
const pids = /* @__PURE__ */ new Set();
|
|
2107
|
+
for (const pidDir of readdirSync3("/proc")) {
|
|
2108
|
+
if (!/^\d+$/.test(pidDir)) continue;
|
|
2109
|
+
let fds;
|
|
2110
|
+
try {
|
|
2111
|
+
fds = readdirSync3(`/proc/${pidDir}/fd`);
|
|
2112
|
+
} catch {
|
|
2113
|
+
continue;
|
|
2114
|
+
}
|
|
2115
|
+
for (const fd of fds) {
|
|
2116
|
+
let link;
|
|
2117
|
+
try {
|
|
2118
|
+
link = readlinkSync(`/proc/${pidDir}/fd/${fd}`);
|
|
2119
|
+
} catch {
|
|
2120
|
+
continue;
|
|
2121
|
+
}
|
|
2122
|
+
const m = link.match(/^socket:\[(\d+)\]$/);
|
|
2123
|
+
if (m && inodes.has(m[1])) {
|
|
2124
|
+
pids.add(Number(pidDir));
|
|
2125
|
+
break;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
return { pids: [...pids], available: true };
|
|
2130
|
+
} catch {
|
|
2131
|
+
return { pids: [], available };
|
|
1616
2132
|
}
|
|
1617
|
-
return run(["lsof", "-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"]);
|
|
1618
2133
|
}
|
|
1619
2134
|
function childPids(parentPid) {
|
|
1620
2135
|
if (IS_WIN) {
|
|
@@ -1624,22 +2139,31 @@ function childPids(parentPid) {
|
|
|
1624
2139
|
}
|
|
1625
2140
|
return run(["pgrep", "-P", String(parentPid)]);
|
|
1626
2141
|
}
|
|
1627
|
-
function
|
|
2142
|
+
function sweepByName(names) {
|
|
1628
2143
|
const all = /* @__PURE__ */ new Set();
|
|
1629
2144
|
if (IS_WIN) {
|
|
1630
2145
|
const orClauses = names.map((n) => `($_.Name -like '*${n}*') -or ($_.CommandLine -like '*${n}*')`).join(" -or ");
|
|
1631
2146
|
const script = `Get-CimInstance Win32_Process | Where-Object { ${orClauses} } | Select-Object -ExpandProperty ProcessId`;
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
}
|
|
1635
|
-
return [...all];
|
|
2147
|
+
const r = runPowershellChecked(script);
|
|
2148
|
+
for (const pid of r.pids) if (pid !== process.pid) all.add(pid);
|
|
2149
|
+
return { pids: [...all], swept: r.ran };
|
|
1636
2150
|
}
|
|
2151
|
+
let swept = false;
|
|
1637
2152
|
for (const name of names) {
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
2153
|
+
const r = runPgrep(["-f", name]);
|
|
2154
|
+
if (r.ran) swept = true;
|
|
2155
|
+
for (const pid of r.pids) if (pid !== process.pid) all.add(pid);
|
|
2156
|
+
}
|
|
2157
|
+
return { pids: [...all], swept };
|
|
2158
|
+
}
|
|
2159
|
+
function runPgrep(pgrepArgs) {
|
|
2160
|
+
try {
|
|
2161
|
+
const r = spawnSync("pgrep", pgrepArgs, { encoding: "utf-8" });
|
|
2162
|
+
if (r.error) return { pids: [], ran: false };
|
|
2163
|
+
return { pids: r.status === 0 ? parsePidLines(r.stdout) : [], ran: r.status === 0 || r.status === 1 };
|
|
2164
|
+
} catch {
|
|
2165
|
+
return { pids: [], ran: false };
|
|
1641
2166
|
}
|
|
1642
|
-
return [...all];
|
|
1643
2167
|
}
|
|
1644
2168
|
function run(cmd) {
|
|
1645
2169
|
try {
|
|
@@ -1651,15 +2175,18 @@ function run(cmd) {
|
|
|
1651
2175
|
}
|
|
1652
2176
|
}
|
|
1653
2177
|
function runPowershell(script) {
|
|
2178
|
+
return runPowershellChecked(script).pids;
|
|
2179
|
+
}
|
|
2180
|
+
function runPowershellChecked(script) {
|
|
1654
2181
|
const exe = pwshOrPowershell();
|
|
1655
2182
|
try {
|
|
1656
2183
|
const r = spawnSync(exe, ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
1657
2184
|
encoding: "utf-8"
|
|
1658
2185
|
});
|
|
1659
|
-
if (r.
|
|
1660
|
-
return parsePidLines(r.stdout);
|
|
2186
|
+
if (r.error) return { pids: [], ran: false };
|
|
2187
|
+
return { pids: r.status === 0 ? parsePidLines(r.stdout) : [], ran: r.status === 0 };
|
|
1661
2188
|
} catch {
|
|
1662
|
-
return [];
|
|
2189
|
+
return { pids: [], ran: false };
|
|
1663
2190
|
}
|
|
1664
2191
|
}
|
|
1665
2192
|
function pwshOrPowershell() {
|
|
@@ -1738,6 +2265,7 @@ var test_default = defineCommand5({
|
|
|
1738
2265
|
},
|
|
1739
2266
|
env: {
|
|
1740
2267
|
type: "string",
|
|
2268
|
+
alias: "e",
|
|
1741
2269
|
description: "wrangler.toml [env.<name>] block to test (uses secrets config <name> by default).",
|
|
1742
2270
|
required: false
|
|
1743
2271
|
}
|
|
@@ -1773,8 +2301,8 @@ var test_default = defineCommand5({
|
|
|
1773
2301
|
token = await ensureToken();
|
|
1774
2302
|
const payload = decodeJwtPayload(token);
|
|
1775
2303
|
ownerId = payload.sub;
|
|
1776
|
-
} catch {
|
|
1777
|
-
console.error(
|
|
2304
|
+
} catch (err) {
|
|
2305
|
+
console.error(err instanceof Error ? err.message : "Not logged in. Run `deepspace login` first.");
|
|
1778
2306
|
process.exit(1);
|
|
1779
2307
|
}
|
|
1780
2308
|
let generatedSecretsCache;
|
|
@@ -1935,7 +2463,7 @@ var screenshot_default = defineCommand6({
|
|
|
1935
2463
|
// src/cli/commands/test-accounts.ts
|
|
1936
2464
|
import { defineCommand as defineCommand7 } from "citty";
|
|
1937
2465
|
import * as p3 from "@clack/prompts";
|
|
1938
|
-
import { readFileSync as
|
|
2466
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
1939
2467
|
import { join as join10 } from "path";
|
|
1940
2468
|
import { homedir as homedir4 } from "os";
|
|
1941
2469
|
var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
|
|
@@ -1945,7 +2473,7 @@ var ACCOUNTS_PATH = join10(DIR3, "test-accounts.json");
|
|
|
1945
2473
|
function loadAccounts() {
|
|
1946
2474
|
if (!existsSync9(ACCOUNTS_PATH)) return [];
|
|
1947
2475
|
try {
|
|
1948
|
-
return JSON.parse(
|
|
2476
|
+
return JSON.parse(readFileSync10(ACCOUNTS_PATH, "utf-8"));
|
|
1949
2477
|
} catch {
|
|
1950
2478
|
return [];
|
|
1951
2479
|
}
|
|
@@ -1955,7 +2483,7 @@ function saveAccounts(accounts) {
|
|
|
1955
2483
|
writeFileSync8(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), { mode: 384 });
|
|
1956
2484
|
}
|
|
1957
2485
|
function sessionCookie() {
|
|
1958
|
-
const token =
|
|
2486
|
+
const token = readFileSync10(SESSION_PATH2, "utf-8").trim();
|
|
1959
2487
|
return `${SESSION_COOKIE2}=${encodeURIComponent(token)}`;
|
|
1960
2488
|
}
|
|
1961
2489
|
async function fetchRemoteAccounts() {
|
|
@@ -2225,214 +2753,10 @@ var test_accounts_default = defineCommand7({
|
|
|
2225
2753
|
import { defineCommand as defineCommand8 } from "citty";
|
|
2226
2754
|
import { execSync as execSync2 } from "child_process";
|
|
2227
2755
|
import { randomUUID } from "crypto";
|
|
2228
|
-
import { readFileSync as
|
|
2756
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync4, existsSync as existsSync10 } from "fs";
|
|
2229
2757
|
import { join as join11, resolve as resolve7, dirname as dirname2 } from "path";
|
|
2230
2758
|
import { pathToFileURL } from "url";
|
|
2231
2759
|
import * as p4 from "@clack/prompts";
|
|
2232
|
-
|
|
2233
|
-
// src/server/rooms/binding-manifest.ts
|
|
2234
|
-
var AUTO_PROVISION_SENTINEL = "auto";
|
|
2235
|
-
var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
|
|
2236
|
-
var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
|
|
2237
|
-
"vectorize",
|
|
2238
|
-
"ai",
|
|
2239
|
-
"r2_bucket",
|
|
2240
|
-
"kv_namespace",
|
|
2241
|
-
"d1",
|
|
2242
|
-
"queue",
|
|
2243
|
-
"browser_rendering",
|
|
2244
|
-
"analytics_engine",
|
|
2245
|
-
"hyperdrive"
|
|
2246
|
-
]);
|
|
2247
|
-
var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
2248
|
-
"ASSETS",
|
|
2249
|
-
"PLATFORM_WORKER",
|
|
2250
|
-
"API_WORKER",
|
|
2251
|
-
"APP_NAME",
|
|
2252
|
-
"OWNER_USER_ID",
|
|
2253
|
-
"AUTH_JWT_PUBLIC_KEY",
|
|
2254
|
-
"AUTH_JWT_ISSUER",
|
|
2255
|
-
"AUTH_WORKER_URL",
|
|
2256
|
-
"APP_IDENTITY_TOKEN",
|
|
2257
|
-
"APP_OWNER_JWT",
|
|
2258
|
-
"USAGE_EVENTS"
|
|
2259
|
-
]);
|
|
2260
|
-
function validateBindingManifest(manifest) {
|
|
2261
|
-
if (!Array.isArray(manifest)) {
|
|
2262
|
-
return { valid: false, errors: [{ reason: "Manifest must be an array" }] };
|
|
2263
|
-
}
|
|
2264
|
-
const errors = [];
|
|
2265
|
-
const seenNames = /* @__PURE__ */ new Set();
|
|
2266
|
-
for (const entry of manifest) {
|
|
2267
|
-
if (!entry || typeof entry !== "object") {
|
|
2268
|
-
errors.push({ reason: `Entry must be an object (got ${entry === null ? "null" : typeof entry})` });
|
|
2269
|
-
continue;
|
|
2270
|
-
}
|
|
2271
|
-
const e = entry;
|
|
2272
|
-
const type = e.type;
|
|
2273
|
-
const name = e.name;
|
|
2274
|
-
if (typeof type !== "string" || !ALLOWED_BINDING_TYPES.has(type)) {
|
|
2275
|
-
errors.push({
|
|
2276
|
-
reason: `Disallowed binding type: ${typeof type === "string" ? type : `(${typeof type})`}. Allowed: ${[...ALLOWED_BINDING_TYPES].sort().join(", ")}`
|
|
2277
|
-
});
|
|
2278
|
-
continue;
|
|
2279
|
-
}
|
|
2280
|
-
if (typeof name !== "string" || !name) {
|
|
2281
|
-
errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
|
|
2282
|
-
continue;
|
|
2283
|
-
}
|
|
2284
|
-
if (RESERVED_BINDING_NAMES.has(name)) {
|
|
2285
|
-
errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
|
|
2286
|
-
continue;
|
|
2287
|
-
}
|
|
2288
|
-
if (seenNames.has(name)) {
|
|
2289
|
-
errors.push({ reason: `Duplicate binding name "${name}"` });
|
|
2290
|
-
continue;
|
|
2291
|
-
}
|
|
2292
|
-
seenNames.add(name);
|
|
2293
|
-
const binding = entry;
|
|
2294
|
-
const fieldErr = requiredFieldError(binding);
|
|
2295
|
-
if (fieldErr) errors.push({ binding, reason: fieldErr });
|
|
2296
|
-
}
|
|
2297
|
-
if (errors.length) return { valid: false, errors };
|
|
2298
|
-
return { valid: true, bindings: manifest };
|
|
2299
|
-
}
|
|
2300
|
-
function requiredFieldError(b) {
|
|
2301
|
-
switch (b.type) {
|
|
2302
|
-
case "vectorize": {
|
|
2303
|
-
if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
|
|
2304
|
-
if (b.index_name === AUTO_PROVISION_SENTINEL) {
|
|
2305
|
-
if (typeof b.dimensions !== "number" || b.dimensions <= 0) {
|
|
2306
|
-
return `vectorize binding "${b.name}" with index_name="auto" requires "dimensions" (positive number)`;
|
|
2307
|
-
}
|
|
2308
|
-
if (!b.metric || !VECTORIZE_METRICS.has(b.metric)) {
|
|
2309
|
-
return `vectorize binding "${b.name}" with index_name="auto" requires "metric" (one of: ${[...VECTORIZE_METRICS].join(", ")})`;
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
return null;
|
|
2313
|
-
}
|
|
2314
|
-
case "r2_bucket":
|
|
2315
|
-
return b.bucket_name ? null : `r2_bucket binding "${b.name}" missing bucket_name (or "auto")`;
|
|
2316
|
-
case "kv_namespace": {
|
|
2317
|
-
if (!b.namespace_id) return `kv_namespace binding "${b.name}" missing namespace_id (or "auto")`;
|
|
2318
|
-
if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {
|
|
2319
|
-
return `kv_namespace binding "${b.name}" with namespace_id="auto" requires "title"`;
|
|
2320
|
-
}
|
|
2321
|
-
return null;
|
|
2322
|
-
}
|
|
2323
|
-
case "d1": {
|
|
2324
|
-
if (!b.id) return `d1 binding "${b.name}" missing id (or "auto")`;
|
|
2325
|
-
if (b.id === AUTO_PROVISION_SENTINEL && !b.database_name) {
|
|
2326
|
-
return `d1 binding "${b.name}" with id="auto" requires "database_name"`;
|
|
2327
|
-
}
|
|
2328
|
-
return null;
|
|
2329
|
-
}
|
|
2330
|
-
case "queue":
|
|
2331
|
-
return b.queue_name ? null : `queue binding "${b.name}" missing queue_name (or "auto")`;
|
|
2332
|
-
case "hyperdrive":
|
|
2333
|
-
if (!b.id) return `hyperdrive binding "${b.name}" missing id`;
|
|
2334
|
-
if (b.id === AUTO_PROVISION_SENTINEL) {
|
|
2335
|
-
return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
|
|
2336
|
-
}
|
|
2337
|
-
return null;
|
|
2338
|
-
case "ai":
|
|
2339
|
-
case "browser_rendering":
|
|
2340
|
-
case "analytics_engine":
|
|
2341
|
-
return null;
|
|
2342
|
-
// no required fields beyond `name`
|
|
2343
|
-
default: {
|
|
2344
|
-
const _exhaustive = b;
|
|
2345
|
-
void _exhaustive;
|
|
2346
|
-
return null;
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
}
|
|
2350
|
-
function bindingManifestFromOutputConfig(outputConfig) {
|
|
2351
|
-
const out = [];
|
|
2352
|
-
const aiBinding = pluckString(outputConfig.ai, "binding");
|
|
2353
|
-
if (aiBinding) out.push({ type: "ai", name: aiBinding });
|
|
2354
|
-
for (const v of asObjectArray(outputConfig.vectorize)) {
|
|
2355
|
-
const name = pluckString(v, "binding");
|
|
2356
|
-
const index_name = pluckString(v, "index_name");
|
|
2357
|
-
if (!name || !index_name) continue;
|
|
2358
|
-
const dimensions = pluckNumber(v, "dimensions");
|
|
2359
|
-
const metric = pluckString(v, "metric");
|
|
2360
|
-
out.push({
|
|
2361
|
-
type: "vectorize",
|
|
2362
|
-
name,
|
|
2363
|
-
index_name,
|
|
2364
|
-
...dimensions != null && { dimensions },
|
|
2365
|
-
...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
|
|
2366
|
-
});
|
|
2367
|
-
}
|
|
2368
|
-
for (const r of asObjectArray(outputConfig.r2_buckets)) {
|
|
2369
|
-
const name = pluckString(r, "binding");
|
|
2370
|
-
const bucket_name = pluckString(r, "bucket_name");
|
|
2371
|
-
if (name && bucket_name) out.push({ type: "r2_bucket", name, bucket_name });
|
|
2372
|
-
}
|
|
2373
|
-
for (const k of asObjectArray(outputConfig.kv_namespaces)) {
|
|
2374
|
-
const name = pluckString(k, "binding");
|
|
2375
|
-
const namespace_id = pluckString(k, "id");
|
|
2376
|
-
if (!name || !namespace_id) continue;
|
|
2377
|
-
const title = pluckString(k, "title");
|
|
2378
|
-
out.push({
|
|
2379
|
-
type: "kv_namespace",
|
|
2380
|
-
name,
|
|
2381
|
-
namespace_id,
|
|
2382
|
-
...title && { title }
|
|
2383
|
-
});
|
|
2384
|
-
}
|
|
2385
|
-
for (const d of asObjectArray(outputConfig.d1_databases)) {
|
|
2386
|
-
const name = pluckString(d, "binding");
|
|
2387
|
-
const id = pluckString(d, "database_id");
|
|
2388
|
-
if (!name || !id) continue;
|
|
2389
|
-
const database_name = pluckString(d, "database_name");
|
|
2390
|
-
out.push({
|
|
2391
|
-
type: "d1",
|
|
2392
|
-
name,
|
|
2393
|
-
id,
|
|
2394
|
-
...database_name && { database_name }
|
|
2395
|
-
});
|
|
2396
|
-
}
|
|
2397
|
-
const queues = isObject(outputConfig.queues) ? outputConfig.queues : null;
|
|
2398
|
-
for (const q of asObjectArray(queues?.producers)) {
|
|
2399
|
-
const name = pluckString(q, "binding");
|
|
2400
|
-
const queue_name = pluckString(q, "queue");
|
|
2401
|
-
if (name && queue_name) out.push({ type: "queue", name, queue_name });
|
|
2402
|
-
}
|
|
2403
|
-
const browserBinding = pluckString(outputConfig.browser, "binding");
|
|
2404
|
-
if (browserBinding) out.push({ type: "browser_rendering", name: browserBinding });
|
|
2405
|
-
for (const a of asObjectArray(outputConfig.analytics_engine_datasets)) {
|
|
2406
|
-
const name = pluckString(a, "binding");
|
|
2407
|
-
const dataset = pluckString(a, "dataset");
|
|
2408
|
-
if (name) out.push({ type: "analytics_engine", name, dataset: dataset ?? void 0 });
|
|
2409
|
-
}
|
|
2410
|
-
for (const h of asObjectArray(outputConfig.hyperdrive)) {
|
|
2411
|
-
const name = pluckString(h, "binding");
|
|
2412
|
-
const id = pluckString(h, "id");
|
|
2413
|
-
if (name && id) out.push({ type: "hyperdrive", name, id });
|
|
2414
|
-
}
|
|
2415
|
-
return out;
|
|
2416
|
-
}
|
|
2417
|
-
function isObject(v) {
|
|
2418
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
2419
|
-
}
|
|
2420
|
-
function pluckString(v, key) {
|
|
2421
|
-
if (!isObject(v)) return null;
|
|
2422
|
-
const val = v[key];
|
|
2423
|
-
return typeof val === "string" && val.length > 0 ? val : null;
|
|
2424
|
-
}
|
|
2425
|
-
function pluckNumber(v, key) {
|
|
2426
|
-
if (!isObject(v)) return null;
|
|
2427
|
-
const val = v[key];
|
|
2428
|
-
return typeof val === "number" && Number.isFinite(val) ? val : null;
|
|
2429
|
-
}
|
|
2430
|
-
function asObjectArray(v) {
|
|
2431
|
-
if (!Array.isArray(v)) return [];
|
|
2432
|
-
return v.filter(isObject);
|
|
2433
|
-
}
|
|
2434
|
-
|
|
2435
|
-
// src/cli/commands/deploy.ts
|
|
2436
2760
|
var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
2437
2761
|
var API_URL2 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
2438
2762
|
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.";
|
|
@@ -2486,6 +2810,13 @@ var GROUP_BYTES = (() => {
|
|
|
2486
2810
|
const v = Number.isInteger(raw) && raw > 0 ? raw : 3 * 1024 * 1024;
|
|
2487
2811
|
return Math.min(v, MAX_GROUP_BYTES);
|
|
2488
2812
|
})();
|
|
2813
|
+
function classifyDevVarsSecrets(opts) {
|
|
2814
|
+
const store = new Set(opts.storeSecretNames);
|
|
2815
|
+
const strayKeys = opts.handEditedDevVarKeys.filter((k) => !store.has(k));
|
|
2816
|
+
if (strayKeys.length === 0) return { kind: "ok" };
|
|
2817
|
+
if (opts.storeSecretNames.length === 0 && !opts.allowMissing) return { kind: "block", strayKeys };
|
|
2818
|
+
return { kind: "warn", strayKeys };
|
|
2819
|
+
}
|
|
2489
2820
|
function packAssetGroups(assets, maxBytes) {
|
|
2490
2821
|
const groups = [];
|
|
2491
2822
|
let current = [];
|
|
@@ -2549,6 +2880,7 @@ var deploy_default = defineCommand8({
|
|
|
2549
2880
|
},
|
|
2550
2881
|
env: {
|
|
2551
2882
|
type: "string",
|
|
2883
|
+
alias: "e",
|
|
2552
2884
|
description: "wrangler.toml [env.<name>] block to deploy (e.g. --env staging). Omit to deploy the top-level config.",
|
|
2553
2885
|
required: false
|
|
2554
2886
|
},
|
|
@@ -2556,6 +2888,11 @@ var deploy_default = defineCommand8({
|
|
|
2556
2888
|
type: "boolean",
|
|
2557
2889
|
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.",
|
|
2558
2890
|
default: false
|
|
2891
|
+
},
|
|
2892
|
+
"allow-missing-secrets": {
|
|
2893
|
+
type: "boolean",
|
|
2894
|
+
description: "Deploy even though hand-edited .dev.vars secrets are absent from the store (they will NOT be deployed, and any a previous deploy set are dropped).",
|
|
2895
|
+
default: false
|
|
2559
2896
|
}
|
|
2560
2897
|
},
|
|
2561
2898
|
async run({ args }) {
|
|
@@ -2603,11 +2940,26 @@ var deploy_default = defineCommand8({
|
|
|
2603
2940
|
);
|
|
2604
2941
|
}
|
|
2605
2942
|
p4.log.info(`Id: ${appId}`);
|
|
2943
|
+
const readCustomDevVarsAt = (path) => {
|
|
2944
|
+
if (!existsSync10(path)) return {};
|
|
2945
|
+
const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync11(path, "utf-8")));
|
|
2946
|
+
if (!section) return {};
|
|
2947
|
+
try {
|
|
2948
|
+
return parseDevVars(section);
|
|
2949
|
+
} catch (err) {
|
|
2950
|
+
p4.cancel(`.dev.vars: ${errMessage(err)}`);
|
|
2951
|
+
process.exit(1);
|
|
2952
|
+
}
|
|
2953
|
+
};
|
|
2606
2954
|
let linkedSecrets = null;
|
|
2955
|
+
let localOnlyDevVars = {};
|
|
2607
2956
|
try {
|
|
2608
2957
|
const refreshed = await refreshSecretsCache(DEPLOY_URL3, token, appId, envName);
|
|
2609
2958
|
if (refreshed) {
|
|
2610
2959
|
linkedSecrets = refreshed.pulled;
|
|
2960
|
+
localOnlyDevVars = readCustomDevVarsAt(
|
|
2961
|
+
devVarsPathFor(appDir, envName, { sharedDevVarsCache: true })
|
|
2962
|
+
);
|
|
2611
2963
|
await writeDevVars(appDir, ownerId, token, envName, {
|
|
2612
2964
|
appId,
|
|
2613
2965
|
generatedSecretsCache: refreshed.rendered,
|
|
@@ -2616,7 +2968,14 @@ var deploy_default = defineCommand8({
|
|
|
2616
2968
|
p4.log.info(refreshed.summary);
|
|
2617
2969
|
}
|
|
2618
2970
|
} catch (err) {
|
|
2619
|
-
|
|
2971
|
+
const msg = errMessage(err);
|
|
2972
|
+
if (err?.status === 403) {
|
|
2973
|
+
p4.cancel(
|
|
2974
|
+
"You're not authorized to deploy this app \u2014 you must be its owner or a current collaborator. If you were a collaborator, your access may have been revoked."
|
|
2975
|
+
);
|
|
2976
|
+
} else {
|
|
2977
|
+
p4.cancel(`Failed to refresh app secrets before deploy: ${msg}`);
|
|
2978
|
+
}
|
|
2620
2979
|
process.exit(1);
|
|
2621
2980
|
}
|
|
2622
2981
|
const junk = removeMacosJunk(appDir);
|
|
@@ -2648,13 +3007,13 @@ var deploy_default = defineCommand8({
|
|
|
2648
3007
|
p4.cancel("Build output config not found at .wrangler/deploy/config.json");
|
|
2649
3008
|
process.exit(1);
|
|
2650
3009
|
}
|
|
2651
|
-
const deployConfig = JSON.parse(
|
|
3010
|
+
const deployConfig = JSON.parse(readFileSync11(deployConfigPath, "utf-8"));
|
|
2652
3011
|
const outputWranglerPath = resolve7(dirname2(deployConfigPath), deployConfig.configPath);
|
|
2653
3012
|
if (!existsSync10(outputWranglerPath)) {
|
|
2654
3013
|
p4.cancel(`Output wrangler.json not found at ${outputWranglerPath}`);
|
|
2655
3014
|
process.exit(1);
|
|
2656
3015
|
}
|
|
2657
|
-
const outputConfig = JSON.parse(
|
|
3016
|
+
const outputConfig = JSON.parse(readFileSync11(outputWranglerPath, "utf-8"));
|
|
2658
3017
|
if (outputConfig.name && outputConfig.name !== appName) {
|
|
2659
3018
|
const envHint = envName ? ` (--env ${envName})` : "";
|
|
2660
3019
|
p4.cancel(
|
|
@@ -2676,7 +3035,7 @@ var deploy_default = defineCommand8({
|
|
|
2676
3035
|
s.start("Collecting assets...");
|
|
2677
3036
|
const assets = collectAssets(clientDir);
|
|
2678
3037
|
s.stop(`Collected ${assets.length} assets`);
|
|
2679
|
-
const workerJs =
|
|
3038
|
+
const workerJs = readFileSync11(workerBundlePath, "utf-8");
|
|
2680
3039
|
const doBindings = outputConfig.durable_objects?.bindings;
|
|
2681
3040
|
const sqliteClasses = new Set(
|
|
2682
3041
|
outputConfig.migrations?.flatMap(
|
|
@@ -2708,18 +3067,30 @@ var deploy_default = defineCommand8({
|
|
|
2708
3067
|
const devVarsPath = devVarsPathFor(appDir, envName, {
|
|
2709
3068
|
sharedDevVarsCache: linkedSecrets !== null
|
|
2710
3069
|
});
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
3070
|
+
const userSecrets = linkedSecrets ? { ...linkedSecrets.values } : {};
|
|
3071
|
+
const handEditedDevVars = linkedSecrets ? localOnlyDevVars : readCustomDevVarsAt(devVarsPath);
|
|
3072
|
+
const secretsDecision = classifyDevVarsSecrets({
|
|
3073
|
+
storeSecretNames: Object.keys(userSecrets),
|
|
3074
|
+
handEditedDevVarKeys: Object.keys(handEditedDevVars),
|
|
3075
|
+
allowMissing: Boolean(args["allow-missing-secrets"])
|
|
3076
|
+
});
|
|
3077
|
+
if (secretsDecision.kind === "block") {
|
|
3078
|
+
const uploadCmd = envName ? `deepspace secrets upload .dev.vars -e ${envName}` : "deepspace secrets upload .dev.vars";
|
|
3079
|
+
const keys = secretsDecision.strayKeys.join(", ");
|
|
3080
|
+
p4.cancel(
|
|
3081
|
+
`Refusing to deploy: the app store has no secrets, but .dev.vars has ${keys}.
|
|
3082
|
+
Deploying now would ship the app with no secrets \u2014 replacing any a previous deploy set (they would be dropped from production).
|
|
3083
|
+
Upload them to the store first:
|
|
3084
|
+
${uploadCmd}
|
|
3085
|
+
then redeploy. (Pass --allow-missing-secrets to deploy without them.)`
|
|
3086
|
+
);
|
|
3087
|
+
process.exit(1);
|
|
3088
|
+
}
|
|
3089
|
+
if (secretsDecision.kind === "warn") {
|
|
3090
|
+
const stray = secretsDecision.strayKeys;
|
|
3091
|
+
p4.log.warn(
|
|
3092
|
+
`.dev.vars has ${stray.join(", ")} not in the app store \u2014 ${stray.length === 1 ? "it is" : "they are"} NOT deployed. \`.dev.vars\` is a local dev cache, not deploy config. Move ${stray.length === 1 ? "it" : "them"} into the store with \`deepspace secrets set KEY=value\` (or \`deepspace secrets upload .dev.vars\`), then redeploy.`
|
|
3093
|
+
);
|
|
2723
3094
|
}
|
|
2724
3095
|
const userSecretNames = Object.keys(userSecrets);
|
|
2725
3096
|
const declaredBindingNames = /* @__PURE__ */ new Set([
|
|
@@ -2729,22 +3100,19 @@ var deploy_default = defineCommand8({
|
|
|
2729
3100
|
for (const name of userSecretNames) {
|
|
2730
3101
|
if (RESERVED_BINDING_NAMES.has(name)) {
|
|
2731
3102
|
p4.cancel(
|
|
2732
|
-
|
|
3103
|
+
`App secret "${name}" is a reserved binding name \u2014 remove it with \`deepspace secrets delete ${name}\`.`
|
|
2733
3104
|
);
|
|
2734
3105
|
process.exit(1);
|
|
2735
3106
|
}
|
|
2736
3107
|
if (declaredBindingNames.has(name)) {
|
|
2737
3108
|
p4.cancel(
|
|
2738
|
-
|
|
3109
|
+
`App secret "${name}" collides with a binding declared in wrangler.toml \u2014 rename one or the other.`
|
|
2739
3110
|
);
|
|
2740
3111
|
process.exit(1);
|
|
2741
3112
|
}
|
|
2742
3113
|
}
|
|
2743
3114
|
if (userSecretNames.length) {
|
|
2744
|
-
p4.log.info(`App secrets
|
|
2745
|
-
if (!linkedSecrets) {
|
|
2746
|
-
p4.log.info("Not in the app store yet \u2014 migrating after this deploy.");
|
|
2747
|
-
}
|
|
3115
|
+
p4.log.info(`App secrets: ${userSecretNames.join(", ")}`);
|
|
2748
3116
|
}
|
|
2749
3117
|
s.start(`Deploying to ${appName}.app.space...`);
|
|
2750
3118
|
const extraRoutes = extractRunWorkerFirst(wranglerConfig);
|
|
@@ -2819,6 +3187,7 @@ var deploy_default = defineCommand8({
|
|
|
2819
3187
|
if (userSecretNames.length) {
|
|
2820
3188
|
form.append("userSecrets", JSON.stringify(userSecrets));
|
|
2821
3189
|
}
|
|
3190
|
+
form.append("secretsAuthoritative", "true");
|
|
2822
3191
|
if (extraRoutes.length) {
|
|
2823
3192
|
form.append("extraRunWorkerFirst", JSON.stringify(extraRoutes));
|
|
2824
3193
|
}
|
|
@@ -2848,6 +3217,12 @@ var deploy_default = defineCommand8({
|
|
|
2848
3217
|
let body = await res.json().catch(() => ({}));
|
|
2849
3218
|
if (res.status === 409 && body.code === "rename_required" && !confirmRename) {
|
|
2850
3219
|
s.stop("Rename confirmation needed");
|
|
3220
|
+
if (!process.stdin.isTTY) {
|
|
3221
|
+
await bailDeploy(
|
|
3222
|
+
`This deploy renames the app: ${body.fromHost} \u2192 ${body.toHost}. Confirmation needs an interactive terminal \u2014 re-run with --rename to approve the rename, or \`deepspace init --new-id\` if you meant a separate app.`,
|
|
3223
|
+
null
|
|
3224
|
+
);
|
|
3225
|
+
}
|
|
2851
3226
|
const yes = await p4.confirm({
|
|
2852
3227
|
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?`
|
|
2853
3228
|
});
|
|
@@ -2874,16 +3249,6 @@ var deploy_default = defineCommand8({
|
|
|
2874
3249
|
"First requests may briefly hit the assets transitional page; retry in a minute."
|
|
2875
3250
|
);
|
|
2876
3251
|
p4.log.success(`Live at: ${body.url}`);
|
|
2877
|
-
await migrateLegacyDevVarsSecrets({
|
|
2878
|
-
deployUrl: DEPLOY_URL3,
|
|
2879
|
-
token,
|
|
2880
|
-
appId,
|
|
2881
|
-
envName,
|
|
2882
|
-
devVarsPath,
|
|
2883
|
-
userSecrets,
|
|
2884
|
-
userSecretNames,
|
|
2885
|
-
hadStore: linkedSecrets !== null
|
|
2886
|
-
});
|
|
2887
3252
|
await syncSubscriptionPlans(appDir, appId, token);
|
|
2888
3253
|
await syncOneTimeProducts(appDir, appId, token);
|
|
2889
3254
|
p4.outro("Done");
|
|
@@ -2892,41 +3257,11 @@ var deploy_default = defineCommand8({
|
|
|
2892
3257
|
}
|
|
2893
3258
|
s.stop("Deployed!");
|
|
2894
3259
|
p4.log.success(`Live at: ${body.url}`);
|
|
2895
|
-
await migrateLegacyDevVarsSecrets({
|
|
2896
|
-
deployUrl: DEPLOY_URL3,
|
|
2897
|
-
token,
|
|
2898
|
-
appId,
|
|
2899
|
-
envName,
|
|
2900
|
-
devVarsPath,
|
|
2901
|
-
userSecrets,
|
|
2902
|
-
userSecretNames,
|
|
2903
|
-
hadStore: linkedSecrets !== null
|
|
2904
|
-
});
|
|
2905
3260
|
await syncSubscriptionPlans(appDir, appId, token);
|
|
2906
3261
|
await syncOneTimeProducts(appDir, appId, token);
|
|
2907
3262
|
p4.outro("Done");
|
|
2908
3263
|
}
|
|
2909
3264
|
});
|
|
2910
|
-
async function migrateLegacyDevVarsSecrets(opts) {
|
|
2911
|
-
const { deployUrl, token, appId, envName, devVarsPath, userSecrets, userSecretNames, hadStore } = opts;
|
|
2912
|
-
if (hadStore || userSecretNames.length === 0) return;
|
|
2913
|
-
const configName = defaultConfigNameForEnv(envName);
|
|
2914
|
-
try {
|
|
2915
|
-
await uploadSecrets(deployUrl, token, appId, configName, userSecrets, false);
|
|
2916
|
-
if (existsSync10(devVarsPath)) {
|
|
2917
|
-
const cleaned = removeDevVarsKeys(readFileSync9(devVarsPath, "utf-8"), new Set(userSecretNames));
|
|
2918
|
-
writeFileSync9(devVarsPath, cleaned.endsWith("\n") ? cleaned : `${cleaned}
|
|
2919
|
-
`);
|
|
2920
|
-
}
|
|
2921
|
-
p4.log.success(
|
|
2922
|
-
`Migrated ${userSecretNames.length} secret${userSecretNames.length === 1 ? "" : "s"} from .dev.vars into the app store (config ${configName}) \u2014 now managed by \`deepspace secrets\`.`
|
|
2923
|
-
);
|
|
2924
|
-
} catch (err) {
|
|
2925
|
-
p4.log.warn(
|
|
2926
|
-
`Couldn't migrate .dev.vars secrets into the app store (${errMessage(err)}); they deployed as bindings for now and the next deploy will retry.`
|
|
2927
|
-
);
|
|
2928
|
-
}
|
|
2929
|
-
}
|
|
2930
3265
|
async function syncSubscriptionPlans(appDir, appName, token) {
|
|
2931
3266
|
const subsPath = join11(appDir, "src", "subscriptions.ts");
|
|
2932
3267
|
if (!existsSync10(subsPath)) return;
|
|
@@ -3155,7 +3490,7 @@ async function waitForAssetsReady(url, timeoutMs) {
|
|
|
3155
3490
|
function collectAssets(dir) {
|
|
3156
3491
|
const assets = [];
|
|
3157
3492
|
function walk(d, prefix) {
|
|
3158
|
-
for (const entry of
|
|
3493
|
+
for (const entry of readdirSync4(d, { withFileTypes: true })) {
|
|
3159
3494
|
if (entry.name === ".assetsignore") continue;
|
|
3160
3495
|
const full = join11(d, entry.name);
|
|
3161
3496
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
@@ -3164,7 +3499,7 @@ function collectAssets(dir) {
|
|
|
3164
3499
|
} else {
|
|
3165
3500
|
assets.push({
|
|
3166
3501
|
path: "/" + rel,
|
|
3167
|
-
contentBase64:
|
|
3502
|
+
contentBase64: readFileSync11(full).toString("base64")
|
|
3168
3503
|
});
|
|
3169
3504
|
}
|
|
3170
3505
|
}
|
|
@@ -3185,7 +3520,7 @@ var undeploy_default = defineCommand9({
|
|
|
3185
3520
|
args: {
|
|
3186
3521
|
name: {
|
|
3187
3522
|
type: "positional",
|
|
3188
|
-
description: "App id to undeploy (reads DEEPSPACE_APP_ID from wrangler.toml if omitted)",
|
|
3523
|
+
description: "App id or subdomain name to undeploy (reads DEEPSPACE_APP_ID from wrangler.toml if omitted)",
|
|
3189
3524
|
required: false
|
|
3190
3525
|
},
|
|
3191
3526
|
env: {
|
|
@@ -3196,18 +3531,6 @@ var undeploy_default = defineCommand9({
|
|
|
3196
3531
|
},
|
|
3197
3532
|
async run({ args }) {
|
|
3198
3533
|
const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
3199
|
-
let appId = args.name;
|
|
3200
|
-
if (appId && !APP_ID_RE.test(appId)) {
|
|
3201
|
-
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.`);
|
|
3202
|
-
process.exit(1);
|
|
3203
|
-
}
|
|
3204
|
-
if (!appId && hasWranglerConfig(process.cwd())) {
|
|
3205
|
-
appId = readAppId(process.cwd(), envName) ?? void 0;
|
|
3206
|
-
}
|
|
3207
|
-
if (!appId) {
|
|
3208
|
-
p5.cancel("Provide an app id or run from a DeepSpace app directory with a DEEPSPACE_APP_ID.");
|
|
3209
|
-
process.exit(1);
|
|
3210
|
-
}
|
|
3211
3534
|
let token;
|
|
3212
3535
|
try {
|
|
3213
3536
|
token = await ensureToken();
|
|
@@ -3215,6 +3538,22 @@ var undeploy_default = defineCommand9({
|
|
|
3215
3538
|
p5.cancel(err instanceof Error ? err.message : String(err));
|
|
3216
3539
|
process.exit(1);
|
|
3217
3540
|
}
|
|
3541
|
+
let appId;
|
|
3542
|
+
const positional = typeof args.name === "string" && args.name.trim() ? args.name.trim() : void 0;
|
|
3543
|
+
if (positional) {
|
|
3544
|
+
try {
|
|
3545
|
+
appId = await resolveAppSelector(DEPLOY_URL4, token, positional);
|
|
3546
|
+
} catch (err) {
|
|
3547
|
+
p5.cancel(err instanceof Error ? err.message : String(err));
|
|
3548
|
+
process.exit(1);
|
|
3549
|
+
}
|
|
3550
|
+
} else if (hasWranglerConfig(process.cwd())) {
|
|
3551
|
+
appId = readAppId(process.cwd(), envName) ?? void 0;
|
|
3552
|
+
}
|
|
3553
|
+
if (!appId) {
|
|
3554
|
+
p5.cancel("Provide an app id or subdomain name, or run from a DeepSpace app directory with a DEEPSPACE_APP_ID.");
|
|
3555
|
+
process.exit(1);
|
|
3556
|
+
}
|
|
3218
3557
|
p5.intro(`Undeploying ${appId}`);
|
|
3219
3558
|
const s = createSpinner();
|
|
3220
3559
|
s.start("Removing...");
|
|
@@ -3251,24 +3590,28 @@ var whoami_default = defineCommand10({
|
|
|
3251
3590
|
}
|
|
3252
3591
|
},
|
|
3253
3592
|
async run({ args }) {
|
|
3593
|
+
const failLoggedOut = (msg) => {
|
|
3594
|
+
if (args.json) console.log(JSON.stringify({ loggedIn: false, error: msg }));
|
|
3595
|
+
else console.error(msg);
|
|
3596
|
+
process.exit(1);
|
|
3597
|
+
};
|
|
3254
3598
|
let jwt;
|
|
3255
3599
|
try {
|
|
3256
3600
|
jwt = await ensureToken();
|
|
3257
3601
|
} catch (err) {
|
|
3258
|
-
|
|
3259
|
-
process.exit(1);
|
|
3602
|
+
failLoggedOut(err instanceof Error ? err.message : "Not logged in. Run `deepspace login` first.");
|
|
3260
3603
|
}
|
|
3261
3604
|
let payload;
|
|
3262
3605
|
try {
|
|
3263
3606
|
payload = decodeJwtPayload(jwt);
|
|
3264
3607
|
} catch {
|
|
3265
|
-
|
|
3266
|
-
process.exit(1);
|
|
3608
|
+
failLoggedOut("Malformed session token. Run `deepspace login` again.");
|
|
3267
3609
|
}
|
|
3268
3610
|
const isTestAccount = !!payload.email?.endsWith("@deepspace.test");
|
|
3269
3611
|
const accountType = isTestAccount ? "test-account" : payload.email ? "developer" : "unknown";
|
|
3270
3612
|
if (args.json) {
|
|
3271
3613
|
console.log(JSON.stringify({
|
|
3614
|
+
loggedIn: true,
|
|
3272
3615
|
name: payload.name ?? null,
|
|
3273
3616
|
email: payload.email ?? null,
|
|
3274
3617
|
userId: payload.sub ?? null,
|
|
@@ -3292,11 +3635,48 @@ var whoami_default = defineCommand10({
|
|
|
3292
3635
|
}
|
|
3293
3636
|
});
|
|
3294
3637
|
|
|
3295
|
-
// src/cli/commands/
|
|
3638
|
+
// src/cli/commands/apps.ts
|
|
3296
3639
|
import { defineCommand as defineCommand11 } from "citty";
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3640
|
+
var DEPLOY_URL5 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
3641
|
+
var apps_default = defineCommand11({
|
|
3642
|
+
meta: {
|
|
3643
|
+
name: "apps",
|
|
3644
|
+
description: "List your apps (deployed and registered)"
|
|
3645
|
+
},
|
|
3646
|
+
args: {
|
|
3647
|
+
json: {
|
|
3648
|
+
type: "boolean",
|
|
3649
|
+
description: "Emit JSON instead of human output",
|
|
3650
|
+
default: false
|
|
3651
|
+
}
|
|
3652
|
+
},
|
|
3653
|
+
async run({ args }) {
|
|
3654
|
+
const token = await ensureToken();
|
|
3655
|
+
const { apps } = await apiFetch(DEPLOY_URL5, token, "/api/apps");
|
|
3656
|
+
if (args.json) {
|
|
3657
|
+
process.stdout.write(JSON.stringify(apps, null, 2) + "\n");
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3660
|
+
if (!apps.length) {
|
|
3661
|
+
console.log("No apps yet. Create one with `npx create-deepspace <name>` and `deepspace deploy`.");
|
|
3662
|
+
return;
|
|
3663
|
+
}
|
|
3664
|
+
const nameWidth = Math.max(4, ...apps.map((a) => (a.name ?? "\u2014").length));
|
|
3665
|
+
const idWidth = Math.max(6, ...apps.map((a) => a.appId.length));
|
|
3666
|
+
console.log(`${"NAME".padEnd(nameWidth)} ${"APP ID".padEnd(idWidth)} URL`);
|
|
3667
|
+
for (const a of apps) {
|
|
3668
|
+
const url = a.url ?? "(not deployed)";
|
|
3669
|
+
console.log(`${(a.name ?? "\u2014").padEnd(nameWidth)} ${a.appId.padEnd(idWidth)} ${url}`);
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
});
|
|
3673
|
+
|
|
3674
|
+
// src/cli/commands/add.ts
|
|
3675
|
+
import { defineCommand as defineCommand12 } from "citty";
|
|
3676
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
3677
|
+
import { resolve as resolve8, join as join12, dirname as dirname3 } from "path";
|
|
3678
|
+
import { fileURLToPath } from "url";
|
|
3679
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
3300
3680
|
function findScript(from) {
|
|
3301
3681
|
const inNodeModules = join12(from, "node_modules", "deepspace", "scripts", "add-feature.cjs");
|
|
3302
3682
|
if (existsSync11(inNodeModules)) return inNodeModules;
|
|
@@ -3304,7 +3684,31 @@ function findScript(from) {
|
|
|
3304
3684
|
if (existsSync11(inDotDeepspace)) return inDotDeepspace;
|
|
3305
3685
|
return null;
|
|
3306
3686
|
}
|
|
3307
|
-
|
|
3687
|
+
function runningScript() {
|
|
3688
|
+
const here = dirname3(fileURLToPath(import.meta.url));
|
|
3689
|
+
const script = join12(here, "..", "scripts", "add-feature.cjs");
|
|
3690
|
+
return existsSync11(script) ? script : null;
|
|
3691
|
+
}
|
|
3692
|
+
function isDeepSpaceApp(dir) {
|
|
3693
|
+
if (existsSync11(join12(dir, "wrangler.toml"))) return true;
|
|
3694
|
+
const pkgPath = join12(dir, "package.json");
|
|
3695
|
+
if (!existsSync11(pkgPath)) return false;
|
|
3696
|
+
try {
|
|
3697
|
+
const pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
3698
|
+
return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
|
|
3699
|
+
} catch {
|
|
3700
|
+
return false;
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
function runInstaller(script, passArgs, cwd) {
|
|
3704
|
+
const res = spawnSync5(process.execPath, [script, ...passArgs], { stdio: "inherit", cwd });
|
|
3705
|
+
if (res.error) {
|
|
3706
|
+
console.error(`Could not run the feature installer: ${res.error.message}`);
|
|
3707
|
+
process.exit(1);
|
|
3708
|
+
}
|
|
3709
|
+
process.exit(res.status ?? 1);
|
|
3710
|
+
}
|
|
3711
|
+
var add_default = defineCommand12({
|
|
3308
3712
|
meta: {
|
|
3309
3713
|
name: "add",
|
|
3310
3714
|
description: "Add a feature to your DeepSpace app"
|
|
@@ -3321,6 +3725,11 @@ var add_default = defineCommand11({
|
|
|
3321
3725
|
description: "Show details about a feature",
|
|
3322
3726
|
required: false
|
|
3323
3727
|
},
|
|
3728
|
+
install: {
|
|
3729
|
+
type: "boolean",
|
|
3730
|
+
description: "Run your package manager after adding dependencies",
|
|
3731
|
+
required: false
|
|
3732
|
+
},
|
|
3324
3733
|
feature: {
|
|
3325
3734
|
type: "positional",
|
|
3326
3735
|
description: "Feature to install",
|
|
@@ -3334,6 +3743,27 @@ var add_default = defineCommand11({
|
|
|
3334
3743
|
},
|
|
3335
3744
|
run({ args }) {
|
|
3336
3745
|
const appDir = resolve8(args.dir ?? ".");
|
|
3746
|
+
if (args.list || args.info || !args.feature) {
|
|
3747
|
+
const script2 = runningScript();
|
|
3748
|
+
if (!script2) {
|
|
3749
|
+
console.error("Could not find the deepspace feature catalog.");
|
|
3750
|
+
console.error("Reinstall deepspace (`npm i -g deepspace`) or run inside a DeepSpace app.");
|
|
3751
|
+
process.exit(1);
|
|
3752
|
+
}
|
|
3753
|
+
const passArgs2 = args.list ? ["--list"] : args.info ? ["--info", String(args.info)] : ["--help"];
|
|
3754
|
+
runInstaller(script2, passArgs2, process.cwd());
|
|
3755
|
+
}
|
|
3756
|
+
if (!existsSync11(appDir)) {
|
|
3757
|
+
console.error(`Target directory not found: ${appDir}`);
|
|
3758
|
+
console.error("Create a new app first: deepspace create <name>");
|
|
3759
|
+
process.exit(1);
|
|
3760
|
+
}
|
|
3761
|
+
if (!isDeepSpaceApp(appDir)) {
|
|
3762
|
+
console.error(`Not a DeepSpace app: ${appDir}`);
|
|
3763
|
+
console.error('(no wrangler.toml and no "deepspace" dependency found)');
|
|
3764
|
+
console.error("Create one with: deepspace create <name>");
|
|
3765
|
+
process.exit(1);
|
|
3766
|
+
}
|
|
3337
3767
|
ensureInstallReady(appDir);
|
|
3338
3768
|
const script = findScript(appDir);
|
|
3339
3769
|
if (!script) {
|
|
@@ -3344,136 +3774,14 @@ var add_default = defineCommand11({
|
|
|
3344
3774
|
console.error("Are you in a DeepSpace app directory? Try `npm install` first.");
|
|
3345
3775
|
process.exit(1);
|
|
3346
3776
|
}
|
|
3347
|
-
const passArgs = [];
|
|
3348
|
-
if (args.
|
|
3349
|
-
|
|
3350
|
-
} else if (args.info) {
|
|
3351
|
-
passArgs.push("--info", args.info);
|
|
3352
|
-
} else if (args.feature) {
|
|
3353
|
-
passArgs.push(args.feature, appDir);
|
|
3354
|
-
} else {
|
|
3355
|
-
passArgs.push("--help");
|
|
3356
|
-
}
|
|
3357
|
-
execSync3(`node ${JSON.stringify(script)} ${passArgs.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
3358
|
-
stdio: "inherit",
|
|
3359
|
-
cwd: appDir
|
|
3360
|
-
});
|
|
3361
|
-
}
|
|
3362
|
-
});
|
|
3363
|
-
|
|
3364
|
-
// src/cli/commands/skills.ts
|
|
3365
|
-
import { defineCommand as defineCommand12 } from "citty";
|
|
3366
|
-
import spawn2 from "cross-spawn";
|
|
3367
|
-
import { mkdirSync as mkdirSync5 } from "fs";
|
|
3368
|
-
import { join as join13 } from "path";
|
|
3369
|
-
import { homedir as homedir5 } from "os";
|
|
3370
|
-
var SKILL_REPO = "deepdotspace/deepspace-skill";
|
|
3371
|
-
function runSkillsAdd(extraArgs) {
|
|
3372
|
-
return new Promise((resolve10) => {
|
|
3373
|
-
const child = spawn2(
|
|
3374
|
-
"npx",
|
|
3375
|
-
["-y", "skills@latest", "add", SKILL_REPO, ...extraArgs],
|
|
3376
|
-
{ stdio: "inherit" }
|
|
3377
|
-
);
|
|
3378
|
-
child.on("close", (code) => resolve10(code ?? 1));
|
|
3379
|
-
child.on("error", (err) => {
|
|
3380
|
-
console.error(`Failed to invoke npx: ${err.message}`);
|
|
3381
|
-
console.error("Is Node.js installed and on PATH?");
|
|
3382
|
-
resolve10(1);
|
|
3383
|
-
});
|
|
3384
|
-
});
|
|
3385
|
-
}
|
|
3386
|
-
function precreateClaudeDir(baseDir) {
|
|
3387
|
-
mkdirSync5(join13(baseDir, ".claude"), { recursive: true });
|
|
3388
|
-
}
|
|
3389
|
-
var add = defineCommand12({
|
|
3390
|
-
meta: {
|
|
3391
|
-
name: "add",
|
|
3392
|
-
description: "Install the DeepSpace skill for your coding agent"
|
|
3393
|
-
},
|
|
3394
|
-
args: {
|
|
3395
|
-
global: {
|
|
3396
|
-
type: "boolean",
|
|
3397
|
-
alias: "g",
|
|
3398
|
-
description: "Install globally (~/.agents/skills/) instead of into the current directory",
|
|
3399
|
-
default: false
|
|
3400
|
-
},
|
|
3401
|
-
yes: {
|
|
3402
|
-
type: "boolean",
|
|
3403
|
-
alias: "y",
|
|
3404
|
-
description: "Skip prompts (assume yes for any confirmation)",
|
|
3405
|
-
default: false
|
|
3406
|
-
},
|
|
3407
|
-
agent: {
|
|
3408
|
-
type: "string",
|
|
3409
|
-
description: "Target a single agent (e.g. claude-code, codex, opencode). Default: install for all detected agents.",
|
|
3410
|
-
required: false
|
|
3411
|
-
}
|
|
3412
|
-
},
|
|
3413
|
-
async run({ args }) {
|
|
3414
|
-
const extra = [];
|
|
3415
|
-
if (args.global) extra.push("-g");
|
|
3416
|
-
if (args.yes) extra.push("-y");
|
|
3417
|
-
if (args.agent) extra.push("--agent", args.agent);
|
|
3418
|
-
if (!args.agent || args.agent === "claude-code") {
|
|
3419
|
-
const baseDir = args.global ? homedir5() : process.cwd();
|
|
3420
|
-
try {
|
|
3421
|
-
precreateClaudeDir(baseDir);
|
|
3422
|
-
} catch (err) {
|
|
3423
|
-
console.warn(`
|
|
3424
|
-
Note: could not pre-create .claude/ for Claude Code: ${err instanceof Error ? err.message : String(err)}`);
|
|
3425
|
-
}
|
|
3426
|
-
}
|
|
3427
|
-
const code = await runSkillsAdd(extra);
|
|
3428
|
-
if (code !== 0) process.exit(code);
|
|
3429
|
-
}
|
|
3430
|
-
});
|
|
3431
|
-
var skills_default = defineCommand12({
|
|
3432
|
-
meta: {
|
|
3433
|
-
name: "skills",
|
|
3434
|
-
description: "Manage the DeepSpace agent skill (Claude Code, Codex, OpenCode, Cursor, ...)"
|
|
3435
|
-
},
|
|
3436
|
-
subCommands: {
|
|
3437
|
-
add
|
|
3777
|
+
const passArgs = [args.feature, appDir];
|
|
3778
|
+
if (args.install) passArgs.push("--install");
|
|
3779
|
+
runInstaller(script, passArgs, appDir);
|
|
3438
3780
|
}
|
|
3439
3781
|
});
|
|
3440
3782
|
|
|
3441
3783
|
// src/cli/commands/domain.ts
|
|
3442
3784
|
import { defineCommand as defineCommand13 } from "citty";
|
|
3443
|
-
|
|
3444
|
-
// src/cli/lib/api.ts
|
|
3445
|
-
var ApiError = class extends Error {
|
|
3446
|
-
constructor(message, status3, code) {
|
|
3447
|
-
super(message);
|
|
3448
|
-
this.status = status3;
|
|
3449
|
-
this.code = code;
|
|
3450
|
-
}
|
|
3451
|
-
};
|
|
3452
|
-
async function apiFetch(baseUrl, token, path, init) {
|
|
3453
|
-
const res = await fetch(`${baseUrl}${path}`, {
|
|
3454
|
-
...init,
|
|
3455
|
-
headers: {
|
|
3456
|
-
Authorization: `Bearer ${token}`,
|
|
3457
|
-
"Content-Type": "application/json",
|
|
3458
|
-
...init?.headers ?? {}
|
|
3459
|
-
}
|
|
3460
|
-
});
|
|
3461
|
-
const text2 = await res.text();
|
|
3462
|
-
if (!res.ok) {
|
|
3463
|
-
let msg = text2;
|
|
3464
|
-
let code;
|
|
3465
|
-
try {
|
|
3466
|
-
const body = JSON.parse(text2);
|
|
3467
|
-
msg = body.error ?? text2;
|
|
3468
|
-
code = body.code;
|
|
3469
|
-
} catch {
|
|
3470
|
-
}
|
|
3471
|
-
throw new ApiError(`API ${path} (${res.status}): ${msg}`, res.status, code);
|
|
3472
|
-
}
|
|
3473
|
-
return text2 ? JSON.parse(text2) : {};
|
|
3474
|
-
}
|
|
3475
|
-
|
|
3476
|
-
// src/cli/commands/domain.ts
|
|
3477
3785
|
var API_URL3 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
3478
3786
|
var api = (token, path, init) => apiFetch(API_URL3, token, path, init);
|
|
3479
3787
|
function scrubInternal(value) {
|
|
@@ -3810,18 +4118,19 @@ function isTestAccountEmail(email) {
|
|
|
3810
4118
|
|
|
3811
4119
|
// src/cli/commands/collaborators.ts
|
|
3812
4120
|
var API_URL4 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
4121
|
+
var DEPLOY_URL6 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
3813
4122
|
function api2(token, path, init) {
|
|
3814
4123
|
return apiFetch(API_URL4, token, path, init);
|
|
3815
4124
|
}
|
|
3816
4125
|
var list3 = defineCommand14({
|
|
3817
4126
|
meta: { name: "list", description: "List collaborators on your app" },
|
|
3818
4127
|
args: {
|
|
3819
|
-
app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
|
|
4128
|
+
app: { type: "string", alias: "a", description: "App id or name (defaults to ./wrangler.toml)" },
|
|
3820
4129
|
json: { type: "boolean", description: "Emit JSON" }
|
|
3821
4130
|
},
|
|
3822
4131
|
async run({ args }) {
|
|
3823
4132
|
const token = await ensureToken();
|
|
3824
|
-
const app =
|
|
4133
|
+
const app = await resolveAppTarget(DEPLOY_URL6, token, args.app);
|
|
3825
4134
|
const { collaborators } = await api2(
|
|
3826
4135
|
token,
|
|
3827
4136
|
`/api/app-collaborators/${encodeURIComponent(app)}`
|
|
@@ -3834,12 +4143,13 @@ var list3 = defineCommand14({
|
|
|
3834
4143
|
console.log(`No collaborators on ${app}. Add one with \`deepspace collaborators add <email>\`.`);
|
|
3835
4144
|
return;
|
|
3836
4145
|
}
|
|
4146
|
+
console.log(`COLLABORATORS ON ${app}`);
|
|
3837
4147
|
for (const c of collaborators) {
|
|
3838
|
-
console.log(
|
|
4148
|
+
console.log(` ${c.emailDisplay}`);
|
|
3839
4149
|
}
|
|
3840
4150
|
}
|
|
3841
4151
|
});
|
|
3842
|
-
var
|
|
4152
|
+
var add = defineCommand14({
|
|
3843
4153
|
meta: { name: "add", description: "Authorize a DeepSpace user to deploy your app" },
|
|
3844
4154
|
args: {
|
|
3845
4155
|
email: {
|
|
@@ -3847,12 +4157,12 @@ var add2 = defineCommand14({
|
|
|
3847
4157
|
description: "Collaborator email (must already be a DeepSpace user)",
|
|
3848
4158
|
required: true
|
|
3849
4159
|
},
|
|
3850
|
-
app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
|
|
4160
|
+
app: { type: "string", alias: "a", description: "App id or name (defaults to ./wrangler.toml)" },
|
|
3851
4161
|
json: { type: "boolean", description: "Emit JSON" }
|
|
3852
4162
|
},
|
|
3853
4163
|
async run({ args }) {
|
|
3854
4164
|
const token = await ensureToken();
|
|
3855
|
-
const app =
|
|
4165
|
+
const app = await resolveAppTarget(DEPLOY_URL6, token, args.app);
|
|
3856
4166
|
if (isTestAccountEmail(args.email)) {
|
|
3857
4167
|
console.error(
|
|
3858
4168
|
"Test accounts (@deepspace.test) cannot be added as collaborators. Use a real DeepSpace account."
|
|
@@ -3875,12 +4185,12 @@ var remove = defineCommand14({
|
|
|
3875
4185
|
meta: { name: "remove", description: "Remove a collaborator from your app" },
|
|
3876
4186
|
args: {
|
|
3877
4187
|
email: { type: "positional", description: "Collaborator email", required: true },
|
|
3878
|
-
app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
|
|
4188
|
+
app: { type: "string", alias: "a", description: "App id or name (defaults to ./wrangler.toml)" },
|
|
3879
4189
|
json: { type: "boolean", description: "Emit JSON" }
|
|
3880
4190
|
},
|
|
3881
4191
|
async run({ args }) {
|
|
3882
4192
|
const token = await ensureToken();
|
|
3883
|
-
const app =
|
|
4193
|
+
const app = await resolveAppTarget(DEPLOY_URL6, token, args.app);
|
|
3884
4194
|
const { collaborators } = await api2(
|
|
3885
4195
|
token,
|
|
3886
4196
|
`/api/app-collaborators/${encodeURIComponent(app)}`
|
|
@@ -3905,28 +4215,57 @@ var remove = defineCommand14({
|
|
|
3905
4215
|
});
|
|
3906
4216
|
var collaborators_default = defineCommand14({
|
|
3907
4217
|
meta: { name: "collaborators", description: "Manage who can deploy your app" },
|
|
3908
|
-
subCommands: { list: list3, add
|
|
4218
|
+
subCommands: { list: list3, add, remove }
|
|
3909
4219
|
});
|
|
3910
4220
|
|
|
3911
4221
|
// src/cli/commands/transfer.ts
|
|
3912
4222
|
import { defineCommand as defineCommand15 } from "citty";
|
|
4223
|
+
import * as p6 from "@clack/prompts";
|
|
3913
4224
|
var API_URL5 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
3914
|
-
var
|
|
4225
|
+
var DEPLOY_URL7 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
3915
4226
|
var offer = defineCommand15({
|
|
3916
4227
|
meta: { name: "offer", description: "Offer this app to another DeepSpace user" },
|
|
3917
4228
|
args: {
|
|
3918
4229
|
email: { type: "positional", description: "Recipient email", required: true },
|
|
3919
|
-
app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
|
|
4230
|
+
app: { type: "string", alias: "a", description: "App id or name (defaults to ./wrangler.toml)" },
|
|
4231
|
+
replace: {
|
|
4232
|
+
type: "boolean",
|
|
4233
|
+
description: "Replace a pending offer to someone else without asking"
|
|
4234
|
+
}
|
|
3920
4235
|
},
|
|
3921
4236
|
async run({ args }) {
|
|
3922
4237
|
const token = await ensureToken();
|
|
3923
|
-
const app =
|
|
4238
|
+
const app = await resolveAppTarget(DEPLOY_URL7, token, args.app);
|
|
4239
|
+
const pending = await apiFetch(
|
|
4240
|
+
API_URL5,
|
|
4241
|
+
token,
|
|
4242
|
+
`/api/app-transfers/${encodeURIComponent(app)}`
|
|
4243
|
+
);
|
|
4244
|
+
const pendingTo = pending.transfer?.toEmailDisplay;
|
|
4245
|
+
if (pendingTo && pendingTo.toLowerCase() !== args.email.trim().toLowerCase() && !args.replace) {
|
|
4246
|
+
if (!process.stdin.isTTY) {
|
|
4247
|
+
console.error(
|
|
4248
|
+
`A pending offer to ${pendingTo} already exists for ${app}. Re-run with --replace to replace it, or \`deepspace transfer cancel\` first.`
|
|
4249
|
+
);
|
|
4250
|
+
process.exit(1);
|
|
4251
|
+
}
|
|
4252
|
+
const yes = await p6.confirm({
|
|
4253
|
+
message: `A pending offer to ${pendingTo} exists \u2014 replace it with ${args.email}?`
|
|
4254
|
+
});
|
|
4255
|
+
if (p6.isCancel(yes) || !yes) {
|
|
4256
|
+
console.log(`Kept the pending offer to ${pendingTo}.`);
|
|
4257
|
+
process.exit(1);
|
|
4258
|
+
}
|
|
4259
|
+
}
|
|
3924
4260
|
const { expiresAt } = await apiFetch(
|
|
3925
4261
|
API_URL5,
|
|
3926
4262
|
token,
|
|
3927
4263
|
`/api/app-transfers/${encodeURIComponent(app)}`,
|
|
3928
4264
|
{ method: "POST", body: JSON.stringify({ email: args.email }) }
|
|
3929
4265
|
);
|
|
4266
|
+
if (pendingTo && pendingTo.toLowerCase() !== args.email.trim().toLowerCase()) {
|
|
4267
|
+
console.log(`\u25B2 Replaced the pending offer to ${pendingTo}.`);
|
|
4268
|
+
}
|
|
3930
4269
|
console.log(
|
|
3931
4270
|
`\u2713 Offered ${app} to ${args.email} (expires ${expiresAt}).
|
|
3932
4271
|
They accept with: deepspace transfer accept --app ${app}`
|
|
@@ -3936,11 +4275,11 @@ var offer = defineCommand15({
|
|
|
3936
4275
|
var status2 = defineCommand15({
|
|
3937
4276
|
meta: { name: "status", description: "Show the pending transfer offer, if any" },
|
|
3938
4277
|
args: {
|
|
3939
|
-
app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
|
|
4278
|
+
app: { type: "string", alias: "a", description: "App id or name (defaults to ./wrangler.toml)" }
|
|
3940
4279
|
},
|
|
3941
4280
|
async run({ args }) {
|
|
3942
4281
|
const token = await ensureToken();
|
|
3943
|
-
const app =
|
|
4282
|
+
const app = await resolveAppTarget(DEPLOY_URL7, token, args.app);
|
|
3944
4283
|
const { transfer } = await apiFetch(API_URL5, token, `/api/app-transfers/${encodeURIComponent(app)}`);
|
|
3945
4284
|
if (!transfer) {
|
|
3946
4285
|
console.log(`No pending transfer for ${app}.`);
|
|
@@ -3957,7 +4296,7 @@ var accept = defineCommand15({
|
|
|
3957
4296
|
async run({ args }) {
|
|
3958
4297
|
const token = await ensureToken();
|
|
3959
4298
|
const app = requireAppIdArg(args.app);
|
|
3960
|
-
await apiFetch(
|
|
4299
|
+
await apiFetch(DEPLOY_URL7, token, `/api/apps/${encodeURIComponent(app)}/transfer/accept`, {
|
|
3961
4300
|
method: "POST"
|
|
3962
4301
|
});
|
|
3963
4302
|
console.log(
|
|
@@ -3968,11 +4307,11 @@ var accept = defineCommand15({
|
|
|
3968
4307
|
var cancel4 = defineCommand15({
|
|
3969
4308
|
meta: { name: "cancel", description: "Cancel/decline the pending offer (either party)" },
|
|
3970
4309
|
args: {
|
|
3971
|
-
app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
|
|
4310
|
+
app: { type: "string", alias: "a", description: "App id or name (defaults to ./wrangler.toml)" }
|
|
3972
4311
|
},
|
|
3973
4312
|
async run({ args }) {
|
|
3974
4313
|
const token = await ensureToken();
|
|
3975
|
-
const app =
|
|
4314
|
+
const app = await resolveAppTarget(DEPLOY_URL7, token, args.app);
|
|
3976
4315
|
await apiFetch(API_URL5, token, `/api/app-transfers/${encodeURIComponent(app)}`, { method: "DELETE" });
|
|
3977
4316
|
console.log(`\u2713 Transfer offer for ${app} cancelled.`);
|
|
3978
4317
|
}
|
|
@@ -3986,7 +4325,8 @@ var transfer_default = defineCommand15({
|
|
|
3986
4325
|
import { defineCommand as defineCommand16 } from "citty";
|
|
3987
4326
|
|
|
3988
4327
|
// src/cli/commands/_invoke-impl.ts
|
|
3989
|
-
import { readFileSync as
|
|
4328
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
4329
|
+
import * as p7 from "@clack/prompts";
|
|
3990
4330
|
var API_URL6 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
3991
4331
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
3992
4332
|
function parseTarget(target) {
|
|
@@ -4011,7 +4351,7 @@ function resolveBody(opts) {
|
|
|
4011
4351
|
if (opts.body != null) {
|
|
4012
4352
|
raw = opts.body;
|
|
4013
4353
|
} else if (opts.bodyFile != null) {
|
|
4014
|
-
raw = opts.bodyFile === "-" ?
|
|
4354
|
+
raw = opts.bodyFile === "-" ? readFileSync13(0, "utf-8") : readFileSync13(opts.bodyFile, "utf-8");
|
|
4015
4355
|
} else {
|
|
4016
4356
|
return "{}";
|
|
4017
4357
|
}
|
|
@@ -4045,6 +4385,17 @@ function formatCurrency(value, currency) {
|
|
|
4045
4385
|
if (currency === "USD") return `$${value}`;
|
|
4046
4386
|
return `${value} ${currency}`;
|
|
4047
4387
|
}
|
|
4388
|
+
function billingUnit(model) {
|
|
4389
|
+
if (model === "per_token") return "per token";
|
|
4390
|
+
if (model === "per_call") return "per call";
|
|
4391
|
+
return model.startsWith("per_") ? model.replace(/^per_/, "per ").replace(/_/g, " ") : model;
|
|
4392
|
+
}
|
|
4393
|
+
function isInteractive(stdin, stdout) {
|
|
4394
|
+
return Boolean(stdin.isTTY && stdout.isTTY);
|
|
4395
|
+
}
|
|
4396
|
+
function shouldConfirmCost(opts) {
|
|
4397
|
+
return !opts.json && !opts.yes && opts.isTTY && opts.baseCost > 0;
|
|
4398
|
+
}
|
|
4048
4399
|
async function runList(args) {
|
|
4049
4400
|
const catalog = await fetchCatalog({ summary: true });
|
|
4050
4401
|
if (args.json) {
|
|
@@ -4090,7 +4441,7 @@ async function runInfo(args) {
|
|
|
4090
4441
|
}
|
|
4091
4442
|
console.log(`${integration}/${endpoint}`);
|
|
4092
4443
|
console.log(
|
|
4093
|
-
` billing: ${info2.billing.
|
|
4444
|
+
` billing: ${formatCurrency(info2.billing.baseCost, info2.billing.currency)} ${billingUnit(info2.billing.model)}`
|
|
4094
4445
|
);
|
|
4095
4446
|
console.log();
|
|
4096
4447
|
console.log("Input schema:");
|
|
@@ -4105,6 +4456,31 @@ async function runInvoke(args) {
|
|
|
4105
4456
|
const { integration, endpoint } = parseTarget(args.target);
|
|
4106
4457
|
const body = resolveBody({ body: args.body, bodyFile: args.bodyFile });
|
|
4107
4458
|
const timeoutMs = args.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
4459
|
+
const interactive = isInteractive(process.stdin, process.stdout);
|
|
4460
|
+
if (!args.json && !args.yes && interactive) {
|
|
4461
|
+
let info2 = null;
|
|
4462
|
+
try {
|
|
4463
|
+
info2 = findEndpoint(await fetchCatalog({ summary: true }), integration, endpoint);
|
|
4464
|
+
} catch {
|
|
4465
|
+
console.error("Note: could not verify the call cost; proceeding. Pass --yes to skip this check.");
|
|
4466
|
+
info2 = null;
|
|
4467
|
+
}
|
|
4468
|
+
if (info2 && shouldConfirmCost({
|
|
4469
|
+
json: !!args.json,
|
|
4470
|
+
yes: !!args.yes,
|
|
4471
|
+
isTTY: interactive,
|
|
4472
|
+
baseCost: info2.billing.baseCost
|
|
4473
|
+
})) {
|
|
4474
|
+
const cost = formatCurrency(info2.billing.baseCost, info2.billing.currency);
|
|
4475
|
+
const ok2 = await p7.confirm({
|
|
4476
|
+
message: `${integration}/${endpoint} costs ${cost} ${billingUnit(info2.billing.model)}, billed to your account. Continue?`
|
|
4477
|
+
});
|
|
4478
|
+
if (p7.isCancel(ok2) || !ok2) {
|
|
4479
|
+
p7.cancel("Cancelled \u2014 no call made.");
|
|
4480
|
+
process.exit(0);
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4108
4484
|
let jwt;
|
|
4109
4485
|
try {
|
|
4110
4486
|
jwt = await ensureToken();
|
|
@@ -4247,6 +4623,12 @@ var invoke = defineCommand16({
|
|
|
4247
4623
|
type: "string",
|
|
4248
4624
|
description: "Request timeout in milliseconds (default: 120000)",
|
|
4249
4625
|
required: false
|
|
4626
|
+
},
|
|
4627
|
+
yes: {
|
|
4628
|
+
type: "boolean",
|
|
4629
|
+
alias: "y",
|
|
4630
|
+
description: "Skip the paid-call cost confirmation",
|
|
4631
|
+
default: false
|
|
4250
4632
|
}
|
|
4251
4633
|
},
|
|
4252
4634
|
async run({ args }) {
|
|
@@ -4260,7 +4642,8 @@ var invoke = defineCommand16({
|
|
|
4260
4642
|
body: args.body,
|
|
4261
4643
|
bodyFile: args["body-file"],
|
|
4262
4644
|
json: args.json,
|
|
4263
|
-
timeout
|
|
4645
|
+
timeout,
|
|
4646
|
+
yes: args.yes
|
|
4264
4647
|
});
|
|
4265
4648
|
} catch (err) {
|
|
4266
4649
|
console.error(err instanceof Error ? err.message : String(err));
|
|
@@ -4324,6 +4707,12 @@ var invoke_default = defineCommand17({
|
|
|
4324
4707
|
type: "string",
|
|
4325
4708
|
description: "Request timeout in milliseconds (default: 120000)",
|
|
4326
4709
|
required: false
|
|
4710
|
+
},
|
|
4711
|
+
yes: {
|
|
4712
|
+
type: "boolean",
|
|
4713
|
+
alias: "y",
|
|
4714
|
+
description: "Skip the paid-call cost confirmation",
|
|
4715
|
+
default: false
|
|
4327
4716
|
}
|
|
4328
4717
|
},
|
|
4329
4718
|
async run({ args }) {
|
|
@@ -4345,7 +4734,8 @@ var invoke_default = defineCommand17({
|
|
|
4345
4734
|
body: args.body,
|
|
4346
4735
|
bodyFile: args["body-file"],
|
|
4347
4736
|
json: args.json,
|
|
4348
|
-
timeout
|
|
4737
|
+
timeout,
|
|
4738
|
+
yes: args.yes
|
|
4349
4739
|
});
|
|
4350
4740
|
} catch (err) {
|
|
4351
4741
|
console.error(err instanceof Error ? err.message : String(err));
|
|
@@ -4356,10 +4746,10 @@ var invoke_default = defineCommand17({
|
|
|
4356
4746
|
|
|
4357
4747
|
// src/cli/commands/library.ts
|
|
4358
4748
|
import { defineCommand as defineCommand18 } from "citty";
|
|
4359
|
-
import { existsSync as existsSync12, readFileSync as
|
|
4360
|
-
import { join as
|
|
4749
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
4750
|
+
import { join as join13, resolve as resolve9 } from "path";
|
|
4361
4751
|
import { parse as parseToml4 } from "smol-toml";
|
|
4362
|
-
import * as
|
|
4752
|
+
import * as p8 from "@clack/prompts";
|
|
4363
4753
|
var DEFAULT_LIBRARY_APP = "deepdotspace-site";
|
|
4364
4754
|
var publish = defineCommand18({
|
|
4365
4755
|
meta: {
|
|
@@ -4421,25 +4811,25 @@ var publish = defineCommand18({
|
|
|
4421
4811
|
async run({ args }) {
|
|
4422
4812
|
const json = !!args.json;
|
|
4423
4813
|
const intro6 = (s) => {
|
|
4424
|
-
if (!json)
|
|
4814
|
+
if (!json) p8.intro(s);
|
|
4425
4815
|
};
|
|
4426
4816
|
const info2 = (s) => {
|
|
4427
|
-
if (!json)
|
|
4817
|
+
if (!json) p8.log.info(s);
|
|
4428
4818
|
};
|
|
4429
4819
|
const die = (msg) => {
|
|
4430
4820
|
if (json) console.error(JSON.stringify({ ok: false, error: msg }));
|
|
4431
|
-
else
|
|
4821
|
+
else p8.cancel(msg);
|
|
4432
4822
|
process.exit(1);
|
|
4433
4823
|
};
|
|
4434
4824
|
intro6("Publishing to the DeepSpace library");
|
|
4435
4825
|
let appName = typeof args.app === "string" && args.app.trim() ? args.app.trim() : void 0;
|
|
4436
4826
|
if (!appName) {
|
|
4437
4827
|
const appDir = resolve9(typeof args.dir === "string" && args.dir ? args.dir : ".");
|
|
4438
|
-
const wranglerPath =
|
|
4828
|
+
const wranglerPath = join13(appDir, "wrangler.toml");
|
|
4439
4829
|
if (!existsSync12(wranglerPath)) {
|
|
4440
4830
|
return die("No --app provided and no wrangler.toml found. Either pass --app <name> or run from your app's directory.");
|
|
4441
4831
|
}
|
|
4442
|
-
const cfg = parseToml4(
|
|
4832
|
+
const cfg = parseToml4(readFileSync14(wranglerPath, "utf-8"));
|
|
4443
4833
|
const nameRes = resolveAppName(cfg.name);
|
|
4444
4834
|
if (!nameRes.ok) return die(`wrangler.toml: ${nameRes.reason}`);
|
|
4445
4835
|
appName = nameRes.name;
|
|
@@ -4495,9 +4885,9 @@ var publish = defineCommand18({
|
|
|
4495
4885
|
if (!parsed.success) {
|
|
4496
4886
|
return die(parsed.error ?? `Publish failed (HTTP ${res.status}).`);
|
|
4497
4887
|
}
|
|
4498
|
-
|
|
4499
|
-
if (parsed.data?.url)
|
|
4500
|
-
|
|
4888
|
+
p8.log.success(`Published ${appName} as ${parsed.data?.templateHandle} (v${parsed.data?.version})`);
|
|
4889
|
+
if (parsed.data?.url) p8.log.message(parsed.data.url);
|
|
4890
|
+
p8.outro("Done");
|
|
4501
4891
|
}
|
|
4502
4892
|
});
|
|
4503
4893
|
var unpublish = defineCommand18({
|
|
@@ -4526,10 +4916,10 @@ var unpublish = defineCommand18({
|
|
|
4526
4916
|
const json = !!args.json;
|
|
4527
4917
|
const die = (msg) => {
|
|
4528
4918
|
if (json) console.error(JSON.stringify({ ok: false, error: msg }));
|
|
4529
|
-
else
|
|
4919
|
+
else p8.cancel(msg);
|
|
4530
4920
|
process.exit(1);
|
|
4531
4921
|
};
|
|
4532
|
-
if (!json)
|
|
4922
|
+
if (!json) p8.intro("Unpublishing library entry");
|
|
4533
4923
|
let token;
|
|
4534
4924
|
try {
|
|
4535
4925
|
token = await ensureToken();
|
|
@@ -4555,8 +4945,8 @@ var unpublish = defineCommand18({
|
|
|
4555
4945
|
process.exit(parsed.success ? 0 : 1);
|
|
4556
4946
|
}
|
|
4557
4947
|
if (!parsed.success) return die(parsed.error ?? `HTTP ${res.status}`);
|
|
4558
|
-
|
|
4559
|
-
|
|
4948
|
+
p8.log.success(`Unpublished ${parsed.data?.templateHandle}`);
|
|
4949
|
+
p8.outro("Done");
|
|
4560
4950
|
}
|
|
4561
4951
|
});
|
|
4562
4952
|
var library_default = defineCommand18({
|
|
@@ -4572,10 +4962,10 @@ var library_default = defineCommand18({
|
|
|
4572
4962
|
|
|
4573
4963
|
// src/cli/commands/feedback.ts
|
|
4574
4964
|
import { defineCommand as defineCommand19 } from "citty";
|
|
4575
|
-
import * as
|
|
4576
|
-
import { existsSync as existsSync13, readFileSync as
|
|
4577
|
-
import { dirname as
|
|
4578
|
-
import { fileURLToPath } from "url";
|
|
4965
|
+
import * as p9 from "@clack/prompts";
|
|
4966
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
|
|
4967
|
+
import { dirname as dirname4, join as join14 } from "path";
|
|
4968
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4579
4969
|
import { release } from "os";
|
|
4580
4970
|
var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
4581
4971
|
var TYPES = ["bug", "feature", "other"];
|
|
@@ -4595,14 +4985,14 @@ function normalizeType(raw) {
|
|
|
4595
4985
|
}
|
|
4596
4986
|
function readCliVersion() {
|
|
4597
4987
|
try {
|
|
4598
|
-
const here =
|
|
4988
|
+
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
4599
4989
|
const candidates = [
|
|
4600
|
-
|
|
4601
|
-
|
|
4990
|
+
join14(here, "..", "package.json"),
|
|
4991
|
+
join14(here, "..", "..", "..", "package.json")
|
|
4602
4992
|
];
|
|
4603
4993
|
for (const path of candidates) {
|
|
4604
4994
|
if (!existsSync13(path)) continue;
|
|
4605
|
-
const pkg2 = JSON.parse(
|
|
4995
|
+
const pkg2 = JSON.parse(readFileSync15(path, "utf-8"));
|
|
4606
4996
|
if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
|
|
4607
4997
|
}
|
|
4608
4998
|
} catch {
|
|
@@ -4707,21 +5097,21 @@ var feedback_default = defineCommand19({
|
|
|
4707
5097
|
);
|
|
4708
5098
|
}
|
|
4709
5099
|
if (interactive && (!args.type || !title || !body)) {
|
|
4710
|
-
|
|
5100
|
+
p9.intro("DeepSpace Feedback");
|
|
4711
5101
|
if (!args.type) {
|
|
4712
|
-
const picked = await
|
|
5102
|
+
const picked = await p9.select({
|
|
4713
5103
|
message: "What kind of feedback is this?",
|
|
4714
5104
|
options: TYPES.map((t) => ({ value: t, label: TYPE_LABELS[t] })),
|
|
4715
5105
|
initialValue: type
|
|
4716
5106
|
});
|
|
4717
|
-
if (
|
|
4718
|
-
|
|
5107
|
+
if (p9.isCancel(picked)) {
|
|
5108
|
+
p9.cancel("Cancelled.");
|
|
4719
5109
|
process.exit(0);
|
|
4720
5110
|
}
|
|
4721
5111
|
type = picked;
|
|
4722
5112
|
}
|
|
4723
5113
|
if (!title) {
|
|
4724
|
-
const t = await
|
|
5114
|
+
const t = await p9.text({
|
|
4725
5115
|
message: "Title",
|
|
4726
5116
|
placeholder: "Short summary of the issue",
|
|
4727
5117
|
validate: (v) => {
|
|
@@ -4731,14 +5121,14 @@ var feedback_default = defineCommand19({
|
|
|
4731
5121
|
return void 0;
|
|
4732
5122
|
}
|
|
4733
5123
|
});
|
|
4734
|
-
if (
|
|
4735
|
-
|
|
5124
|
+
if (p9.isCancel(t)) {
|
|
5125
|
+
p9.cancel("Cancelled.");
|
|
4736
5126
|
process.exit(0);
|
|
4737
5127
|
}
|
|
4738
5128
|
title = String(t);
|
|
4739
5129
|
}
|
|
4740
5130
|
if (!body) {
|
|
4741
|
-
const m = await
|
|
5131
|
+
const m = await p9.text({
|
|
4742
5132
|
message: "Details",
|
|
4743
5133
|
placeholder: "What happened? Steps to reproduce, what you expected\u2026",
|
|
4744
5134
|
validate: (v) => {
|
|
@@ -4748,8 +5138,8 @@ var feedback_default = defineCommand19({
|
|
|
4748
5138
|
return void 0;
|
|
4749
5139
|
}
|
|
4750
5140
|
});
|
|
4751
|
-
if (
|
|
4752
|
-
|
|
5141
|
+
if (p9.isCancel(m)) {
|
|
5142
|
+
p9.cancel("Cancelled.");
|
|
4753
5143
|
process.exit(0);
|
|
4754
5144
|
}
|
|
4755
5145
|
body = String(m);
|
|
@@ -4767,7 +5157,7 @@ var feedback_default = defineCommand19({
|
|
|
4767
5157
|
return fail2(err.message);
|
|
4768
5158
|
}
|
|
4769
5159
|
if (interactive && !args.yes) {
|
|
4770
|
-
|
|
5160
|
+
p9.note(
|
|
4771
5161
|
`${TYPE_LABELS[payload.type]}
|
|
4772
5162
|
${payload.title}
|
|
4773
5163
|
|
|
@@ -4776,9 +5166,9 @@ ${payload.body}
|
|
|
4776
5166
|
Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payload.platform}` + (payload.appName ? ` \xB7 app ${payload.appName}` : ""),
|
|
4777
5167
|
"Review"
|
|
4778
5168
|
);
|
|
4779
|
-
const ok = await
|
|
4780
|
-
if (
|
|
4781
|
-
|
|
5169
|
+
const ok = await p9.confirm({ message: "Send this to the DeepSpace team?" });
|
|
5170
|
+
if (p9.isCancel(ok) || !ok) {
|
|
5171
|
+
p9.cancel("Not sent.");
|
|
4782
5172
|
process.exit(0);
|
|
4783
5173
|
}
|
|
4784
5174
|
}
|
|
@@ -4802,7 +5192,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
|
|
|
4802
5192
|
return;
|
|
4803
5193
|
}
|
|
4804
5194
|
if (interactive) {
|
|
4805
|
-
|
|
5195
|
+
p9.outro(`Thanks! Submitted as ${result.report.id}.`);
|
|
4806
5196
|
} else {
|
|
4807
5197
|
console.log(`Submitted feedback ${result.report.id} (${result.report.type}).`);
|
|
4808
5198
|
}
|
|
@@ -4812,8 +5202,18 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
|
|
|
4812
5202
|
|
|
4813
5203
|
// src/cli/commands/secrets.ts
|
|
4814
5204
|
import { defineCommand as defineCommand20 } from "citty";
|
|
4815
|
-
import { readFileSync as
|
|
4816
|
-
|
|
5205
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
5206
|
+
|
|
5207
|
+
// src/cli/lib/citty-args.ts
|
|
5208
|
+
function dedupePositionals(bound, rest) {
|
|
5209
|
+
const restArr = Array.isArray(rest) ? rest : [];
|
|
5210
|
+
return [.../* @__PURE__ */ new Set([bound, ...restArr])].filter(
|
|
5211
|
+
(v) => typeof v === "string" && v.length > 0
|
|
5212
|
+
);
|
|
5213
|
+
}
|
|
5214
|
+
|
|
5215
|
+
// src/cli/commands/secrets.ts
|
|
5216
|
+
var DEPLOY_URL8 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
4817
5217
|
var COMMON_ARGS = {
|
|
4818
5218
|
app: {
|
|
4819
5219
|
type: "string",
|
|
@@ -4844,6 +5244,11 @@ async function resolveTarget(args) {
|
|
|
4844
5244
|
const appDir = findAppDir();
|
|
4845
5245
|
appId = appDir && readAppId(appDir, wranglerEnv) || void 0;
|
|
4846
5246
|
if (!appId) {
|
|
5247
|
+
if (wranglerEnv && appDir && readAppId(appDir, void 0)) {
|
|
5248
|
+
throw new Error(
|
|
5249
|
+
`No app id for env "${wranglerEnv}" \u2014 wrangler.toml has no [env.${wranglerEnv}] block with its own DEEPSPACE_APP_ID. To target the "${wranglerEnv}" config on this app, use \`-c ${wranglerEnv}\` instead of \`--env ${wranglerEnv}\`.`
|
|
5250
|
+
);
|
|
5251
|
+
}
|
|
4847
5252
|
throw new Error(
|
|
4848
5253
|
"No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID (`deepspace init` mints one), or pass --app <appId>."
|
|
4849
5254
|
);
|
|
@@ -4857,6 +5262,15 @@ function fail(err) {
|
|
|
4857
5262
|
console.error(err instanceof Error ? err.message : String(err));
|
|
4858
5263
|
process.exit(1);
|
|
4859
5264
|
}
|
|
5265
|
+
function warnIfDebugRoutesEnabled(secrets) {
|
|
5266
|
+
if (secrets.ALLOW_DEBUG_ROUTES !== "true") return false;
|
|
5267
|
+
console.warn(
|
|
5268
|
+
"\n\u26A0 ALLOW_DEBUG_ROUTES=true exposes an UNAUTHENTICATED debug API on the deployed app \u2014\n anyone who can reach /api/debug/* can read and mutate any record. Redeploy to apply;\n `deepspace secrets delete ALLOW_DEBUG_ROUTES` (then redeploy) turns it back off."
|
|
5269
|
+
);
|
|
5270
|
+
return true;
|
|
5271
|
+
}
|
|
5272
|
+
var APPLY_HINT = "Run `deepspace deploy` to apply \u2014 secrets take effect at deploy time.";
|
|
5273
|
+
var LOCAL_DEV_HINT = "Running `deepspace dev`? Restart it to load the change locally.";
|
|
4860
5274
|
var list5 = defineCommand20({
|
|
4861
5275
|
meta: { name: "list", description: "List masked secrets in a config" },
|
|
4862
5276
|
args: {
|
|
@@ -4871,7 +5285,7 @@ var list5 = defineCommand20({
|
|
|
4871
5285
|
async run({ args }) {
|
|
4872
5286
|
try {
|
|
4873
5287
|
const t = await resolveTarget(args);
|
|
4874
|
-
const { secrets } = await listSecrets(
|
|
5288
|
+
const { secrets } = await listSecrets(DEPLOY_URL8, t.token, t.appId, t.configName);
|
|
4875
5289
|
if (args.json) {
|
|
4876
5290
|
console.log(JSON.stringify({ appId: t.appId, config: t.configName, secrets }, null, 2));
|
|
4877
5291
|
return;
|
|
@@ -4881,7 +5295,7 @@ var list5 = defineCommand20({
|
|
|
4881
5295
|
return;
|
|
4882
5296
|
}
|
|
4883
5297
|
for (const s of secrets) {
|
|
4884
|
-
console.log(args["only-names"] ? s.key : `${s.key} (v${s.version}, ${s.
|
|
5298
|
+
console.log(args["only-names"] ? s.key : `${s.key} (v${s.version}, ${s.updatedAt})`);
|
|
4885
5299
|
}
|
|
4886
5300
|
} catch (err) {
|
|
4887
5301
|
fail(err);
|
|
@@ -4897,25 +5311,34 @@ var set = defineCommand20({
|
|
|
4897
5311
|
async run({ args }) {
|
|
4898
5312
|
try {
|
|
4899
5313
|
const t = await resolveTarget(args);
|
|
4900
|
-
const pairs =
|
|
4901
|
-
(v) => typeof v === "string" && v.length > 0
|
|
4902
|
-
);
|
|
5314
|
+
const pairs = dedupePositionals(args.secret, args._);
|
|
4903
5315
|
const secrets = {};
|
|
5316
|
+
const dupes = [];
|
|
4904
5317
|
for (const pair of pairs) {
|
|
4905
5318
|
const eq = pair.indexOf("=");
|
|
4906
5319
|
if (eq <= 0) throw new Error(`Expected KEY=value, got "${pair}"`);
|
|
4907
5320
|
const key = validateSecretName(pair.slice(0, eq));
|
|
4908
5321
|
const value = pair.slice(eq + 1);
|
|
4909
5322
|
validateSecretValue(key, value);
|
|
5323
|
+
if (key in secrets && !dupes.includes(key)) dupes.push(key);
|
|
4910
5324
|
secrets[key] = value;
|
|
4911
5325
|
}
|
|
5326
|
+
if (dupes.length) {
|
|
5327
|
+
console.warn(
|
|
5328
|
+
`Warning: ${dupes.join(", ")} given more than once \u2014 kept the last value for each.`
|
|
5329
|
+
);
|
|
5330
|
+
}
|
|
4912
5331
|
if (Object.keys(secrets).length === 1) {
|
|
4913
5332
|
const [[k, v]] = Object.entries(secrets);
|
|
4914
|
-
await setSecret(
|
|
5333
|
+
await setSecret(DEPLOY_URL8, t.token, t.appId, t.configName, k, v);
|
|
4915
5334
|
} else {
|
|
4916
|
-
await uploadSecrets(
|
|
5335
|
+
await uploadSecrets(DEPLOY_URL8, t.token, t.appId, t.configName, secrets, false);
|
|
4917
5336
|
}
|
|
4918
5337
|
console.log(`Set ${Object.keys(secrets).length} secret${Object.keys(secrets).length === 1 ? "" : "s"} in ${t.configName}.`);
|
|
5338
|
+
if (!warnIfDebugRoutesEnabled(secrets)) {
|
|
5339
|
+
console.log(APPLY_HINT);
|
|
5340
|
+
console.log(LOCAL_DEV_HINT);
|
|
5341
|
+
}
|
|
4919
5342
|
} catch (err) {
|
|
4920
5343
|
fail(err);
|
|
4921
5344
|
}
|
|
@@ -4933,14 +5356,14 @@ var get = defineCommand20({
|
|
|
4933
5356
|
const t = await resolveTarget(args);
|
|
4934
5357
|
const key = validateSecretName(args.key);
|
|
4935
5358
|
if (args.plain) {
|
|
4936
|
-
const { value } = await getSecretPlain(
|
|
4937
|
-
process.stdout.write(value + "\n");
|
|
5359
|
+
const { value } = await getSecretPlain(DEPLOY_URL8, t.token, t.appId, t.configName, key);
|
|
5360
|
+
process.stdout.write(process.stdout.isTTY ? value + "\n" : value);
|
|
4938
5361
|
return;
|
|
4939
5362
|
}
|
|
4940
|
-
const { secrets } = await listSecrets(
|
|
5363
|
+
const { secrets } = await listSecrets(DEPLOY_URL8, t.token, t.appId, t.configName);
|
|
4941
5364
|
const item = secrets.find((s) => s.key === key);
|
|
4942
5365
|
if (!item) fail(new Error(`Secret "${key}" not found in ${t.configName}`));
|
|
4943
|
-
console.log(`${item.key} (v${item.version}, ${item.
|
|
5366
|
+
console.log(`${item.key} (v${item.version}, ${item.updatedAt})`);
|
|
4944
5367
|
} catch (err) {
|
|
4945
5368
|
fail(err);
|
|
4946
5369
|
}
|
|
@@ -4955,13 +5378,22 @@ var del2 = defineCommand20({
|
|
|
4955
5378
|
async run({ args }) {
|
|
4956
5379
|
try {
|
|
4957
5380
|
const t = await resolveTarget(args);
|
|
4958
|
-
const keys =
|
|
4959
|
-
|
|
4960
|
-
|
|
5381
|
+
const keys = dedupePositionals(args.key, args._);
|
|
5382
|
+
let deleted = 0;
|
|
5383
|
+
let absent = 0;
|
|
4961
5384
|
for (const key of keys) {
|
|
4962
|
-
|
|
5385
|
+
const name = validateSecretName(key);
|
|
5386
|
+
try {
|
|
5387
|
+
await deleteSecret(DEPLOY_URL8, t.token, t.appId, t.configName, name);
|
|
5388
|
+
deleted++;
|
|
5389
|
+
} catch (err) {
|
|
5390
|
+
if (err?.status === 404) absent++;
|
|
5391
|
+
else throw err;
|
|
5392
|
+
}
|
|
4963
5393
|
}
|
|
4964
|
-
|
|
5394
|
+
const note3 = absent > 0 ? ` (${absent} already absent)` : "";
|
|
5395
|
+
console.log(`Deleted ${deleted} secret${deleted === 1 ? "" : "s"} from ${t.configName}.${note3}`);
|
|
5396
|
+
if (deleted > 0) console.log(APPLY_HINT);
|
|
4965
5397
|
} catch (err) {
|
|
4966
5398
|
fail(err);
|
|
4967
5399
|
}
|
|
@@ -4981,11 +5413,12 @@ var upload = defineCommand20({
|
|
|
4981
5413
|
async run({ args }) {
|
|
4982
5414
|
try {
|
|
4983
5415
|
const t = await resolveTarget(args);
|
|
4984
|
-
const content = args.file === "-" ?
|
|
5416
|
+
const content = args.file === "-" ? readFileSync16(0, "utf-8") : readFileSync16(args.file, "utf-8");
|
|
4985
5417
|
const secrets = parseSecretsUpload(content);
|
|
4986
5418
|
if (Object.keys(secrets).length === 0) fail(new Error("No secrets found in the input."));
|
|
4987
|
-
await uploadSecrets(
|
|
5419
|
+
await uploadSecrets(DEPLOY_URL8, t.token, t.appId, t.configName, secrets, args.replace);
|
|
4988
5420
|
console.log(`Uploaded ${Object.keys(secrets).length} secrets to ${t.configName}.`);
|
|
5421
|
+
if (!warnIfDebugRoutesEnabled(secrets)) console.log(APPLY_HINT);
|
|
4989
5422
|
} catch (err) {
|
|
4990
5423
|
fail(err);
|
|
4991
5424
|
}
|
|
@@ -5008,7 +5441,7 @@ var download = defineCommand20({
|
|
|
5008
5441
|
if (!["dotenv", "json", "shell"].includes(format)) {
|
|
5009
5442
|
fail(new Error(`Unknown format "${args.format}" \u2014 use dotenv, json, or shell.`));
|
|
5010
5443
|
}
|
|
5011
|
-
const { secrets } = await fetchSecretsValues(
|
|
5444
|
+
const { secrets } = await fetchSecretsValues(DEPLOY_URL8, t.token, t.appId, t.configName);
|
|
5012
5445
|
process.stdout.write(formatSecretsDownload(secrets, format));
|
|
5013
5446
|
} catch (err) {
|
|
5014
5447
|
fail(err);
|
|
@@ -5025,7 +5458,7 @@ var pull = defineCommand20({
|
|
|
5025
5458
|
if (!appDir) fail(new Error("Run from a DeepSpace app directory (one containing wrangler.toml)."));
|
|
5026
5459
|
const t = await resolveTarget(args);
|
|
5027
5460
|
const ownerId = decodeJwtPayload(t.token).sub;
|
|
5028
|
-
const pulled = await pullAppSecretsCache(
|
|
5461
|
+
const pulled = await pullAppSecretsCache(DEPLOY_URL8, t.token, t.appId, t.configName);
|
|
5029
5462
|
if (!pulled) {
|
|
5030
5463
|
console.log(`No secrets in ${t.configName} yet \u2014 nothing to pull.`);
|
|
5031
5464
|
return;
|
|
@@ -5049,7 +5482,7 @@ var configsList = defineCommand20({
|
|
|
5049
5482
|
async run({ args }) {
|
|
5050
5483
|
try {
|
|
5051
5484
|
const t = await resolveTarget(args);
|
|
5052
|
-
const { configs: configs2 } = await listConfigs(
|
|
5485
|
+
const { configs: configs2 } = await listConfigs(DEPLOY_URL8, t.token, t.appId);
|
|
5053
5486
|
if (configs2.length === 0) {
|
|
5054
5487
|
console.log("No configs yet \u2014 the first `secrets set` creates one.");
|
|
5055
5488
|
return;
|
|
@@ -5073,7 +5506,20 @@ var configsCreate = defineCommand20({
|
|
|
5073
5506
|
try {
|
|
5074
5507
|
const t = await resolveTarget(args);
|
|
5075
5508
|
const name = validateConfigName(args.name);
|
|
5076
|
-
|
|
5509
|
+
const copyFrom = args["copy-from"] || void 0;
|
|
5510
|
+
const { configs: configs2 } = await listConfigs(DEPLOY_URL8, t.token, t.appId);
|
|
5511
|
+
if (configs2.some((c) => c.name === name)) {
|
|
5512
|
+
if (copyFrom) {
|
|
5513
|
+
fail(
|
|
5514
|
+
new Error(
|
|
5515
|
+
`Config "${name}" already exists \u2014 refusing to copy "${copyFrom}" over it. Delete it first, or pick a new name.`
|
|
5516
|
+
)
|
|
5517
|
+
);
|
|
5518
|
+
}
|
|
5519
|
+
console.log(`Config ${name} already exists.`);
|
|
5520
|
+
return;
|
|
5521
|
+
}
|
|
5522
|
+
await createConfig(DEPLOY_URL8, t.token, t.appId, name, copyFrom);
|
|
5077
5523
|
console.log(`Created ${name}.`);
|
|
5078
5524
|
} catch (err) {
|
|
5079
5525
|
fail(err);
|
|
@@ -5090,7 +5536,7 @@ var configsDelete = defineCommand20({
|
|
|
5090
5536
|
try {
|
|
5091
5537
|
const t = await resolveTarget(args);
|
|
5092
5538
|
const name = validateConfigName(args.name);
|
|
5093
|
-
await deleteConfig(
|
|
5539
|
+
await deleteConfig(DEPLOY_URL8, t.token, t.appId, name);
|
|
5094
5540
|
console.log(`Deleted ${name}.`);
|
|
5095
5541
|
} catch (err) {
|
|
5096
5542
|
fail(err);
|
|
@@ -5138,7 +5584,9 @@ var init_default = defineCommand21({
|
|
|
5138
5584
|
async run({ args }) {
|
|
5139
5585
|
const appDir = findAppDir();
|
|
5140
5586
|
if (!appDir) {
|
|
5141
|
-
console.error(
|
|
5587
|
+
console.error(
|
|
5588
|
+
"No wrangler.toml here \u2014 `deepspace init` stamps an app id into an existing DeepSpace app. To create a new app, run `deepspace create <name>` (or `npm create deepspace@latest <name>`), then run this from inside it."
|
|
5589
|
+
);
|
|
5142
5590
|
process.exit(1);
|
|
5143
5591
|
}
|
|
5144
5592
|
const envName = args.env || void 0;
|
|
@@ -5160,28 +5608,36 @@ var init_default = defineCommand21({
|
|
|
5160
5608
|
});
|
|
5161
5609
|
|
|
5162
5610
|
// src/cli/cli.ts
|
|
5163
|
-
var __dirname =
|
|
5164
|
-
var pkg = JSON.parse(
|
|
5611
|
+
var __dirname = dirname5(fileURLToPath3(import.meta.url));
|
|
5612
|
+
var pkg = JSON.parse(readFileSync17(join15(__dirname, "..", "package.json"), "utf-8"));
|
|
5165
5613
|
if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync14("/etc/ssl/certs/ca-certificates.crt")) {
|
|
5166
5614
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
5167
5615
|
}
|
|
5168
5616
|
var create2 = defineCommand22({
|
|
5169
5617
|
meta: {
|
|
5170
5618
|
name: "create",
|
|
5171
|
-
description: "Create a new DeepSpace app"
|
|
5619
|
+
description: "Create a new DeepSpace app (runs create-deepspace; all flags are forwarded)"
|
|
5172
5620
|
},
|
|
5173
5621
|
args: {
|
|
5174
5622
|
name: {
|
|
5175
5623
|
type: "positional",
|
|
5176
5624
|
description: "App name",
|
|
5177
5625
|
required: false
|
|
5626
|
+
},
|
|
5627
|
+
interactive: {
|
|
5628
|
+
type: "boolean",
|
|
5629
|
+
description: "Prompt for options instead of using defaults"
|
|
5630
|
+
},
|
|
5631
|
+
local: {
|
|
5632
|
+
type: "string",
|
|
5633
|
+
description: "Use a local SDK monorepo checkout instead of the published package"
|
|
5178
5634
|
}
|
|
5179
5635
|
},
|
|
5180
|
-
run({
|
|
5181
|
-
const
|
|
5182
|
-
console.log(`Running:
|
|
5183
|
-
|
|
5184
|
-
|
|
5636
|
+
run({ rawArgs }) {
|
|
5637
|
+
const scaffolder = `create-deepspace@${pkg.version}`;
|
|
5638
|
+
console.log(`Running: npx ${scaffolder} ${rawArgs.join(" ")}`.trimEnd() + "\n");
|
|
5639
|
+
const res = spawnSync6("npx", ["-y", scaffolder, ...rawArgs], { stdio: "inherit" });
|
|
5640
|
+
process.exit(res.status ?? 1);
|
|
5185
5641
|
}
|
|
5186
5642
|
});
|
|
5187
5643
|
var main = defineCommand22({
|
|
@@ -5196,13 +5652,13 @@ var main = defineCommand22({
|
|
|
5196
5652
|
login: login_default,
|
|
5197
5653
|
logout: logout_default,
|
|
5198
5654
|
whoami: whoami_default,
|
|
5655
|
+
apps: apps_default,
|
|
5199
5656
|
dev: dev_default,
|
|
5200
5657
|
kill: kill_default,
|
|
5201
5658
|
test: test_default,
|
|
5202
5659
|
screenshot: screenshot_default,
|
|
5203
5660
|
"test-accounts": test_accounts_default,
|
|
5204
5661
|
add: add_default,
|
|
5205
|
-
skills: skills_default,
|
|
5206
5662
|
integrations: integrations_default,
|
|
5207
5663
|
invoke: invoke_default,
|
|
5208
5664
|
deploy: deploy_default,
|