deepspace 0.12.0 → 0.13.0
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 +153 -0
- package/dist/{chunk-ZJ43EK3N.js → chunk-233GQU2H.js} +492 -365
- package/dist/chunk-233GQU2H.js.map +1 -0
- package/dist/cli.js +441 -167
- package/dist/cli.js.map +1 -1
- package/dist/documentation-client-core.d.ts +2 -2
- package/dist/documentation-client-core.js +1 -1
- package/dist/documentation-default-renderer.cjs +34 -34
- package/dist/documentation-react.d.ts +1 -1
- package/dist/documentation-react.js +1 -1
- package/dist/documentation-runtime.js +10 -10
- package/dist/documentation-server-core.d.ts +2 -2
- package/dist/documentation-server-core.js +1 -1
- package/dist/documentation.js +2 -2
- package/dist/documentation.js.map +1 -1
- package/dist/index.d.ts +28 -3
- package/dist/index.js +86 -8
- package/dist/index.js.map +1 -1
- package/dist/{public-BGRX_Cjd.d.ts → public-BtArwbVM.d.ts} +8 -0
- package/dist/server.d.ts +94 -23
- package/dist/server.js +340 -87
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +95 -24
- package/dist/worker.js +345 -87
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZJ43EK3N.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/cli.ts
|
|
4
|
-
import { defineCommand as defineCommand14, runMain, runCommand } from "citty";
|
|
4
|
+
import { defineCommand as defineCommand14, runMain, runCommand, renderUsage } from "citty";
|
|
5
5
|
import { sync as spawnSync8 } from "cross-spawn";
|
|
6
6
|
import { readFileSync as readFileSync28, existsSync as existsSync26 } from "fs";
|
|
7
7
|
import { dirname as dirname10, join as join24 } from "path";
|
|
@@ -44,6 +44,13 @@ import * as p2 from "@clack/prompts";
|
|
|
44
44
|
import { isAbsolute } from "path";
|
|
45
45
|
|
|
46
46
|
// src/cli/lib/cli-format.ts
|
|
47
|
+
var ANSI_RE = /\[[0-9;]*m/g;
|
|
48
|
+
function stripAnsi(s) {
|
|
49
|
+
return s.replace(ANSI_RE, "");
|
|
50
|
+
}
|
|
51
|
+
function forTerminal(s) {
|
|
52
|
+
return process.stdout.isTTY ? s : stripAnsi(s);
|
|
53
|
+
}
|
|
47
54
|
function shQuote(s) {
|
|
48
55
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
49
56
|
}
|
|
@@ -176,7 +183,10 @@ var API_ERROR_HINTS = {
|
|
|
176
183
|
test_account_cannot_be_collaborator: "Test accounts cannot be added as collaborators. Use a real DeepSpace account.",
|
|
177
184
|
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.",
|
|
178
185
|
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.",
|
|
179
|
-
|
|
186
|
+
// "Try again" alone sends callers into a loop when the address itself is the
|
|
187
|
+
// problem — the send is what failed, and an undeliverable address fails the
|
|
188
|
+
// same way every time.
|
|
189
|
+
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."
|
|
180
190
|
};
|
|
181
191
|
function formatCliError(err) {
|
|
182
192
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -222,6 +232,18 @@ function wrapCommandErrors(cmd) {
|
|
|
222
232
|
}
|
|
223
233
|
|
|
224
234
|
// src/cli/lib/command-suggestions.ts
|
|
235
|
+
function splitTokenPath(token, table) {
|
|
236
|
+
const parts = token.trim().split(/\s+/);
|
|
237
|
+
if (parts.length < 2) return null;
|
|
238
|
+
let node = table;
|
|
239
|
+
for (const part of parts) {
|
|
240
|
+
const next = node?.[part];
|
|
241
|
+
if (!next) return null;
|
|
242
|
+
node = next.subCommands;
|
|
243
|
+
}
|
|
244
|
+
return parts;
|
|
245
|
+
}
|
|
246
|
+
var DESTRUCTIVE_VERBS = /* @__PURE__ */ new Set(["rm", "delete", "remove", "drop", "clear", "undeploy", "kill"]);
|
|
225
247
|
function editDistance(a, b) {
|
|
226
248
|
const left = a.toLowerCase();
|
|
227
249
|
const right = b.toLowerCase();
|
|
@@ -263,12 +285,15 @@ function findUnknownCommand(argv, rootCommands) {
|
|
|
263
285
|
if (!table || token.startsWith("-")) return null;
|
|
264
286
|
const command = table[token];
|
|
265
287
|
if (!command) {
|
|
266
|
-
const
|
|
288
|
+
const split = splitTokenPath(token, table);
|
|
289
|
+
const relativeSuggestion = split ?? closestCommandPath(token, table);
|
|
267
290
|
if (!relativeSuggestion) return null;
|
|
268
291
|
return {
|
|
269
292
|
attemptedPath: ["deepspace", ...accepted, token],
|
|
270
293
|
helpPath: ["deepspace", ...accepted],
|
|
271
|
-
suggestion: ["deepspace", ...accepted, ...relativeSuggestion]
|
|
294
|
+
suggestion: ["deepspace", ...accepted, ...relativeSuggestion],
|
|
295
|
+
executable: !DESTRUCTIVE_VERBS.has(relativeSuggestion.at(-1) ?? ""),
|
|
296
|
+
...split ? { quotedToken: true } : {}
|
|
272
297
|
};
|
|
273
298
|
}
|
|
274
299
|
accepted.push(token);
|
|
@@ -2544,16 +2569,31 @@ function formatSchemaLintFindings(findings) {
|
|
|
2544
2569
|
}
|
|
2545
2570
|
|
|
2546
2571
|
// src/cli/lib/port.ts
|
|
2547
|
-
import { createServer } from "net";
|
|
2572
|
+
import { createServer, Socket } from "net";
|
|
2548
2573
|
var DEFAULT_PORT = 5173;
|
|
2549
|
-
function
|
|
2574
|
+
function isPortListening(port, host, timeoutMs = 500) {
|
|
2575
|
+
const target2 = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
2550
2576
|
return new Promise((resolve19) => {
|
|
2551
|
-
const
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2577
|
+
const socket = new Socket();
|
|
2578
|
+
const done = (listening) => {
|
|
2579
|
+
socket.destroy();
|
|
2580
|
+
resolve19(listening);
|
|
2581
|
+
};
|
|
2582
|
+
socket.setTimeout(timeoutMs);
|
|
2583
|
+
socket.once("connect", () => done(true));
|
|
2584
|
+
socket.once("timeout", () => done(false));
|
|
2585
|
+
socket.once("error", () => done(false));
|
|
2586
|
+
socket.connect(port, target2);
|
|
2555
2587
|
});
|
|
2556
2588
|
}
|
|
2589
|
+
async function waitForPortListening(port, host, timeoutMs) {
|
|
2590
|
+
const deadline = Date.now() + timeoutMs;
|
|
2591
|
+
while (Date.now() < deadline) {
|
|
2592
|
+
if (await isPortListening(port, host)) return true;
|
|
2593
|
+
await new Promise((resolve19) => setTimeout(resolve19, 250));
|
|
2594
|
+
}
|
|
2595
|
+
return false;
|
|
2596
|
+
}
|
|
2557
2597
|
function resolvePort(arg) {
|
|
2558
2598
|
const raw = arg ?? process.env.DEEPSPACE_PORT;
|
|
2559
2599
|
if (!raw) return DEFAULT_PORT;
|
|
@@ -2565,6 +2605,8 @@ function resolvePort(arg) {
|
|
|
2565
2605
|
}
|
|
2566
2606
|
|
|
2567
2607
|
// src/cli/commands/dev.ts
|
|
2608
|
+
var DEFAULT_DEV_HOST = "0.0.0.0";
|
|
2609
|
+
var READINESS_TIMEOUT_MS = 12e4;
|
|
2568
2610
|
var DEPLOY_URL = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
2569
2611
|
function noAppDirMessage(start, children) {
|
|
2570
2612
|
const lines = [`No wrangler.toml found at or above ${start}.`];
|
|
@@ -2600,6 +2642,11 @@ var dev_default = defineDeepspaceCommand({
|
|
|
2600
2642
|
type: "string",
|
|
2601
2643
|
description: `Port to bind (default ${DEFAULT_PORT}, or $DEEPSPACE_PORT)`,
|
|
2602
2644
|
required: false
|
|
2645
|
+
},
|
|
2646
|
+
host: {
|
|
2647
|
+
type: "string",
|
|
2648
|
+
description: `Interface to bind (default ${DEFAULT_DEV_HOST} \u2014 reachable from outside a container)`,
|
|
2649
|
+
required: false
|
|
2603
2650
|
}
|
|
2604
2651
|
},
|
|
2605
2652
|
async run({ args }) {
|
|
@@ -2608,6 +2655,7 @@ var dev_default = defineDeepspaceCommand({
|
|
|
2608
2655
|
};
|
|
2609
2656
|
preflightNodeVersion("dev start");
|
|
2610
2657
|
const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
2658
|
+
const host = typeof args.host === "string" && args.host.trim() ? args.host.trim() : DEFAULT_DEV_HOST;
|
|
2611
2659
|
const start = resolve7(args.dir ?? ".");
|
|
2612
2660
|
const appDir = findAppDir(start);
|
|
2613
2661
|
if (!appDir) {
|
|
@@ -2671,6 +2719,7 @@ var dev_default = defineDeepspaceCommand({
|
|
|
2671
2719
|
say(`Logged in as ${payload.name ?? payload.email}`);
|
|
2672
2720
|
if (wranglerEnv) say(`Wrangler env: ${wranglerEnv}`);
|
|
2673
2721
|
say(`Port: ${port}`);
|
|
2722
|
+
if (host !== DEFAULT_DEV_HOST) say(`Host: ${host}`);
|
|
2674
2723
|
let generatedSecretsCache;
|
|
2675
2724
|
const appIdForSecrets = readAppId(appDir, wranglerEnv);
|
|
2676
2725
|
if (appIdForSecrets) {
|
|
@@ -2698,7 +2747,7 @@ var dev_default = defineDeepspaceCommand({
|
|
|
2698
2747
|
{ sharedDevVarsCache }
|
|
2699
2748
|
);
|
|
2700
2749
|
preflightWindowsWorkerd(appDir);
|
|
2701
|
-
if (
|
|
2750
|
+
if (await isPortListening(port, host)) {
|
|
2702
2751
|
const killArgv = port === DEFAULT_PORT ? ["deepspace", "dev", "kill"] : ["deepspace", "dev", "kill", "--port", String(port)];
|
|
2703
2752
|
const killCmd = killArgv.join(" ");
|
|
2704
2753
|
wranglerConfig.cleanup();
|
|
@@ -2709,12 +2758,34 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
|
|
|
2709
2758
|
{ extra: { port } }
|
|
2710
2759
|
);
|
|
2711
2760
|
}
|
|
2761
|
+
const url = `http://${host === DEFAULT_DEV_HOST ? "localhost" : host}:${port}`;
|
|
2712
2762
|
say("Starting dev server...\n");
|
|
2713
|
-
const vite = spawn(
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2763
|
+
const vite = spawn(
|
|
2764
|
+
"npx",
|
|
2765
|
+
["vite", "--port", String(port), "--strictPort", "--host", host],
|
|
2766
|
+
{
|
|
2767
|
+
cwd: appDir,
|
|
2768
|
+
stdio: "inherit",
|
|
2769
|
+
env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
|
|
2770
|
+
}
|
|
2771
|
+
);
|
|
2772
|
+
if (args.json) {
|
|
2773
|
+
void waitForPortListening(port, host, READINESS_TIMEOUT_MS).then((ready) => {
|
|
2774
|
+
console.log(
|
|
2775
|
+
JSON.stringify(
|
|
2776
|
+
ready ? { ok: true, ready: true, url, port, host, appDir } : {
|
|
2777
|
+
ok: false,
|
|
2778
|
+
code: "dev_server_not_ready",
|
|
2779
|
+
error: `The dev server did not answer on ${url} within ${READINESS_TIMEOUT_MS / 1e3}s.`,
|
|
2780
|
+
url,
|
|
2781
|
+
port,
|
|
2782
|
+
host,
|
|
2783
|
+
appDir
|
|
2784
|
+
}
|
|
2785
|
+
)
|
|
2786
|
+
);
|
|
2787
|
+
});
|
|
2788
|
+
}
|
|
2718
2789
|
let stopping = false;
|
|
2719
2790
|
const stop = () => {
|
|
2720
2791
|
stopping = true;
|
|
@@ -2736,7 +2807,7 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
|
|
|
2736
2807
|
extra: { port, appDir, exitCode: code }
|
|
2737
2808
|
});
|
|
2738
2809
|
}
|
|
2739
|
-
return { data: { port, appDir, wranglerEnv: wranglerEnv ?? null } };
|
|
2810
|
+
return { data: { port, host, url, appDir, wranglerEnv: wranglerEnv ?? null } };
|
|
2740
2811
|
}
|
|
2741
2812
|
});
|
|
2742
2813
|
|
|
@@ -3725,6 +3796,96 @@ import * as p12 from "@clack/prompts";
|
|
|
3725
3796
|
import { defineCommand as defineCommand3 } from "citty";
|
|
3726
3797
|
import { resolve as resolve12 } from "path";
|
|
3727
3798
|
|
|
3799
|
+
// src/cli/lib/edge-propagation.ts
|
|
3800
|
+
var NOT_SERVING = ["Assets have not yet deployed", "No app configured"];
|
|
3801
|
+
async function waitForEdgePropagation(url, timeoutMs) {
|
|
3802
|
+
const deadline = Date.now() + timeoutMs;
|
|
3803
|
+
let attempt = 0;
|
|
3804
|
+
let streak = 0;
|
|
3805
|
+
while (Date.now() < deadline) {
|
|
3806
|
+
attempt++;
|
|
3807
|
+
try {
|
|
3808
|
+
const response = await fetch(url, { redirect: "manual" });
|
|
3809
|
+
const body = await response.text();
|
|
3810
|
+
if (!NOT_SERVING.some((marker) => body.includes(marker))) {
|
|
3811
|
+
streak++;
|
|
3812
|
+
if (streak >= 3) return true;
|
|
3813
|
+
await new Promise((resolve19) => setTimeout(resolve19, 1500));
|
|
3814
|
+
continue;
|
|
3815
|
+
}
|
|
3816
|
+
streak = 0;
|
|
3817
|
+
} catch {
|
|
3818
|
+
streak = 0;
|
|
3819
|
+
}
|
|
3820
|
+
const waitMs = Math.min(8e3, 1e3 * 2 ** (attempt - 1));
|
|
3821
|
+
await new Promise((resolve19) => setTimeout(resolve19, waitMs));
|
|
3822
|
+
}
|
|
3823
|
+
return false;
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3826
|
+
// src/shared/app-files.ts
|
|
3827
|
+
var MAX_APP_FILE_BYTES = 1024 * 1024 * 1024;
|
|
3828
|
+
var MAX_UPLOAD_REQUEST_BYTES = 25 * 1024 * 1024;
|
|
3829
|
+
var UPLOAD_PART_BYTES = 20 * 1024 * 1024;
|
|
3830
|
+
var MAX_UPLOAD_PARTS = Math.ceil(MAX_APP_FILE_BYTES / UPLOAD_PART_BYTES);
|
|
3831
|
+
var MAX_BASE64_UPLOAD_BYTES = Math.floor(MAX_UPLOAD_REQUEST_BYTES * 3 / 4);
|
|
3832
|
+
function planUploadParts(totalBytes, partSize) {
|
|
3833
|
+
const parts = [];
|
|
3834
|
+
for (let start = 0; start < totalBytes; start += partSize) {
|
|
3835
|
+
parts.push({
|
|
3836
|
+
partNumber: parts.length + 1,
|
|
3837
|
+
start,
|
|
3838
|
+
end: Math.min(start + partSize, totalBytes)
|
|
3839
|
+
});
|
|
3840
|
+
}
|
|
3841
|
+
return parts;
|
|
3842
|
+
}
|
|
3843
|
+
var MAX_DEPLOY_ASSET_FILE_BYTES = 25 * 1024 * 1024;
|
|
3844
|
+
var DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
3845
|
+
"text/html",
|
|
3846
|
+
"application/xhtml+xml",
|
|
3847
|
+
"image/svg+xml",
|
|
3848
|
+
"application/xml",
|
|
3849
|
+
"text/xml",
|
|
3850
|
+
"application/javascript",
|
|
3851
|
+
"text/javascript",
|
|
3852
|
+
"application/ecmascript",
|
|
3853
|
+
"text/ecmascript"
|
|
3854
|
+
]);
|
|
3855
|
+
function activeContentMessage(mimeType) {
|
|
3856
|
+
return `${mimeType} is not accepted: the files API serves your app's own origin, so stored HTML/SVG/JavaScript would execute there with your users' sessions. Ship it as a build asset under \`public/\` instead, where it is served as part of your app rather than as user-uploaded content.`;
|
|
3857
|
+
}
|
|
3858
|
+
function formatBytes(bytes) {
|
|
3859
|
+
if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GiB`;
|
|
3860
|
+
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
3861
|
+
if (bytes >= 1024) return `${Math.round(bytes / 1024)} KiB`;
|
|
3862
|
+
return `${bytes} B`;
|
|
3863
|
+
}
|
|
3864
|
+
function encodeKeyPath(key) {
|
|
3865
|
+
return key.split("/").map(encodeURIComponent).join("/");
|
|
3866
|
+
}
|
|
3867
|
+
function oversizeMessage(bytes, limit = MAX_APP_FILE_BYTES) {
|
|
3868
|
+
return `That file is ${formatBytes(bytes)}; the limit is ${formatBytes(limit)} per file.`;
|
|
3869
|
+
}
|
|
3870
|
+
function describeFilesFailure(status3, body) {
|
|
3871
|
+
const trimmed = body.trim();
|
|
3872
|
+
if (trimmed.startsWith("{")) {
|
|
3873
|
+
try {
|
|
3874
|
+
const parsed = JSON.parse(trimmed);
|
|
3875
|
+
if (parsed.error) return parsed.error;
|
|
3876
|
+
} catch {
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
if (status3 === 413) {
|
|
3880
|
+
return `The upload is too large. Files are limited to ${formatBytes(MAX_APP_FILE_BYTES)}, and one request may carry ${formatBytes(MAX_UPLOAD_REQUEST_BYTES)} \u2014 a larger request is rejected by the network before the app receives it.`;
|
|
3881
|
+
}
|
|
3882
|
+
if (status3 === 411) return "The upload did not declare its length.";
|
|
3883
|
+
if (status3 === 401 || status3 === 403) return "Not authorized to write these files.";
|
|
3884
|
+
if (status3 === 415) return "That file type is not accepted.";
|
|
3885
|
+
if (status3 >= 500) return `The files service failed (HTTP ${status3}). Retry shortly.`;
|
|
3886
|
+
return `Upload failed (HTTP ${status3}).`;
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3728
3889
|
// src/cli/commands/deploy/build.ts
|
|
3729
3890
|
import * as p6 from "@clack/prompts";
|
|
3730
3891
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -4044,7 +4205,14 @@ var RESERVED_RUN_WORKER_FIRST = /* @__PURE__ */ new Set([
|
|
|
4044
4205
|
"/_documentation",
|
|
4045
4206
|
"/_documentation/*",
|
|
4046
4207
|
"/_documentation-root",
|
|
4047
|
-
"/_documentation-root/*"
|
|
4208
|
+
"/_documentation-root/*",
|
|
4209
|
+
// Agent-protocol paths: the worker must see them so they cannot fall through
|
|
4210
|
+
// to the SPA shell (templates/base/src/server/http-routes.ts reserves them).
|
|
4211
|
+
"/llms.txt",
|
|
4212
|
+
"/llms-full.txt",
|
|
4213
|
+
"/.well-known/mcp",
|
|
4214
|
+
"/.well-known/mcp.json",
|
|
4215
|
+
"/.well-known/mcp/*"
|
|
4048
4216
|
]);
|
|
4049
4217
|
async function buildDeployBundle(options) {
|
|
4050
4218
|
const { appDir, appName, envName, sharedDevVarsCache, output, spinner: spinner2 } = options;
|
|
@@ -4178,6 +4346,12 @@ function resolveDeployRunWorkerFirst(config) {
|
|
|
4178
4346
|
const routes = extractRunWorkerFirst(config);
|
|
4179
4347
|
return routes;
|
|
4180
4348
|
}
|
|
4349
|
+
function oversizedAssetRefusal(assets) {
|
|
4350
|
+
const tooBig = assets.find((asset) => asset.size > MAX_DEPLOY_ASSET_FILE_BYTES);
|
|
4351
|
+
if (!tooBig) return null;
|
|
4352
|
+
return `${tooBig.path} is ${formatBytes(tooBig.size)}; the per-file limit for deploy assets is ${formatBytes(MAX_DEPLOY_ASSET_FILE_BYTES)}. ${LARGE_MEDIA_HOME}`;
|
|
4353
|
+
}
|
|
4354
|
+
var LARGE_MEDIA_HOME = "Large media belongs in your app files (`deepspace app files put`), not in the deploy bundle \u2014 move it out of `public/` and reference it at runtime.";
|
|
4181
4355
|
function collectAssets(dir) {
|
|
4182
4356
|
const assets = [];
|
|
4183
4357
|
const walk = (currentDir, prefix) => {
|
|
@@ -4670,37 +4844,6 @@ function repoApi(deployUrl, token, appId) {
|
|
|
4670
4844
|
};
|
|
4671
4845
|
}
|
|
4672
4846
|
|
|
4673
|
-
// src/shared/app-files.ts
|
|
4674
|
-
var MAX_APP_FILE_BYTES = 25 * 1024 * 1024;
|
|
4675
|
-
function formatBytes(bytes) {
|
|
4676
|
-
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
4677
|
-
if (bytes >= 1024) return `${Math.round(bytes / 1024)} KiB`;
|
|
4678
|
-
return `${bytes} B`;
|
|
4679
|
-
}
|
|
4680
|
-
function encodeKeyPath(key) {
|
|
4681
|
-
return key.split("/").map(encodeURIComponent).join("/");
|
|
4682
|
-
}
|
|
4683
|
-
function oversizeMessage(bytes) {
|
|
4684
|
-
return `That file is ${formatBytes(bytes)}; the limit is ${formatBytes(MAX_APP_FILE_BYTES)} per file.`;
|
|
4685
|
-
}
|
|
4686
|
-
function describeFilesFailure(status3, body) {
|
|
4687
|
-
const trimmed = body.trim();
|
|
4688
|
-
if (trimmed.startsWith("{")) {
|
|
4689
|
-
try {
|
|
4690
|
-
const parsed = JSON.parse(trimmed);
|
|
4691
|
-
if (parsed.error) return parsed.error;
|
|
4692
|
-
} catch {
|
|
4693
|
-
}
|
|
4694
|
-
}
|
|
4695
|
-
if (status3 === 413) {
|
|
4696
|
-
return `The file is too large to upload. The limit is ${formatBytes(MAX_APP_FILE_BYTES)} per file; larger uploads are rejected by the network before the app receives them.`;
|
|
4697
|
-
}
|
|
4698
|
-
if (status3 === 401 || status3 === 403) return "Not authorized to write these files.";
|
|
4699
|
-
if (status3 === 415) return "That file type is not accepted.";
|
|
4700
|
-
if (status3 >= 500) return `The files service failed (HTTP ${status3}). Retry shortly.`;
|
|
4701
|
-
return `Upload failed (HTTP ${status3}).`;
|
|
4702
|
-
}
|
|
4703
|
-
|
|
4704
4847
|
// src/cli/lib/vc-remote.ts
|
|
4705
4848
|
import { existsSync as existsSync17 } from "fs";
|
|
4706
4849
|
import { tmpdir } from "os";
|
|
@@ -4913,10 +5056,11 @@ function pushToSpace(cwd, token, refspec, opts = {}) {
|
|
|
4913
5056
|
var OVERSIZED_PUSH_RE = /too large|exceeds|\bLFS\b/i;
|
|
4914
5057
|
var SERVER_OBJECT_CAP_BYTES = 20 * 1024 * 1024;
|
|
4915
5058
|
var SERVER_PACK_CAP_BYTES = 32 * 1024 * 1024;
|
|
5059
|
+
var PUSH_CEILINGS = `${formatBytes(SERVER_OBJECT_CAP_BYTES)} per file, ${formatBytes(SERVER_PACK_CAP_BYTES)} per push`;
|
|
4916
5060
|
var OVERSIZED_PUSH_FIX = `Find the offending object with \`git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectsize) %(rest)' | sort -k2 -n | tail\`.`;
|
|
4917
5061
|
function removeFromHistoryAdvice(path) {
|
|
4918
5062
|
const file = path ? shQuote(path) : "<file>";
|
|
4919
|
-
return `Media belongs in the app's files rather than Git history \u2014 \`deepspace app files put ${file}\` stores it in the app's own allocation and serves it over HTTP. Then get it out of the push:
|
|
5063
|
+
return `Media belongs in the app's files rather than Git history \u2014 \`deepspace app files put ${file}\` stores it in the app's own allocation and serves it over HTTP. Then get it out of both the push and the bundle, in this order: (1) move it OUT of \`public/\` \u2014 .gitignore keeps it out of Git but not out of the deploy bundle, which uploads every file under \`public/\`; (2) if the commits that carry it are still local, \`git reset --soft <last pushed commit>\`, then \`git restore --staged ${file}\` \u2014 reset --soft moves HEAD only and leaves the file staged, so skipping this step re-commits the same blob; (3) commit what remains, then push \u2014 if git says "nothing to commit", that commit held only the file, so skip straight to the push and "Everything up-to-date" means you are done. If it already reached pushed history, rewrite it out instead (\`git filter-repo --path ${file} --invert-paths\`).`;
|
|
4920
5064
|
}
|
|
4921
5065
|
function fixForObjects(objects) {
|
|
4922
5066
|
if (objects.length === 0) return `${OVERSIZED_PUSH_FIX} ${removeFromHistoryAdvice(null)}`;
|
|
@@ -5402,7 +5546,7 @@ async function deployBuiltBundle(options) {
|
|
|
5402
5546
|
);
|
|
5403
5547
|
}
|
|
5404
5548
|
if (body.onBehalfOfOwner) {
|
|
5405
|
-
p10.log.warn(
|
|
5549
|
+
p10.log.warn("This release is attributed to the app's owner, not to your account.");
|
|
5406
5550
|
}
|
|
5407
5551
|
if (body.staleBaseGuard === "skipped") {
|
|
5408
5552
|
p10.log.warn(
|
|
@@ -5483,10 +5627,11 @@ async function uploadDeployAssets(options) {
|
|
|
5483
5627
|
p10.log.info(`Assets: ${byHash.size} file(s), all already uploaded`);
|
|
5484
5628
|
return;
|
|
5485
5629
|
}
|
|
5486
|
-
const missingBytes = missing.reduce((
|
|
5630
|
+
const missingBytes = missing.reduce((total2, asset) => total2 + asset.size, 0);
|
|
5487
5631
|
p10.log.info(
|
|
5488
5632
|
`Assets: ${byHash.size} file(s), uploading ${missing.length} (${Math.ceil(missingBytes / 1024)} KiB); the rest are already stored`
|
|
5489
5633
|
);
|
|
5634
|
+
const total = missing.length;
|
|
5490
5635
|
let uploaded = 0;
|
|
5491
5636
|
const next = () => missing.pop();
|
|
5492
5637
|
const worker = async () => {
|
|
@@ -5515,7 +5660,7 @@ async function uploadDeployAssets(options) {
|
|
|
5515
5660
|
);
|
|
5516
5661
|
}
|
|
5517
5662
|
uploaded++;
|
|
5518
|
-
spinner2.message(`Uploading assets \u2014 ${uploaded}/${
|
|
5663
|
+
spinner2.message(`Uploading assets \u2014 ${uploaded}/${total}...`);
|
|
5519
5664
|
}
|
|
5520
5665
|
};
|
|
5521
5666
|
try {
|
|
@@ -5746,7 +5891,11 @@ var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
|
5746
5891
|
var deploy_default = defineCommand3({
|
|
5747
5892
|
meta: {
|
|
5748
5893
|
name: "deploy",
|
|
5749
|
-
|
|
5894
|
+
// The per-file ceiling belongs in --help: it was previously discoverable
|
|
5895
|
+
// only by hitting it, after the build and the push had already run. The
|
|
5896
|
+
// per-deploy total is deliberately absent — it is env-configurable, so any
|
|
5897
|
+
// number stated here would be wrong for some environment.
|
|
5898
|
+
description: `Build and deploy your DeepSpace app (assets: ${formatBytes(MAX_DEPLOY_ASSET_FILE_BYTES)} per file)`
|
|
5750
5899
|
},
|
|
5751
5900
|
args: {
|
|
5752
5901
|
dir: {
|
|
@@ -5842,6 +5991,20 @@ var deploy_default = defineCommand3({
|
|
|
5842
5991
|
output
|
|
5843
5992
|
});
|
|
5844
5993
|
const sourceState = await getAppSource(DEPLOY_URL3, token, appId);
|
|
5994
|
+
const spinner2 = createSpinner();
|
|
5995
|
+
const bundle = await buildDeployBundle({
|
|
5996
|
+
appDir,
|
|
5997
|
+
appName,
|
|
5998
|
+
envName,
|
|
5999
|
+
sharedDevVarsCache: secretsCache.linked !== null,
|
|
6000
|
+
output,
|
|
6001
|
+
spinner: spinner2
|
|
6002
|
+
});
|
|
6003
|
+
const oversized = oversizedAssetRefusal(bundle.assets);
|
|
6004
|
+
if (oversized) {
|
|
6005
|
+
spinner2.stop("Deploy failed");
|
|
6006
|
+
output.die(oversized, "assets_too_large");
|
|
6007
|
+
}
|
|
5845
6008
|
const repository = await syncDeployRepository({
|
|
5846
6009
|
deployUrl: DEPLOY_URL3,
|
|
5847
6010
|
appDir,
|
|
@@ -5852,15 +6015,6 @@ var deploy_default = defineCommand3({
|
|
|
5852
6015
|
output,
|
|
5853
6016
|
sourceState
|
|
5854
6017
|
});
|
|
5855
|
-
const spinner2 = createSpinner();
|
|
5856
|
-
const bundle = await buildDeployBundle({
|
|
5857
|
-
appDir,
|
|
5858
|
-
appName,
|
|
5859
|
-
envName,
|
|
5860
|
-
sharedDevVarsCache: secretsCache.linked !== null,
|
|
5861
|
-
output,
|
|
5862
|
-
spinner: spinner2
|
|
5863
|
-
});
|
|
5864
6018
|
const secrets = prepareDeploySecrets({
|
|
5865
6019
|
appDir,
|
|
5866
6020
|
envName,
|
|
@@ -5886,7 +6040,7 @@ var deploy_default = defineCommand3({
|
|
|
5886
6040
|
});
|
|
5887
6041
|
if (body.url) {
|
|
5888
6042
|
spinner2.message("Waiting for edge propagation...");
|
|
5889
|
-
const ready = await
|
|
6043
|
+
const ready = await waitForEdgePropagation(body.url, 9e4);
|
|
5890
6044
|
if (!ready) {
|
|
5891
6045
|
spinner2.stop("Deployed (edge propagation still in progress after 90s)");
|
|
5892
6046
|
p12.log.warn(
|
|
@@ -5951,30 +6105,6 @@ function blankSelectorRefusal(args) {
|
|
|
5951
6105
|
function staleBaseGuardFields(body) {
|
|
5952
6106
|
return body.staleBaseGuard === "skipped" ? { staleBaseGuard: "skipped" } : {};
|
|
5953
6107
|
}
|
|
5954
|
-
async function waitForAssetsReady(url, timeoutMs) {
|
|
5955
|
-
const deadline = Date.now() + timeoutMs;
|
|
5956
|
-
let attempt = 0;
|
|
5957
|
-
let streak = 0;
|
|
5958
|
-
while (Date.now() < deadline) {
|
|
5959
|
-
attempt++;
|
|
5960
|
-
try {
|
|
5961
|
-
const response = await fetch(url, { redirect: "manual" });
|
|
5962
|
-
const body = await response.text();
|
|
5963
|
-
if (!body.includes("Assets have not yet deployed") && !body.includes("No app configured")) {
|
|
5964
|
-
streak++;
|
|
5965
|
-
if (streak >= 3) return true;
|
|
5966
|
-
await new Promise((resolve19) => setTimeout(resolve19, 1500));
|
|
5967
|
-
continue;
|
|
5968
|
-
}
|
|
5969
|
-
streak = 0;
|
|
5970
|
-
} catch {
|
|
5971
|
-
streak = 0;
|
|
5972
|
-
}
|
|
5973
|
-
const waitMs = Math.min(8e3, 1e3 * 2 ** (attempt - 1));
|
|
5974
|
-
await new Promise((resolve19) => setTimeout(resolve19, waitMs));
|
|
5975
|
-
}
|
|
5976
|
-
return false;
|
|
5977
|
-
}
|
|
5978
6108
|
function errorMessage6(error) {
|
|
5979
6109
|
return error instanceof Error ? error.message : String(error);
|
|
5980
6110
|
}
|
|
@@ -6014,6 +6144,19 @@ function requireAppIdArg(explicit) {
|
|
|
6014
6144
|
}
|
|
6015
6145
|
return id;
|
|
6016
6146
|
}
|
|
6147
|
+
function liveAppUrl(app2) {
|
|
6148
|
+
return app2.status === "active" && app2.deployedAt !== null ? app2.url : null;
|
|
6149
|
+
}
|
|
6150
|
+
async function listApps(deployUrl, token) {
|
|
6151
|
+
const { apps } = await apiFetch(deployUrl, token, "/api/apps");
|
|
6152
|
+
if (!Array.isArray(apps)) {
|
|
6153
|
+
throw new InputError(
|
|
6154
|
+
`The deploy service at ${deployUrl} returned an unexpected response shape for the app list \u2014 check DEEPSPACE_DEPLOY_URL (is this the service the app lives on?).`,
|
|
6155
|
+
"invalid_response"
|
|
6156
|
+
);
|
|
6157
|
+
}
|
|
6158
|
+
return apps;
|
|
6159
|
+
}
|
|
6017
6160
|
function matchAppSelector(apps, selector) {
|
|
6018
6161
|
const byId = apps.find((app2) => app2.appId === selector);
|
|
6019
6162
|
if (byId) return byId.appId;
|
|
@@ -6023,14 +6166,7 @@ function matchAppSelector(apps, selector) {
|
|
|
6023
6166
|
}
|
|
6024
6167
|
async function resolveAppSelector(deployUrl, token, selector) {
|
|
6025
6168
|
if (STRICT_APP_ID_RE.test(selector)) return selector;
|
|
6026
|
-
const
|
|
6027
|
-
if (!Array.isArray(apps)) {
|
|
6028
|
-
throw new InputError(
|
|
6029
|
-
`The deploy service at ${deployUrl} returned an unexpected response shape for the app list \u2014 check DEEPSPACE_DEPLOY_URL (is this the service the app lives on?).`,
|
|
6030
|
-
"invalid_response"
|
|
6031
|
-
);
|
|
6032
|
-
}
|
|
6033
|
-
const id = matchAppSelector(apps, selector);
|
|
6169
|
+
const id = matchAppSelector(await listApps(deployUrl, token), selector);
|
|
6034
6170
|
if (id) return id;
|
|
6035
6171
|
const shared = await resolveAppIdByName(deployUrl, token, selector);
|
|
6036
6172
|
if (shared) return shared;
|
|
@@ -6113,7 +6249,7 @@ function assertAppTargetResolvable(appArg, options = {}) {
|
|
|
6113
6249
|
async function warnIfPhantomApp(deployUrl, token, appId, explicit) {
|
|
6114
6250
|
if (!explicit || !STRICT_APP_ID_RE.test(explicit.trim())) return;
|
|
6115
6251
|
try {
|
|
6116
|
-
const
|
|
6252
|
+
const apps = await listApps(deployUrl, token);
|
|
6117
6253
|
if (!apps.some((app2) => app2.appId === appId)) {
|
|
6118
6254
|
process.stderr.write(
|
|
6119
6255
|
`warning: ${appId} is not among your apps. If it exists and you have access (collaborator/admin), the server will use it; if it doesn't exist, this will register it as a NEW app under your account \u2014 check the id if that's not what you meant (a subdomain name also works with -a).
|
|
@@ -6227,7 +6363,6 @@ var whoami_default = defineDeepspaceCommand({
|
|
|
6227
6363
|
if (!args.json) {
|
|
6228
6364
|
console.log(`Name: ${payload.name ?? "(none)"}`);
|
|
6229
6365
|
console.log(`Email: ${payload.email ?? "(none)"}`);
|
|
6230
|
-
console.log(`UserID: ${payload.sub ?? "(none)"}`);
|
|
6231
6366
|
console.log(`Type: ${accountType}`);
|
|
6232
6367
|
if (payload.iss) console.log(`Issuer: ${payload.iss}`);
|
|
6233
6368
|
console.log(`Dashboard: ${DASHBOARD_URL}`);
|
|
@@ -6259,7 +6394,7 @@ var apps_default = defineDeepspaceCommand({
|
|
|
6259
6394
|
},
|
|
6260
6395
|
async run({ args }) {
|
|
6261
6396
|
const token = await ensureToken();
|
|
6262
|
-
const
|
|
6397
|
+
const apps = await listApps(DEPLOY_URL5, token);
|
|
6263
6398
|
if (!args.json) {
|
|
6264
6399
|
if (!apps.length) {
|
|
6265
6400
|
console.log("No apps yet. Create one with `npx create-deepspace <name>` and `deepspace deploy`.");
|
|
@@ -6267,16 +6402,18 @@ var apps_default = defineDeepspaceCommand({
|
|
|
6267
6402
|
const rows = [...apps].sort(
|
|
6268
6403
|
(left, right) => Number(right.status !== "undeployed") - Number(left.status !== "undeployed")
|
|
6269
6404
|
);
|
|
6405
|
+
const roleOf = (a) => a.role ?? "owner";
|
|
6406
|
+
const roleWidth = Math.max(4, ...rows.map((a) => roleOf(a).length));
|
|
6270
6407
|
const nameWidth = Math.max(4, ...rows.map((a) => (a.name ?? "\u2014").length));
|
|
6271
6408
|
const statusWidth = Math.max(6, ...rows.map((a) => a.status.length));
|
|
6272
6409
|
const idWidth = Math.max(6, ...rows.map((a) => a.appId.length));
|
|
6273
6410
|
console.log(
|
|
6274
|
-
`${"NAME".padEnd(nameWidth)} ${"STATUS".padEnd(statusWidth)} ${"APP ID".padEnd(idWidth)} URL`
|
|
6411
|
+
`${"NAME".padEnd(nameWidth)} ${"STATUS".padEnd(statusWidth)} ${"ROLE".padEnd(roleWidth)} ${"APP ID".padEnd(idWidth)} URL`
|
|
6275
6412
|
);
|
|
6276
6413
|
for (const a of rows) {
|
|
6277
|
-
const url = a
|
|
6414
|
+
const url = liveAppUrl(a) ?? "(not deployed)";
|
|
6278
6415
|
console.log(
|
|
6279
|
-
`${(a.name ?? "\u2014").padEnd(nameWidth)} ${a.status.padEnd(statusWidth)} ${a.appId.padEnd(idWidth)} ${url}`
|
|
6416
|
+
`${(a.name ?? "\u2014").padEnd(nameWidth)} ${a.status.padEnd(statusWidth)} ${roleOf(a).padEnd(roleWidth)} ${a.appId.padEnd(idWidth)} ${url}`
|
|
6280
6417
|
);
|
|
6281
6418
|
}
|
|
6282
6419
|
}
|
|
@@ -7989,11 +8126,16 @@ var CONTENT_TYPES = {
|
|
|
7989
8126
|
".css": "text/css",
|
|
7990
8127
|
".csv": "text/csv",
|
|
7991
8128
|
".gif": "image/gif",
|
|
8129
|
+
".htm": "text/html",
|
|
8130
|
+
".html": "text/html",
|
|
7992
8131
|
".ico": "image/x-icon",
|
|
7993
8132
|
".jpeg": "image/jpeg",
|
|
7994
8133
|
".jpg": "image/jpeg",
|
|
8134
|
+
".js": "text/javascript",
|
|
7995
8135
|
".json": "application/json",
|
|
7996
8136
|
".md": "text/markdown",
|
|
8137
|
+
".mjs": "text/javascript",
|
|
8138
|
+
".svg": "image/svg+xml",
|
|
7997
8139
|
".mp3": "audio/mpeg",
|
|
7998
8140
|
".mp4": "video/mp4",
|
|
7999
8141
|
".otf": "font/otf",
|
|
@@ -8039,7 +8181,10 @@ async function request(baseUrl, token, path, init = {}) {
|
|
|
8039
8181
|
throw new ApiError(
|
|
8040
8182
|
describeFilesFailure(res.status, text2),
|
|
8041
8183
|
res.status,
|
|
8042
|
-
|
|
8184
|
+
// Same slug the worker emits for the same condition — the edge cutting an
|
|
8185
|
+
// oversized request short is not a different failure from the worker
|
|
8186
|
+
// refusing it, and two codes made it look like one.
|
|
8187
|
+
code ?? (res.status === 413 ? "too_large" : res.status >= 500 ? "server_error" : "http_error"),
|
|
8043
8188
|
path
|
|
8044
8189
|
);
|
|
8045
8190
|
}
|
|
@@ -8072,31 +8217,96 @@ Content-Type: ${contentType}\r
|
|
|
8072
8217
|
contentType: `multipart/form-data; boundary=${boundary}`
|
|
8073
8218
|
};
|
|
8074
8219
|
}
|
|
8075
|
-
async function uploadAppFile(baseUrl, token, appId, localPath, key) {
|
|
8220
|
+
async function uploadAppFile(baseUrl, token, appId, localPath, key, onProgress) {
|
|
8076
8221
|
const path = filesPath(appId, `/upload?key=${encodeKeyPath(key)}`);
|
|
8077
8222
|
const size = statSync(localPath).size;
|
|
8078
8223
|
if (size > MAX_APP_FILE_BYTES) {
|
|
8079
|
-
throw new ApiError(oversizeMessage(size), 0, "
|
|
8224
|
+
throw new ApiError(oversizeMessage(size), 0, "too_large", path);
|
|
8080
8225
|
}
|
|
8081
|
-
const
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
)
|
|
8226
|
+
const declaredType = contentTypeFor(localPath);
|
|
8227
|
+
if (DANGEROUS_MIME_TYPES.has(declaredType)) {
|
|
8228
|
+
throw new ApiError(activeContentMessage(declaredType), 0, "active_content", path);
|
|
8229
|
+
}
|
|
8230
|
+
if (size > UPLOAD_PART_BYTES) {
|
|
8231
|
+
return uploadInParts(baseUrl, token, appId, localPath, key, size, onProgress);
|
|
8232
|
+
}
|
|
8233
|
+
const { blob, contentType } = await multipartBody(localPath, basename3(localPath), declaredType);
|
|
8086
8234
|
const res = await request(baseUrl, token, path, {
|
|
8087
8235
|
method: "POST",
|
|
8088
8236
|
headers: { "Content-Type": contentType },
|
|
8089
8237
|
body: blob
|
|
8090
8238
|
});
|
|
8091
8239
|
const body = await readJson(res, path);
|
|
8092
|
-
|
|
8240
|
+
onProgress?.(size, size, 1, 1);
|
|
8241
|
+
return { key: body.key, name: body.name, size, parts: 1 };
|
|
8242
|
+
}
|
|
8243
|
+
function isRetryable(err) {
|
|
8244
|
+
if (!(err instanceof ApiError)) return false;
|
|
8245
|
+
return err.status === 0 || err.status === 429 || err.status >= 500;
|
|
8246
|
+
}
|
|
8247
|
+
async function uploadInParts(baseUrl, token, appId, localPath, key, size, onProgress) {
|
|
8248
|
+
const contentType = contentTypeFor(localPath);
|
|
8249
|
+
const initPath = filesPath(appId, `/multipart?key=${encodeKeyPath(key)}`);
|
|
8250
|
+
const session = await readJson(
|
|
8251
|
+
await request(baseUrl, token, initPath, {
|
|
8252
|
+
method: "POST",
|
|
8253
|
+
headers: { "Content-Type": "application/json" },
|
|
8254
|
+
body: JSON.stringify({ name: basename3(localPath), mimeType: contentType, size })
|
|
8255
|
+
}),
|
|
8256
|
+
initPath
|
|
8257
|
+
);
|
|
8258
|
+
const query = `uploadKey=${encodeKeyPath(session.uploadKey)}&uploadId=${encodeURIComponent(session.uploadId)}`;
|
|
8259
|
+
if (!Number.isSafeInteger(session.partSize) || session.partSize < 1) {
|
|
8260
|
+
throw new ApiError(
|
|
8261
|
+
`The platform advertised an unusable part size (${session.partSize}).`,
|
|
8262
|
+
0,
|
|
8263
|
+
"invalid_response",
|
|
8264
|
+
initPath
|
|
8265
|
+
);
|
|
8266
|
+
}
|
|
8267
|
+
const plan = planUploadParts(size, session.partSize);
|
|
8268
|
+
const file = await openAsBlob(localPath, { type: contentType });
|
|
8269
|
+
const uploaded = [];
|
|
8270
|
+
try {
|
|
8271
|
+
for (const part of plan) {
|
|
8272
|
+
const partPath = filesPath(appId, `/multipart/part?${query}&partNumber=${part.partNumber}`);
|
|
8273
|
+
const send = () => request(baseUrl, token, partPath, {
|
|
8274
|
+
method: "PUT",
|
|
8275
|
+
body: file.slice(part.start, part.end, contentType)
|
|
8276
|
+
});
|
|
8277
|
+
const res = await send().catch(async (err) => {
|
|
8278
|
+
if (!isRetryable(err)) throw err;
|
|
8279
|
+
return send();
|
|
8280
|
+
});
|
|
8281
|
+
uploaded.push(await readJson(res, partPath));
|
|
8282
|
+
onProgress?.(part.end, size, part.partNumber, plan.length);
|
|
8283
|
+
}
|
|
8284
|
+
const completePath = filesPath(appId, `/multipart/complete?${query}`);
|
|
8285
|
+
const body = await readJson(
|
|
8286
|
+
await request(baseUrl, token, completePath, {
|
|
8287
|
+
method: "POST",
|
|
8288
|
+
headers: { "Content-Type": "application/json" },
|
|
8289
|
+
body: JSON.stringify({ parts: uploaded })
|
|
8290
|
+
}),
|
|
8291
|
+
completePath
|
|
8292
|
+
);
|
|
8293
|
+
return { key: body.key, name: body.name, size, parts: plan.length };
|
|
8294
|
+
} catch (err) {
|
|
8295
|
+
await request(baseUrl, token, filesPath(appId, `/multipart?${query}`), {
|
|
8296
|
+
method: "DELETE"
|
|
8297
|
+
}).catch(() => void 0);
|
|
8298
|
+
throw err;
|
|
8299
|
+
}
|
|
8093
8300
|
}
|
|
8094
8301
|
async function listAppFiles(baseUrl, token, appId, query) {
|
|
8095
8302
|
const path = filesPath(appId, `?${query}`);
|
|
8096
8303
|
return readJson(await request(baseUrl, token, path), path);
|
|
8097
8304
|
}
|
|
8098
8305
|
async function deleteAppFile(baseUrl, token, appId, key) {
|
|
8099
|
-
|
|
8306
|
+
const path = filesPath(appId, `/${encodeKeyPath(key)}`);
|
|
8307
|
+
const res = await request(baseUrl, token, path, { method: "DELETE" });
|
|
8308
|
+
const body = await readJson(res, path);
|
|
8309
|
+
return { existed: body.existed !== false };
|
|
8100
8310
|
}
|
|
8101
8311
|
async function downloadAppFile(baseUrl, token, appId, key, destination) {
|
|
8102
8312
|
const path = filesPath(appId, `/${encodeKeyPath(key)}`);
|
|
@@ -8156,7 +8366,10 @@ function servedPath(key) {
|
|
|
8156
8366
|
return `/api/files/${encodeKeyPath(key)}?scope=app`;
|
|
8157
8367
|
}
|
|
8158
8368
|
var put = defineDeepspaceCommand({
|
|
8159
|
-
meta: {
|
|
8369
|
+
meta: {
|
|
8370
|
+
name: "put",
|
|
8371
|
+
description: `Upload a file to the app\u2019s files (${formatBytes(MAX_APP_FILE_BYTES)} per file)`
|
|
8372
|
+
},
|
|
8160
8373
|
args: {
|
|
8161
8374
|
file: { type: "positional", description: "Local file to upload", required: true },
|
|
8162
8375
|
key: { type: "string", description: "Key to store it under (default: the file name)" },
|
|
@@ -8169,12 +8382,42 @@ var put = defineDeepspaceCommand({
|
|
|
8169
8382
|
}
|
|
8170
8383
|
const key = requireKey(args.key ?? basename4(localPath));
|
|
8171
8384
|
const { token, appId } = await target(args.app);
|
|
8172
|
-
const
|
|
8385
|
+
const spinner2 = args.json ? null : createSpinner();
|
|
8386
|
+
let started = false;
|
|
8387
|
+
const result = await uploadAppFile(
|
|
8388
|
+
PLATFORM_URL,
|
|
8389
|
+
token,
|
|
8390
|
+
appId,
|
|
8391
|
+
localPath,
|
|
8392
|
+
key,
|
|
8393
|
+
(sent, total, part, parts) => {
|
|
8394
|
+
if (parts === 1 || !spinner2) return;
|
|
8395
|
+
if (!started) {
|
|
8396
|
+
spinner2.start(`Uploading ${key} in ${parts} parts`);
|
|
8397
|
+
started = true;
|
|
8398
|
+
}
|
|
8399
|
+
spinner2.message(
|
|
8400
|
+
`${key} \u2014 part ${part}/${parts} (${formatBytes(sent)} of ${formatBytes(total)})`
|
|
8401
|
+
);
|
|
8402
|
+
}
|
|
8403
|
+
).finally(() => {
|
|
8404
|
+
if (started) spinner2?.stop();
|
|
8405
|
+
});
|
|
8173
8406
|
if (!args.json) {
|
|
8174
|
-
|
|
8407
|
+
const how = result.parts > 1 ? ` in ${result.parts} parts` : "";
|
|
8408
|
+
console.log(`\u2713 ${key} (${formatBytes(result.size)})${how}`);
|
|
8175
8409
|
console.log(` Served from your app at ${servedPath(result.key)}`);
|
|
8176
8410
|
}
|
|
8177
|
-
return {
|
|
8411
|
+
return {
|
|
8412
|
+
data: {
|
|
8413
|
+
appId,
|
|
8414
|
+
key,
|
|
8415
|
+
storedKey: result.key,
|
|
8416
|
+
size: result.size,
|
|
8417
|
+
parts: result.parts,
|
|
8418
|
+
path: servedPath(result.key)
|
|
8419
|
+
}
|
|
8420
|
+
};
|
|
8178
8421
|
}
|
|
8179
8422
|
});
|
|
8180
8423
|
var list3 = defineDeepspaceCommand({
|
|
@@ -8244,7 +8487,13 @@ var rm = defineDeepspaceCommand({
|
|
|
8244
8487
|
async run({ args }) {
|
|
8245
8488
|
const key = requireKey(String(args.key));
|
|
8246
8489
|
const { token, appId } = await target(args.app);
|
|
8247
|
-
await deleteAppFile(PLATFORM_URL, token, appId, key);
|
|
8490
|
+
const { existed } = await deleteAppFile(PLATFORM_URL, token, appId, key);
|
|
8491
|
+
if (!existed) {
|
|
8492
|
+
throw new Refusal(
|
|
8493
|
+
`No file at ${key} \u2014 nothing was deleted. Check the key with \`deepspace app files list\`.`,
|
|
8494
|
+
"file_not_found"
|
|
8495
|
+
);
|
|
8496
|
+
}
|
|
8248
8497
|
if (!args.json) console.log(`\u2713 deleted ${key}`);
|
|
8249
8498
|
return { data: { appId, key, deleted: true } };
|
|
8250
8499
|
}
|
|
@@ -9795,24 +10044,36 @@ var status_default = defineCommand11({
|
|
|
9795
10044
|
const failure = statusRemoteFailure(error);
|
|
9796
10045
|
lines.push([label, failure.human]);
|
|
9797
10046
|
json[key] = failure.json;
|
|
9798
|
-
},
|
|
10047
|
+
}, reportLive2 = function(rel, entry) {
|
|
10048
|
+
if (rel === null) {
|
|
10049
|
+
lines.push(["Live", "never deployed"]);
|
|
10050
|
+
json.live = null;
|
|
10051
|
+
return;
|
|
10052
|
+
}
|
|
9799
10053
|
const byYou = userId !== null && rel.actor === userId;
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
`release #${rel.seq} (${rel.kind} of ${rel.commitOid?.slice(0, 10) ?? "unknown source"}, ${timeAgo(rel.createdAt)}${byYou ? ", by you" : ""})` + (rel.url ? ` \xB7 ${rel.url}` : "")
|
|
9803
|
-
]);
|
|
9804
|
-
json.live = {
|
|
10054
|
+
const release2 = `release #${rel.seq} (${rel.kind} of ${rel.commitOid?.slice(0, 10) ?? "unknown source"}, ${timeAgo(rel.createdAt)}${byYou ? ", by you" : ""})`;
|
|
10055
|
+
const details = {
|
|
9805
10056
|
seq: rel.seq,
|
|
9806
10057
|
kind: rel.kind,
|
|
9807
10058
|
commitOid: rel.commitOid,
|
|
9808
10059
|
createdAt: rel.createdAt,
|
|
9809
|
-
url: rel.url,
|
|
9810
10060
|
byYou,
|
|
9811
10061
|
source: rel.source,
|
|
9812
10062
|
sourceRevision: rel.sourceRevision
|
|
9813
10063
|
};
|
|
10064
|
+
if (!entry) {
|
|
10065
|
+
lines.push(["Live", `${release2} \xB7 serving state not visible from your account`]);
|
|
10066
|
+
json.live = { ...details, serving: null, url: null };
|
|
10067
|
+
return;
|
|
10068
|
+
}
|
|
10069
|
+
const url = liveAppUrl(entry);
|
|
10070
|
+
lines.push([
|
|
10071
|
+
"Live",
|
|
10072
|
+
url ? `${release2} \xB7 ${url}` : `not serving \u2014 ${entry.status}, last ${release2}`
|
|
10073
|
+
]);
|
|
10074
|
+
json.live = { ...details, serving: url !== null, url };
|
|
9814
10075
|
};
|
|
9815
|
-
var reportWorkspace = reportWorkspace2, reportRemoteFailure = reportRemoteFailure2,
|
|
10076
|
+
var reportWorkspace = reportWorkspace2, reportRemoteFailure = reportRemoteFailure2, reportLive = reportLive2;
|
|
9816
10077
|
const api4 = repoApi(deployBaseUrl(), token, appId);
|
|
9817
10078
|
const sourceResult = await observeRemote(getAppSource(deployBaseUrl(), token, appId));
|
|
9818
10079
|
const source = sourceResult.ok ? sourceResult.value.source : null;
|
|
@@ -9832,10 +10093,11 @@ var status_default = defineCommand11({
|
|
|
9832
10093
|
lines.push(["Source", "unclaimed \xB7 next normal deploy defaults to DeepSpace"]);
|
|
9833
10094
|
json.source = null;
|
|
9834
10095
|
}
|
|
9835
|
-
const [refsResult, wsResult, releaseResult] = await Promise.all([
|
|
10096
|
+
const [refsResult, wsResult, releaseResult, appsResult] = await Promise.all([
|
|
9836
10097
|
githubSource ? Promise.resolve({ ok: true, value: null }) : observeRemote(api4.getRefs()),
|
|
9837
10098
|
facts.workspaceId && !githubSource ? observeRemote(api4.getWorkspace(facts.workspaceId).then((r) => r.view)) : Promise.resolve({ ok: true, value: null }),
|
|
9838
|
-
observeRemote(api4.latestRelease().then((r) => r.release))
|
|
10099
|
+
observeRemote(api4.latestRelease().then((r) => r.release)),
|
|
10100
|
+
observeRemote(listApps(deployBaseUrl(), token))
|
|
9839
10101
|
]);
|
|
9840
10102
|
if (facts.workspaceId) {
|
|
9841
10103
|
if (githubSource) {
|
|
@@ -9864,12 +10126,8 @@ var status_default = defineCommand11({
|
|
|
9864
10126
|
}
|
|
9865
10127
|
}
|
|
9866
10128
|
if (!releaseResult.ok) reportRemoteFailure2("Live", "live", releaseResult.error);
|
|
9867
|
-
else if (
|
|
9868
|
-
|
|
9869
|
-
json.live = null;
|
|
9870
|
-
} else {
|
|
9871
|
-
reportRelease2(releaseResult.value);
|
|
9872
|
-
}
|
|
10129
|
+
else if (!appsResult.ok) reportRemoteFailure2("Live", "live", appsResult.error);
|
|
10130
|
+
else reportLive2(releaseResult.value, appsResult.value.find((a) => a.appId === appId));
|
|
9873
10131
|
}
|
|
9874
10132
|
spinner2?.stop("Cloud state checked.");
|
|
9875
10133
|
}
|
|
@@ -9912,7 +10170,7 @@ function workspaceBranchPushRefusal(branch) {
|
|
|
9912
10170
|
var push_default = defineDeepspaceCommand({
|
|
9913
10171
|
meta: {
|
|
9914
10172
|
name: "push",
|
|
9915
|
-
description:
|
|
10173
|
+
description: `Push local git commits to the app's cloud repo (${PUSH_CEILINGS})`
|
|
9916
10174
|
},
|
|
9917
10175
|
args: {
|
|
9918
10176
|
branch: {
|
|
@@ -10493,6 +10751,27 @@ var clone_default = defineDeepspaceCommand({
|
|
|
10493
10751
|
|
|
10494
10752
|
// src/cli/commands/releases.ts
|
|
10495
10753
|
import * as p21 from "@clack/prompts";
|
|
10754
|
+
|
|
10755
|
+
// src/cli/lib/actor-labels.ts
|
|
10756
|
+
var API_URL9 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
10757
|
+
async function actorLabels(token, appId) {
|
|
10758
|
+
const labels = /* @__PURE__ */ new Map();
|
|
10759
|
+
try {
|
|
10760
|
+
const { sub, email } = decodeJwtPayload(token);
|
|
10761
|
+
if (sub && email) labels.set(sub, `${email} (you)`);
|
|
10762
|
+
} catch {
|
|
10763
|
+
}
|
|
10764
|
+
try {
|
|
10765
|
+
const { collaborators } = await apiFetch(API_URL9, token, `/api/app-collaborators/${encodeURIComponent(appId)}`);
|
|
10766
|
+
for (const c of collaborators) {
|
|
10767
|
+
if (!labels.has(c.userId)) labels.set(c.userId, c.emailDisplay);
|
|
10768
|
+
}
|
|
10769
|
+
} catch {
|
|
10770
|
+
}
|
|
10771
|
+
return labels;
|
|
10772
|
+
}
|
|
10773
|
+
|
|
10774
|
+
// src/cli/commands/releases.ts
|
|
10496
10775
|
var DEPLOY_URL12 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
10497
10776
|
var releases_default = defineDeepspaceCommand({
|
|
10498
10777
|
meta: {
|
|
@@ -10535,11 +10814,12 @@ var releases_default = defineDeepspaceCommand({
|
|
|
10535
10814
|
if (releases.length === 0) {
|
|
10536
10815
|
p21.log.info("No releases yet \u2014 the history starts with the next `deepspace deploy`.");
|
|
10537
10816
|
} else {
|
|
10817
|
+
const actors = await actorLabels(token, appId);
|
|
10538
10818
|
for (const r of releases) {
|
|
10539
10819
|
const source = r.commitOid ? `commit ${r.commitOid.slice(0, 10)}` : "(no source recorded)";
|
|
10540
10820
|
const rollback = r.rollbackAvailable ? "rollback available" : "rollback unavailable";
|
|
10541
10821
|
console.log(
|
|
10542
|
-
`#${r.seq} ${r.id} ${r.kind.padEnd(8)} ${r.createdAt} ${r.actor} ${source} ${rollback}`
|
|
10822
|
+
`#${r.seq} ${r.id} ${r.kind.padEnd(8)} ${r.createdAt} ${actors.get(r.actor) ?? r.actor} ${source} ${rollback}`
|
|
10543
10823
|
);
|
|
10544
10824
|
}
|
|
10545
10825
|
p21.log.info(
|
|
@@ -10662,10 +10942,20 @@ Re-run with --allow-do-deletion to accept the data loss.`,
|
|
|
10662
10942
|
"invalid_response"
|
|
10663
10943
|
);
|
|
10664
10944
|
}
|
|
10665
|
-
|
|
10945
|
+
let edgePropagating = false;
|
|
10946
|
+
if (result.url) {
|
|
10947
|
+
spinner2?.message("Waiting for edge propagation...");
|
|
10948
|
+
edgePropagating = !await waitForEdgePropagation(result.url, 9e4);
|
|
10949
|
+
}
|
|
10950
|
+
spinner2?.stop(
|
|
10951
|
+
edgePropagating ? `Rolled back to ${result.rolledBackTo ?? releaseId} (edge propagation still in progress after 90s).` : `Rolled back to ${result.rolledBackTo ?? releaseId}.`
|
|
10952
|
+
);
|
|
10666
10953
|
const doClassGuard = result.doClassGuard === "unverified" ? "unverified" : void 0;
|
|
10667
10954
|
if (!args.json) {
|
|
10668
|
-
if (result.url)
|
|
10955
|
+
if (result.url) {
|
|
10956
|
+
if (edgePropagating) p22.log.warn(`URL: ${result.url} (accepted; serving not yet verified)`);
|
|
10957
|
+
else p22.log.success(`Live at: ${result.url}`);
|
|
10958
|
+
}
|
|
10669
10959
|
if (result.bundleRetained === false) {
|
|
10670
10960
|
p22.log.warn(
|
|
10671
10961
|
"The rollback is live, but its bundle was concurrently retired and is not available for a future rollback."
|
|
@@ -10689,6 +10979,7 @@ Re-run with --allow-do-deletion to accept the data loss.`,
|
|
|
10689
10979
|
url: result.url ?? null,
|
|
10690
10980
|
versionId: result.versionId ?? null,
|
|
10691
10981
|
wranglerEnv: wranglerEnv ?? null,
|
|
10982
|
+
...edgePropagating ? { edgePropagating: true } : {},
|
|
10692
10983
|
...doClassGuard ? { doClassGuard } : {}
|
|
10693
10984
|
}
|
|
10694
10985
|
};
|
|
@@ -11573,27 +11864,6 @@ var syncWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11573
11864
|
|
|
11574
11865
|
// src/cli/commands/workspace/list.ts
|
|
11575
11866
|
import * as p28 from "@clack/prompts";
|
|
11576
|
-
|
|
11577
|
-
// src/cli/lib/actor-labels.ts
|
|
11578
|
-
var API_URL9 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
11579
|
-
async function actorLabels(token, appId) {
|
|
11580
|
-
const labels = /* @__PURE__ */ new Map();
|
|
11581
|
-
try {
|
|
11582
|
-
const { sub, email } = decodeJwtPayload(token);
|
|
11583
|
-
if (sub && email) labels.set(sub, `${email} (you)`);
|
|
11584
|
-
} catch {
|
|
11585
|
-
}
|
|
11586
|
-
try {
|
|
11587
|
-
const { collaborators } = await apiFetch(API_URL9, token, `/api/app-collaborators/${encodeURIComponent(appId)}`);
|
|
11588
|
-
for (const c of collaborators) {
|
|
11589
|
-
if (!labels.has(c.userId)) labels.set(c.userId, c.emailDisplay);
|
|
11590
|
-
}
|
|
11591
|
-
} catch {
|
|
11592
|
-
}
|
|
11593
|
-
return labels;
|
|
11594
|
-
}
|
|
11595
|
-
|
|
11596
|
-
// src/cli/commands/workspace/list.ts
|
|
11597
11867
|
function withWorkspaceOverlaps(views, overlapsById) {
|
|
11598
11868
|
return views.map((view) => ({
|
|
11599
11869
|
...view,
|
|
@@ -13363,7 +13633,7 @@ function commandTree() {
|
|
|
13363
13633
|
return main.subCommands ?? {};
|
|
13364
13634
|
}
|
|
13365
13635
|
function unknownCommandMessage(unknown) {
|
|
13366
|
-
return `Unknown command: ${unknown.attemptedPath
|
|
13636
|
+
return `Unknown command: ${humanCommand(unknown.attemptedPath)} \u2014 did you mean \`${humanCommand(unknown.suggestion)}\`?` + (unknown.quotedToken ? " The whole command arrived as one quoted argument; pass each word separately." : "");
|
|
13367
13637
|
}
|
|
13368
13638
|
function assertKnownCommandPath(argv) {
|
|
13369
13639
|
const unknown = findUnknownCommand(argv, commandTree());
|
|
@@ -13372,7 +13642,7 @@ function assertKnownCommandPath(argv) {
|
|
|
13372
13642
|
`${unknownCommandMessage(unknown)}
|
|
13373
13643
|
Run \`${unknown.helpPath.join(" ")} --help\` for the command list.`
|
|
13374
13644
|
);
|
|
13375
|
-
printAction({ cwd: process.cwd(), argv: unknown.suggestion });
|
|
13645
|
+
if (unknown.executable) printAction({ cwd: process.cwd(), argv: unknown.suggestion });
|
|
13376
13646
|
process.exit(1);
|
|
13377
13647
|
}
|
|
13378
13648
|
var rawArgs = process.argv.slice(2);
|
|
@@ -13394,7 +13664,7 @@ if (rawArgs.includes("--json") && !wantsHelp) {
|
|
|
13394
13664
|
ok: false,
|
|
13395
13665
|
code: "unknown_command",
|
|
13396
13666
|
error: unknownCommandMessage(unknown),
|
|
13397
|
-
action: { cwd: process.cwd(), argv: unknown.suggestion }
|
|
13667
|
+
...unknown.executable ? { action: { cwd: process.cwd(), argv: unknown.suggestion } } : {}
|
|
13398
13668
|
})
|
|
13399
13669
|
);
|
|
13400
13670
|
process.exit(1);
|
|
@@ -13403,16 +13673,20 @@ if (rawArgs.includes("--json") && !wantsHelp) {
|
|
|
13403
13673
|
stopActiveSpinner();
|
|
13404
13674
|
const cittyCode = err instanceof Error && err.name === "CLIError" ? err.code : void 0;
|
|
13405
13675
|
const code = cittyCode === "EARG" ? "missing_argument" : cittyCode === "E_UNKNOWN_COMMAND" ? "unknown_command" : cittyCode === "E_NO_COMMAND" ? "no_command" : errorCode(err);
|
|
13406
|
-
const message = (err instanceof Error ? err.message : String(err))
|
|
13407
|
-
// eslint-disable-next-line no-control-regex
|
|
13408
|
-
/\u001b\[[0-9;]*m/g,
|
|
13409
|
-
""
|
|
13410
|
-
);
|
|
13676
|
+
const message = stripAnsi(err instanceof Error ? err.message : String(err));
|
|
13411
13677
|
console.log(JSON.stringify({ ok: false, error: message, ...code ? { code } : {} }));
|
|
13412
13678
|
process.exit(1);
|
|
13413
13679
|
});
|
|
13414
13680
|
} else {
|
|
13415
13681
|
assertKnownCommandPath(rawArgs);
|
|
13416
|
-
runMain(wrapCommandErrors(main)
|
|
13682
|
+
runMain(wrapCommandErrors(main), {
|
|
13683
|
+
showUsage: async (cmd, parent) => {
|
|
13684
|
+
try {
|
|
13685
|
+
console.log(forTerminal(await renderUsage(cmd, parent)) + "\n");
|
|
13686
|
+
} catch (error) {
|
|
13687
|
+
console.error(error);
|
|
13688
|
+
}
|
|
13689
|
+
}
|
|
13690
|
+
});
|
|
13417
13691
|
}
|
|
13418
13692
|
//# sourceMappingURL=cli.js.map
|