workon 3.5.1 → 3.6.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/dist/cli.js +119 -31
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1458,7 +1458,7 @@ function deriveProjectIdentifier(projectPath) {
|
|
|
1458
1458
|
function getWorktreesDirForProject(projectIdentifier) {
|
|
1459
1459
|
return join(homedir(), WORKON_DIR, WORKTREES_SUBDIR, projectIdentifier);
|
|
1460
1460
|
}
|
|
1461
|
-
var exec2, WORKON_DIR, WORKTREES_SUBDIR, HOOK_DIR, SETUP_HOOK, WorktreeManager;
|
|
1461
|
+
var exec2, WORKON_DIR, WORKTREES_SUBDIR, HOOK_DIR, SETUP_HOOK, TEARDOWN_HOOK, WorktreeManager;
|
|
1462
1462
|
var init_worktree = __esm({
|
|
1463
1463
|
"src/lib/worktree.ts"() {
|
|
1464
1464
|
"use strict";
|
|
@@ -1467,6 +1467,7 @@ var init_worktree = __esm({
|
|
|
1467
1467
|
WORKTREES_SUBDIR = "worktrees";
|
|
1468
1468
|
HOOK_DIR = ".workon";
|
|
1469
1469
|
SETUP_HOOK = "worktree-setup.sh";
|
|
1470
|
+
TEARDOWN_HOOK = "worktree-teardown.sh";
|
|
1470
1471
|
WorktreeManager = class {
|
|
1471
1472
|
projectPath;
|
|
1472
1473
|
projectIdentifier;
|
|
@@ -1569,11 +1570,12 @@ var init_worktree = __esm({
|
|
|
1569
1570
|
if (worktree.isMain) {
|
|
1570
1571
|
throw new Error("Cannot remove the main worktree");
|
|
1571
1572
|
}
|
|
1572
|
-
|
|
1573
|
+
const pathMissing = !existsSync2(worktree.path);
|
|
1574
|
+
if (!pathMissing && !force && await this.hasUncommittedChanges(name)) {
|
|
1573
1575
|
throw new Error(`Worktree '${name}' has uncommitted changes. Use --force to remove anyway.`);
|
|
1574
1576
|
}
|
|
1575
1577
|
const args = ["worktree", "remove"];
|
|
1576
|
-
if (force) {
|
|
1578
|
+
if (force || pathMissing) {
|
|
1577
1579
|
args.push("--force");
|
|
1578
1580
|
}
|
|
1579
1581
|
args.push(worktree.path);
|
|
@@ -1592,6 +1594,9 @@ var init_worktree = __esm({
|
|
|
1592
1594
|
if (!worktree) {
|
|
1593
1595
|
throw new Error(`Worktree '${nameOrPath}' not found`);
|
|
1594
1596
|
}
|
|
1597
|
+
if (!existsSync2(worktree.path)) {
|
|
1598
|
+
return false;
|
|
1599
|
+
}
|
|
1595
1600
|
const worktreeGit = simpleGit2(worktree.path);
|
|
1596
1601
|
const status = await worktreeGit.status();
|
|
1597
1602
|
return !status.isClean();
|
|
@@ -1680,6 +1685,42 @@ var init_worktree = __esm({
|
|
|
1680
1685
|
});
|
|
1681
1686
|
return { stdout, stderr };
|
|
1682
1687
|
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Check if a pre-teardown hook exists
|
|
1690
|
+
*/
|
|
1691
|
+
hasTeardownHook() {
|
|
1692
|
+
const hookPath = this.getTeardownHookPath();
|
|
1693
|
+
return existsSync2(hookPath);
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Get the path to the teardown hook
|
|
1697
|
+
*/
|
|
1698
|
+
getTeardownHookPath() {
|
|
1699
|
+
return join(this.projectPath, HOOK_DIR, TEARDOWN_HOOK);
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Run the pre-teardown hook for a worktree
|
|
1703
|
+
*/
|
|
1704
|
+
async runPreTeardownHook(worktreePath) {
|
|
1705
|
+
const hookPath = this.getTeardownHookPath();
|
|
1706
|
+
if (!existsSync2(hookPath)) {
|
|
1707
|
+
throw new Error("Teardown hook not found");
|
|
1708
|
+
}
|
|
1709
|
+
try {
|
|
1710
|
+
chmodSync(hookPath, "755");
|
|
1711
|
+
} catch {
|
|
1712
|
+
}
|
|
1713
|
+
const env = {
|
|
1714
|
+
...process.env,
|
|
1715
|
+
WORKTREE_PATH: worktreePath,
|
|
1716
|
+
PROJECT_PATH: this.projectPath
|
|
1717
|
+
};
|
|
1718
|
+
const { stdout, stderr } = await exec2(hookPath, {
|
|
1719
|
+
cwd: worktreePath,
|
|
1720
|
+
env
|
|
1721
|
+
});
|
|
1722
|
+
return { stdout, stderr };
|
|
1723
|
+
}
|
|
1683
1724
|
/**
|
|
1684
1725
|
* Parse the porcelain output of git worktree list
|
|
1685
1726
|
*/
|
|
@@ -1933,6 +1974,7 @@ var init_utils = __esm({
|
|
|
1933
1974
|
import { Command } from "commander";
|
|
1934
1975
|
import chalk from "chalk";
|
|
1935
1976
|
import path2 from "path";
|
|
1977
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1936
1978
|
function createListCommand(ctx) {
|
|
1937
1979
|
const { config, log } = ctx;
|
|
1938
1980
|
return new Command("list").description("List worktrees for the current project").option("-a, --all", "Show all worktrees (including main)").action(async (options) => {
|
|
@@ -1966,9 +2008,10 @@ Worktrees for ${displayName}:`));
|
|
|
1966
2008
|
for (const wt of worktrees) {
|
|
1967
2009
|
const isManaged = managedPaths.has(wt.path);
|
|
1968
2010
|
const mainLabel = wt.isMain ? chalk.gray(" (main)") : "";
|
|
1969
|
-
const
|
|
2011
|
+
const missingLabel = !wt.isMain && !existsSync3(wt.path) ? chalk.red(" (missing)") : "";
|
|
2012
|
+
const externalLabel = !wt.isMain && !isManaged && !missingLabel ? chalk.yellow(" (external)") : "";
|
|
1970
2013
|
const branchDisplay = wt.branch === "(detached)" ? chalk.yellow(wt.branch) : chalk.green(wt.branch);
|
|
1971
|
-
console.log(` ${chalk.cyan(wt.name)}${mainLabel}${externalLabel}`);
|
|
2014
|
+
console.log(` ${chalk.cyan(wt.name)}${mainLabel}${missingLabel}${externalLabel}`);
|
|
1972
2015
|
console.log(` Branch: ${branchDisplay}`);
|
|
1973
2016
|
console.log(` Path: ${chalk.gray(wt.path)}`);
|
|
1974
2017
|
console.log(` HEAD: ${chalk.gray(wt.head.substring(0, 8))}`);
|
|
@@ -2105,10 +2148,11 @@ import { Command as Command3 } from "commander";
|
|
|
2105
2148
|
import chalk3 from "chalk";
|
|
2106
2149
|
import ora2 from "ora";
|
|
2107
2150
|
import path3 from "path";
|
|
2151
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2108
2152
|
import { confirm as confirm5 } from "@inquirer/prompts";
|
|
2109
2153
|
function createRemoveCommand(ctx) {
|
|
2110
2154
|
const { config, log } = ctx;
|
|
2111
|
-
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) => {
|
|
2155
|
+
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) => {
|
|
2112
2156
|
const projectCtx = await resolveProjectFromCwd(config, log);
|
|
2113
2157
|
if (!projectCtx) {
|
|
2114
2158
|
log.error("Not in a git repository. Run this command from within a git project.");
|
|
@@ -2134,22 +2178,29 @@ function createRemoveCommand(ctx) {
|
|
|
2134
2178
|
log.error("Cannot remove the main worktree");
|
|
2135
2179
|
process.exit(1);
|
|
2136
2180
|
}
|
|
2137
|
-
const
|
|
2138
|
-
if (
|
|
2139
|
-
log.warn(`Worktree
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2181
|
+
const pathMissing = !existsSync4(worktree.path);
|
|
2182
|
+
if (pathMissing) {
|
|
2183
|
+
log.warn(`Worktree directory is missing from disk: ${worktree.path}`);
|
|
2184
|
+
options.force = true;
|
|
2185
|
+
}
|
|
2186
|
+
if (!pathMissing) {
|
|
2187
|
+
const hasChanges = await manager.hasUncommittedChanges(name);
|
|
2188
|
+
if (hasChanges && !options.force) {
|
|
2189
|
+
log.warn(`Worktree '${name}' has uncommitted changes.`);
|
|
2190
|
+
if (!options.yes) {
|
|
2191
|
+
const shouldForce = await confirm5({
|
|
2192
|
+
message: "Do you want to force removal and lose these changes?",
|
|
2193
|
+
default: false
|
|
2194
|
+
});
|
|
2195
|
+
if (!shouldForce) {
|
|
2196
|
+
log.info("Removal cancelled.");
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
options.force = true;
|
|
2200
|
+
} else {
|
|
2201
|
+
log.error("Use --force to remove worktrees with uncommitted changes.");
|
|
2202
|
+
process.exit(1);
|
|
2148
2203
|
}
|
|
2149
|
-
options.force = true;
|
|
2150
|
-
} else {
|
|
2151
|
-
log.error("Use --force to remove worktrees with uncommitted changes.");
|
|
2152
|
-
process.exit(1);
|
|
2153
2204
|
}
|
|
2154
2205
|
}
|
|
2155
2206
|
if (!options.yes) {
|
|
@@ -2174,6 +2225,21 @@ ${chalk3.bold("Worktree to remove:")}`);
|
|
|
2174
2225
|
log.debug(`Killing tmux session: ${sessionName}`);
|
|
2175
2226
|
await tmux.killSession(sessionName);
|
|
2176
2227
|
}
|
|
2228
|
+
if (options.hook !== false && !pathMissing && manager.hasTeardownHook()) {
|
|
2229
|
+
const hookSpinner = ora2("Running pre-teardown hook...").start();
|
|
2230
|
+
try {
|
|
2231
|
+
const { stdout, stderr } = await manager.runPreTeardownHook(worktree.path);
|
|
2232
|
+
hookSpinner.succeed("Pre-teardown hook completed");
|
|
2233
|
+
if (stdout.trim()) {
|
|
2234
|
+
console.log(chalk3.gray(stdout.trim()));
|
|
2235
|
+
}
|
|
2236
|
+
if (stderr.trim()) {
|
|
2237
|
+
console.log(chalk3.yellow(stderr.trim()));
|
|
2238
|
+
}
|
|
2239
|
+
} catch (error) {
|
|
2240
|
+
hookSpinner.warn(`Pre-teardown hook failed: ${error.message}`);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2177
2243
|
const spinner = ora2(`Removing worktree '${name}'...`).start();
|
|
2178
2244
|
try {
|
|
2179
2245
|
await manager.remove(name, options.force);
|
|
@@ -2788,6 +2854,7 @@ __export(interactive_exports, {
|
|
|
2788
2854
|
runInteractive: () => runInteractive
|
|
2789
2855
|
});
|
|
2790
2856
|
import { select as select4, input as input5, checkbox as checkbox2, confirm as confirm8 } from "@inquirer/prompts";
|
|
2857
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2791
2858
|
import File5 from "phylo";
|
|
2792
2859
|
import path7 from "path";
|
|
2793
2860
|
import deepAssign2 from "deep-assign";
|
|
@@ -3607,6 +3674,18 @@ async function removeWorktreeManage(projectName, manager, log) {
|
|
|
3607
3674
|
});
|
|
3608
3675
|
if (confirmed) {
|
|
3609
3676
|
try {
|
|
3677
|
+
if (manager.hasTeardownHook()) {
|
|
3678
|
+
const worktree = await manager.get(worktreeName);
|
|
3679
|
+
if (worktree && existsSync5(worktree.path)) {
|
|
3680
|
+
log.info("Running pre-teardown hook...");
|
|
3681
|
+
try {
|
|
3682
|
+
await manager.runPreTeardownHook(worktree.path);
|
|
3683
|
+
log.info("Pre-teardown hook completed.");
|
|
3684
|
+
} catch (error) {
|
|
3685
|
+
log.warn(`Pre-teardown hook failed: ${error.message}`);
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3610
3689
|
await manager.remove(worktreeName, true);
|
|
3611
3690
|
log.info(`Worktree '${worktreeName}' removed.`);
|
|
3612
3691
|
} catch (error) {
|
|
@@ -3656,6 +3735,15 @@ async function mergeWorktreeManage(projectName, manager, log) {
|
|
|
3656
3735
|
await manager.merge(worktreeName, { targetBranch, squash });
|
|
3657
3736
|
log.info(`Merged '${worktree.branch}' into '${targetBranch}'`);
|
|
3658
3737
|
if (removeAfter) {
|
|
3738
|
+
if (manager.hasTeardownHook() && existsSync5(worktree.path)) {
|
|
3739
|
+
log.info("Running pre-teardown hook...");
|
|
3740
|
+
try {
|
|
3741
|
+
await manager.runPreTeardownHook(worktree.path);
|
|
3742
|
+
log.info("Pre-teardown hook completed.");
|
|
3743
|
+
} catch (error) {
|
|
3744
|
+
log.warn(`Pre-teardown hook failed: ${error.message}`);
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3659
3747
|
await manager.remove(worktreeName, true);
|
|
3660
3748
|
log.info(`Worktree '${worktreeName}' removed.`);
|
|
3661
3749
|
}
|
|
@@ -4105,7 +4193,7 @@ init_environment();
|
|
|
4105
4193
|
init_registry();
|
|
4106
4194
|
init_open2();
|
|
4107
4195
|
import { Command as Command16 } from "commander";
|
|
4108
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
4196
|
+
import { readFileSync as readFileSync2, existsSync as existsSync7 } from "fs";
|
|
4109
4197
|
import { join as join2, dirname as dirname2 } from "path";
|
|
4110
4198
|
import { fileURLToPath } from "url";
|
|
4111
4199
|
import loog from "loog";
|
|
@@ -4478,7 +4566,7 @@ async function listProjects(ctx) {
|
|
|
4478
4566
|
|
|
4479
4567
|
// src/commands/add.ts
|
|
4480
4568
|
import { Command as Command14 } from "commander";
|
|
4481
|
-
import { existsSync as
|
|
4569
|
+
import { existsSync as existsSync6, readFileSync } from "fs";
|
|
4482
4570
|
import { basename as basename2, resolve } from "path";
|
|
4483
4571
|
import File8 from "phylo";
|
|
4484
4572
|
import { confirm as confirm10 } from "@inquirer/prompts";
|
|
@@ -4500,7 +4588,7 @@ async function addProject(pathArg, options, ctx) {
|
|
|
4500
4588
|
const projects = config.getProjects();
|
|
4501
4589
|
const targetPath = resolve(pathArg);
|
|
4502
4590
|
log.debug(`Resolved path: ${targetPath}`);
|
|
4503
|
-
if (!
|
|
4591
|
+
if (!existsSync6(targetPath)) {
|
|
4504
4592
|
log.error(`Path does not exist: ${targetPath}`);
|
|
4505
4593
|
process.exit(1);
|
|
4506
4594
|
}
|
|
@@ -4584,7 +4672,7 @@ function discoverProject(targetPath, log) {
|
|
|
4584
4672
|
packageJson: null
|
|
4585
4673
|
};
|
|
4586
4674
|
const packageJsonPath = resolve(targetPath, "package.json");
|
|
4587
|
-
if (
|
|
4675
|
+
if (existsSync6(packageJsonPath)) {
|
|
4588
4676
|
discovery.isNode = true;
|
|
4589
4677
|
log.debug("Detected Node project (package.json found)");
|
|
4590
4678
|
try {
|
|
@@ -4600,25 +4688,25 @@ function discoverProject(targetPath, log) {
|
|
|
4600
4688
|
}
|
|
4601
4689
|
}
|
|
4602
4690
|
const bunLockPath = resolve(targetPath, "bun.lockb");
|
|
4603
|
-
if (
|
|
4691
|
+
if (existsSync6(bunLockPath)) {
|
|
4604
4692
|
discovery.isBun = true;
|
|
4605
4693
|
log.debug("Detected Bun project (bun.lockb found)");
|
|
4606
4694
|
}
|
|
4607
4695
|
const vscodeDir = resolve(targetPath, ".vscode");
|
|
4608
4696
|
const cursorDir = resolve(targetPath, ".cursor");
|
|
4609
4697
|
const ideaDir = resolve(targetPath, ".idea");
|
|
4610
|
-
if (
|
|
4698
|
+
if (existsSync6(cursorDir)) {
|
|
4611
4699
|
discovery.detectedIde = "cursor";
|
|
4612
4700
|
log.debug("Detected Cursor (.cursor directory found)");
|
|
4613
|
-
} else if (
|
|
4701
|
+
} else if (existsSync6(vscodeDir)) {
|
|
4614
4702
|
discovery.detectedIde = "code";
|
|
4615
4703
|
log.debug("Detected VS Code (.vscode directory found)");
|
|
4616
|
-
} else if (
|
|
4704
|
+
} else if (existsSync6(ideaDir)) {
|
|
4617
4705
|
discovery.detectedIde = "idea";
|
|
4618
4706
|
log.debug("Detected IntelliJ IDEA (.idea directory found)");
|
|
4619
4707
|
}
|
|
4620
4708
|
const claudeMdPath = resolve(targetPath, "CLAUDE.md");
|
|
4621
|
-
if (
|
|
4709
|
+
if (existsSync6(claudeMdPath)) {
|
|
4622
4710
|
discovery.hasClaude = true;
|
|
4623
4711
|
log.debug("Detected Claude Code project (CLAUDE.md found)");
|
|
4624
4712
|
}
|
|
@@ -4914,7 +5002,7 @@ function findPackageJson() {
|
|
|
4914
5002
|
join2(process.cwd(), "package.json")
|
|
4915
5003
|
];
|
|
4916
5004
|
for (const p of paths) {
|
|
4917
|
-
if (
|
|
5005
|
+
if (existsSync7(p)) {
|
|
4918
5006
|
return p;
|
|
4919
5007
|
}
|
|
4920
5008
|
}
|