gh-stack 0.1.2 → 0.2.1
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 +128 -93
- package/package.json +3 -3
- package/src/commands/bottom.ts +54 -0
- package/src/commands/{switch.ts → checkout.ts} +11 -8
- package/src/commands/create.ts +83 -0
- package/src/commands/{remove.ts → delete.ts} +9 -9
- package/src/commands/down.ts +67 -0
- package/src/commands/init.ts +142 -93
- package/src/commands/list.ts +1 -1
- package/src/commands/{show.ts → log.ts} +3 -3
- package/src/commands/submit.ts +326 -0
- package/src/commands/top.ts +79 -0
- package/src/commands/up.ts +82 -0
- package/src/index.ts +52 -20
- package/src/lib/cli.ts +1 -1
- package/src/lib/git.ts +11 -0
- package/src/lib/safety.ts +1 -1
- package/src/commands/add.ts +0 -140
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// gh-stack top — Jump to the tip of the stack
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
import * as git from "../lib/git.ts";
|
|
5
|
+
import {
|
|
6
|
+
findStackForBranch,
|
|
7
|
+
getOrderedBranches,
|
|
8
|
+
getChildren,
|
|
9
|
+
writeMetadata,
|
|
10
|
+
} from "../lib/metadata.ts";
|
|
11
|
+
import { ensureMetadata } from "../lib/safety.ts";
|
|
12
|
+
|
|
13
|
+
export default async function top(args: string[]): Promise<void> {
|
|
14
|
+
if (args.includes("--help")) {
|
|
15
|
+
console.log(`
|
|
16
|
+
gh-stack top — Jump to the tip of the stack
|
|
17
|
+
|
|
18
|
+
USAGE
|
|
19
|
+
gh-stack top
|
|
20
|
+
|
|
21
|
+
Switches to the topmost (leaf) branch of the current stack.
|
|
22
|
+
Prompts if there are multiple leaf branches.
|
|
23
|
+
`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const meta = await ensureMetadata();
|
|
28
|
+
const currentBranch = await git.currentBranch();
|
|
29
|
+
const stackName = findStackForBranch(meta, currentBranch);
|
|
30
|
+
|
|
31
|
+
if (!stackName) {
|
|
32
|
+
p.cancel(`Branch ${pc.yellow(currentBranch)} is not in any stack`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const stack = meta.stacks[stackName]!;
|
|
37
|
+
const ordered = getOrderedBranches(stack);
|
|
38
|
+
|
|
39
|
+
// Find leaf branches (no children)
|
|
40
|
+
const leaves = ordered.filter((b) => getChildren(stack, b).length === 0);
|
|
41
|
+
|
|
42
|
+
if (leaves.length === 0) {
|
|
43
|
+
p.log.warn("No branches in stack");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let target: string;
|
|
48
|
+
if (leaves.length === 1) {
|
|
49
|
+
target = leaves[0]!;
|
|
50
|
+
} else {
|
|
51
|
+
const result = await p.select({
|
|
52
|
+
message: "Multiple tips — which branch?",
|
|
53
|
+
options: leaves.map((l) => ({
|
|
54
|
+
value: l,
|
|
55
|
+
label: l,
|
|
56
|
+
hint: stack.branches[l]?.pr ? `#${stack.branches[l]!.pr}` : undefined,
|
|
57
|
+
})),
|
|
58
|
+
});
|
|
59
|
+
if (p.isCancel(result)) {
|
|
60
|
+
p.cancel("Cancelled");
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
target = result as string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (target === currentBranch) {
|
|
67
|
+
p.log.info(`Already at the top: ${pc.yellow(currentBranch)}`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await git.checkout(target);
|
|
72
|
+
|
|
73
|
+
// Update tracking
|
|
74
|
+
stack.last_branch = target;
|
|
75
|
+
meta.current_stack = stackName;
|
|
76
|
+
await writeMetadata(meta);
|
|
77
|
+
|
|
78
|
+
p.log.success(`On ${pc.yellow(target)}`);
|
|
79
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// gh-stack up — Move to child branch (upstack)
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
import * as git from "../lib/git.ts";
|
|
5
|
+
import { findStackForBranch, getChildren, writeMetadata } from "../lib/metadata.ts";
|
|
6
|
+
import { ensureMetadata } from "../lib/safety.ts";
|
|
7
|
+
|
|
8
|
+
export default async function up(args: string[]): Promise<void> {
|
|
9
|
+
if (args.includes("--help")) {
|
|
10
|
+
console.log(`
|
|
11
|
+
gh-stack up — Move to child branch (upstack)
|
|
12
|
+
|
|
13
|
+
USAGE
|
|
14
|
+
gh-stack up [steps]
|
|
15
|
+
|
|
16
|
+
OPTIONS
|
|
17
|
+
[steps] Number of levels to move up (default: 1)
|
|
18
|
+
`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const steps = parseInt(args[0] || "1", 10);
|
|
23
|
+
if (isNaN(steps) || steps < 1) {
|
|
24
|
+
p.cancel("Invalid step count");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const meta = await ensureMetadata();
|
|
29
|
+
let currentBranch = await git.currentBranch();
|
|
30
|
+
const stackName = findStackForBranch(meta, currentBranch);
|
|
31
|
+
|
|
32
|
+
if (!stackName) {
|
|
33
|
+
p.cancel(`Branch ${pc.yellow(currentBranch)} is not in any stack`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const stack = meta.stacks[stackName]!;
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < steps; i++) {
|
|
40
|
+
const children = getChildren(stack, currentBranch);
|
|
41
|
+
|
|
42
|
+
if (children.length === 0) {
|
|
43
|
+
if (i === 0) {
|
|
44
|
+
p.log.warn(`${pc.yellow(currentBranch)} is already at the top of the stack`);
|
|
45
|
+
} else {
|
|
46
|
+
p.log.info(`Reached top of stack at ${pc.yellow(currentBranch)} after ${i} step(s)`);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let target: string;
|
|
52
|
+
if (children.length === 1) {
|
|
53
|
+
target = children[0]!;
|
|
54
|
+
} else {
|
|
55
|
+
// Multiple children — prompt
|
|
56
|
+
const result = await p.select({
|
|
57
|
+
message: "Multiple children — which branch?",
|
|
58
|
+
options: children.map((c) => ({
|
|
59
|
+
value: c,
|
|
60
|
+
label: c,
|
|
61
|
+
hint: stack.branches[c]?.pr ? `#${stack.branches[c]!.pr}` : undefined,
|
|
62
|
+
})),
|
|
63
|
+
});
|
|
64
|
+
if (p.isCancel(result)) {
|
|
65
|
+
p.cancel("Cancelled");
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
target = result as string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
currentBranch = target;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await git.checkout(currentBranch);
|
|
75
|
+
|
|
76
|
+
// Update tracking
|
|
77
|
+
stack.last_branch = currentBranch;
|
|
78
|
+
meta.current_stack = stackName;
|
|
79
|
+
await writeMetadata(meta);
|
|
80
|
+
|
|
81
|
+
p.log.success(`On ${pc.yellow(currentBranch)}`);
|
|
82
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -39,27 +39,27 @@ await ensureGitRepo();
|
|
|
39
39
|
const commandArgs = parsed.commandArgs;
|
|
40
40
|
|
|
41
41
|
switch (command) {
|
|
42
|
-
case "
|
|
43
|
-
await (await import("./commands/
|
|
42
|
+
case "log":
|
|
43
|
+
await (await import("./commands/log.ts")).default(commandArgs);
|
|
44
44
|
break;
|
|
45
45
|
|
|
46
46
|
case "init":
|
|
47
47
|
await (await import("./commands/init.ts")).default(commandArgs);
|
|
48
48
|
break;
|
|
49
49
|
|
|
50
|
-
case "
|
|
51
|
-
await (await import("./commands/
|
|
50
|
+
case "create":
|
|
51
|
+
await (await import("./commands/create.ts")).default(commandArgs);
|
|
52
52
|
break;
|
|
53
53
|
|
|
54
|
-
case "
|
|
55
|
-
await (await import("./commands/
|
|
54
|
+
case "delete":
|
|
55
|
+
await (await import("./commands/delete.ts")).default(commandArgs);
|
|
56
56
|
break;
|
|
57
57
|
|
|
58
|
-
case "
|
|
59
|
-
|
|
58
|
+
case "checkout":
|
|
59
|
+
case "co":
|
|
60
|
+
await (await import("./commands/checkout.ts")).default(commandArgs);
|
|
60
61
|
break;
|
|
61
62
|
|
|
62
|
-
case "list":
|
|
63
63
|
case "ls":
|
|
64
64
|
await (await import("./commands/list.ts")).default(commandArgs);
|
|
65
65
|
break;
|
|
@@ -73,8 +73,8 @@ switch (command) {
|
|
|
73
73
|
await (await import("./commands/sync.ts")).default(commandArgs);
|
|
74
74
|
break;
|
|
75
75
|
|
|
76
|
-
case "
|
|
77
|
-
await (await import("./commands/
|
|
76
|
+
case "submit":
|
|
77
|
+
await (await import("./commands/submit.ts")).default(commandArgs);
|
|
78
78
|
break;
|
|
79
79
|
|
|
80
80
|
case "status":
|
|
@@ -93,6 +93,22 @@ switch (command) {
|
|
|
93
93
|
await (await import("./commands/archive.ts")).default(commandArgs);
|
|
94
94
|
break;
|
|
95
95
|
|
|
96
|
+
case "up":
|
|
97
|
+
await (await import("./commands/up.ts")).default(commandArgs);
|
|
98
|
+
break;
|
|
99
|
+
|
|
100
|
+
case "down":
|
|
101
|
+
await (await import("./commands/down.ts")).default(commandArgs);
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
case "top":
|
|
105
|
+
await (await import("./commands/top.ts")).default(commandArgs);
|
|
106
|
+
break;
|
|
107
|
+
|
|
108
|
+
case "bottom":
|
|
109
|
+
await (await import("./commands/bottom.ts")).default(commandArgs);
|
|
110
|
+
break;
|
|
111
|
+
|
|
96
112
|
case "--help":
|
|
97
113
|
case "help":
|
|
98
114
|
printHelp();
|
|
@@ -111,18 +127,34 @@ gh-stack v${VERSION} — Stacked PR manager for squash-merge workflows
|
|
|
111
127
|
${bold("USAGE")}
|
|
112
128
|
gh-stack <command> [options]
|
|
113
129
|
|
|
114
|
-
${bold("
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
130
|
+
${bold("TERMS")}
|
|
131
|
+
trunk Base branch of the repository (usually main)
|
|
132
|
+
stack A chain of dependent branches
|
|
133
|
+
upstack Branches that depend on the current branch (children)
|
|
134
|
+
downstack Branches the current branch depends on (ancestors)
|
|
135
|
+
|
|
136
|
+
${bold("CORE WORKFLOW")}
|
|
137
|
+
${green("init")} Create a new stack from the current branch
|
|
138
|
+
${green("create")} Create a new branch and add to the stack
|
|
139
|
+
${green("submit")} Push branches and create/update PRs
|
|
140
|
+
${green("log")} Display current stack tree ${dim("(default)")}
|
|
141
|
+
|
|
142
|
+
${bold("STACK NAVIGATION")}
|
|
143
|
+
${green("checkout")} Switch to a branch or stack ${dim("(alias: co)")}
|
|
144
|
+
${green("up")} Move to child branch (upstack)
|
|
145
|
+
${green("down")} Move to parent branch (downstack)
|
|
146
|
+
${green("top")} Jump to the tip of the stack
|
|
147
|
+
${green("bottom")} Jump to the base of the stack
|
|
148
|
+
${green("ls")} List branches with numbers
|
|
149
|
+
|
|
150
|
+
${bold("STACK MANAGEMENT")}
|
|
121
151
|
${green("restack")} Rebase children onto updated parents
|
|
122
152
|
${green("sync")} Sync base with main + restack all
|
|
123
|
-
${green("update-prs")} Update PR descriptions with stack viz
|
|
124
|
-
${green("status")} PR dashboard (CI, reviews)
|
|
125
153
|
${green("merge")} Local squash-merge top-down
|
|
154
|
+
${green("delete")} Remove a branch from the stack
|
|
155
|
+
|
|
156
|
+
${bold("INFO & MAINTENANCE")}
|
|
157
|
+
${green("status")} PR dashboard (CI, reviews)
|
|
126
158
|
${green("undo")} Restore from last snapshot
|
|
127
159
|
${green("archive")} Manage archived stacks
|
|
128
160
|
|
package/src/lib/cli.ts
CHANGED
|
@@ -17,7 +17,7 @@ export function parseCliArgs(rawArgs: string[], env: NodeJS.ProcessEnv): ParsedC
|
|
|
17
17
|
commandIndex++;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const command = rawArgs[commandIndex] || "
|
|
20
|
+
const command = rawArgs[commandIndex] || "log";
|
|
21
21
|
const commandArgs = rawArgs
|
|
22
22
|
.slice(commandIndex + 1)
|
|
23
23
|
.filter((arg) => arg !== "--yes" && arg !== "-y");
|
package/src/lib/git.ts
CHANGED
|
@@ -40,9 +40,12 @@ export async function gitDir(): Promise<string> {
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Check if working tree is clean (no uncommitted changes).
|
|
43
|
+
* Refreshes the index first to avoid false positives from stale stat info.
|
|
43
44
|
*/
|
|
44
45
|
export async function isCleanWorkingTree(): Promise<boolean> {
|
|
45
46
|
try {
|
|
47
|
+
// Refresh index to avoid false positives from stat changes (timestamps, etc.)
|
|
48
|
+
await $`git update-index -q --refresh`.quiet().nothrow();
|
|
46
49
|
await $`git diff-index --quiet HEAD --`.quiet();
|
|
47
50
|
return true;
|
|
48
51
|
} catch {
|
|
@@ -243,6 +246,14 @@ export async function commitCount(from: string, to: string): Promise<number> {
|
|
|
243
246
|
}
|
|
244
247
|
}
|
|
245
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Get all local branch names.
|
|
251
|
+
*/
|
|
252
|
+
export async function allLocalBranches(): Promise<string[]> {
|
|
253
|
+
const result = await $`git branch --format=${"%(refname:short)"}`.text();
|
|
254
|
+
return result.trim().split("\n").filter(Boolean);
|
|
255
|
+
}
|
|
256
|
+
|
|
246
257
|
/**
|
|
247
258
|
* Build a deterministic temp tag name for a branch.
|
|
248
259
|
* Includes a readable slug plus a stable hash to avoid collisions.
|
package/src/lib/safety.ts
CHANGED
|
@@ -58,7 +58,7 @@ export async function ensureBranchInStack(meta: StackMetadata): Promise<string>
|
|
|
58
58
|
|
|
59
59
|
if (!stackName) {
|
|
60
60
|
p.cancel(
|
|
61
|
-
`Branch ${pc.blue(branch)} is not in any stack.\n\n Add it with:\n ${pc.green("gh-stack
|
|
61
|
+
`Branch ${pc.blue(branch)} is not in any stack.\n\n Add it with:\n ${pc.green("gh-stack create")}\n\n Or create a new stack:\n ${pc.green("gh-stack init")}`,
|
|
62
62
|
);
|
|
63
63
|
process.exit(1);
|
|
64
64
|
}
|
package/src/commands/add.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
// gh-stack add — Add a branch to the current stack
|
|
2
|
-
import * as p from "@clack/prompts";
|
|
3
|
-
import pc from "picocolors";
|
|
4
|
-
import * as git from "../lib/git.ts";
|
|
5
|
-
import { addBranchToStack, getTopOfStack, getOrderedBranches } from "../lib/metadata.ts";
|
|
6
|
-
import { ensureMetadata, ensureCurrentStack, ensureValidStack } from "../lib/safety.ts";
|
|
7
|
-
import { getPrNumber } from "../lib/github.ts";
|
|
8
|
-
import { isAutoYes, selectParent } from "../lib/ui.ts";
|
|
9
|
-
import type { Branch } from "../types.ts";
|
|
10
|
-
|
|
11
|
-
export default async function add(args: string[]): Promise<void> {
|
|
12
|
-
// Parse flags
|
|
13
|
-
let parentFlag: string | undefined;
|
|
14
|
-
let descFlag: string | undefined;
|
|
15
|
-
let createFlag: string | undefined;
|
|
16
|
-
let branchArg: string | undefined;
|
|
17
|
-
|
|
18
|
-
for (let i = 0; i < args.length; i++) {
|
|
19
|
-
switch (args[i]) {
|
|
20
|
-
case "--parent":
|
|
21
|
-
parentFlag = args[++i];
|
|
22
|
-
break;
|
|
23
|
-
case "--description":
|
|
24
|
-
descFlag = args[++i];
|
|
25
|
-
break;
|
|
26
|
-
case "--create":
|
|
27
|
-
createFlag = args[++i];
|
|
28
|
-
break;
|
|
29
|
-
case "--help":
|
|
30
|
-
console.log(`
|
|
31
|
-
gh-stack add — Add a branch to the current stack
|
|
32
|
-
|
|
33
|
-
USAGE
|
|
34
|
-
gh-stack add [<branch>] [options]
|
|
35
|
-
|
|
36
|
-
OPTIONS
|
|
37
|
-
--parent <branch> Parent branch (default: top of stack)
|
|
38
|
-
--create <branch> Create new branch off top of stack and add it
|
|
39
|
-
--description <desc> Description for the branch
|
|
40
|
-
`);
|
|
41
|
-
return;
|
|
42
|
-
default:
|
|
43
|
-
if (!args[i]!.startsWith("--")) {
|
|
44
|
-
branchArg = args[i];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const meta = await ensureMetadata();
|
|
50
|
-
const stackName = ensureCurrentStack(meta);
|
|
51
|
-
await ensureValidStack(meta, stackName);
|
|
52
|
-
const stack = meta.stacks[stackName]!;
|
|
53
|
-
|
|
54
|
-
p.intro(pc.cyan(`Add branch to ${pc.yellow(stackName)}`));
|
|
55
|
-
|
|
56
|
-
let branchToAdd: string;
|
|
57
|
-
|
|
58
|
-
// --create: make a new branch off the top of the stack
|
|
59
|
-
if (createFlag) {
|
|
60
|
-
const topBranch = getTopOfStack(stack);
|
|
61
|
-
if (!topBranch) {
|
|
62
|
-
p.cancel("Stack has no branches yet. Add the base branch first.");
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const currentBranch = await git.currentBranch();
|
|
67
|
-
if (currentBranch !== topBranch) {
|
|
68
|
-
p.log.info(`Switching to ${pc.yellow(topBranch)}...`);
|
|
69
|
-
await git.checkout(topBranch);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
p.log.info(`Creating branch off top of stack: ${pc.yellow(topBranch)}`);
|
|
73
|
-
await git.createBranch(createFlag);
|
|
74
|
-
p.log.success(`Created branch: ${pc.yellow(createFlag)}`);
|
|
75
|
-
|
|
76
|
-
branchToAdd = createFlag;
|
|
77
|
-
parentFlag = topBranch;
|
|
78
|
-
} else {
|
|
79
|
-
branchToAdd = branchArg || (await git.currentBranch());
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Check if branch is already in the stack
|
|
83
|
-
if (stack.branches[branchToAdd]) {
|
|
84
|
-
p.cancel(`Branch ${pc.yellow(branchToAdd)} is already in stack ${pc.blue(stackName)}`);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
console.log(` Adding: ${pc.yellow(branchToAdd)}`);
|
|
89
|
-
console.log();
|
|
90
|
-
|
|
91
|
-
// Get parent
|
|
92
|
-
let parent = parentFlag;
|
|
93
|
-
if (!parent) {
|
|
94
|
-
// Default to top of stack
|
|
95
|
-
const ordered = getOrderedBranches(stack);
|
|
96
|
-
|
|
97
|
-
if (ordered.length === 0) {
|
|
98
|
-
parent = "main";
|
|
99
|
-
} else if (isAutoYes()) {
|
|
100
|
-
parent = getTopOfStack(stack) ?? "main";
|
|
101
|
-
} else {
|
|
102
|
-
parent = (await selectParent(stack, branchToAdd)) ?? undefined;
|
|
103
|
-
if (!parent) {
|
|
104
|
-
p.cancel("Cancelled");
|
|
105
|
-
process.exit(0);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
p.log.info(`Parent: ${pc.yellow(parent)}`);
|
|
111
|
-
|
|
112
|
-
// Auto-detect PR number
|
|
113
|
-
const s = p.spinner();
|
|
114
|
-
s.start("Looking for PR...");
|
|
115
|
-
const prNumber = await getPrNumber(branchToAdd);
|
|
116
|
-
s.stop(prNumber ? `Found PR #${prNumber}` : "No PR found");
|
|
117
|
-
|
|
118
|
-
// Get description
|
|
119
|
-
let description = descFlag;
|
|
120
|
-
if (description === undefined && !createFlag) {
|
|
121
|
-
const result = await p.text({
|
|
122
|
-
message: "Branch description (optional)",
|
|
123
|
-
placeholder: "e.g., API layer",
|
|
124
|
-
});
|
|
125
|
-
if (!p.isCancel(result)) {
|
|
126
|
-
description = (result as string) || undefined;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Add branch
|
|
131
|
-
const branchData: Branch = {
|
|
132
|
-
parent,
|
|
133
|
-
...(prNumber && { pr: prNumber }),
|
|
134
|
-
...(description && { description }),
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
await addBranchToStack(meta, stackName, branchToAdd, branchData);
|
|
138
|
-
|
|
139
|
-
p.outro(pc.green(`Added ${pc.yellow(branchToAdd)} to stack ${pc.blue(stackName)}`));
|
|
140
|
-
}
|