mergestorm 0.3.9 → 0.3.13
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/README.md +57 -13
- package/dist/api.d.ts +27 -38
- package/dist/api.js +81 -4
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.js +5 -0
- package/dist/commands/credits.js +33 -12
- package/dist/commands/login.js +28 -7
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/registry.js +26 -19
- package/dist/commands/review-client.d.ts +93 -0
- package/dist/commands/review-client.js +289 -0
- package/dist/commands/review.d.ts +30 -6
- package/dist/commands/review.js +385 -146
- package/dist/commands/stack.d.ts +34 -2
- package/dist/commands/stack.js +252 -58
- package/dist/commands/status.d.ts +16 -2
- package/dist/commands/status.js +221 -21
- package/dist/errors.d.ts +18 -2
- package/dist/errors.js +22 -1
- package/dist/git-stack.d.ts +2 -0
- package/dist/git-stack.js +4 -0
- package/dist/git.d.ts +12 -1
- package/dist/git.js +52 -7
- package/dist/shell.d.ts +1 -1
- package/dist/shell.js +6 -3
- package/dist/stack-dto.d.ts +185 -0
- package/dist/stack-dto.js +7 -0
- package/dist/submit-pr-base.d.ts +31 -0
- package/dist/submit-pr-base.js +69 -0
- package/dist/ui/banner.js +7 -0
- package/dist/ui/box.d.ts +13 -9
- package/dist/ui/box.js +28 -44
- package/dist/ui/findings.d.ts +2 -0
- package/dist/ui/findings.js +2 -1
- package/dist/ui/job-envelope.d.ts +68 -0
- package/dist/ui/job-envelope.js +35 -0
- package/dist/ui/prompt.d.ts +91 -0
- package/dist/ui/prompt.js +241 -62
- package/dist/ui/usage.d.ts +25 -1
- package/dist/ui/usage.js +50 -1
- package/dist/ui/width.d.ts +26 -0
- package/dist/ui/width.js +262 -0
- package/package.json +5 -1
package/dist/commands/stack.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { adoptStack, landNextStack, listStacks, restackStack, setStackAutoPromote, } from "../api.js";
|
|
1
|
+
import { adoptStack, ensureUpperPark, landNextStack, listStacks, restackStack, setStackAutoPromote, } from "../api.js";
|
|
2
2
|
import { loadConfig } from "../config.js";
|
|
3
3
|
import { CommandError } from "../errors.js";
|
|
4
4
|
import { createPr, findOpenPrNumber, requireGh } from "../gh.js";
|
|
5
5
|
import { parseGithubOriginRepo } from "../git.js";
|
|
6
|
-
import { branchExists, commitsAheadOf, createBranchFromHead, currentBranch, defaultLayerBranchName, deleteBranch, discoverTrunk, pushBranch, tipCommitMessage, tipCommitSubject, worktreeDirty, } from "../git-stack.js";
|
|
6
|
+
import { branchExists, commitsAheadOf, createBranchFromHead, currentBranch, defaultLayerBranchName, deleteBranch, discoverTrunk, fetchRemoteBranch, pushBranch, tipCommitMessage, tipCommitSubject, worktreeDirty, } from "../git-stack.js";
|
|
7
7
|
import { buildPrBodyFromCommit } from "../pr-body.js";
|
|
8
8
|
import { gitTopLevel } from "../repo-identity.js";
|
|
9
9
|
import { activeLayers, appendLayer, emptyStackMeta, loadStackMeta, removeActiveStack, resetStackMeta, saveStackMeta, selectStackForCreate, } from "../stack-meta.js";
|
|
10
|
+
import { isMgParkBranch, planSubmitLayerBases } from "../submit-pr-base.js";
|
|
10
11
|
import { ansi } from "../ui/ansi.js";
|
|
11
12
|
const STACK_USAGE = `usage:
|
|
12
|
-
mergestorm stack create [name] [--onto <branch>] [--trunk <branch>] [--json]
|
|
13
|
-
mergestorm stack submit [--json]
|
|
13
|
+
mergestorm stack create [name] [--onto <branch>] [--trunk <branch>] [--extend] [--json]
|
|
14
|
+
mergestorm stack submit [--extend] [--json]
|
|
14
15
|
mergestorm stack reset --force
|
|
15
16
|
mergestorm stack list [--json]
|
|
16
17
|
mergestorm stack adopt <owner/repo>#<pr>
|
|
@@ -19,8 +20,55 @@ const STACK_USAGE = `usage:
|
|
|
19
20
|
mergestorm stack auto-promote on|off <stack-id> [--json]
|
|
20
21
|
|
|
21
22
|
stack submit opens PRs with a body generated from each layer tip commit.
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
Layers 1–2 use the git parent as GitHub base. Layer 3+ opens onto the
|
|
24
|
+
review-unit park freeze (mg-park-*) so adopt does not retarget after open.
|
|
25
|
+
Parenting onto a branch that already belongs to a registered (submitted)
|
|
26
|
+
stack requires --extend on create and submit — otherwise mg starts a new
|
|
27
|
+
stack from trunk. stack land promotes into the review unit when one exists;
|
|
28
|
+
otherwise lands the bottom open PR (unit-less stacks).`;
|
|
29
|
+
/** Cap the registered-stack guard's API probe so it cannot stall local authoring. */
|
|
30
|
+
const STACK_GUARD_TIMEOUT_MS = 8_000;
|
|
31
|
+
/** Find a registered stack layer whose branch matches `parentBranch`. */
|
|
32
|
+
export function findRegisteredParent(stacks, parentBranch, owner, repo) {
|
|
33
|
+
const want = parentBranch.trim();
|
|
34
|
+
if (!want)
|
|
35
|
+
return null;
|
|
36
|
+
for (const s of stacks) {
|
|
37
|
+
if (owner !== undefined && repo !== undefined) {
|
|
38
|
+
if (s.owner !== owner || s.repo !== repo)
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
for (const layer of s.layers) {
|
|
42
|
+
if (layer.branch === want) {
|
|
43
|
+
return {
|
|
44
|
+
stackId: s.id,
|
|
45
|
+
owner: s.owner,
|
|
46
|
+
repo: s.repo,
|
|
47
|
+
branch: layer.branch,
|
|
48
|
+
prNumber: layer.prNumber,
|
|
49
|
+
position: layer.position,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Refuse silently growing an already-registered stack.
|
|
58
|
+
* Local unsubmitted parents are fine; only API-listed layers trip this.
|
|
59
|
+
*/
|
|
60
|
+
export function assertMayParentOntoRegistered(parentBranch, stacks, extend, verb, owner, repo) {
|
|
61
|
+
const hit = findRegisteredParent(stacks, parentBranch, owner, repo);
|
|
62
|
+
if (!hit)
|
|
63
|
+
return null;
|
|
64
|
+
if (extend)
|
|
65
|
+
return hit;
|
|
66
|
+
const pr = hit.prNumber > 0 ? `PR #${hit.prNumber}` : "an open PR";
|
|
67
|
+
throw new CommandError(`Parent \`${hit.branch}\` is already layer ${hit.position} of registered stack ${hit.stackId} (${hit.owner}/${hit.repo}, ${pr}).\n` +
|
|
68
|
+
`Refusing to ${verb} a new layer onto that stack without an explicit opt-in.\n` +
|
|
69
|
+
`• New independent stack: check out trunk and pass \`--onto <trunk>\` (e.g. \`mg stack create --onto main\`).\n` +
|
|
70
|
+
`• Intentionally grow that stack: pass \`--extend\` on \`stack ${verb}\`.`, 1, "registered_parent");
|
|
71
|
+
}
|
|
24
72
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
25
73
|
export function requireStackId(raw, usage) {
|
|
26
74
|
const id = raw?.trim() ?? "";
|
|
@@ -70,6 +118,7 @@ export function parseStackCreateArgs(args) {
|
|
|
70
118
|
let name;
|
|
71
119
|
let onto;
|
|
72
120
|
let trunk;
|
|
121
|
+
let extend = false;
|
|
73
122
|
let asJson = false;
|
|
74
123
|
for (let i = 0; i < args.length; i++) {
|
|
75
124
|
const a = args[i];
|
|
@@ -77,10 +126,15 @@ export function parseStackCreateArgs(args) {
|
|
|
77
126
|
asJson = true;
|
|
78
127
|
continue;
|
|
79
128
|
}
|
|
129
|
+
if (a === "--extend") {
|
|
130
|
+
extend = true;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
80
133
|
if (a === "--onto") {
|
|
81
134
|
const v = args[++i];
|
|
82
|
-
if (!v)
|
|
83
|
-
throw new CommandError("usage: mergestorm stack create [name] [--onto <branch>]");
|
|
135
|
+
if (!v) {
|
|
136
|
+
throw new CommandError("usage: mergestorm stack create [name] [--onto <branch>] [--trunk <branch>] [--extend]");
|
|
137
|
+
}
|
|
84
138
|
onto = v;
|
|
85
139
|
continue;
|
|
86
140
|
}
|
|
@@ -90,8 +144,9 @@ export function parseStackCreateArgs(args) {
|
|
|
90
144
|
}
|
|
91
145
|
if (a === "--trunk") {
|
|
92
146
|
const v = args[++i];
|
|
93
|
-
if (!v)
|
|
94
|
-
throw new CommandError("usage: mergestorm stack create [name] [--trunk <branch>]");
|
|
147
|
+
if (!v) {
|
|
148
|
+
throw new CommandError("usage: mergestorm stack create [name] [--onto <branch>] [--trunk <branch>] [--extend]");
|
|
149
|
+
}
|
|
95
150
|
trunk = v;
|
|
96
151
|
continue;
|
|
97
152
|
}
|
|
@@ -103,20 +158,40 @@ export function parseStackCreateArgs(args) {
|
|
|
103
158
|
throw new CommandError(`${STACK_USAGE}\nunknown flag: ${a}`);
|
|
104
159
|
}
|
|
105
160
|
if (name != null) {
|
|
106
|
-
throw new CommandError("usage: mergestorm stack create [name] [--onto <branch>] [--trunk <branch>]");
|
|
161
|
+
throw new CommandError("usage: mergestorm stack create [name] [--onto <branch>] [--trunk <branch>] [--extend]");
|
|
107
162
|
}
|
|
108
163
|
name = a;
|
|
109
164
|
}
|
|
110
|
-
return { name, onto, trunk, asJson };
|
|
165
|
+
return { name, onto, trunk, extend, asJson };
|
|
166
|
+
}
|
|
167
|
+
/** Parse `stack submit` argv. Exported for tests. */
|
|
168
|
+
export function parseStackSubmitArgs(args) {
|
|
169
|
+
let extend = false;
|
|
170
|
+
let asJson = false;
|
|
171
|
+
for (const a of args) {
|
|
172
|
+
if (a === "--json") {
|
|
173
|
+
asJson = true;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (a === "--extend") {
|
|
177
|
+
extend = true;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
throw new CommandError("usage: mergestorm stack submit [--extend] [--json]");
|
|
181
|
+
}
|
|
182
|
+
return { extend, asJson };
|
|
111
183
|
}
|
|
112
184
|
export function parseStackResetArgs(args) {
|
|
113
185
|
if (args.length !== 1 || args[0] !== "--force") {
|
|
114
186
|
throw new CommandError("usage: mergestorm stack reset --force\nClears only CLI-managed pre-submit state; branches, PRs, and registered stacks are unchanged.");
|
|
115
187
|
}
|
|
116
188
|
}
|
|
117
|
-
async function cmdStackCreate(args) {
|
|
118
|
-
const { name, onto, trunk: trunkFlag, asJson } = parseStackCreateArgs(args);
|
|
119
|
-
const cwd = process.cwd();
|
|
189
|
+
async function cmdStackCreate(args, deps = {}) {
|
|
190
|
+
const { name, onto, trunk: trunkFlag, extend, asJson } = parseStackCreateArgs(args);
|
|
191
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
192
|
+
const listStacksFn = deps.listStacks ?? listStacks;
|
|
193
|
+
const loadConfigFn = deps.loadConfig ?? loadConfig;
|
|
194
|
+
const parseOriginFn = deps.parseGithubOriginRepo ?? parseGithubOriginRepo;
|
|
120
195
|
try {
|
|
121
196
|
gitTopLevel(cwd);
|
|
122
197
|
}
|
|
@@ -145,6 +220,37 @@ async function cmdStackCreate(args) {
|
|
|
145
220
|
else if (trunkFlag?.trim() && meta.trunk !== trunk) {
|
|
146
221
|
throw new CommandError(`Stack trunk is ${meta.trunk}; refusing --trunk ${trunk}. Run \`mg stack reset --force\` to discard local pre-submit state.`);
|
|
147
222
|
}
|
|
223
|
+
// --trunk only sets metadata; parent defaults to current branch. Call that out
|
|
224
|
+
// when someone passes --trunk while sitting on a different branch without --onto.
|
|
225
|
+
if (trunkFlag?.trim() && !onto?.trim() && parentBranch !== trunk) {
|
|
226
|
+
console.error(ansi.dim(` note: --trunk ${trunk} does not change the parent; new layer parents onto \`${parentBranch}\`. ` +
|
|
227
|
+
`Pass \`--onto ${trunk}\` for a fresh stack from trunk.`));
|
|
228
|
+
}
|
|
229
|
+
// Block silent attach onto an already-submitted stack tip (requires --extend).
|
|
230
|
+
// --trunk is user metadata, so a registered tip must not be able to masquerade
|
|
231
|
+
// as trunk — always consult the stacks API rather than trusting parent === trunk.
|
|
232
|
+
// Best-effort: API/key failures degrade to a warning so create stays local.
|
|
233
|
+
const origin = parseOriginFn(cwd);
|
|
234
|
+
let registeredHit = null;
|
|
235
|
+
if (origin) {
|
|
236
|
+
try {
|
|
237
|
+
const cfg = await loadConfigFn();
|
|
238
|
+
const registered = await listStacksFn(cfg, { timeoutMs: STACK_GUARD_TIMEOUT_MS });
|
|
239
|
+
registeredHit = assertMayParentOntoRegistered(parentBranch, registered, extend, "create", origin.owner, origin.repo);
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
if (err instanceof CommandError && err.code === "registered_parent")
|
|
243
|
+
throw err;
|
|
244
|
+
console.error(ansi.dim(` warning: could not check registered stacks (${err instanceof Error ? err.message : String(err)}); ` +
|
|
245
|
+
`skipping --extend guard. Pass --extend if you intend to grow an open stack.`));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
console.error(ansi.dim(" warning: could not determine GitHub owner/repo from `origin`; skipping --extend guard. Pass --extend if you intend to grow an open stack."));
|
|
250
|
+
}
|
|
251
|
+
if (parentBranch === trunk && extend && !registeredHit) {
|
|
252
|
+
throw new CommandError("`--extend` is only valid when parenting onto a registered stack layer, not trunk. Omit --extend (or pass --onto <tip> --extend).");
|
|
253
|
+
}
|
|
148
254
|
try {
|
|
149
255
|
meta = selectStackForCreate(meta, parentBranch);
|
|
150
256
|
}
|
|
@@ -174,6 +280,7 @@ async function cmdStackCreate(args) {
|
|
|
174
280
|
branch,
|
|
175
281
|
parentBranch,
|
|
176
282
|
trunk: meta.trunk,
|
|
283
|
+
extend: Boolean(registeredHit),
|
|
177
284
|
active: meta.active,
|
|
178
285
|
stacks: meta.stacks.length,
|
|
179
286
|
layers,
|
|
@@ -182,14 +289,15 @@ async function cmdStackCreate(args) {
|
|
|
182
289
|
return;
|
|
183
290
|
}
|
|
184
291
|
console.log(ansi.green(` Created ${branch} (onto ${parentBranch})`));
|
|
292
|
+
if (registeredHit) {
|
|
293
|
+
console.log(ansi.dim(` extending registered stack ${registeredHit.stackId}` +
|
|
294
|
+
(registeredHit.prNumber > 0 ? ` (from #${registeredHit.prNumber})` : "")));
|
|
295
|
+
}
|
|
185
296
|
console.log(ansi.dim(` trunk: ${meta.trunk} · local stacks: ${meta.stacks.length} · active layers: ${layers.length}`));
|
|
186
297
|
console.log(ansi.dim(" authoring state: managed automatically outside the repository"));
|
|
187
298
|
}
|
|
188
299
|
export async function cmdStackSubmit(args, deps = {}) {
|
|
189
|
-
const asJson = args
|
|
190
|
-
if (args.some((a) => a !== "--json")) {
|
|
191
|
-
throw new CommandError("usage: mergestorm stack submit [--json]");
|
|
192
|
-
}
|
|
300
|
+
const { extend, asJson } = parseStackSubmitArgs(args);
|
|
193
301
|
const cwd = deps.cwd ?? process.cwd();
|
|
194
302
|
const gitTopLevelFn = deps.gitTopLevel ?? gitTopLevel;
|
|
195
303
|
const loadStackMetaFn = deps.loadStackMeta ?? loadStackMeta;
|
|
@@ -197,6 +305,7 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
197
305
|
const parseOriginFn = deps.parseGithubOriginRepo ?? parseGithubOriginRepo;
|
|
198
306
|
const requireGhFn = deps.requireGh ?? requireGh;
|
|
199
307
|
const branchExistsFn = deps.branchExists ?? branchExists;
|
|
308
|
+
const fetchRemoteBranchFn = deps.fetchRemoteBranch ?? fetchRemoteBranch;
|
|
200
309
|
const pushBranchFn = deps.pushBranch ?? pushBranch;
|
|
201
310
|
const findOpenPrNumberFn = deps.findOpenPrNumber ?? findOpenPrNumber;
|
|
202
311
|
const commitsAheadOfFn = deps.commitsAheadOf ?? commitsAheadOf;
|
|
@@ -205,7 +314,9 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
205
314
|
const buildPrBodyFn = deps.buildPrBodyFromCommit ?? buildPrBodyFromCommit;
|
|
206
315
|
const createPrFn = deps.createPr ?? createPr;
|
|
207
316
|
const loadConfigFn = deps.loadConfig ?? loadConfig;
|
|
317
|
+
const listStacksFn = deps.listStacks ?? listStacks;
|
|
208
318
|
const adoptStackFn = deps.adoptStack ?? adoptStack;
|
|
319
|
+
const ensureUpperParkFn = deps.ensureUpperPark ?? ensureUpperPark;
|
|
209
320
|
try {
|
|
210
321
|
gitTopLevelFn(cwd);
|
|
211
322
|
}
|
|
@@ -223,9 +334,41 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
223
334
|
}
|
|
224
335
|
const { owner, repo } = origin;
|
|
225
336
|
requireGhFn(cwd);
|
|
226
|
-
|
|
337
|
+
// Safety net: refuse submit that would open/register onto a registered tip
|
|
338
|
+
// without --extend. Check every layer's parent (mid-stack layers from older
|
|
339
|
+
// local state must not sail through), scoped to this repo, and fail closed
|
|
340
|
+
// when the API cannot be consulted — otherwise the attach happens anyway.
|
|
341
|
+
// meta.trunk is user metadata, so it must not exempt a registered tip from
|
|
342
|
+
// the check (a tip can masquerade as trunk via --trunk at create).
|
|
343
|
+
const parents = [...new Set(layers.map((l) => l.parentBranch || meta.trunk))];
|
|
344
|
+
let registeredHits = 0;
|
|
345
|
+
let registered = [];
|
|
346
|
+
try {
|
|
347
|
+
const cfg = await loadConfigFn();
|
|
348
|
+
registered = await listStacksFn(cfg, { timeoutMs: STACK_GUARD_TIMEOUT_MS });
|
|
349
|
+
for (const parent of parents) {
|
|
350
|
+
if (assertMayParentOntoRegistered(parent, registered, extend, "submit", owner, repo)) {
|
|
351
|
+
registeredHits += 1;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
if (err instanceof CommandError && err.code === "registered_parent")
|
|
357
|
+
throw err;
|
|
358
|
+
throw new CommandError(`Could not verify that the stack parents onto registered stacks (${err instanceof Error ? err.message : String(err)}); ` +
|
|
359
|
+
`refusing to submit. If a layer grows a registered stack, pass --extend (retry when the API is reachable).`);
|
|
360
|
+
}
|
|
361
|
+
if (extend && registeredHits === 0 && parents.every((p) => p === meta.trunk)) {
|
|
362
|
+
throw new CommandError("`--extend` is only valid when a layer parents onto a registered stack layer, not trunk.");
|
|
363
|
+
}
|
|
364
|
+
const plans = planSubmitLayerBases({
|
|
365
|
+
layers,
|
|
366
|
+
trunk: meta.trunk,
|
|
367
|
+
registered,
|
|
368
|
+
owner,
|
|
369
|
+
repo,
|
|
370
|
+
});
|
|
227
371
|
for (const layer of layers) {
|
|
228
|
-
const base = layer.parentBranch || meta.trunk;
|
|
229
372
|
if (!branchExistsFn(layer.branch, cwd)) {
|
|
230
373
|
throw new CommandError(`Stack layer branch missing locally: ${layer.branch}`);
|
|
231
374
|
}
|
|
@@ -236,58 +379,109 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
236
379
|
catch (err) {
|
|
237
380
|
throw new CommandError(err instanceof Error ? err.message : String(err));
|
|
238
381
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
382
|
+
}
|
|
383
|
+
const opened = [];
|
|
384
|
+
const pendingPark = plans.filter((plan) => {
|
|
385
|
+
if (findOpenPrNumberFn(owner, repo, plan.branch, cwd) != null)
|
|
386
|
+
return false;
|
|
387
|
+
return plan.githubBase == null;
|
|
388
|
+
});
|
|
389
|
+
const openLayerPr = (branch, base) => {
|
|
390
|
+
const existing = findOpenPrNumberFn(owner, repo, branch, cwd);
|
|
391
|
+
if (existing != null) {
|
|
392
|
+
console.log(ansi.dim(` PR #${existing} already open for ${branch}`));
|
|
393
|
+
return { prNumber: existing, created: false };
|
|
394
|
+
}
|
|
395
|
+
const aheadRef = isMgParkBranch(base) ? `origin/${base}` : base;
|
|
396
|
+
if (isMgParkBranch(base)) {
|
|
247
397
|
try {
|
|
248
|
-
|
|
398
|
+
fetchRemoteBranchFn(base, cwd);
|
|
249
399
|
}
|
|
250
400
|
catch (err) {
|
|
251
|
-
throw new CommandError(err instanceof Error ? err.message : String(err));
|
|
252
|
-
}
|
|
253
|
-
if (ahead < 1) {
|
|
254
|
-
throw new CommandError(`No commits on \`${layer.branch}\` ahead of \`${base}\`. ` +
|
|
255
|
-
`Commit your changes, then retry \`mg stack submit\`.`);
|
|
401
|
+
throw new CommandError(`Could not fetch park base \`${base}\` (${err instanceof Error ? err.message : String(err)}).`);
|
|
256
402
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
403
|
+
}
|
|
404
|
+
else if (!branchExistsFn(base, cwd)) {
|
|
405
|
+
throw new CommandError(`Stack base branch missing locally: ${base}`);
|
|
406
|
+
}
|
|
407
|
+
let ahead;
|
|
408
|
+
try {
|
|
409
|
+
ahead = commitsAheadOfFn(aheadRef, branch, cwd);
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
throw new CommandError(err instanceof Error ? err.message : String(err));
|
|
413
|
+
}
|
|
414
|
+
if (ahead < 1) {
|
|
415
|
+
throw new CommandError(`No commits on \`${branch}\` ahead of \`${base}\`. ` +
|
|
416
|
+
`Commit your changes, then retry \`mg stack submit\`.`);
|
|
417
|
+
}
|
|
418
|
+
const title = tipCommitSubjectFn(branch, cwd);
|
|
419
|
+
const body = buildPrBodyFn(tipCommitMessageFn(branch, cwd));
|
|
420
|
+
console.log(ansi.dim(` Opening PR ${branch} → ${base} …`));
|
|
421
|
+
return {
|
|
422
|
+
prNumber: createPrFn({
|
|
261
423
|
owner,
|
|
262
424
|
repo,
|
|
263
425
|
base,
|
|
264
|
-
head:
|
|
426
|
+
head: branch,
|
|
265
427
|
title,
|
|
266
428
|
body,
|
|
267
429
|
cwd,
|
|
268
|
-
})
|
|
269
|
-
created
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
430
|
+
}),
|
|
431
|
+
created: true,
|
|
432
|
+
};
|
|
433
|
+
};
|
|
434
|
+
for (const plan of plans) {
|
|
435
|
+
if (plan.githubBase == null && findOpenPrNumberFn(owner, repo, plan.branch, cwd) == null) {
|
|
436
|
+
continue;
|
|
273
437
|
}
|
|
274
|
-
|
|
438
|
+
const base = plan.githubBase ?? plan.gitParent;
|
|
439
|
+
const { prNumber, created } = openLayerPr(plan.branch, base);
|
|
440
|
+
opened.push({ branch: plan.branch, base, prNumber, created });
|
|
275
441
|
}
|
|
276
|
-
const registerPr = opened[0].prNumber;
|
|
277
442
|
const cfg = await loadConfigFn();
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
443
|
+
let adoptData = null;
|
|
444
|
+
let stackId = plans.find((plan) => plan.stackId)?.stackId ?? null;
|
|
445
|
+
const registerPr = async (prNumber) => {
|
|
446
|
+
console.log(ansi.dim(` Registering stack via import (${owner}/${repo}#${prNumber}) …`));
|
|
447
|
+
const body = await adoptStackFn(owner, repo, prNumber, cfg);
|
|
448
|
+
if (typeof body !== "object" || body === null) {
|
|
449
|
+
throw new CommandError("Unexpected response shape from adopt API");
|
|
450
|
+
}
|
|
451
|
+
const data = body;
|
|
452
|
+
if (data.error) {
|
|
453
|
+
throw new CommandError(data.error);
|
|
454
|
+
}
|
|
455
|
+
if (!data.stack) {
|
|
456
|
+
throw new CommandError("Stack was not registered");
|
|
457
|
+
}
|
|
458
|
+
adoptData = data;
|
|
459
|
+
if (typeof data.stack.id === "string" && data.stack.id) {
|
|
460
|
+
stackId = data.stack.id;
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
if (pendingPark.length > 0) {
|
|
464
|
+
if (opened.length > 0) {
|
|
465
|
+
await registerPr(opened[0].prNumber);
|
|
466
|
+
}
|
|
467
|
+
if (!stackId) {
|
|
468
|
+
throw new CommandError("Cannot mint a park freeze before the stack is registered. Open layers 1–2 first, then retry.");
|
|
469
|
+
}
|
|
470
|
+
console.log(ansi.dim(` Ensuring upper-park freeze for stack ${stackId} …`));
|
|
471
|
+
const park = await ensureUpperParkFn(stackId, cfg);
|
|
472
|
+
for (const plan of pendingPark) {
|
|
473
|
+
const { prNumber, created } = openLayerPr(plan.branch, park.freezeBranch);
|
|
474
|
+
opened.push({ branch: plan.branch, base: park.freezeBranch, prNumber, created });
|
|
475
|
+
}
|
|
476
|
+
await registerPr(opened[opened.length - 1].prNumber);
|
|
286
477
|
}
|
|
287
|
-
|
|
288
|
-
|
|
478
|
+
else {
|
|
479
|
+
if (opened.length === 0) {
|
|
480
|
+
throw new CommandError("No pull requests to register after submit.");
|
|
481
|
+
}
|
|
482
|
+
await registerPr(opened[0].prNumber);
|
|
289
483
|
}
|
|
290
|
-
const
|
|
484
|
+
const data = adoptData;
|
|
291
485
|
// Drop the submitted local stack so the next create onto trunk starts fresh.
|
|
292
486
|
await saveStackMetaFn(removeActiveStack(meta), cwd);
|
|
293
487
|
if (asJson) {
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
export type ParsedStatusArgs = {
|
|
2
|
+
jobId: string;
|
|
3
|
+
format: "json" | "pretty";
|
|
4
|
+
wait: boolean;
|
|
5
|
+
timeoutMs: number;
|
|
6
|
+
};
|
|
1
7
|
export type StatusOptions = {
|
|
2
|
-
|
|
8
|
+
/** Oneshot defaults to json; the shell defaults to pretty. */
|
|
9
|
+
defaultFormat?: "json" | "pretty";
|
|
3
10
|
signal?: AbortSignal;
|
|
11
|
+
/** When true, Ctrl+C / per-request timeout detaches instead of exiting 5. */
|
|
12
|
+
interactive?: boolean;
|
|
13
|
+
/** Test seam; production uses the two-second poll interval. */
|
|
14
|
+
pollIntervalMs?: number;
|
|
4
15
|
};
|
|
5
|
-
export declare function
|
|
16
|
+
export declare function parseStatusArgs(args: string[], defaults?: {
|
|
17
|
+
format: "json" | "pretty";
|
|
18
|
+
}): ParsedStatusArgs;
|
|
19
|
+
export declare function cmdStatus(args: string[], opts?: StatusOptions): Promise<void>;
|