deepspace 0.18.0 → 0.19.1
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 +47 -0
- package/README.md +21 -0
- package/dist/cli.js +889 -323
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +26 -12
- package/dist/index.js +100 -27
- package/dist/index.js.map +1 -1
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +51 -15
- package/dist/server.js +212 -61
- package/dist/server.js.map +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +28 -25
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +69 -10
- package/dist/worker.js +251 -61
- package/dist/worker.js.map +1 -1
- package/features/admin-page/src/AdminPage.tsx +4 -4
- package/features/documentation/feature.json +2 -1
- package/features/documents/feature.json +3 -2
- package/features/documents/src/DocumentsEditorPage.tsx +47 -62
- package/features/documents/src/DocumentsListPage.tsx +59 -26
- package/features/documents/src/InviteDialog.tsx +49 -14
- package/features/documents/src/LibrarySidebar.tsx +92 -84
- package/features/documents/src/__tests__/documents-presence-access.test.ts +15 -2
- package/features/documents/src/documents-library-types.ts +6 -0
- package/features/documents/src/editor/useDocEditor.ts +1 -1
- package/features/documents/src/use-documents-presence-access.ts +1 -3
- package/features/tasks/src/TasksPage.tsx +6 -3
- package/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
renderUsage
|
|
9
9
|
} from "citty";
|
|
10
10
|
import { sync as spawnSync8 } from "cross-spawn";
|
|
11
|
-
import { readFileSync as readFileSync28, existsSync as
|
|
12
|
-
import { dirname as
|
|
11
|
+
import { readFileSync as readFileSync28, existsSync as existsSync26 } from "fs";
|
|
12
|
+
import { dirname as dirname12, join as join24 } from "path";
|
|
13
13
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14
14
|
|
|
15
15
|
// src/cli/lib/spinner.ts
|
|
@@ -205,6 +205,13 @@ var InputError = class extends Error {
|
|
|
205
205
|
this.code = code;
|
|
206
206
|
}
|
|
207
207
|
};
|
|
208
|
+
var CliExit = class extends Error {
|
|
209
|
+
constructor(exitCode) {
|
|
210
|
+
super(`exit ${exitCode}`);
|
|
211
|
+
this.exitCode = exitCode;
|
|
212
|
+
this.name = "CliExit";
|
|
213
|
+
}
|
|
214
|
+
};
|
|
208
215
|
function errorCode(err) {
|
|
209
216
|
if (err instanceof ApiError || err instanceof InputError) return err.code;
|
|
210
217
|
if (err instanceof Error && err.name === "GitError") return err.code ?? "git_error";
|
|
@@ -230,12 +237,17 @@ function formatCliError(err) {
|
|
|
230
237
|
${hint}` : message;
|
|
231
238
|
}
|
|
232
239
|
function renderCliError(err) {
|
|
240
|
+
if (err instanceof CliExit) {
|
|
241
|
+
process.exitCode = err.exitCode;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
233
244
|
stopActiveSpinner();
|
|
234
245
|
const message = formatCliError(err);
|
|
235
246
|
const slug = errorCode(err);
|
|
236
247
|
if (process.argv.includes("--json")) {
|
|
237
248
|
console.log(JSON.stringify({ ok: false, ...slug ? { code: slug } : {}, error: message }));
|
|
238
|
-
process.
|
|
249
|
+
process.exitCode = 1;
|
|
250
|
+
return;
|
|
239
251
|
}
|
|
240
252
|
console.error(slug ? withSlug(message, slug) : message);
|
|
241
253
|
if (process.env.DEBUG) {
|
|
@@ -244,7 +256,7 @@ function renderCliError(err) {
|
|
|
244
256
|
API ${apiPath}${status3 ? ` (${status3})` : ""}`);
|
|
245
257
|
if (err instanceof Error && err.stack) console.error("\n" + err.stack);
|
|
246
258
|
}
|
|
247
|
-
process.
|
|
259
|
+
process.exitCode = 1;
|
|
248
260
|
}
|
|
249
261
|
function wrapCommandErrors(cmd) {
|
|
250
262
|
const run2 = cmd.run;
|
|
@@ -301,10 +313,9 @@ function withoutReservedKeys(payload) {
|
|
|
301
313
|
)
|
|
302
314
|
);
|
|
303
315
|
}
|
|
304
|
-
function
|
|
305
|
-
|
|
306
|
-
process.
|
|
307
|
-
return void 0;
|
|
316
|
+
function finishCommand(code) {
|
|
317
|
+
stopActiveSpinner();
|
|
318
|
+
process.exitCode = code;
|
|
308
319
|
}
|
|
309
320
|
function nameVariants(name) {
|
|
310
321
|
return [
|
|
@@ -342,7 +353,7 @@ function defineDeepspaceCommand(def) {
|
|
|
342
353
|
...def.args ?? {},
|
|
343
354
|
json: {
|
|
344
355
|
type: "boolean",
|
|
345
|
-
description: "Emit a single-line JSON result for scripts/agents",
|
|
356
|
+
description: def.jsonDescription ?? "Emit a single-line JSON result for scripts/agents",
|
|
346
357
|
default: false
|
|
347
358
|
}
|
|
348
359
|
};
|
|
@@ -398,9 +409,9 @@ function defineDeepspaceCommand(def) {
|
|
|
398
409
|
else p3.log.error(line);
|
|
399
410
|
if (refusal?.action) printAction(refusal.action);
|
|
400
411
|
}
|
|
401
|
-
|
|
412
|
+
finishCommand(refusal?.actionRequired ? 2 : 1);
|
|
402
413
|
}
|
|
403
|
-
if (succeeded)
|
|
414
|
+
if (succeeded) finishCommand(0);
|
|
404
415
|
}
|
|
405
416
|
});
|
|
406
417
|
}
|
|
@@ -515,7 +526,7 @@ function effectivePlatformUrls(env = process.env) {
|
|
|
515
526
|
var DASHBOARD_URL = DEEPSPACE_ENV === "production" ? "https://dashboard.deep.space" : DEEPSPACE_ENV === "staging" ? "https://dashboard.deepspacesites.com" : "http://127.0.0.1:9";
|
|
516
527
|
|
|
517
528
|
// src/cli/auth.ts
|
|
518
|
-
import { readFileSync as readFileSync2,
|
|
529
|
+
import { readFileSync as readFileSync2, existsSync, mkdirSync } from "fs";
|
|
519
530
|
import { join } from "path";
|
|
520
531
|
import { homedir } from "os";
|
|
521
532
|
|
|
@@ -543,6 +554,16 @@ async function exchangeSession(authUrl, sessionToken) {
|
|
|
543
554
|
return data.token ?? null;
|
|
544
555
|
}
|
|
545
556
|
|
|
557
|
+
// src/cli/lib/secure-file.ts
|
|
558
|
+
import { chmodSync, writeFileSync } from "fs";
|
|
559
|
+
function writeSecretFileSync(path, content) {
|
|
560
|
+
writeFileSync(path, content, { mode: 384 });
|
|
561
|
+
try {
|
|
562
|
+
chmodSync(path, 384);
|
|
563
|
+
} catch {
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
546
567
|
// src/cli/auth.ts
|
|
547
568
|
var AUTH_URL = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
|
|
548
569
|
var PROD_AUTH_URL = "https://auth.deep.space";
|
|
@@ -585,7 +606,7 @@ async function ensureToken() {
|
|
|
585
606
|
);
|
|
586
607
|
}
|
|
587
608
|
mkdirSync(DIR, { recursive: true, mode: 448 });
|
|
588
|
-
|
|
609
|
+
writeSecretFileSync(TOKEN_PATH, token);
|
|
589
610
|
return token;
|
|
590
611
|
}
|
|
591
612
|
function isTokenValid(jwt) {
|
|
@@ -603,7 +624,7 @@ registerAuthRefresh(async () => {
|
|
|
603
624
|
const token = await exchangeSession(AUTH_URL, sessionToken);
|
|
604
625
|
if (!token) return null;
|
|
605
626
|
mkdirSync(DIR, { recursive: true, mode: 448 });
|
|
606
|
-
|
|
627
|
+
writeSecretFileSync(TOKEN_PATH, token);
|
|
607
628
|
return token;
|
|
608
629
|
} catch {
|
|
609
630
|
return null;
|
|
@@ -611,19 +632,18 @@ registerAuthRefresh(async () => {
|
|
|
611
632
|
});
|
|
612
633
|
|
|
613
634
|
// src/cli/commands/login.ts
|
|
614
|
-
import {
|
|
615
|
-
import {
|
|
616
|
-
import { homedir as homedir2 } from "os";
|
|
635
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
636
|
+
import { dirname as dirname2 } from "path";
|
|
617
637
|
import { randomBytes, createHash } from "crypto";
|
|
618
638
|
import * as p4 from "@clack/prompts";
|
|
619
639
|
|
|
620
640
|
// src/cli/lib/open-browser.ts
|
|
621
|
-
import {
|
|
641
|
+
import { spawn } from "child_process";
|
|
622
642
|
function openBrowser(url) {
|
|
623
643
|
const quoted = JSON.stringify(url);
|
|
624
644
|
const cmd = process.platform === "darwin" ? `open ${quoted}` : process.platform === "win32" ? `start "" ${quoted}` : `xdg-open ${quoted}`;
|
|
625
|
-
|
|
626
|
-
});
|
|
645
|
+
spawn(cmd, { shell: true, detached: true, stdio: "ignore", windowsHide: true }).on("error", () => {
|
|
646
|
+
}).unref();
|
|
627
647
|
}
|
|
628
648
|
|
|
629
649
|
// src/cli/commands/login.ts
|
|
@@ -716,9 +736,13 @@ var login_default = defineDeepspaceCommand({
|
|
|
716
736
|
if (!json) s.start("Waiting for authentication...");
|
|
717
737
|
const result = await pollForCompletion(sessionId, codeVerifier);
|
|
718
738
|
if (!result) {
|
|
719
|
-
throw new Refusal(
|
|
720
|
-
|
|
721
|
-
|
|
739
|
+
throw new Refusal(
|
|
740
|
+
"Login timed out. Run `deepspace auth login` to try again.",
|
|
741
|
+
"login_timeout",
|
|
742
|
+
{
|
|
743
|
+
action: cliAction("deepspace", "auth", "login")
|
|
744
|
+
}
|
|
745
|
+
);
|
|
722
746
|
}
|
|
723
747
|
storeCredentials(result.sessionToken, result.jwt);
|
|
724
748
|
await provisionProfile(result.jwt);
|
|
@@ -798,11 +822,10 @@ async function pollForCompletion(sessionId, codeVerifier) {
|
|
|
798
822
|
return null;
|
|
799
823
|
}
|
|
800
824
|
function storeCredentials(sessionToken, jwt) {
|
|
801
|
-
const dir = join2(homedir2(), ".deepspace");
|
|
802
|
-
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
803
825
|
const { sessionPath, tokenPath } = credentialPaths(AUTH_URL2);
|
|
804
|
-
|
|
805
|
-
|
|
826
|
+
mkdirSync2(dirname2(sessionPath), { recursive: true, mode: 448 });
|
|
827
|
+
writeSecretFileSync(sessionPath, sessionToken);
|
|
828
|
+
writeSecretFileSync(tokenPath, jwt);
|
|
806
829
|
}
|
|
807
830
|
async function provisionProfile(jwt) {
|
|
808
831
|
try {
|
|
@@ -883,8 +906,8 @@ var logout_default = defineDeepspaceCommand({
|
|
|
883
906
|
});
|
|
884
907
|
|
|
885
908
|
// src/cli/lib/app-identity.ts
|
|
886
|
-
import { existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as
|
|
887
|
-
import { join as
|
|
909
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
910
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
888
911
|
import { parse as parseToml } from "smol-toml";
|
|
889
912
|
|
|
890
913
|
// src/server/utils/registry-client.ts
|
|
@@ -919,7 +942,7 @@ function mintAppId(now = Date.now()) {
|
|
|
919
942
|
return `app_${mintUlid(now)}`;
|
|
920
943
|
}
|
|
921
944
|
function readWranglerIdentityConfig(cwd) {
|
|
922
|
-
const wranglerPath =
|
|
945
|
+
const wranglerPath = join2(resolve2(cwd), "wrangler.toml");
|
|
923
946
|
if (!existsSync3(wranglerPath)) return null;
|
|
924
947
|
try {
|
|
925
948
|
return parseToml(readFileSync5(wranglerPath, "utf-8"));
|
|
@@ -938,7 +961,7 @@ function readAppId(cwd = process.cwd(), wranglerEnv) {
|
|
|
938
961
|
return typeof id === "string" && APP_ID_RE.test(id) ? id : null;
|
|
939
962
|
}
|
|
940
963
|
function writeAppId(cwd, appId, opts = {}) {
|
|
941
|
-
const wranglerPath =
|
|
964
|
+
const wranglerPath = join2(resolve2(cwd), "wrangler.toml");
|
|
942
965
|
if (!existsSync3(wranglerPath)) {
|
|
943
966
|
throw new Error(`No wrangler.toml in ${resolve2(cwd)}`);
|
|
944
967
|
}
|
|
@@ -971,19 +994,19 @@ ${header}
|
|
|
971
994
|
${line}
|
|
972
995
|
`;
|
|
973
996
|
}
|
|
974
|
-
|
|
997
|
+
writeFileSync2(wranglerPath, src);
|
|
975
998
|
}
|
|
976
999
|
|
|
977
1000
|
// src/cli/commands/dev.ts
|
|
978
|
-
import { resolve as resolve8, basename as basename2, join as
|
|
979
|
-
import
|
|
1001
|
+
import { resolve as resolve8, basename as basename2, join as join11 } from "path";
|
|
1002
|
+
import spawn2 from "cross-spawn";
|
|
980
1003
|
|
|
981
1004
|
// src/cli/lib/app-context.ts
|
|
982
1005
|
import { existsSync as existsSync4, readFileSync as readFileSync6, readdirSync } from "fs";
|
|
983
|
-
import { dirname as
|
|
1006
|
+
import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
|
|
984
1007
|
import { parse as parseToml2 } from "smol-toml";
|
|
985
1008
|
function detectAppName(cwd = process.cwd()) {
|
|
986
|
-
const wranglerPath =
|
|
1009
|
+
const wranglerPath = join3(resolve3(cwd), "wrangler.toml");
|
|
987
1010
|
if (!existsSync4(wranglerPath)) return null;
|
|
988
1011
|
try {
|
|
989
1012
|
const config = parseToml2(readFileSync6(wranglerPath, "utf-8"));
|
|
@@ -998,15 +1021,15 @@ function detectAppName(cwd = process.cwd()) {
|
|
|
998
1021
|
function findAppDir(start = process.cwd()) {
|
|
999
1022
|
let dir = resolve3(start);
|
|
1000
1023
|
for (; ; ) {
|
|
1001
|
-
if (existsSync4(
|
|
1002
|
-
const parent =
|
|
1024
|
+
if (existsSync4(join3(dir, "wrangler.toml"))) return dir;
|
|
1025
|
+
const parent = dirname3(dir);
|
|
1003
1026
|
if (parent === dir) return null;
|
|
1004
1027
|
dir = parent;
|
|
1005
1028
|
}
|
|
1006
1029
|
}
|
|
1007
1030
|
function findChildApps(dir) {
|
|
1008
1031
|
try {
|
|
1009
|
-
return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync4(
|
|
1032
|
+
return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync4(join3(dir, entry.name, "wrangler.toml"))).map((entry) => entry.name).sort();
|
|
1010
1033
|
} catch {
|
|
1011
1034
|
return [];
|
|
1012
1035
|
}
|
|
@@ -1017,16 +1040,16 @@ import {
|
|
|
1017
1040
|
existsSync as existsSync5,
|
|
1018
1041
|
readFileSync as readFileSync8,
|
|
1019
1042
|
realpathSync as realpathSync2,
|
|
1020
|
-
writeFileSync as
|
|
1043
|
+
writeFileSync as writeFileSync3,
|
|
1021
1044
|
mkdirSync as mkdirSync4,
|
|
1022
1045
|
renameSync,
|
|
1023
1046
|
unlinkSync
|
|
1024
1047
|
} from "fs";
|
|
1025
|
-
import { join as
|
|
1048
|
+
import { join as join5, resolve as resolve5, dirname as dirname5, basename, isAbsolute as isAbsolute2, sep } from "path";
|
|
1026
1049
|
|
|
1027
1050
|
// src/cli/lib/git/repository.ts
|
|
1028
1051
|
import { appendFileSync, mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
|
|
1029
|
-
import { dirname as
|
|
1052
|
+
import { dirname as dirname4, join as join4, resolve as resolve4 } from "path";
|
|
1030
1053
|
|
|
1031
1054
|
// src/cli/lib/git/process.ts
|
|
1032
1055
|
import { sync as spawnSync } from "cross-spawn";
|
|
@@ -1230,7 +1253,7 @@ function listWorktrees(cwd) {
|
|
|
1230
1253
|
}
|
|
1231
1254
|
function ensureLocalExclude(cwd, pattern) {
|
|
1232
1255
|
const commonDir = resolve4(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
|
|
1233
|
-
const excludePath =
|
|
1256
|
+
const excludePath = join4(commonDir, "info", "exclude");
|
|
1234
1257
|
let current = "";
|
|
1235
1258
|
try {
|
|
1236
1259
|
current = readFileSync7(excludePath, "utf-8");
|
|
@@ -1238,7 +1261,7 @@ function ensureLocalExclude(cwd, pattern) {
|
|
|
1238
1261
|
}
|
|
1239
1262
|
if (current.split("\n").some((line) => line.trim() === pattern)) return;
|
|
1240
1263
|
try {
|
|
1241
|
-
mkdirSync3(
|
|
1264
|
+
mkdirSync3(dirname4(excludePath), { recursive: true });
|
|
1242
1265
|
appendFileSync(
|
|
1243
1266
|
excludePath,
|
|
1244
1267
|
`${current.endsWith("\n") || current === "" ? "" : "\n"}${pattern}
|
|
@@ -1250,9 +1273,9 @@ function ensureLocalExclude(cwd, pattern) {
|
|
|
1250
1273
|
|
|
1251
1274
|
// src/cli/lib/launch-config.ts
|
|
1252
1275
|
function writeLaunchFile(launchPath, config) {
|
|
1253
|
-
mkdirSync4(
|
|
1276
|
+
mkdirSync4(dirname5(launchPath), { recursive: true });
|
|
1254
1277
|
const tmpPath = `${launchPath}.${process.pid}.tmp`;
|
|
1255
|
-
|
|
1278
|
+
writeFileSync3(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
1256
1279
|
try {
|
|
1257
1280
|
renameSync(tmpPath, launchPath);
|
|
1258
1281
|
} catch (err) {
|
|
@@ -1273,7 +1296,7 @@ function readLaunchFile(launchPath) {
|
|
|
1273
1296
|
return parsed;
|
|
1274
1297
|
}
|
|
1275
1298
|
function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
|
|
1276
|
-
const launchPath =
|
|
1299
|
+
const launchPath = join5(appDir, ".claude", "launch.json");
|
|
1277
1300
|
try {
|
|
1278
1301
|
const config = readLaunchFile(launchPath);
|
|
1279
1302
|
if (!config) return;
|
|
@@ -1314,7 +1337,7 @@ function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
|
|
|
1314
1337
|
}
|
|
1315
1338
|
function resolveAppLaunchPort(appDir) {
|
|
1316
1339
|
try {
|
|
1317
|
-
const config = readLaunchFile(
|
|
1340
|
+
const config = readLaunchFile(join5(appDir, ".claude", "launch.json"));
|
|
1318
1341
|
if (!config) return null;
|
|
1319
1342
|
const appName = detectAppName(appDir) ?? basename(appDir);
|
|
1320
1343
|
const entry = config.configurations.find((c) => c?.name === appName) ?? (config.configurations.length === 1 ? config.configurations[0] : void 0);
|
|
@@ -1331,10 +1354,10 @@ function detectClaudeWorktree(appDir) {
|
|
|
1331
1354
|
listWorktrees(appDir).map((worktree) => canonicalPath(worktree.path))
|
|
1332
1355
|
);
|
|
1333
1356
|
if (!registered.has(worktreeRoot)) return null;
|
|
1334
|
-
const worktreesDir =
|
|
1335
|
-
const claudeDir =
|
|
1357
|
+
const worktreesDir = dirname5(worktreeRoot);
|
|
1358
|
+
const claudeDir = dirname5(worktreesDir);
|
|
1336
1359
|
if (basename(worktreesDir) !== "worktrees" || basename(claudeDir) !== ".claude") return null;
|
|
1337
|
-
const ownerRoot = canonicalPath(
|
|
1360
|
+
const ownerRoot = canonicalPath(dirname5(claudeDir));
|
|
1338
1361
|
if (!registered.has(ownerRoot)) return null;
|
|
1339
1362
|
return { mainRepoRoot: ownerRoot, worktreeName: basename(worktreeRoot) };
|
|
1340
1363
|
} catch {
|
|
@@ -1368,12 +1391,12 @@ function deriveWorktreePort(worktreeKey) {
|
|
|
1368
1391
|
return WORKTREE_PORT_MIN + hash % WORKTREE_PORT_RANGE;
|
|
1369
1392
|
}
|
|
1370
1393
|
function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
|
|
1371
|
-
const launchPath =
|
|
1394
|
+
const launchPath = join5(mainRepoRoot, ".claude", "launch.json");
|
|
1372
1395
|
const entryName = WORKTREE_ENTRY_PREFIX + worktreeName;
|
|
1373
1396
|
try {
|
|
1374
1397
|
const config = readLaunchFile(launchPath);
|
|
1375
1398
|
if (!config) return null;
|
|
1376
|
-
const worktreesRoot =
|
|
1399
|
+
const worktreesRoot = join5(mainRepoRoot, ".claude", "worktrees") + sep;
|
|
1377
1400
|
config.configurations = config.configurations.filter((c) => {
|
|
1378
1401
|
if (c?.name === entryName) return false;
|
|
1379
1402
|
const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute2(c.cwd) && c.cwd.startsWith(worktreesRoot) && !existsSync5(c.cwd);
|
|
@@ -1411,7 +1434,7 @@ function resolveWorktreePort(appDir) {
|
|
|
1411
1434
|
const claude = detectClaudeWorktree(appDir);
|
|
1412
1435
|
if (claude) {
|
|
1413
1436
|
try {
|
|
1414
|
-
const config = readLaunchFile(
|
|
1437
|
+
const config = readLaunchFile(join5(claude.mainRepoRoot, ".claude", "launch.json"));
|
|
1415
1438
|
const entry = config?.configurations.find(
|
|
1416
1439
|
(c) => c?.name === WORKTREE_ENTRY_PREFIX + claude.worktreeName
|
|
1417
1440
|
);
|
|
@@ -1472,19 +1495,9 @@ async function fetchAppIdentityToken(deployUrl, callerJwt, appId) {
|
|
|
1472
1495
|
return body.token;
|
|
1473
1496
|
}
|
|
1474
1497
|
|
|
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 {
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
1498
|
// src/cli/lib/wrangler-env.ts
|
|
1486
|
-
import { existsSync as existsSync6, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as
|
|
1487
|
-
import { join as
|
|
1499
|
+
import { existsSync as existsSync6, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1500
|
+
import { join as join6 } from "path";
|
|
1488
1501
|
import { parse as parseToml3, stringify as stringifyToml } from "smol-toml";
|
|
1489
1502
|
|
|
1490
1503
|
// src/server/rooms/app-name.ts
|
|
@@ -1585,7 +1598,7 @@ function resolveAppNameForEnv(config, envName) {
|
|
|
1585
1598
|
return resolveAppName(envBlock.name);
|
|
1586
1599
|
}
|
|
1587
1600
|
function devVarsPathFor(appDir) {
|
|
1588
|
-
return
|
|
1601
|
+
return join6(appDir, ".dev.vars");
|
|
1589
1602
|
}
|
|
1590
1603
|
function prepareWranglerEnvConfig(appDir, envName) {
|
|
1591
1604
|
if (!envName) return { cleanup() {
|
|
@@ -1594,11 +1607,11 @@ function prepareWranglerEnvConfig(appDir, envName) {
|
|
|
1594
1607
|
const resolved = resolveAppNameForEnv(config, envName);
|
|
1595
1608
|
if (!resolved.ok) throw new Error(resolved.reason);
|
|
1596
1609
|
const flattened = flattenWranglerEnvConfig(config, envName);
|
|
1597
|
-
const configPath =
|
|
1610
|
+
const configPath = join6(
|
|
1598
1611
|
appDir,
|
|
1599
1612
|
`.wrangler.deepspace.${safeGeneratedConfigSegment(envName)}.${process.pid}.${++generatedConfigSeq}.toml`
|
|
1600
1613
|
);
|
|
1601
|
-
|
|
1614
|
+
writeFileSync4(
|
|
1602
1615
|
configPath,
|
|
1603
1616
|
[
|
|
1604
1617
|
"# Generated by DeepSpace for this CLI run.",
|
|
@@ -1660,7 +1673,7 @@ var WranglerConfigError = class extends Error {
|
|
|
1660
1673
|
}
|
|
1661
1674
|
};
|
|
1662
1675
|
function readWranglerConfig(appDir) {
|
|
1663
|
-
const path =
|
|
1676
|
+
const path = join6(appDir, "wrangler.toml");
|
|
1664
1677
|
const raw = readFileSync9(path, "utf-8");
|
|
1665
1678
|
try {
|
|
1666
1679
|
return parseToml3(raw);
|
|
@@ -1669,7 +1682,7 @@ function readWranglerConfig(appDir) {
|
|
|
1669
1682
|
}
|
|
1670
1683
|
}
|
|
1671
1684
|
function hasWranglerConfig(appDir) {
|
|
1672
|
-
return existsSync6(
|
|
1685
|
+
return existsSync6(join6(appDir, "wrangler.toml"));
|
|
1673
1686
|
}
|
|
1674
1687
|
|
|
1675
1688
|
// src/cli/lib/dev-vars.ts
|
|
@@ -1712,21 +1725,21 @@ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {})
|
|
|
1712
1725
|
|
|
1713
1726
|
// src/cli/lib/install-status.ts
|
|
1714
1727
|
import { existsSync as existsSync8, readFileSync as readFileSync11, realpathSync as realpathSync3 } from "fs";
|
|
1715
|
-
import { dirname as
|
|
1728
|
+
import { dirname as dirname6, join as join8, resolve as resolve6 } from "path";
|
|
1716
1729
|
|
|
1717
1730
|
// src/cli/lib/package-manager.ts
|
|
1718
1731
|
import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
|
|
1719
|
-
import { join as
|
|
1732
|
+
import { join as join7 } from "path";
|
|
1720
1733
|
function detectPackageManager(appDir) {
|
|
1721
1734
|
try {
|
|
1722
|
-
const pkg2 = JSON.parse(readFileSync10(
|
|
1735
|
+
const pkg2 = JSON.parse(readFileSync10(join7(appDir, "package.json"), "utf-8"));
|
|
1723
1736
|
const name = typeof pkg2.packageManager === "string" ? pkg2.packageManager.split("@")[0] : "";
|
|
1724
1737
|
if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") return name;
|
|
1725
1738
|
} catch {
|
|
1726
1739
|
}
|
|
1727
|
-
if (existsSync7(
|
|
1728
|
-
if (existsSync7(
|
|
1729
|
-
if (existsSync7(
|
|
1740
|
+
if (existsSync7(join7(appDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
1741
|
+
if (existsSync7(join7(appDir, "yarn.lock"))) return "yarn";
|
|
1742
|
+
if (existsSync7(join7(appDir, "bun.lock")) || existsSync7(join7(appDir, "bun.lockb"))) return "bun";
|
|
1730
1743
|
return "npm";
|
|
1731
1744
|
}
|
|
1732
1745
|
function installCommand(appDir) {
|
|
@@ -1736,10 +1749,10 @@ function installCommand(appDir) {
|
|
|
1736
1749
|
// src/cli/lib/install-status.ts
|
|
1737
1750
|
function installState(appDir) {
|
|
1738
1751
|
if (resolvesDeepspace(appDir)) return "ready";
|
|
1739
|
-
const sentinelDir =
|
|
1740
|
-
if (existsSync8(
|
|
1741
|
-
if (existsSync8(
|
|
1742
|
-
if (!existsSync8(
|
|
1752
|
+
const sentinelDir = join8(appDir, ".deepspace");
|
|
1753
|
+
if (existsSync8(join8(sentinelDir, "install.err"))) return "failed";
|
|
1754
|
+
if (existsSync8(join8(sentinelDir, "install.started"))) {
|
|
1755
|
+
if (!existsSync8(join8(sentinelDir, "install.done")) && installWorkerAlive(sentinelDir)) {
|
|
1743
1756
|
return "installing";
|
|
1744
1757
|
}
|
|
1745
1758
|
return "failed";
|
|
@@ -1751,11 +1764,11 @@ function tailHint(logPath, platform = process.platform) {
|
|
|
1751
1764
|
}
|
|
1752
1765
|
function ensureInstallReady(appDir) {
|
|
1753
1766
|
if (resolvesDeepspace(appDir)) return;
|
|
1754
|
-
const sentinelDir =
|
|
1755
|
-
const errPath =
|
|
1756
|
-
const startedPath =
|
|
1757
|
-
const donePath =
|
|
1758
|
-
const logPath =
|
|
1767
|
+
const sentinelDir = join8(appDir, ".deepspace");
|
|
1768
|
+
const errPath = join8(sentinelDir, "install.err");
|
|
1769
|
+
const startedPath = join8(sentinelDir, "install.started");
|
|
1770
|
+
const donePath = join8(sentinelDir, "install.done");
|
|
1771
|
+
const logPath = join8(sentinelDir, "install.log");
|
|
1759
1772
|
if (existsSync8(errPath)) {
|
|
1760
1773
|
const detail = readFileSync11(errPath, "utf-8").trimEnd();
|
|
1761
1774
|
const log29 = existsSync8(logPath) ? ` Full log: ${logPath}.` : "";
|
|
@@ -1796,9 +1809,9 @@ function resolvesDeepspace(fromDir) {
|
|
|
1796
1809
|
} catch {
|
|
1797
1810
|
}
|
|
1798
1811
|
for (; ; ) {
|
|
1799
|
-
if (existsSync8(
|
|
1812
|
+
if (existsSync8(join8(dir, "node_modules", "deepspace", "package.json"))) return true;
|
|
1800
1813
|
if (dir === checkoutRoot) return false;
|
|
1801
|
-
const parent =
|
|
1814
|
+
const parent = dirname6(dir);
|
|
1802
1815
|
if (parent === dir) return false;
|
|
1803
1816
|
dir = parent;
|
|
1804
1817
|
}
|
|
@@ -1811,7 +1824,7 @@ function canonicalPath2(path) {
|
|
|
1811
1824
|
}
|
|
1812
1825
|
}
|
|
1813
1826
|
function installWorkerAlive(sentinelDir) {
|
|
1814
|
-
const pidPath =
|
|
1827
|
+
const pidPath = join8(sentinelDir, "install.pid");
|
|
1815
1828
|
if (!existsSync8(pidPath)) return false;
|
|
1816
1829
|
const pid = Number(readFileSync11(pidPath, "utf-8").trim());
|
|
1817
1830
|
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
@@ -1864,7 +1877,7 @@ function preflightWindowsWorkerd(appDir) {
|
|
|
1864
1877
|
|
|
1865
1878
|
// src/cli/lib/macos-junk.ts
|
|
1866
1879
|
import { readdirSync as readdirSync2, rmSync as rmSync3 } from "fs";
|
|
1867
|
-
import { join as
|
|
1880
|
+
import { join as join9 } from "path";
|
|
1868
1881
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".wrangler", ".vite"]);
|
|
1869
1882
|
function removeMacosJunk(dir) {
|
|
1870
1883
|
let removed = 0;
|
|
@@ -1875,7 +1888,7 @@ function removeMacosJunk(dir) {
|
|
|
1875
1888
|
return removed;
|
|
1876
1889
|
}
|
|
1877
1890
|
for (const entry of entries) {
|
|
1878
|
-
const path =
|
|
1891
|
+
const path = join9(dir, entry.name);
|
|
1879
1892
|
if (entry.isDirectory()) {
|
|
1880
1893
|
if (!SKIP_DIRS.has(entry.name)) removed += removeMacosJunk(path);
|
|
1881
1894
|
continue;
|
|
@@ -2373,7 +2386,7 @@ async function refreshSecretsCache(deployUrl, token, appId, wranglerEnv, selecte
|
|
|
2373
2386
|
|
|
2374
2387
|
// src/cli/lib/schema-lint.ts
|
|
2375
2388
|
import { existsSync as existsSync10 } from "fs";
|
|
2376
|
-
import { join as
|
|
2389
|
+
import { join as join10 } from "path";
|
|
2377
2390
|
import { pathToFileURL } from "url";
|
|
2378
2391
|
|
|
2379
2392
|
// src/server/schemas/registry.ts
|
|
@@ -2412,7 +2425,7 @@ function lintSchema(schema) {
|
|
|
2412
2425
|
if (schema.ownerField) {
|
|
2413
2426
|
const col = schema.columns.find((c) => c.name === schema.ownerField);
|
|
2414
2427
|
const spoofableRoles = Object.entries(schema.permissions).filter(
|
|
2415
|
-
([role, permissions]) => role !== "owner" && role !== "admin" && permissions.create === true
|
|
2428
|
+
([role, permissions]) => role !== "owner" && role !== "admin" && permissions.create === true && permissions.update !== "unclaimed-or-own"
|
|
2416
2429
|
).map(([role]) => role);
|
|
2417
2430
|
if (col && !col.userBound && spoofableRoles.length > 0) {
|
|
2418
2431
|
warnings.push(
|
|
@@ -2432,12 +2445,12 @@ function lintSchema(schema) {
|
|
|
2432
2445
|
|
|
2433
2446
|
// src/cli/lib/schema-lint.ts
|
|
2434
2447
|
async function lintProjectSchemas(appDir) {
|
|
2435
|
-
const schemasPath =
|
|
2448
|
+
const schemasPath = join10(appDir, "src", "schemas.ts");
|
|
2436
2449
|
if (!existsSync10(schemasPath)) return null;
|
|
2437
2450
|
let schemas;
|
|
2438
2451
|
try {
|
|
2439
2452
|
const esbuild = await import("esbuild");
|
|
2440
|
-
const outFile =
|
|
2453
|
+
const outFile = join10(appDir, ".wrangler", "deploy", "schemas.lint.bundle.mjs");
|
|
2441
2454
|
await esbuild.build({
|
|
2442
2455
|
entryPoints: [schemasPath],
|
|
2443
2456
|
bundle: true,
|
|
@@ -2498,9 +2511,9 @@ function isPortListening(port, host, timeoutMs = 500) {
|
|
|
2498
2511
|
socket.connect(port, target2);
|
|
2499
2512
|
});
|
|
2500
2513
|
}
|
|
2501
|
-
async function waitForPortListening(port, host, timeoutMs) {
|
|
2514
|
+
async function waitForPortListening(port, host, timeoutMs, signal) {
|
|
2502
2515
|
const deadline = Date.now() + timeoutMs;
|
|
2503
|
-
while (Date.now() < deadline) {
|
|
2516
|
+
while (Date.now() < deadline && !signal?.aborted) {
|
|
2504
2517
|
if (await isPortListening(port, host)) return true;
|
|
2505
2518
|
await new Promise((resolve21) => setTimeout(resolve21, 250));
|
|
2506
2519
|
}
|
|
@@ -2598,7 +2611,7 @@ var dev_default = defineDeepspaceCommand({
|
|
|
2598
2611
|
);
|
|
2599
2612
|
} else if (!args.json) {
|
|
2600
2613
|
console.warn(
|
|
2601
|
-
`Warning: could not update ${
|
|
2614
|
+
`Warning: could not update ${join11(worktree.mainRepoRoot, ".claude", "launch.json")} (malformed or unwritable) \u2014 the Claude preview tool may serve the main repo's code instead of this worktree.`
|
|
2602
2615
|
);
|
|
2603
2616
|
}
|
|
2604
2617
|
}
|
|
@@ -2664,13 +2677,15 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
|
|
|
2664
2677
|
}
|
|
2665
2678
|
const url = `http://${host === DEFAULT_DEV_HOST ? "localhost" : host}:${port}`;
|
|
2666
2679
|
say("Starting dev server...\n");
|
|
2667
|
-
const vite =
|
|
2680
|
+
const vite = spawn2("npx", ["vite", "--port", String(port), "--strictPort", "--host", host], {
|
|
2668
2681
|
cwd: appDir,
|
|
2669
2682
|
stdio: "inherit",
|
|
2670
2683
|
env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
|
|
2671
2684
|
});
|
|
2685
|
+
const readiness = new AbortController();
|
|
2672
2686
|
if (args.json) {
|
|
2673
|
-
void waitForPortListening(port, host, READINESS_TIMEOUT_MS).then((ready) => {
|
|
2687
|
+
void waitForPortListening(port, host, READINESS_TIMEOUT_MS, readiness.signal).then((ready) => {
|
|
2688
|
+
if (readiness.signal.aborted) return;
|
|
2674
2689
|
console.log(
|
|
2675
2690
|
JSON.stringify(
|
|
2676
2691
|
ready ? { ok: true, ready: true, url, port, host, appDir } : {
|
|
@@ -2702,6 +2717,7 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
|
|
|
2702
2717
|
});
|
|
2703
2718
|
process.off("SIGINT", stop);
|
|
2704
2719
|
process.off("SIGTERM", stop);
|
|
2720
|
+
readiness.abort();
|
|
2705
2721
|
if (!devExitSucceeded(code, stopping)) {
|
|
2706
2722
|
throw new Refusal(`Dev server exited with code ${code}.`, "dev_server_failed", {
|
|
2707
2723
|
extra: { port, appDir, exitCode: code }
|
|
@@ -2817,12 +2833,12 @@ var kill_default = defineDeepspaceCommand({
|
|
|
2817
2833
|
}
|
|
2818
2834
|
});
|
|
2819
2835
|
function noTargetsMessage(opts) {
|
|
2820
|
-
const { enumerated, swept, all, port } = opts;
|
|
2836
|
+
const { enumerated, swept, all, port, platform = process.platform } = opts;
|
|
2821
2837
|
const couldObserve = enumerated || all && swept;
|
|
2822
2838
|
if (!couldObserve) {
|
|
2823
2839
|
return {
|
|
2824
2840
|
ok: false,
|
|
2825
|
-
message: `Couldn't determine what's running on :${port}: no lsof/pgrep and /proc is unavailable, so nothing was inspected or killed. Install lsof (or procps for pgrep), or find and kill the process manually.`
|
|
2841
|
+
message: platform === "win32" ? `Couldn't determine what's running on :${port}: the PowerShell port query could not be run, so nothing was inspected or killed. Either powershell.exe (or pwsh) is not on PATH, or Get-NetTCPConnection is unavailable on this Windows edition. Find the process manually with \`netstat -ano | findstr :${port}\`.` : `Couldn't determine what's running on :${port}: no lsof/pgrep and /proc is unavailable, so nothing was inspected or killed. Install lsof (or procps for pgrep), or find and kill the process manually.`
|
|
2826
2842
|
};
|
|
2827
2843
|
}
|
|
2828
2844
|
return {
|
|
@@ -2917,7 +2933,7 @@ function sweepByName(names) {
|
|
|
2917
2933
|
const all = /* @__PURE__ */ new Set();
|
|
2918
2934
|
if (IS_WIN) {
|
|
2919
2935
|
const orClauses = names.map((n) => `($_.Name -like '*${n}*') -or ($_.CommandLine -like '*${n}*')`).join(" -or ");
|
|
2920
|
-
const script = `Get-CimInstance Win32_Process | Where-Object { ${orClauses} } | Select-Object -ExpandProperty ProcessId`;
|
|
2936
|
+
const script = `Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -ne $PID -and (${orClauses}) } | Select-Object -ExpandProperty ProcessId`;
|
|
2921
2937
|
const r = runPowershellChecked(script);
|
|
2922
2938
|
for (const pid of r.pids) if (pid !== process.pid) all.add(pid);
|
|
2923
2939
|
return { pids: [...all], swept: r.ran };
|
|
@@ -2954,14 +2970,22 @@ function run(cmd) {
|
|
|
2954
2970
|
function runPowershell(script) {
|
|
2955
2971
|
return runPowershellChecked(script).pids;
|
|
2956
2972
|
}
|
|
2973
|
+
var PS_SENTINEL = "__deepspace_ps_ok__";
|
|
2974
|
+
function withSentinel(script) {
|
|
2975
|
+
return `$ErrorActionPreference='Stop'; try { ${script}; '${PS_SENTINEL}' } catch { }`;
|
|
2976
|
+
}
|
|
2977
|
+
function readPowershellPids(stdout) {
|
|
2978
|
+
if (!stdout.includes(PS_SENTINEL)) return { pids: [], ran: false };
|
|
2979
|
+
return { pids: parsePidLines(stdout.replace(PS_SENTINEL, "")), ran: true };
|
|
2980
|
+
}
|
|
2957
2981
|
function runPowershellChecked(script) {
|
|
2958
2982
|
const exe = pwshOrPowershell();
|
|
2959
2983
|
try {
|
|
2960
|
-
const r = spawnSync2(exe, ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
2984
|
+
const r = spawnSync2(exe, ["-NoProfile", "-NonInteractive", "-Command", withSentinel(script)], {
|
|
2961
2985
|
encoding: "utf-8"
|
|
2962
2986
|
});
|
|
2963
2987
|
if (r.error) return { pids: [], ran: false };
|
|
2964
|
-
return
|
|
2988
|
+
return readPowershellPids(r.stdout ?? "");
|
|
2965
2989
|
} catch {
|
|
2966
2990
|
return { pids: [], ran: false };
|
|
2967
2991
|
}
|
|
@@ -3026,10 +3050,10 @@ function ensurePlaywright(appDir) {
|
|
|
3026
3050
|
import { readFileSync as readFileSync14 } from "fs";
|
|
3027
3051
|
|
|
3028
3052
|
// src/testing/accounts.ts
|
|
3029
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as
|
|
3030
|
-
import { homedir as
|
|
3031
|
-
import { join as
|
|
3032
|
-
var TEST_ACCOUNTS_PATH =
|
|
3053
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
|
|
3054
|
+
import { homedir as homedir2 } from "os";
|
|
3055
|
+
import { join as join12 } from "path";
|
|
3056
|
+
var TEST_ACCOUNTS_PATH = join12(homedir2(), ".deepspace", "test-accounts.json");
|
|
3033
3057
|
var PRODUCTION_AUTH_ORIGIN = "https://auth.deep.space";
|
|
3034
3058
|
var STAGING_AUTH_ORIGIN = "https://auth.deepspacesites.com";
|
|
3035
3059
|
function currentTestAccountScope(env = process.env) {
|
|
@@ -3045,7 +3069,7 @@ function normalizeTestAccountScope(value) {
|
|
|
3045
3069
|
}
|
|
3046
3070
|
function loadAllTestAccounts(scope = currentTestAccountScope()) {
|
|
3047
3071
|
const store = readTestAccountStore();
|
|
3048
|
-
return
|
|
3072
|
+
return store.scopes[scope] ?? [];
|
|
3049
3073
|
}
|
|
3050
3074
|
function reconcileTestAccountStore(remote, scope = currentTestAccountScope()) {
|
|
3051
3075
|
const result = reconcileTestAccountScopes(readTestAccountStore(), remote, scope);
|
|
@@ -3054,24 +3078,20 @@ function reconcileTestAccountStore(remote, scope = currentTestAccountScope()) {
|
|
|
3054
3078
|
}
|
|
3055
3079
|
function reconcileTestAccountScopes(store, remote, scope) {
|
|
3056
3080
|
const scoped = store.scopes[scope] ?? [];
|
|
3057
|
-
const
|
|
3058
|
-
const migrated = reconcileTestAccounts(store.unscoped, remote);
|
|
3059
|
-
const accounts = mergeTestAccounts(current, migrated);
|
|
3081
|
+
const accounts = reconcileTestAccounts(scoped, remote);
|
|
3060
3082
|
return {
|
|
3061
3083
|
accounts,
|
|
3062
|
-
removed: Math.max(0, scoped.length -
|
|
3084
|
+
removed: Math.max(0, scoped.length - accounts.length),
|
|
3063
3085
|
store: {
|
|
3064
3086
|
version: 2,
|
|
3065
|
-
scopes: { ...store.scopes, [scope]: accounts }
|
|
3066
|
-
unscoped: store.unscoped.filter((account) => !remote.some((item) => accountMatchesRemote(account, item)))
|
|
3087
|
+
scopes: { ...store.scopes, [scope]: accounts }
|
|
3067
3088
|
}
|
|
3068
3089
|
};
|
|
3069
3090
|
}
|
|
3070
3091
|
function reconcileTestAccounts(local, remote) {
|
|
3071
3092
|
const localById = new Map(local.filter((account) => account.id).map((account) => [account.id, account]));
|
|
3072
|
-
const legacyByEmail = new Map(local.filter((account) => !account.id).map((account) => [account.email, account]));
|
|
3073
3093
|
return remote.flatMap((account) => {
|
|
3074
|
-
const stored = localById.get(account.id)
|
|
3094
|
+
const stored = localById.get(account.id);
|
|
3075
3095
|
return stored ? [{ ...stored, ...account }] : [];
|
|
3076
3096
|
});
|
|
3077
3097
|
}
|
|
@@ -3082,10 +3102,7 @@ function upsertTestAccount(account, scope = currentTestAccountScope()) {
|
|
|
3082
3102
|
);
|
|
3083
3103
|
saveTestAccountStore({
|
|
3084
3104
|
version: 2,
|
|
3085
|
-
scopes: { ...store.scopes, [scope]: [...accounts, account] }
|
|
3086
|
-
unscoped: store.unscoped.filter(
|
|
3087
|
-
(stored) => stored.id !== account.id && stored.email !== account.email
|
|
3088
|
-
)
|
|
3105
|
+
scopes: { ...store.scopes, [scope]: [...accounts, account] }
|
|
3089
3106
|
});
|
|
3090
3107
|
}
|
|
3091
3108
|
function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope()) {
|
|
@@ -3095,45 +3112,35 @@ function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope())
|
|
|
3095
3112
|
const store = readTestAccountStore();
|
|
3096
3113
|
saveTestAccountStore({
|
|
3097
3114
|
version: 2,
|
|
3098
|
-
scopes: { ...store.scopes, [scope]: (store.scopes[scope] ?? []).filter(keep) }
|
|
3099
|
-
unscoped: store.unscoped.filter(keep)
|
|
3115
|
+
scopes: { ...store.scopes, [scope]: (store.scopes[scope] ?? []).filter(keep) }
|
|
3100
3116
|
});
|
|
3101
3117
|
}
|
|
3102
3118
|
function readTestAccountStore() {
|
|
3103
|
-
if (!existsSync11(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {}
|
|
3119
|
+
if (!existsSync11(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {} };
|
|
3104
3120
|
try {
|
|
3105
3121
|
const raw = JSON.parse(readFileSync13(TEST_ACCOUNTS_PATH, "utf-8"));
|
|
3106
|
-
if (Array.isArray(raw)) return { version: 2, scopes: {}
|
|
3107
|
-
if (!raw || typeof raw !== "object"
|
|
3108
|
-
return { version: 2, scopes: {}
|
|
3122
|
+
if (Array.isArray(raw)) return { version: 2, scopes: {} };
|
|
3123
|
+
if (!raw || typeof raw !== "object") {
|
|
3124
|
+
return { version: 2, scopes: {} };
|
|
3109
3125
|
}
|
|
3110
3126
|
const candidate = raw;
|
|
3111
3127
|
const scopes = candidate.scopes && typeof candidate.scopes === "object" && !Array.isArray(candidate.scopes) ? Object.fromEntries(Object.entries(candidate.scopes).map(([key, value]) => [key, validTestAccounts(value)])) : {};
|
|
3112
|
-
return { version: 2, scopes
|
|
3128
|
+
return { version: 2, scopes };
|
|
3113
3129
|
} catch {
|
|
3114
|
-
return { version: 2, scopes: {}
|
|
3130
|
+
return { version: 2, scopes: {} };
|
|
3115
3131
|
}
|
|
3116
3132
|
}
|
|
3117
3133
|
function validTestAccounts(value) {
|
|
3118
3134
|
if (!Array.isArray(value)) return [];
|
|
3119
|
-
return value.filter(
|
|
3120
|
-
item
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
const accounts = /* @__PURE__ */ new Map();
|
|
3125
|
-
for (const account of groups.flat()) {
|
|
3126
|
-
const key = account.id ? `id:${account.id}` : `email:${account.email}`;
|
|
3127
|
-
if (!accounts.has(key)) accounts.set(key, account);
|
|
3128
|
-
}
|
|
3129
|
-
return [...accounts.values()];
|
|
3130
|
-
}
|
|
3131
|
-
function accountMatchesRemote(local, remote) {
|
|
3132
|
-
return local.id ? local.id === remote.id : local.email === remote.email;
|
|
3135
|
+
return value.filter(
|
|
3136
|
+
(item) => Boolean(
|
|
3137
|
+
item && typeof item === "object" && typeof item.id === "string" && typeof item.email === "string" && typeof item.password === "string"
|
|
3138
|
+
)
|
|
3139
|
+
);
|
|
3133
3140
|
}
|
|
3134
3141
|
function saveTestAccountStore(store) {
|
|
3135
|
-
mkdirSync5(
|
|
3136
|
-
|
|
3142
|
+
mkdirSync5(join12(homedir2(), ".deepspace"), { recursive: true });
|
|
3143
|
+
writeFileSync5(TEST_ACCOUNTS_PATH, JSON.stringify(store, null, 2), { mode: 384 });
|
|
3137
3144
|
}
|
|
3138
3145
|
|
|
3139
3146
|
// src/cli/lib/test-account-service.ts
|
|
@@ -3187,11 +3194,24 @@ async function syncTestAccountStore() {
|
|
|
3187
3194
|
|
|
3188
3195
|
// src/cli/commands/test.ts
|
|
3189
3196
|
var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
3197
|
+
var PLAYWRIGHT_OUTPUT_DIR = ".deepspace/test-results";
|
|
3198
|
+
function playwrightTestArgs(testFiles) {
|
|
3199
|
+
return [
|
|
3200
|
+
"playwright",
|
|
3201
|
+
"test",
|
|
3202
|
+
"--config",
|
|
3203
|
+
"tests/playwright.config.ts",
|
|
3204
|
+
"--output",
|
|
3205
|
+
PLAYWRIGHT_OUTPUT_DIR,
|
|
3206
|
+
...testFiles
|
|
3207
|
+
];
|
|
3208
|
+
}
|
|
3190
3209
|
var test_default = defineDeepspaceCommand({
|
|
3191
3210
|
meta: {
|
|
3192
3211
|
name: "test",
|
|
3193
3212
|
description: "Run tests for your DeepSpace app"
|
|
3194
3213
|
},
|
|
3214
|
+
jsonDescription: "Stream test output; emit the final JSON result on the last line",
|
|
3195
3215
|
args: {
|
|
3196
3216
|
suite: {
|
|
3197
3217
|
type: "positional",
|
|
@@ -3324,15 +3344,14 @@ function runPlaywright(appDir, testFiles, port, wranglerEnv) {
|
|
|
3324
3344
|
return 1;
|
|
3325
3345
|
}
|
|
3326
3346
|
try {
|
|
3327
|
-
const result = spawnSync4(
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
{
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
);
|
|
3347
|
+
const result = spawnSync4("npx", playwrightTestArgs(testFiles), {
|
|
3348
|
+
cwd: appDir,
|
|
3349
|
+
stdio: "inherit",
|
|
3350
|
+
env: wranglerViteEnv(process.env, wranglerConfig, {
|
|
3351
|
+
DEEPSPACE_PORT: String(port),
|
|
3352
|
+
PLAYWRIGHT_HTML_OUTPUT_DIR: ".deepspace/playwright-report"
|
|
3353
|
+
})
|
|
3354
|
+
});
|
|
3336
3355
|
return result.status ?? 1;
|
|
3337
3356
|
} finally {
|
|
3338
3357
|
wranglerConfig.cleanup();
|
|
@@ -3621,7 +3640,7 @@ var clear = defineDeepspaceCommand({
|
|
|
3621
3640
|
return { data: { deleted: 0, requested: 0, failures: [] } };
|
|
3622
3641
|
}
|
|
3623
3642
|
if (!args.yes) {
|
|
3624
|
-
if (args.json) {
|
|
3643
|
+
if (!process.stdin.isTTY || args.json) {
|
|
3625
3644
|
throw new Refusal(
|
|
3626
3645
|
"Deleting test accounts needs confirmation. Pass --yes to confirm non-interactively.",
|
|
3627
3646
|
"confirmation_required"
|
|
@@ -3851,14 +3870,14 @@ import * as p6 from "@clack/prompts";
|
|
|
3851
3870
|
import { execSync as execSync2 } from "child_process";
|
|
3852
3871
|
import { createHash as createHash2 } from "crypto";
|
|
3853
3872
|
import { existsSync as existsSync14, readFileSync as readFileSync17, readdirSync as readdirSync4 } from "fs";
|
|
3854
|
-
import { dirname as
|
|
3873
|
+
import { dirname as dirname7, join as join15, resolve as resolve11 } from "path";
|
|
3855
3874
|
|
|
3856
3875
|
// src/documentation/deploy.ts
|
|
3857
3876
|
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
|
|
3858
|
-
import { join as
|
|
3877
|
+
import { join as join13 } from "path";
|
|
3859
3878
|
function readDocumentationDeployManifest(appDir, clientDir) {
|
|
3860
|
-
if (!existsSync12(
|
|
3861
|
-
const manifestPath =
|
|
3879
|
+
if (!existsSync12(join13(appDir, "documentation.json"))) return void 0;
|
|
3880
|
+
const manifestPath = join13(clientDir, "_documentation", "manifest.json");
|
|
3862
3881
|
if (!existsSync12(manifestPath)) {
|
|
3863
3882
|
throw new Error(
|
|
3864
3883
|
"documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
|
|
@@ -3879,12 +3898,13 @@ import {
|
|
|
3879
3898
|
readFileSync as readFileSync16,
|
|
3880
3899
|
writeSync
|
|
3881
3900
|
} from "fs";
|
|
3882
|
-
import { join as
|
|
3901
|
+
import { join as join14 } from "path";
|
|
3883
3902
|
|
|
3884
3903
|
// src/shared/protocol/app-migrations.ts
|
|
3885
3904
|
var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
3886
3905
|
var MAX_APP_MIGRATION_IDS = 256;
|
|
3887
3906
|
var WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
|
|
3907
|
+
var SECURE_ROOM_BOUNDARIES_MIGRATION_ID = "2026-08-secure-room-boundaries";
|
|
3888
3908
|
function validateAppMigrationIds(value) {
|
|
3889
3909
|
if (!Array.isArray(value)) {
|
|
3890
3910
|
return { valid: false, reason: "app migrations must be a JSON string array" };
|
|
@@ -3918,13 +3938,19 @@ var SOURCE_MIGRATIONS = [
|
|
|
3918
3938
|
description: "Let the worker answer misses, so deleted files 404 instead of serving HTML",
|
|
3919
3939
|
transform: migrateWorkerOwnedNotFound,
|
|
3920
3940
|
findBlockers: findWorkerOwnedNotFoundBlockers
|
|
3941
|
+
},
|
|
3942
|
+
{
|
|
3943
|
+
id: SECURE_ROOM_BOUNDARIES_MIGRATION_ID,
|
|
3944
|
+
description: "Secure room identity, job mutations, and production debug routes",
|
|
3945
|
+
transform: migrateSecureRoomBoundaries,
|
|
3946
|
+
findBlockers: findSecureRoomBoundaryBlockers
|
|
3921
3947
|
}
|
|
3922
3948
|
];
|
|
3923
3949
|
var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
|
|
3924
3950
|
({ id, description }) => ({ id, description })
|
|
3925
3951
|
);
|
|
3926
3952
|
function readAppliedAppMigrations(appDir) {
|
|
3927
|
-
const path =
|
|
3953
|
+
const path = join14(appDir, APP_MIGRATIONS_MANIFEST);
|
|
3928
3954
|
if (!existsSync13(path)) return [];
|
|
3929
3955
|
const source = readRegularFile(path);
|
|
3930
3956
|
let parsed;
|
|
@@ -3951,7 +3977,7 @@ function planAppMigrations(appDir) {
|
|
|
3951
3977
|
const edits = [];
|
|
3952
3978
|
for (const repoPath of tracked) {
|
|
3953
3979
|
if (!isSourcePath(repoPath, appPrefix)) continue;
|
|
3954
|
-
const absolutePath =
|
|
3980
|
+
const absolutePath = join14(repo, repoPath);
|
|
3955
3981
|
let stat;
|
|
3956
3982
|
try {
|
|
3957
3983
|
stat = lstatSync(absolutePath);
|
|
@@ -3959,7 +3985,8 @@ function planAppMigrations(appDir) {
|
|
|
3959
3985
|
continue;
|
|
3960
3986
|
}
|
|
3961
3987
|
if (!stat.isFile() || stat.isSymbolicLink()) continue;
|
|
3962
|
-
const
|
|
3988
|
+
const checkout = readRegularFile(absolutePath);
|
|
3989
|
+
const before = normalizeEol(checkout);
|
|
3963
3990
|
let after = before;
|
|
3964
3991
|
for (const migration of pendingMigrations) {
|
|
3965
3992
|
const result = migration.transform(after, repoPath);
|
|
@@ -3977,7 +4004,9 @@ function planAppMigrations(appDir) {
|
|
|
3977
4004
|
}
|
|
3978
4005
|
blockers.push(...migration.findBlockers(after, repoPath));
|
|
3979
4006
|
}
|
|
3980
|
-
if (after !== before)
|
|
4007
|
+
if (after !== before) {
|
|
4008
|
+
edits.push({ path: repoPath, before: checkout, after: restoreEol(after, checkout) });
|
|
4009
|
+
}
|
|
3981
4010
|
}
|
|
3982
4011
|
const pending = pendingMigrations.map((migration) => {
|
|
3983
4012
|
const planned = perMigration.get(migration.id);
|
|
@@ -3985,7 +4014,7 @@ function planAppMigrations(appDir) {
|
|
|
3985
4014
|
});
|
|
3986
4015
|
if (pending.length > 0 && blockers.length === 0) {
|
|
3987
4016
|
const manifestPath = `${appPrefix}${APP_MIGRATIONS_MANIFEST}`;
|
|
3988
|
-
const absoluteManifestPath =
|
|
4017
|
+
const absoluteManifestPath = join14(repo, manifestPath);
|
|
3989
4018
|
const before = existsSync13(absoluteManifestPath) ? readRegularFile(absoluteManifestPath) : null;
|
|
3990
4019
|
const after = `${JSON.stringify([...appliedIds, ...pending.map(({ id }) => id)], null, 2)}
|
|
3991
4020
|
`;
|
|
@@ -4001,13 +4030,19 @@ function planAppMigrations(appDir) {
|
|
|
4001
4030
|
function applyAppMigrationPlan(appDir, plan) {
|
|
4002
4031
|
const repo = repoToplevel(appDir);
|
|
4003
4032
|
for (const edit of plan.edits) {
|
|
4004
|
-
const absolutePath =
|
|
4033
|
+
const absolutePath = join14(repo, edit.path);
|
|
4005
4034
|
const current = existsSync13(absolutePath) ? readRegularFile(absolutePath) : null;
|
|
4006
4035
|
if (current !== edit.before) {
|
|
4007
4036
|
throw new Error(`Migration input changed after planning: ${edit.path}`);
|
|
4008
4037
|
}
|
|
4009
4038
|
}
|
|
4010
|
-
for (const edit of plan.edits) writeRegularFile(
|
|
4039
|
+
for (const edit of plan.edits) writeRegularFile(join14(repo, edit.path), edit.before, edit.after);
|
|
4040
|
+
}
|
|
4041
|
+
function normalizeEol(source) {
|
|
4042
|
+
return source.replaceAll("\r\n", "\n");
|
|
4043
|
+
}
|
|
4044
|
+
function restoreEol(source, checkout) {
|
|
4045
|
+
return checkout.includes("\r\n") ? source.replaceAll("\n", "\r\n") : source;
|
|
4011
4046
|
}
|
|
4012
4047
|
function readRegularFile(path) {
|
|
4013
4048
|
const before = lstatSync(path);
|
|
@@ -4061,6 +4096,252 @@ function isSourcePath(repoPath, appPrefix) {
|
|
|
4061
4096
|
if (repoPath.endsWith("wrangler.toml")) return true;
|
|
4062
4097
|
return /\.(?:[cm]?[jt]sx?)$/.test(repoPath);
|
|
4063
4098
|
}
|
|
4099
|
+
var LEGACY_AUTHENTICATED_ROOM_HELPER = `const IDENTITY_QUERY_PARAMS = ['userId', 'userName', 'userEmail', 'userImageUrl', 'role'] as const
|
|
4100
|
+
|
|
4101
|
+
function authenticatedRoomUrl(
|
|
4102
|
+
requestUrl: string,
|
|
4103
|
+
auth: VerifyResult | null,
|
|
4104
|
+
extraParams?: (auth: VerifyResult) => Record<string, string>,
|
|
4105
|
+
): URL {
|
|
4106
|
+
const doUrl = new URL(requestUrl)
|
|
4107
|
+
doUrl.searchParams.delete('token')
|
|
4108
|
+
for (const key of IDENTITY_QUERY_PARAMS) {
|
|
4109
|
+
doUrl.searchParams.delete(key)
|
|
4110
|
+
}
|
|
4111
|
+
|
|
4112
|
+
if (!auth) return doUrl
|
|
4113
|
+
|
|
4114
|
+
doUrl.searchParams.set('userId', auth.userId)
|
|
4115
|
+
if (auth.claims.name) doUrl.searchParams.set('userName', auth.claims.name)
|
|
4116
|
+
if (auth.claims.email) doUrl.searchParams.set('userEmail', auth.claims.email)
|
|
4117
|
+
if (auth.claims.image) doUrl.searchParams.set('userImageUrl', auth.claims.image)
|
|
4118
|
+
if (extraParams) {
|
|
4119
|
+
for (const [key, value] of Object.entries(extraParams(auth))) {
|
|
4120
|
+
doUrl.searchParams.set(key, value)
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
return doUrl
|
|
4124
|
+
}`;
|
|
4125
|
+
var IDENTITY_ROUTE_REPLACEMENTS = [
|
|
4126
|
+
[
|
|
4127
|
+
`import { verifyJwt } from 'deepspace/worker'`,
|
|
4128
|
+
`import { authenticatedRoomRequest, verifyJwt } from 'deepspace/worker'`,
|
|
4129
|
+
1
|
|
4130
|
+
],
|
|
4131
|
+
[LEGACY_AUTHENTICATED_ROOM_HELPER, "", 1],
|
|
4132
|
+
[
|
|
4133
|
+
`function wsRoute(
|
|
4134
|
+
doNamespace: (env: Env) => DurableObjectNamespace,
|
|
4135
|
+
extraParams?: (auth: VerifyResult) => Record<string, string>,
|
|
4136
|
+
) {`,
|
|
4137
|
+
`function wsRoute(
|
|
4138
|
+
doNamespace: (env: Env) => DurableObjectNamespace,
|
|
4139
|
+
extraIdentity?: (auth: VerifyResult) => { role?: string },
|
|
4140
|
+
) {`,
|
|
4141
|
+
1
|
|
4142
|
+
],
|
|
4143
|
+
[
|
|
4144
|
+
` const doUrl = authenticatedRoomUrl(c.req.url, auth, extraParams)`,
|
|
4145
|
+
` const roomRequest = authenticatedRoomRequest(
|
|
4146
|
+
c.req.raw,
|
|
4147
|
+
auth,
|
|
4148
|
+
auth ? extraIdentity?.(auth) : undefined,
|
|
4149
|
+
)`,
|
|
4150
|
+
1
|
|
4151
|
+
],
|
|
4152
|
+
[
|
|
4153
|
+
` const doUrl = authenticatedRoomUrl(c.req.url, auth, () => ({ role }))`,
|
|
4154
|
+
` const roomRequest = authenticatedRoomRequest(c.req.raw, auth, { role })`,
|
|
4155
|
+
1
|
|
4156
|
+
],
|
|
4157
|
+
[
|
|
4158
|
+
` return stub.fetch(new Request(doUrl.toString(), c.req.raw))`,
|
|
4159
|
+
` return stub.fetch(roomRequest)`,
|
|
4160
|
+
2
|
|
4161
|
+
]
|
|
4162
|
+
];
|
|
4163
|
+
function occurrences(source, pattern) {
|
|
4164
|
+
return source.split(pattern).length - 1;
|
|
4165
|
+
}
|
|
4166
|
+
var SECURE_JOB_ROOM_CONSTRUCTOR = ` constructor(state: DurableObjectState, env: Env) {
|
|
4167
|
+
super(state, env, {
|
|
4168
|
+
authorizeWrite: async (user) => {
|
|
4169
|
+
if (user.userId.startsWith('anon-')) return false
|
|
4170
|
+
const role = await resolveAppRole(env, user.userId)
|
|
4171
|
+
return role === 'member' || role === 'admin'
|
|
4172
|
+
},
|
|
4173
|
+
})
|
|
4174
|
+
}`;
|
|
4175
|
+
var APP_JOB_ROOM_CLASS = `export class AppJobRoom extends JobRoom<Env> {`;
|
|
4176
|
+
var SECURE_APP_JOB_ROOM = `${APP_JOB_ROOM_CLASS}
|
|
4177
|
+
${SECURE_JOB_ROOM_CONSTRUCTOR}`;
|
|
4178
|
+
function hasSecureAppJobRoom(source) {
|
|
4179
|
+
return occurrences(source, "class AppJobRoom") === 1 && source.includes(SECURE_APP_JOB_ROOM);
|
|
4180
|
+
}
|
|
4181
|
+
var WORKER_JOB_REPLACEMENTS = [
|
|
4182
|
+
[
|
|
4183
|
+
`import type { DOBindings, DOManifest, Job, JobContext } from 'deepspace/worker'`,
|
|
4184
|
+
`import { resolveAppRole } from 'deepspace/worker'
|
|
4185
|
+
import type { DOBindings, DOManifest, Job, JobContext } from 'deepspace/worker'`
|
|
4186
|
+
],
|
|
4187
|
+
[
|
|
4188
|
+
`export class AppJobRoom extends JobRoom<Env> {
|
|
4189
|
+
constructor(state: DurableObjectState, env: Env) {
|
|
4190
|
+
super(state, env)
|
|
4191
|
+
}`,
|
|
4192
|
+
`export class AppJobRoom extends JobRoom<Env> {
|
|
4193
|
+
${SECURE_JOB_ROOM_CONSTRUCTOR}`
|
|
4194
|
+
]
|
|
4195
|
+
];
|
|
4196
|
+
var HTTP_ROLE_IMPORT = `import { resolveAppRole } from 'deepspace/worker'`;
|
|
4197
|
+
var HTTP_ROLE_IMPORT_PATTERN = /\bimport\s*\{[^}]*\bresolveAppRole\b[^}]*\}\s*from\s*(['"])deepspace\/worker\1/;
|
|
4198
|
+
var HTTP_ROLE_IMPORT_SEAM = `import type { AppContext, Env } from '../../worker.js'`;
|
|
4199
|
+
var DEBUG_ROUTE_SEAM = ` app.all('/api/debug/*', async (c) => {
|
|
4200
|
+
if (c.env.ALLOW_DEBUG_ROUTES !== 'true') {
|
|
4201
|
+
return c.notFound()
|
|
4202
|
+
}
|
|
4203
|
+
const stub = c.env.RECORD_ROOMS.get(`;
|
|
4204
|
+
var DOUBLE_QUOTED_DEBUG_ROUTE_SEAM = DEBUG_ROUTE_SEAM.replace(
|
|
4205
|
+
"app.all('/api/debug/*'",
|
|
4206
|
+
'app.all("/api/debug/*"'
|
|
4207
|
+
);
|
|
4208
|
+
var SECURE_DEBUG_ROUTE_SEAM = ` app.all('/api/debug/*', async (c) => {
|
|
4209
|
+
if (c.env.ALLOW_DEBUG_ROUTES !== 'true') {
|
|
4210
|
+
return c.notFound()
|
|
4211
|
+
}
|
|
4212
|
+
const auth = await resolveAuth(c.req.raw, c.env)
|
|
4213
|
+
if (!auth) return c.json({ error: 'unauthorized' }, 401)
|
|
4214
|
+
if ((await resolveAppRole(c.env, auth.userId)) !== 'admin') {
|
|
4215
|
+
return c.json({ error: 'forbidden' }, 403)
|
|
4216
|
+
}
|
|
4217
|
+
const stub = c.env.RECORD_ROOMS.get(`;
|
|
4218
|
+
var DOUBLE_QUOTED_SECURE_DEBUG_ROUTE_SEAM = SECURE_DEBUG_ROUTE_SEAM.replace(
|
|
4219
|
+
"app.all('/api/debug/*'",
|
|
4220
|
+
'app.all("/api/debug/*"'
|
|
4221
|
+
);
|
|
4222
|
+
var DEBUG_ROUTE_REPLACEMENTS = [
|
|
4223
|
+
[DEBUG_ROUTE_SEAM, SECURE_DEBUG_ROUTE_SEAM],
|
|
4224
|
+
[DOUBLE_QUOTED_DEBUG_ROUTE_SEAM, DOUBLE_QUOTED_SECURE_DEBUG_ROUTE_SEAM]
|
|
4225
|
+
];
|
|
4226
|
+
var DEBUG_ROUTE_PATTERN = /\bapp\.all\(\s*(['"])\/api\/debug\/\*\1\s*,/;
|
|
4227
|
+
function debugRouteOccurrences(source) {
|
|
4228
|
+
return source.match(new RegExp(DEBUG_ROUTE_PATTERN.source, "g"))?.length ?? 0;
|
|
4229
|
+
}
|
|
4230
|
+
function secureDebugRouteOccurrences(source) {
|
|
4231
|
+
return DEBUG_ROUTE_REPLACEMENTS.reduce(
|
|
4232
|
+
(count, [, secure]) => count + occurrences(source, secure),
|
|
4233
|
+
0
|
|
4234
|
+
);
|
|
4235
|
+
}
|
|
4236
|
+
function httpRoleImportOccurrences(source) {
|
|
4237
|
+
return source.match(new RegExp(HTTP_ROLE_IMPORT_PATTERN.source, "g"))?.length ?? 0;
|
|
4238
|
+
}
|
|
4239
|
+
function hasSecureDebugRoute(source) {
|
|
4240
|
+
return debugRouteOccurrences(source) === 1 && secureDebugRouteOccurrences(source) === 1 && httpRoleImportOccurrences(source) === 1;
|
|
4241
|
+
}
|
|
4242
|
+
function migrateSecureRoomBoundaries(source, file) {
|
|
4243
|
+
if (file.endsWith("src/server/realtime-routes.ts")) {
|
|
4244
|
+
return migrateRealtimeRoomBoundaries(source);
|
|
4245
|
+
}
|
|
4246
|
+
if (file.endsWith("worker.ts")) return migrateWorkerJobAuthorization(source);
|
|
4247
|
+
if (file.endsWith("src/server/http-routes.ts")) return migrateDebugRouteAuthorization(source);
|
|
4248
|
+
return { source, replacements: 0 };
|
|
4249
|
+
}
|
|
4250
|
+
function migrateRealtimeRoomBoundaries(source) {
|
|
4251
|
+
let migrated = source;
|
|
4252
|
+
let replacements = 0;
|
|
4253
|
+
if (!migrated.includes("authenticatedRoomRequest(")) {
|
|
4254
|
+
if (IDENTITY_ROUTE_REPLACEMENTS.some(
|
|
4255
|
+
([before, , expected]) => occurrences(migrated, before) !== expected
|
|
4256
|
+
)) {
|
|
4257
|
+
return { source, replacements: 0 };
|
|
4258
|
+
}
|
|
4259
|
+
for (const [before, after, expected] of IDENTITY_ROUTE_REPLACEMENTS) {
|
|
4260
|
+
migrated = migrated.replaceAll(before, after);
|
|
4261
|
+
replacements += expected;
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
return { source: migrated, replacements };
|
|
4265
|
+
}
|
|
4266
|
+
function migrateWorkerJobAuthorization(source) {
|
|
4267
|
+
if (!source.includes("class AppJobRoom")) return { source, replacements: 0 };
|
|
4268
|
+
if (hasSecureAppJobRoom(source)) return { source, replacements: 0 };
|
|
4269
|
+
if (WORKER_JOB_REPLACEMENTS.some(([before]) => occurrences(source, before) !== 1)) {
|
|
4270
|
+
return { source, replacements: 0 };
|
|
4271
|
+
}
|
|
4272
|
+
let migrated = source;
|
|
4273
|
+
for (const [before, after] of WORKER_JOB_REPLACEMENTS) migrated = migrated.replace(before, after);
|
|
4274
|
+
return { source: migrated, replacements: WORKER_JOB_REPLACEMENTS.length };
|
|
4275
|
+
}
|
|
4276
|
+
function migrateDebugRouteAuthorization(source) {
|
|
4277
|
+
if (debugRouteOccurrences(source) === 0 || hasSecureDebugRoute(source)) {
|
|
4278
|
+
return { source, replacements: 0 };
|
|
4279
|
+
}
|
|
4280
|
+
const matchingRoutes = DEBUG_ROUTE_REPLACEMENTS.filter(
|
|
4281
|
+
([before2]) => occurrences(source, before2) === 1
|
|
4282
|
+
);
|
|
4283
|
+
if (debugRouteOccurrences(source) !== 1 || matchingRoutes.length !== 1 || httpRoleImportOccurrences(source) !== 0 || occurrences(source, HTTP_ROLE_IMPORT_SEAM) !== 1) {
|
|
4284
|
+
return { source, replacements: 0 };
|
|
4285
|
+
}
|
|
4286
|
+
const [before, after] = matchingRoutes[0];
|
|
4287
|
+
return {
|
|
4288
|
+
source: source.replace(HTTP_ROLE_IMPORT_SEAM, `${HTTP_ROLE_IMPORT_SEAM}
|
|
4289
|
+
${HTTP_ROLE_IMPORT}`).replace(before, after),
|
|
4290
|
+
replacements: 2
|
|
4291
|
+
};
|
|
4292
|
+
}
|
|
4293
|
+
function findSecureRoomBoundaryBlockers(source, file) {
|
|
4294
|
+
if (file.endsWith("worker.ts") && source.includes("class AppJobRoom")) {
|
|
4295
|
+
if (!hasSecureAppJobRoom(source)) {
|
|
4296
|
+
return [
|
|
4297
|
+
roomBoundaryBlocker(
|
|
4298
|
+
file,
|
|
4299
|
+
source,
|
|
4300
|
+
"class AppJobRoom",
|
|
4301
|
+
"Configure AppJobRoom with authorizeWrite using the current users.role."
|
|
4302
|
+
)
|
|
4303
|
+
];
|
|
4304
|
+
}
|
|
4305
|
+
return [];
|
|
4306
|
+
}
|
|
4307
|
+
if (file.endsWith("src/server/http-routes.ts") && debugRouteOccurrences(source) > 0) {
|
|
4308
|
+
if (!hasSecureDebugRoute(source)) {
|
|
4309
|
+
const route = source.match(DEBUG_ROUTE_PATTERN)?.[0] ?? "/api/debug/*";
|
|
4310
|
+
return [
|
|
4311
|
+
roomBoundaryBlocker(
|
|
4312
|
+
file,
|
|
4313
|
+
source,
|
|
4314
|
+
route,
|
|
4315
|
+
"Require verified owner/admin access inside the enabled debug-route handler."
|
|
4316
|
+
)
|
|
4317
|
+
];
|
|
4318
|
+
}
|
|
4319
|
+
return [];
|
|
4320
|
+
}
|
|
4321
|
+
if (!file.endsWith("src/server/realtime-routes.ts")) return [];
|
|
4322
|
+
const lines = source.split("\n");
|
|
4323
|
+
let index = lines.findIndex(
|
|
4324
|
+
(line) => line.includes("authenticatedRoomUrl") || /searchParams\.(?:set|append)\(\s*['"](?:userId|userName|userEmail|userImageUrl|role)['"]/.test(
|
|
4325
|
+
line
|
|
4326
|
+
)
|
|
4327
|
+
);
|
|
4328
|
+
if (index < 0 && /\bauth\.(?:userId|claims)\b/.test(source)) {
|
|
4329
|
+
index = lines.findIndex((line) => /searchParams\.(?:set|append)\s*\(/.test(line));
|
|
4330
|
+
}
|
|
4331
|
+
if (index < 0) return [];
|
|
4332
|
+
return [
|
|
4333
|
+
{
|
|
4334
|
+
migrationId: SECURE_ROOM_BOUNDARIES_MIGRATION_ID,
|
|
4335
|
+
file,
|
|
4336
|
+
line: index + 1,
|
|
4337
|
+
message: "This customized room proxy still places verified user identity in its internal URL. Replace that forwarding code with authenticatedRoomRequest() from deepspace/worker before the Durable Object fetch."
|
|
4338
|
+
}
|
|
4339
|
+
];
|
|
4340
|
+
}
|
|
4341
|
+
function roomBoundaryBlocker(file, source, seam, message) {
|
|
4342
|
+
const line = source.slice(0, source.indexOf(seam)).split("\n").length;
|
|
4343
|
+
return { migrationId: SECURE_ROOM_BOUNDARIES_MIGRATION_ID, file, line, message };
|
|
4344
|
+
}
|
|
4064
4345
|
function migrateWorkerOwnedNotFound(source, file) {
|
|
4065
4346
|
let replacements = 0;
|
|
4066
4347
|
if (file.endsWith("wrangler.toml")) {
|
|
@@ -4113,7 +4394,9 @@ function findWorkerOwnedNotFoundBlockers(source, file) {
|
|
|
4113
4394
|
const blockers = [];
|
|
4114
4395
|
const lines = source.split("\n");
|
|
4115
4396
|
for (const [index, line] of lines.entries()) {
|
|
4116
|
-
if (!/['"`]\/index\.html/.test(line))
|
|
4397
|
+
if (!/(?:\burl\.pathname\s*=\s*|\bnew\s+URL\s*\(\s*)['"`]\/index\.html/.test(line)) {
|
|
4398
|
+
continue;
|
|
4399
|
+
}
|
|
4117
4400
|
blockers.push({
|
|
4118
4401
|
migrationId: WORKER_OWNED_NOT_FOUND_MIGRATION_ID,
|
|
4119
4402
|
file,
|
|
@@ -4161,7 +4444,7 @@ async function buildDeployBundle(options) {
|
|
|
4161
4444
|
preparedWranglerConfig?.cleanup();
|
|
4162
4445
|
}
|
|
4163
4446
|
spinner2.stop("Built");
|
|
4164
|
-
const deployConfigPath =
|
|
4447
|
+
const deployConfigPath = join15(appDir, ".wrangler", "deploy", "config.json");
|
|
4165
4448
|
if (!existsSync14(deployConfigPath)) {
|
|
4166
4449
|
output.die(
|
|
4167
4450
|
"Build output config not found at .wrangler/deploy/config.json",
|
|
@@ -4169,7 +4452,7 @@ async function buildDeployBundle(options) {
|
|
|
4169
4452
|
);
|
|
4170
4453
|
}
|
|
4171
4454
|
const deployConfig = JSON.parse(readFileSync17(deployConfigPath, "utf-8"));
|
|
4172
|
-
const outputWranglerPath = resolve11(
|
|
4455
|
+
const outputWranglerPath = resolve11(dirname7(deployConfigPath), deployConfig.configPath);
|
|
4173
4456
|
if (!existsSync14(outputWranglerPath)) {
|
|
4174
4457
|
output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
|
|
4175
4458
|
}
|
|
@@ -4181,8 +4464,8 @@ async function buildDeployBundle(options) {
|
|
|
4181
4464
|
"build_output_mismatch"
|
|
4182
4465
|
);
|
|
4183
4466
|
}
|
|
4184
|
-
const workerDir =
|
|
4185
|
-
const workerBundlePath =
|
|
4467
|
+
const workerDir = dirname7(outputWranglerPath);
|
|
4468
|
+
const workerBundlePath = join15(workerDir, outputConfig.main);
|
|
4186
4469
|
const clientDir = outputConfig.assets?.directory ? resolve11(workerDir, outputConfig.assets.directory) : null;
|
|
4187
4470
|
if (!existsSync14(workerBundlePath)) {
|
|
4188
4471
|
output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
|
|
@@ -4274,7 +4557,7 @@ function collectAssets(dir) {
|
|
|
4274
4557
|
const walk = (currentDir, prefix) => {
|
|
4275
4558
|
for (const entry of readdirSync4(currentDir, { withFileTypes: true })) {
|
|
4276
4559
|
if (isDeployAssetControlFile(entry.name)) continue;
|
|
4277
|
-
const fullPath =
|
|
4560
|
+
const fullPath = join15(currentDir, entry.name);
|
|
4278
4561
|
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
4279
4562
|
if (entry.isDirectory()) {
|
|
4280
4563
|
walk(fullPath, relativePath);
|
|
@@ -4298,7 +4581,7 @@ function readDeployAssetConfig(clientDir) {
|
|
|
4298
4581
|
["_headers", "_headers"],
|
|
4299
4582
|
["_redirects", "_redirects"]
|
|
4300
4583
|
]) {
|
|
4301
|
-
const filePath =
|
|
4584
|
+
const filePath = join15(clientDir, file);
|
|
4302
4585
|
if (!existsSync14(filePath)) continue;
|
|
4303
4586
|
const content = readFileSync17(filePath, "utf8");
|
|
4304
4587
|
if (content.trim()) config[key] = content;
|
|
@@ -4315,17 +4598,17 @@ function errorMessage(error) {
|
|
|
4315
4598
|
// src/cli/commands/deploy/commerce.ts
|
|
4316
4599
|
import * as p7 from "@clack/prompts";
|
|
4317
4600
|
import { existsSync as existsSync15 } from "fs";
|
|
4318
|
-
import { join as
|
|
4601
|
+
import { join as join16 } from "path";
|
|
4319
4602
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
4320
4603
|
var API_URL2 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
4321
4604
|
function errorMessage2(error) {
|
|
4322
4605
|
return error instanceof Error ? error.message : String(error);
|
|
4323
4606
|
}
|
|
4324
4607
|
async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
|
|
4325
|
-
const sourcePath =
|
|
4608
|
+
const sourcePath = join16(appDir, "src", sourceFile);
|
|
4326
4609
|
if (!existsSync15(sourcePath)) return void 0;
|
|
4327
4610
|
const esbuild = await import("esbuild");
|
|
4328
|
-
const outputPath =
|
|
4611
|
+
const outputPath = join16(appDir, ".wrangler", "deploy", outputFile);
|
|
4329
4612
|
await esbuild.build({
|
|
4330
4613
|
entryPoints: [sourcePath],
|
|
4331
4614
|
bundle: true,
|
|
@@ -4418,7 +4701,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
|
|
|
4418
4701
|
}
|
|
4419
4702
|
}
|
|
4420
4703
|
async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
4421
|
-
if (!existsSync15(
|
|
4704
|
+
if (!existsSync15(join16(appDir, "src", "subscriptions.ts"))) return;
|
|
4422
4705
|
let plans;
|
|
4423
4706
|
try {
|
|
4424
4707
|
plans = await loadDeclaration(
|
|
@@ -4455,7 +4738,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
|
4455
4738
|
spinner2.stop(`Synced ${plans.length} plan${plans.length === 1 ? "" : "s"} to Stripe`);
|
|
4456
4739
|
if (body.connectMissing) {
|
|
4457
4740
|
p7.log.warn(
|
|
4458
|
-
"Stripe Connect not
|
|
4741
|
+
"Stripe Connect is not ready. Paid checkout will remain unavailable until the app owner finishes onboarding at /earnings on the dashboard."
|
|
4459
4742
|
);
|
|
4460
4743
|
}
|
|
4461
4744
|
const changes = (body.planChanges ?? []).filter((change) => change.affectedSubscribers > 0);
|
|
@@ -4473,7 +4756,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
|
4473
4756
|
}
|
|
4474
4757
|
}
|
|
4475
4758
|
async function syncOneTimeProducts(appDir, appName, token) {
|
|
4476
|
-
if (!existsSync15(
|
|
4759
|
+
if (!existsSync15(join16(appDir, "src", "products.ts"))) return;
|
|
4477
4760
|
let products;
|
|
4478
4761
|
try {
|
|
4479
4762
|
products = await loadDeclaration(
|
|
@@ -4568,7 +4851,7 @@ function createDeployOutput(json) {
|
|
|
4568
4851
|
else console.error(withSlug(message, code));
|
|
4569
4852
|
if (action) printAction(action);
|
|
4570
4853
|
}
|
|
4571
|
-
|
|
4854
|
+
throw new CliExit(opts.actionRequired ? 2 : 1);
|
|
4572
4855
|
}
|
|
4573
4856
|
};
|
|
4574
4857
|
}
|
|
@@ -5096,7 +5379,16 @@ function selectGitHubRemote(cwd, options = {}) {
|
|
|
5096
5379
|
return matching.find((remote) => remote.name === "origin") ?? (matching.length === 1 ? matching[0] : null);
|
|
5097
5380
|
}
|
|
5098
5381
|
function remotePublicRefs(cwd, remote) {
|
|
5099
|
-
|
|
5382
|
+
let output;
|
|
5383
|
+
try {
|
|
5384
|
+
output = runGit(cwd, ["ls-remote", "--refs", remote]).stdout.toString("utf-8");
|
|
5385
|
+
} catch (error) {
|
|
5386
|
+
if (!isGitCredentialFailure(error)) throw error;
|
|
5387
|
+
throw new GitError(
|
|
5388
|
+
"Git could not authenticate the remote. In a container, forward an SSH agent or use an ephemeral Git credential helper, then retry. Do not put tokens in remote URLs.",
|
|
5389
|
+
"github_credentials_required"
|
|
5390
|
+
);
|
|
5391
|
+
}
|
|
5100
5392
|
return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
5101
5393
|
const [oid, name] = line.split(/\s+/, 2);
|
|
5102
5394
|
return { oid, name };
|
|
@@ -5104,6 +5396,12 @@ function remotePublicRefs(cwd, remote) {
|
|
|
5104
5396
|
(ref) => /^[0-9a-f]{40}$/.test(ref.oid) && (ref.name.startsWith("refs/heads/") || ref.name.startsWith("refs/tags/"))
|
|
5105
5397
|
);
|
|
5106
5398
|
}
|
|
5399
|
+
function isGitCredentialFailure(error) {
|
|
5400
|
+
if (!(error instanceof Error)) return false;
|
|
5401
|
+
return /authentication failed|could not read username|permission denied \(publickey\)|repository not found|terminal prompts disabled/i.test(
|
|
5402
|
+
error.message
|
|
5403
|
+
);
|
|
5404
|
+
}
|
|
5107
5405
|
function remoteBranchOid(cwd, remote, branch) {
|
|
5108
5406
|
return remotePublicRefs(cwd, remote).find((ref) => ref.name === `refs/heads/${branch}`)?.oid ?? null;
|
|
5109
5407
|
}
|
|
@@ -5145,6 +5443,25 @@ function setAppSource(deployUrl, token, appId, input) {
|
|
|
5145
5443
|
}
|
|
5146
5444
|
|
|
5147
5445
|
// src/cli/commands/deploy/repository.ts
|
|
5446
|
+
function preflightDeployRepository(options) {
|
|
5447
|
+
const { appDir, push, source } = options;
|
|
5448
|
+
if (!push || source?.provider === "github") return null;
|
|
5449
|
+
try {
|
|
5450
|
+
assertSyncableRepo(appDir);
|
|
5451
|
+
if (!source && listGitHubRemotes(appDir).length > 0) {
|
|
5452
|
+
return {
|
|
5453
|
+
code: "source_unclaimed",
|
|
5454
|
+
error: "This app has a GitHub remote but no claimed source. Choose once with `deepspace app source github` (manual GitHub ownership) or `deepspace app source deepspace` (packaged DeepSpace source), then deploy again."
|
|
5455
|
+
};
|
|
5456
|
+
}
|
|
5457
|
+
const branch = currentBranch(appDir);
|
|
5458
|
+
if (!isWorkTreeClean(appDir)) return dirtyWorktreeRefusal(branch);
|
|
5459
|
+
if (!branch) return detachedHeadRefusal();
|
|
5460
|
+
return null;
|
|
5461
|
+
} catch (error) {
|
|
5462
|
+
return deployRepositoryFailure(error, appDir);
|
|
5463
|
+
}
|
|
5464
|
+
}
|
|
5148
5465
|
async function syncDeployRepository(options) {
|
|
5149
5466
|
const { deployUrl, appDir, appId, token, push, ignoreStale, output, sourceState } = options;
|
|
5150
5467
|
let commitOid = null;
|
|
@@ -5165,21 +5482,12 @@ async function syncDeployRepository(options) {
|
|
|
5165
5482
|
return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId };
|
|
5166
5483
|
}
|
|
5167
5484
|
try {
|
|
5168
|
-
|
|
5169
|
-
if (
|
|
5170
|
-
output.die(
|
|
5171
|
-
"This app has a GitHub remote but no claimed source. Choose once with `deepspace app source github` (manual GitHub ownership) or `deepspace app source deepspace` (packaged DeepSpace source), then deploy again.",
|
|
5172
|
-
"source_unclaimed"
|
|
5173
|
-
);
|
|
5174
|
-
}
|
|
5485
|
+
const preflight = preflightDeployRepository({ appDir, push, source });
|
|
5486
|
+
if (preflight) output.die(preflight.error, preflight.code);
|
|
5175
5487
|
const sourceRemote = spaceRemoteName();
|
|
5176
5488
|
ensureSpaceRemote(appDir, appId, sourceRemote);
|
|
5177
5489
|
const branch = currentBranch(appDir);
|
|
5178
5490
|
const workspaceBranchId = workspaceIdFromBranch(branch);
|
|
5179
|
-
if (!isWorkTreeClean(appDir)) {
|
|
5180
|
-
const refusal = dirtyWorktreeRefusal(branch);
|
|
5181
|
-
output.die(refusal.error, refusal.code);
|
|
5182
|
-
}
|
|
5183
5491
|
if (!branch) {
|
|
5184
5492
|
const refusal = detachedHeadRefusal();
|
|
5185
5493
|
output.die(refusal.error, refusal.code);
|
|
@@ -5272,6 +5580,7 @@ async function syncDeployRepository(options) {
|
|
|
5272
5580
|
sourceRevision = claimed.revision;
|
|
5273
5581
|
}
|
|
5274
5582
|
} catch (error) {
|
|
5583
|
+
if (error instanceof CliExit) throw error;
|
|
5275
5584
|
const failure = deployRepositoryFailure(error, appDir);
|
|
5276
5585
|
output.die(failure.error, failure.code);
|
|
5277
5586
|
}
|
|
@@ -5281,12 +5590,7 @@ function classifyRemoteState(appDir, token, branch, localTip, remote) {
|
|
|
5281
5590
|
let remoteTip = null;
|
|
5282
5591
|
const remoteRef = `refs/remotes/${remote}/${branch}`;
|
|
5283
5592
|
try {
|
|
5284
|
-
runGitRemote(appDir, token, [
|
|
5285
|
-
"fetch",
|
|
5286
|
-
"--quiet",
|
|
5287
|
-
remote,
|
|
5288
|
-
`+refs/heads/${branch}:${remoteRef}`
|
|
5289
|
-
]);
|
|
5593
|
+
runGitRemote(appDir, token, ["fetch", "--quiet", remote, `+refs/heads/${branch}:${remoteRef}`]);
|
|
5290
5594
|
remoteTip = resolveCommit(appDir, remoteRef);
|
|
5291
5595
|
return {
|
|
5292
5596
|
remoteTip,
|
|
@@ -5393,7 +5697,7 @@ async function deployBuiltBundle(options) {
|
|
|
5393
5697
|
...action ? { action } : {}
|
|
5394
5698
|
});
|
|
5395
5699
|
}
|
|
5396
|
-
|
|
5700
|
+
throw new CliExit(actionRequired ? 2 : 1);
|
|
5397
5701
|
};
|
|
5398
5702
|
const makeForm = () => {
|
|
5399
5703
|
const form = new FormData();
|
|
@@ -5860,6 +6164,14 @@ var deploy_default = defineCommand3({
|
|
|
5860
6164
|
output
|
|
5861
6165
|
});
|
|
5862
6166
|
const sourceState = await getAppSource(DEPLOY_URL3, token, appId);
|
|
6167
|
+
const repositoryPreflight = preflightDeployRepository({
|
|
6168
|
+
appDir,
|
|
6169
|
+
push: args.push !== false,
|
|
6170
|
+
source: sourceState.source
|
|
6171
|
+
});
|
|
6172
|
+
if (repositoryPreflight) {
|
|
6173
|
+
output.die(repositoryPreflight.error, repositoryPreflight.code);
|
|
6174
|
+
}
|
|
5863
6175
|
const spinner2 = createSpinner();
|
|
5864
6176
|
const bundle = await buildDeployBundle({
|
|
5865
6177
|
appDir,
|
|
@@ -6296,6 +6608,7 @@ var apps_default = defineDeepspaceCommand({
|
|
|
6296
6608
|
|
|
6297
6609
|
// src/cli/commands/logs.ts
|
|
6298
6610
|
import { defineCommand as defineCommand4 } from "citty";
|
|
6611
|
+
import { setTimeout as delay } from "timers/promises";
|
|
6299
6612
|
|
|
6300
6613
|
// src/shared/log-events.ts
|
|
6301
6614
|
var LOG_LEVELS = ["debug", "log", "info", "warn", "error"];
|
|
@@ -6322,11 +6635,17 @@ function parseSince(input, now = Date.now()) {
|
|
|
6322
6635
|
} else {
|
|
6323
6636
|
ts = Date.parse(trimmed);
|
|
6324
6637
|
if (Number.isNaN(ts)) {
|
|
6325
|
-
throw new InputError(
|
|
6638
|
+
throw new InputError(
|
|
6639
|
+
`Invalid --since "${input}" \u2014 use 30s, 15m, 2h, 7d, or an ISO timestamp.`,
|
|
6640
|
+
"invalid_since"
|
|
6641
|
+
);
|
|
6326
6642
|
}
|
|
6327
6643
|
}
|
|
6328
6644
|
if (now - ts > RETENTION_MS) {
|
|
6329
|
-
throw new InputError(
|
|
6645
|
+
throw new InputError(
|
|
6646
|
+
"Logs are retained for 7 days \u2014 --since can reach back at most 7d.",
|
|
6647
|
+
"since_out_of_range"
|
|
6648
|
+
);
|
|
6330
6649
|
}
|
|
6331
6650
|
return ts;
|
|
6332
6651
|
}
|
|
@@ -6400,7 +6719,7 @@ function formatEvent(e, color, now = Date.now()) {
|
|
|
6400
6719
|
const client = e.source === "client" ? paint("35", "CLIENT", color) + " " : "";
|
|
6401
6720
|
return `${time} ${client}${tag} ${body}`;
|
|
6402
6721
|
}
|
|
6403
|
-
var sleep3 = (ms) =>
|
|
6722
|
+
var sleep3 = (ms, signal) => delay(ms, void 0, { signal });
|
|
6404
6723
|
var logs_default = defineCommand4({
|
|
6405
6724
|
meta: {
|
|
6406
6725
|
name: "logs",
|
|
@@ -6457,29 +6776,42 @@ var logs_default = defineCommand4({
|
|
|
6457
6776
|
const token = await ensureToken();
|
|
6458
6777
|
const appId = await resolveAppTarget(DEPLOY_URL6, token, args.app, { wranglerEnv: envArg });
|
|
6459
6778
|
if (args.level && !LOG_LEVELS.includes(args.level)) {
|
|
6460
|
-
throw new InputError(
|
|
6779
|
+
throw new InputError(
|
|
6780
|
+
`Invalid --level "${args.level}". Use: ${LOG_LEVELS.join(", ")}`,
|
|
6781
|
+
"invalid_level"
|
|
6782
|
+
);
|
|
6461
6783
|
}
|
|
6462
6784
|
let limit;
|
|
6463
6785
|
if (args.limit !== void 0) {
|
|
6464
6786
|
limit = Number(args.limit);
|
|
6465
6787
|
if (!Number.isInteger(limit) || limit < 1 || limit > 500) {
|
|
6466
|
-
throw new InputError(
|
|
6788
|
+
throw new InputError(
|
|
6789
|
+
"Invalid --limit \u2014 expected an integer between 1 and 500.",
|
|
6790
|
+
"invalid_limit"
|
|
6791
|
+
);
|
|
6467
6792
|
}
|
|
6468
6793
|
}
|
|
6469
6794
|
const initialSince = args.since ? parseSince(args.since) : Date.now() - DEFAULT_WINDOW_MS;
|
|
6470
6795
|
const windowLabel = args.since?.trim() || "15m";
|
|
6471
6796
|
const MAX_LIMIT = 500;
|
|
6472
6797
|
const followLimit = followInitialLimit(limit, MAX_LIMIT);
|
|
6473
|
-
const fetchPage = async (since, pageLimit, token2) => {
|
|
6798
|
+
const fetchPage = async (since, pageLimit, token2, signal) => {
|
|
6474
6799
|
const params = new URLSearchParams({ since: String(since) });
|
|
6475
6800
|
if (pageLimit !== void 0) params.set("limit", String(pageLimit));
|
|
6476
6801
|
if (args.level) params.set("level", args.level);
|
|
6477
6802
|
if (args.search) params.set("search", args.search);
|
|
6478
|
-
return apiFetch(
|
|
6803
|
+
return apiFetch(
|
|
6804
|
+
DEPLOY_URL6,
|
|
6805
|
+
token2,
|
|
6806
|
+
`/api/apps/${appId}/logs?${params}`,
|
|
6807
|
+
signal ? { signal } : void 0
|
|
6808
|
+
);
|
|
6479
6809
|
};
|
|
6480
6810
|
const print = (events) => {
|
|
6481
6811
|
for (const e of events) {
|
|
6482
|
-
process.stdout.write(
|
|
6812
|
+
process.stdout.write(
|
|
6813
|
+
args.json ? JSON.stringify(e) + "\n" : formatEvent(e, stdoutColor()) + "\n"
|
|
6814
|
+
);
|
|
6483
6815
|
}
|
|
6484
6816
|
};
|
|
6485
6817
|
const first = await fetchPage(initialSince, args.follow ? followLimit : limit, token);
|
|
@@ -6487,19 +6819,30 @@ var logs_default = defineCommand4({
|
|
|
6487
6819
|
if (!args.follow) {
|
|
6488
6820
|
if (first.events.length === 0) {
|
|
6489
6821
|
if (args.json) {
|
|
6490
|
-
process.stdout.write(
|
|
6822
|
+
process.stdout.write(
|
|
6823
|
+
JSON.stringify({ type: "meta", count: 0, window: windowLabel }) + "\n"
|
|
6824
|
+
);
|
|
6491
6825
|
} else {
|
|
6492
6826
|
console.log(
|
|
6493
6827
|
`No logs in the last ${windowLabel} for ${appId}. New logs can take up to a minute to appear; retention is 7 days.`
|
|
6494
6828
|
);
|
|
6495
6829
|
}
|
|
6496
6830
|
} else if (first.truncated) {
|
|
6497
|
-
if (args.json)
|
|
6498
|
-
|
|
6831
|
+
if (args.json)
|
|
6832
|
+
process.stdout.write(JSON.stringify({ type: "meta", truncated: true }) + "\n");
|
|
6833
|
+
else if (args.search)
|
|
6834
|
+
console.error(
|
|
6835
|
+
`(showing the newest ${first.events.length} matches \u2014 search scans at most the newest 500 level-filtered events per fetch; narrow with --since/--level or raise --limit)`
|
|
6836
|
+
);
|
|
6837
|
+
else
|
|
6838
|
+
console.error(
|
|
6839
|
+
`(showing the newest ${first.events.length} events \u2014 narrow with --since/--level)`
|
|
6840
|
+
);
|
|
6499
6841
|
}
|
|
6500
6842
|
return;
|
|
6501
6843
|
}
|
|
6502
|
-
|
|
6844
|
+
const tail = new AbortController();
|
|
6845
|
+
process.once("SIGINT", () => tail.abort());
|
|
6503
6846
|
if (!args.json) {
|
|
6504
6847
|
console.error(`Tailing logs for ${appId} \u2014 Ctrl+C to stop.`);
|
|
6505
6848
|
}
|
|
@@ -6515,35 +6858,56 @@ var logs_default = defineCommand4({
|
|
|
6515
6858
|
process.stdout.write(JSON.stringify({ type: "meta", truncated: true }) + "\n");
|
|
6516
6859
|
} else {
|
|
6517
6860
|
console.error(
|
|
6518
|
-
paint(
|
|
6861
|
+
paint(
|
|
6862
|
+
"33",
|
|
6863
|
+
args.search ? `(search scanned only the newest ${MAX_LIMIT} level-filtered events in the initial ${windowLabel} window \u2014 older matches may be omitted; narrow with --since/--level)` : `(more than ${followLimit} events in the initial ${windowLabel} window \u2014 some older events not shown; narrow with --since/--level)`,
|
|
6864
|
+
stderrColor()
|
|
6865
|
+
)
|
|
6519
6866
|
);
|
|
6520
6867
|
}
|
|
6521
6868
|
}
|
|
6522
6869
|
for (; ; ) {
|
|
6523
|
-
|
|
6870
|
+
try {
|
|
6871
|
+
await sleep3(backoff || interval, tail.signal);
|
|
6872
|
+
} catch {
|
|
6873
|
+
return;
|
|
6874
|
+
}
|
|
6524
6875
|
const freshToken = await ensureToken();
|
|
6876
|
+
if (tail.signal.aborted) return;
|
|
6525
6877
|
let page;
|
|
6526
6878
|
try {
|
|
6527
|
-
page = await fetchPage(
|
|
6879
|
+
page = await fetchPage(
|
|
6880
|
+
nextPollSince(cursor, initialSince),
|
|
6881
|
+
followLimit,
|
|
6882
|
+
freshToken,
|
|
6883
|
+
tail.signal
|
|
6884
|
+
);
|
|
6528
6885
|
} catch (err) {
|
|
6886
|
+
if (tail.signal.aborted) return;
|
|
6529
6887
|
if (err instanceof ApiError && [401, 403, 404].includes(err.status)) throw err;
|
|
6530
6888
|
backoff = Math.min(backoff ? backoff * 2 : MIN_POLL_MS, 3e4);
|
|
6531
6889
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6532
|
-
console.error(
|
|
6890
|
+
console.error(
|
|
6891
|
+
paint("2", `(logs fetch failed: ${msg} \u2014 retrying in ${backoff / 1e3}s)`, stderrColor())
|
|
6892
|
+
);
|
|
6533
6893
|
continue;
|
|
6534
6894
|
}
|
|
6535
6895
|
backoff = 0;
|
|
6536
6896
|
interval = Math.max(page.pollIntervalMs || 0, MIN_POLL_MS);
|
|
6537
6897
|
const fresh = seen.fresh(page.events);
|
|
6538
6898
|
print(fresh);
|
|
6539
|
-
const realDrop = fresh.length >= followLimit;
|
|
6899
|
+
const realDrop = fresh.length >= followLimit || Boolean(args.search && page.truncated);
|
|
6540
6900
|
if (realDrop && !warnedTruncated) {
|
|
6541
6901
|
warnedTruncated = true;
|
|
6542
6902
|
if (args.json) {
|
|
6543
6903
|
process.stdout.write(JSON.stringify({ type: "meta", truncated: true }) + "\n");
|
|
6544
6904
|
} else {
|
|
6545
6905
|
console.error(
|
|
6546
|
-
paint(
|
|
6906
|
+
paint(
|
|
6907
|
+
"33",
|
|
6908
|
+
args.search ? `(search scanned only the newest ${MAX_LIMIT} level-filtered events this poll \u2014 older matches may be omitted; narrow with --level)` : `(burst exceeded ${followLimit} events/poll \u2014 some may be dropped; narrow with --level)`,
|
|
6909
|
+
stderrColor()
|
|
6910
|
+
)
|
|
6547
6911
|
);
|
|
6548
6912
|
}
|
|
6549
6913
|
} else if (!realDrop) {
|
|
@@ -6625,7 +6989,7 @@ var usage_default = defineDeepspaceCommand({
|
|
|
6625
6989
|
|
|
6626
6990
|
// src/cli/commands/add.ts
|
|
6627
6991
|
import { existsSync as existsSync18, readFileSync as readFileSync20, realpathSync as realpathSync4 } from "fs";
|
|
6628
|
-
import { dirname as
|
|
6992
|
+
import { dirname as dirname9, join as join18, resolve as resolve15 } from "path";
|
|
6629
6993
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
6630
6994
|
|
|
6631
6995
|
// src/cli/commands/feature-installer.ts
|
|
@@ -6641,17 +7005,17 @@ import {
|
|
|
6641
7005
|
readSync,
|
|
6642
7006
|
readdirSync as readdirSync5,
|
|
6643
7007
|
rmSync as rmSync4,
|
|
6644
|
-
writeFileSync as
|
|
7008
|
+
writeFileSync as writeFileSync6
|
|
6645
7009
|
} from "fs";
|
|
6646
7010
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
6647
7011
|
import { tmpdir as tmpdir2 } from "os";
|
|
6648
|
-
import { dirname as
|
|
7012
|
+
import { dirname as dirname8, isAbsolute as isAbsolute4, join as join17, resolve as resolve14 } from "path";
|
|
6649
7013
|
import { fileURLToPath } from "url";
|
|
6650
7014
|
var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
|
|
6651
7015
|
function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
6652
|
-
let dir =
|
|
7016
|
+
let dir = dirname8(fileURLToPath(moduleUrl));
|
|
6653
7017
|
for (; ; ) {
|
|
6654
|
-
const packagePath =
|
|
7018
|
+
const packagePath = join17(dir, "package.json");
|
|
6655
7019
|
if (existsSync17(packagePath)) {
|
|
6656
7020
|
try {
|
|
6657
7021
|
const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
|
|
@@ -6659,16 +7023,16 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
|
6659
7023
|
} catch {
|
|
6660
7024
|
}
|
|
6661
7025
|
}
|
|
6662
|
-
const parent =
|
|
7026
|
+
const parent = dirname8(dir);
|
|
6663
7027
|
if (parent === dir) throw new Error("Could not locate the deepspace package root");
|
|
6664
7028
|
dir = parent;
|
|
6665
7029
|
}
|
|
6666
7030
|
}
|
|
6667
7031
|
function hasFeatureCatalog(packageRoot = featurePackageRoot()) {
|
|
6668
|
-
return existsSync17(
|
|
7032
|
+
return existsSync17(join17(packageRoot, "features"));
|
|
6669
7033
|
}
|
|
6670
7034
|
function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
6671
|
-
const configPath =
|
|
7035
|
+
const configPath = join17(packageRoot, "features", id, "feature.json");
|
|
6672
7036
|
if (!existsSync17(configPath)) return null;
|
|
6673
7037
|
try {
|
|
6674
7038
|
const config = JSON.parse(readFileSync19(configPath, "utf-8"));
|
|
@@ -6681,7 +7045,7 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
|
6681
7045
|
}
|
|
6682
7046
|
}
|
|
6683
7047
|
function listFeatureConfigs(packageRoot = featurePackageRoot()) {
|
|
6684
|
-
const featuresDir =
|
|
7048
|
+
const featuresDir = join17(packageRoot, "features");
|
|
6685
7049
|
if (!existsSync17(featuresDir)) return [];
|
|
6686
7050
|
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));
|
|
6687
7051
|
}
|
|
@@ -6730,17 +7094,17 @@ function resolveFeatureDestPath(fileDest, route) {
|
|
|
6730
7094
|
return route?.protected === false ? `${appPages}${rest}` : `${appPages}(protected)/${rest}`;
|
|
6731
7095
|
}
|
|
6732
7096
|
function copyFile(src, dest) {
|
|
6733
|
-
mkdirSync6(
|
|
7097
|
+
mkdirSync6(dirname8(dest), { recursive: true });
|
|
6734
7098
|
copyFileSync(src, dest);
|
|
6735
7099
|
}
|
|
6736
7100
|
function copyFileWithImportShift(src, dest, levels) {
|
|
6737
|
-
mkdirSync6(
|
|
7101
|
+
mkdirSync6(dirname8(dest), { recursive: true });
|
|
6738
7102
|
const prefix = "../".repeat(levels);
|
|
6739
7103
|
const shifted = readFileSync19(src, "utf-8").replace(
|
|
6740
7104
|
/(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
|
|
6741
7105
|
`$1$2${prefix}$3`
|
|
6742
7106
|
);
|
|
6743
|
-
|
|
7107
|
+
writeFileSync6(dest, shifted);
|
|
6744
7108
|
}
|
|
6745
7109
|
function installFiles(packageRoot, config, targetDir, emit) {
|
|
6746
7110
|
let copied = 0;
|
|
@@ -6749,9 +7113,9 @@ function installFiles(packageRoot, config, targetDir, emit) {
|
|
|
6749
7113
|
const preexisting = [];
|
|
6750
7114
|
const missing = [];
|
|
6751
7115
|
for (const file of config.files) {
|
|
6752
|
-
const srcPath =
|
|
7116
|
+
const srcPath = join17(packageRoot, "features", config.id, file.src);
|
|
6753
7117
|
const resolvedDest = resolveFeatureDestPath(file.dest, config.route);
|
|
6754
|
-
const destPath =
|
|
7118
|
+
const destPath = join17(targetDir, resolvedDest);
|
|
6755
7119
|
const existed = existsSync17(destPath);
|
|
6756
7120
|
if (existed) preexisting.push(resolvedDest);
|
|
6757
7121
|
if (!existsSync17(srcPath)) {
|
|
@@ -6791,7 +7155,7 @@ function unresolvedSchema(schema, reason, emit) {
|
|
|
6791
7155
|
}
|
|
6792
7156
|
function integrateSchema(config, targetDir, emit) {
|
|
6793
7157
|
if (!config.schema) return [];
|
|
6794
|
-
const schemasPath =
|
|
7158
|
+
const schemasPath = join17(targetDir, "src", "schemas.ts");
|
|
6795
7159
|
if (!existsSync17(schemasPath)) {
|
|
6796
7160
|
return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
|
|
6797
7161
|
}
|
|
@@ -6826,7 +7190,7 @@ export const schemas`
|
|
|
6826
7190
|
return [unresolvedSchema(config.schema, reason, emit)];
|
|
6827
7191
|
}
|
|
6828
7192
|
content = withEntry;
|
|
6829
|
-
|
|
7193
|
+
writeFileSync6(schemasPath, content);
|
|
6830
7194
|
emit(` Schema integrated: ${exportName} -> schemas.ts`);
|
|
6831
7195
|
return [];
|
|
6832
7196
|
}
|
|
@@ -6848,7 +7212,7 @@ function unresolvedActions(actions, reason, emit) {
|
|
|
6848
7212
|
}
|
|
6849
7213
|
function integrateActions(config, targetDir, emit) {
|
|
6850
7214
|
if (!config.actions?.length) return [];
|
|
6851
|
-
const actionsPath =
|
|
7215
|
+
const actionsPath = join17(targetDir, "src", "actions", "index.ts");
|
|
6852
7216
|
if (!existsSync17(actionsPath)) {
|
|
6853
7217
|
return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
|
|
6854
7218
|
}
|
|
@@ -6892,13 +7256,13 @@ ${registryEntry}`);
|
|
|
6892
7256
|
integrated++;
|
|
6893
7257
|
emit(` Action integrated: ${action.registerAs} (${action.exportName})`);
|
|
6894
7258
|
}
|
|
6895
|
-
if (integrated > 0)
|
|
7259
|
+
if (integrated > 0) writeFileSync6(actionsPath, content);
|
|
6896
7260
|
return unresolved;
|
|
6897
7261
|
}
|
|
6898
7262
|
function integrateCode(config, targetDir, emit) {
|
|
6899
7263
|
const unresolved = [];
|
|
6900
7264
|
for (const insertion of config.code ?? []) {
|
|
6901
|
-
const targetPath =
|
|
7265
|
+
const targetPath = join17(targetDir, insertion.file);
|
|
6902
7266
|
if (!existsSync17(targetPath)) {
|
|
6903
7267
|
unresolved.push({
|
|
6904
7268
|
area: "code",
|
|
@@ -6935,27 +7299,27 @@ ${content.slice(importIndex < 0 ? 0 : importIndex)}`;
|
|
|
6935
7299
|
const prefix = insertion.placement === "before" ? "" : "\n";
|
|
6936
7300
|
const suffix = insertion.placement === "before" ? "\n" : "";
|
|
6937
7301
|
content = `${content.slice(0, insertAt)}${prefix}${insertion.insert}${suffix}${content.slice(insertAt)}`;
|
|
6938
|
-
|
|
7302
|
+
writeFileSync6(targetPath, content);
|
|
6939
7303
|
emit(` Integrated: ${insertion.file}`);
|
|
6940
7304
|
}
|
|
6941
7305
|
return unresolved;
|
|
6942
7306
|
}
|
|
6943
7307
|
function integrateIgnore(config, targetDir) {
|
|
6944
7308
|
if (!config.ignore?.length) return;
|
|
6945
|
-
const targetPath =
|
|
7309
|
+
const targetPath = join17(targetDir, ".gitignore");
|
|
6946
7310
|
const current = existsSync17(targetPath) ? readFileSync19(targetPath, "utf-8") : "";
|
|
6947
7311
|
const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
|
|
6948
7312
|
const missing = config.ignore.filter((pattern) => !present.has(pattern));
|
|
6949
7313
|
if (missing.length === 0) return;
|
|
6950
7314
|
const separator = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
6951
|
-
|
|
7315
|
+
writeFileSync6(targetPath, `${current}${separator}${missing.join("\n")}
|
|
6952
7316
|
`);
|
|
6953
7317
|
}
|
|
6954
7318
|
function integrateWrangler(config, targetDir, emit) {
|
|
6955
7319
|
const runWorkerFirst = config.wrangler?.runWorkerFirst === true;
|
|
6956
7320
|
const rateLimits = config.wrangler?.rateLimits ?? [];
|
|
6957
7321
|
if (!runWorkerFirst && rateLimits.length === 0) return [];
|
|
6958
|
-
const targetPath =
|
|
7322
|
+
const targetPath = join17(targetDir, "wrangler.toml");
|
|
6959
7323
|
const steps = [
|
|
6960
7324
|
...runWorkerFirst ? ["Set assets.run_worker_first = true in wrangler.toml."] : [],
|
|
6961
7325
|
...rateLimits.map((binding) => `Add the ${binding.name} rate-limit binding to wrangler.toml.`)
|
|
@@ -6985,7 +7349,7 @@ ${rateLimitBindingBlock(scope, binding)}
|
|
|
6985
7349
|
}
|
|
6986
7350
|
}
|
|
6987
7351
|
if (changed) {
|
|
6988
|
-
|
|
7352
|
+
writeFileSync6(targetPath, source);
|
|
6989
7353
|
emit(" Integrated: wrangler.toml");
|
|
6990
7354
|
} else emit(" Integration already present: wrangler.toml");
|
|
6991
7355
|
return [];
|
|
@@ -7039,7 +7403,7 @@ function rateLimitBindingBlock(scope, binding) {
|
|
|
7039
7403
|
}
|
|
7040
7404
|
function integrateCss(packageRoot, config, targetDir, emit) {
|
|
7041
7405
|
if (!config.css?.length) return [];
|
|
7042
|
-
const stylesPath =
|
|
7406
|
+
const stylesPath = join17(targetDir, "src", "styles.css");
|
|
7043
7407
|
if (!existsSync17(stylesPath)) {
|
|
7044
7408
|
emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
|
|
7045
7409
|
return [
|
|
@@ -7056,7 +7420,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
7056
7420
|
let integrated = 0;
|
|
7057
7421
|
const unresolved = [];
|
|
7058
7422
|
for (const cssFile of config.css) {
|
|
7059
|
-
const srcPath =
|
|
7423
|
+
const srcPath = join17(packageRoot, "features", config.id, cssFile);
|
|
7060
7424
|
if (!existsSync17(srcPath)) {
|
|
7061
7425
|
emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
|
|
7062
7426
|
unresolved.push({
|
|
@@ -7078,7 +7442,7 @@ ${css}`;
|
|
|
7078
7442
|
integrated++;
|
|
7079
7443
|
emit(` CSS integrated: ${cssFile} -> styles.css`);
|
|
7080
7444
|
}
|
|
7081
|
-
if (integrated > 0)
|
|
7445
|
+
if (integrated > 0) writeFileSync6(stylesPath, styles);
|
|
7082
7446
|
return unresolved;
|
|
7083
7447
|
}
|
|
7084
7448
|
function unresolvedRoute(route, config, reason, emit) {
|
|
@@ -7093,7 +7457,7 @@ function unresolvedRoute(route, config, reason, emit) {
|
|
|
7093
7457
|
}
|
|
7094
7458
|
function integrateRoute(config, targetDir, emit) {
|
|
7095
7459
|
if (!config.route) return [];
|
|
7096
|
-
const navPath =
|
|
7460
|
+
const navPath = join17(targetDir, "src", "nav.ts");
|
|
7097
7461
|
if (!existsSync17(navPath)) {
|
|
7098
7462
|
return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
|
|
7099
7463
|
}
|
|
@@ -7118,7 +7482,7 @@ function integrateRoute(config, targetDir, emit) {
|
|
|
7118
7482
|
const entry = ` { path: '${routePath}', label: '${config.name}'${roles} },`;
|
|
7119
7483
|
content = content.replace(NAV_MARKER, `${NAV_MARKER}
|
|
7120
7484
|
${entry}`);
|
|
7121
|
-
|
|
7485
|
+
writeFileSync6(navPath, content);
|
|
7122
7486
|
emit(` Nav wired: ${routePath} (${config.name})`);
|
|
7123
7487
|
emit(" Route: automatic (file-based routing via src/pages/)");
|
|
7124
7488
|
return [];
|
|
@@ -7144,10 +7508,10 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
|
|
|
7144
7508
|
const child = spawnSync6(packageManager, ["install"], { ...options, stdio: "inherit" });
|
|
7145
7509
|
return { error: child.error, status: child.status };
|
|
7146
7510
|
}
|
|
7147
|
-
const captureDir = mkdtempSync(
|
|
7511
|
+
const captureDir = mkdtempSync(join17(tmpdir2(), "deepspace-add-install-"));
|
|
7148
7512
|
let outputFd;
|
|
7149
7513
|
try {
|
|
7150
|
-
outputFd = openSync2(
|
|
7514
|
+
outputFd = openSync2(join17(captureDir, "output.log"), "w+");
|
|
7151
7515
|
const child = spawnSync6(packageManager, ["install"], {
|
|
7152
7516
|
...options,
|
|
7153
7517
|
stdio: ["ignore", outputFd, outputFd]
|
|
@@ -7171,7 +7535,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
|
|
|
7171
7535
|
if (declarations.every(({ values }) => !values || Object.keys(values).length === 0)) {
|
|
7172
7536
|
return { added: [], installRan: false, packageManager };
|
|
7173
7537
|
}
|
|
7174
|
-
const packagePath =
|
|
7538
|
+
const packagePath = join17(targetDir, "package.json");
|
|
7175
7539
|
if (!existsSync17(packagePath)) {
|
|
7176
7540
|
emit(" Could not patch package.json (file not found)");
|
|
7177
7541
|
return { added: [], installRan: false, packageManager };
|
|
@@ -7202,7 +7566,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
|
|
|
7202
7566
|
if (added.length === 0) {
|
|
7203
7567
|
emit(" Dependencies already present");
|
|
7204
7568
|
} else {
|
|
7205
|
-
|
|
7569
|
+
writeFileSync6(packagePath, `${JSON.stringify(pkg2, null, 2)}
|
|
7206
7570
|
`, "utf-8");
|
|
7207
7571
|
emit(` Added to package.json: ${added.map(({ name }) => name).join(", ")}`);
|
|
7208
7572
|
if (skipped.length) emit(` Already present (kept your version): ${skipped.join(", ")}`);
|
|
@@ -7238,7 +7602,7 @@ ${child.diagnosticTail}` : "";
|
|
|
7238
7602
|
}
|
|
7239
7603
|
function dependencySnapshot(targetDir) {
|
|
7240
7604
|
try {
|
|
7241
|
-
const pkg2 = JSON.parse(readFileSync19(
|
|
7605
|
+
const pkg2 = JSON.parse(readFileSync19(join17(targetDir, "package.json"), "utf-8"));
|
|
7242
7606
|
return {
|
|
7243
7607
|
dependencies: pkg2.dependencies ?? {},
|
|
7244
7608
|
devDependencies: pkg2.devDependencies ?? {}
|
|
@@ -7250,7 +7614,7 @@ function dependencySnapshot(targetDir) {
|
|
|
7250
7614
|
function installedNavFeatures(packageRoot, targetDir) {
|
|
7251
7615
|
return listFeatureConfigs(packageRoot).filter((feature) => feature.category === "nav" && feature.files.length > 0).filter((feature) => {
|
|
7252
7616
|
const signature = resolveFeatureDestPath(feature.files[0].dest, feature.route);
|
|
7253
|
-
return existsSync17(
|
|
7617
|
+
return existsSync17(join17(targetDir, signature));
|
|
7254
7618
|
}).map((feature) => feature.id);
|
|
7255
7619
|
}
|
|
7256
7620
|
function warnNavConflicts(packageRoot, config, targetDir, emit) {
|
|
@@ -7483,8 +7847,8 @@ function renderFeatureInfo(config) {
|
|
|
7483
7847
|
return lines.join("\n");
|
|
7484
7848
|
}
|
|
7485
7849
|
function isDeepSpaceApp(dir) {
|
|
7486
|
-
if (existsSync18(
|
|
7487
|
-
const packagePath =
|
|
7850
|
+
if (existsSync18(join18(dir, "wrangler.toml"))) return true;
|
|
7851
|
+
const packagePath = join18(dir, "package.json");
|
|
7488
7852
|
if (!existsSync18(packagePath)) return false;
|
|
7489
7853
|
try {
|
|
7490
7854
|
const pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
|
|
@@ -7496,9 +7860,9 @@ function isDeepSpaceApp(dir) {
|
|
|
7496
7860
|
function installedPackageRoot(fromDir) {
|
|
7497
7861
|
let dir = fromDir;
|
|
7498
7862
|
for (; ; ) {
|
|
7499
|
-
const candidate =
|
|
7500
|
-
if (existsSync18(
|
|
7501
|
-
const parent =
|
|
7863
|
+
const candidate = join18(dir, "node_modules", "deepspace");
|
|
7864
|
+
if (existsSync18(join18(candidate, "package.json"))) return candidate;
|
|
7865
|
+
const parent = dirname9(dir);
|
|
7502
7866
|
if (parent === dir) return null;
|
|
7503
7867
|
dir = parent;
|
|
7504
7868
|
}
|
|
@@ -7514,8 +7878,8 @@ async function targetInstaller(appDir) {
|
|
|
7514
7878
|
if (realpathSync4(installedRoot) === realpathSync4(featurePackageRoot())) {
|
|
7515
7879
|
return { installFeature };
|
|
7516
7880
|
}
|
|
7517
|
-
const entry =
|
|
7518
|
-
const catalog =
|
|
7881
|
+
const entry = join18(installedRoot, "dist", "feature-installer.js");
|
|
7882
|
+
const catalog = join18(installedRoot, "features");
|
|
7519
7883
|
if (!existsSync18(entry) || !existsSync18(catalog)) {
|
|
7520
7884
|
throw new Refusal(
|
|
7521
7885
|
`The app's installed deepspace package does not contain the typed feature installer.
|
|
@@ -7690,13 +8054,12 @@ function fmtDate(iso) {
|
|
|
7690
8054
|
}
|
|
7691
8055
|
async function confirm4(message) {
|
|
7692
8056
|
process.stdout.write(`${message} [y/N] `);
|
|
7693
|
-
|
|
8057
|
+
const answer = await new Promise((resolve21) => {
|
|
7694
8058
|
process.stdin.setEncoding("utf-8");
|
|
7695
|
-
process.stdin.once("data", (data) =>
|
|
7696
|
-
const answer = data.toString().trim().toLowerCase();
|
|
7697
|
-
resolve21(answer === "y" || answer === "yes");
|
|
7698
|
-
});
|
|
8059
|
+
process.stdin.once("data", (data) => resolve21(data.toString().trim().toLowerCase()));
|
|
7699
8060
|
});
|
|
8061
|
+
process.stdin.pause();
|
|
8062
|
+
return answer === "y" || answer === "yes";
|
|
7700
8063
|
}
|
|
7701
8064
|
var cannotPrompt = (json) => json || !process.stdin.isTTY;
|
|
7702
8065
|
var search = defineDeepspaceCommand({
|
|
@@ -9047,7 +9410,7 @@ var integrations_default = defineCommand9({
|
|
|
9047
9410
|
// src/cli/commands/feedback.ts
|
|
9048
9411
|
import * as p16 from "@clack/prompts";
|
|
9049
9412
|
import { existsSync as existsSync20, readFileSync as readFileSync22 } from "fs";
|
|
9050
|
-
import { dirname as
|
|
9413
|
+
import { dirname as dirname10, join as join19 } from "path";
|
|
9051
9414
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9052
9415
|
import { release } from "os";
|
|
9053
9416
|
var API_URL8 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
@@ -9068,10 +9431,10 @@ function normalizeType(raw) {
|
|
|
9068
9431
|
}
|
|
9069
9432
|
function readCliVersion() {
|
|
9070
9433
|
try {
|
|
9071
|
-
const here =
|
|
9434
|
+
const here = dirname10(fileURLToPath2(import.meta.url));
|
|
9072
9435
|
const candidates = [
|
|
9073
|
-
|
|
9074
|
-
|
|
9436
|
+
join19(here, "..", "package.json"),
|
|
9437
|
+
join19(here, "..", "..", "..", "package.json")
|
|
9075
9438
|
];
|
|
9076
9439
|
for (const path of candidates) {
|
|
9077
9440
|
if (!existsSync20(path)) continue;
|
|
@@ -9368,7 +9731,7 @@ function ok(json, data, human) {
|
|
|
9368
9731
|
function warnIfDebugRoutesEnabled(secrets) {
|
|
9369
9732
|
if (secrets.ALLOW_DEBUG_ROUTES !== "true") return false;
|
|
9370
9733
|
console.warn(
|
|
9371
|
-
"\n\u26A0 ALLOW_DEBUG_ROUTES=true
|
|
9734
|
+
"\n\u26A0 ALLOW_DEBUG_ROUTES=true enables the owner/admin debug API on the deployed app \u2014\n those users can read and mutate any app record through /api/debug/*. Redeploy to apply;\n `deepspace secrets delete ALLOW_DEBUG_ROUTES` (then redeploy) turns it back off."
|
|
9372
9735
|
);
|
|
9373
9736
|
return true;
|
|
9374
9737
|
}
|
|
@@ -9472,7 +9835,7 @@ var get2 = defineCommand10({
|
|
|
9472
9835
|
const { secrets } = await listSecrets(DEPLOY_URL11, t.token, t.appId, t.configName);
|
|
9473
9836
|
const item = secrets.find((s) => s.key === key);
|
|
9474
9837
|
if (!item)
|
|
9475
|
-
|
|
9838
|
+
throw new InputError(`Secret "${key}" not found in ${t.configName}`, "secret_not_found");
|
|
9476
9839
|
ok(
|
|
9477
9840
|
args.json === true,
|
|
9478
9841
|
{
|
|
@@ -9544,7 +9907,7 @@ var upload = defineCommand10({
|
|
|
9544
9907
|
const content = args.file === "-" ? readFileSync23(0, "utf-8") : readFileSync23(args.file, "utf-8");
|
|
9545
9908
|
const secrets = parseSecretsUpload(content);
|
|
9546
9909
|
if (Object.keys(secrets).length === 0)
|
|
9547
|
-
|
|
9910
|
+
throw new InputError("No secrets found in the input.", "empty_input");
|
|
9548
9911
|
await uploadSecrets(DEPLOY_URL11, t.token, t.appId, t.configName, secrets, args.replace);
|
|
9549
9912
|
const uploaded = Object.keys(secrets);
|
|
9550
9913
|
ok(
|
|
@@ -9575,11 +9938,9 @@ var download = defineCommand10({
|
|
|
9575
9938
|
const t = await resolveTarget(args);
|
|
9576
9939
|
const format = args.format;
|
|
9577
9940
|
if (!["dotenv", "json", "shell"].includes(format)) {
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
"invalid_format"
|
|
9582
|
-
)
|
|
9941
|
+
throw new InputError(
|
|
9942
|
+
`Unknown format "${args.format}" \u2014 use dotenv, json, or shell.`,
|
|
9943
|
+
"invalid_format"
|
|
9583
9944
|
);
|
|
9584
9945
|
}
|
|
9585
9946
|
const { secrets } = await fetchSecretsValues(DEPLOY_URL11, t.token, t.appId, t.configName);
|
|
@@ -9597,11 +9958,9 @@ var pull = defineCommand10({
|
|
|
9597
9958
|
const wranglerEnv = args.env?.trim() || void 0;
|
|
9598
9959
|
const appDir = findAppDir();
|
|
9599
9960
|
if (!appDir)
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
"not_in_app_repo"
|
|
9604
|
-
)
|
|
9961
|
+
throw new InputError(
|
|
9962
|
+
"Run from a DeepSpace app directory (one containing wrangler.toml).",
|
|
9963
|
+
"not_in_app_repo"
|
|
9605
9964
|
);
|
|
9606
9965
|
const t = await resolveTarget(args);
|
|
9607
9966
|
const ownerId = decodeJwtPayload(t.token).sub;
|
|
@@ -9665,11 +10024,9 @@ var configsCreate = defineCommand10({
|
|
|
9665
10024
|
const { configs: configs2 } = await listConfigs(DEPLOY_URL11, t.token, t.appId);
|
|
9666
10025
|
if (configs2.some((c) => c.name === name)) {
|
|
9667
10026
|
if (copyFrom) {
|
|
9668
|
-
|
|
9669
|
-
new
|
|
9670
|
-
|
|
9671
|
-
"config_exists"
|
|
9672
|
-
)
|
|
10027
|
+
throw new InputError(
|
|
10028
|
+
`Config "${name}" already exists \u2014 refusing to copy "${copyFrom}" over it. Delete it first, or pick a new name.`,
|
|
10029
|
+
"config_exists"
|
|
9673
10030
|
);
|
|
9674
10031
|
}
|
|
9675
10032
|
ok(
|
|
@@ -10569,7 +10926,7 @@ var pull_default = defineDeepspaceCommand({
|
|
|
10569
10926
|
// src/cli/commands/clone.ts
|
|
10570
10927
|
import * as p20 from "@clack/prompts";
|
|
10571
10928
|
import { existsSync as existsSync22, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
|
|
10572
|
-
import { resolve as resolve17, basename as basename5, join as
|
|
10929
|
+
import { resolve as resolve17, basename as basename5, join as join20 } from "path";
|
|
10573
10930
|
function cleanFailedCloneDir(dir) {
|
|
10574
10931
|
let entries;
|
|
10575
10932
|
try {
|
|
@@ -10579,7 +10936,7 @@ function cleanFailedCloneDir(dir) {
|
|
|
10579
10936
|
}
|
|
10580
10937
|
if (entries.length === 1 && entries[0] === ".git") {
|
|
10581
10938
|
try {
|
|
10582
|
-
rmSync5(
|
|
10939
|
+
rmSync5(join20(dir, ".git"), { recursive: true, force: true });
|
|
10583
10940
|
return [];
|
|
10584
10941
|
} catch {
|
|
10585
10942
|
return entries;
|
|
@@ -11064,8 +11421,16 @@ function printOverlaps(overlaps, json) {
|
|
|
11064
11421
|
|
|
11065
11422
|
// src/cli/commands/workspace/local.ts
|
|
11066
11423
|
import * as p24 from "@clack/prompts";
|
|
11067
|
-
import {
|
|
11068
|
-
|
|
11424
|
+
import {
|
|
11425
|
+
existsSync as existsSync23,
|
|
11426
|
+
mkdirSync as mkdirSync7,
|
|
11427
|
+
readFileSync as readFileSync25,
|
|
11428
|
+
realpathSync as realpathSync6,
|
|
11429
|
+
renameSync as renameSync2,
|
|
11430
|
+
statSync as statSync3,
|
|
11431
|
+
writeFileSync as writeFileSync7
|
|
11432
|
+
} from "fs";
|
|
11433
|
+
import { dirname as dirname11, isAbsolute as isAbsolute5, join as join21, relative as relative2, sep as sep2 } from "path";
|
|
11069
11434
|
var WORKSPACE_MARKER = "deepspace-workspace.json";
|
|
11070
11435
|
function excludeWorktreeDir(appDir, dir) {
|
|
11071
11436
|
try {
|
|
@@ -11086,7 +11451,7 @@ function worktreeMarkerPath(worktreeRoot) {
|
|
|
11086
11451
|
const result = runGit(worktreeRoot, ["rev-parse", "--absolute-git-dir"], { allowFail: true });
|
|
11087
11452
|
if (result.status !== 0) return null;
|
|
11088
11453
|
const gitDir = result.stdout.toString("utf-8").trim();
|
|
11089
|
-
return isAbsolute5(gitDir) ?
|
|
11454
|
+
return isAbsolute5(gitDir) ? join21(gitDir, WORKSPACE_MARKER) : null;
|
|
11090
11455
|
} catch {
|
|
11091
11456
|
return null;
|
|
11092
11457
|
}
|
|
@@ -11097,8 +11462,8 @@ function markManagedWorkspace(worktreeRoot, workspaceId) {
|
|
|
11097
11462
|
const marker = { version: 1, workspaceId, mode: "managed-linked" };
|
|
11098
11463
|
const tempPath = `${markerPath}.${process.pid}.tmp`;
|
|
11099
11464
|
try {
|
|
11100
|
-
mkdirSync7(
|
|
11101
|
-
|
|
11465
|
+
mkdirSync7(dirname11(markerPath), { recursive: true });
|
|
11466
|
+
writeFileSync7(tempPath, JSON.stringify(marker) + "\n");
|
|
11102
11467
|
renameSync2(tempPath, markerPath);
|
|
11103
11468
|
return true;
|
|
11104
11469
|
} catch {
|
|
@@ -11131,11 +11496,16 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
|
|
|
11131
11496
|
function defaultWorkspaceRoot(appDir, workspaceId) {
|
|
11132
11497
|
const primary = listWorktrees(appDir)[0];
|
|
11133
11498
|
if (!primary) throw new Error("Git did not report a primary worktree");
|
|
11134
|
-
return
|
|
11499
|
+
return join21(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
|
|
11135
11500
|
}
|
|
11136
11501
|
function appDirInWorktree(appDir, worktreeRoot) {
|
|
11137
11502
|
const appRelative = relative2(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
|
|
11138
|
-
return appRelative ?
|
|
11503
|
+
return appRelative ? join21(worktreeRoot, appRelative) : worktreeRoot;
|
|
11504
|
+
}
|
|
11505
|
+
function primaryAppDir(appDir) {
|
|
11506
|
+
const primary = listWorktrees(appDir)[0];
|
|
11507
|
+
if (!primary) throw new Error("Git did not report a primary worktree");
|
|
11508
|
+
return appDirInWorktree(appDir, primary.path);
|
|
11139
11509
|
}
|
|
11140
11510
|
function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint, workspaceId, options = {}) {
|
|
11141
11511
|
const createdOid = resolveCommit(appDir, startPoint);
|
|
@@ -11165,7 +11535,7 @@ function rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, workspaceI
|
|
|
11165
11535
|
function inOwnLinkedWorktree(appDir, id) {
|
|
11166
11536
|
if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
|
|
11167
11537
|
try {
|
|
11168
|
-
return statSync3(
|
|
11538
|
+
return statSync3(join21(repoToplevel(appDir), ".git")).isFile();
|
|
11169
11539
|
} catch {
|
|
11170
11540
|
return false;
|
|
11171
11541
|
}
|
|
@@ -11206,6 +11576,7 @@ function cleanupJson(outcome) {
|
|
|
11206
11576
|
worktreeRetained: outcome.worktreeRetained,
|
|
11207
11577
|
branchDeleted: outcome.branchDeleted,
|
|
11208
11578
|
...outcome.mainDir ? { mainDir: outcome.mainDir } : {},
|
|
11579
|
+
...outcome.leftoverDir ? { leftoverDir: outcome.leftoverDir } : {},
|
|
11209
11580
|
...outcome.error ? { error: outcome.error } : {}
|
|
11210
11581
|
};
|
|
11211
11582
|
}
|
|
@@ -11280,7 +11651,23 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
|
|
|
11280
11651
|
}
|
|
11281
11652
|
const remove2 = runGit(mainDir, ["worktree", "remove", linked.path], { allowFail: true });
|
|
11282
11653
|
if (remove2.status !== 0) {
|
|
11283
|
-
|
|
11654
|
+
const reason = remove2.stderr.toString("utf-8").trim() || "unknown git error";
|
|
11655
|
+
const registered = listWorktrees(mainDir);
|
|
11656
|
+
const stillRegistered = registered.length === 0 || registered.some((worktree, index) => index > 0 && worktree.path === linked.path);
|
|
11657
|
+
if (stillRegistered) {
|
|
11658
|
+
out.error = `git worktree remove ${linked.path} failed: ${reason}`;
|
|
11659
|
+
return out;
|
|
11660
|
+
}
|
|
11661
|
+
runGit(mainDir, ["worktree", "prune"], { allowFail: true });
|
|
11662
|
+
out.worktreeDir = null;
|
|
11663
|
+
const leftover = existsSync23(linked.path);
|
|
11664
|
+
if (leftover) out.leftoverDir = linked.path;
|
|
11665
|
+
else out.worktreeRemoved = linked.path;
|
|
11666
|
+
deleteWsBranch(mainDir, branch, expectedBranchOid, out);
|
|
11667
|
+
if (leftover) {
|
|
11668
|
+
const branchProblem = out.error;
|
|
11669
|
+
out.error = `git removed the worktree but could not delete its files (${reason})` + (branchProblem ? `. Also: ${branchProblem}` : "");
|
|
11670
|
+
}
|
|
11284
11671
|
return out;
|
|
11285
11672
|
}
|
|
11286
11673
|
out.worktreeRemoved = linked.path;
|
|
@@ -11302,12 +11689,20 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
|
|
|
11302
11689
|
function cleanupRefusalMessage(outcome) {
|
|
11303
11690
|
if (outcome.worktreeDir) {
|
|
11304
11691
|
const dir = shQuote(outcome.worktreeDir);
|
|
11305
|
-
|
|
11692
|
+
if (outcome.error.includes("contains modified or untracked files")) {
|
|
11693
|
+
return `Cleanup incomplete: ${outcome.error}. The worktree at ${dir} still holds uncommitted or untracked work \u2014 that's why the plain removal refused. Inspect it (\`git -C ${dir} status\`) and commit anything worth keeping to the workspace branch. Once it's clean, remove it from the main checkout: \`git worktree remove ${dir}\`.`;
|
|
11694
|
+
}
|
|
11695
|
+
return `Cleanup incomplete: ${outcome.error}. The worktree at ${dir} is still registered; git refused the plain removal for the reason above. Inspect it (\`git -C ${dir} status\`), resolve that, then remove it from the main checkout: \`git worktree remove ${dir}\`.`;
|
|
11696
|
+
}
|
|
11697
|
+
if (outcome.leftoverDir) {
|
|
11698
|
+
return `Cleanup incomplete: ${outcome.error}. Close anything using that folder \u2014 a dev server, an editor, or a shell inside it \u2014 then delete: ${shQuote(outcome.leftoverDir)}`;
|
|
11306
11699
|
}
|
|
11307
11700
|
return `Cleanup incomplete: ${outcome.error}. Finish it from the main checkout once ${shQuote(outcome.branch)} is safe to delete.`;
|
|
11308
11701
|
}
|
|
11309
11702
|
function cleanupAction(outcome) {
|
|
11310
|
-
|
|
11703
|
+
if (outcome.worktreeDir) return { cwd: outcome.worktreeDir, argv: ["git", "status"] };
|
|
11704
|
+
if (outcome.leftoverDir) return { cwd: process.cwd(), argv: ["git", "worktree", "list"] };
|
|
11705
|
+
return { cwd: process.cwd(), argv: ["git", "branch", "--list", outcome.branch] };
|
|
11311
11706
|
}
|
|
11312
11707
|
function reportCleanupHuman(outcome) {
|
|
11313
11708
|
if (outcome.error) {
|
|
@@ -11315,6 +11710,7 @@ function reportCleanupHuman(outcome) {
|
|
|
11315
11710
|
cleanupRefusalMessage({
|
|
11316
11711
|
error: outcome.error,
|
|
11317
11712
|
worktreeDir: outcome.worktreeDir,
|
|
11713
|
+
leftoverDir: outcome.leftoverDir,
|
|
11318
11714
|
branch: outcome.branch
|
|
11319
11715
|
})
|
|
11320
11716
|
);
|
|
@@ -11562,7 +11958,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11562
11958
|
|
|
11563
11959
|
// src/cli/commands/workspace/attach.ts
|
|
11564
11960
|
import * as p26 from "@clack/prompts";
|
|
11565
|
-
import { existsSync as
|
|
11961
|
+
import { existsSync as existsSync24, readdirSync as readdirSync7, realpathSync as realpathSync7, rmSync as rmSync6 } from "fs";
|
|
11566
11962
|
import { isAbsolute as isAbsolute7, resolve as resolve19 } from "path";
|
|
11567
11963
|
function samePath(a, b) {
|
|
11568
11964
|
const real = (p35) => {
|
|
@@ -11582,7 +11978,7 @@ function finishedWorkspaceMessage(id, view) {
|
|
|
11582
11978
|
return `Workspace ${id} is ${workspace.status} \u2014 its ref is retained briefly (fetch ${workspace.ref} to inspect), but attach only works on active workspaces.`;
|
|
11583
11979
|
}
|
|
11584
11980
|
function cleanFailedFreshAttachDir(dir) {
|
|
11585
|
-
if (!
|
|
11981
|
+
if (!existsSync24(dir)) return [];
|
|
11586
11982
|
let entries;
|
|
11587
11983
|
try {
|
|
11588
11984
|
entries = readdirSync7(dir);
|
|
@@ -11664,7 +12060,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11664
12060
|
const localTip = resolveCommit(appDir, `refs/heads/${branch}`);
|
|
11665
12061
|
if (localTip) {
|
|
11666
12062
|
let existing = resolveWorkspaceWorktree(listWorktrees(appDir), id);
|
|
11667
|
-
if (existing && !
|
|
12063
|
+
if (existing && !existsSync24(existing)) {
|
|
11668
12064
|
runGit(appDir, ["worktree", "prune"], { allowFail: true });
|
|
11669
12065
|
existing = null;
|
|
11670
12066
|
}
|
|
@@ -11782,7 +12178,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11782
12178
|
process.cwd(),
|
|
11783
12179
|
(typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
|
|
11784
12180
|
);
|
|
11785
|
-
if (
|
|
12181
|
+
if (existsSync24(dir)) {
|
|
11786
12182
|
spinner2?.stop("Target exists.");
|
|
11787
12183
|
throw new Refusal(`${dir} already exists \u2014 pass a different directory.`, "dir_exists");
|
|
11788
12184
|
}
|
|
@@ -12120,12 +12516,12 @@ import * as p30 from "@clack/prompts";
|
|
|
12120
12516
|
// src/cli/lib/validation.ts
|
|
12121
12517
|
import { sync as spawnSync7 } from "cross-spawn";
|
|
12122
12518
|
import { readFileSync as readFileSync26 } from "fs";
|
|
12123
|
-
import { join as
|
|
12519
|
+
import { join as join22 } from "path";
|
|
12124
12520
|
var MAX_SUMMARY_CHARS = 4e3;
|
|
12125
12521
|
var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
|
|
12126
12522
|
function configuredCommand(appDir) {
|
|
12127
12523
|
try {
|
|
12128
|
-
const pkg2 = JSON.parse(readFileSync26(
|
|
12524
|
+
const pkg2 = JSON.parse(readFileSync26(join22(appDir, "package.json"), "utf-8"));
|
|
12129
12525
|
if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
|
|
12130
12526
|
} catch {
|
|
12131
12527
|
}
|
|
@@ -12163,6 +12559,11 @@ function conflictMarkerFiles(diffCheckOutput) {
|
|
|
12163
12559
|
}
|
|
12164
12560
|
return [...files];
|
|
12165
12561
|
}
|
|
12562
|
+
function pullAfterLandAction(primaryDir, intoBranch, landedOid) {
|
|
12563
|
+
if (currentBranch(primaryDir) !== intoBranch) return null;
|
|
12564
|
+
if (resolveCommit(primaryDir, "HEAD") === landedOid) return null;
|
|
12565
|
+
return { cwd: primaryDir, argv: ["deepspace", "pull"] };
|
|
12566
|
+
}
|
|
12166
12567
|
function landResumeArgv(args) {
|
|
12167
12568
|
const argv = ["deepspace", "workspace", "land"];
|
|
12168
12569
|
const workspace = args.workspace?.trim();
|
|
@@ -12441,6 +12842,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12441
12842
|
);
|
|
12442
12843
|
}
|
|
12443
12844
|
spinner2?.stop(`Landed ${id} into ${intoBranch} at ${landedOid.slice(0, 10)}.`);
|
|
12845
|
+
const primaryDir = primaryAppDir(appDir);
|
|
12444
12846
|
const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, intoBranch, { expectedBranchOid: landedOid });
|
|
12445
12847
|
const inOwnWorktree = !cleanup && inOwnLinkedWorktree(appDir, id);
|
|
12446
12848
|
const retainedWorktree = cleanup?.worktreeRetained ?? (keepWorktree || inOwnWorktree ? appDir : null);
|
|
@@ -12458,21 +12860,24 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12458
12860
|
p30.log.info(`Retained checkout: ${retainedWorktree}`);
|
|
12459
12861
|
}
|
|
12460
12862
|
if (cleanup?.error) {
|
|
12461
|
-
const
|
|
12863
|
+
const action2 = cleanupAction({
|
|
12462
12864
|
worktreeDir: cleanup.worktreeDir,
|
|
12865
|
+
leftoverDir: cleanup.leftoverDir,
|
|
12463
12866
|
branch: cleanup.branch
|
|
12464
12867
|
});
|
|
12465
12868
|
throw new Refusal(
|
|
12466
12869
|
cleanupRefusalMessage({
|
|
12467
12870
|
error: cleanup.error,
|
|
12468
12871
|
worktreeDir: cleanup.worktreeDir,
|
|
12872
|
+
leftoverDir: cleanup.leftoverDir,
|
|
12469
12873
|
branch: cleanup.branch
|
|
12470
12874
|
}),
|
|
12471
12875
|
"cleanup_incomplete",
|
|
12472
|
-
{ actionRequired: true, action, extra: data }
|
|
12876
|
+
{ actionRequired: true, action: action2, extra: data }
|
|
12473
12877
|
);
|
|
12474
12878
|
}
|
|
12475
|
-
|
|
12879
|
+
const action = pullAfterLandAction(primaryDir, intoBranch, landedOid);
|
|
12880
|
+
return { data, ...action ? { action } : {} };
|
|
12476
12881
|
}
|
|
12477
12882
|
});
|
|
12478
12883
|
|
|
@@ -12630,12 +13035,14 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12630
13035
|
if (cleanup?.error) {
|
|
12631
13036
|
const action = cleanupAction({
|
|
12632
13037
|
worktreeDir: cleanup.worktreeDir,
|
|
13038
|
+
leftoverDir: cleanup.leftoverDir,
|
|
12633
13039
|
branch: cleanup.branch
|
|
12634
13040
|
});
|
|
12635
13041
|
throw new Refusal(
|
|
12636
13042
|
cleanupRefusalMessage({
|
|
12637
13043
|
error: cleanup.error,
|
|
12638
13044
|
worktreeDir: cleanup.worktreeDir,
|
|
13045
|
+
leftoverDir: cleanup.leftoverDir,
|
|
12639
13046
|
branch: cleanup.branch
|
|
12640
13047
|
}),
|
|
12641
13048
|
"cleanup_incomplete",
|
|
@@ -12912,7 +13319,7 @@ var source_default = defineDeepspaceCommand({
|
|
|
12912
13319
|
args: {
|
|
12913
13320
|
provider: {
|
|
12914
13321
|
type: "positional",
|
|
12915
|
-
description: "Source
|
|
13322
|
+
description: "Source authority: deepspace is platform-hosted and commit-first; github deploys the local working tree without Git sync (omit to show)",
|
|
12916
13323
|
required: false
|
|
12917
13324
|
},
|
|
12918
13325
|
app: {
|
|
@@ -13190,10 +13597,165 @@ function sameRefs(left, right) {
|
|
|
13190
13597
|
|
|
13191
13598
|
// src/cli/commands/update.ts
|
|
13192
13599
|
import * as p34 from "@clack/prompts";
|
|
13193
|
-
import { existsSync as
|
|
13194
|
-
import { join as
|
|
13600
|
+
import { existsSync as existsSync25, readFileSync as readFileSync27, writeFileSync as writeFileSync8 } from "fs";
|
|
13601
|
+
import { join as join23, resolve as resolve20 } from "path";
|
|
13602
|
+
|
|
13603
|
+
// package.json
|
|
13604
|
+
var package_default = {
|
|
13605
|
+
name: "deepspace",
|
|
13606
|
+
version: "0.19.1",
|
|
13607
|
+
engines: {
|
|
13608
|
+
node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
|
|
13609
|
+
},
|
|
13610
|
+
description: "DeepSpace SDK \u2014 build real-time collaborative apps on Cloudflare Workers",
|
|
13611
|
+
type: "module",
|
|
13612
|
+
sideEffects: false,
|
|
13613
|
+
main: "./dist/index.js",
|
|
13614
|
+
types: "./dist/index.d.ts",
|
|
13615
|
+
bin: {
|
|
13616
|
+
deepspace: "dist/cli.js"
|
|
13617
|
+
},
|
|
13618
|
+
exports: {
|
|
13619
|
+
".": {
|
|
13620
|
+
types: "./dist/index.d.ts",
|
|
13621
|
+
import: "./dist/index.js"
|
|
13622
|
+
},
|
|
13623
|
+
"./schema": {
|
|
13624
|
+
types: "./dist/schema.d.ts",
|
|
13625
|
+
import: "./dist/schema.js"
|
|
13626
|
+
},
|
|
13627
|
+
"./worker": {
|
|
13628
|
+
types: "./dist/worker.d.ts",
|
|
13629
|
+
import: "./dist/worker.js"
|
|
13630
|
+
},
|
|
13631
|
+
"./server": {
|
|
13632
|
+
types: "./dist/server.d.ts",
|
|
13633
|
+
import: "./dist/server.js"
|
|
13634
|
+
},
|
|
13635
|
+
"./testing": {
|
|
13636
|
+
types: "./dist/testing.d.ts",
|
|
13637
|
+
import: "./dist/testing.js"
|
|
13638
|
+
},
|
|
13639
|
+
"./documentation": {
|
|
13640
|
+
types: "./dist/documentation.d.ts",
|
|
13641
|
+
import: "./dist/documentation.js"
|
|
13642
|
+
},
|
|
13643
|
+
"./documentation/react": {
|
|
13644
|
+
types: "./dist/documentation-react.d.ts",
|
|
13645
|
+
import: "./dist/documentation-react.js"
|
|
13646
|
+
}
|
|
13647
|
+
},
|
|
13648
|
+
files: [
|
|
13649
|
+
"dist",
|
|
13650
|
+
"features",
|
|
13651
|
+
"CHANGELOG.md"
|
|
13652
|
+
],
|
|
13653
|
+
scripts: {
|
|
13654
|
+
build: "node ../../scripts/clean-artifacts.mjs dist --tsbuildinfo && tsup",
|
|
13655
|
+
"type-check": "tsc --noEmit",
|
|
13656
|
+
test: "vitest run",
|
|
13657
|
+
"test:documentation-browser": "playwright test --config src/documentation/runtime/__browser__/playwright.config.ts",
|
|
13658
|
+
clean: "node ../../scripts/clean-artifacts.mjs dist --tsbuildinfo",
|
|
13659
|
+
prepublishOnly: "pnpm build"
|
|
13660
|
+
},
|
|
13661
|
+
dependencies: {
|
|
13662
|
+
"@ai-sdk/anthropic": "2.0.91",
|
|
13663
|
+
"@ai-sdk/openai": "2.0.116",
|
|
13664
|
+
"@ai-sdk/openai-compatible": "1.0.46",
|
|
13665
|
+
"@clack/prompts": "^1.2.0",
|
|
13666
|
+
"@mdx-js/mdx": "^3.1.1",
|
|
13667
|
+
acorn: "^8.16.0",
|
|
13668
|
+
ai: "5.0.222",
|
|
13669
|
+
"better-auth": "1.6.23",
|
|
13670
|
+
citty: "^0.2.0",
|
|
13671
|
+
"cross-spawn": "^7.0.6",
|
|
13672
|
+
esbuild: "^0.27.0",
|
|
13673
|
+
"highlight.js": "^11.11.1",
|
|
13674
|
+
hono: "^4.6.0",
|
|
13675
|
+
jose: "^6.1.0",
|
|
13676
|
+
marked: "^18.0.7",
|
|
13677
|
+
"mdast-util-gfm": "^3.1.0",
|
|
13678
|
+
"mdast-util-to-markdown": "^2.1.2",
|
|
13679
|
+
"rehype-highlight": "^7.0.2",
|
|
13680
|
+
"remark-frontmatter": "^5.0.0",
|
|
13681
|
+
"remark-gfm": "^4.0.1",
|
|
13682
|
+
"sanitize-html": "^2.17.6",
|
|
13683
|
+
"smol-toml": "^1.6.1",
|
|
13684
|
+
yaml: "^2.9.0",
|
|
13685
|
+
yjs: "^13.6.0",
|
|
13686
|
+
zod: "^4.3.6"
|
|
13687
|
+
},
|
|
13688
|
+
peerDependencies: {
|
|
13689
|
+
"@playwright/test": ">=1.40.0",
|
|
13690
|
+
clsx: ">=2.0.0",
|
|
13691
|
+
"lucide-react": ">=0.400.0",
|
|
13692
|
+
react: "^18.0.0 || ^19.0.0",
|
|
13693
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
13694
|
+
"tailwind-merge": ">=2.0.0"
|
|
13695
|
+
},
|
|
13696
|
+
peerDependenciesMeta: {
|
|
13697
|
+
"@playwright/test": {
|
|
13698
|
+
optional: true
|
|
13699
|
+
},
|
|
13700
|
+
react: {
|
|
13701
|
+
optional: true
|
|
13702
|
+
},
|
|
13703
|
+
"react-dom": {
|
|
13704
|
+
optional: true
|
|
13705
|
+
},
|
|
13706
|
+
clsx: {
|
|
13707
|
+
optional: true
|
|
13708
|
+
},
|
|
13709
|
+
"tailwind-merge": {
|
|
13710
|
+
optional: true
|
|
13711
|
+
},
|
|
13712
|
+
"lucide-react": {
|
|
13713
|
+
optional: true
|
|
13714
|
+
}
|
|
13715
|
+
},
|
|
13716
|
+
devDependencies: {
|
|
13717
|
+
"@cloudflare/workers-types": "^4.20241205.0",
|
|
13718
|
+
"@playwright/test": "^1.59.1",
|
|
13719
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
13720
|
+
"@types/cross-spawn": "^6.0.6",
|
|
13721
|
+
"@types/react": "^19.0.0",
|
|
13722
|
+
"@types/react-dom": "^19.0.0",
|
|
13723
|
+
"@types/sanitize-html": "^2.16.1",
|
|
13724
|
+
"better-sqlite3": "^12.0.0",
|
|
13725
|
+
clsx: "^2.1.1",
|
|
13726
|
+
"framer-motion": "^12.38.0",
|
|
13727
|
+
jsdom: "^29.1.1",
|
|
13728
|
+
"lucide-react": "^1.7.0",
|
|
13729
|
+
"react-router-dom": "^7.1.0",
|
|
13730
|
+
"tailwind-merge": "^3.5.0",
|
|
13731
|
+
tsup: "^8.3.0",
|
|
13732
|
+
typescript: "^5.7.0",
|
|
13733
|
+
vitest: "^4.1.0"
|
|
13734
|
+
},
|
|
13735
|
+
keywords: [
|
|
13736
|
+
"deepspace",
|
|
13737
|
+
"cloudflare-workers",
|
|
13738
|
+
"durable-objects",
|
|
13739
|
+
"real-time",
|
|
13740
|
+
"collaborative",
|
|
13741
|
+
"sdk"
|
|
13742
|
+
],
|
|
13743
|
+
license: "Apache-2.0",
|
|
13744
|
+
homepage: "https://deep.space",
|
|
13745
|
+
repository: {
|
|
13746
|
+
type: "git",
|
|
13747
|
+
url: "git+https://github.com/deepdotspace/deepspace.git",
|
|
13748
|
+
directory: "packages/deepspace"
|
|
13749
|
+
},
|
|
13750
|
+
bugs: {
|
|
13751
|
+
url: "https://github.com/deepdotspace/deepspace/issues"
|
|
13752
|
+
}
|
|
13753
|
+
};
|
|
13754
|
+
|
|
13755
|
+
// src/cli/commands/update.ts
|
|
13195
13756
|
var REGISTRY = "https://registry.npmjs.org/deepspace/latest";
|
|
13196
13757
|
var DOCS_URL = "https://documentation.deep.space/cli-reference/commands";
|
|
13758
|
+
var COMPATIBLE_AI_VERSION = package_default.dependencies.ai;
|
|
13197
13759
|
var OLDEST_SUPPORTED_MINOR = 12;
|
|
13198
13760
|
function parseVersion(raw) {
|
|
13199
13761
|
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(raw.trim().replace(/^[\^~]/, ""));
|
|
@@ -13205,25 +13767,29 @@ function gapIsTooWide(from, to) {
|
|
|
13205
13767
|
return from.major === 0 && from.minor < OLDEST_SUPPORTED_MINOR;
|
|
13206
13768
|
}
|
|
13207
13769
|
function readAppSdkVersion(appDir) {
|
|
13208
|
-
const installed =
|
|
13209
|
-
if (
|
|
13770
|
+
const installed = join23(appDir, "node_modules", "deepspace", "package.json");
|
|
13771
|
+
if (existsSync25(installed)) {
|
|
13210
13772
|
const parsed2 = JSON.parse(readFileSync27(installed, "utf8"));
|
|
13211
13773
|
if (typeof parsed2.version === "string") return parsed2.version;
|
|
13212
13774
|
}
|
|
13213
|
-
const manifest =
|
|
13214
|
-
if (!
|
|
13775
|
+
const manifest = join23(appDir, "package.json");
|
|
13776
|
+
if (!existsSync25(manifest)) return null;
|
|
13215
13777
|
const parsed = JSON.parse(readFileSync27(manifest, "utf8"));
|
|
13216
13778
|
return parsed.dependencies?.deepspace ?? null;
|
|
13217
13779
|
}
|
|
13218
13780
|
function pinSdkVersion(appDir, version) {
|
|
13219
|
-
const path =
|
|
13781
|
+
const path = join23(appDir, "package.json");
|
|
13220
13782
|
const source = readFileSync27(path, "utf8");
|
|
13221
|
-
|
|
13783
|
+
let updated = source.replace(
|
|
13222
13784
|
/("deepspace"\s*:\s*")[^"]+(")/,
|
|
13223
13785
|
(_match, head, tail) => `${head}^${version}${tail}`
|
|
13224
13786
|
);
|
|
13787
|
+
updated = updated.replace(
|
|
13788
|
+
/("ai"\s*:\s*")[^"]+(")/,
|
|
13789
|
+
(_match, head, tail) => `${head}${COMPATIBLE_AI_VERSION}${tail}`
|
|
13790
|
+
);
|
|
13225
13791
|
if (updated === source) return false;
|
|
13226
|
-
|
|
13792
|
+
writeFileSync8(path, updated);
|
|
13227
13793
|
return true;
|
|
13228
13794
|
}
|
|
13229
13795
|
async function latestPublishedVersion() {
|
|
@@ -13362,9 +13928,9 @@ var update_default = defineDeepspaceCommand({
|
|
|
13362
13928
|
});
|
|
13363
13929
|
|
|
13364
13930
|
// src/cli/cli.ts
|
|
13365
|
-
var __dirname =
|
|
13366
|
-
var pkg = JSON.parse(readFileSync28(
|
|
13367
|
-
if (process.platform === "linux" && !process.env.SSL_CERT_FILE &&
|
|
13931
|
+
var __dirname = dirname12(fileURLToPath3(import.meta.url));
|
|
13932
|
+
var pkg = JSON.parse(readFileSync28(join24(__dirname, "..", "package.json"), "utf-8"));
|
|
13933
|
+
if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync26("/etc/ssl/certs/ca-certificates.crt")) {
|
|
13368
13934
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
13369
13935
|
}
|
|
13370
13936
|
var create2 = defineCommand14({
|