deepspace 0.27.1 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +27 -8
- package/dist/{chunk-CRIG6LPA.js → chunk-PVEUIQN4.js} +2 -2
- package/dist/{chunk-CRIG6LPA.js.map → chunk-PVEUIQN4.js.map} +1 -1
- package/dist/cli.js +2004 -879
- package/dist/cli.js.map +1 -1
- package/dist/documentation-client-core.js +1 -1
- package/dist/documentation-default-renderer.cjs +1 -1
- package/dist/documentation-react.js +1 -1
- package/dist/documentation-runtime.js +1 -1
- package/dist/documentation-server-core.js +1 -1
- package/dist/documentation.js +1 -1
- package/dist/documentation.js.map +1 -1
- package/dist/server.d.ts +24 -2
- package/dist/server.js +59 -18
- package/dist/server.js.map +1 -1
- package/dist/testing.js +2 -2
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +99 -1
- package/dist/worker.js +318 -40
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-Q5YJGF5Z.js +0 -769
- package/dist/chunk-Q5YJGF5Z.js.map +0 -1
- package/dist/repo-api-7GDPVSFV.js +0 -14
- package/dist/repo-api-7GDPVSFV.js.map +0 -1
package/dist/chunk-Q5YJGF5Z.js
DELETED
|
@@ -1,769 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/cli/lib/repo-api.ts
|
|
4
|
-
import { randomUUID } from "crypto";
|
|
5
|
-
|
|
6
|
-
// src/cli/lib/cli-format.ts
|
|
7
|
-
var ANSI_RE = /\[[0-9;]*m/g;
|
|
8
|
-
function stripAnsi(s) {
|
|
9
|
-
return s.replace(ANSI_RE, "");
|
|
10
|
-
}
|
|
11
|
-
function forTerminal(s) {
|
|
12
|
-
return process.stdout.isTTY ? s : stripAnsi(s);
|
|
13
|
-
}
|
|
14
|
-
function shQuote(s) {
|
|
15
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
16
|
-
}
|
|
17
|
-
function humanCommand(argv) {
|
|
18
|
-
return argv.map((arg) => /^[A-Za-z0-9_./=:@-]+$/.test(arg) ? arg : shQuote(arg)).join(" ");
|
|
19
|
-
}
|
|
20
|
-
function displayText(value) {
|
|
21
|
-
return value.replace(/[\u0000-\u001f\u007f-\u009f]/g, (ch) => {
|
|
22
|
-
if (ch === " ") return " ";
|
|
23
|
-
const code = ch.codePointAt(0);
|
|
24
|
-
return `\\x${code.toString(16).padStart(2, "0")}`;
|
|
25
|
-
}).replace(/[\p{Cf}\p{Zl}\p{Zp}]/gu, (ch) => {
|
|
26
|
-
if (ch === "\u200C" || ch === "\u200D") return ch;
|
|
27
|
-
const code = ch.codePointAt(0);
|
|
28
|
-
return `\\u{${code.toString(16)}}`;
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
function displayLines(value) {
|
|
32
|
-
return value.split("\n").map((line) => displayText(line)).join("\n");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/cli/lib/fetch-retry.ts
|
|
36
|
-
async function retryTransient(run, {
|
|
37
|
-
delaysMs,
|
|
38
|
-
shouldRetryError = () => true,
|
|
39
|
-
shouldRetryResult = () => false
|
|
40
|
-
}) {
|
|
41
|
-
for (let attempt = 0; ; attempt++) {
|
|
42
|
-
try {
|
|
43
|
-
const result = await run();
|
|
44
|
-
if (!shouldRetryResult(result) || attempt >= delaysMs.length) return result;
|
|
45
|
-
} catch (error) {
|
|
46
|
-
if (!shouldRetryError(error) || attempt >= delaysMs.length) throw error;
|
|
47
|
-
}
|
|
48
|
-
await new Promise((resolve2) => setTimeout(resolve2, delaysMs[attempt]));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
async function fetchWithTransientRetry(url, makeInit, {
|
|
52
|
-
attempts = 4,
|
|
53
|
-
retryServerErrors = true,
|
|
54
|
-
timeoutMs = 6e4
|
|
55
|
-
} = {}) {
|
|
56
|
-
return retryTransient(
|
|
57
|
-
async () => {
|
|
58
|
-
return await fetch(url, { ...makeInit(), signal: AbortSignal.timeout(timeoutMs) });
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
delaysMs: Array.from(
|
|
62
|
-
{ length: Math.max(0, attempts - 1) },
|
|
63
|
-
(_, attempt) => Math.min(8e3, 500 * 2 ** attempt)
|
|
64
|
-
),
|
|
65
|
-
shouldRetryResult: (response) => retryServerErrors && (response.status >= 500 || response.status === 408 || response.status === 429)
|
|
66
|
-
}
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// src/cli/lib/api.ts
|
|
71
|
-
var ApiError = class extends Error {
|
|
72
|
-
constructor(message, status, code, apiPath, details) {
|
|
73
|
-
super(displayLines(message));
|
|
74
|
-
this.status = status;
|
|
75
|
-
this.code = code;
|
|
76
|
-
this.apiPath = apiPath;
|
|
77
|
-
this.details = details;
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
var refreshAuthToken = null;
|
|
81
|
-
function registerAuthRefresh(fn) {
|
|
82
|
-
refreshAuthToken = fn;
|
|
83
|
-
}
|
|
84
|
-
async function apiFetch(baseUrl, token, path, init, isRetry = false) {
|
|
85
|
-
let res;
|
|
86
|
-
let text;
|
|
87
|
-
try {
|
|
88
|
-
res = await fetch(`${baseUrl}${path}`, {
|
|
89
|
-
...init,
|
|
90
|
-
headers: {
|
|
91
|
-
Authorization: `Bearer ${token}`,
|
|
92
|
-
"Content-Type": "application/json",
|
|
93
|
-
...init?.headers ?? {}
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
} catch (err) {
|
|
97
|
-
throw new ApiError(
|
|
98
|
-
`Could not reach the deploy service at ${baseUrl}: ${err instanceof Error ? err.message : String(err)}. Check your connection (and DEEPSPACE_DEPLOY_URL), then retry.`,
|
|
99
|
-
0,
|
|
100
|
-
"network_error",
|
|
101
|
-
path
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
if (res.status === 401 && !isRetry && refreshAuthToken) {
|
|
105
|
-
const fresh = await refreshAuthToken();
|
|
106
|
-
if (fresh && fresh !== token) return apiFetch(baseUrl, fresh, path, init, true);
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
text = await res.text();
|
|
110
|
-
} catch (err) {
|
|
111
|
-
throw new ApiError(
|
|
112
|
-
`Could not read the deploy service response at ${baseUrl}: ${err instanceof Error ? err.message : String(err)}. Check your connection (and DEEPSPACE_DEPLOY_URL), then retry.`,
|
|
113
|
-
0,
|
|
114
|
-
"network_error",
|
|
115
|
-
path
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
if (!res.ok) {
|
|
119
|
-
let msg = text;
|
|
120
|
-
let code;
|
|
121
|
-
let details;
|
|
122
|
-
try {
|
|
123
|
-
const body = JSON.parse(text);
|
|
124
|
-
if (body !== null && typeof body === "object" && !Array.isArray(body)) {
|
|
125
|
-
msg = typeof body.error === "string" ? body.error : text;
|
|
126
|
-
code = typeof body.code === "string" ? body.code : void 0;
|
|
127
|
-
const rest = Object.fromEntries(
|
|
128
|
-
Object.entries(body).filter(([key]) => key !== "error" && key !== "code")
|
|
129
|
-
);
|
|
130
|
-
if (Object.keys(rest).length > 0) details = rest;
|
|
131
|
-
}
|
|
132
|
-
} catch {
|
|
133
|
-
}
|
|
134
|
-
if (!code && res.status === 404 && msg.trim() === "Not found") {
|
|
135
|
-
code = "unrecognized_service";
|
|
136
|
-
msg = `The deploy service at ${baseUrl} doesn't recognize this request \u2014 check DEEPSPACE_DEPLOY_URL (is this the service the app lives on?); if it is, the server may be older than this CLI.`;
|
|
137
|
-
}
|
|
138
|
-
code = code ?? (res.status >= 500 ? "server_error" : "http_error");
|
|
139
|
-
throw new ApiError(msg || `Request failed (${res.status})`, res.status, code, path, details);
|
|
140
|
-
}
|
|
141
|
-
if (!text) return {};
|
|
142
|
-
let parsed;
|
|
143
|
-
try {
|
|
144
|
-
parsed = JSON.parse(text);
|
|
145
|
-
} catch {
|
|
146
|
-
throw new ApiError(
|
|
147
|
-
`The deploy service at ${baseUrl} returned a malformed (non-JSON) response \u2014 check DEEPSPACE_DEPLOY_URL (is this the service the app lives on?).`,
|
|
148
|
-
res.status,
|
|
149
|
-
"invalid_response",
|
|
150
|
-
path
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
154
|
-
throw new ApiError(
|
|
155
|
-
`The deploy service at ${baseUrl} returned an unexpected response shape \u2014 check DEEPSPACE_DEPLOY_URL (is this the service the app lives on?).`,
|
|
156
|
-
res.status,
|
|
157
|
-
"invalid_response",
|
|
158
|
-
path
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
return parsed;
|
|
162
|
-
}
|
|
163
|
-
async function apiFetchWithTransientRetry(baseUrl, token, path, init) {
|
|
164
|
-
return retryTransient(
|
|
165
|
-
async () => await apiFetch(baseUrl, token, path, {
|
|
166
|
-
...init,
|
|
167
|
-
signal: AbortSignal.timeout(15e3)
|
|
168
|
-
}),
|
|
169
|
-
{
|
|
170
|
-
delaysMs: [250, 750, 1500],
|
|
171
|
-
// A 401 refresh owns its own bounded auth-service retry loop. Only retry
|
|
172
|
-
// failures produced by this deploy-service request, never multiply an
|
|
173
|
-
// exhausted /api/auth/token exchange by replaying the rejected bearer.
|
|
174
|
-
shouldRetryError: (error) => isTransientFailure(error) && error.apiPath === path
|
|
175
|
-
}
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
function apiFetchReadWithRetry(baseUrl, token, path) {
|
|
179
|
-
return apiFetchWithTransientRetry(baseUrl, token, path);
|
|
180
|
-
}
|
|
181
|
-
function isTransientFailure(error) {
|
|
182
|
-
return error instanceof ApiError && (error.status === 0 || error.status === 408 || error.status === 429 || error.status >= 500);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// src/cli/lib/workspace-id.ts
|
|
186
|
-
var ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
|
|
187
|
-
var WORKSPACE_ID_RE = /^ws_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
188
|
-
function isWorkspaceId(id) {
|
|
189
|
-
return WORKSPACE_ID_RE.test(id);
|
|
190
|
-
}
|
|
191
|
-
function workspaceBranchName(workspaceId) {
|
|
192
|
-
return `ws/${workspaceId.replace(/^ws_/, "").toLowerCase()}`;
|
|
193
|
-
}
|
|
194
|
-
function workspaceIdFromBranch(branch) {
|
|
195
|
-
if (!branch || !branch.startsWith("ws/")) return null;
|
|
196
|
-
const ulid = branch.slice(3);
|
|
197
|
-
if (!ULID_RE.test(ulid)) return null;
|
|
198
|
-
return `ws_${ulid.toUpperCase()}`;
|
|
199
|
-
}
|
|
200
|
-
function isSelectedWorkspaceCheckout(branch, workspaceId) {
|
|
201
|
-
return workspaceIdFromBranch(branch) === workspaceId;
|
|
202
|
-
}
|
|
203
|
-
function resolveWorkspaceWorktree(worktrees, workspaceId) {
|
|
204
|
-
const branch = workspaceBranchName(workspaceId);
|
|
205
|
-
return worktrees.find((worktree) => worktree.branch === branch)?.path ?? null;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// src/cli/lib/command.ts
|
|
209
|
-
import { defineCommand, parseArgs } from "citty";
|
|
210
|
-
import * as p3 from "@clack/prompts";
|
|
211
|
-
|
|
212
|
-
// src/cli/lib/spinner.ts
|
|
213
|
-
import * as p from "@clack/prompts";
|
|
214
|
-
var activeSpinner = null;
|
|
215
|
-
var plainProgress = false;
|
|
216
|
-
function setPlainProgress(enabled) {
|
|
217
|
-
plainProgress = enabled;
|
|
218
|
-
}
|
|
219
|
-
function createSpinner() {
|
|
220
|
-
const impl = !plainProgress && process.stdout.isTTY ? p.spinner() : {
|
|
221
|
-
start: (msg) => {
|
|
222
|
-
if (msg) console.log(msg);
|
|
223
|
-
},
|
|
224
|
-
stop: (msg) => {
|
|
225
|
-
if (msg) console.log(msg);
|
|
226
|
-
},
|
|
227
|
-
message: (msg) => {
|
|
228
|
-
if (msg) console.log(msg);
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
const wrapper = {
|
|
232
|
-
start: (msg) => {
|
|
233
|
-
activeSpinner = wrapper;
|
|
234
|
-
impl.start(msg === void 0 ? msg : displayLines(msg));
|
|
235
|
-
},
|
|
236
|
-
stop: (msg) => {
|
|
237
|
-
impl.stop(msg === void 0 ? msg : displayLines(msg));
|
|
238
|
-
if (activeSpinner === wrapper) activeSpinner = null;
|
|
239
|
-
},
|
|
240
|
-
message: (msg) => impl.message(msg === void 0 ? msg : displayLines(msg))
|
|
241
|
-
};
|
|
242
|
-
return wrapper;
|
|
243
|
-
}
|
|
244
|
-
function stopActiveSpinner() {
|
|
245
|
-
activeSpinner?.stop();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// src/cli/lib/output.ts
|
|
249
|
-
import * as p2 from "@clack/prompts";
|
|
250
|
-
import { readFileSync, realpathSync } from "fs";
|
|
251
|
-
import { dirname, isAbsolute, resolve } from "path";
|
|
252
|
-
function resolveActionArgv(argv, cwd) {
|
|
253
|
-
if (argv[0] !== "deepspace") return argv;
|
|
254
|
-
const entry = (cwd ? installedCliEntry(cwd) : null) ?? cliEntryPath();
|
|
255
|
-
return entry ? [process.execPath, entry, ...argv.slice(1)] : argv;
|
|
256
|
-
}
|
|
257
|
-
function installedCliEntry(cwd) {
|
|
258
|
-
let dir = resolve(cwd);
|
|
259
|
-
while (true) {
|
|
260
|
-
const entry = verifiedCliEntry(resolve(dir, "node_modules", "deepspace", "dist", "cli.js"));
|
|
261
|
-
if (entry) return entry;
|
|
262
|
-
const parent = dirname(dir);
|
|
263
|
-
if (parent === dir) return null;
|
|
264
|
-
dir = parent;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
function cliEntryPath() {
|
|
268
|
-
const entry = process.argv[1];
|
|
269
|
-
if (!entry || !isAbsolute(entry)) return null;
|
|
270
|
-
return verifiedCliEntry(entry);
|
|
271
|
-
}
|
|
272
|
-
function verifiedCliEntry(entry) {
|
|
273
|
-
let real;
|
|
274
|
-
try {
|
|
275
|
-
real = realpathSync(entry);
|
|
276
|
-
} catch {
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
|
-
if (!/(^|[\\/])cli\.[cm]?js$/.test(real)) return null;
|
|
280
|
-
try {
|
|
281
|
-
const pkg = JSON.parse(readFileSync(resolve(dirname(real), "..", "package.json"), "utf-8"));
|
|
282
|
-
return pkg.name === "deepspace" ? real : null;
|
|
283
|
-
} catch {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
function executableAction(action) {
|
|
288
|
-
const pinned = { ...action, argv: resolveActionArgv(action.argv, action.cwd) };
|
|
289
|
-
assertExecutableAction(pinned);
|
|
290
|
-
return pinned;
|
|
291
|
-
}
|
|
292
|
-
function cliAction(...argv) {
|
|
293
|
-
return executableAction({ cwd: process.cwd(), argv });
|
|
294
|
-
}
|
|
295
|
-
function assertExecutableAction(action) {
|
|
296
|
-
if (!isAbsolute(action.cwd)) throw new TypeError("CLI action cwd must be absolute");
|
|
297
|
-
if (action.argv.length === 0 || action.argv[0].trim() === "") {
|
|
298
|
-
throw new TypeError("CLI action argv must name an executable");
|
|
299
|
-
}
|
|
300
|
-
if (action.argv.some((arg) => /^<[^<>]+>$/.test(arg))) {
|
|
301
|
-
throw new TypeError("CLI action argv must not contain placeholder tokens");
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
function withSlug(msg, code) {
|
|
305
|
-
const tag = `[${code}]`;
|
|
306
|
-
return msg.trimEnd().endsWith(tag) ? msg : `${msg.trimEnd()} ${tag}`;
|
|
307
|
-
}
|
|
308
|
-
function displayArg(arg) {
|
|
309
|
-
const safe = displayText(arg);
|
|
310
|
-
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(safe)) return safe;
|
|
311
|
-
if (process.platform === "win32") return `"${safe.replaceAll('"', '""')}"`;
|
|
312
|
-
return shQuote(safe);
|
|
313
|
-
}
|
|
314
|
-
function printAction(action) {
|
|
315
|
-
assertExecutableAction(action);
|
|
316
|
-
const command = action.argv.map(displayArg).join(" ");
|
|
317
|
-
const prefix = action.cwd === process.cwd() ? "" : `cd ${displayArg(action.cwd)} && `;
|
|
318
|
-
p2.log.message(`Next: ${prefix}${command}`);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// src/cli/lib/cli-errors.ts
|
|
322
|
-
var InputError = class extends Error {
|
|
323
|
-
constructor(message, code) {
|
|
324
|
-
super(displayLines(message));
|
|
325
|
-
this.code = code;
|
|
326
|
-
}
|
|
327
|
-
};
|
|
328
|
-
function isMissingCwdError(err) {
|
|
329
|
-
if (!(err instanceof Error)) return false;
|
|
330
|
-
const errno = err;
|
|
331
|
-
if (errno.code !== "ENOENT") return false;
|
|
332
|
-
return errno.syscall === "uv_cwd";
|
|
333
|
-
}
|
|
334
|
-
var Refusal = class extends Error {
|
|
335
|
-
code;
|
|
336
|
-
action;
|
|
337
|
-
actionRequired;
|
|
338
|
-
extra;
|
|
339
|
-
constructor(message, code, opts = {}) {
|
|
340
|
-
super(displayLines(message));
|
|
341
|
-
this.name = "Refusal";
|
|
342
|
-
this.code = code;
|
|
343
|
-
this.action = opts.action ? executableAction(opts.action) : void 0;
|
|
344
|
-
this.actionRequired = opts.actionRequired ?? false;
|
|
345
|
-
this.extra = opts.extra ?? {};
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
function withoutReservedKeys(payload) {
|
|
349
|
-
return Object.fromEntries(
|
|
350
|
-
Object.entries(payload).filter(
|
|
351
|
-
([key]) => !["ok", "code", "error", "action", "actionRequired", "next", "nextAction", "resume"].includes(
|
|
352
|
-
key
|
|
353
|
-
)
|
|
354
|
-
)
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
function failureEnvelope(err) {
|
|
358
|
-
const refusal = err instanceof Refusal ? err : null;
|
|
359
|
-
const code = errorCode(err);
|
|
360
|
-
const details = refusal?.extra ?? (err instanceof ApiError ? err.details : void 0) ?? {};
|
|
361
|
-
return {
|
|
362
|
-
ok: false,
|
|
363
|
-
...code ? { code } : {},
|
|
364
|
-
...refusal?.actionRequired ? { actionRequired: true } : {},
|
|
365
|
-
error: formatCliError(err),
|
|
366
|
-
...refusal?.action ? { action: refusal.action } : {},
|
|
367
|
-
...withoutReservedKeys(details)
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
function failureExitCode(err) {
|
|
371
|
-
return err instanceof Refusal && err.actionRequired ? 2 : 1;
|
|
372
|
-
}
|
|
373
|
-
var CliExit = class extends Error {
|
|
374
|
-
constructor(exitCode) {
|
|
375
|
-
super(`exit ${exitCode}`);
|
|
376
|
-
this.exitCode = exitCode;
|
|
377
|
-
this.name = "CliExit";
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
function errorCode(err) {
|
|
381
|
-
if (err instanceof ApiError || err instanceof InputError || err instanceof Refusal) return err.code;
|
|
382
|
-
if (isMissingCwdError(err)) return "worktree_missing";
|
|
383
|
-
if (err instanceof Error && err.name === "GitError") return err.code ?? "git_error";
|
|
384
|
-
if (err instanceof Error && err.name === "WranglerConfigError")
|
|
385
|
-
return err.code ?? "invalid_config";
|
|
386
|
-
return void 0;
|
|
387
|
-
}
|
|
388
|
-
var API_ERROR_HINTS = {
|
|
389
|
-
// Starts where the server sentence ("Only the app owner can do this.")
|
|
390
|
-
// stops, so the two read as one message instead of a stutter.
|
|
391
|
-
// Neutral about the reader's standing: this hint also reaches a REMOVED
|
|
392
|
-
// collaborator, to whom "collaborators can deploy" reads as "you should
|
|
393
|
-
// have been able to" (v0.26.0 collab AX).
|
|
394
|
-
not_app_owner: "Ownership verbs (collaborators, transfer, undeploy) run only as the owner; current collaborators can deploy and manage secrets, and an account that was removed has no access at all (`deepspace app list` shows your standing). Ask the owner to run it, have them transfer the app (`deepspace app transfer offer`), or fork your checkout as your own app (`deepspace app init --new-id`).",
|
|
395
|
-
app_not_found: "App not found on the service this command asked. Check the app id \u2014 the DEEPSPACE_APP_ID value in wrangler.toml, usually `app_\u2026` (a legacy app's id is its name); list your apps with `deepspace app list`. If you are overriding DEEPSPACE_DEPLOY_URL (staging), note that account/collaborator commands go to the PLATFORM API \u2014 an app registered only on a staging deploy service is unknown there. (`domain` commands take the deployed app *name* instead.)",
|
|
396
|
-
not_app_owner_or_collaborator: "You must be the app owner or a collaborator to do this.",
|
|
397
|
-
test_account_cannot_be_collaborator: "Test accounts cannot be added as collaborators. Use a real DeepSpace account.",
|
|
398
|
-
user_not_found: "No DeepSpace user with that email. They need to log in to DeepSpace at least once (`deepspace auth login`, or sign in to any app) before they can be referenced by email.",
|
|
399
|
-
insufficient_credits: "Out of credits. Inviting a new collaborator by email sends them a transactional email billed to your account \u2014 top up your credits and try again.",
|
|
400
|
-
// "Try again" alone sends callers into a loop when the address itself is the
|
|
401
|
-
// problem — the send is what failed, and an undeliverable address fails the
|
|
402
|
-
// same way every time.
|
|
403
|
-
invite_email_failed: "The invite itself was not created and the charge was voided \u2014 nothing to undo. Retrying helps only if the send was transient; check the address is real and deliverable first. An email is needed ONLY for someone with no DeepSpace account yet: an existing user is added instantly, so having them sign in once and re-running this skips email entirely. If a retry now reports `already_invited` with no email sent, cancel the stuck invite (`deepspace app collaborators cancel <email>`) and invite again."
|
|
404
|
-
};
|
|
405
|
-
function formatCliError(err) {
|
|
406
|
-
if (isMissingCwdError(err)) {
|
|
407
|
-
const pwd = process.env.PWD;
|
|
408
|
-
return `The directory this command was run from no longer exists${pwd ? ` (${pwd})` : ""} \u2014 it was deleted or moved while the shell was still in it. Move somewhere that exists (\`cd\` to the main checkout). If it was a workspace worktree that a land, merge, or drop removed, \`deepspace workspace attach <ws_\u2026>\` re-materializes it there.`;
|
|
409
|
-
}
|
|
410
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
411
|
-
const slug = errorCode(err);
|
|
412
|
-
const hint = slug ? API_ERROR_HINTS[slug] : void 0;
|
|
413
|
-
return hint && !message.includes(hint) ? `${message}
|
|
414
|
-
${hint}` : message;
|
|
415
|
-
}
|
|
416
|
-
function renderCliError(err) {
|
|
417
|
-
stopActiveSpinner();
|
|
418
|
-
if (err instanceof CliExit) {
|
|
419
|
-
process.exitCode = err.exitCode;
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
const message = formatCliError(err);
|
|
423
|
-
const slug = errorCode(err);
|
|
424
|
-
if (process.argv.includes("--json")) {
|
|
425
|
-
console.log(JSON.stringify(failureEnvelope(err)));
|
|
426
|
-
process.exitCode = failureExitCode(err);
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
console.error(slug ? withSlug(message, slug) : message);
|
|
430
|
-
if (err instanceof Refusal && err.action) printAction(err.action);
|
|
431
|
-
if (process.env.DEBUG) {
|
|
432
|
-
const { apiPath, status } = err;
|
|
433
|
-
if (apiPath) console.error(`
|
|
434
|
-
API ${apiPath}${status ? ` (${status})` : ""}`);
|
|
435
|
-
if (err instanceof Error && err.stack) console.error("\n" + err.stack);
|
|
436
|
-
}
|
|
437
|
-
process.exitCode = failureExitCode(err);
|
|
438
|
-
}
|
|
439
|
-
function wrapCommandErrors(cmd) {
|
|
440
|
-
const run = cmd.run;
|
|
441
|
-
if (run) {
|
|
442
|
-
cmd.run = (async (ctx) => {
|
|
443
|
-
try {
|
|
444
|
-
await run(ctx);
|
|
445
|
-
} catch (err) {
|
|
446
|
-
renderCliError(err);
|
|
447
|
-
}
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
if (cmd.subCommands && typeof cmd.subCommands === "object") {
|
|
451
|
-
for (const sub of Object.values(cmd.subCommands)) {
|
|
452
|
-
if (sub && typeof sub === "object") wrapCommandErrors(sub);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
return cmd;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// src/cli/lib/command.ts
|
|
459
|
-
function finishCommand(code) {
|
|
460
|
-
stopActiveSpinner();
|
|
461
|
-
process.exitCode = code;
|
|
462
|
-
}
|
|
463
|
-
function nameVariants(name) {
|
|
464
|
-
return [
|
|
465
|
-
name,
|
|
466
|
-
name.replace(/-([a-z])/g, (_, c) => c.toUpperCase()),
|
|
467
|
-
name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)
|
|
468
|
-
];
|
|
469
|
-
}
|
|
470
|
-
function unknownOptionNames(parsed, args) {
|
|
471
|
-
const known = /* @__PURE__ */ new Set(["_"]);
|
|
472
|
-
for (const [name, def] of Object.entries(args)) {
|
|
473
|
-
for (const variant of nameVariants(name)) known.add(variant);
|
|
474
|
-
const alias = def.alias;
|
|
475
|
-
for (const a of Array.isArray(alias) ? alias : alias ? [alias] : []) {
|
|
476
|
-
for (const variant of nameVariants(a)) known.add(variant);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
return Object.keys(parsed).filter((key) => !known.has(key));
|
|
480
|
-
}
|
|
481
|
-
function unknownOptionNamesFromRaw(rawArgs, args) {
|
|
482
|
-
const relaxedArgs = Object.fromEntries(
|
|
483
|
-
Object.entries(args).map(([name, definition]) => [
|
|
484
|
-
name,
|
|
485
|
-
definition.type === "enum" ? { ...definition, type: "string", required: false, options: void 0 } : { ...definition, required: false }
|
|
486
|
-
])
|
|
487
|
-
);
|
|
488
|
-
return unknownOptionNames(parseArgs(rawArgs, relaxedArgs), args);
|
|
489
|
-
}
|
|
490
|
-
function unknownOptionMessage(commandName, unknown, args) {
|
|
491
|
-
const valid = Object.entries(args).filter(([, definition]) => definition.type !== "positional").map(([name]) => name).sort().join(", ");
|
|
492
|
-
return `Unknown ${unknown.length === 1 ? "option" : "options"}: ${unknown.map((name) => `--${name}`).join(", ")}. \`${commandName}\` accepts: ${valid || "(none)"}.`;
|
|
493
|
-
}
|
|
494
|
-
function defineDeepspaceCommand(def) {
|
|
495
|
-
const declaredArgs = {
|
|
496
|
-
...def.args ?? {},
|
|
497
|
-
json: {
|
|
498
|
-
type: "boolean",
|
|
499
|
-
description: def.jsonDescription ?? "Emit a single-line JSON result for scripts/agents",
|
|
500
|
-
default: false
|
|
501
|
-
}
|
|
502
|
-
};
|
|
503
|
-
return defineCommand({
|
|
504
|
-
meta: def.meta,
|
|
505
|
-
args: declaredArgs,
|
|
506
|
-
async run({ args }) {
|
|
507
|
-
const json = Boolean(args.json);
|
|
508
|
-
let succeeded = false;
|
|
509
|
-
try {
|
|
510
|
-
const unknown = unknownOptionNames(args, declaredArgs);
|
|
511
|
-
if (unknown.length > 0) {
|
|
512
|
-
throw new Refusal(
|
|
513
|
-
unknownOptionMessage(def.meta.name, unknown, declaredArgs),
|
|
514
|
-
"unknown_option"
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
const out = await def.run({
|
|
518
|
-
args
|
|
519
|
-
}) ?? {};
|
|
520
|
-
if (out.action) out.action = executableAction(out.action);
|
|
521
|
-
if (json) {
|
|
522
|
-
console.log(
|
|
523
|
-
JSON.stringify({
|
|
524
|
-
ok: true,
|
|
525
|
-
...withoutReservedKeys(out.data ?? {}),
|
|
526
|
-
...out.action ? { action: out.action } : {}
|
|
527
|
-
})
|
|
528
|
-
);
|
|
529
|
-
} else if (out.action) {
|
|
530
|
-
printAction(out.action);
|
|
531
|
-
}
|
|
532
|
-
succeeded = true;
|
|
533
|
-
} catch (err) {
|
|
534
|
-
stopActiveSpinner();
|
|
535
|
-
const refusal = err instanceof Refusal ? err : null;
|
|
536
|
-
const code = errorCode(err);
|
|
537
|
-
if (json) {
|
|
538
|
-
console.log(JSON.stringify(failureEnvelope(err)));
|
|
539
|
-
} else {
|
|
540
|
-
const message = formatCliError(err);
|
|
541
|
-
const line = code ? withSlug(message, code) : message;
|
|
542
|
-
if (refusal?.actionRequired) p3.log.warn(line);
|
|
543
|
-
else p3.log.error(line);
|
|
544
|
-
if (refusal?.action) printAction(refusal.action);
|
|
545
|
-
}
|
|
546
|
-
finishCommand(failureExitCode(err));
|
|
547
|
-
}
|
|
548
|
-
if (succeeded) finishCommand(0);
|
|
549
|
-
}
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// src/cli/lib/source-api.ts
|
|
554
|
-
function githubSourceRefusal(appId, repository) {
|
|
555
|
-
return new Refusal(
|
|
556
|
-
`This app uses GitHub source (${repository}). Use normal Git/GitHub for source operations (clone, fetch, push); \`deepspace deploy\` ships the local working tree without changing Git.`,
|
|
557
|
-
"source_managed_by_github",
|
|
558
|
-
{ extra: { appId, repository } }
|
|
559
|
-
);
|
|
560
|
-
}
|
|
561
|
-
function githubInferredRefusal(appId, repository) {
|
|
562
|
-
return new Refusal(
|
|
563
|
-
`This app ships from GitHub (${repository}) \u2014 inferred from its releases; its DeepSpace cloud repo is intentionally empty. Use normal Git/GitHub for source operations. (\`deepspace push\` would permanently claim DeepSpace source for this app \u2014 only run it to adopt DeepSpace source on purpose.)`,
|
|
564
|
-
"source_managed_by_github",
|
|
565
|
-
{ extra: { appId, repository, inferred: true } }
|
|
566
|
-
);
|
|
567
|
-
}
|
|
568
|
-
async function inferredGitHubRepository(deployUrl, token, appId) {
|
|
569
|
-
try {
|
|
570
|
-
const { repoApi: repoApi2 } = await import("./repo-api-7GDPVSFV.js");
|
|
571
|
-
const { release } = await repoApi2(deployUrl, token, appId).latestRelease();
|
|
572
|
-
if (release?.source?.provider === "github") return release.source.repository;
|
|
573
|
-
} catch {
|
|
574
|
-
}
|
|
575
|
-
return null;
|
|
576
|
-
}
|
|
577
|
-
async function getAppSource(deployUrl, token, appId) {
|
|
578
|
-
try {
|
|
579
|
-
const state = await apiFetchReadWithRetry(
|
|
580
|
-
deployUrl,
|
|
581
|
-
token,
|
|
582
|
-
`/api/apps/${encodeURIComponent(appId)}/source`
|
|
583
|
-
);
|
|
584
|
-
return { ...state, registered: true };
|
|
585
|
-
} catch (error) {
|
|
586
|
-
if (error instanceof ApiError && error.code === "app_not_found") {
|
|
587
|
-
return { appId, source: null, revision: 0, registered: false };
|
|
588
|
-
}
|
|
589
|
-
throw error;
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// src/cli/lib/repo-api.ts
|
|
594
|
-
function isWorkspaceRef(ref) {
|
|
595
|
-
const prefix = "refs/deepspace/ws/";
|
|
596
|
-
return ref.startsWith(prefix) && isWorkspaceId(ref.slice(prefix.length));
|
|
597
|
-
}
|
|
598
|
-
function releaseSourceLabel(release) {
|
|
599
|
-
const dirty = release.sourceDirty === true ? " (dirty worktree)" : "";
|
|
600
|
-
if (release.commitOid) return `commit ${release.commitOid.slice(0, 10)}${dirty}`;
|
|
601
|
-
if (release.source?.provider === "github") {
|
|
602
|
-
return `GitHub \xB7 ${release.source.repository}${dirty ? ", dirty worktree" : ""}`;
|
|
603
|
-
}
|
|
604
|
-
if (release.source?.provider === "deepspace") {
|
|
605
|
-
return `DeepSpace source, no commit recorded${dirty}`;
|
|
606
|
-
}
|
|
607
|
-
return `no source recorded${dirty}`;
|
|
608
|
-
}
|
|
609
|
-
function mintIdempotencyKey() {
|
|
610
|
-
return randomUUID();
|
|
611
|
-
}
|
|
612
|
-
async function resolveAppIdByName(deployUrl, token, name) {
|
|
613
|
-
try {
|
|
614
|
-
const { appId } = await apiFetch(
|
|
615
|
-
deployUrl,
|
|
616
|
-
token,
|
|
617
|
-
`/api/repo/resolve-name/${encodeURIComponent(name)}`
|
|
618
|
-
);
|
|
619
|
-
return appId ?? null;
|
|
620
|
-
} catch (err) {
|
|
621
|
-
if (err instanceof ApiError && (err.status === 404 || err.status === 400)) return null;
|
|
622
|
-
throw err;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
function repoApi(deployUrl, token, appId) {
|
|
626
|
-
const base = `/api/repo/${encodeURIComponent(appId)}`;
|
|
627
|
-
const call = async (path, init) => {
|
|
628
|
-
return await apiFetch(deployUrl, token, `${base}${path}`, init);
|
|
629
|
-
};
|
|
630
|
-
const post = (path, body) => call(path, { method: "POST", body: JSON.stringify(body) });
|
|
631
|
-
const KIND = {
|
|
632
|
-
array: (v) => Array.isArray(v) && v.every((e) => e !== null),
|
|
633
|
-
object: (v) => v !== null && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length > 0,
|
|
634
|
-
present: (v) => v !== void 0
|
|
635
|
-
};
|
|
636
|
-
const shapeError = (path) => new ApiError(
|
|
637
|
-
`The deploy service at ${deployUrl} returned an unexpected response shape \u2014 check DEEPSPACE_DEPLOY_URL (is this the service the app lives on?).`,
|
|
638
|
-
200,
|
|
639
|
-
"invalid_response",
|
|
640
|
-
`${base}${path}`
|
|
641
|
-
);
|
|
642
|
-
const normalizeWorkspace = (view, path) => {
|
|
643
|
-
if (view === null || typeof view !== "object") return;
|
|
644
|
-
const workspace = view.workspace;
|
|
645
|
-
if (workspace === null || typeof workspace !== "object") return;
|
|
646
|
-
const fields = workspace;
|
|
647
|
-
if (typeof fields.ref !== "string" || !isWorkspaceRef(fields.ref)) throw shapeError(path);
|
|
648
|
-
if (typeof fields.task === "string") fields.task = displayText(fields.task);
|
|
649
|
-
};
|
|
650
|
-
const assertShape = (r, spec, path) => {
|
|
651
|
-
if (r === null || typeof r !== "object") throw shapeError(path);
|
|
652
|
-
const obj = r;
|
|
653
|
-
for (const [key, kind] of Object.entries(spec)) {
|
|
654
|
-
if (!KIND[kind](obj[key])) throw shapeError(path);
|
|
655
|
-
}
|
|
656
|
-
normalizeWorkspace(obj.view, path);
|
|
657
|
-
if (Array.isArray(obj.views)) for (const view of obj.views) normalizeWorkspace(view, path);
|
|
658
|
-
return r;
|
|
659
|
-
};
|
|
660
|
-
const getShape = async (path, spec) => assertShape(await call(path), spec, path);
|
|
661
|
-
const postShape = async (path, body, spec) => assertShape(await post(path, body), spec, path);
|
|
662
|
-
return {
|
|
663
|
-
/** null ⇒ the app has never been registered (fresh repo, no history). */
|
|
664
|
-
async getRefs() {
|
|
665
|
-
try {
|
|
666
|
-
return await getShape("/refs", { refs: "array", head: "present" });
|
|
667
|
-
} catch (err) {
|
|
668
|
-
if (err instanceof ApiError && err.code === "app_not_found") return null;
|
|
669
|
-
if (err instanceof ApiError && err.code === "source_managed_by_github") {
|
|
670
|
-
const repository = err.details?.repository;
|
|
671
|
-
if (typeof repository === "string") throw githubSourceRefusal(appId, repository);
|
|
672
|
-
}
|
|
673
|
-
throw err;
|
|
674
|
-
}
|
|
675
|
-
},
|
|
676
|
-
createWorkspace(args) {
|
|
677
|
-
return postShape("/workspaces", args, { view: "object" });
|
|
678
|
-
},
|
|
679
|
-
/** `truncated` ⇒ the server had more rows than `limit` and stopped. Without
|
|
680
|
-
* it a capped page is indistinguishable from the whole set, so a caller
|
|
681
|
-
* reading "3 workspaces" cannot tell that a fourth exists. */
|
|
682
|
-
listWorkspaces(opts) {
|
|
683
|
-
const params = new URLSearchParams();
|
|
684
|
-
if (opts?.all) params.set("all", "1");
|
|
685
|
-
if (opts?.limit) params.set("limit", String(opts.limit));
|
|
686
|
-
const qs = params.toString();
|
|
687
|
-
return getShape(`/workspaces${qs ? `?${qs}` : ""}`, { views: "array" });
|
|
688
|
-
},
|
|
689
|
-
getWorkspace(id) {
|
|
690
|
-
return getShape(`/workspaces/${encodeURIComponent(id)}`, { view: "object" });
|
|
691
|
-
},
|
|
692
|
-
syncWorkspace(id, args) {
|
|
693
|
-
return postShape(`/workspaces/${encodeURIComponent(id)}/sync`, args, { view: "object" });
|
|
694
|
-
},
|
|
695
|
-
landWorkspace(id, args) {
|
|
696
|
-
return postShape(`/workspaces/${encodeURIComponent(id)}/land`, args, { view: "object" });
|
|
697
|
-
},
|
|
698
|
-
dropWorkspace(id) {
|
|
699
|
-
return postShape(`/workspaces/${encodeURIComponent(id)}/drop`, {}, { view: "object" });
|
|
700
|
-
},
|
|
701
|
-
listActivity(opts) {
|
|
702
|
-
const params = new URLSearchParams();
|
|
703
|
-
if (opts?.since) params.set("since", String(opts.since));
|
|
704
|
-
if (opts?.limit) params.set("limit", String(opts.limit));
|
|
705
|
-
if (opts?.tail) params.set("tail", "1");
|
|
706
|
-
const qs = params.toString();
|
|
707
|
-
return getShape(`/activity${qs ? `?${qs}` : ""}`, {
|
|
708
|
-
events: "array",
|
|
709
|
-
cursor: "present",
|
|
710
|
-
hasMore: "present"
|
|
711
|
-
});
|
|
712
|
-
},
|
|
713
|
-
listReleases(limit) {
|
|
714
|
-
return getShape(`/releases${limit ? `?limit=${limit}` : ""}`, { releases: "array" });
|
|
715
|
-
},
|
|
716
|
-
latestRelease() {
|
|
717
|
-
return getShape("/releases/latest", { release: "present" });
|
|
718
|
-
},
|
|
719
|
-
getRelease(id) {
|
|
720
|
-
return getShape(`/releases/${encodeURIComponent(id)}`, { release: "object" });
|
|
721
|
-
}
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
export {
|
|
726
|
-
stripAnsi,
|
|
727
|
-
forTerminal,
|
|
728
|
-
shQuote,
|
|
729
|
-
humanCommand,
|
|
730
|
-
displayText,
|
|
731
|
-
displayLines,
|
|
732
|
-
setPlainProgress,
|
|
733
|
-
createSpinner,
|
|
734
|
-
stopActiveSpinner,
|
|
735
|
-
executableAction,
|
|
736
|
-
cliAction,
|
|
737
|
-
withSlug,
|
|
738
|
-
printAction,
|
|
739
|
-
fetchWithTransientRetry,
|
|
740
|
-
ApiError,
|
|
741
|
-
registerAuthRefresh,
|
|
742
|
-
apiFetch,
|
|
743
|
-
apiFetchWithTransientRetry,
|
|
744
|
-
apiFetchReadWithRetry,
|
|
745
|
-
InputError,
|
|
746
|
-
Refusal,
|
|
747
|
-
withoutReservedKeys,
|
|
748
|
-
CliExit,
|
|
749
|
-
errorCode,
|
|
750
|
-
formatCliError,
|
|
751
|
-
wrapCommandErrors,
|
|
752
|
-
unknownOptionNamesFromRaw,
|
|
753
|
-
unknownOptionMessage,
|
|
754
|
-
defineDeepspaceCommand,
|
|
755
|
-
isWorkspaceId,
|
|
756
|
-
workspaceBranchName,
|
|
757
|
-
workspaceIdFromBranch,
|
|
758
|
-
isSelectedWorkspaceCheckout,
|
|
759
|
-
resolveWorkspaceWorktree,
|
|
760
|
-
githubSourceRefusal,
|
|
761
|
-
githubInferredRefusal,
|
|
762
|
-
inferredGitHubRepository,
|
|
763
|
-
getAppSource,
|
|
764
|
-
releaseSourceLabel,
|
|
765
|
-
mintIdempotencyKey,
|
|
766
|
-
resolveAppIdByName,
|
|
767
|
-
repoApi
|
|
768
|
-
};
|
|
769
|
-
//# sourceMappingURL=chunk-Q5YJGF5Z.js.map
|