runcastle 1.0.1 → 1.0.5
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/bin/runcastle.js +84 -19
- package/drizzle/0006_living_typhoid_mary.sql +1 -0
- package/drizzle/meta/0006_snapshot.json +603 -0
- package/drizzle/meta/_journal.json +8 -1
- package/index.js +84 -19
- package/package.json +11 -1
- package/web/assets/{index-YBuT7tTz.js → index-CRhFvrAu.js} +2 -2
- package/web/assets/{index-CKZ5Z7vc.css → index-ClNOSIVg.css} +1 -1
- package/web/index.html +2 -2
package/bin/runcastle.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __returnValue = (v) => v;
|
|
5
|
+
function __exportSetter(name, newValue) {
|
|
6
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
7
|
+
}
|
|
4
8
|
var __export = (target, all) => {
|
|
5
9
|
for (var name in all)
|
|
6
10
|
__defProp(target, name, {
|
|
7
11
|
get: all[name],
|
|
8
12
|
enumerable: true,
|
|
9
13
|
configurable: true,
|
|
10
|
-
set: (
|
|
14
|
+
set: __exportSetter.bind(all, name)
|
|
11
15
|
});
|
|
12
16
|
};
|
|
13
17
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -226,6 +230,7 @@ var init_schemas = __esm(() => {
|
|
|
226
230
|
mapped: z2.boolean(),
|
|
227
231
|
phase: Phase,
|
|
228
232
|
branch: z2.string(),
|
|
233
|
+
baseBranch: z2.string().optional(),
|
|
229
234
|
status: FeatureStatus,
|
|
230
235
|
createdAt: z2.number()
|
|
231
236
|
});
|
|
@@ -439,6 +444,7 @@ var init_db_schema = __esm(() => {
|
|
|
439
444
|
mapped: integer("mapped", { mode: "boolean" }).notNull().default(false),
|
|
440
445
|
phase: text("phase").notNull().$type(),
|
|
441
446
|
branch: text("branch").notNull(),
|
|
447
|
+
baseBranch: text("base_branch"),
|
|
442
448
|
status: text("status").notNull().$type(),
|
|
443
449
|
createdAt: integer("created_at").notNull()
|
|
444
450
|
});
|
|
@@ -1165,6 +1171,7 @@ function rowToFeature(row) {
|
|
|
1165
1171
|
mapped: row.mapped,
|
|
1166
1172
|
phase: row.phase,
|
|
1167
1173
|
branch: row.branch,
|
|
1174
|
+
baseBranch: row.baseBranch ?? undefined,
|
|
1168
1175
|
status: row.status,
|
|
1169
1176
|
createdAt: row.createdAt
|
|
1170
1177
|
};
|
|
@@ -1901,20 +1908,59 @@ async function detectMainBranch(repoPath) {
|
|
|
1901
1908
|
} catch {}
|
|
1902
1909
|
return "main";
|
|
1903
1910
|
}
|
|
1904
|
-
async function createFeatureBranch(project, slug) {
|
|
1911
|
+
async function createFeatureBranch(project, slug, base) {
|
|
1905
1912
|
const branch = featureBranch(slug);
|
|
1913
|
+
const from = base?.trim() || project.mainBranch;
|
|
1906
1914
|
const g = git(project.repoPath);
|
|
1907
1915
|
const branches = await g.branchLocal();
|
|
1908
1916
|
if (branches.all.includes(branch))
|
|
1909
1917
|
return branch;
|
|
1910
|
-
|
|
1918
|
+
if (!branches.all.includes(from)) {
|
|
1919
|
+
throw new Error(`base branch "${from}" does not exist in ${project.repoPath}`);
|
|
1920
|
+
}
|
|
1921
|
+
await g.raw(["branch", branch, from]);
|
|
1911
1922
|
return branch;
|
|
1912
1923
|
}
|
|
1924
|
+
async function listBranches(project) {
|
|
1925
|
+
const g = git(project.repoPath);
|
|
1926
|
+
const local = await g.branchLocal();
|
|
1927
|
+
const branches = local.all.filter((name) => !name.startsWith("feature/"));
|
|
1928
|
+
const localSet = new Set(local.all);
|
|
1929
|
+
let remoteBranches = [];
|
|
1930
|
+
try {
|
|
1931
|
+
const remote = await g.branch(["-r"]);
|
|
1932
|
+
remoteBranches = remote.all.filter((r) => !r.includes(" -> ") && !r.endsWith("/HEAD")).filter((r) => {
|
|
1933
|
+
const short = r.replace(/^[^/]+\//, "");
|
|
1934
|
+
return !short.startsWith("feature/") && !localSet.has(short);
|
|
1935
|
+
});
|
|
1936
|
+
} catch {}
|
|
1937
|
+
return { current: local.current, mainBranch: project.mainBranch, branches, remoteBranches };
|
|
1938
|
+
}
|
|
1939
|
+
async function resolveBaseBranch(project, base) {
|
|
1940
|
+
const g = git(project.repoPath);
|
|
1941
|
+
const local = await g.branchLocal();
|
|
1942
|
+
if (local.all.includes(base))
|
|
1943
|
+
return base;
|
|
1944
|
+
const remotes = (await g.getRemotes()).map((r) => r.name);
|
|
1945
|
+
for (const rem of remotes) {
|
|
1946
|
+
const prefix = `${rem}/`;
|
|
1947
|
+
if (!base.startsWith(prefix))
|
|
1948
|
+
continue;
|
|
1949
|
+
const name = base.slice(prefix.length);
|
|
1950
|
+
if (!name || name.endsWith("/HEAD"))
|
|
1951
|
+
break;
|
|
1952
|
+
if (local.all.includes(name))
|
|
1953
|
+
return name;
|
|
1954
|
+
await g.raw(["branch", "--track", name, base]);
|
|
1955
|
+
return name;
|
|
1956
|
+
}
|
|
1957
|
+
throw new Error(`base branch "${base}" does not exist in ${project.repoPath}`);
|
|
1958
|
+
}
|
|
1913
1959
|
async function ensureTalkWorktree(project, feature) {
|
|
1914
1960
|
const worktreePath = worktreeDir(project.id, feature.slug);
|
|
1915
1961
|
const branch = featureBranch(feature.slug);
|
|
1916
1962
|
const g = git(project.repoPath);
|
|
1917
|
-
await ensureBranchExists(g, branch, project.mainBranch);
|
|
1963
|
+
await ensureBranchExists(g, branch, feature.baseBranch ?? project.mainBranch);
|
|
1918
1964
|
if (await worktreeIsValid(g, worktreePath, branch))
|
|
1919
1965
|
return worktreePath;
|
|
1920
1966
|
mkdirSync2(dirname(worktreePath), { recursive: true });
|
|
@@ -2185,6 +2231,7 @@ async function testDrive(ctx, project, feature, action) {
|
|
|
2185
2231
|
return { ok: true, branch };
|
|
2186
2232
|
}
|
|
2187
2233
|
async function mergeFeature(project, feature) {
|
|
2234
|
+
const target = feature.baseBranch ?? project.mainBranch;
|
|
2188
2235
|
if (testDriveState) {
|
|
2189
2236
|
throw new GateError("Cannot merge while a test drive is active \u2014 stop it first");
|
|
2190
2237
|
}
|
|
@@ -2193,19 +2240,33 @@ async function mergeFeature(project, feature) {
|
|
|
2193
2240
|
if (porcelain !== "") {
|
|
2194
2241
|
throw new GateError("Cannot merge \u2014 working tree has uncommitted changes");
|
|
2195
2242
|
}
|
|
2243
|
+
const branches = await g.branchLocal();
|
|
2244
|
+
if (!branches.all.includes(target)) {
|
|
2245
|
+
throw new GateError(`Cannot merge \u2014 base branch "${target}" no longer exists`);
|
|
2246
|
+
}
|
|
2247
|
+
const previous = (await g.revparse(["--abbrev-ref", "HEAD"])).trim();
|
|
2196
2248
|
const branch = featureBranch(feature.slug);
|
|
2197
|
-
await g.checkout(
|
|
2249
|
+
await g.checkout(target);
|
|
2198
2250
|
try {
|
|
2199
2251
|
await g.merge(["--no-ff", branch]);
|
|
2200
|
-
|
|
2252
|
+
await restoreBranch(g, previous, target);
|
|
2253
|
+
return { ok: true, target };
|
|
2201
2254
|
} catch (e) {
|
|
2202
2255
|
if (await mergeInProgress(g)) {
|
|
2203
2256
|
await g.raw(["merge", "--abort"]);
|
|
2204
|
-
|
|
2257
|
+
await restoreBranch(g, previous, target);
|
|
2258
|
+
return { ok: false, conflict: true, target };
|
|
2205
2259
|
}
|
|
2206
2260
|
throw e instanceof Error ? e : new Error(errMsg(e));
|
|
2207
2261
|
}
|
|
2208
2262
|
}
|
|
2263
|
+
async function restoreBranch(g, previous, target) {
|
|
2264
|
+
if (previous === target || previous === "HEAD" || !previous)
|
|
2265
|
+
return;
|
|
2266
|
+
try {
|
|
2267
|
+
await g.checkout(previous);
|
|
2268
|
+
} catch {}
|
|
2269
|
+
}
|
|
2209
2270
|
async function mergeInProgress(g) {
|
|
2210
2271
|
try {
|
|
2211
2272
|
await g.raw(["rev-parse", "--verify", "--quiet", "MERGE_HEAD"]);
|
|
@@ -3119,7 +3180,8 @@ async function createFeature(ctx, input) {
|
|
|
3119
3180
|
const project = requireProjectById(ctx, input.projectId);
|
|
3120
3181
|
const slug = uniqueSlug(ctx, project.id, input.title);
|
|
3121
3182
|
const branch = `feature/${slug}`;
|
|
3122
|
-
const
|
|
3183
|
+
const requestedBase = input.baseBranch?.trim() || project.mainBranch;
|
|
3184
|
+
const { branchReady, baseBranch } = await ensureFeatureBranch(project, slug, requestedBase);
|
|
3123
3185
|
const row = {
|
|
3124
3186
|
id: newId("feat"),
|
|
3125
3187
|
projectId: project.id,
|
|
@@ -3130,6 +3192,7 @@ async function createFeature(ctx, input) {
|
|
|
3130
3192
|
mapped: input.mapped ?? false,
|
|
3131
3193
|
phase: "ideation",
|
|
3132
3194
|
branch,
|
|
3195
|
+
baseBranch,
|
|
3133
3196
|
status: "active",
|
|
3134
3197
|
createdAt: Date.now()
|
|
3135
3198
|
};
|
|
@@ -3137,8 +3200,8 @@ async function createFeature(ctx, input) {
|
|
|
3137
3200
|
const feature = rowToFeature(inserted);
|
|
3138
3201
|
emit(ctx, feature.id, {
|
|
3139
3202
|
type: "feature.created",
|
|
3140
|
-
message: branchReady ? `feature.created (${branch})` : "feature.created (branch pending)",
|
|
3141
|
-
data: { slug, branch, branchReady }
|
|
3203
|
+
message: branchReady ? `feature.created (${branch} \u2190 ${baseBranch})` : "feature.created (branch pending)",
|
|
3204
|
+
data: { slug, branch, baseBranch, branchReady }
|
|
3142
3205
|
});
|
|
3143
3206
|
scaffoldDocs(ctx, feature);
|
|
3144
3207
|
if (branchReady) {
|
|
@@ -3148,13 +3211,14 @@ async function createFeature(ctx, input) {
|
|
|
3148
3211
|
}
|
|
3149
3212
|
return feature;
|
|
3150
3213
|
}
|
|
3151
|
-
async function ensureFeatureBranch(project, slug) {
|
|
3214
|
+
async function ensureFeatureBranch(project, slug, requestedBase) {
|
|
3152
3215
|
try {
|
|
3153
|
-
await
|
|
3154
|
-
|
|
3216
|
+
const baseBranch = await resolveBaseBranch(project, requestedBase);
|
|
3217
|
+
await createFeatureBranch(project, slug, baseBranch);
|
|
3218
|
+
return { branchReady: true, baseBranch };
|
|
3155
3219
|
} catch (e) {
|
|
3156
3220
|
if (isNotImplemented(e))
|
|
3157
|
-
return { branchReady: false };
|
|
3221
|
+
return { branchReady: false, baseBranch: requestedBase };
|
|
3158
3222
|
throw e;
|
|
3159
3223
|
}
|
|
3160
3224
|
}
|
|
@@ -4383,7 +4447,8 @@ var init_feature = __esm(() => {
|
|
|
4383
4447
|
title: z6.string().min(1),
|
|
4384
4448
|
oneLiner: z6.string(),
|
|
4385
4449
|
size: FeatureSize,
|
|
4386
|
-
mapped: z6.boolean().optional()
|
|
4450
|
+
mapped: z6.boolean().optional(),
|
|
4451
|
+
baseBranch: z6.string().optional()
|
|
4387
4452
|
})).mutation(({ ctx, input }) => createFeature(ctx, input)),
|
|
4388
4453
|
list: publicProcedure.input(z6.object({ projectId: z6.string() })).query(({ ctx, input }) => list(ctx, input.projectId)),
|
|
4389
4454
|
get: publicProcedure.input(z6.object({ id: z6.string() })).query(({ ctx, input }) => getFeatureFull(ctx, input.id)),
|
|
@@ -4408,7 +4473,7 @@ var init_feature = __esm(() => {
|
|
|
4408
4473
|
}
|
|
4409
4474
|
const res = await mergeFeature(project, feature);
|
|
4410
4475
|
if (res.ok) {
|
|
4411
|
-
setPhase(ctx, input.featureId, "shipped", "feature.shipped", `merged to ${
|
|
4476
|
+
setPhase(ctx, input.featureId, "shipped", "feature.shipped", `merged to ${res.target}`);
|
|
4412
4477
|
setFeatureStatus(ctx, input.featureId, "shipped");
|
|
4413
4478
|
} else {
|
|
4414
4479
|
emit(ctx, input.featureId, {
|
|
@@ -4514,10 +4579,13 @@ var init_projects = __esm(() => {
|
|
|
4514
4579
|
import * as z7 from "zod";
|
|
4515
4580
|
var projectRouter;
|
|
4516
4581
|
var init_project = __esm(() => {
|
|
4582
|
+
init_git();
|
|
4517
4583
|
init_projects();
|
|
4584
|
+
init_repo();
|
|
4518
4585
|
init_context();
|
|
4519
4586
|
projectRouter = router({
|
|
4520
4587
|
list: publicProcedure.query(({ ctx }) => listProjects(ctx)),
|
|
4588
|
+
branches: publicProcedure.input(z7.object({ projectId: z7.string() })).query(({ ctx, input }) => listBranches(requireProjectById(ctx, input.projectId))),
|
|
4521
4589
|
open: publicProcedure.input(z7.object({ repoPath: z7.string().min(1) })).mutation(({ ctx, input }) => openProject(ctx, input.repoPath)),
|
|
4522
4590
|
close: publicProcedure.input(z7.object({ projectId: z7.string() })).mutation(({ ctx, input }) => closeProject(ctx, input.projectId)),
|
|
4523
4591
|
rename: publicProcedure.input(z7.object({ projectId: z7.string(), name: z7.string().min(1) })).mutation(({ ctx, input }) => renameProject(ctx, input.projectId, input.name))
|
|
@@ -5442,9 +5510,6 @@ var init_src2 = __esm(() => {
|
|
|
5442
5510
|
init_web();
|
|
5443
5511
|
init_router();
|
|
5444
5512
|
init_reconcile_runs();
|
|
5445
|
-
if (import.meta.main) {
|
|
5446
|
-
startServer();
|
|
5447
|
-
}
|
|
5448
5513
|
});
|
|
5449
5514
|
|
|
5450
5515
|
// src/doctor/report.ts
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE `features` ADD `base_branch` text;
|