Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions src/commands/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import chalk from 'chalk';
import { getDefaultEditor, setDefaultEditor, getGitProvider, setGitProvider, getConfigPath, getDefaultWorktreePath, setDefaultWorktreePath, clearDefaultWorktreePath } from '../config.js';
import { getDefaultEditor, setDefaultEditor, getGitProvider, setGitProvider, getConfigPath, getDefaultWorktreePath, setDefaultWorktreePath, clearDefaultWorktreePath, getTrust, setTrust, getWorktreeSubfolder, setWorktreeSubfolder } from '../config.js';

export async function configHandler(action: 'get' | 'set' | 'path' | 'clear', key?: string, value?: string) {
try {
Expand All @@ -18,9 +18,23 @@ export async function configHandler(action: 'get' | 'set' | 'path' | 'clear', ke
} else {
console.log(chalk.blue(`Default worktree path is not set (using sibling directory behavior).`));
}
} else if (key === 'trust') {
const trust = getTrust();
console.log(chalk.blue(`Trust mode is currently: ${chalk.bold(trust ? 'enabled' : 'disabled')}`));
if (trust) {
console.log(chalk.gray(` Setup commands will run without confirmation prompts.`));
}
} else if (key === 'subfolder') {
const subfolder = getWorktreeSubfolder();
console.log(chalk.blue(`Subfolder mode is currently: ${chalk.bold(subfolder ? 'enabled' : 'disabled')}`));
if (subfolder) {
console.log(chalk.gray(` Worktrees will be created in: my-app-worktrees/feature`));
} else {
console.log(chalk.gray(` Worktrees will be created as: my-app-feature (siblings)`));
}
} else {
console.error(chalk.red(`Unknown configuration key to get: ${key}`));
console.error(chalk.yellow(`Available keys: editor, provider, worktreepath`));
console.error(chalk.yellow(`Available keys: editor, provider, worktreepath, trust, subfolder`));
process.exit(1);
}
break;
Expand All @@ -40,6 +54,22 @@ export async function configHandler(action: 'get' | 'set' | 'path' | 'clear', ke
setDefaultWorktreePath(value);
const resolvedPath = getDefaultWorktreePath();
console.log(chalk.green(`Default worktree path set to: ${chalk.bold(resolvedPath)}`));
} else if (key === 'trust' && value !== undefined) {
const trustValue = value.toLowerCase() === 'true' || value === '1';
setTrust(trustValue);
console.log(chalk.green(`Trust mode ${trustValue ? 'enabled' : 'disabled'}.`));
if (trustValue) {
console.log(chalk.gray(` Setup commands will now run without confirmation prompts.`));
}
} else if (key === 'subfolder' && value !== undefined) {
const subfolderValue = value.toLowerCase() === 'true' || value === '1';
setWorktreeSubfolder(subfolderValue);
console.log(chalk.green(`Subfolder mode ${subfolderValue ? 'enabled' : 'disabled'}.`));
if (subfolderValue) {
console.log(chalk.gray(` Worktrees will now be created in: my-app-worktrees/feature`));
} else {
console.log(chalk.gray(` Worktrees will now be created as: my-app-feature (siblings)`));
}
} else if (key === 'editor') {
console.error(chalk.red(`You must provide an editor name.`));
process.exit(1);
Expand All @@ -49,9 +79,15 @@ export async function configHandler(action: 'get' | 'set' | 'path' | 'clear', ke
} else if (key === 'worktreepath') {
console.error(chalk.red(`You must provide a path.`));
process.exit(1);
} else if (key === 'trust') {
console.error(chalk.red(`You must provide a value (true or false).`));
process.exit(1);
} else if (key === 'subfolder') {
console.error(chalk.red(`You must provide a value (true or false).`));
process.exit(1);
} else {
console.error(chalk.red(`Unknown configuration key to set: ${key}`));
console.error(chalk.yellow(`Available keys: editor, provider, worktreepath`));
console.error(chalk.yellow(`Available keys: editor, provider, worktreepath, trust, subfolder`));
process.exit(1);
}
break;
Expand Down
33 changes: 33 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ interface ConfigSchema {
defaultEditor: string;
gitProvider: 'gh' | 'glab';
defaultWorktreePath?: string;
trust?: boolean;
worktreeSubfolder?: boolean;
}

// Initialize conf with a schema and project name
Expand All @@ -32,6 +34,15 @@ const schema = {
type: 'string',
// No default - falls back to sibling directory behavior when not set
},
trust: {
type: 'boolean',
default: false, // Default is to require confirmation for setup commands
},
worktreeSubfolder: {
type: 'boolean',
default: false, // Default is sibling directory behavior (my-app-feature)
// When true: my-app-worktrees/feature subfolder pattern
},
} as const;

const config = new Conf<ConfigSchema>({
Expand Down Expand Up @@ -94,4 +105,26 @@ export function setDefaultWorktreePath(worktreePath: string): void {
// Function to clear the default worktree path
export function clearDefaultWorktreePath(): void {
config.delete('defaultWorktreePath');
}

// Function to get the trust setting (bypass setup command confirmation)
export function getTrust(): boolean {
return config.get('trust') ?? false;
}

// Function to set the trust setting
export function setTrust(trust: boolean): void {
config.set('trust', trust);
}

// Function to get the worktree subfolder setting
// When true: creates worktrees in my-app-worktrees/feature pattern
// When false: creates worktrees as my-app-feature siblings
export function getWorktreeSubfolder(): boolean {
return config.get('worktreeSubfolder') ?? false;
}

// Function to set the worktree subfolder setting
export function setWorktreeSubfolder(subfolder: boolean): void {
config.set('worktreeSubfolder', subfolder);
}
28 changes: 28 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,24 @@ program
.description("Set the default directory for new worktrees.")
.action((worktreePath) => configHandler("set", "worktreepath", worktreePath))
)
.addCommand(
new Command("trust")
.argument(
"<value>",
"Enable or disable trust mode (true/false)"
)
.description("Set trust mode to skip setup command confirmations.")
.action((value) => configHandler("set", "trust", value))
Comment on lines +205 to +209

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rebuild missing for new config subcommands

The new config set trust/subfolder commands added here only exist in the TypeScript source, but the compiled entrypoint (build/index.js, which is what the wt bin points to and what test/config.test.ts executes via CLI_PATH) was not regenerated in this commit. The current build still registers only editor/provider/worktreepath, so on a fresh checkout or published package without manually re-running pnpm build, wt config set trust/subfolder will fail with an unknown-command error and the newly added tests will break. Please regenerate and commit the build outputs or run the tests against the source.

Useful? React with 👍 / 👎.

)
.addCommand(
new Command("subfolder")
.argument(
"<value>",
"Enable or disable subfolder mode (true/false)"
)
.description("Set subfolder mode for worktree paths (my-app-worktrees/feature).")
.action((value) => configHandler("set", "subfolder", value))
)
)
.addCommand(
new Command("get")
Expand All @@ -218,6 +236,16 @@ program
.description("Get the currently configured default worktree directory.")
.action(() => configHandler("get", "worktreepath"))
)
.addCommand(
new Command("trust")
.description("Get the current trust mode setting.")
.action(() => configHandler("get", "trust"))
)
.addCommand(
new Command("subfolder")
.description("Get the current subfolder mode setting.")
.action(() => configHandler("get", "subfolder"))
)
)
.addCommand(
new Command("clear")
Expand Down
18 changes: 14 additions & 4 deletions src/utils/paths.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { join, dirname, basename, resolve } from "node:path";
import { getDefaultWorktreePath } from "../config.js";
import { getDefaultWorktreePath, getWorktreeSubfolder } from "../config.js";
import { getRepoName } from "./git.js";

/**
Expand Down Expand Up @@ -48,10 +48,11 @@ export interface ResolveWorktreePathOptions {
/**
* Resolve the full path for a new worktree
*
* Handles three cases:
* Handles four cases:
* 1. Custom path provided - use it directly
* 2. Global defaultWorktreePath configured - use it with repo namespace
* 3. No config - create sibling directory
* 3. Subfolder mode enabled - create in my-app-worktrees/feature pattern
* 4. No config - create sibling directory (my-app-feature)
*
* @param branchName - The branch name to create worktree for
* @param options - Configuration options
Expand Down Expand Up @@ -88,9 +89,18 @@ export async function resolveWorktreePath(
return join(defaultWorktreePath, worktreeName);
}

// Case 3: No config - create sibling directory
// Check if subfolder mode is enabled
const useSubfolder = getWorktreeSubfolder();
const parentDir = dirname(cwd);
const currentDirName = basename(cwd);

if (useSubfolder) {
// Case 3: Subfolder mode - create in my-app-worktrees/feature pattern
// This keeps worktrees organized in a dedicated folder
return join(parentDir, `${currentDirName}-worktrees`, worktreeName);
}

// Case 4: No config - create sibling directory (my-app-feature)
return join(parentDir, `${currentDirName}-${worktreeName}`);
}

Expand Down
5 changes: 4 additions & 1 deletion src/utils/tui.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import prompts from "prompts";
import chalk from "chalk";
import { getWorktrees, WorktreeInfo } from "./git.js";
import { getTrust } from "../config.js";

/**
* Interactive worktree selector
Expand Down Expand Up @@ -165,7 +166,9 @@ export async function confirmCommands(commands: string[], options: {
} = {}): Promise<boolean> {
const { title = "The following commands will be executed:", trust = false } = options;

if (trust) {
// Check both the flag and the config setting
// If either is true, skip confirmation
if (trust || getTrust()) {
return true;
}

Expand Down
92 changes: 92 additions & 0 deletions test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,96 @@ describe('Config Management', () => {
expect(invalidResult.stderr).toContain('Valid providers: gh, glab');
});
});

describe('Trust config (Issue #34)', () => {
it('should get trust mode default (disabled)', async () => {
const result = await runConfig(['get', 'trust']);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Trust mode is currently');
});

it('should set trust mode to true', async () => {
const result = await runConfig(['set', 'trust', 'true']);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Trust mode enabled');

const config = await getConfigFileContent();
expect(config).toBeDefined();
expect(config.trust).toBe(true);
});

it('should set trust mode to false', async () => {
// First enable it
await runConfig(['set', 'trust', 'true']);

// Then disable it
const result = await runConfig(['set', 'trust', 'false']);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Trust mode disabled');

const config = await getConfigFileContent();
expect(config).toBeDefined();
expect(config.trust).toBe(false);
});

it('should accept 1 as truthy value', async () => {
const result = await runConfig(['set', 'trust', '1']);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Trust mode enabled');

const config = await getConfigFileContent();
expect(config.trust).toBe(true);
});
});

describe('Subfolder config (Issue #33)', () => {
it('should get subfolder mode default (disabled)', async () => {
const result = await runConfig(['get', 'subfolder']);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Subfolder mode is currently');
});

it('should set subfolder mode to true', async () => {
const result = await runConfig(['set', 'subfolder', 'true']);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Subfolder mode enabled');
expect(result.stdout).toContain('my-app-worktrees/feature');

const config = await getConfigFileContent();
expect(config).toBeDefined();
expect(config.worktreeSubfolder).toBe(true);
});

it('should set subfolder mode to false', async () => {
// First enable it
await runConfig(['set', 'subfolder', 'true']);

// Then disable it
const result = await runConfig(['set', 'subfolder', 'false']);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Subfolder mode disabled');
expect(result.stdout).toContain('siblings');

const config = await getConfigFileContent();
expect(config).toBeDefined();
expect(config.worktreeSubfolder).toBe(false);
});

it('should accept 1 as truthy value', async () => {
const result = await runConfig(['set', 'subfolder', '1']);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Subfolder mode enabled');

const config = await getConfigFileContent();
expect(config.worktreeSubfolder).toBe(true);
});
});
});