deepspace 0.0.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1158 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +7148 -0
- package/dist/index.js +6382 -0
- package/dist/index.js.map +1 -0
- package/dist/worker.d.ts +3173 -0
- package/dist/worker.js +5374 -0
- package/dist/worker.js.map +1 -0
- package/package.json +121 -3
- package/index.js +0 -1
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/cli.ts
|
|
4
|
+
import { defineCommand as defineCommand10, runMain } from "citty";
|
|
5
|
+
import { execSync as execSync4 } from "child_process";
|
|
6
|
+
|
|
7
|
+
// src/cli/commands/login.ts
|
|
8
|
+
import { defineCommand } from "citty";
|
|
9
|
+
import { writeFileSync as writeFileSync2, mkdirSync } from "fs";
|
|
10
|
+
import { join as join2 } from "path";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
import { exec } from "child_process";
|
|
13
|
+
import { platform } from "os";
|
|
14
|
+
import * as p from "@clack/prompts";
|
|
15
|
+
|
|
16
|
+
// src/cli/env.ts
|
|
17
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
18
|
+
import { join } from "path";
|
|
19
|
+
import { parse as parseToml } from "smol-toml";
|
|
20
|
+
var ENVS = {
|
|
21
|
+
dev: {
|
|
22
|
+
auth: "https://deepspace-auth.eudaimonicincorporated.workers.dev",
|
|
23
|
+
api: "https://deepspace-api.eudaimonicincorporated.workers.dev",
|
|
24
|
+
platform: "https://deepspace-platform-worker.eudaimonicincorporated.workers.dev",
|
|
25
|
+
deploy: "https://deepspace-deploy.eudaimonicincorporated.workers.dev"
|
|
26
|
+
},
|
|
27
|
+
prod: {
|
|
28
|
+
auth: "https://deepspace-auth.eudaimonicincorporated.workers.dev",
|
|
29
|
+
api: "https://deepspace-api.eudaimonicincorporated.workers.dev",
|
|
30
|
+
platform: "https://deepspace-platform-worker.eudaimonicincorporated.workers.dev",
|
|
31
|
+
deploy: "https://deepspace-deploy.eudaimonicincorporated.workers.dev"
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
async function fetchPublicKey(authUrl) {
|
|
35
|
+
const res = await fetch(`${authUrl}/api/auth/jwks`);
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
throw new Error(`Failed to fetch JWT public key (${res.status})`);
|
|
38
|
+
}
|
|
39
|
+
const data = await res.json();
|
|
40
|
+
if (!data.publicKey) {
|
|
41
|
+
throw new Error("JWKS response missing publicKey");
|
|
42
|
+
}
|
|
43
|
+
return data.publicKey;
|
|
44
|
+
}
|
|
45
|
+
function readAppName(appDir) {
|
|
46
|
+
const wranglerPath = join(appDir, "wrangler.toml");
|
|
47
|
+
if (!existsSync(wranglerPath)) {
|
|
48
|
+
throw new Error("No wrangler.toml found. Are you in a DeepSpace app directory?");
|
|
49
|
+
}
|
|
50
|
+
const parsed = parseToml(readFileSync(wranglerPath, "utf-8"));
|
|
51
|
+
if (!parsed.name) {
|
|
52
|
+
throw new Error("wrangler.toml has no `name` field");
|
|
53
|
+
}
|
|
54
|
+
return parsed.name;
|
|
55
|
+
}
|
|
56
|
+
async function mintAppOwnerJwt(authUrl, callerJwt, appName) {
|
|
57
|
+
const res = await fetch(`${authUrl}/api/auth/mint-app-token`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: {
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
Authorization: `Bearer ${callerJwt}`
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify({ appName })
|
|
64
|
+
});
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
const err = await res.text().catch(() => "");
|
|
67
|
+
throw new Error(`Failed to mint APP_OWNER_JWT (${res.status}): ${err}`);
|
|
68
|
+
}
|
|
69
|
+
const body = await res.json();
|
|
70
|
+
if (!body.token) {
|
|
71
|
+
throw new Error(`Auth worker returned no token: ${body.error ?? "unknown error"}`);
|
|
72
|
+
}
|
|
73
|
+
return body.token;
|
|
74
|
+
}
|
|
75
|
+
async function writeDevVars(appDir, env, ownerId, callerJwt) {
|
|
76
|
+
const urls = ENVS[env];
|
|
77
|
+
const publicKey = await fetchPublicKey(urls.auth);
|
|
78
|
+
const appName = readAppName(appDir);
|
|
79
|
+
const appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt, appName);
|
|
80
|
+
const devVars = [
|
|
81
|
+
`AUTH_JWT_PUBLIC_KEY="${publicKey}"`,
|
|
82
|
+
`AUTH_JWT_ISSUER=${urls.auth}/api/auth`,
|
|
83
|
+
`AUTH_WORKER_URL=${urls.auth}`,
|
|
84
|
+
`API_WORKER_URL=${urls.api}`,
|
|
85
|
+
`PLATFORM_WORKER_URL=${urls.platform}`,
|
|
86
|
+
`OWNER_USER_ID=${ownerId}`,
|
|
87
|
+
`APP_OWNER_JWT=${appOwnerJwt}`,
|
|
88
|
+
`INTERNAL_STORAGE_HMAC_SECRET=dev-${Date.now()}`,
|
|
89
|
+
// Open /api/debug/* (proxied to RecordRoom) for dev + tests only.
|
|
90
|
+
// Production secrets never set this, so deployed apps 404 the route.
|
|
91
|
+
`ALLOW_DEBUG_ROUTES=true`
|
|
92
|
+
].join("\n");
|
|
93
|
+
writeFileSync(join(appDir, ".dev.vars"), devVars + "\n");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/cli/session.ts
|
|
97
|
+
var SESSION_COOKIE = "__Secure-better-auth.session_token";
|
|
98
|
+
async function exchangeSession(authUrl, sessionToken) {
|
|
99
|
+
const res = await fetch(`${authUrl}/api/auth/token`, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: {
|
|
102
|
+
Cookie: `${SESSION_COOKIE}=${encodeURIComponent(sessionToken)}`,
|
|
103
|
+
Origin: authUrl
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
if (!res.ok) return null;
|
|
107
|
+
const data = await res.json();
|
|
108
|
+
return data.token ?? null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/cli/commands/login.ts
|
|
112
|
+
var AUTH_URL = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
|
|
113
|
+
var login_default = defineCommand({
|
|
114
|
+
meta: {
|
|
115
|
+
name: "login",
|
|
116
|
+
description: "Log in to your DeepSpace account"
|
|
117
|
+
},
|
|
118
|
+
args: {
|
|
119
|
+
email: {
|
|
120
|
+
type: "string",
|
|
121
|
+
description: "Email address (non-interactive mode)",
|
|
122
|
+
required: false
|
|
123
|
+
},
|
|
124
|
+
password: {
|
|
125
|
+
type: "string",
|
|
126
|
+
description: "Password (non-interactive mode)",
|
|
127
|
+
required: false
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
async run({ args }) {
|
|
131
|
+
if (args.email && args.password) {
|
|
132
|
+
console.log(`Signing in as ${args.email}...`);
|
|
133
|
+
await doEmailLogin(args.email, args.password);
|
|
134
|
+
console.log("Logged in");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
p.intro("DeepSpace Login");
|
|
138
|
+
const s = p.spinner();
|
|
139
|
+
s.start("Creating login session...");
|
|
140
|
+
const sessionRes = await fetch(`${AUTH_URL}/api/auth/cli/session`, { method: "POST" });
|
|
141
|
+
if (!sessionRes.ok) {
|
|
142
|
+
s.stop("Failed");
|
|
143
|
+
p.cancel("Could not create login session");
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
const sessionData = await sessionRes.json();
|
|
147
|
+
if (!sessionData.sessionId || !sessionData.loginUrl) {
|
|
148
|
+
s.stop("Failed");
|
|
149
|
+
p.cancel("Could not create login session");
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
const { sessionId, loginUrl } = sessionData;
|
|
153
|
+
s.stop("Opening browser...");
|
|
154
|
+
p.log.info(`If the browser doesn't open, visit:
|
|
155
|
+
${loginUrl}`);
|
|
156
|
+
openBrowser(loginUrl);
|
|
157
|
+
s.start("Waiting for authentication...");
|
|
158
|
+
const result = await pollForCompletion(sessionId);
|
|
159
|
+
if (!result) {
|
|
160
|
+
s.stop("Timed out");
|
|
161
|
+
p.cancel("Login timed out. Run `deepspace login` to try again.");
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
storeCredentials(result.sessionToken, result.jwt);
|
|
165
|
+
s.stop("Authenticated");
|
|
166
|
+
p.outro(`Logged in as ${result.name ?? result.email}`);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
async function pollForCompletion(sessionId) {
|
|
170
|
+
const maxAttempts = 120;
|
|
171
|
+
const interval = 5e3;
|
|
172
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
173
|
+
await sleep(interval);
|
|
174
|
+
try {
|
|
175
|
+
const res = await fetch(`${AUTH_URL}/api/auth/cli/status/${sessionId}`);
|
|
176
|
+
if (res.status === 410 || res.status === 404) return null;
|
|
177
|
+
if (!res.ok) continue;
|
|
178
|
+
const data = await res.json();
|
|
179
|
+
if (data.state === "complete" && data.sessionToken && data.jwt) {
|
|
180
|
+
return {
|
|
181
|
+
sessionToken: data.sessionToken,
|
|
182
|
+
jwt: data.jwt,
|
|
183
|
+
email: data.email ?? "",
|
|
184
|
+
name: data.name ?? null
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
function storeCredentials(sessionToken, jwt) {
|
|
193
|
+
const dir = join2(homedir(), ".deepspace");
|
|
194
|
+
mkdirSync(dir, { recursive: true });
|
|
195
|
+
writeFileSync2(join2(dir, "session"), sessionToken, { mode: 384 });
|
|
196
|
+
writeFileSync2(join2(dir, "token"), jwt, { mode: 384 });
|
|
197
|
+
}
|
|
198
|
+
function openBrowser(url) {
|
|
199
|
+
const cmd = platform() === "darwin" ? `open "${url}"` : platform() === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
200
|
+
exec(cmd, () => {
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function sleep(ms) {
|
|
204
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
205
|
+
}
|
|
206
|
+
async function doEmailLogin(email, password) {
|
|
207
|
+
const res = await fetch(`${AUTH_URL}/api/auth/sign-in/email`, {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: { "Content-Type": "application/json", Origin: AUTH_URL },
|
|
210
|
+
body: JSON.stringify({ email, password }),
|
|
211
|
+
redirect: "manual"
|
|
212
|
+
});
|
|
213
|
+
if (!res.ok) {
|
|
214
|
+
const body = await res.json().catch(() => ({}));
|
|
215
|
+
throw new Error(body.message ?? `Authentication failed (${res.status})`);
|
|
216
|
+
}
|
|
217
|
+
const setCookie = res.headers.get("set-cookie") ?? "";
|
|
218
|
+
const cookieMatch = setCookie.match(new RegExp(`${SESSION_COOKIE}=([^;]+)`));
|
|
219
|
+
if (!cookieMatch) {
|
|
220
|
+
throw new Error("No session cookie returned");
|
|
221
|
+
}
|
|
222
|
+
const sessionToken = decodeURIComponent(cookieMatch[1]);
|
|
223
|
+
const jwt = await exchangeSession(AUTH_URL, sessionToken);
|
|
224
|
+
if (!jwt) {
|
|
225
|
+
throw new Error("JWT issuance failed");
|
|
226
|
+
}
|
|
227
|
+
storeCredentials(sessionToken, jwt);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/cli/commands/logout.ts
|
|
231
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
232
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync } from "fs";
|
|
233
|
+
import { join as join3 } from "path";
|
|
234
|
+
import { homedir as homedir2 } from "os";
|
|
235
|
+
var AUTH_URL2 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
|
|
236
|
+
var DIR = join3(homedir2(), ".deepspace");
|
|
237
|
+
var SESSION_PATH = join3(DIR, "session");
|
|
238
|
+
var TOKEN_PATH = join3(DIR, "token");
|
|
239
|
+
var logout_default = defineCommand2({
|
|
240
|
+
meta: {
|
|
241
|
+
name: "logout",
|
|
242
|
+
description: "Sign out and remove cached credentials"
|
|
243
|
+
},
|
|
244
|
+
async run() {
|
|
245
|
+
const sessionToken = existsSync2(SESSION_PATH) ? readFileSync2(SESSION_PATH, "utf-8").trim() : null;
|
|
246
|
+
if (sessionToken) {
|
|
247
|
+
try {
|
|
248
|
+
await fetch(`${AUTH_URL2}/api/auth/sign-out`, {
|
|
249
|
+
method: "POST",
|
|
250
|
+
headers: {
|
|
251
|
+
Cookie: `${SESSION_COOKIE}=${encodeURIComponent(sessionToken)}`,
|
|
252
|
+
Origin: AUTH_URL2
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
let removed = 0;
|
|
259
|
+
for (const path of [SESSION_PATH, TOKEN_PATH]) {
|
|
260
|
+
if (existsSync2(path)) {
|
|
261
|
+
rmSync(path);
|
|
262
|
+
removed++;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (removed === 0) {
|
|
266
|
+
console.log("Already logged out.");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
console.log("Logged out.");
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// src/cli/commands/dev.ts
|
|
274
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
275
|
+
import { existsSync as existsSync4 } from "fs";
|
|
276
|
+
import { join as join5, resolve } from "path";
|
|
277
|
+
import { spawn } from "child_process";
|
|
278
|
+
|
|
279
|
+
// src/cli/auth.ts
|
|
280
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
281
|
+
import { join as join4 } from "path";
|
|
282
|
+
import { homedir as homedir3 } from "os";
|
|
283
|
+
var AUTH_URL3 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
|
|
284
|
+
var DIR2 = join4(homedir3(), ".deepspace");
|
|
285
|
+
var SESSION_PATH2 = join4(DIR2, "session");
|
|
286
|
+
var TOKEN_PATH2 = join4(DIR2, "token");
|
|
287
|
+
async function ensureToken() {
|
|
288
|
+
if (!existsSync3(SESSION_PATH2)) {
|
|
289
|
+
throw new Error("Not logged in. Run `deepspace login` first.");
|
|
290
|
+
}
|
|
291
|
+
const sessionToken = readFileSync3(SESSION_PATH2, "utf-8").trim();
|
|
292
|
+
if (existsSync3(TOKEN_PATH2)) {
|
|
293
|
+
const existing = readFileSync3(TOKEN_PATH2, "utf-8").trim();
|
|
294
|
+
if (isTokenValid(existing)) {
|
|
295
|
+
return existing;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const token = await exchangeSession(AUTH_URL3, sessionToken);
|
|
299
|
+
if (!token) {
|
|
300
|
+
throw new Error("Session expired. Run `deepspace login` to re-authenticate.");
|
|
301
|
+
}
|
|
302
|
+
mkdirSync2(DIR2, { recursive: true });
|
|
303
|
+
writeFileSync3(TOKEN_PATH2, token, { mode: 384 });
|
|
304
|
+
return token;
|
|
305
|
+
}
|
|
306
|
+
function isTokenValid(jwt) {
|
|
307
|
+
try {
|
|
308
|
+
const payload = JSON.parse(atob(jwt.split(".")[1]));
|
|
309
|
+
return payload.exp * 1e3 > Date.now() + 3e4;
|
|
310
|
+
} catch {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/cli/commands/dev.ts
|
|
316
|
+
var DEFAULT_PORT = 5173;
|
|
317
|
+
var dev_default = defineCommand3({
|
|
318
|
+
meta: {
|
|
319
|
+
name: "dev",
|
|
320
|
+
description: "Start local development server"
|
|
321
|
+
},
|
|
322
|
+
args: {
|
|
323
|
+
dir: {
|
|
324
|
+
type: "positional",
|
|
325
|
+
description: "App directory (default: current directory)",
|
|
326
|
+
required: false
|
|
327
|
+
},
|
|
328
|
+
prod: {
|
|
329
|
+
type: "boolean",
|
|
330
|
+
description: "Use production workers (real auth, real API calls)",
|
|
331
|
+
default: false
|
|
332
|
+
},
|
|
333
|
+
port: {
|
|
334
|
+
type: "string",
|
|
335
|
+
description: `Port to bind (default ${DEFAULT_PORT}, or $DEEPSPACE_PORT)`,
|
|
336
|
+
required: false
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
async run({ args }) {
|
|
340
|
+
const appDir = resolve(args.dir ?? ".");
|
|
341
|
+
const env = args.prod ? "prod" : "dev";
|
|
342
|
+
const port = resolvePort(args.port);
|
|
343
|
+
if (!existsSync4(join5(appDir, "wrangler.toml"))) {
|
|
344
|
+
console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
let token;
|
|
348
|
+
try {
|
|
349
|
+
token = await ensureToken();
|
|
350
|
+
} catch (err) {
|
|
351
|
+
console.error(err.message);
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
let payload;
|
|
355
|
+
try {
|
|
356
|
+
payload = JSON.parse(atob(token.split(".")[1]));
|
|
357
|
+
} catch {
|
|
358
|
+
console.error("Malformed session token. Run `npx deepspace login`.");
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
console.log(`Logged in as ${payload.name ?? payload.email}`);
|
|
362
|
+
console.log(`Environment: ${env}`);
|
|
363
|
+
console.log(`Port: ${port}`);
|
|
364
|
+
await writeDevVars(appDir, env, payload.sub, token);
|
|
365
|
+
console.log("Starting dev server...\n");
|
|
366
|
+
const vite = spawn(
|
|
367
|
+
"npx",
|
|
368
|
+
["vite", "--port", String(port), "--strictPort"],
|
|
369
|
+
{
|
|
370
|
+
cwd: appDir,
|
|
371
|
+
stdio: "inherit",
|
|
372
|
+
shell: process.platform === "win32",
|
|
373
|
+
env: { ...process.env, DEEPSPACE_PORT: String(port) }
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
process.on("SIGINT", () => vite.kill());
|
|
377
|
+
process.on("SIGTERM", () => vite.kill());
|
|
378
|
+
vite.on("close", (code) => process.exit(code ?? 0));
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
function resolvePort(arg) {
|
|
382
|
+
const raw = arg ?? process.env.DEEPSPACE_PORT;
|
|
383
|
+
if (!raw) return DEFAULT_PORT;
|
|
384
|
+
const n = Number(raw);
|
|
385
|
+
if (!Number.isInteger(n) || n < 1 || n > 65535) {
|
|
386
|
+
console.error(`Invalid port: ${raw}. Must be an integer between 1 and 65535.`);
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
return n;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/cli/commands/test.ts
|
|
393
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
394
|
+
import { existsSync as existsSync5 } from "fs";
|
|
395
|
+
import { join as join6, resolve as resolve2 } from "path";
|
|
396
|
+
import { execSync, spawnSync } from "child_process";
|
|
397
|
+
var DEFAULT_PORT2 = 5173;
|
|
398
|
+
var test_default = defineCommand4({
|
|
399
|
+
meta: {
|
|
400
|
+
name: "test",
|
|
401
|
+
description: "Run tests for your DeepSpace app"
|
|
402
|
+
},
|
|
403
|
+
args: {
|
|
404
|
+
suite: {
|
|
405
|
+
type: "positional",
|
|
406
|
+
description: "Test suite: smoke, api, e2e, unit, all (default: smoke+api)",
|
|
407
|
+
required: false
|
|
408
|
+
},
|
|
409
|
+
port: {
|
|
410
|
+
type: "string",
|
|
411
|
+
description: `Port for vite/webServer (default ${DEFAULT_PORT2}, or $DEEPSPACE_PORT)`,
|
|
412
|
+
required: false
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
async run({ args }) {
|
|
416
|
+
const appDir = resolve2(".");
|
|
417
|
+
const suite = args.suite ?? "default";
|
|
418
|
+
const port = resolvePort2(args.port);
|
|
419
|
+
if (!existsSync5(join6(appDir, "wrangler.toml"))) {
|
|
420
|
+
console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
let token;
|
|
424
|
+
let ownerId;
|
|
425
|
+
try {
|
|
426
|
+
token = await ensureToken();
|
|
427
|
+
const payload = JSON.parse(atob(token.split(".")[1]));
|
|
428
|
+
ownerId = payload.sub;
|
|
429
|
+
} catch (err) {
|
|
430
|
+
console.error("`deepspace test` requires you to be logged in. Run `deepspace login` first.");
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
await writeDevVars(appDir, "dev", ownerId, token);
|
|
434
|
+
if (suite !== "unit") {
|
|
435
|
+
ensurePlaywright(appDir);
|
|
436
|
+
}
|
|
437
|
+
let exitCode = 0;
|
|
438
|
+
switch (suite) {
|
|
439
|
+
case "smoke":
|
|
440
|
+
exitCode = runPlaywright(appDir, ["tests/smoke.spec.ts"], port);
|
|
441
|
+
break;
|
|
442
|
+
case "api":
|
|
443
|
+
exitCode = runPlaywright(appDir, ["tests/api.spec.ts"], port);
|
|
444
|
+
break;
|
|
445
|
+
case "e2e":
|
|
446
|
+
exitCode = runPlaywright(appDir, [], port);
|
|
447
|
+
break;
|
|
448
|
+
case "unit":
|
|
449
|
+
exitCode = runVitest(appDir);
|
|
450
|
+
break;
|
|
451
|
+
case "all":
|
|
452
|
+
exitCode = runVitest(appDir);
|
|
453
|
+
if (exitCode === 0) exitCode = runPlaywright(appDir, [], port);
|
|
454
|
+
break;
|
|
455
|
+
case "default":
|
|
456
|
+
exitCode = runPlaywright(appDir, ["tests/smoke.spec.ts", "tests/api.spec.ts"], port);
|
|
457
|
+
break;
|
|
458
|
+
default:
|
|
459
|
+
if (suite.endsWith(".spec.ts")) {
|
|
460
|
+
exitCode = runPlaywright(appDir, [suite], port);
|
|
461
|
+
} else {
|
|
462
|
+
console.error(`Unknown test suite: ${suite}`);
|
|
463
|
+
console.error("Available: smoke, api, e2e, unit, all");
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
process.exit(exitCode);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
function runPlaywright(appDir, testFiles, port) {
|
|
471
|
+
const result = spawnSync("npx", [
|
|
472
|
+
"playwright",
|
|
473
|
+
"test",
|
|
474
|
+
"--config",
|
|
475
|
+
"tests/playwright.config.ts",
|
|
476
|
+
...testFiles
|
|
477
|
+
], {
|
|
478
|
+
cwd: appDir,
|
|
479
|
+
stdio: "inherit",
|
|
480
|
+
shell: process.platform === "win32",
|
|
481
|
+
env: { ...process.env, DEEPSPACE_PORT: String(port) }
|
|
482
|
+
});
|
|
483
|
+
return result.status ?? 1;
|
|
484
|
+
}
|
|
485
|
+
function runVitest(appDir) {
|
|
486
|
+
const result = spawnSync("npx", ["vitest", "run", "--passWithNoTests"], {
|
|
487
|
+
cwd: appDir,
|
|
488
|
+
stdio: "inherit",
|
|
489
|
+
shell: process.platform === "win32"
|
|
490
|
+
});
|
|
491
|
+
return result.status ?? 1;
|
|
492
|
+
}
|
|
493
|
+
function ensurePlaywright(appDir) {
|
|
494
|
+
try {
|
|
495
|
+
execSync("npx playwright --version", { cwd: appDir, stdio: "pipe" });
|
|
496
|
+
} catch {
|
|
497
|
+
console.log("Installing Playwright...");
|
|
498
|
+
execSync("npm install -D @playwright/test", { cwd: appDir, stdio: "pipe" });
|
|
499
|
+
}
|
|
500
|
+
try {
|
|
501
|
+
execSync("npx playwright install --dry-run chromium", { cwd: appDir, stdio: "pipe" });
|
|
502
|
+
} catch {
|
|
503
|
+
console.log("Installing Chromium for Playwright...");
|
|
504
|
+
execSync("npx playwright install chromium", { cwd: appDir, stdio: "inherit" });
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function resolvePort2(arg) {
|
|
508
|
+
const raw = arg ?? process.env.DEEPSPACE_PORT;
|
|
509
|
+
if (!raw) return DEFAULT_PORT2;
|
|
510
|
+
const n = Number(raw);
|
|
511
|
+
if (!Number.isInteger(n) || n < 1 || n > 65535) {
|
|
512
|
+
console.error(`Invalid port: ${raw}. Must be an integer between 1 and 65535.`);
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
return n;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/cli/commands/test-accounts.ts
|
|
519
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
520
|
+
import * as p2 from "@clack/prompts";
|
|
521
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
522
|
+
import { join as join7 } from "path";
|
|
523
|
+
import { homedir as homedir4 } from "os";
|
|
524
|
+
var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
|
|
525
|
+
var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
|
|
526
|
+
var DIR3 = join7(homedir4(), ".deepspace");
|
|
527
|
+
var ACCOUNTS_PATH = join7(DIR3, "test-accounts.json");
|
|
528
|
+
function loadAccounts() {
|
|
529
|
+
if (!existsSync6(ACCOUNTS_PATH)) return [];
|
|
530
|
+
try {
|
|
531
|
+
return JSON.parse(readFileSync4(ACCOUNTS_PATH, "utf-8"));
|
|
532
|
+
} catch {
|
|
533
|
+
return [];
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function saveAccounts(accounts) {
|
|
537
|
+
mkdirSync3(DIR3, { recursive: true });
|
|
538
|
+
writeFileSync4(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), { mode: 384 });
|
|
539
|
+
}
|
|
540
|
+
function sessionCookie() {
|
|
541
|
+
const token = readFileSync4(SESSION_PATH2, "utf-8").trim();
|
|
542
|
+
return `${SESSION_COOKIE2}=${encodeURIComponent(token)}`;
|
|
543
|
+
}
|
|
544
|
+
async function fetchRemoteAccounts() {
|
|
545
|
+
const res = await fetch(`${AUTH_URL4}/api/auth/test-accounts`, {
|
|
546
|
+
headers: { Cookie: sessionCookie(), Origin: AUTH_URL4 }
|
|
547
|
+
});
|
|
548
|
+
const data = await res.json().catch(() => ({}));
|
|
549
|
+
if (!res.ok || !data.accounts) {
|
|
550
|
+
throw new Error(data.error ?? "Failed to list test accounts");
|
|
551
|
+
}
|
|
552
|
+
return data.accounts;
|
|
553
|
+
}
|
|
554
|
+
async function deleteRemote(id) {
|
|
555
|
+
const res = await fetch(`${AUTH_URL4}/api/auth/test-accounts/${id}`, {
|
|
556
|
+
method: "DELETE",
|
|
557
|
+
headers: { Cookie: sessionCookie(), Origin: AUTH_URL4 }
|
|
558
|
+
});
|
|
559
|
+
if (!res.ok) {
|
|
560
|
+
const data = await res.json().catch(() => ({}));
|
|
561
|
+
throw new Error(data.error ?? `DELETE returned ${res.status}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
var create = defineCommand5({
|
|
565
|
+
meta: {
|
|
566
|
+
name: "create",
|
|
567
|
+
description: "Create a test account"
|
|
568
|
+
},
|
|
569
|
+
args: {
|
|
570
|
+
email: {
|
|
571
|
+
type: "string",
|
|
572
|
+
description: "Email (must end with @deepspace.test)",
|
|
573
|
+
required: true
|
|
574
|
+
},
|
|
575
|
+
password: {
|
|
576
|
+
type: "string",
|
|
577
|
+
description: "Password (min 8 characters)",
|
|
578
|
+
required: true
|
|
579
|
+
},
|
|
580
|
+
name: {
|
|
581
|
+
type: "string",
|
|
582
|
+
description: "Display name",
|
|
583
|
+
required: false
|
|
584
|
+
},
|
|
585
|
+
label: {
|
|
586
|
+
type: "string",
|
|
587
|
+
description: "Label for this test account",
|
|
588
|
+
required: false
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
async run({ args }) {
|
|
592
|
+
await ensureToken();
|
|
593
|
+
const res = await fetch(`${AUTH_URL4}/api/auth/test-accounts`, {
|
|
594
|
+
method: "POST",
|
|
595
|
+
headers: {
|
|
596
|
+
"Content-Type": "application/json",
|
|
597
|
+
Cookie: sessionCookie(),
|
|
598
|
+
Origin: AUTH_URL4
|
|
599
|
+
},
|
|
600
|
+
body: JSON.stringify({
|
|
601
|
+
email: args.email,
|
|
602
|
+
password: args.password,
|
|
603
|
+
name: args.name,
|
|
604
|
+
label: args.label
|
|
605
|
+
})
|
|
606
|
+
});
|
|
607
|
+
const data = await res.json().catch(() => ({}));
|
|
608
|
+
if (!res.ok || !data.id) {
|
|
609
|
+
console.error(`Failed: ${data.error ?? "Unknown error"}`);
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
const account = data;
|
|
613
|
+
const accounts = loadAccounts();
|
|
614
|
+
accounts.push({
|
|
615
|
+
id: account.id,
|
|
616
|
+
email: args.email,
|
|
617
|
+
password: args.password,
|
|
618
|
+
userId: account.userId,
|
|
619
|
+
name: args.name,
|
|
620
|
+
label: account.label,
|
|
621
|
+
createdAt: account.createdAt
|
|
622
|
+
});
|
|
623
|
+
saveAccounts(accounts);
|
|
624
|
+
console.log(`Created test account:`);
|
|
625
|
+
console.log(` ID: ${account.id}`);
|
|
626
|
+
console.log(` Email: ${account.email}`);
|
|
627
|
+
console.log(` Password: ${args.password}`);
|
|
628
|
+
console.log(` UserID: ${account.userId}`);
|
|
629
|
+
if (account.label) console.log(` Label: ${account.label}`);
|
|
630
|
+
console.log(`
|
|
631
|
+
Saved to ${ACCOUNTS_PATH}`);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
var list = defineCommand5({
|
|
635
|
+
meta: {
|
|
636
|
+
name: "list",
|
|
637
|
+
description: "List your test accounts"
|
|
638
|
+
},
|
|
639
|
+
async run() {
|
|
640
|
+
await ensureToken();
|
|
641
|
+
let remote;
|
|
642
|
+
try {
|
|
643
|
+
remote = await fetchRemoteAccounts();
|
|
644
|
+
} catch (err) {
|
|
645
|
+
console.error(`Failed: ${err.message}`);
|
|
646
|
+
process.exit(1);
|
|
647
|
+
}
|
|
648
|
+
if (remote.length === 0) {
|
|
649
|
+
console.log("No test accounts. Create one with: deepspace test-accounts create --email <email> --password <password>");
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
const local = loadAccounts();
|
|
653
|
+
const localByEmail = new Map(local.map((a) => [a.email, a]));
|
|
654
|
+
console.log(`Test accounts (${remote.length}/10):
|
|
655
|
+
`);
|
|
656
|
+
for (const a of remote) {
|
|
657
|
+
const stored = localByEmail.get(a.email);
|
|
658
|
+
const date = new Date(a.createdAt).toLocaleDateString();
|
|
659
|
+
console.log(` ${a.email}${a.label ? ` (${a.label})` : ""}`);
|
|
660
|
+
console.log(` ID: ${a.id} UserID: ${a.userId} Created: ${date}`);
|
|
661
|
+
if (stored?.password) {
|
|
662
|
+
console.log(` Password: ${stored.password}`);
|
|
663
|
+
} else {
|
|
664
|
+
console.log(` Password: (not saved locally)`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
var del = defineCommand5({
|
|
670
|
+
meta: {
|
|
671
|
+
name: "delete",
|
|
672
|
+
description: "Delete a test account by --email or --id"
|
|
673
|
+
},
|
|
674
|
+
args: {
|
|
675
|
+
email: {
|
|
676
|
+
type: "string",
|
|
677
|
+
description: "Email of the test account to delete",
|
|
678
|
+
required: false
|
|
679
|
+
},
|
|
680
|
+
id: {
|
|
681
|
+
type: "string",
|
|
682
|
+
description: "ID of the test account to delete",
|
|
683
|
+
required: false
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
async run({ args }) {
|
|
687
|
+
if (!args.email && !args.id) {
|
|
688
|
+
console.error("Provide --email <email> or --id <id>.");
|
|
689
|
+
process.exit(1);
|
|
690
|
+
}
|
|
691
|
+
if (args.email && args.id) {
|
|
692
|
+
console.error("Provide either --email or --id, not both.");
|
|
693
|
+
process.exit(1);
|
|
694
|
+
}
|
|
695
|
+
await ensureToken();
|
|
696
|
+
let targetId = args.id;
|
|
697
|
+
let targetEmail = args.email;
|
|
698
|
+
if (targetEmail && !targetId) {
|
|
699
|
+
let remote;
|
|
700
|
+
try {
|
|
701
|
+
remote = await fetchRemoteAccounts();
|
|
702
|
+
} catch (err) {
|
|
703
|
+
console.error(`Failed: ${err.message}`);
|
|
704
|
+
process.exit(1);
|
|
705
|
+
}
|
|
706
|
+
const match = remote.find((a) => a.email === targetEmail);
|
|
707
|
+
if (!match) {
|
|
708
|
+
console.error(`No test account with email ${targetEmail}.`);
|
|
709
|
+
process.exit(1);
|
|
710
|
+
}
|
|
711
|
+
targetId = match.id;
|
|
712
|
+
}
|
|
713
|
+
try {
|
|
714
|
+
await deleteRemote(targetId);
|
|
715
|
+
} catch (err) {
|
|
716
|
+
console.error(`Failed: ${err.message}`);
|
|
717
|
+
process.exit(1);
|
|
718
|
+
}
|
|
719
|
+
const accounts = loadAccounts().filter(
|
|
720
|
+
(a) => a.id !== targetId && (!targetEmail || a.email !== targetEmail)
|
|
721
|
+
);
|
|
722
|
+
saveAccounts(accounts);
|
|
723
|
+
console.log(`Test account deleted${targetEmail ? `: ${targetEmail}` : `: ${targetId}`}`);
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
var clear = defineCommand5({
|
|
727
|
+
meta: {
|
|
728
|
+
name: "clear",
|
|
729
|
+
description: "Delete all your test accounts (or those matching --label)"
|
|
730
|
+
},
|
|
731
|
+
args: {
|
|
732
|
+
label: {
|
|
733
|
+
type: "string",
|
|
734
|
+
description: "Only delete accounts with this label (e.g. e2e, slack-clone)",
|
|
735
|
+
required: false
|
|
736
|
+
},
|
|
737
|
+
yes: {
|
|
738
|
+
type: "boolean",
|
|
739
|
+
description: "Skip confirmation prompt (for CI scripts)",
|
|
740
|
+
default: false
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
async run({ args }) {
|
|
744
|
+
await ensureToken();
|
|
745
|
+
let remote;
|
|
746
|
+
try {
|
|
747
|
+
remote = await fetchRemoteAccounts();
|
|
748
|
+
} catch (err) {
|
|
749
|
+
console.error(`Failed: ${err.message}`);
|
|
750
|
+
process.exit(1);
|
|
751
|
+
}
|
|
752
|
+
const targets = args.label ? remote.filter((a) => a.label === args.label) : remote;
|
|
753
|
+
if (targets.length === 0) {
|
|
754
|
+
const suffix = args.label ? ` with label '${args.label}'` : "";
|
|
755
|
+
console.log(`No test accounts${suffix} to delete.`);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
if (!args.yes) {
|
|
759
|
+
const subject = args.label ? `${targets.length} test account(s) labeled '${args.label}'` : `all ${targets.length} test account(s)`;
|
|
760
|
+
const confirmed = await p2.confirm({
|
|
761
|
+
message: `Delete ${subject}? This is not reversible.`,
|
|
762
|
+
initialValue: false
|
|
763
|
+
});
|
|
764
|
+
if (p2.isCancel(confirmed) || !confirmed) {
|
|
765
|
+
console.log("Cancelled.");
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
let ok = 0;
|
|
770
|
+
const failures = [];
|
|
771
|
+
for (const a of targets) {
|
|
772
|
+
try {
|
|
773
|
+
await deleteRemote(a.id);
|
|
774
|
+
ok++;
|
|
775
|
+
} catch (err) {
|
|
776
|
+
failures.push({ email: a.email, error: err.message });
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const deletedIds = new Set(
|
|
780
|
+
targets.filter((t) => !failures.find((f) => f.email === t.email)).map((t) => t.id)
|
|
781
|
+
);
|
|
782
|
+
saveAccounts(loadAccounts().filter((a) => !deletedIds.has(a.id)));
|
|
783
|
+
console.log(`Deleted ${ok}/${targets.length} test account(s).`);
|
|
784
|
+
if (failures.length > 0) {
|
|
785
|
+
console.error(`
|
|
786
|
+
Failed:`);
|
|
787
|
+
for (const f of failures) console.error(` ${f.email}: ${f.error}`);
|
|
788
|
+
process.exit(1);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
var test_accounts_default = defineCommand5({
|
|
793
|
+
meta: {
|
|
794
|
+
name: "test-accounts",
|
|
795
|
+
description: "Manage test accounts for development"
|
|
796
|
+
},
|
|
797
|
+
subCommands: {
|
|
798
|
+
create,
|
|
799
|
+
list,
|
|
800
|
+
delete: del,
|
|
801
|
+
clear
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// src/cli/commands/deploy.ts
|
|
806
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
807
|
+
import { execSync as execSync2 } from "child_process";
|
|
808
|
+
import { readFileSync as readFileSync5, readdirSync, existsSync as existsSync7 } from "fs";
|
|
809
|
+
import { parse as parseToml2 } from "smol-toml";
|
|
810
|
+
import { join as join8, resolve as resolve3, dirname } from "path";
|
|
811
|
+
import * as p3 from "@clack/prompts";
|
|
812
|
+
var DEPLOY_URL = process.env.DEEPSPACE_DEPLOY_URL ?? ENVS.prod.deploy;
|
|
813
|
+
var deploy_default = defineCommand6({
|
|
814
|
+
meta: {
|
|
815
|
+
name: "deploy",
|
|
816
|
+
description: "Build and deploy your DeepSpace app"
|
|
817
|
+
},
|
|
818
|
+
args: {
|
|
819
|
+
dir: {
|
|
820
|
+
type: "positional",
|
|
821
|
+
description: "App directory (default: current directory)",
|
|
822
|
+
required: false
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
async run({ args }) {
|
|
826
|
+
const appDir = resolve3(args.dir ?? ".");
|
|
827
|
+
p3.intro("Deploying DeepSpace app");
|
|
828
|
+
const wranglerPath = join8(appDir, "wrangler.toml");
|
|
829
|
+
if (!existsSync7(wranglerPath)) {
|
|
830
|
+
p3.cancel("No wrangler.toml found. Are you in a DeepSpace app directory?");
|
|
831
|
+
process.exit(1);
|
|
832
|
+
}
|
|
833
|
+
const wranglerConfig = parseToml2(readFileSync5(wranglerPath, "utf-8"));
|
|
834
|
+
const appName = wranglerConfig.name;
|
|
835
|
+
if (!appName) {
|
|
836
|
+
p3.cancel("Could not find app name in wrangler.toml");
|
|
837
|
+
process.exit(1);
|
|
838
|
+
}
|
|
839
|
+
p3.log.info(`App: ${appName}`);
|
|
840
|
+
let token;
|
|
841
|
+
try {
|
|
842
|
+
token = await ensureToken();
|
|
843
|
+
} catch (err) {
|
|
844
|
+
p3.cancel(err.message);
|
|
845
|
+
process.exit(1);
|
|
846
|
+
}
|
|
847
|
+
const s = p3.spinner();
|
|
848
|
+
s.start("Building...");
|
|
849
|
+
try {
|
|
850
|
+
execSync2("npx vite build", { cwd: appDir, stdio: "pipe" });
|
|
851
|
+
} catch (err) {
|
|
852
|
+
s.stop("Build failed");
|
|
853
|
+
console.error(err.stderr?.toString() ?? err.message);
|
|
854
|
+
process.exit(1);
|
|
855
|
+
}
|
|
856
|
+
s.stop("Built");
|
|
857
|
+
const deployConfigPath = join8(appDir, ".wrangler", "deploy", "config.json");
|
|
858
|
+
if (!existsSync7(deployConfigPath)) {
|
|
859
|
+
p3.cancel("Build output config not found at .wrangler/deploy/config.json");
|
|
860
|
+
process.exit(1);
|
|
861
|
+
}
|
|
862
|
+
const deployConfig = JSON.parse(readFileSync5(deployConfigPath, "utf-8"));
|
|
863
|
+
const outputWranglerPath = resolve3(dirname(deployConfigPath), deployConfig.configPath);
|
|
864
|
+
if (!existsSync7(outputWranglerPath)) {
|
|
865
|
+
p3.cancel(`Output wrangler.json not found at ${outputWranglerPath}`);
|
|
866
|
+
process.exit(1);
|
|
867
|
+
}
|
|
868
|
+
const outputConfig = JSON.parse(readFileSync5(outputWranglerPath, "utf-8"));
|
|
869
|
+
const workerDir = dirname(outputWranglerPath);
|
|
870
|
+
const workerBundlePath = join8(workerDir, outputConfig.main);
|
|
871
|
+
const clientDir = outputConfig.assets?.directory ? resolve3(workerDir, outputConfig.assets.directory) : null;
|
|
872
|
+
if (!existsSync7(workerBundlePath)) {
|
|
873
|
+
p3.cancel(`Worker bundle not found at ${workerBundlePath}`);
|
|
874
|
+
process.exit(1);
|
|
875
|
+
}
|
|
876
|
+
if (!clientDir || !existsSync7(clientDir)) {
|
|
877
|
+
p3.cancel(`Client assets not found at ${clientDir}`);
|
|
878
|
+
process.exit(1);
|
|
879
|
+
}
|
|
880
|
+
s.start("Collecting assets...");
|
|
881
|
+
const assets = collectAssets(clientDir);
|
|
882
|
+
s.stop(`Collected ${assets.length} assets`);
|
|
883
|
+
const workerJs = readFileSync5(workerBundlePath, "utf-8");
|
|
884
|
+
const doBindings = outputConfig.durable_objects?.bindings;
|
|
885
|
+
const sqliteClasses = new Set(
|
|
886
|
+
outputConfig.migrations?.flatMap((m) => m.new_sqlite_classes ?? []) ?? []
|
|
887
|
+
);
|
|
888
|
+
const doManifest = doBindings?.map((b) => ({
|
|
889
|
+
binding: b.name,
|
|
890
|
+
className: b.class_name,
|
|
891
|
+
sqlite: sqliteClasses.has(b.class_name)
|
|
892
|
+
}));
|
|
893
|
+
if (doManifest?.length) {
|
|
894
|
+
p3.log.info(`DO manifest: ${doManifest.length} binding(s)`);
|
|
895
|
+
}
|
|
896
|
+
s.start(`Deploying to ${appName}.app.space...`);
|
|
897
|
+
const form = new FormData();
|
|
898
|
+
form.append("worker", new Blob([workerJs], { type: "application/javascript" }), "worker.js");
|
|
899
|
+
form.append("assets", JSON.stringify(assets));
|
|
900
|
+
if (doManifest) {
|
|
901
|
+
form.append("doManifest", JSON.stringify(doManifest));
|
|
902
|
+
}
|
|
903
|
+
const res = await fetch(`${DEPLOY_URL}/api/deploy/${appName}`, {
|
|
904
|
+
method: "POST",
|
|
905
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
906
|
+
body: form
|
|
907
|
+
});
|
|
908
|
+
const body = await res.json().catch(() => ({}));
|
|
909
|
+
if (!res.ok || !body.success) {
|
|
910
|
+
s.stop("Deploy failed");
|
|
911
|
+
p3.cancel(body.error ?? `Deployment error (${res.status})`);
|
|
912
|
+
process.exit(1);
|
|
913
|
+
}
|
|
914
|
+
s.stop("Deployed!");
|
|
915
|
+
p3.log.success(`Live at: ${body.url}`);
|
|
916
|
+
p3.outro("Done");
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
function collectAssets(dir) {
|
|
920
|
+
const assets = [];
|
|
921
|
+
function walk(d, prefix) {
|
|
922
|
+
for (const entry of readdirSync(d, { withFileTypes: true })) {
|
|
923
|
+
if (entry.name === ".assetsignore") continue;
|
|
924
|
+
const full = join8(d, entry.name);
|
|
925
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
926
|
+
if (entry.isDirectory()) {
|
|
927
|
+
walk(full, rel);
|
|
928
|
+
} else {
|
|
929
|
+
assets.push({
|
|
930
|
+
path: "/" + rel,
|
|
931
|
+
contentBase64: readFileSync5(full).toString("base64")
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
walk(dir, "");
|
|
937
|
+
return assets;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// src/cli/commands/undeploy.ts
|
|
941
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
942
|
+
import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
|
|
943
|
+
import { join as join9 } from "path";
|
|
944
|
+
import { parse as parseToml3 } from "smol-toml";
|
|
945
|
+
import * as p4 from "@clack/prompts";
|
|
946
|
+
var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? ENVS.prod.deploy;
|
|
947
|
+
var undeploy_default = defineCommand7({
|
|
948
|
+
meta: {
|
|
949
|
+
name: "undeploy",
|
|
950
|
+
description: "Remove a deployed DeepSpace app"
|
|
951
|
+
},
|
|
952
|
+
args: {
|
|
953
|
+
name: {
|
|
954
|
+
type: "positional",
|
|
955
|
+
description: "App name to undeploy (reads from wrangler.toml if omitted)",
|
|
956
|
+
required: false
|
|
957
|
+
}
|
|
958
|
+
},
|
|
959
|
+
async run({ args }) {
|
|
960
|
+
let appName = args.name;
|
|
961
|
+
if (!appName) {
|
|
962
|
+
const wranglerPath = join9(process.cwd(), "wrangler.toml");
|
|
963
|
+
if (existsSync8(wranglerPath)) {
|
|
964
|
+
const config = parseToml3(readFileSync6(wranglerPath, "utf-8"));
|
|
965
|
+
if (config.name) appName = config.name;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
if (!appName) {
|
|
969
|
+
p4.cancel("Provide an app name or run from a DeepSpace app directory.");
|
|
970
|
+
process.exit(1);
|
|
971
|
+
}
|
|
972
|
+
let token;
|
|
973
|
+
try {
|
|
974
|
+
token = await ensureToken();
|
|
975
|
+
} catch (err) {
|
|
976
|
+
p4.cancel(err.message);
|
|
977
|
+
process.exit(1);
|
|
978
|
+
}
|
|
979
|
+
p4.intro(`Undeploying ${appName}`);
|
|
980
|
+
const s = p4.spinner();
|
|
981
|
+
s.start("Removing...");
|
|
982
|
+
const res = await fetch(`${DEPLOY_URL2}/api/deploy/${appName}`, {
|
|
983
|
+
method: "DELETE",
|
|
984
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
985
|
+
});
|
|
986
|
+
const body = await res.json().catch(() => ({}));
|
|
987
|
+
if (!res.ok || !body.success) {
|
|
988
|
+
s.stop("Failed");
|
|
989
|
+
p4.cancel(body.error ?? `Undeploy error (${res.status})`);
|
|
990
|
+
process.exit(1);
|
|
991
|
+
}
|
|
992
|
+
s.stop("Removed");
|
|
993
|
+
p4.outro(`${appName}.app.space has been taken down`);
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
// src/cli/commands/whoami.ts
|
|
998
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
999
|
+
var whoami_default = defineCommand8({
|
|
1000
|
+
meta: {
|
|
1001
|
+
name: "whoami",
|
|
1002
|
+
description: "Show the currently logged-in user"
|
|
1003
|
+
},
|
|
1004
|
+
args: {
|
|
1005
|
+
json: {
|
|
1006
|
+
type: "boolean",
|
|
1007
|
+
description: "Emit a single-line JSON object instead of human output",
|
|
1008
|
+
default: false
|
|
1009
|
+
}
|
|
1010
|
+
},
|
|
1011
|
+
async run({ args }) {
|
|
1012
|
+
let jwt;
|
|
1013
|
+
try {
|
|
1014
|
+
jwt = await ensureToken();
|
|
1015
|
+
} catch (err) {
|
|
1016
|
+
console.error(err.message ?? "Not signed in. Run `deepspace login`.");
|
|
1017
|
+
process.exit(1);
|
|
1018
|
+
}
|
|
1019
|
+
let payload;
|
|
1020
|
+
try {
|
|
1021
|
+
payload = JSON.parse(atob(jwt.split(".")[1]));
|
|
1022
|
+
} catch {
|
|
1023
|
+
console.error("Malformed session token. Run `deepspace login` again.");
|
|
1024
|
+
process.exit(1);
|
|
1025
|
+
}
|
|
1026
|
+
const isTestAccount = !!payload.email?.endsWith("@deepspace.test");
|
|
1027
|
+
const accountType = isTestAccount ? "test-account" : payload.email ? "developer" : "unknown";
|
|
1028
|
+
if (args.json) {
|
|
1029
|
+
console.log(JSON.stringify({
|
|
1030
|
+
name: payload.name ?? null,
|
|
1031
|
+
email: payload.email ?? null,
|
|
1032
|
+
userId: payload.sub ?? null,
|
|
1033
|
+
accountType,
|
|
1034
|
+
issuer: payload.iss ?? null,
|
|
1035
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null
|
|
1036
|
+
}));
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
console.log(`Name: ${payload.name ?? "(none)"}`);
|
|
1040
|
+
console.log(`Email: ${payload.email ?? "(none)"}`);
|
|
1041
|
+
console.log(`UserID: ${payload.sub ?? "(none)"}`);
|
|
1042
|
+
console.log(`Type: ${accountType}`);
|
|
1043
|
+
if (payload.iss) console.log(`Issuer: ${payload.iss}`);
|
|
1044
|
+
if (isTestAccount) {
|
|
1045
|
+
console.log("");
|
|
1046
|
+
console.log("Note: this is a test account, not a real OAuth developer.");
|
|
1047
|
+
console.log("See docs/auth/dev-vs-test-accounts.md.");
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
// src/cli/commands/add.ts
|
|
1053
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
1054
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1055
|
+
import { resolve as resolve4, join as join10 } from "path";
|
|
1056
|
+
import { execSync as execSync3 } from "child_process";
|
|
1057
|
+
function findScript(from) {
|
|
1058
|
+
const candidate = join10(from, ".deepspace", "scripts", "add-feature.cjs");
|
|
1059
|
+
if (existsSync9(candidate)) return candidate;
|
|
1060
|
+
return null;
|
|
1061
|
+
}
|
|
1062
|
+
var add_default = defineCommand9({
|
|
1063
|
+
meta: {
|
|
1064
|
+
name: "add",
|
|
1065
|
+
description: "Add a feature to your DeepSpace app"
|
|
1066
|
+
},
|
|
1067
|
+
args: {
|
|
1068
|
+
list: {
|
|
1069
|
+
type: "boolean",
|
|
1070
|
+
alias: "l",
|
|
1071
|
+
description: "List available features",
|
|
1072
|
+
required: false
|
|
1073
|
+
},
|
|
1074
|
+
info: {
|
|
1075
|
+
type: "string",
|
|
1076
|
+
description: "Show details about a feature",
|
|
1077
|
+
required: false
|
|
1078
|
+
},
|
|
1079
|
+
feature: {
|
|
1080
|
+
type: "positional",
|
|
1081
|
+
description: "Feature to install",
|
|
1082
|
+
required: false
|
|
1083
|
+
},
|
|
1084
|
+
dir: {
|
|
1085
|
+
type: "positional",
|
|
1086
|
+
description: "App directory (default: current directory)",
|
|
1087
|
+
required: false
|
|
1088
|
+
}
|
|
1089
|
+
},
|
|
1090
|
+
run({ args }) {
|
|
1091
|
+
const appDir = resolve4(args.dir ?? ".");
|
|
1092
|
+
const script = findScript(appDir);
|
|
1093
|
+
if (!script) {
|
|
1094
|
+
console.error("No .deepspace/scripts/add-feature.cjs found.");
|
|
1095
|
+
console.error("Are you in a DeepSpace app directory?");
|
|
1096
|
+
process.exit(1);
|
|
1097
|
+
}
|
|
1098
|
+
const passArgs = [];
|
|
1099
|
+
if (args.list) {
|
|
1100
|
+
passArgs.push("--list");
|
|
1101
|
+
} else if (args.info) {
|
|
1102
|
+
passArgs.push("--info", args.info);
|
|
1103
|
+
} else if (args.feature) {
|
|
1104
|
+
passArgs.push(args.feature, appDir);
|
|
1105
|
+
} else {
|
|
1106
|
+
passArgs.push("--help");
|
|
1107
|
+
}
|
|
1108
|
+
execSync3(`node ${JSON.stringify(script)} ${passArgs.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
1109
|
+
stdio: "inherit",
|
|
1110
|
+
cwd: appDir
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
// src/cli/cli.ts
|
|
1116
|
+
var create2 = defineCommand10({
|
|
1117
|
+
meta: {
|
|
1118
|
+
name: "create",
|
|
1119
|
+
description: "Create a new DeepSpace app"
|
|
1120
|
+
},
|
|
1121
|
+
args: {
|
|
1122
|
+
name: {
|
|
1123
|
+
type: "positional",
|
|
1124
|
+
description: "App name",
|
|
1125
|
+
required: false
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
run({ args }) {
|
|
1129
|
+
const name = args.name ? ` ${args.name}` : "";
|
|
1130
|
+
console.log(`Running: npm create deepspace${name}
|
|
1131
|
+
`);
|
|
1132
|
+
execSync4(`npm create deepspace@latest${name}`, { stdio: "inherit" });
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
var main = defineCommand10({
|
|
1136
|
+
meta: {
|
|
1137
|
+
name: "deepspace",
|
|
1138
|
+
version: "0.2.1",
|
|
1139
|
+
description: "DeepSpace SDK CLI"
|
|
1140
|
+
},
|
|
1141
|
+
subCommands: {
|
|
1142
|
+
create: create2,
|
|
1143
|
+
login: login_default,
|
|
1144
|
+
logout: logout_default,
|
|
1145
|
+
whoami: whoami_default,
|
|
1146
|
+
dev: dev_default,
|
|
1147
|
+
test: test_default,
|
|
1148
|
+
"test-accounts": test_accounts_default,
|
|
1149
|
+
add: add_default,
|
|
1150
|
+
deploy: deploy_default,
|
|
1151
|
+
undeploy: undeploy_default
|
|
1152
|
+
},
|
|
1153
|
+
run() {
|
|
1154
|
+
console.log("deepspace CLI \u2014 run `npx deepspace --help` for usage");
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
runMain(main);
|
|
1158
|
+
//# sourceMappingURL=cli.js.map
|