workon 3.6.0 → 3.7.1
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 +290 -160
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +8 -1
- 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 +8 -1
- 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
|
}
|
|
@@ -1268,7 +1275,7 @@ var init_tmux = __esm({
|
|
|
1268
1275
|
getWorktreeSessionName(projectName, worktreeName) {
|
|
1269
1276
|
const sanitizedProject = sanitizeForShell(projectName);
|
|
1270
1277
|
const sanitizedWorktree = sanitizeForShell(worktreeName);
|
|
1271
|
-
return `${this.sessionPrefix}${sanitizedProject}
|
|
1278
|
+
return `${this.sessionPrefix}${sanitizedProject}_wt_${sanitizedWorktree}`;
|
|
1272
1279
|
}
|
|
1273
1280
|
async killSession(sessionName) {
|
|
1274
1281
|
try {
|
|
@@ -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";
|
|
@@ -1771,6 +1760,24 @@ var init_worktree = __esm({
|
|
|
1771
1760
|
}
|
|
1772
1761
|
});
|
|
1773
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
|
+
|
|
1774
1781
|
// src/commands/worktrees/utils.ts
|
|
1775
1782
|
import File3 from "phylo";
|
|
1776
1783
|
import path from "path";
|
|
@@ -2035,7 +2042,7 @@ import ora from "ora";
|
|
|
2035
2042
|
import { select as select2, confirm as confirm4 } from "@inquirer/prompts";
|
|
2036
2043
|
function createAddCommand(ctx) {
|
|
2037
2044
|
const { config, log } = ctx;
|
|
2038
|
-
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) => {
|
|
2039
2046
|
const projectCtx = await resolveProjectFromCwd(config, log);
|
|
2040
2047
|
if (!projectCtx) {
|
|
2041
2048
|
log.error("Not in a git repository. Run this command from within a git project.");
|
|
@@ -2045,11 +2052,15 @@ function createAddCommand(ctx) {
|
|
|
2045
2052
|
process.exit(1);
|
|
2046
2053
|
}
|
|
2047
2054
|
if (!projectCtx.isRegistered) {
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
projectCtx.
|
|
2052
|
-
|
|
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
|
+
}
|
|
2053
2064
|
}
|
|
2054
2065
|
}
|
|
2055
2066
|
const { projectPath, projectName } = projectCtx;
|
|
@@ -2057,16 +2068,21 @@ function createAddCommand(ctx) {
|
|
|
2057
2068
|
const branchExists = await manager.branchExists(branch);
|
|
2058
2069
|
let baseBranch = options.base;
|
|
2059
2070
|
if (!branchExists && !baseBranch) {
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
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
|
+
}
|
|
2070
2086
|
}
|
|
2071
2087
|
const spinner = ora(`Creating worktree for branch '${branch}'...`).start();
|
|
2072
2088
|
try {
|
|
@@ -2099,6 +2115,9 @@ ${chalk2.bold("Worktree details:")}`);
|
|
|
2099
2115
|
if (projectCtx.isRegistered && projectName) {
|
|
2100
2116
|
if (options.open) {
|
|
2101
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}`)}`);
|
|
2102
2121
|
} else {
|
|
2103
2122
|
const shouldOpen = await confirm4({
|
|
2104
2123
|
message: "Open workon session in this worktree?",
|
|
@@ -2268,7 +2287,7 @@ import path4 from "path";
|
|
|
2268
2287
|
import { select as select3, confirm as confirm6 } from "@inquirer/prompts";
|
|
2269
2288
|
function createMergeCommand(ctx) {
|
|
2270
2289
|
const { config, log } = ctx;
|
|
2271
|
-
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) => {
|
|
2272
2291
|
const projectCtx = await resolveProjectFromCwd(config, log);
|
|
2273
2292
|
if (!projectCtx) {
|
|
2274
2293
|
log.error("Not in a git repository. Run this command from within a git project.");
|
|
@@ -2310,14 +2329,19 @@ function createMergeCommand(ctx) {
|
|
|
2310
2329
|
}
|
|
2311
2330
|
const commonTargets = ["main", "master", "develop", "dev"];
|
|
2312
2331
|
const defaultTarget = commonTargets.find((t) => targetBranches.includes(t)) || targetBranches[0];
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
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
|
+
}
|
|
2321
2345
|
}
|
|
2322
2346
|
if (!await manager.branchExists(targetBranch)) {
|
|
2323
2347
|
log.error(`Target branch '${targetBranch}' does not exist.`);
|
|
@@ -2359,6 +2383,11 @@ ${chalk4.bold("Merge operation:")}`);
|
|
|
2359
2383
|
log.info("You may need to resolve conflicts manually.");
|
|
2360
2384
|
process.exit(1);
|
|
2361
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
|
+
}
|
|
2362
2391
|
if (!options.keep) {
|
|
2363
2392
|
const tmux = new TmuxManager();
|
|
2364
2393
|
const sessionName = tmux.getWorktreeSessionName(displayName, name);
|
|
@@ -2374,21 +2403,22 @@ ${chalk4.bold("Merge operation:")}`);
|
|
|
2374
2403
|
removeSpinner.warn(`Failed to remove worktree: ${error.message}`);
|
|
2375
2404
|
log.info(`You can remove it manually with: workon worktrees remove ${name}`);
|
|
2376
2405
|
}
|
|
2377
|
-
|
|
2378
|
-
|
|
2406
|
+
let shouldDeleteBranch = options.deleteBranch || false;
|
|
2407
|
+
if (!shouldDeleteBranch && !options.yes) {
|
|
2408
|
+
shouldDeleteBranch = await confirm6({
|
|
2379
2409
|
message: `Delete the merged branch '${worktree.branch}'?`,
|
|
2380
2410
|
default: false
|
|
2381
2411
|
});
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
}
|
|
2390
|
-
|
|
2391
|
-
}
|
|
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}`);
|
|
2392
2422
|
}
|
|
2393
2423
|
}
|
|
2394
2424
|
}
|
|
@@ -2414,7 +2444,7 @@ import { confirm as confirm7 } from "@inquirer/prompts";
|
|
|
2414
2444
|
import { simpleGit as simpleGit4 } from "simple-git";
|
|
2415
2445
|
function createBranchCommand(ctx) {
|
|
2416
2446
|
const { config, log } = ctx;
|
|
2417
|
-
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) => {
|
|
2418
2448
|
const projectCtx = await resolveProjectFromCwd(config, log);
|
|
2419
2449
|
if (!projectCtx) {
|
|
2420
2450
|
log.error("Not in a git repository. Run this command from within a git project.");
|
|
@@ -2477,6 +2507,8 @@ ${chalk5.bold("Action:")}`);
|
|
|
2477
2507
|
pushSpinner.fail(`Failed to push: ${error.message}`);
|
|
2478
2508
|
log.info(`You can push manually with: git push -u origin ${branchName}`);
|
|
2479
2509
|
}
|
|
2510
|
+
} else if (options.yes) {
|
|
2511
|
+
log.info(`You can push manually with: git push -u origin ${branchName}`);
|
|
2480
2512
|
} else {
|
|
2481
2513
|
const shouldPush = await confirm7({
|
|
2482
2514
|
message: "Push branch to origin for PR?",
|
|
@@ -2579,13 +2611,12 @@ __export(open_exports, {
|
|
|
2579
2611
|
});
|
|
2580
2612
|
import { Command as Command7 } from "commander";
|
|
2581
2613
|
import chalk6 from "chalk";
|
|
2582
|
-
import File4 from "phylo";
|
|
2583
2614
|
import path6 from "path";
|
|
2584
2615
|
import { exec as execCallback3 } from "child_process";
|
|
2585
2616
|
import { promisify as promisify3 } from "util";
|
|
2586
2617
|
function createOpenCommand(ctx) {
|
|
2587
2618
|
const { config, log } = ctx;
|
|
2588
|
-
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) => {
|
|
2589
2620
|
if (options.debug) {
|
|
2590
2621
|
log.setLogLevel("debug");
|
|
2591
2622
|
}
|
|
@@ -2598,12 +2629,16 @@ function createOpenCommand(ctx) {
|
|
|
2598
2629
|
process.exit(1);
|
|
2599
2630
|
}
|
|
2600
2631
|
if (!projectCtx.isRegistered) {
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
projectCtx.
|
|
2606
|
-
|
|
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
|
+
}
|
|
2607
2642
|
}
|
|
2608
2643
|
}
|
|
2609
2644
|
await runWorktreeOpen(projectCtx, name, options, { config, log });
|
|
@@ -2636,7 +2671,7 @@ async function runWorktreeOpen(projectCtx, worktreeName, options, ctx) {
|
|
|
2636
2671
|
let hasNpmEvent = false;
|
|
2637
2672
|
if (isRegistered && projectName && projectConfig) {
|
|
2638
2673
|
project = new Project(projectName, projectConfig, defaults);
|
|
2639
|
-
project.
|
|
2674
|
+
project.overridePath(worktree.path);
|
|
2640
2675
|
const events = project.events || {};
|
|
2641
2676
|
hasClaudeEvent = !!events.claude;
|
|
2642
2677
|
hasNpmEvent = !!events.npm;
|
|
@@ -2855,7 +2890,7 @@ __export(interactive_exports, {
|
|
|
2855
2890
|
});
|
|
2856
2891
|
import { select as select4, input as input5, checkbox as checkbox2, confirm as confirm8 } from "@inquirer/prompts";
|
|
2857
2892
|
import { existsSync as existsSync5 } from "fs";
|
|
2858
|
-
import
|
|
2893
|
+
import File4 from "phylo";
|
|
2859
2894
|
import path7 from "path";
|
|
2860
2895
|
import deepAssign2 from "deep-assign";
|
|
2861
2896
|
import chalk7 from "chalk";
|
|
@@ -2863,7 +2898,7 @@ async function runInteractive(ctx) {
|
|
|
2863
2898
|
const { config, log, environment, suggestedName } = ctx;
|
|
2864
2899
|
showLogo(config);
|
|
2865
2900
|
log.log("");
|
|
2866
|
-
const defaultName = suggestedName ?? (environment.$isProjectEnvironment ? environment.project.name :
|
|
2901
|
+
const defaultName = suggestedName ?? (environment.$isProjectEnvironment ? environment.project.name : File4.cwd().name);
|
|
2867
2902
|
const fromUser = !!suggestedName;
|
|
2868
2903
|
await startInteractive(defaultName, fromUser, ctx);
|
|
2869
2904
|
}
|
|
@@ -2964,15 +2999,15 @@ async function initProject(defaultName, fromUser, ctx) {
|
|
|
2964
2999
|
let basePath;
|
|
2965
3000
|
if (isBranch) {
|
|
2966
3001
|
const projectName = name.substring(0, name.indexOf("#"));
|
|
2967
|
-
basePath = defaults?.base ?
|
|
3002
|
+
basePath = defaults?.base ? File4.from(defaults.base).join(projects[projectName].path).absolutePath() : projects[projectName].path;
|
|
2968
3003
|
log.log(`Project path: ${basePath}`);
|
|
2969
3004
|
} else {
|
|
2970
3005
|
const pathAnswer = await input5({
|
|
2971
3006
|
message: "What is the path to the project?",
|
|
2972
|
-
default: defaults?.base ?
|
|
3007
|
+
default: defaults?.base ? File4.from(defaults.base).join(name).path : name
|
|
2973
3008
|
});
|
|
2974
|
-
let answerFile =
|
|
2975
|
-
const defaultBase = defaults?.base ?
|
|
3009
|
+
let answerFile = File4.from(pathAnswer);
|
|
3010
|
+
const defaultBase = defaults?.base ? File4.from(defaults.base).absolutify() : File4.cwd();
|
|
2976
3011
|
if (!answerFile.isAbsolute()) {
|
|
2977
3012
|
answerFile = defaultBase.join(answerFile.path);
|
|
2978
3013
|
}
|
|
@@ -3180,15 +3215,15 @@ async function createProjectManage(ctx) {
|
|
|
3180
3215
|
return true;
|
|
3181
3216
|
}
|
|
3182
3217
|
});
|
|
3183
|
-
const defaultPath = defaults?.base ?
|
|
3218
|
+
const defaultPath = defaults?.base ? File4.from(defaults.base).absolutify().join(name).path : name;
|
|
3184
3219
|
const pathInput = await input5({
|
|
3185
3220
|
message: "Project path:",
|
|
3186
3221
|
default: defaultPath
|
|
3187
3222
|
});
|
|
3188
3223
|
let relativePath = pathInput;
|
|
3189
3224
|
if (defaults?.base) {
|
|
3190
|
-
const baseDir =
|
|
3191
|
-
const pathFile =
|
|
3225
|
+
const baseDir = File4.from(defaults.base).absolutify();
|
|
3226
|
+
const pathFile = File4.from(pathInput);
|
|
3192
3227
|
try {
|
|
3193
3228
|
if (pathFile.isAbsolute()) {
|
|
3194
3229
|
const relPath = pathFile.relativize(baseDir.path);
|
|
@@ -3255,8 +3290,8 @@ async function editProjectManage(ctx) {
|
|
|
3255
3290
|
});
|
|
3256
3291
|
let relativePath = pathInput;
|
|
3257
3292
|
if (defaults?.base) {
|
|
3258
|
-
const baseDir =
|
|
3259
|
-
const pathFile =
|
|
3293
|
+
const baseDir = File4.from(defaults.base).absolutify();
|
|
3294
|
+
const pathFile = File4.from(pathInput);
|
|
3260
3295
|
try {
|
|
3261
3296
|
if (pathFile.isAbsolute()) {
|
|
3262
3297
|
const relPath = pathFile.relativize(baseDir.path);
|
|
@@ -3359,7 +3394,7 @@ function listProjectsManage(ctx) {
|
|
|
3359
3394
|
const baseProjects = Object.keys(projects).filter((name) => !name.includes("#"));
|
|
3360
3395
|
for (const name of baseProjects) {
|
|
3361
3396
|
const project = projects[name];
|
|
3362
|
-
const fullPath = defaults?.base ?
|
|
3397
|
+
const fullPath = defaults?.base ? File4.from(defaults.base).join(project.path).path : project.path;
|
|
3363
3398
|
console.log(` ${name}`);
|
|
3364
3399
|
console.log(` Path: ${fullPath}`);
|
|
3365
3400
|
console.log(` IDE: ${project.ide || "not set"}`);
|
|
@@ -3483,11 +3518,11 @@ async function manageWorktrees(projectName, ctx) {
|
|
|
3483
3518
|
const projectConfig = projects[projectName];
|
|
3484
3519
|
const basePath = defaults?.base || "";
|
|
3485
3520
|
let projectPath;
|
|
3486
|
-
const configPath =
|
|
3521
|
+
const configPath = File4.from(projectConfig.path);
|
|
3487
3522
|
if (configPath.path.startsWith("/") || configPath.path.startsWith("~")) {
|
|
3488
3523
|
projectPath = configPath.absolutify().path;
|
|
3489
3524
|
} else if (basePath) {
|
|
3490
|
-
projectPath =
|
|
3525
|
+
projectPath = File4.from(basePath).absolutify().join(projectConfig.path).path;
|
|
3491
3526
|
} else {
|
|
3492
3527
|
projectPath = configPath.absolutify().path;
|
|
3493
3528
|
}
|
|
@@ -3618,11 +3653,11 @@ async function openWorktreeManage(projectName, manager, config, log) {
|
|
|
3618
3653
|
const projectConfig = projects[projectName];
|
|
3619
3654
|
const basePath = defaults?.base || "";
|
|
3620
3655
|
let projectPath;
|
|
3621
|
-
const configPath =
|
|
3656
|
+
const configPath = File4.from(projectConfig.path);
|
|
3622
3657
|
if (configPath.path.startsWith("/") || configPath.path.startsWith("~")) {
|
|
3623
3658
|
projectPath = configPath.absolutify().path;
|
|
3624
3659
|
} else if (basePath) {
|
|
3625
|
-
projectPath =
|
|
3660
|
+
projectPath = File4.from(basePath).absolutify().join(projectConfig.path).path;
|
|
3626
3661
|
} else {
|
|
3627
3662
|
projectPath = configPath.absolutify().path;
|
|
3628
3663
|
}
|
|
@@ -3865,7 +3900,7 @@ __export(open_exports2, {
|
|
|
3865
3900
|
runOpen: () => runOpen
|
|
3866
3901
|
});
|
|
3867
3902
|
import { Command as Command8 } from "commander";
|
|
3868
|
-
import
|
|
3903
|
+
import File5 from "phylo";
|
|
3869
3904
|
async function runOpen(projectArg, options, ctx) {
|
|
3870
3905
|
const { log } = ctx;
|
|
3871
3906
|
if (options.debug) {
|
|
@@ -3884,7 +3919,7 @@ function createOpenCommand2(ctx) {
|
|
|
3884
3919
|
} else {
|
|
3885
3920
|
log.debug("No project name provided, starting interactive mode");
|
|
3886
3921
|
const { runInteractive: runInteractive2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
3887
|
-
const environment = await EnvironmentRecognizer.recognize(
|
|
3922
|
+
const environment = await EnvironmentRecognizer.recognize(File5.cwd());
|
|
3888
3923
|
await runInteractive2({ config, log, environment });
|
|
3889
3924
|
}
|
|
3890
3925
|
});
|
|
@@ -3892,18 +3927,28 @@ function createOpenCommand2(ctx) {
|
|
|
3892
3927
|
}
|
|
3893
3928
|
async function processProject(projectParam, options, ctx) {
|
|
3894
3929
|
const { config, log } = ctx;
|
|
3895
|
-
const
|
|
3896
|
-
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;
|
|
3897
3935
|
if (commandsString === "help") {
|
|
3898
3936
|
await showProjectHelp(projectName, ctx);
|
|
3899
3937
|
return;
|
|
3900
3938
|
}
|
|
3901
3939
|
log.debug(
|
|
3902
|
-
`Project: ${projectName}, Commands: ${requestedCommands ? requestedCommands.join(", ") : "all"}`
|
|
3940
|
+
`Project: ${projectName}, Commands: ${requestedCommands ? requestedCommands.join(", ") : "all"}` + (worktreeName ? `, Worktree: ${worktreeName}` : "")
|
|
3903
3941
|
);
|
|
3904
3942
|
const projects = config.getProjects();
|
|
3905
|
-
const environment = await EnvironmentRecognizer.recognize(
|
|
3943
|
+
const environment = await EnvironmentRecognizer.recognize(File5.cwd());
|
|
3906
3944
|
if (environment.$isProjectEnvironment && (projectName === "this" || projectName === ".")) {
|
|
3945
|
+
if (worktreeName) {
|
|
3946
|
+
log.error(
|
|
3947
|
+
`Worktree syntax is not supported with '${projectName}'. Use the full project name instead:`
|
|
3948
|
+
);
|
|
3949
|
+
log.info(` workon ${environment.project.name}::${worktreeName}`);
|
|
3950
|
+
process.exit(1);
|
|
3951
|
+
}
|
|
3907
3952
|
log.info(`Opening current project: ${environment.project.name}`);
|
|
3908
3953
|
await switchTo(environment, requestedCommands, options, ctx);
|
|
3909
3954
|
return;
|
|
@@ -3915,7 +3960,23 @@ async function processProject(projectParam, options, ctx) {
|
|
|
3915
3960
|
validateRequestedCommands(requestedCommands, projectCfg, projectName);
|
|
3916
3961
|
}
|
|
3917
3962
|
const projectEnv = ProjectEnvironment.load(projectCfg, config.getDefaults());
|
|
3918
|
-
|
|
3963
|
+
if (worktreeName) {
|
|
3964
|
+
const projectPath = projectEnv.project.path.path;
|
|
3965
|
+
const manager = new WorktreeManager(projectPath, projectName);
|
|
3966
|
+
const worktree = await manager.get(worktreeName);
|
|
3967
|
+
if (!worktree) {
|
|
3968
|
+
log.error(`Worktree '${worktreeName}' not found for project '${projectName}'.`);
|
|
3969
|
+
const worktrees = await manager.listManagedWorktrees();
|
|
3970
|
+
if (worktrees.length > 0) {
|
|
3971
|
+
log.info("Available worktrees:");
|
|
3972
|
+
worktrees.forEach((wt) => log.info(` - ${wt.name}`));
|
|
3973
|
+
}
|
|
3974
|
+
process.exit(1);
|
|
3975
|
+
}
|
|
3976
|
+
log.debug(`Using worktree path: ${worktree.path}`);
|
|
3977
|
+
projectEnv.project.overridePath(worktree.path);
|
|
3978
|
+
}
|
|
3979
|
+
await switchTo(projectEnv, requestedCommands, options, ctx, worktreeName);
|
|
3919
3980
|
} else {
|
|
3920
3981
|
log.error(`Project '${projectName}' not found.`);
|
|
3921
3982
|
log.info(`Run 'workon' without arguments to see available projects or create a new one.`);
|
|
@@ -3933,7 +3994,7 @@ Available commands: ${availableCommands}`
|
|
|
3933
3994
|
);
|
|
3934
3995
|
}
|
|
3935
3996
|
}
|
|
3936
|
-
async function switchTo(environment, requestedCommands, options, ctx) {
|
|
3997
|
+
async function switchTo(environment, requestedCommands, options, ctx, worktreeName) {
|
|
3937
3998
|
const { log } = ctx;
|
|
3938
3999
|
const project = environment.project;
|
|
3939
4000
|
let events;
|
|
@@ -3957,11 +4018,35 @@ async function switchTo(environment, requestedCommands, options, ctx) {
|
|
|
3957
4018
|
const hasNpmEvent = events.includes("npm");
|
|
3958
4019
|
const dryRun = options.dryRun || false;
|
|
3959
4020
|
if (hasCwd && hasClaudeEvent && hasNpmEvent) {
|
|
3960
|
-
await handleThreePaneLayout(
|
|
4021
|
+
await handleThreePaneLayout(
|
|
4022
|
+
project,
|
|
4023
|
+
isShellMode,
|
|
4024
|
+
dryRun,
|
|
4025
|
+
shellCommands,
|
|
4026
|
+
events,
|
|
4027
|
+
ctx,
|
|
4028
|
+
worktreeName
|
|
4029
|
+
);
|
|
3961
4030
|
} else if (hasCwd && hasNpmEvent) {
|
|
3962
|
-
await handleTwoPaneNpmLayout(
|
|
4031
|
+
await handleTwoPaneNpmLayout(
|
|
4032
|
+
project,
|
|
4033
|
+
isShellMode,
|
|
4034
|
+
dryRun,
|
|
4035
|
+
shellCommands,
|
|
4036
|
+
events,
|
|
4037
|
+
ctx,
|
|
4038
|
+
worktreeName
|
|
4039
|
+
);
|
|
3963
4040
|
} else if (hasCwd && hasClaudeEvent) {
|
|
3964
|
-
await handleSplitTerminal(
|
|
4041
|
+
await handleSplitTerminal(
|
|
4042
|
+
project,
|
|
4043
|
+
isShellMode,
|
|
4044
|
+
dryRun,
|
|
4045
|
+
shellCommands,
|
|
4046
|
+
events,
|
|
4047
|
+
ctx,
|
|
4048
|
+
worktreeName
|
|
4049
|
+
);
|
|
3965
4050
|
} else {
|
|
3966
4051
|
for (const event of events) {
|
|
3967
4052
|
if (!dryRun) {
|
|
@@ -3995,10 +4080,11 @@ async function getNpmCommand2(project) {
|
|
|
3995
4080
|
const { NpmEvent: NpmEvent2 } = await Promise.resolve().then(() => (init_npm(), npm_exports));
|
|
3996
4081
|
return NpmEvent2.getNpmCommand(npmConfig);
|
|
3997
4082
|
}
|
|
3998
|
-
async function handleTmuxLayout(project, layout, options, shellCommands, events, ctx) {
|
|
4083
|
+
async function handleTmuxLayout(project, layout, options, shellCommands, events, ctx, worktreeName) {
|
|
3999
4084
|
const { log } = ctx;
|
|
4000
4085
|
const tmux = new TmuxManager();
|
|
4001
4086
|
const { isShellMode, dryRun } = options;
|
|
4087
|
+
const tmuxSessionId = worktreeName ? `${project.name}_wt_${worktreeName}` : project.name;
|
|
4002
4088
|
let tmuxHandled = false;
|
|
4003
4089
|
if (isShellMode) {
|
|
4004
4090
|
if (await tmux.isTmuxAvailable()) {
|
|
@@ -4014,7 +4100,7 @@ async function handleTmuxLayout(project, layout, options, shellCommands, events,
|
|
|
4014
4100
|
shellCommands.push(...cmds);
|
|
4015
4101
|
}
|
|
4016
4102
|
}
|
|
4017
|
-
const commands = buildLayoutShellCommands(tmux, project, layout);
|
|
4103
|
+
const commands = buildLayoutShellCommands(tmux, tmuxSessionId, project, layout);
|
|
4018
4104
|
shellCommands.push(...commands);
|
|
4019
4105
|
tmuxHandled = true;
|
|
4020
4106
|
} else {
|
|
@@ -4026,7 +4112,7 @@ async function handleTmuxLayout(project, layout, options, shellCommands, events,
|
|
|
4026
4112
|
} else if (!dryRun) {
|
|
4027
4113
|
if (await tmux.isTmuxAvailable()) {
|
|
4028
4114
|
try {
|
|
4029
|
-
const sessionName = await createTmuxSession(tmux, project, layout);
|
|
4115
|
+
const sessionName = await createTmuxSession(tmux, tmuxSessionId, project, layout);
|
|
4030
4116
|
await tmux.attachToSession(sessionName);
|
|
4031
4117
|
tmuxHandled = true;
|
|
4032
4118
|
} catch (error) {
|
|
@@ -4050,19 +4136,19 @@ async function handleTmuxLayout(project, layout, options, shellCommands, events,
|
|
|
4050
4136
|
}
|
|
4051
4137
|
}
|
|
4052
4138
|
}
|
|
4053
|
-
function buildLayoutShellCommands(tmux, project, layout) {
|
|
4139
|
+
function buildLayoutShellCommands(tmux, sessionId, project, layout) {
|
|
4054
4140
|
switch (layout.type) {
|
|
4055
4141
|
case "split-claude":
|
|
4056
|
-
return tmux.buildShellCommands(
|
|
4142
|
+
return tmux.buildShellCommands(sessionId, project.path.path, layout.claudeArgs);
|
|
4057
4143
|
case "three-pane":
|
|
4058
4144
|
return tmux.buildThreePaneShellCommands(
|
|
4059
|
-
|
|
4145
|
+
sessionId,
|
|
4060
4146
|
project.path.path,
|
|
4061
4147
|
layout.claudeArgs,
|
|
4062
4148
|
layout.npmCommand
|
|
4063
4149
|
);
|
|
4064
4150
|
case "two-pane-npm":
|
|
4065
|
-
return tmux.buildTwoPaneNpmShellCommands(
|
|
4151
|
+
return tmux.buildTwoPaneNpmShellCommands(sessionId, project.path.path, layout.npmCommand);
|
|
4066
4152
|
}
|
|
4067
4153
|
}
|
|
4068
4154
|
function buildFallbackCommandsWithEvents(shellCommands, project, layout, remainingEvents, _ctx) {
|
|
@@ -4080,50 +4166,77 @@ function buildFallbackCommandsWithEvents(shellCommands, project, layout, remaini
|
|
|
4080
4166
|
}
|
|
4081
4167
|
}
|
|
4082
4168
|
}
|
|
4083
|
-
async function createTmuxSession(tmux, project, layout) {
|
|
4169
|
+
async function createTmuxSession(tmux, sessionId, project, layout) {
|
|
4084
4170
|
switch (layout.type) {
|
|
4085
4171
|
case "split-claude":
|
|
4086
|
-
return tmux.createSplitSession(
|
|
4172
|
+
return tmux.createSplitSession(sessionId, project.path.path, layout.claudeArgs);
|
|
4087
4173
|
case "three-pane":
|
|
4088
4174
|
return tmux.createThreePaneSession(
|
|
4089
|
-
|
|
4175
|
+
sessionId,
|
|
4090
4176
|
project.path.path,
|
|
4091
4177
|
layout.claudeArgs,
|
|
4092
4178
|
layout.npmCommand
|
|
4093
4179
|
);
|
|
4094
4180
|
case "two-pane-npm":
|
|
4095
|
-
return tmux.createTwoPaneNpmSession(
|
|
4181
|
+
return tmux.createTwoPaneNpmSession(sessionId, project.path.path, layout.npmCommand);
|
|
4096
4182
|
}
|
|
4097
4183
|
}
|
|
4098
|
-
async function handleSplitTerminal(project, isShellMode, dryRun, shellCommands, events, ctx) {
|
|
4184
|
+
async function handleSplitTerminal(project, isShellMode, dryRun, shellCommands, events, ctx, worktreeName) {
|
|
4185
|
+
const sessionLabel = worktreeName ? `${project.name}::${worktreeName}` : project.name;
|
|
4099
4186
|
const layout = {
|
|
4100
4187
|
type: "split-claude",
|
|
4101
4188
|
handledEvents: ["cwd", "claude"],
|
|
4102
|
-
dryRunMessage: `Would create split tmux session '${
|
|
4189
|
+
dryRunMessage: `Would create split tmux session '${sessionLabel}' with Claude`,
|
|
4103
4190
|
claudeArgs: getClaudeArgs2(project),
|
|
4104
4191
|
npmCommand: null
|
|
4105
4192
|
};
|
|
4106
|
-
await handleTmuxLayout(
|
|
4193
|
+
await handleTmuxLayout(
|
|
4194
|
+
project,
|
|
4195
|
+
layout,
|
|
4196
|
+
{ isShellMode, dryRun },
|
|
4197
|
+
shellCommands,
|
|
4198
|
+
events,
|
|
4199
|
+
ctx,
|
|
4200
|
+
worktreeName
|
|
4201
|
+
);
|
|
4107
4202
|
}
|
|
4108
|
-
async function handleThreePaneLayout(project, isShellMode, dryRun, shellCommands, events, ctx) {
|
|
4203
|
+
async function handleThreePaneLayout(project, isShellMode, dryRun, shellCommands, events, ctx, worktreeName) {
|
|
4204
|
+
const sessionLabel = worktreeName ? `${project.name}::${worktreeName}` : project.name;
|
|
4109
4205
|
const layout = {
|
|
4110
4206
|
type: "three-pane",
|
|
4111
4207
|
handledEvents: ["cwd", "claude", "npm"],
|
|
4112
|
-
dryRunMessage: `Would create three-pane tmux session '${
|
|
4208
|
+
dryRunMessage: `Would create three-pane tmux session '${sessionLabel}' with Claude and NPM`,
|
|
4113
4209
|
claudeArgs: getClaudeArgs2(project),
|
|
4114
4210
|
npmCommand: await getNpmCommand2(project)
|
|
4115
4211
|
};
|
|
4116
|
-
await handleTmuxLayout(
|
|
4212
|
+
await handleTmuxLayout(
|
|
4213
|
+
project,
|
|
4214
|
+
layout,
|
|
4215
|
+
{ isShellMode, dryRun },
|
|
4216
|
+
shellCommands,
|
|
4217
|
+
events,
|
|
4218
|
+
ctx,
|
|
4219
|
+
worktreeName
|
|
4220
|
+
);
|
|
4117
4221
|
}
|
|
4118
|
-
async function handleTwoPaneNpmLayout(project, isShellMode, dryRun, shellCommands, events, ctx) {
|
|
4222
|
+
async function handleTwoPaneNpmLayout(project, isShellMode, dryRun, shellCommands, events, ctx, worktreeName) {
|
|
4223
|
+
const sessionLabel = worktreeName ? `${project.name}::${worktreeName}` : project.name;
|
|
4119
4224
|
const layout = {
|
|
4120
4225
|
type: "two-pane-npm",
|
|
4121
4226
|
handledEvents: ["cwd", "npm"],
|
|
4122
|
-
dryRunMessage: `Would create two-pane tmux session '${
|
|
4227
|
+
dryRunMessage: `Would create two-pane tmux session '${sessionLabel}' with NPM`,
|
|
4123
4228
|
claudeArgs: [],
|
|
4124
4229
|
npmCommand: await getNpmCommand2(project)
|
|
4125
4230
|
};
|
|
4126
|
-
await handleTmuxLayout(
|
|
4231
|
+
await handleTmuxLayout(
|
|
4232
|
+
project,
|
|
4233
|
+
layout,
|
|
4234
|
+
{ isShellMode, dryRun },
|
|
4235
|
+
shellCommands,
|
|
4236
|
+
events,
|
|
4237
|
+
ctx,
|
|
4238
|
+
worktreeName
|
|
4239
|
+
);
|
|
4127
4240
|
}
|
|
4128
4241
|
async function processEvent(event, context, ctx) {
|
|
4129
4242
|
const { log } = ctx;
|
|
@@ -4175,7 +4288,9 @@ Available commands for '${projectName}':`);
|
|
|
4175
4288
|
const twoCommands = configuredEvents.slice(0, 2).join(",");
|
|
4176
4289
|
console.log(` workon ${projectName}:${twoCommands.padEnd(12)} # Multiple commands`);
|
|
4177
4290
|
}
|
|
4178
|
-
console.log(` workon ${projectName}:cwd --shell # Output shell commands
|
|
4291
|
+
console.log(` workon ${projectName}:cwd --shell # Output shell commands`);
|
|
4292
|
+
console.log(` workon ${projectName}:cwd:my-worktree # Run in a worktree`);
|
|
4293
|
+
console.log(` workon ${projectName}::my-worktree # All commands in a worktree
|
|
4179
4294
|
`);
|
|
4180
4295
|
}
|
|
4181
4296
|
var init_open2 = __esm({
|
|
@@ -4184,6 +4299,7 @@ var init_open2 = __esm({
|
|
|
4184
4299
|
init_environment();
|
|
4185
4300
|
init_tmux();
|
|
4186
4301
|
init_registry();
|
|
4302
|
+
init_worktree();
|
|
4187
4303
|
}
|
|
4188
4304
|
});
|
|
4189
4305
|
|
|
@@ -4198,7 +4314,7 @@ import { join as join2, dirname as dirname2 } from "path";
|
|
|
4198
4314
|
import { fileURLToPath } from "url";
|
|
4199
4315
|
import loog from "loog";
|
|
4200
4316
|
import omelette from "omelette";
|
|
4201
|
-
import
|
|
4317
|
+
import File8 from "phylo";
|
|
4202
4318
|
|
|
4203
4319
|
// src/commands/config/index.ts
|
|
4204
4320
|
import { Command as Command12 } from "commander";
|
|
@@ -4295,7 +4411,7 @@ init_registry();
|
|
|
4295
4411
|
init_constants();
|
|
4296
4412
|
import { Command as Command13 } from "commander";
|
|
4297
4413
|
import { select as select5, input as input6, confirm as confirm9, checkbox as checkbox3 } from "@inquirer/prompts";
|
|
4298
|
-
import
|
|
4414
|
+
import File6 from "phylo";
|
|
4299
4415
|
function createManageCommand(ctx) {
|
|
4300
4416
|
const { log } = ctx;
|
|
4301
4417
|
return new Command13("manage").description("Interactive project management").option("-d, --debug", "Enable debug logging").action(async (options) => {
|
|
@@ -4354,12 +4470,12 @@ async function createProject(ctx) {
|
|
|
4354
4470
|
return true;
|
|
4355
4471
|
}
|
|
4356
4472
|
});
|
|
4357
|
-
const defaultPath = defaults?.base ?
|
|
4473
|
+
const defaultPath = defaults?.base ? File6.from(defaults.base).join(name).path : name;
|
|
4358
4474
|
const pathInput = await input6({
|
|
4359
4475
|
message: "Project path:",
|
|
4360
4476
|
default: defaultPath,
|
|
4361
4477
|
validate: (value) => {
|
|
4362
|
-
const path8 =
|
|
4478
|
+
const path8 = File6.from(value);
|
|
4363
4479
|
try {
|
|
4364
4480
|
const exists = path8.exists();
|
|
4365
4481
|
if (!exists) return `Path does not exist: ${value}`;
|
|
@@ -4373,8 +4489,8 @@ async function createProject(ctx) {
|
|
|
4373
4489
|
});
|
|
4374
4490
|
let relativePath = pathInput;
|
|
4375
4491
|
if (defaults?.base) {
|
|
4376
|
-
const baseDir =
|
|
4377
|
-
const pathFile =
|
|
4492
|
+
const baseDir = File6.from(defaults.base).absolutify();
|
|
4493
|
+
const pathFile = File6.from(pathInput);
|
|
4378
4494
|
try {
|
|
4379
4495
|
const relPath = pathFile.relativize(baseDir.path);
|
|
4380
4496
|
if (relPath && !relPath.path.startsWith("..")) {
|
|
@@ -4451,8 +4567,8 @@ async function editProject(ctx) {
|
|
|
4451
4567
|
});
|
|
4452
4568
|
let relativePath = pathInput;
|
|
4453
4569
|
if (defaults?.base) {
|
|
4454
|
-
const baseDir =
|
|
4455
|
-
const pathFile =
|
|
4570
|
+
const baseDir = File6.from(defaults.base).absolutify();
|
|
4571
|
+
const pathFile = File6.from(pathInput);
|
|
4456
4572
|
try {
|
|
4457
4573
|
if (pathFile.isAbsolute()) {
|
|
4458
4574
|
const relPath = pathFile.relativize(baseDir.path);
|
|
@@ -4552,7 +4668,7 @@ async function listProjects(ctx) {
|
|
|
4552
4668
|
const defaults = config.getDefaults();
|
|
4553
4669
|
console.log("\nConfigured projects:\n");
|
|
4554
4670
|
for (const [name, project] of Object.entries(projects)) {
|
|
4555
|
-
const fullPath = defaults?.base ?
|
|
4671
|
+
const fullPath = defaults?.base ? File6.from(defaults.base).join(project.path).path : project.path;
|
|
4556
4672
|
console.log(` ${name}`);
|
|
4557
4673
|
console.log(` Path: ${fullPath}`);
|
|
4558
4674
|
console.log(` IDE: ${project.ide || "not set"}`);
|
|
@@ -4568,7 +4684,7 @@ async function listProjects(ctx) {
|
|
|
4568
4684
|
import { Command as Command14 } from "commander";
|
|
4569
4685
|
import { existsSync as existsSync6, readFileSync } from "fs";
|
|
4570
4686
|
import { basename as basename2, resolve } from "path";
|
|
4571
|
-
import
|
|
4687
|
+
import File7 from "phylo";
|
|
4572
4688
|
import { confirm as confirm10 } from "@inquirer/prompts";
|
|
4573
4689
|
function createAddCommand2(ctx) {
|
|
4574
4690
|
const { log } = ctx;
|
|
@@ -4592,7 +4708,7 @@ async function addProject(pathArg, options, ctx) {
|
|
|
4592
4708
|
log.error(`Path does not exist: ${targetPath}`);
|
|
4593
4709
|
process.exit(1);
|
|
4594
4710
|
}
|
|
4595
|
-
const pathFile =
|
|
4711
|
+
const pathFile = File7.from(targetPath);
|
|
4596
4712
|
try {
|
|
4597
4713
|
const stat = pathFile.stat();
|
|
4598
4714
|
if (!stat.isDirectory()) {
|
|
@@ -4626,7 +4742,7 @@ async function addProject(pathArg, options, ctx) {
|
|
|
4626
4742
|
log.debug(`IDE: ${ide}`);
|
|
4627
4743
|
let relativePath = targetPath;
|
|
4628
4744
|
if (defaults?.base) {
|
|
4629
|
-
const baseDir =
|
|
4745
|
+
const baseDir = File7.from(defaults.base).absolutify();
|
|
4630
4746
|
try {
|
|
4631
4747
|
const relPath = pathFile.relativize(baseDir.path);
|
|
4632
4748
|
if (relPath && !relPath.path.startsWith("..")) {
|
|
@@ -4755,19 +4871,21 @@ function createWorktreeCommand(ctx) {
|
|
|
4755
4871
|
}
|
|
4756
4872
|
await showWorktreeStatus(worktreeInfo, log);
|
|
4757
4873
|
});
|
|
4758
|
-
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").
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4874
|
+
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(
|
|
4875
|
+
async (options) => {
|
|
4876
|
+
const worktreeInfo = await detectWorktreeContext();
|
|
4877
|
+
if (!worktreeInfo) {
|
|
4878
|
+
log.error("Not in a git repository.");
|
|
4879
|
+
process.exit(1);
|
|
4880
|
+
}
|
|
4881
|
+
if (!worktreeInfo.isWorktree) {
|
|
4882
|
+
log.error("You're in the main repository, not a worktree.");
|
|
4883
|
+
log.info(`Use 'workon worktrees merge <name>' from the main repository.`);
|
|
4884
|
+
process.exit(1);
|
|
4885
|
+
}
|
|
4886
|
+
await mergeCurrentWorktree(worktreeInfo, options, ctx);
|
|
4768
4887
|
}
|
|
4769
|
-
|
|
4770
|
-
});
|
|
4888
|
+
);
|
|
4771
4889
|
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) => {
|
|
4772
4890
|
const worktreeInfo = await detectWorktreeContext();
|
|
4773
4891
|
if (!worktreeInfo) {
|
|
@@ -4852,11 +4970,16 @@ async function mergeCurrentWorktree(worktreeInfo, options, ctx) {
|
|
|
4852
4970
|
}
|
|
4853
4971
|
const commonTargets = ["main", "master", "develop", "dev"];
|
|
4854
4972
|
const defaultTarget = commonTargets.find((t) => targetBranches.includes(t)) || targetBranches[0];
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4973
|
+
if (options.yes) {
|
|
4974
|
+
targetBranch = defaultTarget;
|
|
4975
|
+
log.info(`Auto-selected target branch: '${targetBranch}'`);
|
|
4976
|
+
} else {
|
|
4977
|
+
targetBranch = await select6({
|
|
4978
|
+
message: `Merge '${worktreeInfo.branch}' into which branch?`,
|
|
4979
|
+
choices: targetBranches.map((b) => ({ name: b, value: b })),
|
|
4980
|
+
default: defaultTarget
|
|
4981
|
+
});
|
|
4982
|
+
}
|
|
4860
4983
|
}
|
|
4861
4984
|
if (!await manager.branchExists(targetBranch)) {
|
|
4862
4985
|
log.error(`Target branch '${targetBranch}' does not exist.`);
|
|
@@ -4898,12 +5021,18 @@ ${chalk8.bold("Merge operation:")}`);
|
|
|
4898
5021
|
log.info("You may need to resolve conflicts manually.");
|
|
4899
5022
|
process.exit(1);
|
|
4900
5023
|
}
|
|
5024
|
+
if (options.keep && options.deleteBranch) {
|
|
5025
|
+
log.warn("--delete-branch is ignored when --keep is set (branch is needed by the worktree).");
|
|
5026
|
+
}
|
|
4901
5027
|
if (!options.keep) {
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
5028
|
+
let shouldContinue = true;
|
|
5029
|
+
if (!options.yes) {
|
|
5030
|
+
log.warn("You need to exit this worktree directory before it can be removed.");
|
|
5031
|
+
shouldContinue = await confirm11({
|
|
5032
|
+
message: `Remove worktree '${worktreeInfo.worktreeName}'? (You'll need to cd out first)`,
|
|
5033
|
+
default: true
|
|
5034
|
+
});
|
|
5035
|
+
}
|
|
4907
5036
|
if (shouldContinue) {
|
|
4908
5037
|
const tmux = new TmuxManager();
|
|
4909
5038
|
const sessionName = tmux.getWorktreeSessionName(
|
|
@@ -4919,20 +5048,21 @@ To complete removal, run from the main project directory:`);
|
|
|
4919
5048
|
console.log(chalk8.cyan(` cd ${worktreeInfo.mainRepoPath}`));
|
|
4920
5049
|
console.log(chalk8.cyan(` workon worktrees remove ${worktreeInfo.worktreeName}`));
|
|
4921
5050
|
}
|
|
4922
|
-
|
|
4923
|
-
|
|
5051
|
+
let shouldDeleteBranch = options.deleteBranch || false;
|
|
5052
|
+
if (!shouldDeleteBranch && !options.yes) {
|
|
5053
|
+
shouldDeleteBranch = await confirm11({
|
|
4924
5054
|
message: `Delete the merged branch '${worktreeInfo.branch}'?`,
|
|
4925
5055
|
default: false
|
|
4926
5056
|
});
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
}
|
|
4934
|
-
|
|
4935
|
-
}
|
|
5057
|
+
}
|
|
5058
|
+
if (shouldDeleteBranch) {
|
|
5059
|
+
const deleteSpinner = ora5(`Deleting branch '${worktreeInfo.branch}'...`).start();
|
|
5060
|
+
try {
|
|
5061
|
+
const git = simpleGit5(worktreeInfo.mainRepoPath);
|
|
5062
|
+
await git.deleteLocalBranch(worktreeInfo.branch, true);
|
|
5063
|
+
deleteSpinner.succeed(`Branch '${worktreeInfo.branch}' deleted`);
|
|
5064
|
+
} catch (error) {
|
|
5065
|
+
deleteSpinner.warn(`Failed to delete branch: ${error.message}`);
|
|
4936
5066
|
}
|
|
4937
5067
|
}
|
|
4938
5068
|
}
|
|
@@ -5046,7 +5176,7 @@ function createCli() {
|
|
|
5046
5176
|
await runOpen2(project, { debug: options.debug, shell: options.shell }, { config, log });
|
|
5047
5177
|
return;
|
|
5048
5178
|
}
|
|
5049
|
-
const environment = await EnvironmentRecognizer.recognize(
|
|
5179
|
+
const environment = await EnvironmentRecognizer.recognize(File8.cwd());
|
|
5050
5180
|
program2.setOptionValue("_environment", environment);
|
|
5051
5181
|
program2.setOptionValue("_config", config);
|
|
5052
5182
|
program2.setOptionValue("_log", log);
|