deepspace 0.24.1 → 0.25.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 +11 -0
- package/dist/{chunk-6RQ4GDEH.js → chunk-2GUGZLAX.js} +2 -2
- package/dist/{chunk-6RQ4GDEH.js.map → chunk-2GUGZLAX.js.map} +1 -1
- package/dist/cli.js +1359 -783
- package/dist/cli.js.map +1 -1
- package/dist/documentation-client-core.js +1 -1
- package/dist/documentation-default-renderer.cjs +1 -1
- package/dist/documentation-react.js +1 -1
- package/dist/documentation-runtime.js +1 -1
- package/dist/documentation-server-core.js +1 -1
- package/dist/documentation.js +1 -1
- package/dist/documentation.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -8,12 +8,43 @@ import {
|
|
|
8
8
|
renderUsage
|
|
9
9
|
} from "citty";
|
|
10
10
|
import { sync as spawnSync8 } from "cross-spawn";
|
|
11
|
-
import { readFileSync as readFileSync27, existsSync as
|
|
11
|
+
import { readFileSync as readFileSync27, existsSync as existsSync30 } from "fs";
|
|
12
12
|
import { dirname as dirname13, join as join29 } from "path";
|
|
13
13
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14
14
|
|
|
15
15
|
// src/cli/lib/spinner.ts
|
|
16
16
|
import * as p from "@clack/prompts";
|
|
17
|
+
|
|
18
|
+
// src/cli/lib/cli-format.ts
|
|
19
|
+
var ANSI_RE = /\[[0-9;]*m/g;
|
|
20
|
+
function stripAnsi(s) {
|
|
21
|
+
return s.replace(ANSI_RE, "");
|
|
22
|
+
}
|
|
23
|
+
function forTerminal(s) {
|
|
24
|
+
return process.stdout.isTTY ? s : stripAnsi(s);
|
|
25
|
+
}
|
|
26
|
+
function shQuote(s) {
|
|
27
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
28
|
+
}
|
|
29
|
+
function humanCommand(argv) {
|
|
30
|
+
return argv.map((arg) => /^[A-Za-z0-9_./=:@-]+$/.test(arg) ? arg : shQuote(arg)).join(" ");
|
|
31
|
+
}
|
|
32
|
+
function displayText(value) {
|
|
33
|
+
return value.replace(/[\u0000-\u001f\u007f-\u009f]/g, (ch) => {
|
|
34
|
+
if (ch === " ") return " ";
|
|
35
|
+
const code = ch.codePointAt(0);
|
|
36
|
+
return `\\x${code.toString(16).padStart(2, "0")}`;
|
|
37
|
+
}).replace(/[\p{Cf}\p{Zl}\p{Zp}]/gu, (ch) => {
|
|
38
|
+
if (ch === "\u200C" || ch === "\u200D") return ch;
|
|
39
|
+
const code = ch.codePointAt(0);
|
|
40
|
+
return `\\u{${code.toString(16)}}`;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function displayLines(value) {
|
|
44
|
+
return value.split("\n").map((line) => displayText(line)).join("\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/cli/lib/spinner.ts
|
|
17
48
|
var activeSpinner = null;
|
|
18
49
|
function createSpinner() {
|
|
19
50
|
const impl = process.stdout.isTTY ? p.spinner() : {
|
|
@@ -30,13 +61,13 @@ function createSpinner() {
|
|
|
30
61
|
const wrapper = {
|
|
31
62
|
start: (msg) => {
|
|
32
63
|
activeSpinner = wrapper;
|
|
33
|
-
impl.start(msg);
|
|
64
|
+
impl.start(msg === void 0 ? msg : displayLines(msg));
|
|
34
65
|
},
|
|
35
66
|
stop: (msg) => {
|
|
36
|
-
impl.stop(msg);
|
|
67
|
+
impl.stop(msg === void 0 ? msg : displayLines(msg));
|
|
37
68
|
if (activeSpinner === wrapper) activeSpinner = null;
|
|
38
69
|
},
|
|
39
|
-
message: (msg) => impl.message(msg)
|
|
70
|
+
message: (msg) => impl.message(msg === void 0 ? msg : displayLines(msg))
|
|
40
71
|
};
|
|
41
72
|
return wrapper;
|
|
42
73
|
}
|
|
@@ -48,23 +79,6 @@ function stopActiveSpinner() {
|
|
|
48
79
|
import * as p2 from "@clack/prompts";
|
|
49
80
|
import { readFileSync, realpathSync } from "fs";
|
|
50
81
|
import { dirname, isAbsolute, resolve } from "path";
|
|
51
|
-
|
|
52
|
-
// src/cli/lib/cli-format.ts
|
|
53
|
-
var ANSI_RE = /\[[0-9;]*m/g;
|
|
54
|
-
function stripAnsi(s) {
|
|
55
|
-
return s.replace(ANSI_RE, "");
|
|
56
|
-
}
|
|
57
|
-
function forTerminal(s) {
|
|
58
|
-
return process.stdout.isTTY ? s : stripAnsi(s);
|
|
59
|
-
}
|
|
60
|
-
function shQuote(s) {
|
|
61
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
62
|
-
}
|
|
63
|
-
function humanCommand(argv) {
|
|
64
|
-
return argv.map((arg) => /^[A-Za-z0-9_./=:@-]+$/.test(arg) ? arg : shQuote(arg)).join(" ");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// src/cli/lib/output.ts
|
|
68
82
|
function resolveActionArgv(argv, cwd) {
|
|
69
83
|
if (argv[0] !== "deepspace") return argv;
|
|
70
84
|
const entry = (cwd ? installedCliEntry(cwd) : null) ?? cliEntryPath();
|
|
@@ -122,9 +136,10 @@ function withSlug(msg, code) {
|
|
|
122
136
|
return msg.trimEnd().endsWith(tag) ? msg : `${msg.trimEnd()} ${tag}`;
|
|
123
137
|
}
|
|
124
138
|
function displayArg(arg) {
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
return
|
|
139
|
+
const safe = displayText(arg);
|
|
140
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(safe)) return safe;
|
|
141
|
+
if (process.platform === "win32") return `"${safe.replaceAll('"', '""')}"`;
|
|
142
|
+
return shQuote(safe);
|
|
128
143
|
}
|
|
129
144
|
function printAction(action) {
|
|
130
145
|
assertExecutableAction(action);
|
|
@@ -171,7 +186,7 @@ async function fetchWithTransientRetry(url, makeInit, {
|
|
|
171
186
|
// src/cli/lib/api.ts
|
|
172
187
|
var ApiError = class extends Error {
|
|
173
188
|
constructor(message, status3, code, apiPath, details) {
|
|
174
|
-
super(message);
|
|
189
|
+
super(displayLines(message));
|
|
175
190
|
this.status = status3;
|
|
176
191
|
this.code = code;
|
|
177
192
|
this.apiPath = apiPath;
|
|
@@ -232,7 +247,7 @@ async function apiFetch(baseUrl, token, path, init, isRetry = false) {
|
|
|
232
247
|
}
|
|
233
248
|
} catch {
|
|
234
249
|
}
|
|
235
|
-
if (!code && res.status === 404 &&
|
|
250
|
+
if (!code && res.status === 404 && msg.trim() === "Not found") {
|
|
236
251
|
code = "unrecognized_service";
|
|
237
252
|
msg = `The deploy service at ${baseUrl} doesn't recognize this request \u2014 check DEEPSPACE_DEPLOY_URL (is this the service the app lives on?); if it is, the server may be older than this CLI.`;
|
|
238
253
|
}
|
|
@@ -286,17 +301,23 @@ function isTransientFailure(error) {
|
|
|
286
301
|
// src/cli/lib/cli-errors.ts
|
|
287
302
|
var InputError = class extends Error {
|
|
288
303
|
constructor(message, code) {
|
|
289
|
-
super(message);
|
|
304
|
+
super(displayLines(message));
|
|
290
305
|
this.code = code;
|
|
291
306
|
}
|
|
292
307
|
};
|
|
308
|
+
function isMissingCwdError(err) {
|
|
309
|
+
if (!(err instanceof Error)) return false;
|
|
310
|
+
const errno = err;
|
|
311
|
+
if (errno.code !== "ENOENT") return false;
|
|
312
|
+
return errno.syscall === "uv_cwd";
|
|
313
|
+
}
|
|
293
314
|
var Refusal = class extends Error {
|
|
294
315
|
code;
|
|
295
316
|
action;
|
|
296
317
|
actionRequired;
|
|
297
318
|
extra;
|
|
298
319
|
constructor(message, code, opts = {}) {
|
|
299
|
-
super(message);
|
|
320
|
+
super(displayLines(message));
|
|
300
321
|
this.name = "Refusal";
|
|
301
322
|
this.code = code;
|
|
302
323
|
this.action = opts.action ? executableAction(opts.action) : void 0;
|
|
@@ -338,6 +359,7 @@ var CliExit = class extends Error {
|
|
|
338
359
|
};
|
|
339
360
|
function errorCode(err) {
|
|
340
361
|
if (err instanceof ApiError || err instanceof InputError || err instanceof Refusal) return err.code;
|
|
362
|
+
if (isMissingCwdError(err)) return "worktree_missing";
|
|
341
363
|
if (err instanceof Error && err.name === "GitError") return err.code ?? "git_error";
|
|
342
364
|
if (err instanceof Error && err.name === "WranglerConfigError")
|
|
343
365
|
return err.code ?? "invalid_config";
|
|
@@ -356,6 +378,10 @@ var API_ERROR_HINTS = {
|
|
|
356
378
|
invite_email_failed: "The invite itself was not created and the charge was voided \u2014 nothing to undo. Retrying helps only if the send was transient; check the address is real and deliverable first. An email is needed ONLY for someone with no DeepSpace account yet: an existing user is added instantly, so having them sign in once and re-running this skips email entirely. If a retry now reports `already_invited` with no email sent, cancel the stuck invite (`deepspace app collaborators cancel <email>`) and invite again."
|
|
357
379
|
};
|
|
358
380
|
function formatCliError(err) {
|
|
381
|
+
if (isMissingCwdError(err)) {
|
|
382
|
+
const pwd = process.env.PWD;
|
|
383
|
+
return `The directory this command was run from no longer exists${pwd ? ` (${pwd})` : ""} \u2014 it was deleted or moved while the shell was still in it. Move somewhere that exists (\`cd\` to the main checkout). If it was a workspace worktree that a land, merge, or drop removed, \`deepspace workspace attach <ws_\u2026>\` re-materializes it there.`;
|
|
384
|
+
}
|
|
359
385
|
const message = err instanceof Error ? err.message : String(err);
|
|
360
386
|
const slug = errorCode(err);
|
|
361
387
|
const hint = slug ? API_ERROR_HINTS[slug] : void 0;
|
|
@@ -363,11 +389,11 @@ function formatCliError(err) {
|
|
|
363
389
|
${hint}` : message;
|
|
364
390
|
}
|
|
365
391
|
function renderCliError(err) {
|
|
392
|
+
stopActiveSpinner();
|
|
366
393
|
if (err instanceof CliExit) {
|
|
367
394
|
process.exitCode = err.exitCode;
|
|
368
395
|
return;
|
|
369
396
|
}
|
|
370
|
-
stopActiveSpinner();
|
|
371
397
|
const message = formatCliError(err);
|
|
372
398
|
const slug = errorCode(err);
|
|
373
399
|
if (process.argv.includes("--json")) {
|
|
@@ -529,6 +555,9 @@ function editDistance(a, b) {
|
|
|
529
555
|
dp[i][j - 1] + 1,
|
|
530
556
|
dp[i - 1][j - 1] + (left[i - 1] === right[j - 1] ? 0 : 1)
|
|
531
557
|
);
|
|
558
|
+
if (i > 1 && j > 1 && left[i - 1] === right[j - 2] && left[i - 2] === right[j - 1]) {
|
|
559
|
+
dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + 1);
|
|
560
|
+
}
|
|
532
561
|
}
|
|
533
562
|
}
|
|
534
563
|
return dp[left.length][right.length];
|
|
@@ -548,15 +577,21 @@ function closestCommandPath(input, table) {
|
|
|
548
577
|
);
|
|
549
578
|
return ranked[0]?.path ?? null;
|
|
550
579
|
}
|
|
580
|
+
function takeLeadingPositionals(rest) {
|
|
581
|
+
const stop = rest.findIndex((token) => token.startsWith("-"));
|
|
582
|
+
return stop === -1 ? rest : rest.slice(0, stop);
|
|
583
|
+
}
|
|
551
584
|
function findUnknownCommand(argv, rootCommands) {
|
|
552
585
|
let table = rootCommands;
|
|
553
586
|
const accepted = [];
|
|
554
|
-
for (const token of argv) {
|
|
587
|
+
for (const [index, token] of argv.entries()) {
|
|
555
588
|
if (!table || token.startsWith("-")) return null;
|
|
556
589
|
const command = table[token];
|
|
557
590
|
if (!command) {
|
|
591
|
+
const exactAll = commandPaths(table).filter((path) => path.at(-1) === token);
|
|
592
|
+
const exactHit = exactAll.length === 1 ? exactAll[0] : null;
|
|
558
593
|
const split = splitTokenPath(token, table);
|
|
559
|
-
const relativeSuggestion = split ?? closestCommandPath(token, table);
|
|
594
|
+
const relativeSuggestion = exactHit ?? split ?? closestCommandPath(token, table);
|
|
560
595
|
if (!relativeSuggestion) return null;
|
|
561
596
|
const guessed = relativeSuggestion.at(-1) ?? "";
|
|
562
597
|
return {
|
|
@@ -564,6 +599,11 @@ function findUnknownCommand(argv, rootCommands) {
|
|
|
564
599
|
helpPath: ["deepspace", ...accepted],
|
|
565
600
|
suggestion: ["deepspace", ...accepted, ...relativeSuggestion],
|
|
566
601
|
executable: split !== null && !DESTRUCTIVE_VERBS.has(guessed),
|
|
602
|
+
// POSITIONALS ONLY. The remainder exists so the corrected verb keeps
|
|
603
|
+
// the arguments it needs; carrying flags too meant `auth logn --email
|
|
604
|
+
// a@b --password hunter2` echoed the password into `action.argv`, and
|
|
605
|
+
// agents persist envelopes. Cut at the first `-` token.
|
|
606
|
+
remainder: takeLeadingPositionals(argv.slice(index + 1)),
|
|
567
607
|
...split ? { quotedToken: true } : {}
|
|
568
608
|
};
|
|
569
609
|
}
|
|
@@ -1352,6 +1392,20 @@ function hasWranglerConfig(appDir) {
|
|
|
1352
1392
|
}
|
|
1353
1393
|
|
|
1354
1394
|
// src/cli/lib/app-identity.ts
|
|
1395
|
+
function declaredAppIds(cwd = process.cwd()) {
|
|
1396
|
+
const appDir = resolve2(cwd);
|
|
1397
|
+
if (!hasWranglerConfig(appDir)) return /* @__PURE__ */ new Set();
|
|
1398
|
+
let cfg;
|
|
1399
|
+
try {
|
|
1400
|
+
cfg = readWranglerConfig(appDir);
|
|
1401
|
+
} catch {
|
|
1402
|
+
return /* @__PURE__ */ new Set();
|
|
1403
|
+
}
|
|
1404
|
+
const sections = [cfg.vars, ...Object.values(cfg.env ?? {}).map((env) => env?.vars)];
|
|
1405
|
+
return new Set(
|
|
1406
|
+
sections.map((vars) => vars?.DEEPSPACE_APP_ID).filter((id) => typeof id === "string" && APP_ID_RE.test(id))
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1355
1409
|
var APP_ID_PLACEHOLDER = "__APP_ID__";
|
|
1356
1410
|
function readAppId(cwd = process.cwd(), wranglerEnv) {
|
|
1357
1411
|
const appDir = resolve2(cwd);
|
|
@@ -1437,7 +1491,7 @@ function findChildApps(dir) {
|
|
|
1437
1491
|
|
|
1438
1492
|
// src/cli/lib/launch-config.ts
|
|
1439
1493
|
import {
|
|
1440
|
-
existsSync as
|
|
1494
|
+
existsSync as existsSync8,
|
|
1441
1495
|
readFileSync as readFileSync7,
|
|
1442
1496
|
realpathSync as realpathSync2,
|
|
1443
1497
|
writeFileSync as writeFileSync4,
|
|
@@ -1448,16 +1502,17 @@ import {
|
|
|
1448
1502
|
import { join as join6, resolve as resolve5, dirname as dirname5, basename, isAbsolute as isAbsolute2, sep } from "path";
|
|
1449
1503
|
|
|
1450
1504
|
// src/cli/lib/git/repository.ts
|
|
1451
|
-
import { appendFileSync, existsSync as
|
|
1505
|
+
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
|
|
1452
1506
|
import { dirname as dirname4, join as join5, resolve as resolve4 } from "path";
|
|
1453
1507
|
|
|
1454
1508
|
// src/cli/lib/git/process.ts
|
|
1455
1509
|
import { sync as spawnSync } from "cross-spawn";
|
|
1510
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1456
1511
|
var MAX_GIT_BUFFER = 512 * 1024 * 1024;
|
|
1457
1512
|
var GIT_TIMEOUT_MS = 3e5;
|
|
1458
1513
|
var GitError = class extends Error {
|
|
1459
1514
|
constructor(message, code) {
|
|
1460
|
-
super(message);
|
|
1515
|
+
super(displayLines(message));
|
|
1461
1516
|
this.code = code;
|
|
1462
1517
|
this.name = "GitError";
|
|
1463
1518
|
}
|
|
@@ -1489,7 +1544,13 @@ function runGit(cwd, args, opts = {}) {
|
|
|
1489
1544
|
}
|
|
1490
1545
|
if (result.error) {
|
|
1491
1546
|
const code = result.error.code;
|
|
1492
|
-
if (code === "ENOENT") {
|
|
1547
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
1548
|
+
if (code === "ENOTDIR" || !existsSync6(cwd)) {
|
|
1549
|
+
throw new GitError(
|
|
1550
|
+
`The directory this command runs in is not there: ${cwd}. If it was a workspace worktree, prune it (\`git worktree prune\`) and re-attach.`,
|
|
1551
|
+
"worktree_missing"
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1493
1554
|
throw new GitError(
|
|
1494
1555
|
"git is not installed or not on PATH \u2014 install git and retry.",
|
|
1495
1556
|
"git_not_installed"
|
|
@@ -1583,7 +1644,7 @@ function assertNoOperationInProgress(cwd) {
|
|
|
1583
1644
|
["REVERT_HEAD", "revert", "git revert --continue", "git revert --abort"],
|
|
1584
1645
|
["rebase-merge", "rebase", "git rebase --continue", "git rebase --abort"],
|
|
1585
1646
|
["rebase-apply", "rebase", "git rebase --continue", "git rebase --abort"]
|
|
1586
|
-
].find(([marker]) =>
|
|
1647
|
+
].find(([marker]) => existsSync7(join5(resolve4(cwd, gitDir), marker)));
|
|
1587
1648
|
if (!inProgress) return;
|
|
1588
1649
|
const [, kind, finish, abort] = inProgress;
|
|
1589
1650
|
throw new InputError(
|
|
@@ -1594,6 +1655,18 @@ function assertNoOperationInProgress(cwd) {
|
|
|
1594
1655
|
function repoToplevel(cwd) {
|
|
1595
1656
|
return gitLine(cwd, ["rev-parse", "--show-toplevel"]);
|
|
1596
1657
|
}
|
|
1658
|
+
function interruptedGitOperation(cwd) {
|
|
1659
|
+
const gitDir = runGit(cwd, ["rev-parse", "--git-path", "HEAD"], { allowFail: true });
|
|
1660
|
+
if (gitDir.status !== 0) return null;
|
|
1661
|
+
const dir = dirname4(resolve4(cwd, gitDir.stdout.toString("utf-8").trim()));
|
|
1662
|
+
if (existsSync7(join5(dir, "rebase-apply", "applying"))) return "am";
|
|
1663
|
+
if (existsSync7(join5(dir, "rebase-merge")) || existsSync7(join5(dir, "rebase-apply"))) return "rebase";
|
|
1664
|
+
if (existsSync7(join5(dir, "MERGE_HEAD"))) return "merge";
|
|
1665
|
+
if (existsSync7(join5(dir, "CHERRY_PICK_HEAD"))) return "cherry-pick";
|
|
1666
|
+
if (existsSync7(join5(dir, "REVERT_HEAD"))) return "revert";
|
|
1667
|
+
if (existsSync7(join5(dir, "BISECT_LOG"))) return "bisect";
|
|
1668
|
+
return null;
|
|
1669
|
+
}
|
|
1597
1670
|
function currentBranch(cwd) {
|
|
1598
1671
|
const result = runGit(cwd, ["symbolic-ref", "--short", "-q", "HEAD"], { allowFail: true });
|
|
1599
1672
|
const name = result.stdout.toString("utf-8").trim();
|
|
@@ -1609,6 +1682,9 @@ function resolveCommit(cwd, ref) {
|
|
|
1609
1682
|
function isWorkTreeClean(cwd) {
|
|
1610
1683
|
return gitLine(cwd, ["status", "--porcelain"]) === "";
|
|
1611
1684
|
}
|
|
1685
|
+
function hasUnmergedEntries(cwd) {
|
|
1686
|
+
return gitLine(cwd, ["ls-files", "-u"]) !== "";
|
|
1687
|
+
}
|
|
1612
1688
|
function updateRef(cwd, ref, oid) {
|
|
1613
1689
|
runGit(cwd, ["update-ref", ref, oid]);
|
|
1614
1690
|
}
|
|
@@ -1665,7 +1741,7 @@ function listWorktrees(cwd) {
|
|
|
1665
1741
|
current.branch = ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref;
|
|
1666
1742
|
}
|
|
1667
1743
|
}
|
|
1668
|
-
return worktrees;
|
|
1744
|
+
return worktrees.filter((worktree) => existsSync7(worktree.path));
|
|
1669
1745
|
}
|
|
1670
1746
|
function ensureLocalExclude(cwd, pattern) {
|
|
1671
1747
|
const commonDir = resolve4(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
|
|
@@ -1704,7 +1780,7 @@ function writeLaunchFile(launchPath, config) {
|
|
|
1704
1780
|
}
|
|
1705
1781
|
function readLaunchFile(launchPath) {
|
|
1706
1782
|
const empty = { version: "0.0.1", configurations: [] };
|
|
1707
|
-
if (!
|
|
1783
|
+
if (!existsSync8(launchPath)) return empty;
|
|
1708
1784
|
const raw = readFileSync7(launchPath, "utf-8").trim();
|
|
1709
1785
|
if (!raw) return empty;
|
|
1710
1786
|
const parsed = JSON.parse(raw);
|
|
@@ -1815,13 +1891,13 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
|
|
|
1815
1891
|
const worktreesRoot = join6(mainRepoRoot, ".claude", "worktrees") + sep;
|
|
1816
1892
|
config.configurations = config.configurations.filter((c) => {
|
|
1817
1893
|
if (c?.name === entryName) return false;
|
|
1818
|
-
const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute2(c.cwd) && c.cwd.startsWith(worktreesRoot) && !
|
|
1894
|
+
const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute2(c.cwd) && c.cwd.startsWith(worktreesRoot) && !existsSync8(c.cwd);
|
|
1819
1895
|
return !isStaleWorktreeEntry;
|
|
1820
1896
|
});
|
|
1821
1897
|
let port = opts.port;
|
|
1822
1898
|
if (opts.probePort) {
|
|
1823
1899
|
const taken = new Set(
|
|
1824
|
-
config.configurations.map((c) => Number(c?.port)).filter((
|
|
1900
|
+
config.configurations.map((c) => Number(c?.port)).filter((p36) => Number.isInteger(p36) && p36 > 0)
|
|
1825
1901
|
);
|
|
1826
1902
|
for (let i = 0; i < WORKTREE_PORT_RANGE && taken.has(port); i++) {
|
|
1827
1903
|
port = WORKTREE_PORT_MIN + (port - WORKTREE_PORT_MIN + 1) % WORKTREE_PORT_RANGE;
|
|
@@ -1865,6 +1941,29 @@ function resolveWorktreePort(appDir) {
|
|
|
1865
1941
|
// src/cli/lib/repo-api.ts
|
|
1866
1942
|
import { randomUUID } from "crypto";
|
|
1867
1943
|
|
|
1944
|
+
// src/cli/lib/workspace-id.ts
|
|
1945
|
+
var ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
|
|
1946
|
+
var WORKSPACE_ID_RE = /^ws_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
1947
|
+
function isWorkspaceId(id) {
|
|
1948
|
+
return WORKSPACE_ID_RE.test(id);
|
|
1949
|
+
}
|
|
1950
|
+
function workspaceBranchName(workspaceId) {
|
|
1951
|
+
return `ws/${workspaceId.replace(/^ws_/, "").toLowerCase()}`;
|
|
1952
|
+
}
|
|
1953
|
+
function workspaceIdFromBranch(branch) {
|
|
1954
|
+
if (!branch || !branch.startsWith("ws/")) return null;
|
|
1955
|
+
const ulid = branch.slice(3);
|
|
1956
|
+
if (!ULID_RE.test(ulid)) return null;
|
|
1957
|
+
return `ws_${ulid.toUpperCase()}`;
|
|
1958
|
+
}
|
|
1959
|
+
function isSelectedWorkspaceCheckout(branch, workspaceId) {
|
|
1960
|
+
return workspaceIdFromBranch(branch) === workspaceId;
|
|
1961
|
+
}
|
|
1962
|
+
function resolveWorkspaceWorktree(worktrees, workspaceId) {
|
|
1963
|
+
const branch = workspaceBranchName(workspaceId);
|
|
1964
|
+
return worktrees.find((worktree) => worktree.branch === branch)?.path ?? null;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1868
1967
|
// src/cli/lib/source-api.ts
|
|
1869
1968
|
function githubSourceRefusal(appId, repository) {
|
|
1870
1969
|
return new Refusal(
|
|
@@ -1898,6 +1997,10 @@ function setAppSource(deployUrl, token, appId, input) {
|
|
|
1898
1997
|
}
|
|
1899
1998
|
|
|
1900
1999
|
// src/cli/lib/repo-api.ts
|
|
2000
|
+
function isWorkspaceRef(ref) {
|
|
2001
|
+
const prefix = "refs/deepspace/ws/";
|
|
2002
|
+
return ref.startsWith(prefix) && isWorkspaceId(ref.slice(prefix.length));
|
|
2003
|
+
}
|
|
1901
2004
|
function releaseSourceLabel(release2) {
|
|
1902
2005
|
if (release2.commitOid) return `commit ${release2.commitOid.slice(0, 10)}`;
|
|
1903
2006
|
if (release2.source?.provider === "github") return `GitHub \xB7 ${release2.source.repository}`;
|
|
@@ -1937,12 +2040,22 @@ function repoApi(deployUrl, token, appId) {
|
|
|
1937
2040
|
"invalid_response",
|
|
1938
2041
|
`${base}${path}`
|
|
1939
2042
|
);
|
|
2043
|
+
const normalizeWorkspace = (view, path) => {
|
|
2044
|
+
if (view === null || typeof view !== "object") return;
|
|
2045
|
+
const workspace = view.workspace;
|
|
2046
|
+
if (workspace === null || typeof workspace !== "object") return;
|
|
2047
|
+
const fields = workspace;
|
|
2048
|
+
if (typeof fields.ref !== "string" || !isWorkspaceRef(fields.ref)) throw shapeError(path);
|
|
2049
|
+
if (typeof fields.task === "string") fields.task = displayText(fields.task);
|
|
2050
|
+
};
|
|
1940
2051
|
const assertShape = (r, spec, path) => {
|
|
1941
2052
|
if (r === null || typeof r !== "object") throw shapeError(path);
|
|
1942
2053
|
const obj = r;
|
|
1943
2054
|
for (const [key, kind] of Object.entries(spec)) {
|
|
1944
2055
|
if (!KIND[kind](obj[key])) throw shapeError(path);
|
|
1945
2056
|
}
|
|
2057
|
+
normalizeWorkspace(obj.view, path);
|
|
2058
|
+
if (Array.isArray(obj.views)) for (const view of obj.views) normalizeWorkspace(view, path);
|
|
1946
2059
|
return r;
|
|
1947
2060
|
};
|
|
1948
2061
|
const getShape = async (path, spec) => assertShape(await call(path), spec, path);
|
|
@@ -1964,6 +2077,9 @@ function repoApi(deployUrl, token, appId) {
|
|
|
1964
2077
|
createWorkspace(args) {
|
|
1965
2078
|
return postShape("/workspaces", args, { view: "object" });
|
|
1966
2079
|
},
|
|
2080
|
+
/** `truncated` ⇒ the server had more rows than `limit` and stopped. Without
|
|
2081
|
+
* it a capped page is indistinguishable from the whole set, so a caller
|
|
2082
|
+
* reading "3 workspaces" cannot tell that a fourth exists. */
|
|
1967
2083
|
listWorkspaces(opts) {
|
|
1968
2084
|
const params = new URLSearchParams();
|
|
1969
2085
|
if (opts?.all) params.set("all", "1");
|
|
@@ -2241,11 +2357,11 @@ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {})
|
|
|
2241
2357
|
}
|
|
2242
2358
|
|
|
2243
2359
|
// src/cli/lib/install-status.ts
|
|
2244
|
-
import { existsSync as
|
|
2360
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9, realpathSync as realpathSync3 } from "fs";
|
|
2245
2361
|
import { dirname as dirname6, join as join8, resolve as resolve6 } from "path";
|
|
2246
2362
|
|
|
2247
2363
|
// src/cli/lib/package-manager.ts
|
|
2248
|
-
import { existsSync as
|
|
2364
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
2249
2365
|
import { join as join7 } from "path";
|
|
2250
2366
|
function detectPackageManager(appDir) {
|
|
2251
2367
|
try {
|
|
@@ -2254,9 +2370,9 @@ function detectPackageManager(appDir) {
|
|
|
2254
2370
|
if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") return name;
|
|
2255
2371
|
} catch {
|
|
2256
2372
|
}
|
|
2257
|
-
if (
|
|
2258
|
-
if (
|
|
2259
|
-
if (
|
|
2373
|
+
if (existsSync9(join7(appDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
2374
|
+
if (existsSync9(join7(appDir, "yarn.lock"))) return "yarn";
|
|
2375
|
+
if (existsSync9(join7(appDir, "bun.lock")) || existsSync9(join7(appDir, "bun.lockb"))) return "bun";
|
|
2260
2376
|
return "npm";
|
|
2261
2377
|
}
|
|
2262
2378
|
function installCommand(appDir) {
|
|
@@ -2267,9 +2383,9 @@ function installCommand(appDir) {
|
|
|
2267
2383
|
function installState(appDir) {
|
|
2268
2384
|
if (resolvesDeepspace(appDir)) return "ready";
|
|
2269
2385
|
const sentinelDir = join8(appDir, ".deepspace");
|
|
2270
|
-
if (
|
|
2271
|
-
if (
|
|
2272
|
-
if (!
|
|
2386
|
+
if (existsSync10(join8(sentinelDir, "install.err"))) return "failed";
|
|
2387
|
+
if (existsSync10(join8(sentinelDir, "install.started"))) {
|
|
2388
|
+
if (!existsSync10(join8(sentinelDir, "install.done")) && installWorkerAlive(sentinelDir)) {
|
|
2273
2389
|
return "installing";
|
|
2274
2390
|
}
|
|
2275
2391
|
return "failed";
|
|
@@ -2286,25 +2402,25 @@ function ensureInstallReady(appDir) {
|
|
|
2286
2402
|
const startedPath = join8(sentinelDir, "install.started");
|
|
2287
2403
|
const donePath = join8(sentinelDir, "install.done");
|
|
2288
2404
|
const logPath = join8(sentinelDir, "install.log");
|
|
2289
|
-
if (
|
|
2405
|
+
if (existsSync10(errPath)) {
|
|
2290
2406
|
const detail = readFileSync9(errPath, "utf-8").trimEnd();
|
|
2291
|
-
const log29 =
|
|
2407
|
+
const log29 = existsSync10(logPath) ? ` Full log: ${logPath}.` : "";
|
|
2292
2408
|
throw new Refusal(
|
|
2293
2409
|
`Dependency install failed: ${detail}.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
|
|
2294
2410
|
"install_failed",
|
|
2295
2411
|
{ action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
|
|
2296
2412
|
);
|
|
2297
2413
|
}
|
|
2298
|
-
if (
|
|
2414
|
+
if (existsSync10(startedPath) && !existsSync10(donePath)) {
|
|
2299
2415
|
if (!installWorkerAlive(sentinelDir)) {
|
|
2300
|
-
const log29 =
|
|
2416
|
+
const log29 = existsSync10(logPath) ? ` See what happened: ${logPath}.` : "";
|
|
2301
2417
|
throw new Refusal(
|
|
2302
2418
|
`The dependency install is no longer running and never finished.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
|
|
2303
2419
|
"install_failed",
|
|
2304
2420
|
{ action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
|
|
2305
2421
|
);
|
|
2306
2422
|
}
|
|
2307
|
-
const tail =
|
|
2423
|
+
const tail = existsSync10(logPath) ? ` Tail progress: ${tailHint(logPath)}.` : "";
|
|
2308
2424
|
throw new Refusal(
|
|
2309
2425
|
`Dependencies are still installing.${tail} Retry once it finishes.`,
|
|
2310
2426
|
"install_in_progress"
|
|
@@ -2326,7 +2442,7 @@ function resolvesDeepspace(fromDir) {
|
|
|
2326
2442
|
} catch {
|
|
2327
2443
|
}
|
|
2328
2444
|
for (; ; ) {
|
|
2329
|
-
if (
|
|
2445
|
+
if (existsSync10(join8(dir, "node_modules", "deepspace", "package.json"))) return true;
|
|
2330
2446
|
if (dir === checkoutRoot) return false;
|
|
2331
2447
|
const parent = dirname6(dir);
|
|
2332
2448
|
if (parent === dir) return false;
|
|
@@ -2342,7 +2458,7 @@ function canonicalPath2(path) {
|
|
|
2342
2458
|
}
|
|
2343
2459
|
function installWorkerAlive(sentinelDir) {
|
|
2344
2460
|
const pidPath = join8(sentinelDir, "install.pid");
|
|
2345
|
-
if (!
|
|
2461
|
+
if (!existsSync10(pidPath)) return false;
|
|
2346
2462
|
return processAlive(Number(readFileSync9(pidPath, "utf-8").trim()));
|
|
2347
2463
|
}
|
|
2348
2464
|
function processAlive(pid) {
|
|
@@ -2357,7 +2473,7 @@ function processAlive(pid) {
|
|
|
2357
2473
|
|
|
2358
2474
|
// src/cli/lib/preflight.ts
|
|
2359
2475
|
import { execFileSync } from "child_process";
|
|
2360
|
-
import { existsSync as
|
|
2476
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2361
2477
|
import * as nodeModule from "module";
|
|
2362
2478
|
import { resolve as resolve7 } from "path";
|
|
2363
2479
|
function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.registerHooks === "function", version = process.versions.node) {
|
|
@@ -2380,7 +2496,7 @@ function preflightWindowsWorkerd(appDir) {
|
|
|
2380
2496
|
"bin",
|
|
2381
2497
|
"workerd.exe"
|
|
2382
2498
|
);
|
|
2383
|
-
if (!
|
|
2499
|
+
if (!existsSync11(workerdExe)) return;
|
|
2384
2500
|
try {
|
|
2385
2501
|
execFileSync(workerdExe, ["--help"], { stdio: "pipe", timeout: 1e4 });
|
|
2386
2502
|
return;
|
|
@@ -2731,28 +2847,34 @@ var CONFIG_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
|
2731
2847
|
var MAX_SECRET_VALUE_BYTES = 32 * 1024;
|
|
2732
2848
|
function validateConfigName(configName) {
|
|
2733
2849
|
if (!CONFIG_NAME_RE.test(configName)) {
|
|
2734
|
-
throw new
|
|
2735
|
-
`Invalid config "${configName}". Use letters, numbers, underscores, or hyphens; start with a letter or number
|
|
2850
|
+
throw new InputError(
|
|
2851
|
+
`Invalid config "${configName}". Use letters, numbers, underscores, or hyphens; start with a letter or number.`,
|
|
2852
|
+
"invalid_config_name"
|
|
2736
2853
|
);
|
|
2737
2854
|
}
|
|
2738
2855
|
return configName;
|
|
2739
2856
|
}
|
|
2740
2857
|
function validateSecretName(name) {
|
|
2741
2858
|
if (!SECRET_NAME_RE.test(name)) {
|
|
2742
|
-
throw new
|
|
2743
|
-
`Invalid secret name "${name}". Use letters, numbers, and underscores; start with a letter or underscore
|
|
2859
|
+
throw new InputError(
|
|
2860
|
+
`Invalid secret name "${name}". Use letters, numbers, and underscores; start with a letter or underscore.`,
|
|
2861
|
+
"invalid_secret_name"
|
|
2744
2862
|
);
|
|
2745
2863
|
}
|
|
2746
2864
|
if (RESERVED_SECRET_NAMES.has(name)) {
|
|
2747
|
-
throw new
|
|
2748
|
-
`"${name}" is managed by the DeepSpace SDK and can't be set as an app secret \u2014 the platform injects it at deploy time
|
|
2865
|
+
throw new InputError(
|
|
2866
|
+
`"${name}" is managed by the DeepSpace SDK and can't be set as an app secret \u2014 the platform injects it at deploy time.`,
|
|
2867
|
+
"reserved_secret_name"
|
|
2749
2868
|
);
|
|
2750
2869
|
}
|
|
2751
2870
|
return name;
|
|
2752
2871
|
}
|
|
2753
2872
|
function validateSecretValue(key, value) {
|
|
2754
2873
|
if (new TextEncoder().encode(value).byteLength > MAX_SECRET_VALUE_BYTES) {
|
|
2755
|
-
throw new
|
|
2874
|
+
throw new InputError(
|
|
2875
|
+
`Secret "${key}" exceeds ${MAX_SECRET_VALUE_BYTES} bytes`,
|
|
2876
|
+
"secret_too_large"
|
|
2877
|
+
);
|
|
2756
2878
|
}
|
|
2757
2879
|
}
|
|
2758
2880
|
function defaultConfigNameForEnv(wranglerEnv) {
|
|
@@ -2835,7 +2957,9 @@ async function pullAppSecretsCache(deployUrl, token, appId, configName) {
|
|
|
2835
2957
|
}
|
|
2836
2958
|
return { appId, configName, values: secrets };
|
|
2837
2959
|
} catch (err) {
|
|
2838
|
-
if (err
|
|
2960
|
+
if (err instanceof ApiError && err.status === 404 && err.code !== "unrecognized_service") {
|
|
2961
|
+
return null;
|
|
2962
|
+
}
|
|
2839
2963
|
throw err;
|
|
2840
2964
|
}
|
|
2841
2965
|
}
|
|
@@ -2857,14 +2981,27 @@ function parseSecretsUpload(content) {
|
|
|
2857
2981
|
const trimmed = content.trim();
|
|
2858
2982
|
if (!trimmed) return {};
|
|
2859
2983
|
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
2860
|
-
|
|
2984
|
+
let parsed;
|
|
2985
|
+
try {
|
|
2986
|
+
parsed = JSON.parse(trimmed);
|
|
2987
|
+
} catch {
|
|
2988
|
+
throw new InputError(
|
|
2989
|
+
"The input starts with { or [ but is not valid JSON. Pass a JSON object of KEY:value strings, or a dotenv file.",
|
|
2990
|
+
"invalid_format"
|
|
2991
|
+
);
|
|
2992
|
+
}
|
|
2861
2993
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2862
|
-
throw new
|
|
2994
|
+
throw new InputError("JSON upload must be an object of KEY=value strings.", "invalid_format");
|
|
2863
2995
|
}
|
|
2864
2996
|
const out2 = {};
|
|
2865
2997
|
for (const [key, value] of Object.entries(parsed)) {
|
|
2866
2998
|
validateSecretName(key);
|
|
2867
|
-
if (typeof value !== "string")
|
|
2999
|
+
if (typeof value !== "string") {
|
|
3000
|
+
throw new InputError(
|
|
3001
|
+
`Secret "${key}" must be a string \u2014 got ${Array.isArray(value) ? "an array" : typeof value}.`,
|
|
3002
|
+
"invalid_format"
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
2868
3005
|
validateSecretValue(key, value);
|
|
2869
3006
|
out2[key] = value;
|
|
2870
3007
|
}
|
|
@@ -2909,8 +3046,9 @@ function parseSecretsUpload(content) {
|
|
|
2909
3046
|
parts.push(lines[i]);
|
|
2910
3047
|
}
|
|
2911
3048
|
if (!closed) {
|
|
2912
|
-
throw new
|
|
2913
|
-
`Unterminated quoted value for "${key}" (opens on line ${startLine}). Close the ${quote === '"' ? "double" : "single"} quote, or escape newlines as \\n on one line
|
|
3049
|
+
throw new InputError(
|
|
3050
|
+
`Unterminated quoted value for "${key}" (opens on line ${startLine}). Close the ${quote === '"' ? "double" : "single"} quote, or escape newlines as \\n on one line.`,
|
|
3051
|
+
"invalid_format"
|
|
2914
3052
|
);
|
|
2915
3053
|
}
|
|
2916
3054
|
value = parts.join("\n");
|
|
@@ -2967,7 +3105,7 @@ async function refreshSecretsCache(deployUrl, token, appId, wranglerEnv, selecte
|
|
|
2967
3105
|
}
|
|
2968
3106
|
|
|
2969
3107
|
// src/cli/lib/schema-lint.ts
|
|
2970
|
-
import { existsSync as
|
|
3108
|
+
import { existsSync as existsSync12 } from "fs";
|
|
2971
3109
|
import { join as join10 } from "path";
|
|
2972
3110
|
import { pathToFileURL } from "url";
|
|
2973
3111
|
|
|
@@ -3001,7 +3139,7 @@ function lintSchema(schema) {
|
|
|
3001
3139
|
const offendingRoles = Object.entries(schema.permissions).filter(([, perms]) => perms.read === true).map(([role]) => role);
|
|
3002
3140
|
if (offendingRoles.length > 0) {
|
|
3003
3141
|
const usesAware = Object.values(schema.permissions).some(
|
|
3004
|
-
(
|
|
3142
|
+
(p36) => typeof p36.read === "string" && VISIBILITY_AWARE.includes(p36.read)
|
|
3005
3143
|
);
|
|
3006
3144
|
if (!usesAware) {
|
|
3007
3145
|
warnings.push(
|
|
@@ -3037,7 +3175,7 @@ function lintSchemaSet(schemas) {
|
|
|
3037
3175
|
const warnings = [];
|
|
3038
3176
|
if (!schemas.some((schema) => schema.name === "team_members")) {
|
|
3039
3177
|
for (const schema of schemas) {
|
|
3040
|
-
const teamRoles = Object.entries(schema.permissions).filter(([,
|
|
3178
|
+
const teamRoles = Object.entries(schema.permissions).filter(([, p36]) => [p36.read, p36.update, p36.delete].includes("team")).map(([role]) => role);
|
|
3041
3179
|
if (teamRoles.length > 0) {
|
|
3042
3180
|
warnings.push(
|
|
3043
3181
|
`[${schema.name}] role(s) ${teamRoles.join(", ")} use the 'team' permission level, but no 'team_members' collection is registered. 'team' resolves membership from team_members (teamId, userId, status), so without it those roles are denied everything \u2014 silently. Register a team_members schema or use another level.`
|
|
@@ -3051,7 +3189,7 @@ function lintSchemaSet(schemas) {
|
|
|
3051
3189
|
// src/cli/lib/schema-lint.ts
|
|
3052
3190
|
async function lintProjectSchemas(appDir) {
|
|
3053
3191
|
const schemasPath = join10(appDir, "src", "schemas.ts");
|
|
3054
|
-
if (!
|
|
3192
|
+
if (!existsSync12(schemasPath)) return null;
|
|
3055
3193
|
let schemas;
|
|
3056
3194
|
try {
|
|
3057
3195
|
const esbuild = await import("esbuild");
|
|
@@ -3678,7 +3816,7 @@ function ensurePlaywright(appDir) {
|
|
|
3678
3816
|
import { readFileSync as readFileSync12 } from "fs";
|
|
3679
3817
|
|
|
3680
3818
|
// src/testing/accounts.ts
|
|
3681
|
-
import { existsSync as
|
|
3819
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync11 } from "fs";
|
|
3682
3820
|
import { homedir as homedir2 } from "os";
|
|
3683
3821
|
import { join as join12 } from "path";
|
|
3684
3822
|
var TEST_ACCOUNTS_PATH = join12(homedir2(), ".deepspace", "test-accounts.json");
|
|
@@ -3746,7 +3884,7 @@ function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope())
|
|
|
3746
3884
|
});
|
|
3747
3885
|
}
|
|
3748
3886
|
function readTestAccountStore() {
|
|
3749
|
-
if (!
|
|
3887
|
+
if (!existsSync13(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {} };
|
|
3750
3888
|
try {
|
|
3751
3889
|
const raw = JSON.parse(readFileSync11(TEST_ACCOUNTS_PATH, "utf-8"));
|
|
3752
3890
|
if (Array.isArray(raw)) return { version: 2, scopes: {} };
|
|
@@ -4714,7 +4852,7 @@ function describeFilesFailure(status3, body) {
|
|
|
4714
4852
|
import * as p6 from "@clack/prompts";
|
|
4715
4853
|
import { execSync as execSync2 } from "child_process";
|
|
4716
4854
|
import { createHash as createHash2 } from "crypto";
|
|
4717
|
-
import { existsSync as
|
|
4855
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15, readdirSync as readdirSync6 } from "fs";
|
|
4718
4856
|
import { dirname as dirname7, isAbsolute as isAbsolute3, join as join17, relative as relative2, resolve as resolve13, sep as sep3 } from "path";
|
|
4719
4857
|
|
|
4720
4858
|
// src/build/app-id.ts
|
|
@@ -4732,11 +4870,11 @@ var APP_ID_ADOPTION_STEPS = `Point the client at the build-injected id \u2014 th
|
|
|
4732
4870
|
`).join("") + APP_ID_ADOPTION_TOGETHER;
|
|
4733
4871
|
|
|
4734
4872
|
// src/build/plugin.ts
|
|
4735
|
-
import { existsSync as
|
|
4873
|
+
import { existsSync as existsSync14, lstatSync, readdirSync as readdirSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
4736
4874
|
import { join as join14, resolve as resolve12 } from "path";
|
|
4737
4875
|
function removeBuildDevVars(workerDir) {
|
|
4738
4876
|
const path = join14(workerDir, ".dev.vars");
|
|
4739
|
-
if (!
|
|
4877
|
+
if (!existsSync14(path)) return false;
|
|
4740
4878
|
if (!lstatSync(path).isFile()) {
|
|
4741
4879
|
throw new Error(`Refusing unsafe build secret path: ${path}`);
|
|
4742
4880
|
}
|
|
@@ -4745,12 +4883,12 @@ function removeBuildDevVars(workerDir) {
|
|
|
4745
4883
|
}
|
|
4746
4884
|
|
|
4747
4885
|
// src/documentation/deploy.ts
|
|
4748
|
-
import { existsSync as
|
|
4886
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
4749
4887
|
import { join as join15 } from "path";
|
|
4750
4888
|
function readDocumentationDeployManifest(appDir, clientDir) {
|
|
4751
|
-
if (!
|
|
4889
|
+
if (!existsSync15(join15(appDir, "documentation.json"))) return void 0;
|
|
4752
4890
|
const manifestPath = join15(clientDir, "_documentation", "manifest.json");
|
|
4753
|
-
if (!
|
|
4891
|
+
if (!existsSync15(manifestPath)) {
|
|
4754
4892
|
throw new Error(
|
|
4755
4893
|
"documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
|
|
4756
4894
|
);
|
|
@@ -4759,7 +4897,7 @@ function readDocumentationDeployManifest(appDir, clientDir) {
|
|
|
4759
4897
|
}
|
|
4760
4898
|
|
|
4761
4899
|
// src/cli/commands/update/app-migrations.ts
|
|
4762
|
-
import { existsSync as
|
|
4900
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
4763
4901
|
import { join as join16 } from "path";
|
|
4764
4902
|
|
|
4765
4903
|
// src/shared/protocol/app-migrations.ts
|
|
@@ -4835,7 +4973,7 @@ var APP_MIGRATION_DEFINITIONS = APP_MIGRATION_GUIDANCE.map(({ id, description })
|
|
|
4835
4973
|
}));
|
|
4836
4974
|
function readAppliedAppMigrations(appDir) {
|
|
4837
4975
|
const path = join16(appDir, APP_MIGRATIONS_MANIFEST);
|
|
4838
|
-
if (!
|
|
4976
|
+
if (!existsSync16(path)) return [];
|
|
4839
4977
|
let parsed;
|
|
4840
4978
|
try {
|
|
4841
4979
|
parsed = JSON.parse(readFileSync14(path, "utf8"));
|
|
@@ -4898,7 +5036,7 @@ async function buildDeployBundle(options) {
|
|
|
4898
5036
|
}
|
|
4899
5037
|
spinner2.stop(`Built (${Math.round((Date.now() - buildStarted) / 1e3)}s)`);
|
|
4900
5038
|
const deployConfigPath = join17(appDir, ".wrangler", "deploy", "config.json");
|
|
4901
|
-
if (!
|
|
5039
|
+
if (!existsSync17(deployConfigPath)) {
|
|
4902
5040
|
output.die(
|
|
4903
5041
|
"Build output config not found at .wrangler/deploy/config.json",
|
|
4904
5042
|
"build_output_missing"
|
|
@@ -4906,7 +5044,7 @@ async function buildDeployBundle(options) {
|
|
|
4906
5044
|
}
|
|
4907
5045
|
const deployConfig = JSON.parse(readFileSync15(deployConfigPath, "utf-8"));
|
|
4908
5046
|
const outputWranglerPath = resolve13(dirname7(deployConfigPath), deployConfig.configPath);
|
|
4909
|
-
if (!
|
|
5047
|
+
if (!existsSync17(outputWranglerPath)) {
|
|
4910
5048
|
output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
|
|
4911
5049
|
}
|
|
4912
5050
|
const outputConfig = JSON.parse(readFileSync15(outputWranglerPath, "utf-8"));
|
|
@@ -4925,10 +5063,10 @@ async function buildDeployBundle(options) {
|
|
|
4925
5063
|
}
|
|
4926
5064
|
const workerBundlePath = join17(workerDir, outputConfig.main);
|
|
4927
5065
|
const clientDir = outputConfig.assets?.directory ? resolve13(workerDir, outputConfig.assets.directory) : null;
|
|
4928
|
-
if (!
|
|
5066
|
+
if (!existsSync17(workerBundlePath)) {
|
|
4929
5067
|
output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
|
|
4930
5068
|
}
|
|
4931
|
-
if (!clientDir || !
|
|
5069
|
+
if (!clientDir || !existsSync17(clientDir)) {
|
|
4932
5070
|
return output.die(`Client assets not found at ${clientDir}`, "build_output_missing");
|
|
4933
5071
|
}
|
|
4934
5072
|
try {
|
|
@@ -5116,7 +5254,7 @@ function readDeployAssetConfig(clientDir) {
|
|
|
5116
5254
|
["_redirects", "_redirects"]
|
|
5117
5255
|
]) {
|
|
5118
5256
|
const filePath = join17(clientDir, file);
|
|
5119
|
-
if (!
|
|
5257
|
+
if (!existsSync17(filePath)) continue;
|
|
5120
5258
|
const content = readFileSync15(filePath, "utf8");
|
|
5121
5259
|
if (content.trim()) config[key] = content;
|
|
5122
5260
|
}
|
|
@@ -5131,7 +5269,7 @@ function errorMessage(error) {
|
|
|
5131
5269
|
|
|
5132
5270
|
// src/cli/commands/deploy/commerce.ts
|
|
5133
5271
|
import * as p7 from "@clack/prompts";
|
|
5134
|
-
import { existsSync as
|
|
5272
|
+
import { existsSync as existsSync18 } from "fs";
|
|
5135
5273
|
import { join as join18 } from "path";
|
|
5136
5274
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
5137
5275
|
|
|
@@ -5185,7 +5323,7 @@ function errorMessage2(error) {
|
|
|
5185
5323
|
}
|
|
5186
5324
|
async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
|
|
5187
5325
|
const sourcePath = join18(appDir, "src", sourceFile);
|
|
5188
|
-
if (!
|
|
5326
|
+
if (!existsSync18(sourcePath)) return void 0;
|
|
5189
5327
|
const esbuild = await import("esbuild");
|
|
5190
5328
|
const outputPath = join18(appDir, ".wrangler", "deploy", outputFile);
|
|
5191
5329
|
await esbuild.build({
|
|
@@ -5280,7 +5418,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
|
|
|
5280
5418
|
}
|
|
5281
5419
|
}
|
|
5282
5420
|
async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
5283
|
-
if (!
|
|
5421
|
+
if (!existsSync18(join18(appDir, "src", "subscriptions.ts"))) return;
|
|
5284
5422
|
let plans;
|
|
5285
5423
|
try {
|
|
5286
5424
|
plans = await loadDeclaration(
|
|
@@ -5335,7 +5473,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
|
5335
5473
|
}
|
|
5336
5474
|
}
|
|
5337
5475
|
async function syncOneTimeProducts(appDir, appName, token) {
|
|
5338
|
-
if (!
|
|
5476
|
+
if (!existsSync18(join18(appDir, "src", "products.ts"))) return;
|
|
5339
5477
|
let products;
|
|
5340
5478
|
try {
|
|
5341
5479
|
products = await loadDeclaration(
|
|
@@ -5447,92 +5585,8 @@ import { readFileSync as readFileSync16 } from "fs";
|
|
|
5447
5585
|
// src/cli/commands/deploy/repository.ts
|
|
5448
5586
|
import * as p9 from "@clack/prompts";
|
|
5449
5587
|
|
|
5450
|
-
// src/cli/lib/git/safety.ts
|
|
5451
|
-
var SECRET_FILE_RE = /(^|\/)(\.dev\.vars(\.[^/]*)?|\.env(\.[^/]*)?|\.npmrc|\.envrc|\.mcp\.json)$/i;
|
|
5452
|
-
var SECRET_TEMPLATE_RE = /\.(example|sample|template)$/i;
|
|
5453
|
-
function findOversizedObjects(cwd, capBytes, limit = 3) {
|
|
5454
|
-
try {
|
|
5455
|
-
const revisionObjects = runGit(cwd, ["rev-list", "--objects", "--all"], { allowFail: true });
|
|
5456
|
-
if (revisionObjects.status !== 0) return [];
|
|
5457
|
-
const pathByOid = /* @__PURE__ */ new Map();
|
|
5458
|
-
for (const line of revisionObjects.stdout.toString("utf-8").split("\n")) {
|
|
5459
|
-
const separator = line.indexOf(" ");
|
|
5460
|
-
if (separator > 0) pathByOid.set(line.slice(0, separator), line.slice(separator + 1));
|
|
5461
|
-
}
|
|
5462
|
-
const sizedObjects = runGit(
|
|
5463
|
-
cwd,
|
|
5464
|
-
[
|
|
5465
|
-
"cat-file",
|
|
5466
|
-
"--batch-all-objects",
|
|
5467
|
-
"--batch-check=%(objectname) %(objecttype) %(objectsize)"
|
|
5468
|
-
],
|
|
5469
|
-
{ allowFail: true }
|
|
5470
|
-
);
|
|
5471
|
-
if (sizedObjects.status !== 0) return [];
|
|
5472
|
-
const oversized = [];
|
|
5473
|
-
for (const line of sizedObjects.stdout.toString("utf-8").split("\n")) {
|
|
5474
|
-
const [oid, type, size] = line.split(" ");
|
|
5475
|
-
if (type !== "blob") continue;
|
|
5476
|
-
const bytes = Number(size);
|
|
5477
|
-
const path = pathByOid.get(oid);
|
|
5478
|
-
if (path && Number.isFinite(bytes) && bytes > capBytes) oversized.push({ path, bytes });
|
|
5479
|
-
}
|
|
5480
|
-
return oversized.sort((left, right) => right.bytes - left.bytes).slice(0, limit);
|
|
5481
|
-
} catch {
|
|
5482
|
-
return [];
|
|
5483
|
-
}
|
|
5484
|
-
}
|
|
5485
|
-
function statusFiles(cwd) {
|
|
5486
|
-
const records = splitNulFields(
|
|
5487
|
-
runGit(cwd, [
|
|
5488
|
-
"-c",
|
|
5489
|
-
"core.quotePath=false",
|
|
5490
|
-
"status",
|
|
5491
|
-
"--porcelain",
|
|
5492
|
-
"--untracked-files=all",
|
|
5493
|
-
"-z"
|
|
5494
|
-
]).stdout
|
|
5495
|
-
);
|
|
5496
|
-
const files = [];
|
|
5497
|
-
for (let index = 0; index < records.length; index++) {
|
|
5498
|
-
const record = records[index];
|
|
5499
|
-
files.push(record.slice(3));
|
|
5500
|
-
if (/[RC]/.test(record.slice(0, 2))) {
|
|
5501
|
-
index++;
|
|
5502
|
-
if (/R/.test(record.slice(0, 2)) && index < records.length) files.push(records[index]);
|
|
5503
|
-
}
|
|
5504
|
-
}
|
|
5505
|
-
return [
|
|
5506
|
-
...new Set(
|
|
5507
|
-
files.filter((path) => !(SECRET_FILE_RE.test(path) && !SECRET_TEMPLATE_RE.test(path)))
|
|
5508
|
-
)
|
|
5509
|
-
];
|
|
5510
|
-
}
|
|
5511
|
-
function trackedSecretFiles(cwd, ref) {
|
|
5512
|
-
const result = runGit(
|
|
5513
|
-
repoToplevel(cwd),
|
|
5514
|
-
["-c", "core.quotePath=false", "ls-tree", "-r", "--name-only", "-z", ref],
|
|
5515
|
-
{ allowFail: true }
|
|
5516
|
-
);
|
|
5517
|
-
if (result.status !== 0) return [];
|
|
5518
|
-
return splitNulFields(result.stdout).filter(
|
|
5519
|
-
(path) => SECRET_FILE_RE.test(path) && !SECRET_TEMPLATE_RE.test(path)
|
|
5520
|
-
);
|
|
5521
|
-
}
|
|
5522
|
-
var SECRET_IN_HISTORY_CODE = "secret_in_history";
|
|
5523
|
-
function committedSecretRefusal(cwd, commitOid, options) {
|
|
5524
|
-
if (!commitOid) return null;
|
|
5525
|
-
const files = trackedSecretFiles(cwd, commitOid);
|
|
5526
|
-
if (files.length === 0) return null;
|
|
5527
|
-
return {
|
|
5528
|
-
files,
|
|
5529
|
-
code: SECRET_IN_HISTORY_CODE,
|
|
5530
|
-
message: `Refusing to ${options.action}: the commit being published carries secret file(s) \u2014 ${files.join(", ")}. These hold local secrets and must not reach the cloud repo, but pushing this commit would upload them through its history. Untrack with \`git rm --cached ${shQuote(files[0])}\`, ensure it's .gitignored, commit, then ${options.then}.`
|
|
5531
|
-
};
|
|
5532
|
-
}
|
|
5533
|
-
|
|
5534
5588
|
// src/cli/lib/vc-remote.ts
|
|
5535
|
-
import { existsSync as
|
|
5589
|
+
import { existsSync as existsSync19 } from "fs";
|
|
5536
5590
|
import { tmpdir } from "os";
|
|
5537
5591
|
import { isAbsolute as isAbsolute4, relative as relative3, resolve as resolve14 } from "path";
|
|
5538
5592
|
function spaceRemoteName(environment = DEEPSPACE_ENV) {
|
|
@@ -5587,7 +5641,7 @@ function helperEntryUsable(command) {
|
|
|
5587
5641
|
const match = /^!(?:DEEPSPACE_ENV=(production|staging) )?'([^']*)' '([^']*)' git-credential$/.exec(command);
|
|
5588
5642
|
const expectedEnvironment = DEEPSPACE_ENV === "production" ? void 0 : DEEPSPACE_ENV;
|
|
5589
5643
|
if (!match || match[1] !== expectedEnvironment) return false;
|
|
5590
|
-
return
|
|
5644
|
+
return existsSync19(match[2]) && existsSync19(match[3]);
|
|
5591
5645
|
}
|
|
5592
5646
|
function credentialHelperCommand(environment = DEEPSPACE_ENV) {
|
|
5593
5647
|
const entry = process.argv[1];
|
|
@@ -5632,23 +5686,31 @@ function installCredentialHelper(cwd, url) {
|
|
|
5632
5686
|
} catch {
|
|
5633
5687
|
}
|
|
5634
5688
|
}
|
|
5635
|
-
function ensureSpaceRemote(cwd, appId, remote = SPACE_REMOTE) {
|
|
5689
|
+
function ensureSpaceRemote(cwd, appId, remote = SPACE_REMOTE, token) {
|
|
5636
5690
|
const url = repoUrl(appId);
|
|
5637
5691
|
const current = runGit(cwd, ["remote", "get-url", remote], { allowFail: true });
|
|
5638
|
-
|
|
5692
|
+
const existing = current.status === 0 ? current.stdout.toString("utf-8").trim() : null;
|
|
5693
|
+
if (appIdFromRepoUrl(existing ?? "") !== appId) {
|
|
5694
|
+
const owned = declaredAppIds(cwd);
|
|
5695
|
+
if (owned.size > 0 && !owned.has(appId)) {
|
|
5696
|
+
throw new InputError(
|
|
5697
|
+
`This checkout declares ${[...owned].join(", ")}, but this command targets ${appId}. Pointing its \`${remote}\` remote at another app would send every later plain \`git push ${remote}\` to the wrong repo. Work on ${appId} from its own checkout (\`deepspace clone ${appId}\` into a new directory), or fork THIS checkout into a brand-new app of your own with \`deepspace app init --new-id\`.`,
|
|
5698
|
+
"app_checkout_mismatch"
|
|
5699
|
+
);
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
if (token) ensureGitIdentity(cwd, token);
|
|
5703
|
+
if (existing === null) {
|
|
5639
5704
|
runGit(cwd, ["remote", "add", remote, url]);
|
|
5640
|
-
} else {
|
|
5641
|
-
const
|
|
5642
|
-
if (
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
process.stderr.write(
|
|
5646
|
-
`warning: remote '${remote}' was pointed at app ${priorApp}; re-aiming it at ${appId}.
|
|
5705
|
+
} else if (existing !== url) {
|
|
5706
|
+
const priorApp = appIdFromRepoUrl(existing);
|
|
5707
|
+
if (priorApp && priorApp !== appId && !process.argv.includes("--json")) {
|
|
5708
|
+
process.stderr.write(
|
|
5709
|
+
`warning: remote '${remote}' was pointed at app ${priorApp}; re-aiming it at ${appId}.
|
|
5647
5710
|
`
|
|
5648
|
-
|
|
5649
|
-
}
|
|
5650
|
-
runGit(cwd, ["remote", "set-url", remote, url]);
|
|
5711
|
+
);
|
|
5651
5712
|
}
|
|
5713
|
+
runGit(cwd, ["remote", "set-url", remote, url]);
|
|
5652
5714
|
}
|
|
5653
5715
|
installCredentialHelper(cwd, url);
|
|
5654
5716
|
return url;
|
|
@@ -5665,6 +5727,9 @@ function ensureGitIdentity(cwd, token) {
|
|
|
5665
5727
|
const missingName = !configured("user.name");
|
|
5666
5728
|
const missingEmail = !configured("user.email");
|
|
5667
5729
|
if (!missingName && !missingEmail) return;
|
|
5730
|
+
if (runGit(cwd, ["config", "--get", "--type=bool", "user.useConfigOnly"], { allowFail: true }).stdout.toString("utf-8").trim() === "true") {
|
|
5731
|
+
return;
|
|
5732
|
+
}
|
|
5668
5733
|
const payload = decodeJwtPayload(token);
|
|
5669
5734
|
if (!payload.email) return;
|
|
5670
5735
|
if (missingName) {
|
|
@@ -5675,6 +5740,14 @@ function ensureGitIdentity(cwd, token) {
|
|
|
5675
5740
|
if (missingEmail) {
|
|
5676
5741
|
runGit(cwd, ["config", "--local", "user.email", payload.email], { allowFail: true });
|
|
5677
5742
|
}
|
|
5743
|
+
const wrote = [...missingEmail ? ["user.email"] : [], ...missingName ? ["user.name"] : []];
|
|
5744
|
+
const dir = /^[A-Za-z0-9_./:@-]+$/.test(cwd) ? cwd : shQuote(cwd);
|
|
5745
|
+
const undo = wrote.map((key) => `git -C ${dir} config --local --unset ${key}`).join(" && ");
|
|
5746
|
+
process.stderr.write(
|
|
5747
|
+
`Git identity for this checkout set from your DeepSpace login (${payload.email}).
|
|
5748
|
+
Undo with: ${undo}
|
|
5749
|
+
`
|
|
5750
|
+
);
|
|
5678
5751
|
} catch {
|
|
5679
5752
|
}
|
|
5680
5753
|
}
|
|
@@ -5683,74 +5756,287 @@ function appIdFromRepoUrl(url) {
|
|
|
5683
5756
|
return match ? decodeURIComponent(match[1]) : null;
|
|
5684
5757
|
}
|
|
5685
5758
|
|
|
5686
|
-
// src/cli/lib/
|
|
5687
|
-
var
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
const
|
|
5692
|
-
if (
|
|
5693
|
-
const
|
|
5694
|
-
const
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
let status3;
|
|
5698
|
-
let reason;
|
|
5699
|
-
if (flag === "=") {
|
|
5700
|
-
status3 = "up_to_date";
|
|
5701
|
-
} else if (flag === "!") {
|
|
5702
|
-
reason = /\((.*)\)\s*$/.exec(summary)?.[1];
|
|
5703
|
-
if (!summary.startsWith("[remote rejected]")) {
|
|
5704
|
-
status3 = "non_fast_forward";
|
|
5705
|
-
} else if (reason && CAS_CONFLICT_REASON.test(reason)) {
|
|
5706
|
-
status3 = "ref_conflict";
|
|
5707
|
-
} else {
|
|
5708
|
-
status3 = "rejected";
|
|
5709
|
-
}
|
|
5710
|
-
} else {
|
|
5711
|
-
status3 = "committed";
|
|
5759
|
+
// src/cli/lib/git/safety.ts
|
|
5760
|
+
var SECRET_FILE_RE = /(^|\/)(\.dev\.vars(\.[^/]*)?|\.env(\.[^/]*)?|\.npmrc|\.envrc|\.mcp\.json)$/i;
|
|
5761
|
+
var SECRET_TEMPLATE_RE = /\.(example|sample|template)$/i;
|
|
5762
|
+
function findOversizedObjects(cwd, capBytes, limit = 3) {
|
|
5763
|
+
try {
|
|
5764
|
+
const revisionObjects = runGit(cwd, ["rev-list", "--objects", "--all"], { allowFail: true });
|
|
5765
|
+
if (revisionObjects.status !== 0) return [];
|
|
5766
|
+
const pathByOid = /* @__PURE__ */ new Map();
|
|
5767
|
+
for (const line of revisionObjects.stdout.toString("utf-8").split("\n")) {
|
|
5768
|
+
const separator = line.indexOf(" ");
|
|
5769
|
+
if (separator > 0) pathByOid.set(line.slice(0, separator), line.slice(separator + 1));
|
|
5712
5770
|
}
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5771
|
+
const sizedObjects = runGit(
|
|
5772
|
+
cwd,
|
|
5773
|
+
[
|
|
5774
|
+
"cat-file",
|
|
5775
|
+
"--batch-all-objects",
|
|
5776
|
+
"--batch-check=%(objectname) %(objecttype) %(objectsize)"
|
|
5777
|
+
],
|
|
5778
|
+
{ allowFail: true }
|
|
5779
|
+
);
|
|
5780
|
+
if (sizedObjects.status !== 0) return [];
|
|
5781
|
+
const oversized = [];
|
|
5782
|
+
for (const line of sizedObjects.stdout.toString("utf-8").split("\n")) {
|
|
5783
|
+
const [oid, type, size] = line.split(" ");
|
|
5784
|
+
if (type !== "blob") continue;
|
|
5785
|
+
const bytes = Number(size);
|
|
5786
|
+
const path = pathByOid.get(oid);
|
|
5787
|
+
if (path && Number.isFinite(bytes) && bytes > capBytes) oversized.push({ path, bytes });
|
|
5788
|
+
}
|
|
5789
|
+
return oversized.sort((left, right) => right.bytes - left.bytes).slice(0, limit);
|
|
5790
|
+
} catch {
|
|
5791
|
+
return [];
|
|
5792
|
+
}
|
|
5793
|
+
}
|
|
5794
|
+
function statusFiles(cwd) {
|
|
5795
|
+
const records = splitNulFields(
|
|
5796
|
+
runGit(cwd, [
|
|
5797
|
+
"-c",
|
|
5798
|
+
"core.quotePath=false",
|
|
5799
|
+
"status",
|
|
5800
|
+
"--porcelain",
|
|
5801
|
+
"--untracked-files=all",
|
|
5802
|
+
"-z"
|
|
5803
|
+
]).stdout
|
|
5804
|
+
);
|
|
5805
|
+
const files = [];
|
|
5806
|
+
for (let index = 0; index < records.length; index++) {
|
|
5807
|
+
const record = records[index];
|
|
5808
|
+
files.push(record.slice(3));
|
|
5809
|
+
if (/[RC]/.test(record.slice(0, 2))) {
|
|
5810
|
+
index++;
|
|
5811
|
+
if (/R/.test(record.slice(0, 2)) && index < records.length) files.push(records[index]);
|
|
5812
|
+
}
|
|
5813
|
+
}
|
|
5814
|
+
return [
|
|
5815
|
+
...new Set(
|
|
5816
|
+
files.filter((path) => !(SECRET_FILE_RE.test(path) && !SECRET_TEMPLATE_RE.test(path)))
|
|
5817
|
+
)
|
|
5818
|
+
];
|
|
5819
|
+
}
|
|
5820
|
+
function trackedSecretFiles(cwd, ref) {
|
|
5821
|
+
const result = runGit(
|
|
5822
|
+
repoToplevel(cwd),
|
|
5823
|
+
["-c", "core.quotePath=false", "ls-tree", "-r", "--name-only", "-z", ref],
|
|
5824
|
+
{ allowFail: true }
|
|
5825
|
+
);
|
|
5826
|
+
if (result.status !== 0) return [];
|
|
5827
|
+
return splitNulFields(result.stdout).filter(
|
|
5828
|
+
(path) => SECRET_FILE_RE.test(path) && !SECRET_TEMPLATE_RE.test(path)
|
|
5829
|
+
);
|
|
5830
|
+
}
|
|
5831
|
+
function pushRange(root, base, tip) {
|
|
5832
|
+
if (base) return ["--root", `${base}..${tip}`];
|
|
5833
|
+
const listed = runGit(
|
|
5834
|
+
root,
|
|
5835
|
+
["for-each-ref", "--format=%(refname)", `refs/remotes/${SPACE_REMOTE}/`],
|
|
5836
|
+
{ allowFail: true }
|
|
5837
|
+
);
|
|
5838
|
+
const spaceRefs = listed.status === 0 ? listed.stdout.toString("utf-8").split("\n").map((ref) => ref.trim()).filter(Boolean) : [];
|
|
5839
|
+
return spaceRefs.length === 0 ? ["--root", tip] : ["--root", tip, "--not", ...spaceRefs];
|
|
5840
|
+
}
|
|
5841
|
+
function secretFilesInPushRange(cwd, base, tip) {
|
|
5842
|
+
const root = repoToplevel(cwd);
|
|
5843
|
+
const found = new Set(trackedSecretFiles(root, tip));
|
|
5844
|
+
const result = runGit(
|
|
5845
|
+
root,
|
|
5846
|
+
[
|
|
5847
|
+
"-c",
|
|
5848
|
+
"core.quotePath=false",
|
|
5849
|
+
"log",
|
|
5850
|
+
// Rename detection is on by default, so `git mv notes.txt .env` is
|
|
5851
|
+
// reported as R100 and never as an addition — while the blob still
|
|
5852
|
+
// rides the pack and the server still refuses it.
|
|
5853
|
+
"--no-renames",
|
|
5854
|
+
"--diff-filter=A",
|
|
5855
|
+
"--name-only",
|
|
5856
|
+
"--pretty=format:",
|
|
5857
|
+
"-z",
|
|
5858
|
+
...pushRange(root, base, tip)
|
|
5859
|
+
],
|
|
5860
|
+
{ allowFail: true }
|
|
5861
|
+
);
|
|
5862
|
+
if (result.status !== 0) {
|
|
5863
|
+
return [...found];
|
|
5864
|
+
}
|
|
5865
|
+
for (const path of splitNulFields(result.stdout)) {
|
|
5866
|
+
if (path && SECRET_FILE_RE.test(path) && !SECRET_TEMPLATE_RE.test(path)) found.add(path);
|
|
5867
|
+
}
|
|
5868
|
+
return [...found];
|
|
5869
|
+
}
|
|
5870
|
+
var SECRET_IN_HISTORY_CODE = "secret_in_history";
|
|
5871
|
+
function secretRecoverySentence(files, then) {
|
|
5872
|
+
const first = shQuote(files[0]);
|
|
5873
|
+
return `Untracking alone will not fix this: the file is already in the commits being sent, so its contents upload through history either way. Rewrite the history that carries it \u2014 \`git rebase -i\` back past the commit that added ${first}, or \`git reset --soft\` to before it and re-commit without the file \u2014 and add it to .gitignore, then ${then}. Treat any credential it held as exposed and rotate it.`;
|
|
5874
|
+
}
|
|
5875
|
+
function committedSecretRefusal(cwd, commitOid, options) {
|
|
5876
|
+
if (!commitOid) return null;
|
|
5877
|
+
const files = options.base === void 0 ? trackedSecretFiles(cwd, commitOid) : secretFilesInPushRange(cwd, options.base, commitOid);
|
|
5878
|
+
if (files.length === 0) return null;
|
|
5879
|
+
return {
|
|
5880
|
+
files,
|
|
5881
|
+
code: SECRET_IN_HISTORY_CODE,
|
|
5882
|
+
message: `Refusing to ${options.action}: the commit being published carries secret file(s) \u2014 ${files.join(", ")}. These hold local secrets and must not reach the cloud repo, but publishing this commit would upload them through its history. ` + secretRecoverySentence(files, options.then)
|
|
5883
|
+
};
|
|
5884
|
+
}
|
|
5885
|
+
function unmergedIndexRefusal(cwd, opts) {
|
|
5886
|
+
const marker = interruptedGitOperation(cwd);
|
|
5887
|
+
const unmerged = hasUnmergedEntries(cwd);
|
|
5888
|
+
const operation = marker === "bisect" ? null : marker;
|
|
5889
|
+
if (operation === null && !unmerged) return null;
|
|
5890
|
+
if (operation !== null && !unmerged) {
|
|
5891
|
+
return {
|
|
5892
|
+
operation,
|
|
5893
|
+
code: "git_operation_in_progress",
|
|
5894
|
+
message: `A ${operation} is in progress in this checkout \u2014 finish it (\`git ${operation} --continue\`) or abort it (\`git ${operation} --abort\`), then re-run \`${opts.resume}\`. If it stopped at a conflict, anything already staged can still carry \`<<<<<<<\` markers; staging is not resolving.`
|
|
5895
|
+
};
|
|
5896
|
+
}
|
|
5897
|
+
if (operation === "merge" || operation === null) {
|
|
5898
|
+
return {
|
|
5899
|
+
operation,
|
|
5900
|
+
code: "merge_conflict",
|
|
5901
|
+
message: (operation === "merge" ? `${opts.ours} is already in progress here, stopped at unresolved conflicts. ` : `The index has unresolved conflicts with no operation to continue \u2014 a squash merge or a stash pop. `) + `Resolve the conflicted files (remove the \`<<<<<<<\` markers), \`git add\` them, ` + (operation === "merge" ? `\`git commit\`, then re-run \`${opts.resume}\`. To abandon the merge instead: \`git merge --abort\`.` : `\`git commit\`, then re-run \`${opts.resume}\`. To discard them instead: \`git reset --merge\`.`)
|
|
5902
|
+
};
|
|
5903
|
+
}
|
|
5904
|
+
return {
|
|
5905
|
+
operation,
|
|
5906
|
+
code: "git_operation_in_progress",
|
|
5907
|
+
message: `A ${operation} is unfinished in this checkout with unresolved conflicts \u2014 finish it (\`git ${operation} --continue\` after resolving, or \`git ${operation} --abort\`), then re-run \`${opts.resume}\`.`
|
|
5908
|
+
};
|
|
5909
|
+
}
|
|
5910
|
+
|
|
5911
|
+
// src/cli/lib/vc-push.ts
|
|
5912
|
+
function parseRefusalCode(reason) {
|
|
5913
|
+
const match = /^([a-z_]+): (.*)$/s.exec(reason);
|
|
5914
|
+
if (!match) return null;
|
|
5915
|
+
const remainder = match[2];
|
|
5916
|
+
const cut = remainder.indexOf(" \u2014 ");
|
|
5917
|
+
return cut === -1 ? { code: match[1], sentence: remainder } : {
|
|
5918
|
+
code: match[1],
|
|
5919
|
+
sentence: remainder.slice(0, cut),
|
|
5920
|
+
detail: remainder.slice(cut + " \u2014 ".length)
|
|
5921
|
+
};
|
|
5922
|
+
}
|
|
5923
|
+
function parsePushPorcelain(out) {
|
|
5924
|
+
const results = [];
|
|
5925
|
+
for (const line of out.split("\n")) {
|
|
5926
|
+
const match = /^([ +\-*=!])\t([^\t]*)\t(.*)$/.exec(line);
|
|
5927
|
+
if (!match) continue;
|
|
5928
|
+
const [, flag, refspec, summary] = match;
|
|
5929
|
+
const colon = refspec.indexOf(":");
|
|
5930
|
+
const localRef = colon >= 0 ? refspec.slice(0, colon) : refspec;
|
|
5931
|
+
const remoteRef = colon >= 0 ? refspec.slice(colon + 1) : refspec;
|
|
5932
|
+
let status3;
|
|
5933
|
+
let reason;
|
|
5934
|
+
let code;
|
|
5935
|
+
if (flag === "=") {
|
|
5936
|
+
status3 = "up_to_date";
|
|
5937
|
+
} else if (flag === "!") {
|
|
5938
|
+
reason = /\((.*)\)\s*$/.exec(summary)?.[1];
|
|
5939
|
+
code = reason ? parseRefusalCode(reason)?.code ?? void 0 : void 0;
|
|
5940
|
+
if (!summary.startsWith("[remote rejected]")) {
|
|
5941
|
+
status3 = "non_fast_forward";
|
|
5942
|
+
} else if (code === "stale_ref") {
|
|
5943
|
+
status3 = "ref_conflict";
|
|
5944
|
+
} else {
|
|
5945
|
+
status3 = "rejected";
|
|
5946
|
+
}
|
|
5947
|
+
} else {
|
|
5948
|
+
status3 = "committed";
|
|
5949
|
+
}
|
|
5950
|
+
results.push({
|
|
5951
|
+
status: status3,
|
|
5952
|
+
localRef,
|
|
5953
|
+
remoteRef,
|
|
5954
|
+
summary,
|
|
5955
|
+
...reason ? { reason } : {},
|
|
5956
|
+
...code ? { code } : {}
|
|
5957
|
+
});
|
|
5958
|
+
}
|
|
5959
|
+
return results;
|
|
5960
|
+
}
|
|
5961
|
+
function isThinPackRejection(result) {
|
|
5962
|
+
return result.status === "rejected" && result.code === "thin_pack";
|
|
5963
|
+
}
|
|
5964
|
+
function isRecoverablePushFailure(status3) {
|
|
5965
|
+
return status3 === "non_fast_forward" || status3 === "ref_conflict";
|
|
5966
|
+
}
|
|
5967
|
+
function gitHttpStatus(message) {
|
|
5968
|
+
for (const line of message.split("\n")) {
|
|
5969
|
+
if (/^\s*remote:/i.test(line)) continue;
|
|
5970
|
+
const match = /(?:HTTP |error: )(\d{3})\b/i.exec(line);
|
|
5971
|
+
if (match) return Number(match[1]);
|
|
5972
|
+
}
|
|
5973
|
+
return null;
|
|
5974
|
+
}
|
|
5975
|
+
function classifyPushTransportFailure(error, cwd) {
|
|
5976
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5977
|
+
const status3 = gitHttpStatus(message);
|
|
5978
|
+
if (status3 === 409) {
|
|
5979
|
+
return {
|
|
5980
|
+
code: "app_quota_exceeded",
|
|
5981
|
+
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.`
|
|
5982
|
+
};
|
|
5983
|
+
}
|
|
5984
|
+
if (status3 === 429) {
|
|
5985
|
+
return {
|
|
5986
|
+
code: "rate_limited",
|
|
5734
5987
|
error: "The cloud repo rate-limited this push. Wait a few seconds, then retry."
|
|
5735
5988
|
};
|
|
5736
5989
|
}
|
|
5737
|
-
if (
|
|
5990
|
+
if (status3 === 413) {
|
|
5738
5991
|
return { code: "push_too_large", error: pushTooLargeMessage(cwd) };
|
|
5739
5992
|
}
|
|
5740
|
-
if (
|
|
5993
|
+
if (status3 === 422) {
|
|
5741
5994
|
return {
|
|
5742
5995
|
code: "source_managed_by_github",
|
|
5743
5996
|
error: "This app's source is managed by GitHub, so pushes to the DeepSpace repo are refused. Push to the GitHub repository instead \u2014 `deepspace app status` names it."
|
|
5744
5997
|
};
|
|
5745
5998
|
}
|
|
5999
|
+
if (status3 === 401) {
|
|
6000
|
+
return {
|
|
6001
|
+
code: "not_authenticated",
|
|
6002
|
+
error: "The cloud repo rejected the push as unauthenticated \u2014 the session likely expired mid-push. Run `deepspace auth login`, then push again. Nothing was applied."
|
|
6003
|
+
};
|
|
6004
|
+
}
|
|
6005
|
+
if (status3 === 403) {
|
|
6006
|
+
return {
|
|
6007
|
+
code: "forbidden",
|
|
6008
|
+
error: "The cloud repo refused this push: you do not have write access to that app, or its id belongs to another account (`deepspace app init --new-id` mints your own). Nothing was applied."
|
|
6009
|
+
};
|
|
6010
|
+
}
|
|
6011
|
+
if (status3 === null && /unexpected disconnect|Empty reply from server|curl (?:18|52|56)/i.test(
|
|
6012
|
+
message
|
|
6013
|
+
)) {
|
|
6014
|
+
return {
|
|
6015
|
+
code: "push_outcome_unknown",
|
|
6016
|
+
error: "The connection dropped while the push was in flight with no reply from the server, so whether the cloud repo applied it is unknown \u2014 it may have landed. Re-run this push before anything else: an already-applied push reports `up_to_date`. Do NOT force-push or reset until you have."
|
|
6017
|
+
};
|
|
6018
|
+
}
|
|
5746
6019
|
return null;
|
|
5747
6020
|
}
|
|
6021
|
+
function representativeResult(results) {
|
|
6022
|
+
const refused = results.filter(
|
|
6023
|
+
(result) => result.status !== "committed" && result.status !== "up_to_date"
|
|
6024
|
+
);
|
|
6025
|
+
return refused.find((result) => result.code !== "not_attempted") ?? refused[0] ?? results[0];
|
|
6026
|
+
}
|
|
5748
6027
|
function pushToSpace(cwd, token, refspec, opts = {}) {
|
|
5749
6028
|
const doPush = (extra) => {
|
|
5750
6029
|
const result = runGitRemote(
|
|
5751
6030
|
cwd,
|
|
5752
6031
|
token,
|
|
5753
|
-
[
|
|
6032
|
+
[
|
|
6033
|
+
"push",
|
|
6034
|
+
"--porcelain",
|
|
6035
|
+
...opts.force ? ["--force"] : [],
|
|
6036
|
+
...extra,
|
|
6037
|
+
opts.remote ?? SPACE_REMOTE,
|
|
6038
|
+
refspec
|
|
6039
|
+
],
|
|
5754
6040
|
{ allowFail: true }
|
|
5755
6041
|
);
|
|
5756
6042
|
const parsed = parsePushPorcelain(result.stdout.toString("utf-8"));
|
|
@@ -5760,12 +6046,11 @@ function pushToSpace(cwd, token, refspec, opts = {}) {
|
|
|
5760
6046
|
stderr || `git push exited ${result.status} without reporting a ref status`
|
|
5761
6047
|
);
|
|
5762
6048
|
}
|
|
5763
|
-
return parsed
|
|
6049
|
+
return representativeResult(parsed);
|
|
5764
6050
|
};
|
|
5765
6051
|
const first = doPush([]);
|
|
5766
6052
|
return isThinPackRejection(first) ? doPush(["--no-thin"]) : first;
|
|
5767
6053
|
}
|
|
5768
|
-
var OVERSIZED_PUSH_RE = /too large|exceeds|\bLFS\b/i;
|
|
5769
6054
|
var SERVER_OBJECT_CAP_BYTES = 20 * 1024 * 1024;
|
|
5770
6055
|
var SERVER_PACK_CAP_BYTES = 32 * 1024 * 1024;
|
|
5771
6056
|
var PUSH_CEILINGS = `${formatBytes(SERVER_OBJECT_CAP_BYTES)} per file, ${formatBytes(SERVER_PACK_CAP_BYTES)} per push`;
|
|
@@ -5791,39 +6076,83 @@ function pushTooLargeMessage(cwd) {
|
|
|
5791
6076
|
}
|
|
5792
6077
|
return `${ceilings} ${fixForObjects(objects)}`;
|
|
5793
6078
|
}
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
}
|
|
5800
|
-
|
|
5801
|
-
|
|
6079
|
+
var GENERIC_REJECTION = {
|
|
6080
|
+
code: "rejected",
|
|
6081
|
+
message: `The server rejected this push; correct the reported history/ref problem, or report it with \`deepspace feedback\` if the reason is unclear.`
|
|
6082
|
+
};
|
|
6083
|
+
var REFUSAL_ADVICE = {
|
|
6084
|
+
push_too_large: ({ cwd }) => ({
|
|
6085
|
+
code: "push_too_large",
|
|
6086
|
+
message: `An object exceeds the server's size cap, so retrying cannot succeed. ${oversizedPushFix(cwd)}`
|
|
6087
|
+
}),
|
|
6088
|
+
repo_full: () => ({
|
|
6089
|
+
code: "repo_full",
|
|
6090
|
+
message: `The cloud repo is at its size ceiling, so retrying cannot succeed. Large objects already committed upload through their history, so untracking them is not enough \u2014 ${removeFromHistoryAdvice(null)}`
|
|
6091
|
+
}),
|
|
6092
|
+
secret_committed: ({ detail }) => {
|
|
6093
|
+
const files = (detail ?? "").split(", ").map((name) => name.trim()).filter(Boolean);
|
|
6094
|
+
return {
|
|
6095
|
+
code: SECRET_IN_HISTORY_CODE,
|
|
6096
|
+
message: files.length > 0 ? secretRecoverySentence(files, "push again") : "A secret file is committed in the history being pushed. Rewrite that history to drop it (untracking alone is not enough), then push again."
|
|
6097
|
+
};
|
|
6098
|
+
},
|
|
6099
|
+
missing_objects: () => ({
|
|
6100
|
+
code: "missing_objects",
|
|
6101
|
+
message: "Retry; if it persists, report it with `deepspace feedback`."
|
|
6102
|
+
}),
|
|
6103
|
+
thin_pack: () => ({
|
|
6104
|
+
code: "thin_pack",
|
|
6105
|
+
// `pushToSpace` already retried once with `--no-thin`; reaching a caller
|
|
6106
|
+
// means that retry also failed.
|
|
6107
|
+
message: "Retry with `git push --no-thin`; if it persists, report it with `deepspace feedback`."
|
|
6108
|
+
}),
|
|
6109
|
+
// No `stale_ref` entry: it is encoded once, in `parsePushPorcelain`, as the
|
|
6110
|
+
// `ref_conflict` STATUS. That is what drives the pull action and exit 2, and
|
|
6111
|
+
// it is why no caller can reach this table with that code.
|
|
6112
|
+
funny_refname: () => ({
|
|
6113
|
+
code: "funny_refname",
|
|
6114
|
+
message: "Rename the branch to a valid git ref name, then push again."
|
|
6115
|
+
}),
|
|
6116
|
+
workspace_creator: () => ({
|
|
6117
|
+
code: "workspace_creator",
|
|
6118
|
+
message: "Another agent owns that workspace ref. Publish your own with `deepspace workspace sync`."
|
|
6119
|
+
}),
|
|
6120
|
+
internal_ref: () => ({
|
|
6121
|
+
code: "internal_ref",
|
|
6122
|
+
message: "That ref namespace is managed by DeepSpace. Push a branch instead."
|
|
6123
|
+
}),
|
|
6124
|
+
bad_tip: () => ({
|
|
6125
|
+
code: "bad_tip",
|
|
6126
|
+
message: "Point the branch at a commit (not a tag or tree object), then push again."
|
|
6127
|
+
}),
|
|
6128
|
+
unpacker_error: () => ({
|
|
6129
|
+
code: "unpacker_error",
|
|
6130
|
+
message: "The server could not unpack this push. Report it with `deepspace feedback` \u2014 a plain retry sends the identical pack."
|
|
6131
|
+
}),
|
|
6132
|
+
// The server has no more specific code; its own sentence is the whole fact,
|
|
6133
|
+
// so this reads exactly like an untagged rejection.
|
|
6134
|
+
push_failed: () => GENERIC_REJECTION,
|
|
6135
|
+
// Reaching here means `representativeResult` found nothing better — every
|
|
6136
|
+
// ref reported "not attempted", so the refusal that stopped the push was
|
|
6137
|
+
// never reported. Re-running would produce the same incomplete report, so
|
|
6138
|
+
// say what is actually true rather than prescribing a loop.
|
|
6139
|
+
not_attempted: () => ({
|
|
6140
|
+
code: "rejected",
|
|
6141
|
+
message: 'The push is atomic and every ref reports "not attempted", so the server did not report the refusal that stopped it. That should not happen \u2014 send this output with `deepspace feedback`.'
|
|
6142
|
+
})
|
|
6143
|
+
};
|
|
6144
|
+
function classifyRejection(reason, cwd) {
|
|
6145
|
+
const parsed = parseRefusalCode(reason);
|
|
6146
|
+
if (parsed && Object.hasOwn(REFUSAL_ADVICE, parsed.code)) {
|
|
6147
|
+
return REFUSAL_ADVICE[parsed.code]({ detail: parsed.detail, cwd });
|
|
5802
6148
|
}
|
|
5803
|
-
return
|
|
5804
|
-
}
|
|
5805
|
-
|
|
5806
|
-
// src/cli/lib/workspace-id.ts
|
|
5807
|
-
var ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
|
|
5808
|
-
var WORKSPACE_ID_RE = /^ws_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
5809
|
-
function isWorkspaceId(id) {
|
|
5810
|
-
return WORKSPACE_ID_RE.test(id);
|
|
6149
|
+
return GENERIC_REJECTION;
|
|
5811
6150
|
}
|
|
5812
|
-
function
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
const ulid = branch.slice(3);
|
|
5818
|
-
if (!ULID_RE.test(ulid)) return null;
|
|
5819
|
-
return `ws_${ulid.toUpperCase()}`;
|
|
5820
|
-
}
|
|
5821
|
-
function isSelectedWorkspaceCheckout(branch, workspaceId) {
|
|
5822
|
-
return workspaceIdFromBranch(branch) === workspaceId;
|
|
5823
|
-
}
|
|
5824
|
-
function resolveWorkspaceWorktree(worktrees, workspaceId) {
|
|
5825
|
-
const branch = workspaceBranchName(workspaceId);
|
|
5826
|
-
return worktrees.find((worktree) => worktree.branch === branch)?.path ?? null;
|
|
6151
|
+
function pushFailureMessage(subject, push, cwd) {
|
|
6152
|
+
const reason = push.reason ?? push.summary ?? "";
|
|
6153
|
+
const sentence = parseRefusalCode(reason)?.sentence ?? reason;
|
|
6154
|
+
const detail = `${push.status}${sentence ? `: ${sentence}` : ""}`;
|
|
6155
|
+
return `${subject} failed (${detail}). ${classifyRejection(reason, cwd).message}`;
|
|
5827
6156
|
}
|
|
5828
6157
|
|
|
5829
6158
|
// src/cli/lib/source-control.ts
|
|
@@ -5924,7 +6253,7 @@ function preflightDeployRepository(options) {
|
|
|
5924
6253
|
}
|
|
5925
6254
|
assertNoOperationInProgress(appDir);
|
|
5926
6255
|
const branch = currentBranch(appDir);
|
|
5927
|
-
if (!isWorkTreeClean(appDir)) return dirtyWorktreeRefusal(branch);
|
|
6256
|
+
if (!isWorkTreeClean(appDir)) return dirtyWorktreeRefusal(branch, appDir);
|
|
5928
6257
|
if (!branch) return detachedHeadRefusal();
|
|
5929
6258
|
return null;
|
|
5930
6259
|
} catch (error) {
|
|
@@ -5999,7 +6328,7 @@ async function syncDeployRepository(options) {
|
|
|
5999
6328
|
p9.log.info(`Workspace ${workspaceBranchId} is synced at ${tip.slice(0, 10)}.`);
|
|
6000
6329
|
} else if (secretFiles.length > 0) {
|
|
6001
6330
|
p9.log.warn(
|
|
6002
|
-
`Skipping cloud-repo push \u2014 the branch tracks secret file(s): ${secretFiles.join(", ")}.
|
|
6331
|
+
`Skipping cloud-repo push \u2014 the branch tracks secret file(s): ${secretFiles.join(", ")}. ` + secretRecoverySentence(secretFiles, "`deepspace push`") + ` Without the push this deploy carries no source lineage: if the live release has one, the server refuses it as stale (--ignore-stale replaces it anyway), and this release's source isn't recoverable until the push works.`
|
|
6003
6332
|
);
|
|
6004
6333
|
} else {
|
|
6005
6334
|
const pushResult = await pushWithTransientRetry(
|
|
@@ -6117,7 +6446,14 @@ function workspaceDeployLineage(status3, publishedTip, localTip) {
|
|
|
6117
6446
|
if (status3 !== "active") return "inactive";
|
|
6118
6447
|
return publishedTip === localTip ? "recoverable" : "unsynced";
|
|
6119
6448
|
}
|
|
6120
|
-
function dirtyWorktreeRefusal(branch) {
|
|
6449
|
+
function dirtyWorktreeRefusal(branch, cwd) {
|
|
6450
|
+
const conflicted = cwd ? unmergedIndexRefusal(cwd, { ours: "A merge", resume: "deepspace deploy" }) : null;
|
|
6451
|
+
if (conflicted) {
|
|
6452
|
+
return {
|
|
6453
|
+
code: conflicted.code,
|
|
6454
|
+
error: `${conflicted.message} A deploy records the commit it ships, so committing an unresolved merge would ship the markers.`
|
|
6455
|
+
};
|
|
6456
|
+
}
|
|
6121
6457
|
const workspaceId = workspaceIdFromBranch(branch);
|
|
6122
6458
|
return {
|
|
6123
6459
|
code: "dirty_worktree",
|
|
@@ -6350,7 +6686,7 @@ async function deployBuiltBundle(options) {
|
|
|
6350
6686
|
await bail(
|
|
6351
6687
|
formatDeployWorkerError(response.status, body.error, body.code),
|
|
6352
6688
|
"Deploy failed",
|
|
6353
|
-
body.code
|
|
6689
|
+
body.code ?? "deploy_failed"
|
|
6354
6690
|
);
|
|
6355
6691
|
}
|
|
6356
6692
|
p10.log.info(`Platform commit: ${Math.round((Date.now() - commitStarted) / 1e3)}s`);
|
|
@@ -7227,6 +7563,7 @@ var RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
7227
7563
|
var DEFAULT_WINDOW_MS = 15 * 60 * 1e3;
|
|
7228
7564
|
var MIN_POLL_MS = 2500;
|
|
7229
7565
|
var FOLLOW_LAG_MS = 9e4;
|
|
7566
|
+
var LOG_RETENTION_DAYS = 7;
|
|
7230
7567
|
function followInitialLimit(limit, maxLimit) {
|
|
7231
7568
|
return limit ?? maxLimit;
|
|
7232
7569
|
}
|
|
@@ -7306,23 +7643,23 @@ function formatEvent(e, color, now = Date.now()) {
|
|
|
7306
7643
|
if (e.eventType === "exception" && e.exception) {
|
|
7307
7644
|
tag = paint("31", "ERROR", color);
|
|
7308
7645
|
const where = e.request ? ` \u2014 ${e.request.method} ${e.request.path}` : "";
|
|
7309
|
-
body = `${e.exception.name}: ${e.exception.message}${where}
|
|
7646
|
+
body = displayLines(`${e.exception.name}: ${e.exception.message}${where}`);
|
|
7310
7647
|
if (e.exception.stack) {
|
|
7311
7648
|
const lines = e.exception.stack.split("\n");
|
|
7312
7649
|
const firstIsHeader = lines.length > 0 && !/^\s*at\s/.test(lines[0]) && lines[0].includes(e.exception.message);
|
|
7313
7650
|
const frames = (firstIsHeader ? lines.slice(1) : lines).map((l) => " " + l.trim()).filter((l) => l.trim().length > 0);
|
|
7314
|
-
if (frames.length) body += "\n" + paint("2", frames.join("\n"), color);
|
|
7651
|
+
if (frames.length) body += "\n" + paint("2", displayLines(frames.join("\n")), color);
|
|
7315
7652
|
}
|
|
7316
7653
|
} else if (e.eventType === "request" && e.request) {
|
|
7317
7654
|
tag = paint("36", "REQ ", color);
|
|
7318
7655
|
const status3 = e.request.status !== void 0 ? ` ${e.request.status}` : "";
|
|
7319
7656
|
const outcome = e.outcome && e.outcome !== "ok" ? ` (${e.outcome})` : "";
|
|
7320
|
-
body = `${e.request.method} ${e.request.path}${status3}${outcome}
|
|
7657
|
+
body = displayLines(`${e.request.method} ${e.request.path}${status3}${outcome}`);
|
|
7321
7658
|
} else {
|
|
7322
7659
|
const style = LEVEL_STYLE[e.level];
|
|
7323
7660
|
tag = (e.level.toUpperCase() + " ").slice(0, 5);
|
|
7324
7661
|
if (style) tag = paint(style, tag, color);
|
|
7325
|
-
body = style ? paint(style, e.message, color) : e.message;
|
|
7662
|
+
body = style ? paint(style, displayLines(e.message), color) : displayLines(e.message);
|
|
7326
7663
|
}
|
|
7327
7664
|
const client = e.source === "client" ? paint("35", "CLIENT", color) + " " : "";
|
|
7328
7665
|
return `${time} ${client}${tag} ${body}`;
|
|
@@ -7433,16 +7770,33 @@ var logs_default = defineCommand4({
|
|
|
7433
7770
|
}
|
|
7434
7771
|
};
|
|
7435
7772
|
const first = await fetchPage(initialSince, args.follow ? followLimit : limit, token);
|
|
7773
|
+
if (args.follow && args.json) {
|
|
7774
|
+
process.stdout.write(
|
|
7775
|
+
JSON.stringify({
|
|
7776
|
+
type: "ready",
|
|
7777
|
+
appId,
|
|
7778
|
+
window: windowLabel,
|
|
7779
|
+
count: first.events.length,
|
|
7780
|
+
retentionDays: LOG_RETENTION_DAYS
|
|
7781
|
+
}) + "\n"
|
|
7782
|
+
);
|
|
7783
|
+
}
|
|
7436
7784
|
print(first.events);
|
|
7437
7785
|
if (!args.follow) {
|
|
7438
7786
|
if (first.events.length === 0) {
|
|
7439
7787
|
if (args.json) {
|
|
7440
7788
|
process.stdout.write(
|
|
7441
|
-
JSON.stringify({
|
|
7789
|
+
JSON.stringify({
|
|
7790
|
+
type: "meta",
|
|
7791
|
+
count: 0,
|
|
7792
|
+
window: windowLabel,
|
|
7793
|
+
appId,
|
|
7794
|
+
retentionDays: LOG_RETENTION_DAYS
|
|
7795
|
+
}) + "\n"
|
|
7442
7796
|
);
|
|
7443
7797
|
} else {
|
|
7444
7798
|
console.log(
|
|
7445
|
-
`No logs in the last ${windowLabel} for ${appId}. New logs can take up to a minute to appear; retention is
|
|
7799
|
+
`No logs in the last ${windowLabel} for ${appId}. New logs can take up to a minute to appear; retention is ${LOG_RETENTION_DAYS} days.`
|
|
7446
7800
|
);
|
|
7447
7801
|
}
|
|
7448
7802
|
} else if (first.truncated) {
|
|
@@ -7606,7 +7960,7 @@ var usage_default = defineDeepspaceCommand({
|
|
|
7606
7960
|
});
|
|
7607
7961
|
|
|
7608
7962
|
// src/cli/commands/add.ts
|
|
7609
|
-
import { existsSync as
|
|
7963
|
+
import { existsSync as existsSync21, readFileSync as readFileSync19, realpathSync as realpathSync4 } from "fs";
|
|
7610
7964
|
import { dirname as dirname10, join as join22, resolve as resolve17 } from "path";
|
|
7611
7965
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
7612
7966
|
|
|
@@ -7614,7 +7968,7 @@ import { pathToFileURL as pathToFileURL3 } from "url";
|
|
|
7614
7968
|
import {
|
|
7615
7969
|
closeSync,
|
|
7616
7970
|
copyFileSync,
|
|
7617
|
-
existsSync as
|
|
7971
|
+
existsSync as existsSync20,
|
|
7618
7972
|
fstatSync,
|
|
7619
7973
|
mkdirSync as mkdirSync7,
|
|
7620
7974
|
mkdtempSync,
|
|
@@ -7634,7 +7988,7 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
|
7634
7988
|
let dir = dirname9(fileURLToPath(moduleUrl));
|
|
7635
7989
|
for (; ; ) {
|
|
7636
7990
|
const packagePath = join21(dir, "package.json");
|
|
7637
|
-
if (
|
|
7991
|
+
if (existsSync20(packagePath)) {
|
|
7638
7992
|
try {
|
|
7639
7993
|
const pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
|
|
7640
7994
|
if (pkg2.name === "deepspace") return dir;
|
|
@@ -7647,11 +8001,11 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
|
7647
8001
|
}
|
|
7648
8002
|
}
|
|
7649
8003
|
function hasFeatureCatalog(packageRoot = featurePackageRoot()) {
|
|
7650
|
-
return
|
|
8004
|
+
return existsSync20(join21(packageRoot, "features"));
|
|
7651
8005
|
}
|
|
7652
8006
|
function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
7653
8007
|
const configPath = join21(packageRoot, "features", id, "feature.json");
|
|
7654
|
-
if (!
|
|
8008
|
+
if (!existsSync20(configPath)) return null;
|
|
7655
8009
|
try {
|
|
7656
8010
|
const config = JSON.parse(readFileSync18(configPath, "utf-8"));
|
|
7657
8011
|
if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
|
|
@@ -7664,7 +8018,7 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
|
7664
8018
|
}
|
|
7665
8019
|
function listFeatureConfigs(packageRoot = featurePackageRoot()) {
|
|
7666
8020
|
const featuresDir = join21(packageRoot, "features");
|
|
7667
|
-
if (!
|
|
8021
|
+
if (!existsSync20(featuresDir)) return [];
|
|
7668
8022
|
return readdirSync7(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readFeatureConfig(entry.name, packageRoot)).filter((config) => config !== null && !config.internal).sort((a, b) => a.id.localeCompare(b.id));
|
|
7669
8023
|
}
|
|
7670
8024
|
function featureCatalog(packageRoot = featurePackageRoot()) {
|
|
@@ -7734,9 +8088,9 @@ function installFiles(packageRoot, config, targetDir, emit) {
|
|
|
7734
8088
|
const srcPath = join21(packageRoot, "features", config.id, file.src);
|
|
7735
8089
|
const resolvedDest = resolveFeatureDestPath(file.dest, config.route);
|
|
7736
8090
|
const destPath = join21(targetDir, resolvedDest);
|
|
7737
|
-
const existed =
|
|
8091
|
+
const existed = existsSync20(destPath);
|
|
7738
8092
|
if (existed) preexisting.push(resolvedDest);
|
|
7739
|
-
if (!
|
|
8093
|
+
if (!existsSync20(srcPath)) {
|
|
7740
8094
|
emit(` Warning: source not found: ${file.src}`, "stderr");
|
|
7741
8095
|
if (!existed) missing.push(resolvedDest);
|
|
7742
8096
|
continue;
|
|
@@ -7774,7 +8128,7 @@ function unresolvedSchema(schema, reason, emit) {
|
|
|
7774
8128
|
function integrateSchema(config, targetDir, emit) {
|
|
7775
8129
|
if (!config.schema) return [];
|
|
7776
8130
|
const schemasPath = join21(targetDir, "src", "schemas.ts");
|
|
7777
|
-
if (!
|
|
8131
|
+
if (!existsSync20(schemasPath)) {
|
|
7778
8132
|
return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
|
|
7779
8133
|
}
|
|
7780
8134
|
let content = readFileSync18(schemasPath, "utf-8");
|
|
@@ -7831,7 +8185,7 @@ function unresolvedActions(actions, reason, emit) {
|
|
|
7831
8185
|
function integrateActions(config, targetDir, emit) {
|
|
7832
8186
|
if (!config.actions?.length) return [];
|
|
7833
8187
|
const actionsPath = join21(targetDir, "src", "actions", "index.ts");
|
|
7834
|
-
if (!
|
|
8188
|
+
if (!existsSync20(actionsPath)) {
|
|
7835
8189
|
return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
|
|
7836
8190
|
}
|
|
7837
8191
|
let content = readFileSync18(actionsPath, "utf-8");
|
|
@@ -7881,7 +8235,7 @@ function integrateCode(config, targetDir, emit) {
|
|
|
7881
8235
|
const unresolved = [];
|
|
7882
8236
|
for (const insertion of config.code ?? []) {
|
|
7883
8237
|
const targetPath = join21(targetDir, insertion.file);
|
|
7884
|
-
if (!
|
|
8238
|
+
if (!existsSync20(targetPath)) {
|
|
7885
8239
|
unresolved.push({
|
|
7886
8240
|
area: "code",
|
|
7887
8241
|
reason: `${insertion.file} was not found.`,
|
|
@@ -7927,7 +8281,7 @@ ${content.slice(
|
|
|
7927
8281
|
function integrateIgnore(config, targetDir) {
|
|
7928
8282
|
if (!config.ignore?.length) return;
|
|
7929
8283
|
const targetPath = join21(targetDir, ".gitignore");
|
|
7930
|
-
const current =
|
|
8284
|
+
const current = existsSync20(targetPath) ? readFileSync18(targetPath, "utf-8") : "";
|
|
7931
8285
|
const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
|
|
7932
8286
|
const missing = config.ignore.filter((pattern) => !present.has(pattern));
|
|
7933
8287
|
if (missing.length === 0) return;
|
|
@@ -7944,7 +8298,7 @@ function integrateWrangler(config, targetDir, emit) {
|
|
|
7944
8298
|
...runWorkerFirst ? ["Set assets.run_worker_first = true in wrangler.toml."] : [],
|
|
7945
8299
|
...rateLimits.map((binding) => `Add the ${binding.name} rate-limit binding to wrangler.toml.`)
|
|
7946
8300
|
];
|
|
7947
|
-
if (!
|
|
8301
|
+
if (!existsSync20(targetPath)) {
|
|
7948
8302
|
return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
|
|
7949
8303
|
}
|
|
7950
8304
|
let source = readFileSync18(targetPath, "utf-8");
|
|
@@ -8024,7 +8378,7 @@ function rateLimitBindingBlock(scope, binding) {
|
|
|
8024
8378
|
function integrateCss(packageRoot, config, targetDir, emit) {
|
|
8025
8379
|
if (!config.css?.length) return [];
|
|
8026
8380
|
const stylesPath = join21(targetDir, "src", "styles.css");
|
|
8027
|
-
if (!
|
|
8381
|
+
if (!existsSync20(stylesPath)) {
|
|
8028
8382
|
emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
|
|
8029
8383
|
return [
|
|
8030
8384
|
{
|
|
@@ -8041,7 +8395,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
8041
8395
|
const unresolved = [];
|
|
8042
8396
|
for (const cssFile of config.css) {
|
|
8043
8397
|
const srcPath = join21(packageRoot, "features", config.id, cssFile);
|
|
8044
|
-
if (!
|
|
8398
|
+
if (!existsSync20(srcPath)) {
|
|
8045
8399
|
emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
|
|
8046
8400
|
unresolved.push({
|
|
8047
8401
|
area: "css",
|
|
@@ -8085,7 +8439,7 @@ function routeRolesSuffix(route) {
|
|
|
8085
8439
|
function integrateRoute(config, targetDir, emit) {
|
|
8086
8440
|
if (!config.route) return [];
|
|
8087
8441
|
const navPath = join21(targetDir, "src", "nav.ts");
|
|
8088
|
-
if (!
|
|
8442
|
+
if (!existsSync20(navPath)) {
|
|
8089
8443
|
return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
|
|
8090
8444
|
}
|
|
8091
8445
|
let content = readFileSync18(navPath, "utf-8");
|
|
@@ -8163,7 +8517,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
|
|
|
8163
8517
|
return { added: [], installRan: false, packageManager };
|
|
8164
8518
|
}
|
|
8165
8519
|
const packagePath = join21(targetDir, "package.json");
|
|
8166
|
-
if (!
|
|
8520
|
+
if (!existsSync20(packagePath)) {
|
|
8167
8521
|
emit(" Could not patch package.json (file not found)");
|
|
8168
8522
|
return { added: [], installRan: false, packageManager };
|
|
8169
8523
|
}
|
|
@@ -8241,7 +8595,7 @@ function dependencySnapshot(targetDir) {
|
|
|
8241
8595
|
function installedNavFeatures(packageRoot, targetDir) {
|
|
8242
8596
|
return listFeatureConfigs(packageRoot).filter((feature) => feature.category === "nav" && feature.files.length > 0).filter((feature) => {
|
|
8243
8597
|
const signature = resolveFeatureDestPath(feature.files[0].dest, feature.route);
|
|
8244
|
-
return
|
|
8598
|
+
return existsSync20(join21(targetDir, signature));
|
|
8245
8599
|
}).map((feature) => feature.id);
|
|
8246
8600
|
}
|
|
8247
8601
|
function warnNavConflicts(packageRoot, config, targetDir, emit) {
|
|
@@ -8474,9 +8828,9 @@ function renderFeatureInfo(config) {
|
|
|
8474
8828
|
return lines.join("\n");
|
|
8475
8829
|
}
|
|
8476
8830
|
function isDeepSpaceApp(dir) {
|
|
8477
|
-
if (
|
|
8831
|
+
if (existsSync21(join22(dir, "wrangler.toml"))) return true;
|
|
8478
8832
|
const packagePath = join22(dir, "package.json");
|
|
8479
|
-
if (!
|
|
8833
|
+
if (!existsSync21(packagePath)) return false;
|
|
8480
8834
|
try {
|
|
8481
8835
|
const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
|
|
8482
8836
|
return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
|
|
@@ -8488,7 +8842,7 @@ function installedPackageRoot(fromDir) {
|
|
|
8488
8842
|
let dir = fromDir;
|
|
8489
8843
|
for (; ; ) {
|
|
8490
8844
|
const candidate = join22(dir, "node_modules", "deepspace");
|
|
8491
|
-
if (
|
|
8845
|
+
if (existsSync21(join22(candidate, "package.json"))) return candidate;
|
|
8492
8846
|
const parent = dirname10(dir);
|
|
8493
8847
|
if (parent === dir) return null;
|
|
8494
8848
|
dir = parent;
|
|
@@ -8507,7 +8861,7 @@ async function targetInstaller(appDir) {
|
|
|
8507
8861
|
}
|
|
8508
8862
|
const entry = join22(installedRoot, "dist", "feature-installer.js");
|
|
8509
8863
|
const catalog = join22(installedRoot, "features");
|
|
8510
|
-
if (!
|
|
8864
|
+
if (!existsSync21(entry) || !existsSync21(catalog)) {
|
|
8511
8865
|
throw new Refusal(
|
|
8512
8866
|
`The app's installed deepspace package does not contain the typed feature installer.
|
|
8513
8867
|
Expected: ${entry}
|
|
@@ -8600,7 +8954,7 @@ var add_default = defineDeepspaceCommand({
|
|
|
8600
8954
|
return { data: { features: featureCatalog(root) } };
|
|
8601
8955
|
}
|
|
8602
8956
|
const appDir = resolve17(args.dir ?? ".");
|
|
8603
|
-
if (!
|
|
8957
|
+
if (!existsSync21(appDir)) {
|
|
8604
8958
|
throw new Refusal(
|
|
8605
8959
|
`Target directory not found: ${appDir}
|
|
8606
8960
|
Create a new app first: deepspace app create <name>`,
|
|
@@ -8976,7 +9330,7 @@ var domain_default = defineCommand5({
|
|
|
8976
9330
|
// src/cli/commands/app-files.ts
|
|
8977
9331
|
import { defineCommand as defineCommand6 } from "citty";
|
|
8978
9332
|
import { basename as basename4, resolve as resolve18 } from "path";
|
|
8979
|
-
import { existsSync as
|
|
9333
|
+
import { existsSync as existsSync22, statSync as statSync2 } from "fs";
|
|
8980
9334
|
|
|
8981
9335
|
// src/cli/lib/app-files-api.ts
|
|
8982
9336
|
import { createWriteStream, openAsBlob, statSync } from "fs";
|
|
@@ -9255,7 +9609,7 @@ var put = defineDeepspaceCommand({
|
|
|
9255
9609
|
},
|
|
9256
9610
|
async run({ args }) {
|
|
9257
9611
|
const localPath = resolve18(String(args.file));
|
|
9258
|
-
if (!
|
|
9612
|
+
if (!existsSync22(localPath) || !statSync2(localPath).isFile()) {
|
|
9259
9613
|
throw new Refusal(`No such file: ${args.file}`, "file_not_found");
|
|
9260
9614
|
}
|
|
9261
9615
|
const key = requireKey(args.key ?? basename4(localPath));
|
|
@@ -9428,8 +9782,8 @@ var list4 = defineDeepspaceCommand({
|
|
|
9428
9782
|
}
|
|
9429
9783
|
if (pending.length) {
|
|
9430
9784
|
console.log(`PENDING INVITES ON ${app2}`);
|
|
9431
|
-
for (const
|
|
9432
|
-
console.log(` ${
|
|
9785
|
+
for (const p36 of pending) {
|
|
9786
|
+
console.log(` ${p36.email} (expires ${new Date(p36.expiresAt).toLocaleDateString()})`);
|
|
9433
9787
|
}
|
|
9434
9788
|
}
|
|
9435
9789
|
}
|
|
@@ -9535,7 +9889,7 @@ var cancel3 = defineDeepspaceCommand({
|
|
|
9535
9889
|
`/api/app-collaborators/${encodeURIComponent(app2)}`
|
|
9536
9890
|
);
|
|
9537
9891
|
const target2 = email.trim().toLowerCase();
|
|
9538
|
-
const found = pending.find((
|
|
9892
|
+
const found = pending.find((p36) => p36.email.toLowerCase() === target2);
|
|
9539
9893
|
if (!found) {
|
|
9540
9894
|
throw new Refusal(`${email} has no pending invite on ${app2}`, "no_pending_invite", {
|
|
9541
9895
|
action: cliAction("deepspace", "app", "collaborators", "list", "--app", app2)
|
|
@@ -10137,7 +10491,7 @@ var integrations_default = defineCommand9({
|
|
|
10137
10491
|
|
|
10138
10492
|
// src/cli/commands/feedback.ts
|
|
10139
10493
|
import * as p16 from "@clack/prompts";
|
|
10140
|
-
import { existsSync as
|
|
10494
|
+
import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
|
|
10141
10495
|
import { dirname as dirname11, join as join23 } from "path";
|
|
10142
10496
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10143
10497
|
import { release } from "os";
|
|
@@ -10165,7 +10519,7 @@ function readCliVersion() {
|
|
|
10165
10519
|
join23(here, "..", "..", "..", "package.json")
|
|
10166
10520
|
];
|
|
10167
10521
|
for (const path of candidates) {
|
|
10168
|
-
if (!
|
|
10522
|
+
if (!existsSync23(path)) continue;
|
|
10169
10523
|
const pkg2 = JSON.parse(readFileSync21(path, "utf-8"));
|
|
10170
10524
|
if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
|
|
10171
10525
|
}
|
|
@@ -10362,6 +10716,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
|
|
|
10362
10716
|
|
|
10363
10717
|
// src/cli/commands/secrets.ts
|
|
10364
10718
|
import { defineCommand as defineCommand10 } from "citty";
|
|
10719
|
+
import * as p17 from "@clack/prompts";
|
|
10365
10720
|
import { readFileSync as readFileSync22 } from "fs";
|
|
10366
10721
|
|
|
10367
10722
|
// src/cli/lib/citty-args.ts
|
|
@@ -10429,9 +10784,6 @@ async function resolveTarget(args) {
|
|
|
10429
10784
|
const appId = await resolveAppTarget(DEPLOY_URL11, token, args.app, { wranglerEnv });
|
|
10430
10785
|
return { appId, configName, token };
|
|
10431
10786
|
}
|
|
10432
|
-
function fail(err) {
|
|
10433
|
-
renderCliError(err);
|
|
10434
|
-
}
|
|
10435
10787
|
function ok(json, data, human) {
|
|
10436
10788
|
if (json) console.log(JSON.stringify({ ok: true, ...data }));
|
|
10437
10789
|
else human();
|
|
@@ -10457,22 +10809,18 @@ var list6 = defineCommand10({
|
|
|
10457
10809
|
}
|
|
10458
10810
|
},
|
|
10459
10811
|
async run({ args }) {
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
console.log(args["only-names"] ? s.key : `${s.key} (v${s.version}, ${s.updatedAt})`);
|
|
10473
|
-
}
|
|
10474
|
-
} catch (err) {
|
|
10475
|
-
fail(err);
|
|
10812
|
+
const t = await resolveTarget(args);
|
|
10813
|
+
const { secrets } = await listSecrets(DEPLOY_URL11, t.token, t.appId, t.configName);
|
|
10814
|
+
if (args.json) {
|
|
10815
|
+
console.log(JSON.stringify({ ok: true, appId: t.appId, config: t.configName, secrets }));
|
|
10816
|
+
return;
|
|
10817
|
+
}
|
|
10818
|
+
if (secrets.length === 0) {
|
|
10819
|
+
console.log(`No secrets in ${t.configName}.`);
|
|
10820
|
+
return;
|
|
10821
|
+
}
|
|
10822
|
+
for (const s of secrets) {
|
|
10823
|
+
console.log(args["only-names"] ? s.key : `${s.key} (v${s.version}, ${s.updatedAt})`);
|
|
10476
10824
|
}
|
|
10477
10825
|
}
|
|
10478
10826
|
});
|
|
@@ -10483,46 +10831,42 @@ var set = defineCommand10({
|
|
|
10483
10831
|
secret: { type: "positional", description: "KEY=value (repeatable)", required: true }
|
|
10484
10832
|
},
|
|
10485
10833
|
async run({ args }) {
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
`Warning: ${dupes.join(", ")} given more than once \u2014 kept the last value for each.`
|
|
10503
|
-
);
|
|
10504
|
-
}
|
|
10505
|
-
if (Object.keys(secrets).length === 1) {
|
|
10506
|
-
const [[k, v]] = Object.entries(secrets);
|
|
10507
|
-
await setSecret(DEPLOY_URL11, t.token, t.appId, t.configName, k, v);
|
|
10508
|
-
} else {
|
|
10509
|
-
await uploadSecrets(DEPLOY_URL11, t.token, t.appId, t.configName, secrets, false);
|
|
10510
|
-
}
|
|
10511
|
-
const n = Object.keys(secrets).length;
|
|
10512
|
-
ok(
|
|
10513
|
-
args.json === true,
|
|
10514
|
-
{ appId: t.appId, config: t.configName, set: Object.keys(secrets), ...APPLIES_AT_DEPLOY },
|
|
10515
|
-
() => {
|
|
10516
|
-
console.log(`Set ${n} secret${n === 1 ? "" : "s"} in ${t.configName}.`);
|
|
10517
|
-
if (!warnIfDebugRoutesEnabled(secrets)) {
|
|
10518
|
-
console.log(APPLY_HINT);
|
|
10519
|
-
console.log(LOCAL_DEV_HINT);
|
|
10520
|
-
}
|
|
10521
|
-
}
|
|
10834
|
+
const t = await resolveTarget(args);
|
|
10835
|
+
const pairs = dedupePositionals(args.secret, args._);
|
|
10836
|
+
const secrets = {};
|
|
10837
|
+
const dupes = [];
|
|
10838
|
+
for (const pair of pairs) {
|
|
10839
|
+
const eq = pair.indexOf("=");
|
|
10840
|
+
if (eq <= 0) throw new InputError(`Expected KEY=value, got "${pair}"`, "invalid_pair");
|
|
10841
|
+
const key = validateSecretName(pair.slice(0, eq));
|
|
10842
|
+
const value = pair.slice(eq + 1);
|
|
10843
|
+
validateSecretValue(key, value);
|
|
10844
|
+
if (key in secrets && !dupes.includes(key)) dupes.push(key);
|
|
10845
|
+
secrets[key] = value;
|
|
10846
|
+
}
|
|
10847
|
+
if (dupes.length) {
|
|
10848
|
+
console.warn(
|
|
10849
|
+
`Warning: ${dupes.join(", ")} given more than once \u2014 kept the last value for each.`
|
|
10522
10850
|
);
|
|
10523
|
-
} catch (err) {
|
|
10524
|
-
fail(err);
|
|
10525
10851
|
}
|
|
10852
|
+
if (Object.keys(secrets).length === 1) {
|
|
10853
|
+
const [[k, v]] = Object.entries(secrets);
|
|
10854
|
+
await setSecret(DEPLOY_URL11, t.token, t.appId, t.configName, k, v);
|
|
10855
|
+
} else {
|
|
10856
|
+
await uploadSecrets(DEPLOY_URL11, t.token, t.appId, t.configName, secrets, false);
|
|
10857
|
+
}
|
|
10858
|
+
const n = Object.keys(secrets).length;
|
|
10859
|
+
ok(
|
|
10860
|
+
args.json === true,
|
|
10861
|
+
{ appId: t.appId, config: t.configName, set: Object.keys(secrets), ...APPLIES_AT_DEPLOY },
|
|
10862
|
+
() => {
|
|
10863
|
+
console.log(`Set ${n} secret${n === 1 ? "" : "s"} in ${t.configName}.`);
|
|
10864
|
+
if (!warnIfDebugRoutesEnabled(secrets)) {
|
|
10865
|
+
console.log(APPLY_HINT);
|
|
10866
|
+
console.log(LOCAL_DEV_HINT);
|
|
10867
|
+
}
|
|
10868
|
+
}
|
|
10869
|
+
);
|
|
10526
10870
|
}
|
|
10527
10871
|
});
|
|
10528
10872
|
var get2 = defineCommand10({
|
|
@@ -10533,32 +10877,34 @@ var get2 = defineCommand10({
|
|
|
10533
10877
|
plain: { type: "boolean", description: "Print the plaintext value", default: false }
|
|
10534
10878
|
},
|
|
10535
10879
|
async run({ args }) {
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
const { value } = await getSecretPlain(DEPLOY_URL11, t.token, t.appId, t.configName, key);
|
|
10541
|
-
process.stdout.write(process.stdout.isTTY ? value + "\n" : value);
|
|
10542
|
-
return;
|
|
10543
|
-
}
|
|
10544
|
-
const { secrets } = await listSecrets(DEPLOY_URL11, t.token, t.appId, t.configName);
|
|
10545
|
-
const item = secrets.find((s) => s.key === key);
|
|
10546
|
-
if (!item)
|
|
10547
|
-
throw new InputError(`Secret "${key}" not found in ${t.configName}`, "secret_not_found");
|
|
10548
|
-
ok(
|
|
10549
|
-
args.json === true,
|
|
10550
|
-
{
|
|
10551
|
-
appId: t.appId,
|
|
10552
|
-
config: t.configName,
|
|
10553
|
-
key: item.key,
|
|
10554
|
-
version: item.version,
|
|
10555
|
-
updatedAt: item.updatedAt
|
|
10556
|
-
},
|
|
10557
|
-
() => console.log(`${item.key} (v${item.version}, ${item.updatedAt})`)
|
|
10880
|
+
if (args.plain && args.json === true) {
|
|
10881
|
+
throw new InputError(
|
|
10882
|
+
"--plain writes the raw secret value, which is not JSON. Use --plain to capture the value, or --json for the metadata envelope \u2014 not both.",
|
|
10883
|
+
"invalid_flags"
|
|
10558
10884
|
);
|
|
10559
|
-
} catch (err) {
|
|
10560
|
-
fail(err);
|
|
10561
10885
|
}
|
|
10886
|
+
const t = await resolveTarget(args);
|
|
10887
|
+
const key = validateSecretName(args.key);
|
|
10888
|
+
if (args.plain) {
|
|
10889
|
+
const { value } = await getSecretPlain(DEPLOY_URL11, t.token, t.appId, t.configName, key);
|
|
10890
|
+
process.stdout.write(process.stdout.isTTY ? value + "\n" : value);
|
|
10891
|
+
return;
|
|
10892
|
+
}
|
|
10893
|
+
const { secrets } = await listSecrets(DEPLOY_URL11, t.token, t.appId, t.configName);
|
|
10894
|
+
const item = secrets.find((s) => s.key === key);
|
|
10895
|
+
if (!item)
|
|
10896
|
+
throw new InputError(`Secret "${key}" not found in ${t.configName}`, "secret_not_found");
|
|
10897
|
+
ok(
|
|
10898
|
+
args.json === true,
|
|
10899
|
+
{
|
|
10900
|
+
appId: t.appId,
|
|
10901
|
+
config: t.configName,
|
|
10902
|
+
key: item.key,
|
|
10903
|
+
version: item.version,
|
|
10904
|
+
updatedAt: item.updatedAt
|
|
10905
|
+
},
|
|
10906
|
+
() => console.log(`${item.key} (v${item.version}, ${item.updatedAt})`)
|
|
10907
|
+
);
|
|
10562
10908
|
}
|
|
10563
10909
|
});
|
|
10564
10910
|
var del2 = defineCommand10({
|
|
@@ -10568,36 +10914,33 @@ var del2 = defineCommand10({
|
|
|
10568
10914
|
key: { type: "positional", description: "Secret name (repeatable)", required: true }
|
|
10569
10915
|
},
|
|
10570
10916
|
async run({ args }) {
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
}
|
|
10917
|
+
const t = await resolveTarget(args);
|
|
10918
|
+
const keys = dedupePositionals(args.key, args._);
|
|
10919
|
+
let deleted = 0;
|
|
10920
|
+
let absent = 0;
|
|
10921
|
+
for (const key of keys) {
|
|
10922
|
+
const name = validateSecretName(key);
|
|
10923
|
+
try {
|
|
10924
|
+
const result = await deleteSecret(DEPLOY_URL11, t.token, t.appId, t.configName, name);
|
|
10925
|
+
if (result.deleted === false) absent++;
|
|
10926
|
+
else deleted++;
|
|
10927
|
+
} catch (err) {
|
|
10928
|
+
if (err instanceof ApiError && err.status === 404 && err.code !== "unrecognized_service") {
|
|
10929
|
+
absent++;
|
|
10930
|
+
} else throw err;
|
|
10586
10931
|
}
|
|
10587
|
-
const note3 = absent > 0 ? ` (${absent} already absent)` : "";
|
|
10588
|
-
ok(
|
|
10589
|
-
args.json === true,
|
|
10590
|
-
{ appId: t.appId, config: t.configName, deleted, absent, ...APPLIES_AT_DEPLOY },
|
|
10591
|
-
() => {
|
|
10592
|
-
console.log(
|
|
10593
|
-
`Deleted ${deleted} secret${deleted === 1 ? "" : "s"} from ${t.configName}.${note3}`
|
|
10594
|
-
);
|
|
10595
|
-
if (deleted > 0) console.log(APPLY_HINT);
|
|
10596
|
-
}
|
|
10597
|
-
);
|
|
10598
|
-
} catch (err) {
|
|
10599
|
-
fail(err);
|
|
10600
10932
|
}
|
|
10933
|
+
const note3 = absent > 0 ? ` (${absent} already absent)` : "";
|
|
10934
|
+
ok(
|
|
10935
|
+
args.json === true,
|
|
10936
|
+
{ appId: t.appId, config: t.configName, deleted, absent, ...APPLIES_AT_DEPLOY },
|
|
10937
|
+
() => {
|
|
10938
|
+
console.log(
|
|
10939
|
+
`Deleted ${deleted} secret${deleted === 1 ? "" : "s"} from ${t.configName}.${note3}`
|
|
10940
|
+
);
|
|
10941
|
+
if (deleted > 0) console.log(APPLY_HINT);
|
|
10942
|
+
}
|
|
10943
|
+
);
|
|
10601
10944
|
}
|
|
10602
10945
|
});
|
|
10603
10946
|
var upload = defineCommand10({
|
|
@@ -10616,30 +10959,26 @@ var upload = defineCommand10({
|
|
|
10616
10959
|
}
|
|
10617
10960
|
},
|
|
10618
10961
|
async run({ args }) {
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
(
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
);
|
|
10640
|
-
} catch (err) {
|
|
10641
|
-
fail(err);
|
|
10642
|
-
}
|
|
10962
|
+
const t = await resolveTarget(args);
|
|
10963
|
+
const secrets = parseSecretsUpload(await readUploadSource(String(args.file)));
|
|
10964
|
+
if (Object.keys(secrets).length === 0)
|
|
10965
|
+
throw new InputError("No secrets found in the input.", "empty_input");
|
|
10966
|
+
await uploadSecrets(DEPLOY_URL11, t.token, t.appId, t.configName, secrets, args.replace);
|
|
10967
|
+
const uploaded = Object.keys(secrets);
|
|
10968
|
+
ok(
|
|
10969
|
+
args.json === true,
|
|
10970
|
+
{
|
|
10971
|
+
appId: t.appId,
|
|
10972
|
+
config: t.configName,
|
|
10973
|
+
uploaded,
|
|
10974
|
+
replaced: args.replace === true,
|
|
10975
|
+
...APPLIES_AT_DEPLOY
|
|
10976
|
+
},
|
|
10977
|
+
() => {
|
|
10978
|
+
console.log(`Uploaded ${uploaded.length} secrets to ${t.configName}.`);
|
|
10979
|
+
if (!warnIfDebugRoutesEnabled(secrets)) console.log(APPLY_HINT);
|
|
10980
|
+
}
|
|
10981
|
+
);
|
|
10643
10982
|
}
|
|
10644
10983
|
});
|
|
10645
10984
|
async function readUploadSource(file) {
|
|
@@ -10655,10 +10994,11 @@ async function readUploadSource(file) {
|
|
|
10655
10994
|
try {
|
|
10656
10995
|
return readFileSync22(file, "utf-8");
|
|
10657
10996
|
} catch (err) {
|
|
10658
|
-
|
|
10997
|
+
const errno = err.code;
|
|
10998
|
+
if (typeof errno !== "string" || errno.length === 0) throw err;
|
|
10659
10999
|
throw new InputError(
|
|
10660
|
-
`No such file: ${file}. Pass the path of a dotenv or JSON file to upload, or \`-\` to read it from stdin.`,
|
|
10661
|
-
"file_not_found"
|
|
11000
|
+
errno === "ENOENT" ? `No such file: ${file}. Pass the path of a dotenv or JSON file to upload, or \`-\` to read it from stdin.` : `Cannot read ${file} (${errno}).`,
|
|
11001
|
+
errno === "ENOENT" ? "file_not_found" : "file_unreadable"
|
|
10662
11002
|
);
|
|
10663
11003
|
}
|
|
10664
11004
|
}
|
|
@@ -10666,6 +11006,17 @@ var download = defineCommand10({
|
|
|
10666
11006
|
meta: { name: "download", description: "Download a config\u2019s secrets (dotenv/json/shell)" },
|
|
10667
11007
|
args: {
|
|
10668
11008
|
...COMMON_ARGS,
|
|
11009
|
+
// `download` writes a FILE FORMAT to stdout, so `--json` has no meaning
|
|
11010
|
+
// here and is REFUSED rather than described: rewording it was not enough,
|
|
11011
|
+
// because the flag still silently emitted dotenv — zero JSON documents on
|
|
11012
|
+
// a success path, with the plaintext secrets themselves as the output a
|
|
11013
|
+
// parser choked on. `secrets get --plain --json` already refuses the same
|
|
11014
|
+
// contradiction.
|
|
11015
|
+
json: {
|
|
11016
|
+
type: "boolean",
|
|
11017
|
+
description: "Not supported here \u2014 secrets are a stream; use --format json for JSON output",
|
|
11018
|
+
default: false
|
|
11019
|
+
},
|
|
10669
11020
|
format: {
|
|
10670
11021
|
type: "string",
|
|
10671
11022
|
description: "dotenv (default) | json | shell",
|
|
@@ -10673,79 +11024,73 @@ var download = defineCommand10({
|
|
|
10673
11024
|
}
|
|
10674
11025
|
},
|
|
10675
11026
|
async run({ args }) {
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
11027
|
+
if (args.json === true) {
|
|
11028
|
+
throw new InputError(
|
|
11029
|
+
"--json is not supported by `secrets download`: it writes the secrets themselves to stdout, not an envelope. Use `--format json` for JSON-shaped secrets, or `secrets list --json` for the metadata envelope.",
|
|
11030
|
+
"invalid_flags"
|
|
11031
|
+
);
|
|
11032
|
+
}
|
|
11033
|
+
const t = await resolveTarget(args);
|
|
11034
|
+
const format = args.format;
|
|
11035
|
+
if (!["dotenv", "json", "shell"].includes(format)) {
|
|
11036
|
+
throw new InputError(
|
|
11037
|
+
`Unknown format "${args.format}" \u2014 use dotenv, json, or shell.`,
|
|
11038
|
+
"invalid_format"
|
|
11039
|
+
);
|
|
10689
11040
|
}
|
|
11041
|
+
const { secrets } = await fetchSecretsValues(DEPLOY_URL11, t.token, t.appId, t.configName);
|
|
11042
|
+
process.stdout.write(formatSecretsDownload(secrets, format));
|
|
10690
11043
|
}
|
|
10691
11044
|
});
|
|
10692
11045
|
var pull = defineCommand10({
|
|
10693
11046
|
meta: { name: "pull", description: "Refresh the .dev.vars cache from the app store" },
|
|
10694
11047
|
args: { ...COMMON_ARGS },
|
|
10695
11048
|
async run({ args }) {
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
"not_in_app_repo"
|
|
10703
|
-
);
|
|
10704
|
-
const t = await resolveTarget(args);
|
|
10705
|
-
const ownerId = decodeJwtPayload(t.token).sub;
|
|
10706
|
-
const refreshed = await refreshSecretsCache(
|
|
10707
|
-
DEPLOY_URL11,
|
|
10708
|
-
t.token,
|
|
10709
|
-
t.appId,
|
|
10710
|
-
wranglerEnv,
|
|
10711
|
-
t.configName
|
|
10712
|
-
);
|
|
10713
|
-
await writeDevVars(appDir, ownerId, t.token, wranglerEnv, {
|
|
10714
|
-
appId: t.appId,
|
|
10715
|
-
generatedSecretsCache: refreshed.rendered
|
|
10716
|
-
});
|
|
10717
|
-
const count = Object.keys(refreshed.pulled?.values ?? {}).length;
|
|
10718
|
-
ok(
|
|
10719
|
-
args.json === true,
|
|
10720
|
-
{ appId: t.appId, config: t.configName, pulled: count },
|
|
10721
|
-
() => console.log(
|
|
10722
|
-
refreshed.pulled ? `Pulled ${count} secrets (${t.configName}) into .dev.vars.` : `Config ${t.configName} does not exist yet; regenerated .dev.vars without app secrets.`
|
|
10723
|
-
)
|
|
11049
|
+
const wranglerEnv = args.env?.trim() || void 0;
|
|
11050
|
+
const appDir = findAppDir();
|
|
11051
|
+
if (!appDir)
|
|
11052
|
+
throw new InputError(
|
|
11053
|
+
"Run from a DeepSpace app directory (one containing wrangler.toml).",
|
|
11054
|
+
"not_in_app_repo"
|
|
10724
11055
|
);
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
|
|
11056
|
+
const t = await resolveTarget(args);
|
|
11057
|
+
const ownerId = decodeJwtPayload(t.token).sub;
|
|
11058
|
+
const refreshed = await refreshSecretsCache(
|
|
11059
|
+
DEPLOY_URL11,
|
|
11060
|
+
t.token,
|
|
11061
|
+
t.appId,
|
|
11062
|
+
wranglerEnv,
|
|
11063
|
+
t.configName
|
|
11064
|
+
);
|
|
11065
|
+
await writeDevVars(appDir, ownerId, t.token, wranglerEnv, {
|
|
11066
|
+
appId: t.appId,
|
|
11067
|
+
generatedSecretsCache: refreshed.rendered
|
|
11068
|
+
});
|
|
11069
|
+
const count = Object.keys(refreshed.pulled?.values ?? {}).length;
|
|
11070
|
+
ok(
|
|
11071
|
+
args.json === true,
|
|
11072
|
+
{ appId: t.appId, config: t.configName, pulled: count },
|
|
11073
|
+
() => console.log(
|
|
11074
|
+
refreshed.pulled ? `Pulled ${count} secrets (${t.configName}) into .dev.vars.` : `Config ${t.configName} does not exist yet; regenerated .dev.vars without app secrets.`
|
|
11075
|
+
)
|
|
11076
|
+
);
|
|
10728
11077
|
}
|
|
10729
11078
|
});
|
|
10730
11079
|
var configsList = defineCommand10({
|
|
10731
11080
|
meta: { name: "list", description: "List the app\u2019s configs" },
|
|
10732
11081
|
args: { ...COMMON_ARGS },
|
|
10733
11082
|
async run({ args }) {
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
|
|
10740
|
-
|
|
10741
|
-
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
});
|
|
10746
|
-
} catch (err) {
|
|
10747
|
-
fail(err);
|
|
10748
|
-
}
|
|
11083
|
+
const t = await resolveTarget(args);
|
|
11084
|
+
const { configs: configs2 } = await listConfigs(DEPLOY_URL11, t.token, t.appId);
|
|
11085
|
+
ok(args.json === true, { appId: t.appId, configs: configs2 }, () => {
|
|
11086
|
+
if (configs2.length === 0) {
|
|
11087
|
+
console.log("No configs yet \u2014 the first `secrets set` creates one.");
|
|
11088
|
+
return;
|
|
11089
|
+
}
|
|
11090
|
+
for (const cfg of configs2) {
|
|
11091
|
+
console.log(`${cfg.name} (${cfg.secretCount ?? 0} secrets, updated ${cfg.updatedAt})`);
|
|
11092
|
+
}
|
|
11093
|
+
});
|
|
10749
11094
|
}
|
|
10750
11095
|
});
|
|
10751
11096
|
var configsCreate = defineCommand10({
|
|
@@ -10756,55 +11101,80 @@ var configsCreate = defineCommand10({
|
|
|
10756
11101
|
"copy-from": { type: "string", description: "Copy secrets from this config", required: false }
|
|
10757
11102
|
},
|
|
10758
11103
|
async run({ args }) {
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
if (
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
"config_exists"
|
|
10769
|
-
);
|
|
10770
|
-
}
|
|
10771
|
-
ok(
|
|
10772
|
-
args.json === true,
|
|
10773
|
-
{ appId: t.appId, config: name, created: false },
|
|
10774
|
-
() => console.log(`Config ${name} already exists.`)
|
|
11104
|
+
const t = await resolveTarget(args);
|
|
11105
|
+
const name = validateConfigName(args.name);
|
|
11106
|
+
const copyFrom = args["copy-from"] || void 0;
|
|
11107
|
+
const { configs: configs2 } = await listConfigs(DEPLOY_URL11, t.token, t.appId);
|
|
11108
|
+
if (configs2.some((c) => c.name === name)) {
|
|
11109
|
+
if (copyFrom) {
|
|
11110
|
+
throw new InputError(
|
|
11111
|
+
`Config "${name}" already exists \u2014 refusing to copy "${copyFrom}" over it. Delete it first, or pick a new name.`,
|
|
11112
|
+
"config_exists"
|
|
10775
11113
|
);
|
|
10776
|
-
return;
|
|
10777
11114
|
}
|
|
10778
|
-
await createConfig(DEPLOY_URL11, t.token, t.appId, name, copyFrom);
|
|
10779
11115
|
ok(
|
|
10780
11116
|
args.json === true,
|
|
10781
|
-
{ appId: t.appId, config: name, created:
|
|
10782
|
-
() => console.log(`
|
|
11117
|
+
{ appId: t.appId, config: name, created: false },
|
|
11118
|
+
() => console.log(`Config ${name} already exists.`)
|
|
10783
11119
|
);
|
|
10784
|
-
|
|
10785
|
-
fail(err);
|
|
11120
|
+
return;
|
|
10786
11121
|
}
|
|
11122
|
+
await createConfig(DEPLOY_URL11, t.token, t.appId, name, copyFrom);
|
|
11123
|
+
ok(
|
|
11124
|
+
args.json === true,
|
|
11125
|
+
{ appId: t.appId, config: name, created: true, ...copyFrom ? { copyFrom } : {} },
|
|
11126
|
+
() => console.log(`Created ${name}.`)
|
|
11127
|
+
);
|
|
10787
11128
|
}
|
|
10788
11129
|
});
|
|
10789
11130
|
var configsDelete = defineCommand10({
|
|
10790
11131
|
meta: { name: "delete", description: "Delete a config and all of its secrets" },
|
|
10791
11132
|
args: {
|
|
10792
11133
|
...COMMON_ARGS,
|
|
10793
|
-
name: { type: "positional", description: "Config name", required: true }
|
|
11134
|
+
name: { type: "positional", description: "Config name", required: true },
|
|
11135
|
+
yes: {
|
|
11136
|
+
type: "boolean",
|
|
11137
|
+
alias: "y",
|
|
11138
|
+
description: "Skip the confirmation (required for --json / non-interactive)",
|
|
11139
|
+
default: false
|
|
11140
|
+
}
|
|
10794
11141
|
},
|
|
10795
11142
|
async run({ args }) {
|
|
10796
|
-
|
|
10797
|
-
|
|
10798
|
-
|
|
10799
|
-
|
|
10800
|
-
|
|
10801
|
-
|
|
10802
|
-
{ appId: t.appId, config: name, deleted: true },
|
|
10803
|
-
() => console.log(`Deleted ${name}.`)
|
|
11143
|
+
const t = await resolveTarget(args);
|
|
11144
|
+
const name = validateConfigName(args.name);
|
|
11145
|
+
if (args.yes !== true && (args.json === true || !process.stdin.isTTY)) {
|
|
11146
|
+
throw new InputError(
|
|
11147
|
+
`Deleting config ${name} removes every secret in it permanently. Re-run with --yes to confirm.`,
|
|
11148
|
+
"confirmation_required"
|
|
10804
11149
|
);
|
|
10805
|
-
} catch (err) {
|
|
10806
|
-
fail(err);
|
|
10807
11150
|
}
|
|
11151
|
+
let secrets = [];
|
|
11152
|
+
let counted = true;
|
|
11153
|
+
try {
|
|
11154
|
+
secrets = (await listSecrets(DEPLOY_URL11, t.token, t.appId, name)).secrets;
|
|
11155
|
+
} catch (error) {
|
|
11156
|
+
if (args.yes !== true) throw error;
|
|
11157
|
+
counted = false;
|
|
11158
|
+
}
|
|
11159
|
+
const count = secrets.length;
|
|
11160
|
+
if (args.yes !== true) {
|
|
11161
|
+
const detail = count === 0 ? `Config ${name} is empty.` : `Config ${name} holds ${count} secret${count === 1 ? "" : "s"}: ${secrets.map((secret) => secret.key).slice(0, 8).join(", ")}${count > 8 ? `, \u2026` : ""}.`;
|
|
11162
|
+
const confirmed = await p17.confirm({
|
|
11163
|
+
message: `${detail} Deleting it removes ${count === 1 ? "that secret" : "them all"} permanently.`,
|
|
11164
|
+
initialValue: false
|
|
11165
|
+
});
|
|
11166
|
+
if (p17.isCancel(confirmed) || !confirmed) {
|
|
11167
|
+
throw new InputError("Cancelled.", "confirmation_required");
|
|
11168
|
+
}
|
|
11169
|
+
}
|
|
11170
|
+
await deleteConfig(DEPLOY_URL11, t.token, t.appId, name);
|
|
11171
|
+
ok(
|
|
11172
|
+
args.json === true,
|
|
11173
|
+
{ appId: t.appId, config: name, deleted: true, secretsDeleted: counted ? count : null },
|
|
11174
|
+
() => console.log(
|
|
11175
|
+
counted ? `Deleted ${name} and ${count} secret${count === 1 ? "" : "s"}.` : `Deleted ${name}.`
|
|
11176
|
+
)
|
|
11177
|
+
);
|
|
10808
11178
|
}
|
|
10809
11179
|
});
|
|
10810
11180
|
var configs = defineCommand10({
|
|
@@ -10826,11 +11196,11 @@ var secrets_default = defineCommand10({
|
|
|
10826
11196
|
});
|
|
10827
11197
|
|
|
10828
11198
|
// src/cli/commands/init.ts
|
|
10829
|
-
import { existsSync as
|
|
11199
|
+
import { existsSync as existsSync24 } from "fs";
|
|
10830
11200
|
import { join as join24 } from "path";
|
|
10831
11201
|
var DEPLOY_URL12 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
10832
11202
|
function wranglerConfigUncommitted(appDir) {
|
|
10833
|
-
if (!
|
|
11203
|
+
if (!existsSync24(join24(appDir, ".git"))) return false;
|
|
10834
11204
|
const dryRun = runGit(appDir, ["commit", "--dry-run", "-m", "x", "--", "wrangler.toml"], {
|
|
10835
11205
|
allowFail: true
|
|
10836
11206
|
});
|
|
@@ -10838,7 +11208,7 @@ function wranglerConfigUncommitted(appDir) {
|
|
|
10838
11208
|
}
|
|
10839
11209
|
function commitScaffoldIfUnborn(appDir, token) {
|
|
10840
11210
|
try {
|
|
10841
|
-
if (!
|
|
11211
|
+
if (!existsSync24(join24(appDir, ".git"))) return false;
|
|
10842
11212
|
if (runGit(appDir, ["rev-parse", "--verify", "HEAD"], { allowFail: true }).status === 0) {
|
|
10843
11213
|
return false;
|
|
10844
11214
|
}
|
|
@@ -10989,8 +11359,8 @@ var init_default = defineDeepspaceCommand({
|
|
|
10989
11359
|
|
|
10990
11360
|
// src/cli/commands/status.ts
|
|
10991
11361
|
import { defineCommand as defineCommand11 } from "citty";
|
|
10992
|
-
import * as
|
|
10993
|
-
import { existsSync as
|
|
11362
|
+
import * as p18 from "@clack/prompts";
|
|
11363
|
+
import { existsSync as existsSync25, readFileSync as readFileSync23 } from "fs";
|
|
10994
11364
|
function timeAgo(iso) {
|
|
10995
11365
|
const ms = Date.now() - Date.parse(iso);
|
|
10996
11366
|
if (!Number.isFinite(ms) || ms < 0) return iso;
|
|
@@ -11002,6 +11372,7 @@ function timeAgo(iso) {
|
|
|
11002
11372
|
return `${Math.floor(h / 24)}d ago`;
|
|
11003
11373
|
}
|
|
11004
11374
|
var UNREACHABLE = "(unreachable)";
|
|
11375
|
+
var sessionError = (error) => ({ state: "unavailable", error, code: "not_authenticated" });
|
|
11005
11376
|
var observeRemote = (promise) => promise.then(
|
|
11006
11377
|
(value) => ({ ok: true, value }),
|
|
11007
11378
|
(error) => ({ ok: false, error })
|
|
@@ -11082,13 +11453,14 @@ var status_default = defineCommand11({
|
|
|
11082
11453
|
]);
|
|
11083
11454
|
if (urlOverrides.length > 0) json.urlOverrides = urlOverrides;
|
|
11084
11455
|
}
|
|
11085
|
-
facts.loggedIn =
|
|
11456
|
+
facts.loggedIn = existsSync25(SESSION_PATH) || existsSync25(TOKEN_PATH);
|
|
11086
11457
|
let userId = null;
|
|
11087
11458
|
if (!facts.loggedIn) {
|
|
11088
11459
|
lines.push(["Session", "not logged in"]);
|
|
11089
11460
|
json.loggedIn = false;
|
|
11461
|
+
json.sessionError = sessionError("Not logged in. Run `deepspace auth login` first.");
|
|
11090
11462
|
} else {
|
|
11091
|
-
let who =
|
|
11463
|
+
let who = null;
|
|
11092
11464
|
try {
|
|
11093
11465
|
const payload = decodeJwtPayload(
|
|
11094
11466
|
readFileSync23(TOKEN_PATH, "utf-8").trim()
|
|
@@ -11097,9 +11469,12 @@ var status_default = defineCommand11({
|
|
|
11097
11469
|
userId = payload.sub ?? null;
|
|
11098
11470
|
} catch {
|
|
11099
11471
|
}
|
|
11100
|
-
lines.push([
|
|
11472
|
+
lines.push([
|
|
11473
|
+
"Session",
|
|
11474
|
+
`${who ?? "logged in"} \xB7 renews on use (re-login only after 30 idle days)`
|
|
11475
|
+
]);
|
|
11101
11476
|
json.loggedIn = true;
|
|
11102
|
-
json.user = who;
|
|
11477
|
+
if (who) json.user = who;
|
|
11103
11478
|
}
|
|
11104
11479
|
const appDir = findAppDir();
|
|
11105
11480
|
if (!appDir) {
|
|
@@ -11158,6 +11533,9 @@ var status_default = defineCommand11({
|
|
|
11158
11533
|
lines.push(["Cloud", "session expired \u2014 run `deepspace auth login`"]);
|
|
11159
11534
|
facts.loggedIn = false;
|
|
11160
11535
|
json.sessionExpired = true;
|
|
11536
|
+
json.loggedIn = false;
|
|
11537
|
+
delete json.user;
|
|
11538
|
+
json.sessionError = sessionError("Session expired. Run `deepspace auth login`.");
|
|
11161
11539
|
}
|
|
11162
11540
|
}
|
|
11163
11541
|
if (token) {
|
|
@@ -11245,9 +11623,13 @@ var status_default = defineCommand11({
|
|
|
11245
11623
|
const refs = refsResult.ok ? refsResult.value : void 0;
|
|
11246
11624
|
const trunkRef = refs?.refs.find((r) => `refs/heads/${branch}` === r.name);
|
|
11247
11625
|
const defaultBranch = refs?.head && refs.head.startsWith("refs/heads/") ? refs.head.slice("refs/heads/".length) : null;
|
|
11626
|
+
if (!githubSource && refsResult.ok && defaultBranch) json.defaultBranch = defaultBranch;
|
|
11248
11627
|
if (!facts.workspaceId && branch) {
|
|
11249
11628
|
const isTrunk = defaultBranch === null ? null : branch === defaultBranch;
|
|
11250
11629
|
const label = isTrunk === false ? "Branch sync" : "Trunk";
|
|
11630
|
+
if (isTrunk === false && defaultBranch && !githubSource) {
|
|
11631
|
+
lines.push(["Trunk", `${defaultBranch} (this checkout is on ${branch})`]);
|
|
11632
|
+
}
|
|
11251
11633
|
if (githubSource) {
|
|
11252
11634
|
lines.push([label, `${branch} is owned through normal Git/GitHub`]);
|
|
11253
11635
|
json.trunk = { state: "external", provider: "github", branch, isTrunk };
|
|
@@ -11276,12 +11658,12 @@ var status_default = defineCommand11({
|
|
|
11276
11658
|
return;
|
|
11277
11659
|
}
|
|
11278
11660
|
const width = Math.max(...lines.map(([k]) => k.length));
|
|
11279
|
-
for (const [k, v] of lines)
|
|
11661
|
+
for (const [k, v] of lines) p18.log.message(displayLines(`${k.padEnd(width)} ${v}`));
|
|
11280
11662
|
}
|
|
11281
11663
|
});
|
|
11282
11664
|
|
|
11283
11665
|
// src/cli/commands/push.ts
|
|
11284
|
-
import * as
|
|
11666
|
+
import * as p19 from "@clack/prompts";
|
|
11285
11667
|
function pullRecoveryAction(cwd, appId, branch) {
|
|
11286
11668
|
return {
|
|
11287
11669
|
cwd,
|
|
@@ -11298,12 +11680,26 @@ function forcePushOrphansWork(lastPushed, remoteTip, tipOid, tipContainsRemoteTi
|
|
|
11298
11680
|
if (remoteTip === lastPushed) return false;
|
|
11299
11681
|
return !tipContainsRemoteTip;
|
|
11300
11682
|
}
|
|
11683
|
+
function fetchRemoteTip(appDir, token, branch) {
|
|
11684
|
+
try {
|
|
11685
|
+
runGitRemote(appDir, token, [
|
|
11686
|
+
"fetch",
|
|
11687
|
+
"--quiet",
|
|
11688
|
+
"--refmap=",
|
|
11689
|
+
SPACE_REMOTE,
|
|
11690
|
+
`refs/heads/${branch}`
|
|
11691
|
+
]);
|
|
11692
|
+
return resolveCommit(appDir, "FETCH_HEAD");
|
|
11693
|
+
} catch {
|
|
11694
|
+
return null;
|
|
11695
|
+
}
|
|
11696
|
+
}
|
|
11301
11697
|
function workspaceBranchPushRefusal(branch) {
|
|
11302
11698
|
const id = workspaceIdFromBranch(branch);
|
|
11303
|
-
if (!id) return null;
|
|
11699
|
+
if (!id && !branch?.startsWith("ws/")) return null;
|
|
11304
11700
|
return {
|
|
11305
11701
|
code: "workspace_branch",
|
|
11306
|
-
error: `"${branch}" is
|
|
11702
|
+
error: `"${branch}" is in the workspace branch namespace${id ? ` (${id})` : ""}. A plain push moves only the visible ref and bypasses workspace coordination \u2014 the workspace tip, overlap warnings, and activity would go stale. Publish it with \`deepspace workspace sync\` instead.`
|
|
11307
11703
|
};
|
|
11308
11704
|
}
|
|
11309
11705
|
var push_default = defineDeepspaceCommand({
|
|
@@ -11326,7 +11722,7 @@ var push_default = defineDeepspaceCommand({
|
|
|
11326
11722
|
},
|
|
11327
11723
|
force: {
|
|
11328
11724
|
type: "boolean",
|
|
11329
|
-
description: "Allow a non-fast-forward ref move to rewrite history you previously pushed with `deepspace push`. Guarded:
|
|
11725
|
+
description: "Allow a non-fast-forward ref move to rewrite history you previously pushed with `deepspace push`. Guarded by a per-checkout push record: allowed when THIS checkout published the cloud tip with `deepspace push` (your own amend/rebase), refused when the cloud tip is any other commit your branch does not contain \u2014 so a peer's work is never silently dropped. A plain `git push space` leaves no record, so a force over a tip published that way is refused until you re-integrate it, as is a first force from a fresh clone/pull.",
|
|
11330
11726
|
default: false
|
|
11331
11727
|
}
|
|
11332
11728
|
},
|
|
@@ -11390,7 +11786,14 @@ var push_default = defineDeepspaceCommand({
|
|
|
11390
11786
|
const { appId: appId2 } = await resolveTarget3();
|
|
11391
11787
|
const checkoutPath = selectedBranchCheckout(appDir, branch);
|
|
11392
11788
|
const workspaceId = workspaceIdFromBranch(branch);
|
|
11393
|
-
if (!workspaceId)
|
|
11789
|
+
if (!workspaceId) {
|
|
11790
|
+
spinner2?.stop("Workspace branch.");
|
|
11791
|
+
throw new Refusal(
|
|
11792
|
+
`${wsRefusal.error} "${branch}" carries no workspace id, so nothing can publish it \u2014 rename it out of the \`ws/\` namespace (\`git branch -m ${shQuote(branch)} <name>\`) and push that.`,
|
|
11793
|
+
wsRefusal.code,
|
|
11794
|
+
{ extra: { appId: appId2, branch } }
|
|
11795
|
+
);
|
|
11796
|
+
}
|
|
11394
11797
|
const syncCommand = `deepspace workspace sync --app ${shQuote(appId2)} --workspace ${shQuote(workspaceId)}`;
|
|
11395
11798
|
const quotedSyncCommand = `\`${syncCommand}\``;
|
|
11396
11799
|
const message = checkoutPath ? `${wsRefusal.error} Run ${quotedSyncCommand} from its checkout at ${checkoutPath}.` : `${wsRefusal.error} ${branch} exists locally but is not checked out. Check it out in a clean worktree, then run ${quotedSyncCommand}.`;
|
|
@@ -11405,16 +11808,23 @@ var push_default = defineDeepspaceCommand({
|
|
|
11405
11808
|
extra: { appId: appId2, branch, ...checkoutPath ? { worktreePath: checkoutPath } : {} }
|
|
11406
11809
|
});
|
|
11407
11810
|
}
|
|
11408
|
-
const secretFiles =
|
|
11811
|
+
const secretFiles = secretFilesInPushRange(
|
|
11812
|
+
appDir,
|
|
11813
|
+
resolveCommit(appDir, `refs/remotes/${SPACE_REMOTE}/${branch}`) ?? resolveCommit(appDir, spacePrivateRef(`pushed/${branch}`)),
|
|
11814
|
+
tipOid
|
|
11815
|
+
);
|
|
11409
11816
|
if (secretFiles.length > 0) {
|
|
11410
11817
|
const checkoutPath = selectedBranchCheckout(appDir, branch);
|
|
11411
|
-
const checkoutNote = checkoutPath ? "" : ` Check out ${shQuote(branch)} in a clean worktree
|
|
11818
|
+
const checkoutNote = checkoutPath ? "" : ` Check out ${shQuote(branch)} in a clean worktree to do it.`;
|
|
11412
11819
|
throw new Refusal(
|
|
11413
|
-
`Refusing to push: the
|
|
11820
|
+
`Refusing to push: the commits being sent carry secret file(s) \u2014 ${secretFiles.join(", ")}. These hold local secrets and must not reach the cloud repo. ` + secretRecoverySentence(secretFiles, "push") + checkoutNote,
|
|
11414
11821
|
SECRET_IN_HISTORY_CODE,
|
|
11415
11822
|
{
|
|
11416
|
-
|
|
11417
|
-
|
|
11823
|
+
extra: {
|
|
11824
|
+
branch,
|
|
11825
|
+
files: secretFiles,
|
|
11826
|
+
...checkoutPath ? { worktreePath: checkoutPath } : {}
|
|
11827
|
+
}
|
|
11418
11828
|
}
|
|
11419
11829
|
);
|
|
11420
11830
|
}
|
|
@@ -11441,11 +11851,11 @@ var push_default = defineDeepspaceCommand({
|
|
|
11441
11851
|
throw githubSourceRefusal(appId, sourceState.source.repository);
|
|
11442
11852
|
}
|
|
11443
11853
|
const pullRecoveryCwd = selectedBranchCheckout(appDir, branch) ?? appDir;
|
|
11444
|
-
ensureSpaceRemote(appDir, appId);
|
|
11854
|
+
ensureSpaceRemote(appDir, appId, void 0, token);
|
|
11445
11855
|
if (force) {
|
|
11446
11856
|
spinner2?.message(`Checking the cloud ${branch} tip before force-push\u2026`);
|
|
11447
11857
|
const lastPushed = resolveCommit(appDir, spacePrivateRef(`pushed/${branch}`));
|
|
11448
|
-
let
|
|
11858
|
+
let remoteTip2 = null;
|
|
11449
11859
|
try {
|
|
11450
11860
|
runGitRemote(appDir, token, [
|
|
11451
11861
|
"fetch",
|
|
@@ -11454,7 +11864,7 @@ var push_default = defineDeepspaceCommand({
|
|
|
11454
11864
|
SPACE_REMOTE,
|
|
11455
11865
|
`refs/heads/${branch}`
|
|
11456
11866
|
]);
|
|
11457
|
-
|
|
11867
|
+
remoteTip2 = resolveCommit(appDir, "FETCH_HEAD");
|
|
11458
11868
|
} catch {
|
|
11459
11869
|
const ls = runGitRemote(
|
|
11460
11870
|
appDir,
|
|
@@ -11465,14 +11875,25 @@ var push_default = defineDeepspaceCommand({
|
|
|
11465
11875
|
}
|
|
11466
11876
|
);
|
|
11467
11877
|
if (ls.status === 0 && ls.stdout.toString("utf-8").trim() === "") {
|
|
11468
|
-
|
|
11878
|
+
remoteTip2 = null;
|
|
11469
11879
|
} else {
|
|
11470
11880
|
const error = `Couldn't reach the cloud repo to check ${branch} before force-pushing. Refusing --force so it can't silently drop a peer's commits \u2014 retry when connected, or run \`deepspace pull --app ${shQuote(appId)} --branch ${shQuote(branch)}\` to integrate first.`;
|
|
11471
11881
|
throw new Refusal(error, "force_unverified", { extra: { appId, branch } });
|
|
11472
11882
|
}
|
|
11473
11883
|
}
|
|
11474
|
-
if (
|
|
11475
|
-
|
|
11884
|
+
if (remoteTip2 && forcePushOrphansWork(lastPushed, remoteTip2, tipOid, isAncestor(appDir, remoteTip2, tipOid))) {
|
|
11885
|
+
if (isAncestor(appDir, tipOid, remoteTip2)) {
|
|
11886
|
+
throw new Refusal(
|
|
11887
|
+
`The cloud repo's ${branch} is at ${remoteTip2.slice(0, 10)} and already contains everything this checkout has \u2014 this checkout is strictly behind. Force-pushing would REWIND the branch and drop the newer commit(s). Run \`deepspace pull --app ${shQuote(appId)} --branch ${shQuote(branch)}\` to fast-forward, then push normally if anything is left to publish.`,
|
|
11888
|
+
"behind",
|
|
11889
|
+
{
|
|
11890
|
+
actionRequired: true,
|
|
11891
|
+
action: pullRecoveryAction(pullRecoveryCwd, appId, branch),
|
|
11892
|
+
extra: { appId, branch }
|
|
11893
|
+
}
|
|
11894
|
+
);
|
|
11895
|
+
}
|
|
11896
|
+
const error = `The cloud repo's ${branch} is at ${remoteTip2.slice(0, 10)}, and this checkout has no record of publishing it (\`deepspace push\` records its own pushes; a plain \`git push ${SPACE_REMOTE}\` does not), so a peer's work on top can't be ruled out. Force-pushing now would DROP that line. Run \`deepspace pull --app ${shQuote(appId)} --branch ${shQuote(branch)}\` and merge (or rebase onto it), then push.`;
|
|
11476
11897
|
throw new Refusal(error, "diverged", {
|
|
11477
11898
|
actionRequired: true,
|
|
11478
11899
|
action: pullRecoveryAction(pullRecoveryCwd, appId, branch),
|
|
@@ -11484,20 +11905,35 @@ var push_default = defineDeepspaceCommand({
|
|
|
11484
11905
|
const result = pushToSpace(appDir, token, `refs/heads/${branch}:refs/heads/${branch}`, {
|
|
11485
11906
|
force
|
|
11486
11907
|
});
|
|
11487
|
-
spinner2?.stop(
|
|
11488
|
-
result.status === "up_to_date" ? `${branch} is already up to date at ${tipOid.slice(0, 10)}.` : result.status === "committed" ? `Pushed ${branch} \u2192 ${tipOid.slice(0, 10)}.` : `Push did not complete.`
|
|
11489
|
-
);
|
|
11490
11908
|
const ok2 = result.status === "up_to_date" || result.status === "committed";
|
|
11491
|
-
if (
|
|
11909
|
+
if (result.status === "committed") {
|
|
11492
11910
|
updateRef(appDir, spacePrivateRef(`pushed/${branch}`), tipOid);
|
|
11493
11911
|
}
|
|
11494
11912
|
const recoverable = isRecoverablePushFailure(result.status);
|
|
11495
11913
|
const rejectReason = result.reason ?? result.summary;
|
|
11496
|
-
const
|
|
11497
|
-
if (
|
|
11498
|
-
const
|
|
11914
|
+
const rejectedFastForward = !ok2 && result.status === "non_fast_forward" && !force;
|
|
11915
|
+
if (rejectedFastForward) spinner2?.message(`Checking the cloud ${branch} tip\u2026`);
|
|
11916
|
+
const remoteTip = rejectedFastForward ? fetchRemoteTip(appDir, token, branch) : null;
|
|
11917
|
+
const ownRewrite = remoteTip !== null && remoteTip === resolveCommit(appDir, spacePrivateRef(`pushed/${branch}`));
|
|
11918
|
+
const strictlyBehind = remoteTip !== null && !ownRewrite && isAncestor(appDir, tipOid, remoteTip);
|
|
11919
|
+
spinner2?.stop(
|
|
11920
|
+
result.status === "up_to_date" ? `${branch} is already up to date at ${tipOid.slice(0, 10)}.` : result.status === "committed" ? `Pushed ${branch} \u2192 ${tipOid.slice(0, 10)}.` : `Push did not complete.`
|
|
11921
|
+
);
|
|
11922
|
+
const pullNext = `Run \`deepspace pull --app ${shQuote(appId)} --branch ${shQuote(branch)}\` and merge (or rebase onto it), then push.`;
|
|
11923
|
+
const errorMsg = ok2 ? null : result.status === "ref_conflict" ? (
|
|
11924
|
+
// The machine token belongs in `code`, not mid-sentence: the reason
|
|
11925
|
+
// arrives as `<code>: <sentence>`, and printing it raw put
|
|
11926
|
+
// "stale_ref: stale ref, fetch first" inside human prose.
|
|
11927
|
+
`The cloud repo's ${branch} moved while you worked (${parseRefusalCode(rejectReason)?.sentence ?? rejectReason}). Run \`deepspace pull --app ${shQuote(appId)} --branch ${shQuote(branch)}\` to integrate, then push again.`
|
|
11928
|
+
) : result.status === "rejected" ? pushFailureMessage(`The push of ${branch}`, result, appDir ?? void 0) : ownRewrite ? `The cloud repo's ${branch} is exactly the tip THIS checkout last pushed, and your local ${branch} no longer contains it \u2014 you rewrote your own published line (amend/rebase). Publish the rewrite with \`deepspace push --force --branch ${shQuote(branch)}\`: the guard allows it because it discards only your own superseded tip. (Pulling instead would merge the commit you just amended away back in.)` : strictlyBehind ? `The cloud repo's ${branch} is at ${remoteTip.slice(0, 10)} and already contains everything this checkout has \u2014 this checkout is just behind, with nothing of its own to publish. Run \`deepspace pull --app ${shQuote(appId)} --branch ${shQuote(branch)}\` to fast-forward, then push again if anything is left.` : `The cloud repo's ${branch} has commit(s) your local ${branch} doesn't. ${pullNext} Avoid --force here \u2014 it's guarded and will refuse rather than silently drop a peer's commits.`;
|
|
11929
|
+
if (!args.json && ok2) p19.log.info(`App: ${appId}`);
|
|
11930
|
+
const action = ownRewrite ? {
|
|
11931
|
+
cwd: pullRecoveryCwd,
|
|
11932
|
+
argv: ["deepspace", "push", "--force", "--app", appId, "--branch", branch]
|
|
11933
|
+
} : recoverable ? pullRecoveryAction(pullRecoveryCwd, appId, branch) : void 0;
|
|
11499
11934
|
if (!ok2) {
|
|
11500
|
-
|
|
11935
|
+
const code = result.status === "rejected" ? classifyRejection(rejectReason, appDir ?? void 0).code : strictlyBehind ? "behind" : result.status;
|
|
11936
|
+
throw new Refusal(errorMsg ?? `Push did not complete.`, code, {
|
|
11501
11937
|
actionRequired: recoverable,
|
|
11502
11938
|
action,
|
|
11503
11939
|
extra: { status: result.status, appId, branch, oid: tipOid }
|
|
@@ -11508,7 +11944,9 @@ var push_default = defineDeepspaceCommand({
|
|
|
11508
11944
|
if (err instanceof Refusal) throw err;
|
|
11509
11945
|
const transportFailure = classifyPushTransportFailure(err, appDir ?? void 0);
|
|
11510
11946
|
if (transportFailure) {
|
|
11511
|
-
throw new Refusal(transportFailure.error, transportFailure.code
|
|
11947
|
+
throw new Refusal(transportFailure.error, transportFailure.code, {
|
|
11948
|
+
extra: { gitError: err instanceof Error ? err.message : String(err) }
|
|
11949
|
+
});
|
|
11512
11950
|
}
|
|
11513
11951
|
throw err;
|
|
11514
11952
|
}
|
|
@@ -11516,7 +11954,7 @@ var push_default = defineDeepspaceCommand({
|
|
|
11516
11954
|
});
|
|
11517
11955
|
|
|
11518
11956
|
// src/cli/commands/pull.ts
|
|
11519
|
-
import * as
|
|
11957
|
+
import * as p20 from "@clack/prompts";
|
|
11520
11958
|
import { realpathSync as realpathSync5 } from "fs";
|
|
11521
11959
|
function targetedVcAction(command, cwd, appId, branch) {
|
|
11522
11960
|
return {
|
|
@@ -11610,7 +12048,7 @@ var pull_default = defineDeepspaceCommand({
|
|
|
11610
12048
|
const appId = await resolveAppTarget(deployUrl, token, appArg);
|
|
11611
12049
|
spinner2?.message(`Checking the cloud ${branch} ref\u2026`);
|
|
11612
12050
|
const remote = await repoApi(deployUrl, token, appId).getRefs();
|
|
11613
|
-
ensureSpaceRemote(appDir, appId);
|
|
12051
|
+
ensureSpaceRemote(appDir, appId, void 0, token);
|
|
11614
12052
|
if (!remote) {
|
|
11615
12053
|
spinner2?.stop("No cloud repo yet.");
|
|
11616
12054
|
const pushCommand2 = targetedVcCommand("push", appId, branch);
|
|
@@ -11778,7 +12216,7 @@ var pull_default = defineDeepspaceCommand({
|
|
|
11778
12216
|
const actionRequired = recoveryAction !== void 0;
|
|
11779
12217
|
spinner2?.stop(codeForState ? `Fetched ${SPACE_REMOTE}/${branch}.` : line);
|
|
11780
12218
|
if (!args.json)
|
|
11781
|
-
|
|
12219
|
+
p20.log.info(`App: ${appId} \xB7 ${SPACE_REMOTE}/${branch} at ${remoteOid.slice(0, 10)}`);
|
|
11782
12220
|
if (codeForState) {
|
|
11783
12221
|
throw new Refusal(line, codeForState, {
|
|
11784
12222
|
actionRequired,
|
|
@@ -11797,15 +12235,15 @@ var pull_default = defineDeepspaceCommand({
|
|
|
11797
12235
|
});
|
|
11798
12236
|
|
|
11799
12237
|
// src/cli/commands/clone.ts
|
|
11800
|
-
import * as
|
|
11801
|
-
import { existsSync as
|
|
12238
|
+
import * as p21 from "@clack/prompts";
|
|
12239
|
+
import { existsSync as existsSync26, readdirSync as readdirSync8, rmSync as rmSync6 } from "fs";
|
|
11802
12240
|
import { resolve as resolve19, basename as basename5, join as join25 } from "path";
|
|
11803
12241
|
function cleanFailedCloneDir(dir) {
|
|
11804
12242
|
let entries;
|
|
11805
12243
|
try {
|
|
11806
12244
|
entries = readdirSync8(dir);
|
|
11807
12245
|
} catch {
|
|
11808
|
-
return
|
|
12246
|
+
return existsSync26(dir) ? ["<unreadable>"] : [];
|
|
11809
12247
|
}
|
|
11810
12248
|
if (entries.length === 1 && entries[0] === ".git") {
|
|
11811
12249
|
try {
|
|
@@ -11818,7 +12256,7 @@ function cleanFailedCloneDir(dir) {
|
|
|
11818
12256
|
return entries;
|
|
11819
12257
|
}
|
|
11820
12258
|
function validateCloneTarget(dir) {
|
|
11821
|
-
if (!
|
|
12259
|
+
if (!existsSync26(dir)) return false;
|
|
11822
12260
|
let entries;
|
|
11823
12261
|
try {
|
|
11824
12262
|
entries = readdirSync8(dir);
|
|
@@ -11892,15 +12330,14 @@ var clone_default = defineDeepspaceCommand({
|
|
|
11892
12330
|
}
|
|
11893
12331
|
throw err;
|
|
11894
12332
|
}
|
|
11895
|
-
ensureSpaceRemote(dir, appId);
|
|
11896
|
-
ensureGitIdentity(dir, token);
|
|
12333
|
+
ensureSpaceRemote(dir, appId, void 0, token);
|
|
11897
12334
|
const head = resolveCommit(dir, "HEAD");
|
|
11898
12335
|
const branch = currentBranch(dir);
|
|
11899
12336
|
spinner2?.stop(
|
|
11900
12337
|
`Cloned ${selector} (${branches.length} ${branches.length === 1 ? "branch" : "branches"}) into ${basename5(dir)}/ at ${head?.slice(0, 10) ?? "(unborn)"}.`
|
|
11901
12338
|
);
|
|
11902
12339
|
if (!args.json && !branch) {
|
|
11903
|
-
|
|
12340
|
+
p21.log.warn("HEAD is detached \u2014 check `git branch -a`.");
|
|
11904
12341
|
}
|
|
11905
12342
|
return {
|
|
11906
12343
|
data: {
|
|
@@ -11919,7 +12356,7 @@ var clone_default = defineDeepspaceCommand({
|
|
|
11919
12356
|
});
|
|
11920
12357
|
|
|
11921
12358
|
// src/cli/commands/releases.ts
|
|
11922
|
-
import * as
|
|
12359
|
+
import * as p22 from "@clack/prompts";
|
|
11923
12360
|
|
|
11924
12361
|
// src/cli/lib/actor-labels.ts
|
|
11925
12362
|
var API_URL9 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
@@ -11981,7 +12418,7 @@ var releases_default = defineDeepspaceCommand({
|
|
|
11981
12418
|
spinner2?.stop(`Loaded ${releases.length} ${releases.length === 1 ? "release" : "releases"}.`);
|
|
11982
12419
|
if (!args.json) {
|
|
11983
12420
|
if (releases.length === 0) {
|
|
11984
|
-
|
|
12421
|
+
p22.log.info("No releases yet \u2014 the history starts with the next `deepspace deploy`.");
|
|
11985
12422
|
} else {
|
|
11986
12423
|
const actors = await actorLabels(token, appId);
|
|
11987
12424
|
for (const r of releases) {
|
|
@@ -11991,7 +12428,7 @@ var releases_default = defineDeepspaceCommand({
|
|
|
11991
12428
|
`#${r.seq} ${r.id} ${r.kind.padEnd(8)} ${r.createdAt} ${actors.get(r.actor) ?? r.actor} ${source} ${rollback}`
|
|
11992
12429
|
);
|
|
11993
12430
|
}
|
|
11994
|
-
|
|
12431
|
+
p22.log.info(
|
|
11995
12432
|
`Roll back an available release with: deepspace rollback <release-id>${wranglerEnv ? ` --env ${wranglerEnv}` : ""}`
|
|
11996
12433
|
);
|
|
11997
12434
|
}
|
|
@@ -12001,7 +12438,7 @@ var releases_default = defineDeepspaceCommand({
|
|
|
12001
12438
|
});
|
|
12002
12439
|
|
|
12003
12440
|
// src/cli/commands/rollback.ts
|
|
12004
|
-
import * as
|
|
12441
|
+
import * as p23 from "@clack/prompts";
|
|
12005
12442
|
var REL_ID_RE = /^rel_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
12006
12443
|
function unavailableDoGuardRefusal(message) {
|
|
12007
12444
|
return new Refusal(
|
|
@@ -12122,20 +12559,20 @@ Re-run with --allow-do-deletion to accept the data loss.`,
|
|
|
12122
12559
|
const doClassGuard = result.doClassGuard === "unverified" ? "unverified" : void 0;
|
|
12123
12560
|
if (!args.json) {
|
|
12124
12561
|
if (result.url) {
|
|
12125
|
-
if (serving === "unconfirmed")
|
|
12126
|
-
else
|
|
12562
|
+
if (serving === "unconfirmed") p23.log.warn(`URL: ${result.url} (accepted; serving not yet verified)`);
|
|
12563
|
+
else p23.log.success(`Live at: ${result.url}`);
|
|
12127
12564
|
}
|
|
12128
12565
|
if (result.bundleRetained === false) {
|
|
12129
|
-
|
|
12566
|
+
p23.log.warn(
|
|
12130
12567
|
"The rollback is live, but its bundle was concurrently retired and is not available for a future rollback."
|
|
12131
12568
|
);
|
|
12132
12569
|
}
|
|
12133
12570
|
if (doClassGuard === "unverified") {
|
|
12134
|
-
|
|
12571
|
+
p23.log.warn(
|
|
12135
12572
|
"DO-class guard bypassed (--allow-do-deletion): this rollback was NOT checked against the live Durable Object classes \u2014 any class the target doesn't declare has been deleted with its data."
|
|
12136
12573
|
);
|
|
12137
12574
|
}
|
|
12138
|
-
|
|
12575
|
+
p23.log.info(
|
|
12139
12576
|
"This rollback is itself the newest release \u2014 `deepspace releases` shows the full history."
|
|
12140
12577
|
);
|
|
12141
12578
|
}
|
|
@@ -12160,24 +12597,24 @@ Re-run with --allow-do-deletion to accept the data loss.`,
|
|
|
12160
12597
|
import { defineCommand as defineCommand12 } from "citty";
|
|
12161
12598
|
|
|
12162
12599
|
// src/cli/commands/workspace/new.ts
|
|
12163
|
-
import * as
|
|
12600
|
+
import * as p26 from "@clack/prompts";
|
|
12164
12601
|
import { isAbsolute as isAbsolute7, resolve as resolve20 } from "path";
|
|
12165
12602
|
|
|
12166
12603
|
// src/cli/commands/workspace/analysis.ts
|
|
12167
|
-
import * as
|
|
12604
|
+
import * as p24 from "@clack/prompts";
|
|
12168
12605
|
var MAX_OVERLAP_PATHS = 20;
|
|
12169
12606
|
var PEER_REF_PREFIX = `${spacePrivateRef("peers")}/`;
|
|
12607
|
+
function workspaceSyncRelation(dir, headOid, publishedTip) {
|
|
12608
|
+
if (dir === null || headOid === null || publishedTip === null) return "unknown";
|
|
12609
|
+
if (headOid === publishedTip) return "in_sync";
|
|
12610
|
+
if (resolveCommit(dir, publishedTip) === null) return "unknown";
|
|
12611
|
+
if (isAncestor(dir, headOid, publishedTip)) return "behind";
|
|
12612
|
+
if (isAncestor(dir, publishedTip, headOid)) return "ahead";
|
|
12613
|
+
return "diverged";
|
|
12614
|
+
}
|
|
12170
12615
|
function peerWorkspaceRef(workspaceId) {
|
|
12171
12616
|
return `${PEER_REF_PREFIX}${workspaceId}`;
|
|
12172
12617
|
}
|
|
12173
|
-
function workspaceSyncRelation(dir, localOid, publishedOid) {
|
|
12174
|
-
if (localOid !== null && localOid === publishedOid) return "in_sync";
|
|
12175
|
-
if (localOid === null || publishedOid === null) return "unknown";
|
|
12176
|
-
if (resolveCommit(dir, publishedOid) === null) return "unknown";
|
|
12177
|
-
if (isAncestor(dir, localOid, publishedOid)) return "behind";
|
|
12178
|
-
if (isAncestor(dir, publishedOid, localOid)) return "ahead";
|
|
12179
|
-
return "diverged";
|
|
12180
|
-
}
|
|
12181
12618
|
function formatCount(count) {
|
|
12182
12619
|
if (!count) return "?";
|
|
12183
12620
|
return count.capped ? `${count.count}+` : String(count.count);
|
|
@@ -12286,16 +12723,16 @@ function printOverlaps(overlaps, json) {
|
|
|
12286
12723
|
for (const overlap of overlaps) {
|
|
12287
12724
|
const sample = overlap.paths.slice(0, 5).join(", ");
|
|
12288
12725
|
const more = overlap.paths.length - 5 + overlap.morePaths;
|
|
12289
|
-
|
|
12726
|
+
p24.log.warn(
|
|
12290
12727
|
`Overlap: ${overlap.workspaceId} ("${overlap.task}") also modified ${sample}${more > 0 ? ` (+${more} more)` : ""} \u2014 coordinate before landing.`
|
|
12291
12728
|
);
|
|
12292
12729
|
}
|
|
12293
12730
|
}
|
|
12294
12731
|
|
|
12295
12732
|
// src/cli/commands/workspace/local.ts
|
|
12296
|
-
import * as
|
|
12733
|
+
import * as p25 from "@clack/prompts";
|
|
12297
12734
|
import {
|
|
12298
|
-
existsSync as
|
|
12735
|
+
existsSync as existsSync27,
|
|
12299
12736
|
mkdirSync as mkdirSync8,
|
|
12300
12737
|
readFileSync as readFileSync24,
|
|
12301
12738
|
realpathSync as realpathSync6,
|
|
@@ -12375,11 +12812,6 @@ function appDirInWorktree(appDir, worktreeRoot) {
|
|
|
12375
12812
|
const appRelative = relative4(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
|
|
12376
12813
|
return appRelative ? join26(worktreeRoot, appRelative) : worktreeRoot;
|
|
12377
12814
|
}
|
|
12378
|
-
function primaryAppDir(appDir) {
|
|
12379
|
-
const primary = listWorktrees(appDir)[0];
|
|
12380
|
-
if (!primary) throw new Error("Git did not report a primary worktree");
|
|
12381
|
-
return appDirInWorktree(appDir, primary.path);
|
|
12382
|
-
}
|
|
12383
12815
|
function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint, workspaceId, options = {}) {
|
|
12384
12816
|
const createdOid = resolveCommit(appDir, startPoint);
|
|
12385
12817
|
if (!createdOid) throw new Error(`Could not resolve workspace start point ${startPoint}`);
|
|
@@ -12533,7 +12965,7 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
|
|
|
12533
12965
|
}
|
|
12534
12966
|
runGit(mainDir, ["worktree", "prune"], { allowFail: true });
|
|
12535
12967
|
out.worktreeDir = null;
|
|
12536
|
-
const leftover =
|
|
12968
|
+
const leftover = existsSync27(linked.path);
|
|
12537
12969
|
if (leftover) out.leftoverDir = linked.path;
|
|
12538
12970
|
else out.worktreeRemoved = linked.path;
|
|
12539
12971
|
deleteWsBranch(mainDir, branch, expectedBranchOid, out);
|
|
@@ -12548,6 +12980,7 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
|
|
|
12548
12980
|
deleteWsBranch(mainDir, branch, expectedBranchOid, out);
|
|
12549
12981
|
return out;
|
|
12550
12982
|
}
|
|
12983
|
+
runGit(mainDir, ["worktree", "prune"], { allowFail: true });
|
|
12551
12984
|
if (currentBranch(mainDir) === branch && !switchOffWsBranch(mainDir, trunkBranch)) {
|
|
12552
12985
|
out.error = `could not switch off ${branch} to delete it`;
|
|
12553
12986
|
return out;
|
|
@@ -12579,7 +13012,7 @@ function cleanupAction(outcome) {
|
|
|
12579
13012
|
}
|
|
12580
13013
|
function reportCleanupHuman(outcome) {
|
|
12581
13014
|
if (outcome.error) {
|
|
12582
|
-
|
|
13015
|
+
p25.log.warn(
|
|
12583
13016
|
cleanupRefusalMessage({
|
|
12584
13017
|
error: outcome.error,
|
|
12585
13018
|
worktreeDir: outcome.worktreeDir,
|
|
@@ -12593,12 +13026,12 @@ function reportCleanupHuman(outcome) {
|
|
|
12593
13026
|
outcome.worktreeRemoved ? "worktree" : null,
|
|
12594
13027
|
outcome.branchDeleted ? `branch ${outcome.branch}` : null
|
|
12595
13028
|
].filter(Boolean);
|
|
12596
|
-
if (parts.length)
|
|
13029
|
+
if (parts.length) p25.log.success(`Removed ${parts.join(" + ")}.`);
|
|
12597
13030
|
if (outcome.worktreeRetained) {
|
|
12598
|
-
|
|
13031
|
+
p25.log.info(`Retained externally managed worktree: ${outcome.worktreeRetained}`);
|
|
12599
13032
|
}
|
|
12600
13033
|
if (outcome.worktreeRemoved && outcome.mainDir) {
|
|
12601
|
-
|
|
13034
|
+
p25.log.info(`This directory was removed \u2014 run: cd ${shQuote(outcome.mainDir)}`);
|
|
12602
13035
|
}
|
|
12603
13036
|
}
|
|
12604
13037
|
|
|
@@ -12634,6 +13067,15 @@ async function resolveApiOnly(appArg) {
|
|
|
12634
13067
|
function inferWorkspaceId(appDir, explicit) {
|
|
12635
13068
|
const id = explicit?.trim() || workspaceIdFromBranch(currentBranch(appDir));
|
|
12636
13069
|
if (!id) {
|
|
13070
|
+
const interrupted = currentBranch(appDir) === null ? interruptedGitOperation(appDir) : null;
|
|
13071
|
+
if (interrupted) {
|
|
13072
|
+
const finish = interrupted === "bisect" ? "end it (`git bisect reset`)" : `finish it (\`git ${interrupted} --continue\`) or abandon it (\`git ${interrupted} --abort\`)`;
|
|
13073
|
+
throw new Refusal(
|
|
13074
|
+
`A ${interrupted} is in progress here, so HEAD is detached and the branch no longer names a workspace. ${finish[0].toUpperCase()}${finish.slice(1)}, then re-run \u2014 the workspace is unchanged either way.`,
|
|
13075
|
+
"git_operation_in_progress",
|
|
13076
|
+
{ extra: { operation: interrupted } }
|
|
13077
|
+
);
|
|
13078
|
+
}
|
|
12637
13079
|
throw new Refusal(
|
|
12638
13080
|
'Not inside a workspace worktree (the branch is not ws/<id>). Select one with `-w ws_\u2026` (for `workspace drop`, pass the id as the argument), or create one with `deepspace workspace new -t "\u2026"`.',
|
|
12639
13081
|
"not_in_workspace"
|
|
@@ -12679,17 +13121,30 @@ function assertExplicitWorkspaceId(explicit) {
|
|
|
12679
13121
|
);
|
|
12680
13122
|
}
|
|
12681
13123
|
}
|
|
12682
|
-
function pushWorkspaceRef(appDir, token, ref, headOid) {
|
|
13124
|
+
function pushWorkspaceRef(appDir, token, ref, headOid, publishedTip) {
|
|
12683
13125
|
const secret = committedSecretRefusal(appDir, headOid, {
|
|
12684
13126
|
action: "publish this workspace",
|
|
12685
|
-
then: "re-run the command"
|
|
13127
|
+
then: "re-run the command",
|
|
13128
|
+
// What the cloud already has for this ref, so the scan covers every commit
|
|
13129
|
+
// this publish would upload — which is what the server scans.
|
|
13130
|
+
base: publishedTip ?? null
|
|
12686
13131
|
});
|
|
12687
13132
|
if (secret) throw new Refusal(secret.message, secret.code);
|
|
12688
13133
|
const push = pushToSpace(appDir, token, `${headOid}:${ref}`);
|
|
12689
13134
|
if (push.status === "committed" || push.status === "up_to_date") return;
|
|
12690
13135
|
if (push.status === "ref_conflict" || push.status === "non_fast_forward") {
|
|
13136
|
+
const conflicted = unmergedIndexRefusal(appDir, {
|
|
13137
|
+
ours: "The merge this workspace's last pull started",
|
|
13138
|
+
resume: "the command"
|
|
13139
|
+
});
|
|
13140
|
+
if (conflicted) {
|
|
13141
|
+
throw new Refusal(conflicted.message, conflicted.code, {
|
|
13142
|
+
extra: { conflict: true, operation: conflicted.operation }
|
|
13143
|
+
});
|
|
13144
|
+
}
|
|
13145
|
+
const strictlyBehind = typeof publishedTip === "string" && resolveCommit(appDir, publishedTip) !== null && isAncestor(appDir, headOid, publishedTip);
|
|
12691
13146
|
throw new Refusal(
|
|
12692
|
-
`Another checkout advanced this workspace's line, so publishing yours is not a fast-forward \u2014 the push was refused rather than drop that work. Integrate its tip (\`git pull --no-rebase ${SPACE_REMOTE} ${ref}\`, or re-attach the workspace in a fresh dir), resolve any conflicts, then re-run the command. (Amended or rebased your own commits? Same refusal \u2014 merge your old tip back in.)`,
|
|
13147
|
+
strictlyBehind ? `Another checkout already synced ahead of this one \u2014 nothing of yours is unpublished; this checkout is just behind. Fast-forward it (\`git pull --no-rebase ${SPACE_REMOTE} ${ref}\`), then re-run the command.` : `Another checkout advanced this workspace's line, so publishing yours is not a fast-forward \u2014 the push was refused rather than drop that work. Integrate its tip (\`git pull --no-rebase ${SPACE_REMOTE} ${ref}\`, or re-attach the workspace in a fresh dir), resolve any conflicts, then re-run the command. (Amended or rebased your own commits? Same refusal \u2014 merge your old tip back in.)`,
|
|
12693
13148
|
push.status,
|
|
12694
13149
|
{
|
|
12695
13150
|
actionRequired: true,
|
|
@@ -12697,7 +13152,8 @@ function pushWorkspaceRef(appDir, token, ref, headOid) {
|
|
|
12697
13152
|
}
|
|
12698
13153
|
);
|
|
12699
13154
|
}
|
|
12700
|
-
|
|
13155
|
+
const code = push.status === "rejected" ? classifyRejection(push.reason ?? push.summary ?? "", appDir).code : push.status;
|
|
13156
|
+
throw new Refusal(pushFailureMessage("Workspace upload", push, appDir), code);
|
|
12701
13157
|
}
|
|
12702
13158
|
|
|
12703
13159
|
// src/cli/commands/workspace/new.ts
|
|
@@ -12748,8 +13204,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12748
13204
|
typeof args.app === "string" ? args.app : void 0
|
|
12749
13205
|
);
|
|
12750
13206
|
spinner2?.message("Creating workspace\u2026");
|
|
12751
|
-
ensureSpaceRemote(appDir, appId);
|
|
12752
|
-
ensureGitIdentity(appDir, token);
|
|
13207
|
+
ensureSpaceRemote(appDir, appId, void 0, token);
|
|
12753
13208
|
let baseOid = null;
|
|
12754
13209
|
const refs = await api4.getRefs();
|
|
12755
13210
|
if (baseArg) {
|
|
@@ -12808,8 +13263,8 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12808
13263
|
}
|
|
12809
13264
|
spinner2?.stop(`Workspace ${id} created.`);
|
|
12810
13265
|
if (!args.json) {
|
|
12811
|
-
|
|
12812
|
-
|
|
13266
|
+
p26.log.info(`Work in: cd ${dir}`);
|
|
13267
|
+
p26.log.info(
|
|
12813
13268
|
`Then: commit as usual, \`deepspace workspace sync\` to publish, \`deepspace workspace land\` when done.`
|
|
12814
13269
|
);
|
|
12815
13270
|
}
|
|
@@ -12830,15 +13285,15 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12830
13285
|
});
|
|
12831
13286
|
|
|
12832
13287
|
// src/cli/commands/workspace/attach.ts
|
|
12833
|
-
import * as
|
|
12834
|
-
import { existsSync as
|
|
13288
|
+
import * as p27 from "@clack/prompts";
|
|
13289
|
+
import { existsSync as existsSync28, readdirSync as readdirSync9, realpathSync as realpathSync7, rmSync as rmSync7 } from "fs";
|
|
12835
13290
|
import { isAbsolute as isAbsolute8, resolve as resolve21 } from "path";
|
|
12836
13291
|
function samePath(a, b) {
|
|
12837
|
-
const real = (
|
|
13292
|
+
const real = (p36) => {
|
|
12838
13293
|
try {
|
|
12839
|
-
return realpathSync7(
|
|
13294
|
+
return realpathSync7(p36);
|
|
12840
13295
|
} catch {
|
|
12841
|
-
return resolve21(
|
|
13296
|
+
return resolve21(p36);
|
|
12842
13297
|
}
|
|
12843
13298
|
};
|
|
12844
13299
|
return real(a) === real(b);
|
|
@@ -12851,7 +13306,7 @@ function finishedWorkspaceMessage(id, view) {
|
|
|
12851
13306
|
return `Workspace ${id} is ${workspace.status} \u2014 its ref is retained briefly (fetch ${workspace.ref} to inspect), but attach only works on active workspaces.`;
|
|
12852
13307
|
}
|
|
12853
13308
|
function cleanFailedFreshAttachDir(dir) {
|
|
12854
|
-
if (!
|
|
13309
|
+
if (!existsSync28(dir)) return [];
|
|
12855
13310
|
let entries;
|
|
12856
13311
|
try {
|
|
12857
13312
|
entries = readdirSync9(dir);
|
|
@@ -12910,8 +13365,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12910
13365
|
extra: { status: view2.workspace.status }
|
|
12911
13366
|
});
|
|
12912
13367
|
}
|
|
12913
|
-
ensureSpaceRemote(appDir, appId2);
|
|
12914
|
-
ensureGitIdentity(appDir, token2);
|
|
13368
|
+
ensureSpaceRemote(appDir, appId2, void 0, token2);
|
|
12915
13369
|
const localWorkspaceRef = spacePrivateRef(`workspaces/${id}`);
|
|
12916
13370
|
runGitRemote(appDir, token2, [
|
|
12917
13371
|
"fetch",
|
|
@@ -12933,7 +13387,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12933
13387
|
const localTip = resolveCommit(appDir, `refs/heads/${branch}`);
|
|
12934
13388
|
if (localTip) {
|
|
12935
13389
|
let existing = resolveWorkspaceWorktree(listWorktrees(appDir), id);
|
|
12936
|
-
if (existing && !
|
|
13390
|
+
if (existing && !existsSync28(existing)) {
|
|
12937
13391
|
runGit(appDir, ["worktree", "prune"], { allowFail: true });
|
|
12938
13392
|
existing = null;
|
|
12939
13393
|
}
|
|
@@ -12946,10 +13400,10 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12946
13400
|
const existingDir = appDirInWorktree(appDir, existing);
|
|
12947
13401
|
spinner2?.stop(`Already attached: ${id}.`);
|
|
12948
13402
|
if (!args.json) {
|
|
12949
|
-
|
|
12950
|
-
|
|
13403
|
+
p27.log.info(`Task: ${view2.workspace.task}`);
|
|
13404
|
+
p27.log.info(`Work in: cd ${existingDir}`);
|
|
12951
13405
|
if (!inSync) {
|
|
12952
|
-
|
|
13406
|
+
p27.log.warn(
|
|
12953
13407
|
`The checkout is at ${localTip.slice(0, 10)} but the workspace's published tip is ${tip2.slice(0, 10)} \u2014 ${syncAdvice}.`
|
|
12954
13408
|
);
|
|
12955
13409
|
}
|
|
@@ -12981,10 +13435,10 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12981
13435
|
}
|
|
12982
13436
|
spinner2?.stop(`Re-attached ${id}.`);
|
|
12983
13437
|
if (!args.json) {
|
|
12984
|
-
|
|
12985
|
-
|
|
13438
|
+
p27.log.info(`Task: ${view2.workspace.task}`);
|
|
13439
|
+
p27.log.info(`Work in: cd ${dir2}`);
|
|
12986
13440
|
if (!inSync) {
|
|
12987
|
-
|
|
13441
|
+
p27.log.warn(
|
|
12988
13442
|
`The re-materialized checkout is at ${localTip.slice(0, 10)} but the workspace's published tip is ${tip2.slice(0, 10)} \u2014 after installing, ${syncAdvice}.`
|
|
12989
13443
|
);
|
|
12990
13444
|
}
|
|
@@ -13015,8 +13469,8 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13015
13469
|
materializeWorkspaceWorktree(appDir, worktreeRoot, branch, tip2, id);
|
|
13016
13470
|
spinner2?.stop(`Attached ${id}.`);
|
|
13017
13471
|
if (!args.json) {
|
|
13018
|
-
|
|
13019
|
-
|
|
13472
|
+
p27.log.info(`Task: ${view2.workspace.task}`);
|
|
13473
|
+
p27.log.info(`Work in: cd ${dir2}`);
|
|
13020
13474
|
}
|
|
13021
13475
|
const action2 = { cwd: dir2, argv: [detectPackageManager(dir2), "install"] };
|
|
13022
13476
|
return {
|
|
@@ -13051,7 +13505,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13051
13505
|
process.cwd(),
|
|
13052
13506
|
(typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
|
|
13053
13507
|
);
|
|
13054
|
-
if (
|
|
13508
|
+
if (existsSync28(dir)) {
|
|
13055
13509
|
spinner2?.stop("Target exists.");
|
|
13056
13510
|
throw new Refusal(`${dir} already exists \u2014 pass a different directory.`, "dir_exists");
|
|
13057
13511
|
}
|
|
@@ -13065,8 +13519,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13065
13519
|
let tip;
|
|
13066
13520
|
try {
|
|
13067
13521
|
runGit(process.cwd(), ["init", "--quiet", dir]);
|
|
13068
|
-
ensureSpaceRemote(dir, appId);
|
|
13069
|
-
ensureGitIdentity(dir, token);
|
|
13522
|
+
ensureSpaceRemote(dir, appId, void 0, token);
|
|
13070
13523
|
const localWorkspaceRef = spacePrivateRef(`workspaces/${id}`);
|
|
13071
13524
|
runGitRemote(dir, token, [
|
|
13072
13525
|
"fetch",
|
|
@@ -13093,8 +13546,8 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13093
13546
|
spinner2?.stop(`Attached ${id}.`);
|
|
13094
13547
|
const action = { cwd: dir, argv: [detectPackageManager(dir), "install"] };
|
|
13095
13548
|
if (!args.json) {
|
|
13096
|
-
|
|
13097
|
-
|
|
13549
|
+
p27.log.info(`Task: ${view.workspace.task}`);
|
|
13550
|
+
p27.log.info("Pull app secrets after install if this workspace needs them.");
|
|
13098
13551
|
}
|
|
13099
13552
|
return {
|
|
13100
13553
|
data: {
|
|
@@ -13113,7 +13566,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13113
13566
|
});
|
|
13114
13567
|
|
|
13115
13568
|
// src/cli/commands/workspace/sync.ts
|
|
13116
|
-
import * as
|
|
13569
|
+
import * as p28 from "@clack/prompts";
|
|
13117
13570
|
var syncWorkspaceCommand = defineDeepspaceCommand({
|
|
13118
13571
|
meta: {
|
|
13119
13572
|
name: "sync",
|
|
@@ -13161,15 +13614,13 @@ var syncWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13161
13614
|
]);
|
|
13162
13615
|
if (before.workspace.status !== "active") {
|
|
13163
13616
|
spinner2?.stop("Workspace finished.");
|
|
13164
|
-
throw
|
|
13165
|
-
|
|
13166
|
-
"
|
|
13167
|
-
|
|
13168
|
-
);
|
|
13617
|
+
throw workspaceNotActiveRefusal(id, before.workspace.status, {
|
|
13618
|
+
cwd: appDir,
|
|
13619
|
+
argv: ["deepspace", "workspace", "drop", id, "--app", appId]
|
|
13620
|
+
});
|
|
13169
13621
|
}
|
|
13170
|
-
ensureSpaceRemote(appDir, appId);
|
|
13171
|
-
|
|
13172
|
-
pushWorkspaceRef(appDir, token, before.workspace.ref, headOid);
|
|
13622
|
+
ensureSpaceRemote(appDir, appId, void 0, token);
|
|
13623
|
+
pushWorkspaceRef(appDir, token, before.workspace.ref, headOid, before.tipOid);
|
|
13173
13624
|
try {
|
|
13174
13625
|
fetchTrunk(appDir, token, refs?.head ?? null);
|
|
13175
13626
|
} catch {
|
|
@@ -13183,16 +13634,16 @@ var syncWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13183
13634
|
const view = upToDate ? before : (await api4.syncWorkspace(id, { headOid })).view;
|
|
13184
13635
|
const dirty = statusFiles(appDir);
|
|
13185
13636
|
spinner2?.stop(
|
|
13186
|
-
upToDate ? `Already synced \u2014 ${id} is up to date at ${headOid.slice(0, 10)}.` : changedPaths !== void 0 ? `Synced ${id} \u2192 ${headOid.slice(0, 10)} (${changedPaths.length} ${changedPaths.length === 1 ? "file" : "files"} vs base).` : `Synced ${id} \u2192 ${headOid.slice(0, 10)} (base not present locally \u2014 no overlap report).`
|
|
13637
|
+
upToDate ? `Already synced \u2014 ${id} is up to date at ${headOid.slice(0, 10)}${dirty.length > 0 ? ` (${dirty.length} uncommitted file(s) remain unpublished)` : ""}.` : changedPaths !== void 0 ? `Synced ${id} \u2192 ${headOid.slice(0, 10)} (${changedPaths.length} ${changedPaths.length === 1 ? "file" : "files"} vs base).` : `Synced ${id} \u2192 ${headOid.slice(0, 10)} (base not present locally \u2014 no overlap report).`
|
|
13187
13638
|
);
|
|
13188
13639
|
printOverlaps(overlaps, args.json);
|
|
13189
13640
|
if (!args.json && trunkOverlap.length > 0) {
|
|
13190
|
-
|
|
13641
|
+
p28.log.warn(
|
|
13191
13642
|
`Trunk already changed ${trunkOverlap.length} file(s) you also touch \u2014 landed work you haven't merged: ${trunkOverlap.slice(0, 5).join(", ")}${trunkOverlap.length > 5 ? ` (+${trunkOverlap.length - 5} more)` : ""}. Merge trunk before landing (\`deepspace workspace status\` shows the full picture).`
|
|
13192
13643
|
);
|
|
13193
13644
|
}
|
|
13194
13645
|
if (!args.json && dirty.length > 0) {
|
|
13195
|
-
|
|
13646
|
+
p28.log.warn(
|
|
13196
13647
|
`${dirty.length} uncommitted ${dirty.length === 1 ? "file" : "files"} NOT synced \u2014 commit them to this workspace branch (WIP commits are fine \u2014 they land with the merge).`
|
|
13197
13648
|
);
|
|
13198
13649
|
}
|
|
@@ -13212,7 +13663,7 @@ var syncWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13212
13663
|
});
|
|
13213
13664
|
|
|
13214
13665
|
// src/cli/commands/workspace/list.ts
|
|
13215
|
-
import * as
|
|
13666
|
+
import * as p29 from "@clack/prompts";
|
|
13216
13667
|
function withWorkspaceOverlaps(views, overlapsById) {
|
|
13217
13668
|
return views.map((view) => ({
|
|
13218
13669
|
...view,
|
|
@@ -13235,21 +13686,24 @@ var listWorkspacesCommand = defineDeepspaceCommand({
|
|
|
13235
13686
|
typeof args.app === "string" ? args.app : void 0
|
|
13236
13687
|
);
|
|
13237
13688
|
const all = Boolean(args.all);
|
|
13238
|
-
const { views } = await api4.listWorkspaces({ all, limit });
|
|
13689
|
+
const { views, truncated } = await api4.listWorkspaces({ all, limit });
|
|
13239
13690
|
if (views.length === 0) {
|
|
13240
13691
|
spinner2?.stop("No workspaces found.");
|
|
13241
13692
|
if (!args.json) {
|
|
13242
|
-
|
|
13693
|
+
p29.log.info(
|
|
13243
13694
|
all ? "No workspaces." : 'No active workspaces. Create one with `deepspace workspace new -t "\u2026"`.'
|
|
13244
13695
|
);
|
|
13245
13696
|
}
|
|
13246
|
-
return { data: { workspaces: views } };
|
|
13697
|
+
return { data: { workspaces: views, truncated: truncated === true } };
|
|
13247
13698
|
}
|
|
13248
13699
|
spinner2?.message("Checking workspace overlaps\u2026");
|
|
13249
13700
|
const overlapsPromise = listOverlaps(appId, token, api4, views);
|
|
13250
13701
|
if (args.json) {
|
|
13251
13702
|
return {
|
|
13252
|
-
data: {
|
|
13703
|
+
data: {
|
|
13704
|
+
workspaces: withWorkspaceOverlaps(views, await overlapsPromise),
|
|
13705
|
+
truncated: truncated === true
|
|
13706
|
+
}
|
|
13253
13707
|
};
|
|
13254
13708
|
}
|
|
13255
13709
|
const [actors, overlapsById] = await Promise.all([actorLabels(token, appId), overlapsPromise]);
|
|
@@ -13268,13 +13722,18 @@ var listWorkspacesCommand = defineDeepspaceCommand({
|
|
|
13268
13722
|
);
|
|
13269
13723
|
}
|
|
13270
13724
|
}
|
|
13271
|
-
|
|
13272
|
-
|
|
13725
|
+
if (truncated) {
|
|
13726
|
+
p29.log.warn(
|
|
13727
|
+
`More workspaces exist than shown \u2014 this is the first ${views.length}. Raise it with \`--limit\` (up to 200); past that, narrow with \`--all\` off or land/drop finished work.`
|
|
13728
|
+
);
|
|
13729
|
+
}
|
|
13730
|
+
p29.log.info("\u2191 commits ahead of base \xB7 \u2193 trunk commits since base (staleness)");
|
|
13731
|
+
return { data: { workspaces, truncated: truncated === true } };
|
|
13273
13732
|
}
|
|
13274
13733
|
});
|
|
13275
13734
|
|
|
13276
13735
|
// src/cli/commands/workspace/status.ts
|
|
13277
|
-
import * as
|
|
13736
|
+
import * as p30 from "@clack/prompts";
|
|
13278
13737
|
var workspaceStatusCommand = defineDeepspaceCommand({
|
|
13279
13738
|
meta: { name: "status", description: "This workspace: base staleness, overlaps, sync state" },
|
|
13280
13739
|
args: {
|
|
@@ -13307,9 +13766,16 @@ var workspaceStatusCommand = defineDeepspaceCommand({
|
|
|
13307
13766
|
const baseLocal = resolveCommit(appDir, workspace.baseOid) !== null;
|
|
13308
13767
|
const behindExact = trunk && baseLocal ? revListCount(appDir, workspace.baseOid, trunk.oid) : null;
|
|
13309
13768
|
const aheadExact = headOid && baseLocal ? revListCount(appDir, workspace.baseOid, headOid) : null;
|
|
13310
|
-
const
|
|
13769
|
+
const publishedTip = view.tipOid ?? workspace.landedOid;
|
|
13770
|
+
if (publishedTip !== null && resolveCommit(appDir, publishedTip) === null) {
|
|
13771
|
+
runGitRemote(appDir, token, ["fetch", "--quiet", "--refmap=", SPACE_REMOTE, workspace.ref], {
|
|
13772
|
+
allowFail: true
|
|
13773
|
+
});
|
|
13774
|
+
}
|
|
13775
|
+
const headPublished = headOid !== null && publishedTip !== null && (publishedTip === headOid || workspaceDir !== null && resolveCommit(workspaceDir, publishedTip) !== null && isAncestor(workspaceDir, headOid, publishedTip));
|
|
13776
|
+
const unsynced = headOid !== null && !headPublished;
|
|
13311
13777
|
const dirty = workspaceDir ? statusFiles(workspaceDir) : [];
|
|
13312
|
-
const relation =
|
|
13778
|
+
const relation = workspace.status === "active" && workspaceDir ? workspaceSyncRelation(workspaceDir, headOid, view.tipOid) : "unknown";
|
|
13313
13779
|
const lines = await loadWorkspaceLines(appDir, token, api4, trunkOid);
|
|
13314
13780
|
const myTip = headOid ?? resolveCommit(appDir, peerWorkspaceRef(id)) ?? view.tipOid;
|
|
13315
13781
|
const line = myTip ? lineChangedPaths(appDir, workspace.baseOid, myTip, trunkOid) : null;
|
|
@@ -13339,41 +13805,39 @@ var workspaceStatusCommand = defineDeepspaceCommand({
|
|
|
13339
13805
|
overlaps
|
|
13340
13806
|
};
|
|
13341
13807
|
if (!args.json) {
|
|
13342
|
-
|
|
13343
|
-
|
|
13808
|
+
p30.log.info(`Task: ${workspace.task}`);
|
|
13809
|
+
p30.log.info(
|
|
13344
13810
|
`Base: ${workspace.baseOid.slice(0, 10)} \xB7 ahead ${aheadExact ?? "?"} commit(s) \xB7 trunk moved ${behindExact ?? "?"} commit(s) since your base`
|
|
13345
13811
|
);
|
|
13346
13812
|
if (!attached) {
|
|
13347
|
-
|
|
13348
|
-
`Not checked out in this clone \u2014 showing the published state (local HEAD/uncommitted unavailable). Attach it with \`deepspace workspace attach ${id} <dir>\`.`
|
|
13813
|
+
p30.log.info(
|
|
13814
|
+
workspace.status === "active" ? `Not checked out in this clone \u2014 showing the published state (local HEAD/uncommitted unavailable). Attach it with \`deepspace workspace attach ${id} <dir>\`.` : `Not checked out in this clone \u2014 showing the recorded state (the workspace is ${workspace.status}).`
|
|
13349
13815
|
);
|
|
13350
13816
|
}
|
|
13351
|
-
if (
|
|
13352
|
-
|
|
13353
|
-
|
|
13354
|
-
|
|
13355
|
-
|
|
13356
|
-
|
|
13357
|
-
|
|
13358
|
-
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
13367
|
-
|
|
13368
|
-
|
|
13369
|
-
|
|
13370
|
-
|
|
13371
|
-
);
|
|
13372
|
-
}
|
|
13817
|
+
if (attached && workspace.status === "landed") {
|
|
13818
|
+
p30.log.info(
|
|
13819
|
+
`Already landed${workspace.landedOid ? ` at ${workspace.landedOid.slice(0, 10)}` : ""} \u2014 this checkout is a leftover; \`deepspace workspace drop ${id}\` cleans it up.` + (unsynced ? " It has commits beyond the landed tip \u2014 nothing can publish them now." : "")
|
|
13820
|
+
);
|
|
13821
|
+
} else if (attached && workspace.status === "dropped") {
|
|
13822
|
+
p30.log.info(
|
|
13823
|
+
`Already dropped \u2014 this checkout is a leftover; \`deepspace workspace drop ${id}\` cleans it up.`
|
|
13824
|
+
);
|
|
13825
|
+
} else if (relation === "behind") {
|
|
13826
|
+
p30.log.warn(
|
|
13827
|
+
`Local HEAD ${headOid.slice(0, 10)} is BEHIND the published tip ${view.tipOid.slice(0, 10)} (another checkout synced ahead) \u2014 fast-forward first (\`git pull --no-rebase ${SPACE_REMOTE} ${workspace.ref}\`).`
|
|
13828
|
+
);
|
|
13829
|
+
} else if (relation === "diverged") {
|
|
13830
|
+
p30.log.warn(
|
|
13831
|
+
`Local HEAD ${headOid.slice(0, 10)} has DIVERGED from the published tip ${view.tipOid.slice(0, 10)} \u2014 integrate it first (\`git pull --no-rebase ${SPACE_REMOTE} ${workspace.ref}\`), then \`deepspace workspace sync\`.`
|
|
13832
|
+
);
|
|
13833
|
+
} else if (unsynced) {
|
|
13834
|
+
p30.log.warn(
|
|
13835
|
+
`Local HEAD ${headOid?.slice(0, 10)} is not synced (server has ${view.tipOid?.slice(0, 10) ?? "nothing"}) \u2014 run \`deepspace workspace sync\`.`
|
|
13836
|
+
);
|
|
13373
13837
|
}
|
|
13374
|
-
if (dirty.length > 0)
|
|
13838
|
+
if (dirty.length > 0) p30.log.warn(`${dirty.length} uncommitted file(s) in the worktree.`);
|
|
13375
13839
|
if (trunkOverlap.length > 0) {
|
|
13376
|
-
|
|
13840
|
+
p30.log.warn(
|
|
13377
13841
|
`Trunk also modified: ${trunkOverlap.slice(0, 5).join(", ")}${trunkOverlap.length > 5 ? ` (+${trunkOverlap.length - 5} more)` : ""} \u2014 expect merge attention at land time.`
|
|
13378
13842
|
);
|
|
13379
13843
|
}
|
|
@@ -13384,7 +13848,7 @@ var workspaceStatusCommand = defineDeepspaceCommand({
|
|
|
13384
13848
|
});
|
|
13385
13849
|
|
|
13386
13850
|
// src/cli/commands/workspace/land.ts
|
|
13387
|
-
import * as
|
|
13851
|
+
import * as p31 from "@clack/prompts";
|
|
13388
13852
|
|
|
13389
13853
|
// src/cli/lib/validation.ts
|
|
13390
13854
|
import { sync as spawnSync7 } from "cross-spawn";
|
|
@@ -13432,10 +13896,27 @@ function conflictMarkerFiles(diffCheckOutput) {
|
|
|
13432
13896
|
}
|
|
13433
13897
|
return [...files];
|
|
13434
13898
|
}
|
|
13435
|
-
function
|
|
13436
|
-
|
|
13437
|
-
|
|
13438
|
-
|
|
13899
|
+
function staleTrunkPullAction(trunkCheckoutDir, intoBranch, appArg) {
|
|
13900
|
+
return executableAction({
|
|
13901
|
+
cwd: trunkCheckoutDir,
|
|
13902
|
+
argv: [
|
|
13903
|
+
"deepspace",
|
|
13904
|
+
"pull",
|
|
13905
|
+
"-b",
|
|
13906
|
+
intoBranch,
|
|
13907
|
+
// The trunk checkout is a DIFFERENT directory; carry `-a` when the
|
|
13908
|
+
// caller named an app, or the action could resolve a different one
|
|
13909
|
+
// from that cwd.
|
|
13910
|
+
...appArg && appArg.trim() ? ["-a", appArg.trim()] : []
|
|
13911
|
+
]
|
|
13912
|
+
});
|
|
13913
|
+
}
|
|
13914
|
+
function staleTrunkCheckout(appDir, intoBranch, landedOid) {
|
|
13915
|
+
const dir = listWorktrees(appDir).find((worktree) => worktree.branch === intoBranch)?.path ?? null;
|
|
13916
|
+
if (dir === null) return null;
|
|
13917
|
+
const localTip = resolveCommit(dir, `refs/heads/${intoBranch}`);
|
|
13918
|
+
if (localTip === null || localTip === landedOid) return null;
|
|
13919
|
+
return isAncestor(dir, localTip, landedOid) ? dir : null;
|
|
13439
13920
|
}
|
|
13440
13921
|
function landResumeArgv(args) {
|
|
13441
13922
|
const argv = ["deepspace", "workspace", "land"];
|
|
@@ -13533,11 +14014,10 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13533
14014
|
spinner2?.stop("Workspace finished.");
|
|
13534
14015
|
throw workspaceNotActiveRefusal(id, view.workspace.status, dropAction);
|
|
13535
14016
|
}
|
|
13536
|
-
ensureSpaceRemote(appDir, appId);
|
|
13537
|
-
ensureGitIdentity(appDir, token);
|
|
14017
|
+
ensureSpaceRemote(appDir, appId, void 0, token);
|
|
13538
14018
|
const isResumedMerge = resolveCommit(appDir, "HEAD^2") !== null && view.tipOid !== null && resolveCommit(appDir, "HEAD^") === view.tipOid;
|
|
13539
14019
|
if (!isResumedMerge) {
|
|
13540
|
-
pushWorkspaceRef(appDir, token, view.workspace.ref, headBefore);
|
|
14020
|
+
pushWorkspaceRef(appDir, token, view.workspace.ref, headBefore, view.tipOid);
|
|
13541
14021
|
}
|
|
13542
14022
|
const intoBranch = intoArg?.trim() || (refs?.head?.startsWith("refs/heads/") ? refs.head.slice("refs/heads/".length) : null);
|
|
13543
14023
|
if (!intoBranch) {
|
|
@@ -13655,7 +14135,11 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13655
14135
|
}
|
|
13656
14136
|
const trunkSecret = committedSecretRefusal(appDir, resolveCommit(appDir, "HEAD"), {
|
|
13657
14137
|
action: `land onto ${intoBranch}`,
|
|
13658
|
-
then: "re-run the land"
|
|
14138
|
+
then: "re-run the land",
|
|
14139
|
+
// The fetched trunk tip is what this push uploads against, so the scan
|
|
14140
|
+
// covers exactly the commits the merge carries onto trunk — the same
|
|
14141
|
+
// range the server scans.
|
|
14142
|
+
base: remoteTip
|
|
13659
14143
|
});
|
|
13660
14144
|
if (trunkSecret) {
|
|
13661
14145
|
spinner2?.stop("Secret in history.");
|
|
@@ -13667,7 +14151,10 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13667
14151
|
if (push.status === "ref_conflict" || push.status === "non_fast_forward") {
|
|
13668
14152
|
spinner2?.stop("Trunk moved.");
|
|
13669
14153
|
throw new Refusal(
|
|
13670
|
-
|
|
14154
|
+
// The machine token belongs in `code`, not mid-sentence: the reason
|
|
14155
|
+
// arrives as `<code>: <sentence>`, and printing it raw put
|
|
14156
|
+
// "stale_ref: stale ref, fetch first" inside human prose.
|
|
14157
|
+
`${intoBranch} moved while you merged (${parseRefusalCode(push.reason ?? push.summary)?.sentence ?? push.reason ?? push.summary}). Re-run \`${resumeNext}\` to integrate the new tip.`,
|
|
13671
14158
|
"trunk_moved",
|
|
13672
14159
|
{
|
|
13673
14160
|
actionRequired: true,
|
|
@@ -13679,8 +14166,8 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13679
14166
|
if (push.status === "rejected") {
|
|
13680
14167
|
spinner2?.stop("Push rejected.");
|
|
13681
14168
|
throw new Refusal(
|
|
13682
|
-
|
|
13683
|
-
"
|
|
14169
|
+
pushFailureMessage("The land push", push, appDir),
|
|
14170
|
+
classifyRejection(push.reason ?? push.summary ?? "", appDir).code
|
|
13684
14171
|
);
|
|
13685
14172
|
}
|
|
13686
14173
|
try {
|
|
@@ -13715,8 +14202,13 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13715
14202
|
);
|
|
13716
14203
|
}
|
|
13717
14204
|
spinner2?.stop(`Landed ${id} into ${intoBranch} at ${landedOid.slice(0, 10)}.`);
|
|
13718
|
-
const
|
|
13719
|
-
const
|
|
14205
|
+
const trunkCheckoutDir = staleTrunkCheckout(appDir, intoBranch, landedOid);
|
|
14206
|
+
const localTrunkBehind = trunkCheckoutDir !== null;
|
|
14207
|
+
const action = trunkCheckoutDir ? staleTrunkPullAction(
|
|
14208
|
+
trunkCheckoutDir,
|
|
14209
|
+
intoBranch,
|
|
14210
|
+
typeof args.app === "string" ? args.app : void 0
|
|
14211
|
+
) : null;
|
|
13720
14212
|
const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, intoBranch, { expectedBranchOid: landedOid });
|
|
13721
14213
|
const inOwnWorktree = !cleanup && inOwnLinkedWorktree(appDir, id);
|
|
13722
14214
|
const retainedWorktree = cleanup?.worktreeRetained ?? (keepWorktree || inOwnWorktree ? appDir : null);
|
|
@@ -13724,6 +14216,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13724
14216
|
workspaceId: id,
|
|
13725
14217
|
into: intoBranch,
|
|
13726
14218
|
landedOid,
|
|
14219
|
+
...localTrunkBehind ? { localTrunkBehind: true } : {},
|
|
13727
14220
|
...validationResult ? { validation: validationResult } : {},
|
|
13728
14221
|
...retainedWorktree ? { worktree: retainedWorktree } : {},
|
|
13729
14222
|
...cleanup ? { cleanup: cleanupJson(cleanup) } : {}
|
|
@@ -13731,7 +14224,12 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13731
14224
|
if (!args.json && cleanup && !cleanup.error) {
|
|
13732
14225
|
reportCleanupHuman(cleanup);
|
|
13733
14226
|
} else if (!args.json && retainedWorktree) {
|
|
13734
|
-
|
|
14227
|
+
p31.log.info(`Retained checkout: ${retainedWorktree}`);
|
|
14228
|
+
}
|
|
14229
|
+
if (!args.json && localTrunkBehind) {
|
|
14230
|
+
p31.log.info(
|
|
14231
|
+
`Local ${intoBranch} in ${trunkCheckoutDir} is behind the landed merge \u2014 run \`deepspace pull\` there to catch up.`
|
|
14232
|
+
);
|
|
13735
14233
|
}
|
|
13736
14234
|
if (cleanup?.error) {
|
|
13737
14235
|
const action2 = cleanupAction({
|
|
@@ -13755,31 +14253,23 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13755
14253
|
});
|
|
13756
14254
|
|
|
13757
14255
|
// src/cli/commands/workspace/drop.ts
|
|
13758
|
-
import * as
|
|
14256
|
+
import * as p32 from "@clack/prompts";
|
|
13759
14257
|
function isWorkspaceTipPublished(appDir, localTip, publishedOids) {
|
|
13760
14258
|
return publishedOids.some(
|
|
13761
14259
|
(publishedOid) => publishedOid === localTip || isAncestor(appDir, localTip, publishedOid)
|
|
13762
14260
|
);
|
|
13763
14261
|
}
|
|
13764
14262
|
function workspaceUnsyncedRefusal(args) {
|
|
13765
|
-
const
|
|
13766
|
-
"deepspace",
|
|
13767
|
-
"workspace",
|
|
13768
|
-
"drop",
|
|
13769
|
-
args.id,
|
|
13770
|
-
"--app",
|
|
13771
|
-
args.appId,
|
|
13772
|
-
"--keep-worktree"
|
|
13773
|
-
];
|
|
14263
|
+
const dropArgv = ["deepspace", "workspace", "drop", args.id, "--app", args.appId];
|
|
13774
14264
|
if (args.status !== "active") {
|
|
14265
|
+
const keepArgv = [...dropArgv, "--keep-worktree"];
|
|
13775
14266
|
return new Refusal(
|
|
13776
|
-
`${args.id} is already ${args.status} on the server, and this checkout
|
|
14267
|
+
`${args.id} is already ${args.status} on the server, and this checkout holds commits that nothing in the cloud represents any more \u2014 nothing can publish them now. \`${humanCommand(keepArgv)}\` drops the cloud workspace but retains the local branch/worktree for manual disposal, or delete the branch yourself. Nothing was dropped.`,
|
|
13777
14268
|
"workspace_unsynced",
|
|
13778
14269
|
{ extra: { workspaceId: args.id, status: args.status } }
|
|
13779
14270
|
);
|
|
13780
14271
|
}
|
|
13781
14272
|
const syncArgv = ["deepspace", "workspace", "sync", "--app", args.appId, "--workspace", args.id];
|
|
13782
|
-
const dropArgv = ["deepspace", "workspace", "drop", args.id, "--app", args.appId];
|
|
13783
14273
|
const publish = args.workspaceDir ? `Publish them first with \`${humanCommand(syncArgv)}\`, then re-run \`${humanCommand(dropArgv)}\`.` : `Recreate a worktree from the branch (\`git worktree add <dir> ${args.branch}\`), publish from there with \`${humanCommand(syncArgv)}\`, then re-run \`${humanCommand(dropArgv)}\`.`;
|
|
13784
14274
|
return new Refusal(
|
|
13785
14275
|
`${args.id} has committed work that isn't published yet. ${publish} Alternatively, pass \`--keep-worktree\` to drop the cloud workspace while retaining the local branch/worktree for manual disposal. Nothing was dropped.`,
|
|
@@ -13817,6 +14307,11 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13817
14307
|
description: "Keep the local worktree and branch after dropping",
|
|
13818
14308
|
default: false
|
|
13819
14309
|
},
|
|
14310
|
+
"abandon-unseen": {
|
|
14311
|
+
type: "boolean",
|
|
14312
|
+
description: "Drop even though this checkout has not seen the published tip \u2014 an explicit decision to abandon commits you have not read (workspace_behind)",
|
|
14313
|
+
default: false
|
|
14314
|
+
},
|
|
13820
14315
|
app: APP_ARG
|
|
13821
14316
|
},
|
|
13822
14317
|
async run({ args }) {
|
|
@@ -13824,28 +14319,63 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13824
14319
|
assertExplicitWorkspaceId(explicitId);
|
|
13825
14320
|
const appArg = typeof args.app === "string" ? args.app : void 0;
|
|
13826
14321
|
const keepWorktree = Boolean(args["keep-worktree"]);
|
|
14322
|
+
const abandonUnseen = Boolean(args["abandon-unseen"]);
|
|
13827
14323
|
const spinner2 = args.json ? null : createSpinner();
|
|
13828
14324
|
spinner2?.start(`Preparing to drop ${explicitId ?? "workspace"}\u2026`);
|
|
13829
|
-
|
|
13830
|
-
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
|
|
13835
|
-
|
|
13836
|
-
|
|
14325
|
+
const inCheckout = findAppDir() !== null || !explicitId;
|
|
14326
|
+
const { appDir, appId, token, api: api4 } = inCheckout ? await resolveTarget2(appArg) : { appDir: null, ...await resolveApiOnly(appArg) };
|
|
14327
|
+
const id = appDir ? inferWorkspaceId(appDir, explicitId) : explicitId;
|
|
14328
|
+
const inspection = appDir ? inspectWorkspaceCleanup(appDir, id) : null;
|
|
14329
|
+
let approvedBranchOid;
|
|
14330
|
+
const { view: before } = await api4.getWorkspace(id);
|
|
14331
|
+
if (before.workspace.status === "active" && before.tipOid !== null) {
|
|
14332
|
+
if (appDir) {
|
|
14333
|
+
ensureSpaceRemote(appDir, appId, void 0, token);
|
|
14334
|
+
if (resolveCommit(appDir, before.tipOid) === null) {
|
|
14335
|
+
runGitRemote(
|
|
14336
|
+
appDir,
|
|
14337
|
+
token,
|
|
14338
|
+
["fetch", "--quiet", "--refmap=", SPACE_REMOTE, before.workspace.ref],
|
|
14339
|
+
{ allowFail: true }
|
|
14340
|
+
);
|
|
14341
|
+
}
|
|
14342
|
+
}
|
|
14343
|
+
const relation = workspaceSyncRelation(appDir, inspection?.branchOid ?? null, before.tipOid);
|
|
14344
|
+
if (!abandonUnseen && relation !== "in_sync" && relation !== "ahead") {
|
|
14345
|
+
const pullArgv = ["git", "pull", "--no-rebase", SPACE_REMOTE, before.workspace.ref];
|
|
14346
|
+
const attachArgv = ["deepspace", "workspace", "attach", id, "--app", appId];
|
|
14347
|
+
const abandonCommand = humanCommand([
|
|
14348
|
+
"deepspace",
|
|
14349
|
+
"workspace",
|
|
14350
|
+
"drop",
|
|
14351
|
+
id,
|
|
14352
|
+
"--app",
|
|
14353
|
+
appId,
|
|
14354
|
+
"--abandon-unseen"
|
|
14355
|
+
]);
|
|
14356
|
+
const worktreeDir = inspection?.checkout.dir ?? null;
|
|
14357
|
+
throw new Refusal(
|
|
14358
|
+
`Dropping ${id} would discard its published tip ${before.tipOid.slice(0, 10)}, which this seat has not read (${relation}) \u2014 integrate it first, or run \`${abandonCommand}\` to abandon it unread. Nothing was dropped.`,
|
|
14359
|
+
"workspace_behind",
|
|
14360
|
+
{
|
|
14361
|
+
action: worktreeDir ? { cwd: worktreeDir, argv: pullArgv } : appDir ? { cwd: appDir, argv: attachArgv } : void 0,
|
|
14362
|
+
extra: {
|
|
14363
|
+
workspaceId: id,
|
|
14364
|
+
publishedTip: before.tipOid,
|
|
14365
|
+
localTip: inspection?.branchOid ?? null,
|
|
14366
|
+
relation
|
|
14367
|
+
}
|
|
14368
|
+
}
|
|
13837
14369
|
);
|
|
13838
14370
|
}
|
|
13839
|
-
return { data: { workspaceId: explicitId, status: view2.workspace.status, remoteDropped: remoteDropped2 } };
|
|
13840
14371
|
}
|
|
13841
|
-
|
|
13842
|
-
const id = inferWorkspaceId(appDir, explicitId);
|
|
13843
|
-
const inspection = inspectWorkspaceCleanup(appDir, id);
|
|
13844
|
-
let approvedBranchOid;
|
|
13845
|
-
const { view: before } = await api4.getWorkspace(id);
|
|
13846
|
-
if (!keepWorktree && inspection.willDeleteBranch && inspection.branchOid) {
|
|
14372
|
+
if (appDir && inspection && !keepWorktree && inspection.willDeleteBranch && inspection.branchOid) {
|
|
13847
14373
|
const publishedOids = [
|
|
13848
|
-
|
|
14374
|
+
// A FINISHED workspace's own ref is being reaped — it can prove
|
|
14375
|
+
// nothing. Only trunk (landed) or the pre-existing base counts, or a
|
|
14376
|
+
// dropped workspace's last local copy gets deleted on the strength of
|
|
14377
|
+
// the very ref that is going away.
|
|
14378
|
+
...before.workspace.status === "active" ? [before.tipOid] : [],
|
|
13849
14379
|
before.workspace.landedOid,
|
|
13850
14380
|
before.workspace.baseOid
|
|
13851
14381
|
].filter((oid) => oid !== null);
|
|
@@ -13873,37 +14403,56 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
|
|
|
13873
14403
|
}
|
|
13874
14404
|
approvedBranchOid = inspection.branchOid;
|
|
13875
14405
|
}
|
|
13876
|
-
const cleanupDir = inspection
|
|
14406
|
+
const cleanupDir = inspection?.willDeleteBranch ? inspection.checkout.dir : null;
|
|
13877
14407
|
if (!keepWorktree && cleanupDir && !isWorkTreeClean(cleanupDir)) {
|
|
13878
14408
|
const dropArgv = ["deepspace", "workspace", "drop", id, "--app", appId];
|
|
13879
14409
|
const dropNext = humanCommand(dropArgv);
|
|
14410
|
+
const conflicted = unmergedIndexRefusal(cleanupDir, {
|
|
14411
|
+
ours: "A merge",
|
|
14412
|
+
resume: dropNext
|
|
14413
|
+
});
|
|
14414
|
+
if (conflicted) {
|
|
14415
|
+
throw new Refusal(
|
|
14416
|
+
`${conflicted.message} Or pass \`--keep-worktree\` to drop the workspace while retaining this checkout. Nothing was dropped.`,
|
|
14417
|
+
conflicted.code,
|
|
14418
|
+
{ extra: { workspaceId: id, conflict: true, operation: conflicted.operation } }
|
|
14419
|
+
);
|
|
14420
|
+
}
|
|
13880
14421
|
throw new Refusal(
|
|
13881
14422
|
`The workspace checkout has uncommitted changes and cleanup would remove it or switch it off ${inspection.branch}. Commit them and re-run \`${dropNext}\`, or pass \`--keep-worktree\` to drop while retaining the checkout. Nothing was dropped.`,
|
|
13882
14423
|
"dirty_worktree",
|
|
13883
14424
|
{ extra: { workspaceId: id } }
|
|
13884
14425
|
);
|
|
13885
14426
|
}
|
|
13886
|
-
const
|
|
13887
|
-
|
|
13888
|
-
|
|
14427
|
+
const statusBefore = (await api4.getWorkspace(id)).view.workspace.status;
|
|
14428
|
+
const { view, remoteDropped } = await dropRemoteTolerant(api4, id, statusBefore);
|
|
14429
|
+
if (appDir && !keepWorktree) spinner2?.message(`Cleaning up ${id} locally\u2026`);
|
|
14430
|
+
const cleanup = appDir && inspection && !keepWorktree ? cleanupWorkspaceLocal(appDir, id, null, {
|
|
13889
14431
|
expectedBranchOid: approvedBranchOid,
|
|
13890
14432
|
retainLocal: !inspection.willDeleteBranch
|
|
13891
|
-
});
|
|
13892
|
-
const retainedWorktree = cleanup?.worktreeRetained ?? (keepWorktree ? inspection
|
|
14433
|
+
}) : null;
|
|
14434
|
+
const retainedWorktree = cleanup?.worktreeRetained ?? (keepWorktree ? inspection?.checkout.dir ?? null : null);
|
|
14435
|
+
const discardedTip = remoteDropped && before.tipOid && (!inspection?.branchOid || abandonUnseen) ? before.tipOid : null;
|
|
13893
14436
|
const data = {
|
|
13894
14437
|
workspaceId: id,
|
|
13895
14438
|
status: view.workspace.status,
|
|
13896
14439
|
remoteDropped,
|
|
14440
|
+
...discardedTip ? { discardedTip } : {},
|
|
13897
14441
|
...retainedWorktree ? { worktree: retainedWorktree } : {},
|
|
13898
14442
|
...cleanup ? { cleanup: cleanupJson(cleanup) } : {}
|
|
13899
14443
|
};
|
|
13900
14444
|
spinner2?.stop();
|
|
13901
14445
|
if (!args.json) {
|
|
13902
|
-
|
|
13903
|
-
remoteDropped ? `Dropped ${id}. Its ref sticks around briefly for undo, then is reaped.` : `Workspace ${id} was already ${
|
|
14446
|
+
p32.log.success(
|
|
14447
|
+
remoteDropped ? `Dropped ${id}. Its ref sticks around briefly for undo, then is reaped.` : `Workspace ${id} was already ${statusBefore} on the server \u2014 nothing to drop remotely.`
|
|
13904
14448
|
);
|
|
14449
|
+
if (discardedTip) {
|
|
14450
|
+
p32.log.warn(
|
|
14451
|
+
`The published tip ${discardedTip.slice(0, 10)} was abandoned unread \u2014 this seat could not prove it had read it. Its ref is retained briefly if that was a mistake.`
|
|
14452
|
+
);
|
|
14453
|
+
}
|
|
13905
14454
|
if (cleanup && !cleanup.error) reportCleanupHuman(cleanup);
|
|
13906
|
-
else if (retainedWorktree)
|
|
14455
|
+
else if (retainedWorktree) p32.log.info(`Retained checkout: ${retainedWorktree}`);
|
|
13907
14456
|
}
|
|
13908
14457
|
if (cleanup?.error) {
|
|
13909
14458
|
const action = cleanupAction({
|
|
@@ -13944,7 +14493,7 @@ var workspace_default = defineCommand12({
|
|
|
13944
14493
|
});
|
|
13945
14494
|
|
|
13946
14495
|
// src/cli/commands/activity.ts
|
|
13947
|
-
import * as
|
|
14496
|
+
import * as p33 from "@clack/prompts";
|
|
13948
14497
|
var FOLLOW_INTERVAL_MS = 5e3;
|
|
13949
14498
|
function landIndex(events) {
|
|
13950
14499
|
const m = /* @__PURE__ */ new Map();
|
|
@@ -13958,12 +14507,16 @@ function landIndex(events) {
|
|
|
13958
14507
|
function formatEvent2(e, lands, actors) {
|
|
13959
14508
|
const s = e.summary ?? {};
|
|
13960
14509
|
const short = (oid) => typeof oid === "string" ? oid.slice(0, 10) : "?";
|
|
14510
|
+
const isZeroOid = (oid) => typeof oid === "string" && /^0{40}$/.test(oid);
|
|
13961
14511
|
const who = (id) => typeof id === "string" ? actors?.get(id) ?? id : String(id ?? "?");
|
|
13962
14512
|
let detail = "";
|
|
13963
14513
|
switch (e.kind) {
|
|
13964
14514
|
case "push": {
|
|
13965
14515
|
const refs = Array.isArray(s.refs) ? s.refs : [];
|
|
13966
|
-
detail = refs.map((r) =>
|
|
14516
|
+
detail = refs.map((r) => {
|
|
14517
|
+
const name = String(r.ref ?? "").replace("refs/heads/", "");
|
|
14518
|
+
return isZeroOid(r.newOid) ? `deleted ${name}` : `${name} \u2192 ${short(r.newOid)}`;
|
|
14519
|
+
}).join(", ");
|
|
13967
14520
|
const landWs = refs.map((r) => typeof r.newOid === "string" ? lands?.get(r.newOid) : void 0).find(Boolean);
|
|
13968
14521
|
if (landWs) detail += ` (land of ${landWs})`;
|
|
13969
14522
|
break;
|
|
@@ -13991,7 +14544,7 @@ function formatEvent2(e, lands, actors) {
|
|
|
13991
14544
|
default:
|
|
13992
14545
|
detail = "";
|
|
13993
14546
|
}
|
|
13994
|
-
return `#${e.seq} ${e.createdAt} ${e.kind} ${who(e.actor)} ${detail}`.trimEnd();
|
|
14547
|
+
return displayText(`#${e.seq} ${e.createdAt} ${e.kind} ${who(e.actor)} ${detail}`.trimEnd());
|
|
13995
14548
|
}
|
|
13996
14549
|
var activity_default = defineDeepspaceCommand({
|
|
13997
14550
|
meta: {
|
|
@@ -14060,7 +14613,7 @@ var activity_default = defineDeepspaceCommand({
|
|
|
14060
14613
|
const lands = landIndex(page.events);
|
|
14061
14614
|
for (const e of page.events) console.log(formatEvent2(e, lands, actors));
|
|
14062
14615
|
if (page.events.length === 0 && !args.follow) {
|
|
14063
|
-
|
|
14616
|
+
p33.log.info(cursor > 0 ? `No activity after #${cursor}.` : "No activity yet.");
|
|
14064
14617
|
}
|
|
14065
14618
|
}
|
|
14066
14619
|
cursor = page.cursor;
|
|
@@ -14070,7 +14623,7 @@ var activity_default = defineDeepspaceCommand({
|
|
|
14070
14623
|
}
|
|
14071
14624
|
if (!args.follow) {
|
|
14072
14625
|
if (page.events.length > 0) {
|
|
14073
|
-
|
|
14626
|
+
p33.log.info(
|
|
14074
14627
|
`Cursor: ${cursor}` + (page.hasMore ? ` \xB7 more available (next page: --since ${cursor})` : ` (resume later with --since ${cursor})`)
|
|
14075
14628
|
);
|
|
14076
14629
|
}
|
|
@@ -14092,7 +14645,7 @@ var activity_default = defineDeepspaceCommand({
|
|
|
14092
14645
|
})
|
|
14093
14646
|
);
|
|
14094
14647
|
} else {
|
|
14095
|
-
|
|
14648
|
+
p33.log.warn(`Activity feed interrupted (${code}); retrying in 5s.`);
|
|
14096
14649
|
}
|
|
14097
14650
|
}
|
|
14098
14651
|
await new Promise((resolveSleep) => setTimeout(resolveSleep, FOLLOW_INTERVAL_MS));
|
|
@@ -14143,13 +14696,6 @@ function declineHint(op, input, token, deployUrl) {
|
|
|
14143
14696
|
}
|
|
14144
14697
|
return "";
|
|
14145
14698
|
}
|
|
14146
|
-
async function readStdin() {
|
|
14147
|
-
if (process.stdin.isTTY) return "";
|
|
14148
|
-
process.stdin.setEncoding("utf-8");
|
|
14149
|
-
let data = "";
|
|
14150
|
-
for await (const chunk of process.stdin) data += chunk;
|
|
14151
|
-
return data;
|
|
14152
|
-
}
|
|
14153
14699
|
var git_credential_default = defineCommand13({
|
|
14154
14700
|
meta: {
|
|
14155
14701
|
name: "git-credential",
|
|
@@ -14164,7 +14710,7 @@ var git_credential_default = defineCommand13({
|
|
|
14164
14710
|
},
|
|
14165
14711
|
async run({ args }) {
|
|
14166
14712
|
const op = String(args.op ?? "");
|
|
14167
|
-
const input = await
|
|
14713
|
+
const input = process.stdin.isTTY ? "" : await readStreamText(process.stdin, MAX_STDIN_BYTES);
|
|
14168
14714
|
if (op !== "get") return;
|
|
14169
14715
|
const url = deployBaseUrl();
|
|
14170
14716
|
let token = null;
|
|
@@ -14185,7 +14731,7 @@ var git_credential_default = defineCommand13({
|
|
|
14185
14731
|
});
|
|
14186
14732
|
|
|
14187
14733
|
// src/cli/commands/source.ts
|
|
14188
|
-
import * as
|
|
14734
|
+
import * as p34 from "@clack/prompts";
|
|
14189
14735
|
var TRANSFER_PREFIX = spacePrivateRef("source-transfer");
|
|
14190
14736
|
var source_default = defineDeepspaceCommand({
|
|
14191
14737
|
meta: {
|
|
@@ -14239,14 +14785,14 @@ var source_default = defineDeepspaceCommand({
|
|
|
14239
14785
|
const reconciliation = reconcileSpaceRemote(appDir, appId, result.source);
|
|
14240
14786
|
const spaceRemote = reconciliation.spaceRemote;
|
|
14241
14787
|
if (!args.json) {
|
|
14242
|
-
|
|
14788
|
+
p34.log.success(
|
|
14243
14789
|
result.source.provider === "github" ? `Source: GitHub \xB7 ${result.source.repository} (revision ${result.revision})` : `Source: DeepSpace (revision ${result.revision})`
|
|
14244
14790
|
);
|
|
14245
|
-
|
|
14791
|
+
p34.log.info(
|
|
14246
14792
|
spaceRemote === "removed" ? `Removed the \`${SPACE_REMOTE}\` git remote \u2014 DeepSpace no longer owns this repo.` : spaceRemote === "absent" ? `No \`${SPACE_REMOTE}\` git remote to remove \u2014 source operations go through GitHub.` : spaceRemote === "present" ? `The \`${SPACE_REMOTE}\` git remote points at this app's cloud repo.` : `The local \`${SPACE_REMOTE}\` git remote still needs reconciliation.`
|
|
14247
14793
|
);
|
|
14248
14794
|
if (reconciliation.localReconciliation) {
|
|
14249
|
-
|
|
14795
|
+
p34.log.warn(reconciliation.localReconciliation.message);
|
|
14250
14796
|
}
|
|
14251
14797
|
}
|
|
14252
14798
|
return {
|
|
@@ -14482,14 +15028,14 @@ function sameRefs(left, right) {
|
|
|
14482
15028
|
}
|
|
14483
15029
|
|
|
14484
15030
|
// src/cli/commands/update.ts
|
|
14485
|
-
import * as
|
|
14486
|
-
import { existsSync as
|
|
15031
|
+
import * as p35 from "@clack/prompts";
|
|
15032
|
+
import { existsSync as existsSync29, readFileSync as readFileSync26 } from "fs";
|
|
14487
15033
|
import { join as join28, resolve as resolve22 } from "path";
|
|
14488
15034
|
|
|
14489
15035
|
// package.json
|
|
14490
15036
|
var package_default = {
|
|
14491
15037
|
name: "deepspace",
|
|
14492
|
-
version: "0.
|
|
15038
|
+
version: "0.25.0",
|
|
14493
15039
|
engines: {
|
|
14494
15040
|
node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
|
|
14495
15041
|
},
|
|
@@ -14565,7 +15111,7 @@ var package_default = {
|
|
|
14565
15111
|
"cross-spawn": "^7.0.6",
|
|
14566
15112
|
esbuild: "^0.27.0",
|
|
14567
15113
|
"highlight.js": "^11.11.1",
|
|
14568
|
-
hono: "^4.
|
|
15114
|
+
hono: "^4.12.34",
|
|
14569
15115
|
jose: "^6.1.0",
|
|
14570
15116
|
marked: "^18.0.7",
|
|
14571
15117
|
"mdast-util-gfm": "^3.1.0",
|
|
@@ -14667,7 +15213,7 @@ function gapIsTooWide(from, to) {
|
|
|
14667
15213
|
}
|
|
14668
15214
|
function readManifest(appDir) {
|
|
14669
15215
|
const path = join28(appDir, "package.json");
|
|
14670
|
-
if (!
|
|
15216
|
+
if (!existsSync29(path)) {
|
|
14671
15217
|
throw new Refusal("This app has no package.json \u2014 nothing to update.", "not_in_app_repo");
|
|
14672
15218
|
}
|
|
14673
15219
|
try {
|
|
@@ -14706,7 +15252,7 @@ function planDependencyGuidance(appDir) {
|
|
|
14706
15252
|
function usersSchemaVisibilityUpgradeInstruction(appDir) {
|
|
14707
15253
|
const schemaName = "src/schemas/users-schema.ts";
|
|
14708
15254
|
const path = join28(appDir, schemaName);
|
|
14709
|
-
if (!
|
|
15255
|
+
if (!existsSync29(path)) return null;
|
|
14710
15256
|
const source = readFileSync26(path, "utf8");
|
|
14711
15257
|
if (!/\bmember\s*:\s*\{[^}]*\bread\s*:\s*true\b/s.test(source)) return null;
|
|
14712
15258
|
return `${schemaName}: review member user-row visibility at ${USERS_SCHEMA_VISIBILITY_GUIDE}; leave the app-owned schema unchanged until you choose its policy.`;
|
|
@@ -14744,7 +15290,7 @@ var update_default = defineDeepspaceCommand({
|
|
|
14744
15290
|
} : null;
|
|
14745
15291
|
if (versionGuidance) {
|
|
14746
15292
|
const steps2 = [versionGuidance.step];
|
|
14747
|
-
if (!args.json)
|
|
15293
|
+
if (!args.json) p35.log.warn(versionGuidance.step);
|
|
14748
15294
|
return {
|
|
14749
15295
|
data: {
|
|
14750
15296
|
ready: false,
|
|
@@ -14780,9 +15326,9 @@ var update_default = defineDeepspaceCommand({
|
|
|
14780
15326
|
];
|
|
14781
15327
|
const status3 = unverifiedSdk ? "dependency_unverified" : steps.length === 0 ? "aligned" : "guidance_available";
|
|
14782
15328
|
if (!args.json) {
|
|
14783
|
-
|
|
14784
|
-
for (const step of steps)
|
|
14785
|
-
if (steps.length === 0)
|
|
15329
|
+
p35.log.info(`App dependency: ${currentSpec}; guidance owner: deepspace CLI ${targetVersion}`);
|
|
15330
|
+
for (const step of steps) p35.log.message(`\u2022 ${step}`);
|
|
15331
|
+
if (steps.length === 0) p35.log.success("This app is already aligned with this CLI version.");
|
|
14786
15332
|
}
|
|
14787
15333
|
const data = {
|
|
14788
15334
|
ready: status3 === "aligned",
|
|
@@ -14802,9 +15348,13 @@ var update_default = defineDeepspaceCommand({
|
|
|
14802
15348
|
});
|
|
14803
15349
|
|
|
14804
15350
|
// src/cli/cli.ts
|
|
15351
|
+
process.stdout.on("error", (error) => {
|
|
15352
|
+
if (error.code === "EPIPE") return;
|
|
15353
|
+
throw error;
|
|
15354
|
+
});
|
|
14805
15355
|
var __dirname = dirname13(fileURLToPath3(import.meta.url));
|
|
14806
15356
|
var pkg = JSON.parse(readFileSync27(join29(__dirname, "..", "package.json"), "utf-8"));
|
|
14807
|
-
if (process.platform === "linux" && !process.env.SSL_CERT_FILE &&
|
|
15357
|
+
if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync30("/etc/ssl/certs/ca-certificates.crt")) {
|
|
14808
15358
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
14809
15359
|
}
|
|
14810
15360
|
var create2 = defineCommand14({
|
|
@@ -14832,6 +15382,16 @@ var create2 = defineCommand14({
|
|
|
14832
15382
|
}
|
|
14833
15383
|
},
|
|
14834
15384
|
run({ rawArgs: rawArgs2 }) {
|
|
15385
|
+
if (rawArgs2.includes("--json")) {
|
|
15386
|
+
console.log(
|
|
15387
|
+
JSON.stringify({
|
|
15388
|
+
ok: false,
|
|
15389
|
+
code: "invalid_flags",
|
|
15390
|
+
error: "`app create` runs the interactive scaffolder, which has no JSON mode \u2014 its output is a directory, not a document. Run it without --json; every later command in the new app supports it."
|
|
15391
|
+
})
|
|
15392
|
+
);
|
|
15393
|
+
process.exit(1);
|
|
15394
|
+
}
|
|
14835
15395
|
const scaffolder = `create-deepspace@${pkg.version}`;
|
|
14836
15396
|
console.log(`Running: npx ${scaffolder} ${rawArgs2.join(" ")}`.trimEnd() + "\n");
|
|
14837
15397
|
const res = spawnSync8("npx", ["-y", scaffolder, ...rawArgs2], { stdio: "inherit" });
|
|
@@ -14931,7 +15491,12 @@ function assertKnownCommandPath(argv) {
|
|
|
14931
15491
|
Run \`${unknown.helpPath.join(" ")} --help\` for the command list.`
|
|
14932
15492
|
);
|
|
14933
15493
|
if (unknown.executable) {
|
|
14934
|
-
printAction(
|
|
15494
|
+
printAction(
|
|
15495
|
+
executableAction({
|
|
15496
|
+
cwd: process.cwd(),
|
|
15497
|
+
argv: [...unknown.suggestion, ...unknown.remainder]
|
|
15498
|
+
})
|
|
15499
|
+
);
|
|
14935
15500
|
}
|
|
14936
15501
|
process.exit(1);
|
|
14937
15502
|
}
|
|
@@ -14997,7 +15562,18 @@ if (rawArgs.includes("--json") && !wantsHelp) {
|
|
|
14997
15562
|
ok: false,
|
|
14998
15563
|
code: "unknown_command",
|
|
14999
15564
|
error: unknownCommandMessage(unknown),
|
|
15000
|
-
|
|
15565
|
+
// The corrected verb PLUS what the caller wrote after it: an action
|
|
15566
|
+
// that drops the positionals refuses `missing_argument` when run
|
|
15567
|
+
// verbatim, which is the one thing the action contract promises it
|
|
15568
|
+
// will not do. Attached ONLY when the correction is executable
|
|
15569
|
+
// (certain and non-destructive); a guess would hand an agent a
|
|
15570
|
+
// different verb to execute against its own arguments.
|
|
15571
|
+
...unknown.executable ? {
|
|
15572
|
+
action: executableAction({
|
|
15573
|
+
cwd: process.cwd(),
|
|
15574
|
+
argv: [...unknown.suggestion, ...unknown.remainder]
|
|
15575
|
+
})
|
|
15576
|
+
} : {}
|
|
15001
15577
|
})
|
|
15002
15578
|
);
|
|
15003
15579
|
process.exit(1);
|
|
@@ -15006,7 +15582,7 @@ if (rawArgs.includes("--json") && !wantsHelp) {
|
|
|
15006
15582
|
stopActiveSpinner();
|
|
15007
15583
|
const cittyCode = err instanceof Error && err.name === "CLIError" ? err.code : void 0;
|
|
15008
15584
|
const code = cittyCode === "EARG" ? "missing_argument" : cittyCode === "E_UNKNOWN_COMMAND" ? "unknown_command" : cittyCode === "E_NO_COMMAND" ? "no_command" : errorCode(err);
|
|
15009
|
-
const message = stripAnsi(
|
|
15585
|
+
const message = stripAnsi(formatCliError(err));
|
|
15010
15586
|
console.log(JSON.stringify({ ok: false, error: message, ...code ? { code } : {} }));
|
|
15011
15587
|
process.exit(1);
|
|
15012
15588
|
});
|