deepspace 0.13.0 → 0.15.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 +33 -0
- package/dist/cli.js +426 -707
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +50 -5
- package/dist/server.js +95 -12
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +125 -1
- package/dist/worker.js +177 -12
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// src/cli/cli.ts
|
|
4
4
|
import { defineCommand as defineCommand14, runMain, runCommand, renderUsage } from "citty";
|
|
5
5
|
import { sync as spawnSync8 } from "cross-spawn";
|
|
6
|
-
import { readFileSync as
|
|
7
|
-
import { dirname as dirname10, join as
|
|
6
|
+
import { readFileSync as readFileSync29, existsSync as existsSync27 } from "fs";
|
|
7
|
+
import { dirname as dirname10, join as join25 } from "path";
|
|
8
8
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9
9
|
|
|
10
10
|
// src/cli/lib/spinner.ts
|
|
@@ -89,11 +89,12 @@ function printAction(action) {
|
|
|
89
89
|
|
|
90
90
|
// src/cli/lib/api.ts
|
|
91
91
|
var ApiError = class extends Error {
|
|
92
|
-
constructor(message, status3, code, apiPath) {
|
|
92
|
+
constructor(message, status3, code, apiPath, details) {
|
|
93
93
|
super(message);
|
|
94
94
|
this.status = status3;
|
|
95
95
|
this.code = code;
|
|
96
96
|
this.apiPath = apiPath;
|
|
97
|
+
this.details = details;
|
|
97
98
|
}
|
|
98
99
|
};
|
|
99
100
|
var refreshAuthToken = null;
|
|
@@ -387,6 +388,9 @@ var Refusal = class extends Error {
|
|
|
387
388
|
this.extra = opts.extra ?? {};
|
|
388
389
|
}
|
|
389
390
|
};
|
|
391
|
+
function apiErrorDetails(err) {
|
|
392
|
+
return err instanceof ApiError ? err.details : void 0;
|
|
393
|
+
}
|
|
390
394
|
function withoutReservedKeys(payload) {
|
|
391
395
|
return Object.fromEntries(
|
|
392
396
|
Object.entries(payload).filter(
|
|
@@ -450,7 +454,7 @@ function defineDeepspaceCommand(def) {
|
|
|
450
454
|
...refusal?.actionRequired ? { actionRequired: true } : {},
|
|
451
455
|
error: message,
|
|
452
456
|
...refusal?.action ? { action: refusal.action } : {},
|
|
453
|
-
...withoutReservedKeys(refusal?.extra ?? {})
|
|
457
|
+
...withoutReservedKeys(refusal?.extra ?? apiErrorDetails(err) ?? {})
|
|
454
458
|
})
|
|
455
459
|
);
|
|
456
460
|
} else {
|
|
@@ -734,7 +738,7 @@ async function provisionProfile(jwt) {
|
|
|
734
738
|
}
|
|
735
739
|
}
|
|
736
740
|
function sleep(ms) {
|
|
737
|
-
return new Promise((
|
|
741
|
+
return new Promise((resolve20) => setTimeout(resolve20, ms));
|
|
738
742
|
}
|
|
739
743
|
function base64url(bytes) {
|
|
740
744
|
return bytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
@@ -1305,7 +1309,7 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
|
|
|
1305
1309
|
let port = opts.port;
|
|
1306
1310
|
if (opts.probePort) {
|
|
1307
1311
|
const taken = new Set(
|
|
1308
|
-
config.configurations.map((c) => Number(c?.port)).filter((
|
|
1312
|
+
config.configurations.map((c) => Number(c?.port)).filter((p35) => Number.isInteger(p35) && p35 > 0)
|
|
1309
1313
|
);
|
|
1310
1314
|
for (let i = 0; i < WORKTREE_PORT_RANGE && taken.has(port); i++) {
|
|
1311
1315
|
port = WORKTREE_PORT_MIN + (port - WORKTREE_PORT_MIN + 1) % WORKTREE_PORT_RANGE;
|
|
@@ -2328,18 +2332,18 @@ function ensureInstallReady(appDir) {
|
|
|
2328
2332
|
const logPath = join9(sentinelDir, "install.log");
|
|
2329
2333
|
if (existsSync9(errPath)) {
|
|
2330
2334
|
const detail = readFileSync11(errPath, "utf-8").trimEnd();
|
|
2331
|
-
const
|
|
2335
|
+
const log29 = existsSync9(logPath) ? ` Full log: ${logPath}.` : "";
|
|
2332
2336
|
throw new Refusal(
|
|
2333
|
-
`Dependency install failed: ${detail}.${
|
|
2337
|
+
`Dependency install failed: ${detail}.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
|
|
2334
2338
|
"install_failed",
|
|
2335
2339
|
{ action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
|
|
2336
2340
|
);
|
|
2337
2341
|
}
|
|
2338
2342
|
if (existsSync9(startedPath) && !existsSync9(donePath)) {
|
|
2339
2343
|
if (!installWorkerAlive(sentinelDir)) {
|
|
2340
|
-
const
|
|
2344
|
+
const log29 = existsSync9(logPath) ? ` See what happened: ${logPath}.` : "";
|
|
2341
2345
|
throw new Refusal(
|
|
2342
|
-
`The dependency install is no longer running and never finished.${
|
|
2346
|
+
`The dependency install is no longer running and never finished.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
|
|
2343
2347
|
"install_failed",
|
|
2344
2348
|
{ action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
|
|
2345
2349
|
);
|
|
@@ -2488,7 +2492,7 @@ function lintSchema(schema) {
|
|
|
2488
2492
|
const offendingRoles = Object.entries(schema.permissions).filter(([, perms]) => perms.read === true).map(([role]) => role);
|
|
2489
2493
|
if (offendingRoles.length > 0) {
|
|
2490
2494
|
const usesAware = Object.values(schema.permissions).some(
|
|
2491
|
-
(
|
|
2495
|
+
(p35) => typeof p35.read === "string" && VISIBILITY_AWARE.includes(p35.read)
|
|
2492
2496
|
);
|
|
2493
2497
|
if (!usesAware) {
|
|
2494
2498
|
warnings.push(
|
|
@@ -2573,11 +2577,11 @@ import { createServer, Socket } from "net";
|
|
|
2573
2577
|
var DEFAULT_PORT = 5173;
|
|
2574
2578
|
function isPortListening(port, host, timeoutMs = 500) {
|
|
2575
2579
|
const target2 = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
2576
|
-
return new Promise((
|
|
2580
|
+
return new Promise((resolve20) => {
|
|
2577
2581
|
const socket = new Socket();
|
|
2578
2582
|
const done = (listening) => {
|
|
2579
2583
|
socket.destroy();
|
|
2580
|
-
|
|
2584
|
+
resolve20(listening);
|
|
2581
2585
|
};
|
|
2582
2586
|
socket.setTimeout(timeoutMs);
|
|
2583
2587
|
socket.once("connect", () => done(true));
|
|
@@ -2590,7 +2594,7 @@ async function waitForPortListening(port, host, timeoutMs) {
|
|
|
2590
2594
|
const deadline = Date.now() + timeoutMs;
|
|
2591
2595
|
while (Date.now() < deadline) {
|
|
2592
2596
|
if (await isPortListening(port, host)) return true;
|
|
2593
|
-
await new Promise((
|
|
2597
|
+
await new Promise((resolve20) => setTimeout(resolve20, 250));
|
|
2594
2598
|
}
|
|
2595
2599
|
return false;
|
|
2596
2600
|
}
|
|
@@ -3797,30 +3801,97 @@ import { defineCommand as defineCommand3 } from "citty";
|
|
|
3797
3801
|
import { resolve as resolve12 } from "path";
|
|
3798
3802
|
|
|
3799
3803
|
// src/cli/lib/edge-propagation.ts
|
|
3800
|
-
|
|
3801
|
-
|
|
3804
|
+
import { request as httpRequest } from "http";
|
|
3805
|
+
import { request as httpsRequest } from "https";
|
|
3806
|
+
|
|
3807
|
+
// src/shared/app-routing.ts
|
|
3808
|
+
var RELEASE_STAMP_PATH = "/.well-known/deepspace/release.json";
|
|
3809
|
+
var SDK_RUN_WORKER_FIRST = [
|
|
3810
|
+
"/api/*",
|
|
3811
|
+
"/ws/*",
|
|
3812
|
+
"/internal/*",
|
|
3813
|
+
"/v1/*",
|
|
3814
|
+
"/_deepspace/*",
|
|
3815
|
+
"/llms.txt",
|
|
3816
|
+
"/llms-full.txt",
|
|
3817
|
+
"/.well-known/mcp",
|
|
3818
|
+
"/.well-known/mcp.json",
|
|
3819
|
+
"/.well-known/mcp/*"
|
|
3820
|
+
];
|
|
3821
|
+
var DOCUMENTATION_PATHS = [
|
|
3822
|
+
"/_documentation",
|
|
3823
|
+
"/_documentation/*",
|
|
3824
|
+
"/_documentation-root",
|
|
3825
|
+
"/_documentation-root/*"
|
|
3826
|
+
];
|
|
3827
|
+
var RESERVED_RUN_WORKER_FIRST = [
|
|
3828
|
+
...SDK_RUN_WORKER_FIRST,
|
|
3829
|
+
...DOCUMENTATION_PATHS
|
|
3830
|
+
];
|
|
3831
|
+
|
|
3832
|
+
// src/cli/lib/edge-propagation.ts
|
|
3833
|
+
var CONSECUTIVE_MATCHES = 10;
|
|
3834
|
+
var MATCH_INTERVAL_MS = 250;
|
|
3835
|
+
async function waitForLiveRelease(url, releaseStamp, timeoutMs) {
|
|
3836
|
+
if (!releaseStamp) return "unverifiable";
|
|
3802
3837
|
const deadline = Date.now() + timeoutMs;
|
|
3803
3838
|
let attempt = 0;
|
|
3804
3839
|
let streak = 0;
|
|
3805
3840
|
while (Date.now() < deadline) {
|
|
3806
3841
|
attempt++;
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
continue;
|
|
3815
|
-
}
|
|
3816
|
-
streak = 0;
|
|
3817
|
-
} catch {
|
|
3818
|
-
streak = 0;
|
|
3842
|
+
const probe = new URL(RELEASE_STAMP_PATH, url);
|
|
3843
|
+
probe.searchParams.set("probe", `${Date.now()}-${attempt}`);
|
|
3844
|
+
if (await servesRelease(probe, releaseStamp)) {
|
|
3845
|
+
streak++;
|
|
3846
|
+
if (streak >= CONSECUTIVE_MATCHES) return "confirmed";
|
|
3847
|
+
await sleep2(MATCH_INTERVAL_MS);
|
|
3848
|
+
continue;
|
|
3819
3849
|
}
|
|
3820
|
-
|
|
3821
|
-
await
|
|
3850
|
+
streak = 0;
|
|
3851
|
+
await sleep2(Math.min(4e3, 500 * 2 ** Math.min(attempt - 1, 3)));
|
|
3852
|
+
}
|
|
3853
|
+
return "unconfirmed";
|
|
3854
|
+
}
|
|
3855
|
+
async function servesRelease(probe, releaseStamp) {
|
|
3856
|
+
const body = await getOnFreshConnection(probe);
|
|
3857
|
+
if (body === null) return false;
|
|
3858
|
+
try {
|
|
3859
|
+
return JSON.parse(body).release === releaseStamp;
|
|
3860
|
+
} catch {
|
|
3861
|
+
return false;
|
|
3822
3862
|
}
|
|
3823
|
-
|
|
3863
|
+
}
|
|
3864
|
+
function getOnFreshConnection(url, timeoutMs = 5e3) {
|
|
3865
|
+
const send = url.protocol === "http:" ? httpRequest : httpsRequest;
|
|
3866
|
+
return new Promise((resolve20) => {
|
|
3867
|
+
const request2 = send(
|
|
3868
|
+
url,
|
|
3869
|
+
{ method: "GET", agent: false, timeout: timeoutMs, headers: { "cache-control": "no-cache" } },
|
|
3870
|
+
(response) => {
|
|
3871
|
+
if (response.statusCode !== 200) {
|
|
3872
|
+
response.resume();
|
|
3873
|
+
resolve20(null);
|
|
3874
|
+
return;
|
|
3875
|
+
}
|
|
3876
|
+
let body = "";
|
|
3877
|
+
response.setEncoding("utf8");
|
|
3878
|
+
response.on("data", (chunk) => {
|
|
3879
|
+
if (body.length < 4096) body += chunk;
|
|
3880
|
+
});
|
|
3881
|
+
response.on("end", () => resolve20(body));
|
|
3882
|
+
response.on("error", () => resolve20(null));
|
|
3883
|
+
}
|
|
3884
|
+
);
|
|
3885
|
+
request2.on("timeout", () => {
|
|
3886
|
+
request2.destroy();
|
|
3887
|
+
resolve20(null);
|
|
3888
|
+
});
|
|
3889
|
+
request2.on("error", () => resolve20(null));
|
|
3890
|
+
request2.end();
|
|
3891
|
+
});
|
|
3892
|
+
}
|
|
3893
|
+
function sleep2(ms) {
|
|
3894
|
+
return new Promise((resolve20) => setTimeout(resolve20, ms));
|
|
3824
3895
|
}
|
|
3825
3896
|
|
|
3826
3897
|
// src/shared/app-files.ts
|
|
@@ -3840,6 +3911,13 @@ function planUploadParts(totalBytes, partSize) {
|
|
|
3840
3911
|
}
|
|
3841
3912
|
return parts;
|
|
3842
3913
|
}
|
|
3914
|
+
var APP_STORAGE_LIMIT_BYTES = {
|
|
3915
|
+
test: 0,
|
|
3916
|
+
free: 128 * 1024 * 1024,
|
|
3917
|
+
starter: 512 * 1024 * 1024,
|
|
3918
|
+
premium: 2 * 1024 * 1024 * 1024,
|
|
3919
|
+
admin: 10 * 1024 * 1024 * 1024
|
|
3920
|
+
};
|
|
3843
3921
|
var MAX_DEPLOY_ASSET_FILE_BYTES = 25 * 1024 * 1024;
|
|
3844
3922
|
var DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
3845
3923
|
"text/html",
|
|
@@ -3907,7 +3985,7 @@ function readDocumentationDeployManifest(appDir, clientDir) {
|
|
|
3907
3985
|
return JSON.parse(readFileSync15(manifestPath, "utf8"));
|
|
3908
3986
|
}
|
|
3909
3987
|
|
|
3910
|
-
// src/cli/commands/
|
|
3988
|
+
// src/cli/commands/update/app-migrations.ts
|
|
3911
3989
|
import {
|
|
3912
3990
|
closeSync,
|
|
3913
3991
|
constants,
|
|
@@ -3924,7 +4002,7 @@ import { join as join15 } from "path";
|
|
|
3924
4002
|
// src/shared/protocol/app-migrations.ts
|
|
3925
4003
|
var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
3926
4004
|
var MAX_APP_MIGRATION_IDS = 256;
|
|
3927
|
-
var
|
|
4005
|
+
var WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
|
|
3928
4006
|
function validateAppMigrationIds(value) {
|
|
3929
4007
|
if (!Array.isArray(value)) {
|
|
3930
4008
|
return { valid: false, reason: "app migrations must be a JSON string array" };
|
|
@@ -3950,14 +4028,14 @@ function validateAppMigrationIds(value) {
|
|
|
3950
4028
|
return { valid: true, ids };
|
|
3951
4029
|
}
|
|
3952
4030
|
|
|
3953
|
-
// src/cli/commands/
|
|
4031
|
+
// src/cli/commands/update/app-migrations.ts
|
|
3954
4032
|
var APP_MIGRATIONS_MANIFEST = "deepspace.migrations.json";
|
|
3955
4033
|
var SOURCE_MIGRATIONS = [
|
|
3956
4034
|
{
|
|
3957
|
-
id:
|
|
3958
|
-
description: "
|
|
3959
|
-
transform:
|
|
3960
|
-
findBlockers:
|
|
4035
|
+
id: WORKER_OWNED_NOT_FOUND_MIGRATION_ID,
|
|
4036
|
+
description: "Let the worker answer misses, so deleted files 404 instead of serving HTML",
|
|
4037
|
+
transform: migrateWorkerOwnedNotFound,
|
|
4038
|
+
findBlockers: findWorkerOwnedNotFoundBlockers
|
|
3961
4039
|
}
|
|
3962
4040
|
];
|
|
3963
4041
|
var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
|
|
@@ -4002,7 +4080,7 @@ function planAppMigrations(appDir) {
|
|
|
4002
4080
|
const before = readRegularFile(absolutePath);
|
|
4003
4081
|
let after = before;
|
|
4004
4082
|
for (const migration of pendingMigrations) {
|
|
4005
|
-
const result = migration.transform(after);
|
|
4083
|
+
const result = migration.transform(after, repoPath);
|
|
4006
4084
|
after = result.source;
|
|
4007
4085
|
if (result.replacements > 0) {
|
|
4008
4086
|
const planned = perMigration.get(migration.id) ?? {
|
|
@@ -4039,9 +4117,6 @@ function planAppMigrations(appDir) {
|
|
|
4039
4117
|
};
|
|
4040
4118
|
}
|
|
4041
4119
|
function applyAppMigrationPlan(appDir, plan) {
|
|
4042
|
-
if (plan.blockers.length > 0) {
|
|
4043
|
-
throw new Error("Cannot apply an app migration plan with unresolved blockers.");
|
|
4044
|
-
}
|
|
4045
4120
|
const repo = repoToplevel(appDir);
|
|
4046
4121
|
for (const edit of plan.edits) {
|
|
4047
4122
|
const absolutePath = join15(repo, edit.path);
|
|
@@ -4101,119 +4176,78 @@ function writeRegularFile(path, before, after) {
|
|
|
4101
4176
|
}
|
|
4102
4177
|
function isSourcePath(repoPath, appPrefix) {
|
|
4103
4178
|
if (appPrefix && !repoPath.startsWith(appPrefix)) return false;
|
|
4179
|
+
if (repoPath.endsWith("wrangler.toml")) return true;
|
|
4104
4180
|
return /\.(?:[cm]?[jt]sx?)$/.test(repoPath);
|
|
4105
4181
|
}
|
|
4106
|
-
function
|
|
4182
|
+
function migrateWorkerOwnedNotFound(source, file) {
|
|
4107
4183
|
let replacements = 0;
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
replacements++;
|
|
4115
|
-
return `${open}${quote}x-app-id${quote}${separator}${receiver}.DEEPSPACE_APP_ID${close}`;
|
|
4116
|
-
}
|
|
4117
|
-
);
|
|
4118
|
-
line = line.replace(
|
|
4119
|
-
/(['"])x-app-name\1(\s*:\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME/g,
|
|
4120
|
-
(_match, quote, separator, receiver) => {
|
|
4121
|
-
replacements++;
|
|
4122
|
-
return `${quote}x-app-id${quote}${separator}${receiver}.DEEPSPACE_APP_ID`;
|
|
4123
|
-
}
|
|
4124
|
-
);
|
|
4125
|
-
line = line.replace(
|
|
4126
|
-
/(\[\s*)(['"])x-app-name\2(\s*\]\s*=\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME/g,
|
|
4127
|
-
(_match, open, quote, separator, receiver) => {
|
|
4128
|
-
replacements++;
|
|
4129
|
-
return `${open}${quote}x-app-id${quote}${separator}${receiver}.DEEPSPACE_APP_ID`;
|
|
4130
|
-
}
|
|
4131
|
-
);
|
|
4132
|
-
line = line.replace(
|
|
4133
|
-
/(forwardedParams\.set\(\s*)(['"])appName\2(\s*,\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME(\s*\))/g,
|
|
4134
|
-
(_match, open, quote, separator, receiver, close) => {
|
|
4135
|
-
replacements++;
|
|
4136
|
-
return `${open}${quote}appId${quote}${separator}${receiver}.DEEPSPACE_APP_ID${close}`;
|
|
4137
|
-
}
|
|
4138
|
-
);
|
|
4139
|
-
lines[index] = line;
|
|
4140
|
-
}
|
|
4141
|
-
if (replacements === 0) return { source, replacements };
|
|
4142
|
-
let migrated = lines.join("\n");
|
|
4143
|
-
if (/forwardedParams\.set\(\s*['"]appId['"]/.test(migrated)) {
|
|
4144
|
-
migrated = migrated.replaceAll("injectAppName", "injectAppId").replaceAll("`?appName=...`", "`?appId=...`").replaceAll("Inject appName into the query string", "Inject appId into the query string").replaceAll("caller-supplied appName", "caller-supplied appId").replaceAll(
|
|
4145
|
-
"/_deepspace/subscriptions/plans?appName=",
|
|
4146
|
-
"/_deepspace/subscriptions/plans?appId="
|
|
4147
|
-
);
|
|
4184
|
+
if (file.endsWith("wrangler.toml")) {
|
|
4185
|
+
const next = source.replace(/not_found_handling\s*=\s*"single-page-application"/g, () => {
|
|
4186
|
+
replacements++;
|
|
4187
|
+
return 'not_found_handling = "none"';
|
|
4188
|
+
});
|
|
4189
|
+
return { source: next, replacements };
|
|
4148
4190
|
}
|
|
4149
|
-
|
|
4150
|
-
"
|
|
4151
|
-
|
|
4191
|
+
const retargeted = source.replace(
|
|
4192
|
+
/(url\.pathname\s*=\s*)(['"])\/index\.html\2/g,
|
|
4193
|
+
(_match, assignment, quote) => {
|
|
4194
|
+
replacements++;
|
|
4195
|
+
return `${assignment}${quote}/${quote}`;
|
|
4196
|
+
}
|
|
4152
4197
|
);
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
replacements
|
|
4156
|
-
};
|
|
4198
|
+
const guarded = insertFileMissGuard(retargeted);
|
|
4199
|
+
return { source: guarded.source, replacements: replacements + guarded.replacements };
|
|
4157
4200
|
}
|
|
4158
|
-
|
|
4201
|
+
var SHELL_FALLBACK_LINE = /^(\s*)url\.pathname\s*=\s*(['"])\/\2\s*$/;
|
|
4202
|
+
var GUARD_LOOKBACK_LINES = 8;
|
|
4203
|
+
function insertFileMissGuard(source) {
|
|
4159
4204
|
const lines = source.split("\n");
|
|
4205
|
+
let replacements = 0;
|
|
4160
4206
|
for (let index = lines.length - 1; index >= 0; index--) {
|
|
4161
|
-
const match =
|
|
4162
|
-
if (!match ||
|
|
4163
|
-
const
|
|
4164
|
-
lines.splice(
|
|
4207
|
+
const match = SHELL_FALLBACK_LINE.exec(lines[index]);
|
|
4208
|
+
if (!match || hasFileMissGuard(lines, index)) continue;
|
|
4209
|
+
const indent = match[1];
|
|
4210
|
+
lines.splice(
|
|
4211
|
+
index,
|
|
4212
|
+
0,
|
|
4213
|
+
`${indent}// A FILE, not a client route: a miss must 404. Returning the shell here`,
|
|
4214
|
+
`${indent}// is HTML parsed as JavaScript, which is a blank page.`,
|
|
4215
|
+
`${indent}if (${FILE_MISS_TEST}) {`,
|
|
4216
|
+
`${indent} return c.json({ error: 'not_found' }, 404)`,
|
|
4217
|
+
`${indent}}`
|
|
4218
|
+
);
|
|
4219
|
+
replacements++;
|
|
4165
4220
|
}
|
|
4166
|
-
return lines.join("\n");
|
|
4221
|
+
return { source: lines.join("\n"), replacements };
|
|
4167
4222
|
}
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
if (/^\s*(?:readonly\s+)?DEEPSPACE_APP_ID\??\s*:/.test(line)) return true;
|
|
4173
|
-
const trimmed = line.trimStart();
|
|
4174
|
-
if (!trimmed) continue;
|
|
4175
|
-
const indent = line.length - trimmed.length;
|
|
4176
|
-
if (indent < propertyIndent && (trimmed.includes("{") || trimmed.startsWith("}"))) break;
|
|
4177
|
-
}
|
|
4178
|
-
}
|
|
4179
|
-
return false;
|
|
4223
|
+
var FILE_MISS_TEST = "url.pathname.slice(url.pathname.lastIndexOf('/') + 1).includes('.')";
|
|
4224
|
+
function hasFileMissGuard(lines, fallbackIndex) {
|
|
4225
|
+
const start = Math.max(0, fallbackIndex - GUARD_LOOKBACK_LINES);
|
|
4226
|
+
return lines.slice(start, fallbackIndex).some((line) => line.includes(FILE_MISS_TEST) || /\bnamesAFile\s*\(/.test(line));
|
|
4180
4227
|
}
|
|
4181
|
-
function
|
|
4228
|
+
function findWorkerOwnedNotFoundBlockers(source, file) {
|
|
4229
|
+
if (file.endsWith("wrangler.toml")) return [];
|
|
4230
|
+
if (!source.includes("ASSETS")) return [];
|
|
4182
4231
|
const blockers = [];
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
if (!/['"]
|
|
4186
|
-
continue;
|
|
4187
|
-
}
|
|
4232
|
+
const lines = source.split("\n");
|
|
4233
|
+
for (const [index, line] of lines.entries()) {
|
|
4234
|
+
if (!/['"`]\/index\.html/.test(line)) continue;
|
|
4188
4235
|
blockers.push({
|
|
4189
|
-
migrationId:
|
|
4236
|
+
migrationId: WORKER_OWNED_NOT_FOUND_MIGRATION_ID,
|
|
4190
4237
|
file,
|
|
4191
4238
|
line: index + 1,
|
|
4192
|
-
message:
|
|
4239
|
+
message: `This worker still asks the asset layer for "/index.html". Deploys now set not_found_handling = "none", and "/index.html" redirects to "/" \u2014 the browser follows it and loses the path it asked for. Fetch "/" instead, and 404 first when the last path segment contains a "." (a file, not a client route).`
|
|
4193
4240
|
});
|
|
4194
4241
|
}
|
|
4195
4242
|
return blockers;
|
|
4196
4243
|
}
|
|
4197
4244
|
|
|
4198
4245
|
// src/cli/commands/deploy/build.ts
|
|
4199
|
-
var
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
"
|
|
4203
|
-
|
|
4204
|
-
"/_deepspace/*",
|
|
4205
|
-
"/_documentation",
|
|
4206
|
-
"/_documentation/*",
|
|
4207
|
-
"/_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/*"
|
|
4216
|
-
]);
|
|
4246
|
+
var RESERVED_RUN_WORKER_FIRST2 = new Set(RESERVED_RUN_WORKER_FIRST);
|
|
4247
|
+
function declaredCompatibilityDate(config) {
|
|
4248
|
+
const declared = config.compatibility_date;
|
|
4249
|
+
return typeof declared === "string" && /^\d{4}-\d{2}-\d{2}$/.test(declared) ? declared : null;
|
|
4250
|
+
}
|
|
4217
4251
|
async function buildDeployBundle(options) {
|
|
4218
4252
|
const { appDir, appName, envName, sharedDevVarsCache, output, spinner: spinner2 } = options;
|
|
4219
4253
|
const junk = removeMacosJunk(appDir);
|
|
@@ -4325,7 +4359,10 @@ async function buildDeployBundle(options) {
|
|
|
4325
4359
|
appMigrations,
|
|
4326
4360
|
doManifest,
|
|
4327
4361
|
customBindings,
|
|
4328
|
-
extraRoutes
|
|
4362
|
+
extraRoutes,
|
|
4363
|
+
compatibilityDate: declaredCompatibilityDate(outputConfig),
|
|
4364
|
+
compatibilityFlags: Array.isArray(outputConfig.compatibility_flags) ? outputConfig.compatibility_flags.filter((flag) => typeof flag === "string") : [],
|
|
4365
|
+
notFoundHandling: typeof outputConfig.assets?.not_found_handling === "string" ? outputConfig.assets.not_found_handling : null
|
|
4329
4366
|
};
|
|
4330
4367
|
}
|
|
4331
4368
|
function extractRunWorkerFirst(config) {
|
|
@@ -4335,7 +4372,7 @@ function extractRunWorkerFirst(config) {
|
|
|
4335
4372
|
for (const entry of raw) {
|
|
4336
4373
|
if (typeof entry !== "string") continue;
|
|
4337
4374
|
const route = entry.trim();
|
|
4338
|
-
if (!route.startsWith("/") ||
|
|
4375
|
+
if (!route.startsWith("/") || RESERVED_RUN_WORKER_FIRST2.has(route) || seen.has(route)) continue;
|
|
4339
4376
|
seen.add(route);
|
|
4340
4377
|
routes.push(route);
|
|
4341
4378
|
}
|
|
@@ -5381,7 +5418,7 @@ async function pushWithTransientRetry(push) {
|
|
|
5381
5418
|
const message = errorMessage3(error);
|
|
5382
5419
|
const transient = /(?:HTTP |error: )(?:429|503)\b/i.test(message);
|
|
5383
5420
|
if (transient && attempt < backoffMs.length && Date.now() + backoffMs[attempt] < deadline) {
|
|
5384
|
-
await new Promise((
|
|
5421
|
+
await new Promise((resolve20) => setTimeout(resolve20, backoffMs[attempt]));
|
|
5385
5422
|
continue;
|
|
5386
5423
|
}
|
|
5387
5424
|
throw error;
|
|
@@ -5473,6 +5510,15 @@ async function deployBuiltBundle(options) {
|
|
|
5473
5510
|
if (Object.keys(bundle.assetConfig).length) {
|
|
5474
5511
|
form.append("assetConfig", JSON.stringify(bundle.assetConfig));
|
|
5475
5512
|
}
|
|
5513
|
+
if (bundle.compatibilityDate) {
|
|
5514
|
+
form.append("compatibilityDate", bundle.compatibilityDate);
|
|
5515
|
+
}
|
|
5516
|
+
if (bundle.compatibilityFlags.length) {
|
|
5517
|
+
form.append("compatibilityFlags", JSON.stringify(bundle.compatibilityFlags));
|
|
5518
|
+
}
|
|
5519
|
+
if (bundle.notFoundHandling) {
|
|
5520
|
+
form.append("notFoundHandling", bundle.notFoundHandling);
|
|
5521
|
+
}
|
|
5476
5522
|
form.append("name", appName);
|
|
5477
5523
|
if (confirmRename) form.append("confirmRename", "true");
|
|
5478
5524
|
if (shouldSendLineage(repository.commitOid, repository.recoverable)) {
|
|
@@ -5726,7 +5772,7 @@ async function postWithRetry(url, makeInit, {
|
|
|
5726
5772
|
lastError = error;
|
|
5727
5773
|
}
|
|
5728
5774
|
if (attempt < attempts) {
|
|
5729
|
-
await new Promise((
|
|
5775
|
+
await new Promise((resolve20) => setTimeout(resolve20, Math.min(8e3, 500 * 2 ** (attempt - 1))));
|
|
5730
5776
|
}
|
|
5731
5777
|
}
|
|
5732
5778
|
if (lastResponse) return lastResponse;
|
|
@@ -5846,46 +5892,6 @@ function errorMessage5(error) {
|
|
|
5846
5892
|
return error instanceof Error ? error.message : String(error);
|
|
5847
5893
|
}
|
|
5848
5894
|
|
|
5849
|
-
// src/cli/commands/migrate/legacy-app-id.ts
|
|
5850
|
-
var LEGACY_APP_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
5851
|
-
function readLegacyAppId(cwd = process.cwd(), wranglerEnv) {
|
|
5852
|
-
const cfg = readWranglerIdentityConfig(cwd);
|
|
5853
|
-
if (!cfg) return null;
|
|
5854
|
-
const slot = wranglerEnv ? cfg.env?.[wranglerEnv] : cfg;
|
|
5855
|
-
if (!slot) return null;
|
|
5856
|
-
const declaredId = slot.vars?.DEEPSPACE_APP_ID;
|
|
5857
|
-
if (declaredId !== void 0) {
|
|
5858
|
-
if (typeof declaredId === "string" && LEGACY_APP_ID_RE.test(declaredId)) return declaredId;
|
|
5859
|
-
throw new InputError(
|
|
5860
|
-
"DEEPSPACE_APP_ID is present but invalid; correct it before migrating.",
|
|
5861
|
-
"invalid_app_id"
|
|
5862
|
-
);
|
|
5863
|
-
}
|
|
5864
|
-
const appName = typeof slot.vars?.APP_NAME === "string" ? slot.vars.APP_NAME : null;
|
|
5865
|
-
const workerName = typeof slot.name === "string" ? slot.name : null;
|
|
5866
|
-
if (!appName) return null;
|
|
5867
|
-
if (!workerName) {
|
|
5868
|
-
throw new InputError(
|
|
5869
|
-
"Legacy migration requires both Worker name and APP_NAME to be present and identical.",
|
|
5870
|
-
"ambiguous_legacy_app_id"
|
|
5871
|
-
);
|
|
5872
|
-
}
|
|
5873
|
-
if (appName !== workerName) {
|
|
5874
|
-
throw new InputError(
|
|
5875
|
-
`Legacy APP_NAME "${appName}" does not match Worker name "${workerName}"; migration cannot choose safely.`,
|
|
5876
|
-
"ambiguous_legacy_app_id"
|
|
5877
|
-
);
|
|
5878
|
-
}
|
|
5879
|
-
return !APP_ID_RE.test(appName) && LEGACY_APP_ID_RE.test(appName) ? appName : null;
|
|
5880
|
-
}
|
|
5881
|
-
function legacyDeployRefusal(appDir, wranglerEnv) {
|
|
5882
|
-
const legacyAppId = readLegacyAppId(appDir, wranglerEnv);
|
|
5883
|
-
return legacyAppId ? {
|
|
5884
|
-
error: `This checkout still uses legacy app identity ${legacyAppId}. Run \`deepspace app migrate --dry-run\`, then complete \`deepspace app migrate\` before deploying.`,
|
|
5885
|
-
code: "legacy_app_migration_required"
|
|
5886
|
-
} : null;
|
|
5887
|
-
}
|
|
5888
|
-
|
|
5889
5895
|
// src/cli/commands/deploy.ts
|
|
5890
5896
|
var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
5891
5897
|
var deploy_default = defineCommand3({
|
|
@@ -5969,10 +5975,6 @@ var deploy_default = defineCommand3({
|
|
|
5969
5975
|
}
|
|
5970
5976
|
const appName = nameResult.name;
|
|
5971
5977
|
let appId = readAppId(appDir, envName);
|
|
5972
|
-
if (!appId) {
|
|
5973
|
-
const refusal = legacyDeployRefusal(appDir, envName);
|
|
5974
|
-
if (refusal) output.die(refusal.error, refusal.code);
|
|
5975
|
-
}
|
|
5976
5978
|
p12.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
|
|
5977
5979
|
const { token, ownerId } = await authenticate(appDir, output);
|
|
5978
5980
|
if (!appId) {
|
|
@@ -6038,15 +6040,18 @@ var deploy_default = defineCommand3({
|
|
|
6038
6040
|
output,
|
|
6039
6041
|
spinner: spinner2
|
|
6040
6042
|
});
|
|
6043
|
+
let serving = "unverifiable";
|
|
6041
6044
|
if (body.url) {
|
|
6042
|
-
spinner2.message("Waiting for edge
|
|
6043
|
-
|
|
6044
|
-
if (
|
|
6045
|
-
spinner2.stop(
|
|
6045
|
+
spinner2.message("Waiting for the edge to serve this release...");
|
|
6046
|
+
serving = await waitForLiveRelease(body.url, body.releaseStamp, 9e4);
|
|
6047
|
+
if (serving !== "confirmed") {
|
|
6048
|
+
spinner2.stop(
|
|
6049
|
+
serving === "unconfirmed" ? "Deployed \u2014 the edge is still rolling this release out" : "Deployed \u2014 serving could not be verified from here"
|
|
6050
|
+
);
|
|
6046
6051
|
p12.log.warn(
|
|
6047
|
-
"
|
|
6052
|
+
serving === "unconfirmed" ? "Some requests still get the previous release. Cloudflare rolls a version out per edge machine; it usually settles within a couple of minutes. Wait before asserting against it." : "This release carries no serving stamp (older platform, or a resumed deploy), so the CLI cannot tell which release the edge answers with."
|
|
6048
6053
|
);
|
|
6049
|
-
p12.log.info(`URL: ${body.url}
|
|
6054
|
+
p12.log.info(`URL: ${body.url}`);
|
|
6050
6055
|
await syncCommerce(appDir, appId, token, output.nonInteractive);
|
|
6051
6056
|
if (output.json) {
|
|
6052
6057
|
return output.emitJson({
|
|
@@ -6056,7 +6061,10 @@ var deploy_default = defineCommand3({
|
|
|
6056
6061
|
url: body.url,
|
|
6057
6062
|
releaseId: body.releaseId ?? null,
|
|
6058
6063
|
bundleRetained: body.bundleRetained ?? null,
|
|
6059
|
-
edgePropagating
|
|
6064
|
+
// `serving` says what was actually established; `edgePropagating`
|
|
6065
|
+
// stays for callers written against the old shape.
|
|
6066
|
+
serving,
|
|
6067
|
+
edgePropagating: serving === "unconfirmed",
|
|
6060
6068
|
recoverable: repository.recoverable,
|
|
6061
6069
|
...staleBaseGuardFields(body)
|
|
6062
6070
|
});
|
|
@@ -6074,6 +6082,9 @@ var deploy_default = defineCommand3({
|
|
|
6074
6082
|
appId,
|
|
6075
6083
|
appName,
|
|
6076
6084
|
url: body.url ?? null,
|
|
6085
|
+
// Always present, so a caller branches on one field instead of
|
|
6086
|
+
// inferring success from the absence of `edgePropagating`.
|
|
6087
|
+
serving,
|
|
6077
6088
|
releaseId: body.releaseId ?? null,
|
|
6078
6089
|
bundleRetained: body.bundleRetained ?? null,
|
|
6079
6090
|
recoverable: repository.recoverable,
|
|
@@ -6528,7 +6539,7 @@ function formatEvent(e, color, now = Date.now()) {
|
|
|
6528
6539
|
const client = e.source === "client" ? paint("35", "CLIENT", color) + " " : "";
|
|
6529
6540
|
return `${time} ${client}${tag} ${body}`;
|
|
6530
6541
|
}
|
|
6531
|
-
var
|
|
6542
|
+
var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6532
6543
|
var logs_default = defineCommand4({
|
|
6533
6544
|
meta: {
|
|
6534
6545
|
name: "logs",
|
|
@@ -6648,7 +6659,7 @@ var logs_default = defineCommand4({
|
|
|
6648
6659
|
}
|
|
6649
6660
|
}
|
|
6650
6661
|
for (; ; ) {
|
|
6651
|
-
await
|
|
6662
|
+
await sleep3(backoff || interval);
|
|
6652
6663
|
const freshToken = await ensureToken();
|
|
6653
6664
|
let page;
|
|
6654
6665
|
try {
|
|
@@ -7818,11 +7829,11 @@ function fmtDate(iso) {
|
|
|
7818
7829
|
}
|
|
7819
7830
|
async function confirm4(message) {
|
|
7820
7831
|
process.stdout.write(`${message} [y/N] `);
|
|
7821
|
-
return new Promise((
|
|
7832
|
+
return new Promise((resolve20) => {
|
|
7822
7833
|
process.stdin.setEncoding("utf-8");
|
|
7823
7834
|
process.stdin.once("data", (data) => {
|
|
7824
7835
|
const answer = data.toString().trim().toLowerCase();
|
|
7825
|
-
|
|
7836
|
+
resolve20(answer === "y" || answer === "yes");
|
|
7826
7837
|
});
|
|
7827
7838
|
});
|
|
7828
7839
|
}
|
|
@@ -8174,8 +8185,14 @@ async function request(baseUrl, token, path, init = {}) {
|
|
|
8174
8185
|
if (res.ok) return res;
|
|
8175
8186
|
const text2 = await res.text();
|
|
8176
8187
|
let code;
|
|
8188
|
+
let details;
|
|
8177
8189
|
try {
|
|
8178
|
-
|
|
8190
|
+
const body = JSON.parse(text2.trim());
|
|
8191
|
+
code = typeof body.code === "string" ? body.code : void 0;
|
|
8192
|
+
const rest = Object.fromEntries(
|
|
8193
|
+
Object.entries(body).filter(([key]) => key !== "error" && key !== "code")
|
|
8194
|
+
);
|
|
8195
|
+
if (Object.keys(rest).length > 0) details = rest;
|
|
8179
8196
|
} catch {
|
|
8180
8197
|
}
|
|
8181
8198
|
throw new ApiError(
|
|
@@ -8185,7 +8202,8 @@ async function request(baseUrl, token, path, init = {}) {
|
|
|
8185
8202
|
// oversized request short is not a different failure from the worker
|
|
8186
8203
|
// refusing it, and two codes made it look like one.
|
|
8187
8204
|
code ?? (res.status === 413 ? "too_large" : res.status >= 500 ? "server_error" : "http_error"),
|
|
8188
|
-
path
|
|
8205
|
+
path,
|
|
8206
|
+
details
|
|
8189
8207
|
);
|
|
8190
8208
|
}
|
|
8191
8209
|
async function readJson(res, path) {
|
|
@@ -8458,8 +8476,14 @@ var list3 = defineDeepspaceCommand({
|
|
|
8458
8476
|
const more = limit < MAX_LIST_LIMIT ? "raise --limit or narrow with --prefix" : "narrow with --prefix";
|
|
8459
8477
|
if (result.truncated) console.log(`(truncated at ${limit} \u2014 ${more})`);
|
|
8460
8478
|
}
|
|
8479
|
+
if (result.storage) {
|
|
8480
|
+
const { usedBytes, limitBytes } = result.storage;
|
|
8481
|
+
console.log(
|
|
8482
|
+
limitBytes === void 0 ? `Storage: ${formatBytes(usedBytes)} used (limit unavailable right now)` : `Storage: ${formatBytes(usedBytes)} of ${formatBytes(limitBytes)} used`
|
|
8483
|
+
);
|
|
8484
|
+
}
|
|
8461
8485
|
}
|
|
8462
|
-
return { data: { appId, files, truncated: result.truncated } };
|
|
8486
|
+
return { data: { appId, files, truncated: result.truncated, storage: result.storage ?? null } };
|
|
8463
8487
|
}
|
|
8464
8488
|
});
|
|
8465
8489
|
var get = defineDeepspaceCommand({
|
|
@@ -8542,8 +8566,8 @@ var list4 = defineDeepspaceCommand({
|
|
|
8542
8566
|
}
|
|
8543
8567
|
if (pending.length) {
|
|
8544
8568
|
console.log(`PENDING INVITES ON ${app2}`);
|
|
8545
|
-
for (const
|
|
8546
|
-
console.log(` ${
|
|
8569
|
+
for (const p35 of pending) {
|
|
8570
|
+
console.log(` ${p35.email} (expires ${new Date(p35.expiresAt).toLocaleDateString()})`);
|
|
8547
8571
|
}
|
|
8548
8572
|
}
|
|
8549
8573
|
}
|
|
@@ -8587,7 +8611,7 @@ var add = defineDeepspaceCommand({
|
|
|
8587
8611
|
}
|
|
8588
8612
|
if (!args.json) {
|
|
8589
8613
|
console.log(
|
|
8590
|
-
`\u2713 Invite sent to ${res.email} (expires ${expires}). They
|
|
8614
|
+
`\u2713 Invite sent to ${res.email} (expires ${expires}). They accept it from the emailed link, or by signing in and running \`deepspace app collaborators accept ${app2}\`.`
|
|
8591
8615
|
);
|
|
8592
8616
|
}
|
|
8593
8617
|
return { data: { ...res } };
|
|
@@ -8641,7 +8665,7 @@ var cancel3 = defineDeepspaceCommand({
|
|
|
8641
8665
|
`/api/app-collaborators/${encodeURIComponent(app2)}`
|
|
8642
8666
|
);
|
|
8643
8667
|
const target2 = email.trim().toLowerCase();
|
|
8644
|
-
const found = pending.find((
|
|
8668
|
+
const found = pending.find((p35) => p35.email.toLowerCase() === target2);
|
|
8645
8669
|
if (!found) {
|
|
8646
8670
|
throw new Refusal(`${email} has no pending invite on ${app2}`, "no_pending_invite", {
|
|
8647
8671
|
action: cliAction("deepspace", "app", "collaborators", "list", "--app", app2)
|
|
@@ -8656,9 +8680,59 @@ var cancel3 = defineDeepspaceCommand({
|
|
|
8656
8680
|
return { data: { cancelled: found } };
|
|
8657
8681
|
}
|
|
8658
8682
|
});
|
|
8683
|
+
var invites = defineDeepspaceCommand({
|
|
8684
|
+
meta: { name: "invites", description: "List invites waiting for you" },
|
|
8685
|
+
async run({ args }) {
|
|
8686
|
+
const token = await ensureToken();
|
|
8687
|
+
const { invites: invites2 } = await api2(
|
|
8688
|
+
token,
|
|
8689
|
+
"/api/app-collaborators/invites/mine"
|
|
8690
|
+
);
|
|
8691
|
+
if (!args.json) {
|
|
8692
|
+
if (!invites2.length) {
|
|
8693
|
+
console.log("No pending invites for your email.");
|
|
8694
|
+
} else {
|
|
8695
|
+
for (const invite of invites2) {
|
|
8696
|
+
console.log(
|
|
8697
|
+
`${invite.appId} invited by ${invite.invitedByName} (expires ${new Date(invite.expiresAt).toLocaleDateString()})`
|
|
8698
|
+
);
|
|
8699
|
+
}
|
|
8700
|
+
console.log("Accept one with `deepspace app collaborators accept <app-id>`.");
|
|
8701
|
+
}
|
|
8702
|
+
}
|
|
8703
|
+
return { data: { invites: invites2 } };
|
|
8704
|
+
}
|
|
8705
|
+
});
|
|
8706
|
+
var accept = defineDeepspaceCommand({
|
|
8707
|
+
meta: { name: "accept", description: "Accept an invite to collaborate on an app" },
|
|
8708
|
+
args: {
|
|
8709
|
+
app: { type: "positional", description: "App id (see `collaborators invites`)", required: true }
|
|
8710
|
+
},
|
|
8711
|
+
async run({ args }) {
|
|
8712
|
+
const appId = String(args.app);
|
|
8713
|
+
const token = await ensureToken();
|
|
8714
|
+
const res = await api2(
|
|
8715
|
+
token,
|
|
8716
|
+
`/api/app-collaborators/invites/${encodeURIComponent(appId)}/accept`,
|
|
8717
|
+
{ method: "POST" }
|
|
8718
|
+
);
|
|
8719
|
+
if (!args.json) {
|
|
8720
|
+
console.log(
|
|
8721
|
+
res.alreadyMember ? `\u2022 You already have access to ${res.appId}.` : `\u2713 You can now deploy ${res.appId}.`
|
|
8722
|
+
);
|
|
8723
|
+
}
|
|
8724
|
+
return {
|
|
8725
|
+
data: { ...res },
|
|
8726
|
+
// `pull` fast-forwards a checkout this caller does not have yet — the
|
|
8727
|
+
// invite just granted access, so there is nothing local to update.
|
|
8728
|
+
// `clone` is the step that actually works from here.
|
|
8729
|
+
action: cliAction("deepspace", "clone", res.appId)
|
|
8730
|
+
};
|
|
8731
|
+
}
|
|
8732
|
+
});
|
|
8659
8733
|
var collaborators_default = defineCommand7({
|
|
8660
8734
|
meta: { name: "collaborators", description: "Manage who can deploy your app" },
|
|
8661
|
-
subCommands: { list: list4, add, remove, cancel: cancel3 }
|
|
8735
|
+
subCommands: { list: list4, add, remove, cancel: cancel3, invites, accept }
|
|
8662
8736
|
});
|
|
8663
8737
|
|
|
8664
8738
|
// src/cli/commands/transfer.ts
|
|
@@ -8743,7 +8817,7 @@ var status2 = defineDeepspaceCommand({
|
|
|
8743
8817
|
return { data: { app: app2, transfer } };
|
|
8744
8818
|
}
|
|
8745
8819
|
});
|
|
8746
|
-
var
|
|
8820
|
+
var accept2 = defineDeepspaceCommand({
|
|
8747
8821
|
meta: { name: "accept", description: "Accept a transfer offered to you" },
|
|
8748
8822
|
args: {
|
|
8749
8823
|
app: { type: "string", alias: "a", description: "App id of the offered app", required: true }
|
|
@@ -8784,7 +8858,7 @@ var cancel4 = defineDeepspaceCommand({
|
|
|
8784
8858
|
});
|
|
8785
8859
|
var transfer_default = defineCommand8({
|
|
8786
8860
|
meta: { name: "transfer", description: "Transfer app ownership to another user" },
|
|
8787
|
-
subCommands: { offer, status: status2, accept, cancel: cancel4 }
|
|
8861
|
+
subCommands: { offer, status: status2, accept: accept2, cancel: cancel4 }
|
|
8788
8862
|
});
|
|
8789
8863
|
|
|
8790
8864
|
// src/cli/commands/integrations.ts
|
|
@@ -10942,13 +11016,14 @@ Re-run with --allow-do-deletion to accept the data loss.`,
|
|
|
10942
11016
|
"invalid_response"
|
|
10943
11017
|
);
|
|
10944
11018
|
}
|
|
10945
|
-
let
|
|
11019
|
+
let serving = "unverifiable";
|
|
10946
11020
|
if (result.url) {
|
|
10947
|
-
spinner2?.message("Waiting for edge
|
|
10948
|
-
|
|
11021
|
+
spinner2?.message("Waiting for the edge to serve this release...");
|
|
11022
|
+
serving = await waitForLiveRelease(result.url, result.releaseStamp, 9e4);
|
|
10949
11023
|
}
|
|
11024
|
+
const edgePropagating = serving === "unconfirmed";
|
|
10950
11025
|
spinner2?.stop(
|
|
10951
|
-
|
|
11026
|
+
serving === "confirmed" ? `Rolled back to ${result.rolledBackTo ?? releaseId}.` : serving === "unconfirmed" ? `Rolled back to ${result.rolledBackTo ?? releaseId} \u2014 the edge is still rolling it out.` : `Rolled back to ${result.rolledBackTo ?? releaseId} \u2014 serving could not be verified from here.`
|
|
10952
11027
|
);
|
|
10953
11028
|
const doClassGuard = result.doClassGuard === "unverified" ? "unverified" : void 0;
|
|
10954
11029
|
if (!args.json) {
|
|
@@ -10979,6 +11054,8 @@ Re-run with --allow-do-deletion to accept the data loss.`,
|
|
|
10979
11054
|
url: result.url ?? null,
|
|
10980
11055
|
versionId: result.versionId ?? null,
|
|
10981
11056
|
wranglerEnv: wranglerEnv ?? null,
|
|
11057
|
+
// Always present, matching `deploy`: one field to branch on.
|
|
11058
|
+
serving,
|
|
10982
11059
|
...edgePropagating ? { edgePropagating: true } : {},
|
|
10983
11060
|
...doClassGuard ? { doClassGuard } : {}
|
|
10984
11061
|
}
|
|
@@ -12982,541 +13059,183 @@ function sameRefs(left, right) {
|
|
|
12982
13059
|
return left.every((ref) => expected.get(ref.name) === ref.oid);
|
|
12983
13060
|
}
|
|
12984
13061
|
|
|
12985
|
-
// src/cli/commands/
|
|
12986
|
-
|
|
12987
|
-
|
|
12988
|
-
|
|
12989
|
-
|
|
12990
|
-
|
|
12991
|
-
|
|
12992
|
-
|
|
12993
|
-
|
|
12994
|
-
|
|
12995
|
-
|
|
12996
|
-
|
|
12997
|
-
|
|
12998
|
-
|
|
12999
|
-
|
|
13000
|
-
|
|
13001
|
-
|
|
13002
|
-
|
|
13003
|
-
|
|
13004
|
-
|
|
13062
|
+
// src/cli/commands/update.ts
|
|
13063
|
+
import * as p34 from "@clack/prompts";
|
|
13064
|
+
import { existsSync as existsSync26, readFileSync as readFileSync28, writeFileSync as writeFileSync10 } from "fs";
|
|
13065
|
+
import { join as join24, resolve as resolve19 } from "path";
|
|
13066
|
+
var REGISTRY = "https://registry.npmjs.org/deepspace/latest";
|
|
13067
|
+
var DOCS_URL = "https://documentation.deep.space/cli-reference/commands";
|
|
13068
|
+
var OLDEST_SUPPORTED_MINOR = 12;
|
|
13069
|
+
function parseVersion(raw) {
|
|
13070
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(raw.trim().replace(/^[\^~]/, ""));
|
|
13071
|
+
if (!match) return null;
|
|
13072
|
+
return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
|
|
13073
|
+
}
|
|
13074
|
+
function gapIsTooWide(from, to) {
|
|
13075
|
+
if (from.major !== to.major) return true;
|
|
13076
|
+
return from.major === 0 && from.minor < OLDEST_SUPPORTED_MINOR;
|
|
13077
|
+
}
|
|
13078
|
+
function readAppSdkVersion(appDir) {
|
|
13079
|
+
const installed = join24(appDir, "node_modules", "deepspace", "package.json");
|
|
13080
|
+
if (existsSync26(installed)) {
|
|
13081
|
+
const parsed2 = JSON.parse(readFileSync28(installed, "utf8"));
|
|
13082
|
+
if (typeof parsed2.version === "string") return parsed2.version;
|
|
13083
|
+
}
|
|
13084
|
+
const manifest = join24(appDir, "package.json");
|
|
13085
|
+
if (!existsSync26(manifest)) return null;
|
|
13086
|
+
const parsed = JSON.parse(readFileSync28(manifest, "utf8"));
|
|
13087
|
+
return parsed.dependencies?.deepspace ?? null;
|
|
13088
|
+
}
|
|
13089
|
+
function pinSdkVersion(appDir, version) {
|
|
13090
|
+
const path = join24(appDir, "package.json");
|
|
13091
|
+
const source = readFileSync28(path, "utf8");
|
|
13092
|
+
const updated = source.replace(
|
|
13093
|
+
/("deepspace"\s*:\s*")[^"]+(")/,
|
|
13094
|
+
(_match, head, tail) => `${head}^${version}${tail}`
|
|
13005
13095
|
);
|
|
13006
|
-
return
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
const result = await apiFetch(
|
|
13010
|
-
deployUrl,
|
|
13011
|
-
token,
|
|
13012
|
-
`/api/apps/${encodeURIComponent(legacyAppId)}/identity-migration/inspect`,
|
|
13013
|
-
{ method: "POST", body: JSON.stringify(input) }
|
|
13014
|
-
);
|
|
13015
|
-
return result.inventory;
|
|
13016
|
-
}
|
|
13017
|
-
async function commitIdentityMigration(deployUrl, token, appId, input) {
|
|
13018
|
-
const result = await apiFetch(
|
|
13019
|
-
deployUrl,
|
|
13020
|
-
token,
|
|
13021
|
-
`/api/apps/${encodeURIComponent(appId)}/identity-migration/commit`,
|
|
13022
|
-
{ method: "POST", body: JSON.stringify(input) }
|
|
13023
|
-
);
|
|
13024
|
-
return result.migration;
|
|
13096
|
+
if (updated === source) return false;
|
|
13097
|
+
writeFileSync10(path, updated);
|
|
13098
|
+
return true;
|
|
13025
13099
|
}
|
|
13026
|
-
async function
|
|
13027
|
-
await
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13100
|
+
async function latestPublishedVersion() {
|
|
13101
|
+
const response = await fetch(REGISTRY, { signal: AbortSignal.timeout(15e3) }).catch(
|
|
13102
|
+
(error) => {
|
|
13103
|
+
throw new Refusal(
|
|
13104
|
+
`Could not reach the npm registry to find the latest version: ${error instanceof Error ? error.message : String(error)}. Pass --to <version> to update without a lookup.`,
|
|
13105
|
+
"registry_unreachable"
|
|
13106
|
+
);
|
|
13107
|
+
}
|
|
13032
13108
|
);
|
|
13109
|
+
if (!response.ok) {
|
|
13110
|
+
throw new Refusal(
|
|
13111
|
+
`The npm registry answered ${response.status} for the latest deepspace version. Pass --to <version> to update without a lookup.`,
|
|
13112
|
+
"registry_unreachable"
|
|
13113
|
+
);
|
|
13114
|
+
}
|
|
13115
|
+
const body = await response.json();
|
|
13116
|
+
if (typeof body.version !== "string") {
|
|
13117
|
+
throw new Refusal("The npm registry returned no version for deepspace.", "registry_unreachable");
|
|
13118
|
+
}
|
|
13119
|
+
return body.version;
|
|
13033
13120
|
}
|
|
13034
|
-
|
|
13035
|
-
const
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13121
|
+
function reportPlan(plan, applied) {
|
|
13122
|
+
for (const migration of plan.pending) {
|
|
13123
|
+
const verb = applied ? "Applied" : "Would apply";
|
|
13124
|
+
p34.log.success(
|
|
13125
|
+
`${verb}: ${migration.description} \u2014 ${migration.replacements} change(s) in ${migration.files.join(", ")}`
|
|
13126
|
+
);
|
|
13127
|
+
}
|
|
13128
|
+
if (plan.blockers.length) {
|
|
13129
|
+
p34.log.warn(
|
|
13130
|
+
`${plan.blockers.length} change(s) could not be made for you \u2014 this file differs from the version the change was written against:`
|
|
13131
|
+
);
|
|
13132
|
+
for (const blocker of plan.blockers) {
|
|
13133
|
+
p34.log.message(` ${blocker.file}:${blocker.line}
|
|
13134
|
+
${blocker.message}`);
|
|
13135
|
+
}
|
|
13136
|
+
}
|
|
13042
13137
|
}
|
|
13043
|
-
|
|
13044
|
-
// src/cli/commands/migrate.ts
|
|
13045
|
-
var STRICT_APP_ID_RE2 = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
13046
|
-
var migrate_default = defineDeepspaceCommand({
|
|
13138
|
+
var update_default = defineDeepspaceCommand({
|
|
13047
13139
|
meta: {
|
|
13048
|
-
name: "
|
|
13049
|
-
description: "
|
|
13140
|
+
name: "update",
|
|
13141
|
+
description: "Move this app onto the current SDK and apply the source changes it requires"
|
|
13050
13142
|
},
|
|
13051
13143
|
args: {
|
|
13052
|
-
|
|
13144
|
+
to: {
|
|
13053
13145
|
type: "string",
|
|
13054
|
-
description: "
|
|
13055
|
-
required: false
|
|
13056
|
-
},
|
|
13057
|
-
env: {
|
|
13058
|
-
type: "string",
|
|
13059
|
-
alias: "e",
|
|
13060
|
-
description: "wrangler.toml environment whose app id should be migrated",
|
|
13146
|
+
description: "Target version (default: the latest published deepspace)",
|
|
13061
13147
|
required: false
|
|
13062
13148
|
},
|
|
13063
13149
|
"dry-run": {
|
|
13064
13150
|
type: "boolean",
|
|
13065
|
-
description: "
|
|
13066
|
-
default: false
|
|
13067
|
-
},
|
|
13068
|
-
cancel: {
|
|
13069
|
-
type: "boolean",
|
|
13070
|
-
description: "Cancel a prepared migration after restoring and publishing the legacy id",
|
|
13151
|
+
description: "Report what would change without touching the checkout",
|
|
13071
13152
|
default: false
|
|
13072
13153
|
}
|
|
13073
13154
|
},
|
|
13074
13155
|
async run({ args }) {
|
|
13075
|
-
const
|
|
13076
|
-
|
|
13077
|
-
if (exclusiveModes > 1) {
|
|
13078
|
-
throw new Refusal("Use only one of --dry-run or --cancel.", "conflicting_migration_mode");
|
|
13079
|
-
}
|
|
13080
|
-
const appDir = findAppDir();
|
|
13081
|
-
if (!appDir) {
|
|
13082
|
-
throw new Refusal("Run this command from a DeepSpace app Git checkout.", "not_in_app_repo");
|
|
13083
|
-
}
|
|
13084
|
-
assertSyncableRepo(appDir);
|
|
13085
|
-
const envName = typeof args.env === "string" && args.env ? args.env : void 0;
|
|
13086
|
-
const currentAppId = readAppId(appDir, envName) ?? readLegacyAppId(appDir, envName);
|
|
13087
|
-
if (!currentAppId) {
|
|
13156
|
+
const appDir = resolve19(".");
|
|
13157
|
+
if (!hasWranglerConfig(appDir)) {
|
|
13088
13158
|
throw new Refusal(
|
|
13089
|
-
"wrangler.toml
|
|
13090
|
-
"
|
|
13159
|
+
"No wrangler.toml found \u2014 run this from inside a DeepSpace app directory.",
|
|
13160
|
+
"not_in_app_repo"
|
|
13091
13161
|
);
|
|
13092
13162
|
}
|
|
13093
|
-
|
|
13094
|
-
const
|
|
13095
|
-
|
|
13096
|
-
const existingMigration = await getIdentityMigration(deployUrl, token, currentAppId);
|
|
13097
|
-
if (recoveryMode) {
|
|
13098
|
-
if (!existingMigration) {
|
|
13099
|
-
if (!args.json) console.log("No reversible identity migration is recorded.");
|
|
13100
|
-
return { data: { status: "no_migration", appId: currentAppId } };
|
|
13101
|
-
}
|
|
13102
|
-
return recoverMigration({
|
|
13103
|
-
mode: recoveryMode,
|
|
13104
|
-
appDir,
|
|
13105
|
-
envName,
|
|
13106
|
-
remoteName: typeof args.remote === "string" ? args.remote : void 0,
|
|
13107
|
-
currentAppId,
|
|
13108
|
-
token,
|
|
13109
|
-
deployUrl,
|
|
13110
|
-
migration: existingMigration,
|
|
13111
|
-
json: args.json
|
|
13112
|
-
});
|
|
13113
|
-
}
|
|
13114
|
-
const sourcePlan = planAppMigrations(appDir);
|
|
13115
|
-
if (sourcePlan.blockers.length > 0) {
|
|
13116
|
-
const first = sourcePlan.blockers[0];
|
|
13163
|
+
assertSyncableRepo(appDir);
|
|
13164
|
+
const currentRaw = readAppSdkVersion(appDir);
|
|
13165
|
+
if (!currentRaw) {
|
|
13117
13166
|
throw new Refusal(
|
|
13118
|
-
|
|
13119
|
-
"
|
|
13120
|
-
{ extra: { blockers: sourcePlan.blockers } }
|
|
13167
|
+
"This app does not depend on `deepspace` \u2014 nothing to update.",
|
|
13168
|
+
"not_in_app_repo"
|
|
13121
13169
|
);
|
|
13122
13170
|
}
|
|
13123
|
-
|
|
13124
|
-
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
return {
|
|
13128
|
-
data: {
|
|
13129
|
-
status: "ready",
|
|
13130
|
-
appId: currentAppId,
|
|
13131
|
-
sourceMigrations: sourcePlan.pending
|
|
13132
|
-
}
|
|
13133
|
-
};
|
|
13134
|
-
}
|
|
13135
|
-
if (sourcePlan.pending.length > 0) {
|
|
13136
|
-
requireSourceMigrationWorktree(appDir);
|
|
13137
|
-
applyAppMigrationPlan(appDir, sourcePlan);
|
|
13138
|
-
throw sourceMigrationCommitRequired(appDir, sourcePlan);
|
|
13139
|
-
}
|
|
13140
|
-
await requireSourceMigrationDeploy(appDir, currentAppId, deployUrl, token, {
|
|
13141
|
-
envName,
|
|
13142
|
-
remoteName: typeof args.remote === "string" ? args.remote : void 0
|
|
13143
|
-
});
|
|
13144
|
-
reportUpToDate(currentAppId, args.json);
|
|
13145
|
-
return { data: { status: "up_to_date", appId: currentAppId } };
|
|
13146
|
-
}
|
|
13147
|
-
if (sourcePlan.pending.length === 0 && existingMigration.status === "verified") {
|
|
13148
|
-
await requireSourceMigrationDeploy(appDir, currentAppId, deployUrl, token, {
|
|
13149
|
-
envName,
|
|
13150
|
-
remoteName: typeof args.remote === "string" ? args.remote : void 0
|
|
13151
|
-
});
|
|
13152
|
-
}
|
|
13153
|
-
if (args["dry-run"]) {
|
|
13154
|
-
reportMigration(existingMigration, args.json);
|
|
13155
|
-
reportSourcePlan(sourcePlan, args.json);
|
|
13156
|
-
return {
|
|
13157
|
-
data: {
|
|
13158
|
-
status: existingMigration.status,
|
|
13159
|
-
migration: existingMigration,
|
|
13160
|
-
sourceMigrations: sourcePlan.pending
|
|
13161
|
-
}
|
|
13162
|
-
};
|
|
13163
|
-
}
|
|
13164
|
-
if (sourcePlan.pending.length > 0) {
|
|
13165
|
-
requireSourceMigrationWorktree(appDir, existingMigration.status === "prepared");
|
|
13166
|
-
applyAppMigrationPlan(appDir, sourcePlan);
|
|
13167
|
-
throw existingMigration.status === "prepared" ? commitRequired(appDir, existingMigration, envName, sourcePlan.changedFiles) : sourceMigrationCommitRequired(appDir, sourcePlan);
|
|
13168
|
-
}
|
|
13169
|
-
return continuePreparedMigration({
|
|
13170
|
-
appDir,
|
|
13171
|
-
envName,
|
|
13172
|
-
remoteName: typeof args.remote === "string" ? args.remote : void 0,
|
|
13173
|
-
token,
|
|
13174
|
-
deployUrl,
|
|
13175
|
-
migration: existingMigration,
|
|
13176
|
-
json: args.json
|
|
13177
|
-
});
|
|
13178
|
-
}
|
|
13179
|
-
if (existingMigration && existingMigration.status !== "prepared" && existingMigration.status !== "rolled_back") {
|
|
13171
|
+
const targetRaw = typeof args.to === "string" && args.to.trim() ? args.to.trim() : await latestPublishedVersion();
|
|
13172
|
+
const current = parseVersion(currentRaw);
|
|
13173
|
+
const target2 = parseVersion(targetRaw);
|
|
13174
|
+
if (!current || !target2) {
|
|
13180
13175
|
throw new Refusal(
|
|
13181
|
-
`
|
|
13182
|
-
"
|
|
13183
|
-
{ extra: { migration: existingMigration } }
|
|
13176
|
+
`Could not read a version to update from ("${currentRaw}") or to ("${targetRaw}").`,
|
|
13177
|
+
"invalid_version"
|
|
13184
13178
|
);
|
|
13185
13179
|
}
|
|
13186
|
-
|
|
13187
|
-
appDir,
|
|
13188
|
-
typeof args.remote === "string" ? args.remote : void 0
|
|
13189
|
-
);
|
|
13190
|
-
const head = requirePublishedHead2(appDir, remote);
|
|
13191
|
-
const proposedAppId = existingMigration?.appId ?? mintAppId();
|
|
13192
|
-
const inventory = await inspectIdentityMigration(deployUrl, token, currentAppId, {
|
|
13193
|
-
destinationAppId: proposedAppId,
|
|
13194
|
-
repository: remote.repository
|
|
13195
|
-
});
|
|
13196
|
-
if (!inventory.ready) {
|
|
13180
|
+
if (gapIsTooWide(current, target2)) {
|
|
13197
13181
|
throw new Refusal(
|
|
13198
|
-
`
|
|
13199
|
-
"
|
|
13200
|
-
{ extra: {
|
|
13182
|
+
`This app is on deepspace ${currentRaw} and the current release is ${targetRaw}. That gap is too wide to migrate automatically \u2014 the changes between them are structural, not mechanical. Follow the upgrade guide instead: ${DOCS_URL}`,
|
|
13183
|
+
"version_gap_too_wide",
|
|
13184
|
+
{ actionRequired: true, extra: { currentVersion: currentRaw, targetVersion: targetRaw } }
|
|
13201
13185
|
);
|
|
13202
13186
|
}
|
|
13203
|
-
|
|
13204
|
-
|
|
13205
|
-
|
|
13206
|
-
|
|
13207
|
-
|
|
13208
|
-
|
|
13209
|
-
|
|
13210
|
-
|
|
13211
|
-
|
|
13212
|
-
);
|
|
13213
|
-
console.log(
|
|
13214
|
-
`Physical stores retained at ${inventory.app.resourceId}: ${inventory.retainedPhysicalStores.map((store) => store.kind).join(", ")}`
|
|
13215
|
-
);
|
|
13216
|
-
console.log("No changes made.");
|
|
13217
|
-
}
|
|
13218
|
-
reportSourcePlan(sourcePlan, args.json);
|
|
13219
|
-
return {
|
|
13220
|
-
data: {
|
|
13221
|
-
status: "ready",
|
|
13222
|
-
legacyAppId: currentAppId,
|
|
13223
|
-
proposedAppId,
|
|
13224
|
-
resourceId: inventory.app.resourceId,
|
|
13225
|
-
repository: remote.repository,
|
|
13226
|
-
branch: head.branch,
|
|
13227
|
-
commitOid: head.oid,
|
|
13228
|
-
sourceClaimRequired: inventory.app.sourceClaimRequired,
|
|
13229
|
-
sourceMigrations: sourcePlan.pending,
|
|
13230
|
-
inventory
|
|
13231
|
-
}
|
|
13232
|
-
};
|
|
13233
|
-
}
|
|
13234
|
-
const migration = existingMigration?.status === "prepared" ? existingMigration : await prepareIdentityMigration(deployUrl, token, currentAppId, {
|
|
13235
|
-
destinationAppId: proposedAppId,
|
|
13236
|
-
repository: remote.repository,
|
|
13237
|
-
expectedSourceRevision: inventory.app.sourceRevision
|
|
13238
|
-
});
|
|
13239
|
-
applyAppMigrationPlan(appDir, sourcePlan);
|
|
13240
|
-
writeAppId(appDir, migration.appId, { wranglerEnv: envName, force: true });
|
|
13241
|
-
throw commitRequired(appDir, migration, envName, sourcePlan.changedFiles);
|
|
13242
|
-
}
|
|
13243
|
-
});
|
|
13244
|
-
async function recoverMigration(input) {
|
|
13245
|
-
const { appDir, envName, remoteName, currentAppId, token, deployUrl, migration, json } = input;
|
|
13246
|
-
if (migration.status === "rolled_back") {
|
|
13247
|
-
if (currentAppId !== migration.legacyAppId) {
|
|
13248
|
-
writeAppId(appDir, migration.legacyAppId, { wranglerEnv: envName, force: true });
|
|
13249
|
-
throw restoreLegacyCommitRequired(appDir, migration, envName);
|
|
13250
|
-
}
|
|
13251
|
-
reportMigration(migration, json);
|
|
13252
|
-
return { data: { status: "rolled_back", migration } };
|
|
13253
|
-
}
|
|
13254
|
-
if (migration.status !== "prepared") {
|
|
13255
|
-
throw new Refusal(
|
|
13256
|
-
"This migration is already committed. Complete it by deploying the canonical app.",
|
|
13257
|
-
"forward_recovery_required",
|
|
13258
|
-
{ extra: { migration } }
|
|
13259
|
-
);
|
|
13260
|
-
}
|
|
13261
|
-
if (currentAppId !== migration.legacyAppId) {
|
|
13262
|
-
writeAppId(appDir, migration.legacyAppId, { wranglerEnv: envName, force: true });
|
|
13263
|
-
throw restoreLegacyCommitRequired(appDir, migration, envName);
|
|
13264
|
-
}
|
|
13265
|
-
const remote = requireGitHubRemote2(appDir, remoteName, migration.repository);
|
|
13266
|
-
requirePublishedHead2(appDir, remote);
|
|
13267
|
-
await cancelIdentityMigration(deployUrl, token, migration.legacyAppId);
|
|
13268
|
-
if (!json) console.log(`Canceled ${migration.legacyAppId} \u2192 ${migration.appId}.`);
|
|
13269
|
-
return { data: { status: "cancelled", legacyAppId: migration.legacyAppId } };
|
|
13270
|
-
}
|
|
13271
|
-
async function continuePreparedMigration(input) {
|
|
13272
|
-
const { appDir, envName, remoteName, token, deployUrl, migration, json } = input;
|
|
13273
|
-
if (migration.status === "rolled_back") {
|
|
13274
|
-
throw new Refusal(
|
|
13275
|
-
`This migration was rolled back. Restore DEEPSPACE_APP_ID to ${migration.legacyAppId} before continuing.`,
|
|
13276
|
-
"migration_rolled_back"
|
|
13277
|
-
);
|
|
13278
|
-
}
|
|
13279
|
-
if (migration.status === "verified") {
|
|
13280
|
-
reportMigration(migration, json);
|
|
13281
|
-
return { data: { status: "verified", migration } };
|
|
13282
|
-
}
|
|
13283
|
-
if (migration.status === "committed") {
|
|
13284
|
-
await requireSourceMigrationDeploy(appDir, migration.appId, deployUrl, token, {
|
|
13285
|
-
envName,
|
|
13286
|
-
remoteName,
|
|
13287
|
-
after: migration.committedAt ?? void 0
|
|
13288
|
-
});
|
|
13289
|
-
const verified = await verifyIdentityMigration(deployUrl, token, migration.appId);
|
|
13290
|
-
reportMigration(verified, json);
|
|
13291
|
-
return { data: { status: "verified", migration: verified } };
|
|
13292
|
-
}
|
|
13293
|
-
if (!isWorkTreeClean(appDir)) {
|
|
13294
|
-
const wranglerPath = wranglerRepoPath(appDir);
|
|
13295
|
-
if (statusFiles(repoToplevel(appDir)).includes(wranglerPath)) {
|
|
13296
|
-
throw commitRequired(appDir, migration, envName);
|
|
13297
|
-
}
|
|
13298
|
-
throw new Refusal(
|
|
13299
|
-
"Commit or discard unrelated working-tree changes before completing the migration.",
|
|
13300
|
-
"dirty_worktree"
|
|
13301
|
-
);
|
|
13302
|
-
}
|
|
13303
|
-
const remote = requireGitHubRemote2(appDir, remoteName, migration.repository);
|
|
13304
|
-
const head = requireLocalHead2(appDir);
|
|
13305
|
-
const remoteOid = remoteBranchOid(appDir, remote.name, head.branch);
|
|
13306
|
-
if (remoteOid !== head.oid) {
|
|
13307
|
-
throw new Refusal(
|
|
13308
|
-
`GitHub ${remote.repository} does not have ${head.branch} at ${head.oid.slice(0, 10)}. Push the migration commit, then rerun.`,
|
|
13309
|
-
"github_push_required",
|
|
13310
|
-
{
|
|
13311
|
-
actionRequired: true,
|
|
13312
|
-
action: { cwd: appDir, argv: ["git", "push", remote.name, head.branch] },
|
|
13313
|
-
extra: { appId: migration.appId, repository: remote.repository, ...head }
|
|
13314
|
-
}
|
|
13315
|
-
);
|
|
13316
|
-
}
|
|
13317
|
-
const committed = await commitIdentityMigration(deployUrl, token, migration.appId, {
|
|
13318
|
-
commitOid: head.oid,
|
|
13319
|
-
branch: head.branch
|
|
13320
|
-
});
|
|
13321
|
-
throw deployRequired(appDir, committed, envName);
|
|
13322
|
-
}
|
|
13323
|
-
function requireGitHubRemote2(appDir, remoteName, repository) {
|
|
13324
|
-
const selected = selectGitHubRemote(appDir, { name: remoteName, repository });
|
|
13325
|
-
if (selected) return selected;
|
|
13326
|
-
throw new Refusal(
|
|
13327
|
-
remoteName ? `Remote "${remoteName}" is not the expected GitHub repository.` : repository ? `No local GitHub remote points at ${repository}. Add one or pass --remote <name>.` : "No unambiguous GitHub remote was found. Add `origin` or pass --remote <name>.",
|
|
13328
|
-
"github_remote_required"
|
|
13329
|
-
);
|
|
13330
|
-
}
|
|
13331
|
-
function requireLocalHead2(appDir) {
|
|
13332
|
-
const branch = currentBranch(appDir);
|
|
13333
|
-
if (!branch) throw new Refusal("HEAD is detached; switch to a branch first.", "detached_head");
|
|
13334
|
-
const oid = resolveCommit(appDir, "HEAD");
|
|
13335
|
-
if (!oid) throw new Refusal("The repository has no commit to publish.", "no_commits");
|
|
13336
|
-
return { branch, oid };
|
|
13337
|
-
}
|
|
13338
|
-
function requirePublishedHead2(appDir, remote) {
|
|
13339
|
-
if (!isWorkTreeClean(appDir)) {
|
|
13340
|
-
throw new Refusal(
|
|
13341
|
-
"Commit or discard local changes before preparing migration.",
|
|
13342
|
-
"dirty_worktree"
|
|
13343
|
-
);
|
|
13344
|
-
}
|
|
13345
|
-
const head = requireLocalHead2(appDir);
|
|
13346
|
-
if (remoteBranchOid(appDir, remote.name, head.branch) !== head.oid) {
|
|
13347
|
-
throw new Refusal(
|
|
13348
|
-
`GitHub ${remote.repository} does not have ${head.branch} at ${head.oid.slice(0, 10)}. Push it, then rerun.`,
|
|
13349
|
-
"github_push_required",
|
|
13350
|
-
{
|
|
13351
|
-
actionRequired: true,
|
|
13352
|
-
action: { cwd: appDir, argv: ["git", "push", remote.name, head.branch] },
|
|
13353
|
-
extra: { repository: remote.repository, ...head }
|
|
13187
|
+
const dryRun = args["dry-run"] === true;
|
|
13188
|
+
const plan = planAppMigrations(appDir);
|
|
13189
|
+
const pinned = dryRun ? false : pinSdkVersion(appDir, targetRaw);
|
|
13190
|
+
if (!dryRun && plan.edits.length) applyAppMigrationPlan(appDir, plan);
|
|
13191
|
+
if (!args.json) {
|
|
13192
|
+
p34.log.info(`deepspace ${currentRaw} \u2192 ${targetRaw}`);
|
|
13193
|
+
if (pinned) p34.log.success("package.json now asks for the current SDK");
|
|
13194
|
+
if (!plan.pending.length && !plan.blockers.length) {
|
|
13195
|
+
p34.log.success("No source changes required.");
|
|
13354
13196
|
}
|
|
13355
|
-
|
|
13356
|
-
}
|
|
13357
|
-
return head;
|
|
13358
|
-
}
|
|
13359
|
-
function commitRequired(appDir, migration, envName, additionalPaths = []) {
|
|
13360
|
-
const wranglerPath = wranglerRepoPath(appDir);
|
|
13361
|
-
const migrationPaths = [.../* @__PURE__ */ new Set([wranglerPath, ...additionalPaths])].sort();
|
|
13362
|
-
return new Refusal(
|
|
13363
|
-
`Prepared ${migration.legacyAppId} \u2192 ${migration.appId}. Commit the deterministic app migration changes, then rerun.`,
|
|
13364
|
-
"migration_commit_required",
|
|
13365
|
-
{
|
|
13366
|
-
actionRequired: true,
|
|
13367
|
-
action: {
|
|
13368
|
-
cwd: repoToplevel(appDir),
|
|
13369
|
-
argv: [
|
|
13370
|
-
"git",
|
|
13371
|
-
"commit",
|
|
13372
|
-
"--only",
|
|
13373
|
-
"--message",
|
|
13374
|
-
`Migrate DeepSpace app identity to ${migration.appId}`,
|
|
13375
|
-
"--",
|
|
13376
|
-
...migrationPaths
|
|
13377
|
-
]
|
|
13378
|
-
},
|
|
13379
|
-
extra: { migration, env: envName ?? null }
|
|
13197
|
+
reportPlan(plan, !dryRun);
|
|
13380
13198
|
}
|
|
13381
|
-
|
|
13382
|
-
|
|
13383
|
-
|
|
13384
|
-
|
|
13385
|
-
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
|
|
13389
|
-
|
|
13390
|
-
|
|
13391
|
-
|
|
13392
|
-
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
"--only",
|
|
13396
|
-
"--message",
|
|
13397
|
-
"Apply DeepSpace app migrations",
|
|
13398
|
-
"--",
|
|
13399
|
-
...plan.changedFiles
|
|
13400
|
-
]
|
|
13401
|
-
},
|
|
13402
|
-
extra: { sourceMigrations: plan.pending }
|
|
13403
|
-
}
|
|
13404
|
-
);
|
|
13405
|
-
}
|
|
13406
|
-
async function requireSourceMigrationDeploy(appDir, appId, deployUrl, token, options = {}) {
|
|
13407
|
-
const { release: release2 } = await repoApi(deployUrl, token, appId).latestRelease();
|
|
13408
|
-
const deployedIds = releaseMigrationIds(release2?.config);
|
|
13409
|
-
const pendingIds = APP_MIGRATION_DEFINITIONS.map(({ id }) => id).filter(
|
|
13410
|
-
(id) => !deployedIds.has(id)
|
|
13411
|
-
);
|
|
13412
|
-
const releaseAfterCutover = options.after === void 0 || typeof release2?.createdAt === "string" && Number.isFinite(Date.parse(release2.createdAt)) && Date.parse(release2.createdAt) > Date.parse(options.after);
|
|
13413
|
-
if (pendingIds.length === 0 && releaseAfterCutover) return;
|
|
13414
|
-
const sourceState = await getAppSource(deployUrl, token, appId);
|
|
13415
|
-
if (sourceState.source?.provider === "github") {
|
|
13416
|
-
const remote = requireGitHubRemote2(appDir, options.remoteName, sourceState.source.repository);
|
|
13417
|
-
requirePublishedHead2(appDir, remote);
|
|
13418
|
-
}
|
|
13419
|
-
throw new Refusal(
|
|
13420
|
-
pendingIds.length > 0 ? `App migrations ${pendingIds.join(", ")} are not in the live release. Deploy them, then rerun.` : "The canonical app has not been deployed since its identity cutover. Deploy it, then rerun.",
|
|
13421
|
-
"migration_deploy_required",
|
|
13422
|
-
{
|
|
13423
|
-
actionRequired: true,
|
|
13424
|
-
action: {
|
|
13425
|
-
cwd: appDir,
|
|
13426
|
-
argv: ["deepspace", "deploy", ...options.envName ? ["--env", options.envName] : []]
|
|
13427
|
-
},
|
|
13428
|
-
extra: { appId, sourceMigrations: pendingIds, after: options.after ?? null }
|
|
13429
|
-
}
|
|
13430
|
-
);
|
|
13431
|
-
}
|
|
13432
|
-
function releaseMigrationIds(config) {
|
|
13433
|
-
if (config === null || typeof config !== "object" || Array.isArray(config)) return /* @__PURE__ */ new Set();
|
|
13434
|
-
const validation = validateAppMigrationIds(
|
|
13435
|
-
config.appMigrations ?? []
|
|
13436
|
-
);
|
|
13437
|
-
return new Set(validation.valid ? validation.ids : []);
|
|
13438
|
-
}
|
|
13439
|
-
function requireSourceMigrationWorktree(appDir, allowDirtyWrangler = false) {
|
|
13440
|
-
if (isWorkTreeClean(appDir)) return;
|
|
13441
|
-
const changed = statusFiles(repoToplevel(appDir));
|
|
13442
|
-
if (allowDirtyWrangler && changed.every((path) => path === wranglerRepoPath(appDir))) return;
|
|
13443
|
-
throw new Refusal(
|
|
13444
|
-
"Commit or discard unrelated working-tree changes before applying app migrations.",
|
|
13445
|
-
"dirty_worktree"
|
|
13446
|
-
);
|
|
13447
|
-
}
|
|
13448
|
-
function restoreLegacyCommitRequired(appDir, migration, envName) {
|
|
13449
|
-
const wranglerPath = wranglerRepoPath(appDir);
|
|
13450
|
-
return new Refusal(
|
|
13451
|
-
`Restored ${migration.legacyAppId} in wrangler.toml. Commit and push this recovery change, then rerun.`,
|
|
13452
|
-
"migration_restore_commit_required",
|
|
13453
|
-
{
|
|
13454
|
-
actionRequired: true,
|
|
13455
|
-
action: {
|
|
13456
|
-
cwd: repoToplevel(appDir),
|
|
13457
|
-
argv: [
|
|
13458
|
-
"git",
|
|
13459
|
-
"commit",
|
|
13460
|
-
"--only",
|
|
13461
|
-
"--message",
|
|
13462
|
-
`Restore DeepSpace app identity to ${migration.legacyAppId}`,
|
|
13463
|
-
"--",
|
|
13464
|
-
wranglerPath
|
|
13465
|
-
]
|
|
13466
|
-
},
|
|
13467
|
-
extra: { migration, env: envName ?? null }
|
|
13199
|
+
if (plan.blockers.length) {
|
|
13200
|
+
throw new Refusal(
|
|
13201
|
+
`${plan.blockers.length} change(s) need a human or agent edit; each is listed above with its file, line, and the change required.`,
|
|
13202
|
+
"manual_changes_required",
|
|
13203
|
+
{
|
|
13204
|
+
actionRequired: true,
|
|
13205
|
+
extra: {
|
|
13206
|
+
currentVersion: currentRaw,
|
|
13207
|
+
targetVersion: targetRaw,
|
|
13208
|
+
applied: plan.pending.map((migration) => migration.id),
|
|
13209
|
+
blockers: plan.blockers
|
|
13210
|
+
}
|
|
13211
|
+
}
|
|
13212
|
+
);
|
|
13468
13213
|
}
|
|
13469
|
-
|
|
13470
|
-
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
}
|
|
13482
|
-
}
|
|
13483
|
-
function deployRequired(appDir, migration, envName) {
|
|
13484
|
-
return new Refusal(
|
|
13485
|
-
`Identity cutover committed; deploy once to verify ${migration.appId} on the existing physical resources.`,
|
|
13486
|
-
"migration_deploy_required",
|
|
13487
|
-
{
|
|
13488
|
-
actionRequired: true,
|
|
13489
|
-
action: {
|
|
13490
|
-
cwd: appDir,
|
|
13491
|
-
argv: ["deepspace", "deploy", ...envName ? ["--env", envName] : []]
|
|
13214
|
+
return {
|
|
13215
|
+
data: {
|
|
13216
|
+
currentVersion: currentRaw,
|
|
13217
|
+
targetVersion: targetRaw,
|
|
13218
|
+
pinned,
|
|
13219
|
+
dryRun,
|
|
13220
|
+
applied: plan.pending.map((migration) => ({
|
|
13221
|
+
id: migration.id,
|
|
13222
|
+
description: migration.description,
|
|
13223
|
+
files: migration.files,
|
|
13224
|
+
replacements: migration.replacements
|
|
13225
|
+
}))
|
|
13492
13226
|
},
|
|
13493
|
-
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
}
|
|
13497
|
-
|
|
13498
|
-
if (!json) console.log(`No pending DeepSpace app migrations for ${appId}.`);
|
|
13499
|
-
}
|
|
13500
|
-
function reportSourcePlan(plan, json) {
|
|
13501
|
-
if (json || plan.pending.length === 0) return;
|
|
13502
|
-
for (const migration of plan.pending) {
|
|
13503
|
-
console.log(
|
|
13504
|
-
`Pending source migration: ${migration.id} (${migration.files.length} file${migration.files.length === 1 ? "" : "s"})`
|
|
13505
|
-
);
|
|
13227
|
+
// The dependency is pinned but not installed: installing is the package
|
|
13228
|
+
// manager's job, and guessing which one an app uses is how a CLI breaks
|
|
13229
|
+
// a pnpm or bun project.
|
|
13230
|
+
...pinned ? { action: cliAction("npm", "install") } : {}
|
|
13231
|
+
};
|
|
13506
13232
|
}
|
|
13507
|
-
}
|
|
13508
|
-
function reportMigration(migration, json) {
|
|
13509
|
-
if (json) return;
|
|
13510
|
-
console.log(`Migration: ${migration.legacyAppId} \u2192 ${migration.appId}`);
|
|
13511
|
-
console.log(`Status: ${migration.status}`);
|
|
13512
|
-
console.log(`Physical resources: ${migration.resourceId}`);
|
|
13513
|
-
console.log(`GitHub: ${migration.repository}`);
|
|
13514
|
-
}
|
|
13233
|
+
});
|
|
13515
13234
|
|
|
13516
13235
|
// src/cli/cli.ts
|
|
13517
13236
|
var __dirname = dirname10(fileURLToPath3(import.meta.url));
|
|
13518
|
-
var pkg = JSON.parse(
|
|
13519
|
-
if (process.platform === "linux" && !process.env.SSL_CERT_FILE &&
|
|
13237
|
+
var pkg = JSON.parse(readFileSync29(join25(__dirname, "..", "package.json"), "utf-8"));
|
|
13238
|
+
if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync27("/etc/ssl/certs/ca-certificates.crt")) {
|
|
13520
13239
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
13521
13240
|
}
|
|
13522
13241
|
var create2 = defineCommand14({
|
|
@@ -13565,7 +13284,7 @@ var auth = defineCommand14({
|
|
|
13565
13284
|
var app = defineCommand14({
|
|
13566
13285
|
meta: {
|
|
13567
13286
|
name: "app",
|
|
13568
|
-
description: "The app itself: create, init, list, files, source,
|
|
13287
|
+
description: "The app itself: create, init, list, files, source, update, undeploy, transfer, collaborators, domain, usage"
|
|
13569
13288
|
},
|
|
13570
13289
|
subCommands: {
|
|
13571
13290
|
create: create2,
|
|
@@ -13578,7 +13297,7 @@ var app = defineCommand14({
|
|
|
13578
13297
|
domain: domain_default,
|
|
13579
13298
|
usage: usage_default,
|
|
13580
13299
|
source: source_default,
|
|
13581
|
-
|
|
13300
|
+
update: update_default
|
|
13582
13301
|
}
|
|
13583
13302
|
});
|
|
13584
13303
|
var devGroup = defineCommand14({
|