deepspace 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +169 -1
- package/README.md +39 -41
- package/dist/cli.js +1585 -1403
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.js +148 -25
- package/dist/index.js.map +1 -1
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +40 -9
- package/dist/server.js +151 -44
- package/dist/server.js.map +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +28 -25
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +61 -9
- package/dist/worker.js +190 -44
- package/dist/worker.js.map +1 -1
- package/features/documentation/feature.json +2 -1
- package/features/documents/feature.json +1 -1
- package/features/documents/src/DocumentsEditorPage.tsx +16 -52
- package/features/documents/src/LibrarySidebar.tsx +6 -8
- package/features/documents/src/editor/useDocEditor.ts +4 -9
- package/features/documents/src/use-documents-presence-access.ts +1 -3
- package/features/landing/feature.json +1 -1
- package/features/messaging/src/components/chat/MessageItem.tsx +83 -34
- package/features/messaging/src/hooks/useChatChannel.ts +2 -8
- package/features/tasks/src/TasksPage.tsx +6 -3
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/cli.ts
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
defineCommand as defineCommand14,
|
|
6
|
+
runMain,
|
|
7
|
+
runCommand,
|
|
8
|
+
renderUsage
|
|
9
|
+
} from "citty";
|
|
5
10
|
import { sync as spawnSync8 } from "cross-spawn";
|
|
6
|
-
import { readFileSync as
|
|
11
|
+
import { readFileSync as readFileSync28, existsSync as existsSync25 } from "fs";
|
|
7
12
|
import { dirname as dirname11, join as join25 } from "path";
|
|
8
13
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9
14
|
|
|
@@ -260,6 +265,146 @@ function wrapCommandErrors(cmd) {
|
|
|
260
265
|
return cmd;
|
|
261
266
|
}
|
|
262
267
|
|
|
268
|
+
// src/cli/lib/command.ts
|
|
269
|
+
import { defineCommand, parseArgs } from "citty";
|
|
270
|
+
import * as p3 from "@clack/prompts";
|
|
271
|
+
var Refusal = class extends Error {
|
|
272
|
+
code;
|
|
273
|
+
action;
|
|
274
|
+
actionRequired;
|
|
275
|
+
extra;
|
|
276
|
+
constructor(message, code, opts = {}) {
|
|
277
|
+
super(message);
|
|
278
|
+
this.name = "Refusal";
|
|
279
|
+
this.code = code;
|
|
280
|
+
this.action = opts.action ? executableAction(opts.action) : void 0;
|
|
281
|
+
this.actionRequired = opts.actionRequired ?? false;
|
|
282
|
+
this.extra = opts.extra ?? {};
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
function apiErrorDetails(err) {
|
|
286
|
+
return err instanceof ApiError ? err.details : void 0;
|
|
287
|
+
}
|
|
288
|
+
function withoutReservedKeys(payload) {
|
|
289
|
+
return Object.fromEntries(
|
|
290
|
+
Object.entries(payload).filter(
|
|
291
|
+
([key]) => ![
|
|
292
|
+
"ok",
|
|
293
|
+
"code",
|
|
294
|
+
"error",
|
|
295
|
+
"action",
|
|
296
|
+
"actionRequired",
|
|
297
|
+
"next",
|
|
298
|
+
"nextAction",
|
|
299
|
+
"resume"
|
|
300
|
+
].includes(key)
|
|
301
|
+
)
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
function exitWhenFlushed(code) {
|
|
305
|
+
if (process.stdout.writableLength === 0) process.exit(code);
|
|
306
|
+
process.stdout.write("", () => process.exit(code));
|
|
307
|
+
return void 0;
|
|
308
|
+
}
|
|
309
|
+
function nameVariants(name) {
|
|
310
|
+
return [
|
|
311
|
+
name,
|
|
312
|
+
name.replace(/-([a-z])/g, (_, c) => c.toUpperCase()),
|
|
313
|
+
name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)
|
|
314
|
+
];
|
|
315
|
+
}
|
|
316
|
+
function unknownOptionNames(parsed, args) {
|
|
317
|
+
const known = /* @__PURE__ */ new Set(["_"]);
|
|
318
|
+
for (const [name, def] of Object.entries(args)) {
|
|
319
|
+
for (const variant of nameVariants(name)) known.add(variant);
|
|
320
|
+
const alias = def.alias;
|
|
321
|
+
for (const a of Array.isArray(alias) ? alias : alias ? [alias] : []) {
|
|
322
|
+
for (const variant of nameVariants(a)) known.add(variant);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return Object.keys(parsed).filter((key) => !known.has(key));
|
|
326
|
+
}
|
|
327
|
+
function unknownOptionNamesFromRaw(rawArgs2, args) {
|
|
328
|
+
const relaxedArgs = Object.fromEntries(
|
|
329
|
+
Object.entries(args).map(([name, definition]) => [
|
|
330
|
+
name,
|
|
331
|
+
definition.type === "enum" ? { ...definition, type: "string", required: false, options: void 0 } : { ...definition, required: false }
|
|
332
|
+
])
|
|
333
|
+
);
|
|
334
|
+
return unknownOptionNames(parseArgs(rawArgs2, relaxedArgs), args);
|
|
335
|
+
}
|
|
336
|
+
function unknownOptionMessage(commandName, unknown, args) {
|
|
337
|
+
const valid = Object.entries(args).filter(([, definition]) => definition.type !== "positional").map(([name]) => name).sort().join(", ");
|
|
338
|
+
return `Unknown ${unknown.length === 1 ? "option" : "options"}: ${unknown.map((name) => `--${name}`).join(", ")}. \`${commandName}\` accepts: ${valid || "(none)"}.`;
|
|
339
|
+
}
|
|
340
|
+
function defineDeepspaceCommand(def) {
|
|
341
|
+
const declaredArgs = {
|
|
342
|
+
...def.args ?? {},
|
|
343
|
+
json: {
|
|
344
|
+
type: "boolean",
|
|
345
|
+
description: def.jsonDescription ?? "Emit a single-line JSON result for scripts/agents",
|
|
346
|
+
default: false
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
return defineCommand({
|
|
350
|
+
meta: def.meta,
|
|
351
|
+
args: declaredArgs,
|
|
352
|
+
async run({ args }) {
|
|
353
|
+
const json = Boolean(args.json);
|
|
354
|
+
let succeeded = false;
|
|
355
|
+
try {
|
|
356
|
+
const unknown = unknownOptionNames(args, declaredArgs);
|
|
357
|
+
if (unknown.length > 0) {
|
|
358
|
+
throw new Refusal(
|
|
359
|
+
unknownOptionMessage(def.meta.name, unknown, declaredArgs),
|
|
360
|
+
"unknown_option"
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
const out = await def.run({
|
|
364
|
+
args
|
|
365
|
+
}) ?? {};
|
|
366
|
+
if (out.action) out.action = executableAction(out.action);
|
|
367
|
+
if (json) {
|
|
368
|
+
console.log(
|
|
369
|
+
JSON.stringify({
|
|
370
|
+
ok: true,
|
|
371
|
+
...withoutReservedKeys(out.data ?? {}),
|
|
372
|
+
...out.action ? { action: out.action } : {}
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
} else if (out.action) {
|
|
376
|
+
printAction(out.action);
|
|
377
|
+
}
|
|
378
|
+
succeeded = true;
|
|
379
|
+
} catch (err) {
|
|
380
|
+
stopActiveSpinner();
|
|
381
|
+
const refusal = err instanceof Refusal ? err : null;
|
|
382
|
+
const code = refusal?.code ?? errorCode(err);
|
|
383
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
384
|
+
if (json) {
|
|
385
|
+
console.log(
|
|
386
|
+
JSON.stringify({
|
|
387
|
+
ok: false,
|
|
388
|
+
...code ? { code } : {},
|
|
389
|
+
...refusal?.actionRequired ? { actionRequired: true } : {},
|
|
390
|
+
error: message,
|
|
391
|
+
...refusal?.action ? { action: refusal.action } : {},
|
|
392
|
+
...withoutReservedKeys(refusal?.extra ?? apiErrorDetails(err) ?? {})
|
|
393
|
+
})
|
|
394
|
+
);
|
|
395
|
+
} else {
|
|
396
|
+
const line = code ? withSlug(message, code) : message;
|
|
397
|
+
if (refusal?.actionRequired) p3.log.warn(line);
|
|
398
|
+
else p3.log.error(line);
|
|
399
|
+
if (refusal?.action) printAction(refusal.action);
|
|
400
|
+
}
|
|
401
|
+
exitWhenFlushed(refusal?.actionRequired ? 2 : 1);
|
|
402
|
+
}
|
|
403
|
+
if (succeeded) exitWhenFlushed(0);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
263
408
|
// src/cli/lib/command-suggestions.ts
|
|
264
409
|
function splitTokenPath(token, table) {
|
|
265
410
|
const parts = token.trim().split(/\s+/);
|
|
@@ -398,105 +543,6 @@ async function exchangeSession(authUrl, sessionToken) {
|
|
|
398
543
|
return data.token ?? null;
|
|
399
544
|
}
|
|
400
545
|
|
|
401
|
-
// src/cli/lib/command.ts
|
|
402
|
-
import { defineCommand } from "citty";
|
|
403
|
-
import * as p3 from "@clack/prompts";
|
|
404
|
-
var Refusal = class extends Error {
|
|
405
|
-
code;
|
|
406
|
-
action;
|
|
407
|
-
actionRequired;
|
|
408
|
-
extra;
|
|
409
|
-
constructor(message, code, opts = {}) {
|
|
410
|
-
super(message);
|
|
411
|
-
this.name = "Refusal";
|
|
412
|
-
this.code = code;
|
|
413
|
-
this.action = opts.action ? executableAction(opts.action) : void 0;
|
|
414
|
-
this.actionRequired = opts.actionRequired ?? false;
|
|
415
|
-
this.extra = opts.extra ?? {};
|
|
416
|
-
}
|
|
417
|
-
};
|
|
418
|
-
function apiErrorDetails(err) {
|
|
419
|
-
return err instanceof ApiError ? err.details : void 0;
|
|
420
|
-
}
|
|
421
|
-
function withoutReservedKeys(payload) {
|
|
422
|
-
return Object.fromEntries(
|
|
423
|
-
Object.entries(payload).filter(
|
|
424
|
-
([key]) => ![
|
|
425
|
-
"ok",
|
|
426
|
-
"code",
|
|
427
|
-
"error",
|
|
428
|
-
"action",
|
|
429
|
-
"actionRequired",
|
|
430
|
-
"next",
|
|
431
|
-
"nextAction",
|
|
432
|
-
"resume"
|
|
433
|
-
].includes(key)
|
|
434
|
-
)
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
function exitWhenFlushed(code) {
|
|
438
|
-
if (process.stdout.writableLength === 0) process.exit(code);
|
|
439
|
-
process.stdout.write("", () => process.exit(code));
|
|
440
|
-
return void 0;
|
|
441
|
-
}
|
|
442
|
-
function defineDeepspaceCommand(def) {
|
|
443
|
-
return defineCommand({
|
|
444
|
-
meta: def.meta,
|
|
445
|
-
args: {
|
|
446
|
-
...def.args ?? {},
|
|
447
|
-
json: {
|
|
448
|
-
type: "boolean",
|
|
449
|
-
description: "Emit a single-line JSON result for scripts/agents",
|
|
450
|
-
default: false
|
|
451
|
-
}
|
|
452
|
-
},
|
|
453
|
-
async run({ args }) {
|
|
454
|
-
const json = Boolean(args.json);
|
|
455
|
-
let succeeded = false;
|
|
456
|
-
try {
|
|
457
|
-
const out = await def.run({ args }) ?? {};
|
|
458
|
-
if (out.action) out.action = executableAction(out.action);
|
|
459
|
-
if (json) {
|
|
460
|
-
console.log(
|
|
461
|
-
JSON.stringify({
|
|
462
|
-
ok: true,
|
|
463
|
-
...withoutReservedKeys(out.data ?? {}),
|
|
464
|
-
...out.action ? { action: out.action } : {}
|
|
465
|
-
})
|
|
466
|
-
);
|
|
467
|
-
} else if (out.action) {
|
|
468
|
-
printAction(out.action);
|
|
469
|
-
}
|
|
470
|
-
succeeded = true;
|
|
471
|
-
} catch (err) {
|
|
472
|
-
stopActiveSpinner();
|
|
473
|
-
const refusal = err instanceof Refusal ? err : null;
|
|
474
|
-
const code = refusal?.code ?? errorCode(err);
|
|
475
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
476
|
-
if (json) {
|
|
477
|
-
console.log(
|
|
478
|
-
JSON.stringify({
|
|
479
|
-
ok: false,
|
|
480
|
-
...code ? { code } : {},
|
|
481
|
-
...refusal?.actionRequired ? { actionRequired: true } : {},
|
|
482
|
-
error: message,
|
|
483
|
-
...refusal?.action ? { action: refusal.action } : {},
|
|
484
|
-
...withoutReservedKeys(refusal?.extra ?? apiErrorDetails(err) ?? {})
|
|
485
|
-
})
|
|
486
|
-
);
|
|
487
|
-
} else {
|
|
488
|
-
const line = code ? withSlug(message, code) : message;
|
|
489
|
-
if (refusal?.actionRequired) p3.log.warn(line);
|
|
490
|
-
else p3.log.error(line);
|
|
491
|
-
if (refusal?.action) printAction(refusal.action);
|
|
492
|
-
}
|
|
493
|
-
exitWhenFlushed(refusal?.actionRequired ? 2 : 1);
|
|
494
|
-
}
|
|
495
|
-
if (succeeded) exitWhenFlushed(0);
|
|
496
|
-
}
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
|
|
500
546
|
// src/cli/auth.ts
|
|
501
547
|
var AUTH_URL = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
|
|
502
548
|
var PROD_AUTH_URL = "https://auth.deep.space";
|
|
@@ -1377,9 +1423,6 @@ function resolveWorktreePort(appDir) {
|
|
|
1377
1423
|
return deriveWorktreePort(linked.worktreeRoot);
|
|
1378
1424
|
}
|
|
1379
1425
|
|
|
1380
|
-
// src/cli/lib/dev-vars.ts
|
|
1381
|
-
import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
|
|
1382
|
-
|
|
1383
1426
|
// src/cli/lib/app-tokens.ts
|
|
1384
1427
|
async function fetchPublicKey(authUrl) {
|
|
1385
1428
|
const response = await fetch(`${authUrl}/api/auth/jwks`);
|
|
@@ -1429,1069 +1472,907 @@ async function fetchAppIdentityToken(deployUrl, callerJwt, appId) {
|
|
|
1429
1472
|
return body.token;
|
|
1430
1473
|
}
|
|
1431
1474
|
|
|
1432
|
-
// src/
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
"kv_namespace",
|
|
1440
|
-
"d1",
|
|
1441
|
-
"queue",
|
|
1442
|
-
"browser_rendering",
|
|
1443
|
-
"analytics_engine",
|
|
1444
|
-
"hyperdrive",
|
|
1445
|
-
"ratelimit"
|
|
1446
|
-
]);
|
|
1447
|
-
var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
1448
|
-
"ASSETS",
|
|
1449
|
-
"PLATFORM_WORKER",
|
|
1450
|
-
"API_WORKER",
|
|
1451
|
-
"DEEPSPACE_APP_ID",
|
|
1452
|
-
"DEEPSPACE_RESOURCE_ID",
|
|
1453
|
-
"DEEPSPACE_RELEASE_OPERATION_ID",
|
|
1454
|
-
"APP_NAME",
|
|
1455
|
-
"OWNER_USER_ID",
|
|
1456
|
-
"AUTH_JWT_PUBLIC_KEY",
|
|
1457
|
-
"AUTH_JWT_ISSUER",
|
|
1458
|
-
"AUTH_WORKER_URL",
|
|
1459
|
-
"APP_IDENTITY_TOKEN",
|
|
1460
|
-
"APP_OWNER_JWT",
|
|
1461
|
-
"USAGE_EVENTS"
|
|
1462
|
-
]);
|
|
1463
|
-
function validateBindingManifest(manifest) {
|
|
1464
|
-
if (!Array.isArray(manifest)) {
|
|
1465
|
-
return { valid: false, errors: [{ reason: "Manifest must be an array" }] };
|
|
1466
|
-
}
|
|
1467
|
-
const errors = [];
|
|
1468
|
-
const seenNames = /* @__PURE__ */ new Set();
|
|
1469
|
-
for (const entry of manifest) {
|
|
1470
|
-
if (!entry || typeof entry !== "object") {
|
|
1471
|
-
errors.push({
|
|
1472
|
-
reason: `Entry must be an object (got ${entry === null ? "null" : typeof entry})`
|
|
1473
|
-
});
|
|
1474
|
-
continue;
|
|
1475
|
-
}
|
|
1476
|
-
const e = entry;
|
|
1477
|
-
const type = e.type;
|
|
1478
|
-
const name = e.name;
|
|
1479
|
-
if (typeof type !== "string" || !ALLOWED_BINDING_TYPES.has(type)) {
|
|
1480
|
-
errors.push({
|
|
1481
|
-
reason: `Disallowed binding type: ${typeof type === "string" ? type : `(${typeof type})`}. Allowed: ${[...ALLOWED_BINDING_TYPES].sort().join(", ")}`
|
|
1482
|
-
});
|
|
1483
|
-
continue;
|
|
1484
|
-
}
|
|
1485
|
-
if (typeof name !== "string" || !name) {
|
|
1486
|
-
errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
|
|
1487
|
-
continue;
|
|
1488
|
-
}
|
|
1489
|
-
if (RESERVED_BINDING_NAMES.has(name)) {
|
|
1490
|
-
errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
|
|
1491
|
-
continue;
|
|
1492
|
-
}
|
|
1493
|
-
if (seenNames.has(name)) {
|
|
1494
|
-
errors.push({ reason: `Duplicate binding name "${name}"` });
|
|
1495
|
-
continue;
|
|
1496
|
-
}
|
|
1497
|
-
seenNames.add(name);
|
|
1498
|
-
const binding = entry;
|
|
1499
|
-
const fieldErr = requiredFieldError(binding);
|
|
1500
|
-
if (fieldErr) errors.push({ binding, reason: fieldErr });
|
|
1501
|
-
}
|
|
1502
|
-
if (errors.length) return { valid: false, errors };
|
|
1503
|
-
return { valid: true, bindings: manifest };
|
|
1504
|
-
}
|
|
1505
|
-
function requiredFieldError(b) {
|
|
1506
|
-
switch (b.type) {
|
|
1507
|
-
case "vectorize": {
|
|
1508
|
-
if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
|
|
1509
|
-
if (b.index_name === AUTO_PROVISION_SENTINEL) {
|
|
1510
|
-
if (typeof b.dimensions !== "number" || b.dimensions <= 0) {
|
|
1511
|
-
return `vectorize binding "${b.name}" with index_name="auto" requires "dimensions" (positive number)`;
|
|
1512
|
-
}
|
|
1513
|
-
if (!b.metric || !VECTORIZE_METRICS.has(b.metric)) {
|
|
1514
|
-
return `vectorize binding "${b.name}" with index_name="auto" requires "metric" (one of: ${[...VECTORIZE_METRICS].join(", ")})`;
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
return null;
|
|
1518
|
-
}
|
|
1519
|
-
case "r2_bucket":
|
|
1520
|
-
return b.bucket_name ? null : `r2_bucket binding "${b.name}" missing bucket_name (or "auto")`;
|
|
1521
|
-
case "kv_namespace": {
|
|
1522
|
-
if (!b.namespace_id)
|
|
1523
|
-
return `kv_namespace binding "${b.name}" missing namespace_id (or "auto")`;
|
|
1524
|
-
if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {
|
|
1525
|
-
return `kv_namespace binding "${b.name}" with namespace_id="auto" requires "title"`;
|
|
1526
|
-
}
|
|
1527
|
-
return null;
|
|
1528
|
-
}
|
|
1529
|
-
case "d1": {
|
|
1530
|
-
if (!b.id) return `d1 binding "${b.name}" missing id (or "auto")`;
|
|
1531
|
-
if (b.id === AUTO_PROVISION_SENTINEL && !b.database_name) {
|
|
1532
|
-
return `d1 binding "${b.name}" with id="auto" requires "database_name"`;
|
|
1533
|
-
}
|
|
1534
|
-
return null;
|
|
1535
|
-
}
|
|
1536
|
-
case "queue":
|
|
1537
|
-
return b.queue_name ? null : `queue binding "${b.name}" missing queue_name (or "auto")`;
|
|
1538
|
-
case "hyperdrive":
|
|
1539
|
-
if (!b.id) return `hyperdrive binding "${b.name}" missing id`;
|
|
1540
|
-
if (b.id === AUTO_PROVISION_SENTINEL) {
|
|
1541
|
-
return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
|
|
1542
|
-
}
|
|
1543
|
-
return null;
|
|
1544
|
-
case "ratelimit":
|
|
1545
|
-
if (!/^\d+$/u.test(b.namespace_id) || Number(b.namespace_id) <= 0) {
|
|
1546
|
-
return `ratelimit binding "${b.name}" requires a positive integer namespace_id string`;
|
|
1547
|
-
}
|
|
1548
|
-
if (!Number.isInteger(b.simple?.limit) || b.simple.limit <= 0) {
|
|
1549
|
-
return `ratelimit binding "${b.name}" requires simple.limit (positive integer)`;
|
|
1550
|
-
}
|
|
1551
|
-
if (b.simple.period !== 10 && b.simple.period !== 60) {
|
|
1552
|
-
return `ratelimit binding "${b.name}" requires simple.period (10 or 60)`;
|
|
1553
|
-
}
|
|
1554
|
-
return null;
|
|
1555
|
-
case "ai":
|
|
1556
|
-
case "browser_rendering":
|
|
1557
|
-
case "analytics_engine":
|
|
1558
|
-
return null;
|
|
1559
|
-
// no required fields beyond `name`
|
|
1560
|
-
default: {
|
|
1561
|
-
const _exhaustive = b;
|
|
1562
|
-
void _exhaustive;
|
|
1563
|
-
return null;
|
|
1564
|
-
}
|
|
1475
|
+
// src/cli/lib/secure-file.ts
|
|
1476
|
+
import { chmodSync, writeFileSync as writeFileSync5 } from "fs";
|
|
1477
|
+
function writeSecretFileSync(path, content) {
|
|
1478
|
+
writeFileSync5(path, content, { mode: 384 });
|
|
1479
|
+
try {
|
|
1480
|
+
chmodSync(path, 384);
|
|
1481
|
+
} catch {
|
|
1565
1482
|
}
|
|
1566
1483
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
});
|
|
1584
|
-
}
|
|
1585
|
-
for (const r of asObjectArray(outputConfig.r2_buckets)) {
|
|
1586
|
-
const name = pluckString(r, "binding");
|
|
1587
|
-
const bucket_name = pluckString(r, "bucket_name");
|
|
1588
|
-
if (name && bucket_name) out.push({ type: "r2_bucket", name, bucket_name });
|
|
1589
|
-
}
|
|
1590
|
-
for (const k of asObjectArray(outputConfig.kv_namespaces)) {
|
|
1591
|
-
const name = pluckString(k, "binding");
|
|
1592
|
-
const namespace_id = pluckString(k, "id");
|
|
1593
|
-
if (!name || !namespace_id) continue;
|
|
1594
|
-
const title = pluckString(k, "title");
|
|
1595
|
-
out.push({
|
|
1596
|
-
type: "kv_namespace",
|
|
1597
|
-
name,
|
|
1598
|
-
namespace_id,
|
|
1599
|
-
...title && { title }
|
|
1600
|
-
});
|
|
1601
|
-
}
|
|
1602
|
-
for (const d of asObjectArray(outputConfig.d1_databases)) {
|
|
1603
|
-
const name = pluckString(d, "binding");
|
|
1604
|
-
const id = pluckString(d, "database_id");
|
|
1605
|
-
if (!name || !id) continue;
|
|
1606
|
-
const database_name = pluckString(d, "database_name");
|
|
1607
|
-
out.push({
|
|
1608
|
-
type: "d1",
|
|
1609
|
-
name,
|
|
1610
|
-
id,
|
|
1611
|
-
...database_name && { database_name }
|
|
1612
|
-
});
|
|
1484
|
+
|
|
1485
|
+
// src/cli/lib/wrangler-env.ts
|
|
1486
|
+
import { existsSync as existsSync6, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
1487
|
+
import { join as join7 } from "path";
|
|
1488
|
+
import { parse as parseToml3, stringify as stringifyToml } from "smol-toml";
|
|
1489
|
+
|
|
1490
|
+
// src/server/rooms/app-name.ts
|
|
1491
|
+
var APP_NAME_RULES = {
|
|
1492
|
+
/** ^[a-z0-9](-?[a-z0-9])+$ — RFC 1035 host label, no consecutive dashes. */
|
|
1493
|
+
pattern: /^[a-z0-9](?:-?[a-z0-9])+$/,
|
|
1494
|
+
minLength: 2,
|
|
1495
|
+
maxLength: 63
|
|
1496
|
+
};
|
|
1497
|
+
function validateAppName(raw) {
|
|
1498
|
+
if (typeof raw !== "string") {
|
|
1499
|
+
return { valid: false, reason: "App name must be a string" };
|
|
1613
1500
|
}
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
const queue_name = pluckString(q, "queue");
|
|
1618
|
-
if (name && queue_name) out.push({ type: "queue", name, queue_name });
|
|
1501
|
+
const name = raw.trim();
|
|
1502
|
+
if (!name) {
|
|
1503
|
+
return { valid: false, reason: "App name is required" };
|
|
1619
1504
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
for (const a of asObjectArray(outputConfig.analytics_engine_datasets)) {
|
|
1623
|
-
const name = pluckString(a, "binding");
|
|
1624
|
-
const dataset = pluckString(a, "dataset");
|
|
1625
|
-
if (name) out.push({ type: "analytics_engine", name, dataset: dataset ?? void 0 });
|
|
1505
|
+
if (name.length < APP_NAME_RULES.minLength) {
|
|
1506
|
+
return { valid: false, reason: `App name "${name}" is too short (min ${APP_NAME_RULES.minLength} chars)` };
|
|
1626
1507
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
const id = pluckString(h, "id");
|
|
1630
|
-
if (name && id) out.push({ type: "hyperdrive", name, id });
|
|
1508
|
+
if (name.length > APP_NAME_RULES.maxLength) {
|
|
1509
|
+
return { valid: false, reason: `App name "${name}" is too long (max ${APP_NAME_RULES.maxLength} chars)` };
|
|
1631
1510
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
const period = pluckNumber(simple, "period");
|
|
1638
|
-
if (!name || !namespace_id || limit === null || period !== 10 && period !== 60) continue;
|
|
1639
|
-
out.push({ type: "ratelimit", name, namespace_id, simple: { limit, period } });
|
|
1511
|
+
if (!APP_NAME_RULES.pattern.test(name)) {
|
|
1512
|
+
return {
|
|
1513
|
+
valid: false,
|
|
1514
|
+
reason: `App name "${name}" is invalid. Use lowercase letters, digits, and single hyphens only (must start + end with alphanumeric). Examples: my-app, search-v2, unison-search.`
|
|
1515
|
+
};
|
|
1640
1516
|
}
|
|
1641
|
-
return
|
|
1642
|
-
}
|
|
1643
|
-
function isObject(v) {
|
|
1644
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
1645
|
-
}
|
|
1646
|
-
function pluckString(v, key) {
|
|
1647
|
-
if (!isObject(v)) return null;
|
|
1648
|
-
const val = v[key];
|
|
1649
|
-
return typeof val === "string" && val.length > 0 ? val : null;
|
|
1517
|
+
return { valid: true, name };
|
|
1650
1518
|
}
|
|
1651
|
-
function
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1519
|
+
function resolveAppName(raw) {
|
|
1520
|
+
const strict = validateAppName(raw);
|
|
1521
|
+
if (strict.valid) return { ok: true, name: strict.name };
|
|
1522
|
+
if (typeof raw !== "string") return { ok: false, reason: strict.reason };
|
|
1523
|
+
const trimmed = raw.trim();
|
|
1524
|
+
if (!trimmed) return { ok: false, reason: "App name is required" };
|
|
1525
|
+
const sanitized = sanitizeAppName(trimmed);
|
|
1526
|
+
const sanitizedStrict = validateAppName(sanitized);
|
|
1527
|
+
if (!sanitizedStrict.valid) {
|
|
1528
|
+
return {
|
|
1529
|
+
ok: false,
|
|
1530
|
+
reason: `App name "${trimmed}" can't be sanitized into a valid name (${sanitizedStrict.reason}). Pick something matching: lowercase letters, digits, single hyphens, ${APP_NAME_RULES.minLength}-${APP_NAME_RULES.maxLength} chars.`
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
return {
|
|
1534
|
+
ok: true,
|
|
1535
|
+
name: sanitized,
|
|
1536
|
+
warning: `App name "${trimmed}" doesn't conform to deploy rules \u2014 using "${sanitized}" instead. Update wrangler.toml's \`name\` to silence this warning.`
|
|
1537
|
+
};
|
|
1655
1538
|
}
|
|
1656
|
-
function
|
|
1657
|
-
|
|
1658
|
-
return v.filter(isObject);
|
|
1539
|
+
function sanitizeAppName(raw) {
|
|
1540
|
+
return raw.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
1659
1541
|
}
|
|
1660
1542
|
|
|
1661
|
-
// src/cli/lib/
|
|
1662
|
-
var
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
"
|
|
1666
|
-
"
|
|
1543
|
+
// src/cli/lib/wrangler-env.ts
|
|
1544
|
+
var NON_INHERITABLE_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
1545
|
+
"define",
|
|
1546
|
+
"vars",
|
|
1547
|
+
"durable_objects",
|
|
1548
|
+
"kv_namespaces",
|
|
1549
|
+
"r2_buckets",
|
|
1550
|
+
"ai_search_namespaces",
|
|
1551
|
+
"ai_search",
|
|
1552
|
+
"ratelimits",
|
|
1553
|
+
"vectorize",
|
|
1554
|
+
"services",
|
|
1555
|
+
"queues",
|
|
1556
|
+
"workflows",
|
|
1557
|
+
"tail_consumers",
|
|
1558
|
+
"secrets",
|
|
1559
|
+
"secrets_store_secrets"
|
|
1667
1560
|
]);
|
|
1668
|
-
var
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
if (!CONFIG_NAME_RE.test(configName)) {
|
|
1673
|
-
throw new Error(
|
|
1674
|
-
`Invalid config "${configName}". Use letters, numbers, underscores, or hyphens; start with a letter or number.`
|
|
1675
|
-
);
|
|
1561
|
+
var generatedConfigSeq = 0;
|
|
1562
|
+
function resolveAppNameForEnv(config, envName) {
|
|
1563
|
+
if (!envName) {
|
|
1564
|
+
return resolveAppName(config.name);
|
|
1676
1565
|
}
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
);
|
|
1566
|
+
const envBlock = config.env?.[envName];
|
|
1567
|
+
if (!envBlock) {
|
|
1568
|
+
return {
|
|
1569
|
+
ok: false,
|
|
1570
|
+
reason: `wrangler.toml: no [env.${envName}] block found. Add one with a distinct \`name\` to deploy this environment.`
|
|
1571
|
+
};
|
|
1684
1572
|
}
|
|
1685
|
-
if (
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1573
|
+
if (typeof envBlock.name !== "string" || envBlock.name.trim() === "") {
|
|
1574
|
+
return {
|
|
1575
|
+
ok: false,
|
|
1576
|
+
reason: `wrangler.toml: [env.${envName}].name is missing. Set a distinct app name (e.g. "${typeof config.name === "string" ? config.name : "myapp"}-${envName}") so this environment doesn't overwrite the default.`
|
|
1577
|
+
};
|
|
1689
1578
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1579
|
+
if (envBlock.name === config.name) {
|
|
1580
|
+
return {
|
|
1581
|
+
ok: false,
|
|
1582
|
+
reason: `wrangler.toml: [env.${envName}].name "${envBlock.name}" is the same as the top-level name. Pick a distinct name (e.g. "${config.name}-${envName}") so deploys don't collide.`
|
|
1583
|
+
};
|
|
1695
1584
|
}
|
|
1585
|
+
return resolveAppName(envBlock.name);
|
|
1696
1586
|
}
|
|
1697
|
-
function
|
|
1698
|
-
return
|
|
1587
|
+
function devVarsPathFor(appDir) {
|
|
1588
|
+
return join7(appDir, ".dev.vars");
|
|
1699
1589
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1590
|
+
function prepareWranglerEnvConfig(appDir, envName) {
|
|
1591
|
+
if (!envName) return { cleanup() {
|
|
1592
|
+
} };
|
|
1593
|
+
const config = readWranglerConfig(appDir);
|
|
1594
|
+
const resolved = resolveAppNameForEnv(config, envName);
|
|
1595
|
+
if (!resolved.ok) throw new Error(resolved.reason);
|
|
1596
|
+
const flattened = flattenWranglerEnvConfig(config, envName);
|
|
1597
|
+
const configPath = join7(
|
|
1598
|
+
appDir,
|
|
1599
|
+
`.wrangler.deepspace.${safeGeneratedConfigSegment(envName)}.${process.pid}.${++generatedConfigSeq}.toml`
|
|
1600
|
+
);
|
|
1601
|
+
writeFileSync6(
|
|
1602
|
+
configPath,
|
|
1603
|
+
[
|
|
1604
|
+
"# Generated by DeepSpace for this CLI run.",
|
|
1605
|
+
"# Do not edit; wrangler.toml remains the source of truth.",
|
|
1606
|
+
stringifyToml(flattened)
|
|
1607
|
+
].join("\n").replace(/\s*$/u, "\n")
|
|
1608
|
+
);
|
|
1609
|
+
return {
|
|
1610
|
+
configPath,
|
|
1611
|
+
cleanup() {
|
|
1612
|
+
rmSync2(configPath, { force: true });
|
|
1707
1613
|
}
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
function wranglerViteEnv(baseEnv, prepared, extraEnv = {}) {
|
|
1617
|
+
const env = { ...baseEnv, ...extraEnv };
|
|
1618
|
+
if (prepared.configPath) {
|
|
1619
|
+
delete env.CLOUDFLARE_ENV;
|
|
1620
|
+
env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH = prepared.configPath;
|
|
1715
1621
|
}
|
|
1716
|
-
return
|
|
1622
|
+
return env;
|
|
1717
1623
|
}
|
|
1718
|
-
function
|
|
1719
|
-
|
|
1624
|
+
function flattenWranglerEnvConfig(config, envName) {
|
|
1625
|
+
const envRoot = asTomlRecord(config.env);
|
|
1626
|
+
const envBlock = asTomlRecord(envRoot?.[envName]);
|
|
1627
|
+
if (!envBlock) {
|
|
1628
|
+
throw new Error(`wrangler.toml: no [env.${envName}] block found.`);
|
|
1629
|
+
}
|
|
1630
|
+
const flattened = {};
|
|
1631
|
+
for (const [key, value] of Object.entries(config)) {
|
|
1632
|
+
if (key === "env" || NON_INHERITABLE_ENV_KEYS.has(key)) continue;
|
|
1633
|
+
flattened[key] = value;
|
|
1634
|
+
}
|
|
1635
|
+
for (const [key, value] of Object.entries(envBlock)) {
|
|
1636
|
+
flattened[key] = value;
|
|
1637
|
+
}
|
|
1638
|
+
if (!("durable_objects" in envBlock) && config.durable_objects) {
|
|
1639
|
+
flattened.durable_objects = config.durable_objects;
|
|
1640
|
+
}
|
|
1641
|
+
const envVars = asTomlRecord(envBlock.vars) ?? {};
|
|
1642
|
+
if (!("APP_NAME" in envVars) && typeof envBlock.name === "string") {
|
|
1643
|
+
flattened.vars = { ...envVars, APP_NAME: envBlock.name };
|
|
1644
|
+
}
|
|
1645
|
+
return flattened;
|
|
1720
1646
|
}
|
|
1721
|
-
function
|
|
1722
|
-
return
|
|
1647
|
+
function asTomlRecord(value) {
|
|
1648
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1723
1649
|
}
|
|
1724
|
-
function
|
|
1725
|
-
return
|
|
1726
|
-
deployUrl,
|
|
1727
|
-
token,
|
|
1728
|
-
`/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}?plain=true`
|
|
1729
|
-
);
|
|
1650
|
+
function safeGeneratedConfigSegment(value) {
|
|
1651
|
+
return value.replace(/[^A-Za-z0-9._-]/gu, "_");
|
|
1730
1652
|
}
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1653
|
+
var WranglerConfigError = class extends Error {
|
|
1654
|
+
constructor(path, cause) {
|
|
1655
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
1656
|
+
super(`wrangler.toml: malformed TOML at ${path}: ${detail}`);
|
|
1657
|
+
this.path = path;
|
|
1658
|
+
this.cause = cause;
|
|
1659
|
+
this.name = "WranglerConfigError";
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
function readWranglerConfig(appDir) {
|
|
1663
|
+
const path = join7(appDir, "wrangler.toml");
|
|
1664
|
+
const raw = readFileSync9(path, "utf-8");
|
|
1665
|
+
try {
|
|
1666
|
+
return parseToml3(raw);
|
|
1667
|
+
} catch (err) {
|
|
1668
|
+
throw new WranglerConfigError(path, err);
|
|
1669
|
+
}
|
|
1738
1670
|
}
|
|
1739
|
-
function
|
|
1740
|
-
return
|
|
1741
|
-
deployUrl,
|
|
1742
|
-
token,
|
|
1743
|
-
`/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}`,
|
|
1744
|
-
{ method: "DELETE" }
|
|
1745
|
-
);
|
|
1671
|
+
function hasWranglerConfig(appDir) {
|
|
1672
|
+
return existsSync6(join7(appDir, "wrangler.toml"));
|
|
1746
1673
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1674
|
+
|
|
1675
|
+
// src/cli/lib/dev-vars.ts
|
|
1676
|
+
var HEADER = [
|
|
1677
|
+
"# Generated by deepspace \u2014 do not edit; this file is rewritten on every",
|
|
1678
|
+
"# dev, test, deploy, and secrets pull run. App values live in the secret store:",
|
|
1679
|
+
"# npx deepspace secrets set KEY=value"
|
|
1680
|
+
].join("\n");
|
|
1681
|
+
function requireAppIdFor(appDir, wranglerEnv) {
|
|
1682
|
+
const id = readAppId(appDir, wranglerEnv);
|
|
1683
|
+
if (id) return id;
|
|
1684
|
+
throw new Error(
|
|
1685
|
+
"wrangler.toml has no DEEPSPACE_APP_ID" + (wranglerEnv ? ` for [env.${wranglerEnv}]` : "") + ". Run `deepspace app init` (or `deepspace app init --env <name>`) and retry."
|
|
1753
1686
|
);
|
|
1754
1687
|
}
|
|
1755
|
-
function
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1688
|
+
async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {}) {
|
|
1689
|
+
const urls = PLATFORM_URLS;
|
|
1690
|
+
const publicKey = await fetchPublicKey(urls.auth);
|
|
1691
|
+
const appId = opts.appId ?? requireAppIdFor(appDir, wranglerEnv);
|
|
1692
|
+
const appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt, appId);
|
|
1693
|
+
const appIdentityToken = await fetchAppIdentityToken(urls.deploy, callerJwt, appId);
|
|
1694
|
+
const sdkVars = [
|
|
1695
|
+
`AUTH_JWT_PUBLIC_KEY="${publicKey}"`,
|
|
1696
|
+
`AUTH_JWT_ISSUER=${urls.auth}/api/auth`,
|
|
1697
|
+
`AUTH_WORKER_URL=${urls.auth}`,
|
|
1698
|
+
`API_WORKER_URL=${urls.api}`,
|
|
1699
|
+
`PLATFORM_WORKER_URL=${urls.platform}`,
|
|
1700
|
+
`OWNER_USER_ID=${ownerId}`,
|
|
1701
|
+
`APP_OWNER_JWT=${appOwnerJwt}`,
|
|
1702
|
+
...appIdentityToken ? [`APP_IDENTITY_TOKEN=${appIdentityToken}`] : [],
|
|
1703
|
+
`ALLOW_DEBUG_ROUTES=true`
|
|
1704
|
+
].join("\n");
|
|
1705
|
+
const devVarsPath = devVarsPathFor(appDir);
|
|
1706
|
+
const cache = opts.generatedSecretsCache;
|
|
1707
|
+
writeSecretFileSync(
|
|
1708
|
+
devVarsPath,
|
|
1709
|
+
[HEADER, "", sdkVars, ...cache ? ["", cache] : [], ""].join("\n")
|
|
1710
|
+
);
|
|
1768
1711
|
}
|
|
1769
|
-
|
|
1712
|
+
|
|
1713
|
+
// src/cli/lib/install-status.ts
|
|
1714
|
+
import { existsSync as existsSync8, readFileSync as readFileSync11, realpathSync as realpathSync3 } from "fs";
|
|
1715
|
+
import { dirname as dirname5, join as join9, resolve as resolve6 } from "path";
|
|
1716
|
+
|
|
1717
|
+
// src/cli/lib/package-manager.ts
|
|
1718
|
+
import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
|
|
1719
|
+
import { join as join8 } from "path";
|
|
1720
|
+
function detectPackageManager(appDir) {
|
|
1770
1721
|
try {
|
|
1771
|
-
const
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
200,
|
|
1776
|
-
"invalid_response",
|
|
1777
|
-
`/${appId}/configs/${configName}/values`
|
|
1778
|
-
);
|
|
1779
|
-
}
|
|
1780
|
-
return { appId, configName, values: secrets };
|
|
1781
|
-
} catch (err) {
|
|
1782
|
-
if (err?.status === 404) return null;
|
|
1783
|
-
throw err;
|
|
1722
|
+
const pkg2 = JSON.parse(readFileSync10(join8(appDir, "package.json"), "utf-8"));
|
|
1723
|
+
const name = typeof pkg2.packageManager === "string" ? pkg2.packageManager.split("@")[0] : "";
|
|
1724
|
+
if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") return name;
|
|
1725
|
+
} catch {
|
|
1784
1726
|
}
|
|
1727
|
+
if (existsSync7(join8(appDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
1728
|
+
if (existsSync7(join8(appDir, "yarn.lock"))) return "yarn";
|
|
1729
|
+
if (existsSync7(join8(appDir, "bun.lock")) || existsSync7(join8(appDir, "bun.lockb"))) return "bun";
|
|
1730
|
+
return "npm";
|
|
1785
1731
|
}
|
|
1786
|
-
function
|
|
1787
|
-
|
|
1788
|
-
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
1789
|
-
return `"${escaped}"`;
|
|
1732
|
+
function installCommand(appDir) {
|
|
1733
|
+
return `${detectPackageManager(appDir)} install`;
|
|
1790
1734
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1735
|
+
|
|
1736
|
+
// src/cli/lib/install-status.ts
|
|
1737
|
+
function installState(appDir) {
|
|
1738
|
+
if (resolvesDeepspace(appDir)) return "ready";
|
|
1739
|
+
const sentinelDir = join9(appDir, ".deepspace");
|
|
1740
|
+
if (existsSync8(join9(sentinelDir, "install.err"))) return "failed";
|
|
1741
|
+
if (existsSync8(join9(sentinelDir, "install.started"))) {
|
|
1742
|
+
if (!existsSync8(join9(sentinelDir, "install.done")) && installWorkerAlive(sentinelDir)) {
|
|
1743
|
+
return "installing";
|
|
1744
|
+
}
|
|
1745
|
+
return "failed";
|
|
1801
1746
|
}
|
|
1802
|
-
return
|
|
1747
|
+
return "missing";
|
|
1803
1748
|
}
|
|
1804
|
-
function
|
|
1805
|
-
|
|
1806
|
-
if (idx === -1) return content;
|
|
1807
|
-
return content.slice(0, idx).trimEnd() + "\n";
|
|
1749
|
+
function tailHint(logPath, platform = process.platform) {
|
|
1750
|
+
return platform === "win32" ? `Get-Content -Wait ${logPath}` : `tail -f ${logPath}`;
|
|
1808
1751
|
}
|
|
1809
|
-
function
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
const
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
return out2;
|
|
1752
|
+
function ensureInstallReady(appDir) {
|
|
1753
|
+
if (resolvesDeepspace(appDir)) return;
|
|
1754
|
+
const sentinelDir = join9(appDir, ".deepspace");
|
|
1755
|
+
const errPath = join9(sentinelDir, "install.err");
|
|
1756
|
+
const startedPath = join9(sentinelDir, "install.started");
|
|
1757
|
+
const donePath = join9(sentinelDir, "install.done");
|
|
1758
|
+
const logPath = join9(sentinelDir, "install.log");
|
|
1759
|
+
if (existsSync8(errPath)) {
|
|
1760
|
+
const detail = readFileSync11(errPath, "utf-8").trimEnd();
|
|
1761
|
+
const log29 = existsSync8(logPath) ? ` Full log: ${logPath}.` : "";
|
|
1762
|
+
throw new Refusal(
|
|
1763
|
+
`Dependency install failed: ${detail}.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
|
|
1764
|
+
"install_failed",
|
|
1765
|
+
{ action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
|
|
1766
|
+
);
|
|
1825
1767
|
}
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
if (!SECRET_NAME_RE.test(key)) continue;
|
|
1835
|
-
validateSecretName(key);
|
|
1836
|
-
const raw = line.slice(eq + 1).trim();
|
|
1837
|
-
const quote = raw[0] === '"' || raw[0] === "'" ? raw[0] : null;
|
|
1838
|
-
let value;
|
|
1839
|
-
if (!quote) {
|
|
1840
|
-
value = raw;
|
|
1841
|
-
} else if (endsWithUnescapedQuote(raw.slice(1), quote)) {
|
|
1842
|
-
const inner = raw.slice(1, -1);
|
|
1843
|
-
value = quote === '"' ? unescapeDotenv(inner) : inner;
|
|
1844
|
-
} else {
|
|
1845
|
-
const startLine = i + 1;
|
|
1846
|
-
const parts = [raw.slice(1)];
|
|
1847
|
-
let closed = false;
|
|
1848
|
-
while (++i < lines.length) {
|
|
1849
|
-
const cont = lines[i].trimEnd();
|
|
1850
|
-
if (endsWithUnescapedQuote(cont, quote)) {
|
|
1851
|
-
parts.push(cont.slice(0, -1));
|
|
1852
|
-
closed = true;
|
|
1853
|
-
break;
|
|
1854
|
-
}
|
|
1855
|
-
parts.push(lines[i]);
|
|
1856
|
-
}
|
|
1857
|
-
if (!closed) {
|
|
1858
|
-
throw new Error(
|
|
1859
|
-
`Unterminated quoted value for "${key}" (opens on line ${startLine}). Close the ${quote === '"' ? "double" : "single"} quote, or escape newlines as \\n on one line.`
|
|
1860
|
-
);
|
|
1861
|
-
}
|
|
1862
|
-
value = parts.join("\n");
|
|
1863
|
-
if (quote === '"') value = unescapeDotenv(value);
|
|
1768
|
+
if (existsSync8(startedPath) && !existsSync8(donePath)) {
|
|
1769
|
+
if (!installWorkerAlive(sentinelDir)) {
|
|
1770
|
+
const log29 = existsSync8(logPath) ? ` See what happened: ${logPath}.` : "";
|
|
1771
|
+
throw new Refusal(
|
|
1772
|
+
`The dependency install is no longer running and never finished.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
|
|
1773
|
+
"install_failed",
|
|
1774
|
+
{ action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
|
|
1775
|
+
);
|
|
1864
1776
|
}
|
|
1865
|
-
|
|
1866
|
-
|
|
1777
|
+
const tail = existsSync8(logPath) ? ` Tail progress: ${tailHint(logPath)}.` : "";
|
|
1778
|
+
throw new Refusal(
|
|
1779
|
+
`Dependencies are still installing.${tail} Retry once it finishes.`,
|
|
1780
|
+
"install_in_progress"
|
|
1781
|
+
);
|
|
1867
1782
|
}
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
let backslashes = 0;
|
|
1874
|
-
for (let j = s.length - 2; j >= 0 && s[j] === "\\"; j--) backslashes++;
|
|
1875
|
-
return backslashes % 2 === 0;
|
|
1876
|
-
}
|
|
1877
|
-
function unescapeDotenv(v) {
|
|
1878
|
-
let out = "";
|
|
1879
|
-
for (let i = 0; i < v.length; i++) {
|
|
1880
|
-
if (v[i] === "\\" && i + 1 < v.length) {
|
|
1881
|
-
const c = v[++i];
|
|
1882
|
-
out += c === "n" ? "\n" : c === "r" ? "\r" : c;
|
|
1883
|
-
} else {
|
|
1884
|
-
out += v[i];
|
|
1783
|
+
throw new Refusal(
|
|
1784
|
+
"Dependencies not installed. Run `npm install` (or `bun install`) first.",
|
|
1785
|
+
"deps_missing",
|
|
1786
|
+
{
|
|
1787
|
+
action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] }
|
|
1885
1788
|
}
|
|
1886
|
-
|
|
1887
|
-
return out;
|
|
1888
|
-
}
|
|
1889
|
-
function shellSingleQuote(value) {
|
|
1890
|
-
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
1789
|
+
);
|
|
1891
1790
|
}
|
|
1892
|
-
function
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1791
|
+
function resolvesDeepspace(fromDir) {
|
|
1792
|
+
let dir = canonicalPath2(fromDir);
|
|
1793
|
+
let checkoutRoot = null;
|
|
1794
|
+
try {
|
|
1795
|
+
checkoutRoot = canonicalPath2(repoToplevel(dir));
|
|
1796
|
+
} catch {
|
|
1896
1797
|
}
|
|
1897
|
-
|
|
1898
|
-
|
|
1798
|
+
for (; ; ) {
|
|
1799
|
+
if (existsSync8(join9(dir, "node_modules", "deepspace", "package.json"))) return true;
|
|
1800
|
+
if (dir === checkoutRoot) return false;
|
|
1801
|
+
const parent = dirname5(dir);
|
|
1802
|
+
if (parent === dir) return false;
|
|
1803
|
+
dir = parent;
|
|
1899
1804
|
}
|
|
1900
|
-
return entries.map(([k, v]) => `${k}=${quoteDotenvValue(v)}`).join("\n") + "\n";
|
|
1901
|
-
}
|
|
1902
|
-
async function refreshSecretsCache(deployUrl, token, appId, wranglerEnv) {
|
|
1903
|
-
const configName = defaultConfigNameForEnv(wranglerEnv);
|
|
1904
|
-
const pulled = await pullAppSecretsCache(deployUrl, token, appId, configName);
|
|
1905
|
-
if (!pulled) return null;
|
|
1906
|
-
const count = Object.keys(pulled.values).length;
|
|
1907
|
-
return {
|
|
1908
|
-
pulled,
|
|
1909
|
-
rendered: renderSecretsCache(pulled.values, pulled),
|
|
1910
|
-
// An empty store is the common case and not worth a status line.
|
|
1911
|
-
summary: count ? `Secrets: ${pulled.configName} (${count} secret${count === 1 ? "" : "s"})` : null
|
|
1912
|
-
};
|
|
1913
1805
|
}
|
|
1914
|
-
|
|
1915
|
-
// src/cli/lib/secure-file.ts
|
|
1916
|
-
import { chmodSync, writeFileSync as writeFileSync5 } from "fs";
|
|
1917
|
-
function writeSecretFileSync(path, content) {
|
|
1918
|
-
writeFileSync5(path, content, { mode: 384 });
|
|
1806
|
+
function canonicalPath2(path) {
|
|
1919
1807
|
try {
|
|
1920
|
-
|
|
1808
|
+
return realpathSync3(path);
|
|
1921
1809
|
} catch {
|
|
1810
|
+
return resolve6(path);
|
|
1922
1811
|
}
|
|
1923
1812
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
minLength: 2,
|
|
1935
|
-
maxLength: 63
|
|
1936
|
-
};
|
|
1937
|
-
function validateAppName(raw) {
|
|
1938
|
-
if (typeof raw !== "string") {
|
|
1939
|
-
return { valid: false, reason: "App name must be a string" };
|
|
1940
|
-
}
|
|
1941
|
-
const name = raw.trim();
|
|
1942
|
-
if (!name) {
|
|
1943
|
-
return { valid: false, reason: "App name is required" };
|
|
1944
|
-
}
|
|
1945
|
-
if (name.length < APP_NAME_RULES.minLength) {
|
|
1946
|
-
return { valid: false, reason: `App name "${name}" is too short (min ${APP_NAME_RULES.minLength} chars)` };
|
|
1947
|
-
}
|
|
1948
|
-
if (name.length > APP_NAME_RULES.maxLength) {
|
|
1949
|
-
return { valid: false, reason: `App name "${name}" is too long (max ${APP_NAME_RULES.maxLength} chars)` };
|
|
1950
|
-
}
|
|
1951
|
-
if (!APP_NAME_RULES.pattern.test(name)) {
|
|
1952
|
-
return {
|
|
1953
|
-
valid: false,
|
|
1954
|
-
reason: `App name "${name}" is invalid. Use lowercase letters, digits, and single hyphens only (must start + end with alphanumeric). Examples: my-app, search-v2, unison-search.`
|
|
1955
|
-
};
|
|
1813
|
+
function installWorkerAlive(sentinelDir) {
|
|
1814
|
+
const pidPath = join9(sentinelDir, "install.pid");
|
|
1815
|
+
if (!existsSync8(pidPath)) return false;
|
|
1816
|
+
const pid = Number(readFileSync11(pidPath, "utf-8").trim());
|
|
1817
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
1818
|
+
try {
|
|
1819
|
+
process.kill(pid, 0);
|
|
1820
|
+
return true;
|
|
1821
|
+
} catch (err) {
|
|
1822
|
+
return err.code === "EPERM";
|
|
1956
1823
|
}
|
|
1957
|
-
return { valid: true, name };
|
|
1958
1824
|
}
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
const
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1825
|
+
|
|
1826
|
+
// src/cli/lib/preflight.ts
|
|
1827
|
+
import { execFileSync } from "child_process";
|
|
1828
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1829
|
+
import * as nodeModule from "module";
|
|
1830
|
+
import { resolve as resolve7 } from "path";
|
|
1831
|
+
function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.registerHooks === "function", version = process.versions.node) {
|
|
1832
|
+
const [major, minor] = version.split(".").map(Number);
|
|
1833
|
+
const supportedLine = major === 22 && minor >= 15 || major === 24 || major === 26;
|
|
1834
|
+
if (hasRegisterHooks && supportedLine) return;
|
|
1835
|
+
throw new Refusal(
|
|
1836
|
+
`deepspace ${command} requires a supported Node release: 22.15+, 24, or 26 (found v${version}). Install the current LTS from https://nodejs.org and re-run.`,
|
|
1837
|
+
supportedLine ? "node_capability_missing" : "node_unsupported"
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
var DLL_NOT_FOUND = -1073741515;
|
|
1841
|
+
function preflightWindowsWorkerd(appDir) {
|
|
1842
|
+
if (process.platform !== "win32") return;
|
|
1843
|
+
const workerdExe = resolve7(
|
|
1844
|
+
appDir,
|
|
1845
|
+
"node_modules",
|
|
1846
|
+
"@cloudflare",
|
|
1847
|
+
"workerd-windows-64",
|
|
1848
|
+
"bin",
|
|
1849
|
+
"workerd.exe"
|
|
1850
|
+
);
|
|
1851
|
+
if (!existsSync9(workerdExe)) return;
|
|
1852
|
+
try {
|
|
1853
|
+
execFileSync(workerdExe, ["--help"], { stdio: "pipe", timeout: 1e4 });
|
|
1854
|
+
return;
|
|
1855
|
+
} catch (err) {
|
|
1856
|
+
const status3 = err.status;
|
|
1857
|
+
if (status3 !== DLL_NOT_FOUND) return;
|
|
1858
|
+
throw new Refusal(
|
|
1859
|
+
"workerd (Cloudflare Workers runtime) cannot start on this system: it needs the Microsoft Visual C++ Redistributable, which is not installed. Install it with `winget install Microsoft.VCRedist.2015+.x64` (or choco install -y vcredist140, or https://aka.ms/vs/17/release/vc_redist.x64.exe), then restart this terminal and re-run.",
|
|
1860
|
+
"vcredist_missing"
|
|
1861
|
+
);
|
|
1972
1862
|
}
|
|
1973
|
-
return {
|
|
1974
|
-
ok: true,
|
|
1975
|
-
name: sanitized,
|
|
1976
|
-
warning: `App name "${trimmed}" doesn't conform to deploy rules \u2014 using "${sanitized}" instead. Update wrangler.toml's \`name\` to silence this warning.`
|
|
1977
|
-
};
|
|
1978
1863
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
1864
|
+
|
|
1865
|
+
// src/cli/lib/macos-junk.ts
|
|
1866
|
+
import { readdirSync as readdirSync2, rmSync as rmSync3 } from "fs";
|
|
1867
|
+
import { join as join10 } from "path";
|
|
1868
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".wrangler", ".vite"]);
|
|
1869
|
+
function removeMacosJunk(dir) {
|
|
1870
|
+
let removed = 0;
|
|
1871
|
+
let entries;
|
|
1872
|
+
try {
|
|
1873
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
1874
|
+
} catch {
|
|
1875
|
+
return removed;
|
|
1876
|
+
}
|
|
1877
|
+
for (const entry of entries) {
|
|
1878
|
+
const path = join10(dir, entry.name);
|
|
1879
|
+
if (entry.isDirectory()) {
|
|
1880
|
+
if (!SKIP_DIRS.has(entry.name)) removed += removeMacosJunk(path);
|
|
1881
|
+
continue;
|
|
1882
|
+
}
|
|
1883
|
+
if (entry.name === ".DS_Store" || entry.name.startsWith("._")) {
|
|
1884
|
+
try {
|
|
1885
|
+
rmSync3(path, { force: true });
|
|
1886
|
+
removed++;
|
|
1887
|
+
} catch {
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
return removed;
|
|
1981
1892
|
}
|
|
1982
1893
|
|
|
1983
|
-
// src/
|
|
1984
|
-
var
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
"durable_objects",
|
|
1988
|
-
"kv_namespaces",
|
|
1989
|
-
"r2_buckets",
|
|
1990
|
-
"ai_search_namespaces",
|
|
1991
|
-
"ai_search",
|
|
1992
|
-
"ratelimits",
|
|
1894
|
+
// src/server/rooms/binding-manifest.ts
|
|
1895
|
+
var AUTO_PROVISION_SENTINEL = "auto";
|
|
1896
|
+
var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
|
|
1897
|
+
var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
|
|
1993
1898
|
"vectorize",
|
|
1994
|
-
"
|
|
1995
|
-
"
|
|
1996
|
-
"
|
|
1997
|
-
"
|
|
1998
|
-
"
|
|
1999
|
-
"
|
|
1899
|
+
"ai",
|
|
1900
|
+
"r2_bucket",
|
|
1901
|
+
"kv_namespace",
|
|
1902
|
+
"d1",
|
|
1903
|
+
"queue",
|
|
1904
|
+
"browser_rendering",
|
|
1905
|
+
"analytics_engine",
|
|
1906
|
+
"hyperdrive",
|
|
1907
|
+
"ratelimit"
|
|
2000
1908
|
]);
|
|
2001
|
-
var
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
1909
|
+
var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
1910
|
+
"ASSETS",
|
|
1911
|
+
"PLATFORM_WORKER",
|
|
1912
|
+
"API_WORKER",
|
|
1913
|
+
"DEEPSPACE_APP_ID",
|
|
1914
|
+
"DEEPSPACE_RESOURCE_ID",
|
|
1915
|
+
"DEEPSPACE_RELEASE_OPERATION_ID",
|
|
1916
|
+
"APP_NAME",
|
|
1917
|
+
"OWNER_USER_ID",
|
|
1918
|
+
"AUTH_JWT_PUBLIC_KEY",
|
|
1919
|
+
"AUTH_JWT_ISSUER",
|
|
1920
|
+
"AUTH_WORKER_URL",
|
|
1921
|
+
"APP_IDENTITY_TOKEN",
|
|
1922
|
+
"APP_OWNER_JWT",
|
|
1923
|
+
"USAGE_EVENTS"
|
|
1924
|
+
]);
|
|
1925
|
+
function validateBindingManifest(manifest) {
|
|
1926
|
+
if (!Array.isArray(manifest)) {
|
|
1927
|
+
return { valid: false, errors: [{ reason: "Manifest must be an array" }] };
|
|
2018
1928
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
1929
|
+
const errors = [];
|
|
1930
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
1931
|
+
for (const entry of manifest) {
|
|
1932
|
+
if (!entry || typeof entry !== "object") {
|
|
1933
|
+
errors.push({
|
|
1934
|
+
reason: `Entry must be an object (got ${entry === null ? "null" : typeof entry})`
|
|
1935
|
+
});
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
const e = entry;
|
|
1939
|
+
const type = e.type;
|
|
1940
|
+
const name = e.name;
|
|
1941
|
+
if (typeof type !== "string" || !ALLOWED_BINDING_TYPES.has(type)) {
|
|
1942
|
+
errors.push({
|
|
1943
|
+
reason: `Disallowed binding type: ${typeof type === "string" ? type : `(${typeof type})`}. Allowed: ${[...ALLOWED_BINDING_TYPES].sort().join(", ")}`
|
|
1944
|
+
});
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
if (typeof name !== "string" || !name) {
|
|
1948
|
+
errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
if (RESERVED_BINDING_NAMES.has(name)) {
|
|
1952
|
+
errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
|
|
1953
|
+
continue;
|
|
1954
|
+
}
|
|
1955
|
+
if (seenNames.has(name)) {
|
|
1956
|
+
errors.push({ reason: `Duplicate binding name "${name}"` });
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1959
|
+
seenNames.add(name);
|
|
1960
|
+
const binding = entry;
|
|
1961
|
+
const fieldErr = requiredFieldError(binding);
|
|
1962
|
+
if (fieldErr) errors.push({ binding, reason: fieldErr });
|
|
2024
1963
|
}
|
|
2025
|
-
|
|
2026
|
-
}
|
|
2027
|
-
function devVarsPathFor(appDir, envName, opts = {}) {
|
|
2028
|
-
if (opts.sharedDevVarsCache || !envName) return join7(appDir, ".dev.vars");
|
|
2029
|
-
return join7(appDir, `.dev.vars.${envName}`);
|
|
2030
|
-
}
|
|
2031
|
-
function legacyEnvDevVarsPathFor(appDir, envName) {
|
|
2032
|
-
return envName ? join7(appDir, `.dev.vars.${envName}`) : void 0;
|
|
2033
|
-
}
|
|
2034
|
-
function warnIfLegacyEnvDevVarsExists(appDir, envName, warn = console.warn) {
|
|
2035
|
-
const legacyPath = legacyEnvDevVarsPathFor(appDir, envName);
|
|
2036
|
-
if (!legacyPath || !existsSync6(legacyPath)) return;
|
|
2037
|
-
warn(
|
|
2038
|
-
`Warning: ${legacyPath} exists, but DeepSpace now uses ${devVarsPathFor(appDir, envName, { sharedDevVarsCache: true })} for every environment. This run will ignore ${legacyPath}; move any secrets you still need into .dev.vars, then remove the env-specific file when you're ready.`
|
|
2039
|
-
);
|
|
1964
|
+
if (errors.length) return { valid: false, errors };
|
|
1965
|
+
return { valid: true, bindings: manifest };
|
|
2040
1966
|
}
|
|
2041
|
-
function
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
1967
|
+
function requiredFieldError(b) {
|
|
1968
|
+
switch (b.type) {
|
|
1969
|
+
case "vectorize": {
|
|
1970
|
+
if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
|
|
1971
|
+
if (b.index_name === AUTO_PROVISION_SENTINEL) {
|
|
1972
|
+
if (typeof b.dimensions !== "number" || b.dimensions <= 0) {
|
|
1973
|
+
return `vectorize binding "${b.name}" with index_name="auto" requires "dimensions" (positive number)`;
|
|
1974
|
+
}
|
|
1975
|
+
if (!b.metric || !VECTORIZE_METRICS.has(b.metric)) {
|
|
1976
|
+
return `vectorize binding "${b.name}" with index_name="auto" requires "metric" (one of: ${[...VECTORIZE_METRICS].join(", ")})`;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
return null;
|
|
1980
|
+
}
|
|
1981
|
+
case "r2_bucket":
|
|
1982
|
+
return b.bucket_name ? null : `r2_bucket binding "${b.name}" missing bucket_name (or "auto")`;
|
|
1983
|
+
case "kv_namespace": {
|
|
1984
|
+
if (!b.namespace_id)
|
|
1985
|
+
return `kv_namespace binding "${b.name}" missing namespace_id (or "auto")`;
|
|
1986
|
+
if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {
|
|
1987
|
+
return `kv_namespace binding "${b.name}" with namespace_id="auto" requires "title"`;
|
|
1988
|
+
}
|
|
1989
|
+
return null;
|
|
1990
|
+
}
|
|
1991
|
+
case "d1": {
|
|
1992
|
+
if (!b.id) return `d1 binding "${b.name}" missing id (or "auto")`;
|
|
1993
|
+
if (b.id === AUTO_PROVISION_SENTINEL && !b.database_name) {
|
|
1994
|
+
return `d1 binding "${b.name}" with id="auto" requires "database_name"`;
|
|
1995
|
+
}
|
|
1996
|
+
return null;
|
|
1997
|
+
}
|
|
1998
|
+
case "queue":
|
|
1999
|
+
return b.queue_name ? null : `queue binding "${b.name}" missing queue_name (or "auto")`;
|
|
2000
|
+
case "hyperdrive":
|
|
2001
|
+
if (!b.id) return `hyperdrive binding "${b.name}" missing id`;
|
|
2002
|
+
if (b.id === AUTO_PROVISION_SENTINEL) {
|
|
2003
|
+
return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
|
|
2004
|
+
}
|
|
2005
|
+
return null;
|
|
2006
|
+
case "ratelimit":
|
|
2007
|
+
if (!/^\d+$/u.test(b.namespace_id) || Number(b.namespace_id) <= 0) {
|
|
2008
|
+
return `ratelimit binding "${b.name}" requires a positive integer namespace_id string`;
|
|
2009
|
+
}
|
|
2010
|
+
if (!Number.isInteger(b.simple?.limit) || b.simple.limit <= 0) {
|
|
2011
|
+
return `ratelimit binding "${b.name}" requires simple.limit (positive integer)`;
|
|
2012
|
+
}
|
|
2013
|
+
if (b.simple.period !== 10 && b.simple.period !== 60) {
|
|
2014
|
+
return `ratelimit binding "${b.name}" requires simple.period (10 or 60)`;
|
|
2015
|
+
}
|
|
2016
|
+
return null;
|
|
2017
|
+
case "ai":
|
|
2018
|
+
case "browser_rendering":
|
|
2019
|
+
case "analytics_engine":
|
|
2020
|
+
return null;
|
|
2021
|
+
// no required fields beyond `name`
|
|
2022
|
+
default: {
|
|
2023
|
+
const _exhaustive = b;
|
|
2024
|
+
void _exhaustive;
|
|
2025
|
+
return null;
|
|
2067
2026
|
}
|
|
2068
|
-
};
|
|
2069
|
-
}
|
|
2070
|
-
function wranglerViteEnv(baseEnv, prepared, extraEnv = {}) {
|
|
2071
|
-
const env = { ...baseEnv, ...extraEnv };
|
|
2072
|
-
if (prepared.configPath) {
|
|
2073
|
-
delete env.CLOUDFLARE_ENV;
|
|
2074
|
-
env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH = prepared.configPath;
|
|
2075
|
-
} else if (prepared.wranglerEnv) {
|
|
2076
|
-
env.CLOUDFLARE_ENV = prepared.wranglerEnv;
|
|
2077
2027
|
}
|
|
2078
|
-
return env;
|
|
2079
2028
|
}
|
|
2080
|
-
function
|
|
2081
|
-
const
|
|
2082
|
-
const
|
|
2083
|
-
if (
|
|
2084
|
-
|
|
2029
|
+
function bindingManifestFromOutputConfig(outputConfig) {
|
|
2030
|
+
const out = [];
|
|
2031
|
+
const aiBinding = pluckString(outputConfig.ai, "binding");
|
|
2032
|
+
if (aiBinding) out.push({ type: "ai", name: aiBinding });
|
|
2033
|
+
for (const v of asObjectArray(outputConfig.vectorize)) {
|
|
2034
|
+
const name = pluckString(v, "binding");
|
|
2035
|
+
const index_name = pluckString(v, "index_name");
|
|
2036
|
+
if (!name || !index_name) continue;
|
|
2037
|
+
const dimensions = pluckNumber(v, "dimensions");
|
|
2038
|
+
const metric = pluckString(v, "metric");
|
|
2039
|
+
out.push({
|
|
2040
|
+
type: "vectorize",
|
|
2041
|
+
name,
|
|
2042
|
+
index_name,
|
|
2043
|
+
...dimensions != null && { dimensions },
|
|
2044
|
+
...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
|
|
2045
|
+
});
|
|
2085
2046
|
}
|
|
2086
|
-
const
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2047
|
+
for (const r of asObjectArray(outputConfig.r2_buckets)) {
|
|
2048
|
+
const name = pluckString(r, "binding");
|
|
2049
|
+
const bucket_name = pluckString(r, "bucket_name");
|
|
2050
|
+
if (name && bucket_name) out.push({ type: "r2_bucket", name, bucket_name });
|
|
2090
2051
|
}
|
|
2091
|
-
for (const
|
|
2092
|
-
|
|
2052
|
+
for (const k of asObjectArray(outputConfig.kv_namespaces)) {
|
|
2053
|
+
const name = pluckString(k, "binding");
|
|
2054
|
+
const namespace_id = pluckString(k, "id");
|
|
2055
|
+
if (!name || !namespace_id) continue;
|
|
2056
|
+
const title = pluckString(k, "title");
|
|
2057
|
+
out.push({
|
|
2058
|
+
type: "kv_namespace",
|
|
2059
|
+
name,
|
|
2060
|
+
namespace_id,
|
|
2061
|
+
...title && { title }
|
|
2062
|
+
});
|
|
2093
2063
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2064
|
+
for (const d of asObjectArray(outputConfig.d1_databases)) {
|
|
2065
|
+
const name = pluckString(d, "binding");
|
|
2066
|
+
const id = pluckString(d, "database_id");
|
|
2067
|
+
if (!name || !id) continue;
|
|
2068
|
+
const database_name = pluckString(d, "database_name");
|
|
2069
|
+
out.push({
|
|
2070
|
+
type: "d1",
|
|
2071
|
+
name,
|
|
2072
|
+
id,
|
|
2073
|
+
...database_name && { database_name }
|
|
2074
|
+
});
|
|
2096
2075
|
}
|
|
2097
|
-
const
|
|
2098
|
-
|
|
2099
|
-
|
|
2076
|
+
const queues = isObject(outputConfig.queues) ? outputConfig.queues : null;
|
|
2077
|
+
for (const q of asObjectArray(queues?.producers)) {
|
|
2078
|
+
const name = pluckString(q, "binding");
|
|
2079
|
+
const queue_name = pluckString(q, "queue");
|
|
2080
|
+
if (name && queue_name) out.push({ type: "queue", name, queue_name });
|
|
2100
2081
|
}
|
|
2101
|
-
|
|
2082
|
+
const browserBinding = pluckString(outputConfig.browser, "binding");
|
|
2083
|
+
if (browserBinding) out.push({ type: "browser_rendering", name: browserBinding });
|
|
2084
|
+
for (const a of asObjectArray(outputConfig.analytics_engine_datasets)) {
|
|
2085
|
+
const name = pluckString(a, "binding");
|
|
2086
|
+
const dataset = pluckString(a, "dataset");
|
|
2087
|
+
if (name) out.push({ type: "analytics_engine", name, dataset: dataset ?? void 0 });
|
|
2088
|
+
}
|
|
2089
|
+
for (const h of asObjectArray(outputConfig.hyperdrive)) {
|
|
2090
|
+
const name = pluckString(h, "binding");
|
|
2091
|
+
const id = pluckString(h, "id");
|
|
2092
|
+
if (name && id) out.push({ type: "hyperdrive", name, id });
|
|
2093
|
+
}
|
|
2094
|
+
for (const r of asObjectArray(outputConfig.ratelimits)) {
|
|
2095
|
+
const name = pluckString(r, "name");
|
|
2096
|
+
const namespace_id = pluckString(r, "namespace_id");
|
|
2097
|
+
const simple = isObject(r.simple) ? r.simple : null;
|
|
2098
|
+
const limit = pluckNumber(simple, "limit");
|
|
2099
|
+
const period = pluckNumber(simple, "period");
|
|
2100
|
+
if (!name || !namespace_id || limit === null || period !== 10 && period !== 60) continue;
|
|
2101
|
+
out.push({ type: "ratelimit", name, namespace_id, simple: { limit, period } });
|
|
2102
|
+
}
|
|
2103
|
+
return out;
|
|
2102
2104
|
}
|
|
2103
|
-
function
|
|
2104
|
-
return
|
|
2105
|
+
function isObject(v) {
|
|
2106
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
2105
2107
|
}
|
|
2106
|
-
function
|
|
2107
|
-
|
|
2108
|
+
function pluckString(v, key) {
|
|
2109
|
+
if (!isObject(v)) return null;
|
|
2110
|
+
const val = v[key];
|
|
2111
|
+
return typeof val === "string" && val.length > 0 ? val : null;
|
|
2108
2112
|
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
this.path = path;
|
|
2114
|
-
this.cause = cause;
|
|
2115
|
-
this.name = "WranglerConfigError";
|
|
2116
|
-
}
|
|
2117
|
-
};
|
|
2118
|
-
function readWranglerConfig(appDir) {
|
|
2119
|
-
const path = join7(appDir, "wrangler.toml");
|
|
2120
|
-
const raw = readFileSync9(path, "utf-8");
|
|
2121
|
-
try {
|
|
2122
|
-
return parseToml3(raw);
|
|
2123
|
-
} catch (err) {
|
|
2124
|
-
throw new WranglerConfigError(path, err);
|
|
2125
|
-
}
|
|
2113
|
+
function pluckNumber(v, key) {
|
|
2114
|
+
if (!isObject(v)) return null;
|
|
2115
|
+
const val = v[key];
|
|
2116
|
+
return typeof val === "number" && Number.isFinite(val) ? val : null;
|
|
2126
2117
|
}
|
|
2127
|
-
function
|
|
2128
|
-
|
|
2118
|
+
function asObjectArray(v) {
|
|
2119
|
+
if (!Array.isArray(v)) return [];
|
|
2120
|
+
return v.filter(isObject);
|
|
2129
2121
|
}
|
|
2130
2122
|
|
|
2131
|
-
// src/cli/lib/
|
|
2132
|
-
var
|
|
2133
|
-
|
|
2134
|
-
"AUTH_JWT_ISSUER",
|
|
2135
|
-
"AUTH_WORKER_URL",
|
|
2123
|
+
// src/cli/lib/secrets.ts
|
|
2124
|
+
var RESERVED_SECRET_NAMES = /* @__PURE__ */ new Set([
|
|
2125
|
+
...RESERVED_BINDING_NAMES,
|
|
2136
2126
|
"API_WORKER_URL",
|
|
2137
|
-
"PLATFORM_WORKER_URL"
|
|
2138
|
-
"OWNER_USER_ID",
|
|
2139
|
-
"APP_OWNER_JWT",
|
|
2140
|
-
"APP_IDENTITY_TOKEN",
|
|
2141
|
-
"ALLOW_DEBUG_ROUTES"
|
|
2127
|
+
"PLATFORM_WORKER_URL"
|
|
2142
2128
|
]);
|
|
2143
|
-
var
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
if (line.trim() === DEV_VARS_DIVIDER.trim()) {
|
|
2152
|
-
belowDivider = true;
|
|
2153
|
-
i++;
|
|
2154
|
-
continue;
|
|
2155
|
-
}
|
|
2156
|
-
const eq = line.indexOf("=");
|
|
2157
|
-
const keyMatch = eq >= 0 ? line.slice(0, eq).match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*$/) : null;
|
|
2158
|
-
if (!keyMatch) {
|
|
2159
|
-
out.push(line);
|
|
2160
|
-
i++;
|
|
2161
|
-
continue;
|
|
2162
|
-
}
|
|
2163
|
-
const key = keyMatch[1];
|
|
2164
|
-
const valueStart = line.slice(eq + 1);
|
|
2165
|
-
const block = [line];
|
|
2166
|
-
if (valueStart.startsWith('"') && !hasUnescapedQuote(valueStart.slice(1))) {
|
|
2167
|
-
i++;
|
|
2168
|
-
while (i < lines.length) {
|
|
2169
|
-
block.push(lines[i]);
|
|
2170
|
-
const closed = hasUnescapedQuote(lines[i]);
|
|
2171
|
-
i++;
|
|
2172
|
-
if (closed) break;
|
|
2173
|
-
}
|
|
2174
|
-
} else {
|
|
2175
|
-
i++;
|
|
2176
|
-
}
|
|
2177
|
-
if (belowDivider || !SDK_MANAGED_KEYS.has(key)) out.push(...block);
|
|
2129
|
+
var SECRET_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2130
|
+
var CONFIG_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
2131
|
+
var MAX_SECRET_VALUE_BYTES = 32 * 1024;
|
|
2132
|
+
function validateConfigName(configName) {
|
|
2133
|
+
if (!CONFIG_NAME_RE.test(configName)) {
|
|
2134
|
+
throw new Error(
|
|
2135
|
+
`Invalid config "${configName}". Use letters, numbers, underscores, or hyphens; start with a letter or number.`
|
|
2136
|
+
);
|
|
2178
2137
|
}
|
|
2179
|
-
|
|
2180
|
-
return out.join("\n");
|
|
2138
|
+
return configName;
|
|
2181
2139
|
}
|
|
2182
|
-
function
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
continue;
|
|
2188
|
-
}
|
|
2189
|
-
if (char === "\\") {
|
|
2190
|
-
escaped = true;
|
|
2191
|
-
continue;
|
|
2192
|
-
}
|
|
2193
|
-
if (char === '"') return true;
|
|
2140
|
+
function validateSecretName(name) {
|
|
2141
|
+
if (!SECRET_NAME_RE.test(name)) {
|
|
2142
|
+
throw new Error(
|
|
2143
|
+
`Invalid secret name "${name}". Use letters, numbers, and underscores; start with a letter or underscore.`
|
|
2144
|
+
);
|
|
2194
2145
|
}
|
|
2195
|
-
|
|
2146
|
+
if (RESERVED_SECRET_NAMES.has(name)) {
|
|
2147
|
+
throw new Error(
|
|
2148
|
+
`"${name}" is managed by the DeepSpace SDK and can't be set as an app secret \u2014 the platform injects it at deploy time.`
|
|
2149
|
+
);
|
|
2150
|
+
}
|
|
2151
|
+
return name;
|
|
2196
2152
|
}
|
|
2197
|
-
function
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
let i = 0;
|
|
2201
|
-
while (i < lines.length) {
|
|
2202
|
-
const line = lines[i];
|
|
2203
|
-
const trimmed = line.trim();
|
|
2204
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
2205
|
-
i++;
|
|
2206
|
-
continue;
|
|
2207
|
-
}
|
|
2208
|
-
const eq = line.indexOf("=");
|
|
2209
|
-
if (eq < 0) {
|
|
2210
|
-
i++;
|
|
2211
|
-
continue;
|
|
2212
|
-
}
|
|
2213
|
-
const keyMatch = line.slice(0, eq).match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*$/);
|
|
2214
|
-
if (!keyMatch) {
|
|
2215
|
-
i++;
|
|
2216
|
-
continue;
|
|
2217
|
-
}
|
|
2218
|
-
const key = keyMatch[1];
|
|
2219
|
-
let value = line.slice(eq + 1);
|
|
2220
|
-
let closed = !value.startsWith('"') || hasUnescapedQuote(value.slice(1));
|
|
2221
|
-
if (!closed) {
|
|
2222
|
-
i++;
|
|
2223
|
-
while (i < lines.length) {
|
|
2224
|
-
value += "\n" + lines[i];
|
|
2225
|
-
if (hasUnescapedQuote(lines[i])) {
|
|
2226
|
-
closed = true;
|
|
2227
|
-
i++;
|
|
2228
|
-
break;
|
|
2229
|
-
}
|
|
2230
|
-
i++;
|
|
2231
|
-
}
|
|
2232
|
-
} else {
|
|
2233
|
-
i++;
|
|
2234
|
-
}
|
|
2235
|
-
if (value.startsWith('"') && !closed) {
|
|
2236
|
-
throw new Error(
|
|
2237
|
-
`parseDevVars: unterminated quoted value for key "${key}" \u2014 check your .dev.vars file for a missing closing quote.`
|
|
2238
|
-
);
|
|
2239
|
-
}
|
|
2240
|
-
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
2241
|
-
value = unescapeQuotedDevVar(value.slice(1, -1));
|
|
2242
|
-
}
|
|
2243
|
-
result[key] = value;
|
|
2153
|
+
function validateSecretValue(key, value) {
|
|
2154
|
+
if (new TextEncoder().encode(value).byteLength > MAX_SECRET_VALUE_BYTES) {
|
|
2155
|
+
throw new Error(`Secret "${key}" exceeds ${MAX_SECRET_VALUE_BYTES} bytes`);
|
|
2244
2156
|
}
|
|
2245
|
-
return result;
|
|
2246
2157
|
}
|
|
2247
|
-
function
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
out += next;
|
|
2258
|
-
i++;
|
|
2259
|
-
continue;
|
|
2158
|
+
function defaultConfigNameForEnv(wranglerEnv) {
|
|
2159
|
+
return wranglerEnv ?? "prd";
|
|
2160
|
+
}
|
|
2161
|
+
async function secretsApi(deployUrl, token, path, init = {}) {
|
|
2162
|
+
const res = await fetch(`${deployUrl}/api/secrets${path}`, {
|
|
2163
|
+
...init,
|
|
2164
|
+
headers: {
|
|
2165
|
+
Authorization: `Bearer ${token}`,
|
|
2166
|
+
"Content-Type": "application/json",
|
|
2167
|
+
...init.headers ?? {}
|
|
2260
2168
|
}
|
|
2261
|
-
|
|
2169
|
+
});
|
|
2170
|
+
const body = await res.json().catch(() => ({}));
|
|
2171
|
+
if (!res.ok) {
|
|
2172
|
+
const err = new Error(body.error ?? `Secrets request failed (${res.status})`);
|
|
2173
|
+
err.status = res.status;
|
|
2174
|
+
err.apiPath = path;
|
|
2175
|
+
throw err;
|
|
2262
2176
|
}
|
|
2263
|
-
return
|
|
2177
|
+
return body;
|
|
2178
|
+
}
|
|
2179
|
+
function listSecrets(deployUrl, token, appId, configName) {
|
|
2180
|
+
return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}`);
|
|
2181
|
+
}
|
|
2182
|
+
function fetchSecretsValues(deployUrl, token, appId, configName) {
|
|
2183
|
+
return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}/values`);
|
|
2184
|
+
}
|
|
2185
|
+
function getSecretPlain(deployUrl, token, appId, configName, key) {
|
|
2186
|
+
return secretsApi(
|
|
2187
|
+
deployUrl,
|
|
2188
|
+
token,
|
|
2189
|
+
`/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}?plain=true`
|
|
2190
|
+
);
|
|
2191
|
+
}
|
|
2192
|
+
function setSecret(deployUrl, token, appId, configName, key, value) {
|
|
2193
|
+
return secretsApi(
|
|
2194
|
+
deployUrl,
|
|
2195
|
+
token,
|
|
2196
|
+
`/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}`,
|
|
2197
|
+
{ method: "PUT", body: JSON.stringify({ value }) }
|
|
2198
|
+
);
|
|
2199
|
+
}
|
|
2200
|
+
function deleteSecret(deployUrl, token, appId, configName, key) {
|
|
2201
|
+
return secretsApi(
|
|
2202
|
+
deployUrl,
|
|
2203
|
+
token,
|
|
2204
|
+
`/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}`,
|
|
2205
|
+
{ method: "DELETE" }
|
|
2206
|
+
);
|
|
2264
2207
|
}
|
|
2265
|
-
function
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2208
|
+
function uploadSecrets(deployUrl, token, appId, configName, secrets, replace) {
|
|
2209
|
+
return secretsApi(
|
|
2210
|
+
deployUrl,
|
|
2211
|
+
token,
|
|
2212
|
+
`/${appId}/configs/${encodeURIComponent(configName)}/secrets`,
|
|
2213
|
+
{ method: "PUT", body: JSON.stringify({ secrets, replace }) }
|
|
2270
2214
|
);
|
|
2271
2215
|
}
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
`AUTH_JWT_PUBLIC_KEY="${publicKey}"`,
|
|
2280
|
-
`AUTH_JWT_ISSUER=${urls.auth}/api/auth`,
|
|
2281
|
-
`AUTH_WORKER_URL=${urls.auth}`,
|
|
2282
|
-
`API_WORKER_URL=${urls.api}`,
|
|
2283
|
-
`PLATFORM_WORKER_URL=${urls.platform}`,
|
|
2284
|
-
`OWNER_USER_ID=${ownerId}`,
|
|
2285
|
-
`APP_OWNER_JWT=${appOwnerJwt}`,
|
|
2286
|
-
...appIdentityToken ? [`APP_IDENTITY_TOKEN=${appIdentityToken}`] : [],
|
|
2287
|
-
`ALLOW_DEBUG_ROUTES=true`
|
|
2288
|
-
].join("\n");
|
|
2289
|
-
const useSharedDevVarsCache = opts.sharedDevVarsCache ?? opts.generatedSecretsCache !== void 0;
|
|
2290
|
-
const devVarsPath = devVarsPathFor(appDir, wranglerEnv, {
|
|
2291
|
-
sharedDevVarsCache: useSharedDevVarsCache
|
|
2216
|
+
function listConfigs(deployUrl, token, appId) {
|
|
2217
|
+
return secretsApi(deployUrl, token, `/${appId}/configs`);
|
|
2218
|
+
}
|
|
2219
|
+
function createConfig(deployUrl, token, appId, configName, copyFrom) {
|
|
2220
|
+
return secretsApi(deployUrl, token, `/${appId}/configs`, {
|
|
2221
|
+
method: "POST",
|
|
2222
|
+
body: JSON.stringify({ name: configName, ...copyFrom ? { copyFrom } : {} })
|
|
2292
2223
|
});
|
|
2293
|
-
const existing = existsSync7(devVarsPath) ? readFileSync10(devVarsPath, "utf-8") : "";
|
|
2294
|
-
const custom = opts.generatedSecretsCache ?? extractCustomDevVars(stripGeneratedSecretsCache(existing).trimEnd());
|
|
2295
|
-
let body;
|
|
2296
|
-
if (!custom) {
|
|
2297
|
-
body = `${sdkVars}
|
|
2298
|
-
`;
|
|
2299
|
-
} else if (opts.generatedSecretsCache) {
|
|
2300
|
-
body = `${sdkVars}
|
|
2301
|
-
|
|
2302
|
-
${custom}
|
|
2303
|
-
`;
|
|
2304
|
-
} else {
|
|
2305
|
-
body = `${sdkVars}
|
|
2306
|
-
|
|
2307
|
-
${DEV_VARS_DIVIDER}
|
|
2308
|
-
${custom}
|
|
2309
|
-
`;
|
|
2310
|
-
}
|
|
2311
|
-
writeSecretFileSync(devVarsPath, body);
|
|
2312
2224
|
}
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
|
|
2320
|
-
import { join as join8 } from "path";
|
|
2321
|
-
function detectPackageManager(appDir) {
|
|
2225
|
+
function deleteConfig(deployUrl, token, appId, configName) {
|
|
2226
|
+
return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}`, {
|
|
2227
|
+
method: "DELETE"
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
async function pullAppSecretsCache(deployUrl, token, appId, configName) {
|
|
2322
2231
|
try {
|
|
2323
|
-
const
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2232
|
+
const { secrets } = await fetchSecretsValues(deployUrl, token, appId, configName);
|
|
2233
|
+
if (secrets === null || typeof secrets !== "object" || Array.isArray(secrets)) {
|
|
2234
|
+
throw new ApiError(
|
|
2235
|
+
`The secrets service at ${deployUrl} returned an unexpected response shape \u2014 check DEEPSPACE_DEPLOY_URL (is this the service the app lives on?).`,
|
|
2236
|
+
200,
|
|
2237
|
+
"invalid_response",
|
|
2238
|
+
`/${appId}/configs/${configName}/values`
|
|
2239
|
+
);
|
|
2240
|
+
}
|
|
2241
|
+
return { appId, configName, values: secrets };
|
|
2242
|
+
} catch (err) {
|
|
2243
|
+
if (err?.status === 404) return null;
|
|
2244
|
+
throw err;
|
|
2327
2245
|
}
|
|
2328
|
-
if (existsSync8(join8(appDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
2329
|
-
if (existsSync8(join8(appDir, "yarn.lock"))) return "yarn";
|
|
2330
|
-
if (existsSync8(join8(appDir, "bun.lock")) || existsSync8(join8(appDir, "bun.lockb"))) return "bun";
|
|
2331
|
-
return "npm";
|
|
2332
2246
|
}
|
|
2333
|
-
function
|
|
2334
|
-
|
|
2247
|
+
function quoteDotenvValue(value) {
|
|
2248
|
+
if (/^[A-Za-z0-9_./:@+-]*$/.test(value) && value !== "") return value;
|
|
2249
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
2250
|
+
return `"${escaped}"`;
|
|
2335
2251
|
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
if (existsSync9(join9(sentinelDir, "install.started"))) {
|
|
2343
|
-
if (!existsSync9(join9(sentinelDir, "install.done")) && installWorkerAlive(sentinelDir)) {
|
|
2344
|
-
return "installing";
|
|
2345
|
-
}
|
|
2346
|
-
return "failed";
|
|
2252
|
+
function renderSecretsCache(secrets, source) {
|
|
2253
|
+
const keys = Object.keys(secrets).sort();
|
|
2254
|
+
const lines = [`# App secrets \xB7 config ${source.configName} \xB7 app ${source.appId}`];
|
|
2255
|
+
for (const key of keys) {
|
|
2256
|
+
validateSecretValue(key, secrets[key]);
|
|
2257
|
+
lines.push(`${key}=${quoteDotenvValue(secrets[key])}`);
|
|
2347
2258
|
}
|
|
2348
|
-
return "
|
|
2349
|
-
}
|
|
2350
|
-
function tailHint(logPath, platform = process.platform) {
|
|
2351
|
-
return platform === "win32" ? `Get-Content -Wait ${logPath}` : `tail -f ${logPath}`;
|
|
2259
|
+
return lines.join("\n");
|
|
2352
2260
|
}
|
|
2353
|
-
function
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
const
|
|
2362
|
-
const
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2261
|
+
function parseSecretsUpload(content) {
|
|
2262
|
+
const trimmed = content.trim();
|
|
2263
|
+
if (!trimmed) return {};
|
|
2264
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
2265
|
+
const parsed = JSON.parse(trimmed);
|
|
2266
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2267
|
+
throw new Error("JSON upload must be an object of KEY=value strings.");
|
|
2268
|
+
}
|
|
2269
|
+
const out2 = {};
|
|
2270
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
2271
|
+
validateSecretName(key);
|
|
2272
|
+
if (typeof value !== "string") throw new Error(`Secret "${key}" must be a string`);
|
|
2273
|
+
validateSecretValue(key, value);
|
|
2274
|
+
out2[key] = value;
|
|
2275
|
+
}
|
|
2276
|
+
return out2;
|
|
2368
2277
|
}
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2278
|
+
const out = {};
|
|
2279
|
+
const lines = trimmed.split(/\r?\n/);
|
|
2280
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2281
|
+
const line = lines[i].trim();
|
|
2282
|
+
if (!line || line.startsWith("#")) continue;
|
|
2283
|
+
const lineNumber = i + 1;
|
|
2284
|
+
const eq = line.indexOf("=");
|
|
2285
|
+
if (eq <= 0) {
|
|
2286
|
+
throw new Error(`Invalid dotenv assignment on line ${lineNumber}: expected KEY=value.`);
|
|
2287
|
+
}
|
|
2288
|
+
const key = line.slice(0, eq).trim();
|
|
2289
|
+
if (!SECRET_NAME_RE.test(key)) {
|
|
2290
|
+
throw new Error(
|
|
2291
|
+
`Invalid secret name on line ${lineNumber}: use letters, numbers, and underscores; start with a letter or underscore.`
|
|
2376
2292
|
);
|
|
2377
2293
|
}
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
)
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
{
|
|
2388
|
-
|
|
2294
|
+
validateSecretName(key);
|
|
2295
|
+
const raw = line.slice(eq + 1).trim();
|
|
2296
|
+
const quote = raw[0] === '"' || raw[0] === "'" ? raw[0] : null;
|
|
2297
|
+
let value;
|
|
2298
|
+
if (!quote) {
|
|
2299
|
+
value = raw;
|
|
2300
|
+
} else if (endsWithUnescapedQuote(raw.slice(1), quote)) {
|
|
2301
|
+
const inner = raw.slice(1, -1);
|
|
2302
|
+
value = quote === '"' ? unescapeDotenv(inner) : inner;
|
|
2303
|
+
} else {
|
|
2304
|
+
const startLine = i + 1;
|
|
2305
|
+
const parts = [raw.slice(1)];
|
|
2306
|
+
let closed = false;
|
|
2307
|
+
while (++i < lines.length) {
|
|
2308
|
+
const cont = lines[i].trimEnd();
|
|
2309
|
+
if (endsWithUnescapedQuote(cont, quote)) {
|
|
2310
|
+
parts.push(cont.slice(0, -1));
|
|
2311
|
+
closed = true;
|
|
2312
|
+
break;
|
|
2313
|
+
}
|
|
2314
|
+
parts.push(lines[i]);
|
|
2315
|
+
}
|
|
2316
|
+
if (!closed) {
|
|
2317
|
+
throw new Error(
|
|
2318
|
+
`Unterminated quoted value for "${key}" (opens on line ${startLine}). Close the ${quote === '"' ? "double" : "single"} quote, or escape newlines as \\n on one line.`
|
|
2319
|
+
);
|
|
2320
|
+
}
|
|
2321
|
+
value = parts.join("\n");
|
|
2322
|
+
if (quote === '"') value = unescapeDotenv(value);
|
|
2389
2323
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
function resolvesDeepspace(fromDir) {
|
|
2393
|
-
let dir = canonicalPath2(fromDir);
|
|
2394
|
-
let checkoutRoot = null;
|
|
2395
|
-
try {
|
|
2396
|
-
checkoutRoot = canonicalPath2(repoToplevel(dir));
|
|
2397
|
-
} catch {
|
|
2398
|
-
}
|
|
2399
|
-
for (; ; ) {
|
|
2400
|
-
if (existsSync9(join9(dir, "node_modules", "deepspace", "package.json"))) return true;
|
|
2401
|
-
if (dir === checkoutRoot) return false;
|
|
2402
|
-
const parent = dirname5(dir);
|
|
2403
|
-
if (parent === dir) return false;
|
|
2404
|
-
dir = parent;
|
|
2405
|
-
}
|
|
2406
|
-
}
|
|
2407
|
-
function canonicalPath2(path) {
|
|
2408
|
-
try {
|
|
2409
|
-
return realpathSync3(path);
|
|
2410
|
-
} catch {
|
|
2411
|
-
return resolve6(path);
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
function installWorkerAlive(sentinelDir) {
|
|
2415
|
-
const pidPath = join9(sentinelDir, "install.pid");
|
|
2416
|
-
if (!existsSync9(pidPath)) return false;
|
|
2417
|
-
const pid = Number(readFileSync12(pidPath, "utf-8").trim());
|
|
2418
|
-
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
2419
|
-
try {
|
|
2420
|
-
process.kill(pid, 0);
|
|
2421
|
-
return true;
|
|
2422
|
-
} catch (err) {
|
|
2423
|
-
return err.code === "EPERM";
|
|
2324
|
+
validateSecretValue(key, value);
|
|
2325
|
+
out[key] = value;
|
|
2424
2326
|
}
|
|
2327
|
+
return out;
|
|
2425
2328
|
}
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.registerHooks === "function") {
|
|
2433
|
-
if (hasRegisterHooks) return;
|
|
2434
|
-
throw new Refusal(
|
|
2435
|
-
`deepspace ${command} requires Node 22.15 or newer (found v${process.versions.node}). Install the current LTS from https://nodejs.org and re-run.`,
|
|
2436
|
-
"node_too_old"
|
|
2437
|
-
);
|
|
2329
|
+
function endsWithUnescapedQuote(s, q) {
|
|
2330
|
+
if (!s.endsWith(q)) return false;
|
|
2331
|
+
if (q === "'") return true;
|
|
2332
|
+
let backslashes = 0;
|
|
2333
|
+
for (let j = s.length - 2; j >= 0 && s[j] === "\\"; j--) backslashes++;
|
|
2334
|
+
return backslashes % 2 === 0;
|
|
2438
2335
|
}
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
"workerd.exe"
|
|
2449
|
-
);
|
|
2450
|
-
if (!existsSync10(workerdExe)) return;
|
|
2451
|
-
try {
|
|
2452
|
-
execFileSync(workerdExe, ["--help"], { stdio: "pipe", timeout: 1e4 });
|
|
2453
|
-
return;
|
|
2454
|
-
} catch (err) {
|
|
2455
|
-
const status3 = err.status;
|
|
2456
|
-
if (status3 !== DLL_NOT_FOUND) return;
|
|
2457
|
-
throw new Refusal(
|
|
2458
|
-
"workerd (Cloudflare Workers runtime) cannot start on this system: it needs the Microsoft Visual C++ Redistributable, which is not installed. Install it with `winget install Microsoft.VCRedist.2015+.x64` (or choco install -y vcredist140, or https://aka.ms/vs/17/release/vc_redist.x64.exe), then restart this terminal and re-run.",
|
|
2459
|
-
"vcredist_missing"
|
|
2460
|
-
);
|
|
2336
|
+
function unescapeDotenv(v) {
|
|
2337
|
+
let out = "";
|
|
2338
|
+
for (let i = 0; i < v.length; i++) {
|
|
2339
|
+
if (v[i] === "\\" && i + 1 < v.length) {
|
|
2340
|
+
const c = v[++i];
|
|
2341
|
+
out += c === "n" ? "\n" : c === "r" ? "\r" : c;
|
|
2342
|
+
} else {
|
|
2343
|
+
out += v[i];
|
|
2344
|
+
}
|
|
2461
2345
|
}
|
|
2346
|
+
return out;
|
|
2462
2347
|
}
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
let entries;
|
|
2471
|
-
try {
|
|
2472
|
-
entries = readdirSync2(dir, { withFileTypes: true });
|
|
2473
|
-
} catch {
|
|
2474
|
-
return removed;
|
|
2348
|
+
function shellSingleQuote(value) {
|
|
2349
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
2350
|
+
}
|
|
2351
|
+
function formatSecretsDownload(secrets, format) {
|
|
2352
|
+
const entries = Object.entries(secrets).sort(([a], [b]) => a.localeCompare(b));
|
|
2353
|
+
if (format === "json") {
|
|
2354
|
+
return JSON.stringify(Object.fromEntries(entries), null, 2) + "\n";
|
|
2475
2355
|
}
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
if (entry.isDirectory()) {
|
|
2479
|
-
if (!SKIP_DIRS.has(entry.name)) removed += removeMacosJunk(path);
|
|
2480
|
-
continue;
|
|
2481
|
-
}
|
|
2482
|
-
if (entry.name === ".DS_Store" || entry.name.startsWith("._")) {
|
|
2483
|
-
try {
|
|
2484
|
-
rmSync3(path, { force: true });
|
|
2485
|
-
removed++;
|
|
2486
|
-
} catch {
|
|
2487
|
-
}
|
|
2488
|
-
}
|
|
2356
|
+
if (format === "shell") {
|
|
2357
|
+
return entries.map(([k, v]) => `export ${k}=${shellSingleQuote(v)}`).join("\n") + "\n";
|
|
2489
2358
|
}
|
|
2490
|
-
return
|
|
2359
|
+
return entries.map(([k, v]) => `${k}=${quoteDotenvValue(v)}`).join("\n") + "\n";
|
|
2360
|
+
}
|
|
2361
|
+
async function refreshSecretsCache(deployUrl, token, appId, wranglerEnv, selectedConfigName = defaultConfigNameForEnv(wranglerEnv)) {
|
|
2362
|
+
const configName = selectedConfigName;
|
|
2363
|
+
const pulled = await pullAppSecretsCache(deployUrl, token, appId, configName);
|
|
2364
|
+
const values = pulled?.values ?? {};
|
|
2365
|
+
const count = Object.keys(values).length;
|
|
2366
|
+
return {
|
|
2367
|
+
pulled,
|
|
2368
|
+
rendered: renderSecretsCache(values, { appId, configName }),
|
|
2369
|
+
// An empty store is the common case and not worth a status line.
|
|
2370
|
+
summary: count ? `Secrets: ${configName} (${count} secret${count === 1 ? "" : "s"})` : null
|
|
2371
|
+
};
|
|
2491
2372
|
}
|
|
2492
2373
|
|
|
2493
2374
|
// src/cli/lib/schema-lint.ts
|
|
2494
|
-
import { existsSync as
|
|
2375
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2495
2376
|
import { join as join11 } from "path";
|
|
2496
2377
|
import { pathToFileURL } from "url";
|
|
2497
2378
|
|
|
@@ -2531,7 +2412,7 @@ function lintSchema(schema) {
|
|
|
2531
2412
|
if (schema.ownerField) {
|
|
2532
2413
|
const col = schema.columns.find((c) => c.name === schema.ownerField);
|
|
2533
2414
|
const spoofableRoles = Object.entries(schema.permissions).filter(
|
|
2534
|
-
([role, permissions]) => role !== "owner" && role !== "admin" && permissions.create === true
|
|
2415
|
+
([role, permissions]) => role !== "owner" && role !== "admin" && permissions.create === true && permissions.update !== "unclaimed-or-own"
|
|
2535
2416
|
).map(([role]) => role);
|
|
2536
2417
|
if (col && !col.userBound && spoofableRoles.length > 0) {
|
|
2537
2418
|
warnings.push(
|
|
@@ -2552,7 +2433,7 @@ function lintSchema(schema) {
|
|
|
2552
2433
|
// src/cli/lib/schema-lint.ts
|
|
2553
2434
|
async function lintProjectSchemas(appDir) {
|
|
2554
2435
|
const schemasPath = join11(appDir, "src", "schemas.ts");
|
|
2555
|
-
if (!
|
|
2436
|
+
if (!existsSync10(schemasPath)) return null;
|
|
2556
2437
|
let schemas;
|
|
2557
2438
|
try {
|
|
2558
2439
|
const esbuild = await import("esbuild");
|
|
@@ -2756,10 +2637,8 @@ var dev_default = defineDeepspaceCommand({
|
|
|
2756
2637
|
if (appIdForSecrets) {
|
|
2757
2638
|
try {
|
|
2758
2639
|
const refreshed = await refreshSecretsCache(DEPLOY_URL, token, appIdForSecrets, wranglerEnv);
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
if (refreshed.summary) say(refreshed.summary);
|
|
2762
|
-
}
|
|
2640
|
+
generatedSecretsCache = refreshed.rendered;
|
|
2641
|
+
if (refreshed.summary) say(refreshed.summary);
|
|
2763
2642
|
} catch (err) {
|
|
2764
2643
|
throw new Refusal(
|
|
2765
2644
|
`Failed to refresh app secrets: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -2767,16 +2646,10 @@ var dev_default = defineDeepspaceCommand({
|
|
|
2767
2646
|
);
|
|
2768
2647
|
}
|
|
2769
2648
|
}
|
|
2770
|
-
const sharedDevVarsCache = generatedSecretsCache !== void 0;
|
|
2771
2649
|
await writeDevVars(appDir, payload.sub, token, wranglerEnv, {
|
|
2772
|
-
generatedSecretsCache
|
|
2773
|
-
sharedDevVarsCache
|
|
2650
|
+
generatedSecretsCache
|
|
2774
2651
|
});
|
|
2775
|
-
const wranglerConfig = prepareWranglerEnvConfig(
|
|
2776
|
-
appDir,
|
|
2777
|
-
wranglerEnv,
|
|
2778
|
-
{ sharedDevVarsCache }
|
|
2779
|
-
);
|
|
2652
|
+
const wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv);
|
|
2780
2653
|
preflightWindowsWorkerd(appDir);
|
|
2781
2654
|
if (await isPortListening(port, host)) {
|
|
2782
2655
|
const killArgv = port === DEFAULT_PORT ? ["deepspace", "dev", "kill"] : ["deepspace", "dev", "kill", "--port", String(port)];
|
|
@@ -2791,15 +2664,11 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
|
|
|
2791
2664
|
}
|
|
2792
2665
|
const url = `http://${host === DEFAULT_DEV_HOST ? "localhost" : host}:${port}`;
|
|
2793
2666
|
say("Starting dev server...\n");
|
|
2794
|
-
const vite = spawn(
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
{
|
|
2798
|
-
|
|
2799
|
-
stdio: "inherit",
|
|
2800
|
-
env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
|
|
2801
|
-
}
|
|
2802
|
-
);
|
|
2667
|
+
const vite = spawn("npx", ["vite", "--port", String(port), "--strictPort", "--host", host], {
|
|
2668
|
+
cwd: appDir,
|
|
2669
|
+
stdio: "inherit",
|
|
2670
|
+
env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
|
|
2671
|
+
});
|
|
2803
2672
|
if (args.json) {
|
|
2804
2673
|
void waitForPortListening(port, host, READINESS_TIMEOUT_MS).then((ready) => {
|
|
2805
2674
|
console.log(
|
|
@@ -2844,7 +2713,7 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
|
|
|
2844
2713
|
|
|
2845
2714
|
// src/cli/commands/kill.ts
|
|
2846
2715
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2847
|
-
import { readFileSync as
|
|
2716
|
+
import { readFileSync as readFileSync12, readdirSync as readdirSync3, readlinkSync } from "fs";
|
|
2848
2717
|
import { setTimeout as wait } from "timers/promises";
|
|
2849
2718
|
var SIGTERM_GRACE_MS = 1500;
|
|
2850
2719
|
var IS_WIN = process.platform === "win32";
|
|
@@ -2995,7 +2864,7 @@ function listenerPidsViaProc(port) {
|
|
|
2995
2864
|
for (const f of ["/proc/net/tcp", "/proc/net/tcp6"]) {
|
|
2996
2865
|
let data;
|
|
2997
2866
|
try {
|
|
2998
|
-
data =
|
|
2867
|
+
data = readFileSync12(f, "utf-8");
|
|
2999
2868
|
} catch {
|
|
3000
2869
|
continue;
|
|
3001
2870
|
}
|
|
@@ -3154,10 +3023,10 @@ function ensurePlaywright(appDir) {
|
|
|
3154
3023
|
}
|
|
3155
3024
|
|
|
3156
3025
|
// src/cli/lib/test-account-service.ts
|
|
3157
|
-
import { readFileSync as
|
|
3026
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
3158
3027
|
|
|
3159
3028
|
// src/testing/accounts.ts
|
|
3160
|
-
import { existsSync as
|
|
3029
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
|
|
3161
3030
|
import { homedir as homedir3 } from "os";
|
|
3162
3031
|
import { join as join13 } from "path";
|
|
3163
3032
|
var TEST_ACCOUNTS_PATH = join13(homedir3(), ".deepspace", "test-accounts.json");
|
|
@@ -3176,7 +3045,7 @@ function normalizeTestAccountScope(value) {
|
|
|
3176
3045
|
}
|
|
3177
3046
|
function loadAllTestAccounts(scope = currentTestAccountScope()) {
|
|
3178
3047
|
const store = readTestAccountStore();
|
|
3179
|
-
return
|
|
3048
|
+
return store.scopes[scope] ?? [];
|
|
3180
3049
|
}
|
|
3181
3050
|
function reconcileTestAccountStore(remote, scope = currentTestAccountScope()) {
|
|
3182
3051
|
const result = reconcileTestAccountScopes(readTestAccountStore(), remote, scope);
|
|
@@ -3185,24 +3054,20 @@ function reconcileTestAccountStore(remote, scope = currentTestAccountScope()) {
|
|
|
3185
3054
|
}
|
|
3186
3055
|
function reconcileTestAccountScopes(store, remote, scope) {
|
|
3187
3056
|
const scoped = store.scopes[scope] ?? [];
|
|
3188
|
-
const
|
|
3189
|
-
const migrated = reconcileTestAccounts(store.unscoped, remote);
|
|
3190
|
-
const accounts = mergeTestAccounts(current, migrated);
|
|
3057
|
+
const accounts = reconcileTestAccounts(scoped, remote);
|
|
3191
3058
|
return {
|
|
3192
3059
|
accounts,
|
|
3193
|
-
removed: Math.max(0, scoped.length -
|
|
3060
|
+
removed: Math.max(0, scoped.length - accounts.length),
|
|
3194
3061
|
store: {
|
|
3195
3062
|
version: 2,
|
|
3196
|
-
scopes: { ...store.scopes, [scope]: accounts }
|
|
3197
|
-
unscoped: store.unscoped.filter((account) => !remote.some((item) => accountMatchesRemote(account, item)))
|
|
3063
|
+
scopes: { ...store.scopes, [scope]: accounts }
|
|
3198
3064
|
}
|
|
3199
3065
|
};
|
|
3200
3066
|
}
|
|
3201
3067
|
function reconcileTestAccounts(local, remote) {
|
|
3202
3068
|
const localById = new Map(local.filter((account) => account.id).map((account) => [account.id, account]));
|
|
3203
|
-
const legacyByEmail = new Map(local.filter((account) => !account.id).map((account) => [account.email, account]));
|
|
3204
3069
|
return remote.flatMap((account) => {
|
|
3205
|
-
const stored = localById.get(account.id)
|
|
3070
|
+
const stored = localById.get(account.id);
|
|
3206
3071
|
return stored ? [{ ...stored, ...account }] : [];
|
|
3207
3072
|
});
|
|
3208
3073
|
}
|
|
@@ -3213,10 +3078,7 @@ function upsertTestAccount(account, scope = currentTestAccountScope()) {
|
|
|
3213
3078
|
);
|
|
3214
3079
|
saveTestAccountStore({
|
|
3215
3080
|
version: 2,
|
|
3216
|
-
scopes: { ...store.scopes, [scope]: [...accounts, account] }
|
|
3217
|
-
unscoped: store.unscoped.filter(
|
|
3218
|
-
(stored) => stored.id !== account.id && stored.email !== account.email
|
|
3219
|
-
)
|
|
3081
|
+
scopes: { ...store.scopes, [scope]: [...accounts, account] }
|
|
3220
3082
|
});
|
|
3221
3083
|
}
|
|
3222
3084
|
function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope()) {
|
|
@@ -3226,41 +3088,31 @@ function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope())
|
|
|
3226
3088
|
const store = readTestAccountStore();
|
|
3227
3089
|
saveTestAccountStore({
|
|
3228
3090
|
version: 2,
|
|
3229
|
-
scopes: { ...store.scopes, [scope]: (store.scopes[scope] ?? []).filter(keep) }
|
|
3230
|
-
unscoped: store.unscoped.filter(keep)
|
|
3091
|
+
scopes: { ...store.scopes, [scope]: (store.scopes[scope] ?? []).filter(keep) }
|
|
3231
3092
|
});
|
|
3232
3093
|
}
|
|
3233
3094
|
function readTestAccountStore() {
|
|
3234
|
-
if (!
|
|
3095
|
+
if (!existsSync11(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {} };
|
|
3235
3096
|
try {
|
|
3236
|
-
const raw = JSON.parse(
|
|
3237
|
-
if (Array.isArray(raw)) return { version: 2, scopes: {}
|
|
3238
|
-
if (!raw || typeof raw !== "object"
|
|
3239
|
-
return { version: 2, scopes: {}
|
|
3097
|
+
const raw = JSON.parse(readFileSync13(TEST_ACCOUNTS_PATH, "utf-8"));
|
|
3098
|
+
if (Array.isArray(raw)) return { version: 2, scopes: {} };
|
|
3099
|
+
if (!raw || typeof raw !== "object") {
|
|
3100
|
+
return { version: 2, scopes: {} };
|
|
3240
3101
|
}
|
|
3241
3102
|
const candidate = raw;
|
|
3242
3103
|
const scopes = candidate.scopes && typeof candidate.scopes === "object" && !Array.isArray(candidate.scopes) ? Object.fromEntries(Object.entries(candidate.scopes).map(([key, value]) => [key, validTestAccounts(value)])) : {};
|
|
3243
|
-
return { version: 2, scopes
|
|
3104
|
+
return { version: 2, scopes };
|
|
3244
3105
|
} catch {
|
|
3245
|
-
return { version: 2, scopes: {}
|
|
3106
|
+
return { version: 2, scopes: {} };
|
|
3246
3107
|
}
|
|
3247
3108
|
}
|
|
3248
3109
|
function validTestAccounts(value) {
|
|
3249
3110
|
if (!Array.isArray(value)) return [];
|
|
3250
|
-
return value.filter(
|
|
3251
|
-
item
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
const accounts = /* @__PURE__ */ new Map();
|
|
3256
|
-
for (const account of groups.flat()) {
|
|
3257
|
-
const key = account.id ? `id:${account.id}` : `email:${account.email}`;
|
|
3258
|
-
if (!accounts.has(key)) accounts.set(key, account);
|
|
3259
|
-
}
|
|
3260
|
-
return [...accounts.values()];
|
|
3261
|
-
}
|
|
3262
|
-
function accountMatchesRemote(local, remote) {
|
|
3263
|
-
return local.id ? local.id === remote.id : local.email === remote.email;
|
|
3111
|
+
return value.filter(
|
|
3112
|
+
(item) => Boolean(
|
|
3113
|
+
item && typeof item === "object" && typeof item.id === "string" && typeof item.email === "string" && typeof item.password === "string"
|
|
3114
|
+
)
|
|
3115
|
+
);
|
|
3264
3116
|
}
|
|
3265
3117
|
function saveTestAccountStore(store) {
|
|
3266
3118
|
mkdirSync5(join13(homedir3(), ".deepspace"), { recursive: true });
|
|
@@ -3271,7 +3123,7 @@ function saveTestAccountStore(store) {
|
|
|
3271
3123
|
var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
|
|
3272
3124
|
var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
|
|
3273
3125
|
function sessionCookie() {
|
|
3274
|
-
const token =
|
|
3126
|
+
const token = readFileSync14(SESSION_PATH, "utf-8").trim();
|
|
3275
3127
|
return `${SESSION_COOKIE2}=${encodeURIComponent(token)}`;
|
|
3276
3128
|
}
|
|
3277
3129
|
async function fetchRemoteTestAccounts() {
|
|
@@ -3318,11 +3170,24 @@ async function syncTestAccountStore() {
|
|
|
3318
3170
|
|
|
3319
3171
|
// src/cli/commands/test.ts
|
|
3320
3172
|
var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
3173
|
+
var PLAYWRIGHT_OUTPUT_DIR = ".deepspace/test-results";
|
|
3174
|
+
function playwrightTestArgs(testFiles) {
|
|
3175
|
+
return [
|
|
3176
|
+
"playwright",
|
|
3177
|
+
"test",
|
|
3178
|
+
"--config",
|
|
3179
|
+
"tests/playwright.config.ts",
|
|
3180
|
+
"--output",
|
|
3181
|
+
PLAYWRIGHT_OUTPUT_DIR,
|
|
3182
|
+
...testFiles
|
|
3183
|
+
];
|
|
3184
|
+
}
|
|
3321
3185
|
var test_default = defineDeepspaceCommand({
|
|
3322
3186
|
meta: {
|
|
3323
3187
|
name: "test",
|
|
3324
3188
|
description: "Run tests for your DeepSpace app"
|
|
3325
3189
|
},
|
|
3190
|
+
jsonDescription: "Stream test output; emit the final JSON result on the last line",
|
|
3326
3191
|
args: {
|
|
3327
3192
|
suite: {
|
|
3328
3193
|
type: "positional",
|
|
@@ -3375,10 +3240,8 @@ var test_default = defineDeepspaceCommand({
|
|
|
3375
3240
|
if (appIdForSecrets) {
|
|
3376
3241
|
try {
|
|
3377
3242
|
const refreshed = await refreshSecretsCache(DEPLOY_URL2, token, appIdForSecrets, wranglerEnv);
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
if (refreshed.summary) say(refreshed.summary);
|
|
3381
|
-
}
|
|
3243
|
+
generatedSecretsCache = refreshed.rendered;
|
|
3244
|
+
if (refreshed.summary) say(refreshed.summary);
|
|
3382
3245
|
} catch (err) {
|
|
3383
3246
|
throw new Refusal(
|
|
3384
3247
|
`Failed to refresh app secrets: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -3386,17 +3249,17 @@ var test_default = defineDeepspaceCommand({
|
|
|
3386
3249
|
);
|
|
3387
3250
|
}
|
|
3388
3251
|
}
|
|
3389
|
-
const sharedDevVarsCache = generatedSecretsCache !== void 0;
|
|
3390
3252
|
await writeDevVars(appDir, ownerId, token, wranglerEnv, {
|
|
3391
|
-
generatedSecretsCache
|
|
3392
|
-
sharedDevVarsCache
|
|
3253
|
+
generatedSecretsCache
|
|
3393
3254
|
});
|
|
3394
3255
|
if (suite !== "unit") {
|
|
3395
3256
|
try {
|
|
3396
3257
|
const { removed } = await syncTestAccountStore();
|
|
3397
3258
|
if (removed > 0) say(`Removed ${removed} stale test account credential(s).`);
|
|
3398
3259
|
} catch (error) {
|
|
3399
|
-
say(
|
|
3260
|
+
say(
|
|
3261
|
+
`Warning: could not reconcile test accounts: ${error instanceof Error ? error.message : String(error)}`
|
|
3262
|
+
);
|
|
3400
3263
|
}
|
|
3401
3264
|
}
|
|
3402
3265
|
if (suite !== "unit") {
|
|
@@ -3406,46 +3269,32 @@ var test_default = defineDeepspaceCommand({
|
|
|
3406
3269
|
let exitCode = 0;
|
|
3407
3270
|
switch (suite) {
|
|
3408
3271
|
case "smoke":
|
|
3409
|
-
exitCode = runPlaywright(
|
|
3410
|
-
appDir,
|
|
3411
|
-
["tests/smoke.spec.ts"],
|
|
3412
|
-
port,
|
|
3413
|
-
wranglerEnv,
|
|
3414
|
-
sharedDevVarsCache
|
|
3415
|
-
);
|
|
3272
|
+
exitCode = runPlaywright(appDir, ["tests/smoke.spec.ts"], port, wranglerEnv);
|
|
3416
3273
|
break;
|
|
3417
3274
|
case "api":
|
|
3418
|
-
exitCode = runPlaywright(
|
|
3419
|
-
appDir,
|
|
3420
|
-
["tests/api.spec.ts"],
|
|
3421
|
-
port,
|
|
3422
|
-
wranglerEnv,
|
|
3423
|
-
sharedDevVarsCache
|
|
3424
|
-
);
|
|
3275
|
+
exitCode = runPlaywright(appDir, ["tests/api.spec.ts"], port, wranglerEnv);
|
|
3425
3276
|
break;
|
|
3426
3277
|
case "e2e":
|
|
3427
|
-
exitCode = runPlaywright(appDir, [], port, wranglerEnv
|
|
3278
|
+
exitCode = runPlaywright(appDir, [], port, wranglerEnv);
|
|
3428
3279
|
break;
|
|
3429
3280
|
case "unit":
|
|
3430
3281
|
exitCode = runVitest(appDir);
|
|
3431
3282
|
break;
|
|
3432
3283
|
case "all":
|
|
3433
3284
|
exitCode = runVitest(appDir);
|
|
3434
|
-
if (exitCode === 0)
|
|
3435
|
-
exitCode = runPlaywright(appDir, [], port, wranglerEnv, sharedDevVarsCache);
|
|
3285
|
+
if (exitCode === 0) exitCode = runPlaywright(appDir, [], port, wranglerEnv);
|
|
3436
3286
|
break;
|
|
3437
3287
|
case "default":
|
|
3438
3288
|
exitCode = runPlaywright(
|
|
3439
3289
|
appDir,
|
|
3440
3290
|
["tests/smoke.spec.ts", "tests/api.spec.ts"],
|
|
3441
3291
|
port,
|
|
3442
|
-
wranglerEnv
|
|
3443
|
-
sharedDevVarsCache
|
|
3292
|
+
wranglerEnv
|
|
3444
3293
|
);
|
|
3445
3294
|
break;
|
|
3446
3295
|
default:
|
|
3447
3296
|
if (suite.endsWith(".spec.ts")) {
|
|
3448
|
-
exitCode = runPlaywright(appDir, [suite], port, wranglerEnv
|
|
3297
|
+
exitCode = runPlaywright(appDir, [suite], port, wranglerEnv);
|
|
3449
3298
|
} else {
|
|
3450
3299
|
throw new Refusal(
|
|
3451
3300
|
`Unknown test suite: ${suite}
|
|
@@ -3462,24 +3311,23 @@ Available: smoke, api, e2e, unit, all`,
|
|
|
3462
3311
|
return { data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null } };
|
|
3463
3312
|
}
|
|
3464
3313
|
});
|
|
3465
|
-
function runPlaywright(appDir, testFiles, port, wranglerEnv
|
|
3314
|
+
function runPlaywright(appDir, testFiles, port, wranglerEnv) {
|
|
3466
3315
|
let wranglerConfig;
|
|
3467
3316
|
try {
|
|
3468
|
-
wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv
|
|
3317
|
+
wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv);
|
|
3469
3318
|
} catch (err) {
|
|
3470
3319
|
console.error(err instanceof Error ? err.message : String(err));
|
|
3471
3320
|
return 1;
|
|
3472
3321
|
}
|
|
3473
3322
|
try {
|
|
3474
|
-
const result = spawnSync4(
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
{
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
);
|
|
3323
|
+
const result = spawnSync4("npx", playwrightTestArgs(testFiles), {
|
|
3324
|
+
cwd: appDir,
|
|
3325
|
+
stdio: "inherit",
|
|
3326
|
+
env: wranglerViteEnv(process.env, wranglerConfig, {
|
|
3327
|
+
DEEPSPACE_PORT: String(port),
|
|
3328
|
+
PLAYWRIGHT_HTML_OUTPUT_DIR: ".deepspace/playwright-report"
|
|
3329
|
+
})
|
|
3330
|
+
});
|
|
3483
3331
|
return result.status ?? 1;
|
|
3484
3332
|
} finally {
|
|
3485
3333
|
wranglerConfig.cleanup();
|
|
@@ -3997,33 +3845,33 @@ function describeFilesFailure(status3, body) {
|
|
|
3997
3845
|
import * as p6 from "@clack/prompts";
|
|
3998
3846
|
import { execSync as execSync2 } from "child_process";
|
|
3999
3847
|
import { createHash as createHash2 } from "crypto";
|
|
4000
|
-
import { existsSync as
|
|
3848
|
+
import { chmodSync as chmodSync2, existsSync as existsSync14, lstatSync as lstatSync2, readFileSync as readFileSync17, readdirSync as readdirSync4 } from "fs";
|
|
4001
3849
|
import { dirname as dirname6, join as join16, resolve as resolve11 } from "path";
|
|
4002
3850
|
|
|
4003
3851
|
// src/documentation/deploy.ts
|
|
4004
|
-
import { existsSync as
|
|
3852
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
|
|
4005
3853
|
import { join as join14 } from "path";
|
|
4006
3854
|
function readDocumentationDeployManifest(appDir, clientDir) {
|
|
4007
|
-
if (!
|
|
3855
|
+
if (!existsSync12(join14(appDir, "documentation.json"))) return void 0;
|
|
4008
3856
|
const manifestPath = join14(clientDir, "_documentation", "manifest.json");
|
|
4009
|
-
if (!
|
|
3857
|
+
if (!existsSync12(manifestPath)) {
|
|
4010
3858
|
throw new Error(
|
|
4011
3859
|
"documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
|
|
4012
3860
|
);
|
|
4013
3861
|
}
|
|
4014
|
-
return JSON.parse(
|
|
3862
|
+
return JSON.parse(readFileSync15(manifestPath, "utf8"));
|
|
4015
3863
|
}
|
|
4016
3864
|
|
|
4017
3865
|
// src/cli/commands/update/app-migrations.ts
|
|
4018
3866
|
import {
|
|
4019
3867
|
closeSync,
|
|
4020
3868
|
constants,
|
|
4021
|
-
existsSync as
|
|
3869
|
+
existsSync as existsSync13,
|
|
4022
3870
|
fstatSync,
|
|
4023
3871
|
ftruncateSync,
|
|
4024
3872
|
lstatSync,
|
|
4025
3873
|
openSync,
|
|
4026
|
-
readFileSync as
|
|
3874
|
+
readFileSync as readFileSync16,
|
|
4027
3875
|
writeSync
|
|
4028
3876
|
} from "fs";
|
|
4029
3877
|
import { join as join15 } from "path";
|
|
@@ -4032,6 +3880,7 @@ import { join as join15 } from "path";
|
|
|
4032
3880
|
var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
4033
3881
|
var MAX_APP_MIGRATION_IDS = 256;
|
|
4034
3882
|
var WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
|
|
3883
|
+
var SECURE_ROOM_BOUNDARIES_MIGRATION_ID = "2026-08-secure-room-boundaries";
|
|
4035
3884
|
function validateAppMigrationIds(value) {
|
|
4036
3885
|
if (!Array.isArray(value)) {
|
|
4037
3886
|
return { valid: false, reason: "app migrations must be a JSON string array" };
|
|
@@ -4065,6 +3914,12 @@ var SOURCE_MIGRATIONS = [
|
|
|
4065
3914
|
description: "Let the worker answer misses, so deleted files 404 instead of serving HTML",
|
|
4066
3915
|
transform: migrateWorkerOwnedNotFound,
|
|
4067
3916
|
findBlockers: findWorkerOwnedNotFoundBlockers
|
|
3917
|
+
},
|
|
3918
|
+
{
|
|
3919
|
+
id: SECURE_ROOM_BOUNDARIES_MIGRATION_ID,
|
|
3920
|
+
description: "Secure room identity, job mutations, and production debug routes",
|
|
3921
|
+
transform: migrateSecureRoomBoundaries,
|
|
3922
|
+
findBlockers: findSecureRoomBoundaryBlockers
|
|
4068
3923
|
}
|
|
4069
3924
|
];
|
|
4070
3925
|
var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
|
|
@@ -4072,7 +3927,7 @@ var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
|
|
|
4072
3927
|
);
|
|
4073
3928
|
function readAppliedAppMigrations(appDir) {
|
|
4074
3929
|
const path = join15(appDir, APP_MIGRATIONS_MANIFEST);
|
|
4075
|
-
if (!
|
|
3930
|
+
if (!existsSync13(path)) return [];
|
|
4076
3931
|
const source = readRegularFile(path);
|
|
4077
3932
|
let parsed;
|
|
4078
3933
|
try {
|
|
@@ -4133,7 +3988,7 @@ function planAppMigrations(appDir) {
|
|
|
4133
3988
|
if (pending.length > 0 && blockers.length === 0) {
|
|
4134
3989
|
const manifestPath = `${appPrefix}${APP_MIGRATIONS_MANIFEST}`;
|
|
4135
3990
|
const absoluteManifestPath = join15(repo, manifestPath);
|
|
4136
|
-
const before =
|
|
3991
|
+
const before = existsSync13(absoluteManifestPath) ? readRegularFile(absoluteManifestPath) : null;
|
|
4137
3992
|
const after = `${JSON.stringify([...appliedIds, ...pending.map(({ id }) => id)], null, 2)}
|
|
4138
3993
|
`;
|
|
4139
3994
|
if (before !== after) edits.push({ path: manifestPath, before, after });
|
|
@@ -4149,7 +4004,7 @@ function applyAppMigrationPlan(appDir, plan) {
|
|
|
4149
4004
|
const repo = repoToplevel(appDir);
|
|
4150
4005
|
for (const edit of plan.edits) {
|
|
4151
4006
|
const absolutePath = join15(repo, edit.path);
|
|
4152
|
-
const current =
|
|
4007
|
+
const current = existsSync13(absolutePath) ? readRegularFile(absolutePath) : null;
|
|
4153
4008
|
if (current !== edit.before) {
|
|
4154
4009
|
throw new Error(`Migration input changed after planning: ${edit.path}`);
|
|
4155
4010
|
}
|
|
@@ -4168,7 +4023,7 @@ function readRegularFile(path) {
|
|
|
4168
4023
|
if (!opened.isFile() || opened.dev !== after.dev || opened.ino !== after.ino) {
|
|
4169
4024
|
throw new Error(`Migration input changed while opening: ${path}`);
|
|
4170
4025
|
}
|
|
4171
|
-
return
|
|
4026
|
+
return readFileSync16(fd, "utf-8");
|
|
4172
4027
|
} finally {
|
|
4173
4028
|
closeSync(fd);
|
|
4174
4029
|
}
|
|
@@ -4194,7 +4049,7 @@ function writeRegularFile(path, before, after) {
|
|
|
4194
4049
|
if (!opened.isFile() || opened.dev !== currentPath.dev || opened.ino !== currentPath.ino) {
|
|
4195
4050
|
throw new Error(`Migration input changed while opening: ${path}`);
|
|
4196
4051
|
}
|
|
4197
|
-
if (
|
|
4052
|
+
if (readFileSync16(fd, "utf-8") !== before) {
|
|
4198
4053
|
throw new Error(`Migration input changed after planning: ${path}`);
|
|
4199
4054
|
}
|
|
4200
4055
|
ftruncateSync(fd, 0);
|
|
@@ -4208,6 +4063,248 @@ function isSourcePath(repoPath, appPrefix) {
|
|
|
4208
4063
|
if (repoPath.endsWith("wrangler.toml")) return true;
|
|
4209
4064
|
return /\.(?:[cm]?[jt]sx?)$/.test(repoPath);
|
|
4210
4065
|
}
|
|
4066
|
+
var LEGACY_AUTHENTICATED_ROOM_HELPER = `const IDENTITY_QUERY_PARAMS = ['userId', 'userName', 'userEmail', 'userImageUrl', 'role'] as const
|
|
4067
|
+
|
|
4068
|
+
function authenticatedRoomUrl(
|
|
4069
|
+
requestUrl: string,
|
|
4070
|
+
auth: VerifyResult | null,
|
|
4071
|
+
extraParams?: (auth: VerifyResult) => Record<string, string>,
|
|
4072
|
+
): URL {
|
|
4073
|
+
const doUrl = new URL(requestUrl)
|
|
4074
|
+
doUrl.searchParams.delete('token')
|
|
4075
|
+
for (const key of IDENTITY_QUERY_PARAMS) {
|
|
4076
|
+
doUrl.searchParams.delete(key)
|
|
4077
|
+
}
|
|
4078
|
+
|
|
4079
|
+
if (!auth) return doUrl
|
|
4080
|
+
|
|
4081
|
+
doUrl.searchParams.set('userId', auth.userId)
|
|
4082
|
+
if (auth.claims.name) doUrl.searchParams.set('userName', auth.claims.name)
|
|
4083
|
+
if (auth.claims.email) doUrl.searchParams.set('userEmail', auth.claims.email)
|
|
4084
|
+
if (auth.claims.image) doUrl.searchParams.set('userImageUrl', auth.claims.image)
|
|
4085
|
+
if (extraParams) {
|
|
4086
|
+
for (const [key, value] of Object.entries(extraParams(auth))) {
|
|
4087
|
+
doUrl.searchParams.set(key, value)
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
return doUrl
|
|
4091
|
+
}`;
|
|
4092
|
+
var IDENTITY_ROUTE_REPLACEMENTS = [
|
|
4093
|
+
[
|
|
4094
|
+
`import { verifyJwt } from 'deepspace/worker'`,
|
|
4095
|
+
`import { authenticatedRoomRequest, verifyJwt } from 'deepspace/worker'`,
|
|
4096
|
+
1
|
|
4097
|
+
],
|
|
4098
|
+
[LEGACY_AUTHENTICATED_ROOM_HELPER, "", 1],
|
|
4099
|
+
[
|
|
4100
|
+
`function wsRoute(
|
|
4101
|
+
doNamespace: (env: Env) => DurableObjectNamespace,
|
|
4102
|
+
extraParams?: (auth: VerifyResult) => Record<string, string>,
|
|
4103
|
+
) {`,
|
|
4104
|
+
`function wsRoute(
|
|
4105
|
+
doNamespace: (env: Env) => DurableObjectNamespace,
|
|
4106
|
+
extraIdentity?: (auth: VerifyResult) => { role?: string },
|
|
4107
|
+
) {`,
|
|
4108
|
+
1
|
|
4109
|
+
],
|
|
4110
|
+
[
|
|
4111
|
+
` const doUrl = authenticatedRoomUrl(c.req.url, auth, extraParams)`,
|
|
4112
|
+
` const roomRequest = authenticatedRoomRequest(
|
|
4113
|
+
c.req.raw,
|
|
4114
|
+
auth,
|
|
4115
|
+
auth ? extraIdentity?.(auth) : undefined,
|
|
4116
|
+
)`,
|
|
4117
|
+
1
|
|
4118
|
+
],
|
|
4119
|
+
[
|
|
4120
|
+
` const doUrl = authenticatedRoomUrl(c.req.url, auth, () => ({ role }))`,
|
|
4121
|
+
` const roomRequest = authenticatedRoomRequest(c.req.raw, auth, { role })`,
|
|
4122
|
+
1
|
|
4123
|
+
],
|
|
4124
|
+
[
|
|
4125
|
+
` return stub.fetch(new Request(doUrl.toString(), c.req.raw))`,
|
|
4126
|
+
` return stub.fetch(roomRequest)`,
|
|
4127
|
+
2
|
|
4128
|
+
]
|
|
4129
|
+
];
|
|
4130
|
+
function occurrences(source, pattern) {
|
|
4131
|
+
return source.split(pattern).length - 1;
|
|
4132
|
+
}
|
|
4133
|
+
var SECURE_JOB_ROOM_CONSTRUCTOR = ` constructor(state: DurableObjectState, env: Env) {
|
|
4134
|
+
super(state, env, {
|
|
4135
|
+
authorizeWrite: async (user) => {
|
|
4136
|
+
if (user.userId.startsWith('anon-')) return false
|
|
4137
|
+
const role = await resolveAppRole(env, user.userId)
|
|
4138
|
+
return role === 'member' || role === 'admin'
|
|
4139
|
+
},
|
|
4140
|
+
})
|
|
4141
|
+
}`;
|
|
4142
|
+
var APP_JOB_ROOM_CLASS = `export class AppJobRoom extends JobRoom<Env> {`;
|
|
4143
|
+
var SECURE_APP_JOB_ROOM = `${APP_JOB_ROOM_CLASS}
|
|
4144
|
+
${SECURE_JOB_ROOM_CONSTRUCTOR}`;
|
|
4145
|
+
function hasSecureAppJobRoom(source) {
|
|
4146
|
+
return occurrences(source, "class AppJobRoom") === 1 && source.includes(SECURE_APP_JOB_ROOM);
|
|
4147
|
+
}
|
|
4148
|
+
var WORKER_JOB_REPLACEMENTS = [
|
|
4149
|
+
[
|
|
4150
|
+
`import type { DOBindings, DOManifest, Job, JobContext } from 'deepspace/worker'`,
|
|
4151
|
+
`import { resolveAppRole } from 'deepspace/worker'
|
|
4152
|
+
import type { DOBindings, DOManifest, Job, JobContext } from 'deepspace/worker'`
|
|
4153
|
+
],
|
|
4154
|
+
[
|
|
4155
|
+
`export class AppJobRoom extends JobRoom<Env> {
|
|
4156
|
+
constructor(state: DurableObjectState, env: Env) {
|
|
4157
|
+
super(state, env)
|
|
4158
|
+
}`,
|
|
4159
|
+
`export class AppJobRoom extends JobRoom<Env> {
|
|
4160
|
+
${SECURE_JOB_ROOM_CONSTRUCTOR}`
|
|
4161
|
+
]
|
|
4162
|
+
];
|
|
4163
|
+
var HTTP_ROLE_IMPORT = `import { resolveAppRole } from 'deepspace/worker'`;
|
|
4164
|
+
var HTTP_ROLE_IMPORT_SEAM = `import type { AppContext, Env } from '../../worker.js'`;
|
|
4165
|
+
var DEBUG_ROUTE_SEAM = ` app.all('/api/debug/*', async (c) => {
|
|
4166
|
+
if (c.env.ALLOW_DEBUG_ROUTES !== 'true') {
|
|
4167
|
+
return c.notFound()
|
|
4168
|
+
}
|
|
4169
|
+
const stub = c.env.RECORD_ROOMS.get(`;
|
|
4170
|
+
var DOUBLE_QUOTED_DEBUG_ROUTE_SEAM = DEBUG_ROUTE_SEAM.replace(
|
|
4171
|
+
"app.all('/api/debug/*'",
|
|
4172
|
+
'app.all("/api/debug/*"'
|
|
4173
|
+
);
|
|
4174
|
+
var SECURE_DEBUG_ROUTE_SEAM = ` app.all('/api/debug/*', async (c) => {
|
|
4175
|
+
if (c.env.ALLOW_DEBUG_ROUTES !== 'true') {
|
|
4176
|
+
return c.notFound()
|
|
4177
|
+
}
|
|
4178
|
+
const auth = await resolveAuth(c.req.raw, c.env)
|
|
4179
|
+
if (!auth) return c.json({ error: 'unauthorized' }, 401)
|
|
4180
|
+
if ((await resolveAppRole(c.env, auth.userId)) !== 'admin') {
|
|
4181
|
+
return c.json({ error: 'forbidden' }, 403)
|
|
4182
|
+
}
|
|
4183
|
+
const stub = c.env.RECORD_ROOMS.get(`;
|
|
4184
|
+
var DOUBLE_QUOTED_SECURE_DEBUG_ROUTE_SEAM = SECURE_DEBUG_ROUTE_SEAM.replace(
|
|
4185
|
+
"app.all('/api/debug/*'",
|
|
4186
|
+
'app.all("/api/debug/*"'
|
|
4187
|
+
);
|
|
4188
|
+
var DEBUG_ROUTE_REPLACEMENTS = [
|
|
4189
|
+
[DEBUG_ROUTE_SEAM, SECURE_DEBUG_ROUTE_SEAM],
|
|
4190
|
+
[DOUBLE_QUOTED_DEBUG_ROUTE_SEAM, DOUBLE_QUOTED_SECURE_DEBUG_ROUTE_SEAM]
|
|
4191
|
+
];
|
|
4192
|
+
var DEBUG_ROUTE_PATTERN = /\bapp\.all\(\s*(['"])\/api\/debug\/\*\1\s*,/;
|
|
4193
|
+
function debugRouteOccurrences(source) {
|
|
4194
|
+
return source.match(new RegExp(DEBUG_ROUTE_PATTERN.source, "g"))?.length ?? 0;
|
|
4195
|
+
}
|
|
4196
|
+
function secureDebugRouteOccurrences(source) {
|
|
4197
|
+
return DEBUG_ROUTE_REPLACEMENTS.reduce(
|
|
4198
|
+
(count, [, secure]) => count + occurrences(source, secure),
|
|
4199
|
+
0
|
|
4200
|
+
);
|
|
4201
|
+
}
|
|
4202
|
+
function hasSecureDebugRoute(source) {
|
|
4203
|
+
return debugRouteOccurrences(source) === 1 && secureDebugRouteOccurrences(source) === 1 && occurrences(source, HTTP_ROLE_IMPORT) === 1;
|
|
4204
|
+
}
|
|
4205
|
+
function migrateSecureRoomBoundaries(source, file) {
|
|
4206
|
+
if (file.endsWith("src/server/realtime-routes.ts")) {
|
|
4207
|
+
return migrateRealtimeRoomBoundaries(source);
|
|
4208
|
+
}
|
|
4209
|
+
if (file.endsWith("worker.ts")) return migrateWorkerJobAuthorization(source);
|
|
4210
|
+
if (file.endsWith("src/server/http-routes.ts")) return migrateDebugRouteAuthorization(source);
|
|
4211
|
+
return { source, replacements: 0 };
|
|
4212
|
+
}
|
|
4213
|
+
function migrateRealtimeRoomBoundaries(source) {
|
|
4214
|
+
let migrated = source;
|
|
4215
|
+
let replacements = 0;
|
|
4216
|
+
if (!migrated.includes("authenticatedRoomRequest(")) {
|
|
4217
|
+
if (IDENTITY_ROUTE_REPLACEMENTS.some(
|
|
4218
|
+
([before, , expected]) => occurrences(migrated, before) !== expected
|
|
4219
|
+
)) {
|
|
4220
|
+
return { source, replacements: 0 };
|
|
4221
|
+
}
|
|
4222
|
+
for (const [before, after, expected] of IDENTITY_ROUTE_REPLACEMENTS) {
|
|
4223
|
+
migrated = migrated.replaceAll(before, after);
|
|
4224
|
+
replacements += expected;
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
return { source: migrated, replacements };
|
|
4228
|
+
}
|
|
4229
|
+
function migrateWorkerJobAuthorization(source) {
|
|
4230
|
+
if (!source.includes("class AppJobRoom")) return { source, replacements: 0 };
|
|
4231
|
+
if (hasSecureAppJobRoom(source)) return { source, replacements: 0 };
|
|
4232
|
+
if (WORKER_JOB_REPLACEMENTS.some(([before]) => occurrences(source, before) !== 1)) {
|
|
4233
|
+
return { source, replacements: 0 };
|
|
4234
|
+
}
|
|
4235
|
+
let migrated = source;
|
|
4236
|
+
for (const [before, after] of WORKER_JOB_REPLACEMENTS) migrated = migrated.replace(before, after);
|
|
4237
|
+
return { source: migrated, replacements: WORKER_JOB_REPLACEMENTS.length };
|
|
4238
|
+
}
|
|
4239
|
+
function migrateDebugRouteAuthorization(source) {
|
|
4240
|
+
if (debugRouteOccurrences(source) === 0 || hasSecureDebugRoute(source)) {
|
|
4241
|
+
return { source, replacements: 0 };
|
|
4242
|
+
}
|
|
4243
|
+
const matchingRoutes = DEBUG_ROUTE_REPLACEMENTS.filter(
|
|
4244
|
+
([before2]) => occurrences(source, before2) === 1
|
|
4245
|
+
);
|
|
4246
|
+
if (debugRouteOccurrences(source) !== 1 || matchingRoutes.length !== 1 || occurrences(source, HTTP_ROLE_IMPORT) !== 0 || occurrences(source, HTTP_ROLE_IMPORT_SEAM) !== 1) {
|
|
4247
|
+
return { source, replacements: 0 };
|
|
4248
|
+
}
|
|
4249
|
+
const [before, after] = matchingRoutes[0];
|
|
4250
|
+
return {
|
|
4251
|
+
source: source.replace(HTTP_ROLE_IMPORT_SEAM, `${HTTP_ROLE_IMPORT_SEAM}
|
|
4252
|
+
${HTTP_ROLE_IMPORT}`).replace(before, after),
|
|
4253
|
+
replacements: 2
|
|
4254
|
+
};
|
|
4255
|
+
}
|
|
4256
|
+
function findSecureRoomBoundaryBlockers(source, file) {
|
|
4257
|
+
if (file.endsWith("worker.ts") && source.includes("class AppJobRoom")) {
|
|
4258
|
+
if (!hasSecureAppJobRoom(source)) {
|
|
4259
|
+
return [
|
|
4260
|
+
roomBoundaryBlocker(
|
|
4261
|
+
file,
|
|
4262
|
+
source,
|
|
4263
|
+
"class AppJobRoom",
|
|
4264
|
+
"Configure AppJobRoom with authorizeWrite using the current users.role."
|
|
4265
|
+
)
|
|
4266
|
+
];
|
|
4267
|
+
}
|
|
4268
|
+
return [];
|
|
4269
|
+
}
|
|
4270
|
+
if (file.endsWith("src/server/http-routes.ts") && debugRouteOccurrences(source) > 0) {
|
|
4271
|
+
if (!hasSecureDebugRoute(source)) {
|
|
4272
|
+
const route = source.match(DEBUG_ROUTE_PATTERN)?.[0] ?? "/api/debug/*";
|
|
4273
|
+
return [
|
|
4274
|
+
roomBoundaryBlocker(
|
|
4275
|
+
file,
|
|
4276
|
+
source,
|
|
4277
|
+
route,
|
|
4278
|
+
"Require verified owner/admin access inside the enabled debug-route handler."
|
|
4279
|
+
)
|
|
4280
|
+
];
|
|
4281
|
+
}
|
|
4282
|
+
return [];
|
|
4283
|
+
}
|
|
4284
|
+
if (!file.endsWith("src/server/realtime-routes.ts")) return [];
|
|
4285
|
+
const lines = source.split("\n");
|
|
4286
|
+
let index = lines.findIndex(
|
|
4287
|
+
(line) => line.includes("authenticatedRoomUrl") || /searchParams\.(?:set|append)\(\s*['"](?:userId|userName|userEmail|userImageUrl|role)['"]/.test(
|
|
4288
|
+
line
|
|
4289
|
+
)
|
|
4290
|
+
);
|
|
4291
|
+
if (index < 0 && /\bauth\.(?:userId|claims)\b/.test(source)) {
|
|
4292
|
+
index = lines.findIndex((line) => /searchParams\.(?:set|append)\s*\(/.test(line));
|
|
4293
|
+
}
|
|
4294
|
+
if (index < 0) return [];
|
|
4295
|
+
return [
|
|
4296
|
+
{
|
|
4297
|
+
migrationId: SECURE_ROOM_BOUNDARIES_MIGRATION_ID,
|
|
4298
|
+
file,
|
|
4299
|
+
line: index + 1,
|
|
4300
|
+
message: "This customized room proxy still places verified user identity in its internal URL. Replace that forwarding code with authenticatedRoomRequest() from deepspace/worker before the Durable Object fetch."
|
|
4301
|
+
}
|
|
4302
|
+
];
|
|
4303
|
+
}
|
|
4304
|
+
function roomBoundaryBlocker(file, source, seam, message) {
|
|
4305
|
+
const line = source.slice(0, source.indexOf(seam)).split("\n").length;
|
|
4306
|
+
return { migrationId: SECURE_ROOM_BOUNDARIES_MIGRATION_ID, file, line, message };
|
|
4307
|
+
}
|
|
4211
4308
|
function migrateWorkerOwnedNotFound(source, file) {
|
|
4212
4309
|
let replacements = 0;
|
|
4213
4310
|
if (file.endsWith("wrangler.toml")) {
|
|
@@ -4273,12 +4370,22 @@ function findWorkerOwnedNotFoundBlockers(source, file) {
|
|
|
4273
4370
|
|
|
4274
4371
|
// src/cli/commands/deploy/build.ts
|
|
4275
4372
|
var RESERVED_RUN_WORKER_FIRST2 = new Set(RESERVED_RUN_WORKER_FIRST);
|
|
4373
|
+
function secureBuildDevVars(workerDir) {
|
|
4374
|
+
const path = join16(workerDir, ".dev.vars");
|
|
4375
|
+
if (!existsSync14(path)) return false;
|
|
4376
|
+
const stat = lstatSync2(path);
|
|
4377
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
4378
|
+
throw new Error(`Refusing unsafe build secret path: ${path}`);
|
|
4379
|
+
}
|
|
4380
|
+
chmodSync2(path, 384);
|
|
4381
|
+
return true;
|
|
4382
|
+
}
|
|
4276
4383
|
function declaredCompatibilityDate(config) {
|
|
4277
4384
|
const declared = config.compatibility_date;
|
|
4278
4385
|
return typeof declared === "string" && /^\d{4}-\d{2}-\d{2}$/.test(declared) ? declared : null;
|
|
4279
4386
|
}
|
|
4280
4387
|
async function buildDeployBundle(options) {
|
|
4281
|
-
const { appDir, appName, envName,
|
|
4388
|
+
const { appDir, appName, envName, output, spinner: spinner2 } = options;
|
|
4282
4389
|
const junk = removeMacosJunk(appDir);
|
|
4283
4390
|
if (junk > 0) p6.log.info(`Removed ${junk} macOS metadata file(s) (._*, .DS_Store)`);
|
|
4284
4391
|
const lintFindings = await lintProjectSchemas(appDir);
|
|
@@ -4288,9 +4395,7 @@ async function buildDeployBundle(options) {
|
|
|
4288
4395
|
spinner2.start("Building...");
|
|
4289
4396
|
let preparedWranglerConfig;
|
|
4290
4397
|
try {
|
|
4291
|
-
preparedWranglerConfig = prepareWranglerEnvConfig(appDir, envName
|
|
4292
|
-
sharedDevVarsCache
|
|
4293
|
-
});
|
|
4398
|
+
preparedWranglerConfig = prepareWranglerEnvConfig(appDir, envName);
|
|
4294
4399
|
const appDomain = DEEPSPACE_ENV === "staging" ? "spacestest.com" : "app.space";
|
|
4295
4400
|
execSync2("npx vite build", {
|
|
4296
4401
|
cwd: appDir,
|
|
@@ -4311,18 +4416,18 @@ async function buildDeployBundle(options) {
|
|
|
4311
4416
|
}
|
|
4312
4417
|
spinner2.stop("Built");
|
|
4313
4418
|
const deployConfigPath = join16(appDir, ".wrangler", "deploy", "config.json");
|
|
4314
|
-
if (!
|
|
4419
|
+
if (!existsSync14(deployConfigPath)) {
|
|
4315
4420
|
output.die(
|
|
4316
4421
|
"Build output config not found at .wrangler/deploy/config.json",
|
|
4317
4422
|
"build_output_missing"
|
|
4318
4423
|
);
|
|
4319
4424
|
}
|
|
4320
|
-
const deployConfig = JSON.parse(
|
|
4425
|
+
const deployConfig = JSON.parse(readFileSync17(deployConfigPath, "utf-8"));
|
|
4321
4426
|
const outputWranglerPath = resolve11(dirname6(deployConfigPath), deployConfig.configPath);
|
|
4322
|
-
if (!
|
|
4427
|
+
if (!existsSync14(outputWranglerPath)) {
|
|
4323
4428
|
output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
|
|
4324
4429
|
}
|
|
4325
|
-
const outputConfig = JSON.parse(
|
|
4430
|
+
const outputConfig = JSON.parse(readFileSync17(outputWranglerPath, "utf-8"));
|
|
4326
4431
|
if (outputConfig.name && outputConfig.name !== appName) {
|
|
4327
4432
|
const envHint = envName ? ` (--env ${envName})` : "";
|
|
4328
4433
|
output.die(
|
|
@@ -4331,12 +4436,17 @@ async function buildDeployBundle(options) {
|
|
|
4331
4436
|
);
|
|
4332
4437
|
}
|
|
4333
4438
|
const workerDir = dirname6(outputWranglerPath);
|
|
4439
|
+
try {
|
|
4440
|
+
secureBuildDevVars(workerDir);
|
|
4441
|
+
} catch (error) {
|
|
4442
|
+
output.die(errorMessage(error), "build_output_unsafe");
|
|
4443
|
+
}
|
|
4334
4444
|
const workerBundlePath = join16(workerDir, outputConfig.main);
|
|
4335
4445
|
const clientDir = outputConfig.assets?.directory ? resolve11(workerDir, outputConfig.assets.directory) : null;
|
|
4336
|
-
if (!
|
|
4446
|
+
if (!existsSync14(workerBundlePath)) {
|
|
4337
4447
|
output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
|
|
4338
4448
|
}
|
|
4339
|
-
if (!clientDir || !
|
|
4449
|
+
if (!clientDir || !existsSync14(clientDir)) {
|
|
4340
4450
|
return output.die(`Client assets not found at ${clientDir}`, "build_output_missing");
|
|
4341
4451
|
}
|
|
4342
4452
|
try {
|
|
@@ -4349,7 +4459,7 @@ async function buildDeployBundle(options) {
|
|
|
4349
4459
|
const assets = collectAssets(clientDir);
|
|
4350
4460
|
const assetConfig = readDeployAssetConfig(clientDir);
|
|
4351
4461
|
spinner2.stop(`Collected ${assets.length} assets`);
|
|
4352
|
-
const workerJs =
|
|
4462
|
+
const workerJs = readFileSync17(workerBundlePath, "utf-8");
|
|
4353
4463
|
const doBindings = outputConfig.durable_objects?.bindings;
|
|
4354
4464
|
const sqliteClasses = new Set(
|
|
4355
4465
|
outputConfig.migrations?.flatMap((migration) => migration.new_sqlite_classes ?? []) ?? []
|
|
@@ -4428,7 +4538,7 @@ function collectAssets(dir) {
|
|
|
4428
4538
|
if (entry.isDirectory()) {
|
|
4429
4539
|
walk(fullPath, relativePath);
|
|
4430
4540
|
} else {
|
|
4431
|
-
const bytes =
|
|
4541
|
+
const bytes = readFileSync17(fullPath);
|
|
4432
4542
|
assets.push({
|
|
4433
4543
|
path: `/${relativePath}`,
|
|
4434
4544
|
hash: createHash2("sha256").update(bytes).digest("hex"),
|
|
@@ -4443,10 +4553,13 @@ function collectAssets(dir) {
|
|
|
4443
4553
|
}
|
|
4444
4554
|
function readDeployAssetConfig(clientDir) {
|
|
4445
4555
|
const config = {};
|
|
4446
|
-
for (const [file, key] of [
|
|
4556
|
+
for (const [file, key] of [
|
|
4557
|
+
["_headers", "_headers"],
|
|
4558
|
+
["_redirects", "_redirects"]
|
|
4559
|
+
]) {
|
|
4447
4560
|
const filePath = join16(clientDir, file);
|
|
4448
|
-
if (!
|
|
4449
|
-
const content =
|
|
4561
|
+
if (!existsSync14(filePath)) continue;
|
|
4562
|
+
const content = readFileSync17(filePath, "utf8");
|
|
4450
4563
|
if (content.trim()) config[key] = content;
|
|
4451
4564
|
}
|
|
4452
4565
|
return config;
|
|
@@ -4460,7 +4573,7 @@ function errorMessage(error) {
|
|
|
4460
4573
|
|
|
4461
4574
|
// src/cli/commands/deploy/commerce.ts
|
|
4462
4575
|
import * as p7 from "@clack/prompts";
|
|
4463
|
-
import { existsSync as
|
|
4576
|
+
import { existsSync as existsSync15 } from "fs";
|
|
4464
4577
|
import { join as join17 } from "path";
|
|
4465
4578
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
4466
4579
|
var API_URL2 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
@@ -4469,7 +4582,7 @@ function errorMessage2(error) {
|
|
|
4469
4582
|
}
|
|
4470
4583
|
async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
|
|
4471
4584
|
const sourcePath = join17(appDir, "src", sourceFile);
|
|
4472
|
-
if (!
|
|
4585
|
+
if (!existsSync15(sourcePath)) return void 0;
|
|
4473
4586
|
const esbuild = await import("esbuild");
|
|
4474
4587
|
const outputPath = join17(appDir, ".wrangler", "deploy", outputFile);
|
|
4475
4588
|
await esbuild.build({
|
|
@@ -4564,7 +4677,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
|
|
|
4564
4677
|
}
|
|
4565
4678
|
}
|
|
4566
4679
|
async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
4567
|
-
if (!
|
|
4680
|
+
if (!existsSync15(join17(appDir, "src", "subscriptions.ts"))) return;
|
|
4568
4681
|
let plans;
|
|
4569
4682
|
try {
|
|
4570
4683
|
plans = await loadDeclaration(
|
|
@@ -4601,7 +4714,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
|
4601
4714
|
spinner2.stop(`Synced ${plans.length} plan${plans.length === 1 ? "" : "s"} to Stripe`);
|
|
4602
4715
|
if (body.connectMissing) {
|
|
4603
4716
|
p7.log.warn(
|
|
4604
|
-
"Stripe Connect not
|
|
4717
|
+
"Stripe Connect is not ready. Paid checkout will remain unavailable until the app owner finishes onboarding at /earnings on the dashboard."
|
|
4605
4718
|
);
|
|
4606
4719
|
}
|
|
4607
4720
|
const changes = (body.planChanges ?? []).filter((change) => change.affectedSubscribers > 0);
|
|
@@ -4619,7 +4732,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
|
4619
4732
|
}
|
|
4620
4733
|
}
|
|
4621
4734
|
async function syncOneTimeProducts(appDir, appName, token) {
|
|
4622
|
-
if (!
|
|
4735
|
+
if (!existsSync15(join17(appDir, "src", "products.ts"))) return;
|
|
4623
4736
|
let products;
|
|
4624
4737
|
try {
|
|
4625
4738
|
products = await loadDeclaration(
|
|
@@ -4721,7 +4834,7 @@ function createDeployOutput(json) {
|
|
|
4721
4834
|
|
|
4722
4835
|
// src/cli/commands/deploy/request.ts
|
|
4723
4836
|
import * as p10 from "@clack/prompts";
|
|
4724
|
-
import { readFileSync as
|
|
4837
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
4725
4838
|
|
|
4726
4839
|
// src/cli/commands/deploy/repository.ts
|
|
4727
4840
|
import * as p9 from "@clack/prompts";
|
|
@@ -4912,7 +5025,7 @@ function repoApi(deployUrl, token, appId) {
|
|
|
4912
5025
|
}
|
|
4913
5026
|
|
|
4914
5027
|
// src/cli/lib/vc-remote.ts
|
|
4915
|
-
import { existsSync as
|
|
5028
|
+
import { existsSync as existsSync16 } from "fs";
|
|
4916
5029
|
import { tmpdir } from "os";
|
|
4917
5030
|
import { isAbsolute as isAbsolute3, relative, resolve as resolve12 } from "path";
|
|
4918
5031
|
function spaceRemoteName(environment = DEEPSPACE_ENV) {
|
|
@@ -4967,7 +5080,7 @@ function helperEntryUsable(command) {
|
|
|
4967
5080
|
const match = /^!(?:DEEPSPACE_ENV=(production|staging) )?'([^']*)' '([^']*)' git-credential$/.exec(command);
|
|
4968
5081
|
const expectedEnvironment = DEEPSPACE_ENV === "production" ? void 0 : DEEPSPACE_ENV;
|
|
4969
5082
|
if (!match || match[1] !== expectedEnvironment) return false;
|
|
4970
|
-
return
|
|
5083
|
+
return existsSync16(match[2]) && existsSync16(match[3]);
|
|
4971
5084
|
}
|
|
4972
5085
|
function credentialHelperCommand(environment = DEEPSPACE_ENV) {
|
|
4973
5086
|
const entry = process.argv[1];
|
|
@@ -5242,7 +5355,16 @@ function selectGitHubRemote(cwd, options = {}) {
|
|
|
5242
5355
|
return matching.find((remote) => remote.name === "origin") ?? (matching.length === 1 ? matching[0] : null);
|
|
5243
5356
|
}
|
|
5244
5357
|
function remotePublicRefs(cwd, remote) {
|
|
5245
|
-
|
|
5358
|
+
let output;
|
|
5359
|
+
try {
|
|
5360
|
+
output = runGit(cwd, ["ls-remote", "--refs", remote]).stdout.toString("utf-8");
|
|
5361
|
+
} catch (error) {
|
|
5362
|
+
if (!isGitCredentialFailure(error)) throw error;
|
|
5363
|
+
throw new GitError(
|
|
5364
|
+
"Git could not authenticate the remote. In a container, forward an SSH agent or use an ephemeral Git credential helper, then retry. Do not put tokens in remote URLs.",
|
|
5365
|
+
"github_credentials_required"
|
|
5366
|
+
);
|
|
5367
|
+
}
|
|
5246
5368
|
return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
5247
5369
|
const [oid, name] = line.split(/\s+/, 2);
|
|
5248
5370
|
return { oid, name };
|
|
@@ -5250,6 +5372,12 @@ function remotePublicRefs(cwd, remote) {
|
|
|
5250
5372
|
(ref) => /^[0-9a-f]{40}$/.test(ref.oid) && (ref.name.startsWith("refs/heads/") || ref.name.startsWith("refs/tags/"))
|
|
5251
5373
|
);
|
|
5252
5374
|
}
|
|
5375
|
+
function isGitCredentialFailure(error) {
|
|
5376
|
+
if (!(error instanceof Error)) return false;
|
|
5377
|
+
return /authentication failed|could not read username|permission denied \(publickey\)|repository not found|terminal prompts disabled/i.test(
|
|
5378
|
+
error.message
|
|
5379
|
+
);
|
|
5380
|
+
}
|
|
5253
5381
|
function remoteBranchOid(cwd, remote, branch) {
|
|
5254
5382
|
return remotePublicRefs(cwd, remote).find((ref) => ref.name === `refs/heads/${branch}`)?.oid ?? null;
|
|
5255
5383
|
}
|
|
@@ -5291,6 +5419,25 @@ function setAppSource(deployUrl, token, appId, input) {
|
|
|
5291
5419
|
}
|
|
5292
5420
|
|
|
5293
5421
|
// src/cli/commands/deploy/repository.ts
|
|
5422
|
+
function preflightDeployRepository(options) {
|
|
5423
|
+
const { appDir, push, source } = options;
|
|
5424
|
+
if (!push || source?.provider === "github") return null;
|
|
5425
|
+
try {
|
|
5426
|
+
assertSyncableRepo(appDir);
|
|
5427
|
+
if (!source && listGitHubRemotes(appDir).length > 0) {
|
|
5428
|
+
return {
|
|
5429
|
+
code: "source_unclaimed",
|
|
5430
|
+
error: "This app has a GitHub remote but no claimed source. Choose once with `deepspace app source github` (manual GitHub ownership) or `deepspace app source deepspace` (packaged DeepSpace source), then deploy again."
|
|
5431
|
+
};
|
|
5432
|
+
}
|
|
5433
|
+
const branch = currentBranch(appDir);
|
|
5434
|
+
if (!isWorkTreeClean(appDir)) return dirtyWorktreeRefusal(branch);
|
|
5435
|
+
if (!branch) return detachedHeadRefusal();
|
|
5436
|
+
return null;
|
|
5437
|
+
} catch (error) {
|
|
5438
|
+
return deployRepositoryFailure(error, appDir);
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5294
5441
|
async function syncDeployRepository(options) {
|
|
5295
5442
|
const { deployUrl, appDir, appId, token, push, ignoreStale, output, sourceState } = options;
|
|
5296
5443
|
let commitOid = null;
|
|
@@ -5311,21 +5458,12 @@ async function syncDeployRepository(options) {
|
|
|
5311
5458
|
return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId };
|
|
5312
5459
|
}
|
|
5313
5460
|
try {
|
|
5314
|
-
|
|
5315
|
-
if (
|
|
5316
|
-
output.die(
|
|
5317
|
-
"This app has a GitHub remote but no claimed source. Choose once with `deepspace app source github` (manual GitHub ownership) or `deepspace app source deepspace` (packaged DeepSpace source), then deploy again.",
|
|
5318
|
-
"source_unclaimed"
|
|
5319
|
-
);
|
|
5320
|
-
}
|
|
5461
|
+
const preflight = preflightDeployRepository({ appDir, push, source });
|
|
5462
|
+
if (preflight) output.die(preflight.error, preflight.code);
|
|
5321
5463
|
const sourceRemote = spaceRemoteName();
|
|
5322
5464
|
ensureSpaceRemote(appDir, appId, sourceRemote);
|
|
5323
5465
|
const branch = currentBranch(appDir);
|
|
5324
5466
|
const workspaceBranchId = workspaceIdFromBranch(branch);
|
|
5325
|
-
if (!isWorkTreeClean(appDir)) {
|
|
5326
|
-
const refusal = dirtyWorktreeRefusal(branch);
|
|
5327
|
-
output.die(refusal.error, refusal.code);
|
|
5328
|
-
}
|
|
5329
5467
|
if (!branch) {
|
|
5330
5468
|
const refusal = detachedHeadRefusal();
|
|
5331
5469
|
output.die(refusal.error, refusal.code);
|
|
@@ -5427,12 +5565,7 @@ function classifyRemoteState(appDir, token, branch, localTip, remote) {
|
|
|
5427
5565
|
let remoteTip = null;
|
|
5428
5566
|
const remoteRef = `refs/remotes/${remote}/${branch}`;
|
|
5429
5567
|
try {
|
|
5430
|
-
runGitRemote(appDir, token, [
|
|
5431
|
-
"fetch",
|
|
5432
|
-
"--quiet",
|
|
5433
|
-
remote,
|
|
5434
|
-
`+refs/heads/${branch}:${remoteRef}`
|
|
5435
|
-
]);
|
|
5568
|
+
runGitRemote(appDir, token, ["fetch", "--quiet", remote, `+refs/heads/${branch}:${remoteRef}`]);
|
|
5436
5569
|
remoteTip = resolveCommit(appDir, remoteRef);
|
|
5437
5570
|
return {
|
|
5438
5571
|
remoteTip,
|
|
@@ -5554,7 +5687,7 @@ async function deployBuiltBundle(options) {
|
|
|
5554
5687
|
if (bundle.customBindings.length) {
|
|
5555
5688
|
form.append("bindingManifest", JSON.stringify(bundle.customBindings));
|
|
5556
5689
|
}
|
|
5557
|
-
if (secrets.
|
|
5690
|
+
if (secrets.authoritative) form.append("userSecrets", JSON.stringify(secrets.values));
|
|
5558
5691
|
if (bundle.extraRoutes === true || bundle.extraRoutes.length) {
|
|
5559
5692
|
form.append("extraRunWorkerFirst", JSON.stringify(bundle.extraRoutes));
|
|
5560
5693
|
}
|
|
@@ -5746,7 +5879,7 @@ async function uploadDeployAssets(options) {
|
|
|
5746
5879
|
// against the plan before it touches storage, and a stream body
|
|
5747
5880
|
// would be sent chunked with no length at all; a retry also needs a
|
|
5748
5881
|
// body it can send again.
|
|
5749
|
-
body:
|
|
5882
|
+
body: readFileSync18(asset.sourcePath)
|
|
5750
5883
|
})
|
|
5751
5884
|
);
|
|
5752
5885
|
if (!response.ok) {
|
|
@@ -5762,9 +5895,7 @@ async function uploadDeployAssets(options) {
|
|
|
5762
5895
|
}
|
|
5763
5896
|
};
|
|
5764
5897
|
try {
|
|
5765
|
-
await Promise.all(
|
|
5766
|
-
Array.from({ length: Math.min(UPLOAD_CONCURRENCY, missing.length) }, worker)
|
|
5767
|
-
);
|
|
5898
|
+
await Promise.all(Array.from({ length: Math.min(UPLOAD_CONCURRENCY, missing.length) }, worker));
|
|
5768
5899
|
} catch (error) {
|
|
5769
5900
|
spinner2.stop("Deploy failed");
|
|
5770
5901
|
if (error instanceof AssetUploadError) {
|
|
@@ -5833,35 +5964,17 @@ async function postWithRetry(url, makeInit, {
|
|
|
5833
5964
|
|
|
5834
5965
|
// src/cli/commands/deploy/secrets.ts
|
|
5835
5966
|
import * as p11 from "@clack/prompts";
|
|
5836
|
-
import { existsSync as existsSync18, readFileSync as readFileSync20 } from "fs";
|
|
5837
|
-
function classifyDevVarsSecrets(options) {
|
|
5838
|
-
const store = new Set(options.storeSecretNames);
|
|
5839
|
-
const strayKeys = options.handEditedDevVarKeys.filter((key) => !store.has(key));
|
|
5840
|
-
if (strayKeys.length === 0) return { kind: "ok" };
|
|
5841
|
-
if (options.storeSecretNames.length === 0 && !options.allowMissing) {
|
|
5842
|
-
return { kind: "block", strayKeys };
|
|
5843
|
-
}
|
|
5844
|
-
return { kind: "warn", strayKeys };
|
|
5845
|
-
}
|
|
5846
5967
|
async function loadDeploySecrets(options) {
|
|
5847
5968
|
const { deployUrl, appDir, appId, envName, ownerId, token, output } = options;
|
|
5848
5969
|
let linked = null;
|
|
5849
|
-
let localOnlyDevVars = {};
|
|
5850
5970
|
try {
|
|
5851
5971
|
const refreshed = await refreshSecretsCache(deployUrl, token, appId, envName);
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
await writeDevVars(appDir, ownerId, token, envName, {
|
|
5859
|
-
appId,
|
|
5860
|
-
generatedSecretsCache: refreshed.rendered,
|
|
5861
|
-
sharedDevVarsCache: true
|
|
5862
|
-
});
|
|
5863
|
-
if (refreshed.summary) p11.log.info(refreshed.summary);
|
|
5864
|
-
}
|
|
5972
|
+
linked = refreshed.pulled;
|
|
5973
|
+
await writeDevVars(appDir, ownerId, token, envName, {
|
|
5974
|
+
appId,
|
|
5975
|
+
generatedSecretsCache: refreshed.rendered
|
|
5976
|
+
});
|
|
5977
|
+
if (refreshed.summary) p11.log.info(refreshed.summary);
|
|
5865
5978
|
} catch (error) {
|
|
5866
5979
|
if (error?.status === 403) {
|
|
5867
5980
|
output.die(
|
|
@@ -5874,40 +5987,31 @@ async function loadDeploySecrets(options) {
|
|
|
5874
5987
|
"secrets_refresh_failed"
|
|
5875
5988
|
);
|
|
5876
5989
|
}
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
function prepareDeploySecrets(options) {
|
|
5880
|
-
const { appDir, envName, cache, allowMissing, customBindings, doManifest, output } = options;
|
|
5881
|
-
const values = cache.linked ? { ...cache.linked.values } : {};
|
|
5882
|
-
const devVarsPath = devVarsPathFor(appDir, envName, {
|
|
5883
|
-
sharedDevVarsCache: cache.linked !== null
|
|
5884
|
-
});
|
|
5885
|
-
const handEditedDevVars = cache.linked ? cache.localOnlyDevVars : readCustomDevVars(devVarsPath, output);
|
|
5886
|
-
const decision = classifyDevVarsSecrets({
|
|
5887
|
-
storeSecretNames: Object.keys(values),
|
|
5888
|
-
handEditedDevVarKeys: Object.keys(handEditedDevVars),
|
|
5889
|
-
allowMissing
|
|
5890
|
-
});
|
|
5891
|
-
if (decision.kind === "block") {
|
|
5892
|
-
const uploadArgv = [
|
|
5893
|
-
"deepspace",
|
|
5894
|
-
"secrets",
|
|
5895
|
-
"upload",
|
|
5896
|
-
".dev.vars",
|
|
5897
|
-
...envName ? ["-e", envName] : []
|
|
5898
|
-
];
|
|
5990
|
+
if (!linked) {
|
|
5991
|
+
const configName = defaultConfigNameForEnv(envName);
|
|
5899
5992
|
output.die(
|
|
5900
|
-
`
|
|
5901
|
-
"
|
|
5902
|
-
{
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5993
|
+
`Secrets config "${configName}" does not exist. Create it explicitly before deploying; this distinguishes an intentional empty config from an uninitialized app and protects live Worker secrets during the platform rollout.`,
|
|
5994
|
+
"secrets_config_missing",
|
|
5995
|
+
{
|
|
5996
|
+
action: {
|
|
5997
|
+
cwd: appDir,
|
|
5998
|
+
argv: [
|
|
5999
|
+
"deepspace",
|
|
6000
|
+
"secrets",
|
|
6001
|
+
"configs",
|
|
6002
|
+
"create",
|
|
6003
|
+
configName,
|
|
6004
|
+
...envName ? ["--env", envName] : []
|
|
6005
|
+
]
|
|
6006
|
+
}
|
|
6007
|
+
}
|
|
5909
6008
|
);
|
|
5910
6009
|
}
|
|
6010
|
+
return { linked };
|
|
6011
|
+
}
|
|
6012
|
+
function prepareDeploySecrets(options) {
|
|
6013
|
+
const { cache, customBindings, doManifest, output } = options;
|
|
6014
|
+
const values = cache.linked ? { ...cache.linked.values } : {};
|
|
5911
6015
|
const names = Object.keys(values);
|
|
5912
6016
|
const declaredBindingNames = /* @__PURE__ */ new Set([
|
|
5913
6017
|
...customBindings.map((binding) => binding.name),
|
|
@@ -5928,17 +6032,7 @@ function prepareDeploySecrets(options) {
|
|
|
5928
6032
|
}
|
|
5929
6033
|
}
|
|
5930
6034
|
if (names.length) p11.log.info(`App secrets: ${names.join(", ")}`);
|
|
5931
|
-
return { values, names };
|
|
5932
|
-
}
|
|
5933
|
-
function readCustomDevVars(path, output) {
|
|
5934
|
-
if (!existsSync18(path)) return {};
|
|
5935
|
-
const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync20(path, "utf-8")));
|
|
5936
|
-
if (!section) return {};
|
|
5937
|
-
try {
|
|
5938
|
-
return parseDevVars(section);
|
|
5939
|
-
} catch (error) {
|
|
5940
|
-
output.die(`.dev.vars: ${errorMessage5(error)}`, "invalid_dev_vars");
|
|
5941
|
-
}
|
|
6035
|
+
return { values, names, authoritative: cache.linked !== null };
|
|
5942
6036
|
}
|
|
5943
6037
|
function errorMessage5(error) {
|
|
5944
6038
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -5977,11 +6071,6 @@ var deploy_default = defineCommand3({
|
|
|
5977
6071
|
description: "Platform admins only: claim an app name still inside its 30-day release cooldown. The previous owner permanently loses their reserved reclaim. Non-admin accounts are refused.",
|
|
5978
6072
|
default: false
|
|
5979
6073
|
},
|
|
5980
|
-
"allow-missing-secrets": {
|
|
5981
|
-
type: "boolean",
|
|
5982
|
-
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).",
|
|
5983
|
-
default: false
|
|
5984
|
-
},
|
|
5985
6074
|
// Citty maps --no-push to the negation of an argument named `push`.
|
|
5986
6075
|
push: {
|
|
5987
6076
|
type: "boolean",
|
|
@@ -6050,12 +6139,19 @@ var deploy_default = defineCommand3({
|
|
|
6050
6139
|
output
|
|
6051
6140
|
});
|
|
6052
6141
|
const sourceState = await getAppSource(DEPLOY_URL3, token, appId);
|
|
6142
|
+
const repositoryPreflight = preflightDeployRepository({
|
|
6143
|
+
appDir,
|
|
6144
|
+
push: args.push !== false,
|
|
6145
|
+
source: sourceState.source
|
|
6146
|
+
});
|
|
6147
|
+
if (repositoryPreflight) {
|
|
6148
|
+
output.die(repositoryPreflight.error, repositoryPreflight.code);
|
|
6149
|
+
}
|
|
6053
6150
|
const spinner2 = createSpinner();
|
|
6054
6151
|
const bundle = await buildDeployBundle({
|
|
6055
6152
|
appDir,
|
|
6056
6153
|
appName,
|
|
6057
6154
|
envName,
|
|
6058
|
-
sharedDevVarsCache: secretsCache.linked !== null,
|
|
6059
6155
|
output,
|
|
6060
6156
|
spinner: spinner2
|
|
6061
6157
|
});
|
|
@@ -6075,10 +6171,7 @@ var deploy_default = defineCommand3({
|
|
|
6075
6171
|
sourceState
|
|
6076
6172
|
});
|
|
6077
6173
|
const secrets = prepareDeploySecrets({
|
|
6078
|
-
appDir,
|
|
6079
|
-
envName,
|
|
6080
6174
|
cache: secretsCache,
|
|
6081
|
-
allowMissing: Boolean(args["allow-missing-secrets"]),
|
|
6082
6175
|
customBindings: bundle.customBindings,
|
|
6083
6176
|
doManifest: bundle.doManifest,
|
|
6084
6177
|
output
|
|
@@ -6516,11 +6609,17 @@ function parseSince(input, now = Date.now()) {
|
|
|
6516
6609
|
} else {
|
|
6517
6610
|
ts = Date.parse(trimmed);
|
|
6518
6611
|
if (Number.isNaN(ts)) {
|
|
6519
|
-
throw new InputError(
|
|
6612
|
+
throw new InputError(
|
|
6613
|
+
`Invalid --since "${input}" \u2014 use 30s, 15m, 2h, 7d, or an ISO timestamp.`,
|
|
6614
|
+
"invalid_since"
|
|
6615
|
+
);
|
|
6520
6616
|
}
|
|
6521
6617
|
}
|
|
6522
6618
|
if (now - ts > RETENTION_MS) {
|
|
6523
|
-
throw new InputError(
|
|
6619
|
+
throw new InputError(
|
|
6620
|
+
"Logs are retained for 7 days \u2014 --since can reach back at most 7d.",
|
|
6621
|
+
"since_out_of_range"
|
|
6622
|
+
);
|
|
6524
6623
|
}
|
|
6525
6624
|
return ts;
|
|
6526
6625
|
}
|
|
@@ -6651,13 +6750,19 @@ var logs_default = defineCommand4({
|
|
|
6651
6750
|
const token = await ensureToken();
|
|
6652
6751
|
const appId = await resolveAppTarget(DEPLOY_URL6, token, args.app, { wranglerEnv: envArg });
|
|
6653
6752
|
if (args.level && !LOG_LEVELS.includes(args.level)) {
|
|
6654
|
-
throw new InputError(
|
|
6753
|
+
throw new InputError(
|
|
6754
|
+
`Invalid --level "${args.level}". Use: ${LOG_LEVELS.join(", ")}`,
|
|
6755
|
+
"invalid_level"
|
|
6756
|
+
);
|
|
6655
6757
|
}
|
|
6656
6758
|
let limit;
|
|
6657
6759
|
if (args.limit !== void 0) {
|
|
6658
6760
|
limit = Number(args.limit);
|
|
6659
6761
|
if (!Number.isInteger(limit) || limit < 1 || limit > 500) {
|
|
6660
|
-
throw new InputError(
|
|
6762
|
+
throw new InputError(
|
|
6763
|
+
"Invalid --limit \u2014 expected an integer between 1 and 500.",
|
|
6764
|
+
"invalid_limit"
|
|
6765
|
+
);
|
|
6661
6766
|
}
|
|
6662
6767
|
}
|
|
6663
6768
|
const initialSince = args.since ? parseSince(args.since) : Date.now() - DEFAULT_WINDOW_MS;
|
|
@@ -6673,7 +6778,9 @@ var logs_default = defineCommand4({
|
|
|
6673
6778
|
};
|
|
6674
6779
|
const print = (events) => {
|
|
6675
6780
|
for (const e of events) {
|
|
6676
|
-
process.stdout.write(
|
|
6781
|
+
process.stdout.write(
|
|
6782
|
+
args.json ? JSON.stringify(e) + "\n" : formatEvent(e, stdoutColor()) + "\n"
|
|
6783
|
+
);
|
|
6677
6784
|
}
|
|
6678
6785
|
};
|
|
6679
6786
|
const first = await fetchPage(initialSince, args.follow ? followLimit : limit, token);
|
|
@@ -6681,15 +6788,25 @@ var logs_default = defineCommand4({
|
|
|
6681
6788
|
if (!args.follow) {
|
|
6682
6789
|
if (first.events.length === 0) {
|
|
6683
6790
|
if (args.json) {
|
|
6684
|
-
process.stdout.write(
|
|
6791
|
+
process.stdout.write(
|
|
6792
|
+
JSON.stringify({ type: "meta", count: 0, window: windowLabel }) + "\n"
|
|
6793
|
+
);
|
|
6685
6794
|
} else {
|
|
6686
6795
|
console.log(
|
|
6687
6796
|
`No logs in the last ${windowLabel} for ${appId}. New logs can take up to a minute to appear; retention is 7 days.`
|
|
6688
6797
|
);
|
|
6689
6798
|
}
|
|
6690
6799
|
} else if (first.truncated) {
|
|
6691
|
-
if (args.json)
|
|
6692
|
-
|
|
6800
|
+
if (args.json)
|
|
6801
|
+
process.stdout.write(JSON.stringify({ type: "meta", truncated: true }) + "\n");
|
|
6802
|
+
else if (args.search)
|
|
6803
|
+
console.error(
|
|
6804
|
+
`(showing the newest ${first.events.length} matches \u2014 search scans at most the newest 500 level-filtered events per fetch; narrow with --since/--level or raise --limit)`
|
|
6805
|
+
);
|
|
6806
|
+
else
|
|
6807
|
+
console.error(
|
|
6808
|
+
`(showing the newest ${first.events.length} events \u2014 narrow with --since/--level)`
|
|
6809
|
+
);
|
|
6693
6810
|
}
|
|
6694
6811
|
return;
|
|
6695
6812
|
}
|
|
@@ -6709,7 +6826,11 @@ var logs_default = defineCommand4({
|
|
|
6709
6826
|
process.stdout.write(JSON.stringify({ type: "meta", truncated: true }) + "\n");
|
|
6710
6827
|
} else {
|
|
6711
6828
|
console.error(
|
|
6712
|
-
paint(
|
|
6829
|
+
paint(
|
|
6830
|
+
"33",
|
|
6831
|
+
args.search ? `(search scanned only the newest ${MAX_LIMIT} level-filtered events in the initial ${windowLabel} window \u2014 older matches may be omitted; narrow with --since/--level)` : `(more than ${followLimit} events in the initial ${windowLabel} window \u2014 some older events not shown; narrow with --since/--level)`,
|
|
6832
|
+
stderrColor()
|
|
6833
|
+
)
|
|
6713
6834
|
);
|
|
6714
6835
|
}
|
|
6715
6836
|
}
|
|
@@ -6723,21 +6844,27 @@ var logs_default = defineCommand4({
|
|
|
6723
6844
|
if (err instanceof ApiError && [401, 403, 404].includes(err.status)) throw err;
|
|
6724
6845
|
backoff = Math.min(backoff ? backoff * 2 : MIN_POLL_MS, 3e4);
|
|
6725
6846
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6726
|
-
console.error(
|
|
6847
|
+
console.error(
|
|
6848
|
+
paint("2", `(logs fetch failed: ${msg} \u2014 retrying in ${backoff / 1e3}s)`, stderrColor())
|
|
6849
|
+
);
|
|
6727
6850
|
continue;
|
|
6728
6851
|
}
|
|
6729
6852
|
backoff = 0;
|
|
6730
6853
|
interval = Math.max(page.pollIntervalMs || 0, MIN_POLL_MS);
|
|
6731
6854
|
const fresh = seen.fresh(page.events);
|
|
6732
6855
|
print(fresh);
|
|
6733
|
-
const realDrop = fresh.length >= followLimit;
|
|
6856
|
+
const realDrop = fresh.length >= followLimit || Boolean(args.search && page.truncated);
|
|
6734
6857
|
if (realDrop && !warnedTruncated) {
|
|
6735
6858
|
warnedTruncated = true;
|
|
6736
6859
|
if (args.json) {
|
|
6737
6860
|
process.stdout.write(JSON.stringify({ type: "meta", truncated: true }) + "\n");
|
|
6738
6861
|
} else {
|
|
6739
6862
|
console.error(
|
|
6740
|
-
paint(
|
|
6863
|
+
paint(
|
|
6864
|
+
"33",
|
|
6865
|
+
args.search ? `(search scanned only the newest ${MAX_LIMIT} level-filtered events this poll \u2014 older matches may be omitted; narrow with --level)` : `(burst exceeded ${followLimit} events/poll \u2014 some may be dropped; narrow with --level)`,
|
|
6866
|
+
stderrColor()
|
|
6867
|
+
)
|
|
6741
6868
|
);
|
|
6742
6869
|
}
|
|
6743
6870
|
} else if (!realDrop) {
|
|
@@ -6818,7 +6945,7 @@ var usage_default = defineDeepspaceCommand({
|
|
|
6818
6945
|
});
|
|
6819
6946
|
|
|
6820
6947
|
// src/cli/commands/add.ts
|
|
6821
|
-
import { existsSync as
|
|
6948
|
+
import { existsSync as existsSync18, readFileSync as readFileSync20, realpathSync as realpathSync4 } from "fs";
|
|
6822
6949
|
import { dirname as dirname8, join as join19, resolve as resolve15 } from "path";
|
|
6823
6950
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
6824
6951
|
|
|
@@ -6826,12 +6953,12 @@ import { pathToFileURL as pathToFileURL3 } from "url";
|
|
|
6826
6953
|
import {
|
|
6827
6954
|
closeSync as closeSync2,
|
|
6828
6955
|
copyFileSync,
|
|
6829
|
-
existsSync as
|
|
6956
|
+
existsSync as existsSync17,
|
|
6830
6957
|
fstatSync as fstatSync2,
|
|
6831
6958
|
mkdirSync as mkdirSync6,
|
|
6832
6959
|
mkdtempSync,
|
|
6833
6960
|
openSync as openSync2,
|
|
6834
|
-
readFileSync as
|
|
6961
|
+
readFileSync as readFileSync19,
|
|
6835
6962
|
readSync,
|
|
6836
6963
|
readdirSync as readdirSync5,
|
|
6837
6964
|
rmSync as rmSync4,
|
|
@@ -6846,9 +6973,9 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
|
6846
6973
|
let dir = dirname7(fileURLToPath(moduleUrl));
|
|
6847
6974
|
for (; ; ) {
|
|
6848
6975
|
const packagePath = join18(dir, "package.json");
|
|
6849
|
-
if (
|
|
6976
|
+
if (existsSync17(packagePath)) {
|
|
6850
6977
|
try {
|
|
6851
|
-
const pkg2 = JSON.parse(
|
|
6978
|
+
const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
|
|
6852
6979
|
if (pkg2.name === "deepspace") return dir;
|
|
6853
6980
|
} catch {
|
|
6854
6981
|
}
|
|
@@ -6859,13 +6986,13 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
|
6859
6986
|
}
|
|
6860
6987
|
}
|
|
6861
6988
|
function hasFeatureCatalog(packageRoot = featurePackageRoot()) {
|
|
6862
|
-
return
|
|
6989
|
+
return existsSync17(join18(packageRoot, "features"));
|
|
6863
6990
|
}
|
|
6864
6991
|
function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
6865
6992
|
const configPath = join18(packageRoot, "features", id, "feature.json");
|
|
6866
|
-
if (!
|
|
6993
|
+
if (!existsSync17(configPath)) return null;
|
|
6867
6994
|
try {
|
|
6868
|
-
const config = JSON.parse(
|
|
6995
|
+
const config = JSON.parse(readFileSync19(configPath, "utf-8"));
|
|
6869
6996
|
if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
|
|
6870
6997
|
return null;
|
|
6871
6998
|
}
|
|
@@ -6876,7 +7003,7 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
|
6876
7003
|
}
|
|
6877
7004
|
function listFeatureConfigs(packageRoot = featurePackageRoot()) {
|
|
6878
7005
|
const featuresDir = join18(packageRoot, "features");
|
|
6879
|
-
if (!
|
|
7006
|
+
if (!existsSync17(featuresDir)) return [];
|
|
6880
7007
|
return readdirSync5(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readFeatureConfig(entry.name, packageRoot)).filter((config) => config !== null && !config.internal).sort((a, b) => a.id.localeCompare(b.id));
|
|
6881
7008
|
}
|
|
6882
7009
|
function featureCatalog(packageRoot = featurePackageRoot()) {
|
|
@@ -6930,7 +7057,7 @@ function copyFile(src, dest) {
|
|
|
6930
7057
|
function copyFileWithImportShift(src, dest, levels) {
|
|
6931
7058
|
mkdirSync6(dirname7(dest), { recursive: true });
|
|
6932
7059
|
const prefix = "../".repeat(levels);
|
|
6933
|
-
const shifted =
|
|
7060
|
+
const shifted = readFileSync19(src, "utf-8").replace(
|
|
6934
7061
|
/(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
|
|
6935
7062
|
`$1$2${prefix}$3`
|
|
6936
7063
|
);
|
|
@@ -6946,9 +7073,9 @@ function installFiles(packageRoot, config, targetDir, emit) {
|
|
|
6946
7073
|
const srcPath = join18(packageRoot, "features", config.id, file.src);
|
|
6947
7074
|
const resolvedDest = resolveFeatureDestPath(file.dest, config.route);
|
|
6948
7075
|
const destPath = join18(targetDir, resolvedDest);
|
|
6949
|
-
const existed =
|
|
7076
|
+
const existed = existsSync17(destPath);
|
|
6950
7077
|
if (existed) preexisting.push(resolvedDest);
|
|
6951
|
-
if (!
|
|
7078
|
+
if (!existsSync17(srcPath)) {
|
|
6952
7079
|
emit(` Warning: source not found: ${file.src}`, "stderr");
|
|
6953
7080
|
if (!existed) missing.push(resolvedDest);
|
|
6954
7081
|
continue;
|
|
@@ -6986,10 +7113,10 @@ function unresolvedSchema(schema, reason, emit) {
|
|
|
6986
7113
|
function integrateSchema(config, targetDir, emit) {
|
|
6987
7114
|
if (!config.schema) return [];
|
|
6988
7115
|
const schemasPath = join18(targetDir, "src", "schemas.ts");
|
|
6989
|
-
if (!
|
|
7116
|
+
if (!existsSync17(schemasPath)) {
|
|
6990
7117
|
return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
|
|
6991
7118
|
}
|
|
6992
|
-
let content =
|
|
7119
|
+
let content = readFileSync19(schemasPath, "utf-8");
|
|
6993
7120
|
const { exportName, importPath, spreadOperator } = config.schema;
|
|
6994
7121
|
const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
|
|
6995
7122
|
if (importPattern.test(content)) {
|
|
@@ -7043,10 +7170,10 @@ function unresolvedActions(actions, reason, emit) {
|
|
|
7043
7170
|
function integrateActions(config, targetDir, emit) {
|
|
7044
7171
|
if (!config.actions?.length) return [];
|
|
7045
7172
|
const actionsPath = join18(targetDir, "src", "actions", "index.ts");
|
|
7046
|
-
if (!
|
|
7173
|
+
if (!existsSync17(actionsPath)) {
|
|
7047
7174
|
return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
|
|
7048
7175
|
}
|
|
7049
|
-
let content =
|
|
7176
|
+
let content = readFileSync19(actionsPath, "utf-8");
|
|
7050
7177
|
const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
|
|
7051
7178
|
if (!registryPattern.test(content)) {
|
|
7052
7179
|
const reason = "src/actions/index.ts has an unexpected structure.";
|
|
@@ -7093,7 +7220,7 @@ function integrateCode(config, targetDir, emit) {
|
|
|
7093
7220
|
const unresolved = [];
|
|
7094
7221
|
for (const insertion of config.code ?? []) {
|
|
7095
7222
|
const targetPath = join18(targetDir, insertion.file);
|
|
7096
|
-
if (!
|
|
7223
|
+
if (!existsSync17(targetPath)) {
|
|
7097
7224
|
unresolved.push({
|
|
7098
7225
|
area: "code",
|
|
7099
7226
|
reason: `${insertion.file} was not found.`,
|
|
@@ -7101,7 +7228,7 @@ function integrateCode(config, targetDir, emit) {
|
|
|
7101
7228
|
});
|
|
7102
7229
|
continue;
|
|
7103
7230
|
}
|
|
7104
|
-
const original =
|
|
7231
|
+
const original = readFileSync19(targetPath, "utf-8");
|
|
7105
7232
|
if (original.includes(insertion.insert.trim())) {
|
|
7106
7233
|
emit(` Integration already present: ${insertion.file}`);
|
|
7107
7234
|
continue;
|
|
@@ -7137,7 +7264,7 @@ ${content.slice(importIndex < 0 ? 0 : importIndex)}`;
|
|
|
7137
7264
|
function integrateIgnore(config, targetDir) {
|
|
7138
7265
|
if (!config.ignore?.length) return;
|
|
7139
7266
|
const targetPath = join18(targetDir, ".gitignore");
|
|
7140
|
-
const current =
|
|
7267
|
+
const current = existsSync17(targetPath) ? readFileSync19(targetPath, "utf-8") : "";
|
|
7141
7268
|
const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
|
|
7142
7269
|
const missing = config.ignore.filter((pattern) => !present.has(pattern));
|
|
7143
7270
|
if (missing.length === 0) return;
|
|
@@ -7154,10 +7281,10 @@ function integrateWrangler(config, targetDir, emit) {
|
|
|
7154
7281
|
...runWorkerFirst ? ["Set assets.run_worker_first = true in wrangler.toml."] : [],
|
|
7155
7282
|
...rateLimits.map((binding) => `Add the ${binding.name} rate-limit binding to wrangler.toml.`)
|
|
7156
7283
|
];
|
|
7157
|
-
if (!
|
|
7284
|
+
if (!existsSync17(targetPath)) {
|
|
7158
7285
|
return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
|
|
7159
7286
|
}
|
|
7160
|
-
let source =
|
|
7287
|
+
let source = readFileSync19(targetPath, "utf-8");
|
|
7161
7288
|
if (runWorkerFirst && !/^\[assets\]\s*$/m.test(source)) {
|
|
7162
7289
|
return [{ area: "wrangler", reason: "wrangler.toml has no [assets] section.", steps }];
|
|
7163
7290
|
}
|
|
@@ -7234,7 +7361,7 @@ function rateLimitBindingBlock(scope, binding) {
|
|
|
7234
7361
|
function integrateCss(packageRoot, config, targetDir, emit) {
|
|
7235
7362
|
if (!config.css?.length) return [];
|
|
7236
7363
|
const stylesPath = join18(targetDir, "src", "styles.css");
|
|
7237
|
-
if (!
|
|
7364
|
+
if (!existsSync17(stylesPath)) {
|
|
7238
7365
|
emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
|
|
7239
7366
|
return [
|
|
7240
7367
|
{
|
|
@@ -7246,12 +7373,12 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
7246
7373
|
}
|
|
7247
7374
|
];
|
|
7248
7375
|
}
|
|
7249
|
-
let styles =
|
|
7376
|
+
let styles = readFileSync19(stylesPath, "utf-8");
|
|
7250
7377
|
let integrated = 0;
|
|
7251
7378
|
const unresolved = [];
|
|
7252
7379
|
for (const cssFile of config.css) {
|
|
7253
7380
|
const srcPath = join18(packageRoot, "features", config.id, cssFile);
|
|
7254
|
-
if (!
|
|
7381
|
+
if (!existsSync17(srcPath)) {
|
|
7255
7382
|
emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
|
|
7256
7383
|
unresolved.push({
|
|
7257
7384
|
area: "css",
|
|
@@ -7260,7 +7387,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
7260
7387
|
});
|
|
7261
7388
|
continue;
|
|
7262
7389
|
}
|
|
7263
|
-
const css =
|
|
7390
|
+
const css = readFileSync19(srcPath, "utf-8");
|
|
7264
7391
|
const fingerprint = css.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/);
|
|
7265
7392
|
const marker = fingerprint?.[1] ?? fingerprint?.[2];
|
|
7266
7393
|
if (marker && styles.includes(marker)) {
|
|
@@ -7288,10 +7415,10 @@ function unresolvedRoute(route, config, reason, emit) {
|
|
|
7288
7415
|
function integrateRoute(config, targetDir, emit) {
|
|
7289
7416
|
if (!config.route) return [];
|
|
7290
7417
|
const navPath = join18(targetDir, "src", "nav.ts");
|
|
7291
|
-
if (!
|
|
7418
|
+
if (!existsSync17(navPath)) {
|
|
7292
7419
|
return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
|
|
7293
7420
|
}
|
|
7294
|
-
let content =
|
|
7421
|
+
let content = readFileSync19(navPath, "utf-8");
|
|
7295
7422
|
const routePath = config.route.path;
|
|
7296
7423
|
if (content.includes(`'${routePath}'`)) {
|
|
7297
7424
|
emit(` Nav already present: ${routePath}`);
|
|
@@ -7366,13 +7493,13 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
|
|
|
7366
7493
|
return { added: [], installRan: false, packageManager };
|
|
7367
7494
|
}
|
|
7368
7495
|
const packagePath = join18(targetDir, "package.json");
|
|
7369
|
-
if (!
|
|
7496
|
+
if (!existsSync17(packagePath)) {
|
|
7370
7497
|
emit(" Could not patch package.json (file not found)");
|
|
7371
7498
|
return { added: [], installRan: false, packageManager };
|
|
7372
7499
|
}
|
|
7373
7500
|
let pkg2;
|
|
7374
7501
|
try {
|
|
7375
|
-
pkg2 = JSON.parse(
|
|
7502
|
+
pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
|
|
7376
7503
|
} catch (error) {
|
|
7377
7504
|
emit(
|
|
7378
7505
|
` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
|
|
@@ -7432,7 +7559,7 @@ ${child.diagnosticTail}` : "";
|
|
|
7432
7559
|
}
|
|
7433
7560
|
function dependencySnapshot(targetDir) {
|
|
7434
7561
|
try {
|
|
7435
|
-
const pkg2 = JSON.parse(
|
|
7562
|
+
const pkg2 = JSON.parse(readFileSync19(join18(targetDir, "package.json"), "utf-8"));
|
|
7436
7563
|
return {
|
|
7437
7564
|
dependencies: pkg2.dependencies ?? {},
|
|
7438
7565
|
devDependencies: pkg2.devDependencies ?? {}
|
|
@@ -7444,7 +7571,7 @@ function dependencySnapshot(targetDir) {
|
|
|
7444
7571
|
function installedNavFeatures(packageRoot, targetDir) {
|
|
7445
7572
|
return listFeatureConfigs(packageRoot).filter((feature) => feature.category === "nav" && feature.files.length > 0).filter((feature) => {
|
|
7446
7573
|
const signature = resolveFeatureDestPath(feature.files[0].dest, feature.route);
|
|
7447
|
-
return
|
|
7574
|
+
return existsSync17(join18(targetDir, signature));
|
|
7448
7575
|
}).map((feature) => feature.id);
|
|
7449
7576
|
}
|
|
7450
7577
|
function warnNavConflicts(packageRoot, config, targetDir, emit) {
|
|
@@ -7677,11 +7804,11 @@ function renderFeatureInfo(config) {
|
|
|
7677
7804
|
return lines.join("\n");
|
|
7678
7805
|
}
|
|
7679
7806
|
function isDeepSpaceApp(dir) {
|
|
7680
|
-
if (
|
|
7807
|
+
if (existsSync18(join19(dir, "wrangler.toml"))) return true;
|
|
7681
7808
|
const packagePath = join19(dir, "package.json");
|
|
7682
|
-
if (!
|
|
7809
|
+
if (!existsSync18(packagePath)) return false;
|
|
7683
7810
|
try {
|
|
7684
|
-
const pkg2 = JSON.parse(
|
|
7811
|
+
const pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
|
|
7685
7812
|
return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
|
|
7686
7813
|
} catch {
|
|
7687
7814
|
return false;
|
|
@@ -7691,7 +7818,7 @@ function installedPackageRoot(fromDir) {
|
|
|
7691
7818
|
let dir = fromDir;
|
|
7692
7819
|
for (; ; ) {
|
|
7693
7820
|
const candidate = join19(dir, "node_modules", "deepspace");
|
|
7694
|
-
if (
|
|
7821
|
+
if (existsSync18(join19(candidate, "package.json"))) return candidate;
|
|
7695
7822
|
const parent = dirname8(dir);
|
|
7696
7823
|
if (parent === dir) return null;
|
|
7697
7824
|
dir = parent;
|
|
@@ -7710,7 +7837,7 @@ async function targetInstaller(appDir) {
|
|
|
7710
7837
|
}
|
|
7711
7838
|
const entry = join19(installedRoot, "dist", "feature-installer.js");
|
|
7712
7839
|
const catalog = join19(installedRoot, "features");
|
|
7713
|
-
if (!
|
|
7840
|
+
if (!existsSync18(entry) || !existsSync18(catalog)) {
|
|
7714
7841
|
throw new Refusal(
|
|
7715
7842
|
`The app's installed deepspace package does not contain the typed feature installer.
|
|
7716
7843
|
Expected: ${entry}
|
|
@@ -7803,7 +7930,7 @@ var add_default = defineDeepspaceCommand({
|
|
|
7803
7930
|
return { data: { features: featureCatalog(root) } };
|
|
7804
7931
|
}
|
|
7805
7932
|
const appDir = resolve15(args.dir ?? ".");
|
|
7806
|
-
if (!
|
|
7933
|
+
if (!existsSync18(appDir)) {
|
|
7807
7934
|
throw new Refusal(
|
|
7808
7935
|
`Target directory not found: ${appDir}
|
|
7809
7936
|
Create a new app first: deepspace app create <name>`,
|
|
@@ -8179,7 +8306,7 @@ var domain_default = defineCommand5({
|
|
|
8179
8306
|
// src/cli/commands/app-files.ts
|
|
8180
8307
|
import { defineCommand as defineCommand6 } from "citty";
|
|
8181
8308
|
import { basename as basename4, resolve as resolve16 } from "path";
|
|
8182
|
-
import { existsSync as
|
|
8309
|
+
import { existsSync as existsSync19, statSync as statSync2 } from "fs";
|
|
8183
8310
|
|
|
8184
8311
|
// src/cli/lib/app-files-api.ts
|
|
8185
8312
|
import { createWriteStream, openAsBlob, statSync } from "fs";
|
|
@@ -8450,7 +8577,7 @@ var put = defineDeepspaceCommand({
|
|
|
8450
8577
|
},
|
|
8451
8578
|
async run({ args }) {
|
|
8452
8579
|
const localPath = resolve16(String(args.file));
|
|
8453
|
-
if (!
|
|
8580
|
+
if (!existsSync19(localPath) || !statSync2(localPath).isFile()) {
|
|
8454
8581
|
throw new Refusal(`No such file: ${args.file}`, "file_not_found");
|
|
8455
8582
|
}
|
|
8456
8583
|
const key = requireKey(args.key ?? basename4(localPath));
|
|
@@ -8920,7 +9047,7 @@ var transfer_default = defineCommand8({
|
|
|
8920
9047
|
import { defineCommand as defineCommand9 } from "citty";
|
|
8921
9048
|
|
|
8922
9049
|
// src/cli/commands/_invoke-impl.ts
|
|
8923
|
-
import { readFileSync as
|
|
9050
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
8924
9051
|
import * as p15 from "@clack/prompts";
|
|
8925
9052
|
var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
8926
9053
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -8961,7 +9088,7 @@ function resolveBody(opts) {
|
|
|
8961
9088
|
if (opts.body != null) {
|
|
8962
9089
|
raw = opts.body;
|
|
8963
9090
|
} else if (opts.bodyFile != null) {
|
|
8964
|
-
raw = opts.bodyFile === "-" ?
|
|
9091
|
+
raw = opts.bodyFile === "-" ? readFileSync21(0, "utf-8") : readFileSync21(opts.bodyFile, "utf-8");
|
|
8965
9092
|
} else {
|
|
8966
9093
|
return "{}";
|
|
8967
9094
|
}
|
|
@@ -9240,7 +9367,7 @@ var integrations_default = defineCommand9({
|
|
|
9240
9367
|
|
|
9241
9368
|
// src/cli/commands/feedback.ts
|
|
9242
9369
|
import * as p16 from "@clack/prompts";
|
|
9243
|
-
import { existsSync as
|
|
9370
|
+
import { existsSync as existsSync20, readFileSync as readFileSync22 } from "fs";
|
|
9244
9371
|
import { dirname as dirname9, join as join20 } from "path";
|
|
9245
9372
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9246
9373
|
import { release } from "os";
|
|
@@ -9268,8 +9395,8 @@ function readCliVersion() {
|
|
|
9268
9395
|
join20(here, "..", "..", "..", "package.json")
|
|
9269
9396
|
];
|
|
9270
9397
|
for (const path of candidates) {
|
|
9271
|
-
if (!
|
|
9272
|
-
const pkg2 = JSON.parse(
|
|
9398
|
+
if (!existsSync20(path)) continue;
|
|
9399
|
+
const pkg2 = JSON.parse(readFileSync22(path, "utf-8"));
|
|
9273
9400
|
if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
|
|
9274
9401
|
}
|
|
9275
9402
|
} catch {
|
|
@@ -9465,7 +9592,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
|
|
|
9465
9592
|
|
|
9466
9593
|
// src/cli/commands/secrets.ts
|
|
9467
9594
|
import { defineCommand as defineCommand10 } from "citty";
|
|
9468
|
-
import { readFileSync as
|
|
9595
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
9469
9596
|
|
|
9470
9597
|
// src/cli/lib/citty-args.ts
|
|
9471
9598
|
function dedupePositionals(bound, rest) {
|
|
@@ -9562,7 +9689,7 @@ function ok(json, data, human) {
|
|
|
9562
9689
|
function warnIfDebugRoutesEnabled(secrets) {
|
|
9563
9690
|
if (secrets.ALLOW_DEBUG_ROUTES !== "true") return false;
|
|
9564
9691
|
console.warn(
|
|
9565
|
-
"\n\u26A0 ALLOW_DEBUG_ROUTES=true
|
|
9692
|
+
"\n\u26A0 ALLOW_DEBUG_ROUTES=true enables the owner/admin debug API on the deployed app \u2014\n those users can read and mutate any app record through /api/debug/*. Redeploy to apply;\n `deepspace secrets delete ALLOW_DEBUG_ROUTES` (then redeploy) turns it back off."
|
|
9566
9693
|
);
|
|
9567
9694
|
return true;
|
|
9568
9695
|
}
|
|
@@ -9735,7 +9862,7 @@ var upload = defineCommand10({
|
|
|
9735
9862
|
async run({ args }) {
|
|
9736
9863
|
try {
|
|
9737
9864
|
const t = await resolveTarget(args);
|
|
9738
|
-
const content = args.file === "-" ?
|
|
9865
|
+
const content = args.file === "-" ? readFileSync23(0, "utf-8") : readFileSync23(args.file, "utf-8");
|
|
9739
9866
|
const secrets = parseSecretsUpload(content);
|
|
9740
9867
|
if (Object.keys(secrets).length === 0)
|
|
9741
9868
|
fail(new InputError("No secrets found in the input.", "empty_input"));
|
|
@@ -9799,27 +9926,24 @@ var pull = defineCommand10({
|
|
|
9799
9926
|
);
|
|
9800
9927
|
const t = await resolveTarget(args);
|
|
9801
9928
|
const ownerId = decodeJwtPayload(t.token).sub;
|
|
9802
|
-
const
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
)
|
|
9810
|
-
);
|
|
9811
|
-
return;
|
|
9812
|
-
}
|
|
9929
|
+
const refreshed = await refreshSecretsCache(
|
|
9930
|
+
DEPLOY_URL11,
|
|
9931
|
+
t.token,
|
|
9932
|
+
t.appId,
|
|
9933
|
+
wranglerEnv,
|
|
9934
|
+
t.configName
|
|
9935
|
+
);
|
|
9813
9936
|
await writeDevVars(appDir, ownerId, t.token, wranglerEnv, {
|
|
9814
9937
|
appId: t.appId,
|
|
9815
|
-
generatedSecretsCache:
|
|
9816
|
-
sharedDevVarsCache: true
|
|
9938
|
+
generatedSecretsCache: refreshed.rendered
|
|
9817
9939
|
});
|
|
9818
|
-
const count = Object.keys(pulled
|
|
9940
|
+
const count = Object.keys(refreshed.pulled?.values ?? {}).length;
|
|
9819
9941
|
ok(
|
|
9820
9942
|
args.json === true,
|
|
9821
9943
|
{ appId: t.appId, config: t.configName, pulled: count },
|
|
9822
|
-
() => console.log(
|
|
9944
|
+
() => console.log(
|
|
9945
|
+
refreshed.pulled ? `Pulled ${count} secrets (${t.configName}) into .dev.vars.` : `Config ${t.configName} does not exist yet; regenerated .dev.vars without app secrets.`
|
|
9946
|
+
)
|
|
9823
9947
|
);
|
|
9824
9948
|
} catch (err) {
|
|
9825
9949
|
fail(err);
|
|
@@ -9991,7 +10115,7 @@ var init_default = defineDeepspaceCommand({
|
|
|
9991
10115
|
// src/cli/commands/status.ts
|
|
9992
10116
|
import { defineCommand as defineCommand11 } from "citty";
|
|
9993
10117
|
import * as p17 from "@clack/prompts";
|
|
9994
|
-
import { existsSync as
|
|
10118
|
+
import { existsSync as existsSync21, readFileSync as readFileSync24 } from "fs";
|
|
9995
10119
|
function timeAgo(iso) {
|
|
9996
10120
|
const ms = Date.now() - Date.parse(iso);
|
|
9997
10121
|
if (!Number.isFinite(ms) || ms < 0) return iso;
|
|
@@ -10076,7 +10200,7 @@ var status_default = defineCommand11({
|
|
|
10076
10200
|
json.services = urls;
|
|
10077
10201
|
if (urlOverrides.length > 0) json.urlOverrides = urlOverrides;
|
|
10078
10202
|
}
|
|
10079
|
-
facts.loggedIn =
|
|
10203
|
+
facts.loggedIn = existsSync21(SESSION_PATH);
|
|
10080
10204
|
let userId = null;
|
|
10081
10205
|
if (!facts.loggedIn) {
|
|
10082
10206
|
lines.push(["Session", "not logged in"]);
|
|
@@ -10085,7 +10209,7 @@ var status_default = defineCommand11({
|
|
|
10085
10209
|
let who = "logged in";
|
|
10086
10210
|
try {
|
|
10087
10211
|
const payload = decodeJwtPayload(
|
|
10088
|
-
|
|
10212
|
+
readFileSync24(TOKEN_PATH, "utf-8").trim()
|
|
10089
10213
|
);
|
|
10090
10214
|
if (payload.email) who = payload.email;
|
|
10091
10215
|
userId = payload.sub ?? null;
|
|
@@ -10634,12 +10758,7 @@ var pull_default = defineDeepspaceCommand({
|
|
|
10634
10758
|
}
|
|
10635
10759
|
spinner2?.message(`Fetching ${branch} from the cloud repo\u2026`);
|
|
10636
10760
|
const trackingRef = spaceTrackingRef(branch);
|
|
10637
|
-
runGitRemote(appDir, token, [
|
|
10638
|
-
"fetch",
|
|
10639
|
-
"--quiet",
|
|
10640
|
-
SPACE_REMOTE,
|
|
10641
|
-
`+${refName}:${trackingRef}`
|
|
10642
|
-
]);
|
|
10761
|
+
runGitRemote(appDir, token, ["fetch", "--quiet", SPACE_REMOTE, `+${refName}:${trackingRef}`]);
|
|
10643
10762
|
const remoteOid = resolveCommit(appDir, trackingRef);
|
|
10644
10763
|
if (!remoteOid) {
|
|
10645
10764
|
spinner2?.stop("Fetch failed.");
|
|
@@ -10677,6 +10796,8 @@ var pull_default = defineDeepspaceCommand({
|
|
|
10677
10796
|
integration = "fast_forwarded";
|
|
10678
10797
|
}
|
|
10679
10798
|
}
|
|
10799
|
+
} else if (isAncestor(appDir, remoteOid, localOid)) {
|
|
10800
|
+
integration = "local_ahead";
|
|
10680
10801
|
} else {
|
|
10681
10802
|
integration = "fetched_only_diverged";
|
|
10682
10803
|
}
|
|
@@ -10690,10 +10811,15 @@ var pull_default = defineDeepspaceCommand({
|
|
|
10690
10811
|
let line;
|
|
10691
10812
|
let codeForState;
|
|
10692
10813
|
let recoveryAction;
|
|
10814
|
+
let successAction;
|
|
10693
10815
|
switch (integration) {
|
|
10694
10816
|
case "up_to_date":
|
|
10695
10817
|
line = `${branch} is already up to date.`;
|
|
10696
10818
|
break;
|
|
10819
|
+
case "local_ahead":
|
|
10820
|
+
line = `${branch} already contains ${SPACE_REMOTE}/${branch} at ${remoteOid.slice(0, 10)} \u2014 nothing to pull. Local work is not on the cloud repo yet: run \`${pushCommand}\`.`;
|
|
10821
|
+
successAction = targetedVcAction("push", appDir, appId, branch);
|
|
10822
|
+
break;
|
|
10697
10823
|
case "fast_forwarded":
|
|
10698
10824
|
line = `Fast-forwarded ${branch} to ${remoteOid.slice(0, 10)}.`;
|
|
10699
10825
|
break;
|
|
@@ -10742,7 +10868,8 @@ var pull_default = defineDeepspaceCommand({
|
|
|
10742
10868
|
}
|
|
10743
10869
|
const actionRequired = recoveryAction !== void 0;
|
|
10744
10870
|
spinner2?.stop(codeForState ? `Fetched ${SPACE_REMOTE}/${branch}.` : line);
|
|
10745
|
-
if (!args.json)
|
|
10871
|
+
if (!args.json)
|
|
10872
|
+
p19.log.info(`App: ${appId} \xB7 ${SPACE_REMOTE}/${branch} at ${remoteOid.slice(0, 10)}`);
|
|
10746
10873
|
if (codeForState) {
|
|
10747
10874
|
throw new Refusal(line, codeForState, {
|
|
10748
10875
|
actionRequired,
|
|
@@ -10756,20 +10883,20 @@ var pull_default = defineDeepspaceCommand({
|
|
|
10756
10883
|
}
|
|
10757
10884
|
});
|
|
10758
10885
|
}
|
|
10759
|
-
return { data: { status: integration, appId, branch, remoteOid } };
|
|
10886
|
+
return { data: { status: integration, appId, branch, remoteOid }, action: successAction };
|
|
10760
10887
|
}
|
|
10761
10888
|
});
|
|
10762
10889
|
|
|
10763
10890
|
// src/cli/commands/clone.ts
|
|
10764
10891
|
import * as p20 from "@clack/prompts";
|
|
10765
|
-
import { existsSync as
|
|
10892
|
+
import { existsSync as existsSync22, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
|
|
10766
10893
|
import { resolve as resolve17, basename as basename5, join as join21 } from "path";
|
|
10767
10894
|
function cleanFailedCloneDir(dir) {
|
|
10768
10895
|
let entries;
|
|
10769
10896
|
try {
|
|
10770
10897
|
entries = readdirSync6(dir);
|
|
10771
10898
|
} catch {
|
|
10772
|
-
return
|
|
10899
|
+
return existsSync22(dir) ? ["<unreadable>"] : [];
|
|
10773
10900
|
}
|
|
10774
10901
|
if (entries.length === 1 && entries[0] === ".git") {
|
|
10775
10902
|
try {
|
|
@@ -10782,7 +10909,7 @@ function cleanFailedCloneDir(dir) {
|
|
|
10782
10909
|
return entries;
|
|
10783
10910
|
}
|
|
10784
10911
|
function validateCloneTarget(dir) {
|
|
10785
|
-
if (!
|
|
10912
|
+
if (!existsSync22(dir)) return false;
|
|
10786
10913
|
let entries;
|
|
10787
10914
|
try {
|
|
10788
10915
|
entries = readdirSync6(dir);
|
|
@@ -11258,7 +11385,7 @@ function printOverlaps(overlaps, json) {
|
|
|
11258
11385
|
|
|
11259
11386
|
// src/cli/commands/workspace/local.ts
|
|
11260
11387
|
import * as p24 from "@clack/prompts";
|
|
11261
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
11388
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync25, realpathSync as realpathSync6, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
11262
11389
|
import { dirname as dirname10, isAbsolute as isAbsolute5, join as join22, relative as relative2, sep as sep2 } from "path";
|
|
11263
11390
|
var WORKSPACE_MARKER = "deepspace-workspace.json";
|
|
11264
11391
|
function excludeWorktreeDir(appDir, dir) {
|
|
@@ -11316,7 +11443,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
|
|
|
11316
11443
|
const markerPath = worktreeMarkerPath(worktreeRoot);
|
|
11317
11444
|
if (!markerPath) return false;
|
|
11318
11445
|
try {
|
|
11319
|
-
const marker = JSON.parse(
|
|
11446
|
+
const marker = JSON.parse(readFileSync25(markerPath, "utf-8"));
|
|
11320
11447
|
return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
|
|
11321
11448
|
} catch {
|
|
11322
11449
|
return false;
|
|
@@ -11331,6 +11458,11 @@ function appDirInWorktree(appDir, worktreeRoot) {
|
|
|
11331
11458
|
const appRelative = relative2(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
|
|
11332
11459
|
return appRelative ? join22(worktreeRoot, appRelative) : worktreeRoot;
|
|
11333
11460
|
}
|
|
11461
|
+
function primaryAppDir(appDir) {
|
|
11462
|
+
const primary = listWorktrees(appDir)[0];
|
|
11463
|
+
if (!primary) throw new Error("Git did not report a primary worktree");
|
|
11464
|
+
return appDirInWorktree(appDir, primary.path);
|
|
11465
|
+
}
|
|
11334
11466
|
function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint, workspaceId, options = {}) {
|
|
11335
11467
|
const createdOid = resolveCommit(appDir, startPoint);
|
|
11336
11468
|
if (!createdOid) throw new Error(`Could not resolve workspace start point ${startPoint}`);
|
|
@@ -11756,7 +11888,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11756
11888
|
|
|
11757
11889
|
// src/cli/commands/workspace/attach.ts
|
|
11758
11890
|
import * as p26 from "@clack/prompts";
|
|
11759
|
-
import { existsSync as
|
|
11891
|
+
import { existsSync as existsSync23, readdirSync as readdirSync7, realpathSync as realpathSync7, rmSync as rmSync6 } from "fs";
|
|
11760
11892
|
import { isAbsolute as isAbsolute7, resolve as resolve19 } from "path";
|
|
11761
11893
|
function samePath(a, b) {
|
|
11762
11894
|
const real = (p35) => {
|
|
@@ -11776,7 +11908,7 @@ function finishedWorkspaceMessage(id, view) {
|
|
|
11776
11908
|
return `Workspace ${id} is ${workspace.status} \u2014 its ref is retained briefly (fetch ${workspace.ref} to inspect), but attach only works on active workspaces.`;
|
|
11777
11909
|
}
|
|
11778
11910
|
function cleanFailedFreshAttachDir(dir) {
|
|
11779
|
-
if (!
|
|
11911
|
+
if (!existsSync23(dir)) return [];
|
|
11780
11912
|
let entries;
|
|
11781
11913
|
try {
|
|
11782
11914
|
entries = readdirSync7(dir);
|
|
@@ -11858,7 +11990,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11858
11990
|
const localTip = resolveCommit(appDir, `refs/heads/${branch}`);
|
|
11859
11991
|
if (localTip) {
|
|
11860
11992
|
let existing = resolveWorkspaceWorktree(listWorktrees(appDir), id);
|
|
11861
|
-
if (existing && !
|
|
11993
|
+
if (existing && !existsSync23(existing)) {
|
|
11862
11994
|
runGit(appDir, ["worktree", "prune"], { allowFail: true });
|
|
11863
11995
|
existing = null;
|
|
11864
11996
|
}
|
|
@@ -11976,7 +12108,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11976
12108
|
process.cwd(),
|
|
11977
12109
|
(typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
|
|
11978
12110
|
);
|
|
11979
|
-
if (
|
|
12111
|
+
if (existsSync23(dir)) {
|
|
11980
12112
|
spinner2?.stop("Target exists.");
|
|
11981
12113
|
throw new Refusal(`${dir} already exists \u2014 pass a different directory.`, "dir_exists");
|
|
11982
12114
|
}
|
|
@@ -12313,13 +12445,13 @@ import * as p30 from "@clack/prompts";
|
|
|
12313
12445
|
|
|
12314
12446
|
// src/cli/lib/validation.ts
|
|
12315
12447
|
import { sync as spawnSync7 } from "cross-spawn";
|
|
12316
|
-
import { readFileSync as
|
|
12448
|
+
import { readFileSync as readFileSync26 } from "fs";
|
|
12317
12449
|
import { join as join23 } from "path";
|
|
12318
12450
|
var MAX_SUMMARY_CHARS = 4e3;
|
|
12319
12451
|
var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
|
|
12320
12452
|
function configuredCommand(appDir) {
|
|
12321
12453
|
try {
|
|
12322
|
-
const pkg2 = JSON.parse(
|
|
12454
|
+
const pkg2 = JSON.parse(readFileSync26(join23(appDir, "package.json"), "utf-8"));
|
|
12323
12455
|
if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
|
|
12324
12456
|
} catch {
|
|
12325
12457
|
}
|
|
@@ -12357,6 +12489,11 @@ function conflictMarkerFiles(diffCheckOutput) {
|
|
|
12357
12489
|
}
|
|
12358
12490
|
return [...files];
|
|
12359
12491
|
}
|
|
12492
|
+
function pullAfterLandAction(primaryDir, intoBranch, landedOid) {
|
|
12493
|
+
if (currentBranch(primaryDir) !== intoBranch) return null;
|
|
12494
|
+
if (resolveCommit(primaryDir, "HEAD") === landedOid) return null;
|
|
12495
|
+
return { cwd: primaryDir, argv: ["deepspace", "pull"] };
|
|
12496
|
+
}
|
|
12360
12497
|
function landResumeArgv(args) {
|
|
12361
12498
|
const argv = ["deepspace", "workspace", "land"];
|
|
12362
12499
|
const workspace = args.workspace?.trim();
|
|
@@ -12635,6 +12772,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12635
12772
|
);
|
|
12636
12773
|
}
|
|
12637
12774
|
spinner2?.stop(`Landed ${id} into ${intoBranch} at ${landedOid.slice(0, 10)}.`);
|
|
12775
|
+
const primaryDir = primaryAppDir(appDir);
|
|
12638
12776
|
const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, intoBranch, { expectedBranchOid: landedOid });
|
|
12639
12777
|
const inOwnWorktree = !cleanup && inOwnLinkedWorktree(appDir, id);
|
|
12640
12778
|
const retainedWorktree = cleanup?.worktreeRetained ?? (keepWorktree || inOwnWorktree ? appDir : null);
|
|
@@ -12652,7 +12790,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12652
12790
|
p30.log.info(`Retained checkout: ${retainedWorktree}`);
|
|
12653
12791
|
}
|
|
12654
12792
|
if (cleanup?.error) {
|
|
12655
|
-
const
|
|
12793
|
+
const action2 = cleanupAction({
|
|
12656
12794
|
worktreeDir: cleanup.worktreeDir,
|
|
12657
12795
|
branch: cleanup.branch
|
|
12658
12796
|
});
|
|
@@ -12663,10 +12801,11 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12663
12801
|
branch: cleanup.branch
|
|
12664
12802
|
}),
|
|
12665
12803
|
"cleanup_incomplete",
|
|
12666
|
-
{ actionRequired: true, action, extra: data }
|
|
12804
|
+
{ actionRequired: true, action: action2, extra: data }
|
|
12667
12805
|
);
|
|
12668
12806
|
}
|
|
12669
|
-
|
|
12807
|
+
const action = pullAfterLandAction(primaryDir, intoBranch, landedOid);
|
|
12808
|
+
return { data, ...action ? { action } : {} };
|
|
12670
12809
|
}
|
|
12671
12810
|
});
|
|
12672
12811
|
|
|
@@ -13106,7 +13245,7 @@ var source_default = defineDeepspaceCommand({
|
|
|
13106
13245
|
args: {
|
|
13107
13246
|
provider: {
|
|
13108
13247
|
type: "positional",
|
|
13109
|
-
description: "Source
|
|
13248
|
+
description: "Source authority: deepspace is platform-hosted and commit-first; github deploys the local working tree without Git sync (omit to show)",
|
|
13110
13249
|
required: false
|
|
13111
13250
|
},
|
|
13112
13251
|
app: {
|
|
@@ -13384,7 +13523,7 @@ function sameRefs(left, right) {
|
|
|
13384
13523
|
|
|
13385
13524
|
// src/cli/commands/update.ts
|
|
13386
13525
|
import * as p34 from "@clack/prompts";
|
|
13387
|
-
import { existsSync as
|
|
13526
|
+
import { existsSync as existsSync24, readFileSync as readFileSync27, writeFileSync as writeFileSync10 } from "fs";
|
|
13388
13527
|
import { join as join24, resolve as resolve20 } from "path";
|
|
13389
13528
|
var REGISTRY = "https://registry.npmjs.org/deepspace/latest";
|
|
13390
13529
|
var DOCS_URL = "https://documentation.deep.space/cli-reference/commands";
|
|
@@ -13400,18 +13539,18 @@ function gapIsTooWide(from, to) {
|
|
|
13400
13539
|
}
|
|
13401
13540
|
function readAppSdkVersion(appDir) {
|
|
13402
13541
|
const installed = join24(appDir, "node_modules", "deepspace", "package.json");
|
|
13403
|
-
if (
|
|
13404
|
-
const parsed2 = JSON.parse(
|
|
13542
|
+
if (existsSync24(installed)) {
|
|
13543
|
+
const parsed2 = JSON.parse(readFileSync27(installed, "utf8"));
|
|
13405
13544
|
if (typeof parsed2.version === "string") return parsed2.version;
|
|
13406
13545
|
}
|
|
13407
13546
|
const manifest = join24(appDir, "package.json");
|
|
13408
|
-
if (!
|
|
13409
|
-
const parsed = JSON.parse(
|
|
13547
|
+
if (!existsSync24(manifest)) return null;
|
|
13548
|
+
const parsed = JSON.parse(readFileSync27(manifest, "utf8"));
|
|
13410
13549
|
return parsed.dependencies?.deepspace ?? null;
|
|
13411
13550
|
}
|
|
13412
13551
|
function pinSdkVersion(appDir, version) {
|
|
13413
13552
|
const path = join24(appDir, "package.json");
|
|
13414
|
-
const source =
|
|
13553
|
+
const source = readFileSync27(path, "utf8");
|
|
13415
13554
|
const updated = source.replace(
|
|
13416
13555
|
/("deepspace"\s*:\s*")[^"]+(")/,
|
|
13417
13556
|
(_match, head, tail) => `${head}^${version}${tail}`
|
|
@@ -13557,8 +13696,8 @@ var update_default = defineDeepspaceCommand({
|
|
|
13557
13696
|
|
|
13558
13697
|
// src/cli/cli.ts
|
|
13559
13698
|
var __dirname = dirname11(fileURLToPath3(import.meta.url));
|
|
13560
|
-
var pkg = JSON.parse(
|
|
13561
|
-
if (process.platform === "linux" && !process.env.SSL_CERT_FILE &&
|
|
13699
|
+
var pkg = JSON.parse(readFileSync28(join25(__dirname, "..", "package.json"), "utf-8"));
|
|
13700
|
+
if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync25("/etc/ssl/certs/ca-certificates.crt")) {
|
|
13562
13701
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
13563
13702
|
}
|
|
13564
13703
|
var create2 = defineCommand14({
|
|
@@ -13689,6 +13828,46 @@ Run \`${unknown.helpPath.join(" ")} --help\` for the command list.`
|
|
|
13689
13828
|
}
|
|
13690
13829
|
process.exit(1);
|
|
13691
13830
|
}
|
|
13831
|
+
function selectedLeaf(argv) {
|
|
13832
|
+
let command = main;
|
|
13833
|
+
let remaining = argv;
|
|
13834
|
+
const path = [];
|
|
13835
|
+
const commandPrefixArgs = [];
|
|
13836
|
+
for (; ; ) {
|
|
13837
|
+
const subCommands = command.subCommands;
|
|
13838
|
+
if (!subCommands || Object.keys(subCommands).length === 0) {
|
|
13839
|
+
return { command, rawArgs: [...commandPrefixArgs, ...remaining], path };
|
|
13840
|
+
}
|
|
13841
|
+
const index = remaining.findIndex((argument) => !argument.startsWith("-"));
|
|
13842
|
+
if (index < 0) return { command, rawArgs: [...commandPrefixArgs, ...remaining], path };
|
|
13843
|
+
const name = remaining[index];
|
|
13844
|
+
const entry = Object.entries(subCommands).find(([key, child]) => {
|
|
13845
|
+
if (key === name) return true;
|
|
13846
|
+
const alias = child.meta?.alias;
|
|
13847
|
+
return (Array.isArray(alias) ? alias : alias ? [alias] : []).includes(name);
|
|
13848
|
+
});
|
|
13849
|
+
if (!entry) return null;
|
|
13850
|
+
commandPrefixArgs.push(...remaining.slice(0, index));
|
|
13851
|
+
path.push(entry[0]);
|
|
13852
|
+
command = entry[1];
|
|
13853
|
+
remaining = remaining.slice(index + 1);
|
|
13854
|
+
}
|
|
13855
|
+
}
|
|
13856
|
+
function assertKnownOptions(argv) {
|
|
13857
|
+
const selected = selectedLeaf(argv);
|
|
13858
|
+
if (!selected || selected.path.join(" ") === "app create") return;
|
|
13859
|
+
const args = selected.command.args ?? {};
|
|
13860
|
+
const unknown = unknownOptionNamesFromRaw(selected.rawArgs, args);
|
|
13861
|
+
if (unknown.length === 0) return;
|
|
13862
|
+
const commandName = selected.path.join(" ") || "deepspace";
|
|
13863
|
+
const message = unknownOptionMessage(commandName, unknown, args);
|
|
13864
|
+
if (argv.includes("--json")) {
|
|
13865
|
+
console.log(JSON.stringify({ ok: false, code: "unknown_option", error: message }));
|
|
13866
|
+
} else {
|
|
13867
|
+
console.error(withSlug(message, "unknown_option"));
|
|
13868
|
+
}
|
|
13869
|
+
process.exit(1);
|
|
13870
|
+
}
|
|
13692
13871
|
var rawArgs = process.argv.slice(2);
|
|
13693
13872
|
var wantsHelp = rawArgs.some((arg) => arg === "--help" || arg === "-h");
|
|
13694
13873
|
if (DEEPSPACE_ENV === "invalid") {
|
|
@@ -13700,6 +13879,9 @@ if (DEEPSPACE_ENV === "invalid") {
|
|
|
13700
13879
|
}
|
|
13701
13880
|
process.exit(1);
|
|
13702
13881
|
}
|
|
13882
|
+
if (!wantsHelp && !(rawArgs.length === 1 && ["--version", "-v"].includes(rawArgs[0]))) {
|
|
13883
|
+
assertKnownOptions(rawArgs);
|
|
13884
|
+
}
|
|
13703
13885
|
if (rawArgs.includes("--json") && !wantsHelp) {
|
|
13704
13886
|
const unknown = findUnknownCommand(rawArgs, commandTree());
|
|
13705
13887
|
if (unknown) {
|