feat(lanes): Add Lane repo/worktree mode toggle, manual branch, folder browse

- Repo mode adopts a directory as-is via /lanes/ensure (no worktree, no
  branch fields) - the right choice for a main repo you want stage
  detection on. Worktree mode (default) keeps the existing provisioning
  flow but now requires a manually-typed branch name instead of deriving
  one from the title.
- POST /lanes/worktree accepts an optional `branch`, validated via
  `git check-ref-format --branch`; omitting it preserves the CLI's
  existing auto-derived-branch behavior.
- New GET /lanes/browse lists a directory's immediate subdirectories,
  backing a small folder-browse modal on both path fields - browsers
  cannot expose an absolute path from a native picker, so this is
  server-backed instead, consistent with the tool's local-first model.
This commit is contained in:
2026-08-06 16:03:49 +07:00
parent 9f13769fb4
commit 78f6e1be8e
10 changed files with 594 additions and 72 deletions
+17 -5
View File
@@ -78,6 +78,21 @@ async function isGitRepo(dir) {
}
}
/**
* Whether `name` is a legal git branch name, per git's own rules rather than
* a hand-rolled regex. `cwd` need not be `sourceRepo` specifically — the
* check is not repo-dependent — but `git()` requires a directory to run in.
*/
async function isValidBranchName(cwd, name) {
if (typeof name !== "string" || !name) return false;
try {
await git(cwd, ["check-ref-format", "--branch", name]);
return true;
} catch {
return false;
}
}
/**
* List a repo's local branches plus its current HEAD branch, so a caller can
* offer a real picker instead of asking someone to remember a branch name.
@@ -85,11 +100,7 @@ async function isGitRepo(dir) {
* can actually check a new worktree out onto without a fetch first.
*/
async function listBranches(sourceRepo) {
const result = await git(sourceRepo, [
"for-each-ref",
"--format=%(refname:short)",
"refs/heads",
]);
const result = await git(sourceRepo, ["for-each-ref", "--format=%(refname:short)", "refs/heads"]);
const branches = result.stdout
.split("\n")
.map((line) => line.trim())
@@ -643,6 +654,7 @@ module.exports = {
LANES_ROOT,
git,
isGitRepo,
isValidBranchName,
listBranches,
resolveBase,
slugify,