deepspace 0.14.0 → 0.16.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 +40 -0
- package/dist/cli.js +365 -734
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +18 -5
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +25 -18
- package/dist/server.js +35 -14
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +119 -18
- package/dist/worker.js +118 -14
- 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,60 +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
|
-
|
|
3804
|
+
import { request as httpRequest } from "http";
|
|
3805
|
+
import { request as httpsRequest } from "https";
|
|
3806
|
+
|
|
3807
|
+
// src/shared/app-routing.ts
|
|
3801
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;
|
|
3802
3835
|
async function waitForLiveRelease(url, releaseStamp, timeoutMs) {
|
|
3803
|
-
|
|
3804
|
-
}
|
|
3805
|
-
async function waitForReleaseStamp(url, releaseStamp, timeoutMs) {
|
|
3836
|
+
if (!releaseStamp) return "unverifiable";
|
|
3806
3837
|
const deadline = Date.now() + timeoutMs;
|
|
3807
3838
|
let attempt = 0;
|
|
3808
3839
|
let streak = 0;
|
|
3809
3840
|
while (Date.now() < deadline) {
|
|
3810
3841
|
attempt++;
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
if (streak >= 3) return true;
|
|
3819
|
-
await new Promise((resolve19) => setTimeout(resolve19, 1500));
|
|
3820
|
-
continue;
|
|
3821
|
-
}
|
|
3822
|
-
streak = 0;
|
|
3823
|
-
} catch {
|
|
3824
|
-
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;
|
|
3825
3849
|
}
|
|
3826
|
-
|
|
3827
|
-
await
|
|
3850
|
+
streak = 0;
|
|
3851
|
+
await sleep2(Math.min(4e3, 500 * 2 ** Math.min(attempt - 1, 3)));
|
|
3828
3852
|
}
|
|
3829
|
-
return
|
|
3853
|
+
return "unconfirmed";
|
|
3830
3854
|
}
|
|
3831
|
-
async function
|
|
3832
|
-
const
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
const response = await fetch(url, { redirect: "manual" });
|
|
3839
|
-
const body = await response.text();
|
|
3840
|
-
if (!NOT_SERVING.some((marker) => body.includes(marker))) {
|
|
3841
|
-
streak++;
|
|
3842
|
-
if (streak >= 3) return true;
|
|
3843
|
-
await new Promise((resolve19) => setTimeout(resolve19, 1500));
|
|
3844
|
-
continue;
|
|
3845
|
-
}
|
|
3846
|
-
streak = 0;
|
|
3847
|
-
} catch {
|
|
3848
|
-
streak = 0;
|
|
3849
|
-
}
|
|
3850
|
-
const waitMs = Math.min(8e3, 1e3 * 2 ** (attempt - 1));
|
|
3851
|
-
await new Promise((resolve19) => setTimeout(resolve19, waitMs));
|
|
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;
|
|
3852
3862
|
}
|
|
3853
|
-
|
|
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));
|
|
3854
3895
|
}
|
|
3855
3896
|
|
|
3856
3897
|
// src/shared/app-files.ts
|
|
@@ -3870,13 +3911,15 @@ function planUploadParts(totalBytes, partSize) {
|
|
|
3870
3911
|
}
|
|
3871
3912
|
return parts;
|
|
3872
3913
|
}
|
|
3873
|
-
var
|
|
3914
|
+
var GIB = 1024 * 1024 * 1024;
|
|
3915
|
+
var ACCOUNT_STORAGE_LIMIT_BYTES = {
|
|
3874
3916
|
test: 0,
|
|
3875
|
-
free:
|
|
3876
|
-
starter:
|
|
3877
|
-
premium:
|
|
3878
|
-
admin:
|
|
3917
|
+
free: 1 * GIB,
|
|
3918
|
+
starter: 10 * GIB,
|
|
3919
|
+
premium: 30 * GIB,
|
|
3920
|
+
admin: 100 * GIB
|
|
3879
3921
|
};
|
|
3922
|
+
var REPO_STORE_LIMIT_BYTES = 256 * 1024 * 1024;
|
|
3880
3923
|
var MAX_DEPLOY_ASSET_FILE_BYTES = 25 * 1024 * 1024;
|
|
3881
3924
|
var DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
3882
3925
|
"text/html",
|
|
@@ -3944,7 +3987,7 @@ function readDocumentationDeployManifest(appDir, clientDir) {
|
|
|
3944
3987
|
return JSON.parse(readFileSync15(manifestPath, "utf8"));
|
|
3945
3988
|
}
|
|
3946
3989
|
|
|
3947
|
-
// src/cli/commands/
|
|
3990
|
+
// src/cli/commands/update/app-migrations.ts
|
|
3948
3991
|
import {
|
|
3949
3992
|
closeSync,
|
|
3950
3993
|
constants,
|
|
@@ -3961,7 +4004,7 @@ import { join as join15 } from "path";
|
|
|
3961
4004
|
// src/shared/protocol/app-migrations.ts
|
|
3962
4005
|
var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
3963
4006
|
var MAX_APP_MIGRATION_IDS = 256;
|
|
3964
|
-
var
|
|
4007
|
+
var WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
|
|
3965
4008
|
function validateAppMigrationIds(value) {
|
|
3966
4009
|
if (!Array.isArray(value)) {
|
|
3967
4010
|
return { valid: false, reason: "app migrations must be a JSON string array" };
|
|
@@ -3987,14 +4030,14 @@ function validateAppMigrationIds(value) {
|
|
|
3987
4030
|
return { valid: true, ids };
|
|
3988
4031
|
}
|
|
3989
4032
|
|
|
3990
|
-
// src/cli/commands/
|
|
4033
|
+
// src/cli/commands/update/app-migrations.ts
|
|
3991
4034
|
var APP_MIGRATIONS_MANIFEST = "deepspace.migrations.json";
|
|
3992
4035
|
var SOURCE_MIGRATIONS = [
|
|
3993
4036
|
{
|
|
3994
|
-
id:
|
|
3995
|
-
description: "
|
|
3996
|
-
transform:
|
|
3997
|
-
findBlockers:
|
|
4037
|
+
id: WORKER_OWNED_NOT_FOUND_MIGRATION_ID,
|
|
4038
|
+
description: "Let the worker answer misses, so deleted files 404 instead of serving HTML",
|
|
4039
|
+
transform: migrateWorkerOwnedNotFound,
|
|
4040
|
+
findBlockers: findWorkerOwnedNotFoundBlockers
|
|
3998
4041
|
}
|
|
3999
4042
|
];
|
|
4000
4043
|
var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
|
|
@@ -4039,7 +4082,7 @@ function planAppMigrations(appDir) {
|
|
|
4039
4082
|
const before = readRegularFile(absolutePath);
|
|
4040
4083
|
let after = before;
|
|
4041
4084
|
for (const migration of pendingMigrations) {
|
|
4042
|
-
const result = migration.transform(after);
|
|
4085
|
+
const result = migration.transform(after, repoPath);
|
|
4043
4086
|
after = result.source;
|
|
4044
4087
|
if (result.replacements > 0) {
|
|
4045
4088
|
const planned = perMigration.get(migration.id) ?? {
|
|
@@ -4076,9 +4119,6 @@ function planAppMigrations(appDir) {
|
|
|
4076
4119
|
};
|
|
4077
4120
|
}
|
|
4078
4121
|
function applyAppMigrationPlan(appDir, plan) {
|
|
4079
|
-
if (plan.blockers.length > 0) {
|
|
4080
|
-
throw new Error("Cannot apply an app migration plan with unresolved blockers.");
|
|
4081
|
-
}
|
|
4082
4122
|
const repo = repoToplevel(appDir);
|
|
4083
4123
|
for (const edit of plan.edits) {
|
|
4084
4124
|
const absolutePath = join15(repo, edit.path);
|
|
@@ -4138,119 +4178,78 @@ function writeRegularFile(path, before, after) {
|
|
|
4138
4178
|
}
|
|
4139
4179
|
function isSourcePath(repoPath, appPrefix) {
|
|
4140
4180
|
if (appPrefix && !repoPath.startsWith(appPrefix)) return false;
|
|
4181
|
+
if (repoPath.endsWith("wrangler.toml")) return true;
|
|
4141
4182
|
return /\.(?:[cm]?[jt]sx?)$/.test(repoPath);
|
|
4142
4183
|
}
|
|
4143
|
-
function
|
|
4184
|
+
function migrateWorkerOwnedNotFound(source, file) {
|
|
4144
4185
|
let replacements = 0;
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
replacements++;
|
|
4152
|
-
return `${open}${quote}x-app-id${quote}${separator}${receiver}.DEEPSPACE_APP_ID${close}`;
|
|
4153
|
-
}
|
|
4154
|
-
);
|
|
4155
|
-
line = line.replace(
|
|
4156
|
-
/(['"])x-app-name\1(\s*:\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME/g,
|
|
4157
|
-
(_match, quote, separator, receiver) => {
|
|
4158
|
-
replacements++;
|
|
4159
|
-
return `${quote}x-app-id${quote}${separator}${receiver}.DEEPSPACE_APP_ID`;
|
|
4160
|
-
}
|
|
4161
|
-
);
|
|
4162
|
-
line = line.replace(
|
|
4163
|
-
/(\[\s*)(['"])x-app-name\2(\s*\]\s*=\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME/g,
|
|
4164
|
-
(_match, open, quote, separator, receiver) => {
|
|
4165
|
-
replacements++;
|
|
4166
|
-
return `${open}${quote}x-app-id${quote}${separator}${receiver}.DEEPSPACE_APP_ID`;
|
|
4167
|
-
}
|
|
4168
|
-
);
|
|
4169
|
-
line = line.replace(
|
|
4170
|
-
/(forwardedParams\.set\(\s*)(['"])appName\2(\s*,\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME(\s*\))/g,
|
|
4171
|
-
(_match, open, quote, separator, receiver, close) => {
|
|
4172
|
-
replacements++;
|
|
4173
|
-
return `${open}${quote}appId${quote}${separator}${receiver}.DEEPSPACE_APP_ID${close}`;
|
|
4174
|
-
}
|
|
4175
|
-
);
|
|
4176
|
-
lines[index] = line;
|
|
4177
|
-
}
|
|
4178
|
-
if (replacements === 0) return { source, replacements };
|
|
4179
|
-
let migrated = lines.join("\n");
|
|
4180
|
-
if (/forwardedParams\.set\(\s*['"]appId['"]/.test(migrated)) {
|
|
4181
|
-
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(
|
|
4182
|
-
"/_deepspace/subscriptions/plans?appName=",
|
|
4183
|
-
"/_deepspace/subscriptions/plans?appId="
|
|
4184
|
-
);
|
|
4186
|
+
if (file.endsWith("wrangler.toml")) {
|
|
4187
|
+
const next = source.replace(/not_found_handling\s*=\s*"single-page-application"/g, () => {
|
|
4188
|
+
replacements++;
|
|
4189
|
+
return 'not_found_handling = "none"';
|
|
4190
|
+
});
|
|
4191
|
+
return { source: next, replacements };
|
|
4185
4192
|
}
|
|
4186
|
-
|
|
4187
|
-
"
|
|
4188
|
-
|
|
4193
|
+
const retargeted = source.replace(
|
|
4194
|
+
/(url\.pathname\s*=\s*)(['"])\/index\.html\2/g,
|
|
4195
|
+
(_match, assignment, quote) => {
|
|
4196
|
+
replacements++;
|
|
4197
|
+
return `${assignment}${quote}/${quote}`;
|
|
4198
|
+
}
|
|
4189
4199
|
);
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
replacements
|
|
4193
|
-
};
|
|
4200
|
+
const guarded = insertFileMissGuard(retargeted);
|
|
4201
|
+
return { source: guarded.source, replacements: replacements + guarded.replacements };
|
|
4194
4202
|
}
|
|
4195
|
-
|
|
4203
|
+
var SHELL_FALLBACK_LINE = /^(\s*)url\.pathname\s*=\s*(['"])\/\2\s*$/;
|
|
4204
|
+
var GUARD_LOOKBACK_LINES = 8;
|
|
4205
|
+
function insertFileMissGuard(source) {
|
|
4196
4206
|
const lines = source.split("\n");
|
|
4207
|
+
let replacements = 0;
|
|
4197
4208
|
for (let index = lines.length - 1; index >= 0; index--) {
|
|
4198
|
-
const match =
|
|
4199
|
-
if (!match ||
|
|
4200
|
-
const
|
|
4201
|
-
lines.splice(
|
|
4209
|
+
const match = SHELL_FALLBACK_LINE.exec(lines[index]);
|
|
4210
|
+
if (!match || hasFileMissGuard(lines, index)) continue;
|
|
4211
|
+
const indent = match[1];
|
|
4212
|
+
lines.splice(
|
|
4213
|
+
index,
|
|
4214
|
+
0,
|
|
4215
|
+
`${indent}// A FILE, not a client route: a miss must 404. Returning the shell here`,
|
|
4216
|
+
`${indent}// is HTML parsed as JavaScript, which is a blank page.`,
|
|
4217
|
+
`${indent}if (${FILE_MISS_TEST}) {`,
|
|
4218
|
+
`${indent} return c.json({ error: 'not_found' }, 404)`,
|
|
4219
|
+
`${indent}}`
|
|
4220
|
+
);
|
|
4221
|
+
replacements++;
|
|
4202
4222
|
}
|
|
4203
|
-
return lines.join("\n");
|
|
4223
|
+
return { source: lines.join("\n"), replacements };
|
|
4204
4224
|
}
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
if (/^\s*(?:readonly\s+)?DEEPSPACE_APP_ID\??\s*:/.test(line)) return true;
|
|
4210
|
-
const trimmed = line.trimStart();
|
|
4211
|
-
if (!trimmed) continue;
|
|
4212
|
-
const indent = line.length - trimmed.length;
|
|
4213
|
-
if (indent < propertyIndent && (trimmed.includes("{") || trimmed.startsWith("}"))) break;
|
|
4214
|
-
}
|
|
4215
|
-
}
|
|
4216
|
-
return false;
|
|
4225
|
+
var FILE_MISS_TEST = "url.pathname.slice(url.pathname.lastIndexOf('/') + 1).includes('.')";
|
|
4226
|
+
function hasFileMissGuard(lines, fallbackIndex) {
|
|
4227
|
+
const start = Math.max(0, fallbackIndex - GUARD_LOOKBACK_LINES);
|
|
4228
|
+
return lines.slice(start, fallbackIndex).some((line) => line.includes(FILE_MISS_TEST) || /\bnamesAFile\s*\(/.test(line));
|
|
4217
4229
|
}
|
|
4218
|
-
function
|
|
4230
|
+
function findWorkerOwnedNotFoundBlockers(source, file) {
|
|
4231
|
+
if (file.endsWith("wrangler.toml")) return [];
|
|
4232
|
+
if (!source.includes("ASSETS")) return [];
|
|
4219
4233
|
const blockers = [];
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
if (!/['"]
|
|
4223
|
-
continue;
|
|
4224
|
-
}
|
|
4234
|
+
const lines = source.split("\n");
|
|
4235
|
+
for (const [index, line] of lines.entries()) {
|
|
4236
|
+
if (!/['"`]\/index\.html/.test(line)) continue;
|
|
4225
4237
|
blockers.push({
|
|
4226
|
-
migrationId:
|
|
4238
|
+
migrationId: WORKER_OWNED_NOT_FOUND_MIGRATION_ID,
|
|
4227
4239
|
file,
|
|
4228
4240
|
line: index + 1,
|
|
4229
|
-
message:
|
|
4241
|
+
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).`
|
|
4230
4242
|
});
|
|
4231
4243
|
}
|
|
4232
4244
|
return blockers;
|
|
4233
4245
|
}
|
|
4234
4246
|
|
|
4235
4247
|
// src/cli/commands/deploy/build.ts
|
|
4236
|
-
var
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
"
|
|
4240
|
-
|
|
4241
|
-
"/_deepspace/*",
|
|
4242
|
-
"/_documentation",
|
|
4243
|
-
"/_documentation/*",
|
|
4244
|
-
"/_documentation-root",
|
|
4245
|
-
"/_documentation-root/*",
|
|
4246
|
-
// Agent-protocol paths: the worker must see them so they cannot fall through
|
|
4247
|
-
// to the SPA shell (templates/base/src/server/http-routes.ts reserves them).
|
|
4248
|
-
"/llms.txt",
|
|
4249
|
-
"/llms-full.txt",
|
|
4250
|
-
"/.well-known/mcp",
|
|
4251
|
-
"/.well-known/mcp.json",
|
|
4252
|
-
"/.well-known/mcp/*"
|
|
4253
|
-
]);
|
|
4248
|
+
var RESERVED_RUN_WORKER_FIRST2 = new Set(RESERVED_RUN_WORKER_FIRST);
|
|
4249
|
+
function declaredCompatibilityDate(config) {
|
|
4250
|
+
const declared = config.compatibility_date;
|
|
4251
|
+
return typeof declared === "string" && /^\d{4}-\d{2}-\d{2}$/.test(declared) ? declared : null;
|
|
4252
|
+
}
|
|
4254
4253
|
async function buildDeployBundle(options) {
|
|
4255
4254
|
const { appDir, appName, envName, sharedDevVarsCache, output, spinner: spinner2 } = options;
|
|
4256
4255
|
const junk = removeMacosJunk(appDir);
|
|
@@ -4362,7 +4361,10 @@ async function buildDeployBundle(options) {
|
|
|
4362
4361
|
appMigrations,
|
|
4363
4362
|
doManifest,
|
|
4364
4363
|
customBindings,
|
|
4365
|
-
extraRoutes
|
|
4364
|
+
extraRoutes,
|
|
4365
|
+
compatibilityDate: declaredCompatibilityDate(outputConfig),
|
|
4366
|
+
compatibilityFlags: Array.isArray(outputConfig.compatibility_flags) ? outputConfig.compatibility_flags.filter((flag) => typeof flag === "string") : [],
|
|
4367
|
+
notFoundHandling: typeof outputConfig.assets?.not_found_handling === "string" ? outputConfig.assets.not_found_handling : null
|
|
4366
4368
|
};
|
|
4367
4369
|
}
|
|
4368
4370
|
function extractRunWorkerFirst(config) {
|
|
@@ -4372,7 +4374,7 @@ function extractRunWorkerFirst(config) {
|
|
|
4372
4374
|
for (const entry of raw) {
|
|
4373
4375
|
if (typeof entry !== "string") continue;
|
|
4374
4376
|
const route = entry.trim();
|
|
4375
|
-
if (!route.startsWith("/") ||
|
|
4377
|
+
if (!route.startsWith("/") || RESERVED_RUN_WORKER_FIRST2.has(route) || seen.has(route)) continue;
|
|
4376
4378
|
seen.add(route);
|
|
4377
4379
|
routes.push(route);
|
|
4378
4380
|
}
|
|
@@ -5418,7 +5420,7 @@ async function pushWithTransientRetry(push) {
|
|
|
5418
5420
|
const message = errorMessage3(error);
|
|
5419
5421
|
const transient = /(?:HTTP |error: )(?:429|503)\b/i.test(message);
|
|
5420
5422
|
if (transient && attempt < backoffMs.length && Date.now() + backoffMs[attempt] < deadline) {
|
|
5421
|
-
await new Promise((
|
|
5423
|
+
await new Promise((resolve20) => setTimeout(resolve20, backoffMs[attempt]));
|
|
5422
5424
|
continue;
|
|
5423
5425
|
}
|
|
5424
5426
|
throw error;
|
|
@@ -5510,6 +5512,15 @@ async function deployBuiltBundle(options) {
|
|
|
5510
5512
|
if (Object.keys(bundle.assetConfig).length) {
|
|
5511
5513
|
form.append("assetConfig", JSON.stringify(bundle.assetConfig));
|
|
5512
5514
|
}
|
|
5515
|
+
if (bundle.compatibilityDate) {
|
|
5516
|
+
form.append("compatibilityDate", bundle.compatibilityDate);
|
|
5517
|
+
}
|
|
5518
|
+
if (bundle.compatibilityFlags.length) {
|
|
5519
|
+
form.append("compatibilityFlags", JSON.stringify(bundle.compatibilityFlags));
|
|
5520
|
+
}
|
|
5521
|
+
if (bundle.notFoundHandling) {
|
|
5522
|
+
form.append("notFoundHandling", bundle.notFoundHandling);
|
|
5523
|
+
}
|
|
5513
5524
|
form.append("name", appName);
|
|
5514
5525
|
if (confirmRename) form.append("confirmRename", "true");
|
|
5515
5526
|
if (shouldSendLineage(repository.commitOid, repository.recoverable)) {
|
|
@@ -5763,7 +5774,7 @@ async function postWithRetry(url, makeInit, {
|
|
|
5763
5774
|
lastError = error;
|
|
5764
5775
|
}
|
|
5765
5776
|
if (attempt < attempts) {
|
|
5766
|
-
await new Promise((
|
|
5777
|
+
await new Promise((resolve20) => setTimeout(resolve20, Math.min(8e3, 500 * 2 ** (attempt - 1))));
|
|
5767
5778
|
}
|
|
5768
5779
|
}
|
|
5769
5780
|
if (lastResponse) return lastResponse;
|
|
@@ -5883,46 +5894,6 @@ function errorMessage5(error) {
|
|
|
5883
5894
|
return error instanceof Error ? error.message : String(error);
|
|
5884
5895
|
}
|
|
5885
5896
|
|
|
5886
|
-
// src/cli/commands/migrate/legacy-app-id.ts
|
|
5887
|
-
var LEGACY_APP_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
5888
|
-
function readLegacyAppId(cwd = process.cwd(), wranglerEnv) {
|
|
5889
|
-
const cfg = readWranglerIdentityConfig(cwd);
|
|
5890
|
-
if (!cfg) return null;
|
|
5891
|
-
const slot = wranglerEnv ? cfg.env?.[wranglerEnv] : cfg;
|
|
5892
|
-
if (!slot) return null;
|
|
5893
|
-
const declaredId = slot.vars?.DEEPSPACE_APP_ID;
|
|
5894
|
-
if (declaredId !== void 0) {
|
|
5895
|
-
if (typeof declaredId === "string" && LEGACY_APP_ID_RE.test(declaredId)) return declaredId;
|
|
5896
|
-
throw new InputError(
|
|
5897
|
-
"DEEPSPACE_APP_ID is present but invalid; correct it before migrating.",
|
|
5898
|
-
"invalid_app_id"
|
|
5899
|
-
);
|
|
5900
|
-
}
|
|
5901
|
-
const appName = typeof slot.vars?.APP_NAME === "string" ? slot.vars.APP_NAME : null;
|
|
5902
|
-
const workerName = typeof slot.name === "string" ? slot.name : null;
|
|
5903
|
-
if (!appName) return null;
|
|
5904
|
-
if (!workerName) {
|
|
5905
|
-
throw new InputError(
|
|
5906
|
-
"Legacy migration requires both Worker name and APP_NAME to be present and identical.",
|
|
5907
|
-
"ambiguous_legacy_app_id"
|
|
5908
|
-
);
|
|
5909
|
-
}
|
|
5910
|
-
if (appName !== workerName) {
|
|
5911
|
-
throw new InputError(
|
|
5912
|
-
`Legacy APP_NAME "${appName}" does not match Worker name "${workerName}"; migration cannot choose safely.`,
|
|
5913
|
-
"ambiguous_legacy_app_id"
|
|
5914
|
-
);
|
|
5915
|
-
}
|
|
5916
|
-
return !APP_ID_RE.test(appName) && LEGACY_APP_ID_RE.test(appName) ? appName : null;
|
|
5917
|
-
}
|
|
5918
|
-
function legacyDeployRefusal(appDir, wranglerEnv) {
|
|
5919
|
-
const legacyAppId = readLegacyAppId(appDir, wranglerEnv);
|
|
5920
|
-
return legacyAppId ? {
|
|
5921
|
-
error: `This checkout still uses legacy app identity ${legacyAppId}. Run \`deepspace app migrate --dry-run\`, then complete \`deepspace app migrate\` before deploying.`,
|
|
5922
|
-
code: "legacy_app_migration_required"
|
|
5923
|
-
} : null;
|
|
5924
|
-
}
|
|
5925
|
-
|
|
5926
5897
|
// src/cli/commands/deploy.ts
|
|
5927
5898
|
var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
5928
5899
|
var deploy_default = defineCommand3({
|
|
@@ -6006,10 +5977,6 @@ var deploy_default = defineCommand3({
|
|
|
6006
5977
|
}
|
|
6007
5978
|
const appName = nameResult.name;
|
|
6008
5979
|
let appId = readAppId(appDir, envName);
|
|
6009
|
-
if (!appId) {
|
|
6010
|
-
const refusal = legacyDeployRefusal(appDir, envName);
|
|
6011
|
-
if (refusal) output.die(refusal.error, refusal.code);
|
|
6012
|
-
}
|
|
6013
5980
|
p12.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
|
|
6014
5981
|
const { token, ownerId } = await authenticate(appDir, output);
|
|
6015
5982
|
if (!appId) {
|
|
@@ -6075,15 +6042,18 @@ var deploy_default = defineCommand3({
|
|
|
6075
6042
|
output,
|
|
6076
6043
|
spinner: spinner2
|
|
6077
6044
|
});
|
|
6045
|
+
let serving = "unverifiable";
|
|
6078
6046
|
if (body.url) {
|
|
6079
|
-
spinner2.message("Waiting for edge
|
|
6080
|
-
|
|
6081
|
-
if (
|
|
6082
|
-
spinner2.stop(
|
|
6047
|
+
spinner2.message("Waiting for the edge to serve this release...");
|
|
6048
|
+
serving = await waitForLiveRelease(body.url, body.releaseStamp, 9e4);
|
|
6049
|
+
if (serving !== "confirmed") {
|
|
6050
|
+
spinner2.stop(
|
|
6051
|
+
serving === "unconfirmed" ? "Deployed \u2014 the edge is still rolling this release out" : "Deployed \u2014 serving could not be verified from here"
|
|
6052
|
+
);
|
|
6083
6053
|
p12.log.warn(
|
|
6084
|
-
"
|
|
6054
|
+
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."
|
|
6085
6055
|
);
|
|
6086
|
-
p12.log.info(`URL: ${body.url}
|
|
6056
|
+
p12.log.info(`URL: ${body.url}`);
|
|
6087
6057
|
await syncCommerce(appDir, appId, token, output.nonInteractive);
|
|
6088
6058
|
if (output.json) {
|
|
6089
6059
|
return output.emitJson({
|
|
@@ -6093,7 +6063,10 @@ var deploy_default = defineCommand3({
|
|
|
6093
6063
|
url: body.url,
|
|
6094
6064
|
releaseId: body.releaseId ?? null,
|
|
6095
6065
|
bundleRetained: body.bundleRetained ?? null,
|
|
6096
|
-
edgePropagating
|
|
6066
|
+
// `serving` says what was actually established; `edgePropagating`
|
|
6067
|
+
// stays for callers written against the old shape.
|
|
6068
|
+
serving,
|
|
6069
|
+
edgePropagating: serving === "unconfirmed",
|
|
6097
6070
|
recoverable: repository.recoverable,
|
|
6098
6071
|
...staleBaseGuardFields(body)
|
|
6099
6072
|
});
|
|
@@ -6111,6 +6084,9 @@ var deploy_default = defineCommand3({
|
|
|
6111
6084
|
appId,
|
|
6112
6085
|
appName,
|
|
6113
6086
|
url: body.url ?? null,
|
|
6087
|
+
// Always present, so a caller branches on one field instead of
|
|
6088
|
+
// inferring success from the absence of `edgePropagating`.
|
|
6089
|
+
serving,
|
|
6114
6090
|
releaseId: body.releaseId ?? null,
|
|
6115
6091
|
bundleRetained: body.bundleRetained ?? null,
|
|
6116
6092
|
recoverable: repository.recoverable,
|
|
@@ -6565,7 +6541,7 @@ function formatEvent(e, color, now = Date.now()) {
|
|
|
6565
6541
|
const client = e.source === "client" ? paint("35", "CLIENT", color) + " " : "";
|
|
6566
6542
|
return `${time} ${client}${tag} ${body}`;
|
|
6567
6543
|
}
|
|
6568
|
-
var
|
|
6544
|
+
var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6569
6545
|
var logs_default = defineCommand4({
|
|
6570
6546
|
meta: {
|
|
6571
6547
|
name: "logs",
|
|
@@ -6685,7 +6661,7 @@ var logs_default = defineCommand4({
|
|
|
6685
6661
|
}
|
|
6686
6662
|
}
|
|
6687
6663
|
for (; ; ) {
|
|
6688
|
-
await
|
|
6664
|
+
await sleep3(backoff || interval);
|
|
6689
6665
|
const freshToken = await ensureToken();
|
|
6690
6666
|
let page;
|
|
6691
6667
|
try {
|
|
@@ -7855,11 +7831,11 @@ function fmtDate(iso) {
|
|
|
7855
7831
|
}
|
|
7856
7832
|
async function confirm4(message) {
|
|
7857
7833
|
process.stdout.write(`${message} [y/N] `);
|
|
7858
|
-
return new Promise((
|
|
7834
|
+
return new Promise((resolve20) => {
|
|
7859
7835
|
process.stdin.setEncoding("utf-8");
|
|
7860
7836
|
process.stdin.once("data", (data) => {
|
|
7861
7837
|
const answer = data.toString().trim().toLowerCase();
|
|
7862
|
-
|
|
7838
|
+
resolve20(answer === "y" || answer === "yes");
|
|
7863
7839
|
});
|
|
7864
7840
|
});
|
|
7865
7841
|
}
|
|
@@ -8211,8 +8187,14 @@ async function request(baseUrl, token, path, init = {}) {
|
|
|
8211
8187
|
if (res.ok) return res;
|
|
8212
8188
|
const text2 = await res.text();
|
|
8213
8189
|
let code;
|
|
8190
|
+
let details;
|
|
8214
8191
|
try {
|
|
8215
|
-
|
|
8192
|
+
const body = JSON.parse(text2.trim());
|
|
8193
|
+
code = typeof body.code === "string" ? body.code : void 0;
|
|
8194
|
+
const rest = Object.fromEntries(
|
|
8195
|
+
Object.entries(body).filter(([key]) => key !== "error" && key !== "code")
|
|
8196
|
+
);
|
|
8197
|
+
if (Object.keys(rest).length > 0) details = rest;
|
|
8216
8198
|
} catch {
|
|
8217
8199
|
}
|
|
8218
8200
|
throw new ApiError(
|
|
@@ -8222,7 +8204,8 @@ async function request(baseUrl, token, path, init = {}) {
|
|
|
8222
8204
|
// oversized request short is not a different failure from the worker
|
|
8223
8205
|
// refusing it, and two codes made it look like one.
|
|
8224
8206
|
code ?? (res.status === 413 ? "too_large" : res.status >= 500 ? "server_error" : "http_error"),
|
|
8225
|
-
path
|
|
8207
|
+
path,
|
|
8208
|
+
details
|
|
8226
8209
|
);
|
|
8227
8210
|
}
|
|
8228
8211
|
async function readJson(res, path) {
|
|
@@ -8585,8 +8568,8 @@ var list4 = defineDeepspaceCommand({
|
|
|
8585
8568
|
}
|
|
8586
8569
|
if (pending.length) {
|
|
8587
8570
|
console.log(`PENDING INVITES ON ${app2}`);
|
|
8588
|
-
for (const
|
|
8589
|
-
console.log(` ${
|
|
8571
|
+
for (const p35 of pending) {
|
|
8572
|
+
console.log(` ${p35.email} (expires ${new Date(p35.expiresAt).toLocaleDateString()})`);
|
|
8590
8573
|
}
|
|
8591
8574
|
}
|
|
8592
8575
|
}
|
|
@@ -8684,7 +8667,7 @@ var cancel3 = defineDeepspaceCommand({
|
|
|
8684
8667
|
`/api/app-collaborators/${encodeURIComponent(app2)}`
|
|
8685
8668
|
);
|
|
8686
8669
|
const target2 = email.trim().toLowerCase();
|
|
8687
|
-
const found = pending.find((
|
|
8670
|
+
const found = pending.find((p35) => p35.email.toLowerCase() === target2);
|
|
8688
8671
|
if (!found) {
|
|
8689
8672
|
throw new Refusal(`${email} has no pending invite on ${app2}`, "no_pending_invite", {
|
|
8690
8673
|
action: cliAction("deepspace", "app", "collaborators", "list", "--app", app2)
|
|
@@ -8742,7 +8725,10 @@ var accept = defineDeepspaceCommand({
|
|
|
8742
8725
|
}
|
|
8743
8726
|
return {
|
|
8744
8727
|
data: { ...res },
|
|
8745
|
-
|
|
8728
|
+
// `pull` fast-forwards a checkout this caller does not have yet — the
|
|
8729
|
+
// invite just granted access, so there is nothing local to update.
|
|
8730
|
+
// `clone` is the step that actually works from here.
|
|
8731
|
+
action: cliAction("deepspace", "clone", res.appId)
|
|
8746
8732
|
};
|
|
8747
8733
|
}
|
|
8748
8734
|
});
|
|
@@ -11032,13 +11018,14 @@ Re-run with --allow-do-deletion to accept the data loss.`,
|
|
|
11032
11018
|
"invalid_response"
|
|
11033
11019
|
);
|
|
11034
11020
|
}
|
|
11035
|
-
let
|
|
11021
|
+
let serving = "unverifiable";
|
|
11036
11022
|
if (result.url) {
|
|
11037
|
-
spinner2?.message("Waiting for edge
|
|
11038
|
-
|
|
11023
|
+
spinner2?.message("Waiting for the edge to serve this release...");
|
|
11024
|
+
serving = await waitForLiveRelease(result.url, result.releaseStamp, 9e4);
|
|
11039
11025
|
}
|
|
11026
|
+
const edgePropagating = serving === "unconfirmed";
|
|
11040
11027
|
spinner2?.stop(
|
|
11041
|
-
|
|
11028
|
+
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.`
|
|
11042
11029
|
);
|
|
11043
11030
|
const doClassGuard = result.doClassGuard === "unverified" ? "unverified" : void 0;
|
|
11044
11031
|
if (!args.json) {
|
|
@@ -11069,6 +11056,8 @@ Re-run with --allow-do-deletion to accept the data loss.`,
|
|
|
11069
11056
|
url: result.url ?? null,
|
|
11070
11057
|
versionId: result.versionId ?? null,
|
|
11071
11058
|
wranglerEnv: wranglerEnv ?? null,
|
|
11059
|
+
// Always present, matching `deploy`: one field to branch on.
|
|
11060
|
+
serving,
|
|
11072
11061
|
...edgePropagating ? { edgePropagating: true } : {},
|
|
11073
11062
|
...doClassGuard ? { doClassGuard } : {}
|
|
11074
11063
|
}
|
|
@@ -13072,541 +13061,183 @@ function sameRefs(left, right) {
|
|
|
13072
13061
|
return left.every((ref) => expected.get(ref.name) === ref.oid);
|
|
13073
13062
|
}
|
|
13074
13063
|
|
|
13075
|
-
// src/cli/commands/
|
|
13076
|
-
|
|
13077
|
-
|
|
13078
|
-
|
|
13079
|
-
|
|
13080
|
-
|
|
13081
|
-
|
|
13082
|
-
|
|
13083
|
-
|
|
13084
|
-
|
|
13085
|
-
|
|
13086
|
-
|
|
13087
|
-
|
|
13088
|
-
|
|
13089
|
-
|
|
13090
|
-
|
|
13091
|
-
|
|
13092
|
-
|
|
13093
|
-
|
|
13094
|
-
|
|
13095
|
-
|
|
13096
|
-
|
|
13097
|
-
|
|
13098
|
-
|
|
13099
|
-
const
|
|
13100
|
-
|
|
13101
|
-
|
|
13102
|
-
|
|
13103
|
-
|
|
13064
|
+
// src/cli/commands/update.ts
|
|
13065
|
+
import * as p34 from "@clack/prompts";
|
|
13066
|
+
import { existsSync as existsSync26, readFileSync as readFileSync28, writeFileSync as writeFileSync10 } from "fs";
|
|
13067
|
+
import { join as join24, resolve as resolve19 } from "path";
|
|
13068
|
+
var REGISTRY = "https://registry.npmjs.org/deepspace/latest";
|
|
13069
|
+
var DOCS_URL = "https://documentation.deep.space/cli-reference/commands";
|
|
13070
|
+
var OLDEST_SUPPORTED_MINOR = 12;
|
|
13071
|
+
function parseVersion(raw) {
|
|
13072
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(raw.trim().replace(/^[\^~]/, ""));
|
|
13073
|
+
if (!match) return null;
|
|
13074
|
+
return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
|
|
13075
|
+
}
|
|
13076
|
+
function gapIsTooWide(from, to) {
|
|
13077
|
+
if (from.major !== to.major) return true;
|
|
13078
|
+
return from.major === 0 && from.minor < OLDEST_SUPPORTED_MINOR;
|
|
13079
|
+
}
|
|
13080
|
+
function readAppSdkVersion(appDir) {
|
|
13081
|
+
const installed = join24(appDir, "node_modules", "deepspace", "package.json");
|
|
13082
|
+
if (existsSync26(installed)) {
|
|
13083
|
+
const parsed2 = JSON.parse(readFileSync28(installed, "utf8"));
|
|
13084
|
+
if (typeof parsed2.version === "string") return parsed2.version;
|
|
13085
|
+
}
|
|
13086
|
+
const manifest = join24(appDir, "package.json");
|
|
13087
|
+
if (!existsSync26(manifest)) return null;
|
|
13088
|
+
const parsed = JSON.parse(readFileSync28(manifest, "utf8"));
|
|
13089
|
+
return parsed.dependencies?.deepspace ?? null;
|
|
13090
|
+
}
|
|
13091
|
+
function pinSdkVersion(appDir, version) {
|
|
13092
|
+
const path = join24(appDir, "package.json");
|
|
13093
|
+
const source = readFileSync28(path, "utf8");
|
|
13094
|
+
const updated = source.replace(
|
|
13095
|
+
/("deepspace"\s*:\s*")[^"]+(")/,
|
|
13096
|
+
(_match, head, tail) => `${head}^${version}${tail}`
|
|
13104
13097
|
);
|
|
13105
|
-
return
|
|
13106
|
-
|
|
13107
|
-
|
|
13108
|
-
const result = await apiFetch(
|
|
13109
|
-
deployUrl,
|
|
13110
|
-
token,
|
|
13111
|
-
`/api/apps/${encodeURIComponent(appId)}/identity-migration/commit`,
|
|
13112
|
-
{ method: "POST", body: JSON.stringify(input) }
|
|
13113
|
-
);
|
|
13114
|
-
return result.migration;
|
|
13098
|
+
if (updated === source) return false;
|
|
13099
|
+
writeFileSync10(path, updated);
|
|
13100
|
+
return true;
|
|
13115
13101
|
}
|
|
13116
|
-
async function
|
|
13117
|
-
await
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13102
|
+
async function latestPublishedVersion() {
|
|
13103
|
+
const response = await fetch(REGISTRY, { signal: AbortSignal.timeout(15e3) }).catch(
|
|
13104
|
+
(error) => {
|
|
13105
|
+
throw new Refusal(
|
|
13106
|
+
`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.`,
|
|
13107
|
+
"registry_unreachable"
|
|
13108
|
+
);
|
|
13109
|
+
}
|
|
13122
13110
|
);
|
|
13111
|
+
if (!response.ok) {
|
|
13112
|
+
throw new Refusal(
|
|
13113
|
+
`The npm registry answered ${response.status} for the latest deepspace version. Pass --to <version> to update without a lookup.`,
|
|
13114
|
+
"registry_unreachable"
|
|
13115
|
+
);
|
|
13116
|
+
}
|
|
13117
|
+
const body = await response.json();
|
|
13118
|
+
if (typeof body.version !== "string") {
|
|
13119
|
+
throw new Refusal("The npm registry returned no version for deepspace.", "registry_unreachable");
|
|
13120
|
+
}
|
|
13121
|
+
return body.version;
|
|
13123
13122
|
}
|
|
13124
|
-
|
|
13125
|
-
const
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13123
|
+
function reportPlan(plan, applied) {
|
|
13124
|
+
for (const migration of plan.pending) {
|
|
13125
|
+
const verb = applied ? "Applied" : "Would apply";
|
|
13126
|
+
p34.log.success(
|
|
13127
|
+
`${verb}: ${migration.description} \u2014 ${migration.replacements} change(s) in ${migration.files.join(", ")}`
|
|
13128
|
+
);
|
|
13129
|
+
}
|
|
13130
|
+
if (plan.blockers.length) {
|
|
13131
|
+
p34.log.warn(
|
|
13132
|
+
`${plan.blockers.length} change(s) could not be made for you \u2014 this file differs from the version the change was written against:`
|
|
13133
|
+
);
|
|
13134
|
+
for (const blocker of plan.blockers) {
|
|
13135
|
+
p34.log.message(` ${blocker.file}:${blocker.line}
|
|
13136
|
+
${blocker.message}`);
|
|
13137
|
+
}
|
|
13138
|
+
}
|
|
13132
13139
|
}
|
|
13133
|
-
|
|
13134
|
-
// src/cli/commands/migrate.ts
|
|
13135
|
-
var STRICT_APP_ID_RE2 = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
13136
|
-
var migrate_default = defineDeepspaceCommand({
|
|
13140
|
+
var update_default = defineDeepspaceCommand({
|
|
13137
13141
|
meta: {
|
|
13138
|
-
name: "
|
|
13139
|
-
description: "
|
|
13142
|
+
name: "update",
|
|
13143
|
+
description: "Move this app onto the current SDK and apply the source changes it requires"
|
|
13140
13144
|
},
|
|
13141
13145
|
args: {
|
|
13142
|
-
|
|
13146
|
+
to: {
|
|
13143
13147
|
type: "string",
|
|
13144
|
-
description: "
|
|
13145
|
-
required: false
|
|
13146
|
-
},
|
|
13147
|
-
env: {
|
|
13148
|
-
type: "string",
|
|
13149
|
-
alias: "e",
|
|
13150
|
-
description: "wrangler.toml environment whose app id should be migrated",
|
|
13148
|
+
description: "Target version (default: the latest published deepspace)",
|
|
13151
13149
|
required: false
|
|
13152
13150
|
},
|
|
13153
13151
|
"dry-run": {
|
|
13154
13152
|
type: "boolean",
|
|
13155
|
-
description: "
|
|
13156
|
-
default: false
|
|
13157
|
-
},
|
|
13158
|
-
cancel: {
|
|
13159
|
-
type: "boolean",
|
|
13160
|
-
description: "Cancel a prepared migration after restoring and publishing the legacy id",
|
|
13153
|
+
description: "Report what would change without touching the checkout",
|
|
13161
13154
|
default: false
|
|
13162
13155
|
}
|
|
13163
13156
|
},
|
|
13164
13157
|
async run({ args }) {
|
|
13165
|
-
const
|
|
13166
|
-
|
|
13167
|
-
if (exclusiveModes > 1) {
|
|
13168
|
-
throw new Refusal("Use only one of --dry-run or --cancel.", "conflicting_migration_mode");
|
|
13169
|
-
}
|
|
13170
|
-
const appDir = findAppDir();
|
|
13171
|
-
if (!appDir) {
|
|
13172
|
-
throw new Refusal("Run this command from a DeepSpace app Git checkout.", "not_in_app_repo");
|
|
13173
|
-
}
|
|
13174
|
-
assertSyncableRepo(appDir);
|
|
13175
|
-
const envName = typeof args.env === "string" && args.env ? args.env : void 0;
|
|
13176
|
-
const currentAppId = readAppId(appDir, envName) ?? readLegacyAppId(appDir, envName);
|
|
13177
|
-
if (!currentAppId) {
|
|
13158
|
+
const appDir = resolve19(".");
|
|
13159
|
+
if (!hasWranglerConfig(appDir)) {
|
|
13178
13160
|
throw new Refusal(
|
|
13179
|
-
"wrangler.toml
|
|
13180
|
-
"
|
|
13161
|
+
"No wrangler.toml found \u2014 run this from inside a DeepSpace app directory.",
|
|
13162
|
+
"not_in_app_repo"
|
|
13181
13163
|
);
|
|
13182
13164
|
}
|
|
13183
|
-
|
|
13184
|
-
const
|
|
13185
|
-
|
|
13186
|
-
const existingMigration = await getIdentityMigration(deployUrl, token, currentAppId);
|
|
13187
|
-
if (recoveryMode) {
|
|
13188
|
-
if (!existingMigration) {
|
|
13189
|
-
if (!args.json) console.log("No reversible identity migration is recorded.");
|
|
13190
|
-
return { data: { status: "no_migration", appId: currentAppId } };
|
|
13191
|
-
}
|
|
13192
|
-
return recoverMigration({
|
|
13193
|
-
mode: recoveryMode,
|
|
13194
|
-
appDir,
|
|
13195
|
-
envName,
|
|
13196
|
-
remoteName: typeof args.remote === "string" ? args.remote : void 0,
|
|
13197
|
-
currentAppId,
|
|
13198
|
-
token,
|
|
13199
|
-
deployUrl,
|
|
13200
|
-
migration: existingMigration,
|
|
13201
|
-
json: args.json
|
|
13202
|
-
});
|
|
13203
|
-
}
|
|
13204
|
-
const sourcePlan = planAppMigrations(appDir);
|
|
13205
|
-
if (sourcePlan.blockers.length > 0) {
|
|
13206
|
-
const first = sourcePlan.blockers[0];
|
|
13165
|
+
assertSyncableRepo(appDir);
|
|
13166
|
+
const currentRaw = readAppSdkVersion(appDir);
|
|
13167
|
+
if (!currentRaw) {
|
|
13207
13168
|
throw new Refusal(
|
|
13208
|
-
|
|
13209
|
-
"
|
|
13210
|
-
{ extra: { blockers: sourcePlan.blockers } }
|
|
13169
|
+
"This app does not depend on `deepspace` \u2014 nothing to update.",
|
|
13170
|
+
"not_in_app_repo"
|
|
13211
13171
|
);
|
|
13212
13172
|
}
|
|
13213
|
-
|
|
13214
|
-
|
|
13215
|
-
|
|
13216
|
-
|
|
13217
|
-
return {
|
|
13218
|
-
data: {
|
|
13219
|
-
status: "ready",
|
|
13220
|
-
appId: currentAppId,
|
|
13221
|
-
sourceMigrations: sourcePlan.pending
|
|
13222
|
-
}
|
|
13223
|
-
};
|
|
13224
|
-
}
|
|
13225
|
-
if (sourcePlan.pending.length > 0) {
|
|
13226
|
-
requireSourceMigrationWorktree(appDir);
|
|
13227
|
-
applyAppMigrationPlan(appDir, sourcePlan);
|
|
13228
|
-
throw sourceMigrationCommitRequired(appDir, sourcePlan);
|
|
13229
|
-
}
|
|
13230
|
-
await requireSourceMigrationDeploy(appDir, currentAppId, deployUrl, token, {
|
|
13231
|
-
envName,
|
|
13232
|
-
remoteName: typeof args.remote === "string" ? args.remote : void 0
|
|
13233
|
-
});
|
|
13234
|
-
reportUpToDate(currentAppId, args.json);
|
|
13235
|
-
return { data: { status: "up_to_date", appId: currentAppId } };
|
|
13236
|
-
}
|
|
13237
|
-
if (sourcePlan.pending.length === 0 && existingMigration.status === "verified") {
|
|
13238
|
-
await requireSourceMigrationDeploy(appDir, currentAppId, deployUrl, token, {
|
|
13239
|
-
envName,
|
|
13240
|
-
remoteName: typeof args.remote === "string" ? args.remote : void 0
|
|
13241
|
-
});
|
|
13242
|
-
}
|
|
13243
|
-
if (args["dry-run"]) {
|
|
13244
|
-
reportMigration(existingMigration, args.json);
|
|
13245
|
-
reportSourcePlan(sourcePlan, args.json);
|
|
13246
|
-
return {
|
|
13247
|
-
data: {
|
|
13248
|
-
status: existingMigration.status,
|
|
13249
|
-
migration: existingMigration,
|
|
13250
|
-
sourceMigrations: sourcePlan.pending
|
|
13251
|
-
}
|
|
13252
|
-
};
|
|
13253
|
-
}
|
|
13254
|
-
if (sourcePlan.pending.length > 0) {
|
|
13255
|
-
requireSourceMigrationWorktree(appDir, existingMigration.status === "prepared");
|
|
13256
|
-
applyAppMigrationPlan(appDir, sourcePlan);
|
|
13257
|
-
throw existingMigration.status === "prepared" ? commitRequired(appDir, existingMigration, envName, sourcePlan.changedFiles) : sourceMigrationCommitRequired(appDir, sourcePlan);
|
|
13258
|
-
}
|
|
13259
|
-
return continuePreparedMigration({
|
|
13260
|
-
appDir,
|
|
13261
|
-
envName,
|
|
13262
|
-
remoteName: typeof args.remote === "string" ? args.remote : void 0,
|
|
13263
|
-
token,
|
|
13264
|
-
deployUrl,
|
|
13265
|
-
migration: existingMigration,
|
|
13266
|
-
json: args.json
|
|
13267
|
-
});
|
|
13268
|
-
}
|
|
13269
|
-
if (existingMigration && existingMigration.status !== "prepared" && existingMigration.status !== "rolled_back") {
|
|
13173
|
+
const targetRaw = typeof args.to === "string" && args.to.trim() ? args.to.trim() : await latestPublishedVersion();
|
|
13174
|
+
const current = parseVersion(currentRaw);
|
|
13175
|
+
const target2 = parseVersion(targetRaw);
|
|
13176
|
+
if (!current || !target2) {
|
|
13270
13177
|
throw new Refusal(
|
|
13271
|
-
`
|
|
13272
|
-
"
|
|
13273
|
-
{ extra: { migration: existingMigration } }
|
|
13178
|
+
`Could not read a version to update from ("${currentRaw}") or to ("${targetRaw}").`,
|
|
13179
|
+
"invalid_version"
|
|
13274
13180
|
);
|
|
13275
13181
|
}
|
|
13276
|
-
|
|
13277
|
-
appDir,
|
|
13278
|
-
typeof args.remote === "string" ? args.remote : void 0
|
|
13279
|
-
);
|
|
13280
|
-
const head = requirePublishedHead2(appDir, remote);
|
|
13281
|
-
const proposedAppId = existingMigration?.appId ?? mintAppId();
|
|
13282
|
-
const inventory = await inspectIdentityMigration(deployUrl, token, currentAppId, {
|
|
13283
|
-
destinationAppId: proposedAppId,
|
|
13284
|
-
repository: remote.repository
|
|
13285
|
-
});
|
|
13286
|
-
if (!inventory.ready) {
|
|
13182
|
+
if (gapIsTooWide(current, target2)) {
|
|
13287
13183
|
throw new Refusal(
|
|
13288
|
-
`
|
|
13289
|
-
"
|
|
13290
|
-
{ extra: {
|
|
13184
|
+
`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}`,
|
|
13185
|
+
"version_gap_too_wide",
|
|
13186
|
+
{ actionRequired: true, extra: { currentVersion: currentRaw, targetVersion: targetRaw } }
|
|
13291
13187
|
);
|
|
13292
13188
|
}
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
);
|
|
13303
|
-
console.log(
|
|
13304
|
-
`Physical stores retained at ${inventory.app.resourceId}: ${inventory.retainedPhysicalStores.map((store) => store.kind).join(", ")}`
|
|
13305
|
-
);
|
|
13306
|
-
console.log("No changes made.");
|
|
13307
|
-
}
|
|
13308
|
-
reportSourcePlan(sourcePlan, args.json);
|
|
13309
|
-
return {
|
|
13310
|
-
data: {
|
|
13311
|
-
status: "ready",
|
|
13312
|
-
legacyAppId: currentAppId,
|
|
13313
|
-
proposedAppId,
|
|
13314
|
-
resourceId: inventory.app.resourceId,
|
|
13315
|
-
repository: remote.repository,
|
|
13316
|
-
branch: head.branch,
|
|
13317
|
-
commitOid: head.oid,
|
|
13318
|
-
sourceClaimRequired: inventory.app.sourceClaimRequired,
|
|
13319
|
-
sourceMigrations: sourcePlan.pending,
|
|
13320
|
-
inventory
|
|
13321
|
-
}
|
|
13322
|
-
};
|
|
13323
|
-
}
|
|
13324
|
-
const migration = existingMigration?.status === "prepared" ? existingMigration : await prepareIdentityMigration(deployUrl, token, currentAppId, {
|
|
13325
|
-
destinationAppId: proposedAppId,
|
|
13326
|
-
repository: remote.repository,
|
|
13327
|
-
expectedSourceRevision: inventory.app.sourceRevision
|
|
13328
|
-
});
|
|
13329
|
-
applyAppMigrationPlan(appDir, sourcePlan);
|
|
13330
|
-
writeAppId(appDir, migration.appId, { wranglerEnv: envName, force: true });
|
|
13331
|
-
throw commitRequired(appDir, migration, envName, sourcePlan.changedFiles);
|
|
13332
|
-
}
|
|
13333
|
-
});
|
|
13334
|
-
async function recoverMigration(input) {
|
|
13335
|
-
const { appDir, envName, remoteName, currentAppId, token, deployUrl, migration, json } = input;
|
|
13336
|
-
if (migration.status === "rolled_back") {
|
|
13337
|
-
if (currentAppId !== migration.legacyAppId) {
|
|
13338
|
-
writeAppId(appDir, migration.legacyAppId, { wranglerEnv: envName, force: true });
|
|
13339
|
-
throw restoreLegacyCommitRequired(appDir, migration, envName);
|
|
13340
|
-
}
|
|
13341
|
-
reportMigration(migration, json);
|
|
13342
|
-
return { data: { status: "rolled_back", migration } };
|
|
13343
|
-
}
|
|
13344
|
-
if (migration.status !== "prepared") {
|
|
13345
|
-
throw new Refusal(
|
|
13346
|
-
"This migration is already committed. Complete it by deploying the canonical app.",
|
|
13347
|
-
"forward_recovery_required",
|
|
13348
|
-
{ extra: { migration } }
|
|
13349
|
-
);
|
|
13350
|
-
}
|
|
13351
|
-
if (currentAppId !== migration.legacyAppId) {
|
|
13352
|
-
writeAppId(appDir, migration.legacyAppId, { wranglerEnv: envName, force: true });
|
|
13353
|
-
throw restoreLegacyCommitRequired(appDir, migration, envName);
|
|
13354
|
-
}
|
|
13355
|
-
const remote = requireGitHubRemote2(appDir, remoteName, migration.repository);
|
|
13356
|
-
requirePublishedHead2(appDir, remote);
|
|
13357
|
-
await cancelIdentityMigration(deployUrl, token, migration.legacyAppId);
|
|
13358
|
-
if (!json) console.log(`Canceled ${migration.legacyAppId} \u2192 ${migration.appId}.`);
|
|
13359
|
-
return { data: { status: "cancelled", legacyAppId: migration.legacyAppId } };
|
|
13360
|
-
}
|
|
13361
|
-
async function continuePreparedMigration(input) {
|
|
13362
|
-
const { appDir, envName, remoteName, token, deployUrl, migration, json } = input;
|
|
13363
|
-
if (migration.status === "rolled_back") {
|
|
13364
|
-
throw new Refusal(
|
|
13365
|
-
`This migration was rolled back. Restore DEEPSPACE_APP_ID to ${migration.legacyAppId} before continuing.`,
|
|
13366
|
-
"migration_rolled_back"
|
|
13367
|
-
);
|
|
13368
|
-
}
|
|
13369
|
-
if (migration.status === "verified") {
|
|
13370
|
-
reportMigration(migration, json);
|
|
13371
|
-
return { data: { status: "verified", migration } };
|
|
13372
|
-
}
|
|
13373
|
-
if (migration.status === "committed") {
|
|
13374
|
-
await requireSourceMigrationDeploy(appDir, migration.appId, deployUrl, token, {
|
|
13375
|
-
envName,
|
|
13376
|
-
remoteName,
|
|
13377
|
-
after: migration.committedAt ?? void 0
|
|
13378
|
-
});
|
|
13379
|
-
const verified = await verifyIdentityMigration(deployUrl, token, migration.appId);
|
|
13380
|
-
reportMigration(verified, json);
|
|
13381
|
-
return { data: { status: "verified", migration: verified } };
|
|
13382
|
-
}
|
|
13383
|
-
if (!isWorkTreeClean(appDir)) {
|
|
13384
|
-
const wranglerPath = wranglerRepoPath(appDir);
|
|
13385
|
-
if (statusFiles(repoToplevel(appDir)).includes(wranglerPath)) {
|
|
13386
|
-
throw commitRequired(appDir, migration, envName);
|
|
13387
|
-
}
|
|
13388
|
-
throw new Refusal(
|
|
13389
|
-
"Commit or discard unrelated working-tree changes before completing the migration.",
|
|
13390
|
-
"dirty_worktree"
|
|
13391
|
-
);
|
|
13392
|
-
}
|
|
13393
|
-
const remote = requireGitHubRemote2(appDir, remoteName, migration.repository);
|
|
13394
|
-
const head = requireLocalHead2(appDir);
|
|
13395
|
-
const remoteOid = remoteBranchOid(appDir, remote.name, head.branch);
|
|
13396
|
-
if (remoteOid !== head.oid) {
|
|
13397
|
-
throw new Refusal(
|
|
13398
|
-
`GitHub ${remote.repository} does not have ${head.branch} at ${head.oid.slice(0, 10)}. Push the migration commit, then rerun.`,
|
|
13399
|
-
"github_push_required",
|
|
13400
|
-
{
|
|
13401
|
-
actionRequired: true,
|
|
13402
|
-
action: { cwd: appDir, argv: ["git", "push", remote.name, head.branch] },
|
|
13403
|
-
extra: { appId: migration.appId, repository: remote.repository, ...head }
|
|
13404
|
-
}
|
|
13405
|
-
);
|
|
13406
|
-
}
|
|
13407
|
-
const committed = await commitIdentityMigration(deployUrl, token, migration.appId, {
|
|
13408
|
-
commitOid: head.oid,
|
|
13409
|
-
branch: head.branch
|
|
13410
|
-
});
|
|
13411
|
-
throw deployRequired(appDir, committed, envName);
|
|
13412
|
-
}
|
|
13413
|
-
function requireGitHubRemote2(appDir, remoteName, repository) {
|
|
13414
|
-
const selected = selectGitHubRemote(appDir, { name: remoteName, repository });
|
|
13415
|
-
if (selected) return selected;
|
|
13416
|
-
throw new Refusal(
|
|
13417
|
-
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>.",
|
|
13418
|
-
"github_remote_required"
|
|
13419
|
-
);
|
|
13420
|
-
}
|
|
13421
|
-
function requireLocalHead2(appDir) {
|
|
13422
|
-
const branch = currentBranch(appDir);
|
|
13423
|
-
if (!branch) throw new Refusal("HEAD is detached; switch to a branch first.", "detached_head");
|
|
13424
|
-
const oid = resolveCommit(appDir, "HEAD");
|
|
13425
|
-
if (!oid) throw new Refusal("The repository has no commit to publish.", "no_commits");
|
|
13426
|
-
return { branch, oid };
|
|
13427
|
-
}
|
|
13428
|
-
function requirePublishedHead2(appDir, remote) {
|
|
13429
|
-
if (!isWorkTreeClean(appDir)) {
|
|
13430
|
-
throw new Refusal(
|
|
13431
|
-
"Commit or discard local changes before preparing migration.",
|
|
13432
|
-
"dirty_worktree"
|
|
13433
|
-
);
|
|
13434
|
-
}
|
|
13435
|
-
const head = requireLocalHead2(appDir);
|
|
13436
|
-
if (remoteBranchOid(appDir, remote.name, head.branch) !== head.oid) {
|
|
13437
|
-
throw new Refusal(
|
|
13438
|
-
`GitHub ${remote.repository} does not have ${head.branch} at ${head.oid.slice(0, 10)}. Push it, then rerun.`,
|
|
13439
|
-
"github_push_required",
|
|
13440
|
-
{
|
|
13441
|
-
actionRequired: true,
|
|
13442
|
-
action: { cwd: appDir, argv: ["git", "push", remote.name, head.branch] },
|
|
13443
|
-
extra: { repository: remote.repository, ...head }
|
|
13189
|
+
const dryRun = args["dry-run"] === true;
|
|
13190
|
+
const plan = planAppMigrations(appDir);
|
|
13191
|
+
const pinned = dryRun ? false : pinSdkVersion(appDir, targetRaw);
|
|
13192
|
+
if (!dryRun && plan.edits.length) applyAppMigrationPlan(appDir, plan);
|
|
13193
|
+
if (!args.json) {
|
|
13194
|
+
p34.log.info(`deepspace ${currentRaw} \u2192 ${targetRaw}`);
|
|
13195
|
+
if (pinned) p34.log.success("package.json now asks for the current SDK");
|
|
13196
|
+
if (!plan.pending.length && !plan.blockers.length) {
|
|
13197
|
+
p34.log.success("No source changes required.");
|
|
13444
13198
|
}
|
|
13445
|
-
|
|
13446
|
-
}
|
|
13447
|
-
return head;
|
|
13448
|
-
}
|
|
13449
|
-
function commitRequired(appDir, migration, envName, additionalPaths = []) {
|
|
13450
|
-
const wranglerPath = wranglerRepoPath(appDir);
|
|
13451
|
-
const migrationPaths = [.../* @__PURE__ */ new Set([wranglerPath, ...additionalPaths])].sort();
|
|
13452
|
-
return new Refusal(
|
|
13453
|
-
`Prepared ${migration.legacyAppId} \u2192 ${migration.appId}. Commit the deterministic app migration changes, then rerun.`,
|
|
13454
|
-
"migration_commit_required",
|
|
13455
|
-
{
|
|
13456
|
-
actionRequired: true,
|
|
13457
|
-
action: {
|
|
13458
|
-
cwd: repoToplevel(appDir),
|
|
13459
|
-
argv: [
|
|
13460
|
-
"git",
|
|
13461
|
-
"commit",
|
|
13462
|
-
"--only",
|
|
13463
|
-
"--message",
|
|
13464
|
-
`Migrate DeepSpace app identity to ${migration.appId}`,
|
|
13465
|
-
"--",
|
|
13466
|
-
...migrationPaths
|
|
13467
|
-
]
|
|
13468
|
-
},
|
|
13469
|
-
extra: { migration, env: envName ?? null }
|
|
13470
|
-
}
|
|
13471
|
-
);
|
|
13472
|
-
}
|
|
13473
|
-
function sourceMigrationCommitRequired(appDir, plan) {
|
|
13474
|
-
const migrationIds = plan.pending.map((migration) => migration.id);
|
|
13475
|
-
return new Refusal(
|
|
13476
|
-
`Applied ${migrationIds.join(", ")}. Commit the deterministic app migration changes, then rerun.`,
|
|
13477
|
-
"migration_commit_required",
|
|
13478
|
-
{
|
|
13479
|
-
actionRequired: true,
|
|
13480
|
-
action: {
|
|
13481
|
-
cwd: repoToplevel(appDir),
|
|
13482
|
-
argv: [
|
|
13483
|
-
"git",
|
|
13484
|
-
"commit",
|
|
13485
|
-
"--only",
|
|
13486
|
-
"--message",
|
|
13487
|
-
"Apply DeepSpace app migrations",
|
|
13488
|
-
"--",
|
|
13489
|
-
...plan.changedFiles
|
|
13490
|
-
]
|
|
13491
|
-
},
|
|
13492
|
-
extra: { sourceMigrations: plan.pending }
|
|
13199
|
+
reportPlan(plan, !dryRun);
|
|
13493
13200
|
}
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
}
|
|
13509
|
-
throw new Refusal(
|
|
13510
|
-
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.",
|
|
13511
|
-
"migration_deploy_required",
|
|
13512
|
-
{
|
|
13513
|
-
actionRequired: true,
|
|
13514
|
-
action: {
|
|
13515
|
-
cwd: appDir,
|
|
13516
|
-
argv: ["deepspace", "deploy", ...options.envName ? ["--env", options.envName] : []]
|
|
13517
|
-
},
|
|
13518
|
-
extra: { appId, sourceMigrations: pendingIds, after: options.after ?? null }
|
|
13519
|
-
}
|
|
13520
|
-
);
|
|
13521
|
-
}
|
|
13522
|
-
function releaseMigrationIds(config) {
|
|
13523
|
-
if (config === null || typeof config !== "object" || Array.isArray(config)) return /* @__PURE__ */ new Set();
|
|
13524
|
-
const validation = validateAppMigrationIds(
|
|
13525
|
-
config.appMigrations ?? []
|
|
13526
|
-
);
|
|
13527
|
-
return new Set(validation.valid ? validation.ids : []);
|
|
13528
|
-
}
|
|
13529
|
-
function requireSourceMigrationWorktree(appDir, allowDirtyWrangler = false) {
|
|
13530
|
-
if (isWorkTreeClean(appDir)) return;
|
|
13531
|
-
const changed = statusFiles(repoToplevel(appDir));
|
|
13532
|
-
if (allowDirtyWrangler && changed.every((path) => path === wranglerRepoPath(appDir))) return;
|
|
13533
|
-
throw new Refusal(
|
|
13534
|
-
"Commit or discard unrelated working-tree changes before applying app migrations.",
|
|
13535
|
-
"dirty_worktree"
|
|
13536
|
-
);
|
|
13537
|
-
}
|
|
13538
|
-
function restoreLegacyCommitRequired(appDir, migration, envName) {
|
|
13539
|
-
const wranglerPath = wranglerRepoPath(appDir);
|
|
13540
|
-
return new Refusal(
|
|
13541
|
-
`Restored ${migration.legacyAppId} in wrangler.toml. Commit and push this recovery change, then rerun.`,
|
|
13542
|
-
"migration_restore_commit_required",
|
|
13543
|
-
{
|
|
13544
|
-
actionRequired: true,
|
|
13545
|
-
action: {
|
|
13546
|
-
cwd: repoToplevel(appDir),
|
|
13547
|
-
argv: [
|
|
13548
|
-
"git",
|
|
13549
|
-
"commit",
|
|
13550
|
-
"--only",
|
|
13551
|
-
"--message",
|
|
13552
|
-
`Restore DeepSpace app identity to ${migration.legacyAppId}`,
|
|
13553
|
-
"--",
|
|
13554
|
-
wranglerPath
|
|
13555
|
-
]
|
|
13556
|
-
},
|
|
13557
|
-
extra: { migration, env: envName ?? null }
|
|
13201
|
+
if (plan.blockers.length) {
|
|
13202
|
+
throw new Refusal(
|
|
13203
|
+
`${plan.blockers.length} change(s) need a human or agent edit; each is listed above with its file, line, and the change required.`,
|
|
13204
|
+
"manual_changes_required",
|
|
13205
|
+
{
|
|
13206
|
+
actionRequired: true,
|
|
13207
|
+
extra: {
|
|
13208
|
+
currentVersion: currentRaw,
|
|
13209
|
+
targetVersion: targetRaw,
|
|
13210
|
+
applied: plan.pending.map((migration) => migration.id),
|
|
13211
|
+
blockers: plan.blockers
|
|
13212
|
+
}
|
|
13213
|
+
}
|
|
13214
|
+
);
|
|
13558
13215
|
}
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
}
|
|
13572
|
-
}
|
|
13573
|
-
function deployRequired(appDir, migration, envName) {
|
|
13574
|
-
return new Refusal(
|
|
13575
|
-
`Identity cutover committed; deploy once to verify ${migration.appId} on the existing physical resources.`,
|
|
13576
|
-
"migration_deploy_required",
|
|
13577
|
-
{
|
|
13578
|
-
actionRequired: true,
|
|
13579
|
-
action: {
|
|
13580
|
-
cwd: appDir,
|
|
13581
|
-
argv: ["deepspace", "deploy", ...envName ? ["--env", envName] : []]
|
|
13216
|
+
return {
|
|
13217
|
+
data: {
|
|
13218
|
+
currentVersion: currentRaw,
|
|
13219
|
+
targetVersion: targetRaw,
|
|
13220
|
+
pinned,
|
|
13221
|
+
dryRun,
|
|
13222
|
+
applied: plan.pending.map((migration) => ({
|
|
13223
|
+
id: migration.id,
|
|
13224
|
+
description: migration.description,
|
|
13225
|
+
files: migration.files,
|
|
13226
|
+
replacements: migration.replacements
|
|
13227
|
+
}))
|
|
13582
13228
|
},
|
|
13583
|
-
|
|
13584
|
-
|
|
13585
|
-
|
|
13586
|
-
}
|
|
13587
|
-
|
|
13588
|
-
if (!json) console.log(`No pending DeepSpace app migrations for ${appId}.`);
|
|
13589
|
-
}
|
|
13590
|
-
function reportSourcePlan(plan, json) {
|
|
13591
|
-
if (json || plan.pending.length === 0) return;
|
|
13592
|
-
for (const migration of plan.pending) {
|
|
13593
|
-
console.log(
|
|
13594
|
-
`Pending source migration: ${migration.id} (${migration.files.length} file${migration.files.length === 1 ? "" : "s"})`
|
|
13595
|
-
);
|
|
13229
|
+
// The dependency is pinned but not installed: installing is the package
|
|
13230
|
+
// manager's job, and guessing which one an app uses is how a CLI breaks
|
|
13231
|
+
// a pnpm or bun project.
|
|
13232
|
+
...pinned ? { action: cliAction("npm", "install") } : {}
|
|
13233
|
+
};
|
|
13596
13234
|
}
|
|
13597
|
-
}
|
|
13598
|
-
function reportMigration(migration, json) {
|
|
13599
|
-
if (json) return;
|
|
13600
|
-
console.log(`Migration: ${migration.legacyAppId} \u2192 ${migration.appId}`);
|
|
13601
|
-
console.log(`Status: ${migration.status}`);
|
|
13602
|
-
console.log(`Physical resources: ${migration.resourceId}`);
|
|
13603
|
-
console.log(`GitHub: ${migration.repository}`);
|
|
13604
|
-
}
|
|
13235
|
+
});
|
|
13605
13236
|
|
|
13606
13237
|
// src/cli/cli.ts
|
|
13607
13238
|
var __dirname = dirname10(fileURLToPath3(import.meta.url));
|
|
13608
|
-
var pkg = JSON.parse(
|
|
13609
|
-
if (process.platform === "linux" && !process.env.SSL_CERT_FILE &&
|
|
13239
|
+
var pkg = JSON.parse(readFileSync29(join25(__dirname, "..", "package.json"), "utf-8"));
|
|
13240
|
+
if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync27("/etc/ssl/certs/ca-certificates.crt")) {
|
|
13610
13241
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
13611
13242
|
}
|
|
13612
13243
|
var create2 = defineCommand14({
|
|
@@ -13655,7 +13286,7 @@ var auth = defineCommand14({
|
|
|
13655
13286
|
var app = defineCommand14({
|
|
13656
13287
|
meta: {
|
|
13657
13288
|
name: "app",
|
|
13658
|
-
description: "The app itself: create, init, list, files, source,
|
|
13289
|
+
description: "The app itself: create, init, list, files, source, update, undeploy, transfer, collaborators, domain, usage"
|
|
13659
13290
|
},
|
|
13660
13291
|
subCommands: {
|
|
13661
13292
|
create: create2,
|
|
@@ -13668,7 +13299,7 @@ var app = defineCommand14({
|
|
|
13668
13299
|
domain: domain_default,
|
|
13669
13300
|
usage: usage_default,
|
|
13670
13301
|
source: source_default,
|
|
13671
|
-
|
|
13302
|
+
update: update_default
|
|
13672
13303
|
}
|
|
13673
13304
|
});
|
|
13674
13305
|
var devGroup = defineCommand14({
|