mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
Restore repository selection for CLI worktree commands (#6467)
* fix(cli): pass cwd to worktree ls and archive
worktree ls/archive called getPaseoWorktreeList({}) which fails because
the daemon requires cwd or repoRoot. Default to process.cwd(), accept
optional --cwd, and cover both with unit and CLI help tests.
Closes #2010
* test(cli): reproduce worktree listing without repository scope
* fix(cli): resolve local worktree paths from the calling directory
Local instance paths belong to the CLI working directory. Explicit endpoint paths remain daemon-relative, matching the project command convention. Cover both through the real daemon.
* refactor(cli): make repository directory selection explicit
---------
Co-authored-by: Jason <mildreins@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import path from "path";
|
||||
import type { Command } from "commander";
|
||||
import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
|
||||
import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
|
||||
import { resolveRepositoryDirectory } from "./repository-directory.js";
|
||||
import type {
|
||||
CommandOptions,
|
||||
SingleResult,
|
||||
@@ -31,6 +32,7 @@ export const archiveSchema: OutputSchema<WorktreeArchiveResult> = {
|
||||
|
||||
export interface WorktreeArchiveOptions extends CommandOptions {
|
||||
host?: string;
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
export type WorktreeArchiveCommandResult = SingleResult<WorktreeArchiveResult>;
|
||||
@@ -49,6 +51,7 @@ export async function runArchiveCommandWithDeps(
|
||||
deps: { connectToDaemon: typeof connectToDaemon },
|
||||
): Promise<WorktreeArchiveCommandResult> {
|
||||
const host = getDaemonHost({ target: options.daemonTarget });
|
||||
const cwd = resolveRepositoryDirectory(options);
|
||||
|
||||
// Validate arguments
|
||||
if (!nameArg || nameArg.trim().length === 0) {
|
||||
@@ -76,7 +79,7 @@ export async function runArchiveCommandWithDeps(
|
||||
|
||||
try {
|
||||
// Get the list of worktrees first to resolve the name
|
||||
const listResponse = await client.getPaseoWorktreeList({});
|
||||
const listResponse = await client.getPaseoWorktreeList({ cwd });
|
||||
|
||||
if (listResponse.error) {
|
||||
const error: CommandError = {
|
||||
|
||||
@@ -9,7 +9,10 @@ export function createWorktreeCommand(): Command {
|
||||
const worktree = new Command("worktree").description("Manage Paseo-managed git worktrees");
|
||||
|
||||
addJsonAndDaemonHostOptions(
|
||||
worktree.command("ls").description("List Paseo-managed git worktrees"),
|
||||
worktree
|
||||
.command("ls")
|
||||
.description("List Paseo-managed git worktrees")
|
||||
.option("--cwd <path>", "Repository directory (default: current)"),
|
||||
).action(withOutput(runLsCommand));
|
||||
|
||||
addJsonAndDaemonHostOptions(
|
||||
@@ -31,7 +34,8 @@ export function createWorktreeCommand(): Command {
|
||||
worktree
|
||||
.command("archive")
|
||||
.description("Archive a worktree (removes worktree and associated branch)")
|
||||
.argument("<name>", "Worktree name or branch name"),
|
||||
.argument("<name>", "Worktree name or branch name")
|
||||
.option("--cwd <path>", "Repository directory (default: current)"),
|
||||
).action(withOutput(runArchiveCommand));
|
||||
|
||||
return worktree;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Command } from "commander";
|
||||
import { basename } from "node:path";
|
||||
import { connectToDaemon } from "../../utils/client.js";
|
||||
import { resolveRepositoryDirectory } from "./repository-directory.js";
|
||||
import type { CommandOptions, ListResult, OutputSchema, CommandError } from "../../output/index.js";
|
||||
|
||||
/** Worktree list item for display */
|
||||
@@ -40,12 +41,14 @@ export type WorktreeLsResult = ListResult<WorktreeListItem>;
|
||||
|
||||
export interface WorktreeLsOptions extends CommandOptions {
|
||||
host?: string;
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
export async function runLsCommand(
|
||||
options: WorktreeLsOptions,
|
||||
_command: Command,
|
||||
): Promise<WorktreeLsResult> {
|
||||
const cwd = resolveRepositoryDirectory(options);
|
||||
const client = await connectToDaemon({ target: options.daemonTarget });
|
||||
|
||||
try {
|
||||
@@ -53,7 +56,7 @@ export async function runLsCommand(
|
||||
const agents = agentsPayload.entries.map((entry) => entry.agent);
|
||||
|
||||
// Get worktree list from daemon
|
||||
const response = await client.getPaseoWorktreeList({});
|
||||
const response = await client.getPaseoWorktreeList({ cwd });
|
||||
|
||||
await client.close();
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { resolve } from "node:path";
|
||||
import type { DaemonTarget } from "../../utils/daemon-target.js";
|
||||
|
||||
export function resolveRepositoryDirectory(options: {
|
||||
cwd?: string;
|
||||
daemonTarget: DaemonTarget;
|
||||
}): string {
|
||||
const cwd = options.cwd ?? process.cwd();
|
||||
if (options.daemonTarget.kind === "instance") {
|
||||
return resolve(cwd);
|
||||
}
|
||||
return cwd;
|
||||
}
|
||||
@@ -21,7 +21,10 @@
|
||||
import assert from "node:assert";
|
||||
import { getAvailablePort } from "./helpers/network.ts";
|
||||
import { $ } from "zx";
|
||||
import { mkdtemp, rm } from "fs/promises";
|
||||
import { mkdtemp, rm, mkdir } from "fs/promises";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { runPaseoCli, startTestDaemon } from "./helpers/test-daemon.ts";
|
||||
import { tmpdir } from "os";
|
||||
import { join } from "path";
|
||||
|
||||
@@ -50,6 +53,7 @@ try {
|
||||
const result = await $`npx paseo worktree ls --help`.nothrow();
|
||||
assert.strictEqual(result.exitCode, 0, "worktree ls --help should exit 0");
|
||||
assert(result.stdout.includes("--host"), "help should mention --host option");
|
||||
assert(result.stdout.includes("--cwd"), "help should mention --cwd option");
|
||||
console.log("✓ worktree ls --help shows options\n");
|
||||
}
|
||||
|
||||
@@ -86,6 +90,7 @@ try {
|
||||
const result = await $`npx paseo worktree archive --help`.nothrow();
|
||||
assert.strictEqual(result.exitCode, 0, "worktree archive --help should exit 0");
|
||||
assert(result.stdout.includes("--host"), "help should mention --host option");
|
||||
assert(result.stdout.includes("--cwd"), "help should mention --cwd option");
|
||||
assert(result.stdout.includes("<name>"), "help should mention required name argument");
|
||||
console.log("✓ worktree archive --help shows options\n");
|
||||
}
|
||||
@@ -167,4 +172,66 @@ try {
|
||||
await rm(paseoHome, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
// Exercise repository selection through the real CLI and daemon. An empty
|
||||
// request used to fail before Git could inspect even a valid repository.
|
||||
const daemon = await startTestDaemon();
|
||||
const git = promisify(execFile);
|
||||
try {
|
||||
const repo = join(daemon.workDir, "repository");
|
||||
await mkdir(repo);
|
||||
await git("git", ["init", repo]);
|
||||
await git("git", [
|
||||
"-C",
|
||||
repo,
|
||||
"-c",
|
||||
"user.name=Test",
|
||||
"-c",
|
||||
"user.email=test@example.com",
|
||||
"commit",
|
||||
"--allow-empty",
|
||||
"-m",
|
||||
"Initial commit",
|
||||
]);
|
||||
|
||||
for (const args of [
|
||||
["worktree", "ls", "--json"],
|
||||
["worktree", "ls", "--cwd", repo, "--json"],
|
||||
["worktree", "ls", "--cwd", "./repository", "--json"],
|
||||
["worktree", "ls", "--host", `127.0.0.1:${daemon.port}`, "--cwd", ".", "--json"],
|
||||
]) {
|
||||
const explicit = args.includes("--cwd");
|
||||
const result = await runPaseoCli(daemon, args, {
|
||||
cwd: explicit ? daemon.workDir : repo,
|
||||
env: { FORCE_COLOR: undefined },
|
||||
});
|
||||
assert.strictEqual(result.exitCode, 0, result.stderr);
|
||||
assert.deepStrictEqual(JSON.parse(result.stdout), []);
|
||||
}
|
||||
|
||||
for (const args of [
|
||||
["worktree", "archive", "missing", "--json"],
|
||||
["worktree", "archive", "missing", "--cwd", repo, "--json"],
|
||||
["worktree", "archive", "missing", "--cwd", "./repository", "--json"],
|
||||
[
|
||||
"worktree",
|
||||
"archive",
|
||||
"missing",
|
||||
"--host",
|
||||
`127.0.0.1:${daemon.port}`,
|
||||
"--cwd",
|
||||
".",
|
||||
"--json",
|
||||
],
|
||||
]) {
|
||||
const result = await runPaseoCli(daemon, args, {
|
||||
cwd: args.includes("--cwd") ? daemon.workDir : repo,
|
||||
env: { FORCE_COLOR: undefined },
|
||||
});
|
||||
assert.notStrictEqual(result.exitCode, 0);
|
||||
assert.strictEqual(JSON.parse(result.stderr).error.code, "WORKTREE_NOT_FOUND", result.stderr);
|
||||
}
|
||||
} finally {
|
||||
await daemon.stop();
|
||||
}
|
||||
|
||||
console.log("=== All worktree tests passed ===");
|
||||
|
||||
Reference in New Issue
Block a user