deepspace 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1087 -236
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +174 -10
- package/dist/index.js +589 -276
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +3805 -0
- package/dist/server.js +6371 -0
- package/dist/server.js.map +1 -0
- package/dist/worker.d.ts +152 -16
- package/dist/worker.js +246 -29
- package/dist/worker.js.map +1 -1
- package/package.json +10 -3
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/cli.ts
|
|
4
|
-
import { defineCommand as
|
|
4
|
+
import { defineCommand as defineCommand17, runMain } from "citty";
|
|
5
5
|
import { execSync as execSync5 } from "child_process";
|
|
6
|
-
import { readFileSync as
|
|
7
|
-
import { dirname as dirname2, join as
|
|
6
|
+
import { readFileSync as readFileSync10, existsSync as existsSync13 } from "fs";
|
|
7
|
+
import { dirname as dirname2, join as join13 } from "path";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
|
|
10
10
|
// src/cli/commands/login.ts
|
|
@@ -37,20 +37,72 @@ function createSpinner() {
|
|
|
37
37
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
38
38
|
import { join } from "path";
|
|
39
39
|
import { parse as parseToml } from "smol-toml";
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
|
|
41
|
+
// src/server/rooms/app-name.ts
|
|
42
|
+
var APP_NAME_RULES = {
|
|
43
|
+
/** ^[a-z0-9](-?[a-z0-9])+$ — RFC 1035 host label, no consecutive dashes. */
|
|
44
|
+
pattern: /^[a-z0-9](?:-?[a-z0-9])+$/,
|
|
45
|
+
minLength: 2,
|
|
46
|
+
maxLength: 63
|
|
47
|
+
};
|
|
48
|
+
function validateAppName(raw) {
|
|
49
|
+
if (typeof raw !== "string") {
|
|
50
|
+
return { valid: false, reason: "App name must be a string" };
|
|
51
|
+
}
|
|
52
|
+
const name = raw.trim();
|
|
53
|
+
if (!name) {
|
|
54
|
+
return { valid: false, reason: "App name is required" };
|
|
55
|
+
}
|
|
56
|
+
if (name.length < APP_NAME_RULES.minLength) {
|
|
57
|
+
return { valid: false, reason: `App name "${name}" is too short (min ${APP_NAME_RULES.minLength} chars)` };
|
|
58
|
+
}
|
|
59
|
+
if (name.length > APP_NAME_RULES.maxLength) {
|
|
60
|
+
return { valid: false, reason: `App name "${name}" is too long (max ${APP_NAME_RULES.maxLength} chars)` };
|
|
61
|
+
}
|
|
62
|
+
if (!APP_NAME_RULES.pattern.test(name)) {
|
|
63
|
+
return {
|
|
64
|
+
valid: false,
|
|
65
|
+
reason: `App name "${name}" is invalid. Use lowercase letters, digits, and single hyphens only (must start + end with alphanumeric). Examples: my-app, search-v2, unison-search.`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return { valid: true, name };
|
|
69
|
+
}
|
|
70
|
+
function resolveAppName(raw) {
|
|
71
|
+
const strict = validateAppName(raw);
|
|
72
|
+
if (strict.valid) return { ok: true, name: strict.name };
|
|
73
|
+
if (typeof raw !== "string") return { ok: false, reason: strict.reason };
|
|
74
|
+
const trimmed = raw.trim();
|
|
75
|
+
if (!trimmed) return { ok: false, reason: "App name is required" };
|
|
76
|
+
const sanitized = sanitizeAppName(trimmed);
|
|
77
|
+
const sanitizedStrict = validateAppName(sanitized);
|
|
78
|
+
if (!sanitizedStrict.valid) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
reason: `App name "${trimmed}" can't be sanitized into a valid name (${sanitizedStrict.reason}). Pick something matching: lowercase letters, digits, single hyphens, ${APP_NAME_RULES.minLength}-${APP_NAME_RULES.maxLength} chars.`
|
|
82
|
+
};
|
|
52
83
|
}
|
|
84
|
+
return {
|
|
85
|
+
ok: true,
|
|
86
|
+
name: sanitized,
|
|
87
|
+
warning: `App name "${trimmed}" doesn't conform to deploy rules \u2014 using "${sanitized}" instead. Update wrangler.toml's \`name\` to silence this warning.`
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function sanitizeAppName(raw) {
|
|
91
|
+
return raw.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/cli/env.ts
|
|
95
|
+
var ENV_URLS = {
|
|
96
|
+
auth: "https://deepspace-auth.eudaimonicincorporated.workers.dev",
|
|
97
|
+
api: "https://deepspace-api.eudaimonicincorporated.workers.dev",
|
|
98
|
+
platform: "https://deepspace-platform-worker.eudaimonicincorporated.workers.dev",
|
|
99
|
+
deploy: "https://deepspace-deploy.eudaimonicincorporated.workers.dev"
|
|
53
100
|
};
|
|
101
|
+
var ENVS = {
|
|
102
|
+
dev: ENV_URLS,
|
|
103
|
+
prod: ENV_URLS
|
|
104
|
+
};
|
|
105
|
+
var DASHBOARD_URL = "https://dashboard.deep.space";
|
|
54
106
|
async function fetchPublicKey(authUrl) {
|
|
55
107
|
const res = await fetch(`${authUrl}/api/auth/jwks`);
|
|
56
108
|
if (!res.ok) {
|
|
@@ -68,10 +120,16 @@ function readAppName(appDir) {
|
|
|
68
120
|
throw new Error("No wrangler.toml found. Are you in a DeepSpace app directory?");
|
|
69
121
|
}
|
|
70
122
|
const parsed = parseToml(readFileSync(wranglerPath, "utf-8"));
|
|
71
|
-
|
|
72
|
-
|
|
123
|
+
const resolved = resolveAppName(parsed.name);
|
|
124
|
+
if (!resolved.ok) {
|
|
125
|
+
throw new Error(`wrangler.toml: ${resolved.reason}`);
|
|
126
|
+
}
|
|
127
|
+
if (parsed.name !== resolved.name) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`wrangler.toml: name "${parsed.name}" is not in canonical form. Update \`name\` to "${resolved.name}" and re-run.`
|
|
130
|
+
);
|
|
73
131
|
}
|
|
74
|
-
return
|
|
132
|
+
return resolved.name;
|
|
75
133
|
}
|
|
76
134
|
async function mintAppOwnerJwt(authUrl, callerJwt, appName) {
|
|
77
135
|
const res = await fetch(`${authUrl}/api/auth/mint-app-token`, {
|
|
@@ -92,6 +150,27 @@ async function mintAppOwnerJwt(authUrl, callerJwt, appName) {
|
|
|
92
150
|
}
|
|
93
151
|
return body.token;
|
|
94
152
|
}
|
|
153
|
+
async function fetchAppIdentityToken(deployUrl, callerJwt, appName) {
|
|
154
|
+
const res = await fetch(
|
|
155
|
+
`${deployUrl}/api/apps/${encodeURIComponent(appName)}/identity-token`,
|
|
156
|
+
{
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: { Authorization: `Bearer ${callerJwt}` }
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
if (res.status === 404) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
if (!res.ok) {
|
|
165
|
+
const err = await res.text().catch(() => "");
|
|
166
|
+
throw new Error(`Failed to fetch APP_IDENTITY_TOKEN (${res.status}): ${err}`);
|
|
167
|
+
}
|
|
168
|
+
const body = await res.json();
|
|
169
|
+
if (!body.token) {
|
|
170
|
+
throw new Error(`Deploy worker returned no token: ${body.error ?? "unknown error"}`);
|
|
171
|
+
}
|
|
172
|
+
return body.token;
|
|
173
|
+
}
|
|
95
174
|
var SDK_MANAGED_KEYS = /* @__PURE__ */ new Set([
|
|
96
175
|
"AUTH_JWT_PUBLIC_KEY",
|
|
97
176
|
"AUTH_JWT_ISSUER",
|
|
@@ -100,6 +179,7 @@ var SDK_MANAGED_KEYS = /* @__PURE__ */ new Set([
|
|
|
100
179
|
"PLATFORM_WORKER_URL",
|
|
101
180
|
"OWNER_USER_ID",
|
|
102
181
|
"APP_OWNER_JWT",
|
|
182
|
+
"APP_IDENTITY_TOKEN",
|
|
103
183
|
"INTERNAL_STORAGE_HMAC_SECRET",
|
|
104
184
|
"ALLOW_DEBUG_ROUTES"
|
|
105
185
|
]);
|
|
@@ -212,6 +292,7 @@ async function writeDevVars(appDir, env, ownerId, callerJwt) {
|
|
|
212
292
|
const publicKey = await fetchPublicKey(urls.auth);
|
|
213
293
|
const appName = readAppName(appDir);
|
|
214
294
|
const appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt, appName);
|
|
295
|
+
const appIdentityToken = await fetchAppIdentityToken(urls.deploy, callerJwt, appName);
|
|
215
296
|
const sdkVars = [
|
|
216
297
|
`AUTH_JWT_PUBLIC_KEY="${publicKey}"`,
|
|
217
298
|
`AUTH_JWT_ISSUER=${urls.auth}/api/auth`,
|
|
@@ -220,6 +301,7 @@ async function writeDevVars(appDir, env, ownerId, callerJwt) {
|
|
|
220
301
|
`PLATFORM_WORKER_URL=${urls.platform}`,
|
|
221
302
|
`OWNER_USER_ID=${ownerId}`,
|
|
222
303
|
`APP_OWNER_JWT=${appOwnerJwt}`,
|
|
304
|
+
...appIdentityToken ? [`APP_IDENTITY_TOKEN=${appIdentityToken}`] : [],
|
|
223
305
|
`INTERNAL_STORAGE_HMAC_SECRET=dev-${Date.now()}`,
|
|
224
306
|
// Open /api/debug/* (proxied to RecordRoom) for dev + tests only.
|
|
225
307
|
// Production secrets never set this, so deployed apps 404 the route.
|
|
@@ -307,7 +389,15 @@ var login_default = defineCommand({
|
|
|
307
389
|
}
|
|
308
390
|
storeCredentials(result.sessionToken, result.jwt);
|
|
309
391
|
s.stop("Authenticated");
|
|
310
|
-
p2.
|
|
392
|
+
p2.log.success(`Logged in as ${result.name ?? result.email}`);
|
|
393
|
+
p2.note(
|
|
394
|
+
`Manage your account, deployed apps, billing, and subscription
|
|
395
|
+
plans on the web dashboard:
|
|
396
|
+
|
|
397
|
+
${DASHBOARD_URL}`,
|
|
398
|
+
"Dashboard"
|
|
399
|
+
);
|
|
400
|
+
p2.outro("Done");
|
|
311
401
|
}
|
|
312
402
|
});
|
|
313
403
|
async function pollForCompletion(sessionId) {
|
|
@@ -345,7 +435,7 @@ function openBrowser(url) {
|
|
|
345
435
|
});
|
|
346
436
|
}
|
|
347
437
|
function sleep(ms) {
|
|
348
|
-
return new Promise((
|
|
438
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
349
439
|
}
|
|
350
440
|
async function doEmailLogin(email, password) {
|
|
351
441
|
const res = await fetch(`${AUTH_URL}/api/auth/sign-in/email`, {
|
|
@@ -416,14 +506,25 @@ var logout_default = defineCommand2({
|
|
|
416
506
|
|
|
417
507
|
// src/cli/commands/dev.ts
|
|
418
508
|
import { defineCommand as defineCommand3 } from "citty";
|
|
419
|
-
import { existsSync as
|
|
420
|
-
import { join as join5, resolve } from "path";
|
|
421
|
-
import
|
|
509
|
+
import { existsSync as existsSync5 } from "fs";
|
|
510
|
+
import { join as join5, resolve as resolve2 } from "path";
|
|
511
|
+
import spawn from "cross-spawn";
|
|
422
512
|
|
|
423
513
|
// src/cli/auth.ts
|
|
424
514
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
425
515
|
import { join as join4 } from "path";
|
|
426
516
|
import { homedir as homedir3 } from "os";
|
|
517
|
+
|
|
518
|
+
// src/cli/jwt.ts
|
|
519
|
+
function decodeJwtPayload(token2) {
|
|
520
|
+
const payloadSegment = token2.split(".")[1];
|
|
521
|
+
if (!payloadSegment) {
|
|
522
|
+
throw new Error("JWT payload is missing");
|
|
523
|
+
}
|
|
524
|
+
return JSON.parse(Buffer.from(payloadSegment, "base64url").toString("utf8"));
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/cli/auth.ts
|
|
427
528
|
var AUTH_URL3 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
|
|
428
529
|
var DIR2 = join4(homedir3(), ".deepspace");
|
|
429
530
|
var SESSION_PATH2 = join4(DIR2, "session");
|
|
@@ -439,23 +540,60 @@ async function ensureToken() {
|
|
|
439
540
|
return existing;
|
|
440
541
|
}
|
|
441
542
|
}
|
|
442
|
-
const
|
|
443
|
-
if (!
|
|
543
|
+
const token2 = await exchangeSession(AUTH_URL3, sessionToken);
|
|
544
|
+
if (!token2) {
|
|
444
545
|
throw new Error("Session expired. Run `deepspace login` to re-authenticate.");
|
|
445
546
|
}
|
|
446
547
|
mkdirSync2(DIR2, { recursive: true });
|
|
447
|
-
writeFileSync3(TOKEN_PATH2,
|
|
448
|
-
return
|
|
548
|
+
writeFileSync3(TOKEN_PATH2, token2, { mode: 384 });
|
|
549
|
+
return token2;
|
|
449
550
|
}
|
|
450
551
|
function isTokenValid(jwt) {
|
|
451
552
|
try {
|
|
452
|
-
const payload =
|
|
453
|
-
return payload.exp * 1e3 > Date.now() + 3e4;
|
|
553
|
+
const payload = decodeJwtPayload(jwt);
|
|
554
|
+
return typeof payload.exp === "number" && payload.exp * 1e3 > Date.now() + 3e4;
|
|
454
555
|
} catch {
|
|
455
556
|
return false;
|
|
456
557
|
}
|
|
457
558
|
}
|
|
458
559
|
|
|
560
|
+
// src/cli/lib/preflight.ts
|
|
561
|
+
import { execFileSync } from "child_process";
|
|
562
|
+
import { existsSync as existsSync4 } from "fs";
|
|
563
|
+
import { resolve } from "path";
|
|
564
|
+
var DLL_NOT_FOUND = -1073741515;
|
|
565
|
+
function preflightWindowsWorkerd(appDir) {
|
|
566
|
+
if (process.platform !== "win32") return;
|
|
567
|
+
const workerdExe = resolve(
|
|
568
|
+
appDir,
|
|
569
|
+
"node_modules",
|
|
570
|
+
"@cloudflare",
|
|
571
|
+
"workerd-windows-64",
|
|
572
|
+
"bin",
|
|
573
|
+
"workerd.exe"
|
|
574
|
+
);
|
|
575
|
+
if (!existsSync4(workerdExe)) return;
|
|
576
|
+
try {
|
|
577
|
+
execFileSync(workerdExe, ["--help"], { stdio: "pipe", timeout: 1e4 });
|
|
578
|
+
return;
|
|
579
|
+
} catch (err) {
|
|
580
|
+
const status2 = err.status;
|
|
581
|
+
if (status2 !== DLL_NOT_FOUND) return;
|
|
582
|
+
console.error("");
|
|
583
|
+
console.error(" workerd (Cloudflare Workers runtime) cannot start on this system.");
|
|
584
|
+
console.error(" It needs the Microsoft Visual C++ Redistributable, which is not installed.");
|
|
585
|
+
console.error("");
|
|
586
|
+
console.error(" Install it with any of:");
|
|
587
|
+
console.error(" winget install Microsoft.VCRedist.2015+.x64");
|
|
588
|
+
console.error(" choco install -y vcredist140");
|
|
589
|
+
console.error(" https://aka.ms/vs/17/release/vc_redist.x64.exe");
|
|
590
|
+
console.error("");
|
|
591
|
+
console.error(" Then restart this terminal and re-run the command.");
|
|
592
|
+
console.error("");
|
|
593
|
+
process.exit(1);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
459
597
|
// src/cli/commands/dev.ts
|
|
460
598
|
var DEFAULT_PORT = 5173;
|
|
461
599
|
var dev_default = defineCommand3({
|
|
@@ -481,23 +619,23 @@ var dev_default = defineCommand3({
|
|
|
481
619
|
}
|
|
482
620
|
},
|
|
483
621
|
async run({ args }) {
|
|
484
|
-
const appDir =
|
|
622
|
+
const appDir = resolve2(args.dir ?? ".");
|
|
485
623
|
const env = args.prod ? "prod" : "dev";
|
|
486
624
|
const port = resolvePort(args.port);
|
|
487
|
-
if (!
|
|
625
|
+
if (!existsSync5(join5(appDir, "wrangler.toml"))) {
|
|
488
626
|
console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
|
|
489
627
|
process.exit(1);
|
|
490
628
|
}
|
|
491
|
-
let
|
|
629
|
+
let token2;
|
|
492
630
|
try {
|
|
493
|
-
|
|
631
|
+
token2 = await ensureToken();
|
|
494
632
|
} catch (err) {
|
|
495
633
|
console.error(err.message);
|
|
496
634
|
process.exit(1);
|
|
497
635
|
}
|
|
498
636
|
let payload;
|
|
499
637
|
try {
|
|
500
|
-
payload =
|
|
638
|
+
payload = decodeJwtPayload(token2);
|
|
501
639
|
} catch {
|
|
502
640
|
console.error("Malformed session token. Run `npx deepspace login`.");
|
|
503
641
|
process.exit(1);
|
|
@@ -505,7 +643,8 @@ var dev_default = defineCommand3({
|
|
|
505
643
|
console.log(`Logged in as ${payload.name ?? payload.email}`);
|
|
506
644
|
console.log(`Environment: ${env}`);
|
|
507
645
|
console.log(`Port: ${port}`);
|
|
508
|
-
await writeDevVars(appDir, env, payload.sub,
|
|
646
|
+
await writeDevVars(appDir, env, payload.sub, token2);
|
|
647
|
+
preflightWindowsWorkerd(appDir);
|
|
509
648
|
console.log("Starting dev server...\n");
|
|
510
649
|
const vite = spawn(
|
|
511
650
|
"npx",
|
|
@@ -513,7 +652,6 @@ var dev_default = defineCommand3({
|
|
|
513
652
|
{
|
|
514
653
|
cwd: appDir,
|
|
515
654
|
stdio: "inherit",
|
|
516
|
-
shell: process.platform === "win32",
|
|
517
655
|
env: { ...process.env, DEEPSPACE_PORT: String(port) }
|
|
518
656
|
}
|
|
519
657
|
);
|
|
@@ -696,9 +834,35 @@ function isAlive(pid) {
|
|
|
696
834
|
|
|
697
835
|
// src/cli/commands/test.ts
|
|
698
836
|
import { defineCommand as defineCommand5 } from "citty";
|
|
699
|
-
import { existsSync as
|
|
700
|
-
import { join as join6, resolve as
|
|
701
|
-
import {
|
|
837
|
+
import { existsSync as existsSync6 } from "fs";
|
|
838
|
+
import { join as join6, resolve as resolve3 } from "path";
|
|
839
|
+
import { sync as spawnSync3 } from "cross-spawn";
|
|
840
|
+
|
|
841
|
+
// src/cli/lib/playwright.ts
|
|
842
|
+
import { execSync } from "child_process";
|
|
843
|
+
import { sync as spawnSync2 } from "cross-spawn";
|
|
844
|
+
function ensurePlaywright(appDir) {
|
|
845
|
+
try {
|
|
846
|
+
execSync("npx playwright --version", { cwd: appDir, stdio: "pipe" });
|
|
847
|
+
} catch {
|
|
848
|
+
console.log("Installing Playwright...");
|
|
849
|
+
execSync("npm install -D @playwright/test", { cwd: appDir, stdio: "pipe" });
|
|
850
|
+
}
|
|
851
|
+
const isLinux = process.platform === "linux";
|
|
852
|
+
const isRoot = isLinux && typeof process.getuid === "function" && process.getuid() === 0;
|
|
853
|
+
const args = isRoot ? ["install", "--with-deps", "chromium"] : ["install", "chromium"];
|
|
854
|
+
const result = spawnSync2("npx", ["playwright", ...args], {
|
|
855
|
+
cwd: appDir,
|
|
856
|
+
stdio: "inherit"
|
|
857
|
+
});
|
|
858
|
+
if (result.status !== 0) {
|
|
859
|
+
console.error("\nPlaywright install failed. On Linux, system libs may be missing. Try:");
|
|
860
|
+
console.error(" sudo npx playwright install --with-deps chromium");
|
|
861
|
+
process.exit(1);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// src/cli/commands/test.ts
|
|
702
866
|
var DEFAULT_PORT3 = 5173;
|
|
703
867
|
var test_default = defineCommand5({
|
|
704
868
|
meta: {
|
|
@@ -718,25 +882,26 @@ var test_default = defineCommand5({
|
|
|
718
882
|
}
|
|
719
883
|
},
|
|
720
884
|
async run({ args }) {
|
|
721
|
-
const appDir =
|
|
885
|
+
const appDir = resolve3(".");
|
|
722
886
|
const suite = args.suite ?? "default";
|
|
723
887
|
const port = resolvePort3(args.port);
|
|
724
|
-
if (!
|
|
888
|
+
if (!existsSync6(join6(appDir, "wrangler.toml"))) {
|
|
725
889
|
console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
|
|
726
890
|
process.exit(1);
|
|
727
891
|
}
|
|
728
|
-
let
|
|
892
|
+
let token2;
|
|
729
893
|
let ownerId;
|
|
730
894
|
try {
|
|
731
|
-
|
|
732
|
-
const payload =
|
|
895
|
+
token2 = await ensureToken();
|
|
896
|
+
const payload = decodeJwtPayload(token2);
|
|
733
897
|
ownerId = payload.sub;
|
|
734
898
|
} catch (err) {
|
|
735
899
|
console.error("`deepspace test` requires you to be logged in. Run `deepspace login` first.");
|
|
736
900
|
process.exit(1);
|
|
737
901
|
}
|
|
738
|
-
await writeDevVars(appDir, "dev", ownerId,
|
|
902
|
+
await writeDevVars(appDir, "dev", ownerId, token2);
|
|
739
903
|
if (suite !== "unit") {
|
|
904
|
+
preflightWindowsWorkerd(appDir);
|
|
740
905
|
ensurePlaywright(appDir);
|
|
741
906
|
}
|
|
742
907
|
let exitCode = 0;
|
|
@@ -773,7 +938,7 @@ var test_default = defineCommand5({
|
|
|
773
938
|
}
|
|
774
939
|
});
|
|
775
940
|
function runPlaywright(appDir, testFiles, port) {
|
|
776
|
-
const result =
|
|
941
|
+
const result = spawnSync3("npx", [
|
|
777
942
|
"playwright",
|
|
778
943
|
"test",
|
|
779
944
|
"--config",
|
|
@@ -782,36 +947,17 @@ function runPlaywright(appDir, testFiles, port) {
|
|
|
782
947
|
], {
|
|
783
948
|
cwd: appDir,
|
|
784
949
|
stdio: "inherit",
|
|
785
|
-
shell: process.platform === "win32",
|
|
786
950
|
env: { ...process.env, DEEPSPACE_PORT: String(port) }
|
|
787
951
|
});
|
|
788
952
|
return result.status ?? 1;
|
|
789
953
|
}
|
|
790
954
|
function runVitest(appDir) {
|
|
791
|
-
const result =
|
|
955
|
+
const result = spawnSync3("npx", ["vitest", "run", "--passWithNoTests"], {
|
|
792
956
|
cwd: appDir,
|
|
793
|
-
stdio: "inherit"
|
|
794
|
-
shell: process.platform === "win32"
|
|
957
|
+
stdio: "inherit"
|
|
795
958
|
});
|
|
796
959
|
return result.status ?? 1;
|
|
797
960
|
}
|
|
798
|
-
function ensurePlaywright(appDir) {
|
|
799
|
-
try {
|
|
800
|
-
execSync("npx playwright --version", { cwd: appDir, stdio: "pipe" });
|
|
801
|
-
} catch {
|
|
802
|
-
console.log("Installing Playwright...");
|
|
803
|
-
execSync("npm install -D @playwright/test", { cwd: appDir, stdio: "pipe" });
|
|
804
|
-
}
|
|
805
|
-
const isLinux = process.platform === "linux";
|
|
806
|
-
const isRoot = isLinux && typeof process.getuid === "function" && process.getuid() === 0;
|
|
807
|
-
const args = isRoot ? ["install", "--with-deps", "chromium"] : ["install", "chromium"];
|
|
808
|
-
const result = spawnSync2("npx", ["playwright", ...args], { cwd: appDir, stdio: "inherit" });
|
|
809
|
-
if (result.status !== 0) {
|
|
810
|
-
console.error("\nPlaywright install failed. On Linux, system libs may be missing \u2014 try:");
|
|
811
|
-
console.error(" sudo npx playwright install --with-deps chromium");
|
|
812
|
-
process.exit(1);
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
961
|
function resolvePort3(arg) {
|
|
816
962
|
const raw = arg ?? process.env.DEEPSPACE_PORT;
|
|
817
963
|
if (!raw) return DEFAULT_PORT3;
|
|
@@ -823,10 +969,57 @@ function resolvePort3(arg) {
|
|
|
823
969
|
return n;
|
|
824
970
|
}
|
|
825
971
|
|
|
826
|
-
// src/cli/commands/
|
|
972
|
+
// src/cli/commands/screenshot.ts
|
|
827
973
|
import { defineCommand as defineCommand6 } from "citty";
|
|
974
|
+
import { sync as spawnSync4 } from "cross-spawn";
|
|
975
|
+
import { resolve as resolve4 } from "path";
|
|
976
|
+
var screenshot_default = defineCommand6({
|
|
977
|
+
meta: {
|
|
978
|
+
name: "screenshot",
|
|
979
|
+
description: "Take a Playwright screenshot, installing Chromium on demand"
|
|
980
|
+
},
|
|
981
|
+
args: {
|
|
982
|
+
url: {
|
|
983
|
+
type: "positional",
|
|
984
|
+
description: "URL to capture, for example http://localhost:5173/",
|
|
985
|
+
required: true
|
|
986
|
+
},
|
|
987
|
+
output: {
|
|
988
|
+
type: "positional",
|
|
989
|
+
description: "Output image path, for example screenshot.png",
|
|
990
|
+
required: true
|
|
991
|
+
},
|
|
992
|
+
"full-page": {
|
|
993
|
+
type: "boolean",
|
|
994
|
+
description: "Capture the full scrollable page",
|
|
995
|
+
required: false
|
|
996
|
+
},
|
|
997
|
+
"wait-for-timeout": {
|
|
998
|
+
type: "string",
|
|
999
|
+
description: "Milliseconds to wait before capture",
|
|
1000
|
+
required: false
|
|
1001
|
+
}
|
|
1002
|
+
},
|
|
1003
|
+
run({ args }) {
|
|
1004
|
+
const appDir = resolve4(".");
|
|
1005
|
+
ensurePlaywright(appDir);
|
|
1006
|
+
const playwrightArgs = ["playwright", "screenshot", args.url, args.output];
|
|
1007
|
+
if (args["full-page"]) playwrightArgs.push("--full-page");
|
|
1008
|
+
if (args["wait-for-timeout"]) {
|
|
1009
|
+
playwrightArgs.push("--wait-for-timeout", String(args["wait-for-timeout"]));
|
|
1010
|
+
}
|
|
1011
|
+
const result = spawnSync4("npx", playwrightArgs, {
|
|
1012
|
+
cwd: appDir,
|
|
1013
|
+
stdio: "inherit"
|
|
1014
|
+
});
|
|
1015
|
+
process.exit(result.status ?? 1);
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
// src/cli/commands/test-accounts.ts
|
|
1020
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
828
1021
|
import * as p3 from "@clack/prompts";
|
|
829
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as
|
|
1022
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
|
|
830
1023
|
import { join as join7 } from "path";
|
|
831
1024
|
import { homedir as homedir4 } from "os";
|
|
832
1025
|
var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
|
|
@@ -834,7 +1027,7 @@ var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
|
|
|
834
1027
|
var DIR3 = join7(homedir4(), ".deepspace");
|
|
835
1028
|
var ACCOUNTS_PATH = join7(DIR3, "test-accounts.json");
|
|
836
1029
|
function loadAccounts() {
|
|
837
|
-
if (!
|
|
1030
|
+
if (!existsSync7(ACCOUNTS_PATH)) return [];
|
|
838
1031
|
try {
|
|
839
1032
|
return JSON.parse(readFileSync4(ACCOUNTS_PATH, "utf-8"));
|
|
840
1033
|
} catch {
|
|
@@ -846,8 +1039,8 @@ function saveAccounts(accounts) {
|
|
|
846
1039
|
writeFileSync4(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), { mode: 384 });
|
|
847
1040
|
}
|
|
848
1041
|
function sessionCookie() {
|
|
849
|
-
const
|
|
850
|
-
return `${SESSION_COOKIE2}=${encodeURIComponent(
|
|
1042
|
+
const token2 = readFileSync4(SESSION_PATH2, "utf-8").trim();
|
|
1043
|
+
return `${SESSION_COOKIE2}=${encodeURIComponent(token2)}`;
|
|
851
1044
|
}
|
|
852
1045
|
async function fetchRemoteAccounts() {
|
|
853
1046
|
const res = await fetch(`${AUTH_URL4}/api/auth/test-accounts`, {
|
|
@@ -869,7 +1062,7 @@ async function deleteRemote(id) {
|
|
|
869
1062
|
throw new Error(data.error ?? `DELETE returned ${res.status}`);
|
|
870
1063
|
}
|
|
871
1064
|
}
|
|
872
|
-
var create =
|
|
1065
|
+
var create = defineCommand7({
|
|
873
1066
|
meta: {
|
|
874
1067
|
name: "create",
|
|
875
1068
|
description: "Create a test account"
|
|
@@ -939,7 +1132,7 @@ var create = defineCommand6({
|
|
|
939
1132
|
Saved to ${ACCOUNTS_PATH}`);
|
|
940
1133
|
}
|
|
941
1134
|
});
|
|
942
|
-
var list =
|
|
1135
|
+
var list = defineCommand7({
|
|
943
1136
|
meta: {
|
|
944
1137
|
name: "list",
|
|
945
1138
|
description: "List your test accounts"
|
|
@@ -974,7 +1167,7 @@ var list = defineCommand6({
|
|
|
974
1167
|
}
|
|
975
1168
|
}
|
|
976
1169
|
});
|
|
977
|
-
var del =
|
|
1170
|
+
var del = defineCommand7({
|
|
978
1171
|
meta: {
|
|
979
1172
|
name: "delete",
|
|
980
1173
|
description: "Delete a test account by --email or --id"
|
|
@@ -1031,7 +1224,7 @@ var del = defineCommand6({
|
|
|
1031
1224
|
console.log(`Test account deleted${targetEmail ? `: ${targetEmail}` : `: ${targetId}`}`);
|
|
1032
1225
|
}
|
|
1033
1226
|
});
|
|
1034
|
-
var clear =
|
|
1227
|
+
var clear = defineCommand7({
|
|
1035
1228
|
meta: {
|
|
1036
1229
|
name: "clear",
|
|
1037
1230
|
description: "Delete all your test accounts (or those matching --label)"
|
|
@@ -1097,7 +1290,7 @@ Failed:`);
|
|
|
1097
1290
|
}
|
|
1098
1291
|
}
|
|
1099
1292
|
});
|
|
1100
|
-
var test_accounts_default =
|
|
1293
|
+
var test_accounts_default = defineCommand7({
|
|
1101
1294
|
meta: {
|
|
1102
1295
|
name: "test-accounts",
|
|
1103
1296
|
description: "Manage test accounts for development"
|
|
@@ -1111,11 +1304,12 @@ var test_accounts_default = defineCommand6({
|
|
|
1111
1304
|
});
|
|
1112
1305
|
|
|
1113
1306
|
// src/cli/commands/deploy.ts
|
|
1114
|
-
import { defineCommand as
|
|
1307
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
1115
1308
|
import { execSync as execSync2 } from "child_process";
|
|
1116
|
-
import { readFileSync as readFileSync5, readdirSync, existsSync as
|
|
1309
|
+
import { readFileSync as readFileSync5, readdirSync, existsSync as existsSync8 } from "fs";
|
|
1117
1310
|
import { parse as parseToml2 } from "smol-toml";
|
|
1118
|
-
import { join as join8, resolve as
|
|
1311
|
+
import { join as join8, resolve as resolve5, dirname } from "path";
|
|
1312
|
+
import { pathToFileURL } from "url";
|
|
1119
1313
|
import * as p4 from "@clack/prompts";
|
|
1120
1314
|
|
|
1121
1315
|
// src/server/rooms/binding-manifest.ts
|
|
@@ -1321,62 +1515,33 @@ function asObjectArray(v) {
|
|
|
1321
1515
|
return v.filter(isObject);
|
|
1322
1516
|
}
|
|
1323
1517
|
|
|
1324
|
-
// src/server/rooms/app-name.ts
|
|
1325
|
-
var APP_NAME_RULES = {
|
|
1326
|
-
/** ^[a-z0-9](-?[a-z0-9])+$ — RFC 1035 host label, no consecutive dashes. */
|
|
1327
|
-
pattern: /^[a-z0-9](?:-?[a-z0-9])+$/,
|
|
1328
|
-
minLength: 2,
|
|
1329
|
-
maxLength: 63
|
|
1330
|
-
};
|
|
1331
|
-
function validateAppName(raw) {
|
|
1332
|
-
if (typeof raw !== "string") {
|
|
1333
|
-
return { valid: false, reason: "App name must be a string" };
|
|
1334
|
-
}
|
|
1335
|
-
const name = raw.trim();
|
|
1336
|
-
if (!name) {
|
|
1337
|
-
return { valid: false, reason: "App name is required" };
|
|
1338
|
-
}
|
|
1339
|
-
if (name.length < APP_NAME_RULES.minLength) {
|
|
1340
|
-
return { valid: false, reason: `App name "${name}" is too short (min ${APP_NAME_RULES.minLength} chars)` };
|
|
1341
|
-
}
|
|
1342
|
-
if (name.length > APP_NAME_RULES.maxLength) {
|
|
1343
|
-
return { valid: false, reason: `App name "${name}" is too long (max ${APP_NAME_RULES.maxLength} chars)` };
|
|
1344
|
-
}
|
|
1345
|
-
if (!APP_NAME_RULES.pattern.test(name)) {
|
|
1346
|
-
return {
|
|
1347
|
-
valid: false,
|
|
1348
|
-
reason: `App name "${name}" is invalid. Use lowercase letters, digits, and single hyphens only (must start + end with alphanumeric). Examples: my-app, search-v2, unison-search.`
|
|
1349
|
-
};
|
|
1350
|
-
}
|
|
1351
|
-
return { valid: true, name };
|
|
1352
|
-
}
|
|
1353
|
-
function resolveAppName(raw) {
|
|
1354
|
-
const strict = validateAppName(raw);
|
|
1355
|
-
if (strict.valid) return { ok: true, name: strict.name };
|
|
1356
|
-
if (typeof raw !== "string") return { ok: false, reason: strict.reason };
|
|
1357
|
-
const trimmed = raw.trim();
|
|
1358
|
-
if (!trimmed) return { ok: false, reason: "App name is required" };
|
|
1359
|
-
const sanitized = sanitizeAppName(trimmed);
|
|
1360
|
-
const sanitizedStrict = validateAppName(sanitized);
|
|
1361
|
-
if (!sanitizedStrict.valid) {
|
|
1362
|
-
return {
|
|
1363
|
-
ok: false,
|
|
1364
|
-
reason: `App name "${trimmed}" can't be sanitized into a valid name (${sanitizedStrict.reason}). Pick something matching: lowercase letters, digits, single hyphens, ${APP_NAME_RULES.minLength}-${APP_NAME_RULES.maxLength} chars.`
|
|
1365
|
-
};
|
|
1366
|
-
}
|
|
1367
|
-
return {
|
|
1368
|
-
ok: true,
|
|
1369
|
-
name: sanitized,
|
|
1370
|
-
warning: `App name "${trimmed}" doesn't conform to deploy rules \u2014 using "${sanitized}" instead. Update wrangler.toml's \`name\` to silence this warning.`
|
|
1371
|
-
};
|
|
1372
|
-
}
|
|
1373
|
-
function sanitizeAppName(raw) {
|
|
1374
|
-
return raw.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
1518
|
// src/cli/commands/deploy.ts
|
|
1378
1519
|
var DEPLOY_URL = process.env.DEEPSPACE_DEPLOY_URL ?? ENVS.prod.deploy;
|
|
1379
|
-
var
|
|
1520
|
+
var API_URL = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
|
|
1521
|
+
var RESERVED_RUN_WORKER_FIRST = /* @__PURE__ */ new Set([
|
|
1522
|
+
"/api/*",
|
|
1523
|
+
"/ws/*",
|
|
1524
|
+
"/internal/*",
|
|
1525
|
+
"/v1/*",
|
|
1526
|
+
"/_deepspace/*"
|
|
1527
|
+
]);
|
|
1528
|
+
function extractRunWorkerFirst(cfg) {
|
|
1529
|
+
const raw = cfg.assets?.run_worker_first;
|
|
1530
|
+
if (!Array.isArray(raw)) return [];
|
|
1531
|
+
const out = [];
|
|
1532
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1533
|
+
for (const entry of raw) {
|
|
1534
|
+
if (typeof entry !== "string") continue;
|
|
1535
|
+
const trimmed = entry.trim();
|
|
1536
|
+
if (!trimmed.startsWith("/")) continue;
|
|
1537
|
+
if (RESERVED_RUN_WORKER_FIRST.has(trimmed)) continue;
|
|
1538
|
+
if (seen.has(trimmed)) continue;
|
|
1539
|
+
seen.add(trimmed);
|
|
1540
|
+
out.push(trimmed);
|
|
1541
|
+
}
|
|
1542
|
+
return out;
|
|
1543
|
+
}
|
|
1544
|
+
var deploy_default = defineCommand8({
|
|
1380
1545
|
meta: {
|
|
1381
1546
|
name: "deploy",
|
|
1382
1547
|
description: "Build and deploy your DeepSpace app"
|
|
@@ -1389,10 +1554,10 @@ var deploy_default = defineCommand7({
|
|
|
1389
1554
|
}
|
|
1390
1555
|
},
|
|
1391
1556
|
async run({ args }) {
|
|
1392
|
-
const appDir =
|
|
1557
|
+
const appDir = resolve5(args.dir ?? ".");
|
|
1393
1558
|
p4.intro("Deploying DeepSpace app");
|
|
1394
1559
|
const wranglerPath = join8(appDir, "wrangler.toml");
|
|
1395
|
-
if (!
|
|
1560
|
+
if (!existsSync8(wranglerPath)) {
|
|
1396
1561
|
p4.cancel("No wrangler.toml found. Are you in a DeepSpace app directory?");
|
|
1397
1562
|
process.exit(1);
|
|
1398
1563
|
}
|
|
@@ -1402,12 +1567,17 @@ var deploy_default = defineCommand7({
|
|
|
1402
1567
|
p4.cancel(`wrangler.toml: ${nameRes.reason}`);
|
|
1403
1568
|
process.exit(1);
|
|
1404
1569
|
}
|
|
1405
|
-
if (
|
|
1570
|
+
if (wranglerConfig.name !== nameRes.name) {
|
|
1571
|
+
p4.cancel(
|
|
1572
|
+
`wrangler.toml: name "${wranglerConfig.name}" is not in canonical form. Update \`name\` to "${nameRes.name}" and re-run.`
|
|
1573
|
+
);
|
|
1574
|
+
process.exit(1);
|
|
1575
|
+
}
|
|
1406
1576
|
const appName = nameRes.name;
|
|
1407
1577
|
p4.log.info(`App: ${appName}`);
|
|
1408
|
-
let
|
|
1578
|
+
let token2;
|
|
1409
1579
|
try {
|
|
1410
|
-
|
|
1580
|
+
token2 = await ensureToken();
|
|
1411
1581
|
} catch (err) {
|
|
1412
1582
|
p4.cancel(err.message);
|
|
1413
1583
|
process.exit(1);
|
|
@@ -1423,25 +1593,25 @@ var deploy_default = defineCommand7({
|
|
|
1423
1593
|
}
|
|
1424
1594
|
s.stop("Built");
|
|
1425
1595
|
const deployConfigPath = join8(appDir, ".wrangler", "deploy", "config.json");
|
|
1426
|
-
if (!
|
|
1596
|
+
if (!existsSync8(deployConfigPath)) {
|
|
1427
1597
|
p4.cancel("Build output config not found at .wrangler/deploy/config.json");
|
|
1428
1598
|
process.exit(1);
|
|
1429
1599
|
}
|
|
1430
1600
|
const deployConfig = JSON.parse(readFileSync5(deployConfigPath, "utf-8"));
|
|
1431
|
-
const outputWranglerPath =
|
|
1432
|
-
if (!
|
|
1601
|
+
const outputWranglerPath = resolve5(dirname(deployConfigPath), deployConfig.configPath);
|
|
1602
|
+
if (!existsSync8(outputWranglerPath)) {
|
|
1433
1603
|
p4.cancel(`Output wrangler.json not found at ${outputWranglerPath}`);
|
|
1434
1604
|
process.exit(1);
|
|
1435
1605
|
}
|
|
1436
1606
|
const outputConfig = JSON.parse(readFileSync5(outputWranglerPath, "utf-8"));
|
|
1437
1607
|
const workerDir = dirname(outputWranglerPath);
|
|
1438
1608
|
const workerBundlePath = join8(workerDir, outputConfig.main);
|
|
1439
|
-
const clientDir = outputConfig.assets?.directory ?
|
|
1440
|
-
if (!
|
|
1609
|
+
const clientDir = outputConfig.assets?.directory ? resolve5(workerDir, outputConfig.assets.directory) : null;
|
|
1610
|
+
if (!existsSync8(workerBundlePath)) {
|
|
1441
1611
|
p4.cancel(`Worker bundle not found at ${workerBundlePath}`);
|
|
1442
1612
|
process.exit(1);
|
|
1443
1613
|
}
|
|
1444
|
-
if (!clientDir || !
|
|
1614
|
+
if (!clientDir || !existsSync8(clientDir)) {
|
|
1445
1615
|
p4.cancel(`Client assets not found at ${clientDir}`);
|
|
1446
1616
|
process.exit(1);
|
|
1447
1617
|
}
|
|
@@ -1477,7 +1647,7 @@ var deploy_default = defineCommand7({
|
|
|
1477
1647
|
}
|
|
1478
1648
|
const devVarsPath = join8(appDir, ".dev.vars");
|
|
1479
1649
|
let userSecrets = {};
|
|
1480
|
-
if (
|
|
1650
|
+
if (existsSync8(devVarsPath)) {
|
|
1481
1651
|
const customSection = extractCustomDevVars(readFileSync5(devVarsPath, "utf-8"));
|
|
1482
1652
|
if (customSection) {
|
|
1483
1653
|
try {
|
|
@@ -1523,9 +1693,14 @@ var deploy_default = defineCommand7({
|
|
|
1523
1693
|
if (userSecretNames.length) {
|
|
1524
1694
|
form.append("userSecrets", JSON.stringify(userSecrets));
|
|
1525
1695
|
}
|
|
1696
|
+
const extraRoutes = extractRunWorkerFirst(wranglerConfig);
|
|
1697
|
+
if (extraRoutes.length) {
|
|
1698
|
+
form.append("extraRunWorkerFirst", JSON.stringify(extraRoutes));
|
|
1699
|
+
p4.log.info(`Custom worker-first routes: ${extraRoutes.join(", ")}`);
|
|
1700
|
+
}
|
|
1526
1701
|
const res = await fetch(`${DEPLOY_URL}/api/deploy/${appName}`, {
|
|
1527
1702
|
method: "POST",
|
|
1528
|
-
headers: { Authorization: `Bearer ${
|
|
1703
|
+
headers: { Authorization: `Bearer ${token2}` },
|
|
1529
1704
|
body: form
|
|
1530
1705
|
});
|
|
1531
1706
|
const body = await res.json().catch(() => ({}));
|
|
@@ -1541,34 +1716,247 @@ var deploy_default = defineCommand7({
|
|
|
1541
1716
|
s.stop("Deployed (edge propagation still in progress after 90s)");
|
|
1542
1717
|
p4.log.warn("First requests may briefly hit the assets transitional page; retry in a minute.");
|
|
1543
1718
|
p4.log.success(`Live at: ${body.url}`);
|
|
1719
|
+
await syncSubscriptionPlans(appDir, appName, token2);
|
|
1720
|
+
await syncOneTimeProducts(appDir, appName, token2);
|
|
1544
1721
|
p4.outro("Done");
|
|
1545
1722
|
return;
|
|
1546
1723
|
}
|
|
1547
1724
|
}
|
|
1548
1725
|
s.stop("Deployed!");
|
|
1549
1726
|
p4.log.success(`Live at: ${body.url}`);
|
|
1727
|
+
await syncSubscriptionPlans(appDir, appName, token2);
|
|
1728
|
+
await syncOneTimeProducts(appDir, appName, token2);
|
|
1550
1729
|
p4.outro("Done");
|
|
1551
1730
|
}
|
|
1552
1731
|
});
|
|
1553
|
-
async function
|
|
1554
|
-
const
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1732
|
+
async function syncSubscriptionPlans(appDir, appName, token2) {
|
|
1733
|
+
const subsPath = join8(appDir, "src", "subscriptions.ts");
|
|
1734
|
+
if (!existsSync8(subsPath)) return;
|
|
1735
|
+
const s = createSpinner();
|
|
1736
|
+
s.start("Syncing subscription plans...");
|
|
1737
|
+
let plans;
|
|
1738
|
+
try {
|
|
1739
|
+
const esbuild = await import("esbuild");
|
|
1740
|
+
const outDir = join8(appDir, ".wrangler", "deploy");
|
|
1741
|
+
const outFile = join8(outDir, "subscriptions.bundle.mjs");
|
|
1742
|
+
await esbuild.build({
|
|
1743
|
+
entryPoints: [subsPath],
|
|
1744
|
+
bundle: true,
|
|
1745
|
+
format: "esm",
|
|
1746
|
+
platform: "node",
|
|
1747
|
+
target: "node18",
|
|
1748
|
+
outfile: outFile,
|
|
1749
|
+
logLevel: "silent",
|
|
1750
|
+
write: true
|
|
1751
|
+
});
|
|
1752
|
+
const mod = await import(`${pathToFileURL(outFile).href}?t=${Date.now()}`);
|
|
1753
|
+
plans = mod.subscriptionPlans;
|
|
1754
|
+
} catch (err) {
|
|
1755
|
+
s.stop("Plan sync skipped");
|
|
1756
|
+
p4.log.warn(`Could not load src/subscriptions.ts: ${err.message}`);
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
if (!Array.isArray(plans)) {
|
|
1760
|
+
s.stop("Plan sync skipped");
|
|
1761
|
+
p4.log.warn(
|
|
1762
|
+
"src/subscriptions.ts must `export const subscriptionPlans = [...] as const`"
|
|
1763
|
+
);
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
let res;
|
|
1767
|
+
try {
|
|
1768
|
+
res = await fetch(`${API_URL}/api/subscriptions/sync`, {
|
|
1769
|
+
method: "POST",
|
|
1770
|
+
headers: {
|
|
1771
|
+
Authorization: `Bearer ${token2}`,
|
|
1772
|
+
"Content-Type": "application/json"
|
|
1773
|
+
},
|
|
1774
|
+
body: JSON.stringify({ appName, plans })
|
|
1775
|
+
});
|
|
1776
|
+
} catch (err) {
|
|
1777
|
+
s.stop("Plan sync skipped");
|
|
1778
|
+
p4.log.warn(`Plan sync request failed (network): ${err.message ?? err}`);
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
const body = await res.json().catch(() => ({}));
|
|
1782
|
+
if (res.ok && body.ok) {
|
|
1783
|
+
s.stop(`Synced ${plans.length} plan${plans.length === 1 ? "" : "s"} to Stripe`);
|
|
1784
|
+
if (body.connectMissing) {
|
|
1785
|
+
p4.log.warn(
|
|
1786
|
+
"Stripe Connect not yet set up. Customers can subscribe and earnings will queue in your platform balance until you finish onboarding at /earnings on the dashboard. The first payout after you connect will sweep everything that accumulated in the meantime."
|
|
1787
|
+
);
|
|
1565
1788
|
}
|
|
1566
|
-
const
|
|
1567
|
-
|
|
1789
|
+
const changes = (body.planChanges ?? []).filter((c) => c.affectedSubscribers > 0);
|
|
1790
|
+
if (changes.length > 0) {
|
|
1791
|
+
await promptAndCancelChangedPlans(appName, token2, changes);
|
|
1792
|
+
}
|
|
1793
|
+
return;
|
|
1568
1794
|
}
|
|
1569
|
-
|
|
1795
|
+
s.stop("Plan sync skipped");
|
|
1796
|
+
p4.log.warn(
|
|
1797
|
+
`Plan sync failed: ${body.error ?? `HTTP ${res.status}`}${body.hint ? ` \u2014 ${body.hint}` : ""}`
|
|
1798
|
+
);
|
|
1570
1799
|
}
|
|
1571
|
-
function
|
|
1800
|
+
function formatCents(c) {
|
|
1801
|
+
return c === 0 ? "free" : `$${(c / 100).toFixed(2)}`;
|
|
1802
|
+
}
|
|
1803
|
+
function summaryLine(c) {
|
|
1804
|
+
const n = c.affectedSubscribers;
|
|
1805
|
+
const suffix = `${n} subscriber${n === 1 ? "" : "s"}`;
|
|
1806
|
+
if (c.reason === "removed") {
|
|
1807
|
+
return ` \u2022 ${c.slug} \u2014 removed (${suffix} still on it)`;
|
|
1808
|
+
}
|
|
1809
|
+
const diffs = (c.intervals ?? []).map((i) => `${i.interval}: ${formatCents(i.oldCents)} \u2192 ${formatCents(i.newCents)}`).join("; ");
|
|
1810
|
+
return ` \u2022 ${c.slug} \u2014 repriced (${diffs}; ${suffix} grandfathered on old price)`;
|
|
1811
|
+
}
|
|
1812
|
+
async function promptAndCancelChangedPlans(appName, token2, changes) {
|
|
1813
|
+
const summary = changes.map(summaryLine).join("\n");
|
|
1814
|
+
p4.log.warn(
|
|
1815
|
+
`Plan change${changes.length === 1 ? "" : "s"} from this deploy left subscribers stranded:
|
|
1816
|
+
${summary}
|
|
1817
|
+
|
|
1818
|
+
Stripe will keep charging them until you cancel. Cancel at the end of their
|
|
1819
|
+
current billing period? (Skip to leave them grandfathered.)`
|
|
1820
|
+
);
|
|
1821
|
+
const choice = await p4.confirm({
|
|
1822
|
+
message: `Cancel ${changes.length === 1 ? "these subscribers" : "all of them"} at period end?`,
|
|
1823
|
+
initialValue: false
|
|
1824
|
+
});
|
|
1825
|
+
if (p4.isCancel(choice) || !choice) {
|
|
1826
|
+
p4.log.info("Skipped \u2014 existing subscribers stay on their current plan/price.");
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
const MAX_ITERATIONS = 100;
|
|
1830
|
+
for (const c of changes) {
|
|
1831
|
+
const s = createSpinner();
|
|
1832
|
+
s.start(`Canceling ${c.affectedSubscribers} subscriber${c.affectedSubscribers === 1 ? "" : "s"} on ${c.slug}...`);
|
|
1833
|
+
let totalCanceled = 0;
|
|
1834
|
+
let totalFailed = 0;
|
|
1835
|
+
let iteration = 0;
|
|
1836
|
+
let lastError = null;
|
|
1837
|
+
let stopped = false;
|
|
1838
|
+
try {
|
|
1839
|
+
while (iteration < MAX_ITERATIONS) {
|
|
1840
|
+
const res = await fetch(`${API_URL}/api/subscriptions/admin-cancel`, {
|
|
1841
|
+
method: "POST",
|
|
1842
|
+
headers: {
|
|
1843
|
+
Authorization: `Bearer ${token2}`,
|
|
1844
|
+
"Content-Type": "application/json"
|
|
1845
|
+
},
|
|
1846
|
+
body: JSON.stringify({
|
|
1847
|
+
appName,
|
|
1848
|
+
planSlug: c.slug,
|
|
1849
|
+
atPeriodEnd: true,
|
|
1850
|
+
reason: `plan ${c.reason} via deploy`
|
|
1851
|
+
})
|
|
1852
|
+
});
|
|
1853
|
+
const body = await res.json().catch(() => ({}));
|
|
1854
|
+
if (!res.ok || !body.success) {
|
|
1855
|
+
lastError = body.error ?? `HTTP ${res.status}`;
|
|
1856
|
+
s.stop(`Cancel failed for ${c.slug}: ${lastError}`);
|
|
1857
|
+
stopped = true;
|
|
1858
|
+
break;
|
|
1859
|
+
}
|
|
1860
|
+
const batchCanceled = body.canceled ?? 0;
|
|
1861
|
+
totalCanceled += batchCanceled;
|
|
1862
|
+
totalFailed += body.failures?.length ?? 0;
|
|
1863
|
+
if (!body.hasMore) break;
|
|
1864
|
+
if (batchCanceled === 0) {
|
|
1865
|
+
lastError = "no progress (all rows failed this batch)";
|
|
1866
|
+
break;
|
|
1867
|
+
}
|
|
1868
|
+
iteration++;
|
|
1869
|
+
}
|
|
1870
|
+
if (!stopped) {
|
|
1871
|
+
const tail = totalFailed > 0 ? `; ${totalFailed} failed (see Stripe)` : "";
|
|
1872
|
+
const cap = iteration >= MAX_ITERATIONS ? ` (batch cap reached \u2014 re-run deploy to finish remaining)` : "";
|
|
1873
|
+
const errSuffix = lastError ? ` \u2014 ${lastError}` : "";
|
|
1874
|
+
s.stop(`Canceled ${totalCanceled} on ${c.slug}${tail}${cap}${errSuffix}`);
|
|
1875
|
+
}
|
|
1876
|
+
} catch (err) {
|
|
1877
|
+
s.stop(`Cancel failed for ${c.slug}: ${err.message}`);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
async function syncOneTimeProducts(appDir, appName, token2) {
|
|
1882
|
+
const productsPath = join8(appDir, "src", "products.ts");
|
|
1883
|
+
if (!existsSync8(productsPath)) return;
|
|
1884
|
+
const s = createSpinner();
|
|
1885
|
+
s.start("Syncing one-time products...");
|
|
1886
|
+
let products;
|
|
1887
|
+
try {
|
|
1888
|
+
const esbuild = await import("esbuild");
|
|
1889
|
+
const outDir = join8(appDir, ".wrangler", "deploy");
|
|
1890
|
+
const outFile = join8(outDir, "products.bundle.mjs");
|
|
1891
|
+
await esbuild.build({
|
|
1892
|
+
entryPoints: [productsPath],
|
|
1893
|
+
bundle: true,
|
|
1894
|
+
format: "esm",
|
|
1895
|
+
platform: "node",
|
|
1896
|
+
target: "node18",
|
|
1897
|
+
outfile: outFile,
|
|
1898
|
+
logLevel: "silent",
|
|
1899
|
+
write: true
|
|
1900
|
+
});
|
|
1901
|
+
const mod = await import(`${pathToFileURL(outFile).href}?t=${Date.now()}`);
|
|
1902
|
+
products = mod.oneTimeProducts;
|
|
1903
|
+
} catch (err) {
|
|
1904
|
+
s.stop("Product sync skipped");
|
|
1905
|
+
p4.log.warn(`Could not load src/products.ts: ${err.message}`);
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
if (!Array.isArray(products)) {
|
|
1909
|
+
s.stop("Product sync skipped");
|
|
1910
|
+
p4.log.warn(
|
|
1911
|
+
"src/products.ts must `export const oneTimeProducts = [...] as const`"
|
|
1912
|
+
);
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
let res;
|
|
1916
|
+
try {
|
|
1917
|
+
res = await fetch(`${API_URL}/api/charges/products/sync`, {
|
|
1918
|
+
method: "POST",
|
|
1919
|
+
headers: {
|
|
1920
|
+
Authorization: `Bearer ${token2}`,
|
|
1921
|
+
"Content-Type": "application/json"
|
|
1922
|
+
},
|
|
1923
|
+
body: JSON.stringify({ appName, products })
|
|
1924
|
+
});
|
|
1925
|
+
} catch (err) {
|
|
1926
|
+
s.stop("Product sync skipped");
|
|
1927
|
+
p4.log.warn(`Product sync request failed (network): ${err.message ?? err}`);
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
const body = await res.json().catch(() => ({}));
|
|
1931
|
+
if (res.ok && body.ok) {
|
|
1932
|
+
const n = body.count ?? products.length;
|
|
1933
|
+
s.stop(`Synced ${n} one-time product${n === 1 ? "" : "s"}`);
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
s.stop("Product sync skipped");
|
|
1937
|
+
p4.log.warn(
|
|
1938
|
+
`Product sync failed: ${body.error ?? `HTTP ${res.status}`}${body.details ? ` \u2014 ${JSON.stringify(body.details)}` : ""}`
|
|
1939
|
+
);
|
|
1940
|
+
}
|
|
1941
|
+
async function waitForAssetsReady(url, timeoutMs) {
|
|
1942
|
+
const deadline = Date.now() + timeoutMs;
|
|
1943
|
+
let attempt = 0;
|
|
1944
|
+
while (Date.now() < deadline) {
|
|
1945
|
+
attempt++;
|
|
1946
|
+
try {
|
|
1947
|
+
const res = await fetch(url, { redirect: "manual" });
|
|
1948
|
+
const body = await res.text();
|
|
1949
|
+
if (!body.includes("Assets have not yet deployed")) {
|
|
1950
|
+
return true;
|
|
1951
|
+
}
|
|
1952
|
+
} catch {
|
|
1953
|
+
}
|
|
1954
|
+
const wait2 = Math.min(8e3, 1e3 * 2 ** (attempt - 1));
|
|
1955
|
+
await new Promise((r) => setTimeout(r, wait2));
|
|
1956
|
+
}
|
|
1957
|
+
return false;
|
|
1958
|
+
}
|
|
1959
|
+
function collectAssets(dir) {
|
|
1572
1960
|
const assets = [];
|
|
1573
1961
|
function walk(d, prefix) {
|
|
1574
1962
|
for (const entry of readdirSync(d, { withFileTypes: true })) {
|
|
@@ -1590,13 +1978,13 @@ function collectAssets(dir) {
|
|
|
1590
1978
|
}
|
|
1591
1979
|
|
|
1592
1980
|
// src/cli/commands/undeploy.ts
|
|
1593
|
-
import { defineCommand as
|
|
1594
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
1981
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
1982
|
+
import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
|
|
1595
1983
|
import { join as join9 } from "path";
|
|
1596
1984
|
import { parse as parseToml3 } from "smol-toml";
|
|
1597
1985
|
import * as p5 from "@clack/prompts";
|
|
1598
1986
|
var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? ENVS.prod.deploy;
|
|
1599
|
-
var undeploy_default =
|
|
1987
|
+
var undeploy_default = defineCommand9({
|
|
1600
1988
|
meta: {
|
|
1601
1989
|
name: "undeploy",
|
|
1602
1990
|
description: "Remove a deployed DeepSpace app"
|
|
@@ -1612,7 +2000,7 @@ var undeploy_default = defineCommand8({
|
|
|
1612
2000
|
let appName = args.name;
|
|
1613
2001
|
if (!appName) {
|
|
1614
2002
|
const wranglerPath = join9(process.cwd(), "wrangler.toml");
|
|
1615
|
-
if (
|
|
2003
|
+
if (existsSync9(wranglerPath)) {
|
|
1616
2004
|
const config = parseToml3(readFileSync6(wranglerPath, "utf-8"));
|
|
1617
2005
|
if (config.name) appName = config.name;
|
|
1618
2006
|
}
|
|
@@ -1621,9 +2009,9 @@ var undeploy_default = defineCommand8({
|
|
|
1621
2009
|
p5.cancel("Provide an app name or run from a DeepSpace app directory.");
|
|
1622
2010
|
process.exit(1);
|
|
1623
2011
|
}
|
|
1624
|
-
let
|
|
2012
|
+
let token2;
|
|
1625
2013
|
try {
|
|
1626
|
-
|
|
2014
|
+
token2 = await ensureToken();
|
|
1627
2015
|
} catch (err) {
|
|
1628
2016
|
p5.cancel(err.message);
|
|
1629
2017
|
process.exit(1);
|
|
@@ -1633,7 +2021,7 @@ var undeploy_default = defineCommand8({
|
|
|
1633
2021
|
s.start("Removing...");
|
|
1634
2022
|
const res = await fetch(`${DEPLOY_URL2}/api/deploy/${appName}`, {
|
|
1635
2023
|
method: "DELETE",
|
|
1636
|
-
headers: { Authorization: `Bearer ${
|
|
2024
|
+
headers: { Authorization: `Bearer ${token2}` }
|
|
1637
2025
|
});
|
|
1638
2026
|
const body = await res.json().catch(() => ({}));
|
|
1639
2027
|
if (!res.ok || !body.success) {
|
|
@@ -1647,8 +2035,8 @@ var undeploy_default = defineCommand8({
|
|
|
1647
2035
|
});
|
|
1648
2036
|
|
|
1649
2037
|
// src/cli/commands/whoami.ts
|
|
1650
|
-
import { defineCommand as
|
|
1651
|
-
var whoami_default =
|
|
2038
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
2039
|
+
var whoami_default = defineCommand10({
|
|
1652
2040
|
meta: {
|
|
1653
2041
|
name: "whoami",
|
|
1654
2042
|
description: "Show the currently logged-in user"
|
|
@@ -1670,7 +2058,7 @@ var whoami_default = defineCommand9({
|
|
|
1670
2058
|
}
|
|
1671
2059
|
let payload;
|
|
1672
2060
|
try {
|
|
1673
|
-
payload =
|
|
2061
|
+
payload = decodeJwtPayload(jwt);
|
|
1674
2062
|
} catch {
|
|
1675
2063
|
console.error("Malformed session token. Run `deepspace login` again.");
|
|
1676
2064
|
process.exit(1);
|
|
@@ -1693,6 +2081,7 @@ var whoami_default = defineCommand9({
|
|
|
1693
2081
|
console.log(`UserID: ${payload.sub ?? "(none)"}`);
|
|
1694
2082
|
console.log(`Type: ${accountType}`);
|
|
1695
2083
|
if (payload.iss) console.log(`Issuer: ${payload.iss}`);
|
|
2084
|
+
console.log(`Dashboard: ${DASHBOARD_URL}`);
|
|
1696
2085
|
if (isTestAccount) {
|
|
1697
2086
|
console.log("");
|
|
1698
2087
|
console.log("Note: this is a test account, not a real OAuth developer.");
|
|
@@ -1702,16 +2091,16 @@ var whoami_default = defineCommand9({
|
|
|
1702
2091
|
});
|
|
1703
2092
|
|
|
1704
2093
|
// src/cli/commands/add.ts
|
|
1705
|
-
import { defineCommand as
|
|
1706
|
-
import { existsSync as
|
|
1707
|
-
import { resolve as
|
|
2094
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
2095
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2096
|
+
import { resolve as resolve6, join as join10 } from "path";
|
|
1708
2097
|
import { execSync as execSync3 } from "child_process";
|
|
1709
2098
|
function findScript(from) {
|
|
1710
2099
|
const candidate = join10(from, ".deepspace", "scripts", "add-feature.cjs");
|
|
1711
|
-
if (
|
|
2100
|
+
if (existsSync10(candidate)) return candidate;
|
|
1712
2101
|
return null;
|
|
1713
2102
|
}
|
|
1714
|
-
var add_default =
|
|
2103
|
+
var add_default = defineCommand11({
|
|
1715
2104
|
meta: {
|
|
1716
2105
|
name: "add",
|
|
1717
2106
|
description: "Add a feature to your DeepSpace app"
|
|
@@ -1740,7 +2129,7 @@ var add_default = defineCommand10({
|
|
|
1740
2129
|
}
|
|
1741
2130
|
},
|
|
1742
2131
|
run({ args }) {
|
|
1743
|
-
const appDir =
|
|
2132
|
+
const appDir = resolve6(args.dir ?? ".");
|
|
1744
2133
|
const script = findScript(appDir);
|
|
1745
2134
|
if (!script) {
|
|
1746
2135
|
console.error("No .deepspace/scripts/add-feature.cjs found.");
|
|
@@ -1765,16 +2154,16 @@ var add_default = defineCommand10({
|
|
|
1765
2154
|
});
|
|
1766
2155
|
|
|
1767
2156
|
// src/cli/commands/domain.ts
|
|
1768
|
-
import { defineCommand as
|
|
2157
|
+
import { defineCommand as defineCommand12 } from "citty";
|
|
1769
2158
|
import { execSync as execSync4 } from "child_process";
|
|
1770
2159
|
|
|
1771
2160
|
// src/cli/lib/app-context.ts
|
|
1772
|
-
import { existsSync as
|
|
1773
|
-
import { join as join11, resolve as
|
|
2161
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
|
|
2162
|
+
import { join as join11, resolve as resolve7 } from "path";
|
|
1774
2163
|
import { parse as parseToml4 } from "smol-toml";
|
|
1775
2164
|
function detectAppName(cwd = process.cwd()) {
|
|
1776
|
-
const wranglerPath = join11(
|
|
1777
|
-
if (!
|
|
2165
|
+
const wranglerPath = join11(resolve7(cwd), "wrangler.toml");
|
|
2166
|
+
if (!existsSync11(wranglerPath)) return null;
|
|
1778
2167
|
try {
|
|
1779
2168
|
const cfg = parseToml4(readFileSync7(wranglerPath, "utf-8"));
|
|
1780
2169
|
return typeof cfg.name === "string" && cfg.name.length > 0 ? cfg.name : null;
|
|
@@ -1792,12 +2181,12 @@ function requireAppName(explicit, cwd) {
|
|
|
1792
2181
|
}
|
|
1793
2182
|
|
|
1794
2183
|
// src/cli/commands/domain.ts
|
|
1795
|
-
var
|
|
1796
|
-
async function api(
|
|
1797
|
-
const res = await fetch(`${
|
|
2184
|
+
var API_URL2 = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
|
|
2185
|
+
async function api(token2, path, init) {
|
|
2186
|
+
const res = await fetch(`${API_URL2}${path}`, {
|
|
1798
2187
|
...init,
|
|
1799
2188
|
headers: {
|
|
1800
|
-
Authorization: `Bearer ${
|
|
2189
|
+
Authorization: `Bearer ${token2}`,
|
|
1801
2190
|
"Content-Type": "application/json",
|
|
1802
2191
|
...init?.headers ?? {}
|
|
1803
2192
|
}
|
|
@@ -1833,20 +2222,20 @@ function fmtDate(iso) {
|
|
|
1833
2222
|
if (!iso) return "\u2014";
|
|
1834
2223
|
return iso.slice(0, 10);
|
|
1835
2224
|
}
|
|
1836
|
-
async function
|
|
2225
|
+
async function confirm3(message) {
|
|
1837
2226
|
if (!process.stdin.isTTY) {
|
|
1838
2227
|
throw new Error(`${message} \u2014 pass --yes to confirm non-interactively`);
|
|
1839
2228
|
}
|
|
1840
2229
|
process.stdout.write(`${message} [y/N] `);
|
|
1841
|
-
return new Promise((
|
|
2230
|
+
return new Promise((resolve9) => {
|
|
1842
2231
|
process.stdin.setEncoding("utf-8");
|
|
1843
2232
|
process.stdin.once("data", (data) => {
|
|
1844
2233
|
const answer = data.toString().trim().toLowerCase();
|
|
1845
|
-
|
|
2234
|
+
resolve9(answer === "y" || answer === "yes");
|
|
1846
2235
|
});
|
|
1847
2236
|
});
|
|
1848
2237
|
}
|
|
1849
|
-
var search =
|
|
2238
|
+
var search = defineCommand12({
|
|
1850
2239
|
meta: { name: "search", description: "Search for available domains" },
|
|
1851
2240
|
args: {
|
|
1852
2241
|
query: { type: "positional", description: "Domain or keyword", required: true },
|
|
@@ -1854,10 +2243,10 @@ var search = defineCommand11({
|
|
|
1854
2243
|
json: { type: "boolean", description: "Emit JSON instead of a table" }
|
|
1855
2244
|
},
|
|
1856
2245
|
async run({ args }) {
|
|
1857
|
-
const
|
|
2246
|
+
const token2 = await ensureToken();
|
|
1858
2247
|
const limit = parseInt(args.limit ?? "10", 10) || 10;
|
|
1859
2248
|
const result = await api(
|
|
1860
|
-
|
|
2249
|
+
token2,
|
|
1861
2250
|
`/api/domains/search?q=${encodeURIComponent(args.query)}&limit=${limit}`
|
|
1862
2251
|
);
|
|
1863
2252
|
if (args.json) {
|
|
@@ -1876,7 +2265,7 @@ var search = defineCommand11({
|
|
|
1876
2265
|
}
|
|
1877
2266
|
}
|
|
1878
2267
|
});
|
|
1879
|
-
var buy =
|
|
2268
|
+
var buy = defineCommand12({
|
|
1880
2269
|
meta: { name: "buy", description: "Buy a domain and attach it to an app" },
|
|
1881
2270
|
args: {
|
|
1882
2271
|
domain: { type: "positional", description: "Domain to buy", required: true },
|
|
@@ -1887,9 +2276,9 @@ var buy = defineCommand11({
|
|
|
1887
2276
|
json: { type: "boolean", description: "Emit JSON to stdout", default: false }
|
|
1888
2277
|
},
|
|
1889
2278
|
async run({ args }) {
|
|
1890
|
-
const
|
|
2279
|
+
const token2 = await ensureToken();
|
|
1891
2280
|
const appName = requireAppName(args.app);
|
|
1892
|
-
const priceCheck = await api(
|
|
2281
|
+
const priceCheck = await api(token2, "/api/domains/check", {
|
|
1893
2282
|
method: "POST",
|
|
1894
2283
|
body: JSON.stringify({ domains: [args.domain] })
|
|
1895
2284
|
});
|
|
@@ -1901,7 +2290,7 @@ var buy = defineCommand11({
|
|
|
1901
2290
|
const price = fmtCents(result.chargedCents);
|
|
1902
2291
|
const expectedTime = result.registrar === "cloudflare" ? "~60-90 seconds" : "15-60 minutes (registry NS propagation)";
|
|
1903
2292
|
if (!args.yes) {
|
|
1904
|
-
const ok = await
|
|
2293
|
+
const ok = await confirm3(
|
|
1905
2294
|
`Buy ${args.domain} for ${price}/yr (auto-renews; non-refundable), attach to ${appName}? Provisioning takes ${expectedTime}.`
|
|
1906
2295
|
);
|
|
1907
2296
|
if (!ok) {
|
|
@@ -1910,7 +2299,7 @@ var buy = defineCommand11({
|
|
|
1910
2299
|
}
|
|
1911
2300
|
}
|
|
1912
2301
|
const checkout = await api(
|
|
1913
|
-
|
|
2302
|
+
token2,
|
|
1914
2303
|
"/api/domains/checkout",
|
|
1915
2304
|
{
|
|
1916
2305
|
method: "POST",
|
|
@@ -1937,7 +2326,7 @@ var buy = defineCommand11({
|
|
|
1937
2326
|
await new Promise((r) => setTimeout(r, 5e3));
|
|
1938
2327
|
try {
|
|
1939
2328
|
const detail = await api(
|
|
1940
|
-
|
|
2329
|
+
token2,
|
|
1941
2330
|
`/api/domains/${checkout.purchaseId}`
|
|
1942
2331
|
);
|
|
1943
2332
|
if (detail.domain.status !== lastStatus) {
|
|
@@ -1965,14 +2354,14 @@ var buy = defineCommand11({
|
|
|
1965
2354
|
process.exit(1);
|
|
1966
2355
|
}
|
|
1967
2356
|
});
|
|
1968
|
-
var list2 =
|
|
2357
|
+
var list2 = defineCommand12({
|
|
1969
2358
|
meta: { name: "list", description: "List your domains" },
|
|
1970
2359
|
args: {
|
|
1971
2360
|
json: { type: "boolean", description: "Emit JSON" }
|
|
1972
2361
|
},
|
|
1973
2362
|
async run({ args }) {
|
|
1974
|
-
const
|
|
1975
|
-
const result = await api(
|
|
2363
|
+
const token2 = await ensureToken();
|
|
2364
|
+
const result = await api(token2, "/api/domains");
|
|
1976
2365
|
if (args.json) {
|
|
1977
2366
|
process.stdout.write(JSON.stringify(scrubInternal(result), null, 2) + "\n");
|
|
1978
2367
|
return;
|
|
@@ -1989,15 +2378,15 @@ var list2 = defineCommand11({
|
|
|
1989
2378
|
}
|
|
1990
2379
|
}
|
|
1991
2380
|
});
|
|
1992
|
-
var status =
|
|
2381
|
+
var status = defineCommand12({
|
|
1993
2382
|
meta: { name: "status", description: "Show details for one domain" },
|
|
1994
2383
|
args: {
|
|
1995
2384
|
domain: { type: "positional", description: "Domain", required: true },
|
|
1996
2385
|
json: { type: "boolean" }
|
|
1997
2386
|
},
|
|
1998
2387
|
async run({ args }) {
|
|
1999
|
-
const
|
|
2000
|
-
const all = await api(
|
|
2388
|
+
const token2 = await ensureToken();
|
|
2389
|
+
const all = await api(token2, "/api/domains");
|
|
2001
2390
|
const found = all.domains.find((d) => d.domain === args.domain);
|
|
2002
2391
|
if (!found) {
|
|
2003
2392
|
console.error(`No domain ${args.domain} in your account`);
|
|
@@ -2017,7 +2406,7 @@ var status = defineCommand11({
|
|
|
2017
2406
|
console.log(`Charged: ${fmtCents(found.chargedCents)}/yr`);
|
|
2018
2407
|
}
|
|
2019
2408
|
});
|
|
2020
|
-
var attach =
|
|
2409
|
+
var attach = defineCommand12({
|
|
2021
2410
|
meta: { name: "attach", description: "Re-point a domain at a different app" },
|
|
2022
2411
|
args: {
|
|
2023
2412
|
domain: { type: "positional", description: "Domain to re-point", required: true },
|
|
@@ -2025,16 +2414,16 @@ var attach = defineCommand11({
|
|
|
2025
2414
|
json: { type: "boolean" }
|
|
2026
2415
|
},
|
|
2027
2416
|
async run({ args }) {
|
|
2028
|
-
const
|
|
2417
|
+
const token2 = await ensureToken();
|
|
2029
2418
|
const targetApp = requireAppName(args.app);
|
|
2030
|
-
const all = await api(
|
|
2419
|
+
const all = await api(token2, "/api/domains");
|
|
2031
2420
|
const found = all.domains.find((d) => d.domain === args.domain);
|
|
2032
2421
|
if (!found) {
|
|
2033
2422
|
console.error(`No domain ${args.domain} in your account`);
|
|
2034
2423
|
process.exit(1);
|
|
2035
2424
|
}
|
|
2036
2425
|
const result = await api(
|
|
2037
|
-
|
|
2426
|
+
token2,
|
|
2038
2427
|
`/api/domains/${found.id}/reattach`,
|
|
2039
2428
|
{
|
|
2040
2429
|
method: "POST",
|
|
@@ -2048,7 +2437,7 @@ var attach = defineCommand11({
|
|
|
2048
2437
|
console.log(`\u2713 ${args.domain} now points at ${targetApp}`);
|
|
2049
2438
|
}
|
|
2050
2439
|
});
|
|
2051
|
-
var detach =
|
|
2440
|
+
var detach = defineCommand12({
|
|
2052
2441
|
meta: {
|
|
2053
2442
|
name: "detach",
|
|
2054
2443
|
description: "Stop routing the domain (keeps the registration; auto-renew unchanged)"
|
|
@@ -2058,15 +2447,15 @@ var detach = defineCommand11({
|
|
|
2058
2447
|
yes: { type: "boolean", description: "Skip the confirmation prompt" }
|
|
2059
2448
|
},
|
|
2060
2449
|
async run({ args }) {
|
|
2061
|
-
const
|
|
2062
|
-
const all = await api(
|
|
2450
|
+
const token2 = await ensureToken();
|
|
2451
|
+
const all = await api(token2, "/api/domains");
|
|
2063
2452
|
const found = all.domains.find((d) => d.domain === args.domain);
|
|
2064
2453
|
if (!found) {
|
|
2065
2454
|
console.error(`No domain ${args.domain} in your account`);
|
|
2066
2455
|
process.exit(1);
|
|
2067
2456
|
}
|
|
2068
2457
|
if (!args.yes) {
|
|
2069
|
-
const ok = await
|
|
2458
|
+
const ok = await confirm3(
|
|
2070
2459
|
`Detach ${args.domain}? Routing will stop; the domain stays registered (auto-renew is unchanged).`
|
|
2071
2460
|
);
|
|
2072
2461
|
if (!ok) {
|
|
@@ -2074,11 +2463,11 @@ var detach = defineCommand11({
|
|
|
2074
2463
|
return;
|
|
2075
2464
|
}
|
|
2076
2465
|
}
|
|
2077
|
-
await api(
|
|
2466
|
+
await api(token2, `/api/domains/${found.id}`, { method: "DELETE" });
|
|
2078
2467
|
console.log(`\u2713 ${args.domain} detached. Use \`deepspace domain attach\` to re-route, or \`renew --auto off\` to stop auto-renewal.`);
|
|
2079
2468
|
}
|
|
2080
2469
|
});
|
|
2081
|
-
var renew =
|
|
2470
|
+
var renew = defineCommand12({
|
|
2082
2471
|
meta: { name: "renew", description: "Toggle auto-renewal at the registrar" },
|
|
2083
2472
|
args: {
|
|
2084
2473
|
domain: { type: "positional", description: "Domain", required: true },
|
|
@@ -2089,15 +2478,15 @@ var renew = defineCommand11({
|
|
|
2089
2478
|
console.error('--auto must be "on" or "off"');
|
|
2090
2479
|
process.exit(1);
|
|
2091
2480
|
}
|
|
2092
|
-
const
|
|
2093
|
-
const all = await api(
|
|
2481
|
+
const token2 = await ensureToken();
|
|
2482
|
+
const all = await api(token2, "/api/domains");
|
|
2094
2483
|
const found = all.domains.find((d) => d.domain === args.domain);
|
|
2095
2484
|
if (!found) {
|
|
2096
2485
|
console.error(`No domain ${args.domain} in your account`);
|
|
2097
2486
|
process.exit(1);
|
|
2098
2487
|
}
|
|
2099
2488
|
const enabled = args.auto === "on";
|
|
2100
|
-
await api(
|
|
2489
|
+
await api(token2, `/api/domains/${found.id}/auto-renew`, {
|
|
2101
2490
|
method: "POST",
|
|
2102
2491
|
body: JSON.stringify({ autoRenew: enabled })
|
|
2103
2492
|
});
|
|
@@ -2112,17 +2501,17 @@ function openBrowser2(url) {
|
|
|
2112
2501
|
} catch {
|
|
2113
2502
|
}
|
|
2114
2503
|
}
|
|
2115
|
-
var domain_default =
|
|
2504
|
+
var domain_default = defineCommand12({
|
|
2116
2505
|
meta: { name: "domain", description: "Buy and manage custom domains" },
|
|
2117
2506
|
subCommands: { search, buy, list: list2, status, attach, detach, renew }
|
|
2118
2507
|
});
|
|
2119
2508
|
|
|
2120
2509
|
// src/cli/commands/integrations.ts
|
|
2121
|
-
import { defineCommand as
|
|
2510
|
+
import { defineCommand as defineCommand13 } from "citty";
|
|
2122
2511
|
|
|
2123
2512
|
// src/cli/commands/_invoke-impl.ts
|
|
2124
2513
|
import { readFileSync as readFileSync8 } from "fs";
|
|
2125
|
-
var
|
|
2514
|
+
var API_URL3 = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
|
|
2126
2515
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
2127
2516
|
function parseTarget(target) {
|
|
2128
2517
|
if (!target || typeof target !== "string") {
|
|
@@ -2160,7 +2549,7 @@ function flushAndExit(code) {
|
|
|
2160
2549
|
});
|
|
2161
2550
|
}
|
|
2162
2551
|
async function fetchCatalog() {
|
|
2163
|
-
const res = await fetch(`${
|
|
2552
|
+
const res = await fetch(`${API_URL3}/api/integrations`);
|
|
2164
2553
|
if (!res.ok) {
|
|
2165
2554
|
throw new Error(`Failed to fetch integration catalog (${res.status})`);
|
|
2166
2555
|
}
|
|
@@ -2241,7 +2630,7 @@ async function runInvoke(args) {
|
|
|
2241
2630
|
const startedAt = Date.now();
|
|
2242
2631
|
let res;
|
|
2243
2632
|
try {
|
|
2244
|
-
res = await fetch(`${
|
|
2633
|
+
res = await fetch(`${API_URL3}/api/integrations/${integration}/${endpoint}`, {
|
|
2245
2634
|
method: "POST",
|
|
2246
2635
|
headers: {
|
|
2247
2636
|
"Content-Type": "application/json",
|
|
@@ -2292,7 +2681,7 @@ async function runInvoke(args) {
|
|
|
2292
2681
|
}
|
|
2293
2682
|
|
|
2294
2683
|
// src/cli/commands/integrations.ts
|
|
2295
|
-
var list3 =
|
|
2684
|
+
var list3 = defineCommand13({
|
|
2296
2685
|
meta: {
|
|
2297
2686
|
name: "list",
|
|
2298
2687
|
description: "List all available integrations"
|
|
@@ -2313,7 +2702,7 @@ var list3 = defineCommand12({
|
|
|
2313
2702
|
}
|
|
2314
2703
|
}
|
|
2315
2704
|
});
|
|
2316
|
-
var info =
|
|
2705
|
+
var info = defineCommand13({
|
|
2317
2706
|
meta: {
|
|
2318
2707
|
name: "info",
|
|
2319
2708
|
description: "Print schema + example body for an integration endpoint"
|
|
@@ -2339,7 +2728,7 @@ var info = defineCommand12({
|
|
|
2339
2728
|
}
|
|
2340
2729
|
}
|
|
2341
2730
|
});
|
|
2342
|
-
var invoke =
|
|
2731
|
+
var invoke = defineCommand13({
|
|
2343
2732
|
meta: {
|
|
2344
2733
|
name: "invoke",
|
|
2345
2734
|
description: "Invoke a platform integration as the logged-in user"
|
|
@@ -2392,7 +2781,7 @@ var invoke = defineCommand12({
|
|
|
2392
2781
|
}
|
|
2393
2782
|
}
|
|
2394
2783
|
});
|
|
2395
|
-
var integrations_default =
|
|
2784
|
+
var integrations_default = defineCommand13({
|
|
2396
2785
|
meta: {
|
|
2397
2786
|
name: "integrations",
|
|
2398
2787
|
description: "List, inspect, and invoke platform integrations"
|
|
@@ -2405,8 +2794,8 @@ var integrations_default = defineCommand12({
|
|
|
2405
2794
|
});
|
|
2406
2795
|
|
|
2407
2796
|
// src/cli/commands/invoke.ts
|
|
2408
|
-
import { defineCommand as
|
|
2409
|
-
var invoke_default =
|
|
2797
|
+
import { defineCommand as defineCommand14 } from "citty";
|
|
2798
|
+
var invoke_default = defineCommand14({
|
|
2410
2799
|
meta: {
|
|
2411
2800
|
name: "invoke",
|
|
2412
2801
|
description: "Invoke a platform integration as the logged-in user"
|
|
@@ -2478,13 +2867,472 @@ var invoke_default = defineCommand13({
|
|
|
2478
2867
|
}
|
|
2479
2868
|
});
|
|
2480
2869
|
|
|
2870
|
+
// src/cli/commands/managed-repos.ts
|
|
2871
|
+
import { defineCommand as defineCommand15 } from "citty";
|
|
2872
|
+
var API_URL4 = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
|
|
2873
|
+
var SIMPLE_APP_ID = /^[a-z0-9][a-z0-9_.:-]{0,119}$/i;
|
|
2874
|
+
var SIMPLE_APP_NAME = /^[a-z0-9][a-z0-9_-]{0,119}$/i;
|
|
2875
|
+
async function api2(token2, path, init) {
|
|
2876
|
+
const res = await fetch(`${API_URL4}${path}`, {
|
|
2877
|
+
...init,
|
|
2878
|
+
headers: {
|
|
2879
|
+
Authorization: `Bearer ${token2}`,
|
|
2880
|
+
"Content-Type": "application/json",
|
|
2881
|
+
...init?.headers ?? {}
|
|
2882
|
+
}
|
|
2883
|
+
});
|
|
2884
|
+
const text = await res.text();
|
|
2885
|
+
if (!res.ok) {
|
|
2886
|
+
let msg = text;
|
|
2887
|
+
try {
|
|
2888
|
+
msg = JSON.parse(text).error ?? text;
|
|
2889
|
+
} catch {
|
|
2890
|
+
}
|
|
2891
|
+
throw new Error(`API ${path} (${res.status}): ${msg}`);
|
|
2892
|
+
}
|
|
2893
|
+
return JSON.parse(text);
|
|
2894
|
+
}
|
|
2895
|
+
function defaultAppId(appName) {
|
|
2896
|
+
return `cli-${slugId(appName)}`;
|
|
2897
|
+
}
|
|
2898
|
+
function slugId(raw) {
|
|
2899
|
+
return raw.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "app";
|
|
2900
|
+
}
|
|
2901
|
+
function assertSimple(value, pattern, label) {
|
|
2902
|
+
if (!pattern.test(value)) {
|
|
2903
|
+
throw new Error(`${label} must start with a letter or number and contain only simple identifier characters.`);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
function printQuota(quota) {
|
|
2907
|
+
console.log(`Repo quota: ${quota.activeCount} / ${quota.limit} active (${quota.remaining} remaining)`);
|
|
2908
|
+
}
|
|
2909
|
+
function printRepo(repo) {
|
|
2910
|
+
console.log(`${repo.fullName}`);
|
|
2911
|
+
console.log(` ID: ${repo.id}`);
|
|
2912
|
+
console.log(` App: ${repo.appName}`);
|
|
2913
|
+
console.log(` App ID: ${repo.appId}`);
|
|
2914
|
+
console.log(` URL: ${repo.htmlUrl}`);
|
|
2915
|
+
console.log(` Clone: ${repo.cloneUrl}`);
|
|
2916
|
+
}
|
|
2917
|
+
async function confirm4(message) {
|
|
2918
|
+
if (!process.stdin.isTTY) {
|
|
2919
|
+
throw new Error(`${message} \u2014 pass --yes to confirm non-interactively`);
|
|
2920
|
+
}
|
|
2921
|
+
process.stdout.write(`${message} [y/N] `);
|
|
2922
|
+
return new Promise((resolve9) => {
|
|
2923
|
+
process.stdin.setEncoding("utf-8");
|
|
2924
|
+
process.stdin.once("data", (data) => {
|
|
2925
|
+
const answer = data.toString().trim().toLowerCase();
|
|
2926
|
+
resolve9(answer === "y" || answer === "yes");
|
|
2927
|
+
});
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
var list4 = defineCommand15({
|
|
2931
|
+
meta: {
|
|
2932
|
+
name: "list",
|
|
2933
|
+
description: "List your DeepSpace-managed repos and quota"
|
|
2934
|
+
},
|
|
2935
|
+
args: {
|
|
2936
|
+
json: {
|
|
2937
|
+
type: "boolean",
|
|
2938
|
+
description: "Emit JSON instead of human output",
|
|
2939
|
+
default: false
|
|
2940
|
+
}
|
|
2941
|
+
},
|
|
2942
|
+
async run({ args }) {
|
|
2943
|
+
const token2 = await ensureToken();
|
|
2944
|
+
const result = await api2(token2, "/api/github/repos");
|
|
2945
|
+
if (args.json) {
|
|
2946
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
2947
|
+
return;
|
|
2948
|
+
}
|
|
2949
|
+
printQuota(result.quota);
|
|
2950
|
+
if (result.repos.length === 0) {
|
|
2951
|
+
console.log("No DeepSpace-managed repos. Create one with `deepspace managed-repos create <app-name>`.");
|
|
2952
|
+
return;
|
|
2953
|
+
}
|
|
2954
|
+
console.log("");
|
|
2955
|
+
for (const repo of result.repos) {
|
|
2956
|
+
printRepo(repo);
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
});
|
|
2960
|
+
var create2 = defineCommand15({
|
|
2961
|
+
meta: {
|
|
2962
|
+
name: "create",
|
|
2963
|
+
description: "Create a private DeepSpace-managed GitHub repo"
|
|
2964
|
+
},
|
|
2965
|
+
args: {
|
|
2966
|
+
appName: {
|
|
2967
|
+
type: "positional",
|
|
2968
|
+
description: "Working app name for this repo",
|
|
2969
|
+
required: true
|
|
2970
|
+
},
|
|
2971
|
+
appId: {
|
|
2972
|
+
type: "string",
|
|
2973
|
+
description: "Stable DeepSpace repo app id (defaults to cli-<app-name>)",
|
|
2974
|
+
required: false
|
|
2975
|
+
},
|
|
2976
|
+
nameHint: {
|
|
2977
|
+
type: "string",
|
|
2978
|
+
description: "Repo-name hint (defaults to app name)",
|
|
2979
|
+
required: false
|
|
2980
|
+
},
|
|
2981
|
+
json: {
|
|
2982
|
+
type: "boolean",
|
|
2983
|
+
description: "Emit JSON instead of human output",
|
|
2984
|
+
default: false
|
|
2985
|
+
}
|
|
2986
|
+
},
|
|
2987
|
+
async run({ args }) {
|
|
2988
|
+
const appName = String(args.appName);
|
|
2989
|
+
const appId = args.appId ? String(args.appId) : defaultAppId(appName);
|
|
2990
|
+
const nameHint = args.nameHint ? String(args.nameHint) : void 0;
|
|
2991
|
+
try {
|
|
2992
|
+
assertSimple(appName, SIMPLE_APP_NAME, "appName");
|
|
2993
|
+
assertSimple(appId, SIMPLE_APP_ID, "appId");
|
|
2994
|
+
if (nameHint) assertSimple(nameHint, SIMPLE_APP_NAME, "nameHint");
|
|
2995
|
+
} catch (err) {
|
|
2996
|
+
console.error(err.message);
|
|
2997
|
+
process.exit(1);
|
|
2998
|
+
}
|
|
2999
|
+
const token2 = await ensureToken();
|
|
3000
|
+
const result = await api2(token2, "/api/github/repos", {
|
|
3001
|
+
method: "POST",
|
|
3002
|
+
body: JSON.stringify({ appName, appId, ...nameHint ? { nameHint } : {} })
|
|
3003
|
+
});
|
|
3004
|
+
if (args.json) {
|
|
3005
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
3006
|
+
return;
|
|
3007
|
+
}
|
|
3008
|
+
console.log(result.alreadyExists ? "Existing DeepSpace-managed repo:" : "Created DeepSpace-managed repo:");
|
|
3009
|
+
printRepo(result.repo);
|
|
3010
|
+
}
|
|
3011
|
+
});
|
|
3012
|
+
var token = defineCommand15({
|
|
3013
|
+
meta: {
|
|
3014
|
+
name: "token",
|
|
3015
|
+
description: "Mint a short-lived GitHub token for a DeepSpace-managed repo"
|
|
3016
|
+
},
|
|
3017
|
+
args: {
|
|
3018
|
+
repoId: {
|
|
3019
|
+
type: "positional",
|
|
3020
|
+
description: "Repo id from `deepspace managed-repos list`",
|
|
3021
|
+
required: true
|
|
3022
|
+
},
|
|
3023
|
+
json: {
|
|
3024
|
+
type: "boolean",
|
|
3025
|
+
description: "Emit JSON instead of human output",
|
|
3026
|
+
default: false
|
|
3027
|
+
}
|
|
3028
|
+
},
|
|
3029
|
+
async run({ args }) {
|
|
3030
|
+
const jwt = await ensureToken();
|
|
3031
|
+
const result = await api2(
|
|
3032
|
+
jwt,
|
|
3033
|
+
`/api/github/repos/${encodeURIComponent(String(args.repoId))}/agent-token`,
|
|
3034
|
+
{ method: "POST" }
|
|
3035
|
+
);
|
|
3036
|
+
if (args.json) {
|
|
3037
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
console.log(`Repo: ${result.repo.fullName}`);
|
|
3041
|
+
console.log(`Expires: ${result.token.expiresAt}`);
|
|
3042
|
+
console.log(`Token: ${result.token.token}`);
|
|
3043
|
+
console.log("");
|
|
3044
|
+
console.log(`Clone: git clone https://x-access-token:${encodeURIComponent(result.token.token)}@github.com/${result.repo.fullName}.git`);
|
|
3045
|
+
}
|
|
3046
|
+
});
|
|
3047
|
+
var remove = defineCommand15({
|
|
3048
|
+
meta: {
|
|
3049
|
+
name: "delete",
|
|
3050
|
+
description: "Delete a DeepSpace-managed GitHub repo"
|
|
3051
|
+
},
|
|
3052
|
+
args: {
|
|
3053
|
+
repoId: {
|
|
3054
|
+
type: "positional",
|
|
3055
|
+
description: "Repo id from `deepspace managed-repos list`",
|
|
3056
|
+
required: true
|
|
3057
|
+
},
|
|
3058
|
+
yes: {
|
|
3059
|
+
type: "boolean",
|
|
3060
|
+
description: "Skip the confirmation prompt",
|
|
3061
|
+
default: false
|
|
3062
|
+
},
|
|
3063
|
+
json: {
|
|
3064
|
+
type: "boolean",
|
|
3065
|
+
description: "Emit JSON instead of human output",
|
|
3066
|
+
default: false
|
|
3067
|
+
}
|
|
3068
|
+
},
|
|
3069
|
+
async run({ args }) {
|
|
3070
|
+
if (!args.yes) {
|
|
3071
|
+
try {
|
|
3072
|
+
const ok = await confirm4(
|
|
3073
|
+
"Delete this DeepSpace-managed GitHub repo? This removes the GitHub repository and counts against your daily delete quota."
|
|
3074
|
+
);
|
|
3075
|
+
if (!ok) {
|
|
3076
|
+
console.log("Cancelled.");
|
|
3077
|
+
return;
|
|
3078
|
+
}
|
|
3079
|
+
} catch (err) {
|
|
3080
|
+
console.error(err.message);
|
|
3081
|
+
process.exit(1);
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
const jwt = await ensureToken();
|
|
3085
|
+
const result = await api2(
|
|
3086
|
+
jwt,
|
|
3087
|
+
`/api/github/repos/${encodeURIComponent(String(args.repoId))}`,
|
|
3088
|
+
{ method: "DELETE" }
|
|
3089
|
+
);
|
|
3090
|
+
if (args.json) {
|
|
3091
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
3092
|
+
return;
|
|
3093
|
+
}
|
|
3094
|
+
console.log(`Deleted ${result.repo.fullName}`);
|
|
3095
|
+
console.log(
|
|
3096
|
+
`Delete quota: ${result.deletionQuota.deletedCount} / ${result.deletionQuota.limit} used today (${result.deletionQuota.remaining} remaining)`
|
|
3097
|
+
);
|
|
3098
|
+
}
|
|
3099
|
+
});
|
|
3100
|
+
var managed_repos_default = defineCommand15({
|
|
3101
|
+
meta: {
|
|
3102
|
+
name: "managed-repos",
|
|
3103
|
+
description: "Create and manage DeepSpace-owned GitHub repos"
|
|
3104
|
+
},
|
|
3105
|
+
subCommands: {
|
|
3106
|
+
list: list4,
|
|
3107
|
+
create: create2,
|
|
3108
|
+
token,
|
|
3109
|
+
delete: remove
|
|
3110
|
+
}
|
|
3111
|
+
});
|
|
3112
|
+
|
|
3113
|
+
// src/cli/commands/library.ts
|
|
3114
|
+
import { defineCommand as defineCommand16 } from "citty";
|
|
3115
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
3116
|
+
import { join as join12, resolve as resolve8 } from "path";
|
|
3117
|
+
import { parse as parseToml5 } from "smol-toml";
|
|
3118
|
+
import * as p6 from "@clack/prompts";
|
|
3119
|
+
var DEFAULT_LIBRARY_APP = "deepdotspace-site";
|
|
3120
|
+
var publish = defineCommand16({
|
|
3121
|
+
meta: {
|
|
3122
|
+
name: "publish",
|
|
3123
|
+
description: "Publish the current app to the DeepSpace library"
|
|
3124
|
+
},
|
|
3125
|
+
args: {
|
|
3126
|
+
dir: {
|
|
3127
|
+
type: "string",
|
|
3128
|
+
description: "App directory (default: current directory)",
|
|
3129
|
+
required: false
|
|
3130
|
+
},
|
|
3131
|
+
app: {
|
|
3132
|
+
type: "string",
|
|
3133
|
+
description: "App name (default: read from wrangler.toml)",
|
|
3134
|
+
required: false
|
|
3135
|
+
},
|
|
3136
|
+
name: {
|
|
3137
|
+
type: "string",
|
|
3138
|
+
description: "Display name (default: app name)",
|
|
3139
|
+
required: false
|
|
3140
|
+
},
|
|
3141
|
+
description: {
|
|
3142
|
+
type: "string",
|
|
3143
|
+
description: "Short description",
|
|
3144
|
+
required: false
|
|
3145
|
+
},
|
|
3146
|
+
category: {
|
|
3147
|
+
type: "string",
|
|
3148
|
+
description: "Category (e.g. Productivity, Finance, AI). Default: General",
|
|
3149
|
+
required: false
|
|
3150
|
+
},
|
|
3151
|
+
tags: {
|
|
3152
|
+
type: "string",
|
|
3153
|
+
description: "Comma-separated tags",
|
|
3154
|
+
required: false
|
|
3155
|
+
},
|
|
3156
|
+
visibility: {
|
|
3157
|
+
type: "string",
|
|
3158
|
+
description: "'public' (default), 'unlisted', or 'private'",
|
|
3159
|
+
required: false
|
|
3160
|
+
},
|
|
3161
|
+
screenshot: {
|
|
3162
|
+
type: "string",
|
|
3163
|
+
description: "Override the auto-captured preview with a public PNG URL",
|
|
3164
|
+
required: false
|
|
3165
|
+
},
|
|
3166
|
+
"library-app": {
|
|
3167
|
+
type: "string",
|
|
3168
|
+
description: `Target library app subdomain (default: ${DEFAULT_LIBRARY_APP})`,
|
|
3169
|
+
required: false
|
|
3170
|
+
},
|
|
3171
|
+
json: {
|
|
3172
|
+
type: "boolean",
|
|
3173
|
+
description: "Print only the JSON response (machine-readable)",
|
|
3174
|
+
default: false
|
|
3175
|
+
}
|
|
3176
|
+
},
|
|
3177
|
+
async run({ args }) {
|
|
3178
|
+
const json = !!args.json;
|
|
3179
|
+
const intro5 = (s) => {
|
|
3180
|
+
if (!json) p6.intro(s);
|
|
3181
|
+
};
|
|
3182
|
+
const info2 = (s) => {
|
|
3183
|
+
if (!json) p6.log.info(s);
|
|
3184
|
+
};
|
|
3185
|
+
const die = (msg) => {
|
|
3186
|
+
if (json) console.error(JSON.stringify({ ok: false, error: msg }));
|
|
3187
|
+
else p6.cancel(msg);
|
|
3188
|
+
process.exit(1);
|
|
3189
|
+
};
|
|
3190
|
+
intro5("Publishing to the DeepSpace library");
|
|
3191
|
+
let appName = typeof args.app === "string" && args.app.trim() ? args.app.trim() : void 0;
|
|
3192
|
+
if (!appName) {
|
|
3193
|
+
const appDir = resolve8(typeof args.dir === "string" && args.dir ? args.dir : ".");
|
|
3194
|
+
const wranglerPath = join12(appDir, "wrangler.toml");
|
|
3195
|
+
if (!existsSync12(wranglerPath)) {
|
|
3196
|
+
return die("No --app provided and no wrangler.toml found. Either pass --app <name> or run from your app's directory.");
|
|
3197
|
+
}
|
|
3198
|
+
const cfg = parseToml5(readFileSync9(wranglerPath, "utf-8"));
|
|
3199
|
+
const nameRes = resolveAppName(cfg.name);
|
|
3200
|
+
if (!nameRes.ok) return die(`wrangler.toml: ${nameRes.reason}`);
|
|
3201
|
+
appName = nameRes.name;
|
|
3202
|
+
}
|
|
3203
|
+
info2(`App: ${appName}`);
|
|
3204
|
+
const visibility = args.visibility ?? "public";
|
|
3205
|
+
if (!["public", "private", "unlisted"].includes(visibility)) {
|
|
3206
|
+
return die("--visibility must be 'public', 'unlisted', or 'private'.");
|
|
3207
|
+
}
|
|
3208
|
+
const tags = typeof args.tags === "string" && args.tags ? args.tags.split(",").map((t) => t.trim()).filter(Boolean) : void 0;
|
|
3209
|
+
const libraryApp = typeof args["library-app"] === "string" && args["library-app"] ? args["library-app"] : DEFAULT_LIBRARY_APP;
|
|
3210
|
+
const libraryHost = process.env.DEEPSPACE_LIBRARY_HOST ?? `https://${libraryApp}.app.space`;
|
|
3211
|
+
let token2;
|
|
3212
|
+
try {
|
|
3213
|
+
token2 = await ensureToken();
|
|
3214
|
+
} catch (err) {
|
|
3215
|
+
return die(err instanceof Error ? err.message : String(err));
|
|
3216
|
+
}
|
|
3217
|
+
const payload = {
|
|
3218
|
+
appName,
|
|
3219
|
+
...args.name ? { name: args.name } : {},
|
|
3220
|
+
...args.description ? { description: args.description } : {},
|
|
3221
|
+
...args.category ? { category: args.category } : {},
|
|
3222
|
+
...tags ? { tags } : {},
|
|
3223
|
+
visibility,
|
|
3224
|
+
...args.screenshot ? { screenshotUrl: args.screenshot } : {}
|
|
3225
|
+
};
|
|
3226
|
+
const url = `${libraryHost}/api/actions/library.publish`;
|
|
3227
|
+
let res;
|
|
3228
|
+
try {
|
|
3229
|
+
res = await fetch(url, {
|
|
3230
|
+
method: "POST",
|
|
3231
|
+
headers: {
|
|
3232
|
+
"Content-Type": "application/json",
|
|
3233
|
+
Authorization: `Bearer ${token2}`
|
|
3234
|
+
},
|
|
3235
|
+
body: JSON.stringify(payload)
|
|
3236
|
+
});
|
|
3237
|
+
} catch (err) {
|
|
3238
|
+
return die(`Network error reaching ${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3239
|
+
}
|
|
3240
|
+
const body = await res.text();
|
|
3241
|
+
let parsed;
|
|
3242
|
+
try {
|
|
3243
|
+
parsed = JSON.parse(body);
|
|
3244
|
+
} catch {
|
|
3245
|
+
return die(`Server returned non-JSON (${res.status}): ${body.slice(0, 400)}`);
|
|
3246
|
+
}
|
|
3247
|
+
if (json) {
|
|
3248
|
+
console.log(JSON.stringify(parsed));
|
|
3249
|
+
process.exit(parsed.success ? 0 : 1);
|
|
3250
|
+
}
|
|
3251
|
+
if (!parsed.success) {
|
|
3252
|
+
return die(parsed.error ?? `Publish failed (HTTP ${res.status}).`);
|
|
3253
|
+
}
|
|
3254
|
+
p6.log.success(`Published ${appName} as ${parsed.data?.templateHandle} (v${parsed.data?.version})`);
|
|
3255
|
+
if (parsed.data?.url) p6.log.message(parsed.data.url);
|
|
3256
|
+
p6.outro("Done");
|
|
3257
|
+
}
|
|
3258
|
+
});
|
|
3259
|
+
var unpublish = defineCommand16({
|
|
3260
|
+
meta: {
|
|
3261
|
+
name: "unpublish",
|
|
3262
|
+
description: "Remove a library entry (owner or admin only)"
|
|
3263
|
+
},
|
|
3264
|
+
args: {
|
|
3265
|
+
handle: {
|
|
3266
|
+
type: "positional",
|
|
3267
|
+
description: "templateHandle to remove (usually your app name)",
|
|
3268
|
+
required: true
|
|
3269
|
+
},
|
|
3270
|
+
"library-app": {
|
|
3271
|
+
type: "string",
|
|
3272
|
+
description: `Target library app subdomain (default: ${DEFAULT_LIBRARY_APP})`,
|
|
3273
|
+
required: false
|
|
3274
|
+
},
|
|
3275
|
+
json: {
|
|
3276
|
+
type: "boolean",
|
|
3277
|
+
description: "Print only the JSON response",
|
|
3278
|
+
default: false
|
|
3279
|
+
}
|
|
3280
|
+
},
|
|
3281
|
+
async run({ args }) {
|
|
3282
|
+
const json = !!args.json;
|
|
3283
|
+
const die = (msg) => {
|
|
3284
|
+
if (json) console.error(JSON.stringify({ ok: false, error: msg }));
|
|
3285
|
+
else p6.cancel(msg);
|
|
3286
|
+
process.exit(1);
|
|
3287
|
+
};
|
|
3288
|
+
if (!json) p6.intro("Unpublishing library entry");
|
|
3289
|
+
let token2;
|
|
3290
|
+
try {
|
|
3291
|
+
token2 = await ensureToken();
|
|
3292
|
+
} catch (err) {
|
|
3293
|
+
return die(err instanceof Error ? err.message : String(err));
|
|
3294
|
+
}
|
|
3295
|
+
const libraryApp = typeof args["library-app"] === "string" && args["library-app"] ? args["library-app"] : DEFAULT_LIBRARY_APP;
|
|
3296
|
+
const libraryHost = process.env.DEEPSPACE_LIBRARY_HOST ?? `https://${libraryApp}.app.space`;
|
|
3297
|
+
const res = await fetch(`${libraryHost}/api/actions/library.unpublish`, {
|
|
3298
|
+
method: "POST",
|
|
3299
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token2}` },
|
|
3300
|
+
body: JSON.stringify({ templateHandle: String(args.handle) })
|
|
3301
|
+
});
|
|
3302
|
+
const body = await res.text();
|
|
3303
|
+
let parsed;
|
|
3304
|
+
try {
|
|
3305
|
+
parsed = JSON.parse(body);
|
|
3306
|
+
} catch {
|
|
3307
|
+
return die(`Server returned non-JSON (${res.status}): ${body.slice(0, 400)}`);
|
|
3308
|
+
}
|
|
3309
|
+
if (json) {
|
|
3310
|
+
console.log(JSON.stringify(parsed));
|
|
3311
|
+
process.exit(parsed.success ? 0 : 1);
|
|
3312
|
+
}
|
|
3313
|
+
if (!parsed.success) return die(parsed.error ?? `HTTP ${res.status}`);
|
|
3314
|
+
p6.log.success(`Unpublished ${parsed.data?.templateHandle}`);
|
|
3315
|
+
p6.outro("Done");
|
|
3316
|
+
}
|
|
3317
|
+
});
|
|
3318
|
+
var library_default = defineCommand16({
|
|
3319
|
+
meta: {
|
|
3320
|
+
name: "library",
|
|
3321
|
+
description: "Manage your DeepSpace library entries"
|
|
3322
|
+
},
|
|
3323
|
+
subCommands: {
|
|
3324
|
+
publish,
|
|
3325
|
+
unpublish
|
|
3326
|
+
}
|
|
3327
|
+
});
|
|
3328
|
+
|
|
2481
3329
|
// src/cli/cli.ts
|
|
2482
3330
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
2483
|
-
var pkg = JSON.parse(
|
|
2484
|
-
if (process.platform === "linux" && !process.env.SSL_CERT_FILE &&
|
|
3331
|
+
var pkg = JSON.parse(readFileSync10(join13(__dirname, "..", "package.json"), "utf-8"));
|
|
3332
|
+
if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync13("/etc/ssl/certs/ca-certificates.crt")) {
|
|
2485
3333
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
2486
3334
|
}
|
|
2487
|
-
var
|
|
3335
|
+
var create3 = defineCommand17({
|
|
2488
3336
|
meta: {
|
|
2489
3337
|
name: "create",
|
|
2490
3338
|
description: "Create a new DeepSpace app"
|
|
@@ -2503,27 +3351,30 @@ var create2 = defineCommand14({
|
|
|
2503
3351
|
execSync5(`npm create deepspace@latest${name}`, { stdio: "inherit" });
|
|
2504
3352
|
}
|
|
2505
3353
|
});
|
|
2506
|
-
var main =
|
|
3354
|
+
var main = defineCommand17({
|
|
2507
3355
|
meta: {
|
|
2508
3356
|
name: "deepspace",
|
|
2509
3357
|
version: pkg.version,
|
|
2510
3358
|
description: "DeepSpace SDK CLI"
|
|
2511
3359
|
},
|
|
2512
3360
|
subCommands: {
|
|
2513
|
-
create:
|
|
3361
|
+
create: create3,
|
|
2514
3362
|
login: login_default,
|
|
2515
3363
|
logout: logout_default,
|
|
2516
3364
|
whoami: whoami_default,
|
|
2517
3365
|
dev: dev_default,
|
|
2518
3366
|
kill: kill_default,
|
|
2519
3367
|
test: test_default,
|
|
3368
|
+
screenshot: screenshot_default,
|
|
2520
3369
|
"test-accounts": test_accounts_default,
|
|
2521
3370
|
add: add_default,
|
|
2522
3371
|
integrations: integrations_default,
|
|
3372
|
+
"managed-repos": managed_repos_default,
|
|
2523
3373
|
invoke: invoke_default,
|
|
2524
3374
|
deploy: deploy_default,
|
|
2525
3375
|
undeploy: undeploy_default,
|
|
2526
|
-
domain: domain_default
|
|
3376
|
+
domain: domain_default,
|
|
3377
|
+
library: library_default
|
|
2527
3378
|
}
|
|
2528
3379
|
// No `run()` here — citty cascades parent run() AFTER subcommand finishes,
|
|
2529
3380
|
// which corrupts agent-friendly output (`--json`) and prints noise on every
|