workon 3.5.2 → 3.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 +125 -0
- package/dist/cli.js +291 -146
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -283,6 +283,13 @@ var init_project = __esm({
|
|
|
283
283
|
}
|
|
284
284
|
return this._path;
|
|
285
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* Override the project path with an absolute path.
|
|
288
|
+
* Used when targeting a worktree instead of the main project directory.
|
|
289
|
+
*/
|
|
290
|
+
overridePath(absolutePath) {
|
|
291
|
+
this._path = File.from(absolutePath).absolutify();
|
|
292
|
+
}
|
|
286
293
|
set branch(branch) {
|
|
287
294
|
this._branch = branch;
|
|
288
295
|
}
|
|
@@ -1421,24 +1428,6 @@ var init_tmux = __esm({
|
|
|
1421
1428
|
}
|
|
1422
1429
|
});
|
|
1423
1430
|
|
|
1424
|
-
// src/types/constants.ts
|
|
1425
|
-
var IDE_CHOICES;
|
|
1426
|
-
var init_constants = __esm({
|
|
1427
|
-
"src/types/constants.ts"() {
|
|
1428
|
-
"use strict";
|
|
1429
|
-
IDE_CHOICES = [
|
|
1430
|
-
{ name: "Cursor", value: "cursor" },
|
|
1431
|
-
{ name: "Visual Studio Code", value: "vscode" },
|
|
1432
|
-
{ name: "Visual Studio Code (code)", value: "code" },
|
|
1433
|
-
{ name: "IntelliJ IDEA", value: "idea" },
|
|
1434
|
-
{ name: "Atom", value: "atom" },
|
|
1435
|
-
{ name: "Sublime Text", value: "subl" },
|
|
1436
|
-
{ name: "Vim", value: "vim" },
|
|
1437
|
-
{ name: "Emacs", value: "emacs" }
|
|
1438
|
-
];
|
|
1439
|
-
}
|
|
1440
|
-
});
|
|
1441
|
-
|
|
1442
1431
|
// src/lib/worktree.ts
|
|
1443
1432
|
import { exec as execCallback2 } from "child_process";
|
|
1444
1433
|
import { promisify as promisify2 } from "util";
|
|
@@ -1458,7 +1447,7 @@ function deriveProjectIdentifier(projectPath) {
|
|
|
1458
1447
|
function getWorktreesDirForProject(projectIdentifier) {
|
|
1459
1448
|
return join(homedir(), WORKON_DIR, WORKTREES_SUBDIR, projectIdentifier);
|
|
1460
1449
|
}
|
|
1461
|
-
var exec2, WORKON_DIR, WORKTREES_SUBDIR, HOOK_DIR, SETUP_HOOK, WorktreeManager;
|
|
1450
|
+
var exec2, WORKON_DIR, WORKTREES_SUBDIR, HOOK_DIR, SETUP_HOOK, TEARDOWN_HOOK, WorktreeManager;
|
|
1462
1451
|
var init_worktree = __esm({
|
|
1463
1452
|
"src/lib/worktree.ts"() {
|
|
1464
1453
|
"use strict";
|
|
@@ -1467,6 +1456,7 @@ var init_worktree = __esm({
|
|
|
1467
1456
|
WORKTREES_SUBDIR = "worktrees";
|
|
1468
1457
|
HOOK_DIR = ".workon";
|
|
1469
1458
|
SETUP_HOOK = "worktree-setup.sh";
|
|
1459
|
+
TEARDOWN_HOOK = "worktree-teardown.sh";
|
|
1470
1460
|
WorktreeManager = class {
|
|
1471
1461
|
projectPath;
|
|
1472
1462
|
projectIdentifier;
|
|
@@ -1684,6 +1674,42 @@ var init_worktree = __esm({
|
|
|
1684
1674
|
});
|
|
1685
1675
|
return { stdout, stderr };
|
|
1686
1676
|
}
|
|
1677
|
+
/**
|
|
1678
|
+
* Check if a pre-teardown hook exists
|
|
1679
|
+
*/
|
|
1680
|
+
hasTeardownHook() {
|
|
1681
|
+
const hookPath = this.getTeardownHookPath();
|
|
1682
|
+
return existsSync2(hookPath);
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* Get the path to the teardown hook
|
|
1686
|
+
*/
|
|
1687
|
+
getTeardownHookPath() {
|
|
1688
|
+
return join(this.projectPath, HOOK_DIR, TEARDOWN_HOOK);
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Run the pre-teardown hook for a worktree
|
|
1692
|
+
*/
|
|
1693
|
+
async runPreTeardownHook(worktreePath) {
|
|
1694
|
+
const hookPath = this.getTeardownHookPath();
|
|
1695
|
+
if (!existsSync2(hookPath)) {
|
|
1696
|
+
throw new Error("Teardown hook not found");
|
|
1697
|
+
}
|
|
1698
|
+
try {
|
|
1699
|
+
chmodSync(hookPath, "755");
|
|
1700
|
+
} catch {
|
|
1701
|
+
}
|
|
1702
|
+
const env = {
|
|
1703
|
+
...process.env,
|
|
1704
|
+
WORKTREE_PATH: worktreePath,
|
|
1705
|
+
PROJECT_PATH: this.projectPath
|
|
1706
|
+
};
|
|
1707
|
+
const { stdout, stderr } = await exec2(hookPath, {
|
|
1708
|
+
cwd: worktreePath,
|
|
1709
|
+
env
|
|
1710
|
+
});
|
|
1711
|
+
return { stdout, stderr };
|
|
1712
|
+
}
|
|
1687
1713
|
/**
|
|
1688
1714
|
* Parse the porcelain output of git worktree list
|
|
1689
1715
|
*/
|
|
@@ -1734,6 +1760,24 @@ var init_worktree = __esm({
|
|
|
1734
1760
|
}
|
|
1735
1761
|
});
|
|
1736
1762
|
|
|
1763
|
+
// src/types/constants.ts
|
|
1764
|
+
var IDE_CHOICES;
|
|
1765
|
+
var init_constants = __esm({
|
|
1766
|
+
"src/types/constants.ts"() {
|
|
1767
|
+
"use strict";
|
|
1768
|
+
IDE_CHOICES = [
|
|
1769
|
+
{ name: "Cursor", value: "cursor" },
|
|
1770
|
+
{ name: "Visual Studio Code", value: "vscode" },
|
|
1771
|
+
{ name: "Visual Studio Code (code)", value: "code" },
|
|
1772
|
+
{ name: "IntelliJ IDEA", value: "idea" },
|
|
1773
|
+
{ name: "Atom", value: "atom" },
|
|
1774
|
+
{ name: "Sublime Text", value: "subl" },
|
|
1775
|
+
{ name: "Vim", value: "vim" },
|
|
1776
|
+
{ name: "Emacs", value: "emacs" }
|
|
1777
|
+
];
|
|
1778
|
+
}
|
|
1779
|
+
});
|
|
1780
|
+
|
|
1737
1781
|
// src/commands/worktrees/utils.ts
|
|
1738
1782
|
import File3 from "phylo";
|
|
1739
1783
|
import path from "path";
|
|
@@ -1998,7 +2042,7 @@ import ora from "ora";
|
|
|
1998
2042
|
import { select as select2, confirm as confirm4 } from "@inquirer/prompts";
|
|
1999
2043
|
function createAddCommand(ctx) {
|
|
2000
2044
|
const { config, log } = ctx;
|
|
2001
|
-
return new Command2("add").description("Create a new worktree for a branch").argument("<branch>", "Branch name for the worktree").option("-b, --base <branch>", "Base branch to create new branch from").option("-f, --force", "Overwrite existing worktree").option("-o, --open", "Open the worktree after creation").option("--no-hook", "Skip running the post-setup hook").action(async (branch, options) => {
|
|
2045
|
+
return new Command2("add").description("Create a new worktree for a branch").argument("<branch>", "Branch name for the worktree").option("-b, --base <branch>", "Base branch to create new branch from").option("-f, --force", "Overwrite existing worktree").option("-o, --open", "Open the worktree after creation").option("--no-hook", "Skip running the post-setup hook").option("-y, --yes", "Skip all confirmation prompts (non-interactive mode)").action(async (branch, options) => {
|
|
2002
2046
|
const projectCtx = await resolveProjectFromCwd(config, log);
|
|
2003
2047
|
if (!projectCtx) {
|
|
2004
2048
|
log.error("Not in a git repository. Run this command from within a git project.");
|
|
@@ -2008,11 +2052,15 @@ function createAddCommand(ctx) {
|
|
|
2008
2052
|
process.exit(1);
|
|
2009
2053
|
}
|
|
2010
2054
|
if (!projectCtx.isRegistered) {
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
projectCtx.
|
|
2015
|
-
|
|
2055
|
+
if (options.yes) {
|
|
2056
|
+
log.info("Project is not registered. Proceeding without registration.");
|
|
2057
|
+
} else {
|
|
2058
|
+
const result = await promptToRegisterProject(projectCtx.projectPath, config, log);
|
|
2059
|
+
if (result) {
|
|
2060
|
+
projectCtx.projectName = result.projectName;
|
|
2061
|
+
projectCtx.projectConfig = result.projectConfig;
|
|
2062
|
+
projectCtx.isRegistered = true;
|
|
2063
|
+
}
|
|
2016
2064
|
}
|
|
2017
2065
|
}
|
|
2018
2066
|
const { projectPath, projectName } = projectCtx;
|
|
@@ -2020,16 +2068,21 @@ function createAddCommand(ctx) {
|
|
|
2020
2068
|
const branchExists = await manager.branchExists(branch);
|
|
2021
2069
|
let baseBranch = options.base;
|
|
2022
2070
|
if (!branchExists && !baseBranch) {
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2071
|
+
if (options.yes) {
|
|
2072
|
+
baseBranch = await manager.getCurrentBranch();
|
|
2073
|
+
log.info(`Branch '${branch}' doesn't exist. Creating from '${baseBranch}'.`);
|
|
2074
|
+
} else {
|
|
2075
|
+
const branches = await manager.getBranches();
|
|
2076
|
+
const currentBranch = await manager.getCurrentBranch();
|
|
2077
|
+
baseBranch = await select2({
|
|
2078
|
+
message: `Branch '${branch}' doesn't exist. Create from which branch?`,
|
|
2079
|
+
choices: branches.map((b) => ({
|
|
2080
|
+
name: b === currentBranch ? `${b} (current)` : b,
|
|
2081
|
+
value: b
|
|
2082
|
+
})),
|
|
2083
|
+
default: currentBranch
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2033
2086
|
}
|
|
2034
2087
|
const spinner = ora(`Creating worktree for branch '${branch}'...`).start();
|
|
2035
2088
|
try {
|
|
@@ -2062,6 +2115,9 @@ ${chalk2.bold("Worktree details:")}`);
|
|
|
2062
2115
|
if (projectCtx.isRegistered && projectName) {
|
|
2063
2116
|
if (options.open) {
|
|
2064
2117
|
await openWorktreeSession(projectCtx, worktree.name, config, log);
|
|
2118
|
+
} else if (options.yes) {
|
|
2119
|
+
console.log(`
|
|
2120
|
+
To open later: ${chalk2.cyan(`workon worktrees open ${worktree.name}`)}`);
|
|
2065
2121
|
} else {
|
|
2066
2122
|
const shouldOpen = await confirm4({
|
|
2067
2123
|
message: "Open workon session in this worktree?",
|
|
@@ -2115,7 +2171,7 @@ import { existsSync as existsSync4 } from "fs";
|
|
|
2115
2171
|
import { confirm as confirm5 } from "@inquirer/prompts";
|
|
2116
2172
|
function createRemoveCommand(ctx) {
|
|
2117
2173
|
const { config, log } = ctx;
|
|
2118
|
-
return new Command3("remove").description("Remove a worktree").argument("<name>", "Worktree name").option("-f, --force", "Force removal even with uncommitted changes").option("-y, --yes", "Skip confirmation prompt").action(async (name, options) => {
|
|
2174
|
+
return new Command3("remove").description("Remove a worktree").argument("<name>", "Worktree name").option("-f, --force", "Force removal even with uncommitted changes").option("-y, --yes", "Skip confirmation prompt").option("--no-hook", "Skip running the pre-teardown hook").action(async (name, options) => {
|
|
2119
2175
|
const projectCtx = await resolveProjectFromCwd(config, log);
|
|
2120
2176
|
if (!projectCtx) {
|
|
2121
2177
|
log.error("Not in a git repository. Run this command from within a git project.");
|
|
@@ -2188,6 +2244,21 @@ ${chalk3.bold("Worktree to remove:")}`);
|
|
|
2188
2244
|
log.debug(`Killing tmux session: ${sessionName}`);
|
|
2189
2245
|
await tmux.killSession(sessionName);
|
|
2190
2246
|
}
|
|
2247
|
+
if (options.hook !== false && !pathMissing && manager.hasTeardownHook()) {
|
|
2248
|
+
const hookSpinner = ora2("Running pre-teardown hook...").start();
|
|
2249
|
+
try {
|
|
2250
|
+
const { stdout, stderr } = await manager.runPreTeardownHook(worktree.path);
|
|
2251
|
+
hookSpinner.succeed("Pre-teardown hook completed");
|
|
2252
|
+
if (stdout.trim()) {
|
|
2253
|
+
console.log(chalk3.gray(stdout.trim()));
|
|
2254
|
+
}
|
|
2255
|
+
if (stderr.trim()) {
|
|
2256
|
+
console.log(chalk3.yellow(stderr.trim()));
|
|
2257
|
+
}
|
|
2258
|
+
} catch (error) {
|
|
2259
|
+
hookSpinner.warn(`Pre-teardown hook failed: ${error.message}`);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2191
2262
|
const spinner = ora2(`Removing worktree '${name}'...`).start();
|
|
2192
2263
|
try {
|
|
2193
2264
|
await manager.remove(name, options.force);
|
|
@@ -2216,7 +2287,7 @@ import path4 from "path";
|
|
|
2216
2287
|
import { select as select3, confirm as confirm6 } from "@inquirer/prompts";
|
|
2217
2288
|
function createMergeCommand(ctx) {
|
|
2218
2289
|
const { config, log } = ctx;
|
|
2219
|
-
return new Command4("merge").description("Merge a worktree branch and optionally remove the worktree").argument("<name>", "Worktree name").option("-i, --into <branch>", "Target branch to merge into").option("-s, --squash", "Use squash merge").option("-k, --keep", "Keep the worktree after merging").option("-y, --yes", "Skip confirmation prompts").action(async (name, options) => {
|
|
2290
|
+
return new Command4("merge").description("Merge a worktree branch and optionally remove the worktree").argument("<name>", "Worktree name").option("-i, --into <branch>", "Target branch to merge into").option("-s, --squash", "Use squash merge").option("-k, --keep", "Keep the worktree after merging").option("-y, --yes", "Skip confirmation prompts").option("--delete-branch", "Delete the merged branch after merge").action(async (name, options) => {
|
|
2220
2291
|
const projectCtx = await resolveProjectFromCwd(config, log);
|
|
2221
2292
|
if (!projectCtx) {
|
|
2222
2293
|
log.error("Not in a git repository. Run this command from within a git project.");
|
|
@@ -2258,14 +2329,19 @@ function createMergeCommand(ctx) {
|
|
|
2258
2329
|
}
|
|
2259
2330
|
const commonTargets = ["main", "master", "develop", "dev"];
|
|
2260
2331
|
const defaultTarget = commonTargets.find((t) => targetBranches.includes(t)) || targetBranches[0];
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2332
|
+
if (options.yes) {
|
|
2333
|
+
targetBranch = defaultTarget;
|
|
2334
|
+
log.info(`Auto-selected target branch: '${targetBranch}'`);
|
|
2335
|
+
} else {
|
|
2336
|
+
targetBranch = await select3({
|
|
2337
|
+
message: `Merge '${worktree.branch}' into which branch?`,
|
|
2338
|
+
choices: targetBranches.map((b) => ({
|
|
2339
|
+
name: b,
|
|
2340
|
+
value: b
|
|
2341
|
+
})),
|
|
2342
|
+
default: defaultTarget
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2269
2345
|
}
|
|
2270
2346
|
if (!await manager.branchExists(targetBranch)) {
|
|
2271
2347
|
log.error(`Target branch '${targetBranch}' does not exist.`);
|
|
@@ -2307,6 +2383,11 @@ ${chalk4.bold("Merge operation:")}`);
|
|
|
2307
2383
|
log.info("You may need to resolve conflicts manually.");
|
|
2308
2384
|
process.exit(1);
|
|
2309
2385
|
}
|
|
2386
|
+
if (options.keep && options.deleteBranch) {
|
|
2387
|
+
log.warn(
|
|
2388
|
+
"--delete-branch is ignored when --keep is set (branch is needed by the worktree)."
|
|
2389
|
+
);
|
|
2390
|
+
}
|
|
2310
2391
|
if (!options.keep) {
|
|
2311
2392
|
const tmux = new TmuxManager();
|
|
2312
2393
|
const sessionName = tmux.getWorktreeSessionName(displayName, name);
|
|
@@ -2322,21 +2403,22 @@ ${chalk4.bold("Merge operation:")}`);
|
|
|
2322
2403
|
removeSpinner.warn(`Failed to remove worktree: ${error.message}`);
|
|
2323
2404
|
log.info(`You can remove it manually with: workon worktrees remove ${name}`);
|
|
2324
2405
|
}
|
|
2325
|
-
|
|
2326
|
-
|
|
2406
|
+
let shouldDeleteBranch = options.deleteBranch || false;
|
|
2407
|
+
if (!shouldDeleteBranch && !options.yes) {
|
|
2408
|
+
shouldDeleteBranch = await confirm6({
|
|
2327
2409
|
message: `Delete the merged branch '${worktree.branch}'?`,
|
|
2328
2410
|
default: false
|
|
2329
2411
|
});
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
}
|
|
2338
|
-
|
|
2339
|
-
}
|
|
2412
|
+
}
|
|
2413
|
+
if (shouldDeleteBranch) {
|
|
2414
|
+
const deleteSpinner = ora3(`Deleting branch '${worktree.branch}'...`).start();
|
|
2415
|
+
try {
|
|
2416
|
+
const { simpleGit: simpleGit6 } = await import("simple-git");
|
|
2417
|
+
const git = simpleGit6(projectPath);
|
|
2418
|
+
await git.deleteLocalBranch(worktree.branch, true);
|
|
2419
|
+
deleteSpinner.succeed(`Branch '${worktree.branch}' deleted`);
|
|
2420
|
+
} catch (error) {
|
|
2421
|
+
deleteSpinner.warn(`Failed to delete branch: ${error.message}`);
|
|
2340
2422
|
}
|
|
2341
2423
|
}
|
|
2342
2424
|
}
|
|
@@ -2362,7 +2444,7 @@ import { confirm as confirm7 } from "@inquirer/prompts";
|
|
|
2362
2444
|
import { simpleGit as simpleGit4 } from "simple-git";
|
|
2363
2445
|
function createBranchCommand(ctx) {
|
|
2364
2446
|
const { config, log } = ctx;
|
|
2365
|
-
return new Command5("branch").description("Create a branch from a worktree (useful for detached HEAD state)").argument("<worktree>", "Worktree name").argument("<branch>", "New branch name to create").option("-p, --push", "Push the branch to origin after creating").option("-f, --force", "Overwrite existing branch").action(async (worktreeName, branchName, options) => {
|
|
2447
|
+
return new Command5("branch").description("Create a branch from a worktree (useful for detached HEAD state)").argument("<worktree>", "Worktree name").argument("<branch>", "New branch name to create").option("-p, --push", "Push the branch to origin after creating").option("-f, --force", "Overwrite existing branch").option("-y, --yes", "Skip all confirmation prompts (non-interactive mode)").action(async (worktreeName, branchName, options) => {
|
|
2366
2448
|
const projectCtx = await resolveProjectFromCwd(config, log);
|
|
2367
2449
|
if (!projectCtx) {
|
|
2368
2450
|
log.error("Not in a git repository. Run this command from within a git project.");
|
|
@@ -2425,6 +2507,8 @@ ${chalk5.bold("Action:")}`);
|
|
|
2425
2507
|
pushSpinner.fail(`Failed to push: ${error.message}`);
|
|
2426
2508
|
log.info(`You can push manually with: git push -u origin ${branchName}`);
|
|
2427
2509
|
}
|
|
2510
|
+
} else if (options.yes) {
|
|
2511
|
+
log.info(`You can push manually with: git push -u origin ${branchName}`);
|
|
2428
2512
|
} else {
|
|
2429
2513
|
const shouldPush = await confirm7({
|
|
2430
2514
|
message: "Push branch to origin for PR?",
|
|
@@ -2527,13 +2611,12 @@ __export(open_exports, {
|
|
|
2527
2611
|
});
|
|
2528
2612
|
import { Command as Command7 } from "commander";
|
|
2529
2613
|
import chalk6 from "chalk";
|
|
2530
|
-
import File4 from "phylo";
|
|
2531
2614
|
import path6 from "path";
|
|
2532
2615
|
import { exec as execCallback3 } from "child_process";
|
|
2533
2616
|
import { promisify as promisify3 } from "util";
|
|
2534
2617
|
function createOpenCommand(ctx) {
|
|
2535
2618
|
const { config, log } = ctx;
|
|
2536
|
-
return new Command7("open").description("Open a workon session in a worktree").argument("<name>", "Worktree name").option("-d, --debug", "Enable debug logging").option("--shell", "Output shell commands instead of spawning processes").action(async (name, options) => {
|
|
2619
|
+
return new Command7("open").description("Open a workon session in a worktree").argument("<name>", "Worktree name").option("-d, --debug", "Enable debug logging").option("--shell", "Output shell commands instead of spawning processes").option("-y, --yes", "Skip all confirmation prompts (non-interactive mode)").action(async (name, options) => {
|
|
2537
2620
|
if (options.debug) {
|
|
2538
2621
|
log.setLogLevel("debug");
|
|
2539
2622
|
}
|
|
@@ -2546,12 +2629,16 @@ function createOpenCommand(ctx) {
|
|
|
2546
2629
|
process.exit(1);
|
|
2547
2630
|
}
|
|
2548
2631
|
if (!projectCtx.isRegistered) {
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
projectCtx.
|
|
2554
|
-
|
|
2632
|
+
if (options.yes) {
|
|
2633
|
+
log.info("Project is not registered. Opening with basic shell layout.");
|
|
2634
|
+
} else {
|
|
2635
|
+
log.warn("Project is not registered. Opening with basic shell layout.");
|
|
2636
|
+
const shouldRegister = await promptToRegisterProject(projectCtx.projectPath, config, log);
|
|
2637
|
+
if (shouldRegister) {
|
|
2638
|
+
projectCtx.projectName = shouldRegister.projectName;
|
|
2639
|
+
projectCtx.projectConfig = shouldRegister.projectConfig;
|
|
2640
|
+
projectCtx.isRegistered = true;
|
|
2641
|
+
}
|
|
2555
2642
|
}
|
|
2556
2643
|
}
|
|
2557
2644
|
await runWorktreeOpen(projectCtx, name, options, { config, log });
|
|
@@ -2584,7 +2671,7 @@ async function runWorktreeOpen(projectCtx, worktreeName, options, ctx) {
|
|
|
2584
2671
|
let hasNpmEvent = false;
|
|
2585
2672
|
if (isRegistered && projectName && projectConfig) {
|
|
2586
2673
|
project = new Project(projectName, projectConfig, defaults);
|
|
2587
|
-
project.
|
|
2674
|
+
project.overridePath(worktree.path);
|
|
2588
2675
|
const events = project.events || {};
|
|
2589
2676
|
hasClaudeEvent = !!events.claude;
|
|
2590
2677
|
hasNpmEvent = !!events.npm;
|
|
@@ -2802,7 +2889,8 @@ __export(interactive_exports, {
|
|
|
2802
2889
|
runInteractive: () => runInteractive
|
|
2803
2890
|
});
|
|
2804
2891
|
import { select as select4, input as input5, checkbox as checkbox2, confirm as confirm8 } from "@inquirer/prompts";
|
|
2805
|
-
import
|
|
2892
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2893
|
+
import File4 from "phylo";
|
|
2806
2894
|
import path7 from "path";
|
|
2807
2895
|
import deepAssign2 from "deep-assign";
|
|
2808
2896
|
import chalk7 from "chalk";
|
|
@@ -2810,7 +2898,7 @@ async function runInteractive(ctx) {
|
|
|
2810
2898
|
const { config, log, environment, suggestedName } = ctx;
|
|
2811
2899
|
showLogo(config);
|
|
2812
2900
|
log.log("");
|
|
2813
|
-
const defaultName = suggestedName ?? (environment.$isProjectEnvironment ? environment.project.name :
|
|
2901
|
+
const defaultName = suggestedName ?? (environment.$isProjectEnvironment ? environment.project.name : File4.cwd().name);
|
|
2814
2902
|
const fromUser = !!suggestedName;
|
|
2815
2903
|
await startInteractive(defaultName, fromUser, ctx);
|
|
2816
2904
|
}
|
|
@@ -2911,15 +2999,15 @@ async function initProject(defaultName, fromUser, ctx) {
|
|
|
2911
2999
|
let basePath;
|
|
2912
3000
|
if (isBranch) {
|
|
2913
3001
|
const projectName = name.substring(0, name.indexOf("#"));
|
|
2914
|
-
basePath = defaults?.base ?
|
|
3002
|
+
basePath = defaults?.base ? File4.from(defaults.base).join(projects[projectName].path).absolutePath() : projects[projectName].path;
|
|
2915
3003
|
log.log(`Project path: ${basePath}`);
|
|
2916
3004
|
} else {
|
|
2917
3005
|
const pathAnswer = await input5({
|
|
2918
3006
|
message: "What is the path to the project?",
|
|
2919
|
-
default: defaults?.base ?
|
|
3007
|
+
default: defaults?.base ? File4.from(defaults.base).join(name).path : name
|
|
2920
3008
|
});
|
|
2921
|
-
let answerFile =
|
|
2922
|
-
const defaultBase = defaults?.base ?
|
|
3009
|
+
let answerFile = File4.from(pathAnswer);
|
|
3010
|
+
const defaultBase = defaults?.base ? File4.from(defaults.base).absolutify() : File4.cwd();
|
|
2923
3011
|
if (!answerFile.isAbsolute()) {
|
|
2924
3012
|
answerFile = defaultBase.join(answerFile.path);
|
|
2925
3013
|
}
|
|
@@ -3127,15 +3215,15 @@ async function createProjectManage(ctx) {
|
|
|
3127
3215
|
return true;
|
|
3128
3216
|
}
|
|
3129
3217
|
});
|
|
3130
|
-
const defaultPath = defaults?.base ?
|
|
3218
|
+
const defaultPath = defaults?.base ? File4.from(defaults.base).absolutify().join(name).path : name;
|
|
3131
3219
|
const pathInput = await input5({
|
|
3132
3220
|
message: "Project path:",
|
|
3133
3221
|
default: defaultPath
|
|
3134
3222
|
});
|
|
3135
3223
|
let relativePath = pathInput;
|
|
3136
3224
|
if (defaults?.base) {
|
|
3137
|
-
const baseDir =
|
|
3138
|
-
const pathFile =
|
|
3225
|
+
const baseDir = File4.from(defaults.base).absolutify();
|
|
3226
|
+
const pathFile = File4.from(pathInput);
|
|
3139
3227
|
try {
|
|
3140
3228
|
if (pathFile.isAbsolute()) {
|
|
3141
3229
|
const relPath = pathFile.relativize(baseDir.path);
|
|
@@ -3202,8 +3290,8 @@ async function editProjectManage(ctx) {
|
|
|
3202
3290
|
});
|
|
3203
3291
|
let relativePath = pathInput;
|
|
3204
3292
|
if (defaults?.base) {
|
|
3205
|
-
const baseDir =
|
|
3206
|
-
const pathFile =
|
|
3293
|
+
const baseDir = File4.from(defaults.base).absolutify();
|
|
3294
|
+
const pathFile = File4.from(pathInput);
|
|
3207
3295
|
try {
|
|
3208
3296
|
if (pathFile.isAbsolute()) {
|
|
3209
3297
|
const relPath = pathFile.relativize(baseDir.path);
|
|
@@ -3306,7 +3394,7 @@ function listProjectsManage(ctx) {
|
|
|
3306
3394
|
const baseProjects = Object.keys(projects).filter((name) => !name.includes("#"));
|
|
3307
3395
|
for (const name of baseProjects) {
|
|
3308
3396
|
const project = projects[name];
|
|
3309
|
-
const fullPath = defaults?.base ?
|
|
3397
|
+
const fullPath = defaults?.base ? File4.from(defaults.base).join(project.path).path : project.path;
|
|
3310
3398
|
console.log(` ${name}`);
|
|
3311
3399
|
console.log(` Path: ${fullPath}`);
|
|
3312
3400
|
console.log(` IDE: ${project.ide || "not set"}`);
|
|
@@ -3430,11 +3518,11 @@ async function manageWorktrees(projectName, ctx) {
|
|
|
3430
3518
|
const projectConfig = projects[projectName];
|
|
3431
3519
|
const basePath = defaults?.base || "";
|
|
3432
3520
|
let projectPath;
|
|
3433
|
-
const configPath =
|
|
3521
|
+
const configPath = File4.from(projectConfig.path);
|
|
3434
3522
|
if (configPath.path.startsWith("/") || configPath.path.startsWith("~")) {
|
|
3435
3523
|
projectPath = configPath.absolutify().path;
|
|
3436
3524
|
} else if (basePath) {
|
|
3437
|
-
projectPath =
|
|
3525
|
+
projectPath = File4.from(basePath).absolutify().join(projectConfig.path).path;
|
|
3438
3526
|
} else {
|
|
3439
3527
|
projectPath = configPath.absolutify().path;
|
|
3440
3528
|
}
|
|
@@ -3565,11 +3653,11 @@ async function openWorktreeManage(projectName, manager, config, log) {
|
|
|
3565
3653
|
const projectConfig = projects[projectName];
|
|
3566
3654
|
const basePath = defaults?.base || "";
|
|
3567
3655
|
let projectPath;
|
|
3568
|
-
const configPath =
|
|
3656
|
+
const configPath = File4.from(projectConfig.path);
|
|
3569
3657
|
if (configPath.path.startsWith("/") || configPath.path.startsWith("~")) {
|
|
3570
3658
|
projectPath = configPath.absolutify().path;
|
|
3571
3659
|
} else if (basePath) {
|
|
3572
|
-
projectPath =
|
|
3660
|
+
projectPath = File4.from(basePath).absolutify().join(projectConfig.path).path;
|
|
3573
3661
|
} else {
|
|
3574
3662
|
projectPath = configPath.absolutify().path;
|
|
3575
3663
|
}
|
|
@@ -3621,6 +3709,18 @@ async function removeWorktreeManage(projectName, manager, log) {
|
|
|
3621
3709
|
});
|
|
3622
3710
|
if (confirmed) {
|
|
3623
3711
|
try {
|
|
3712
|
+
if (manager.hasTeardownHook()) {
|
|
3713
|
+
const worktree = await manager.get(worktreeName);
|
|
3714
|
+
if (worktree && existsSync5(worktree.path)) {
|
|
3715
|
+
log.info("Running pre-teardown hook...");
|
|
3716
|
+
try {
|
|
3717
|
+
await manager.runPreTeardownHook(worktree.path);
|
|
3718
|
+
log.info("Pre-teardown hook completed.");
|
|
3719
|
+
} catch (error) {
|
|
3720
|
+
log.warn(`Pre-teardown hook failed: ${error.message}`);
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3624
3724
|
await manager.remove(worktreeName, true);
|
|
3625
3725
|
log.info(`Worktree '${worktreeName}' removed.`);
|
|
3626
3726
|
} catch (error) {
|
|
@@ -3670,6 +3770,15 @@ async function mergeWorktreeManage(projectName, manager, log) {
|
|
|
3670
3770
|
await manager.merge(worktreeName, { targetBranch, squash });
|
|
3671
3771
|
log.info(`Merged '${worktree.branch}' into '${targetBranch}'`);
|
|
3672
3772
|
if (removeAfter) {
|
|
3773
|
+
if (manager.hasTeardownHook() && existsSync5(worktree.path)) {
|
|
3774
|
+
log.info("Running pre-teardown hook...");
|
|
3775
|
+
try {
|
|
3776
|
+
await manager.runPreTeardownHook(worktree.path);
|
|
3777
|
+
log.info("Pre-teardown hook completed.");
|
|
3778
|
+
} catch (error) {
|
|
3779
|
+
log.warn(`Pre-teardown hook failed: ${error.message}`);
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3673
3782
|
await manager.remove(worktreeName, true);
|
|
3674
3783
|
log.info(`Worktree '${worktreeName}' removed.`);
|
|
3675
3784
|
}
|
|
@@ -3791,7 +3900,7 @@ __export(open_exports2, {
|
|
|
3791
3900
|
runOpen: () => runOpen
|
|
3792
3901
|
});
|
|
3793
3902
|
import { Command as Command8 } from "commander";
|
|
3794
|
-
import
|
|
3903
|
+
import File5 from "phylo";
|
|
3795
3904
|
async function runOpen(projectArg, options, ctx) {
|
|
3796
3905
|
const { log } = ctx;
|
|
3797
3906
|
if (options.debug) {
|
|
@@ -3810,7 +3919,7 @@ function createOpenCommand2(ctx) {
|
|
|
3810
3919
|
} else {
|
|
3811
3920
|
log.debug("No project name provided, starting interactive mode");
|
|
3812
3921
|
const { runInteractive: runInteractive2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
3813
|
-
const environment = await EnvironmentRecognizer.recognize(
|
|
3922
|
+
const environment = await EnvironmentRecognizer.recognize(File5.cwd());
|
|
3814
3923
|
await runInteractive2({ config, log, environment });
|
|
3815
3924
|
}
|
|
3816
3925
|
});
|
|
@@ -3818,17 +3927,20 @@ function createOpenCommand2(ctx) {
|
|
|
3818
3927
|
}
|
|
3819
3928
|
async function processProject(projectParam, options, ctx) {
|
|
3820
3929
|
const { config, log } = ctx;
|
|
3821
|
-
const
|
|
3822
|
-
const
|
|
3930
|
+
const parts = projectParam.split(":");
|
|
3931
|
+
const projectName = parts[0];
|
|
3932
|
+
const commandsString = parts[1] || null;
|
|
3933
|
+
const worktreeName = parts[2] || null;
|
|
3934
|
+
const requestedCommands = commandsString && commandsString !== "help" ? commandsString.split(",").map((cmd) => cmd.trim()) : null;
|
|
3823
3935
|
if (commandsString === "help") {
|
|
3824
3936
|
await showProjectHelp(projectName, ctx);
|
|
3825
3937
|
return;
|
|
3826
3938
|
}
|
|
3827
3939
|
log.debug(
|
|
3828
|
-
`Project: ${projectName}, Commands: ${requestedCommands ? requestedCommands.join(", ") : "all"}`
|
|
3940
|
+
`Project: ${projectName}, Commands: ${requestedCommands ? requestedCommands.join(", ") : "all"}` + (worktreeName ? `, Worktree: ${worktreeName}` : "")
|
|
3829
3941
|
);
|
|
3830
3942
|
const projects = config.getProjects();
|
|
3831
|
-
const environment = await EnvironmentRecognizer.recognize(
|
|
3943
|
+
const environment = await EnvironmentRecognizer.recognize(File5.cwd());
|
|
3832
3944
|
if (environment.$isProjectEnvironment && (projectName === "this" || projectName === ".")) {
|
|
3833
3945
|
log.info(`Opening current project: ${environment.project.name}`);
|
|
3834
3946
|
await switchTo(environment, requestedCommands, options, ctx);
|
|
@@ -3841,6 +3953,22 @@ async function processProject(projectParam, options, ctx) {
|
|
|
3841
3953
|
validateRequestedCommands(requestedCommands, projectCfg, projectName);
|
|
3842
3954
|
}
|
|
3843
3955
|
const projectEnv = ProjectEnvironment.load(projectCfg, config.getDefaults());
|
|
3956
|
+
if (worktreeName) {
|
|
3957
|
+
const projectPath = projectEnv.project.path.path;
|
|
3958
|
+
const manager = new WorktreeManager(projectPath, projectName);
|
|
3959
|
+
const worktree = await manager.get(worktreeName);
|
|
3960
|
+
if (!worktree) {
|
|
3961
|
+
log.error(`Worktree '${worktreeName}' not found for project '${projectName}'.`);
|
|
3962
|
+
const worktrees = await manager.listManagedWorktrees();
|
|
3963
|
+
if (worktrees.length > 0) {
|
|
3964
|
+
log.info("Available worktrees:");
|
|
3965
|
+
worktrees.forEach((wt) => log.info(` - ${wt.name}`));
|
|
3966
|
+
}
|
|
3967
|
+
process.exit(1);
|
|
3968
|
+
}
|
|
3969
|
+
log.debug(`Using worktree path: ${worktree.path}`);
|
|
3970
|
+
projectEnv.project.overridePath(worktree.path);
|
|
3971
|
+
}
|
|
3844
3972
|
await switchTo(projectEnv, requestedCommands, options, ctx);
|
|
3845
3973
|
} else {
|
|
3846
3974
|
log.error(`Project '${projectName}' not found.`);
|
|
@@ -4101,7 +4229,9 @@ Available commands for '${projectName}':`);
|
|
|
4101
4229
|
const twoCommands = configuredEvents.slice(0, 2).join(",");
|
|
4102
4230
|
console.log(` workon ${projectName}:${twoCommands.padEnd(12)} # Multiple commands`);
|
|
4103
4231
|
}
|
|
4104
|
-
console.log(` workon ${projectName}:cwd --shell # Output shell commands
|
|
4232
|
+
console.log(` workon ${projectName}:cwd --shell # Output shell commands`);
|
|
4233
|
+
console.log(` workon ${projectName}:cwd:my-worktree # Run in a worktree`);
|
|
4234
|
+
console.log(` workon ${projectName}::my-worktree # All commands in a worktree
|
|
4105
4235
|
`);
|
|
4106
4236
|
}
|
|
4107
4237
|
var init_open2 = __esm({
|
|
@@ -4110,6 +4240,7 @@ var init_open2 = __esm({
|
|
|
4110
4240
|
init_environment();
|
|
4111
4241
|
init_tmux();
|
|
4112
4242
|
init_registry();
|
|
4243
|
+
init_worktree();
|
|
4113
4244
|
}
|
|
4114
4245
|
});
|
|
4115
4246
|
|
|
@@ -4119,12 +4250,12 @@ init_environment();
|
|
|
4119
4250
|
init_registry();
|
|
4120
4251
|
init_open2();
|
|
4121
4252
|
import { Command as Command16 } from "commander";
|
|
4122
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
4253
|
+
import { readFileSync as readFileSync2, existsSync as existsSync7 } from "fs";
|
|
4123
4254
|
import { join as join2, dirname as dirname2 } from "path";
|
|
4124
4255
|
import { fileURLToPath } from "url";
|
|
4125
4256
|
import loog from "loog";
|
|
4126
4257
|
import omelette from "omelette";
|
|
4127
|
-
import
|
|
4258
|
+
import File8 from "phylo";
|
|
4128
4259
|
|
|
4129
4260
|
// src/commands/config/index.ts
|
|
4130
4261
|
import { Command as Command12 } from "commander";
|
|
@@ -4221,7 +4352,7 @@ init_registry();
|
|
|
4221
4352
|
init_constants();
|
|
4222
4353
|
import { Command as Command13 } from "commander";
|
|
4223
4354
|
import { select as select5, input as input6, confirm as confirm9, checkbox as checkbox3 } from "@inquirer/prompts";
|
|
4224
|
-
import
|
|
4355
|
+
import File6 from "phylo";
|
|
4225
4356
|
function createManageCommand(ctx) {
|
|
4226
4357
|
const { log } = ctx;
|
|
4227
4358
|
return new Command13("manage").description("Interactive project management").option("-d, --debug", "Enable debug logging").action(async (options) => {
|
|
@@ -4280,12 +4411,12 @@ async function createProject(ctx) {
|
|
|
4280
4411
|
return true;
|
|
4281
4412
|
}
|
|
4282
4413
|
});
|
|
4283
|
-
const defaultPath = defaults?.base ?
|
|
4414
|
+
const defaultPath = defaults?.base ? File6.from(defaults.base).join(name).path : name;
|
|
4284
4415
|
const pathInput = await input6({
|
|
4285
4416
|
message: "Project path:",
|
|
4286
4417
|
default: defaultPath,
|
|
4287
4418
|
validate: (value) => {
|
|
4288
|
-
const path8 =
|
|
4419
|
+
const path8 = File6.from(value);
|
|
4289
4420
|
try {
|
|
4290
4421
|
const exists = path8.exists();
|
|
4291
4422
|
if (!exists) return `Path does not exist: ${value}`;
|
|
@@ -4299,8 +4430,8 @@ async function createProject(ctx) {
|
|
|
4299
4430
|
});
|
|
4300
4431
|
let relativePath = pathInput;
|
|
4301
4432
|
if (defaults?.base) {
|
|
4302
|
-
const baseDir =
|
|
4303
|
-
const pathFile =
|
|
4433
|
+
const baseDir = File6.from(defaults.base).absolutify();
|
|
4434
|
+
const pathFile = File6.from(pathInput);
|
|
4304
4435
|
try {
|
|
4305
4436
|
const relPath = pathFile.relativize(baseDir.path);
|
|
4306
4437
|
if (relPath && !relPath.path.startsWith("..")) {
|
|
@@ -4377,8 +4508,8 @@ async function editProject(ctx) {
|
|
|
4377
4508
|
});
|
|
4378
4509
|
let relativePath = pathInput;
|
|
4379
4510
|
if (defaults?.base) {
|
|
4380
|
-
const baseDir =
|
|
4381
|
-
const pathFile =
|
|
4511
|
+
const baseDir = File6.from(defaults.base).absolutify();
|
|
4512
|
+
const pathFile = File6.from(pathInput);
|
|
4382
4513
|
try {
|
|
4383
4514
|
if (pathFile.isAbsolute()) {
|
|
4384
4515
|
const relPath = pathFile.relativize(baseDir.path);
|
|
@@ -4478,7 +4609,7 @@ async function listProjects(ctx) {
|
|
|
4478
4609
|
const defaults = config.getDefaults();
|
|
4479
4610
|
console.log("\nConfigured projects:\n");
|
|
4480
4611
|
for (const [name, project] of Object.entries(projects)) {
|
|
4481
|
-
const fullPath = defaults?.base ?
|
|
4612
|
+
const fullPath = defaults?.base ? File6.from(defaults.base).join(project.path).path : project.path;
|
|
4482
4613
|
console.log(` ${name}`);
|
|
4483
4614
|
console.log(` Path: ${fullPath}`);
|
|
4484
4615
|
console.log(` IDE: ${project.ide || "not set"}`);
|
|
@@ -4492,9 +4623,9 @@ async function listProjects(ctx) {
|
|
|
4492
4623
|
|
|
4493
4624
|
// src/commands/add.ts
|
|
4494
4625
|
import { Command as Command14 } from "commander";
|
|
4495
|
-
import { existsSync as
|
|
4626
|
+
import { existsSync as existsSync6, readFileSync } from "fs";
|
|
4496
4627
|
import { basename as basename2, resolve } from "path";
|
|
4497
|
-
import
|
|
4628
|
+
import File7 from "phylo";
|
|
4498
4629
|
import { confirm as confirm10 } from "@inquirer/prompts";
|
|
4499
4630
|
function createAddCommand2(ctx) {
|
|
4500
4631
|
const { log } = ctx;
|
|
@@ -4514,11 +4645,11 @@ async function addProject(pathArg, options, ctx) {
|
|
|
4514
4645
|
const projects = config.getProjects();
|
|
4515
4646
|
const targetPath = resolve(pathArg);
|
|
4516
4647
|
log.debug(`Resolved path: ${targetPath}`);
|
|
4517
|
-
if (!
|
|
4648
|
+
if (!existsSync6(targetPath)) {
|
|
4518
4649
|
log.error(`Path does not exist: ${targetPath}`);
|
|
4519
4650
|
process.exit(1);
|
|
4520
4651
|
}
|
|
4521
|
-
const pathFile =
|
|
4652
|
+
const pathFile = File7.from(targetPath);
|
|
4522
4653
|
try {
|
|
4523
4654
|
const stat = pathFile.stat();
|
|
4524
4655
|
if (!stat.isDirectory()) {
|
|
@@ -4552,7 +4683,7 @@ async function addProject(pathArg, options, ctx) {
|
|
|
4552
4683
|
log.debug(`IDE: ${ide}`);
|
|
4553
4684
|
let relativePath = targetPath;
|
|
4554
4685
|
if (defaults?.base) {
|
|
4555
|
-
const baseDir =
|
|
4686
|
+
const baseDir = File7.from(defaults.base).absolutify();
|
|
4556
4687
|
try {
|
|
4557
4688
|
const relPath = pathFile.relativize(baseDir.path);
|
|
4558
4689
|
if (relPath && !relPath.path.startsWith("..")) {
|
|
@@ -4598,7 +4729,7 @@ function discoverProject(targetPath, log) {
|
|
|
4598
4729
|
packageJson: null
|
|
4599
4730
|
};
|
|
4600
4731
|
const packageJsonPath = resolve(targetPath, "package.json");
|
|
4601
|
-
if (
|
|
4732
|
+
if (existsSync6(packageJsonPath)) {
|
|
4602
4733
|
discovery.isNode = true;
|
|
4603
4734
|
log.debug("Detected Node project (package.json found)");
|
|
4604
4735
|
try {
|
|
@@ -4614,25 +4745,25 @@ function discoverProject(targetPath, log) {
|
|
|
4614
4745
|
}
|
|
4615
4746
|
}
|
|
4616
4747
|
const bunLockPath = resolve(targetPath, "bun.lockb");
|
|
4617
|
-
if (
|
|
4748
|
+
if (existsSync6(bunLockPath)) {
|
|
4618
4749
|
discovery.isBun = true;
|
|
4619
4750
|
log.debug("Detected Bun project (bun.lockb found)");
|
|
4620
4751
|
}
|
|
4621
4752
|
const vscodeDir = resolve(targetPath, ".vscode");
|
|
4622
4753
|
const cursorDir = resolve(targetPath, ".cursor");
|
|
4623
4754
|
const ideaDir = resolve(targetPath, ".idea");
|
|
4624
|
-
if (
|
|
4755
|
+
if (existsSync6(cursorDir)) {
|
|
4625
4756
|
discovery.detectedIde = "cursor";
|
|
4626
4757
|
log.debug("Detected Cursor (.cursor directory found)");
|
|
4627
|
-
} else if (
|
|
4758
|
+
} else if (existsSync6(vscodeDir)) {
|
|
4628
4759
|
discovery.detectedIde = "code";
|
|
4629
4760
|
log.debug("Detected VS Code (.vscode directory found)");
|
|
4630
|
-
} else if (
|
|
4761
|
+
} else if (existsSync6(ideaDir)) {
|
|
4631
4762
|
discovery.detectedIde = "idea";
|
|
4632
4763
|
log.debug("Detected IntelliJ IDEA (.idea directory found)");
|
|
4633
4764
|
}
|
|
4634
4765
|
const claudeMdPath = resolve(targetPath, "CLAUDE.md");
|
|
4635
|
-
if (
|
|
4766
|
+
if (existsSync6(claudeMdPath)) {
|
|
4636
4767
|
discovery.hasClaude = true;
|
|
4637
4768
|
log.debug("Detected Claude Code project (CLAUDE.md found)");
|
|
4638
4769
|
}
|
|
@@ -4681,19 +4812,21 @@ function createWorktreeCommand(ctx) {
|
|
|
4681
4812
|
}
|
|
4682
4813
|
await showWorktreeStatus(worktreeInfo, log);
|
|
4683
4814
|
});
|
|
4684
|
-
command.command("merge").description("Merge this worktree branch into a target branch").option("-i, --into <branch>", "Target branch to merge into").option("-s, --squash", "Use squash merge").option("-k, --keep", "Keep the worktree after merging").option("-y, --yes", "Skip confirmation prompts").
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4815
|
+
command.command("merge").description("Merge this worktree branch into a target branch").option("-i, --into <branch>", "Target branch to merge into").option("-s, --squash", "Use squash merge").option("-k, --keep", "Keep the worktree after merging").option("-y, --yes", "Skip confirmation prompts").option("--delete-branch", "Delete the merged branch after merge").action(
|
|
4816
|
+
async (options) => {
|
|
4817
|
+
const worktreeInfo = await detectWorktreeContext();
|
|
4818
|
+
if (!worktreeInfo) {
|
|
4819
|
+
log.error("Not in a git repository.");
|
|
4820
|
+
process.exit(1);
|
|
4821
|
+
}
|
|
4822
|
+
if (!worktreeInfo.isWorktree) {
|
|
4823
|
+
log.error("You're in the main repository, not a worktree.");
|
|
4824
|
+
log.info(`Use 'workon worktrees merge <name>' from the main repository.`);
|
|
4825
|
+
process.exit(1);
|
|
4826
|
+
}
|
|
4827
|
+
await mergeCurrentWorktree(worktreeInfo, options, ctx);
|
|
4694
4828
|
}
|
|
4695
|
-
|
|
4696
|
-
});
|
|
4829
|
+
);
|
|
4697
4830
|
command.command("remove").description("Remove the current worktree").option("-f, --force", "Force removal even with uncommitted changes").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
|
|
4698
4831
|
const worktreeInfo = await detectWorktreeContext();
|
|
4699
4832
|
if (!worktreeInfo) {
|
|
@@ -4778,11 +4911,16 @@ async function mergeCurrentWorktree(worktreeInfo, options, ctx) {
|
|
|
4778
4911
|
}
|
|
4779
4912
|
const commonTargets = ["main", "master", "develop", "dev"];
|
|
4780
4913
|
const defaultTarget = commonTargets.find((t) => targetBranches.includes(t)) || targetBranches[0];
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4914
|
+
if (options.yes) {
|
|
4915
|
+
targetBranch = defaultTarget;
|
|
4916
|
+
log.info(`Auto-selected target branch: '${targetBranch}'`);
|
|
4917
|
+
} else {
|
|
4918
|
+
targetBranch = await select6({
|
|
4919
|
+
message: `Merge '${worktreeInfo.branch}' into which branch?`,
|
|
4920
|
+
choices: targetBranches.map((b) => ({ name: b, value: b })),
|
|
4921
|
+
default: defaultTarget
|
|
4922
|
+
});
|
|
4923
|
+
}
|
|
4786
4924
|
}
|
|
4787
4925
|
if (!await manager.branchExists(targetBranch)) {
|
|
4788
4926
|
log.error(`Target branch '${targetBranch}' does not exist.`);
|
|
@@ -4824,12 +4962,18 @@ ${chalk8.bold("Merge operation:")}`);
|
|
|
4824
4962
|
log.info("You may need to resolve conflicts manually.");
|
|
4825
4963
|
process.exit(1);
|
|
4826
4964
|
}
|
|
4965
|
+
if (options.keep && options.deleteBranch) {
|
|
4966
|
+
log.warn("--delete-branch is ignored when --keep is set (branch is needed by the worktree).");
|
|
4967
|
+
}
|
|
4827
4968
|
if (!options.keep) {
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4969
|
+
let shouldContinue = true;
|
|
4970
|
+
if (!options.yes) {
|
|
4971
|
+
log.warn("You need to exit this worktree directory before it can be removed.");
|
|
4972
|
+
shouldContinue = await confirm11({
|
|
4973
|
+
message: `Remove worktree '${worktreeInfo.worktreeName}'? (You'll need to cd out first)`,
|
|
4974
|
+
default: true
|
|
4975
|
+
});
|
|
4976
|
+
}
|
|
4833
4977
|
if (shouldContinue) {
|
|
4834
4978
|
const tmux = new TmuxManager();
|
|
4835
4979
|
const sessionName = tmux.getWorktreeSessionName(
|
|
@@ -4845,20 +4989,21 @@ To complete removal, run from the main project directory:`);
|
|
|
4845
4989
|
console.log(chalk8.cyan(` cd ${worktreeInfo.mainRepoPath}`));
|
|
4846
4990
|
console.log(chalk8.cyan(` workon worktrees remove ${worktreeInfo.worktreeName}`));
|
|
4847
4991
|
}
|
|
4848
|
-
|
|
4849
|
-
|
|
4992
|
+
let shouldDeleteBranch = options.deleteBranch || false;
|
|
4993
|
+
if (!shouldDeleteBranch && !options.yes) {
|
|
4994
|
+
shouldDeleteBranch = await confirm11({
|
|
4850
4995
|
message: `Delete the merged branch '${worktreeInfo.branch}'?`,
|
|
4851
4996
|
default: false
|
|
4852
4997
|
});
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
}
|
|
4860
|
-
|
|
4861
|
-
}
|
|
4998
|
+
}
|
|
4999
|
+
if (shouldDeleteBranch) {
|
|
5000
|
+
const deleteSpinner = ora5(`Deleting branch '${worktreeInfo.branch}'...`).start();
|
|
5001
|
+
try {
|
|
5002
|
+
const git = simpleGit5(worktreeInfo.mainRepoPath);
|
|
5003
|
+
await git.deleteLocalBranch(worktreeInfo.branch, true);
|
|
5004
|
+
deleteSpinner.succeed(`Branch '${worktreeInfo.branch}' deleted`);
|
|
5005
|
+
} catch (error) {
|
|
5006
|
+
deleteSpinner.warn(`Failed to delete branch: ${error.message}`);
|
|
4862
5007
|
}
|
|
4863
5008
|
}
|
|
4864
5009
|
}
|
|
@@ -4928,7 +5073,7 @@ function findPackageJson() {
|
|
|
4928
5073
|
join2(process.cwd(), "package.json")
|
|
4929
5074
|
];
|
|
4930
5075
|
for (const p of paths) {
|
|
4931
|
-
if (
|
|
5076
|
+
if (existsSync7(p)) {
|
|
4932
5077
|
return p;
|
|
4933
5078
|
}
|
|
4934
5079
|
}
|
|
@@ -4972,7 +5117,7 @@ function createCli() {
|
|
|
4972
5117
|
await runOpen2(project, { debug: options.debug, shell: options.shell }, { config, log });
|
|
4973
5118
|
return;
|
|
4974
5119
|
}
|
|
4975
|
-
const environment = await EnvironmentRecognizer.recognize(
|
|
5120
|
+
const environment = await EnvironmentRecognizer.recognize(File8.cwd());
|
|
4976
5121
|
program2.setOptionValue("_environment", environment);
|
|
4977
5122
|
program2.setOptionValue("_config", config);
|
|
4978
5123
|
program2.setOptionValue("_log", log);
|