deepspace 0.8.1 → 0.9.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 +34 -0
- package/README.md +43 -4
- package/dist/cli.js +1210 -196
- package/dist/cli.js.map +1 -1
- package/dist/server.d.ts +5 -3
- package/dist/server.js +17 -5
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +5 -3
- package/dist/worker.js +17 -5
- package/dist/worker.js.map +1 -1
- package/package.json +6 -21
package/dist/cli.js
CHANGED
|
@@ -221,6 +221,62 @@ function wrapCommandErrors(cmd) {
|
|
|
221
221
|
return cmd;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
// src/cli/lib/command-suggestions.ts
|
|
225
|
+
function editDistance(a, b) {
|
|
226
|
+
const left = a.toLowerCase();
|
|
227
|
+
const right = b.toLowerCase();
|
|
228
|
+
const dp = Array.from({ length: left.length + 1 }, (_, i) => [
|
|
229
|
+
i,
|
|
230
|
+
...new Array(right.length).fill(0)
|
|
231
|
+
]);
|
|
232
|
+
for (let j = 1; j <= right.length; j++) dp[0][j] = j;
|
|
233
|
+
for (let i = 1; i <= left.length; i++) {
|
|
234
|
+
for (let j = 1; j <= right.length; j++) {
|
|
235
|
+
dp[i][j] = Math.min(
|
|
236
|
+
dp[i - 1][j] + 1,
|
|
237
|
+
dp[i][j - 1] + 1,
|
|
238
|
+
dp[i - 1][j - 1] + (left[i - 1] === right[j - 1] ? 0 : 1)
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return dp[left.length][right.length];
|
|
243
|
+
}
|
|
244
|
+
function commandPaths(table, prefix = []) {
|
|
245
|
+
const paths = [];
|
|
246
|
+
for (const [name, command] of Object.entries(table)) {
|
|
247
|
+
const path = [...prefix, name];
|
|
248
|
+
paths.push(path);
|
|
249
|
+
if (command.subCommands) paths.push(...commandPaths(command.subCommands, path));
|
|
250
|
+
}
|
|
251
|
+
return paths;
|
|
252
|
+
}
|
|
253
|
+
function closestCommandPath(input, table) {
|
|
254
|
+
const ranked = commandPaths(table).map((path) => ({ path, distance: editDistance(input, path.at(-1) ?? "") })).sort(
|
|
255
|
+
(a, b) => a.distance - b.distance || a.path.length - b.path.length || a.path.join(" ").localeCompare(b.path.join(" "))
|
|
256
|
+
);
|
|
257
|
+
return ranked[0]?.path ?? null;
|
|
258
|
+
}
|
|
259
|
+
function findUnknownCommand(argv, rootCommands) {
|
|
260
|
+
let table = rootCommands;
|
|
261
|
+
const accepted = [];
|
|
262
|
+
for (const token of argv) {
|
|
263
|
+
if (!table || token.startsWith("-")) return null;
|
|
264
|
+
const command = table[token];
|
|
265
|
+
if (!command) {
|
|
266
|
+
const relativeSuggestion = closestCommandPath(token, table);
|
|
267
|
+
if (!relativeSuggestion) return null;
|
|
268
|
+
return {
|
|
269
|
+
attemptedPath: ["deepspace", ...accepted, token],
|
|
270
|
+
helpPath: ["deepspace", ...accepted],
|
|
271
|
+
suggestion: ["deepspace", ...accepted, ...relativeSuggestion]
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
accepted.push(token);
|
|
275
|
+
table = command.subCommands;
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
224
280
|
// src/cli/env.ts
|
|
225
281
|
function resolveDeepSpaceEnvironment(value) {
|
|
226
282
|
if (value === void 0 || value === "production") return "production";
|
|
@@ -653,7 +709,7 @@ async function provisionProfile(jwt) {
|
|
|
653
709
|
}
|
|
654
710
|
}
|
|
655
711
|
function sleep(ms) {
|
|
656
|
-
return new Promise((
|
|
712
|
+
return new Promise((resolve19) => setTimeout(resolve19, ms));
|
|
657
713
|
}
|
|
658
714
|
function base64url(bytes) {
|
|
659
715
|
return bytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
@@ -730,8 +786,9 @@ import { join as join3, resolve } from "path";
|
|
|
730
786
|
import { parse as parseToml } from "smol-toml";
|
|
731
787
|
|
|
732
788
|
// src/server/utils/registry-client.ts
|
|
733
|
-
var APP_ID_RE = /^
|
|
734
|
-
var STRICT_APP_ID_RE =
|
|
789
|
+
var APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
790
|
+
var STRICT_APP_ID_RE = APP_ID_RE;
|
|
791
|
+
var LEGACY_APP_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
735
792
|
|
|
736
793
|
// src/cli/lib/app-identity.ts
|
|
737
794
|
var CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
@@ -760,44 +817,59 @@ function mintUlid(now = Date.now()) {
|
|
|
760
817
|
function mintAppId(now = Date.now()) {
|
|
761
818
|
return `app_${mintUlid(now)}`;
|
|
762
819
|
}
|
|
763
|
-
|
|
764
|
-
const headers = { Authorization: `Bearer ${token}` };
|
|
765
|
-
try {
|
|
766
|
-
const res = await fetch(`${deployUrl}/api/apps`, { headers });
|
|
767
|
-
if (res.ok) {
|
|
768
|
-
const body = await res.json();
|
|
769
|
-
const match = body.apps?.find((a) => a.name === appName);
|
|
770
|
-
if (match?.appId) return { kind: "adopted", appId: match.appId, owned: true };
|
|
771
|
-
}
|
|
772
|
-
} catch {
|
|
773
|
-
}
|
|
774
|
-
try {
|
|
775
|
-
const res = await fetch(
|
|
776
|
-
`${deployUrl}/api/apps/${encodeURIComponent(appName)}/analytics?period=1h`,
|
|
777
|
-
{ headers }
|
|
778
|
-
);
|
|
779
|
-
if (res.ok) return { kind: "adopted", appId: appName, owned: false };
|
|
780
|
-
if (res.status === 403) return { kind: "taken" };
|
|
781
|
-
} catch {
|
|
782
|
-
}
|
|
783
|
-
return { kind: "none" };
|
|
784
|
-
}
|
|
785
|
-
function readAppId(cwd = process.cwd(), wranglerEnv) {
|
|
820
|
+
function readWranglerConfig(cwd) {
|
|
786
821
|
const wranglerPath = join3(resolve(cwd), "wrangler.toml");
|
|
787
822
|
if (!existsSync3(wranglerPath)) return null;
|
|
788
|
-
let cfg;
|
|
789
823
|
try {
|
|
790
|
-
|
|
824
|
+
return parseToml(readFileSync4(wranglerPath, "utf-8"));
|
|
791
825
|
} catch (err) {
|
|
792
826
|
throw new InputError(
|
|
793
827
|
`Could not parse ${wranglerPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
794
828
|
"invalid_config"
|
|
795
829
|
);
|
|
796
830
|
}
|
|
831
|
+
}
|
|
832
|
+
function readAppId(cwd = process.cwd(), wranglerEnv) {
|
|
833
|
+
const cfg = readWranglerConfig(cwd);
|
|
834
|
+
if (!cfg) return null;
|
|
797
835
|
const vars = wranglerEnv ? cfg.env?.[wranglerEnv]?.vars : cfg.vars;
|
|
798
836
|
const id = vars?.DEEPSPACE_APP_ID;
|
|
799
837
|
return typeof id === "string" && APP_ID_RE.test(id) ? id : null;
|
|
800
838
|
}
|
|
839
|
+
function readLegacyAppId(cwd = process.cwd(), wranglerEnv) {
|
|
840
|
+
const cfg = readWranglerConfig(cwd);
|
|
841
|
+
if (!cfg) return null;
|
|
842
|
+
const slot = wranglerEnv ? cfg.env?.[wranglerEnv] : cfg;
|
|
843
|
+
if (!slot) return null;
|
|
844
|
+
const declaredId = slot.vars?.DEEPSPACE_APP_ID;
|
|
845
|
+
if (declaredId !== void 0) {
|
|
846
|
+
if (typeof declaredId === "string" && LEGACY_APP_ID_RE.test(declaredId)) return declaredId;
|
|
847
|
+
throw new InputError(
|
|
848
|
+
"DEEPSPACE_APP_ID is present but invalid; correct it before migrating.",
|
|
849
|
+
"invalid_app_id"
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
const appName = typeof slot.vars?.APP_NAME === "string" ? slot.vars.APP_NAME : null;
|
|
853
|
+
const workerName = typeof slot.name === "string" ? slot.name : null;
|
|
854
|
+
if (!appName) return null;
|
|
855
|
+
if (!workerName) {
|
|
856
|
+
throw new InputError(
|
|
857
|
+
"Legacy migration requires both Worker name and APP_NAME to be present and identical.",
|
|
858
|
+
"ambiguous_legacy_app_id"
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
if (appName !== workerName) {
|
|
862
|
+
throw new InputError(
|
|
863
|
+
`Legacy APP_NAME "${appName}" does not match Worker name "${workerName}"; migration cannot choose safely.`,
|
|
864
|
+
"ambiguous_legacy_app_id"
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
const legacyAppId = appName;
|
|
868
|
+
if (!legacyAppId || !LEGACY_APP_ID_RE.test(legacyAppId) || APP_ID_RE.test(legacyAppId)) {
|
|
869
|
+
return null;
|
|
870
|
+
}
|
|
871
|
+
return legacyAppId;
|
|
872
|
+
}
|
|
801
873
|
function writeAppId(cwd, appId, opts = {}) {
|
|
802
874
|
const wranglerPath = join3(resolve(cwd), "wrangler.toml");
|
|
803
875
|
if (!existsSync3(wranglerPath)) {
|
|
@@ -836,7 +908,7 @@ ${line}
|
|
|
836
908
|
}
|
|
837
909
|
|
|
838
910
|
// src/cli/commands/dev.ts
|
|
839
|
-
import { resolve as
|
|
911
|
+
import { resolve as resolve7, basename as basename2, join as join12 } from "path";
|
|
840
912
|
import spawn from "cross-spawn";
|
|
841
913
|
|
|
842
914
|
// src/cli/lib/app-context.ts
|
|
@@ -1230,7 +1302,7 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
|
|
|
1230
1302
|
let port = opts.port;
|
|
1231
1303
|
if (opts.probePort) {
|
|
1232
1304
|
const taken = new Set(
|
|
1233
|
-
config.configurations.map((c) => Number(c?.port)).filter((
|
|
1305
|
+
config.configurations.map((c) => Number(c?.port)).filter((p35) => Number.isInteger(p35) && p35 > 0)
|
|
1234
1306
|
);
|
|
1235
1307
|
for (let i = 0; i < WORKTREE_PORT_RANGE && taken.has(port); i++) {
|
|
1236
1308
|
port = WORKTREE_PORT_MIN + (port - WORKTREE_PORT_MIN + 1) % WORKTREE_PORT_RANGE;
|
|
@@ -1341,6 +1413,8 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
|
1341
1413
|
"ASSETS",
|
|
1342
1414
|
"PLATFORM_WORKER",
|
|
1343
1415
|
"API_WORKER",
|
|
1416
|
+
"DEEPSPACE_APP_ID",
|
|
1417
|
+
"DEEPSPACE_RESOURCE_ID",
|
|
1344
1418
|
"APP_NAME",
|
|
1345
1419
|
"OWNER_USER_ID",
|
|
1346
1420
|
"AUTH_JWT_PUBLIC_KEY",
|
|
@@ -1358,7 +1432,9 @@ function validateBindingManifest(manifest) {
|
|
|
1358
1432
|
const seenNames = /* @__PURE__ */ new Set();
|
|
1359
1433
|
for (const entry of manifest) {
|
|
1360
1434
|
if (!entry || typeof entry !== "object") {
|
|
1361
|
-
errors.push({
|
|
1435
|
+
errors.push({
|
|
1436
|
+
reason: `Entry must be an object (got ${entry === null ? "null" : typeof entry})`
|
|
1437
|
+
});
|
|
1362
1438
|
continue;
|
|
1363
1439
|
}
|
|
1364
1440
|
const e = entry;
|
|
@@ -1407,7 +1483,8 @@ function requiredFieldError(b) {
|
|
|
1407
1483
|
case "r2_bucket":
|
|
1408
1484
|
return b.bucket_name ? null : `r2_bucket binding "${b.name}" missing bucket_name (or "auto")`;
|
|
1409
1485
|
case "kv_namespace": {
|
|
1410
|
-
if (!b.namespace_id)
|
|
1486
|
+
if (!b.namespace_id)
|
|
1487
|
+
return `kv_namespace binding "${b.name}" missing namespace_id (or "auto")`;
|
|
1411
1488
|
if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {
|
|
1412
1489
|
return `kv_namespace binding "${b.name}" with namespace_id="auto" requires "title"`;
|
|
1413
1490
|
}
|
|
@@ -1909,7 +1986,7 @@ function prepareWranglerEnvConfig(appDir, envName, opts = {}) {
|
|
|
1909
1986
|
if (!opts.sharedDevVarsCache) return { wranglerEnv: envName, cleanup() {
|
|
1910
1987
|
} };
|
|
1911
1988
|
warnIfLegacyEnvDevVarsExists(appDir, envName, opts.warn);
|
|
1912
|
-
const config =
|
|
1989
|
+
const config = readWranglerConfig2(appDir);
|
|
1913
1990
|
const resolved = resolveAppNameForEnv(config, envName);
|
|
1914
1991
|
if (!resolved.ok) throw new Error(resolved.reason);
|
|
1915
1992
|
const flattened = flattenWranglerEnvConfig(config, envName);
|
|
@@ -1973,7 +2050,7 @@ var WranglerConfigError = class extends Error {
|
|
|
1973
2050
|
this.name = "WranglerConfigError";
|
|
1974
2051
|
}
|
|
1975
2052
|
};
|
|
1976
|
-
function
|
|
2053
|
+
function readWranglerConfig2(appDir) {
|
|
1977
2054
|
const path = join7(appDir, "wrangler.toml");
|
|
1978
2055
|
const raw = readFileSync8(path, "utf-8");
|
|
1979
2056
|
try {
|
|
@@ -2170,8 +2247,8 @@ ${custom}
|
|
|
2170
2247
|
}
|
|
2171
2248
|
|
|
2172
2249
|
// src/cli/lib/install-status.ts
|
|
2173
|
-
import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
|
|
2174
|
-
import { dirname as dirname4, join as join9 } from "path";
|
|
2250
|
+
import { existsSync as existsSync9, readFileSync as readFileSync11, realpathSync as realpathSync2 } from "fs";
|
|
2251
|
+
import { dirname as dirname4, join as join9, resolve as resolve5 } from "path";
|
|
2175
2252
|
|
|
2176
2253
|
// src/cli/lib/package-manager.ts
|
|
2177
2254
|
import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
|
|
@@ -2217,18 +2294,18 @@ function ensureInstallReady(appDir) {
|
|
|
2217
2294
|
const logPath = join9(sentinelDir, "install.log");
|
|
2218
2295
|
if (existsSync9(errPath)) {
|
|
2219
2296
|
const detail = readFileSync11(errPath, "utf-8").trimEnd();
|
|
2220
|
-
const
|
|
2297
|
+
const log29 = existsSync9(logPath) ? ` Full log: ${logPath}.` : "";
|
|
2221
2298
|
throw new Refusal(
|
|
2222
|
-
`Dependency install failed: ${detail}.${
|
|
2299
|
+
`Dependency install failed: ${detail}.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
|
|
2223
2300
|
"install_failed",
|
|
2224
2301
|
{ action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
|
|
2225
2302
|
);
|
|
2226
2303
|
}
|
|
2227
2304
|
if (existsSync9(startedPath) && !existsSync9(donePath)) {
|
|
2228
2305
|
if (!installWorkerAlive(sentinelDir)) {
|
|
2229
|
-
const
|
|
2306
|
+
const log29 = existsSync9(logPath) ? ` See what happened: ${logPath}.` : "";
|
|
2230
2307
|
throw new Refusal(
|
|
2231
|
-
`The dependency install is no longer running and never finished.${
|
|
2308
|
+
`The dependency install is no longer running and never finished.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
|
|
2232
2309
|
"install_failed",
|
|
2233
2310
|
{ action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
|
|
2234
2311
|
);
|
|
@@ -2248,14 +2325,27 @@ function ensureInstallReady(appDir) {
|
|
|
2248
2325
|
);
|
|
2249
2326
|
}
|
|
2250
2327
|
function resolvesDeepspace(fromDir) {
|
|
2251
|
-
let dir = fromDir;
|
|
2328
|
+
let dir = canonicalPath2(fromDir);
|
|
2329
|
+
let checkoutRoot = null;
|
|
2330
|
+
try {
|
|
2331
|
+
checkoutRoot = canonicalPath2(repoToplevel(dir));
|
|
2332
|
+
} catch {
|
|
2333
|
+
}
|
|
2252
2334
|
for (; ; ) {
|
|
2253
2335
|
if (existsSync9(join9(dir, "node_modules", "deepspace", "package.json"))) return true;
|
|
2336
|
+
if (dir === checkoutRoot) return false;
|
|
2254
2337
|
const parent = dirname4(dir);
|
|
2255
2338
|
if (parent === dir) return false;
|
|
2256
2339
|
dir = parent;
|
|
2257
2340
|
}
|
|
2258
2341
|
}
|
|
2342
|
+
function canonicalPath2(path) {
|
|
2343
|
+
try {
|
|
2344
|
+
return realpathSync2(path);
|
|
2345
|
+
} catch {
|
|
2346
|
+
return resolve5(path);
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2259
2349
|
function installWorkerAlive(sentinelDir) {
|
|
2260
2350
|
const pidPath = join9(sentinelDir, "install.pid");
|
|
2261
2351
|
if (!existsSync9(pidPath)) return false;
|
|
@@ -2273,7 +2363,7 @@ function installWorkerAlive(sentinelDir) {
|
|
|
2273
2363
|
import { execFileSync } from "child_process";
|
|
2274
2364
|
import { existsSync as existsSync10 } from "fs";
|
|
2275
2365
|
import * as nodeModule from "module";
|
|
2276
|
-
import { resolve as
|
|
2366
|
+
import { resolve as resolve6 } from "path";
|
|
2277
2367
|
function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.registerHooks === "function") {
|
|
2278
2368
|
if (hasRegisterHooks) return;
|
|
2279
2369
|
throw new Refusal(
|
|
@@ -2284,7 +2374,7 @@ function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.regi
|
|
|
2284
2374
|
var DLL_NOT_FOUND = -1073741515;
|
|
2285
2375
|
function preflightWindowsWorkerd(appDir) {
|
|
2286
2376
|
if (process.platform !== "win32") return;
|
|
2287
|
-
const workerdExe =
|
|
2377
|
+
const workerdExe = resolve6(
|
|
2288
2378
|
appDir,
|
|
2289
2379
|
"node_modules",
|
|
2290
2380
|
"@cloudflare",
|
|
@@ -2364,7 +2454,7 @@ function lintSchema(schema) {
|
|
|
2364
2454
|
const offendingRoles = Object.entries(schema.permissions).filter(([, perms]) => perms.read === true).map(([role]) => role);
|
|
2365
2455
|
if (offendingRoles.length > 0) {
|
|
2366
2456
|
const usesAware = Object.values(schema.permissions).some(
|
|
2367
|
-
(
|
|
2457
|
+
(p35) => typeof p35.read === "string" && VISIBILITY_AWARE.includes(p35.read)
|
|
2368
2458
|
);
|
|
2369
2459
|
if (!usesAware) {
|
|
2370
2460
|
warnings.push(
|
|
@@ -2445,10 +2535,10 @@ function formatSchemaLintFindings(findings) {
|
|
|
2445
2535
|
import { createServer } from "net";
|
|
2446
2536
|
var DEFAULT_PORT = 5173;
|
|
2447
2537
|
function checkPortAvailable(port, host = "0.0.0.0") {
|
|
2448
|
-
return new Promise((
|
|
2538
|
+
return new Promise((resolve19) => {
|
|
2449
2539
|
const srv = createServer();
|
|
2450
|
-
srv.once("error", () =>
|
|
2451
|
-
srv.once("listening", () => srv.close(() =>
|
|
2540
|
+
srv.once("error", () => resolve19(false));
|
|
2541
|
+
srv.once("listening", () => srv.close(() => resolve19(true)));
|
|
2452
2542
|
srv.listen(port, host);
|
|
2453
2543
|
});
|
|
2454
2544
|
}
|
|
@@ -2503,7 +2593,7 @@ var dev_default = defineDeepspaceCommand({
|
|
|
2503
2593
|
};
|
|
2504
2594
|
preflightNodeVersion("dev start");
|
|
2505
2595
|
const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
2506
|
-
const start =
|
|
2596
|
+
const start = resolve7(args.dir ?? ".");
|
|
2507
2597
|
const appDir = findAppDir(start);
|
|
2508
2598
|
if (!appDir) {
|
|
2509
2599
|
throw new Refusal(noAppDirMessage(start, findChildApps(start)), "no_app_dir");
|
|
@@ -2914,7 +3004,7 @@ function isAlive(pid) {
|
|
|
2914
3004
|
}
|
|
2915
3005
|
|
|
2916
3006
|
// src/cli/commands/test.ts
|
|
2917
|
-
import { resolve as
|
|
3007
|
+
import { resolve as resolve8 } from "path";
|
|
2918
3008
|
import { sync as spawnSync4 } from "cross-spawn";
|
|
2919
3009
|
|
|
2920
3010
|
// src/cli/lib/playwright.ts
|
|
@@ -2974,7 +3064,7 @@ var test_default = defineDeepspaceCommand({
|
|
|
2974
3064
|
preflightNodeVersion("test run");
|
|
2975
3065
|
const suite = args.suite ?? "default";
|
|
2976
3066
|
const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
2977
|
-
const start =
|
|
3067
|
+
const start = resolve8(".");
|
|
2978
3068
|
const appDir = findAppDir(start);
|
|
2979
3069
|
if (!appDir) {
|
|
2980
3070
|
throw new Refusal(noAppDirMessage(start, findChildApps(start)), "no_app_dir");
|
|
@@ -3113,7 +3203,7 @@ function runVitest(appDir) {
|
|
|
3113
3203
|
|
|
3114
3204
|
// src/cli/commands/screenshot.ts
|
|
3115
3205
|
import { sync as spawnSync5 } from "cross-spawn";
|
|
3116
|
-
import { resolve as
|
|
3206
|
+
import { resolve as resolve9 } from "path";
|
|
3117
3207
|
var screenshot_default = defineDeepspaceCommand({
|
|
3118
3208
|
meta: {
|
|
3119
3209
|
name: "screenshot",
|
|
@@ -3142,7 +3232,7 @@ var screenshot_default = defineDeepspaceCommand({
|
|
|
3142
3232
|
}
|
|
3143
3233
|
},
|
|
3144
3234
|
async run({ args }) {
|
|
3145
|
-
const appDir =
|
|
3235
|
+
const appDir = resolve9(".");
|
|
3146
3236
|
ensurePlaywright(appDir);
|
|
3147
3237
|
const url = String(args.url);
|
|
3148
3238
|
const output = String(args.output);
|
|
@@ -3499,13 +3589,13 @@ var test_accounts_default = defineCommand2({
|
|
|
3499
3589
|
// src/cli/commands/deploy.ts
|
|
3500
3590
|
import * as p12 from "@clack/prompts";
|
|
3501
3591
|
import { defineCommand as defineCommand3 } from "citty";
|
|
3502
|
-
import { resolve as
|
|
3592
|
+
import { resolve as resolve12 } from "path";
|
|
3503
3593
|
|
|
3504
3594
|
// src/cli/commands/deploy/build.ts
|
|
3505
3595
|
import * as p6 from "@clack/prompts";
|
|
3506
3596
|
import { execSync as execSync2 } from "child_process";
|
|
3507
3597
|
import { existsSync as existsSync13, readFileSync as readFileSync14, readdirSync as readdirSync4 } from "fs";
|
|
3508
|
-
import { dirname as dirname5, join as join14, resolve as
|
|
3598
|
+
import { dirname as dirname5, join as join14, resolve as resolve10 } from "path";
|
|
3509
3599
|
var RESERVED_RUN_WORKER_FIRST = /* @__PURE__ */ new Set([
|
|
3510
3600
|
"/api/*",
|
|
3511
3601
|
"/ws/*",
|
|
@@ -3549,7 +3639,7 @@ async function buildDeployBundle(options) {
|
|
|
3549
3639
|
);
|
|
3550
3640
|
}
|
|
3551
3641
|
const deployConfig = JSON.parse(readFileSync14(deployConfigPath, "utf-8"));
|
|
3552
|
-
const outputWranglerPath =
|
|
3642
|
+
const outputWranglerPath = resolve10(dirname5(deployConfigPath), deployConfig.configPath);
|
|
3553
3643
|
if (!existsSync13(outputWranglerPath)) {
|
|
3554
3644
|
output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
|
|
3555
3645
|
}
|
|
@@ -3563,7 +3653,7 @@ async function buildDeployBundle(options) {
|
|
|
3563
3653
|
}
|
|
3564
3654
|
const workerDir = dirname5(outputWranglerPath);
|
|
3565
3655
|
const workerBundlePath = join14(workerDir, outputConfig.main);
|
|
3566
|
-
const clientDir = outputConfig.assets?.directory ?
|
|
3656
|
+
const clientDir = outputConfig.assets?.directory ? resolve10(workerDir, outputConfig.assets.directory) : null;
|
|
3567
3657
|
if (!existsSync13(workerBundlePath)) {
|
|
3568
3658
|
output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
|
|
3569
3659
|
}
|
|
@@ -4100,7 +4190,7 @@ function repoApi(deployUrl, token, appId) {
|
|
|
4100
4190
|
// src/cli/lib/vc-remote.ts
|
|
4101
4191
|
import { existsSync as existsSync15 } from "fs";
|
|
4102
4192
|
import { tmpdir } from "os";
|
|
4103
|
-
import { isAbsolute as isAbsolute3, relative, resolve as
|
|
4193
|
+
import { isAbsolute as isAbsolute3, relative, resolve as resolve11 } from "path";
|
|
4104
4194
|
var SPACE_REMOTE = "space";
|
|
4105
4195
|
function deployBaseUrl() {
|
|
4106
4196
|
return process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
@@ -4117,6 +4207,16 @@ function gitAuthEnv(token, base = deployBaseUrl()) {
|
|
|
4117
4207
|
[`GIT_CONFIG_VALUE_${index}`]: `Authorization: Bearer ${token}`
|
|
4118
4208
|
};
|
|
4119
4209
|
}
|
|
4210
|
+
function gitSourceImportEnv(token, revision, base = deployBaseUrl()) {
|
|
4211
|
+
const env = gitAuthEnv(token, base);
|
|
4212
|
+
const index = Number(env.GIT_CONFIG_COUNT);
|
|
4213
|
+
return {
|
|
4214
|
+
...env,
|
|
4215
|
+
GIT_CONFIG_COUNT: String(index + 1),
|
|
4216
|
+
[`GIT_CONFIG_KEY_${index}`]: `http.${base.replace(/\/+$/, "")}.extraHeader`,
|
|
4217
|
+
[`GIT_CONFIG_VALUE_${index}`]: `X-DeepSpace-Source-Revision: ${revision}`
|
|
4218
|
+
};
|
|
4219
|
+
}
|
|
4120
4220
|
function runGitRemote(cwd, token, args, opts = {}) {
|
|
4121
4221
|
return runGit(cwd, args, { ...opts, env: { ...gitAuthEnv(token), ...opts.env ?? {} } });
|
|
4122
4222
|
}
|
|
@@ -4124,7 +4224,7 @@ function entryIsTransient(cwd) {
|
|
|
4124
4224
|
const entry = process.argv[1];
|
|
4125
4225
|
if (!entry) return true;
|
|
4126
4226
|
if (/[\\/]_npx[\\/]/.test(entry) || entry.startsWith(tmpdir())) return true;
|
|
4127
|
-
const rel = relative(
|
|
4227
|
+
const rel = relative(resolve11(cwd), resolve11(entry));
|
|
4128
4228
|
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
4129
4229
|
}
|
|
4130
4230
|
function helperEntryUsable(command) {
|
|
@@ -4244,6 +4344,12 @@ function classifyPushTransportFailure(error) {
|
|
|
4244
4344
|
error: `A first push to this new app hit your plan's active-app quota. Use \`deepspace app list\` to choose an app to undeploy, or upgrade your plan, then retry.`
|
|
4245
4345
|
};
|
|
4246
4346
|
}
|
|
4347
|
+
if (/(?:HTTP |error: )422\b/i.test(message)) {
|
|
4348
|
+
return {
|
|
4349
|
+
code: "source_managed_by_github",
|
|
4350
|
+
error: "This app uses GitHub source. Push with normal Git/GitHub, then run `deepspace deploy`."
|
|
4351
|
+
};
|
|
4352
|
+
}
|
|
4247
4353
|
if (/(?:HTTP |error: )429\b/i.test(message)) {
|
|
4248
4354
|
return {
|
|
4249
4355
|
code: "rate_limited",
|
|
@@ -4322,22 +4428,138 @@ function resolveWorkspaceWorktree(worktrees, workspaceId) {
|
|
|
4322
4428
|
return worktrees.find((worktree) => worktree.branch === branch)?.path ?? null;
|
|
4323
4429
|
}
|
|
4324
4430
|
|
|
4431
|
+
// src/cli/lib/source-control.ts
|
|
4432
|
+
function githubRepositoryFromUrl(url) {
|
|
4433
|
+
const trimmed = url.trim();
|
|
4434
|
+
let path = null;
|
|
4435
|
+
const scp = /^(?:[^@\s]+@)?github\.com:([^\s]+)$/i.exec(trimmed);
|
|
4436
|
+
if (scp) {
|
|
4437
|
+
path = scp[1];
|
|
4438
|
+
} else {
|
|
4439
|
+
try {
|
|
4440
|
+
const parsed = new URL(trimmed);
|
|
4441
|
+
if (parsed.hostname.toLowerCase() !== "github.com") return null;
|
|
4442
|
+
path = parsed.pathname.replace(/^\/+/, "");
|
|
4443
|
+
} catch {
|
|
4444
|
+
return null;
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
const repository = path.replace(/\/+$/, "").replace(/\.git$/i, "");
|
|
4448
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9-]{0,38}\/[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/.test(repository)) {
|
|
4449
|
+
return null;
|
|
4450
|
+
}
|
|
4451
|
+
return repository.toLowerCase();
|
|
4452
|
+
}
|
|
4453
|
+
function listGitHubRemotes(cwd) {
|
|
4454
|
+
const names = runGit(cwd, ["remote"], { allowFail: true }).stdout.toString("utf-8").split("\n").map((name) => name.trim()).filter(Boolean);
|
|
4455
|
+
const remotes = [];
|
|
4456
|
+
for (const name of names) {
|
|
4457
|
+
const result = runGit(cwd, ["remote", "get-url", name], { allowFail: true });
|
|
4458
|
+
if (result.status !== 0) continue;
|
|
4459
|
+
const url = result.stdout.toString("utf-8").trim();
|
|
4460
|
+
const repository = githubRepositoryFromUrl(url);
|
|
4461
|
+
if (repository) remotes.push({ name, repository, url });
|
|
4462
|
+
}
|
|
4463
|
+
return remotes;
|
|
4464
|
+
}
|
|
4465
|
+
function selectGitHubRemote(cwd, options = {}) {
|
|
4466
|
+
const remotes = listGitHubRemotes(cwd);
|
|
4467
|
+
if (options.name) return remotes.find((remote) => remote.name === options.name) ?? null;
|
|
4468
|
+
const repository = options.repository?.toLowerCase();
|
|
4469
|
+
const matching = repository ? remotes.filter((remote) => remote.repository === repository) : remotes;
|
|
4470
|
+
return matching.find((remote) => remote.name === "origin") ?? (matching.length === 1 ? matching[0] : null);
|
|
4471
|
+
}
|
|
4472
|
+
function remotePublicRefs(cwd, remote) {
|
|
4473
|
+
const output = runGit(cwd, ["ls-remote", "--refs", remote]).stdout.toString("utf-8");
|
|
4474
|
+
return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
4475
|
+
const [oid, name] = line.split(/\s+/, 2);
|
|
4476
|
+
return { oid, name };
|
|
4477
|
+
}).filter(
|
|
4478
|
+
(ref) => /^[0-9a-f]{40}$/.test(ref.oid) && (ref.name.startsWith("refs/heads/") || ref.name.startsWith("refs/tags/"))
|
|
4479
|
+
);
|
|
4480
|
+
}
|
|
4481
|
+
function remoteBranchOid(cwd, remote, branch) {
|
|
4482
|
+
return remotePublicRefs(cwd, remote).find((ref) => ref.name === `refs/heads/${branch}`)?.oid ?? null;
|
|
4483
|
+
}
|
|
4484
|
+
function localTransferRefs(cwd, prefix, target) {
|
|
4485
|
+
const output = runGit(cwd, [
|
|
4486
|
+
"for-each-ref",
|
|
4487
|
+
"--format=%(refname) %(objectname)",
|
|
4488
|
+
prefix
|
|
4489
|
+
]).stdout.toString("utf-8");
|
|
4490
|
+
return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
4491
|
+
const [name, oid] = line.split(/\s+/, 2);
|
|
4492
|
+
return { name: `${target}/${name.slice(`${prefix}/`.length)}`, oid };
|
|
4493
|
+
}).filter((ref) => /^[0-9a-f]{40}$/.test(ref.oid));
|
|
4494
|
+
}
|
|
4495
|
+
|
|
4496
|
+
// src/cli/lib/source-api.ts
|
|
4497
|
+
async function getAppSource(deployUrl, token, appId) {
|
|
4498
|
+
try {
|
|
4499
|
+
const state = await apiFetch(
|
|
4500
|
+
deployUrl,
|
|
4501
|
+
token,
|
|
4502
|
+
`/api/apps/${encodeURIComponent(appId)}/source`
|
|
4503
|
+
);
|
|
4504
|
+
return { ...state, registered: true };
|
|
4505
|
+
} catch (error) {
|
|
4506
|
+
if (error instanceof ApiError && error.code === "app_not_found") {
|
|
4507
|
+
return { appId, source: null, revision: 0, registered: false };
|
|
4508
|
+
}
|
|
4509
|
+
throw error;
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
function setAppSource(deployUrl, token, appId, input) {
|
|
4513
|
+
return apiFetch(
|
|
4514
|
+
deployUrl,
|
|
4515
|
+
token,
|
|
4516
|
+
`/api/apps/${encodeURIComponent(appId)}/source`,
|
|
4517
|
+
{ method: "POST", body: JSON.stringify(input) }
|
|
4518
|
+
);
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4325
4521
|
// src/cli/commands/deploy/repository.ts
|
|
4326
4522
|
async function syncDeployRepository(options) {
|
|
4327
|
-
const { deployUrl, appDir, appId, token, push, ignoreStale, output } = options;
|
|
4523
|
+
const { deployUrl, appDir, appId, token, push, ignoreStale, output, sourceState } = options;
|
|
4328
4524
|
let commitOid = null;
|
|
4329
4525
|
let recoverable = false;
|
|
4330
4526
|
const deployKey = mintIdempotencyKey();
|
|
4331
|
-
|
|
4527
|
+
let source = sourceState.source;
|
|
4528
|
+
let sourceRevision = sourceState.revision;
|
|
4529
|
+
const baseReleaseId = null;
|
|
4530
|
+
if (!push && source?.provider !== "github") {
|
|
4332
4531
|
try {
|
|
4333
4532
|
assertSyncableRepo(appDir);
|
|
4334
4533
|
commitOid = resolveCommit(appDir, "HEAD");
|
|
4335
4534
|
} catch {
|
|
4336
4535
|
}
|
|
4337
|
-
return { commitOid, recoverable, deployKey };
|
|
4536
|
+
return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId };
|
|
4338
4537
|
}
|
|
4339
4538
|
try {
|
|
4340
4539
|
assertSyncableRepo(appDir);
|
|
4540
|
+
if (source?.provider === "github") {
|
|
4541
|
+
const github = syncGitHubDeploy({
|
|
4542
|
+
deployUrl,
|
|
4543
|
+
appDir,
|
|
4544
|
+
appId,
|
|
4545
|
+
token,
|
|
4546
|
+
source,
|
|
4547
|
+
ignoreStale,
|
|
4548
|
+
output
|
|
4549
|
+
});
|
|
4550
|
+
return await github.then((result) => ({
|
|
4551
|
+
...result,
|
|
4552
|
+
deployKey,
|
|
4553
|
+
source,
|
|
4554
|
+
sourceRevision
|
|
4555
|
+
}));
|
|
4556
|
+
}
|
|
4557
|
+
if (!source && listGitHubRemotes(appDir).length > 0) {
|
|
4558
|
+
output.die(
|
|
4559
|
+
"This app has a GitHub remote but no claimed source. Choose once with `deepspace app source github` (manual GitHub ownership) or `deepspace app source deepspace` (packaged DeepSpace source), then deploy again.",
|
|
4560
|
+
"source_unclaimed"
|
|
4561
|
+
);
|
|
4562
|
+
}
|
|
4341
4563
|
ensureSpaceRemote(appDir, appId);
|
|
4342
4564
|
const branch = currentBranch(appDir);
|
|
4343
4565
|
const workspaceBranchId = workspaceIdFromBranch(branch);
|
|
@@ -4351,7 +4573,9 @@ async function syncDeployRepository(options) {
|
|
|
4351
4573
|
throw new Error(refusal.error);
|
|
4352
4574
|
}
|
|
4353
4575
|
const tip = resolveCommit(appDir, `refs/heads/${branch}`);
|
|
4354
|
-
if (!tip)
|
|
4576
|
+
if (!tip) {
|
|
4577
|
+
return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId };
|
|
4578
|
+
}
|
|
4355
4579
|
const secretFiles = workspaceBranchId ? [] : trackedSecretFiles(appDir, tip);
|
|
4356
4580
|
if (workspaceBranchId) {
|
|
4357
4581
|
const { view } = await repoApi(deployUrl, token, appId).getWorkspace(workspaceBranchId);
|
|
@@ -4427,11 +4651,81 @@ async function syncDeployRepository(options) {
|
|
|
4427
4651
|
}
|
|
4428
4652
|
}
|
|
4429
4653
|
commitOid = tip;
|
|
4654
|
+
if (!source) {
|
|
4655
|
+
const claimed = await getAppSource(deployUrl, token, appId);
|
|
4656
|
+
source = claimed.source;
|
|
4657
|
+
sourceRevision = claimed.revision;
|
|
4658
|
+
}
|
|
4430
4659
|
} catch (error) {
|
|
4431
4660
|
const failure = deployRepositoryFailure(error);
|
|
4432
4661
|
output.die(failure.error, failure.code);
|
|
4433
4662
|
}
|
|
4434
|
-
return { commitOid, recoverable, deployKey };
|
|
4663
|
+
return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId };
|
|
4664
|
+
}
|
|
4665
|
+
async function syncGitHubDeploy(options) {
|
|
4666
|
+
const { deployUrl, appDir, appId, token, source, ignoreStale, output } = options;
|
|
4667
|
+
if (!isWorkTreeClean(appDir)) {
|
|
4668
|
+
const refusal = dirtyWorktreeRefusal(currentBranch(appDir));
|
|
4669
|
+
output.die(refusal.error, refusal.code);
|
|
4670
|
+
}
|
|
4671
|
+
const branch = currentBranch(appDir);
|
|
4672
|
+
if (!branch) {
|
|
4673
|
+
const refusal = detachedHeadRefusal();
|
|
4674
|
+
output.die(refusal.error, refusal.code);
|
|
4675
|
+
throw new Error(refusal.error);
|
|
4676
|
+
}
|
|
4677
|
+
const tip = resolveCommit(appDir, "HEAD");
|
|
4678
|
+
if (!tip) {
|
|
4679
|
+
output.die("The repository has no commit to deploy.", "no_commits");
|
|
4680
|
+
throw new Error("The repository has no commit to deploy.");
|
|
4681
|
+
}
|
|
4682
|
+
const remote = selectGitHubRemote(appDir, { repository: source.repository });
|
|
4683
|
+
if (!remote) {
|
|
4684
|
+
output.die(
|
|
4685
|
+
`No local GitHub remote points at ${source.repository}. Add one, then deploy again.`,
|
|
4686
|
+
"github_remote_required"
|
|
4687
|
+
);
|
|
4688
|
+
throw new Error(`No local GitHub remote points at ${source.repository}.`);
|
|
4689
|
+
}
|
|
4690
|
+
const secretFiles = trackedSecretFiles(appDir, tip);
|
|
4691
|
+
if (secretFiles.length > 0) {
|
|
4692
|
+
output.die(
|
|
4693
|
+
`Refusing to deploy a GitHub commit that tracks secret file(s): ${secretFiles.join(", ")}. Remove them from Git history, push the repaired branch, then deploy.`,
|
|
4694
|
+
"secret_in_history"
|
|
4695
|
+
);
|
|
4696
|
+
}
|
|
4697
|
+
if (remoteBranchOid(appDir, remote.name, branch) !== tip) {
|
|
4698
|
+
output.die(
|
|
4699
|
+
`GitHub ${source.repository} does not have ${branch} at ${tip.slice(0, 10)}. DeepSpace never pushes GitHub source; publish it manually, then deploy again.`,
|
|
4700
|
+
"github_push_required",
|
|
4701
|
+
{
|
|
4702
|
+
actionRequired: true,
|
|
4703
|
+
action: { cwd: appDir, argv: ["git", "push", remote.name, branch] }
|
|
4704
|
+
}
|
|
4705
|
+
);
|
|
4706
|
+
}
|
|
4707
|
+
const latest = await repoApi(deployUrl, token, appId).latestRelease().then((result) => result.release);
|
|
4708
|
+
if (latest?.commitOid && latest.commitOid !== tip) {
|
|
4709
|
+
const known = resolveCommit(appDir, latest.commitOid) !== null;
|
|
4710
|
+
const containsLive = known && isAncestor(appDir, latest.commitOid, tip);
|
|
4711
|
+
if ((!known || !containsLive) && !ignoreStale) {
|
|
4712
|
+
output.die(
|
|
4713
|
+
known ? `This GitHub branch does not contain live release #${latest.seq} (${latest.commitOid.slice(0, 10)}). Integrate the published GitHub history, then redeploy.` : `The live release commit ${latest.commitOid.slice(0, 10)} is not in this checkout. Fetch ${source.repository}, integrate it, then redeploy.`,
|
|
4714
|
+
"stale_base",
|
|
4715
|
+
{
|
|
4716
|
+
actionRequired: true,
|
|
4717
|
+
action: { cwd: appDir, argv: ["git", "fetch", remote.name] }
|
|
4718
|
+
}
|
|
4719
|
+
);
|
|
4720
|
+
}
|
|
4721
|
+
if (!containsLive) {
|
|
4722
|
+
p9.log.warn(
|
|
4723
|
+
"Deploying GitHub history that does not contain the live release (--ignore-stale)."
|
|
4724
|
+
);
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4727
|
+
p9.log.info(`Verified GitHub ${source.repository}:${branch} at ${tip.slice(0, 10)} (no push).`);
|
|
4728
|
+
return { commitOid: tip, recoverable: true, baseReleaseId: latest?.id ?? null };
|
|
4435
4729
|
}
|
|
4436
4730
|
function classifyRemoteState(appDir, token, branch, localTip) {
|
|
4437
4731
|
let remoteTip = null;
|
|
@@ -4473,7 +4767,7 @@ async function pushWithTransientRetry(push) {
|
|
|
4473
4767
|
} catch (error) {
|
|
4474
4768
|
const message = errorMessage3(error);
|
|
4475
4769
|
if (/(?:HTTP |error: )(?:429|503)\b/i.test(message) && attempt < backoffMs.length) {
|
|
4476
|
-
await new Promise((
|
|
4770
|
+
await new Promise((resolve19) => setTimeout(resolve19, backoffMs[attempt]));
|
|
4477
4771
|
continue;
|
|
4478
4772
|
}
|
|
4479
4773
|
throw error;
|
|
@@ -4601,6 +4895,14 @@ async function deployBuiltBundle(options) {
|
|
|
4601
4895
|
if (shouldSendLineage(repository.commitOid, repository.recoverable)) {
|
|
4602
4896
|
form.append("commitOid", repository.commitOid);
|
|
4603
4897
|
}
|
|
4898
|
+
if (repository.source) {
|
|
4899
|
+
form.append("sourceProvider", repository.source.provider);
|
|
4900
|
+
form.append("sourceRevision", String(repository.sourceRevision));
|
|
4901
|
+
if (repository.source.provider === "github") {
|
|
4902
|
+
form.append("sourceRepository", repository.source.repository);
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
if (repository.baseReleaseId) form.append("baseReleaseId", repository.baseReleaseId);
|
|
4604
4906
|
if (ignoreStale) form.append("ignoreStale", "true");
|
|
4605
4907
|
form.append("deployKey", repository.deployKey);
|
|
4606
4908
|
return form;
|
|
@@ -4627,7 +4929,7 @@ async function deployBuiltBundle(options) {
|
|
|
4627
4929
|
}
|
|
4628
4930
|
let body = await response.json().catch(() => ({}));
|
|
4629
4931
|
for (let attempt = 0; response.status === 503 && body.code === "release_finalization_pending" && attempt < 3; attempt++) {
|
|
4630
|
-
await new Promise((
|
|
4932
|
+
await new Promise((resolve19) => setTimeout(resolve19, 250 * 2 ** attempt));
|
|
4631
4933
|
response = await postCommit();
|
|
4632
4934
|
body = await response.json().catch(() => ({}));
|
|
4633
4935
|
}
|
|
@@ -4736,7 +5038,7 @@ async function postWithRetry(url, makeInit, {
|
|
|
4736
5038
|
lastError = error;
|
|
4737
5039
|
}
|
|
4738
5040
|
if (attempt < attempts) {
|
|
4739
|
-
await new Promise((
|
|
5041
|
+
await new Promise((resolve19) => setTimeout(resolve19, Math.min(8e3, 500 * 2 ** (attempt - 1))));
|
|
4740
5042
|
}
|
|
4741
5043
|
}
|
|
4742
5044
|
if (lastResponse) return lastResponse;
|
|
@@ -4885,15 +5187,10 @@ var deploy_default = defineCommand3({
|
|
|
4885
5187
|
description: "Deploy even though hand-edited .dev.vars secrets are absent from the store (they will NOT be deployed, and any a previous deploy set are dropped).",
|
|
4886
5188
|
default: false
|
|
4887
5189
|
},
|
|
4888
|
-
adopt: {
|
|
4889
|
-
type: "boolean",
|
|
4890
|
-
description: "Confirm adopting an existing app you can deploy but do not own (collaborator/admin on-behalf) when wrangler.toml has no DEEPSPACE_APP_ID. Without this flag an interactive prompt asks.",
|
|
4891
|
-
default: false
|
|
4892
|
-
},
|
|
4893
5190
|
// Citty maps --no-push to the negation of an argument named `push`.
|
|
4894
5191
|
push: {
|
|
4895
5192
|
type: "boolean",
|
|
4896
|
-
description: "Sync
|
|
5193
|
+
description: "Sync DeepSpace source before deploying. The legacy --no-push escape hatch skips recoverable source lineage; GitHub apps are verified and never pushed automatically.",
|
|
4897
5194
|
default: true
|
|
4898
5195
|
},
|
|
4899
5196
|
"ignore-stale": {
|
|
@@ -4910,7 +5207,7 @@ var deploy_default = defineCommand3({
|
|
|
4910
5207
|
async run({ args }) {
|
|
4911
5208
|
const output = createDeployOutput(args.json === true);
|
|
4912
5209
|
preflightNodeVersion("deploy");
|
|
4913
|
-
const appDir =
|
|
5210
|
+
const appDir = resolve12(args.dir ?? ".");
|
|
4914
5211
|
const selectorRefusal = blankSelectorRefusal(args);
|
|
4915
5212
|
if (selectorRefusal) output.die(selectorRefusal.error, selectorRefusal.code);
|
|
4916
5213
|
const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
@@ -4927,7 +5224,7 @@ var deploy_default = defineCommand3({
|
|
|
4927
5224
|
}
|
|
4928
5225
|
p12.intro("Deploying DeepSpace app");
|
|
4929
5226
|
output.showIntro();
|
|
4930
|
-
const wranglerConfig =
|
|
5227
|
+
const wranglerConfig = readWranglerConfig2(appDir);
|
|
4931
5228
|
const nameResult = resolveAppNameForEnv(wranglerConfig, envName);
|
|
4932
5229
|
if (!nameResult.ok) return output.die(nameResult.reason, "invalid_app_name");
|
|
4933
5230
|
const declaredName = envName ? wranglerConfig.env?.[envName]?.name : wranglerConfig.name;
|
|
@@ -4940,35 +5237,21 @@ var deploy_default = defineCommand3({
|
|
|
4940
5237
|
}
|
|
4941
5238
|
const appName = nameResult.name;
|
|
4942
5239
|
let appId = readAppId(appDir, envName);
|
|
4943
|
-
p12.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
|
|
4944
|
-
const { token, ownerId } = await authenticate(appDir, output);
|
|
4945
5240
|
if (!appId) {
|
|
4946
|
-
const
|
|
4947
|
-
if (
|
|
5241
|
+
const legacyAppId = readLegacyAppId(appDir, envName);
|
|
5242
|
+
if (legacyAppId) {
|
|
4948
5243
|
output.die(
|
|
4949
|
-
`
|
|
4950
|
-
"
|
|
5244
|
+
`This checkout still uses legacy app identity ${legacyAppId}. Run \`deepspace app migrate --dry-run\`, then complete \`deepspace app migrate\` before deploying.`,
|
|
5245
|
+
"legacy_app_migration_required"
|
|
4951
5246
|
);
|
|
4952
5247
|
}
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
);
|
|
4959
|
-
}
|
|
4960
|
-
const confirmed = await p12.confirm({
|
|
4961
|
-
message: `"${appName}" is an existing app owned by another account that you can deploy on-behalf (you are a collaborator or admin). Deploy it? (Meant a new app? Pick a different \`name\` in wrangler.toml.)`
|
|
4962
|
-
});
|
|
4963
|
-
if (p12.isCancel(confirmed) || !confirmed) {
|
|
4964
|
-
output.die("Deploy cancelled.", "cancelled");
|
|
4965
|
-
}
|
|
4966
|
-
}
|
|
4967
|
-
appId = existing.kind === "adopted" ? existing.appId : mintAppId();
|
|
5248
|
+
}
|
|
5249
|
+
p12.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
|
|
5250
|
+
const { token, ownerId } = await authenticate(appDir, output);
|
|
5251
|
+
if (!appId) {
|
|
5252
|
+
appId = mintAppId();
|
|
4968
5253
|
writeAppId(appDir, appId, { wranglerEnv: envName });
|
|
4969
|
-
p12.log.info(
|
|
4970
|
-
existing.kind === "adopted" ? `Adopted existing app id ${appId} for ${appName} \u2014 commit wrangler.toml.` : `Minted app id ${appId} \u2014 commit wrangler.toml.`
|
|
4971
|
-
);
|
|
5254
|
+
p12.log.info(`Minted app id ${appId} \u2014 commit wrangler.toml.`);
|
|
4972
5255
|
}
|
|
4973
5256
|
p12.log.info(`Id: ${appId}`);
|
|
4974
5257
|
const secretsCache = await loadDeploySecrets({
|
|
@@ -4980,6 +5263,7 @@ var deploy_default = defineCommand3({
|
|
|
4980
5263
|
token,
|
|
4981
5264
|
output
|
|
4982
5265
|
});
|
|
5266
|
+
const sourceState = await getAppSource(DEPLOY_URL3, token, appId);
|
|
4983
5267
|
const repository = await syncDeployRepository({
|
|
4984
5268
|
deployUrl: DEPLOY_URL3,
|
|
4985
5269
|
appDir,
|
|
@@ -4987,7 +5271,8 @@ var deploy_default = defineCommand3({
|
|
|
4987
5271
|
token,
|
|
4988
5272
|
push: args.push !== false,
|
|
4989
5273
|
ignoreStale: Boolean(args["ignore-stale"]),
|
|
4990
|
-
output
|
|
5274
|
+
output,
|
|
5275
|
+
sourceState
|
|
4991
5276
|
});
|
|
4992
5277
|
const spinner2 = createSpinner();
|
|
4993
5278
|
const bundle = await buildDeployBundle({
|
|
@@ -5101,7 +5386,7 @@ async function waitForAssetsReady(url, timeoutMs) {
|
|
|
5101
5386
|
if (!body.includes("Assets have not yet deployed") && !body.includes("No app configured")) {
|
|
5102
5387
|
streak++;
|
|
5103
5388
|
if (streak >= 3) return true;
|
|
5104
|
-
await new Promise((
|
|
5389
|
+
await new Promise((resolve19) => setTimeout(resolve19, 1500));
|
|
5105
5390
|
continue;
|
|
5106
5391
|
}
|
|
5107
5392
|
streak = 0;
|
|
@@ -5109,7 +5394,7 @@ async function waitForAssetsReady(url, timeoutMs) {
|
|
|
5109
5394
|
streak = 0;
|
|
5110
5395
|
}
|
|
5111
5396
|
const waitMs = Math.min(8e3, 1e3 * 2 ** (attempt - 1));
|
|
5112
|
-
await new Promise((
|
|
5397
|
+
await new Promise((resolve19) => setTimeout(resolve19, waitMs));
|
|
5113
5398
|
}
|
|
5114
5399
|
return false;
|
|
5115
5400
|
}
|
|
@@ -5741,8 +6026,8 @@ var usage_default = defineDeepspaceCommand({
|
|
|
5741
6026
|
});
|
|
5742
6027
|
|
|
5743
6028
|
// src/cli/commands/add.ts
|
|
5744
|
-
import { existsSync as existsSync18, readFileSync as readFileSync17, realpathSync as
|
|
5745
|
-
import { dirname as dirname7, join as join17, resolve as
|
|
6029
|
+
import { existsSync as existsSync18, readFileSync as readFileSync17, realpathSync as realpathSync3 } from "fs";
|
|
6030
|
+
import { dirname as dirname7, join as join17, resolve as resolve14 } from "path";
|
|
5746
6031
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
5747
6032
|
|
|
5748
6033
|
// src/cli/commands/feature-installer.ts
|
|
@@ -5762,7 +6047,7 @@ import {
|
|
|
5762
6047
|
} from "fs";
|
|
5763
6048
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
5764
6049
|
import { tmpdir as tmpdir2 } from "os";
|
|
5765
|
-
import { dirname as dirname6, isAbsolute as isAbsolute4, join as join16, resolve as
|
|
6050
|
+
import { dirname as dirname6, isAbsolute as isAbsolute4, join as join16, resolve as resolve13 } from "path";
|
|
5766
6051
|
import { fileURLToPath } from "url";
|
|
5767
6052
|
var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
|
|
5768
6053
|
function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
@@ -5810,7 +6095,7 @@ function featureCatalog(packageRoot = featurePackageRoot()) {
|
|
|
5810
6095
|
description: config.description ?? null
|
|
5811
6096
|
}));
|
|
5812
6097
|
}
|
|
5813
|
-
function
|
|
6098
|
+
function editDistance2(a, b) {
|
|
5814
6099
|
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
5815
6100
|
for (let i = 1; i <= a.length; i++) {
|
|
5816
6101
|
let previous = row[0];
|
|
@@ -5827,7 +6112,7 @@ function suggestFeature(featureId, packageRoot = featurePackageRoot()) {
|
|
|
5827
6112
|
let closest = null;
|
|
5828
6113
|
let closestDistance = Number.POSITIVE_INFINITY;
|
|
5829
6114
|
for (const feature of listFeatureConfigs(packageRoot)) {
|
|
5830
|
-
const distance =
|
|
6115
|
+
const distance = editDistance2(featureId, feature.id);
|
|
5831
6116
|
if (distance < closestDistance) {
|
|
5832
6117
|
closest = feature.id;
|
|
5833
6118
|
closestDistance = distance;
|
|
@@ -5837,7 +6122,7 @@ function suggestFeature(featureId, packageRoot = featurePackageRoot()) {
|
|
|
5837
6122
|
return closest !== null && closestDistance <= threshold ? closest : null;
|
|
5838
6123
|
}
|
|
5839
6124
|
function resolveTargetDir(raw) {
|
|
5840
|
-
return isAbsolute4(raw) ? raw :
|
|
6125
|
+
return isAbsolute4(raw) ? raw : resolve13(raw);
|
|
5841
6126
|
}
|
|
5842
6127
|
function resolveFeatureDestPath(fileDest, route) {
|
|
5843
6128
|
const pages = "src/pages/";
|
|
@@ -6482,7 +6767,7 @@ async function targetInstaller(appDir) {
|
|
|
6482
6767
|
"feature_installer_missing"
|
|
6483
6768
|
);
|
|
6484
6769
|
}
|
|
6485
|
-
if (
|
|
6770
|
+
if (realpathSync3(installedRoot) === realpathSync3(featurePackageRoot())) {
|
|
6486
6771
|
return { installFeature };
|
|
6487
6772
|
}
|
|
6488
6773
|
const entry = join17(installedRoot, "dist", "feature-installer.js");
|
|
@@ -6579,7 +6864,7 @@ var add_default = defineDeepspaceCommand({
|
|
|
6579
6864
|
}
|
|
6580
6865
|
return { data: { features: featureCatalog(root) } };
|
|
6581
6866
|
}
|
|
6582
|
-
const appDir =
|
|
6867
|
+
const appDir = resolve14(args.dir ?? ".");
|
|
6583
6868
|
if (!existsSync18(appDir)) {
|
|
6584
6869
|
throw new Refusal(
|
|
6585
6870
|
`Target directory not found: ${appDir}
|
|
@@ -6659,13 +6944,13 @@ function fmtDate(iso) {
|
|
|
6659
6944
|
if (!iso) return "\u2014";
|
|
6660
6945
|
return iso.slice(0, 10);
|
|
6661
6946
|
}
|
|
6662
|
-
async function
|
|
6947
|
+
async function confirm4(message) {
|
|
6663
6948
|
process.stdout.write(`${message} [y/N] `);
|
|
6664
|
-
return new Promise((
|
|
6949
|
+
return new Promise((resolve19) => {
|
|
6665
6950
|
process.stdin.setEncoding("utf-8");
|
|
6666
6951
|
process.stdin.once("data", (data) => {
|
|
6667
6952
|
const answer = data.toString().trim().toLowerCase();
|
|
6668
|
-
|
|
6953
|
+
resolve19(answer === "y" || answer === "yes");
|
|
6669
6954
|
});
|
|
6670
6955
|
});
|
|
6671
6956
|
}
|
|
@@ -6739,7 +7024,7 @@ var buy = defineDeepspaceCommand({
|
|
|
6739
7024
|
"confirmation_required"
|
|
6740
7025
|
);
|
|
6741
7026
|
}
|
|
6742
|
-
const ok2 = await
|
|
7027
|
+
const ok2 = await confirm4(
|
|
6743
7028
|
`Buy ${domain} for ${price}/yr (auto-renews; non-refundable), attach to ${appId}? Provisioning takes ${expectedTime}.`
|
|
6744
7029
|
);
|
|
6745
7030
|
if (!ok2) {
|
|
@@ -6909,7 +7194,7 @@ var detach = defineDeepspaceCommand({
|
|
|
6909
7194
|
"confirmation_required"
|
|
6910
7195
|
);
|
|
6911
7196
|
}
|
|
6912
|
-
const ok2 = await
|
|
7197
|
+
const ok2 = await confirm4(
|
|
6913
7198
|
`Detach ${domain}? Routing will stop; the domain stays registered (auto-renew is unchanged).`
|
|
6914
7199
|
);
|
|
6915
7200
|
if (!ok2) {
|
|
@@ -6992,8 +7277,8 @@ var list3 = defineDeepspaceCommand({
|
|
|
6992
7277
|
}
|
|
6993
7278
|
if (pending.length) {
|
|
6994
7279
|
console.log(`PENDING INVITES ON ${app2}`);
|
|
6995
|
-
for (const
|
|
6996
|
-
console.log(` ${
|
|
7280
|
+
for (const p35 of pending) {
|
|
7281
|
+
console.log(` ${p35.email} (expires ${new Date(p35.expiresAt).toLocaleDateString()})`);
|
|
6997
7282
|
}
|
|
6998
7283
|
}
|
|
6999
7284
|
}
|
|
@@ -7091,7 +7376,7 @@ var cancel3 = defineDeepspaceCommand({
|
|
|
7091
7376
|
`/api/app-collaborators/${encodeURIComponent(app2)}`
|
|
7092
7377
|
);
|
|
7093
7378
|
const target = email.trim().toLowerCase();
|
|
7094
|
-
const found = pending.find((
|
|
7379
|
+
const found = pending.find((p35) => p35.email.toLowerCase() === target);
|
|
7095
7380
|
if (!found) {
|
|
7096
7381
|
throw new Refusal(`${email} has no pending invite on ${app2}`, "no_pending_invite", {
|
|
7097
7382
|
action: cliAction("deepspace", "app", "collaborators", "list", "--app", app2)
|
|
@@ -7562,7 +7847,7 @@ var integrations_default = defineCommand8({
|
|
|
7562
7847
|
// src/cli/commands/library.ts
|
|
7563
7848
|
import { defineCommand as defineCommand9 } from "citty";
|
|
7564
7849
|
import { existsSync as existsSync19, readFileSync as readFileSync19 } from "fs";
|
|
7565
|
-
import { join as join18, resolve as
|
|
7850
|
+
import { join as join18, resolve as resolve15 } from "path";
|
|
7566
7851
|
import { parse as parseToml4 } from "smol-toml";
|
|
7567
7852
|
import * as p16 from "@clack/prompts";
|
|
7568
7853
|
var DEFAULT_LIBRARY_APP = "deepdotspace-site";
|
|
@@ -7632,7 +7917,7 @@ var publish = defineDeepspaceCommand({
|
|
|
7632
7917
|
if (appErr) throw new Refusal(appErr, "invalid_app");
|
|
7633
7918
|
let appName = str(args.app)?.trim();
|
|
7634
7919
|
if (!appName) {
|
|
7635
|
-
const appDir =
|
|
7920
|
+
const appDir = resolve15(str(args.dir) ?? ".");
|
|
7636
7921
|
const wranglerPath = join18(appDir, "wrangler.toml");
|
|
7637
7922
|
if (!existsSync19(wranglerPath)) {
|
|
7638
7923
|
throw new Refusal(
|
|
@@ -7818,7 +8103,7 @@ function detectAppName2() {
|
|
|
7818
8103
|
try {
|
|
7819
8104
|
const cwd = process.cwd();
|
|
7820
8105
|
if (!hasWranglerConfig(cwd)) return void 0;
|
|
7821
|
-
const config =
|
|
8106
|
+
const config = readWranglerConfig2(cwd);
|
|
7822
8107
|
return typeof config.name === "string" ? config.name : void 0;
|
|
7823
8108
|
} catch {
|
|
7824
8109
|
return void 0;
|
|
@@ -8707,24 +8992,50 @@ var status_default = defineCommand11({
|
|
|
8707
8992
|
commitOid: rel.commitOid,
|
|
8708
8993
|
createdAt: rel.createdAt,
|
|
8709
8994
|
url: rel.url,
|
|
8710
|
-
byYou
|
|
8995
|
+
byYou,
|
|
8996
|
+
source: rel.source,
|
|
8997
|
+
sourceRevision: rel.sourceRevision
|
|
8711
8998
|
};
|
|
8712
8999
|
};
|
|
8713
9000
|
var reportWorkspace = reportWorkspace2, reportRemoteFailure = reportRemoteFailure2, reportRelease = reportRelease2;
|
|
8714
9001
|
const api4 = repoApi(deployBaseUrl(), token, appId);
|
|
9002
|
+
const sourceResult = await observeRemote(getAppSource(deployBaseUrl(), token, appId));
|
|
9003
|
+
const source = sourceResult.ok ? sourceResult.value.source : null;
|
|
9004
|
+
const githubSource = source?.provider === "github";
|
|
9005
|
+
if (!sourceResult.ok) {
|
|
9006
|
+
reportRemoteFailure2("Source", "source", sourceResult.error);
|
|
9007
|
+
} else if (githubSource) {
|
|
9008
|
+
lines.push(["Source", `GitHub \xB7 ${source.repository} (manual Git ownership)`]);
|
|
9009
|
+
json.source = { ...source, revision: sourceResult.value.revision };
|
|
9010
|
+
} else if (source?.provider === "deepspace") {
|
|
9011
|
+
lines.push([
|
|
9012
|
+
"Source",
|
|
9013
|
+
`DeepSpace \xB7 packaged Git (revision ${sourceResult.value.revision})`
|
|
9014
|
+
]);
|
|
9015
|
+
json.source = { ...source, revision: sourceResult.value.revision };
|
|
9016
|
+
} else {
|
|
9017
|
+
lines.push(["Source", "unclaimed \xB7 next normal deploy defaults to DeepSpace"]);
|
|
9018
|
+
json.source = null;
|
|
9019
|
+
}
|
|
8715
9020
|
const [refsResult, wsResult, releaseResult] = await Promise.all([
|
|
8716
|
-
observeRemote(api4.getRefs()),
|
|
8717
|
-
facts.workspaceId ? observeRemote(api4.getWorkspace(facts.workspaceId).then((r) => r.view)) : Promise.resolve({ ok: true, value: null }),
|
|
9021
|
+
githubSource ? Promise.resolve({ ok: true, value: null }) : observeRemote(api4.getRefs()),
|
|
9022
|
+
facts.workspaceId && !githubSource ? observeRemote(api4.getWorkspace(facts.workspaceId).then((r) => r.view)) : Promise.resolve({ ok: true, value: null }),
|
|
8718
9023
|
observeRemote(api4.latestRelease().then((r) => r.release))
|
|
8719
9024
|
]);
|
|
8720
9025
|
if (facts.workspaceId) {
|
|
8721
|
-
if (
|
|
9026
|
+
if (githubSource) {
|
|
9027
|
+
lines.push(["Workspace", "DeepSpace workspaces are disabled for GitHub source"]);
|
|
9028
|
+
json.workspace = { state: "disabled", provider: "github" };
|
|
9029
|
+
} else if (!wsResult.ok) reportRemoteFailure2("Workspace", "workspace", wsResult.error);
|
|
8722
9030
|
else if (wsResult.value) reportWorkspace2(wsResult.value);
|
|
8723
9031
|
}
|
|
8724
9032
|
const refs = refsResult.ok ? refsResult.value : void 0;
|
|
8725
9033
|
const trunkRef = refs?.refs.find((r) => `refs/heads/${branch}` === r.name);
|
|
8726
9034
|
if (!facts.workspaceId && branch) {
|
|
8727
|
-
if (
|
|
9035
|
+
if (githubSource) {
|
|
9036
|
+
lines.push(["Trunk", `${branch} is owned through normal Git/GitHub`]);
|
|
9037
|
+
json.trunk = { state: "external", provider: "github", branch };
|
|
9038
|
+
} else if (!refsResult.ok) reportRemoteFailure2("Trunk", "trunk", refsResult.error);
|
|
8728
9039
|
else {
|
|
8729
9040
|
const remoteOid = trunkRef?.oid ?? null;
|
|
8730
9041
|
const localOid = resolveCommit(appDir, `refs/heads/${branch}`);
|
|
@@ -8965,7 +9276,7 @@ var push_default = defineDeepspaceCommand({
|
|
|
8965
9276
|
|
|
8966
9277
|
// src/cli/commands/pull.ts
|
|
8967
9278
|
import * as p20 from "@clack/prompts";
|
|
8968
|
-
import { realpathSync as
|
|
9279
|
+
import { realpathSync as realpathSync4 } from "fs";
|
|
8969
9280
|
function targetedVcAction(command, cwd, appId, branch) {
|
|
8970
9281
|
return {
|
|
8971
9282
|
cwd,
|
|
@@ -8989,7 +9300,7 @@ function workspaceBranchPullRefusal(branch, trunkBranch) {
|
|
|
8989
9300
|
function worktreeHoldingBranch(worktrees, branch, selfPath) {
|
|
8990
9301
|
const real = (pth) => {
|
|
8991
9302
|
try {
|
|
8992
|
-
return
|
|
9303
|
+
return realpathSync4(pth);
|
|
8993
9304
|
} catch {
|
|
8994
9305
|
return pth;
|
|
8995
9306
|
}
|
|
@@ -9245,7 +9556,7 @@ var pull_default = defineDeepspaceCommand({
|
|
|
9245
9556
|
// src/cli/commands/clone.ts
|
|
9246
9557
|
import * as p21 from "@clack/prompts";
|
|
9247
9558
|
import { existsSync as existsSync22, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
|
|
9248
|
-
import { resolve as
|
|
9559
|
+
import { resolve as resolve16, basename as basename3, join as join20 } from "path";
|
|
9249
9560
|
function cleanFailedCloneDir(dir) {
|
|
9250
9561
|
let entries;
|
|
9251
9562
|
try {
|
|
@@ -9299,7 +9610,7 @@ var clone_default = defineDeepspaceCommand({
|
|
|
9299
9610
|
async run({ args }) {
|
|
9300
9611
|
assertAppTargetResolvable(String(args.app));
|
|
9301
9612
|
const selector = String(args.app).trim();
|
|
9302
|
-
const dir =
|
|
9613
|
+
const dir = resolve16(String(args.dir ?? selector));
|
|
9303
9614
|
const dirPreexisted = validateCloneTarget(dir);
|
|
9304
9615
|
const spinner2 = args.json ? null : createSpinner();
|
|
9305
9616
|
spinner2?.start(`Resolving ${selector}\u2026`);
|
|
@@ -9553,7 +9864,7 @@ import { defineCommand as defineCommand12 } from "citty";
|
|
|
9553
9864
|
|
|
9554
9865
|
// src/cli/commands/workspace/new.ts
|
|
9555
9866
|
import * as p26 from "@clack/prompts";
|
|
9556
|
-
import { isAbsolute as isAbsolute6, resolve as
|
|
9867
|
+
import { isAbsolute as isAbsolute6, resolve as resolve17 } from "path";
|
|
9557
9868
|
|
|
9558
9869
|
// src/cli/commands/workspace/analysis.ts
|
|
9559
9870
|
import * as p24 from "@clack/prompts";
|
|
@@ -9678,18 +9989,18 @@ function printOverlaps(overlaps, json) {
|
|
|
9678
9989
|
|
|
9679
9990
|
// src/cli/commands/workspace/local.ts
|
|
9680
9991
|
import * as p25 from "@clack/prompts";
|
|
9681
|
-
import { mkdirSync as mkdirSync7, readFileSync as readFileSync23, realpathSync as
|
|
9992
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync23, realpathSync as realpathSync5, renameSync as renameSync2, statSync, writeFileSync as writeFileSync9 } from "fs";
|
|
9682
9993
|
import { dirname as dirname9, isAbsolute as isAbsolute5, join as join21, relative as relative2, sep as sep2 } from "path";
|
|
9683
9994
|
var WORKSPACE_MARKER = "deepspace-workspace.json";
|
|
9684
9995
|
function excludeWorktreeDir(appDir, dir) {
|
|
9685
9996
|
try {
|
|
9686
9997
|
ensureLocalExclude(appDir, ".deepspace/");
|
|
9687
|
-
const realDir =
|
|
9688
|
-
const rel = relative2(
|
|
9998
|
+
const realDir = realpathSync5(dir);
|
|
9999
|
+
const rel = relative2(realpathSync5(repoToplevel(appDir)), realDir);
|
|
9689
10000
|
if (!rel || rel === ".." || rel.startsWith(`..${sep2}`) || isAbsolute5(rel)) return;
|
|
9690
10001
|
const pattern = rel.split(sep2).join("/");
|
|
9691
10002
|
if (pattern === ".deepspace" || pattern.startsWith(".deepspace/")) return;
|
|
9692
|
-
const relApp = relative2(
|
|
10003
|
+
const relApp = relative2(realpathSync5(appDir), realDir).split(sep2).join("/");
|
|
9693
10004
|
if (relApp === ".deepspace" || relApp.startsWith(".deepspace/")) return;
|
|
9694
10005
|
ensureLocalExclude(appDir, `/${pattern}/`);
|
|
9695
10006
|
} catch {
|
|
@@ -9719,6 +10030,19 @@ function markManagedWorkspace(worktreeRoot, workspaceId) {
|
|
|
9719
10030
|
return false;
|
|
9720
10031
|
}
|
|
9721
10032
|
}
|
|
10033
|
+
function rollbackUnmarkedWorkspace(appDir, worktreeRoot, branch, createdOid) {
|
|
10034
|
+
const removed = runGit(appDir, ["worktree", "remove", worktreeRoot], { allowFail: true });
|
|
10035
|
+
if (removed.status !== 0) {
|
|
10036
|
+
return `git worktree remove failed: ${removed.stderr.toString("utf-8").trim() || "unknown git error"}`;
|
|
10037
|
+
}
|
|
10038
|
+
const deleted = runGit(appDir, ["update-ref", "-d", `refs/heads/${branch}`, createdOid], {
|
|
10039
|
+
allowFail: true
|
|
10040
|
+
});
|
|
10041
|
+
if (deleted.status !== 0) {
|
|
10042
|
+
return `the checkout was removed but branch ${branch} remains: ${deleted.stderr.toString("utf-8").trim() || "unknown git error"}`;
|
|
10043
|
+
}
|
|
10044
|
+
return null;
|
|
10045
|
+
}
|
|
9722
10046
|
function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
|
|
9723
10047
|
const markerPath = worktreeMarkerPath(worktreeRoot);
|
|
9724
10048
|
if (!markerPath) return false;
|
|
@@ -9735,13 +10059,21 @@ function defaultWorkspaceRoot(appDir, workspaceId) {
|
|
|
9735
10059
|
return join21(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
|
|
9736
10060
|
}
|
|
9737
10061
|
function appDirInWorktree(appDir, worktreeRoot) {
|
|
9738
|
-
const appRelative = relative2(
|
|
10062
|
+
const appRelative = relative2(realpathSync5(repoToplevel(appDir)), realpathSync5(appDir));
|
|
9739
10063
|
return appRelative ? join21(worktreeRoot, appRelative) : worktreeRoot;
|
|
9740
10064
|
}
|
|
9741
|
-
function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint, workspaceId) {
|
|
10065
|
+
function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint, workspaceId, options = {}) {
|
|
10066
|
+
const createdOid = resolveCommit(appDir, startPoint);
|
|
10067
|
+
if (!createdOid) throw new Error(`Could not resolve workspace start point ${startPoint}`);
|
|
9742
10068
|
addWorktreeNewBranch(appDir, worktreeRoot, branch, startPoint);
|
|
10069
|
+
const marked = (options.markManaged ?? markManagedWorkspace)(worktreeRoot, workspaceId);
|
|
10070
|
+
if (!marked) {
|
|
10071
|
+
const rollbackError = rollbackUnmarkedWorkspace(appDir, worktreeRoot, branch, createdOid);
|
|
10072
|
+
throw new Error(
|
|
10073
|
+
`Could not record DeepSpace ownership for ${worktreeRoot}.` + (rollbackError ? ` Automatic rollback was incomplete: ${rollbackError}` : " The just-created checkout and branch were rolled back.")
|
|
10074
|
+
);
|
|
10075
|
+
}
|
|
9743
10076
|
excludeWorktreeDir(appDir, worktreeRoot);
|
|
9744
|
-
markManagedWorkspace(worktreeRoot, workspaceId);
|
|
9745
10077
|
}
|
|
9746
10078
|
function inOwnLinkedWorktree(appDir, id) {
|
|
9747
10079
|
if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
|
|
@@ -9799,7 +10131,7 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch) {
|
|
|
9799
10131
|
}
|
|
9800
10132
|
let inside = false;
|
|
9801
10133
|
try {
|
|
9802
|
-
inside =
|
|
10134
|
+
inside = realpathSync5(repoToplevel(appDir)) === realpathSync5(linked.path);
|
|
9803
10135
|
} catch {
|
|
9804
10136
|
inside = false;
|
|
9805
10137
|
}
|
|
@@ -10039,7 +10371,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
10039
10371
|
idempotencyKey: mintIdempotencyKey()
|
|
10040
10372
|
});
|
|
10041
10373
|
const dirInput = typeof args.dir === "string" ? args.dir.trim() : "";
|
|
10042
|
-
const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput :
|
|
10374
|
+
const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput : resolve17(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
|
|
10043
10375
|
const dir = appDirInWorktree(appDir, worktreeRoot);
|
|
10044
10376
|
try {
|
|
10045
10377
|
materializeWorkspaceWorktree(appDir, worktreeRoot, branch, baseOid, id);
|
|
@@ -10078,7 +10410,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
10078
10410
|
// src/cli/commands/workspace/attach.ts
|
|
10079
10411
|
import * as p27 from "@clack/prompts";
|
|
10080
10412
|
import { existsSync as existsSync23, readdirSync as readdirSync7, rmSync as rmSync6 } from "fs";
|
|
10081
|
-
import { isAbsolute as isAbsolute7, resolve as
|
|
10413
|
+
import { isAbsolute as isAbsolute7, resolve as resolve18 } from "path";
|
|
10082
10414
|
function finishedWorkspaceMessage(id, view) {
|
|
10083
10415
|
const workspace = view.workspace;
|
|
10084
10416
|
if (workspace.status === "landed") {
|
|
@@ -10162,7 +10494,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
10162
10494
|
);
|
|
10163
10495
|
}
|
|
10164
10496
|
const requestedDir = typeof args.dir === "string" ? args.dir.trim() : "";
|
|
10165
|
-
const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir :
|
|
10497
|
+
const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir : resolve18(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
|
|
10166
10498
|
const dir2 = appDirInWorktree(appDir, worktreeRoot);
|
|
10167
10499
|
if (resolveCommit(appDir, `refs/heads/${branch}`)) {
|
|
10168
10500
|
throw new Refusal(
|
|
@@ -10201,7 +10533,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
10201
10533
|
extra: { status: view.workspace.status }
|
|
10202
10534
|
});
|
|
10203
10535
|
}
|
|
10204
|
-
const dir =
|
|
10536
|
+
const dir = resolve18(
|
|
10205
10537
|
process.cwd(),
|
|
10206
10538
|
(typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
|
|
10207
10539
|
);
|
|
@@ -11164,6 +11496,721 @@ var git_credential_default = defineCommand13({
|
|
|
11164
11496
|
}
|
|
11165
11497
|
});
|
|
11166
11498
|
|
|
11499
|
+
// src/cli/commands/source.ts
|
|
11500
|
+
import * as p34 from "@clack/prompts";
|
|
11501
|
+
var TRANSFER_PREFIX = "refs/deepspace/source-transfer";
|
|
11502
|
+
var source_default = defineDeepspaceCommand({
|
|
11503
|
+
meta: {
|
|
11504
|
+
name: "source",
|
|
11505
|
+
description: "Show or change the app\u2019s one authoritative Git repository"
|
|
11506
|
+
},
|
|
11507
|
+
args: {
|
|
11508
|
+
provider: {
|
|
11509
|
+
type: "positional",
|
|
11510
|
+
description: "Source provider: github or deepspace (omit to show)",
|
|
11511
|
+
required: false
|
|
11512
|
+
},
|
|
11513
|
+
app: {
|
|
11514
|
+
type: "string",
|
|
11515
|
+
alias: "a",
|
|
11516
|
+
description: "App id or subdomain name (default: current app)",
|
|
11517
|
+
required: false
|
|
11518
|
+
},
|
|
11519
|
+
remote: {
|
|
11520
|
+
type: "string",
|
|
11521
|
+
description: "GitHub remote name when it cannot be inferred (default: origin)",
|
|
11522
|
+
required: false
|
|
11523
|
+
}
|
|
11524
|
+
},
|
|
11525
|
+
async run({ args }) {
|
|
11526
|
+
const requested = typeof args.provider === "string" ? args.provider.trim().toLowerCase() : "";
|
|
11527
|
+
if (requested && requested !== "github" && requested !== "deepspace") {
|
|
11528
|
+
throw new Refusal("Source must be `github` or `deepspace`.", "invalid_source");
|
|
11529
|
+
}
|
|
11530
|
+
const token = await ensureToken();
|
|
11531
|
+
const deployUrl = deployBaseUrl();
|
|
11532
|
+
const appId = await resolveAppTarget(
|
|
11533
|
+
deployUrl,
|
|
11534
|
+
token,
|
|
11535
|
+
typeof args.app === "string" ? args.app : void 0
|
|
11536
|
+
);
|
|
11537
|
+
const state = await getAppSource(deployUrl, token, appId);
|
|
11538
|
+
if (!requested) {
|
|
11539
|
+
reportSource(appId, state, args.json);
|
|
11540
|
+
return { data: sourceData(state) };
|
|
11541
|
+
}
|
|
11542
|
+
const appDir = findAppDir();
|
|
11543
|
+
if (!appDir) {
|
|
11544
|
+
throw new Refusal(
|
|
11545
|
+
"Changing source requires a local app checkout with wrangler.toml and Git history.",
|
|
11546
|
+
"not_in_app_repo"
|
|
11547
|
+
);
|
|
11548
|
+
}
|
|
11549
|
+
assertSyncableRepo(appDir);
|
|
11550
|
+
const remoteArg = typeof args.remote === "string" ? args.remote.trim() : void 0;
|
|
11551
|
+
const result = requested === "github" ? await moveToGitHub({ appDir, appId, token, deployUrl, state, remoteArg }) : await moveToDeepSpace({ appDir, appId, token, deployUrl, state, remoteArg });
|
|
11552
|
+
if (!args.json) {
|
|
11553
|
+
p34.log.success(
|
|
11554
|
+
result.source.provider === "github" ? `Source: GitHub \xB7 ${result.source.repository} (revision ${result.revision})` : `Source: DeepSpace (revision ${result.revision})`
|
|
11555
|
+
);
|
|
11556
|
+
}
|
|
11557
|
+
return { data: { appId, source: result.source, revision: result.revision } };
|
|
11558
|
+
}
|
|
11559
|
+
});
|
|
11560
|
+
function sourceData(state) {
|
|
11561
|
+
return {
|
|
11562
|
+
appId: state.appId,
|
|
11563
|
+
source: state.source,
|
|
11564
|
+
revision: state.revision,
|
|
11565
|
+
registered: state.registered
|
|
11566
|
+
};
|
|
11567
|
+
}
|
|
11568
|
+
function reportSource(appId, state, json) {
|
|
11569
|
+
if (json) return;
|
|
11570
|
+
console.log(`App: ${appId}`);
|
|
11571
|
+
if (state.source?.provider === "github") {
|
|
11572
|
+
console.log(`Source: GitHub \xB7 ${state.source.repository}`);
|
|
11573
|
+
} else if (state.source?.provider === "deepspace") {
|
|
11574
|
+
console.log("Source: DeepSpace");
|
|
11575
|
+
} else {
|
|
11576
|
+
console.log("Source: unclaimed (the first normal deploy defaults to DeepSpace)");
|
|
11577
|
+
}
|
|
11578
|
+
console.log(`Revision: ${state.revision}`);
|
|
11579
|
+
}
|
|
11580
|
+
async function moveToGitHub(input) {
|
|
11581
|
+
const { appDir, appId, token, deployUrl, state, remoteArg } = input;
|
|
11582
|
+
const selected = requireGitHubRemote(appDir, remoteArg);
|
|
11583
|
+
const target = { provider: "github", repository: selected.repository };
|
|
11584
|
+
if (sameSource(state.source, target)) return { source: target, revision: state.revision };
|
|
11585
|
+
if (state.source?.provider === "deepspace") {
|
|
11586
|
+
requireLocalHead(appDir);
|
|
11587
|
+
const api4 = repoApi(deployUrl, token, appId);
|
|
11588
|
+
const { views } = await api4.listWorkspaces();
|
|
11589
|
+
if (views.length > 0) {
|
|
11590
|
+
throw new Refusal(
|
|
11591
|
+
"Land or drop every active DeepSpace workspace before moving source to GitHub.",
|
|
11592
|
+
"active_workspaces"
|
|
11593
|
+
);
|
|
11594
|
+
}
|
|
11595
|
+
ensureSpaceRemote(appDir, appId);
|
|
11596
|
+
runGitRemote(appDir, token, [
|
|
11597
|
+
"fetch",
|
|
11598
|
+
"--quiet",
|
|
11599
|
+
"--prune",
|
|
11600
|
+
SPACE_REMOTE,
|
|
11601
|
+
`+refs/heads/*:${TRANSFER_PREFIX}/heads/*`,
|
|
11602
|
+
`+refs/tags/*:${TRANSFER_PREFIX}/tags/*`
|
|
11603
|
+
]);
|
|
11604
|
+
const { refs } = await api4.getRefs().then((value) => {
|
|
11605
|
+
if (!value)
|
|
11606
|
+
throw new Refusal("The DeepSpace source repository is unavailable.", "source_unavailable");
|
|
11607
|
+
return value;
|
|
11608
|
+
});
|
|
11609
|
+
const wanted = refs.filter(isPublicRef).map(({ name, oid }) => ({ name, oid }));
|
|
11610
|
+
const fetched = transferRefs(appDir);
|
|
11611
|
+
if (!sameRefs(fetched, wanted)) {
|
|
11612
|
+
throw new Refusal(
|
|
11613
|
+
"DeepSpace branches or tags changed while they were being fetched. Retry the transfer from fresh refs.",
|
|
11614
|
+
"source_changed"
|
|
11615
|
+
);
|
|
11616
|
+
}
|
|
11617
|
+
const latest = await api4.latestRelease().then((value) => value.release);
|
|
11618
|
+
requireReleaseReachable(appDir, latest, wanted);
|
|
11619
|
+
const remote = new Map(
|
|
11620
|
+
remotePublicRefs(appDir, selected.name).map((ref) => [ref.name, ref.oid])
|
|
11621
|
+
);
|
|
11622
|
+
const missing = wanted.filter((ref) => remote.get(ref.name) !== ref.oid);
|
|
11623
|
+
if (missing.length > 0) {
|
|
11624
|
+
const refspecs = missing.map((ref) => transferRefspec(ref.name));
|
|
11625
|
+
throw new Refusal(
|
|
11626
|
+
`GitHub ${selected.repository} is missing ${missing.length} authoritative branch/tag ref(s). Push them manually, then rerun this command.`,
|
|
11627
|
+
"github_push_required",
|
|
11628
|
+
{
|
|
11629
|
+
actionRequired: true,
|
|
11630
|
+
action: { cwd: appDir, argv: ["git", "push", "--atomic", selected.name, ...refspecs] },
|
|
11631
|
+
extra: {
|
|
11632
|
+
appId,
|
|
11633
|
+
repository: selected.repository,
|
|
11634
|
+
missing: missing.map((ref) => ref.name)
|
|
11635
|
+
}
|
|
11636
|
+
}
|
|
11637
|
+
);
|
|
11638
|
+
}
|
|
11639
|
+
return setAppSource(deployUrl, token, appId, {
|
|
11640
|
+
source: target,
|
|
11641
|
+
expectedRevision: state.revision,
|
|
11642
|
+
refs: wanted,
|
|
11643
|
+
...releaseExpectation(latest)
|
|
11644
|
+
});
|
|
11645
|
+
}
|
|
11646
|
+
requirePublishedHead(appDir, selected, appId);
|
|
11647
|
+
return setAppSource(deployUrl, token, appId, {
|
|
11648
|
+
source: target,
|
|
11649
|
+
expectedRevision: state.revision
|
|
11650
|
+
});
|
|
11651
|
+
}
|
|
11652
|
+
async function moveToDeepSpace(input) {
|
|
11653
|
+
const { appDir, appId, token, deployUrl, state, remoteArg } = input;
|
|
11654
|
+
const target = { provider: "deepspace" };
|
|
11655
|
+
if (sameSource(state.source, target)) return { source: target, revision: state.revision };
|
|
11656
|
+
if (state.source?.provider !== "github") {
|
|
11657
|
+
return setAppSource(deployUrl, token, appId, {
|
|
11658
|
+
source: target,
|
|
11659
|
+
expectedRevision: state.revision
|
|
11660
|
+
});
|
|
11661
|
+
}
|
|
11662
|
+
const selected = requireGitHubRemote(appDir, remoteArg, state.source.repository);
|
|
11663
|
+
const { branch, oid } = requirePublishedHead(appDir, selected);
|
|
11664
|
+
fetchGitHubTransferRefs(appDir, selected.name);
|
|
11665
|
+
const refs = transferRefs(appDir);
|
|
11666
|
+
const latest = await repoApi(deployUrl, token, appId).latestRelease().then((value) => value.release);
|
|
11667
|
+
requireReleaseReachable(appDir, latest, refs);
|
|
11668
|
+
ensureSpaceRemote(appDir, appId);
|
|
11669
|
+
if (refs.length === 0) {
|
|
11670
|
+
throw new Refusal("GitHub has no branches or tags to import.", "source_empty");
|
|
11671
|
+
}
|
|
11672
|
+
const pushed = runGit(
|
|
11673
|
+
appDir,
|
|
11674
|
+
[
|
|
11675
|
+
"push",
|
|
11676
|
+
"--force",
|
|
11677
|
+
"--atomic",
|
|
11678
|
+
"--prune",
|
|
11679
|
+
SPACE_REMOTE,
|
|
11680
|
+
`${TRANSFER_PREFIX}/heads/*:refs/heads/*`,
|
|
11681
|
+
`${TRANSFER_PREFIX}/tags/*:refs/tags/*`
|
|
11682
|
+
],
|
|
11683
|
+
{ allowFail: true, env: gitSourceImportEnv(token, state.revision, deployUrl) }
|
|
11684
|
+
);
|
|
11685
|
+
if (pushed.status !== 0) {
|
|
11686
|
+
throw new Refusal(
|
|
11687
|
+
`DeepSpace source import failed: ${pushed.stderr.toString("utf-8").trim() || "git push failed"}`,
|
|
11688
|
+
"source_import_failed"
|
|
11689
|
+
);
|
|
11690
|
+
}
|
|
11691
|
+
return setAppSource(deployUrl, token, appId, {
|
|
11692
|
+
source: target,
|
|
11693
|
+
expectedRevision: state.revision,
|
|
11694
|
+
branch,
|
|
11695
|
+
commitOid: oid,
|
|
11696
|
+
refs,
|
|
11697
|
+
...releaseExpectation(latest)
|
|
11698
|
+
});
|
|
11699
|
+
}
|
|
11700
|
+
function requireGitHubRemote(appDir, remoteArg, repository) {
|
|
11701
|
+
const selected = selectGitHubRemote(appDir, { name: remoteArg, repository });
|
|
11702
|
+
if (selected) return selected;
|
|
11703
|
+
throw new Refusal(
|
|
11704
|
+
remoteArg ? `Remote "${remoteArg}" is not a GitHub repository${repository ? ` for ${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>.",
|
|
11705
|
+
"github_remote_required"
|
|
11706
|
+
);
|
|
11707
|
+
}
|
|
11708
|
+
function requirePublishedHead(appDir, remote, appId = "") {
|
|
11709
|
+
const head = requireLocalHead(appDir);
|
|
11710
|
+
if (remoteBranchOid(appDir, remote.name, head.branch) !== head.oid) {
|
|
11711
|
+
throw githubPushRequired(appDir, appId, remote, head.branch, head.oid);
|
|
11712
|
+
}
|
|
11713
|
+
return head;
|
|
11714
|
+
}
|
|
11715
|
+
function requireLocalHead(appDir) {
|
|
11716
|
+
if (!isWorkTreeClean(appDir)) {
|
|
11717
|
+
throw new Refusal("Commit or discard local changes before changing source.", "dirty_worktree");
|
|
11718
|
+
}
|
|
11719
|
+
const branch = currentBranch(appDir);
|
|
11720
|
+
if (!branch) throw new Refusal("HEAD is detached; switch to a branch first.", "detached_head");
|
|
11721
|
+
const oid = resolveCommit(appDir, "HEAD");
|
|
11722
|
+
if (!oid) throw new Refusal("The repository has no commit to publish.", "no_commits");
|
|
11723
|
+
return { branch, oid };
|
|
11724
|
+
}
|
|
11725
|
+
function githubPushRequired(appDir, appId, remote, branch, oid) {
|
|
11726
|
+
return new Refusal(
|
|
11727
|
+
`GitHub ${remote.repository} does not have ${branch} at ${oid.slice(0, 10)}. Push it manually, then rerun.`,
|
|
11728
|
+
"github_push_required",
|
|
11729
|
+
{
|
|
11730
|
+
actionRequired: true,
|
|
11731
|
+
action: { cwd: appDir, argv: ["git", "push", remote.name, branch] },
|
|
11732
|
+
extra: { ...appId ? { appId } : {}, repository: remote.repository, branch, oid }
|
|
11733
|
+
}
|
|
11734
|
+
);
|
|
11735
|
+
}
|
|
11736
|
+
function fetchGitHubTransferRefs(appDir, remote) {
|
|
11737
|
+
runGit(appDir, [
|
|
11738
|
+
"fetch",
|
|
11739
|
+
"--quiet",
|
|
11740
|
+
"--prune",
|
|
11741
|
+
remote,
|
|
11742
|
+
`+refs/heads/*:${TRANSFER_PREFIX}/heads/*`,
|
|
11743
|
+
`+refs/tags/*:${TRANSFER_PREFIX}/tags/*`
|
|
11744
|
+
]);
|
|
11745
|
+
}
|
|
11746
|
+
function transferRefs(appDir) {
|
|
11747
|
+
return localTransferRefs(appDir, `${TRANSFER_PREFIX}/heads`, "refs/heads").concat(
|
|
11748
|
+
localTransferRefs(appDir, `${TRANSFER_PREFIX}/tags`, "refs/tags")
|
|
11749
|
+
);
|
|
11750
|
+
}
|
|
11751
|
+
function transferRefspec(ref) {
|
|
11752
|
+
if (ref.startsWith("refs/heads/")) {
|
|
11753
|
+
return `${TRANSFER_PREFIX}/heads/${ref.slice("refs/heads/".length)}:${ref}`;
|
|
11754
|
+
}
|
|
11755
|
+
return `${TRANSFER_PREFIX}/tags/${ref.slice("refs/tags/".length)}:${ref}`;
|
|
11756
|
+
}
|
|
11757
|
+
function isPublicRef(ref) {
|
|
11758
|
+
return ref.name.startsWith("refs/heads/") || ref.name.startsWith("refs/tags/");
|
|
11759
|
+
}
|
|
11760
|
+
function requireReleaseReachable(appDir, release2, refs) {
|
|
11761
|
+
if (!release2?.commitOid) return;
|
|
11762
|
+
const known = resolveCommit(appDir, release2.commitOid) !== null;
|
|
11763
|
+
const reachable = known && refs.some((ref) => isAncestor(appDir, release2.commitOid, ref.oid));
|
|
11764
|
+
if (!reachable) {
|
|
11765
|
+
throw new Refusal(
|
|
11766
|
+
`Live release #${release2.seq} (${release2.commitOid.slice(0, 10)}) is not reachable from a public branch or tag. Redeploy a public branch before changing source.`,
|
|
11767
|
+
"release_not_public"
|
|
11768
|
+
);
|
|
11769
|
+
}
|
|
11770
|
+
}
|
|
11771
|
+
function releaseExpectation(release2) {
|
|
11772
|
+
return {
|
|
11773
|
+
expectedReleaseId: release2?.id ?? null,
|
|
11774
|
+
expectedReleaseCommitOid: release2?.commitOid ?? null
|
|
11775
|
+
};
|
|
11776
|
+
}
|
|
11777
|
+
function sameSource(left, right) {
|
|
11778
|
+
return left?.provider === right.provider && (left.provider !== "github" || right.provider === "github" && left.repository === right.repository);
|
|
11779
|
+
}
|
|
11780
|
+
function sameRefs(left, right) {
|
|
11781
|
+
if (left.length !== right.length) return false;
|
|
11782
|
+
const expected = new Map(right.map((ref) => [ref.name, ref.oid]));
|
|
11783
|
+
return left.every((ref) => expected.get(ref.name) === ref.oid);
|
|
11784
|
+
}
|
|
11785
|
+
|
|
11786
|
+
// src/cli/lib/identity-migration-api.ts
|
|
11787
|
+
async function getIdentityMigration(deployUrl, token, appId) {
|
|
11788
|
+
try {
|
|
11789
|
+
const result = await apiFetch(
|
|
11790
|
+
deployUrl,
|
|
11791
|
+
token,
|
|
11792
|
+
`/api/apps/${encodeURIComponent(appId)}/identity-migration`
|
|
11793
|
+
);
|
|
11794
|
+
return result.migration;
|
|
11795
|
+
} catch (error) {
|
|
11796
|
+
if (error instanceof ApiError && error.code === "migration_not_found") return null;
|
|
11797
|
+
throw error;
|
|
11798
|
+
}
|
|
11799
|
+
}
|
|
11800
|
+
async function prepareIdentityMigration(deployUrl, token, legacyAppId, input) {
|
|
11801
|
+
const result = await apiFetch(
|
|
11802
|
+
deployUrl,
|
|
11803
|
+
token,
|
|
11804
|
+
`/api/apps/${encodeURIComponent(legacyAppId)}/identity-migration/prepare`,
|
|
11805
|
+
{ method: "POST", body: JSON.stringify(input) }
|
|
11806
|
+
);
|
|
11807
|
+
return result.migration;
|
|
11808
|
+
}
|
|
11809
|
+
async function inspectIdentityMigration(deployUrl, token, legacyAppId, input) {
|
|
11810
|
+
const result = await apiFetch(
|
|
11811
|
+
deployUrl,
|
|
11812
|
+
token,
|
|
11813
|
+
`/api/apps/${encodeURIComponent(legacyAppId)}/identity-migration/inspect`,
|
|
11814
|
+
{ method: "POST", body: JSON.stringify(input) }
|
|
11815
|
+
);
|
|
11816
|
+
return result.inventory;
|
|
11817
|
+
}
|
|
11818
|
+
async function commitIdentityMigration(deployUrl, token, appId, input) {
|
|
11819
|
+
const result = await apiFetch(
|
|
11820
|
+
deployUrl,
|
|
11821
|
+
token,
|
|
11822
|
+
`/api/apps/${encodeURIComponent(appId)}/identity-migration/commit`,
|
|
11823
|
+
{ method: "POST", body: JSON.stringify(input) }
|
|
11824
|
+
);
|
|
11825
|
+
return result.migration;
|
|
11826
|
+
}
|
|
11827
|
+
async function cancelIdentityMigration(deployUrl, token, appId) {
|
|
11828
|
+
await apiFetch(
|
|
11829
|
+
deployUrl,
|
|
11830
|
+
token,
|
|
11831
|
+
`/api/apps/${encodeURIComponent(appId)}/identity-migration`,
|
|
11832
|
+
{ method: "DELETE" }
|
|
11833
|
+
);
|
|
11834
|
+
}
|
|
11835
|
+
async function rollbackIdentityMigration(deployUrl, token, appId) {
|
|
11836
|
+
const result = await apiFetch(
|
|
11837
|
+
deployUrl,
|
|
11838
|
+
token,
|
|
11839
|
+
`/api/apps/${encodeURIComponent(appId)}/identity-migration/rollback`,
|
|
11840
|
+
{ method: "POST" }
|
|
11841
|
+
);
|
|
11842
|
+
return result.migration;
|
|
11843
|
+
}
|
|
11844
|
+
|
|
11845
|
+
// src/cli/commands/migrate.ts
|
|
11846
|
+
var STRICT_APP_ID_RE2 = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
11847
|
+
var migrate_default = defineDeepspaceCommand({
|
|
11848
|
+
meta: {
|
|
11849
|
+
name: "migrate",
|
|
11850
|
+
description: "Upgrade a legacy GitHub app to an immutable app id without moving its data"
|
|
11851
|
+
},
|
|
11852
|
+
args: {
|
|
11853
|
+
remote: {
|
|
11854
|
+
type: "string",
|
|
11855
|
+
description: "GitHub remote name when it cannot be inferred (default: origin)",
|
|
11856
|
+
required: false
|
|
11857
|
+
},
|
|
11858
|
+
env: {
|
|
11859
|
+
type: "string",
|
|
11860
|
+
alias: "e",
|
|
11861
|
+
description: "wrangler.toml environment whose app id should be migrated",
|
|
11862
|
+
required: false
|
|
11863
|
+
},
|
|
11864
|
+
"dry-run": {
|
|
11865
|
+
type: "boolean",
|
|
11866
|
+
description: "Inspect prerequisites and show the migration without changing anything",
|
|
11867
|
+
default: false
|
|
11868
|
+
},
|
|
11869
|
+
cancel: {
|
|
11870
|
+
type: "boolean",
|
|
11871
|
+
description: "Cancel a prepared migration after restoring and publishing the legacy id",
|
|
11872
|
+
default: false
|
|
11873
|
+
},
|
|
11874
|
+
rollback: {
|
|
11875
|
+
type: "boolean",
|
|
11876
|
+
description: "Roll back a committed migration before any canonical deploy starts",
|
|
11877
|
+
default: false
|
|
11878
|
+
}
|
|
11879
|
+
},
|
|
11880
|
+
async run({ args }) {
|
|
11881
|
+
const recoveryMode = args.cancel === true ? "cancel" : args.rollback === true ? "rollback" : null;
|
|
11882
|
+
const exclusiveModes = [args["dry-run"], args.cancel, args.rollback].filter(Boolean).length;
|
|
11883
|
+
if (exclusiveModes > 1) {
|
|
11884
|
+
throw new Refusal(
|
|
11885
|
+
"Use only one of --dry-run, --cancel, or --rollback.",
|
|
11886
|
+
"conflicting_migration_mode"
|
|
11887
|
+
);
|
|
11888
|
+
}
|
|
11889
|
+
const appDir = findAppDir();
|
|
11890
|
+
if (!appDir) {
|
|
11891
|
+
throw new Refusal("Run this command from a DeepSpace app Git checkout.", "not_in_app_repo");
|
|
11892
|
+
}
|
|
11893
|
+
assertSyncableRepo(appDir);
|
|
11894
|
+
const envName = typeof args.env === "string" && args.env ? args.env : void 0;
|
|
11895
|
+
const currentAppId = readAppId(appDir, envName) ?? readLegacyAppId(appDir, envName);
|
|
11896
|
+
if (!currentAppId) {
|
|
11897
|
+
throw new Refusal(
|
|
11898
|
+
"wrangler.toml has no unambiguous DeepSpace app identity for this environment.",
|
|
11899
|
+
"missing_app_id"
|
|
11900
|
+
);
|
|
11901
|
+
}
|
|
11902
|
+
if (!STRICT_APP_ID_RE2.test(currentAppId)) requireTrackedWrangler(appDir);
|
|
11903
|
+
const token = await ensureToken();
|
|
11904
|
+
const deployUrl = deployBaseUrl();
|
|
11905
|
+
const existingMigration = await getIdentityMigration(deployUrl, token, currentAppId);
|
|
11906
|
+
if (recoveryMode) {
|
|
11907
|
+
if (!existingMigration) {
|
|
11908
|
+
if (!args.json) console.log("No reversible identity migration is recorded.");
|
|
11909
|
+
return { data: { status: "no_migration", appId: currentAppId } };
|
|
11910
|
+
}
|
|
11911
|
+
return recoverMigration({
|
|
11912
|
+
mode: recoveryMode,
|
|
11913
|
+
appDir,
|
|
11914
|
+
envName,
|
|
11915
|
+
remoteName: typeof args.remote === "string" ? args.remote : void 0,
|
|
11916
|
+
currentAppId,
|
|
11917
|
+
token,
|
|
11918
|
+
deployUrl,
|
|
11919
|
+
migration: existingMigration,
|
|
11920
|
+
json: args.json
|
|
11921
|
+
});
|
|
11922
|
+
}
|
|
11923
|
+
if (STRICT_APP_ID_RE2.test(currentAppId)) {
|
|
11924
|
+
if (!existingMigration) {
|
|
11925
|
+
reportAlreadyModern(currentAppId, args.json);
|
|
11926
|
+
return { data: { status: "already_modern", appId: currentAppId } };
|
|
11927
|
+
}
|
|
11928
|
+
if (args["dry-run"]) {
|
|
11929
|
+
reportMigration(existingMigration, args.json);
|
|
11930
|
+
return { data: { status: existingMigration.status, migration: existingMigration } };
|
|
11931
|
+
}
|
|
11932
|
+
return continuePreparedMigration({
|
|
11933
|
+
appDir,
|
|
11934
|
+
envName,
|
|
11935
|
+
remoteName: typeof args.remote === "string" ? args.remote : void 0,
|
|
11936
|
+
token,
|
|
11937
|
+
deployUrl,
|
|
11938
|
+
migration: existingMigration,
|
|
11939
|
+
json: args.json
|
|
11940
|
+
});
|
|
11941
|
+
}
|
|
11942
|
+
if (existingMigration && existingMigration.status !== "prepared" && existingMigration.status !== "rolled_back") {
|
|
11943
|
+
throw new Refusal(
|
|
11944
|
+
`The registry already uses ${existingMigration.appId}, but wrangler.toml still uses ${currentAppId}. Restore the migration commit before deploying.`,
|
|
11945
|
+
"migration_config_stale",
|
|
11946
|
+
{ extra: { migration: existingMigration } }
|
|
11947
|
+
);
|
|
11948
|
+
}
|
|
11949
|
+
const remote = requireGitHubRemote2(
|
|
11950
|
+
appDir,
|
|
11951
|
+
typeof args.remote === "string" ? args.remote : void 0
|
|
11952
|
+
);
|
|
11953
|
+
const head = requirePublishedHead2(appDir, remote);
|
|
11954
|
+
const proposedAppId = existingMigration?.appId ?? mintAppId();
|
|
11955
|
+
const inventory = await inspectIdentityMigration(deployUrl, token, currentAppId, {
|
|
11956
|
+
destinationAppId: proposedAppId,
|
|
11957
|
+
repository: remote.repository
|
|
11958
|
+
});
|
|
11959
|
+
if (!inventory.ready) {
|
|
11960
|
+
throw new Refusal(
|
|
11961
|
+
`Migration preflight is blocked: ${inventory.blockers.map((blocker) => blocker.message).join("; ")}`,
|
|
11962
|
+
"migration_preflight_blocked",
|
|
11963
|
+
{ extra: { inventory } }
|
|
11964
|
+
);
|
|
11965
|
+
}
|
|
11966
|
+
if (args["dry-run"]) {
|
|
11967
|
+
if (!args.json) {
|
|
11968
|
+
console.log(`Legacy app: ${currentAppId}`);
|
|
11969
|
+
console.log(`GitHub: ${remote.repository} (${head.branch} @ ${head.oid.slice(0, 10)})`);
|
|
11970
|
+
console.log(
|
|
11971
|
+
`Immutable app id: ${proposedAppId}${existingMigration ? "" : " (not reserved)"}`
|
|
11972
|
+
);
|
|
11973
|
+
console.log(
|
|
11974
|
+
`Registry rows to re-key: app=1 routes=${inventory.rekey.routes.length} collaborators=${inventory.rekey.collaborators.length} pendingInvites=${inventory.rekey.pendingCollaborators.length} transfers=${inventory.rekey.transfer ? 1 : 0}`
|
|
11975
|
+
);
|
|
11976
|
+
console.log(
|
|
11977
|
+
`Physical stores retained at ${inventory.app.resourceId}: ${inventory.retainedPhysicalStores.map((store) => store.kind).join(", ")}`
|
|
11978
|
+
);
|
|
11979
|
+
console.log("No changes made.");
|
|
11980
|
+
}
|
|
11981
|
+
return {
|
|
11982
|
+
data: {
|
|
11983
|
+
status: "ready",
|
|
11984
|
+
legacyAppId: currentAppId,
|
|
11985
|
+
proposedAppId,
|
|
11986
|
+
resourceId: inventory.app.resourceId,
|
|
11987
|
+
repository: remote.repository,
|
|
11988
|
+
branch: head.branch,
|
|
11989
|
+
commitOid: head.oid,
|
|
11990
|
+
sourceClaimRequired: inventory.app.sourceClaimRequired,
|
|
11991
|
+
inventory
|
|
11992
|
+
}
|
|
11993
|
+
};
|
|
11994
|
+
}
|
|
11995
|
+
const migration = existingMigration?.status === "prepared" ? existingMigration : await prepareIdentityMigration(deployUrl, token, currentAppId, {
|
|
11996
|
+
destinationAppId: proposedAppId,
|
|
11997
|
+
repository: remote.repository,
|
|
11998
|
+
expectedSourceRevision: inventory.app.sourceRevision
|
|
11999
|
+
});
|
|
12000
|
+
writeAppId(appDir, migration.appId, { wranglerEnv: envName, force: true });
|
|
12001
|
+
throw commitRequired(appDir, migration, envName);
|
|
12002
|
+
}
|
|
12003
|
+
});
|
|
12004
|
+
async function recoverMigration(input) {
|
|
12005
|
+
const { mode, appDir, envName, remoteName, currentAppId, token, deployUrl, migration, json } = input;
|
|
12006
|
+
if (migration.status === "verified" || migration.deployStartedAt) {
|
|
12007
|
+
throw new Refusal(
|
|
12008
|
+
"A canonical deployment has started. Registry rollback is no longer safe; use the forward-recovery procedure to repair or redeploy the canonical app.",
|
|
12009
|
+
"forward_recovery_required",
|
|
12010
|
+
{ extra: { migration } }
|
|
12011
|
+
);
|
|
12012
|
+
}
|
|
12013
|
+
if (migration.status === "rolled_back") {
|
|
12014
|
+
if (currentAppId !== migration.legacyAppId) {
|
|
12015
|
+
writeAppId(appDir, migration.legacyAppId, { wranglerEnv: envName, force: true });
|
|
12016
|
+
throw restoreLegacyCommitRequired(appDir, migration, envName);
|
|
12017
|
+
}
|
|
12018
|
+
reportMigration(migration, json);
|
|
12019
|
+
return { data: { status: "rolled_back", migration } };
|
|
12020
|
+
}
|
|
12021
|
+
if (mode === "cancel" && migration.status !== "prepared") {
|
|
12022
|
+
throw new Refusal(
|
|
12023
|
+
"This migration is committed. Use --rollback before any canonical deploy starts.",
|
|
12024
|
+
"migration_rollback_required",
|
|
12025
|
+
{ extra: { migration } }
|
|
12026
|
+
);
|
|
12027
|
+
}
|
|
12028
|
+
if (mode === "rollback" && migration.status !== "committed") {
|
|
12029
|
+
throw new Refusal(
|
|
12030
|
+
"This migration is only prepared. Use --cancel instead.",
|
|
12031
|
+
"migration_cancel_required",
|
|
12032
|
+
{ extra: { migration } }
|
|
12033
|
+
);
|
|
12034
|
+
}
|
|
12035
|
+
if (currentAppId !== migration.legacyAppId) {
|
|
12036
|
+
writeAppId(appDir, migration.legacyAppId, { wranglerEnv: envName, force: true });
|
|
12037
|
+
throw restoreLegacyCommitRequired(appDir, migration, envName);
|
|
12038
|
+
}
|
|
12039
|
+
const remote = requireGitHubRemote2(appDir, remoteName, migration.repository);
|
|
12040
|
+
requirePublishedHead2(appDir, remote);
|
|
12041
|
+
if (mode === "cancel") {
|
|
12042
|
+
await cancelIdentityMigration(deployUrl, token, migration.legacyAppId);
|
|
12043
|
+
if (!json) console.log(`Canceled ${migration.legacyAppId} \u2192 ${migration.appId}.`);
|
|
12044
|
+
return { data: { status: "cancelled", legacyAppId: migration.legacyAppId } };
|
|
12045
|
+
}
|
|
12046
|
+
const rolledBack = await rollbackIdentityMigration(deployUrl, token, migration.appId);
|
|
12047
|
+
reportMigration(rolledBack, json);
|
|
12048
|
+
return { data: { status: "rolled_back", migration: rolledBack } };
|
|
12049
|
+
}
|
|
12050
|
+
async function continuePreparedMigration(input) {
|
|
12051
|
+
const { appDir, envName, remoteName, token, deployUrl, migration, json } = input;
|
|
12052
|
+
if (migration.status === "rolled_back") {
|
|
12053
|
+
throw new Refusal(
|
|
12054
|
+
`This migration was rolled back. Restore DEEPSPACE_APP_ID to ${migration.legacyAppId} before continuing.`,
|
|
12055
|
+
"migration_rolled_back"
|
|
12056
|
+
);
|
|
12057
|
+
}
|
|
12058
|
+
if (migration.status === "verified") {
|
|
12059
|
+
reportMigration(migration, json);
|
|
12060
|
+
return { data: { status: "verified", migration } };
|
|
12061
|
+
}
|
|
12062
|
+
if (migration.status === "committed") {
|
|
12063
|
+
throw deployRequired(appDir, migration, envName);
|
|
12064
|
+
}
|
|
12065
|
+
if (!isWorkTreeClean(appDir)) {
|
|
12066
|
+
const wranglerPath = wranglerRepoPath(appDir);
|
|
12067
|
+
if (statusFiles(repoToplevel(appDir)).includes(wranglerPath)) {
|
|
12068
|
+
throw commitRequired(appDir, migration, envName);
|
|
12069
|
+
}
|
|
12070
|
+
throw new Refusal(
|
|
12071
|
+
"Commit or discard unrelated working-tree changes before completing the migration.",
|
|
12072
|
+
"dirty_worktree"
|
|
12073
|
+
);
|
|
12074
|
+
}
|
|
12075
|
+
const remote = requireGitHubRemote2(appDir, remoteName, migration.repository);
|
|
12076
|
+
const head = requireLocalHead2(appDir);
|
|
12077
|
+
const remoteOid = remoteBranchOid(appDir, remote.name, head.branch);
|
|
12078
|
+
if (remoteOid !== head.oid) {
|
|
12079
|
+
throw new Refusal(
|
|
12080
|
+
`GitHub ${remote.repository} does not have ${head.branch} at ${head.oid.slice(0, 10)}. Push the migration commit, then rerun.`,
|
|
12081
|
+
"github_push_required",
|
|
12082
|
+
{
|
|
12083
|
+
actionRequired: true,
|
|
12084
|
+
action: { cwd: appDir, argv: ["git", "push", remote.name, head.branch] },
|
|
12085
|
+
extra: { appId: migration.appId, repository: remote.repository, ...head }
|
|
12086
|
+
}
|
|
12087
|
+
);
|
|
12088
|
+
}
|
|
12089
|
+
const committed = await commitIdentityMigration(deployUrl, token, migration.appId, {
|
|
12090
|
+
commitOid: head.oid,
|
|
12091
|
+
branch: head.branch
|
|
12092
|
+
});
|
|
12093
|
+
throw deployRequired(appDir, committed, envName);
|
|
12094
|
+
}
|
|
12095
|
+
function requireGitHubRemote2(appDir, remoteName, repository) {
|
|
12096
|
+
const selected = selectGitHubRemote(appDir, { name: remoteName, repository });
|
|
12097
|
+
if (selected) return selected;
|
|
12098
|
+
throw new Refusal(
|
|
12099
|
+
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>.",
|
|
12100
|
+
"github_remote_required"
|
|
12101
|
+
);
|
|
12102
|
+
}
|
|
12103
|
+
function requireLocalHead2(appDir) {
|
|
12104
|
+
const branch = currentBranch(appDir);
|
|
12105
|
+
if (!branch) throw new Refusal("HEAD is detached; switch to a branch first.", "detached_head");
|
|
12106
|
+
const oid = resolveCommit(appDir, "HEAD");
|
|
12107
|
+
if (!oid) throw new Refusal("The repository has no commit to publish.", "no_commits");
|
|
12108
|
+
return { branch, oid };
|
|
12109
|
+
}
|
|
12110
|
+
function requirePublishedHead2(appDir, remote) {
|
|
12111
|
+
if (!isWorkTreeClean(appDir)) {
|
|
12112
|
+
throw new Refusal(
|
|
12113
|
+
"Commit or discard local changes before preparing migration.",
|
|
12114
|
+
"dirty_worktree"
|
|
12115
|
+
);
|
|
12116
|
+
}
|
|
12117
|
+
const head = requireLocalHead2(appDir);
|
|
12118
|
+
if (remoteBranchOid(appDir, remote.name, head.branch) !== head.oid) {
|
|
12119
|
+
throw new Refusal(
|
|
12120
|
+
`GitHub ${remote.repository} does not have ${head.branch} at ${head.oid.slice(0, 10)}. Push it, then rerun.`,
|
|
12121
|
+
"github_push_required",
|
|
12122
|
+
{
|
|
12123
|
+
actionRequired: true,
|
|
12124
|
+
action: { cwd: appDir, argv: ["git", "push", remote.name, head.branch] },
|
|
12125
|
+
extra: { repository: remote.repository, ...head }
|
|
12126
|
+
}
|
|
12127
|
+
);
|
|
12128
|
+
}
|
|
12129
|
+
return head;
|
|
12130
|
+
}
|
|
12131
|
+
function commitRequired(appDir, migration, envName) {
|
|
12132
|
+
const wranglerPath = wranglerRepoPath(appDir);
|
|
12133
|
+
return new Refusal(
|
|
12134
|
+
`Prepared ${migration.legacyAppId} \u2192 ${migration.appId}. Commit the deterministic wrangler.toml change, then rerun.`,
|
|
12135
|
+
"migration_commit_required",
|
|
12136
|
+
{
|
|
12137
|
+
actionRequired: true,
|
|
12138
|
+
action: {
|
|
12139
|
+
cwd: repoToplevel(appDir),
|
|
12140
|
+
argv: [
|
|
12141
|
+
"git",
|
|
12142
|
+
"commit",
|
|
12143
|
+
"--only",
|
|
12144
|
+
"--message",
|
|
12145
|
+
`Migrate DeepSpace app identity to ${migration.appId}`,
|
|
12146
|
+
"--",
|
|
12147
|
+
wranglerPath
|
|
12148
|
+
]
|
|
12149
|
+
},
|
|
12150
|
+
extra: { migration, env: envName ?? null }
|
|
12151
|
+
}
|
|
12152
|
+
);
|
|
12153
|
+
}
|
|
12154
|
+
function restoreLegacyCommitRequired(appDir, migration, envName) {
|
|
12155
|
+
const wranglerPath = wranglerRepoPath(appDir);
|
|
12156
|
+
return new Refusal(
|
|
12157
|
+
`Restored ${migration.legacyAppId} in wrangler.toml. Commit and push this recovery change, then rerun.`,
|
|
12158
|
+
"migration_restore_commit_required",
|
|
12159
|
+
{
|
|
12160
|
+
actionRequired: true,
|
|
12161
|
+
action: {
|
|
12162
|
+
cwd: repoToplevel(appDir),
|
|
12163
|
+
argv: [
|
|
12164
|
+
"git",
|
|
12165
|
+
"commit",
|
|
12166
|
+
"--only",
|
|
12167
|
+
"--message",
|
|
12168
|
+
`Restore DeepSpace app identity to ${migration.legacyAppId}`,
|
|
12169
|
+
"--",
|
|
12170
|
+
wranglerPath
|
|
12171
|
+
]
|
|
12172
|
+
},
|
|
12173
|
+
extra: { migration, env: envName ?? null }
|
|
12174
|
+
}
|
|
12175
|
+
);
|
|
12176
|
+
}
|
|
12177
|
+
function wranglerRepoPath(appDir) {
|
|
12178
|
+
return `${gitLine(appDir, ["rev-parse", "--show-prefix"])}wrangler.toml`;
|
|
12179
|
+
}
|
|
12180
|
+
function requireTrackedWrangler(appDir) {
|
|
12181
|
+
const path = wranglerRepoPath(appDir);
|
|
12182
|
+
if (runGit(repoToplevel(appDir), ["ls-files", "--error-unmatch", "--", path], { allowFail: true }).status !== 0) {
|
|
12183
|
+
throw new Refusal(
|
|
12184
|
+
"Track, commit, and push wrangler.toml before migrating; GitHub must already contain the deployment configuration.",
|
|
12185
|
+
"wrangler_not_tracked"
|
|
12186
|
+
);
|
|
12187
|
+
}
|
|
12188
|
+
}
|
|
12189
|
+
function deployRequired(appDir, migration, envName) {
|
|
12190
|
+
return new Refusal(
|
|
12191
|
+
`Identity cutover committed; deploy once to verify ${migration.appId} on the existing physical resources.`,
|
|
12192
|
+
"migration_deploy_required",
|
|
12193
|
+
{
|
|
12194
|
+
actionRequired: true,
|
|
12195
|
+
action: {
|
|
12196
|
+
cwd: appDir,
|
|
12197
|
+
argv: ["deepspace", "deploy", ...envName ? ["--env", envName] : []]
|
|
12198
|
+
},
|
|
12199
|
+
extra: { migration, env: envName ?? null }
|
|
12200
|
+
}
|
|
12201
|
+
);
|
|
12202
|
+
}
|
|
12203
|
+
function reportAlreadyModern(appId, json) {
|
|
12204
|
+
if (!json) console.log(`${appId} already uses an immutable DeepSpace app id.`);
|
|
12205
|
+
}
|
|
12206
|
+
function reportMigration(migration, json) {
|
|
12207
|
+
if (json) return;
|
|
12208
|
+
console.log(`Migration: ${migration.legacyAppId} \u2192 ${migration.appId}`);
|
|
12209
|
+
console.log(`Status: ${migration.status}`);
|
|
12210
|
+
console.log(`Physical resources: ${migration.resourceId}`);
|
|
12211
|
+
console.log(`GitHub: ${migration.repository}`);
|
|
12212
|
+
}
|
|
12213
|
+
|
|
11167
12214
|
// src/cli/cli.ts
|
|
11168
12215
|
var __dirname = dirname10(fileURLToPath3(import.meta.url));
|
|
11169
12216
|
var pkg = JSON.parse(readFileSync25(join23(__dirname, "..", "package.json"), "utf-8"));
|
|
@@ -11216,7 +12263,7 @@ var auth = defineCommand14({
|
|
|
11216
12263
|
var app = defineCommand14({
|
|
11217
12264
|
meta: {
|
|
11218
12265
|
name: "app",
|
|
11219
|
-
description: "The app itself: create, init, list, undeploy, transfer, collaborators, domain, library, usage"
|
|
12266
|
+
description: "The app itself: create, init, list, source, migrate, undeploy, transfer, collaborators, domain, library, usage"
|
|
11220
12267
|
},
|
|
11221
12268
|
subCommands: {
|
|
11222
12269
|
create: create2,
|
|
@@ -11227,7 +12274,9 @@ var app = defineCommand14({
|
|
|
11227
12274
|
collaborators: collaborators_default,
|
|
11228
12275
|
domain: domain_default,
|
|
11229
12276
|
library: library_default,
|
|
11230
|
-
usage: usage_default
|
|
12277
|
+
usage: usage_default,
|
|
12278
|
+
source: source_default,
|
|
12279
|
+
migrate: migrate_default
|
|
11231
12280
|
}
|
|
11232
12281
|
});
|
|
11233
12282
|
var devGroup = defineCommand14({
|
|
@@ -11278,55 +12327,21 @@ var main = defineCommand14({
|
|
|
11278
12327
|
// command. Citty's default behavior when no subcommand matches is to print
|
|
11279
12328
|
// help, which is what we want for `deepspace` with no args.
|
|
11280
12329
|
});
|
|
11281
|
-
function
|
|
11282
|
-
|
|
11283
|
-
i,
|
|
11284
|
-
...new Array(b.length).fill(0)
|
|
11285
|
-
]);
|
|
11286
|
-
for (let j = 1; j <= b.length; j++) dp[0][j] = j;
|
|
11287
|
-
for (let i = 1; i <= a.length; i++) {
|
|
11288
|
-
for (let j = 1; j <= b.length; j++) {
|
|
11289
|
-
dp[i][j] = Math.min(
|
|
11290
|
-
dp[i - 1][j] + 1,
|
|
11291
|
-
dp[i][j - 1] + 1,
|
|
11292
|
-
dp[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
|
|
11293
|
-
);
|
|
11294
|
-
}
|
|
11295
|
-
}
|
|
11296
|
-
return dp[a.length][b.length];
|
|
12330
|
+
function commandTree() {
|
|
12331
|
+
return main.subCommands ?? {};
|
|
11297
12332
|
}
|
|
11298
|
-
function
|
|
11299
|
-
|
|
11300
|
-
let bestDist = 4;
|
|
11301
|
-
for (const c of candidates) {
|
|
11302
|
-
const d = editDistance2(input.toLowerCase(), c);
|
|
11303
|
-
if (d < bestDist) {
|
|
11304
|
-
best = c;
|
|
11305
|
-
bestDist = d;
|
|
11306
|
-
}
|
|
11307
|
-
}
|
|
11308
|
-
return best;
|
|
12333
|
+
function unknownCommandMessage(unknown) {
|
|
12334
|
+
return `Unknown command: ${unknown.attemptedPath.join(" ")} \u2014 did you mean \`${unknown.suggestion.join(" ")}\`?`;
|
|
11309
12335
|
}
|
|
11310
12336
|
function assertKnownCommandPath(argv) {
|
|
11311
|
-
|
|
11312
|
-
|
|
11313
|
-
|
|
11314
|
-
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
`Unknown command: ${path} ${tok}${hint ? ` \u2014 did you mean \`${path} ${hint}\`?` : ""}
|
|
11320
|
-
Run \`${path} --help\` for the command list.`
|
|
11321
|
-
);
|
|
11322
|
-
if (hint) {
|
|
11323
|
-
printAction({ cwd: process.cwd(), argv: [...path.split(" "), hint] });
|
|
11324
|
-
}
|
|
11325
|
-
process.exit(1);
|
|
11326
|
-
}
|
|
11327
|
-
path += ` ${tok}`;
|
|
11328
|
-
table = entry.subCommands;
|
|
11329
|
-
}
|
|
12337
|
+
const unknown = findUnknownCommand(argv, commandTree());
|
|
12338
|
+
if (!unknown) return;
|
|
12339
|
+
console.error(
|
|
12340
|
+
`${unknownCommandMessage(unknown)}
|
|
12341
|
+
Run \`${unknown.helpPath.join(" ")} --help\` for the command list.`
|
|
12342
|
+
);
|
|
12343
|
+
printAction({ cwd: process.cwd(), argv: unknown.suggestion });
|
|
12344
|
+
process.exit(1);
|
|
11330
12345
|
}
|
|
11331
12346
|
var rawArgs = process.argv.slice(2);
|
|
11332
12347
|
var wantsHelp = rawArgs.some((arg) => arg === "--help" || arg === "-h");
|
|
@@ -11340,15 +12355,14 @@ if (DEEPSPACE_ENV === "invalid") {
|
|
|
11340
12355
|
process.exit(1);
|
|
11341
12356
|
}
|
|
11342
12357
|
if (rawArgs.includes("--json") && !wantsHelp) {
|
|
11343
|
-
const
|
|
11344
|
-
if (
|
|
11345
|
-
const hint = closestCommand(first, Object.keys(main.subCommands ?? {}));
|
|
12358
|
+
const unknown = findUnknownCommand(rawArgs, commandTree());
|
|
12359
|
+
if (unknown) {
|
|
11346
12360
|
console.log(
|
|
11347
12361
|
JSON.stringify({
|
|
11348
12362
|
ok: false,
|
|
11349
12363
|
code: "unknown_command",
|
|
11350
|
-
error:
|
|
11351
|
-
|
|
12364
|
+
error: unknownCommandMessage(unknown),
|
|
12365
|
+
action: { cwd: process.cwd(), argv: unknown.suggestion }
|
|
11352
12366
|
})
|
|
11353
12367
|
);
|
|
11354
12368
|
process.exit(1);
|
|
@@ -11366,7 +12380,7 @@ if (rawArgs.includes("--json") && !wantsHelp) {
|
|
|
11366
12380
|
process.exit(1);
|
|
11367
12381
|
});
|
|
11368
12382
|
} else {
|
|
11369
|
-
|
|
12383
|
+
assertKnownCommandPath(rawArgs);
|
|
11370
12384
|
runMain(wrapCommandErrors(main));
|
|
11371
12385
|
}
|
|
11372
12386
|
//# sourceMappingURL=cli.js.map
|