bstack 0.3.0 → 1.0.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/dist/bstack.js +143 -73
- package/package.json +4 -1
package/dist/bstack.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
import { parseArgs } from "node:util";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { z } from "zod";
|
|
5
6
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
6
7
|
import { dirname } from "node:path";
|
|
7
8
|
//#region package.json
|
|
8
|
-
var version = "0.
|
|
9
|
+
var version = "1.0.0";
|
|
9
10
|
//#endregion
|
|
10
11
|
//#region src/command.ts
|
|
11
12
|
var CommandError = class extends Error {
|
|
@@ -82,14 +83,14 @@ function checkoutStack(repository, github, options) {
|
|
|
82
83
|
}
|
|
83
84
|
//#endregion
|
|
84
85
|
//#region src/identity.ts
|
|
85
|
-
const trailerPattern = /^
|
|
86
|
+
const trailerPattern = /^bstack-id:\s*(\S+)\s*$/gm;
|
|
86
87
|
function readChangeId(message) {
|
|
87
88
|
const matches = [...message.matchAll(trailerPattern)];
|
|
88
|
-
if (matches.length > 1) throw new Error("A commit contains more than one
|
|
89
|
+
if (matches.length > 1) throw new Error("A commit contains more than one bstack-id trailer");
|
|
89
90
|
return matches[0]?.[1];
|
|
90
91
|
}
|
|
91
92
|
function addChangeId(message, changeId) {
|
|
92
|
-
return `${message.trimEnd()}\n\
|
|
93
|
+
return `${message.trimEnd()}\n\nbstack-id: ${changeId}\n`;
|
|
93
94
|
}
|
|
94
95
|
function newChangeId() {
|
|
95
96
|
return randomUUID().replaceAll("-", "");
|
|
@@ -128,7 +129,7 @@ function rewriteCommit(commit, parent, message) {
|
|
|
128
129
|
return `${rewrittenHeaders.join("\n")}\n\n${message}`;
|
|
129
130
|
}
|
|
130
131
|
function splitCommitMessage(message) {
|
|
131
|
-
const [subject = "Untitled change", ...bodyLines] = message.split("\n").filter((line) => !/^
|
|
132
|
+
const [subject = "Untitled change", ...bodyLines] = message.split("\n").filter((line) => !/^bstack-id:\s*\S+\s*$/.test(line)).join("\n").trim().split("\n");
|
|
132
133
|
return {
|
|
133
134
|
subject,
|
|
134
135
|
body: bodyLines.join("\n").trim()
|
|
@@ -230,7 +231,7 @@ var GitRepository = class {
|
|
|
230
231
|
oid
|
|
231
232
|
]).stdout));
|
|
232
233
|
}
|
|
233
|
-
ensureChangeIds(commits, dryRun) {
|
|
234
|
+
ensureChangeIds(commits, dryRun, userLogin) {
|
|
234
235
|
const assigned = commits.map((commit) => commit.changeId ?? newChangeId());
|
|
235
236
|
const needsRewrite = commits.some((commit) => commit.changeId === void 0);
|
|
236
237
|
let parent = commits[0]?.parent;
|
|
@@ -268,7 +269,7 @@ var GitRepository = class {
|
|
|
268
269
|
oid,
|
|
269
270
|
subject,
|
|
270
271
|
body,
|
|
271
|
-
remoteBranch: `bstack/${id}`
|
|
272
|
+
remoteBranch: `bstack/${userLogin}/${id}`
|
|
272
273
|
};
|
|
273
274
|
});
|
|
274
275
|
}
|
|
@@ -313,6 +314,19 @@ var GitRepository = class {
|
|
|
313
314
|
};
|
|
314
315
|
//#endregion
|
|
315
316
|
//#region src/github.ts
|
|
317
|
+
const pullRequestSchema = z.object({
|
|
318
|
+
number: z.number(),
|
|
319
|
+
url: z.string(),
|
|
320
|
+
state: z.enum([
|
|
321
|
+
"OPEN",
|
|
322
|
+
"CLOSED",
|
|
323
|
+
"MERGED"
|
|
324
|
+
]),
|
|
325
|
+
title: z.string(),
|
|
326
|
+
body: z.string(),
|
|
327
|
+
isDraft: z.boolean()
|
|
328
|
+
});
|
|
329
|
+
const stackSchema = z.object({ number: z.number() });
|
|
316
330
|
var GhPlatform = class {
|
|
317
331
|
cwd;
|
|
318
332
|
runner;
|
|
@@ -331,6 +345,14 @@ var GhPlatform = class {
|
|
|
331
345
|
]);
|
|
332
346
|
this.gh(["stack", "--version"]);
|
|
333
347
|
}
|
|
348
|
+
currentUserLogin() {
|
|
349
|
+
return z.string().min(1).parse(this.gh([
|
|
350
|
+
"api",
|
|
351
|
+
"user",
|
|
352
|
+
"--jq",
|
|
353
|
+
".login"
|
|
354
|
+
]).stdout.trim());
|
|
355
|
+
}
|
|
334
356
|
defaultBranch() {
|
|
335
357
|
return this.gh([
|
|
336
358
|
"repo",
|
|
@@ -354,9 +376,8 @@ var GhPlatform = class {
|
|
|
354
376
|
"--json",
|
|
355
377
|
"number,url,state,title,body,isDraft"
|
|
356
378
|
]).stdout;
|
|
357
|
-
const candidates = JSON.parse(raw);
|
|
358
|
-
|
|
359
|
-
return selected ? normalizePullRequest(selected) : void 0;
|
|
379
|
+
const candidates = pullRequestSchema.array().parse(JSON.parse(raw));
|
|
380
|
+
return candidates.find((pr) => pr.state === "OPEN") ?? candidates.find((pr) => pr.state === "MERGED");
|
|
360
381
|
}
|
|
361
382
|
pullRequest(number) {
|
|
362
383
|
const raw = this.gh([
|
|
@@ -366,7 +387,7 @@ var GhPlatform = class {
|
|
|
366
387
|
"--json",
|
|
367
388
|
"number,url,state,title,body,isDraft"
|
|
368
389
|
]).stdout;
|
|
369
|
-
return
|
|
390
|
+
return pullRequestSchema.parse(JSON.parse(raw));
|
|
370
391
|
}
|
|
371
392
|
createPullRequest(change, base, draft) {
|
|
372
393
|
const args = [
|
|
@@ -418,6 +439,15 @@ var GhPlatform = class {
|
|
|
418
439
|
String(stackNumber)
|
|
419
440
|
]);
|
|
420
441
|
}
|
|
442
|
+
editPullRequestBase(pr, base) {
|
|
443
|
+
this.gh([
|
|
444
|
+
"pr",
|
|
445
|
+
"edit",
|
|
446
|
+
String(pr.number),
|
|
447
|
+
"--base",
|
|
448
|
+
base
|
|
449
|
+
]);
|
|
450
|
+
}
|
|
421
451
|
editPullRequest(pr, change) {
|
|
422
452
|
if (pr.title === change.subject && pr.body === change.body) return;
|
|
423
453
|
this.gh([
|
|
@@ -432,7 +462,7 @@ var GhPlatform = class {
|
|
|
432
462
|
}
|
|
433
463
|
stackNumberForPullRequest(prNumber) {
|
|
434
464
|
const raw = this.gh(["api", `repos/{owner}/{repo}/stacks?pull_request=${prNumber}`]).stdout;
|
|
435
|
-
return JSON.parse(raw)[0]?.number;
|
|
465
|
+
return stackSchema.array().parse(JSON.parse(raw))[0]?.number;
|
|
436
466
|
}
|
|
437
467
|
pullRequestHead(reference) {
|
|
438
468
|
return this.gh([
|
|
@@ -453,13 +483,6 @@ var GhPlatform = class {
|
|
|
453
483
|
]);
|
|
454
484
|
}
|
|
455
485
|
};
|
|
456
|
-
function normalizePullRequest(pr) {
|
|
457
|
-
if (pr.state !== "OPEN" && pr.state !== "CLOSED" && pr.state !== "MERGED") throw new Error(`GitHub returned an unknown PR state: ${pr.state}`);
|
|
458
|
-
return {
|
|
459
|
-
...pr,
|
|
460
|
-
state: pr.state
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
486
|
//#endregion
|
|
464
487
|
//#region src/reporter.ts
|
|
465
488
|
var ConsoleReporter = class {
|
|
@@ -477,6 +500,22 @@ const emptyState = () => ({
|
|
|
477
500
|
schemaVersion: 1,
|
|
478
501
|
stacks: []
|
|
479
502
|
});
|
|
503
|
+
const storedChangeSchema = z.object({
|
|
504
|
+
id: z.string(),
|
|
505
|
+
remoteBranch: z.string(),
|
|
506
|
+
pullRequest: z.number(),
|
|
507
|
+
url: z.string()
|
|
508
|
+
});
|
|
509
|
+
const storedStackSchema = z.object({
|
|
510
|
+
remote: z.string(),
|
|
511
|
+
base: z.string(),
|
|
512
|
+
stackNumber: z.number().optional(),
|
|
513
|
+
changes: storedChangeSchema.array()
|
|
514
|
+
});
|
|
515
|
+
const stateSchema = z.object({
|
|
516
|
+
schemaVersion: z.literal(1),
|
|
517
|
+
stacks: storedStackSchema.array()
|
|
518
|
+
});
|
|
480
519
|
var StateStore = class {
|
|
481
520
|
path;
|
|
482
521
|
constructor(path) {
|
|
@@ -484,11 +523,21 @@ var StateStore = class {
|
|
|
484
523
|
}
|
|
485
524
|
read() {
|
|
486
525
|
try {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
526
|
+
return {
|
|
527
|
+
schemaVersion: 1,
|
|
528
|
+
stacks: stateSchema.parse(JSON.parse(readFileSync(this.path, "utf8"))).stacks.map((stack) => {
|
|
529
|
+
const stored = {
|
|
530
|
+
remote: stack.remote,
|
|
531
|
+
base: stack.base,
|
|
532
|
+
changes: stack.changes
|
|
533
|
+
};
|
|
534
|
+
if (stack.stackNumber !== void 0) stored.stackNumber = stack.stackNumber;
|
|
535
|
+
return stored;
|
|
536
|
+
})
|
|
537
|
+
};
|
|
490
538
|
} catch (error) {
|
|
491
|
-
if (
|
|
539
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return emptyState();
|
|
540
|
+
if (error instanceof z.ZodError) throw new Error(`Unsupported bstack state in ${this.path}`, { cause: error });
|
|
492
541
|
throw error;
|
|
493
542
|
}
|
|
494
543
|
}
|
|
@@ -504,14 +553,6 @@ var StateStore = class {
|
|
|
504
553
|
return matches[0];
|
|
505
554
|
}
|
|
506
555
|
};
|
|
507
|
-
function isMissingFile(error) {
|
|
508
|
-
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
509
|
-
}
|
|
510
|
-
function isState(value) {
|
|
511
|
-
if (typeof value !== "object" || value === null) return false;
|
|
512
|
-
const candidate = value;
|
|
513
|
-
return candidate.schemaVersion === 1 && Array.isArray(candidate.stacks);
|
|
514
|
-
}
|
|
515
556
|
//#endregion
|
|
516
557
|
//#region src/sync.ts
|
|
517
558
|
function syncStack(repository, github, options) {
|
|
@@ -521,7 +562,9 @@ function syncStack(repository, github, options) {
|
|
|
521
562
|
github.assertReady();
|
|
522
563
|
const remote = repository.resolveRemote(options.remote);
|
|
523
564
|
const base = options.base ?? github.defaultBranch();
|
|
565
|
+
const userLogin = github.currentUserLogin();
|
|
524
566
|
reporter.progress(`Using ${remote} as the remote and ${base} as the stack base`);
|
|
567
|
+
reporter.progress(`Using ${userLogin} as the remote branch namespace`);
|
|
525
568
|
reporter.progress(`Fetching ${remote}/${base}`);
|
|
526
569
|
const remoteBase = repository.fetchBase(remote, base);
|
|
527
570
|
const baseOid = repository.mergeBase("HEAD", remoteBase);
|
|
@@ -531,7 +574,7 @@ function syncStack(repository, github, options) {
|
|
|
531
574
|
const rewritten = commits.some((commit) => commit.changeId === void 0);
|
|
532
575
|
if (rewritten) reporter.progress(options.dryRun ? "Stable change IDs would be added to the commits" : "Adding stable change IDs to the commits");
|
|
533
576
|
else reporter.progress("All commits already have stable change IDs");
|
|
534
|
-
const changes = repository.ensureChangeIds(commits, options.dryRun);
|
|
577
|
+
const changes = repository.ensureChangeIds(commits, options.dryRun, userLogin);
|
|
535
578
|
if (options.dryRun) {
|
|
536
579
|
reporter.progress("Dry run complete; no commits or remote branches were changed");
|
|
537
580
|
return {
|
|
@@ -545,7 +588,7 @@ function syncStack(repository, github, options) {
|
|
|
545
588
|
const store = new StateStore(repository.statePath());
|
|
546
589
|
const state = store.read();
|
|
547
590
|
const previous = store.findByChangeIds(state, new Set(changes.map((change) => change.id)));
|
|
548
|
-
const evolution = analyzeEvolution(previous, changes, github);
|
|
591
|
+
const evolution = analyzeEvolution(previous, changes, github, repository.currentBranch() === "");
|
|
549
592
|
reporter.progress(`Publishing ${changes.length} protected remote ref${changes.length === 1 ? "" : "s"}`);
|
|
550
593
|
repository.pushChanges(remote, changes);
|
|
551
594
|
reporter.progress("Looking up existing pull requests");
|
|
@@ -554,24 +597,28 @@ function syncStack(repository, github, options) {
|
|
|
554
597
|
if (changes.length === 1) {
|
|
555
598
|
if (evolution.kind === "partial") reporter.progress("Updating this down-stack prefix while preserving higher pull requests");
|
|
556
599
|
reporter.progress(existing[0] ? "Using the existing pull request" : "Creating a pull request");
|
|
557
|
-
|
|
600
|
+
const pullRequest = existing[0] ?? github.createPullRequest(changes[0], base, options.draft);
|
|
601
|
+
if (evolution.kind === "collapse") {
|
|
602
|
+
reporter.progress(`Removing omitted pull requests from stack #${evolution.stackNumber}`);
|
|
603
|
+
github.unstack(evolution.stackNumber);
|
|
604
|
+
try {
|
|
605
|
+
github.editPullRequestBase(pullRequest, base);
|
|
606
|
+
} catch (error) {
|
|
607
|
+
restorePreviousStack(github, previous, base, remote, reporter, error);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
pullRequests = [pullRequest];
|
|
558
611
|
} else {
|
|
559
612
|
if (evolution.kind === "full") {
|
|
560
613
|
reporter.progress(`Linking ${changes.length} pull requests as a native GitHub stack`);
|
|
561
614
|
github.linkStack(changes.map((change) => change.remoteBranch), base, remote, options.draft);
|
|
562
615
|
} else if (evolution.kind === "rebuild") {
|
|
563
|
-
reporter.progress(`Rebuilding stack #${evolution.stackNumber} to
|
|
616
|
+
reporter.progress(`Rebuilding stack #${evolution.stackNumber} to ${evolution.action} pull requests`);
|
|
564
617
|
github.unstack(evolution.stackNumber);
|
|
565
618
|
try {
|
|
566
619
|
github.linkStack(changes.map((change) => change.remoteBranch), base, remote, options.draft);
|
|
567
620
|
} catch (error) {
|
|
568
|
-
|
|
569
|
-
try {
|
|
570
|
-
github.linkStack(previous.changes.map((change) => change.remoteBranch), base, remote, true);
|
|
571
|
-
} catch (rollbackError) {
|
|
572
|
-
throw new Error(`Stack rebuild failed: ${errorMessage(error)}\nRestoring the previous stack also failed: ${errorMessage(rollbackError)}`);
|
|
573
|
-
}
|
|
574
|
-
throw error;
|
|
621
|
+
restorePreviousStack(github, previous, base, remote, reporter, error);
|
|
575
622
|
}
|
|
576
623
|
} else if (evolution.kind === "append") {
|
|
577
624
|
reporter.progress(`Appending ${evolution.branches.length} pull request${evolution.branches.length === 1 ? "" : "s"} to stack #${evolution.stackNumber}`);
|
|
@@ -589,20 +636,20 @@ function syncStack(repository, github, options) {
|
|
|
589
636
|
github.editPullRequest(pr, changes[index]);
|
|
590
637
|
reporter.progress(`PR #${pr.number}: ${changes[index].subject}`);
|
|
591
638
|
}
|
|
592
|
-
const stackNumber = evolution.kind === "rebuild" ? github.stackNumberForPullRequest(pullRequests[0].number) : previous?.stackNumber ?? (pullRequests.length > 1 ? github.stackNumberForPullRequest(pullRequests[0].number) : void 0);
|
|
639
|
+
const stackNumber = evolution.kind === "rebuild" ? github.stackNumberForPullRequest(pullRequests[0].number) : evolution.kind === "collapse" ? void 0 : previous?.stackNumber ?? (pullRequests.length > 1 ? github.stackNumberForPullRequest(pullRequests[0].number) : void 0);
|
|
593
640
|
const synchronizedChanges = changes.map((change, index) => ({
|
|
594
641
|
id: change.id,
|
|
595
642
|
remoteBranch: change.remoteBranch,
|
|
596
643
|
pullRequest: pullRequests[index].number,
|
|
597
644
|
url: pullRequests[index].url
|
|
598
645
|
}));
|
|
599
|
-
const
|
|
600
|
-
writeUpdatedState(store, state, previous, {
|
|
646
|
+
const stored = {
|
|
601
647
|
remote,
|
|
602
648
|
base,
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
649
|
+
changes: evolution.kind === "partial" && previous ? [...synchronizedChanges, ...previous.changes.slice(evolution.previousOffset + changes.length)] : synchronizedChanges
|
|
650
|
+
};
|
|
651
|
+
if (stackNumber !== void 0) stored.stackNumber = stackNumber;
|
|
652
|
+
writeUpdatedState(store, state, previous, stored);
|
|
606
653
|
reporter.progress("Saved the local stack state");
|
|
607
654
|
return {
|
|
608
655
|
base,
|
|
@@ -614,46 +661,69 @@ function syncStack(repository, github, options) {
|
|
|
614
661
|
}))
|
|
615
662
|
};
|
|
616
663
|
}
|
|
617
|
-
function analyzeEvolution(previous, changes, github) {
|
|
664
|
+
function analyzeEvolution(previous, changes, github, preserveHigherChanges) {
|
|
618
665
|
if (!previous) return { kind: "full" };
|
|
619
666
|
const previousIds = previous.changes.map((change) => change.id);
|
|
620
667
|
const currentIds = changes.map((change) => change.id);
|
|
621
|
-
|
|
668
|
+
const previousIdSet = new Set(previousIds);
|
|
669
|
+
const currentIdSet = new Set(currentIds);
|
|
670
|
+
if (!sameValues(previousIds.filter((id) => currentIdSet.has(id)), currentIds.filter((id) => previousIdSet.has(id)))) throw new Error("Submitted commits cannot be reordered. Restore their original relative order before syncing");
|
|
671
|
+
const removed = previous.changes.filter((change) => !currentIdSet.has(change.id));
|
|
672
|
+
const added = changes.filter((change) => !previousIdSet.has(change.id));
|
|
673
|
+
if (removed.length === 0) {
|
|
622
674
|
if (previousIds.every((id, index) => currentIds[index] === id)) return { kind: "full" };
|
|
623
675
|
const stackNumber = previous.stackNumber ?? github.stackNumberForPullRequest(previous.changes[0].pullRequest);
|
|
624
676
|
return stackNumber === void 0 ? { kind: "full" } : {
|
|
625
677
|
kind: "rebuild",
|
|
626
|
-
stackNumber
|
|
678
|
+
stackNumber,
|
|
679
|
+
action: "insert"
|
|
627
680
|
};
|
|
628
681
|
}
|
|
629
682
|
const firstCurrentIndex = previousIds.indexOf(currentIds[0]);
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
683
|
+
const isPreviousSlice = firstCurrentIndex >= 0 && currentIds.every((id, index) => previousIds[firstCurrentIndex + index] === id);
|
|
684
|
+
if (preserveHigherChanges && added.length === 0 && isPreviousSlice && firstCurrentIndex + currentIds.length < previousIds.length) {
|
|
685
|
+
if (previous.changes.slice(0, firstCurrentIndex).every((change) => github.pullRequest(change.pullRequest).state === "MERGED")) return {
|
|
686
|
+
kind: "partial",
|
|
687
|
+
previousOffset: firstCurrentIndex
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
const removedPrefixWasMerged = removed.every((change, index) => previous.changes[index] === change) && removed.every((change) => github.pullRequest(change.pullRequest).state === "MERGED");
|
|
691
|
+
const survivingIds = previousIds.slice(removed.length);
|
|
692
|
+
const onlyAppendedAfterMergedPrefix = removedPrefixWasMerged && survivingIds.every((id, index) => currentIds[index] === id) && currentIds.slice(survivingIds.length).every((id) => !previousIdSet.has(id));
|
|
693
|
+
if (removedPrefixWasMerged && added.length === 0) return { kind: "skip" };
|
|
694
|
+
if (onlyAppendedAfterMergedPrefix) {
|
|
695
|
+
if (previous.stackNumber === void 0) throw new Error("Cannot append after a merge because the native GitHub stack number is missing from local state");
|
|
696
|
+
return {
|
|
697
|
+
kind: "append",
|
|
698
|
+
stackNumber: previous.stackNumber,
|
|
699
|
+
branches: added.map((change) => change.remoteBranch)
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
const stackNumber = previous.stackNumber ?? github.stackNumberForPullRequest(previous.changes[0].pullRequest);
|
|
703
|
+
if (stackNumber === void 0) throw new Error("Cannot remove submitted commits because the native GitHub stack number is missing from local state");
|
|
704
|
+
if (changes.length === 1) return {
|
|
705
|
+
kind: "collapse",
|
|
706
|
+
stackNumber
|
|
639
707
|
};
|
|
640
|
-
const appended = changes.slice(surviving.length);
|
|
641
|
-
if (removedPrefix.length === 0) return { kind: "full" };
|
|
642
|
-
if (appended.length === 0) return { kind: "skip" };
|
|
643
|
-
if (previous.stackNumber === void 0) throw new Error("Cannot append after a merge because the native GitHub stack number is missing from local state");
|
|
644
708
|
return {
|
|
645
|
-
kind: "
|
|
646
|
-
stackNumber
|
|
647
|
-
|
|
709
|
+
kind: "rebuild",
|
|
710
|
+
stackNumber,
|
|
711
|
+
action: added.length === 0 ? "remove" : "update"
|
|
648
712
|
};
|
|
649
713
|
}
|
|
650
|
-
function
|
|
651
|
-
|
|
652
|
-
for (const value of actual) if (value === expected[expectedIndex]) expectedIndex++;
|
|
653
|
-
return expectedIndex === expected.length;
|
|
714
|
+
function sameValues(left, right) {
|
|
715
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
654
716
|
}
|
|
655
|
-
function
|
|
656
|
-
|
|
717
|
+
function restorePreviousStack(github, previous, base, remote, reporter, rebuildError) {
|
|
718
|
+
const rebuildMessage = rebuildError instanceof Error ? rebuildError.message : String(rebuildError);
|
|
719
|
+
reporter.progress("Rebuild failed; restoring the previous native GitHub stack");
|
|
720
|
+
try {
|
|
721
|
+
github.linkStack(previous.changes.map((change) => change.remoteBranch), base, remote, true);
|
|
722
|
+
} catch (rollbackError) {
|
|
723
|
+
const rollbackMessage = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
|
|
724
|
+
throw new Error(`Stack rebuild failed: ${rebuildMessage}\nRestoring the previous stack also failed: ${rollbackMessage}`);
|
|
725
|
+
}
|
|
726
|
+
throw rebuildError;
|
|
657
727
|
}
|
|
658
728
|
function writeUpdatedState(store, state, previous, updated) {
|
|
659
729
|
const stacks = previous ? state.stacks.map((stack) => stack === previous ? updated : stack) : [...state.stacks, updated];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bstack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"packageManager": "pnpm@11.5.1",
|
|
5
5
|
"description": "Create native GitHub stacked pull requests from a linear series of commits",
|
|
6
6
|
"repository": {
|
|
@@ -38,5 +38,8 @@
|
|
|
38
38
|
"tsdown": "^0.22.14",
|
|
39
39
|
"typescript": "^7.0.2",
|
|
40
40
|
"vitest": "^4.0.0"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"zod": "^4.4.3"
|
|
41
44
|
}
|
|
42
45
|
}
|