deepspace 0.16.0 → 0.18.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 +247 -1
- package/README.md +18 -41
- package/dist/cli.js +1499 -1327
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -157
- package/dist/index.js +248 -326
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +4 -137
- package/dist/server.js +3 -412
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +5 -210
- package/dist/worker.js +3 -419
- package/dist/worker.js.map +1 -1
- package/features/documents/src/LibrarySidebar.tsx +6 -8
- package/features/documents/src/editor/useDocEditor.ts +3 -8
- 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/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
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
|
|
7
|
-
import { dirname as
|
|
11
|
+
import { readFileSync as readFileSync28, existsSync as existsSync25 } from "fs";
|
|
12
|
+
import { dirname as dirname11, join as join25 } from "path";
|
|
8
13
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9
14
|
|
|
10
15
|
// src/cli/lib/spinner.ts
|
|
@@ -41,7 +46,8 @@ function stopActiveSpinner() {
|
|
|
41
46
|
|
|
42
47
|
// src/cli/lib/output.ts
|
|
43
48
|
import * as p2 from "@clack/prompts";
|
|
44
|
-
import {
|
|
49
|
+
import { readFileSync, realpathSync } from "fs";
|
|
50
|
+
import { dirname, isAbsolute, resolve } from "path";
|
|
45
51
|
|
|
46
52
|
// src/cli/lib/cli-format.ts
|
|
47
53
|
var ANSI_RE = /\[[0-9;]*m/g;
|
|
@@ -59,10 +65,35 @@ function humanCommand(argv) {
|
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
// src/cli/lib/output.ts
|
|
68
|
+
function resolveActionArgv(argv) {
|
|
69
|
+
if (argv[0] !== "deepspace") return argv;
|
|
70
|
+
const entry = cliEntryPath();
|
|
71
|
+
return entry ? [process.execPath, entry, ...argv.slice(1)] : argv;
|
|
72
|
+
}
|
|
73
|
+
function cliEntryPath() {
|
|
74
|
+
const entry = process.argv[1];
|
|
75
|
+
if (!entry || !isAbsolute(entry)) return null;
|
|
76
|
+
let real;
|
|
77
|
+
try {
|
|
78
|
+
real = realpathSync(entry);
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
if (!/(^|[\\/])cli\.[cm]?js$/.test(real)) return null;
|
|
83
|
+
try {
|
|
84
|
+
const pkg2 = JSON.parse(readFileSync(resolve(dirname(real), "..", "package.json"), "utf-8"));
|
|
85
|
+
return pkg2.name === "deepspace" ? real : null;
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function executableAction(action) {
|
|
91
|
+
const pinned = { ...action, argv: resolveActionArgv(action.argv) };
|
|
92
|
+
assertExecutableAction(pinned);
|
|
93
|
+
return pinned;
|
|
94
|
+
}
|
|
62
95
|
function cliAction(...argv) {
|
|
63
|
-
|
|
64
|
-
assertExecutableAction(action);
|
|
65
|
-
return action;
|
|
96
|
+
return executableAction({ cwd: process.cwd(), argv });
|
|
66
97
|
}
|
|
67
98
|
function assertExecutableAction(action) {
|
|
68
99
|
if (!isAbsolute(action.cwd)) throw new TypeError("CLI action cwd must be absolute");
|
|
@@ -78,7 +109,9 @@ function withSlug(msg, code) {
|
|
|
78
109
|
return msg.trimEnd().endsWith(tag) ? msg : `${msg.trimEnd()} ${tag}`;
|
|
79
110
|
}
|
|
80
111
|
function displayArg(arg) {
|
|
81
|
-
|
|
112
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(arg)) return arg;
|
|
113
|
+
if (process.platform === "win32") return `"${arg.replaceAll('"', '""')}"`;
|
|
114
|
+
return shQuote(arg);
|
|
82
115
|
}
|
|
83
116
|
function printAction(action) {
|
|
84
117
|
assertExecutableAction(action);
|
|
@@ -232,6 +265,146 @@ function wrapCommandErrors(cmd) {
|
|
|
232
265
|
return cmd;
|
|
233
266
|
}
|
|
234
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: "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
|
+
|
|
235
408
|
// src/cli/lib/command-suggestions.ts
|
|
236
409
|
function splitTokenPath(token, table) {
|
|
237
410
|
const parts = token.trim().split(/\s+/);
|
|
@@ -342,7 +515,7 @@ function effectivePlatformUrls(env = process.env) {
|
|
|
342
515
|
var DASHBOARD_URL = DEEPSPACE_ENV === "production" ? "https://dashboard.deep.space" : DEEPSPACE_ENV === "staging" ? "https://dashboard.deepspacesites.com" : "http://127.0.0.1:9";
|
|
343
516
|
|
|
344
517
|
// src/cli/auth.ts
|
|
345
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
518
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
346
519
|
import { join } from "path";
|
|
347
520
|
import { homedir } from "os";
|
|
348
521
|
|
|
@@ -370,106 +543,6 @@ async function exchangeSession(authUrl, sessionToken) {
|
|
|
370
543
|
return data.token ?? null;
|
|
371
544
|
}
|
|
372
545
|
|
|
373
|
-
// src/cli/lib/command.ts
|
|
374
|
-
import { defineCommand } from "citty";
|
|
375
|
-
import * as p3 from "@clack/prompts";
|
|
376
|
-
var Refusal = class extends Error {
|
|
377
|
-
code;
|
|
378
|
-
action;
|
|
379
|
-
actionRequired;
|
|
380
|
-
extra;
|
|
381
|
-
constructor(message, code, opts = {}) {
|
|
382
|
-
super(message);
|
|
383
|
-
this.name = "Refusal";
|
|
384
|
-
this.code = code;
|
|
385
|
-
if (opts.action) assertExecutableAction(opts.action);
|
|
386
|
-
this.action = opts.action;
|
|
387
|
-
this.actionRequired = opts.actionRequired ?? false;
|
|
388
|
-
this.extra = opts.extra ?? {};
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
function apiErrorDetails(err) {
|
|
392
|
-
return err instanceof ApiError ? err.details : void 0;
|
|
393
|
-
}
|
|
394
|
-
function withoutReservedKeys(payload) {
|
|
395
|
-
return Object.fromEntries(
|
|
396
|
-
Object.entries(payload).filter(
|
|
397
|
-
([key]) => ![
|
|
398
|
-
"ok",
|
|
399
|
-
"code",
|
|
400
|
-
"error",
|
|
401
|
-
"action",
|
|
402
|
-
"actionRequired",
|
|
403
|
-
"next",
|
|
404
|
-
"nextAction",
|
|
405
|
-
"resume"
|
|
406
|
-
].includes(key)
|
|
407
|
-
)
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
function exitWhenFlushed(code) {
|
|
411
|
-
if (process.stdout.writableLength === 0) process.exit(code);
|
|
412
|
-
process.stdout.write("", () => process.exit(code));
|
|
413
|
-
return void 0;
|
|
414
|
-
}
|
|
415
|
-
function defineDeepspaceCommand(def) {
|
|
416
|
-
return defineCommand({
|
|
417
|
-
meta: def.meta,
|
|
418
|
-
args: {
|
|
419
|
-
...def.args ?? {},
|
|
420
|
-
json: {
|
|
421
|
-
type: "boolean",
|
|
422
|
-
description: "Emit a single-line JSON result for scripts/agents",
|
|
423
|
-
default: false
|
|
424
|
-
}
|
|
425
|
-
},
|
|
426
|
-
async run({ args }) {
|
|
427
|
-
const json = Boolean(args.json);
|
|
428
|
-
let succeeded = false;
|
|
429
|
-
try {
|
|
430
|
-
const out = await def.run({ args }) ?? {};
|
|
431
|
-
if (out.action) assertExecutableAction(out.action);
|
|
432
|
-
if (json) {
|
|
433
|
-
console.log(
|
|
434
|
-
JSON.stringify({
|
|
435
|
-
ok: true,
|
|
436
|
-
...withoutReservedKeys(out.data ?? {}),
|
|
437
|
-
...out.action ? { action: out.action } : {}
|
|
438
|
-
})
|
|
439
|
-
);
|
|
440
|
-
} else if (out.action) {
|
|
441
|
-
printAction(out.action);
|
|
442
|
-
}
|
|
443
|
-
succeeded = true;
|
|
444
|
-
} catch (err) {
|
|
445
|
-
stopActiveSpinner();
|
|
446
|
-
const refusal = err instanceof Refusal ? err : null;
|
|
447
|
-
const code = refusal?.code ?? errorCode(err);
|
|
448
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
449
|
-
if (json) {
|
|
450
|
-
console.log(
|
|
451
|
-
JSON.stringify({
|
|
452
|
-
ok: false,
|
|
453
|
-
...code ? { code } : {},
|
|
454
|
-
...refusal?.actionRequired ? { actionRequired: true } : {},
|
|
455
|
-
error: message,
|
|
456
|
-
...refusal?.action ? { action: refusal.action } : {},
|
|
457
|
-
...withoutReservedKeys(refusal?.extra ?? apiErrorDetails(err) ?? {})
|
|
458
|
-
})
|
|
459
|
-
);
|
|
460
|
-
} else {
|
|
461
|
-
const line = code ? withSlug(message, code) : message;
|
|
462
|
-
if (refusal?.actionRequired) p3.log.warn(line);
|
|
463
|
-
else p3.log.error(line);
|
|
464
|
-
if (refusal?.action) printAction(refusal.action);
|
|
465
|
-
}
|
|
466
|
-
exitWhenFlushed(refusal?.actionRequired ? 2 : 1);
|
|
467
|
-
}
|
|
468
|
-
if (succeeded) exitWhenFlushed(0);
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
|
|
473
546
|
// src/cli/auth.ts
|
|
474
547
|
var AUTH_URL = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
|
|
475
548
|
var PROD_AUTH_URL = "https://auth.deep.space";
|
|
@@ -492,7 +565,7 @@ function credentialPaths(authUrl = AUTH_URL) {
|
|
|
492
565
|
var { sessionPath: SESSION_PATH, tokenPath: TOKEN_PATH } = credentialPaths();
|
|
493
566
|
async function ensureToken() {
|
|
494
567
|
if (existsSync(TOKEN_PATH)) {
|
|
495
|
-
const existing =
|
|
568
|
+
const existing = readFileSync2(TOKEN_PATH, "utf-8").trim();
|
|
496
569
|
if (isTokenValid(existing)) {
|
|
497
570
|
return existing;
|
|
498
571
|
}
|
|
@@ -502,7 +575,7 @@ async function ensureToken() {
|
|
|
502
575
|
action: cliAction("deepspace", "auth", "login")
|
|
503
576
|
});
|
|
504
577
|
}
|
|
505
|
-
const sessionToken =
|
|
578
|
+
const sessionToken = readFileSync2(SESSION_PATH, "utf-8").trim();
|
|
506
579
|
const token = await exchangeSession(AUTH_URL, sessionToken);
|
|
507
580
|
if (!token) {
|
|
508
581
|
throw new Refusal(
|
|
@@ -526,7 +599,7 @@ function isTokenValid(jwt) {
|
|
|
526
599
|
registerAuthRefresh(async () => {
|
|
527
600
|
try {
|
|
528
601
|
if (!existsSync(SESSION_PATH)) return null;
|
|
529
|
-
const sessionToken =
|
|
602
|
+
const sessionToken = readFileSync2(SESSION_PATH, "utf-8").trim();
|
|
530
603
|
const token = await exchangeSession(AUTH_URL, sessionToken);
|
|
531
604
|
if (!token) return null;
|
|
532
605
|
mkdirSync(DIR, { recursive: true, mode: 448 });
|
|
@@ -538,7 +611,7 @@ registerAuthRefresh(async () => {
|
|
|
538
611
|
});
|
|
539
612
|
|
|
540
613
|
// src/cli/commands/login.ts
|
|
541
|
-
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, readFileSync as
|
|
614
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
542
615
|
import { join as join2 } from "path";
|
|
543
616
|
import { homedir as homedir2 } from "os";
|
|
544
617
|
import { randomBytes, createHash } from "crypto";
|
|
@@ -697,7 +770,7 @@ function readPasswordFromStdin() {
|
|
|
697
770
|
"password_stdin_needs_pipe"
|
|
698
771
|
);
|
|
699
772
|
}
|
|
700
|
-
return
|
|
773
|
+
return readFileSync3(0, "utf-8").replace(/\r?\n$/, "");
|
|
701
774
|
}
|
|
702
775
|
async function pollForCompletion(sessionId, codeVerifier) {
|
|
703
776
|
const maxAttempts = 120;
|
|
@@ -738,7 +811,7 @@ async function provisionProfile(jwt) {
|
|
|
738
811
|
}
|
|
739
812
|
}
|
|
740
813
|
function sleep(ms) {
|
|
741
|
-
return new Promise((
|
|
814
|
+
return new Promise((resolve21) => setTimeout(resolve21, ms));
|
|
742
815
|
}
|
|
743
816
|
function base64url(bytes) {
|
|
744
817
|
return bytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
@@ -775,7 +848,7 @@ async function doEmailLogin(email, password) {
|
|
|
775
848
|
}
|
|
776
849
|
|
|
777
850
|
// src/cli/commands/logout.ts
|
|
778
|
-
import { existsSync as existsSync2, readFileSync as
|
|
851
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4, rmSync } from "fs";
|
|
779
852
|
var AUTH_URL3 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
|
|
780
853
|
var { sessionPath: SESSION_PATH2, tokenPath: TOKEN_PATH2 } = credentialPaths(AUTH_URL3);
|
|
781
854
|
var logout_default = defineDeepspaceCommand({
|
|
@@ -784,7 +857,7 @@ var logout_default = defineDeepspaceCommand({
|
|
|
784
857
|
description: "Sign out and remove cached credentials"
|
|
785
858
|
},
|
|
786
859
|
async run({ args }) {
|
|
787
|
-
const sessionToken = existsSync2(SESSION_PATH2) ?
|
|
860
|
+
const sessionToken = existsSync2(SESSION_PATH2) ? readFileSync4(SESSION_PATH2, "utf-8").trim() : null;
|
|
788
861
|
if (sessionToken) {
|
|
789
862
|
try {
|
|
790
863
|
await fetch(`${AUTH_URL3}/api/auth/sign-out`, {
|
|
@@ -810,8 +883,8 @@ var logout_default = defineDeepspaceCommand({
|
|
|
810
883
|
});
|
|
811
884
|
|
|
812
885
|
// src/cli/lib/app-identity.ts
|
|
813
|
-
import { existsSync as existsSync3, readFileSync as
|
|
814
|
-
import { join as join3, resolve } from "path";
|
|
886
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
887
|
+
import { join as join3, resolve as resolve2 } from "path";
|
|
815
888
|
import { parse as parseToml } from "smol-toml";
|
|
816
889
|
|
|
817
890
|
// src/server/utils/registry-client.ts
|
|
@@ -846,10 +919,10 @@ function mintAppId(now = Date.now()) {
|
|
|
846
919
|
return `app_${mintUlid(now)}`;
|
|
847
920
|
}
|
|
848
921
|
function readWranglerIdentityConfig(cwd) {
|
|
849
|
-
const wranglerPath = join3(
|
|
922
|
+
const wranglerPath = join3(resolve2(cwd), "wrangler.toml");
|
|
850
923
|
if (!existsSync3(wranglerPath)) return null;
|
|
851
924
|
try {
|
|
852
|
-
return parseToml(
|
|
925
|
+
return parseToml(readFileSync5(wranglerPath, "utf-8"));
|
|
853
926
|
} catch (err) {
|
|
854
927
|
throw new InputError(
|
|
855
928
|
`Could not parse ${wranglerPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -865,9 +938,9 @@ function readAppId(cwd = process.cwd(), wranglerEnv) {
|
|
|
865
938
|
return typeof id === "string" && APP_ID_RE.test(id) ? id : null;
|
|
866
939
|
}
|
|
867
940
|
function writeAppId(cwd, appId, opts = {}) {
|
|
868
|
-
const wranglerPath = join3(
|
|
941
|
+
const wranglerPath = join3(resolve2(cwd), "wrangler.toml");
|
|
869
942
|
if (!existsSync3(wranglerPath)) {
|
|
870
|
-
throw new Error(`No wrangler.toml in ${
|
|
943
|
+
throw new Error(`No wrangler.toml in ${resolve2(cwd)}`);
|
|
871
944
|
}
|
|
872
945
|
const existing = readAppId(cwd, opts.wranglerEnv);
|
|
873
946
|
if (existing && !opts.force) {
|
|
@@ -876,7 +949,7 @@ function writeAppId(cwd, appId, opts = {}) {
|
|
|
876
949
|
`wrangler.toml already carries ${existing}. The app id is immutable \u2014 use --new-id only to fork this repo as a separate app.`
|
|
877
950
|
);
|
|
878
951
|
}
|
|
879
|
-
let src =
|
|
952
|
+
let src = readFileSync5(wranglerPath, "utf-8");
|
|
880
953
|
const header = opts.wranglerEnv ? `[env.${opts.wranglerEnv}.vars]` : "[vars]";
|
|
881
954
|
const line = `DEEPSPACE_APP_ID = "${appId}"`;
|
|
882
955
|
const headerRe = new RegExp(`^\\${header.replace(/\./g, "\\.")}[ \\t]*$`, "m");
|
|
@@ -902,18 +975,18 @@ ${line}
|
|
|
902
975
|
}
|
|
903
976
|
|
|
904
977
|
// src/cli/commands/dev.ts
|
|
905
|
-
import { resolve as
|
|
978
|
+
import { resolve as resolve8, basename as basename2, join as join12 } from "path";
|
|
906
979
|
import spawn from "cross-spawn";
|
|
907
980
|
|
|
908
981
|
// src/cli/lib/app-context.ts
|
|
909
|
-
import { existsSync as existsSync4, readFileSync as
|
|
910
|
-
import { dirname, join as join4, resolve as
|
|
982
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6, readdirSync } from "fs";
|
|
983
|
+
import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
|
|
911
984
|
import { parse as parseToml2 } from "smol-toml";
|
|
912
985
|
function detectAppName(cwd = process.cwd()) {
|
|
913
|
-
const wranglerPath = join4(
|
|
986
|
+
const wranglerPath = join4(resolve3(cwd), "wrangler.toml");
|
|
914
987
|
if (!existsSync4(wranglerPath)) return null;
|
|
915
988
|
try {
|
|
916
|
-
const config = parseToml2(
|
|
989
|
+
const config = parseToml2(readFileSync6(wranglerPath, "utf-8"));
|
|
917
990
|
return typeof config.name === "string" && config.name.length > 0 ? config.name : null;
|
|
918
991
|
} catch (error) {
|
|
919
992
|
throw new InputError(
|
|
@@ -923,10 +996,10 @@ function detectAppName(cwd = process.cwd()) {
|
|
|
923
996
|
}
|
|
924
997
|
}
|
|
925
998
|
function findAppDir(start = process.cwd()) {
|
|
926
|
-
let dir =
|
|
999
|
+
let dir = resolve3(start);
|
|
927
1000
|
for (; ; ) {
|
|
928
1001
|
if (existsSync4(join4(dir, "wrangler.toml"))) return dir;
|
|
929
|
-
const parent =
|
|
1002
|
+
const parent = dirname2(dir);
|
|
930
1003
|
if (parent === dir) return null;
|
|
931
1004
|
dir = parent;
|
|
932
1005
|
}
|
|
@@ -942,18 +1015,18 @@ function findChildApps(dir) {
|
|
|
942
1015
|
// src/cli/lib/launch-config.ts
|
|
943
1016
|
import {
|
|
944
1017
|
existsSync as existsSync5,
|
|
945
|
-
readFileSync as
|
|
946
|
-
realpathSync,
|
|
1018
|
+
readFileSync as readFileSync8,
|
|
1019
|
+
realpathSync as realpathSync2,
|
|
947
1020
|
writeFileSync as writeFileSync4,
|
|
948
1021
|
mkdirSync as mkdirSync4,
|
|
949
1022
|
renameSync,
|
|
950
1023
|
unlinkSync
|
|
951
1024
|
} from "fs";
|
|
952
|
-
import { join as join6, resolve as
|
|
1025
|
+
import { join as join6, resolve as resolve5, dirname as dirname4, basename, isAbsolute as isAbsolute2, sep } from "path";
|
|
953
1026
|
|
|
954
1027
|
// src/cli/lib/git/repository.ts
|
|
955
|
-
import { appendFileSync, mkdirSync as mkdirSync3, readFileSync as
|
|
956
|
-
import { dirname as
|
|
1028
|
+
import { appendFileSync, mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
|
|
1029
|
+
import { dirname as dirname3, join as join5, resolve as resolve4 } from "path";
|
|
957
1030
|
|
|
958
1031
|
// src/cli/lib/git/process.ts
|
|
959
1032
|
import { sync as spawnSync } from "cross-spawn";
|
|
@@ -1156,16 +1229,16 @@ function listWorktrees(cwd) {
|
|
|
1156
1229
|
return worktrees;
|
|
1157
1230
|
}
|
|
1158
1231
|
function ensureLocalExclude(cwd, pattern) {
|
|
1159
|
-
const commonDir =
|
|
1232
|
+
const commonDir = resolve4(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
|
|
1160
1233
|
const excludePath = join5(commonDir, "info", "exclude");
|
|
1161
1234
|
let current = "";
|
|
1162
1235
|
try {
|
|
1163
|
-
current =
|
|
1236
|
+
current = readFileSync7(excludePath, "utf-8");
|
|
1164
1237
|
} catch {
|
|
1165
1238
|
}
|
|
1166
1239
|
if (current.split("\n").some((line) => line.trim() === pattern)) return;
|
|
1167
1240
|
try {
|
|
1168
|
-
mkdirSync3(
|
|
1241
|
+
mkdirSync3(dirname3(excludePath), { recursive: true });
|
|
1169
1242
|
appendFileSync(
|
|
1170
1243
|
excludePath,
|
|
1171
1244
|
`${current.endsWith("\n") || current === "" ? "" : "\n"}${pattern}
|
|
@@ -1177,7 +1250,7 @@ function ensureLocalExclude(cwd, pattern) {
|
|
|
1177
1250
|
|
|
1178
1251
|
// src/cli/lib/launch-config.ts
|
|
1179
1252
|
function writeLaunchFile(launchPath, config) {
|
|
1180
|
-
mkdirSync4(
|
|
1253
|
+
mkdirSync4(dirname4(launchPath), { recursive: true });
|
|
1181
1254
|
const tmpPath = `${launchPath}.${process.pid}.tmp`;
|
|
1182
1255
|
writeFileSync4(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
1183
1256
|
try {
|
|
@@ -1193,7 +1266,7 @@ function writeLaunchFile(launchPath, config) {
|
|
|
1193
1266
|
function readLaunchFile(launchPath) {
|
|
1194
1267
|
const empty = { version: "0.0.1", configurations: [] };
|
|
1195
1268
|
if (!existsSync5(launchPath)) return empty;
|
|
1196
|
-
const raw =
|
|
1269
|
+
const raw = readFileSync8(launchPath, "utf-8").trim();
|
|
1197
1270
|
if (!raw) return empty;
|
|
1198
1271
|
const parsed = JSON.parse(raw);
|
|
1199
1272
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.configurations)) return null;
|
|
@@ -1258,10 +1331,10 @@ function detectClaudeWorktree(appDir) {
|
|
|
1258
1331
|
listWorktrees(appDir).map((worktree) => canonicalPath(worktree.path))
|
|
1259
1332
|
);
|
|
1260
1333
|
if (!registered.has(worktreeRoot)) return null;
|
|
1261
|
-
const worktreesDir =
|
|
1262
|
-
const claudeDir =
|
|
1334
|
+
const worktreesDir = dirname4(worktreeRoot);
|
|
1335
|
+
const claudeDir = dirname4(worktreesDir);
|
|
1263
1336
|
if (basename(worktreesDir) !== "worktrees" || basename(claudeDir) !== ".claude") return null;
|
|
1264
|
-
const ownerRoot = canonicalPath(
|
|
1337
|
+
const ownerRoot = canonicalPath(dirname4(claudeDir));
|
|
1265
1338
|
if (!registered.has(ownerRoot)) return null;
|
|
1266
1339
|
return { mainRepoRoot: ownerRoot, worktreeName: basename(worktreeRoot) };
|
|
1267
1340
|
} catch {
|
|
@@ -1270,9 +1343,9 @@ function detectClaudeWorktree(appDir) {
|
|
|
1270
1343
|
}
|
|
1271
1344
|
function canonicalPath(path) {
|
|
1272
1345
|
try {
|
|
1273
|
-
return
|
|
1346
|
+
return realpathSync2(path);
|
|
1274
1347
|
} catch {
|
|
1275
|
-
return
|
|
1348
|
+
return resolve5(path);
|
|
1276
1349
|
}
|
|
1277
1350
|
}
|
|
1278
1351
|
function detectLinkedWorktree(appDir) {
|
|
@@ -1324,7 +1397,7 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
|
|
|
1324
1397
|
// the Claude Code desktop launch.json reference alongside `port`) and
|
|
1325
1398
|
// verified end-to-end: with it set, the preview server builds from the
|
|
1326
1399
|
// worktree instead of the session's main repo.
|
|
1327
|
-
cwd:
|
|
1400
|
+
cwd: resolve5(appDir)
|
|
1328
1401
|
});
|
|
1329
1402
|
writeLaunchFile(launchPath, config);
|
|
1330
1403
|
return { entryName, port };
|
|
@@ -1350,9 +1423,6 @@ function resolveWorktreePort(appDir) {
|
|
|
1350
1423
|
return deriveWorktreePort(linked.worktreeRoot);
|
|
1351
1424
|
}
|
|
1352
1425
|
|
|
1353
|
-
// src/cli/lib/dev-vars.ts
|
|
1354
|
-
import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
|
|
1355
|
-
|
|
1356
1426
|
// src/cli/lib/app-tokens.ts
|
|
1357
1427
|
async function fetchPublicKey(authUrl) {
|
|
1358
1428
|
const response = await fetch(`${authUrl}/api/auth/jwks`);
|
|
@@ -1402,501 +1472,18 @@ async function fetchAppIdentityToken(deployUrl, callerJwt, appId) {
|
|
|
1402
1472
|
return body.token;
|
|
1403
1473
|
}
|
|
1404
1474
|
|
|
1405
|
-
// src/
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
"kv_namespace",
|
|
1413
|
-
"d1",
|
|
1414
|
-
"queue",
|
|
1415
|
-
"browser_rendering",
|
|
1416
|
-
"analytics_engine",
|
|
1417
|
-
"hyperdrive",
|
|
1418
|
-
"ratelimit"
|
|
1419
|
-
]);
|
|
1420
|
-
var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
1421
|
-
"ASSETS",
|
|
1422
|
-
"PLATFORM_WORKER",
|
|
1423
|
-
"API_WORKER",
|
|
1424
|
-
"DEEPSPACE_APP_ID",
|
|
1425
|
-
"DEEPSPACE_RESOURCE_ID",
|
|
1426
|
-
"DEEPSPACE_RELEASE_OPERATION_ID",
|
|
1427
|
-
"APP_NAME",
|
|
1428
|
-
"OWNER_USER_ID",
|
|
1429
|
-
"AUTH_JWT_PUBLIC_KEY",
|
|
1430
|
-
"AUTH_JWT_ISSUER",
|
|
1431
|
-
"AUTH_WORKER_URL",
|
|
1432
|
-
"APP_IDENTITY_TOKEN",
|
|
1433
|
-
"APP_OWNER_JWT",
|
|
1434
|
-
"USAGE_EVENTS"
|
|
1435
|
-
]);
|
|
1436
|
-
function validateBindingManifest(manifest) {
|
|
1437
|
-
if (!Array.isArray(manifest)) {
|
|
1438
|
-
return { valid: false, errors: [{ reason: "Manifest must be an array" }] };
|
|
1439
|
-
}
|
|
1440
|
-
const errors = [];
|
|
1441
|
-
const seenNames = /* @__PURE__ */ new Set();
|
|
1442
|
-
for (const entry of manifest) {
|
|
1443
|
-
if (!entry || typeof entry !== "object") {
|
|
1444
|
-
errors.push({
|
|
1445
|
-
reason: `Entry must be an object (got ${entry === null ? "null" : typeof entry})`
|
|
1446
|
-
});
|
|
1447
|
-
continue;
|
|
1448
|
-
}
|
|
1449
|
-
const e = entry;
|
|
1450
|
-
const type = e.type;
|
|
1451
|
-
const name = e.name;
|
|
1452
|
-
if (typeof type !== "string" || !ALLOWED_BINDING_TYPES.has(type)) {
|
|
1453
|
-
errors.push({
|
|
1454
|
-
reason: `Disallowed binding type: ${typeof type === "string" ? type : `(${typeof type})`}. Allowed: ${[...ALLOWED_BINDING_TYPES].sort().join(", ")}`
|
|
1455
|
-
});
|
|
1456
|
-
continue;
|
|
1457
|
-
}
|
|
1458
|
-
if (typeof name !== "string" || !name) {
|
|
1459
|
-
errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
|
|
1460
|
-
continue;
|
|
1461
|
-
}
|
|
1462
|
-
if (RESERVED_BINDING_NAMES.has(name)) {
|
|
1463
|
-
errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
|
|
1464
|
-
continue;
|
|
1465
|
-
}
|
|
1466
|
-
if (seenNames.has(name)) {
|
|
1467
|
-
errors.push({ reason: `Duplicate binding name "${name}"` });
|
|
1468
|
-
continue;
|
|
1469
|
-
}
|
|
1470
|
-
seenNames.add(name);
|
|
1471
|
-
const binding = entry;
|
|
1472
|
-
const fieldErr = requiredFieldError(binding);
|
|
1473
|
-
if (fieldErr) errors.push({ binding, reason: fieldErr });
|
|
1474
|
-
}
|
|
1475
|
-
if (errors.length) return { valid: false, errors };
|
|
1476
|
-
return { valid: true, bindings: manifest };
|
|
1477
|
-
}
|
|
1478
|
-
function requiredFieldError(b) {
|
|
1479
|
-
switch (b.type) {
|
|
1480
|
-
case "vectorize": {
|
|
1481
|
-
if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
|
|
1482
|
-
if (b.index_name === AUTO_PROVISION_SENTINEL) {
|
|
1483
|
-
if (typeof b.dimensions !== "number" || b.dimensions <= 0) {
|
|
1484
|
-
return `vectorize binding "${b.name}" with index_name="auto" requires "dimensions" (positive number)`;
|
|
1485
|
-
}
|
|
1486
|
-
if (!b.metric || !VECTORIZE_METRICS.has(b.metric)) {
|
|
1487
|
-
return `vectorize binding "${b.name}" with index_name="auto" requires "metric" (one of: ${[...VECTORIZE_METRICS].join(", ")})`;
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
return null;
|
|
1491
|
-
}
|
|
1492
|
-
case "r2_bucket":
|
|
1493
|
-
return b.bucket_name ? null : `r2_bucket binding "${b.name}" missing bucket_name (or "auto")`;
|
|
1494
|
-
case "kv_namespace": {
|
|
1495
|
-
if (!b.namespace_id)
|
|
1496
|
-
return `kv_namespace binding "${b.name}" missing namespace_id (or "auto")`;
|
|
1497
|
-
if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {
|
|
1498
|
-
return `kv_namespace binding "${b.name}" with namespace_id="auto" requires "title"`;
|
|
1499
|
-
}
|
|
1500
|
-
return null;
|
|
1501
|
-
}
|
|
1502
|
-
case "d1": {
|
|
1503
|
-
if (!b.id) return `d1 binding "${b.name}" missing id (or "auto")`;
|
|
1504
|
-
if (b.id === AUTO_PROVISION_SENTINEL && !b.database_name) {
|
|
1505
|
-
return `d1 binding "${b.name}" with id="auto" requires "database_name"`;
|
|
1506
|
-
}
|
|
1507
|
-
return null;
|
|
1508
|
-
}
|
|
1509
|
-
case "queue":
|
|
1510
|
-
return b.queue_name ? null : `queue binding "${b.name}" missing queue_name (or "auto")`;
|
|
1511
|
-
case "hyperdrive":
|
|
1512
|
-
if (!b.id) return `hyperdrive binding "${b.name}" missing id`;
|
|
1513
|
-
if (b.id === AUTO_PROVISION_SENTINEL) {
|
|
1514
|
-
return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
|
|
1515
|
-
}
|
|
1516
|
-
return null;
|
|
1517
|
-
case "ratelimit":
|
|
1518
|
-
if (!/^\d+$/u.test(b.namespace_id) || Number(b.namespace_id) <= 0) {
|
|
1519
|
-
return `ratelimit binding "${b.name}" requires a positive integer namespace_id string`;
|
|
1520
|
-
}
|
|
1521
|
-
if (!Number.isInteger(b.simple?.limit) || b.simple.limit <= 0) {
|
|
1522
|
-
return `ratelimit binding "${b.name}" requires simple.limit (positive integer)`;
|
|
1523
|
-
}
|
|
1524
|
-
if (b.simple.period !== 10 && b.simple.period !== 60) {
|
|
1525
|
-
return `ratelimit binding "${b.name}" requires simple.period (10 or 60)`;
|
|
1526
|
-
}
|
|
1527
|
-
return null;
|
|
1528
|
-
case "ai":
|
|
1529
|
-
case "browser_rendering":
|
|
1530
|
-
case "analytics_engine":
|
|
1531
|
-
return null;
|
|
1532
|
-
// no required fields beyond `name`
|
|
1533
|
-
default: {
|
|
1534
|
-
const _exhaustive = b;
|
|
1535
|
-
void _exhaustive;
|
|
1536
|
-
return null;
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
function bindingManifestFromOutputConfig(outputConfig) {
|
|
1541
|
-
const out = [];
|
|
1542
|
-
const aiBinding = pluckString(outputConfig.ai, "binding");
|
|
1543
|
-
if (aiBinding) out.push({ type: "ai", name: aiBinding });
|
|
1544
|
-
for (const v of asObjectArray(outputConfig.vectorize)) {
|
|
1545
|
-
const name = pluckString(v, "binding");
|
|
1546
|
-
const index_name = pluckString(v, "index_name");
|
|
1547
|
-
if (!name || !index_name) continue;
|
|
1548
|
-
const dimensions = pluckNumber(v, "dimensions");
|
|
1549
|
-
const metric = pluckString(v, "metric");
|
|
1550
|
-
out.push({
|
|
1551
|
-
type: "vectorize",
|
|
1552
|
-
name,
|
|
1553
|
-
index_name,
|
|
1554
|
-
...dimensions != null && { dimensions },
|
|
1555
|
-
...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
|
|
1556
|
-
});
|
|
1557
|
-
}
|
|
1558
|
-
for (const r of asObjectArray(outputConfig.r2_buckets)) {
|
|
1559
|
-
const name = pluckString(r, "binding");
|
|
1560
|
-
const bucket_name = pluckString(r, "bucket_name");
|
|
1561
|
-
if (name && bucket_name) out.push({ type: "r2_bucket", name, bucket_name });
|
|
1562
|
-
}
|
|
1563
|
-
for (const k of asObjectArray(outputConfig.kv_namespaces)) {
|
|
1564
|
-
const name = pluckString(k, "binding");
|
|
1565
|
-
const namespace_id = pluckString(k, "id");
|
|
1566
|
-
if (!name || !namespace_id) continue;
|
|
1567
|
-
const title = pluckString(k, "title");
|
|
1568
|
-
out.push({
|
|
1569
|
-
type: "kv_namespace",
|
|
1570
|
-
name,
|
|
1571
|
-
namespace_id,
|
|
1572
|
-
...title && { title }
|
|
1573
|
-
});
|
|
1574
|
-
}
|
|
1575
|
-
for (const d of asObjectArray(outputConfig.d1_databases)) {
|
|
1576
|
-
const name = pluckString(d, "binding");
|
|
1577
|
-
const id = pluckString(d, "database_id");
|
|
1578
|
-
if (!name || !id) continue;
|
|
1579
|
-
const database_name = pluckString(d, "database_name");
|
|
1580
|
-
out.push({
|
|
1581
|
-
type: "d1",
|
|
1582
|
-
name,
|
|
1583
|
-
id,
|
|
1584
|
-
...database_name && { database_name }
|
|
1585
|
-
});
|
|
1586
|
-
}
|
|
1587
|
-
const queues = isObject(outputConfig.queues) ? outputConfig.queues : null;
|
|
1588
|
-
for (const q of asObjectArray(queues?.producers)) {
|
|
1589
|
-
const name = pluckString(q, "binding");
|
|
1590
|
-
const queue_name = pluckString(q, "queue");
|
|
1591
|
-
if (name && queue_name) out.push({ type: "queue", name, queue_name });
|
|
1592
|
-
}
|
|
1593
|
-
const browserBinding = pluckString(outputConfig.browser, "binding");
|
|
1594
|
-
if (browserBinding) out.push({ type: "browser_rendering", name: browserBinding });
|
|
1595
|
-
for (const a of asObjectArray(outputConfig.analytics_engine_datasets)) {
|
|
1596
|
-
const name = pluckString(a, "binding");
|
|
1597
|
-
const dataset = pluckString(a, "dataset");
|
|
1598
|
-
if (name) out.push({ type: "analytics_engine", name, dataset: dataset ?? void 0 });
|
|
1599
|
-
}
|
|
1600
|
-
for (const h of asObjectArray(outputConfig.hyperdrive)) {
|
|
1601
|
-
const name = pluckString(h, "binding");
|
|
1602
|
-
const id = pluckString(h, "id");
|
|
1603
|
-
if (name && id) out.push({ type: "hyperdrive", name, id });
|
|
1604
|
-
}
|
|
1605
|
-
for (const r of asObjectArray(outputConfig.ratelimits)) {
|
|
1606
|
-
const name = pluckString(r, "name");
|
|
1607
|
-
const namespace_id = pluckString(r, "namespace_id");
|
|
1608
|
-
const simple = isObject(r.simple) ? r.simple : null;
|
|
1609
|
-
const limit = pluckNumber(simple, "limit");
|
|
1610
|
-
const period = pluckNumber(simple, "period");
|
|
1611
|
-
if (!name || !namespace_id || limit === null || period !== 10 && period !== 60) continue;
|
|
1612
|
-
out.push({ type: "ratelimit", name, namespace_id, simple: { limit, period } });
|
|
1613
|
-
}
|
|
1614
|
-
return out;
|
|
1615
|
-
}
|
|
1616
|
-
function isObject(v) {
|
|
1617
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
1618
|
-
}
|
|
1619
|
-
function pluckString(v, key) {
|
|
1620
|
-
if (!isObject(v)) return null;
|
|
1621
|
-
const val = v[key];
|
|
1622
|
-
return typeof val === "string" && val.length > 0 ? val : null;
|
|
1623
|
-
}
|
|
1624
|
-
function pluckNumber(v, key) {
|
|
1625
|
-
if (!isObject(v)) return null;
|
|
1626
|
-
const val = v[key];
|
|
1627
|
-
return typeof val === "number" && Number.isFinite(val) ? val : null;
|
|
1628
|
-
}
|
|
1629
|
-
function asObjectArray(v) {
|
|
1630
|
-
if (!Array.isArray(v)) return [];
|
|
1631
|
-
return v.filter(isObject);
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
// src/cli/lib/secrets.ts
|
|
1635
|
-
var GENERATED_SECRETS_DIVIDER = "# --- DeepSpace secrets cache: generated by `npx deepspace secrets`; do not edit manually ---";
|
|
1636
|
-
var RESERVED_SECRET_NAMES = /* @__PURE__ */ new Set([
|
|
1637
|
-
...RESERVED_BINDING_NAMES,
|
|
1638
|
-
"API_WORKER_URL",
|
|
1639
|
-
"PLATFORM_WORKER_URL"
|
|
1640
|
-
]);
|
|
1641
|
-
var SECRET_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1642
|
-
var CONFIG_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
1643
|
-
var MAX_SECRET_VALUE_BYTES = 32 * 1024;
|
|
1644
|
-
function validateConfigName(configName) {
|
|
1645
|
-
if (!CONFIG_NAME_RE.test(configName)) {
|
|
1646
|
-
throw new Error(
|
|
1647
|
-
`Invalid config "${configName}". Use letters, numbers, underscores, or hyphens; start with a letter or number.`
|
|
1648
|
-
);
|
|
1649
|
-
}
|
|
1650
|
-
return configName;
|
|
1651
|
-
}
|
|
1652
|
-
function validateSecretName(name) {
|
|
1653
|
-
if (!SECRET_NAME_RE.test(name)) {
|
|
1654
|
-
throw new Error(
|
|
1655
|
-
`Invalid secret name "${name}". Use letters, numbers, and underscores; start with a letter or underscore.`
|
|
1656
|
-
);
|
|
1657
|
-
}
|
|
1658
|
-
if (RESERVED_SECRET_NAMES.has(name)) {
|
|
1659
|
-
throw new Error(
|
|
1660
|
-
`"${name}" is managed by the DeepSpace SDK and can't be set as an app secret \u2014 the platform injects it at deploy time.`
|
|
1661
|
-
);
|
|
1662
|
-
}
|
|
1663
|
-
return name;
|
|
1664
|
-
}
|
|
1665
|
-
function validateSecretValue(key, value) {
|
|
1666
|
-
if (new TextEncoder().encode(value).byteLength > MAX_SECRET_VALUE_BYTES) {
|
|
1667
|
-
throw new Error(`Secret "${key}" exceeds ${MAX_SECRET_VALUE_BYTES} bytes`);
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
function defaultConfigNameForEnv(wranglerEnv) {
|
|
1671
|
-
return wranglerEnv ?? "prd";
|
|
1672
|
-
}
|
|
1673
|
-
async function secretsApi(deployUrl, token, path, init = {}) {
|
|
1674
|
-
const res = await fetch(`${deployUrl}/api/secrets${path}`, {
|
|
1675
|
-
...init,
|
|
1676
|
-
headers: {
|
|
1677
|
-
Authorization: `Bearer ${token}`,
|
|
1678
|
-
"Content-Type": "application/json",
|
|
1679
|
-
...init.headers ?? {}
|
|
1680
|
-
}
|
|
1681
|
-
});
|
|
1682
|
-
const body = await res.json().catch(() => ({}));
|
|
1683
|
-
if (!res.ok) {
|
|
1684
|
-
const err = new Error(body.error ?? `Secrets request failed (${res.status})`);
|
|
1685
|
-
err.status = res.status;
|
|
1686
|
-
err.apiPath = path;
|
|
1687
|
-
throw err;
|
|
1688
|
-
}
|
|
1689
|
-
return body;
|
|
1690
|
-
}
|
|
1691
|
-
function listSecrets(deployUrl, token, appId, configName) {
|
|
1692
|
-
return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}`);
|
|
1693
|
-
}
|
|
1694
|
-
function fetchSecretsValues(deployUrl, token, appId, configName) {
|
|
1695
|
-
return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}/values`);
|
|
1696
|
-
}
|
|
1697
|
-
function getSecretPlain(deployUrl, token, appId, configName, key) {
|
|
1698
|
-
return secretsApi(
|
|
1699
|
-
deployUrl,
|
|
1700
|
-
token,
|
|
1701
|
-
`/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}?plain=true`
|
|
1702
|
-
);
|
|
1703
|
-
}
|
|
1704
|
-
function setSecret(deployUrl, token, appId, configName, key, value) {
|
|
1705
|
-
return secretsApi(
|
|
1706
|
-
deployUrl,
|
|
1707
|
-
token,
|
|
1708
|
-
`/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}`,
|
|
1709
|
-
{ method: "PUT", body: JSON.stringify({ value }) }
|
|
1710
|
-
);
|
|
1711
|
-
}
|
|
1712
|
-
function deleteSecret(deployUrl, token, appId, configName, key) {
|
|
1713
|
-
return secretsApi(
|
|
1714
|
-
deployUrl,
|
|
1715
|
-
token,
|
|
1716
|
-
`/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}`,
|
|
1717
|
-
{ method: "DELETE" }
|
|
1718
|
-
);
|
|
1719
|
-
}
|
|
1720
|
-
function uploadSecrets(deployUrl, token, appId, configName, secrets, replace) {
|
|
1721
|
-
return secretsApi(
|
|
1722
|
-
deployUrl,
|
|
1723
|
-
token,
|
|
1724
|
-
`/${appId}/configs/${encodeURIComponent(configName)}/secrets`,
|
|
1725
|
-
{ method: "PUT", body: JSON.stringify({ secrets, replace }) }
|
|
1726
|
-
);
|
|
1727
|
-
}
|
|
1728
|
-
function listConfigs(deployUrl, token, appId) {
|
|
1729
|
-
return secretsApi(deployUrl, token, `/${appId}/configs`);
|
|
1730
|
-
}
|
|
1731
|
-
function createConfig(deployUrl, token, appId, configName, copyFrom) {
|
|
1732
|
-
return secretsApi(deployUrl, token, `/${appId}/configs`, {
|
|
1733
|
-
method: "POST",
|
|
1734
|
-
body: JSON.stringify({ name: configName, ...copyFrom ? { copyFrom } : {} })
|
|
1735
|
-
});
|
|
1736
|
-
}
|
|
1737
|
-
function deleteConfig(deployUrl, token, appId, configName) {
|
|
1738
|
-
return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}`, {
|
|
1739
|
-
method: "DELETE"
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
|
-
async function pullAppSecretsCache(deployUrl, token, appId, configName) {
|
|
1743
|
-
try {
|
|
1744
|
-
const { secrets } = await fetchSecretsValues(deployUrl, token, appId, configName);
|
|
1745
|
-
if (secrets === null || typeof secrets !== "object" || Array.isArray(secrets)) {
|
|
1746
|
-
throw new ApiError(
|
|
1747
|
-
`The secrets service at ${deployUrl} returned an unexpected response shape \u2014 check DEEPSPACE_DEPLOY_URL (is this the service the app lives on?).`,
|
|
1748
|
-
200,
|
|
1749
|
-
"invalid_response",
|
|
1750
|
-
`/${appId}/configs/${configName}/values`
|
|
1751
|
-
);
|
|
1752
|
-
}
|
|
1753
|
-
return { appId, configName, values: secrets };
|
|
1754
|
-
} catch (err) {
|
|
1755
|
-
if (err?.status === 404) return null;
|
|
1756
|
-
throw err;
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
function quoteDotenvValue(value) {
|
|
1760
|
-
if (/^[A-Za-z0-9_./:@+-]*$/.test(value) && value !== "") return value;
|
|
1761
|
-
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
1762
|
-
return `"${escaped}"`;
|
|
1763
|
-
}
|
|
1764
|
-
function renderSecretsCache(secrets, source) {
|
|
1765
|
-
const keys = Object.keys(secrets).sort();
|
|
1766
|
-
const lines = [
|
|
1767
|
-
GENERATED_SECRETS_DIVIDER,
|
|
1768
|
-
`# app ${source.appId} \xB7 config ${source.configName}`,
|
|
1769
|
-
"# Use `npx deepspace secrets` to change these values."
|
|
1770
|
-
];
|
|
1771
|
-
for (const key of keys) {
|
|
1772
|
-
validateSecretValue(key, secrets[key]);
|
|
1773
|
-
lines.push(`${key}=${quoteDotenvValue(secrets[key])}`);
|
|
1774
|
-
}
|
|
1775
|
-
return lines.join("\n");
|
|
1776
|
-
}
|
|
1777
|
-
function stripGeneratedSecretsCache(content) {
|
|
1778
|
-
const idx = content.indexOf(GENERATED_SECRETS_DIVIDER);
|
|
1779
|
-
if (idx === -1) return content;
|
|
1780
|
-
return content.slice(0, idx).trimEnd() + "\n";
|
|
1781
|
-
}
|
|
1782
|
-
function parseSecretsUpload(content) {
|
|
1783
|
-
const trimmed = content.trim();
|
|
1784
|
-
if (!trimmed) return {};
|
|
1785
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
1786
|
-
const parsed = JSON.parse(trimmed);
|
|
1787
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1788
|
-
throw new Error("JSON upload must be an object of KEY=value strings.");
|
|
1789
|
-
}
|
|
1790
|
-
const out2 = {};
|
|
1791
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
1792
|
-
validateSecretName(key);
|
|
1793
|
-
if (typeof value !== "string") throw new Error(`Secret "${key}" must be a string`);
|
|
1794
|
-
validateSecretValue(key, value);
|
|
1795
|
-
out2[key] = value;
|
|
1796
|
-
}
|
|
1797
|
-
return out2;
|
|
1798
|
-
}
|
|
1799
|
-
const out = {};
|
|
1800
|
-
const lines = trimmed.split(/\r?\n/);
|
|
1801
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1802
|
-
const line = lines[i].trim();
|
|
1803
|
-
if (!line || line.startsWith("#")) continue;
|
|
1804
|
-
const eq = line.indexOf("=");
|
|
1805
|
-
if (eq <= 0) continue;
|
|
1806
|
-
const key = line.slice(0, eq).trim();
|
|
1807
|
-
if (!SECRET_NAME_RE.test(key)) continue;
|
|
1808
|
-
validateSecretName(key);
|
|
1809
|
-
const raw = line.slice(eq + 1).trim();
|
|
1810
|
-
const quote = raw[0] === '"' || raw[0] === "'" ? raw[0] : null;
|
|
1811
|
-
let value;
|
|
1812
|
-
if (!quote) {
|
|
1813
|
-
value = raw;
|
|
1814
|
-
} else if (endsWithUnescapedQuote(raw.slice(1), quote)) {
|
|
1815
|
-
const inner = raw.slice(1, -1);
|
|
1816
|
-
value = quote === '"' ? unescapeDotenv(inner) : inner;
|
|
1817
|
-
} else {
|
|
1818
|
-
const startLine = i + 1;
|
|
1819
|
-
const parts = [raw.slice(1)];
|
|
1820
|
-
let closed = false;
|
|
1821
|
-
while (++i < lines.length) {
|
|
1822
|
-
const cont = lines[i].trimEnd();
|
|
1823
|
-
if (endsWithUnescapedQuote(cont, quote)) {
|
|
1824
|
-
parts.push(cont.slice(0, -1));
|
|
1825
|
-
closed = true;
|
|
1826
|
-
break;
|
|
1827
|
-
}
|
|
1828
|
-
parts.push(lines[i]);
|
|
1829
|
-
}
|
|
1830
|
-
if (!closed) {
|
|
1831
|
-
throw new Error(
|
|
1832
|
-
`Unterminated quoted value for "${key}" (opens on line ${startLine}). Close the ${quote === '"' ? "double" : "single"} quote, or escape newlines as \\n on one line.`
|
|
1833
|
-
);
|
|
1834
|
-
}
|
|
1835
|
-
value = parts.join("\n");
|
|
1836
|
-
if (quote === '"') value = unescapeDotenv(value);
|
|
1837
|
-
}
|
|
1838
|
-
validateSecretValue(key, value);
|
|
1839
|
-
out[key] = value;
|
|
1840
|
-
}
|
|
1841
|
-
return out;
|
|
1842
|
-
}
|
|
1843
|
-
function endsWithUnescapedQuote(s, q) {
|
|
1844
|
-
if (!s.endsWith(q)) return false;
|
|
1845
|
-
if (q === "'") return true;
|
|
1846
|
-
let backslashes = 0;
|
|
1847
|
-
for (let j = s.length - 2; j >= 0 && s[j] === "\\"; j--) backslashes++;
|
|
1848
|
-
return backslashes % 2 === 0;
|
|
1849
|
-
}
|
|
1850
|
-
function unescapeDotenv(v) {
|
|
1851
|
-
let out = "";
|
|
1852
|
-
for (let i = 0; i < v.length; i++) {
|
|
1853
|
-
if (v[i] === "\\" && i + 1 < v.length) {
|
|
1854
|
-
const c = v[++i];
|
|
1855
|
-
out += c === "n" ? "\n" : c === "r" ? "\r" : c;
|
|
1856
|
-
} else {
|
|
1857
|
-
out += v[i];
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
return out;
|
|
1861
|
-
}
|
|
1862
|
-
function shellSingleQuote(value) {
|
|
1863
|
-
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
1864
|
-
}
|
|
1865
|
-
function formatSecretsDownload(secrets, format) {
|
|
1866
|
-
const entries = Object.entries(secrets).sort(([a], [b]) => a.localeCompare(b));
|
|
1867
|
-
if (format === "json") {
|
|
1868
|
-
return JSON.stringify(Object.fromEntries(entries), null, 2) + "\n";
|
|
1869
|
-
}
|
|
1870
|
-
if (format === "shell") {
|
|
1871
|
-
return entries.map(([k, v]) => `export ${k}=${shellSingleQuote(v)}`).join("\n") + "\n";
|
|
1872
|
-
}
|
|
1873
|
-
return entries.map(([k, v]) => `${k}=${quoteDotenvValue(v)}`).join("\n") + "\n";
|
|
1874
|
-
}
|
|
1875
|
-
async function refreshSecretsCache(deployUrl, token, appId, wranglerEnv) {
|
|
1876
|
-
const configName = defaultConfigNameForEnv(wranglerEnv);
|
|
1877
|
-
const pulled = await pullAppSecretsCache(deployUrl, token, appId, configName);
|
|
1878
|
-
if (!pulled) return null;
|
|
1879
|
-
const count = Object.keys(pulled.values).length;
|
|
1880
|
-
return {
|
|
1881
|
-
pulled,
|
|
1882
|
-
rendered: renderSecretsCache(pulled.values, pulled),
|
|
1883
|
-
// An empty store is the common case and not worth a status line.
|
|
1884
|
-
summary: count ? `Secrets: ${pulled.configName} (${count} secret${count === 1 ? "" : "s"})` : null
|
|
1885
|
-
};
|
|
1886
|
-
}
|
|
1887
|
-
|
|
1888
|
-
// src/cli/lib/secure-file.ts
|
|
1889
|
-
import { chmodSync, writeFileSync as writeFileSync5 } from "fs";
|
|
1890
|
-
function writeSecretFileSync(path, content) {
|
|
1891
|
-
writeFileSync5(path, content, { mode: 384 });
|
|
1892
|
-
try {
|
|
1893
|
-
chmodSync(path, 384);
|
|
1894
|
-
} catch {
|
|
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 {
|
|
1895
1482
|
}
|
|
1896
1483
|
}
|
|
1897
1484
|
|
|
1898
1485
|
// src/cli/lib/wrangler-env.ts
|
|
1899
|
-
import { existsSync as existsSync6, readFileSync as
|
|
1486
|
+
import { existsSync as existsSync6, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
1900
1487
|
import { join as join7 } from "path";
|
|
1901
1488
|
import { parse as parseToml3, stringify as stringifyToml } from "smol-toml";
|
|
1902
1489
|
|
|
@@ -1997,26 +1584,12 @@ function resolveAppNameForEnv(config, envName) {
|
|
|
1997
1584
|
}
|
|
1998
1585
|
return resolveAppName(envBlock.name);
|
|
1999
1586
|
}
|
|
2000
|
-
function devVarsPathFor(appDir
|
|
2001
|
-
|
|
2002
|
-
return join7(appDir, `.dev.vars.${envName}`);
|
|
2003
|
-
}
|
|
2004
|
-
function legacyEnvDevVarsPathFor(appDir, envName) {
|
|
2005
|
-
return envName ? join7(appDir, `.dev.vars.${envName}`) : void 0;
|
|
2006
|
-
}
|
|
2007
|
-
function warnIfLegacyEnvDevVarsExists(appDir, envName, warn = console.warn) {
|
|
2008
|
-
const legacyPath = legacyEnvDevVarsPathFor(appDir, envName);
|
|
2009
|
-
if (!legacyPath || !existsSync6(legacyPath)) return;
|
|
2010
|
-
warn(
|
|
2011
|
-
`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.`
|
|
2012
|
-
);
|
|
1587
|
+
function devVarsPathFor(appDir) {
|
|
1588
|
+
return join7(appDir, ".dev.vars");
|
|
2013
1589
|
}
|
|
2014
|
-
function prepareWranglerEnvConfig(appDir, envName
|
|
1590
|
+
function prepareWranglerEnvConfig(appDir, envName) {
|
|
2015
1591
|
if (!envName) return { cleanup() {
|
|
2016
1592
|
} };
|
|
2017
|
-
if (!opts.sharedDevVarsCache) return { wranglerEnv: envName, cleanup() {
|
|
2018
|
-
} };
|
|
2019
|
-
warnIfLegacyEnvDevVarsExists(appDir, envName, opts.warn);
|
|
2020
1593
|
const config = readWranglerConfig(appDir);
|
|
2021
1594
|
const resolved = resolveAppNameForEnv(config, envName);
|
|
2022
1595
|
if (!resolved.ok) throw new Error(resolved.reason);
|
|
@@ -2045,8 +1618,6 @@ function wranglerViteEnv(baseEnv, prepared, extraEnv = {}) {
|
|
|
2045
1618
|
if (prepared.configPath) {
|
|
2046
1619
|
delete env.CLOUDFLARE_ENV;
|
|
2047
1620
|
env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH = prepared.configPath;
|
|
2048
|
-
} else if (prepared.wranglerEnv) {
|
|
2049
|
-
env.CLOUDFLARE_ENV = prepared.wranglerEnv;
|
|
2050
1621
|
}
|
|
2051
1622
|
return env;
|
|
2052
1623
|
}
|
|
@@ -2090,7 +1661,7 @@ var WranglerConfigError = class extends Error {
|
|
|
2090
1661
|
};
|
|
2091
1662
|
function readWranglerConfig(appDir) {
|
|
2092
1663
|
const path = join7(appDir, "wrangler.toml");
|
|
2093
|
-
const raw =
|
|
1664
|
+
const raw = readFileSync9(path, "utf-8");
|
|
2094
1665
|
try {
|
|
2095
1666
|
return parseToml3(raw);
|
|
2096
1667
|
} catch (err) {
|
|
@@ -2102,369 +1673,706 @@ function hasWranglerConfig(appDir) {
|
|
|
2102
1673
|
}
|
|
2103
1674
|
|
|
2104
1675
|
// src/cli/lib/dev-vars.ts
|
|
2105
|
-
var
|
|
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."
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
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
|
+
);
|
|
1711
|
+
}
|
|
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) {
|
|
1721
|
+
try {
|
|
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 {
|
|
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";
|
|
1731
|
+
}
|
|
1732
|
+
function installCommand(appDir) {
|
|
1733
|
+
return `${detectPackageManager(appDir)} install`;
|
|
1734
|
+
}
|
|
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";
|
|
1746
|
+
}
|
|
1747
|
+
return "missing";
|
|
1748
|
+
}
|
|
1749
|
+
function tailHint(logPath, platform = process.platform) {
|
|
1750
|
+
return platform === "win32" ? `Get-Content -Wait ${logPath}` : `tail -f ${logPath}`;
|
|
1751
|
+
}
|
|
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
|
+
);
|
|
1767
|
+
}
|
|
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
|
+
);
|
|
1776
|
+
}
|
|
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
|
+
);
|
|
1782
|
+
}
|
|
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"] }
|
|
1788
|
+
}
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
function resolvesDeepspace(fromDir) {
|
|
1792
|
+
let dir = canonicalPath2(fromDir);
|
|
1793
|
+
let checkoutRoot = null;
|
|
1794
|
+
try {
|
|
1795
|
+
checkoutRoot = canonicalPath2(repoToplevel(dir));
|
|
1796
|
+
} catch {
|
|
1797
|
+
}
|
|
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;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
function canonicalPath2(path) {
|
|
1807
|
+
try {
|
|
1808
|
+
return realpathSync3(path);
|
|
1809
|
+
} catch {
|
|
1810
|
+
return resolve6(path);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
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";
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
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
|
+
);
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
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;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
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([
|
|
1898
|
+
"vectorize",
|
|
1899
|
+
"ai",
|
|
1900
|
+
"r2_bucket",
|
|
1901
|
+
"kv_namespace",
|
|
1902
|
+
"d1",
|
|
1903
|
+
"queue",
|
|
1904
|
+
"browser_rendering",
|
|
1905
|
+
"analytics_engine",
|
|
1906
|
+
"hyperdrive",
|
|
1907
|
+
"ratelimit"
|
|
1908
|
+
]);
|
|
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",
|
|
2106
1918
|
"AUTH_JWT_PUBLIC_KEY",
|
|
2107
1919
|
"AUTH_JWT_ISSUER",
|
|
2108
1920
|
"AUTH_WORKER_URL",
|
|
2109
|
-
"API_WORKER_URL",
|
|
2110
|
-
"PLATFORM_WORKER_URL",
|
|
2111
|
-
"OWNER_USER_ID",
|
|
2112
|
-
"APP_OWNER_JWT",
|
|
2113
1921
|
"APP_IDENTITY_TOKEN",
|
|
2114
|
-
"
|
|
1922
|
+
"APP_OWNER_JWT",
|
|
1923
|
+
"USAGE_EVENTS"
|
|
2115
1924
|
]);
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
const out = [];
|
|
2120
|
-
let belowDivider = false;
|
|
2121
|
-
let i = 0;
|
|
2122
|
-
while (i < lines.length) {
|
|
2123
|
-
const line = lines[i];
|
|
2124
|
-
if (line.trim() === DEV_VARS_DIVIDER.trim()) {
|
|
2125
|
-
belowDivider = true;
|
|
2126
|
-
i++;
|
|
2127
|
-
continue;
|
|
2128
|
-
}
|
|
2129
|
-
const eq = line.indexOf("=");
|
|
2130
|
-
const keyMatch = eq >= 0 ? line.slice(0, eq).match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*$/) : null;
|
|
2131
|
-
if (!keyMatch) {
|
|
2132
|
-
out.push(line);
|
|
2133
|
-
i++;
|
|
2134
|
-
continue;
|
|
2135
|
-
}
|
|
2136
|
-
const key = keyMatch[1];
|
|
2137
|
-
const valueStart = line.slice(eq + 1);
|
|
2138
|
-
const block = [line];
|
|
2139
|
-
if (valueStart.startsWith('"') && !hasUnescapedQuote(valueStart.slice(1))) {
|
|
2140
|
-
i++;
|
|
2141
|
-
while (i < lines.length) {
|
|
2142
|
-
block.push(lines[i]);
|
|
2143
|
-
const closed = hasUnescapedQuote(lines[i]);
|
|
2144
|
-
i++;
|
|
2145
|
-
if (closed) break;
|
|
2146
|
-
}
|
|
2147
|
-
} else {
|
|
2148
|
-
i++;
|
|
2149
|
-
}
|
|
2150
|
-
if (belowDivider || !SDK_MANAGED_KEYS.has(key)) out.push(...block);
|
|
1925
|
+
function validateBindingManifest(manifest) {
|
|
1926
|
+
if (!Array.isArray(manifest)) {
|
|
1927
|
+
return { valid: false, errors: [{ reason: "Manifest must be an array" }] };
|
|
2151
1928
|
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
escaped = false;
|
|
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
|
+
});
|
|
2160
1936
|
continue;
|
|
2161
1937
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
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
|
+
});
|
|
2164
1945
|
continue;
|
|
2165
1946
|
}
|
|
2166
|
-
if (
|
|
2167
|
-
|
|
2168
|
-
return false;
|
|
2169
|
-
}
|
|
2170
|
-
function parseDevVars(content) {
|
|
2171
|
-
const result = {};
|
|
2172
|
-
const lines = content.split("\n");
|
|
2173
|
-
let i = 0;
|
|
2174
|
-
while (i < lines.length) {
|
|
2175
|
-
const line = lines[i];
|
|
2176
|
-
const trimmed = line.trim();
|
|
2177
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
2178
|
-
i++;
|
|
1947
|
+
if (typeof name !== "string" || !name) {
|
|
1948
|
+
errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
|
|
2179
1949
|
continue;
|
|
2180
1950
|
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
i++;
|
|
1951
|
+
if (RESERVED_BINDING_NAMES.has(name)) {
|
|
1952
|
+
errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
|
|
2184
1953
|
continue;
|
|
2185
1954
|
}
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
i++;
|
|
1955
|
+
if (seenNames.has(name)) {
|
|
1956
|
+
errors.push({ reason: `Duplicate binding name "${name}"` });
|
|
2189
1957
|
continue;
|
|
2190
1958
|
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
if (
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
1959
|
+
seenNames.add(name);
|
|
1960
|
+
const binding = entry;
|
|
1961
|
+
const fieldErr = requiredFieldError(binding);
|
|
1962
|
+
if (fieldErr) errors.push({ binding, reason: fieldErr });
|
|
1963
|
+
}
|
|
1964
|
+
if (errors.length) return { valid: false, errors };
|
|
1965
|
+
return { valid: true, bindings: manifest };
|
|
1966
|
+
}
|
|
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(", ")})`;
|
|
2202
1977
|
}
|
|
2203
|
-
i++;
|
|
2204
1978
|
}
|
|
2205
|
-
|
|
2206
|
-
i++;
|
|
1979
|
+
return null;
|
|
2207
1980
|
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
)
|
|
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;
|
|
2212
1990
|
}
|
|
2213
|
-
|
|
2214
|
-
|
|
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;
|
|
2215
2026
|
}
|
|
2216
|
-
result[key] = value;
|
|
2217
2027
|
}
|
|
2218
|
-
return result;
|
|
2219
2028
|
}
|
|
2220
|
-
function
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
const
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
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
|
+
});
|
|
2046
|
+
}
|
|
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 });
|
|
2051
|
+
}
|
|
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
|
+
});
|
|
2063
|
+
}
|
|
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
|
+
});
|
|
2075
|
+
}
|
|
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 });
|
|
2081
|
+
}
|
|
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 } });
|
|
2235
2102
|
}
|
|
2236
2103
|
return out;
|
|
2237
2104
|
}
|
|
2238
|
-
function
|
|
2239
|
-
|
|
2240
|
-
if (id) return id;
|
|
2241
|
-
throw new Error(
|
|
2242
|
-
"wrangler.toml has no DEEPSPACE_APP_ID" + (wranglerEnv ? ` for [env.${wranglerEnv}]` : "") + ". Run `deepspace app init` (or `deepspace app init --env <name>`) and retry."
|
|
2243
|
-
);
|
|
2105
|
+
function isObject(v) {
|
|
2106
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
2244
2107
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
const
|
|
2248
|
-
|
|
2249
|
-
const appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt, appId);
|
|
2250
|
-
const appIdentityToken = await fetchAppIdentityToken(urls.deploy, callerJwt, appId);
|
|
2251
|
-
const sdkVars = [
|
|
2252
|
-
`AUTH_JWT_PUBLIC_KEY="${publicKey}"`,
|
|
2253
|
-
`AUTH_JWT_ISSUER=${urls.auth}/api/auth`,
|
|
2254
|
-
`AUTH_WORKER_URL=${urls.auth}`,
|
|
2255
|
-
`API_WORKER_URL=${urls.api}`,
|
|
2256
|
-
`PLATFORM_WORKER_URL=${urls.platform}`,
|
|
2257
|
-
`OWNER_USER_ID=${ownerId}`,
|
|
2258
|
-
`APP_OWNER_JWT=${appOwnerJwt}`,
|
|
2259
|
-
...appIdentityToken ? [`APP_IDENTITY_TOKEN=${appIdentityToken}`] : [],
|
|
2260
|
-
`ALLOW_DEBUG_ROUTES=true`
|
|
2261
|
-
].join("\n");
|
|
2262
|
-
const useSharedDevVarsCache = opts.sharedDevVarsCache ?? opts.generatedSecretsCache !== void 0;
|
|
2263
|
-
const devVarsPath = devVarsPathFor(appDir, wranglerEnv, {
|
|
2264
|
-
sharedDevVarsCache: useSharedDevVarsCache
|
|
2265
|
-
});
|
|
2266
|
-
const existing = existsSync7(devVarsPath) ? readFileSync9(devVarsPath, "utf-8") : "";
|
|
2267
|
-
const custom = opts.generatedSecretsCache ?? extractCustomDevVars(stripGeneratedSecretsCache(existing).trimEnd());
|
|
2268
|
-
let body;
|
|
2269
|
-
if (!custom) {
|
|
2270
|
-
body = `${sdkVars}
|
|
2271
|
-
`;
|
|
2272
|
-
} else if (opts.generatedSecretsCache) {
|
|
2273
|
-
body = `${sdkVars}
|
|
2274
|
-
|
|
2275
|
-
${custom}
|
|
2276
|
-
`;
|
|
2277
|
-
} else {
|
|
2278
|
-
body = `${sdkVars}
|
|
2279
|
-
|
|
2280
|
-
${DEV_VARS_DIVIDER}
|
|
2281
|
-
${custom}
|
|
2282
|
-
`;
|
|
2283
|
-
}
|
|
2284
|
-
writeSecretFileSync(devVarsPath, body);
|
|
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;
|
|
2285
2112
|
}
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
// src/cli/lib/package-manager.ts
|
|
2292
|
-
import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
|
|
2293
|
-
import { join as join8 } from "path";
|
|
2294
|
-
function detectPackageManager(appDir) {
|
|
2295
|
-
try {
|
|
2296
|
-
const pkg2 = JSON.parse(readFileSync10(join8(appDir, "package.json"), "utf-8"));
|
|
2297
|
-
const name = typeof pkg2.packageManager === "string" ? pkg2.packageManager.split("@")[0] : "";
|
|
2298
|
-
if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") return name;
|
|
2299
|
-
} catch {
|
|
2300
|
-
}
|
|
2301
|
-
if (existsSync8(join8(appDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
2302
|
-
if (existsSync8(join8(appDir, "yarn.lock"))) return "yarn";
|
|
2303
|
-
if (existsSync8(join8(appDir, "bun.lock")) || existsSync8(join8(appDir, "bun.lockb"))) return "bun";
|
|
2304
|
-
return "npm";
|
|
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;
|
|
2305
2117
|
}
|
|
2306
|
-
function
|
|
2307
|
-
|
|
2118
|
+
function asObjectArray(v) {
|
|
2119
|
+
if (!Array.isArray(v)) return [];
|
|
2120
|
+
return v.filter(isObject);
|
|
2308
2121
|
}
|
|
2309
2122
|
|
|
2310
|
-
// src/cli/lib/
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2123
|
+
// src/cli/lib/secrets.ts
|
|
2124
|
+
var RESERVED_SECRET_NAMES = /* @__PURE__ */ new Set([
|
|
2125
|
+
...RESERVED_BINDING_NAMES,
|
|
2126
|
+
"API_WORKER_URL",
|
|
2127
|
+
"PLATFORM_WORKER_URL"
|
|
2128
|
+
]);
|
|
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
|
+
);
|
|
2320
2137
|
}
|
|
2321
|
-
return
|
|
2322
|
-
}
|
|
2323
|
-
function tailHint(logPath, platform = process.platform) {
|
|
2324
|
-
return platform === "win32" ? `Get-Content -Wait ${logPath}` : `tail -f ${logPath}`;
|
|
2138
|
+
return configName;
|
|
2325
2139
|
}
|
|
2326
|
-
function
|
|
2327
|
-
if (
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
const startedPath = join9(sentinelDir, "install.started");
|
|
2331
|
-
const donePath = join9(sentinelDir, "install.done");
|
|
2332
|
-
const logPath = join9(sentinelDir, "install.log");
|
|
2333
|
-
if (existsSync9(errPath)) {
|
|
2334
|
-
const detail = readFileSync11(errPath, "utf-8").trimEnd();
|
|
2335
|
-
const log29 = existsSync9(logPath) ? ` Full log: ${logPath}.` : "";
|
|
2336
|
-
throw new Refusal(
|
|
2337
|
-
`Dependency install failed: ${detail}.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
|
|
2338
|
-
"install_failed",
|
|
2339
|
-
{ action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
|
|
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.`
|
|
2340
2144
|
);
|
|
2341
2145
|
}
|
|
2342
|
-
if (
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
throw new Refusal(
|
|
2346
|
-
`The dependency install is no longer running and never finished.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
|
|
2347
|
-
"install_failed",
|
|
2348
|
-
{ action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
|
|
2349
|
-
);
|
|
2350
|
-
}
|
|
2351
|
-
const tail = existsSync9(logPath) ? ` Tail progress: ${tailHint(logPath)}.` : "";
|
|
2352
|
-
throw new Refusal(
|
|
2353
|
-
`Dependencies are still installing.${tail} Retry once it finishes.`,
|
|
2354
|
-
"install_in_progress"
|
|
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.`
|
|
2355
2149
|
);
|
|
2356
2150
|
}
|
|
2357
|
-
|
|
2358
|
-
"Dependencies not installed. Run `npm install` (or `bun install`) first.",
|
|
2359
|
-
"deps_missing",
|
|
2360
|
-
{
|
|
2361
|
-
action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] }
|
|
2362
|
-
}
|
|
2363
|
-
);
|
|
2151
|
+
return name;
|
|
2364
2152
|
}
|
|
2365
|
-
function
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
try {
|
|
2369
|
-
checkoutRoot = canonicalPath2(repoToplevel(dir));
|
|
2370
|
-
} catch {
|
|
2371
|
-
}
|
|
2372
|
-
for (; ; ) {
|
|
2373
|
-
if (existsSync9(join9(dir, "node_modules", "deepspace", "package.json"))) return true;
|
|
2374
|
-
if (dir === checkoutRoot) return false;
|
|
2375
|
-
const parent = dirname4(dir);
|
|
2376
|
-
if (parent === dir) return false;
|
|
2377
|
-
dir = parent;
|
|
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`);
|
|
2378
2156
|
}
|
|
2379
2157
|
}
|
|
2380
|
-
function
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
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 ?? {}
|
|
2168
|
+
}
|
|
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;
|
|
2385
2176
|
}
|
|
2177
|
+
return body;
|
|
2386
2178
|
}
|
|
2387
|
-
function
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
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
|
+
);
|
|
2207
|
+
}
|
|
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 }) }
|
|
2214
|
+
);
|
|
2215
|
+
}
|
|
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 } : {} })
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
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) {
|
|
2392
2231
|
try {
|
|
2393
|
-
|
|
2394
|
-
|
|
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 };
|
|
2395
2242
|
} catch (err) {
|
|
2396
|
-
|
|
2243
|
+
if (err?.status === 404) return null;
|
|
2244
|
+
throw err;
|
|
2397
2245
|
}
|
|
2398
2246
|
}
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
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}"`;
|
|
2251
|
+
}
|
|
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])}`);
|
|
2258
|
+
}
|
|
2259
|
+
return lines.join("\n");
|
|
2260
|
+
}
|
|
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;
|
|
2277
|
+
}
|
|
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.`
|
|
2292
|
+
);
|
|
2293
|
+
}
|
|
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);
|
|
2323
|
+
}
|
|
2324
|
+
validateSecretValue(key, value);
|
|
2325
|
+
out[key] = value;
|
|
2326
|
+
}
|
|
2327
|
+
return out;
|
|
2411
2328
|
}
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
if (
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
const status3 = err.status;
|
|
2429
|
-
if (status3 !== DLL_NOT_FOUND) return;
|
|
2430
|
-
throw new Refusal(
|
|
2431
|
-
"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.",
|
|
2432
|
-
"vcredist_missing"
|
|
2433
|
-
);
|
|
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;
|
|
2335
|
+
}
|
|
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
|
+
}
|
|
2434
2345
|
}
|
|
2346
|
+
return out;
|
|
2435
2347
|
}
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
let entries;
|
|
2444
|
-
try {
|
|
2445
|
-
entries = readdirSync2(dir, { withFileTypes: true });
|
|
2446
|
-
} catch {
|
|
2447
|
-
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";
|
|
2448
2355
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
if (entry.isDirectory()) {
|
|
2452
|
-
if (!SKIP_DIRS.has(entry.name)) removed += removeMacosJunk(path);
|
|
2453
|
-
continue;
|
|
2454
|
-
}
|
|
2455
|
-
if (entry.name === ".DS_Store" || entry.name.startsWith("._")) {
|
|
2456
|
-
try {
|
|
2457
|
-
rmSync3(path, { force: true });
|
|
2458
|
-
removed++;
|
|
2459
|
-
} catch {
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
2356
|
+
if (format === "shell") {
|
|
2357
|
+
return entries.map(([k, v]) => `export ${k}=${shellSingleQuote(v)}`).join("\n") + "\n";
|
|
2462
2358
|
}
|
|
2463
|
-
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
|
+
};
|
|
2464
2372
|
}
|
|
2465
2373
|
|
|
2466
2374
|
// src/cli/lib/schema-lint.ts
|
|
2467
|
-
import { existsSync as
|
|
2375
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2468
2376
|
import { join as join11 } from "path";
|
|
2469
2377
|
import { pathToFileURL } from "url";
|
|
2470
2378
|
|
|
@@ -2525,7 +2433,7 @@ function lintSchema(schema) {
|
|
|
2525
2433
|
// src/cli/lib/schema-lint.ts
|
|
2526
2434
|
async function lintProjectSchemas(appDir) {
|
|
2527
2435
|
const schemasPath = join11(appDir, "src", "schemas.ts");
|
|
2528
|
-
if (!
|
|
2436
|
+
if (!existsSync10(schemasPath)) return null;
|
|
2529
2437
|
let schemas;
|
|
2530
2438
|
try {
|
|
2531
2439
|
const esbuild = await import("esbuild");
|
|
@@ -2577,11 +2485,11 @@ import { createServer, Socket } from "net";
|
|
|
2577
2485
|
var DEFAULT_PORT = 5173;
|
|
2578
2486
|
function isPortListening(port, host, timeoutMs = 500) {
|
|
2579
2487
|
const target2 = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
2580
|
-
return new Promise((
|
|
2488
|
+
return new Promise((resolve21) => {
|
|
2581
2489
|
const socket = new Socket();
|
|
2582
2490
|
const done = (listening) => {
|
|
2583
2491
|
socket.destroy();
|
|
2584
|
-
|
|
2492
|
+
resolve21(listening);
|
|
2585
2493
|
};
|
|
2586
2494
|
socket.setTimeout(timeoutMs);
|
|
2587
2495
|
socket.once("connect", () => done(true));
|
|
@@ -2594,7 +2502,7 @@ async function waitForPortListening(port, host, timeoutMs) {
|
|
|
2594
2502
|
const deadline = Date.now() + timeoutMs;
|
|
2595
2503
|
while (Date.now() < deadline) {
|
|
2596
2504
|
if (await isPortListening(port, host)) return true;
|
|
2597
|
-
await new Promise((
|
|
2505
|
+
await new Promise((resolve21) => setTimeout(resolve21, 250));
|
|
2598
2506
|
}
|
|
2599
2507
|
return false;
|
|
2600
2508
|
}
|
|
@@ -2660,7 +2568,7 @@ var dev_default = defineDeepspaceCommand({
|
|
|
2660
2568
|
preflightNodeVersion("dev start");
|
|
2661
2569
|
const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
2662
2570
|
const host = typeof args.host === "string" && args.host.trim() ? args.host.trim() : DEFAULT_DEV_HOST;
|
|
2663
|
-
const start =
|
|
2571
|
+
const start = resolve8(args.dir ?? ".");
|
|
2664
2572
|
const appDir = findAppDir(start);
|
|
2665
2573
|
if (!appDir) {
|
|
2666
2574
|
throw new Refusal(noAppDirMessage(start, findChildApps(start)), "no_app_dir");
|
|
@@ -2729,10 +2637,8 @@ var dev_default = defineDeepspaceCommand({
|
|
|
2729
2637
|
if (appIdForSecrets) {
|
|
2730
2638
|
try {
|
|
2731
2639
|
const refreshed = await refreshSecretsCache(DEPLOY_URL, token, appIdForSecrets, wranglerEnv);
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
if (refreshed.summary) say(refreshed.summary);
|
|
2735
|
-
}
|
|
2640
|
+
generatedSecretsCache = refreshed.rendered;
|
|
2641
|
+
if (refreshed.summary) say(refreshed.summary);
|
|
2736
2642
|
} catch (err) {
|
|
2737
2643
|
throw new Refusal(
|
|
2738
2644
|
`Failed to refresh app secrets: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -2740,16 +2646,10 @@ var dev_default = defineDeepspaceCommand({
|
|
|
2740
2646
|
);
|
|
2741
2647
|
}
|
|
2742
2648
|
}
|
|
2743
|
-
const sharedDevVarsCache = generatedSecretsCache !== void 0;
|
|
2744
2649
|
await writeDevVars(appDir, payload.sub, token, wranglerEnv, {
|
|
2745
|
-
generatedSecretsCache
|
|
2746
|
-
sharedDevVarsCache
|
|
2650
|
+
generatedSecretsCache
|
|
2747
2651
|
});
|
|
2748
|
-
const wranglerConfig = prepareWranglerEnvConfig(
|
|
2749
|
-
appDir,
|
|
2750
|
-
wranglerEnv,
|
|
2751
|
-
{ sharedDevVarsCache }
|
|
2752
|
-
);
|
|
2652
|
+
const wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv);
|
|
2753
2653
|
preflightWindowsWorkerd(appDir);
|
|
2754
2654
|
if (await isPortListening(port, host)) {
|
|
2755
2655
|
const killArgv = port === DEFAULT_PORT ? ["deepspace", "dev", "kill"] : ["deepspace", "dev", "kill", "--port", String(port)];
|
|
@@ -2764,15 +2664,11 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
|
|
|
2764
2664
|
}
|
|
2765
2665
|
const url = `http://${host === DEFAULT_DEV_HOST ? "localhost" : host}:${port}`;
|
|
2766
2666
|
say("Starting dev server...\n");
|
|
2767
|
-
const vite = spawn(
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
{
|
|
2771
|
-
|
|
2772
|
-
stdio: "inherit",
|
|
2773
|
-
env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
|
|
2774
|
-
}
|
|
2775
|
-
);
|
|
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
|
+
});
|
|
2776
2672
|
if (args.json) {
|
|
2777
2673
|
void waitForPortListening(port, host, READINESS_TIMEOUT_MS).then((ready) => {
|
|
2778
2674
|
console.log(
|
|
@@ -3098,7 +2994,7 @@ function isAlive(pid) {
|
|
|
3098
2994
|
}
|
|
3099
2995
|
|
|
3100
2996
|
// src/cli/commands/test.ts
|
|
3101
|
-
import { resolve as
|
|
2997
|
+
import { resolve as resolve9 } from "path";
|
|
3102
2998
|
import { sync as spawnSync4 } from "cross-spawn";
|
|
3103
2999
|
|
|
3104
3000
|
// src/cli/lib/playwright.ts
|
|
@@ -3130,7 +3026,7 @@ function ensurePlaywright(appDir) {
|
|
|
3130
3026
|
import { readFileSync as readFileSync14 } from "fs";
|
|
3131
3027
|
|
|
3132
3028
|
// src/testing/accounts.ts
|
|
3133
|
-
import { existsSync as
|
|
3029
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
|
|
3134
3030
|
import { homedir as homedir3 } from "os";
|
|
3135
3031
|
import { join as join13 } from "path";
|
|
3136
3032
|
var TEST_ACCOUNTS_PATH = join13(homedir3(), ".deepspace", "test-accounts.json");
|
|
@@ -3204,7 +3100,7 @@ function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope())
|
|
|
3204
3100
|
});
|
|
3205
3101
|
}
|
|
3206
3102
|
function readTestAccountStore() {
|
|
3207
|
-
if (!
|
|
3103
|
+
if (!existsSync11(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {}, unscoped: [] };
|
|
3208
3104
|
try {
|
|
3209
3105
|
const raw = JSON.parse(readFileSync13(TEST_ACCOUNTS_PATH, "utf-8"));
|
|
3210
3106
|
if (Array.isArray(raw)) return { version: 2, scopes: {}, unscoped: validTestAccounts(raw) };
|
|
@@ -3321,7 +3217,7 @@ var test_default = defineDeepspaceCommand({
|
|
|
3321
3217
|
preflightNodeVersion("test run");
|
|
3322
3218
|
const suite = args.suite ?? "default";
|
|
3323
3219
|
const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
3324
|
-
const start =
|
|
3220
|
+
const start = resolve9(".");
|
|
3325
3221
|
const appDir = findAppDir(start);
|
|
3326
3222
|
if (!appDir) {
|
|
3327
3223
|
throw new Refusal(noAppDirMessage(start, findChildApps(start)), "no_app_dir");
|
|
@@ -3348,10 +3244,8 @@ var test_default = defineDeepspaceCommand({
|
|
|
3348
3244
|
if (appIdForSecrets) {
|
|
3349
3245
|
try {
|
|
3350
3246
|
const refreshed = await refreshSecretsCache(DEPLOY_URL2, token, appIdForSecrets, wranglerEnv);
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
if (refreshed.summary) say(refreshed.summary);
|
|
3354
|
-
}
|
|
3247
|
+
generatedSecretsCache = refreshed.rendered;
|
|
3248
|
+
if (refreshed.summary) say(refreshed.summary);
|
|
3355
3249
|
} catch (err) {
|
|
3356
3250
|
throw new Refusal(
|
|
3357
3251
|
`Failed to refresh app secrets: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -3359,17 +3253,17 @@ var test_default = defineDeepspaceCommand({
|
|
|
3359
3253
|
);
|
|
3360
3254
|
}
|
|
3361
3255
|
}
|
|
3362
|
-
const sharedDevVarsCache = generatedSecretsCache !== void 0;
|
|
3363
3256
|
await writeDevVars(appDir, ownerId, token, wranglerEnv, {
|
|
3364
|
-
generatedSecretsCache
|
|
3365
|
-
sharedDevVarsCache
|
|
3257
|
+
generatedSecretsCache
|
|
3366
3258
|
});
|
|
3367
3259
|
if (suite !== "unit") {
|
|
3368
3260
|
try {
|
|
3369
3261
|
const { removed } = await syncTestAccountStore();
|
|
3370
3262
|
if (removed > 0) say(`Removed ${removed} stale test account credential(s).`);
|
|
3371
3263
|
} catch (error) {
|
|
3372
|
-
say(
|
|
3264
|
+
say(
|
|
3265
|
+
`Warning: could not reconcile test accounts: ${error instanceof Error ? error.message : String(error)}`
|
|
3266
|
+
);
|
|
3373
3267
|
}
|
|
3374
3268
|
}
|
|
3375
3269
|
if (suite !== "unit") {
|
|
@@ -3379,46 +3273,32 @@ var test_default = defineDeepspaceCommand({
|
|
|
3379
3273
|
let exitCode = 0;
|
|
3380
3274
|
switch (suite) {
|
|
3381
3275
|
case "smoke":
|
|
3382
|
-
exitCode = runPlaywright(
|
|
3383
|
-
appDir,
|
|
3384
|
-
["tests/smoke.spec.ts"],
|
|
3385
|
-
port,
|
|
3386
|
-
wranglerEnv,
|
|
3387
|
-
sharedDevVarsCache
|
|
3388
|
-
);
|
|
3276
|
+
exitCode = runPlaywright(appDir, ["tests/smoke.spec.ts"], port, wranglerEnv);
|
|
3389
3277
|
break;
|
|
3390
3278
|
case "api":
|
|
3391
|
-
exitCode = runPlaywright(
|
|
3392
|
-
appDir,
|
|
3393
|
-
["tests/api.spec.ts"],
|
|
3394
|
-
port,
|
|
3395
|
-
wranglerEnv,
|
|
3396
|
-
sharedDevVarsCache
|
|
3397
|
-
);
|
|
3279
|
+
exitCode = runPlaywright(appDir, ["tests/api.spec.ts"], port, wranglerEnv);
|
|
3398
3280
|
break;
|
|
3399
3281
|
case "e2e":
|
|
3400
|
-
exitCode = runPlaywright(appDir, [], port, wranglerEnv
|
|
3282
|
+
exitCode = runPlaywright(appDir, [], port, wranglerEnv);
|
|
3401
3283
|
break;
|
|
3402
3284
|
case "unit":
|
|
3403
3285
|
exitCode = runVitest(appDir);
|
|
3404
3286
|
break;
|
|
3405
3287
|
case "all":
|
|
3406
3288
|
exitCode = runVitest(appDir);
|
|
3407
|
-
if (exitCode === 0)
|
|
3408
|
-
exitCode = runPlaywright(appDir, [], port, wranglerEnv, sharedDevVarsCache);
|
|
3289
|
+
if (exitCode === 0) exitCode = runPlaywright(appDir, [], port, wranglerEnv);
|
|
3409
3290
|
break;
|
|
3410
3291
|
case "default":
|
|
3411
3292
|
exitCode = runPlaywright(
|
|
3412
3293
|
appDir,
|
|
3413
3294
|
["tests/smoke.spec.ts", "tests/api.spec.ts"],
|
|
3414
3295
|
port,
|
|
3415
|
-
wranglerEnv
|
|
3416
|
-
sharedDevVarsCache
|
|
3296
|
+
wranglerEnv
|
|
3417
3297
|
);
|
|
3418
3298
|
break;
|
|
3419
3299
|
default:
|
|
3420
3300
|
if (suite.endsWith(".spec.ts")) {
|
|
3421
|
-
exitCode = runPlaywright(appDir, [suite], port, wranglerEnv
|
|
3301
|
+
exitCode = runPlaywright(appDir, [suite], port, wranglerEnv);
|
|
3422
3302
|
} else {
|
|
3423
3303
|
throw new Refusal(
|
|
3424
3304
|
`Unknown test suite: ${suite}
|
|
@@ -3435,10 +3315,10 @@ Available: smoke, api, e2e, unit, all`,
|
|
|
3435
3315
|
return { data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null } };
|
|
3436
3316
|
}
|
|
3437
3317
|
});
|
|
3438
|
-
function runPlaywright(appDir, testFiles, port, wranglerEnv
|
|
3318
|
+
function runPlaywright(appDir, testFiles, port, wranglerEnv) {
|
|
3439
3319
|
let wranglerConfig;
|
|
3440
3320
|
try {
|
|
3441
|
-
wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv
|
|
3321
|
+
wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv);
|
|
3442
3322
|
} catch (err) {
|
|
3443
3323
|
console.error(err instanceof Error ? err.message : String(err));
|
|
3444
3324
|
return 1;
|
|
@@ -3468,7 +3348,7 @@ function runVitest(appDir) {
|
|
|
3468
3348
|
|
|
3469
3349
|
// src/cli/commands/screenshot.ts
|
|
3470
3350
|
import { sync as spawnSync5 } from "cross-spawn";
|
|
3471
|
-
import { resolve as
|
|
3351
|
+
import { resolve as resolve10 } from "path";
|
|
3472
3352
|
var screenshot_default = defineDeepspaceCommand({
|
|
3473
3353
|
meta: {
|
|
3474
3354
|
name: "screenshot",
|
|
@@ -3497,7 +3377,7 @@ var screenshot_default = defineDeepspaceCommand({
|
|
|
3497
3377
|
}
|
|
3498
3378
|
},
|
|
3499
3379
|
async run({ args }) {
|
|
3500
|
-
const appDir =
|
|
3380
|
+
const appDir = resolve10(".");
|
|
3501
3381
|
ensurePlaywright(appDir);
|
|
3502
3382
|
const url = String(args.url);
|
|
3503
3383
|
const output = String(args.output);
|
|
@@ -3798,7 +3678,7 @@ var test_accounts_default = defineCommand2({
|
|
|
3798
3678
|
// src/cli/commands/deploy.ts
|
|
3799
3679
|
import * as p12 from "@clack/prompts";
|
|
3800
3680
|
import { defineCommand as defineCommand3 } from "citty";
|
|
3801
|
-
import { resolve as
|
|
3681
|
+
import { resolve as resolve13 } from "path";
|
|
3802
3682
|
|
|
3803
3683
|
// src/cli/lib/edge-propagation.ts
|
|
3804
3684
|
import { request as httpRequest } from "http";
|
|
@@ -3863,14 +3743,14 @@ async function servesRelease(probe, releaseStamp) {
|
|
|
3863
3743
|
}
|
|
3864
3744
|
function getOnFreshConnection(url, timeoutMs = 5e3) {
|
|
3865
3745
|
const send = url.protocol === "http:" ? httpRequest : httpsRequest;
|
|
3866
|
-
return new Promise((
|
|
3746
|
+
return new Promise((resolve21) => {
|
|
3867
3747
|
const request2 = send(
|
|
3868
3748
|
url,
|
|
3869
3749
|
{ method: "GET", agent: false, timeout: timeoutMs, headers: { "cache-control": "no-cache" } },
|
|
3870
3750
|
(response) => {
|
|
3871
3751
|
if (response.statusCode !== 200) {
|
|
3872
3752
|
response.resume();
|
|
3873
|
-
|
|
3753
|
+
resolve21(null);
|
|
3874
3754
|
return;
|
|
3875
3755
|
}
|
|
3876
3756
|
let body = "";
|
|
@@ -3878,20 +3758,20 @@ function getOnFreshConnection(url, timeoutMs = 5e3) {
|
|
|
3878
3758
|
response.on("data", (chunk) => {
|
|
3879
3759
|
if (body.length < 4096) body += chunk;
|
|
3880
3760
|
});
|
|
3881
|
-
response.on("end", () =>
|
|
3882
|
-
response.on("error", () =>
|
|
3761
|
+
response.on("end", () => resolve21(body));
|
|
3762
|
+
response.on("error", () => resolve21(null));
|
|
3883
3763
|
}
|
|
3884
3764
|
);
|
|
3885
3765
|
request2.on("timeout", () => {
|
|
3886
3766
|
request2.destroy();
|
|
3887
|
-
|
|
3767
|
+
resolve21(null);
|
|
3888
3768
|
});
|
|
3889
|
-
request2.on("error", () =>
|
|
3769
|
+
request2.on("error", () => resolve21(null));
|
|
3890
3770
|
request2.end();
|
|
3891
3771
|
});
|
|
3892
3772
|
}
|
|
3893
3773
|
function sleep2(ms) {
|
|
3894
|
-
return new Promise((
|
|
3774
|
+
return new Promise((resolve21) => setTimeout(resolve21, ms));
|
|
3895
3775
|
}
|
|
3896
3776
|
|
|
3897
3777
|
// src/shared/app-files.ts
|
|
@@ -3970,16 +3850,16 @@ function describeFilesFailure(status3, body) {
|
|
|
3970
3850
|
import * as p6 from "@clack/prompts";
|
|
3971
3851
|
import { execSync as execSync2 } from "child_process";
|
|
3972
3852
|
import { createHash as createHash2 } from "crypto";
|
|
3973
|
-
import { existsSync as
|
|
3974
|
-
import { dirname as
|
|
3853
|
+
import { existsSync as existsSync14, readFileSync as readFileSync17, readdirSync as readdirSync4 } from "fs";
|
|
3854
|
+
import { dirname as dirname6, join as join16, resolve as resolve11 } from "path";
|
|
3975
3855
|
|
|
3976
3856
|
// src/documentation/deploy.ts
|
|
3977
|
-
import { existsSync as
|
|
3857
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
|
|
3978
3858
|
import { join as join14 } from "path";
|
|
3979
3859
|
function readDocumentationDeployManifest(appDir, clientDir) {
|
|
3980
|
-
if (!
|
|
3860
|
+
if (!existsSync12(join14(appDir, "documentation.json"))) return void 0;
|
|
3981
3861
|
const manifestPath = join14(clientDir, "_documentation", "manifest.json");
|
|
3982
|
-
if (!
|
|
3862
|
+
if (!existsSync12(manifestPath)) {
|
|
3983
3863
|
throw new Error(
|
|
3984
3864
|
"documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
|
|
3985
3865
|
);
|
|
@@ -3991,7 +3871,7 @@ function readDocumentationDeployManifest(appDir, clientDir) {
|
|
|
3991
3871
|
import {
|
|
3992
3872
|
closeSync,
|
|
3993
3873
|
constants,
|
|
3994
|
-
existsSync as
|
|
3874
|
+
existsSync as existsSync13,
|
|
3995
3875
|
fstatSync,
|
|
3996
3876
|
ftruncateSync,
|
|
3997
3877
|
lstatSync,
|
|
@@ -4045,7 +3925,7 @@ var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
|
|
|
4045
3925
|
);
|
|
4046
3926
|
function readAppliedAppMigrations(appDir) {
|
|
4047
3927
|
const path = join15(appDir, APP_MIGRATIONS_MANIFEST);
|
|
4048
|
-
if (!
|
|
3928
|
+
if (!existsSync13(path)) return [];
|
|
4049
3929
|
const source = readRegularFile(path);
|
|
4050
3930
|
let parsed;
|
|
4051
3931
|
try {
|
|
@@ -4106,7 +3986,7 @@ function planAppMigrations(appDir) {
|
|
|
4106
3986
|
if (pending.length > 0 && blockers.length === 0) {
|
|
4107
3987
|
const manifestPath = `${appPrefix}${APP_MIGRATIONS_MANIFEST}`;
|
|
4108
3988
|
const absoluteManifestPath = join15(repo, manifestPath);
|
|
4109
|
-
const before =
|
|
3989
|
+
const before = existsSync13(absoluteManifestPath) ? readRegularFile(absoluteManifestPath) : null;
|
|
4110
3990
|
const after = `${JSON.stringify([...appliedIds, ...pending.map(({ id }) => id)], null, 2)}
|
|
4111
3991
|
`;
|
|
4112
3992
|
if (before !== after) edits.push({ path: manifestPath, before, after });
|
|
@@ -4122,7 +4002,7 @@ function applyAppMigrationPlan(appDir, plan) {
|
|
|
4122
4002
|
const repo = repoToplevel(appDir);
|
|
4123
4003
|
for (const edit of plan.edits) {
|
|
4124
4004
|
const absolutePath = join15(repo, edit.path);
|
|
4125
|
-
const current =
|
|
4005
|
+
const current = existsSync13(absolutePath) ? readRegularFile(absolutePath) : null;
|
|
4126
4006
|
if (current !== edit.before) {
|
|
4127
4007
|
throw new Error(`Migration input changed after planning: ${edit.path}`);
|
|
4128
4008
|
}
|
|
@@ -4251,7 +4131,7 @@ function declaredCompatibilityDate(config) {
|
|
|
4251
4131
|
return typeof declared === "string" && /^\d{4}-\d{2}-\d{2}$/.test(declared) ? declared : null;
|
|
4252
4132
|
}
|
|
4253
4133
|
async function buildDeployBundle(options) {
|
|
4254
|
-
const { appDir, appName, envName,
|
|
4134
|
+
const { appDir, appName, envName, output, spinner: spinner2 } = options;
|
|
4255
4135
|
const junk = removeMacosJunk(appDir);
|
|
4256
4136
|
if (junk > 0) p6.log.info(`Removed ${junk} macOS metadata file(s) (._*, .DS_Store)`);
|
|
4257
4137
|
const lintFindings = await lintProjectSchemas(appDir);
|
|
@@ -4261,9 +4141,7 @@ async function buildDeployBundle(options) {
|
|
|
4261
4141
|
spinner2.start("Building...");
|
|
4262
4142
|
let preparedWranglerConfig;
|
|
4263
4143
|
try {
|
|
4264
|
-
preparedWranglerConfig = prepareWranglerEnvConfig(appDir, envName
|
|
4265
|
-
sharedDevVarsCache
|
|
4266
|
-
});
|
|
4144
|
+
preparedWranglerConfig = prepareWranglerEnvConfig(appDir, envName);
|
|
4267
4145
|
const appDomain = DEEPSPACE_ENV === "staging" ? "spacestest.com" : "app.space";
|
|
4268
4146
|
execSync2("npx vite build", {
|
|
4269
4147
|
cwd: appDir,
|
|
@@ -4284,15 +4162,15 @@ async function buildDeployBundle(options) {
|
|
|
4284
4162
|
}
|
|
4285
4163
|
spinner2.stop("Built");
|
|
4286
4164
|
const deployConfigPath = join16(appDir, ".wrangler", "deploy", "config.json");
|
|
4287
|
-
if (!
|
|
4165
|
+
if (!existsSync14(deployConfigPath)) {
|
|
4288
4166
|
output.die(
|
|
4289
4167
|
"Build output config not found at .wrangler/deploy/config.json",
|
|
4290
4168
|
"build_output_missing"
|
|
4291
4169
|
);
|
|
4292
4170
|
}
|
|
4293
4171
|
const deployConfig = JSON.parse(readFileSync17(deployConfigPath, "utf-8"));
|
|
4294
|
-
const outputWranglerPath =
|
|
4295
|
-
if (!
|
|
4172
|
+
const outputWranglerPath = resolve11(dirname6(deployConfigPath), deployConfig.configPath);
|
|
4173
|
+
if (!existsSync14(outputWranglerPath)) {
|
|
4296
4174
|
output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
|
|
4297
4175
|
}
|
|
4298
4176
|
const outputConfig = JSON.parse(readFileSync17(outputWranglerPath, "utf-8"));
|
|
@@ -4303,13 +4181,13 @@ async function buildDeployBundle(options) {
|
|
|
4303
4181
|
"build_output_mismatch"
|
|
4304
4182
|
);
|
|
4305
4183
|
}
|
|
4306
|
-
const workerDir =
|
|
4184
|
+
const workerDir = dirname6(outputWranglerPath);
|
|
4307
4185
|
const workerBundlePath = join16(workerDir, outputConfig.main);
|
|
4308
|
-
const clientDir = outputConfig.assets?.directory ?
|
|
4309
|
-
if (!
|
|
4186
|
+
const clientDir = outputConfig.assets?.directory ? resolve11(workerDir, outputConfig.assets.directory) : null;
|
|
4187
|
+
if (!existsSync14(workerBundlePath)) {
|
|
4310
4188
|
output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
|
|
4311
4189
|
}
|
|
4312
|
-
if (!clientDir || !
|
|
4190
|
+
if (!clientDir || !existsSync14(clientDir)) {
|
|
4313
4191
|
return output.die(`Client assets not found at ${clientDir}`, "build_output_missing");
|
|
4314
4192
|
}
|
|
4315
4193
|
try {
|
|
@@ -4416,9 +4294,12 @@ function collectAssets(dir) {
|
|
|
4416
4294
|
}
|
|
4417
4295
|
function readDeployAssetConfig(clientDir) {
|
|
4418
4296
|
const config = {};
|
|
4419
|
-
for (const [file, key] of [
|
|
4297
|
+
for (const [file, key] of [
|
|
4298
|
+
["_headers", "_headers"],
|
|
4299
|
+
["_redirects", "_redirects"]
|
|
4300
|
+
]) {
|
|
4420
4301
|
const filePath = join16(clientDir, file);
|
|
4421
|
-
if (!
|
|
4302
|
+
if (!existsSync14(filePath)) continue;
|
|
4422
4303
|
const content = readFileSync17(filePath, "utf8");
|
|
4423
4304
|
if (content.trim()) config[key] = content;
|
|
4424
4305
|
}
|
|
@@ -4433,7 +4314,7 @@ function errorMessage(error) {
|
|
|
4433
4314
|
|
|
4434
4315
|
// src/cli/commands/deploy/commerce.ts
|
|
4435
4316
|
import * as p7 from "@clack/prompts";
|
|
4436
|
-
import { existsSync as
|
|
4317
|
+
import { existsSync as existsSync15 } from "fs";
|
|
4437
4318
|
import { join as join17 } from "path";
|
|
4438
4319
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
4439
4320
|
var API_URL2 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
@@ -4442,7 +4323,7 @@ function errorMessage2(error) {
|
|
|
4442
4323
|
}
|
|
4443
4324
|
async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
|
|
4444
4325
|
const sourcePath = join17(appDir, "src", sourceFile);
|
|
4445
|
-
if (!
|
|
4326
|
+
if (!existsSync15(sourcePath)) return void 0;
|
|
4446
4327
|
const esbuild = await import("esbuild");
|
|
4447
4328
|
const outputPath = join17(appDir, ".wrangler", "deploy", outputFile);
|
|
4448
4329
|
await esbuild.build({
|
|
@@ -4537,7 +4418,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
|
|
|
4537
4418
|
}
|
|
4538
4419
|
}
|
|
4539
4420
|
async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
4540
|
-
if (!
|
|
4421
|
+
if (!existsSync15(join17(appDir, "src", "subscriptions.ts"))) return;
|
|
4541
4422
|
let plans;
|
|
4542
4423
|
try {
|
|
4543
4424
|
plans = await loadDeclaration(
|
|
@@ -4592,7 +4473,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
|
4592
4473
|
}
|
|
4593
4474
|
}
|
|
4594
4475
|
async function syncOneTimeProducts(appDir, appName, token) {
|
|
4595
|
-
if (!
|
|
4476
|
+
if (!existsSync15(join17(appDir, "src", "products.ts"))) return;
|
|
4596
4477
|
let products;
|
|
4597
4478
|
try {
|
|
4598
4479
|
products = await loadDeclaration(
|
|
@@ -4672,19 +4553,20 @@ function createDeployOutput(json) {
|
|
|
4672
4553
|
introShown = true;
|
|
4673
4554
|
},
|
|
4674
4555
|
die(message, code, opts = {}) {
|
|
4556
|
+
const action = opts.action ? executableAction(opts.action) : void 0;
|
|
4675
4557
|
if (json) {
|
|
4676
4558
|
emitJson({
|
|
4677
4559
|
ok: false,
|
|
4678
4560
|
code,
|
|
4679
4561
|
error: message,
|
|
4680
4562
|
...opts.actionRequired ? { actionRequired: true } : {},
|
|
4681
|
-
...
|
|
4563
|
+
...action ? { action } : {}
|
|
4682
4564
|
});
|
|
4683
4565
|
} else {
|
|
4684
4566
|
if (introShown && opts.actionRequired) p8.log.warn(withSlug(message, code));
|
|
4685
4567
|
else if (introShown) p8.cancel(withSlug(message, code));
|
|
4686
4568
|
else console.error(withSlug(message, code));
|
|
4687
|
-
if (
|
|
4569
|
+
if (action) printAction(action);
|
|
4688
4570
|
}
|
|
4689
4571
|
process.exit(opts.actionRequired ? 2 : 1);
|
|
4690
4572
|
}
|
|
@@ -4884,9 +4766,9 @@ function repoApi(deployUrl, token, appId) {
|
|
|
4884
4766
|
}
|
|
4885
4767
|
|
|
4886
4768
|
// src/cli/lib/vc-remote.ts
|
|
4887
|
-
import { existsSync as
|
|
4769
|
+
import { existsSync as existsSync16 } from "fs";
|
|
4888
4770
|
import { tmpdir } from "os";
|
|
4889
|
-
import { isAbsolute as isAbsolute3, relative, resolve as
|
|
4771
|
+
import { isAbsolute as isAbsolute3, relative, resolve as resolve12 } from "path";
|
|
4890
4772
|
function spaceRemoteName(environment = DEEPSPACE_ENV) {
|
|
4891
4773
|
if (environment === "staging") return "space-staging";
|
|
4892
4774
|
if (environment === "production") return "space";
|
|
@@ -4932,14 +4814,14 @@ function entryIsTransient(cwd) {
|
|
|
4932
4814
|
const entry = process.argv[1];
|
|
4933
4815
|
if (!entry) return true;
|
|
4934
4816
|
if (/[\\/]_npx[\\/]/.test(entry) || entry.startsWith(tmpdir())) return true;
|
|
4935
|
-
const rel = relative(
|
|
4817
|
+
const rel = relative(resolve12(cwd), resolve12(entry));
|
|
4936
4818
|
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
4937
4819
|
}
|
|
4938
4820
|
function helperEntryUsable(command) {
|
|
4939
4821
|
const match = /^!(?:DEEPSPACE_ENV=(production|staging) )?'([^']*)' '([^']*)' git-credential$/.exec(command);
|
|
4940
4822
|
const expectedEnvironment = DEEPSPACE_ENV === "production" ? void 0 : DEEPSPACE_ENV;
|
|
4941
4823
|
if (!match || match[1] !== expectedEnvironment) return false;
|
|
4942
|
-
return
|
|
4824
|
+
return existsSync16(match[2]) && existsSync16(match[3]);
|
|
4943
4825
|
}
|
|
4944
4826
|
function credentialHelperCommand(environment = DEEPSPACE_ENV) {
|
|
4945
4827
|
const entry = process.argv[1];
|
|
@@ -5005,6 +4887,25 @@ function ensureSpaceRemote(cwd, appId, remote = SPACE_REMOTE) {
|
|
|
5005
4887
|
installCredentialHelper(cwd, url);
|
|
5006
4888
|
return url;
|
|
5007
4889
|
}
|
|
4890
|
+
function ensureGitIdentity(cwd, token) {
|
|
4891
|
+
try {
|
|
4892
|
+
const configured = (key) => runGit(cwd, ["config", "--get", key], { allowFail: true }).stdout.toString("utf-8").trim() !== "";
|
|
4893
|
+
const missingName = !configured("user.name");
|
|
4894
|
+
const missingEmail = !configured("user.email");
|
|
4895
|
+
if (!missingName && !missingEmail) return;
|
|
4896
|
+
const payload = decodeJwtPayload(token);
|
|
4897
|
+
if (!payload.email) return;
|
|
4898
|
+
if (missingName) {
|
|
4899
|
+
runGit(cwd, ["config", "--local", "user.name", payload.name || payload.email], {
|
|
4900
|
+
allowFail: true
|
|
4901
|
+
});
|
|
4902
|
+
}
|
|
4903
|
+
if (missingEmail) {
|
|
4904
|
+
runGit(cwd, ["config", "--local", "user.email", payload.email], { allowFail: true });
|
|
4905
|
+
}
|
|
4906
|
+
} catch {
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
5008
4909
|
function appIdFromRepoUrl(url) {
|
|
5009
4910
|
const match = /\/api\/repo\/([^/]+)\/?$/.exec(url);
|
|
5010
4911
|
return match ? decodeURIComponent(match[1]) : null;
|
|
@@ -5420,7 +5321,7 @@ async function pushWithTransientRetry(push) {
|
|
|
5420
5321
|
const message = errorMessage3(error);
|
|
5421
5322
|
const transient = /(?:HTTP |error: )(?:429|503)\b/i.test(message);
|
|
5422
5323
|
if (transient && attempt < backoffMs.length && Date.now() + backoffMs[attempt] < deadline) {
|
|
5423
|
-
await new Promise((
|
|
5324
|
+
await new Promise((resolve21) => setTimeout(resolve21, backoffMs[attempt]));
|
|
5424
5325
|
continue;
|
|
5425
5326
|
}
|
|
5426
5327
|
throw error;
|
|
@@ -5460,6 +5361,7 @@ async function deployBuiltBundle(options) {
|
|
|
5460
5361
|
appId,
|
|
5461
5362
|
appName,
|
|
5462
5363
|
token,
|
|
5364
|
+
claimReleased,
|
|
5463
5365
|
ignoreStale,
|
|
5464
5366
|
bundle,
|
|
5465
5367
|
secrets,
|
|
@@ -5477,7 +5379,8 @@ async function deployBuiltBundle(options) {
|
|
|
5477
5379
|
await uploadDeployAssets({ deployUrl, appId, token, assets: bundle.assets, output, spinner: spinner2 });
|
|
5478
5380
|
spinner2.message(`Deploying ${appName}...`);
|
|
5479
5381
|
let confirmRename = options.rename;
|
|
5480
|
-
const bail = async (message, stopLabel = "Deploy failed", code, actionRequired = false,
|
|
5382
|
+
const bail = async (message, stopLabel = "Deploy failed", code, actionRequired = false, rawAction) => {
|
|
5383
|
+
const action = rawAction ? executableAction(rawAction) : void 0;
|
|
5481
5384
|
if (stopLabel !== null) spinner2.stop(stopLabel);
|
|
5482
5385
|
p10.cancel(code ? withSlug(message, code) : message);
|
|
5483
5386
|
if (action) printAction(action);
|
|
@@ -5505,7 +5408,7 @@ async function deployBuiltBundle(options) {
|
|
|
5505
5408
|
if (bundle.customBindings.length) {
|
|
5506
5409
|
form.append("bindingManifest", JSON.stringify(bundle.customBindings));
|
|
5507
5410
|
}
|
|
5508
|
-
if (secrets.
|
|
5411
|
+
if (secrets.authoritative) form.append("userSecrets", JSON.stringify(secrets.values));
|
|
5509
5412
|
if (bundle.extraRoutes === true || bundle.extraRoutes.length) {
|
|
5510
5413
|
form.append("extraRunWorkerFirst", JSON.stringify(bundle.extraRoutes));
|
|
5511
5414
|
}
|
|
@@ -5523,6 +5426,7 @@ async function deployBuiltBundle(options) {
|
|
|
5523
5426
|
}
|
|
5524
5427
|
form.append("name", appName);
|
|
5525
5428
|
if (confirmRename) form.append("confirmRename", "true");
|
|
5429
|
+
if (claimReleased) form.append("claimReleased", "true");
|
|
5526
5430
|
if (shouldSendLineage(repository.commitOid, repository.recoverable)) {
|
|
5527
5431
|
form.append("commitOid", repository.commitOid);
|
|
5528
5432
|
}
|
|
@@ -5712,9 +5616,7 @@ async function uploadDeployAssets(options) {
|
|
|
5712
5616
|
}
|
|
5713
5617
|
};
|
|
5714
5618
|
try {
|
|
5715
|
-
await Promise.all(
|
|
5716
|
-
Array.from({ length: Math.min(UPLOAD_CONCURRENCY, missing.length) }, worker)
|
|
5717
|
-
);
|
|
5619
|
+
await Promise.all(Array.from({ length: Math.min(UPLOAD_CONCURRENCY, missing.length) }, worker));
|
|
5718
5620
|
} catch (error) {
|
|
5719
5621
|
spinner2.stop("Deploy failed");
|
|
5720
5622
|
if (error instanceof AssetUploadError) {
|
|
@@ -5774,7 +5676,7 @@ async function postWithRetry(url, makeInit, {
|
|
|
5774
5676
|
lastError = error;
|
|
5775
5677
|
}
|
|
5776
5678
|
if (attempt < attempts) {
|
|
5777
|
-
await new Promise((
|
|
5679
|
+
await new Promise((resolve21) => setTimeout(resolve21, Math.min(8e3, 500 * 2 ** (attempt - 1))));
|
|
5778
5680
|
}
|
|
5779
5681
|
}
|
|
5780
5682
|
if (lastResponse) return lastResponse;
|
|
@@ -5783,35 +5685,17 @@ async function postWithRetry(url, makeInit, {
|
|
|
5783
5685
|
|
|
5784
5686
|
// src/cli/commands/deploy/secrets.ts
|
|
5785
5687
|
import * as p11 from "@clack/prompts";
|
|
5786
|
-
import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
|
|
5787
|
-
function classifyDevVarsSecrets(options) {
|
|
5788
|
-
const store = new Set(options.storeSecretNames);
|
|
5789
|
-
const strayKeys = options.handEditedDevVarKeys.filter((key) => !store.has(key));
|
|
5790
|
-
if (strayKeys.length === 0) return { kind: "ok" };
|
|
5791
|
-
if (options.storeSecretNames.length === 0 && !options.allowMissing) {
|
|
5792
|
-
return { kind: "block", strayKeys };
|
|
5793
|
-
}
|
|
5794
|
-
return { kind: "warn", strayKeys };
|
|
5795
|
-
}
|
|
5796
5688
|
async function loadDeploySecrets(options) {
|
|
5797
5689
|
const { deployUrl, appDir, appId, envName, ownerId, token, output } = options;
|
|
5798
5690
|
let linked = null;
|
|
5799
|
-
let localOnlyDevVars = {};
|
|
5800
5691
|
try {
|
|
5801
5692
|
const refreshed = await refreshSecretsCache(deployUrl, token, appId, envName);
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
await writeDevVars(appDir, ownerId, token, envName, {
|
|
5809
|
-
appId,
|
|
5810
|
-
generatedSecretsCache: refreshed.rendered,
|
|
5811
|
-
sharedDevVarsCache: true
|
|
5812
|
-
});
|
|
5813
|
-
if (refreshed.summary) p11.log.info(refreshed.summary);
|
|
5814
|
-
}
|
|
5693
|
+
linked = refreshed.pulled;
|
|
5694
|
+
await writeDevVars(appDir, ownerId, token, envName, {
|
|
5695
|
+
appId,
|
|
5696
|
+
generatedSecretsCache: refreshed.rendered
|
|
5697
|
+
});
|
|
5698
|
+
if (refreshed.summary) p11.log.info(refreshed.summary);
|
|
5815
5699
|
} catch (error) {
|
|
5816
5700
|
if (error?.status === 403) {
|
|
5817
5701
|
output.die(
|
|
@@ -5824,40 +5708,31 @@ async function loadDeploySecrets(options) {
|
|
|
5824
5708
|
"secrets_refresh_failed"
|
|
5825
5709
|
);
|
|
5826
5710
|
}
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
function prepareDeploySecrets(options) {
|
|
5830
|
-
const { appDir, envName, cache, allowMissing, customBindings, doManifest, output } = options;
|
|
5831
|
-
const values = cache.linked ? { ...cache.linked.values } : {};
|
|
5832
|
-
const devVarsPath = devVarsPathFor(appDir, envName, {
|
|
5833
|
-
sharedDevVarsCache: cache.linked !== null
|
|
5834
|
-
});
|
|
5835
|
-
const handEditedDevVars = cache.linked ? cache.localOnlyDevVars : readCustomDevVars(devVarsPath, output);
|
|
5836
|
-
const decision = classifyDevVarsSecrets({
|
|
5837
|
-
storeSecretNames: Object.keys(values),
|
|
5838
|
-
handEditedDevVarKeys: Object.keys(handEditedDevVars),
|
|
5839
|
-
allowMissing
|
|
5840
|
-
});
|
|
5841
|
-
if (decision.kind === "block") {
|
|
5842
|
-
const uploadArgv = [
|
|
5843
|
-
"deepspace",
|
|
5844
|
-
"secrets",
|
|
5845
|
-
"upload",
|
|
5846
|
-
".dev.vars",
|
|
5847
|
-
...envName ? ["-e", envName] : []
|
|
5848
|
-
];
|
|
5711
|
+
if (!linked) {
|
|
5712
|
+
const configName = defaultConfigNameForEnv(envName);
|
|
5849
5713
|
output.die(
|
|
5850
|
-
`
|
|
5851
|
-
"
|
|
5852
|
-
{
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5714
|
+
`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.`,
|
|
5715
|
+
"secrets_config_missing",
|
|
5716
|
+
{
|
|
5717
|
+
action: {
|
|
5718
|
+
cwd: appDir,
|
|
5719
|
+
argv: [
|
|
5720
|
+
"deepspace",
|
|
5721
|
+
"secrets",
|
|
5722
|
+
"configs",
|
|
5723
|
+
"create",
|
|
5724
|
+
configName,
|
|
5725
|
+
...envName ? ["--env", envName] : []
|
|
5726
|
+
]
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
5859
5729
|
);
|
|
5860
5730
|
}
|
|
5731
|
+
return { linked };
|
|
5732
|
+
}
|
|
5733
|
+
function prepareDeploySecrets(options) {
|
|
5734
|
+
const { cache, customBindings, doManifest, output } = options;
|
|
5735
|
+
const values = cache.linked ? { ...cache.linked.values } : {};
|
|
5861
5736
|
const names = Object.keys(values);
|
|
5862
5737
|
const declaredBindingNames = /* @__PURE__ */ new Set([
|
|
5863
5738
|
...customBindings.map((binding) => binding.name),
|
|
@@ -5878,17 +5753,7 @@ function prepareDeploySecrets(options) {
|
|
|
5878
5753
|
}
|
|
5879
5754
|
}
|
|
5880
5755
|
if (names.length) p11.log.info(`App secrets: ${names.join(", ")}`);
|
|
5881
|
-
return { values, names };
|
|
5882
|
-
}
|
|
5883
|
-
function readCustomDevVars(path, output) {
|
|
5884
|
-
if (!existsSync18(path)) return {};
|
|
5885
|
-
const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync19(path, "utf-8")));
|
|
5886
|
-
if (!section) return {};
|
|
5887
|
-
try {
|
|
5888
|
-
return parseDevVars(section);
|
|
5889
|
-
} catch (error) {
|
|
5890
|
-
output.die(`.dev.vars: ${errorMessage5(error)}`, "invalid_dev_vars");
|
|
5891
|
-
}
|
|
5756
|
+
return { values, names, authoritative: cache.linked !== null };
|
|
5892
5757
|
}
|
|
5893
5758
|
function errorMessage5(error) {
|
|
5894
5759
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -5922,9 +5787,9 @@ var deploy_default = defineCommand3({
|
|
|
5922
5787
|
description: "Confirm that a changed wrangler `name` renames this app (its URL moves; data, secrets, and collaborators travel). Without this flag an interactive prompt asks.",
|
|
5923
5788
|
default: false
|
|
5924
5789
|
},
|
|
5925
|
-
"
|
|
5790
|
+
"claim-released": {
|
|
5926
5791
|
type: "boolean",
|
|
5927
|
-
description: "
|
|
5792
|
+
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.",
|
|
5928
5793
|
default: false
|
|
5929
5794
|
},
|
|
5930
5795
|
// Citty maps --no-push to the negation of an argument named `push`.
|
|
@@ -5947,7 +5812,7 @@ var deploy_default = defineCommand3({
|
|
|
5947
5812
|
async run({ args }) {
|
|
5948
5813
|
const output = createDeployOutput(args.json === true);
|
|
5949
5814
|
preflightNodeVersion("deploy");
|
|
5950
|
-
const appDir =
|
|
5815
|
+
const appDir = resolve13(args.dir ?? ".");
|
|
5951
5816
|
const selectorRefusal = blankSelectorRefusal(args);
|
|
5952
5817
|
if (selectorRefusal) output.die(selectorRefusal.error, selectorRefusal.code);
|
|
5953
5818
|
const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
@@ -6000,7 +5865,6 @@ var deploy_default = defineCommand3({
|
|
|
6000
5865
|
appDir,
|
|
6001
5866
|
appName,
|
|
6002
5867
|
envName,
|
|
6003
|
-
sharedDevVarsCache: secretsCache.linked !== null,
|
|
6004
5868
|
output,
|
|
6005
5869
|
spinner: spinner2
|
|
6006
5870
|
});
|
|
@@ -6020,10 +5884,7 @@ var deploy_default = defineCommand3({
|
|
|
6020
5884
|
sourceState
|
|
6021
5885
|
});
|
|
6022
5886
|
const secrets = prepareDeploySecrets({
|
|
6023
|
-
appDir,
|
|
6024
|
-
envName,
|
|
6025
5887
|
cache: secretsCache,
|
|
6026
|
-
allowMissing: Boolean(args["allow-missing-secrets"]),
|
|
6027
5888
|
customBindings: bundle.customBindings,
|
|
6028
5889
|
doManifest: bundle.doManifest,
|
|
6029
5890
|
output
|
|
@@ -6035,6 +5896,7 @@ var deploy_default = defineCommand3({
|
|
|
6035
5896
|
appName,
|
|
6036
5897
|
token,
|
|
6037
5898
|
rename: args.rename === true,
|
|
5899
|
+
claimReleased: args["claim-released"] === true,
|
|
6038
5900
|
ignoreStale: Boolean(args["ignore-stale"]),
|
|
6039
5901
|
bundle,
|
|
6040
5902
|
secrets,
|
|
@@ -6063,10 +5925,7 @@ var deploy_default = defineCommand3({
|
|
|
6063
5925
|
url: body.url,
|
|
6064
5926
|
releaseId: body.releaseId ?? null,
|
|
6065
5927
|
bundleRetained: body.bundleRetained ?? null,
|
|
6066
|
-
// `serving` says what was actually established; `edgePropagating`
|
|
6067
|
-
// stays for callers written against the old shape.
|
|
6068
5928
|
serving,
|
|
6069
|
-
edgePropagating: serving === "unconfirmed",
|
|
6070
5929
|
recoverable: repository.recoverable,
|
|
6071
5930
|
...staleBaseGuardFields(body)
|
|
6072
5931
|
});
|
|
@@ -6085,7 +5944,7 @@ var deploy_default = defineCommand3({
|
|
|
6085
5944
|
appName,
|
|
6086
5945
|
url: body.url ?? null,
|
|
6087
5946
|
// Always present, so a caller branches on one field instead of
|
|
6088
|
-
// inferring success from
|
|
5947
|
+
// inferring success from an absent one.
|
|
6089
5948
|
serving,
|
|
6090
5949
|
releaseId: body.releaseId ?? null,
|
|
6091
5950
|
bundleRetained: body.bundleRetained ?? null,
|
|
@@ -6765,20 +6624,20 @@ var usage_default = defineDeepspaceCommand({
|
|
|
6765
6624
|
});
|
|
6766
6625
|
|
|
6767
6626
|
// src/cli/commands/add.ts
|
|
6768
|
-
import { existsSync as
|
|
6769
|
-
import { dirname as
|
|
6627
|
+
import { existsSync as existsSync18, readFileSync as readFileSync20, realpathSync as realpathSync4 } from "fs";
|
|
6628
|
+
import { dirname as dirname8, join as join19, resolve as resolve15 } from "path";
|
|
6770
6629
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
6771
6630
|
|
|
6772
6631
|
// src/cli/commands/feature-installer.ts
|
|
6773
6632
|
import {
|
|
6774
6633
|
closeSync as closeSync2,
|
|
6775
6634
|
copyFileSync,
|
|
6776
|
-
existsSync as
|
|
6635
|
+
existsSync as existsSync17,
|
|
6777
6636
|
fstatSync as fstatSync2,
|
|
6778
6637
|
mkdirSync as mkdirSync6,
|
|
6779
6638
|
mkdtempSync,
|
|
6780
6639
|
openSync as openSync2,
|
|
6781
|
-
readFileSync as
|
|
6640
|
+
readFileSync as readFileSync19,
|
|
6782
6641
|
readSync,
|
|
6783
6642
|
readdirSync as readdirSync5,
|
|
6784
6643
|
rmSync as rmSync4,
|
|
@@ -6786,33 +6645,33 @@ import {
|
|
|
6786
6645
|
} from "fs";
|
|
6787
6646
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
6788
6647
|
import { tmpdir as tmpdir2 } from "os";
|
|
6789
|
-
import { dirname as
|
|
6648
|
+
import { dirname as dirname7, isAbsolute as isAbsolute4, join as join18, resolve as resolve14 } from "path";
|
|
6790
6649
|
import { fileURLToPath } from "url";
|
|
6791
6650
|
var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
|
|
6792
6651
|
function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
6793
|
-
let dir =
|
|
6652
|
+
let dir = dirname7(fileURLToPath(moduleUrl));
|
|
6794
6653
|
for (; ; ) {
|
|
6795
6654
|
const packagePath = join18(dir, "package.json");
|
|
6796
|
-
if (
|
|
6655
|
+
if (existsSync17(packagePath)) {
|
|
6797
6656
|
try {
|
|
6798
|
-
const pkg2 = JSON.parse(
|
|
6657
|
+
const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
|
|
6799
6658
|
if (pkg2.name === "deepspace") return dir;
|
|
6800
6659
|
} catch {
|
|
6801
6660
|
}
|
|
6802
6661
|
}
|
|
6803
|
-
const parent =
|
|
6662
|
+
const parent = dirname7(dir);
|
|
6804
6663
|
if (parent === dir) throw new Error("Could not locate the deepspace package root");
|
|
6805
6664
|
dir = parent;
|
|
6806
6665
|
}
|
|
6807
6666
|
}
|
|
6808
6667
|
function hasFeatureCatalog(packageRoot = featurePackageRoot()) {
|
|
6809
|
-
return
|
|
6668
|
+
return existsSync17(join18(packageRoot, "features"));
|
|
6810
6669
|
}
|
|
6811
6670
|
function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
6812
6671
|
const configPath = join18(packageRoot, "features", id, "feature.json");
|
|
6813
|
-
if (!
|
|
6672
|
+
if (!existsSync17(configPath)) return null;
|
|
6814
6673
|
try {
|
|
6815
|
-
const config = JSON.parse(
|
|
6674
|
+
const config = JSON.parse(readFileSync19(configPath, "utf-8"));
|
|
6816
6675
|
if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
|
|
6817
6676
|
return null;
|
|
6818
6677
|
}
|
|
@@ -6823,7 +6682,7 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
|
6823
6682
|
}
|
|
6824
6683
|
function listFeatureConfigs(packageRoot = featurePackageRoot()) {
|
|
6825
6684
|
const featuresDir = join18(packageRoot, "features");
|
|
6826
|
-
if (!
|
|
6685
|
+
if (!existsSync17(featuresDir)) return [];
|
|
6827
6686
|
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));
|
|
6828
6687
|
}
|
|
6829
6688
|
function featureCatalog(packageRoot = featurePackageRoot()) {
|
|
@@ -6861,7 +6720,7 @@ function suggestFeature(featureId, packageRoot = featurePackageRoot()) {
|
|
|
6861
6720
|
return closest !== null && closestDistance <= threshold ? closest : null;
|
|
6862
6721
|
}
|
|
6863
6722
|
function resolveTargetDir(raw) {
|
|
6864
|
-
return isAbsolute4(raw) ? raw :
|
|
6723
|
+
return isAbsolute4(raw) ? raw : resolve14(raw);
|
|
6865
6724
|
}
|
|
6866
6725
|
function resolveFeatureDestPath(fileDest, route) {
|
|
6867
6726
|
const pages = "src/pages/";
|
|
@@ -6871,13 +6730,13 @@ function resolveFeatureDestPath(fileDest, route) {
|
|
|
6871
6730
|
return route?.protected === false ? `${appPages}${rest}` : `${appPages}(protected)/${rest}`;
|
|
6872
6731
|
}
|
|
6873
6732
|
function copyFile(src, dest) {
|
|
6874
|
-
mkdirSync6(
|
|
6733
|
+
mkdirSync6(dirname7(dest), { recursive: true });
|
|
6875
6734
|
copyFileSync(src, dest);
|
|
6876
6735
|
}
|
|
6877
6736
|
function copyFileWithImportShift(src, dest, levels) {
|
|
6878
|
-
mkdirSync6(
|
|
6737
|
+
mkdirSync6(dirname7(dest), { recursive: true });
|
|
6879
6738
|
const prefix = "../".repeat(levels);
|
|
6880
|
-
const shifted =
|
|
6739
|
+
const shifted = readFileSync19(src, "utf-8").replace(
|
|
6881
6740
|
/(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
|
|
6882
6741
|
`$1$2${prefix}$3`
|
|
6883
6742
|
);
|
|
@@ -6893,9 +6752,9 @@ function installFiles(packageRoot, config, targetDir, emit) {
|
|
|
6893
6752
|
const srcPath = join18(packageRoot, "features", config.id, file.src);
|
|
6894
6753
|
const resolvedDest = resolveFeatureDestPath(file.dest, config.route);
|
|
6895
6754
|
const destPath = join18(targetDir, resolvedDest);
|
|
6896
|
-
const existed =
|
|
6755
|
+
const existed = existsSync17(destPath);
|
|
6897
6756
|
if (existed) preexisting.push(resolvedDest);
|
|
6898
|
-
if (!
|
|
6757
|
+
if (!existsSync17(srcPath)) {
|
|
6899
6758
|
emit(` Warning: source not found: ${file.src}`, "stderr");
|
|
6900
6759
|
if (!existed) missing.push(resolvedDest);
|
|
6901
6760
|
continue;
|
|
@@ -6933,10 +6792,10 @@ function unresolvedSchema(schema, reason, emit) {
|
|
|
6933
6792
|
function integrateSchema(config, targetDir, emit) {
|
|
6934
6793
|
if (!config.schema) return [];
|
|
6935
6794
|
const schemasPath = join18(targetDir, "src", "schemas.ts");
|
|
6936
|
-
if (!
|
|
6795
|
+
if (!existsSync17(schemasPath)) {
|
|
6937
6796
|
return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
|
|
6938
6797
|
}
|
|
6939
|
-
let content =
|
|
6798
|
+
let content = readFileSync19(schemasPath, "utf-8");
|
|
6940
6799
|
const { exportName, importPath, spreadOperator } = config.schema;
|
|
6941
6800
|
const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
|
|
6942
6801
|
if (importPattern.test(content)) {
|
|
@@ -6990,10 +6849,10 @@ function unresolvedActions(actions, reason, emit) {
|
|
|
6990
6849
|
function integrateActions(config, targetDir, emit) {
|
|
6991
6850
|
if (!config.actions?.length) return [];
|
|
6992
6851
|
const actionsPath = join18(targetDir, "src", "actions", "index.ts");
|
|
6993
|
-
if (!
|
|
6852
|
+
if (!existsSync17(actionsPath)) {
|
|
6994
6853
|
return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
|
|
6995
6854
|
}
|
|
6996
|
-
let content =
|
|
6855
|
+
let content = readFileSync19(actionsPath, "utf-8");
|
|
6997
6856
|
const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
|
|
6998
6857
|
if (!registryPattern.test(content)) {
|
|
6999
6858
|
const reason = "src/actions/index.ts has an unexpected structure.";
|
|
@@ -7040,7 +6899,7 @@ function integrateCode(config, targetDir, emit) {
|
|
|
7040
6899
|
const unresolved = [];
|
|
7041
6900
|
for (const insertion of config.code ?? []) {
|
|
7042
6901
|
const targetPath = join18(targetDir, insertion.file);
|
|
7043
|
-
if (!
|
|
6902
|
+
if (!existsSync17(targetPath)) {
|
|
7044
6903
|
unresolved.push({
|
|
7045
6904
|
area: "code",
|
|
7046
6905
|
reason: `${insertion.file} was not found.`,
|
|
@@ -7048,7 +6907,7 @@ function integrateCode(config, targetDir, emit) {
|
|
|
7048
6907
|
});
|
|
7049
6908
|
continue;
|
|
7050
6909
|
}
|
|
7051
|
-
const original =
|
|
6910
|
+
const original = readFileSync19(targetPath, "utf-8");
|
|
7052
6911
|
if (original.includes(insertion.insert.trim())) {
|
|
7053
6912
|
emit(` Integration already present: ${insertion.file}`);
|
|
7054
6913
|
continue;
|
|
@@ -7084,7 +6943,7 @@ ${content.slice(importIndex < 0 ? 0 : importIndex)}`;
|
|
|
7084
6943
|
function integrateIgnore(config, targetDir) {
|
|
7085
6944
|
if (!config.ignore?.length) return;
|
|
7086
6945
|
const targetPath = join18(targetDir, ".gitignore");
|
|
7087
|
-
const current =
|
|
6946
|
+
const current = existsSync17(targetPath) ? readFileSync19(targetPath, "utf-8") : "";
|
|
7088
6947
|
const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
|
|
7089
6948
|
const missing = config.ignore.filter((pattern) => !present.has(pattern));
|
|
7090
6949
|
if (missing.length === 0) return;
|
|
@@ -7101,10 +6960,10 @@ function integrateWrangler(config, targetDir, emit) {
|
|
|
7101
6960
|
...runWorkerFirst ? ["Set assets.run_worker_first = true in wrangler.toml."] : [],
|
|
7102
6961
|
...rateLimits.map((binding) => `Add the ${binding.name} rate-limit binding to wrangler.toml.`)
|
|
7103
6962
|
];
|
|
7104
|
-
if (!
|
|
6963
|
+
if (!existsSync17(targetPath)) {
|
|
7105
6964
|
return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
|
|
7106
6965
|
}
|
|
7107
|
-
let source =
|
|
6966
|
+
let source = readFileSync19(targetPath, "utf-8");
|
|
7108
6967
|
if (runWorkerFirst && !/^\[assets\]\s*$/m.test(source)) {
|
|
7109
6968
|
return [{ area: "wrangler", reason: "wrangler.toml has no [assets] section.", steps }];
|
|
7110
6969
|
}
|
|
@@ -7181,7 +7040,7 @@ function rateLimitBindingBlock(scope, binding) {
|
|
|
7181
7040
|
function integrateCss(packageRoot, config, targetDir, emit) {
|
|
7182
7041
|
if (!config.css?.length) return [];
|
|
7183
7042
|
const stylesPath = join18(targetDir, "src", "styles.css");
|
|
7184
|
-
if (!
|
|
7043
|
+
if (!existsSync17(stylesPath)) {
|
|
7185
7044
|
emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
|
|
7186
7045
|
return [
|
|
7187
7046
|
{
|
|
@@ -7193,12 +7052,12 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
7193
7052
|
}
|
|
7194
7053
|
];
|
|
7195
7054
|
}
|
|
7196
|
-
let styles =
|
|
7055
|
+
let styles = readFileSync19(stylesPath, "utf-8");
|
|
7197
7056
|
let integrated = 0;
|
|
7198
7057
|
const unresolved = [];
|
|
7199
7058
|
for (const cssFile of config.css) {
|
|
7200
7059
|
const srcPath = join18(packageRoot, "features", config.id, cssFile);
|
|
7201
|
-
if (!
|
|
7060
|
+
if (!existsSync17(srcPath)) {
|
|
7202
7061
|
emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
|
|
7203
7062
|
unresolved.push({
|
|
7204
7063
|
area: "css",
|
|
@@ -7207,7 +7066,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
7207
7066
|
});
|
|
7208
7067
|
continue;
|
|
7209
7068
|
}
|
|
7210
|
-
const css =
|
|
7069
|
+
const css = readFileSync19(srcPath, "utf-8");
|
|
7211
7070
|
const fingerprint = css.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/);
|
|
7212
7071
|
const marker = fingerprint?.[1] ?? fingerprint?.[2];
|
|
7213
7072
|
if (marker && styles.includes(marker)) {
|
|
@@ -7235,10 +7094,10 @@ function unresolvedRoute(route, config, reason, emit) {
|
|
|
7235
7094
|
function integrateRoute(config, targetDir, emit) {
|
|
7236
7095
|
if (!config.route) return [];
|
|
7237
7096
|
const navPath = join18(targetDir, "src", "nav.ts");
|
|
7238
|
-
if (!
|
|
7097
|
+
if (!existsSync17(navPath)) {
|
|
7239
7098
|
return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
|
|
7240
7099
|
}
|
|
7241
|
-
let content =
|
|
7100
|
+
let content = readFileSync19(navPath, "utf-8");
|
|
7242
7101
|
const routePath = config.route.path;
|
|
7243
7102
|
if (content.includes(`'${routePath}'`)) {
|
|
7244
7103
|
emit(` Nav already present: ${routePath}`);
|
|
@@ -7313,13 +7172,13 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
|
|
|
7313
7172
|
return { added: [], installRan: false, packageManager };
|
|
7314
7173
|
}
|
|
7315
7174
|
const packagePath = join18(targetDir, "package.json");
|
|
7316
|
-
if (!
|
|
7175
|
+
if (!existsSync17(packagePath)) {
|
|
7317
7176
|
emit(" Could not patch package.json (file not found)");
|
|
7318
7177
|
return { added: [], installRan: false, packageManager };
|
|
7319
7178
|
}
|
|
7320
7179
|
let pkg2;
|
|
7321
7180
|
try {
|
|
7322
|
-
pkg2 = JSON.parse(
|
|
7181
|
+
pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
|
|
7323
7182
|
} catch (error) {
|
|
7324
7183
|
emit(
|
|
7325
7184
|
` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
|
|
@@ -7379,7 +7238,7 @@ ${child.diagnosticTail}` : "";
|
|
|
7379
7238
|
}
|
|
7380
7239
|
function dependencySnapshot(targetDir) {
|
|
7381
7240
|
try {
|
|
7382
|
-
const pkg2 = JSON.parse(
|
|
7241
|
+
const pkg2 = JSON.parse(readFileSync19(join18(targetDir, "package.json"), "utf-8"));
|
|
7383
7242
|
return {
|
|
7384
7243
|
dependencies: pkg2.dependencies ?? {},
|
|
7385
7244
|
devDependencies: pkg2.devDependencies ?? {}
|
|
@@ -7391,7 +7250,7 @@ function dependencySnapshot(targetDir) {
|
|
|
7391
7250
|
function installedNavFeatures(packageRoot, targetDir) {
|
|
7392
7251
|
return listFeatureConfigs(packageRoot).filter((feature) => feature.category === "nav" && feature.files.length > 0).filter((feature) => {
|
|
7393
7252
|
const signature = resolveFeatureDestPath(feature.files[0].dest, feature.route);
|
|
7394
|
-
return
|
|
7253
|
+
return existsSync17(join18(targetDir, signature));
|
|
7395
7254
|
}).map((feature) => feature.id);
|
|
7396
7255
|
}
|
|
7397
7256
|
function warnNavConflicts(packageRoot, config, targetDir, emit) {
|
|
@@ -7624,11 +7483,11 @@ function renderFeatureInfo(config) {
|
|
|
7624
7483
|
return lines.join("\n");
|
|
7625
7484
|
}
|
|
7626
7485
|
function isDeepSpaceApp(dir) {
|
|
7627
|
-
if (
|
|
7486
|
+
if (existsSync18(join19(dir, "wrangler.toml"))) return true;
|
|
7628
7487
|
const packagePath = join19(dir, "package.json");
|
|
7629
|
-
if (!
|
|
7488
|
+
if (!existsSync18(packagePath)) return false;
|
|
7630
7489
|
try {
|
|
7631
|
-
const pkg2 = JSON.parse(
|
|
7490
|
+
const pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
|
|
7632
7491
|
return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
|
|
7633
7492
|
} catch {
|
|
7634
7493
|
return false;
|
|
@@ -7638,8 +7497,8 @@ function installedPackageRoot(fromDir) {
|
|
|
7638
7497
|
let dir = fromDir;
|
|
7639
7498
|
for (; ; ) {
|
|
7640
7499
|
const candidate = join19(dir, "node_modules", "deepspace");
|
|
7641
|
-
if (
|
|
7642
|
-
const parent =
|
|
7500
|
+
if (existsSync18(join19(candidate, "package.json"))) return candidate;
|
|
7501
|
+
const parent = dirname8(dir);
|
|
7643
7502
|
if (parent === dir) return null;
|
|
7644
7503
|
dir = parent;
|
|
7645
7504
|
}
|
|
@@ -7652,12 +7511,12 @@ async function targetInstaller(appDir) {
|
|
|
7652
7511
|
"feature_installer_missing"
|
|
7653
7512
|
);
|
|
7654
7513
|
}
|
|
7655
|
-
if (
|
|
7514
|
+
if (realpathSync4(installedRoot) === realpathSync4(featurePackageRoot())) {
|
|
7656
7515
|
return { installFeature };
|
|
7657
7516
|
}
|
|
7658
7517
|
const entry = join19(installedRoot, "dist", "feature-installer.js");
|
|
7659
7518
|
const catalog = join19(installedRoot, "features");
|
|
7660
|
-
if (!
|
|
7519
|
+
if (!existsSync18(entry) || !existsSync18(catalog)) {
|
|
7661
7520
|
throw new Refusal(
|
|
7662
7521
|
`The app's installed deepspace package does not contain the typed feature installer.
|
|
7663
7522
|
Expected: ${entry}
|
|
@@ -7749,8 +7608,8 @@ var add_default = defineDeepspaceCommand({
|
|
|
7749
7608
|
}
|
|
7750
7609
|
return { data: { features: featureCatalog(root) } };
|
|
7751
7610
|
}
|
|
7752
|
-
const appDir =
|
|
7753
|
-
if (!
|
|
7611
|
+
const appDir = resolve15(args.dir ?? ".");
|
|
7612
|
+
if (!existsSync18(appDir)) {
|
|
7754
7613
|
throw new Refusal(
|
|
7755
7614
|
`Target directory not found: ${appDir}
|
|
7756
7615
|
Create a new app first: deepspace app create <name>`,
|
|
@@ -7831,11 +7690,11 @@ function fmtDate(iso) {
|
|
|
7831
7690
|
}
|
|
7832
7691
|
async function confirm4(message) {
|
|
7833
7692
|
process.stdout.write(`${message} [y/N] `);
|
|
7834
|
-
return new Promise((
|
|
7693
|
+
return new Promise((resolve21) => {
|
|
7835
7694
|
process.stdin.setEncoding("utf-8");
|
|
7836
7695
|
process.stdin.once("data", (data) => {
|
|
7837
7696
|
const answer = data.toString().trim().toLowerCase();
|
|
7838
|
-
|
|
7697
|
+
resolve21(answer === "y" || answer === "yes");
|
|
7839
7698
|
});
|
|
7840
7699
|
});
|
|
7841
7700
|
}
|
|
@@ -8125,8 +7984,8 @@ var domain_default = defineCommand5({
|
|
|
8125
7984
|
|
|
8126
7985
|
// src/cli/commands/app-files.ts
|
|
8127
7986
|
import { defineCommand as defineCommand6 } from "citty";
|
|
8128
|
-
import { basename as basename4, resolve as
|
|
8129
|
-
import { existsSync as
|
|
7987
|
+
import { basename as basename4, resolve as resolve16 } from "path";
|
|
7988
|
+
import { existsSync as existsSync19, statSync as statSync2 } from "fs";
|
|
8130
7989
|
|
|
8131
7990
|
// src/cli/lib/app-files-api.ts
|
|
8132
7991
|
import { createWriteStream, openAsBlob, statSync } from "fs";
|
|
@@ -8326,7 +8185,7 @@ async function deleteAppFile(baseUrl, token, appId, key) {
|
|
|
8326
8185
|
const path = filesPath(appId, `/${encodeKeyPath(key)}`);
|
|
8327
8186
|
const res = await request(baseUrl, token, path, { method: "DELETE" });
|
|
8328
8187
|
const body = await readJson(res, path);
|
|
8329
|
-
return { existed: body.existed
|
|
8188
|
+
return { existed: body.existed };
|
|
8330
8189
|
}
|
|
8331
8190
|
async function downloadAppFile(baseUrl, token, appId, key, destination) {
|
|
8332
8191
|
const path = filesPath(appId, `/${encodeKeyPath(key)}`);
|
|
@@ -8396,8 +8255,8 @@ var put = defineDeepspaceCommand({
|
|
|
8396
8255
|
app: { type: "string", description: "App id or live name (defaults to ./wrangler.toml)" }
|
|
8397
8256
|
},
|
|
8398
8257
|
async run({ args }) {
|
|
8399
|
-
const localPath =
|
|
8400
|
-
if (!
|
|
8258
|
+
const localPath = resolve16(String(args.file));
|
|
8259
|
+
if (!existsSync19(localPath) || !statSync2(localPath).isFile()) {
|
|
8401
8260
|
throw new Refusal(`No such file: ${args.file}`, "file_not_found");
|
|
8402
8261
|
}
|
|
8403
8262
|
const key = requireKey(args.key ?? basename4(localPath));
|
|
@@ -8497,7 +8356,7 @@ var get = defineDeepspaceCommand({
|
|
|
8497
8356
|
},
|
|
8498
8357
|
async run({ args }) {
|
|
8499
8358
|
const key = requireKey(String(args.key));
|
|
8500
|
-
const destination =
|
|
8359
|
+
const destination = resolve16(String(args.out ?? basename4(key)));
|
|
8501
8360
|
const { token, appId } = await target(args.app);
|
|
8502
8361
|
const result = await downloadAppFile(PLATFORM_URL, token, appId, key, destination);
|
|
8503
8362
|
if (!args.json) console.log(`\u2713 ${key} \u2192 ${destination} (${formatBytes(result.bytes)})`);
|
|
@@ -8867,7 +8726,7 @@ var transfer_default = defineCommand8({
|
|
|
8867
8726
|
import { defineCommand as defineCommand9 } from "citty";
|
|
8868
8727
|
|
|
8869
8728
|
// src/cli/commands/_invoke-impl.ts
|
|
8870
|
-
import { readFileSync as
|
|
8729
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
8871
8730
|
import * as p15 from "@clack/prompts";
|
|
8872
8731
|
var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
8873
8732
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -8908,7 +8767,7 @@ function resolveBody(opts) {
|
|
|
8908
8767
|
if (opts.body != null) {
|
|
8909
8768
|
raw = opts.body;
|
|
8910
8769
|
} else if (opts.bodyFile != null) {
|
|
8911
|
-
raw = opts.bodyFile === "-" ?
|
|
8770
|
+
raw = opts.bodyFile === "-" ? readFileSync21(0, "utf-8") : readFileSync21(opts.bodyFile, "utf-8");
|
|
8912
8771
|
} else {
|
|
8913
8772
|
return "{}";
|
|
8914
8773
|
}
|
|
@@ -9187,8 +9046,8 @@ var integrations_default = defineCommand9({
|
|
|
9187
9046
|
|
|
9188
9047
|
// src/cli/commands/feedback.ts
|
|
9189
9048
|
import * as p16 from "@clack/prompts";
|
|
9190
|
-
import { existsSync as
|
|
9191
|
-
import { dirname as
|
|
9049
|
+
import { existsSync as existsSync20, readFileSync as readFileSync22 } from "fs";
|
|
9050
|
+
import { dirname as dirname9, join as join20 } from "path";
|
|
9192
9051
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9193
9052
|
import { release } from "os";
|
|
9194
9053
|
var API_URL8 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
@@ -9209,14 +9068,14 @@ function normalizeType(raw) {
|
|
|
9209
9068
|
}
|
|
9210
9069
|
function readCliVersion() {
|
|
9211
9070
|
try {
|
|
9212
|
-
const here =
|
|
9071
|
+
const here = dirname9(fileURLToPath2(import.meta.url));
|
|
9213
9072
|
const candidates = [
|
|
9214
9073
|
join20(here, "..", "package.json"),
|
|
9215
9074
|
join20(here, "..", "..", "..", "package.json")
|
|
9216
9075
|
];
|
|
9217
9076
|
for (const path of candidates) {
|
|
9218
|
-
if (!
|
|
9219
|
-
const pkg2 = JSON.parse(
|
|
9077
|
+
if (!existsSync20(path)) continue;
|
|
9078
|
+
const pkg2 = JSON.parse(readFileSync22(path, "utf-8"));
|
|
9220
9079
|
if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
|
|
9221
9080
|
}
|
|
9222
9081
|
} catch {
|
|
@@ -9412,7 +9271,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
|
|
|
9412
9271
|
|
|
9413
9272
|
// src/cli/commands/secrets.ts
|
|
9414
9273
|
import { defineCommand as defineCommand10 } from "citty";
|
|
9415
|
-
import { readFileSync as
|
|
9274
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
9416
9275
|
|
|
9417
9276
|
// src/cli/lib/citty-args.ts
|
|
9418
9277
|
function dedupePositionals(bound, rest) {
|
|
@@ -9682,7 +9541,7 @@ var upload = defineCommand10({
|
|
|
9682
9541
|
async run({ args }) {
|
|
9683
9542
|
try {
|
|
9684
9543
|
const t = await resolveTarget(args);
|
|
9685
|
-
const content = args.file === "-" ?
|
|
9544
|
+
const content = args.file === "-" ? readFileSync23(0, "utf-8") : readFileSync23(args.file, "utf-8");
|
|
9686
9545
|
const secrets = parseSecretsUpload(content);
|
|
9687
9546
|
if (Object.keys(secrets).length === 0)
|
|
9688
9547
|
fail(new InputError("No secrets found in the input.", "empty_input"));
|
|
@@ -9746,27 +9605,24 @@ var pull = defineCommand10({
|
|
|
9746
9605
|
);
|
|
9747
9606
|
const t = await resolveTarget(args);
|
|
9748
9607
|
const ownerId = decodeJwtPayload(t.token).sub;
|
|
9749
|
-
const
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
)
|
|
9757
|
-
);
|
|
9758
|
-
return;
|
|
9759
|
-
}
|
|
9608
|
+
const refreshed = await refreshSecretsCache(
|
|
9609
|
+
DEPLOY_URL11,
|
|
9610
|
+
t.token,
|
|
9611
|
+
t.appId,
|
|
9612
|
+
wranglerEnv,
|
|
9613
|
+
t.configName
|
|
9614
|
+
);
|
|
9760
9615
|
await writeDevVars(appDir, ownerId, t.token, wranglerEnv, {
|
|
9761
9616
|
appId: t.appId,
|
|
9762
|
-
generatedSecretsCache:
|
|
9763
|
-
sharedDevVarsCache: true
|
|
9617
|
+
generatedSecretsCache: refreshed.rendered
|
|
9764
9618
|
});
|
|
9765
|
-
const count = Object.keys(pulled
|
|
9619
|
+
const count = Object.keys(refreshed.pulled?.values ?? {}).length;
|
|
9766
9620
|
ok(
|
|
9767
9621
|
args.json === true,
|
|
9768
9622
|
{ appId: t.appId, config: t.configName, pulled: count },
|
|
9769
|
-
() => console.log(
|
|
9623
|
+
() => console.log(
|
|
9624
|
+
refreshed.pulled ? `Pulled ${count} secrets (${t.configName}) into .dev.vars.` : `Config ${t.configName} does not exist yet; regenerated .dev.vars without app secrets.`
|
|
9625
|
+
)
|
|
9770
9626
|
);
|
|
9771
9627
|
} catch (err) {
|
|
9772
9628
|
fail(err);
|
|
@@ -9938,7 +9794,7 @@ var init_default = defineDeepspaceCommand({
|
|
|
9938
9794
|
// src/cli/commands/status.ts
|
|
9939
9795
|
import { defineCommand as defineCommand11 } from "citty";
|
|
9940
9796
|
import * as p17 from "@clack/prompts";
|
|
9941
|
-
import { existsSync as
|
|
9797
|
+
import { existsSync as existsSync21, readFileSync as readFileSync24 } from "fs";
|
|
9942
9798
|
function timeAgo(iso) {
|
|
9943
9799
|
const ms = Date.now() - Date.parse(iso);
|
|
9944
9800
|
if (!Number.isFinite(ms) || ms < 0) return iso;
|
|
@@ -10023,7 +9879,7 @@ var status_default = defineCommand11({
|
|
|
10023
9879
|
json.services = urls;
|
|
10024
9880
|
if (urlOverrides.length > 0) json.urlOverrides = urlOverrides;
|
|
10025
9881
|
}
|
|
10026
|
-
facts.loggedIn =
|
|
9882
|
+
facts.loggedIn = existsSync21(SESSION_PATH);
|
|
10027
9883
|
let userId = null;
|
|
10028
9884
|
if (!facts.loggedIn) {
|
|
10029
9885
|
lines.push(["Session", "not logged in"]);
|
|
@@ -10032,7 +9888,7 @@ var status_default = defineCommand11({
|
|
|
10032
9888
|
let who = "logged in";
|
|
10033
9889
|
try {
|
|
10034
9890
|
const payload = decodeJwtPayload(
|
|
10035
|
-
|
|
9891
|
+
readFileSync24(TOKEN_PATH, "utf-8").trim()
|
|
10036
9892
|
);
|
|
10037
9893
|
if (payload.email) who = payload.email;
|
|
10038
9894
|
userId = payload.sub ?? null;
|
|
@@ -10184,21 +10040,24 @@ var status_default = defineCommand11({
|
|
|
10184
10040
|
}
|
|
10185
10041
|
const refs = refsResult.ok ? refsResult.value : void 0;
|
|
10186
10042
|
const trunkRef = refs?.refs.find((r) => `refs/heads/${branch}` === r.name);
|
|
10043
|
+
const defaultBranch = refs?.head && refs.head.startsWith("refs/heads/") ? refs.head.slice("refs/heads/".length) : null;
|
|
10187
10044
|
if (!facts.workspaceId && branch) {
|
|
10045
|
+
const isTrunk = defaultBranch === null ? null : branch === defaultBranch;
|
|
10046
|
+
const label = isTrunk === false ? "Branch sync" : "Trunk";
|
|
10188
10047
|
if (githubSource) {
|
|
10189
|
-
lines.push([
|
|
10190
|
-
json.trunk = { state: "external", provider: "github", branch };
|
|
10191
|
-
} else if (!refsResult.ok) reportRemoteFailure2(
|
|
10048
|
+
lines.push([label, `${branch} is owned through normal Git/GitHub`]);
|
|
10049
|
+
json.trunk = { state: "external", provider: "github", branch, isTrunk };
|
|
10050
|
+
} else if (!refsResult.ok) reportRemoteFailure2(label, "trunk", refsResult.error);
|
|
10192
10051
|
else {
|
|
10193
10052
|
const remoteOid = trunkRef?.oid ?? null;
|
|
10194
10053
|
const localOid = resolveCommit(appDir, `refs/heads/${branch}`);
|
|
10195
10054
|
const remoteKnownLocally = remoteOid ? resolveCommit(appDir, remoteOid) !== null : false;
|
|
10196
10055
|
facts.trunkState = remoteOid === null && localOid === null ? "unknown" : remoteOid === localOid ? "in_sync" : remoteOid && localOid && remoteKnownLocally && isAncestor(appDir, remoteOid, localOid) ? "local_ahead" : remoteOid && localOid && remoteKnownLocally && isAncestor(appDir, localOid, remoteOid) ? "local_behind" : remoteOid && localOid && remoteKnownLocally ? "diverged" : "differs";
|
|
10197
10056
|
lines.push([
|
|
10198
|
-
|
|
10057
|
+
label,
|
|
10199
10058
|
remoteOid === null ? `cloud repo has no ${branch} yet` : facts.trunkState === "in_sync" ? `${branch} in sync with the cloud repo (${remoteOid.slice(0, 10)})` : `${branch} ${facts.trunkState.replace("_", " ")} relative to the cloud repo (local ${localOid?.slice(0, 10) ?? "none"}, cloud ${remoteOid.slice(0, 10)})`
|
|
10200
10059
|
]);
|
|
10201
|
-
json.trunk = { state: facts.trunkState, localOid, remoteOid };
|
|
10060
|
+
json.trunk = { state: facts.trunkState, localOid, remoteOid, branch, isTrunk };
|
|
10202
10061
|
}
|
|
10203
10062
|
}
|
|
10204
10063
|
if (!releaseResult.ok) reportRemoteFailure2("Live", "live", releaseResult.error);
|
|
@@ -10425,7 +10284,7 @@ var push_default = defineDeepspaceCommand({
|
|
|
10425
10284
|
|
|
10426
10285
|
// src/cli/commands/pull.ts
|
|
10427
10286
|
import * as p19 from "@clack/prompts";
|
|
10428
|
-
import { realpathSync as
|
|
10287
|
+
import { realpathSync as realpathSync5 } from "fs";
|
|
10429
10288
|
function targetedVcAction(command, cwd, appId, branch) {
|
|
10430
10289
|
return {
|
|
10431
10290
|
cwd,
|
|
@@ -10449,7 +10308,7 @@ function workspaceBranchPullRefusal(branch, trunkBranch) {
|
|
|
10449
10308
|
function worktreeHoldingBranch(worktrees, branch, selfPath) {
|
|
10450
10309
|
const real = (pth) => {
|
|
10451
10310
|
try {
|
|
10452
|
-
return
|
|
10311
|
+
return realpathSync5(pth);
|
|
10453
10312
|
} catch {
|
|
10454
10313
|
return pth;
|
|
10455
10314
|
}
|
|
@@ -10578,12 +10437,7 @@ var pull_default = defineDeepspaceCommand({
|
|
|
10578
10437
|
}
|
|
10579
10438
|
spinner2?.message(`Fetching ${branch} from the cloud repo\u2026`);
|
|
10580
10439
|
const trackingRef = spaceTrackingRef(branch);
|
|
10581
|
-
runGitRemote(appDir, token, [
|
|
10582
|
-
"fetch",
|
|
10583
|
-
"--quiet",
|
|
10584
|
-
SPACE_REMOTE,
|
|
10585
|
-
`+${refName}:${trackingRef}`
|
|
10586
|
-
]);
|
|
10440
|
+
runGitRemote(appDir, token, ["fetch", "--quiet", SPACE_REMOTE, `+${refName}:${trackingRef}`]);
|
|
10587
10441
|
const remoteOid = resolveCommit(appDir, trackingRef);
|
|
10588
10442
|
if (!remoteOid) {
|
|
10589
10443
|
spinner2?.stop("Fetch failed.");
|
|
@@ -10621,6 +10475,8 @@ var pull_default = defineDeepspaceCommand({
|
|
|
10621
10475
|
integration = "fast_forwarded";
|
|
10622
10476
|
}
|
|
10623
10477
|
}
|
|
10478
|
+
} else if (isAncestor(appDir, remoteOid, localOid)) {
|
|
10479
|
+
integration = "local_ahead";
|
|
10624
10480
|
} else {
|
|
10625
10481
|
integration = "fetched_only_diverged";
|
|
10626
10482
|
}
|
|
@@ -10634,10 +10490,15 @@ var pull_default = defineDeepspaceCommand({
|
|
|
10634
10490
|
let line;
|
|
10635
10491
|
let codeForState;
|
|
10636
10492
|
let recoveryAction;
|
|
10493
|
+
let successAction;
|
|
10637
10494
|
switch (integration) {
|
|
10638
10495
|
case "up_to_date":
|
|
10639
10496
|
line = `${branch} is already up to date.`;
|
|
10640
10497
|
break;
|
|
10498
|
+
case "local_ahead":
|
|
10499
|
+
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}\`.`;
|
|
10500
|
+
successAction = targetedVcAction("push", appDir, appId, branch);
|
|
10501
|
+
break;
|
|
10641
10502
|
case "fast_forwarded":
|
|
10642
10503
|
line = `Fast-forwarded ${branch} to ${remoteOid.slice(0, 10)}.`;
|
|
10643
10504
|
break;
|
|
@@ -10686,7 +10547,8 @@ var pull_default = defineDeepspaceCommand({
|
|
|
10686
10547
|
}
|
|
10687
10548
|
const actionRequired = recoveryAction !== void 0;
|
|
10688
10549
|
spinner2?.stop(codeForState ? `Fetched ${SPACE_REMOTE}/${branch}.` : line);
|
|
10689
|
-
if (!args.json)
|
|
10550
|
+
if (!args.json)
|
|
10551
|
+
p19.log.info(`App: ${appId} \xB7 ${SPACE_REMOTE}/${branch} at ${remoteOid.slice(0, 10)}`);
|
|
10690
10552
|
if (codeForState) {
|
|
10691
10553
|
throw new Refusal(line, codeForState, {
|
|
10692
10554
|
actionRequired,
|
|
@@ -10700,20 +10562,20 @@ var pull_default = defineDeepspaceCommand({
|
|
|
10700
10562
|
}
|
|
10701
10563
|
});
|
|
10702
10564
|
}
|
|
10703
|
-
return { data: { status: integration, appId, branch, remoteOid } };
|
|
10565
|
+
return { data: { status: integration, appId, branch, remoteOid }, action: successAction };
|
|
10704
10566
|
}
|
|
10705
10567
|
});
|
|
10706
10568
|
|
|
10707
10569
|
// src/cli/commands/clone.ts
|
|
10708
10570
|
import * as p20 from "@clack/prompts";
|
|
10709
|
-
import { existsSync as
|
|
10710
|
-
import { resolve as
|
|
10571
|
+
import { existsSync as existsSync22, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
|
|
10572
|
+
import { resolve as resolve17, basename as basename5, join as join21 } from "path";
|
|
10711
10573
|
function cleanFailedCloneDir(dir) {
|
|
10712
10574
|
let entries;
|
|
10713
10575
|
try {
|
|
10714
10576
|
entries = readdirSync6(dir);
|
|
10715
10577
|
} catch {
|
|
10716
|
-
return
|
|
10578
|
+
return existsSync22(dir) ? ["<unreadable>"] : [];
|
|
10717
10579
|
}
|
|
10718
10580
|
if (entries.length === 1 && entries[0] === ".git") {
|
|
10719
10581
|
try {
|
|
@@ -10726,7 +10588,7 @@ function cleanFailedCloneDir(dir) {
|
|
|
10726
10588
|
return entries;
|
|
10727
10589
|
}
|
|
10728
10590
|
function validateCloneTarget(dir) {
|
|
10729
|
-
if (!
|
|
10591
|
+
if (!existsSync22(dir)) return false;
|
|
10730
10592
|
let entries;
|
|
10731
10593
|
try {
|
|
10732
10594
|
entries = readdirSync6(dir);
|
|
@@ -10761,7 +10623,7 @@ var clone_default = defineDeepspaceCommand({
|
|
|
10761
10623
|
async run({ args }) {
|
|
10762
10624
|
assertAppTargetResolvable(String(args.app));
|
|
10763
10625
|
const selector = String(args.app).trim();
|
|
10764
|
-
const dir =
|
|
10626
|
+
const dir = resolve17(String(args.dir ?? selector));
|
|
10765
10627
|
const dirPreexisted = validateCloneTarget(dir);
|
|
10766
10628
|
const spinner2 = args.json ? null : createSpinner();
|
|
10767
10629
|
spinner2?.start(`Resolving ${selector}\u2026`);
|
|
@@ -10801,6 +10663,7 @@ var clone_default = defineDeepspaceCommand({
|
|
|
10801
10663
|
throw err;
|
|
10802
10664
|
}
|
|
10803
10665
|
ensureSpaceRemote(dir, appId);
|
|
10666
|
+
ensureGitIdentity(dir, token);
|
|
10804
10667
|
const head = resolveCommit(dir, "HEAD");
|
|
10805
10668
|
const branch = currentBranch(dir);
|
|
10806
10669
|
spinner2?.stop(
|
|
@@ -11023,14 +10886,13 @@ Re-run with --allow-do-deletion to accept the data loss.`,
|
|
|
11023
10886
|
spinner2?.message("Waiting for the edge to serve this release...");
|
|
11024
10887
|
serving = await waitForLiveRelease(result.url, result.releaseStamp, 9e4);
|
|
11025
10888
|
}
|
|
11026
|
-
const edgePropagating = serving === "unconfirmed";
|
|
11027
10889
|
spinner2?.stop(
|
|
11028
10890
|
serving === "confirmed" ? `Rolled back to ${result.rolledBackTo ?? releaseId}.` : serving === "unconfirmed" ? `Rolled back to ${result.rolledBackTo ?? releaseId} \u2014 the edge is still rolling it out.` : `Rolled back to ${result.rolledBackTo ?? releaseId} \u2014 serving could not be verified from here.`
|
|
11029
10891
|
);
|
|
11030
10892
|
const doClassGuard = result.doClassGuard === "unverified" ? "unverified" : void 0;
|
|
11031
10893
|
if (!args.json) {
|
|
11032
10894
|
if (result.url) {
|
|
11033
|
-
if (
|
|
10895
|
+
if (serving === "unconfirmed") p22.log.warn(`URL: ${result.url} (accepted; serving not yet verified)`);
|
|
11034
10896
|
else p22.log.success(`Live at: ${result.url}`);
|
|
11035
10897
|
}
|
|
11036
10898
|
if (result.bundleRetained === false) {
|
|
@@ -11058,7 +10920,6 @@ Re-run with --allow-do-deletion to accept the data loss.`,
|
|
|
11058
10920
|
wranglerEnv: wranglerEnv ?? null,
|
|
11059
10921
|
// Always present, matching `deploy`: one field to branch on.
|
|
11060
10922
|
serving,
|
|
11061
|
-
...edgePropagating ? { edgePropagating: true } : {},
|
|
11062
10923
|
...doClassGuard ? { doClassGuard } : {}
|
|
11063
10924
|
}
|
|
11064
10925
|
};
|
|
@@ -11070,7 +10931,7 @@ import { defineCommand as defineCommand12 } from "citty";
|
|
|
11070
10931
|
|
|
11071
10932
|
// src/cli/commands/workspace/new.ts
|
|
11072
10933
|
import * as p25 from "@clack/prompts";
|
|
11073
|
-
import { isAbsolute as isAbsolute6, resolve as
|
|
10934
|
+
import { isAbsolute as isAbsolute6, resolve as resolve18 } from "path";
|
|
11074
10935
|
|
|
11075
10936
|
// src/cli/commands/workspace/analysis.ts
|
|
11076
10937
|
import * as p23 from "@clack/prompts";
|
|
@@ -11079,6 +10940,14 @@ var PEER_REF_PREFIX = `${spacePrivateRef("peers")}/`;
|
|
|
11079
10940
|
function peerWorkspaceRef(workspaceId) {
|
|
11080
10941
|
return `${PEER_REF_PREFIX}${workspaceId}`;
|
|
11081
10942
|
}
|
|
10943
|
+
function workspaceSyncRelation(dir, localOid, publishedOid) {
|
|
10944
|
+
if (localOid !== null && localOid === publishedOid) return "in_sync";
|
|
10945
|
+
if (localOid === null || publishedOid === null) return "unknown";
|
|
10946
|
+
if (resolveCommit(dir, publishedOid) === null) return "unknown";
|
|
10947
|
+
if (isAncestor(dir, localOid, publishedOid)) return "behind";
|
|
10948
|
+
if (isAncestor(dir, publishedOid, localOid)) return "ahead";
|
|
10949
|
+
return "diverged";
|
|
10950
|
+
}
|
|
11082
10951
|
function formatCount(count) {
|
|
11083
10952
|
if (!count) return "?";
|
|
11084
10953
|
return count.capped ? `${count.count}+` : String(count.count);
|
|
@@ -11195,18 +11064,18 @@ function printOverlaps(overlaps, json) {
|
|
|
11195
11064
|
|
|
11196
11065
|
// src/cli/commands/workspace/local.ts
|
|
11197
11066
|
import * as p24 from "@clack/prompts";
|
|
11198
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
11199
|
-
import { dirname as
|
|
11067
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync25, realpathSync as realpathSync6, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
11068
|
+
import { dirname as dirname10, isAbsolute as isAbsolute5, join as join22, relative as relative2, sep as sep2 } from "path";
|
|
11200
11069
|
var WORKSPACE_MARKER = "deepspace-workspace.json";
|
|
11201
11070
|
function excludeWorktreeDir(appDir, dir) {
|
|
11202
11071
|
try {
|
|
11203
11072
|
ensureLocalExclude(appDir, ".deepspace/");
|
|
11204
|
-
const realDir =
|
|
11205
|
-
const rel = relative2(
|
|
11073
|
+
const realDir = realpathSync6(dir);
|
|
11074
|
+
const rel = relative2(realpathSync6(repoToplevel(appDir)), realDir);
|
|
11206
11075
|
if (!rel || rel === ".." || rel.startsWith(`..${sep2}`) || isAbsolute5(rel)) return;
|
|
11207
11076
|
const pattern = rel.split(sep2).join("/");
|
|
11208
11077
|
if (pattern === ".deepspace" || pattern.startsWith(".deepspace/")) return;
|
|
11209
|
-
const relApp = relative2(
|
|
11078
|
+
const relApp = relative2(realpathSync6(appDir), realDir).split(sep2).join("/");
|
|
11210
11079
|
if (relApp === ".deepspace" || relApp.startsWith(".deepspace/")) return;
|
|
11211
11080
|
ensureLocalExclude(appDir, `/${pattern}/`);
|
|
11212
11081
|
} catch {
|
|
@@ -11228,7 +11097,7 @@ function markManagedWorkspace(worktreeRoot, workspaceId) {
|
|
|
11228
11097
|
const marker = { version: 1, workspaceId, mode: "managed-linked" };
|
|
11229
11098
|
const tempPath = `${markerPath}.${process.pid}.tmp`;
|
|
11230
11099
|
try {
|
|
11231
|
-
mkdirSync7(
|
|
11100
|
+
mkdirSync7(dirname10(markerPath), { recursive: true });
|
|
11232
11101
|
writeFileSync9(tempPath, JSON.stringify(marker) + "\n");
|
|
11233
11102
|
renameSync2(tempPath, markerPath);
|
|
11234
11103
|
return true;
|
|
@@ -11253,7 +11122,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
|
|
|
11253
11122
|
const markerPath = worktreeMarkerPath(worktreeRoot);
|
|
11254
11123
|
if (!markerPath) return false;
|
|
11255
11124
|
try {
|
|
11256
|
-
const marker = JSON.parse(
|
|
11125
|
+
const marker = JSON.parse(readFileSync25(markerPath, "utf-8"));
|
|
11257
11126
|
return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
|
|
11258
11127
|
} catch {
|
|
11259
11128
|
return false;
|
|
@@ -11265,7 +11134,7 @@ function defaultWorkspaceRoot(appDir, workspaceId) {
|
|
|
11265
11134
|
return join22(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
|
|
11266
11135
|
}
|
|
11267
11136
|
function appDirInWorktree(appDir, worktreeRoot) {
|
|
11268
|
-
const appRelative = relative2(
|
|
11137
|
+
const appRelative = relative2(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
|
|
11269
11138
|
return appRelative ? join22(worktreeRoot, appRelative) : worktreeRoot;
|
|
11270
11139
|
}
|
|
11271
11140
|
function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint, workspaceId, options = {}) {
|
|
@@ -11281,6 +11150,18 @@ function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint,
|
|
|
11281
11150
|
}
|
|
11282
11151
|
excludeWorktreeDir(appDir, worktreeRoot);
|
|
11283
11152
|
}
|
|
11153
|
+
function rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, workspaceId, options = {}) {
|
|
11154
|
+
const added = runGit(appDir, ["worktree", "add", worktreeRoot, branch], { allowFail: true });
|
|
11155
|
+
if (added.status !== 0) {
|
|
11156
|
+
return added.stderr.toString("utf-8").trim() || "unknown git error";
|
|
11157
|
+
}
|
|
11158
|
+
if (!(options.markManaged ?? markManagedWorkspace)(worktreeRoot, workspaceId)) {
|
|
11159
|
+
runGit(appDir, ["worktree", "remove", worktreeRoot], { allowFail: true });
|
|
11160
|
+
return `could not record DeepSpace ownership for ${worktreeRoot}`;
|
|
11161
|
+
}
|
|
11162
|
+
excludeWorktreeDir(appDir, worktreeRoot);
|
|
11163
|
+
return null;
|
|
11164
|
+
}
|
|
11284
11165
|
function inOwnLinkedWorktree(appDir, id) {
|
|
11285
11166
|
if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
|
|
11286
11167
|
try {
|
|
@@ -11389,7 +11270,7 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
|
|
|
11389
11270
|
}
|
|
11390
11271
|
let inside = false;
|
|
11391
11272
|
try {
|
|
11392
|
-
inside =
|
|
11273
|
+
inside = realpathSync6(repoToplevel(appDir)) === realpathSync6(linked.path);
|
|
11393
11274
|
} catch {
|
|
11394
11275
|
inside = false;
|
|
11395
11276
|
}
|
|
@@ -11485,7 +11366,7 @@ function inferWorkspaceId(appDir, explicit) {
|
|
|
11485
11366
|
const id = explicit?.trim() || workspaceIdFromBranch(currentBranch(appDir));
|
|
11486
11367
|
if (!id) {
|
|
11487
11368
|
throw new Refusal(
|
|
11488
|
-
'Not inside a workspace worktree (the branch is not ws/<id>).
|
|
11369
|
+
'Not inside a workspace worktree (the branch is not ws/<id>). Select one with `-w ws_\u2026` (for `workspace drop`, pass the id as the argument), or create one with `deepspace workspace new -t "\u2026"`.',
|
|
11489
11370
|
"not_in_workspace"
|
|
11490
11371
|
);
|
|
11491
11372
|
}
|
|
@@ -11494,17 +11375,28 @@ function inferWorkspaceId(appDir, explicit) {
|
|
|
11494
11375
|
}
|
|
11495
11376
|
return id;
|
|
11496
11377
|
}
|
|
11378
|
+
function workspaceNotActiveRefusal(id, status3, dropAction) {
|
|
11379
|
+
return new Refusal(
|
|
11380
|
+
`Workspace ${id} is already ${status3}.` + (dropAction ? " This checkout is a leftover \u2014 `deepspace workspace drop` cleans it up." : ""),
|
|
11381
|
+
"workspace_not_active",
|
|
11382
|
+
{
|
|
11383
|
+
...dropAction ? { action: dropAction } : {},
|
|
11384
|
+
extra: { workspaceId: id, status: status3 }
|
|
11385
|
+
}
|
|
11386
|
+
);
|
|
11387
|
+
}
|
|
11497
11388
|
function assertSelectedWorkspaceCheckout(appDir, id, resumeArgv) {
|
|
11498
11389
|
const branch = currentBranch(appDir);
|
|
11499
11390
|
if (isSelectedWorkspaceCheckout(branch, id)) return;
|
|
11500
11391
|
const worktree = resolveWorkspaceWorktree(listWorktrees(appDir), id);
|
|
11501
|
-
const
|
|
11392
|
+
const worktreeAppDir = worktree ? appDirInWorktree(appDir, worktree) : null;
|
|
11393
|
+
const action = worktreeAppDir ? { cwd: worktreeAppDir, argv: resumeArgv } : { cwd: appDir, argv: ["deepspace", "workspace", "attach", id] };
|
|
11502
11394
|
throw new Refusal(
|
|
11503
|
-
`Workspace ${id} is not checked out here (current branch: ${branch ?? "detached"}). ` + (
|
|
11395
|
+
`Workspace ${id} is not checked out here (current branch: ${branch ?? "detached"}). ` + (worktreeAppDir ? `Run this command from its checkout at ${worktreeAppDir}.` : "Attach it in this clone before mutating its published line."),
|
|
11504
11396
|
"workspace_checkout_mismatch",
|
|
11505
11397
|
{
|
|
11506
11398
|
action,
|
|
11507
|
-
extra:
|
|
11399
|
+
extra: worktreeAppDir ? { workspaceDir: worktreeAppDir } : void 0
|
|
11508
11400
|
}
|
|
11509
11401
|
);
|
|
11510
11402
|
}
|
|
@@ -11528,10 +11420,11 @@ function pushWorkspaceRef(appDir, token, ref, headOid) {
|
|
|
11528
11420
|
if (push.status === "committed" || push.status === "up_to_date") return;
|
|
11529
11421
|
if (push.status === "ref_conflict" || push.status === "non_fast_forward") {
|
|
11530
11422
|
throw new Refusal(
|
|
11531
|
-
`Another checkout advanced this workspace's line, so publishing yours is not a fast-forward \u2014 the push was refused rather than drop that work. Integrate its tip (\`git pull ${SPACE_REMOTE} ${ref}\`, or re-attach the workspace in a fresh dir), resolve any conflicts, then re-run the command. (Amended or rebased your own commits? Same refusal \u2014 merge your old tip back in.)`,
|
|
11423
|
+
`Another checkout advanced this workspace's line, so publishing yours is not a fast-forward \u2014 the push was refused rather than drop that work. Integrate its tip (\`git pull --no-rebase ${SPACE_REMOTE} ${ref}\`, or re-attach the workspace in a fresh dir), resolve any conflicts, then re-run the command. (Amended or rebased your own commits? Same refusal \u2014 merge your old tip back in.)`,
|
|
11532
11424
|
push.status,
|
|
11533
11425
|
{
|
|
11534
|
-
|
|
11426
|
+
actionRequired: true,
|
|
11427
|
+
action: { cwd: appDir, argv: ["git", "pull", "--no-rebase", SPACE_REMOTE, ref] }
|
|
11535
11428
|
}
|
|
11536
11429
|
);
|
|
11537
11430
|
}
|
|
@@ -11587,6 +11480,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11587
11480
|
);
|
|
11588
11481
|
spinner2?.message("Creating workspace\u2026");
|
|
11589
11482
|
ensureSpaceRemote(appDir, appId);
|
|
11483
|
+
ensureGitIdentity(appDir, token);
|
|
11590
11484
|
let baseOid = null;
|
|
11591
11485
|
const refs = await api4.getRefs();
|
|
11592
11486
|
if (baseArg) {
|
|
@@ -11630,7 +11524,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11630
11524
|
idempotencyKey: mintIdempotencyKey()
|
|
11631
11525
|
});
|
|
11632
11526
|
const dirInput = typeof args.dir === "string" ? args.dir.trim() : "";
|
|
11633
|
-
const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput :
|
|
11527
|
+
const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput : resolve18(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
|
|
11634
11528
|
const dir = appDirInWorktree(appDir, worktreeRoot);
|
|
11635
11529
|
try {
|
|
11636
11530
|
materializeWorkspaceWorktree(appDir, worktreeRoot, branch, baseOid, id);
|
|
@@ -11668,8 +11562,18 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11668
11562
|
|
|
11669
11563
|
// src/cli/commands/workspace/attach.ts
|
|
11670
11564
|
import * as p26 from "@clack/prompts";
|
|
11671
|
-
import { existsSync as
|
|
11672
|
-
import { isAbsolute as isAbsolute7, resolve as
|
|
11565
|
+
import { existsSync as existsSync23, readdirSync as readdirSync7, realpathSync as realpathSync7, rmSync as rmSync6 } from "fs";
|
|
11566
|
+
import { isAbsolute as isAbsolute7, resolve as resolve19 } from "path";
|
|
11567
|
+
function samePath(a, b) {
|
|
11568
|
+
const real = (p35) => {
|
|
11569
|
+
try {
|
|
11570
|
+
return realpathSync7(p35);
|
|
11571
|
+
} catch {
|
|
11572
|
+
return resolve19(p35);
|
|
11573
|
+
}
|
|
11574
|
+
};
|
|
11575
|
+
return real(a) === real(b);
|
|
11576
|
+
}
|
|
11673
11577
|
function finishedWorkspaceMessage(id, view) {
|
|
11674
11578
|
const workspace = view.workspace;
|
|
11675
11579
|
if (workspace.status === "landed") {
|
|
@@ -11678,7 +11582,7 @@ function finishedWorkspaceMessage(id, view) {
|
|
|
11678
11582
|
return `Workspace ${id} is ${workspace.status} \u2014 its ref is retained briefly (fetch ${workspace.ref} to inspect), but attach only works on active workspaces.`;
|
|
11679
11583
|
}
|
|
11680
11584
|
function cleanFailedFreshAttachDir(dir) {
|
|
11681
|
-
if (!
|
|
11585
|
+
if (!existsSync23(dir)) return [];
|
|
11682
11586
|
let entries;
|
|
11683
11587
|
try {
|
|
11684
11588
|
entries = readdirSync7(dir);
|
|
@@ -11738,6 +11642,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11738
11642
|
});
|
|
11739
11643
|
}
|
|
11740
11644
|
ensureSpaceRemote(appDir, appId2);
|
|
11645
|
+
ensureGitIdentity(appDir, token2);
|
|
11741
11646
|
const localWorkspaceRef = spacePrivateRef(`workspaces/${id}`);
|
|
11742
11647
|
runGitRemote(appDir, token2, [
|
|
11743
11648
|
"fetch",
|
|
@@ -11754,11 +11659,87 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11754
11659
|
);
|
|
11755
11660
|
}
|
|
11756
11661
|
const requestedDir = typeof args.dir === "string" ? args.dir.trim() : "";
|
|
11757
|
-
const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir :
|
|
11662
|
+
const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir : resolve19(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
|
|
11758
11663
|
const dir2 = appDirInWorktree(appDir, worktreeRoot);
|
|
11759
|
-
|
|
11664
|
+
const localTip = resolveCommit(appDir, `refs/heads/${branch}`);
|
|
11665
|
+
if (localTip) {
|
|
11666
|
+
let existing = resolveWorkspaceWorktree(listWorktrees(appDir), id);
|
|
11667
|
+
if (existing && !existsSync23(existing)) {
|
|
11668
|
+
runGit(appDir, ["worktree", "prune"], { allowFail: true });
|
|
11669
|
+
existing = null;
|
|
11670
|
+
}
|
|
11671
|
+
const relation = workspaceSyncRelation(appDir, localTip, tip2);
|
|
11672
|
+
const inSync = relation === "in_sync";
|
|
11673
|
+
const behindOrDiverged = relation === "behind" || relation === "diverged";
|
|
11674
|
+
const syncAdvice = behindOrDiverged ? `integrate the published tip first (\`git pull --no-rebase ${SPACE_REMOTE} ${view2.workspace.ref}\`)` : `run \`deepspace workspace sync\` from it`;
|
|
11675
|
+
const syncAction = (cwd) => behindOrDiverged ? { cwd, argv: ["git", "pull", "--no-rebase", SPACE_REMOTE, view2.workspace.ref] } : { cwd, argv: ["deepspace", "workspace", "sync"] };
|
|
11676
|
+
if (existing && (!requestedDir || samePath(existing, worktreeRoot))) {
|
|
11677
|
+
const existingDir = appDirInWorktree(appDir, existing);
|
|
11678
|
+
spinner2?.stop(`Already attached: ${id}.`);
|
|
11679
|
+
if (!args.json) {
|
|
11680
|
+
p26.log.info(`Task: ${view2.workspace.task}`);
|
|
11681
|
+
p26.log.info(`Work in: cd ${existingDir}`);
|
|
11682
|
+
if (!inSync) {
|
|
11683
|
+
p26.log.warn(
|
|
11684
|
+
`The checkout is at ${localTip.slice(0, 10)} but the workspace's published tip is ${tip2.slice(0, 10)} \u2014 ${syncAdvice}.`
|
|
11685
|
+
);
|
|
11686
|
+
}
|
|
11687
|
+
}
|
|
11688
|
+
return {
|
|
11689
|
+
data: {
|
|
11690
|
+
workspaceId: id,
|
|
11691
|
+
mode: "worktree",
|
|
11692
|
+
dir: existingDir,
|
|
11693
|
+
worktreeRoot: existing,
|
|
11694
|
+
branch,
|
|
11695
|
+
tipOid: localTip,
|
|
11696
|
+
remoteTip: tip2,
|
|
11697
|
+
syncRelation: relation,
|
|
11698
|
+
task: view2.workspace.task,
|
|
11699
|
+
alreadyAttached: true
|
|
11700
|
+
},
|
|
11701
|
+
...inSync ? {} : { action: syncAction(existingDir) }
|
|
11702
|
+
};
|
|
11703
|
+
}
|
|
11704
|
+
if (!existing) {
|
|
11705
|
+
const failure = rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, id);
|
|
11706
|
+
if (failure) {
|
|
11707
|
+
spinner2?.stop("Branch exists.");
|
|
11708
|
+
throw new Refusal(
|
|
11709
|
+
`Branch ${branch} exists locally with no worktree, and re-materializing it failed: ${failure}. Inspect it (\`git log ${branch}\`) and check it out yourself.`,
|
|
11710
|
+
"branch_exists"
|
|
11711
|
+
);
|
|
11712
|
+
}
|
|
11713
|
+
spinner2?.stop(`Re-attached ${id}.`);
|
|
11714
|
+
if (!args.json) {
|
|
11715
|
+
p26.log.info(`Task: ${view2.workspace.task}`);
|
|
11716
|
+
p26.log.info(`Work in: cd ${dir2}`);
|
|
11717
|
+
if (!inSync) {
|
|
11718
|
+
p26.log.warn(
|
|
11719
|
+
`The re-materialized checkout is at ${localTip.slice(0, 10)} but the workspace's published tip is ${tip2.slice(0, 10)} \u2014 after installing, ${syncAdvice}.`
|
|
11720
|
+
);
|
|
11721
|
+
}
|
|
11722
|
+
}
|
|
11723
|
+
const action3 = { cwd: dir2, argv: [detectPackageManager(dir2), "install"] };
|
|
11724
|
+
return {
|
|
11725
|
+
data: {
|
|
11726
|
+
workspaceId: id,
|
|
11727
|
+
mode: "worktree",
|
|
11728
|
+
dir: dir2,
|
|
11729
|
+
worktreeRoot,
|
|
11730
|
+
branch,
|
|
11731
|
+
tipOid: localTip,
|
|
11732
|
+
remoteTip: tip2,
|
|
11733
|
+
syncRelation: relation,
|
|
11734
|
+
task: view2.workspace.task,
|
|
11735
|
+
rematerialized: true
|
|
11736
|
+
},
|
|
11737
|
+
action: action3
|
|
11738
|
+
};
|
|
11739
|
+
}
|
|
11740
|
+
spinner2?.stop("Branch exists.");
|
|
11760
11741
|
throw new Refusal(
|
|
11761
|
-
`
|
|
11742
|
+
`Workspace ${id} is already checked out at ${existing} \u2014 continue there (or drop that worktree before attaching it elsewhere).`,
|
|
11762
11743
|
"branch_exists"
|
|
11763
11744
|
);
|
|
11764
11745
|
}
|
|
@@ -11777,6 +11758,10 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11777
11758
|
worktreeRoot,
|
|
11778
11759
|
branch,
|
|
11779
11760
|
tipOid: tip2,
|
|
11761
|
+
remoteTip: tip2,
|
|
11762
|
+
// Freshly materialized at the published tip — trivially in sync;
|
|
11763
|
+
// every attach outcome carries the same shape.
|
|
11764
|
+
syncRelation: "in_sync",
|
|
11780
11765
|
task: view2.workspace.task
|
|
11781
11766
|
},
|
|
11782
11767
|
action: action2
|
|
@@ -11793,11 +11778,11 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11793
11778
|
extra: { status: view.workspace.status }
|
|
11794
11779
|
});
|
|
11795
11780
|
}
|
|
11796
|
-
const dir =
|
|
11781
|
+
const dir = resolve19(
|
|
11797
11782
|
process.cwd(),
|
|
11798
11783
|
(typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
|
|
11799
11784
|
);
|
|
11800
|
-
if (
|
|
11785
|
+
if (existsSync23(dir)) {
|
|
11801
11786
|
spinner2?.stop("Target exists.");
|
|
11802
11787
|
throw new Refusal(`${dir} already exists \u2014 pass a different directory.`, "dir_exists");
|
|
11803
11788
|
}
|
|
@@ -11812,6 +11797,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11812
11797
|
try {
|
|
11813
11798
|
runGit(process.cwd(), ["init", "--quiet", dir]);
|
|
11814
11799
|
ensureSpaceRemote(dir, appId);
|
|
11800
|
+
ensureGitIdentity(dir, token);
|
|
11815
11801
|
const localWorkspaceRef = spacePrivateRef(`workspaces/${id}`);
|
|
11816
11802
|
runGitRemote(dir, token, [
|
|
11817
11803
|
"fetch",
|
|
@@ -11848,6 +11834,8 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11848
11834
|
dir,
|
|
11849
11835
|
branch,
|
|
11850
11836
|
tipOid: tip,
|
|
11837
|
+
remoteTip: tip,
|
|
11838
|
+
syncRelation: "in_sync",
|
|
11851
11839
|
task: view.workspace.task
|
|
11852
11840
|
},
|
|
11853
11841
|
action
|
|
@@ -11880,7 +11868,19 @@ var syncWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11880
11868
|
typeof args.app === "string" ? args.app : void 0
|
|
11881
11869
|
);
|
|
11882
11870
|
const id = inferWorkspaceId(appDir, workspaceArg);
|
|
11883
|
-
|
|
11871
|
+
try {
|
|
11872
|
+
assertSelectedWorkspaceCheckout(appDir, id, ["deepspace", "workspace", "sync"]);
|
|
11873
|
+
} catch (mismatch) {
|
|
11874
|
+
const current = await api4.getWorkspace(id).then((r) => r.view).catch(() => null);
|
|
11875
|
+
if (current && current.workspace.status !== "active") {
|
|
11876
|
+
spinner2?.stop("Workspace finished.");
|
|
11877
|
+
throw workspaceNotActiveRefusal(id, current.workspace.status, {
|
|
11878
|
+
cwd: appDir,
|
|
11879
|
+
argv: ["deepspace", "workspace", "drop", id, "--app", appId]
|
|
11880
|
+
});
|
|
11881
|
+
}
|
|
11882
|
+
throw mismatch;
|
|
11883
|
+
}
|
|
11884
11884
|
const headOid = resolveCommit(appDir, "HEAD");
|
|
11885
11885
|
if (!headOid) {
|
|
11886
11886
|
throw new Refusal("The workspace has no commits yet \u2014 commit first, then sync.", "no_commits");
|
|
@@ -11899,6 +11899,7 @@ var syncWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11899
11899
|
);
|
|
11900
11900
|
}
|
|
11901
11901
|
ensureSpaceRemote(appDir, appId);
|
|
11902
|
+
ensureGitIdentity(appDir, token);
|
|
11902
11903
|
pushWorkspaceRef(appDir, token, before.workspace.ref, headOid);
|
|
11903
11904
|
try {
|
|
11904
11905
|
fetchTrunk(appDir, token, refs?.head ?? null);
|
|
@@ -12039,6 +12040,7 @@ var workspaceStatusCommand = defineDeepspaceCommand({
|
|
|
12039
12040
|
const aheadExact = headOid && baseLocal ? revListCount(appDir, workspace.baseOid, headOid) : null;
|
|
12040
12041
|
const unsynced = headOid !== null && view.tipOid !== headOid;
|
|
12041
12042
|
const dirty = workspaceDir ? statusFiles(workspaceDir) : [];
|
|
12043
|
+
const relation = !unsynced ? "in_sync" : workspace.status !== "active" || !workspaceDir ? "unknown" : workspaceSyncRelation(workspaceDir, headOid, view.tipOid);
|
|
12042
12044
|
const lines = await loadWorkspaceLines(appDir, token, api4, trunkOid);
|
|
12043
12045
|
const myTip = headOid ?? resolveCommit(appDir, peerWorkspaceRef(id)) ?? view.tipOid;
|
|
12044
12046
|
const line = myTip ? lineChangedPaths(appDir, workspace.baseOid, myTip, trunkOid) : null;
|
|
@@ -12058,6 +12060,10 @@ var workspaceStatusCommand = defineDeepspaceCommand({
|
|
|
12058
12060
|
...attached ? {
|
|
12059
12061
|
headOid,
|
|
12060
12062
|
unsynced,
|
|
12063
|
+
// The human line distinguishes behind/diverged/ahead; the machine
|
|
12064
|
+
// mirror must too, or a JSON caller can't tell a fast-forward from
|
|
12065
|
+
// a real divergence.
|
|
12066
|
+
syncRelation: relation,
|
|
12061
12067
|
uncommitted: dirty,
|
|
12062
12068
|
trunkOverlap
|
|
12063
12069
|
} : {},
|
|
@@ -12073,10 +12079,29 @@ var workspaceStatusCommand = defineDeepspaceCommand({
|
|
|
12073
12079
|
`Not checked out in this clone \u2014 showing the published state (local HEAD/uncommitted unavailable). Attach it with \`deepspace workspace attach ${id} <dir>\`.`
|
|
12074
12080
|
);
|
|
12075
12081
|
}
|
|
12076
|
-
if (unsynced)
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
12082
|
+
if (unsynced) {
|
|
12083
|
+
if (workspace.status === "landed") {
|
|
12084
|
+
p29.log.info(
|
|
12085
|
+
`Already landed${workspace.landedOid ? ` at ${workspace.landedOid.slice(0, 10)}` : ""} \u2014 this checkout is a leftover; \`deepspace workspace drop ${id}\` cleans it up.`
|
|
12086
|
+
);
|
|
12087
|
+
} else if (workspace.status === "dropped") {
|
|
12088
|
+
p29.log.info(
|
|
12089
|
+
`Already dropped \u2014 this checkout is a leftover; \`deepspace workspace drop ${id}\` cleans it up.`
|
|
12090
|
+
);
|
|
12091
|
+
} else if (relation === "behind") {
|
|
12092
|
+
p29.log.warn(
|
|
12093
|
+
`Local HEAD ${headOid?.slice(0, 10)} is BEHIND the published tip ${view.tipOid?.slice(0, 10)} (another checkout synced ahead) \u2014 fast-forward first (\`git pull --no-rebase ${SPACE_REMOTE} ${workspace.ref}\`).`
|
|
12094
|
+
);
|
|
12095
|
+
} else if (relation === "diverged") {
|
|
12096
|
+
p29.log.warn(
|
|
12097
|
+
`Local HEAD ${headOid?.slice(0, 10)} has DIVERGED from the published tip ${view.tipOid?.slice(0, 10)} \u2014 integrate it first (\`git pull --no-rebase ${SPACE_REMOTE} ${workspace.ref}\`), then \`deepspace workspace sync\`.`
|
|
12098
|
+
);
|
|
12099
|
+
} else {
|
|
12100
|
+
p29.log.warn(
|
|
12101
|
+
`Local HEAD ${headOid?.slice(0, 10)} is not synced (server has ${view.tipOid?.slice(0, 10) ?? "nothing"}) \u2014 run \`deepspace workspace sync\`.`
|
|
12102
|
+
);
|
|
12103
|
+
}
|
|
12104
|
+
}
|
|
12080
12105
|
if (dirty.length > 0) p29.log.warn(`${dirty.length} uncommitted file(s) in the worktree.`);
|
|
12081
12106
|
if (trunkOverlap.length > 0) {
|
|
12082
12107
|
p29.log.warn(
|
|
@@ -12094,13 +12119,13 @@ import * as p30 from "@clack/prompts";
|
|
|
12094
12119
|
|
|
12095
12120
|
// src/cli/lib/validation.ts
|
|
12096
12121
|
import { sync as spawnSync7 } from "cross-spawn";
|
|
12097
|
-
import { readFileSync as
|
|
12122
|
+
import { readFileSync as readFileSync26 } from "fs";
|
|
12098
12123
|
import { join as join23 } from "path";
|
|
12099
12124
|
var MAX_SUMMARY_CHARS = 4e3;
|
|
12100
12125
|
var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
|
|
12101
12126
|
function configuredCommand(appDir) {
|
|
12102
12127
|
try {
|
|
12103
|
-
const pkg2 = JSON.parse(
|
|
12128
|
+
const pkg2 = JSON.parse(readFileSync26(join23(appDir, "package.json"), "utf-8"));
|
|
12104
12129
|
if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
|
|
12105
12130
|
} catch {
|
|
12106
12131
|
}
|
|
@@ -12130,6 +12155,14 @@ ${res.stderr?.toString("utf-8") ?? ""}`.trim();
|
|
|
12130
12155
|
function hasLeftoverConflictMarkers(diffCheckOutput) {
|
|
12131
12156
|
return /leftover conflict marker/.test(diffCheckOutput);
|
|
12132
12157
|
}
|
|
12158
|
+
function conflictMarkerFiles(diffCheckOutput) {
|
|
12159
|
+
const files = /* @__PURE__ */ new Set();
|
|
12160
|
+
for (const line of diffCheckOutput.split("\n")) {
|
|
12161
|
+
const match = /^(.+?):\d+: leftover conflict marker/.exec(line);
|
|
12162
|
+
if (match) files.add(match[1]);
|
|
12163
|
+
}
|
|
12164
|
+
return [...files];
|
|
12165
|
+
}
|
|
12133
12166
|
function landResumeArgv(args) {
|
|
12134
12167
|
const argv = ["deepspace", "workspace", "land"];
|
|
12135
12168
|
const workspace = args.workspace?.trim();
|
|
@@ -12195,7 +12228,20 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12195
12228
|
});
|
|
12196
12229
|
const recoveryAction = { cwd: appDir, argv: resumeArgv };
|
|
12197
12230
|
const resumeNext = humanCommand(resumeArgv);
|
|
12198
|
-
|
|
12231
|
+
const dropAction = {
|
|
12232
|
+
cwd: appDir,
|
|
12233
|
+
argv: ["deepspace", "workspace", "drop", id, "--app", appId]
|
|
12234
|
+
};
|
|
12235
|
+
try {
|
|
12236
|
+
assertSelectedWorkspaceCheckout(appDir, id, resumeArgv);
|
|
12237
|
+
} catch (mismatch) {
|
|
12238
|
+
const current = await api4.getWorkspace(id).then((r) => r.view).catch(() => null);
|
|
12239
|
+
if (current && current.workspace.status !== "active") {
|
|
12240
|
+
spinner2?.stop("Workspace finished.");
|
|
12241
|
+
throw workspaceNotActiveRefusal(id, current.workspace.status, dropAction);
|
|
12242
|
+
}
|
|
12243
|
+
throw mismatch;
|
|
12244
|
+
}
|
|
12199
12245
|
const headBefore = resolveCommit(appDir, "HEAD");
|
|
12200
12246
|
if (!headBefore) {
|
|
12201
12247
|
throw new Refusal("The workspace has no commits \u2014 nothing to land.", "no_commits");
|
|
@@ -12211,13 +12257,10 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12211
12257
|
const [{ view }, refs] = await Promise.all([api4.getWorkspace(id), api4.getRefs()]);
|
|
12212
12258
|
if (view.workspace.status !== "active") {
|
|
12213
12259
|
spinner2?.stop("Workspace finished.");
|
|
12214
|
-
throw
|
|
12215
|
-
`Workspace ${id} is already ${view.workspace.status}.`,
|
|
12216
|
-
"workspace_not_active",
|
|
12217
|
-
{ extra: { status: view.workspace.status } }
|
|
12218
|
-
);
|
|
12260
|
+
throw workspaceNotActiveRefusal(id, view.workspace.status, dropAction);
|
|
12219
12261
|
}
|
|
12220
12262
|
ensureSpaceRemote(appDir, appId);
|
|
12263
|
+
ensureGitIdentity(appDir, token);
|
|
12221
12264
|
const isResumedMerge = resolveCommit(appDir, "HEAD^2") !== null && view.tipOid !== null && resolveCommit(appDir, "HEAD^") === view.tipOid;
|
|
12222
12265
|
if (!isResumedMerge) {
|
|
12223
12266
|
pushWorkspaceRef(appDir, token, view.workspace.ref, headBefore);
|
|
@@ -12264,6 +12307,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12264
12307
|
"merge_conflict",
|
|
12265
12308
|
{
|
|
12266
12309
|
actionRequired: true,
|
|
12310
|
+
action: recoveryAction,
|
|
12267
12311
|
extra: { workspaceId: id, conflict: true }
|
|
12268
12312
|
}
|
|
12269
12313
|
);
|
|
@@ -12273,14 +12317,17 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12273
12317
|
const markerCheck = runGit(appDir, ["diff", "--check", "HEAD^", "HEAD"], {
|
|
12274
12318
|
allowFail: true
|
|
12275
12319
|
});
|
|
12276
|
-
|
|
12320
|
+
const markerOutput = markerCheck.stdout.toString("utf-8");
|
|
12321
|
+
if (hasLeftoverConflictMarkers(markerOutput)) {
|
|
12322
|
+
const files = conflictMarkerFiles(markerOutput);
|
|
12277
12323
|
spinner2?.stop("Conflict markers in merge.");
|
|
12278
12324
|
throw new Refusal(
|
|
12279
|
-
`The merge commit still contains unresolved conflict markers (<<<<<<< / ======= / >>>>>>>). Fix the marked files, commit the correction, then re-run \`${resumeNext}\`. Nothing was pushed to ${intoBranch}.`,
|
|
12325
|
+
`The merge commit still contains unresolved conflict markers (<<<<<<< / ======= / >>>>>>>)${files.length ? ` in ${files.join(", ")}` : ""}. Fix the marked files, commit the correction, then re-run \`${resumeNext}\`. Nothing was pushed to ${intoBranch}.`,
|
|
12280
12326
|
"conflict_markers",
|
|
12281
12327
|
{
|
|
12282
12328
|
actionRequired: true,
|
|
12283
|
-
|
|
12329
|
+
action: recoveryAction,
|
|
12330
|
+
extra: { workspaceId: id, conflict: true, ...files.length ? { files } : {} }
|
|
12284
12331
|
}
|
|
12285
12332
|
);
|
|
12286
12333
|
}
|
|
@@ -12314,6 +12361,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12314
12361
|
"validation_failed",
|
|
12315
12362
|
{
|
|
12316
12363
|
actionRequired: true,
|
|
12364
|
+
action: recoveryAction,
|
|
12317
12365
|
extra: { workspaceId: id, passed: false, outputTail }
|
|
12318
12366
|
}
|
|
12319
12367
|
);
|
|
@@ -12325,6 +12373,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12325
12373
|
"validation_mutated_tree",
|
|
12326
12374
|
{
|
|
12327
12375
|
actionRequired: true,
|
|
12376
|
+
action: recoveryAction,
|
|
12328
12377
|
extra: { workspaceId: id }
|
|
12329
12378
|
}
|
|
12330
12379
|
);
|
|
@@ -12360,7 +12409,37 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12360
12409
|
"push_rejected"
|
|
12361
12410
|
);
|
|
12362
12411
|
}
|
|
12363
|
-
|
|
12412
|
+
try {
|
|
12413
|
+
await api4.landWorkspace(id, { landedOid, intoRef });
|
|
12414
|
+
} catch (err) {
|
|
12415
|
+
const code = errorCode(err);
|
|
12416
|
+
if (code === "workspace_not_active") {
|
|
12417
|
+
const status3 = await api4.getWorkspace(id).then((r) => r.view.workspace.status).catch(() => null);
|
|
12418
|
+
if (status3 && status3 !== "active") {
|
|
12419
|
+
spinner2?.stop("Workspace finished.");
|
|
12420
|
+
throw new Refusal(
|
|
12421
|
+
`${intoBranch} took the land push (${landedOid.slice(0, 10)}), and the workspace is already ${status3} \u2014 a concurrent land or drop finished first, so there is nothing left to record. This checkout is a leftover; \`deepspace workspace drop ${id}\` cleans it up.`,
|
|
12422
|
+
"workspace_not_active",
|
|
12423
|
+
{
|
|
12424
|
+
actionRequired: true,
|
|
12425
|
+
action: dropAction,
|
|
12426
|
+
extra: { workspaceId: id, into: intoBranch, landedOid, pushed: true, status: status3 }
|
|
12427
|
+
}
|
|
12428
|
+
);
|
|
12429
|
+
}
|
|
12430
|
+
}
|
|
12431
|
+
spinner2?.stop("Landed; not recorded.");
|
|
12432
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
12433
|
+
throw new Refusal(
|
|
12434
|
+
`The land push succeeded (${intoBranch} was at ${landedOid.slice(0, 10)}), but recording the land failed (${detail}). Resolve that, then re-run \`${resumeNext}\` \u2014 it re-checks the merge (a no-op when already pushed) and records it.`,
|
|
12435
|
+
"land_unrecorded",
|
|
12436
|
+
{
|
|
12437
|
+
actionRequired: true,
|
|
12438
|
+
action: recoveryAction,
|
|
12439
|
+
extra: { workspaceId: id, into: intoBranch, landedOid, pushed: true }
|
|
12440
|
+
}
|
|
12441
|
+
);
|
|
12442
|
+
}
|
|
12364
12443
|
spinner2?.stop(`Landed ${id} into ${intoBranch} at ${landedOid.slice(0, 10)}.`);
|
|
12365
12444
|
const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, intoBranch, { expectedBranchOid: landedOid });
|
|
12366
12445
|
const inOwnWorktree = !cleanup && inOwnLinkedWorktree(appDir, id);
|
|
@@ -12405,6 +12484,22 @@ function isWorkspaceTipPublished(appDir, localTip, publishedOids) {
|
|
|
12405
12484
|
);
|
|
12406
12485
|
}
|
|
12407
12486
|
function workspaceUnsyncedRefusal(args) {
|
|
12487
|
+
const dropKeepArgv = [
|
|
12488
|
+
"deepspace",
|
|
12489
|
+
"workspace",
|
|
12490
|
+
"drop",
|
|
12491
|
+
args.id,
|
|
12492
|
+
"--app",
|
|
12493
|
+
args.appId,
|
|
12494
|
+
"--keep-worktree"
|
|
12495
|
+
];
|
|
12496
|
+
if (args.status !== "active") {
|
|
12497
|
+
return new Refusal(
|
|
12498
|
+
`${args.id} is already ${args.status} on the server, and this checkout has committed work that was never published \u2014 nothing can publish it now. Keep it with \`${humanCommand(dropKeepArgv)}\` (retains the branch/worktree for manual disposal), or delete the branch yourself. Nothing was dropped.`,
|
|
12499
|
+
"workspace_unsynced",
|
|
12500
|
+
{ extra: { workspaceId: args.id, status: args.status } }
|
|
12501
|
+
);
|
|
12502
|
+
}
|
|
12408
12503
|
const syncArgv = ["deepspace", "workspace", "sync", "--app", args.appId, "--workspace", args.id];
|
|
12409
12504
|
const dropArgv = ["deepspace", "workspace", "drop", args.id, "--app", args.appId];
|
|
12410
12505
|
const publish = args.workspaceDir ? `Publish them first with \`${humanCommand(syncArgv)}\`, then re-run \`${humanCommand(dropArgv)}\`.` : `Recreate a worktree from the branch (\`git worktree add <dir> ${args.branch}\`), publish from there with \`${humanCommand(syncArgv)}\`, then re-run \`${humanCommand(dropArgv)}\`.`;
|
|
@@ -12412,11 +12507,25 @@ function workspaceUnsyncedRefusal(args) {
|
|
|
12412
12507
|
`${args.id} has committed work that isn't published yet. ${publish} Alternatively, pass \`--keep-worktree\` to drop the cloud workspace while retaining the local branch/worktree for manual disposal. Nothing was dropped.`,
|
|
12413
12508
|
"workspace_unsynced",
|
|
12414
12509
|
{
|
|
12510
|
+
// Exit 1 with an unblock action, deliberately: drop is destructive and
|
|
12511
|
+
// refusing it is a hard stop, not the "your turn" hand-off exit 2 means.
|
|
12415
12512
|
...args.workspaceDir ? { action: { cwd: args.workspaceDir, argv: syncArgv } } : {},
|
|
12416
|
-
extra: { workspaceId: args.id }
|
|
12513
|
+
extra: { workspaceId: args.id, status: args.status }
|
|
12417
12514
|
}
|
|
12418
12515
|
);
|
|
12419
12516
|
}
|
|
12517
|
+
async function dropRemoteTolerant(api4, id, statusBefore) {
|
|
12518
|
+
try {
|
|
12519
|
+
const view = (await api4.dropWorkspace(id)).view;
|
|
12520
|
+
return { view, remoteDropped: statusBefore === "active" && view.workspace.status === "dropped" };
|
|
12521
|
+
} catch (err) {
|
|
12522
|
+
const code = errorCode(err);
|
|
12523
|
+
if (code !== "workspace_not_active") throw err;
|
|
12524
|
+
const current = await api4.getWorkspace(id).catch(() => null);
|
|
12525
|
+
if (!current || current.view.workspace.status === "active") throw err;
|
|
12526
|
+
return { view: current.view, remoteDropped: false };
|
|
12527
|
+
}
|
|
12528
|
+
}
|
|
12420
12529
|
var dropWorkspaceCommand = defineDeepspaceCommand({
|
|
12421
12530
|
meta: { name: "drop", description: "Abandon a workspace (ref retained briefly, then GC)" },
|
|
12422
12531
|
args: {
|
|
@@ -12441,32 +12550,47 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12441
12550
|
spinner2?.start(`Preparing to drop ${explicitId ?? "workspace"}\u2026`);
|
|
12442
12551
|
if (!findAppDir() && explicitId) {
|
|
12443
12552
|
const { api: api5 } = await resolveApiOnly(appArg);
|
|
12444
|
-
const
|
|
12553
|
+
const statusBefore = (await api5.getWorkspace(explicitId)).view.workspace.status;
|
|
12554
|
+
const { view: view2, remoteDropped: remoteDropped2 } = await dropRemoteTolerant(api5, explicitId, statusBefore);
|
|
12445
12555
|
spinner2?.stop();
|
|
12446
12556
|
if (!args.json) {
|
|
12447
12557
|
p31.log.success(
|
|
12448
|
-
`Dropped ${explicitId}. Its ref sticks around briefly for undo, then is reaped.`
|
|
12558
|
+
remoteDropped2 ? `Dropped ${explicitId}. Its ref sticks around briefly for undo, then is reaped.` : `Workspace ${explicitId} was already ${view2.workspace.status} on the server \u2014 nothing to drop remotely.`
|
|
12449
12559
|
);
|
|
12450
12560
|
}
|
|
12451
|
-
return { data: { workspaceId: explicitId, status: view2.workspace.status } };
|
|
12561
|
+
return { data: { workspaceId: explicitId, status: view2.workspace.status, remoteDropped: remoteDropped2 } };
|
|
12452
12562
|
}
|
|
12453
|
-
const { appDir, appId, api: api4 } = await resolveTarget2(appArg);
|
|
12563
|
+
const { appDir, appId, token, api: api4 } = await resolveTarget2(appArg);
|
|
12454
12564
|
const id = inferWorkspaceId(appDir, explicitId);
|
|
12455
12565
|
const inspection = inspectWorkspaceCleanup(appDir, id);
|
|
12456
12566
|
let approvedBranchOid;
|
|
12567
|
+
const { view: before } = await api4.getWorkspace(id);
|
|
12457
12568
|
if (!keepWorktree && inspection.willDeleteBranch && inspection.branchOid) {
|
|
12458
|
-
const { view: before } = await api4.getWorkspace(id);
|
|
12459
12569
|
const publishedOids = [
|
|
12460
12570
|
before.tipOid,
|
|
12461
12571
|
before.workspace.landedOid,
|
|
12462
12572
|
before.workspace.baseOid
|
|
12463
12573
|
].filter((oid) => oid !== null);
|
|
12464
|
-
|
|
12574
|
+
let published = isWorkspaceTipPublished(appDir, inspection.branchOid, publishedOids);
|
|
12575
|
+
if (!published && before.workspace.status !== "active") {
|
|
12576
|
+
ensureSpaceRemote(appDir, appId);
|
|
12577
|
+
const refs = await api4.getRefs();
|
|
12578
|
+
const trunkRef = refs?.head?.startsWith("refs/heads/") ? refs.head : null;
|
|
12579
|
+
if (trunkRef) {
|
|
12580
|
+
runGitRemote(appDir, token, ["fetch", "--quiet", "--refmap=", SPACE_REMOTE, trunkRef], {
|
|
12581
|
+
allowFail: true
|
|
12582
|
+
});
|
|
12583
|
+
const trunkTip = resolveCommit(appDir, "FETCH_HEAD");
|
|
12584
|
+
published = trunkTip !== null && isWorkspaceTipPublished(appDir, inspection.branchOid, [trunkTip]);
|
|
12585
|
+
}
|
|
12586
|
+
}
|
|
12587
|
+
if (!published) {
|
|
12465
12588
|
throw workspaceUnsyncedRefusal({
|
|
12466
12589
|
appId,
|
|
12467
12590
|
id,
|
|
12468
12591
|
branch: inspection.branch,
|
|
12469
|
-
workspaceDir: inspection.checkout.dir
|
|
12592
|
+
workspaceDir: inspection.checkout.dir,
|
|
12593
|
+
status: before.workspace.status
|
|
12470
12594
|
});
|
|
12471
12595
|
}
|
|
12472
12596
|
approvedBranchOid = inspection.branchOid;
|
|
@@ -12481,7 +12605,7 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12481
12605
|
{ extra: { workspaceId: id } }
|
|
12482
12606
|
);
|
|
12483
12607
|
}
|
|
12484
|
-
const { view } = await api4.
|
|
12608
|
+
const { view, remoteDropped } = await dropRemoteTolerant(api4, id, before.workspace.status);
|
|
12485
12609
|
spinner2?.message(`Cleaning up ${id} locally\u2026`);
|
|
12486
12610
|
const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, null, {
|
|
12487
12611
|
expectedBranchOid: approvedBranchOid,
|
|
@@ -12491,12 +12615,15 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12491
12615
|
const data = {
|
|
12492
12616
|
workspaceId: id,
|
|
12493
12617
|
status: view.workspace.status,
|
|
12618
|
+
remoteDropped,
|
|
12494
12619
|
...retainedWorktree ? { worktree: retainedWorktree } : {},
|
|
12495
12620
|
...cleanup ? { cleanup: cleanupJson(cleanup) } : {}
|
|
12496
12621
|
};
|
|
12497
12622
|
spinner2?.stop();
|
|
12498
12623
|
if (!args.json) {
|
|
12499
|
-
p31.log.success(
|
|
12624
|
+
p31.log.success(
|
|
12625
|
+
remoteDropped ? `Dropped ${id}. Its ref sticks around briefly for undo, then is reaped.` : `Workspace ${id} was already ${view.workspace.status} on the server \u2014 nothing to drop remotely.`
|
|
12626
|
+
);
|
|
12500
12627
|
if (cleanup && !cleanup.error) reportCleanupHuman(cleanup);
|
|
12501
12628
|
else if (retainedWorktree) p31.log.info(`Retained checkout: ${retainedWorktree}`);
|
|
12502
12629
|
}
|
|
@@ -12565,7 +12692,7 @@ function formatEvent2(e, lands, actors) {
|
|
|
12565
12692
|
detail = `"${String(s.task ?? "")}" from ${short(s.baseOid)}`;
|
|
12566
12693
|
break;
|
|
12567
12694
|
case "workspace.synced":
|
|
12568
|
-
detail = `\u2192 ${short(s.headOid)}
|
|
12695
|
+
detail = `\u2192 ${short(s.headOid)}`;
|
|
12569
12696
|
break;
|
|
12570
12697
|
case "workspace.landed":
|
|
12571
12698
|
detail = `"${String(s.task ?? "")}" \u2192 ${short(s.landedOid)} on ${String(s.into ?? "trunk")}`;
|
|
@@ -13063,8 +13190,8 @@ function sameRefs(left, right) {
|
|
|
13063
13190
|
|
|
13064
13191
|
// src/cli/commands/update.ts
|
|
13065
13192
|
import * as p34 from "@clack/prompts";
|
|
13066
|
-
import { existsSync as
|
|
13067
|
-
import { join as join24, resolve as
|
|
13193
|
+
import { existsSync as existsSync24, readFileSync as readFileSync27, writeFileSync as writeFileSync10 } from "fs";
|
|
13194
|
+
import { join as join24, resolve as resolve20 } from "path";
|
|
13068
13195
|
var REGISTRY = "https://registry.npmjs.org/deepspace/latest";
|
|
13069
13196
|
var DOCS_URL = "https://documentation.deep.space/cli-reference/commands";
|
|
13070
13197
|
var OLDEST_SUPPORTED_MINOR = 12;
|
|
@@ -13079,18 +13206,18 @@ function gapIsTooWide(from, to) {
|
|
|
13079
13206
|
}
|
|
13080
13207
|
function readAppSdkVersion(appDir) {
|
|
13081
13208
|
const installed = join24(appDir, "node_modules", "deepspace", "package.json");
|
|
13082
|
-
if (
|
|
13083
|
-
const parsed2 = JSON.parse(
|
|
13209
|
+
if (existsSync24(installed)) {
|
|
13210
|
+
const parsed2 = JSON.parse(readFileSync27(installed, "utf8"));
|
|
13084
13211
|
if (typeof parsed2.version === "string") return parsed2.version;
|
|
13085
13212
|
}
|
|
13086
13213
|
const manifest = join24(appDir, "package.json");
|
|
13087
|
-
if (!
|
|
13088
|
-
const parsed = JSON.parse(
|
|
13214
|
+
if (!existsSync24(manifest)) return null;
|
|
13215
|
+
const parsed = JSON.parse(readFileSync27(manifest, "utf8"));
|
|
13089
13216
|
return parsed.dependencies?.deepspace ?? null;
|
|
13090
13217
|
}
|
|
13091
13218
|
function pinSdkVersion(appDir, version) {
|
|
13092
13219
|
const path = join24(appDir, "package.json");
|
|
13093
|
-
const source =
|
|
13220
|
+
const source = readFileSync27(path, "utf8");
|
|
13094
13221
|
const updated = source.replace(
|
|
13095
13222
|
/("deepspace"\s*:\s*")[^"]+(")/,
|
|
13096
13223
|
(_match, head, tail) => `${head}^${version}${tail}`
|
|
@@ -13155,7 +13282,7 @@ var update_default = defineDeepspaceCommand({
|
|
|
13155
13282
|
}
|
|
13156
13283
|
},
|
|
13157
13284
|
async run({ args }) {
|
|
13158
|
-
const appDir =
|
|
13285
|
+
const appDir = resolve20(".");
|
|
13159
13286
|
if (!hasWranglerConfig(appDir)) {
|
|
13160
13287
|
throw new Refusal(
|
|
13161
13288
|
"No wrangler.toml found \u2014 run this from inside a DeepSpace app directory.",
|
|
@@ -13235,9 +13362,9 @@ var update_default = defineDeepspaceCommand({
|
|
|
13235
13362
|
});
|
|
13236
13363
|
|
|
13237
13364
|
// src/cli/cli.ts
|
|
13238
|
-
var __dirname =
|
|
13239
|
-
var pkg = JSON.parse(
|
|
13240
|
-
if (process.platform === "linux" && !process.env.SSL_CERT_FILE &&
|
|
13365
|
+
var __dirname = dirname11(fileURLToPath3(import.meta.url));
|
|
13366
|
+
var pkg = JSON.parse(readFileSync28(join25(__dirname, "..", "package.json"), "utf-8"));
|
|
13367
|
+
if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync25("/etc/ssl/certs/ca-certificates.crt")) {
|
|
13241
13368
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
13242
13369
|
}
|
|
13243
13370
|
var create2 = defineCommand14({
|
|
@@ -13363,7 +13490,49 @@ function assertKnownCommandPath(argv) {
|
|
|
13363
13490
|
`${unknownCommandMessage(unknown)}
|
|
13364
13491
|
Run \`${unknown.helpPath.join(" ")} --help\` for the command list.`
|
|
13365
13492
|
);
|
|
13366
|
-
if (unknown.executable)
|
|
13493
|
+
if (unknown.executable) {
|
|
13494
|
+
printAction(executableAction({ cwd: process.cwd(), argv: unknown.suggestion }));
|
|
13495
|
+
}
|
|
13496
|
+
process.exit(1);
|
|
13497
|
+
}
|
|
13498
|
+
function selectedLeaf(argv) {
|
|
13499
|
+
let command = main;
|
|
13500
|
+
let remaining = argv;
|
|
13501
|
+
const path = [];
|
|
13502
|
+
const commandPrefixArgs = [];
|
|
13503
|
+
for (; ; ) {
|
|
13504
|
+
const subCommands = command.subCommands;
|
|
13505
|
+
if (!subCommands || Object.keys(subCommands).length === 0) {
|
|
13506
|
+
return { command, rawArgs: [...commandPrefixArgs, ...remaining], path };
|
|
13507
|
+
}
|
|
13508
|
+
const index = remaining.findIndex((argument) => !argument.startsWith("-"));
|
|
13509
|
+
if (index < 0) return { command, rawArgs: [...commandPrefixArgs, ...remaining], path };
|
|
13510
|
+
const name = remaining[index];
|
|
13511
|
+
const entry = Object.entries(subCommands).find(([key, child]) => {
|
|
13512
|
+
if (key === name) return true;
|
|
13513
|
+
const alias = child.meta?.alias;
|
|
13514
|
+
return (Array.isArray(alias) ? alias : alias ? [alias] : []).includes(name);
|
|
13515
|
+
});
|
|
13516
|
+
if (!entry) return null;
|
|
13517
|
+
commandPrefixArgs.push(...remaining.slice(0, index));
|
|
13518
|
+
path.push(entry[0]);
|
|
13519
|
+
command = entry[1];
|
|
13520
|
+
remaining = remaining.slice(index + 1);
|
|
13521
|
+
}
|
|
13522
|
+
}
|
|
13523
|
+
function assertKnownOptions(argv) {
|
|
13524
|
+
const selected = selectedLeaf(argv);
|
|
13525
|
+
if (!selected || selected.path.join(" ") === "app create") return;
|
|
13526
|
+
const args = selected.command.args ?? {};
|
|
13527
|
+
const unknown = unknownOptionNamesFromRaw(selected.rawArgs, args);
|
|
13528
|
+
if (unknown.length === 0) return;
|
|
13529
|
+
const commandName = selected.path.join(" ") || "deepspace";
|
|
13530
|
+
const message = unknownOptionMessage(commandName, unknown, args);
|
|
13531
|
+
if (argv.includes("--json")) {
|
|
13532
|
+
console.log(JSON.stringify({ ok: false, code: "unknown_option", error: message }));
|
|
13533
|
+
} else {
|
|
13534
|
+
console.error(withSlug(message, "unknown_option"));
|
|
13535
|
+
}
|
|
13367
13536
|
process.exit(1);
|
|
13368
13537
|
}
|
|
13369
13538
|
var rawArgs = process.argv.slice(2);
|
|
@@ -13377,6 +13546,9 @@ if (DEEPSPACE_ENV === "invalid") {
|
|
|
13377
13546
|
}
|
|
13378
13547
|
process.exit(1);
|
|
13379
13548
|
}
|
|
13549
|
+
if (!wantsHelp && !(rawArgs.length === 1 && ["--version", "-v"].includes(rawArgs[0]))) {
|
|
13550
|
+
assertKnownOptions(rawArgs);
|
|
13551
|
+
}
|
|
13380
13552
|
if (rawArgs.includes("--json") && !wantsHelp) {
|
|
13381
13553
|
const unknown = findUnknownCommand(rawArgs, commandTree());
|
|
13382
13554
|
if (unknown) {
|
|
@@ -13385,7 +13557,7 @@ if (rawArgs.includes("--json") && !wantsHelp) {
|
|
|
13385
13557
|
ok: false,
|
|
13386
13558
|
code: "unknown_command",
|
|
13387
13559
|
error: unknownCommandMessage(unknown),
|
|
13388
|
-
...unknown.executable ? { action: { cwd: process.cwd(), argv: unknown.suggestion } } : {}
|
|
13560
|
+
...unknown.executable ? { action: executableAction({ cwd: process.cwd(), argv: unknown.suggestion }) } : {}
|
|
13389
13561
|
})
|
|
13390
13562
|
);
|
|
13391
13563
|
process.exit(1);
|