deepspace 0.14.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 +24 -0
- package/dist/cli.js +358 -729
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/worker.d.ts +80 -1
- package/dist/worker.js +82 -0
- 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
|
|
@@ -3944,7 +3985,7 @@ function readDocumentationDeployManifest(appDir, clientDir) {
|
|
|
3944
3985
|
return JSON.parse(readFileSync15(manifestPath, "utf8"));
|
|
3945
3986
|
}
|
|
3946
3987
|
|
|
3947
|
-
// src/cli/commands/
|
|
3988
|
+
// src/cli/commands/update/app-migrations.ts
|
|
3948
3989
|
import {
|
|
3949
3990
|
closeSync,
|
|
3950
3991
|
constants,
|
|
@@ -3961,7 +4002,7 @@ import { join as join15 } from "path";
|
|
|
3961
4002
|
// src/shared/protocol/app-migrations.ts
|
|
3962
4003
|
var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
3963
4004
|
var MAX_APP_MIGRATION_IDS = 256;
|
|
3964
|
-
var
|
|
4005
|
+
var WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
|
|
3965
4006
|
function validateAppMigrationIds(value) {
|
|
3966
4007
|
if (!Array.isArray(value)) {
|
|
3967
4008
|
return { valid: false, reason: "app migrations must be a JSON string array" };
|
|
@@ -3987,14 +4028,14 @@ function validateAppMigrationIds(value) {
|
|
|
3987
4028
|
return { valid: true, ids };
|
|
3988
4029
|
}
|
|
3989
4030
|
|
|
3990
|
-
// src/cli/commands/
|
|
4031
|
+
// src/cli/commands/update/app-migrations.ts
|
|
3991
4032
|
var APP_MIGRATIONS_MANIFEST = "deepspace.migrations.json";
|
|
3992
4033
|
var SOURCE_MIGRATIONS = [
|
|
3993
4034
|
{
|
|
3994
|
-
id:
|
|
3995
|
-
description: "
|
|
3996
|
-
transform:
|
|
3997
|
-
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
|
|
3998
4039
|
}
|
|
3999
4040
|
];
|
|
4000
4041
|
var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
|
|
@@ -4039,7 +4080,7 @@ function planAppMigrations(appDir) {
|
|
|
4039
4080
|
const before = readRegularFile(absolutePath);
|
|
4040
4081
|
let after = before;
|
|
4041
4082
|
for (const migration of pendingMigrations) {
|
|
4042
|
-
const result = migration.transform(after);
|
|
4083
|
+
const result = migration.transform(after, repoPath);
|
|
4043
4084
|
after = result.source;
|
|
4044
4085
|
if (result.replacements > 0) {
|
|
4045
4086
|
const planned = perMigration.get(migration.id) ?? {
|
|
@@ -4076,9 +4117,6 @@ function planAppMigrations(appDir) {
|
|
|
4076
4117
|
};
|
|
4077
4118
|
}
|
|
4078
4119
|
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
4120
|
const repo = repoToplevel(appDir);
|
|
4083
4121
|
for (const edit of plan.edits) {
|
|
4084
4122
|
const absolutePath = join15(repo, edit.path);
|
|
@@ -4138,119 +4176,78 @@ function writeRegularFile(path, before, after) {
|
|
|
4138
4176
|
}
|
|
4139
4177
|
function isSourcePath(repoPath, appPrefix) {
|
|
4140
4178
|
if (appPrefix && !repoPath.startsWith(appPrefix)) return false;
|
|
4179
|
+
if (repoPath.endsWith("wrangler.toml")) return true;
|
|
4141
4180
|
return /\.(?:[cm]?[jt]sx?)$/.test(repoPath);
|
|
4142
4181
|
}
|
|
4143
|
-
function
|
|
4182
|
+
function migrateWorkerOwnedNotFound(source, file) {
|
|
4144
4183
|
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
|
-
);
|
|
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 };
|
|
4185
4190
|
}
|
|
4186
|
-
|
|
4187
|
-
"
|
|
4188
|
-
|
|
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
|
+
}
|
|
4189
4197
|
);
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
replacements
|
|
4193
|
-
};
|
|
4198
|
+
const guarded = insertFileMissGuard(retargeted);
|
|
4199
|
+
return { source: guarded.source, replacements: replacements + guarded.replacements };
|
|
4194
4200
|
}
|
|
4195
|
-
|
|
4201
|
+
var SHELL_FALLBACK_LINE = /^(\s*)url\.pathname\s*=\s*(['"])\/\2\s*$/;
|
|
4202
|
+
var GUARD_LOOKBACK_LINES = 8;
|
|
4203
|
+
function insertFileMissGuard(source) {
|
|
4196
4204
|
const lines = source.split("\n");
|
|
4205
|
+
let replacements = 0;
|
|
4197
4206
|
for (let index = lines.length - 1; index >= 0; index--) {
|
|
4198
|
-
const match =
|
|
4199
|
-
if (!match ||
|
|
4200
|
-
const
|
|
4201
|
-
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++;
|
|
4202
4220
|
}
|
|
4203
|
-
return lines.join("\n");
|
|
4221
|
+
return { source: lines.join("\n"), replacements };
|
|
4204
4222
|
}
|
|
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;
|
|
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));
|
|
4217
4227
|
}
|
|
4218
|
-
function
|
|
4228
|
+
function findWorkerOwnedNotFoundBlockers(source, file) {
|
|
4229
|
+
if (file.endsWith("wrangler.toml")) return [];
|
|
4230
|
+
if (!source.includes("ASSETS")) return [];
|
|
4219
4231
|
const blockers = [];
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
if (!/['"]
|
|
4223
|
-
continue;
|
|
4224
|
-
}
|
|
4232
|
+
const lines = source.split("\n");
|
|
4233
|
+
for (const [index, line] of lines.entries()) {
|
|
4234
|
+
if (!/['"`]\/index\.html/.test(line)) continue;
|
|
4225
4235
|
blockers.push({
|
|
4226
|
-
migrationId:
|
|
4236
|
+
migrationId: WORKER_OWNED_NOT_FOUND_MIGRATION_ID,
|
|
4227
4237
|
file,
|
|
4228
4238
|
line: index + 1,
|
|
4229
|
-
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).`
|
|
4230
4240
|
});
|
|
4231
4241
|
}
|
|
4232
4242
|
return blockers;
|
|
4233
4243
|
}
|
|
4234
4244
|
|
|
4235
4245
|
// 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
|
-
]);
|
|
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
|
+
}
|
|
4254
4251
|
async function buildDeployBundle(options) {
|
|
4255
4252
|
const { appDir, appName, envName, sharedDevVarsCache, output, spinner: spinner2 } = options;
|
|
4256
4253
|
const junk = removeMacosJunk(appDir);
|
|
@@ -4362,7 +4359,10 @@ async function buildDeployBundle(options) {
|
|
|
4362
4359
|
appMigrations,
|
|
4363
4360
|
doManifest,
|
|
4364
4361
|
customBindings,
|
|
4365
|
-
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
|
|
4366
4366
|
};
|
|
4367
4367
|
}
|
|
4368
4368
|
function extractRunWorkerFirst(config) {
|
|
@@ -4372,7 +4372,7 @@ function extractRunWorkerFirst(config) {
|
|
|
4372
4372
|
for (const entry of raw) {
|
|
4373
4373
|
if (typeof entry !== "string") continue;
|
|
4374
4374
|
const route = entry.trim();
|
|
4375
|
-
if (!route.startsWith("/") ||
|
|
4375
|
+
if (!route.startsWith("/") || RESERVED_RUN_WORKER_FIRST2.has(route) || seen.has(route)) continue;
|
|
4376
4376
|
seen.add(route);
|
|
4377
4377
|
routes.push(route);
|
|
4378
4378
|
}
|
|
@@ -5418,7 +5418,7 @@ async function pushWithTransientRetry(push) {
|
|
|
5418
5418
|
const message = errorMessage3(error);
|
|
5419
5419
|
const transient = /(?:HTTP |error: )(?:429|503)\b/i.test(message);
|
|
5420
5420
|
if (transient && attempt < backoffMs.length && Date.now() + backoffMs[attempt] < deadline) {
|
|
5421
|
-
await new Promise((
|
|
5421
|
+
await new Promise((resolve20) => setTimeout(resolve20, backoffMs[attempt]));
|
|
5422
5422
|
continue;
|
|
5423
5423
|
}
|
|
5424
5424
|
throw error;
|
|
@@ -5510,6 +5510,15 @@ async function deployBuiltBundle(options) {
|
|
|
5510
5510
|
if (Object.keys(bundle.assetConfig).length) {
|
|
5511
5511
|
form.append("assetConfig", JSON.stringify(bundle.assetConfig));
|
|
5512
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
|
+
}
|
|
5513
5522
|
form.append("name", appName);
|
|
5514
5523
|
if (confirmRename) form.append("confirmRename", "true");
|
|
5515
5524
|
if (shouldSendLineage(repository.commitOid, repository.recoverable)) {
|
|
@@ -5763,7 +5772,7 @@ async function postWithRetry(url, makeInit, {
|
|
|
5763
5772
|
lastError = error;
|
|
5764
5773
|
}
|
|
5765
5774
|
if (attempt < attempts) {
|
|
5766
|
-
await new Promise((
|
|
5775
|
+
await new Promise((resolve20) => setTimeout(resolve20, Math.min(8e3, 500 * 2 ** (attempt - 1))));
|
|
5767
5776
|
}
|
|
5768
5777
|
}
|
|
5769
5778
|
if (lastResponse) return lastResponse;
|
|
@@ -5883,46 +5892,6 @@ function errorMessage5(error) {
|
|
|
5883
5892
|
return error instanceof Error ? error.message : String(error);
|
|
5884
5893
|
}
|
|
5885
5894
|
|
|
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
5895
|
// src/cli/commands/deploy.ts
|
|
5927
5896
|
var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
5928
5897
|
var deploy_default = defineCommand3({
|
|
@@ -6006,10 +5975,6 @@ var deploy_default = defineCommand3({
|
|
|
6006
5975
|
}
|
|
6007
5976
|
const appName = nameResult.name;
|
|
6008
5977
|
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
5978
|
p12.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
|
|
6014
5979
|
const { token, ownerId } = await authenticate(appDir, output);
|
|
6015
5980
|
if (!appId) {
|
|
@@ -6075,15 +6040,18 @@ var deploy_default = defineCommand3({
|
|
|
6075
6040
|
output,
|
|
6076
6041
|
spinner: spinner2
|
|
6077
6042
|
});
|
|
6043
|
+
let serving = "unverifiable";
|
|
6078
6044
|
if (body.url) {
|
|
6079
|
-
spinner2.message("Waiting for edge
|
|
6080
|
-
|
|
6081
|
-
if (
|
|
6082
|
-
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
|
+
);
|
|
6083
6051
|
p12.log.warn(
|
|
6084
|
-
"
|
|
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."
|
|
6085
6053
|
);
|
|
6086
|
-
p12.log.info(`URL: ${body.url}
|
|
6054
|
+
p12.log.info(`URL: ${body.url}`);
|
|
6087
6055
|
await syncCommerce(appDir, appId, token, output.nonInteractive);
|
|
6088
6056
|
if (output.json) {
|
|
6089
6057
|
return output.emitJson({
|
|
@@ -6093,7 +6061,10 @@ var deploy_default = defineCommand3({
|
|
|
6093
6061
|
url: body.url,
|
|
6094
6062
|
releaseId: body.releaseId ?? null,
|
|
6095
6063
|
bundleRetained: body.bundleRetained ?? null,
|
|
6096
|
-
edgePropagating
|
|
6064
|
+
// `serving` says what was actually established; `edgePropagating`
|
|
6065
|
+
// stays for callers written against the old shape.
|
|
6066
|
+
serving,
|
|
6067
|
+
edgePropagating: serving === "unconfirmed",
|
|
6097
6068
|
recoverable: repository.recoverable,
|
|
6098
6069
|
...staleBaseGuardFields(body)
|
|
6099
6070
|
});
|
|
@@ -6111,6 +6082,9 @@ var deploy_default = defineCommand3({
|
|
|
6111
6082
|
appId,
|
|
6112
6083
|
appName,
|
|
6113
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,
|
|
6114
6088
|
releaseId: body.releaseId ?? null,
|
|
6115
6089
|
bundleRetained: body.bundleRetained ?? null,
|
|
6116
6090
|
recoverable: repository.recoverable,
|
|
@@ -6565,7 +6539,7 @@ function formatEvent(e, color, now = Date.now()) {
|
|
|
6565
6539
|
const client = e.source === "client" ? paint("35", "CLIENT", color) + " " : "";
|
|
6566
6540
|
return `${time} ${client}${tag} ${body}`;
|
|
6567
6541
|
}
|
|
6568
|
-
var
|
|
6542
|
+
var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6569
6543
|
var logs_default = defineCommand4({
|
|
6570
6544
|
meta: {
|
|
6571
6545
|
name: "logs",
|
|
@@ -6685,7 +6659,7 @@ var logs_default = defineCommand4({
|
|
|
6685
6659
|
}
|
|
6686
6660
|
}
|
|
6687
6661
|
for (; ; ) {
|
|
6688
|
-
await
|
|
6662
|
+
await sleep3(backoff || interval);
|
|
6689
6663
|
const freshToken = await ensureToken();
|
|
6690
6664
|
let page;
|
|
6691
6665
|
try {
|
|
@@ -7855,11 +7829,11 @@ function fmtDate(iso) {
|
|
|
7855
7829
|
}
|
|
7856
7830
|
async function confirm4(message) {
|
|
7857
7831
|
process.stdout.write(`${message} [y/N] `);
|
|
7858
|
-
return new Promise((
|
|
7832
|
+
return new Promise((resolve20) => {
|
|
7859
7833
|
process.stdin.setEncoding("utf-8");
|
|
7860
7834
|
process.stdin.once("data", (data) => {
|
|
7861
7835
|
const answer = data.toString().trim().toLowerCase();
|
|
7862
|
-
|
|
7836
|
+
resolve20(answer === "y" || answer === "yes");
|
|
7863
7837
|
});
|
|
7864
7838
|
});
|
|
7865
7839
|
}
|
|
@@ -8211,8 +8185,14 @@ async function request(baseUrl, token, path, init = {}) {
|
|
|
8211
8185
|
if (res.ok) return res;
|
|
8212
8186
|
const text2 = await res.text();
|
|
8213
8187
|
let code;
|
|
8188
|
+
let details;
|
|
8214
8189
|
try {
|
|
8215
|
-
|
|
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;
|
|
8216
8196
|
} catch {
|
|
8217
8197
|
}
|
|
8218
8198
|
throw new ApiError(
|
|
@@ -8222,7 +8202,8 @@ async function request(baseUrl, token, path, init = {}) {
|
|
|
8222
8202
|
// oversized request short is not a different failure from the worker
|
|
8223
8203
|
// refusing it, and two codes made it look like one.
|
|
8224
8204
|
code ?? (res.status === 413 ? "too_large" : res.status >= 500 ? "server_error" : "http_error"),
|
|
8225
|
-
path
|
|
8205
|
+
path,
|
|
8206
|
+
details
|
|
8226
8207
|
);
|
|
8227
8208
|
}
|
|
8228
8209
|
async function readJson(res, path) {
|
|
@@ -8585,8 +8566,8 @@ var list4 = defineDeepspaceCommand({
|
|
|
8585
8566
|
}
|
|
8586
8567
|
if (pending.length) {
|
|
8587
8568
|
console.log(`PENDING INVITES ON ${app2}`);
|
|
8588
|
-
for (const
|
|
8589
|
-
console.log(` ${
|
|
8569
|
+
for (const p35 of pending) {
|
|
8570
|
+
console.log(` ${p35.email} (expires ${new Date(p35.expiresAt).toLocaleDateString()})`);
|
|
8590
8571
|
}
|
|
8591
8572
|
}
|
|
8592
8573
|
}
|
|
@@ -8684,7 +8665,7 @@ var cancel3 = defineDeepspaceCommand({
|
|
|
8684
8665
|
`/api/app-collaborators/${encodeURIComponent(app2)}`
|
|
8685
8666
|
);
|
|
8686
8667
|
const target2 = email.trim().toLowerCase();
|
|
8687
|
-
const found = pending.find((
|
|
8668
|
+
const found = pending.find((p35) => p35.email.toLowerCase() === target2);
|
|
8688
8669
|
if (!found) {
|
|
8689
8670
|
throw new Refusal(`${email} has no pending invite on ${app2}`, "no_pending_invite", {
|
|
8690
8671
|
action: cliAction("deepspace", "app", "collaborators", "list", "--app", app2)
|
|
@@ -8742,7 +8723,10 @@ var accept = defineDeepspaceCommand({
|
|
|
8742
8723
|
}
|
|
8743
8724
|
return {
|
|
8744
8725
|
data: { ...res },
|
|
8745
|
-
|
|
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)
|
|
8746
8730
|
};
|
|
8747
8731
|
}
|
|
8748
8732
|
});
|
|
@@ -11032,13 +11016,14 @@ Re-run with --allow-do-deletion to accept the data loss.`,
|
|
|
11032
11016
|
"invalid_response"
|
|
11033
11017
|
);
|
|
11034
11018
|
}
|
|
11035
|
-
let
|
|
11019
|
+
let serving = "unverifiable";
|
|
11036
11020
|
if (result.url) {
|
|
11037
|
-
spinner2?.message("Waiting for edge
|
|
11038
|
-
|
|
11021
|
+
spinner2?.message("Waiting for the edge to serve this release...");
|
|
11022
|
+
serving = await waitForLiveRelease(result.url, result.releaseStamp, 9e4);
|
|
11039
11023
|
}
|
|
11024
|
+
const edgePropagating = serving === "unconfirmed";
|
|
11040
11025
|
spinner2?.stop(
|
|
11041
|
-
|
|
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.`
|
|
11042
11027
|
);
|
|
11043
11028
|
const doClassGuard = result.doClassGuard === "unverified" ? "unverified" : void 0;
|
|
11044
11029
|
if (!args.json) {
|
|
@@ -11069,6 +11054,8 @@ Re-run with --allow-do-deletion to accept the data loss.`,
|
|
|
11069
11054
|
url: result.url ?? null,
|
|
11070
11055
|
versionId: result.versionId ?? null,
|
|
11071
11056
|
wranglerEnv: wranglerEnv ?? null,
|
|
11057
|
+
// Always present, matching `deploy`: one field to branch on.
|
|
11058
|
+
serving,
|
|
11072
11059
|
...edgePropagating ? { edgePropagating: true } : {},
|
|
11073
11060
|
...doClassGuard ? { doClassGuard } : {}
|
|
11074
11061
|
}
|
|
@@ -13072,541 +13059,183 @@ function sameRefs(left, right) {
|
|
|
13072
13059
|
return left.every((ref) => expected.get(ref.name) === ref.oid);
|
|
13073
13060
|
}
|
|
13074
13061
|
|
|
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
|
-
|
|
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}`
|
|
13104
13095
|
);
|
|
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;
|
|
13096
|
+
if (updated === source) return false;
|
|
13097
|
+
writeFileSync10(path, updated);
|
|
13098
|
+
return true;
|
|
13115
13099
|
}
|
|
13116
|
-
async function
|
|
13117
|
-
await
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
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
|
+
}
|
|
13122
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;
|
|
13123
13120
|
}
|
|
13124
|
-
|
|
13125
|
-
const
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
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
|
+
}
|
|
13132
13137
|
}
|
|
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({
|
|
13138
|
+
var update_default = defineDeepspaceCommand({
|
|
13137
13139
|
meta: {
|
|
13138
|
-
name: "
|
|
13139
|
-
description: "
|
|
13140
|
+
name: "update",
|
|
13141
|
+
description: "Move this app onto the current SDK and apply the source changes it requires"
|
|
13140
13142
|
},
|
|
13141
13143
|
args: {
|
|
13142
|
-
|
|
13144
|
+
to: {
|
|
13143
13145
|
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",
|
|
13146
|
+
description: "Target version (default: the latest published deepspace)",
|
|
13151
13147
|
required: false
|
|
13152
13148
|
},
|
|
13153
13149
|
"dry-run": {
|
|
13154
13150
|
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",
|
|
13151
|
+
description: "Report what would change without touching the checkout",
|
|
13161
13152
|
default: false
|
|
13162
13153
|
}
|
|
13163
13154
|
},
|
|
13164
13155
|
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) {
|
|
13156
|
+
const appDir = resolve19(".");
|
|
13157
|
+
if (!hasWranglerConfig(appDir)) {
|
|
13178
13158
|
throw new Refusal(
|
|
13179
|
-
"wrangler.toml
|
|
13180
|
-
"
|
|
13159
|
+
"No wrangler.toml found \u2014 run this from inside a DeepSpace app directory.",
|
|
13160
|
+
"not_in_app_repo"
|
|
13181
13161
|
);
|
|
13182
13162
|
}
|
|
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];
|
|
13163
|
+
assertSyncableRepo(appDir);
|
|
13164
|
+
const currentRaw = readAppSdkVersion(appDir);
|
|
13165
|
+
if (!currentRaw) {
|
|
13207
13166
|
throw new Refusal(
|
|
13208
|
-
|
|
13209
|
-
"
|
|
13210
|
-
{ extra: { blockers: sourcePlan.blockers } }
|
|
13167
|
+
"This app does not depend on `deepspace` \u2014 nothing to update.",
|
|
13168
|
+
"not_in_app_repo"
|
|
13211
13169
|
);
|
|
13212
13170
|
}
|
|
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") {
|
|
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) {
|
|
13270
13175
|
throw new Refusal(
|
|
13271
|
-
`
|
|
13272
|
-
"
|
|
13273
|
-
{ extra: { migration: existingMigration } }
|
|
13176
|
+
`Could not read a version to update from ("${currentRaw}") or to ("${targetRaw}").`,
|
|
13177
|
+
"invalid_version"
|
|
13274
13178
|
);
|
|
13275
13179
|
}
|
|
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) {
|
|
13180
|
+
if (gapIsTooWide(current, target2)) {
|
|
13287
13181
|
throw new Refusal(
|
|
13288
|
-
`
|
|
13289
|
-
"
|
|
13290
|
-
{ 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 } }
|
|
13291
13185
|
);
|
|
13292
13186
|
}
|
|
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 }
|
|
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.");
|
|
13444
13196
|
}
|
|
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 }
|
|
13197
|
+
reportPlan(plan, !dryRun);
|
|
13493
13198
|
}
|
|
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 }
|
|
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
|
+
);
|
|
13558
13213
|
}
|
|
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] : []]
|
|
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
|
+
}))
|
|
13582
13226
|
},
|
|
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
|
-
);
|
|
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
|
+
};
|
|
13596
13232
|
}
|
|
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
|
-
}
|
|
13233
|
+
});
|
|
13605
13234
|
|
|
13606
13235
|
// src/cli/cli.ts
|
|
13607
13236
|
var __dirname = dirname10(fileURLToPath3(import.meta.url));
|
|
13608
|
-
var pkg = JSON.parse(
|
|
13609
|
-
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")) {
|
|
13610
13239
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
13611
13240
|
}
|
|
13612
13241
|
var create2 = defineCommand14({
|
|
@@ -13655,7 +13284,7 @@ var auth = defineCommand14({
|
|
|
13655
13284
|
var app = defineCommand14({
|
|
13656
13285
|
meta: {
|
|
13657
13286
|
name: "app",
|
|
13658
|
-
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"
|
|
13659
13288
|
},
|
|
13660
13289
|
subCommands: {
|
|
13661
13290
|
create: create2,
|
|
@@ -13668,7 +13297,7 @@ var app = defineCommand14({
|
|
|
13668
13297
|
domain: domain_default,
|
|
13669
13298
|
usage: usage_default,
|
|
13670
13299
|
source: source_default,
|
|
13671
|
-
|
|
13300
|
+
update: update_default
|
|
13672
13301
|
}
|
|
13673
13302
|
});
|
|
13674
13303
|
var devGroup = defineCommand14({
|