@snappedly-tools/shipyard 0.7.0-staging.36082887921 → 0.7.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/README.md +41 -34
- package/dist/MountConfig-bZoCs4Dd.d.ts +26 -0
- package/dist/SandboxProvider-XJQqEdSf.d.ts +261 -0
- package/dist/{chunk-JI3HDDMS.js → chunk-56TDSWFU.js} +3 -3
- package/dist/{chunk-JI3HDDMS.js.map → chunk-56TDSWFU.js.map} +1 -1
- package/dist/chunk-ACD46ZM4.js +136 -0
- package/dist/chunk-ACD46ZM4.js.map +1 -0
- package/dist/{chunk-L6PX5QTU.js → chunk-FDYOTN55.js} +887 -262
- package/dist/chunk-FDYOTN55.js.map +1 -0
- package/dist/chunk-KMGNFXKN.js +38 -0
- package/dist/chunk-KMGNFXKN.js.map +1 -0
- package/dist/chunk-SOJTAJTF.js +78 -0
- package/dist/chunk-SOJTAJTF.js.map +1 -0
- package/dist/{chunk-44I2BL6E.js → chunk-WAXEMZUV.js} +33 -225
- package/dist/chunk-WAXEMZUV.js.map +1 -0
- package/dist/index.d.ts +97 -117
- package/dist/index.js +344 -322
- package/dist/index.js.map +1 -1
- package/dist/main.js +139 -235
- package/dist/main.js.map +1 -1
- package/dist/sandboxes/docker.d.ts +3 -1
- package/dist/sandboxes/docker.js +4 -2
- package/dist/sandboxes/no-sandbox.d.ts +37 -0
- package/dist/sandboxes/no-sandbox.js +4 -0
- package/dist/sandboxes/no-sandbox.js.map +1 -0
- package/dist/sandboxes/vercel.d.ts +104 -0
- package/dist/sandboxes/vercel.js +166 -0
- package/dist/sandboxes/vercel.js.map +1 -0
- package/dist/templates/blank/main.mts +13 -0
- package/dist/templates/blank/prompt.md +12 -0
- package/dist/templates/blank/template.json +4 -0
- package/dist/templates/parallel-planner/main.mts +9 -67
- package/dist/templates/parallel-planner-with-review/main.mts +12 -70
- package/dist/templates/sequential-reviewer/main.mts +3 -52
- package/dist/templates/simple-loop/main.mts +2 -51
- package/package.json +17 -1
- package/dist/MountConfig-BHnKnA4h.d.ts +0 -145
- package/dist/chunk-44I2BL6E.js.map +0 -1
- package/dist/chunk-L6PX5QTU.js.map +0 -1
- package/dist/templates/parallel-planner/planner-branch.mts +0 -151
- package/dist/templates/parallel-planner-with-review/planner-branch.mts +0 -151
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { createInterface } from "node:readline/promises";
|
|
3
|
-
|
|
4
|
-
const PLANNER_BRANCH = "shipyard/planner";
|
|
5
|
-
|
|
6
|
-
type BranchPrompt = (message: string) => Promise<string>;
|
|
7
|
-
|
|
8
|
-
const askInTerminal: BranchPrompt = async (message) => {
|
|
9
|
-
const readline = createInterface({
|
|
10
|
-
input: process.stdin,
|
|
11
|
-
output: process.stdout,
|
|
12
|
-
});
|
|
13
|
-
try {
|
|
14
|
-
return await readline.question(message);
|
|
15
|
-
} finally {
|
|
16
|
-
readline.close();
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const refsConflict = (left: string, right: string): boolean =>
|
|
21
|
-
left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
|
|
22
|
-
|
|
23
|
-
const nextPlannerBranch = (localBranches: readonly string[]): string => {
|
|
24
|
-
for (let suffix = 2; ; suffix++) {
|
|
25
|
-
for (const candidate of [
|
|
26
|
-
`${PLANNER_BRANCH}-${suffix}`,
|
|
27
|
-
`shipyard-planner-${suffix}`,
|
|
28
|
-
]) {
|
|
29
|
-
if (!localBranches.some((branch) => refsConflict(candidate, branch)))
|
|
30
|
-
return candidate;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const reusablePlannerBranch = (
|
|
36
|
-
localBranches: readonly string[],
|
|
37
|
-
): string | undefined => {
|
|
38
|
-
const candidates = localBranches
|
|
39
|
-
.map((branch) => ({
|
|
40
|
-
branch,
|
|
41
|
-
suffix: /^(?:shipyard\/planner-|shipyard-planner-)(\d+)$/.exec(
|
|
42
|
-
branch,
|
|
43
|
-
)?.[1],
|
|
44
|
-
}))
|
|
45
|
-
.filter((candidate) => candidate.suffix !== undefined)
|
|
46
|
-
.sort((left, right) => Number(left.suffix) - Number(right.suffix));
|
|
47
|
-
|
|
48
|
-
return candidates.find(({ branch }) =>
|
|
49
|
-
localBranches.every(
|
|
50
|
-
(other) => other === branch || !refsConflict(branch, other),
|
|
51
|
-
),
|
|
52
|
-
)?.branch;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export const resolvePlannerBranch = async (
|
|
56
|
-
localBranches: readonly string[],
|
|
57
|
-
options: {
|
|
58
|
-
isInteractive?: boolean;
|
|
59
|
-
ask?: BranchPrompt;
|
|
60
|
-
checkedOutBranches?: readonly string[];
|
|
61
|
-
mergedBranches?: readonly string[];
|
|
62
|
-
deleteBranches?: (branches: readonly string[]) => void;
|
|
63
|
-
} = {},
|
|
64
|
-
): Promise<string> => {
|
|
65
|
-
const conflicts = localBranches.filter((branch) =>
|
|
66
|
-
refsConflict(PLANNER_BRANCH, branch),
|
|
67
|
-
);
|
|
68
|
-
if (conflicts.length === 0) return PLANNER_BRANCH;
|
|
69
|
-
|
|
70
|
-
const reusable = reusablePlannerBranch(localBranches);
|
|
71
|
-
if (reusable) return reusable;
|
|
72
|
-
|
|
73
|
-
const alternate = nextPlannerBranch(localBranches);
|
|
74
|
-
|
|
75
|
-
const conflictList = conflicts.map((branch) => ` - ${branch}`).join("\n");
|
|
76
|
-
const context =
|
|
77
|
-
`Planner branch '${PLANNER_BRANCH}' conflicts with local branch refs:\n${conflictList}\n` +
|
|
78
|
-
`Git cannot create both names. The listed local branches will be kept unless you choose deletion.`;
|
|
79
|
-
const isInteractive =
|
|
80
|
-
options.isInteractive ??
|
|
81
|
-
Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
82
|
-
if (!isInteractive) {
|
|
83
|
-
throw new Error(
|
|
84
|
-
`${context}\nRerun in an interactive terminal to keep them and use '${alternate}', or request local deletion after Git checks that they are merged and not checked out. No branches were changed.`,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const checkedOutBranches = new Set(
|
|
89
|
-
options.checkedOutBranches ??
|
|
90
|
-
execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
91
|
-
encoding: "utf8",
|
|
92
|
-
})
|
|
93
|
-
.split(/\r?\n/)
|
|
94
|
-
.filter((line) => line.startsWith("branch refs/heads/"))
|
|
95
|
-
.map((line) => line.slice("branch refs/heads/".length)),
|
|
96
|
-
);
|
|
97
|
-
const mergedBranches = new Set(
|
|
98
|
-
options.mergedBranches ??
|
|
99
|
-
execFileSync(
|
|
100
|
-
"git",
|
|
101
|
-
["branch", "--merged", "HEAD", "--format=%(refname:short)"],
|
|
102
|
-
{ encoding: "utf8" },
|
|
103
|
-
)
|
|
104
|
-
.split(/\r?\n/)
|
|
105
|
-
.filter(Boolean),
|
|
106
|
-
);
|
|
107
|
-
const branchesSafeToDelete = conflicts.every(
|
|
108
|
-
(branch) => !checkedOutBranches.has(branch) && mergedBranches.has(branch),
|
|
109
|
-
);
|
|
110
|
-
const unsafeDeleteReasons = conflicts.flatMap((branch) => {
|
|
111
|
-
if (checkedOutBranches.has(branch))
|
|
112
|
-
return [`'${branch}' is checked out in a worktree`];
|
|
113
|
-
if (!mergedBranches.has(branch))
|
|
114
|
-
return [`'${branch}' has commits not merged into HEAD`];
|
|
115
|
-
return [];
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const ask = options.ask ?? askInTerminal;
|
|
119
|
-
const answer = await ask(
|
|
120
|
-
`${context}\n[Y] Keep them and use '${alternate}' (recommended)\n` +
|
|
121
|
-
`[d] Delete these local branches and use '${PLANNER_BRANCH}'${
|
|
122
|
-
branchesSafeToDelete
|
|
123
|
-
? ""
|
|
124
|
-
: ` (unavailable: ${unsafeDeleteReasons.join("; ")})`
|
|
125
|
-
}\n` +
|
|
126
|
-
`[n] Abort without changes\nChoose [Y/d/n]: `,
|
|
127
|
-
);
|
|
128
|
-
const choice = answer.trim().toLowerCase();
|
|
129
|
-
if (choice === "" || choice === "y" || choice === "yes") return alternate;
|
|
130
|
-
if (choice === "d" || choice === "delete") {
|
|
131
|
-
if (!branchesSafeToDelete) {
|
|
132
|
-
throw new Error(
|
|
133
|
-
`Cannot safely delete the conflicting branches: ${unsafeDeleteReasons.join("; ")}. No branches were changed. Use '${alternate}' or inspect the listed refs manually.`,
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
const deleteBranches =
|
|
137
|
-
options.deleteBranches ??
|
|
138
|
-
((branches: readonly string[]) => {
|
|
139
|
-
for (const branch of branches)
|
|
140
|
-
execFileSync("git", ["branch", "-d", branch], {
|
|
141
|
-
encoding: "utf8",
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
deleteBranches(conflicts);
|
|
145
|
-
return PLANNER_BRANCH;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
throw new Error(
|
|
149
|
-
`Planner branch selection cancelled. No branches were changed. Inspect with 'git worktree list' and 'git branch --list "shipyard/planner*"', then rerun.`,
|
|
150
|
-
);
|
|
151
|
-
};
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { createInterface } from "node:readline/promises";
|
|
3
|
-
|
|
4
|
-
const PLANNER_BRANCH = "shipyard/planner";
|
|
5
|
-
|
|
6
|
-
type BranchPrompt = (message: string) => Promise<string>;
|
|
7
|
-
|
|
8
|
-
const askInTerminal: BranchPrompt = async (message) => {
|
|
9
|
-
const readline = createInterface({
|
|
10
|
-
input: process.stdin,
|
|
11
|
-
output: process.stdout,
|
|
12
|
-
});
|
|
13
|
-
try {
|
|
14
|
-
return await readline.question(message);
|
|
15
|
-
} finally {
|
|
16
|
-
readline.close();
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const refsConflict = (left: string, right: string): boolean =>
|
|
21
|
-
left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
|
|
22
|
-
|
|
23
|
-
const nextPlannerBranch = (localBranches: readonly string[]): string => {
|
|
24
|
-
for (let suffix = 2; ; suffix++) {
|
|
25
|
-
for (const candidate of [
|
|
26
|
-
`${PLANNER_BRANCH}-${suffix}`,
|
|
27
|
-
`shipyard-planner-${suffix}`,
|
|
28
|
-
]) {
|
|
29
|
-
if (!localBranches.some((branch) => refsConflict(candidate, branch)))
|
|
30
|
-
return candidate;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const reusablePlannerBranch = (
|
|
36
|
-
localBranches: readonly string[],
|
|
37
|
-
): string | undefined => {
|
|
38
|
-
const candidates = localBranches
|
|
39
|
-
.map((branch) => ({
|
|
40
|
-
branch,
|
|
41
|
-
suffix: /^(?:shipyard\/planner-|shipyard-planner-)(\d+)$/.exec(
|
|
42
|
-
branch,
|
|
43
|
-
)?.[1],
|
|
44
|
-
}))
|
|
45
|
-
.filter((candidate) => candidate.suffix !== undefined)
|
|
46
|
-
.sort((left, right) => Number(left.suffix) - Number(right.suffix));
|
|
47
|
-
|
|
48
|
-
return candidates.find(({ branch }) =>
|
|
49
|
-
localBranches.every(
|
|
50
|
-
(other) => other === branch || !refsConflict(branch, other),
|
|
51
|
-
),
|
|
52
|
-
)?.branch;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export const resolvePlannerBranch = async (
|
|
56
|
-
localBranches: readonly string[],
|
|
57
|
-
options: {
|
|
58
|
-
isInteractive?: boolean;
|
|
59
|
-
ask?: BranchPrompt;
|
|
60
|
-
checkedOutBranches?: readonly string[];
|
|
61
|
-
mergedBranches?: readonly string[];
|
|
62
|
-
deleteBranches?: (branches: readonly string[]) => void;
|
|
63
|
-
} = {},
|
|
64
|
-
): Promise<string> => {
|
|
65
|
-
const conflicts = localBranches.filter((branch) =>
|
|
66
|
-
refsConflict(PLANNER_BRANCH, branch),
|
|
67
|
-
);
|
|
68
|
-
if (conflicts.length === 0) return PLANNER_BRANCH;
|
|
69
|
-
|
|
70
|
-
const reusable = reusablePlannerBranch(localBranches);
|
|
71
|
-
if (reusable) return reusable;
|
|
72
|
-
|
|
73
|
-
const alternate = nextPlannerBranch(localBranches);
|
|
74
|
-
|
|
75
|
-
const conflictList = conflicts.map((branch) => ` - ${branch}`).join("\n");
|
|
76
|
-
const context =
|
|
77
|
-
`Planner branch '${PLANNER_BRANCH}' conflicts with local branch refs:\n${conflictList}\n` +
|
|
78
|
-
`Git cannot create both names. The listed local branches will be kept unless you choose deletion.`;
|
|
79
|
-
const isInteractive =
|
|
80
|
-
options.isInteractive ??
|
|
81
|
-
Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
82
|
-
if (!isInteractive) {
|
|
83
|
-
throw new Error(
|
|
84
|
-
`${context}\nRerun in an interactive terminal to keep them and use '${alternate}', or request local deletion after Git checks that they are merged and not checked out. No branches were changed.`,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const checkedOutBranches = new Set(
|
|
89
|
-
options.checkedOutBranches ??
|
|
90
|
-
execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
91
|
-
encoding: "utf8",
|
|
92
|
-
})
|
|
93
|
-
.split(/\r?\n/)
|
|
94
|
-
.filter((line) => line.startsWith("branch refs/heads/"))
|
|
95
|
-
.map((line) => line.slice("branch refs/heads/".length)),
|
|
96
|
-
);
|
|
97
|
-
const mergedBranches = new Set(
|
|
98
|
-
options.mergedBranches ??
|
|
99
|
-
execFileSync(
|
|
100
|
-
"git",
|
|
101
|
-
["branch", "--merged", "HEAD", "--format=%(refname:short)"],
|
|
102
|
-
{ encoding: "utf8" },
|
|
103
|
-
)
|
|
104
|
-
.split(/\r?\n/)
|
|
105
|
-
.filter(Boolean),
|
|
106
|
-
);
|
|
107
|
-
const branchesSafeToDelete = conflicts.every(
|
|
108
|
-
(branch) => !checkedOutBranches.has(branch) && mergedBranches.has(branch),
|
|
109
|
-
);
|
|
110
|
-
const unsafeDeleteReasons = conflicts.flatMap((branch) => {
|
|
111
|
-
if (checkedOutBranches.has(branch))
|
|
112
|
-
return [`'${branch}' is checked out in a worktree`];
|
|
113
|
-
if (!mergedBranches.has(branch))
|
|
114
|
-
return [`'${branch}' has commits not merged into HEAD`];
|
|
115
|
-
return [];
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const ask = options.ask ?? askInTerminal;
|
|
119
|
-
const answer = await ask(
|
|
120
|
-
`${context}\n[Y] Keep them and use '${alternate}' (recommended)\n` +
|
|
121
|
-
`[d] Delete these local branches and use '${PLANNER_BRANCH}'${
|
|
122
|
-
branchesSafeToDelete
|
|
123
|
-
? ""
|
|
124
|
-
: ` (unavailable: ${unsafeDeleteReasons.join("; ")})`
|
|
125
|
-
}\n` +
|
|
126
|
-
`[n] Abort without changes\nChoose [Y/d/n]: `,
|
|
127
|
-
);
|
|
128
|
-
const choice = answer.trim().toLowerCase();
|
|
129
|
-
if (choice === "" || choice === "y" || choice === "yes") return alternate;
|
|
130
|
-
if (choice === "d" || choice === "delete") {
|
|
131
|
-
if (!branchesSafeToDelete) {
|
|
132
|
-
throw new Error(
|
|
133
|
-
`Cannot safely delete the conflicting branches: ${unsafeDeleteReasons.join("; ")}. No branches were changed. Use '${alternate}' or inspect the listed refs manually.`,
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
const deleteBranches =
|
|
137
|
-
options.deleteBranches ??
|
|
138
|
-
((branches: readonly string[]) => {
|
|
139
|
-
for (const branch of branches)
|
|
140
|
-
execFileSync("git", ["branch", "-d", branch], {
|
|
141
|
-
encoding: "utf8",
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
deleteBranches(conflicts);
|
|
145
|
-
return PLANNER_BRANCH;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
throw new Error(
|
|
149
|
-
`Planner branch selection cancelled. No branches were changed. Inspect with 'git worktree list' and 'git branch --list "shipyard/planner*"', then rerun.`,
|
|
150
|
-
);
|
|
151
|
-
};
|