deepspace 0.3.8 → 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 +694 -145
- 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" };
|
|
52
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
|
+
};
|
|
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");
|
|
@@ -449,13 +550,50 @@ async function ensureToken() {
|
|
|
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,10 +619,10 @@ 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
|
}
|
|
@@ -497,7 +635,7 @@ var dev_default = defineCommand3({
|
|
|
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);
|
|
@@ -506,6 +644,7 @@ var dev_default = defineCommand3({
|
|
|
506
644
|
console.log(`Environment: ${env}`);
|
|
507
645
|
console.log(`Port: ${port}`);
|
|
508
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,12 +834,13 @@ 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";
|
|
702
840
|
|
|
703
841
|
// src/cli/lib/playwright.ts
|
|
704
|
-
import { execSync
|
|
842
|
+
import { execSync } from "child_process";
|
|
843
|
+
import { sync as spawnSync2 } from "cross-spawn";
|
|
705
844
|
function ensurePlaywright(appDir) {
|
|
706
845
|
try {
|
|
707
846
|
execSync("npx playwright --version", { cwd: appDir, stdio: "pipe" });
|
|
@@ -714,8 +853,7 @@ function ensurePlaywright(appDir) {
|
|
|
714
853
|
const args = isRoot ? ["install", "--with-deps", "chromium"] : ["install", "chromium"];
|
|
715
854
|
const result = spawnSync2("npx", ["playwright", ...args], {
|
|
716
855
|
cwd: appDir,
|
|
717
|
-
stdio: "inherit"
|
|
718
|
-
shell: process.platform === "win32"
|
|
856
|
+
stdio: "inherit"
|
|
719
857
|
});
|
|
720
858
|
if (result.status !== 0) {
|
|
721
859
|
console.error("\nPlaywright install failed. On Linux, system libs may be missing. Try:");
|
|
@@ -744,10 +882,10 @@ var test_default = defineCommand5({
|
|
|
744
882
|
}
|
|
745
883
|
},
|
|
746
884
|
async run({ args }) {
|
|
747
|
-
const appDir =
|
|
885
|
+
const appDir = resolve3(".");
|
|
748
886
|
const suite = args.suite ?? "default";
|
|
749
887
|
const port = resolvePort3(args.port);
|
|
750
|
-
if (!
|
|
888
|
+
if (!existsSync6(join6(appDir, "wrangler.toml"))) {
|
|
751
889
|
console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
|
|
752
890
|
process.exit(1);
|
|
753
891
|
}
|
|
@@ -755,7 +893,7 @@ var test_default = defineCommand5({
|
|
|
755
893
|
let ownerId;
|
|
756
894
|
try {
|
|
757
895
|
token2 = await ensureToken();
|
|
758
|
-
const payload =
|
|
896
|
+
const payload = decodeJwtPayload(token2);
|
|
759
897
|
ownerId = payload.sub;
|
|
760
898
|
} catch (err) {
|
|
761
899
|
console.error("`deepspace test` requires you to be logged in. Run `deepspace login` first.");
|
|
@@ -763,6 +901,7 @@ var test_default = defineCommand5({
|
|
|
763
901
|
}
|
|
764
902
|
await writeDevVars(appDir, "dev", ownerId, token2);
|
|
765
903
|
if (suite !== "unit") {
|
|
904
|
+
preflightWindowsWorkerd(appDir);
|
|
766
905
|
ensurePlaywright(appDir);
|
|
767
906
|
}
|
|
768
907
|
let exitCode = 0;
|
|
@@ -808,7 +947,6 @@ function runPlaywright(appDir, testFiles, port) {
|
|
|
808
947
|
], {
|
|
809
948
|
cwd: appDir,
|
|
810
949
|
stdio: "inherit",
|
|
811
|
-
shell: process.platform === "win32",
|
|
812
950
|
env: { ...process.env, DEEPSPACE_PORT: String(port) }
|
|
813
951
|
});
|
|
814
952
|
return result.status ?? 1;
|
|
@@ -816,8 +954,7 @@ function runPlaywright(appDir, testFiles, port) {
|
|
|
816
954
|
function runVitest(appDir) {
|
|
817
955
|
const result = spawnSync3("npx", ["vitest", "run", "--passWithNoTests"], {
|
|
818
956
|
cwd: appDir,
|
|
819
|
-
stdio: "inherit"
|
|
820
|
-
shell: process.platform === "win32"
|
|
957
|
+
stdio: "inherit"
|
|
821
958
|
});
|
|
822
959
|
return result.status ?? 1;
|
|
823
960
|
}
|
|
@@ -834,8 +971,8 @@ function resolvePort3(arg) {
|
|
|
834
971
|
|
|
835
972
|
// src/cli/commands/screenshot.ts
|
|
836
973
|
import { defineCommand as defineCommand6 } from "citty";
|
|
837
|
-
import {
|
|
838
|
-
import { resolve as
|
|
974
|
+
import { sync as spawnSync4 } from "cross-spawn";
|
|
975
|
+
import { resolve as resolve4 } from "path";
|
|
839
976
|
var screenshot_default = defineCommand6({
|
|
840
977
|
meta: {
|
|
841
978
|
name: "screenshot",
|
|
@@ -864,7 +1001,7 @@ var screenshot_default = defineCommand6({
|
|
|
864
1001
|
}
|
|
865
1002
|
},
|
|
866
1003
|
run({ args }) {
|
|
867
|
-
const appDir =
|
|
1004
|
+
const appDir = resolve4(".");
|
|
868
1005
|
ensurePlaywright(appDir);
|
|
869
1006
|
const playwrightArgs = ["playwright", "screenshot", args.url, args.output];
|
|
870
1007
|
if (args["full-page"]) playwrightArgs.push("--full-page");
|
|
@@ -873,8 +1010,7 @@ var screenshot_default = defineCommand6({
|
|
|
873
1010
|
}
|
|
874
1011
|
const result = spawnSync4("npx", playwrightArgs, {
|
|
875
1012
|
cwd: appDir,
|
|
876
|
-
stdio: "inherit"
|
|
877
|
-
shell: process.platform === "win32"
|
|
1013
|
+
stdio: "inherit"
|
|
878
1014
|
});
|
|
879
1015
|
process.exit(result.status ?? 1);
|
|
880
1016
|
}
|
|
@@ -883,7 +1019,7 @@ var screenshot_default = defineCommand6({
|
|
|
883
1019
|
// src/cli/commands/test-accounts.ts
|
|
884
1020
|
import { defineCommand as defineCommand7 } from "citty";
|
|
885
1021
|
import * as p3 from "@clack/prompts";
|
|
886
|
-
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";
|
|
887
1023
|
import { join as join7 } from "path";
|
|
888
1024
|
import { homedir as homedir4 } from "os";
|
|
889
1025
|
var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
|
|
@@ -891,7 +1027,7 @@ var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
|
|
|
891
1027
|
var DIR3 = join7(homedir4(), ".deepspace");
|
|
892
1028
|
var ACCOUNTS_PATH = join7(DIR3, "test-accounts.json");
|
|
893
1029
|
function loadAccounts() {
|
|
894
|
-
if (!
|
|
1030
|
+
if (!existsSync7(ACCOUNTS_PATH)) return [];
|
|
895
1031
|
try {
|
|
896
1032
|
return JSON.parse(readFileSync4(ACCOUNTS_PATH, "utf-8"));
|
|
897
1033
|
} catch {
|
|
@@ -1170,9 +1306,10 @@ var test_accounts_default = defineCommand7({
|
|
|
1170
1306
|
// src/cli/commands/deploy.ts
|
|
1171
1307
|
import { defineCommand as defineCommand8 } from "citty";
|
|
1172
1308
|
import { execSync as execSync2 } from "child_process";
|
|
1173
|
-
import { readFileSync as readFileSync5, readdirSync, existsSync as
|
|
1309
|
+
import { readFileSync as readFileSync5, readdirSync, existsSync as existsSync8 } from "fs";
|
|
1174
1310
|
import { parse as parseToml2 } from "smol-toml";
|
|
1175
|
-
import { join as join8, resolve as
|
|
1311
|
+
import { join as join8, resolve as resolve5, dirname } from "path";
|
|
1312
|
+
import { pathToFileURL } from "url";
|
|
1176
1313
|
import * as p4 from "@clack/prompts";
|
|
1177
1314
|
|
|
1178
1315
|
// src/server/rooms/binding-manifest.ts
|
|
@@ -1378,61 +1515,32 @@ function asObjectArray(v) {
|
|
|
1378
1515
|
return v.filter(isObject);
|
|
1379
1516
|
}
|
|
1380
1517
|
|
|
1381
|
-
// src/server/rooms/app-name.ts
|
|
1382
|
-
var APP_NAME_RULES = {
|
|
1383
|
-
/** ^[a-z0-9](-?[a-z0-9])+$ — RFC 1035 host label, no consecutive dashes. */
|
|
1384
|
-
pattern: /^[a-z0-9](?:-?[a-z0-9])+$/,
|
|
1385
|
-
minLength: 2,
|
|
1386
|
-
maxLength: 63
|
|
1387
|
-
};
|
|
1388
|
-
function validateAppName(raw) {
|
|
1389
|
-
if (typeof raw !== "string") {
|
|
1390
|
-
return { valid: false, reason: "App name must be a string" };
|
|
1391
|
-
}
|
|
1392
|
-
const name = raw.trim();
|
|
1393
|
-
if (!name) {
|
|
1394
|
-
return { valid: false, reason: "App name is required" };
|
|
1395
|
-
}
|
|
1396
|
-
if (name.length < APP_NAME_RULES.minLength) {
|
|
1397
|
-
return { valid: false, reason: `App name "${name}" is too short (min ${APP_NAME_RULES.minLength} chars)` };
|
|
1398
|
-
}
|
|
1399
|
-
if (name.length > APP_NAME_RULES.maxLength) {
|
|
1400
|
-
return { valid: false, reason: `App name "${name}" is too long (max ${APP_NAME_RULES.maxLength} chars)` };
|
|
1401
|
-
}
|
|
1402
|
-
if (!APP_NAME_RULES.pattern.test(name)) {
|
|
1403
|
-
return {
|
|
1404
|
-
valid: false,
|
|
1405
|
-
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.`
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1408
|
-
return { valid: true, name };
|
|
1409
|
-
}
|
|
1410
|
-
function resolveAppName(raw) {
|
|
1411
|
-
const strict = validateAppName(raw);
|
|
1412
|
-
if (strict.valid) return { ok: true, name: strict.name };
|
|
1413
|
-
if (typeof raw !== "string") return { ok: false, reason: strict.reason };
|
|
1414
|
-
const trimmed = raw.trim();
|
|
1415
|
-
if (!trimmed) return { ok: false, reason: "App name is required" };
|
|
1416
|
-
const sanitized = sanitizeAppName(trimmed);
|
|
1417
|
-
const sanitizedStrict = validateAppName(sanitized);
|
|
1418
|
-
if (!sanitizedStrict.valid) {
|
|
1419
|
-
return {
|
|
1420
|
-
ok: false,
|
|
1421
|
-
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.`
|
|
1422
|
-
};
|
|
1423
|
-
}
|
|
1424
|
-
return {
|
|
1425
|
-
ok: true,
|
|
1426
|
-
name: sanitized,
|
|
1427
|
-
warning: `App name "${trimmed}" doesn't conform to deploy rules \u2014 using "${sanitized}" instead. Update wrangler.toml's \`name\` to silence this warning.`
|
|
1428
|
-
};
|
|
1429
|
-
}
|
|
1430
|
-
function sanitizeAppName(raw) {
|
|
1431
|
-
return raw.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
1518
|
// src/cli/commands/deploy.ts
|
|
1435
1519
|
var DEPLOY_URL = process.env.DEEPSPACE_DEPLOY_URL ?? ENVS.prod.deploy;
|
|
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
|
+
}
|
|
1436
1544
|
var deploy_default = defineCommand8({
|
|
1437
1545
|
meta: {
|
|
1438
1546
|
name: "deploy",
|
|
@@ -1446,10 +1554,10 @@ var deploy_default = defineCommand8({
|
|
|
1446
1554
|
}
|
|
1447
1555
|
},
|
|
1448
1556
|
async run({ args }) {
|
|
1449
|
-
const appDir =
|
|
1557
|
+
const appDir = resolve5(args.dir ?? ".");
|
|
1450
1558
|
p4.intro("Deploying DeepSpace app");
|
|
1451
1559
|
const wranglerPath = join8(appDir, "wrangler.toml");
|
|
1452
|
-
if (!
|
|
1560
|
+
if (!existsSync8(wranglerPath)) {
|
|
1453
1561
|
p4.cancel("No wrangler.toml found. Are you in a DeepSpace app directory?");
|
|
1454
1562
|
process.exit(1);
|
|
1455
1563
|
}
|
|
@@ -1459,7 +1567,12 @@ var deploy_default = defineCommand8({
|
|
|
1459
1567
|
p4.cancel(`wrangler.toml: ${nameRes.reason}`);
|
|
1460
1568
|
process.exit(1);
|
|
1461
1569
|
}
|
|
1462
|
-
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
|
+
}
|
|
1463
1576
|
const appName = nameRes.name;
|
|
1464
1577
|
p4.log.info(`App: ${appName}`);
|
|
1465
1578
|
let token2;
|
|
@@ -1480,25 +1593,25 @@ var deploy_default = defineCommand8({
|
|
|
1480
1593
|
}
|
|
1481
1594
|
s.stop("Built");
|
|
1482
1595
|
const deployConfigPath = join8(appDir, ".wrangler", "deploy", "config.json");
|
|
1483
|
-
if (!
|
|
1596
|
+
if (!existsSync8(deployConfigPath)) {
|
|
1484
1597
|
p4.cancel("Build output config not found at .wrangler/deploy/config.json");
|
|
1485
1598
|
process.exit(1);
|
|
1486
1599
|
}
|
|
1487
1600
|
const deployConfig = JSON.parse(readFileSync5(deployConfigPath, "utf-8"));
|
|
1488
|
-
const outputWranglerPath =
|
|
1489
|
-
if (!
|
|
1601
|
+
const outputWranglerPath = resolve5(dirname(deployConfigPath), deployConfig.configPath);
|
|
1602
|
+
if (!existsSync8(outputWranglerPath)) {
|
|
1490
1603
|
p4.cancel(`Output wrangler.json not found at ${outputWranglerPath}`);
|
|
1491
1604
|
process.exit(1);
|
|
1492
1605
|
}
|
|
1493
1606
|
const outputConfig = JSON.parse(readFileSync5(outputWranglerPath, "utf-8"));
|
|
1494
1607
|
const workerDir = dirname(outputWranglerPath);
|
|
1495
1608
|
const workerBundlePath = join8(workerDir, outputConfig.main);
|
|
1496
|
-
const clientDir = outputConfig.assets?.directory ?
|
|
1497
|
-
if (!
|
|
1609
|
+
const clientDir = outputConfig.assets?.directory ? resolve5(workerDir, outputConfig.assets.directory) : null;
|
|
1610
|
+
if (!existsSync8(workerBundlePath)) {
|
|
1498
1611
|
p4.cancel(`Worker bundle not found at ${workerBundlePath}`);
|
|
1499
1612
|
process.exit(1);
|
|
1500
1613
|
}
|
|
1501
|
-
if (!clientDir || !
|
|
1614
|
+
if (!clientDir || !existsSync8(clientDir)) {
|
|
1502
1615
|
p4.cancel(`Client assets not found at ${clientDir}`);
|
|
1503
1616
|
process.exit(1);
|
|
1504
1617
|
}
|
|
@@ -1534,7 +1647,7 @@ var deploy_default = defineCommand8({
|
|
|
1534
1647
|
}
|
|
1535
1648
|
const devVarsPath = join8(appDir, ".dev.vars");
|
|
1536
1649
|
let userSecrets = {};
|
|
1537
|
-
if (
|
|
1650
|
+
if (existsSync8(devVarsPath)) {
|
|
1538
1651
|
const customSection = extractCustomDevVars(readFileSync5(devVarsPath, "utf-8"));
|
|
1539
1652
|
if (customSection) {
|
|
1540
1653
|
try {
|
|
@@ -1580,6 +1693,11 @@ var deploy_default = defineCommand8({
|
|
|
1580
1693
|
if (userSecretNames.length) {
|
|
1581
1694
|
form.append("userSecrets", JSON.stringify(userSecrets));
|
|
1582
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
|
+
}
|
|
1583
1701
|
const res = await fetch(`${DEPLOY_URL}/api/deploy/${appName}`, {
|
|
1584
1702
|
method: "POST",
|
|
1585
1703
|
headers: { Authorization: `Bearer ${token2}` },
|
|
@@ -1598,15 +1716,228 @@ var deploy_default = defineCommand8({
|
|
|
1598
1716
|
s.stop("Deployed (edge propagation still in progress after 90s)");
|
|
1599
1717
|
p4.log.warn("First requests may briefly hit the assets transitional page; retry in a minute.");
|
|
1600
1718
|
p4.log.success(`Live at: ${body.url}`);
|
|
1719
|
+
await syncSubscriptionPlans(appDir, appName, token2);
|
|
1720
|
+
await syncOneTimeProducts(appDir, appName, token2);
|
|
1601
1721
|
p4.outro("Done");
|
|
1602
1722
|
return;
|
|
1603
1723
|
}
|
|
1604
1724
|
}
|
|
1605
1725
|
s.stop("Deployed!");
|
|
1606
1726
|
p4.log.success(`Live at: ${body.url}`);
|
|
1727
|
+
await syncSubscriptionPlans(appDir, appName, token2);
|
|
1728
|
+
await syncOneTimeProducts(appDir, appName, token2);
|
|
1607
1729
|
p4.outro("Done");
|
|
1608
1730
|
}
|
|
1609
1731
|
});
|
|
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
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
const changes = (body.planChanges ?? []).filter((c) => c.affectedSubscribers > 0);
|
|
1790
|
+
if (changes.length > 0) {
|
|
1791
|
+
await promptAndCancelChangedPlans(appName, token2, changes);
|
|
1792
|
+
}
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
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
|
+
);
|
|
1799
|
+
}
|
|
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
|
+
}
|
|
1610
1941
|
async function waitForAssetsReady(url, timeoutMs) {
|
|
1611
1942
|
const deadline = Date.now() + timeoutMs;
|
|
1612
1943
|
let attempt = 0;
|
|
@@ -1648,7 +1979,7 @@ function collectAssets(dir) {
|
|
|
1648
1979
|
|
|
1649
1980
|
// src/cli/commands/undeploy.ts
|
|
1650
1981
|
import { defineCommand as defineCommand9 } from "citty";
|
|
1651
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
1982
|
+
import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
|
|
1652
1983
|
import { join as join9 } from "path";
|
|
1653
1984
|
import { parse as parseToml3 } from "smol-toml";
|
|
1654
1985
|
import * as p5 from "@clack/prompts";
|
|
@@ -1669,7 +2000,7 @@ var undeploy_default = defineCommand9({
|
|
|
1669
2000
|
let appName = args.name;
|
|
1670
2001
|
if (!appName) {
|
|
1671
2002
|
const wranglerPath = join9(process.cwd(), "wrangler.toml");
|
|
1672
|
-
if (
|
|
2003
|
+
if (existsSync9(wranglerPath)) {
|
|
1673
2004
|
const config = parseToml3(readFileSync6(wranglerPath, "utf-8"));
|
|
1674
2005
|
if (config.name) appName = config.name;
|
|
1675
2006
|
}
|
|
@@ -1727,7 +2058,7 @@ var whoami_default = defineCommand10({
|
|
|
1727
2058
|
}
|
|
1728
2059
|
let payload;
|
|
1729
2060
|
try {
|
|
1730
|
-
payload =
|
|
2061
|
+
payload = decodeJwtPayload(jwt);
|
|
1731
2062
|
} catch {
|
|
1732
2063
|
console.error("Malformed session token. Run `deepspace login` again.");
|
|
1733
2064
|
process.exit(1);
|
|
@@ -1750,6 +2081,7 @@ var whoami_default = defineCommand10({
|
|
|
1750
2081
|
console.log(`UserID: ${payload.sub ?? "(none)"}`);
|
|
1751
2082
|
console.log(`Type: ${accountType}`);
|
|
1752
2083
|
if (payload.iss) console.log(`Issuer: ${payload.iss}`);
|
|
2084
|
+
console.log(`Dashboard: ${DASHBOARD_URL}`);
|
|
1753
2085
|
if (isTestAccount) {
|
|
1754
2086
|
console.log("");
|
|
1755
2087
|
console.log("Note: this is a test account, not a real OAuth developer.");
|
|
@@ -1760,12 +2092,12 @@ var whoami_default = defineCommand10({
|
|
|
1760
2092
|
|
|
1761
2093
|
// src/cli/commands/add.ts
|
|
1762
2094
|
import { defineCommand as defineCommand11 } from "citty";
|
|
1763
|
-
import { existsSync as
|
|
1764
|
-
import { resolve as
|
|
2095
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2096
|
+
import { resolve as resolve6, join as join10 } from "path";
|
|
1765
2097
|
import { execSync as execSync3 } from "child_process";
|
|
1766
2098
|
function findScript(from) {
|
|
1767
2099
|
const candidate = join10(from, ".deepspace", "scripts", "add-feature.cjs");
|
|
1768
|
-
if (
|
|
2100
|
+
if (existsSync10(candidate)) return candidate;
|
|
1769
2101
|
return null;
|
|
1770
2102
|
}
|
|
1771
2103
|
var add_default = defineCommand11({
|
|
@@ -1797,7 +2129,7 @@ var add_default = defineCommand11({
|
|
|
1797
2129
|
}
|
|
1798
2130
|
},
|
|
1799
2131
|
run({ args }) {
|
|
1800
|
-
const appDir =
|
|
2132
|
+
const appDir = resolve6(args.dir ?? ".");
|
|
1801
2133
|
const script = findScript(appDir);
|
|
1802
2134
|
if (!script) {
|
|
1803
2135
|
console.error("No .deepspace/scripts/add-feature.cjs found.");
|
|
@@ -1826,12 +2158,12 @@ import { defineCommand as defineCommand12 } from "citty";
|
|
|
1826
2158
|
import { execSync as execSync4 } from "child_process";
|
|
1827
2159
|
|
|
1828
2160
|
// src/cli/lib/app-context.ts
|
|
1829
|
-
import { existsSync as
|
|
1830
|
-
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";
|
|
1831
2163
|
import { parse as parseToml4 } from "smol-toml";
|
|
1832
2164
|
function detectAppName(cwd = process.cwd()) {
|
|
1833
|
-
const wranglerPath = join11(
|
|
1834
|
-
if (!
|
|
2165
|
+
const wranglerPath = join11(resolve7(cwd), "wrangler.toml");
|
|
2166
|
+
if (!existsSync11(wranglerPath)) return null;
|
|
1835
2167
|
try {
|
|
1836
2168
|
const cfg = parseToml4(readFileSync7(wranglerPath, "utf-8"));
|
|
1837
2169
|
return typeof cfg.name === "string" && cfg.name.length > 0 ? cfg.name : null;
|
|
@@ -1849,9 +2181,9 @@ function requireAppName(explicit, cwd) {
|
|
|
1849
2181
|
}
|
|
1850
2182
|
|
|
1851
2183
|
// src/cli/commands/domain.ts
|
|
1852
|
-
var
|
|
2184
|
+
var API_URL2 = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
|
|
1853
2185
|
async function api(token2, path, init) {
|
|
1854
|
-
const res = await fetch(`${
|
|
2186
|
+
const res = await fetch(`${API_URL2}${path}`, {
|
|
1855
2187
|
...init,
|
|
1856
2188
|
headers: {
|
|
1857
2189
|
Authorization: `Bearer ${token2}`,
|
|
@@ -1890,16 +2222,16 @@ function fmtDate(iso) {
|
|
|
1890
2222
|
if (!iso) return "\u2014";
|
|
1891
2223
|
return iso.slice(0, 10);
|
|
1892
2224
|
}
|
|
1893
|
-
async function
|
|
2225
|
+
async function confirm3(message) {
|
|
1894
2226
|
if (!process.stdin.isTTY) {
|
|
1895
2227
|
throw new Error(`${message} \u2014 pass --yes to confirm non-interactively`);
|
|
1896
2228
|
}
|
|
1897
2229
|
process.stdout.write(`${message} [y/N] `);
|
|
1898
|
-
return new Promise((
|
|
2230
|
+
return new Promise((resolve9) => {
|
|
1899
2231
|
process.stdin.setEncoding("utf-8");
|
|
1900
2232
|
process.stdin.once("data", (data) => {
|
|
1901
2233
|
const answer = data.toString().trim().toLowerCase();
|
|
1902
|
-
|
|
2234
|
+
resolve9(answer === "y" || answer === "yes");
|
|
1903
2235
|
});
|
|
1904
2236
|
});
|
|
1905
2237
|
}
|
|
@@ -1958,7 +2290,7 @@ var buy = defineCommand12({
|
|
|
1958
2290
|
const price = fmtCents(result.chargedCents);
|
|
1959
2291
|
const expectedTime = result.registrar === "cloudflare" ? "~60-90 seconds" : "15-60 minutes (registry NS propagation)";
|
|
1960
2292
|
if (!args.yes) {
|
|
1961
|
-
const ok = await
|
|
2293
|
+
const ok = await confirm3(
|
|
1962
2294
|
`Buy ${args.domain} for ${price}/yr (auto-renews; non-refundable), attach to ${appName}? Provisioning takes ${expectedTime}.`
|
|
1963
2295
|
);
|
|
1964
2296
|
if (!ok) {
|
|
@@ -2123,7 +2455,7 @@ var detach = defineCommand12({
|
|
|
2123
2455
|
process.exit(1);
|
|
2124
2456
|
}
|
|
2125
2457
|
if (!args.yes) {
|
|
2126
|
-
const ok = await
|
|
2458
|
+
const ok = await confirm3(
|
|
2127
2459
|
`Detach ${args.domain}? Routing will stop; the domain stays registered (auto-renew is unchanged).`
|
|
2128
2460
|
);
|
|
2129
2461
|
if (!ok) {
|
|
@@ -2179,7 +2511,7 @@ import { defineCommand as defineCommand13 } from "citty";
|
|
|
2179
2511
|
|
|
2180
2512
|
// src/cli/commands/_invoke-impl.ts
|
|
2181
2513
|
import { readFileSync as readFileSync8 } from "fs";
|
|
2182
|
-
var
|
|
2514
|
+
var API_URL3 = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
|
|
2183
2515
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
2184
2516
|
function parseTarget(target) {
|
|
2185
2517
|
if (!target || typeof target !== "string") {
|
|
@@ -2217,7 +2549,7 @@ function flushAndExit(code) {
|
|
|
2217
2549
|
});
|
|
2218
2550
|
}
|
|
2219
2551
|
async function fetchCatalog() {
|
|
2220
|
-
const res = await fetch(`${
|
|
2552
|
+
const res = await fetch(`${API_URL3}/api/integrations`);
|
|
2221
2553
|
if (!res.ok) {
|
|
2222
2554
|
throw new Error(`Failed to fetch integration catalog (${res.status})`);
|
|
2223
2555
|
}
|
|
@@ -2298,7 +2630,7 @@ async function runInvoke(args) {
|
|
|
2298
2630
|
const startedAt = Date.now();
|
|
2299
2631
|
let res;
|
|
2300
2632
|
try {
|
|
2301
|
-
res = await fetch(`${
|
|
2633
|
+
res = await fetch(`${API_URL3}/api/integrations/${integration}/${endpoint}`, {
|
|
2302
2634
|
method: "POST",
|
|
2303
2635
|
headers: {
|
|
2304
2636
|
"Content-Type": "application/json",
|
|
@@ -2537,11 +2869,11 @@ var invoke_default = defineCommand14({
|
|
|
2537
2869
|
|
|
2538
2870
|
// src/cli/commands/managed-repos.ts
|
|
2539
2871
|
import { defineCommand as defineCommand15 } from "citty";
|
|
2540
|
-
var
|
|
2872
|
+
var API_URL4 = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
|
|
2541
2873
|
var SIMPLE_APP_ID = /^[a-z0-9][a-z0-9_.:-]{0,119}$/i;
|
|
2542
2874
|
var SIMPLE_APP_NAME = /^[a-z0-9][a-z0-9_-]{0,119}$/i;
|
|
2543
2875
|
async function api2(token2, path, init) {
|
|
2544
|
-
const res = await fetch(`${
|
|
2876
|
+
const res = await fetch(`${API_URL4}${path}`, {
|
|
2545
2877
|
...init,
|
|
2546
2878
|
headers: {
|
|
2547
2879
|
Authorization: `Bearer ${token2}`,
|
|
@@ -2582,16 +2914,16 @@ function printRepo(repo) {
|
|
|
2582
2914
|
console.log(` URL: ${repo.htmlUrl}`);
|
|
2583
2915
|
console.log(` Clone: ${repo.cloneUrl}`);
|
|
2584
2916
|
}
|
|
2585
|
-
async function
|
|
2917
|
+
async function confirm4(message) {
|
|
2586
2918
|
if (!process.stdin.isTTY) {
|
|
2587
2919
|
throw new Error(`${message} \u2014 pass --yes to confirm non-interactively`);
|
|
2588
2920
|
}
|
|
2589
2921
|
process.stdout.write(`${message} [y/N] `);
|
|
2590
|
-
return new Promise((
|
|
2922
|
+
return new Promise((resolve9) => {
|
|
2591
2923
|
process.stdin.setEncoding("utf-8");
|
|
2592
2924
|
process.stdin.once("data", (data) => {
|
|
2593
2925
|
const answer = data.toString().trim().toLowerCase();
|
|
2594
|
-
|
|
2926
|
+
resolve9(answer === "y" || answer === "yes");
|
|
2595
2927
|
});
|
|
2596
2928
|
});
|
|
2597
2929
|
}
|
|
@@ -2737,7 +3069,7 @@ var remove = defineCommand15({
|
|
|
2737
3069
|
async run({ args }) {
|
|
2738
3070
|
if (!args.yes) {
|
|
2739
3071
|
try {
|
|
2740
|
-
const ok = await
|
|
3072
|
+
const ok = await confirm4(
|
|
2741
3073
|
"Delete this DeepSpace-managed GitHub repo? This removes the GitHub repository and counts against your daily delete quota."
|
|
2742
3074
|
);
|
|
2743
3075
|
if (!ok) {
|
|
@@ -2778,13 +3110,229 @@ var managed_repos_default = defineCommand15({
|
|
|
2778
3110
|
}
|
|
2779
3111
|
});
|
|
2780
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
|
+
|
|
2781
3329
|
// src/cli/cli.ts
|
|
2782
3330
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
2783
|
-
var pkg = JSON.parse(
|
|
2784
|
-
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")) {
|
|
2785
3333
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
2786
3334
|
}
|
|
2787
|
-
var create3 =
|
|
3335
|
+
var create3 = defineCommand17({
|
|
2788
3336
|
meta: {
|
|
2789
3337
|
name: "create",
|
|
2790
3338
|
description: "Create a new DeepSpace app"
|
|
@@ -2803,7 +3351,7 @@ var create3 = defineCommand16({
|
|
|
2803
3351
|
execSync5(`npm create deepspace@latest${name}`, { stdio: "inherit" });
|
|
2804
3352
|
}
|
|
2805
3353
|
});
|
|
2806
|
-
var main =
|
|
3354
|
+
var main = defineCommand17({
|
|
2807
3355
|
meta: {
|
|
2808
3356
|
name: "deepspace",
|
|
2809
3357
|
version: pkg.version,
|
|
@@ -2825,7 +3373,8 @@ var main = defineCommand16({
|
|
|
2825
3373
|
invoke: invoke_default,
|
|
2826
3374
|
deploy: deploy_default,
|
|
2827
3375
|
undeploy: undeploy_default,
|
|
2828
|
-
domain: domain_default
|
|
3376
|
+
domain: domain_default,
|
|
3377
|
+
library: library_default
|
|
2829
3378
|
}
|
|
2830
3379
|
// No `run()` here — citty cascades parent run() AFTER subcommand finishes,
|
|
2831
3380
|
// which corrupts agent-friendly output (`--json`) and prints noise on every
|