workon 3.4.0 → 3.5.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/dist/cli.js CHANGED
@@ -269,10 +269,11 @@ var init_project = __esm({
269
269
  return this._events;
270
270
  }
271
271
  set path(path8) {
272
- if (this._base) {
272
+ const pathFile = File.from(path8);
273
+ if (this._base && !pathFile.isAbsolute()) {
273
274
  this._path = this._base.join(path8);
274
275
  } else {
275
- this._path = File.from(path8);
276
+ this._path = pathFile;
276
277
  }
277
278
  this._path = this._path.absolutify();
278
279
  }
@@ -365,17 +366,17 @@ var init_environment = __esm({
365
366
  return this.projects;
366
367
  }
367
368
  const defaults = this.config.getDefaults();
368
- if (!defaults?.base) {
369
- this.projects = [];
370
- return this.projects;
371
- }
372
- const baseDir = File2.from(defaults.base);
369
+ const baseDir = defaults?.base ? File2.from(defaults.base).absolutify() : null;
373
370
  const projectsMap = this.config.getProjects();
374
- this.projects = Object.entries(projectsMap).map(([name, project]) => ({
375
- ...project,
376
- name,
377
- path: baseDir.join(project.path)
378
- }));
371
+ this.projects = Object.entries(projectsMap).map(([name, project]) => {
372
+ const projectPath = File2.from(project.path);
373
+ const resolvedPath = baseDir && !projectPath.isAbsolute() ? baseDir.join(project.path) : projectPath;
374
+ return {
375
+ ...project,
376
+ name,
377
+ path: resolvedPath
378
+ };
379
+ });
379
380
  return this.projects;
380
381
  }
381
382
  static getProjectEnvironment(base, _matching) {
@@ -1443,20 +1444,36 @@ import { exec as execCallback2 } from "child_process";
1443
1444
  import { promisify as promisify2 } from "util";
1444
1445
  import { existsSync as existsSync2, chmodSync } from "fs";
1445
1446
  import { join, basename } from "path";
1447
+ import { createHash } from "crypto";
1448
+ import { homedir } from "os";
1446
1449
  import { simpleGit as simpleGit2 } from "simple-git";
1447
- var exec2, WORKTREES_DIR, HOOK_DIR, SETUP_HOOK, WorktreeManager;
1450
+ function shortHash(input7) {
1451
+ return createHash("sha256").update(input7).digest("hex").substring(0, 8);
1452
+ }
1453
+ function deriveProjectIdentifier(projectPath) {
1454
+ const name = basename(projectPath);
1455
+ const hash = shortHash(projectPath);
1456
+ return `${name}-${hash}`;
1457
+ }
1458
+ function getWorktreesDirForProject(projectIdentifier) {
1459
+ return join(homedir(), WORKON_DIR, WORKTREES_SUBDIR, projectIdentifier);
1460
+ }
1461
+ var exec2, WORKON_DIR, WORKTREES_SUBDIR, HOOK_DIR, SETUP_HOOK, WorktreeManager;
1448
1462
  var init_worktree = __esm({
1449
1463
  "src/lib/worktree.ts"() {
1450
1464
  "use strict";
1451
1465
  exec2 = promisify2(execCallback2);
1452
- WORKTREES_DIR = ".worktrees";
1466
+ WORKON_DIR = ".workon";
1467
+ WORKTREES_SUBDIR = "worktrees";
1453
1468
  HOOK_DIR = ".workon";
1454
1469
  SETUP_HOOK = "worktree-setup.sh";
1455
1470
  WorktreeManager = class {
1456
1471
  projectPath;
1472
+ projectIdentifier;
1457
1473
  git;
1458
- constructor(projectPath) {
1474
+ constructor(projectPath, _projectName) {
1459
1475
  this.projectPath = projectPath;
1476
+ this.projectIdentifier = deriveProjectIdentifier(projectPath);
1460
1477
  this.git = simpleGit2(projectPath);
1461
1478
  }
1462
1479
  /**
@@ -1472,9 +1489,10 @@ var init_worktree = __esm({
1472
1489
  }
1473
1490
  /**
1474
1491
  * Get the worktrees directory path
1492
+ * Stored at ~/.workon/worktrees/{project-identifier}/
1475
1493
  */
1476
1494
  getWorktreesDir() {
1477
- return join(this.projectPath, WORKTREES_DIR);
1495
+ return join(homedir(), WORKON_DIR, WORKTREES_SUBDIR, this.projectIdentifier);
1478
1496
  }
1479
1497
  /**
1480
1498
  * Convert branch name to directory name (replace slashes with dashes)
@@ -1765,7 +1783,8 @@ async function findWorktreeNameByPath(mainRepoPath, worktreePath) {
1765
1783
  const git = simpleGit3(mainRepoPath);
1766
1784
  const result = await git.raw(["worktree", "list", "--porcelain"]);
1767
1785
  const blocks = result.trim().split("\n\n");
1768
- const worktreesDir = path.join(mainRepoPath, ".worktrees");
1786
+ const projectIdentifier = deriveProjectIdentifier(mainRepoPath);
1787
+ const projectWorktreesDir = getWorktreesDirForProject(projectIdentifier);
1769
1788
  for (const block of blocks) {
1770
1789
  if (!block.trim()) continue;
1771
1790
  const lines = block.split("\n");
@@ -1781,7 +1800,7 @@ async function findWorktreeNameByPath(mainRepoPath, worktreePath) {
1781
1800
  }
1782
1801
  }
1783
1802
  if (wtPath === worktreePath) {
1784
- if (wtPath.startsWith(worktreesDir)) {
1803
+ if (wtPath.startsWith(projectWorktreesDir)) {
1785
1804
  return path.basename(wtPath);
1786
1805
  } else {
1787
1806
  return branch && branch !== "(detached)" ? branch.replace(/\//g, "-") : path.basename(wtPath);
@@ -1904,6 +1923,7 @@ async function promptToRegisterProject(projectPath, config, log) {
1904
1923
  var init_utils = __esm({
1905
1924
  "src/commands/worktrees/utils.ts"() {
1906
1925
  "use strict";
1926
+ init_worktree();
1907
1927
  init_registry();
1908
1928
  init_constants();
1909
1929
  }
@@ -1926,7 +1946,7 @@ function createListCommand(ctx) {
1926
1946
  }
1927
1947
  const { projectPath, projectName } = projectCtx;
1928
1948
  const displayName = projectName || path2.basename(projectPath);
1929
- const manager = new WorktreeManager(projectPath);
1949
+ const manager = new WorktreeManager(projectPath, projectName ?? void 0);
1930
1950
  const allWorktrees = await manager.list();
1931
1951
  const managedWorktrees = await manager.listManagedWorktrees();
1932
1952
  const managedPaths = new Set(managedWorktrees.map((wt) => wt.path));
@@ -1990,7 +2010,7 @@ function createAddCommand(ctx) {
1990
2010
  }
1991
2011
  }
1992
2012
  const { projectPath, projectName } = projectCtx;
1993
- const manager = new WorktreeManager(projectPath);
2013
+ const manager = new WorktreeManager(projectPath, projectName ?? void 0);
1994
2014
  const branchExists = await manager.branchExists(branch);
1995
2015
  let baseBranch = options.base;
1996
2016
  if (!branchExists && !baseBranch) {
@@ -2099,7 +2119,7 @@ function createRemoveCommand(ctx) {
2099
2119
  }
2100
2120
  const { projectPath, projectName } = projectCtx;
2101
2121
  const displayName = projectName || path3.basename(projectPath);
2102
- const manager = new WorktreeManager(projectPath);
2122
+ const manager = new WorktreeManager(projectPath, projectName ?? void 0);
2103
2123
  const worktree = await manager.get(name);
2104
2124
  if (!worktree) {
2105
2125
  log.error(`Worktree '${name}' not found for '${displayName}'`);
@@ -2193,7 +2213,7 @@ function createMergeCommand(ctx) {
2193
2213
  }
2194
2214
  const { projectPath, projectName } = projectCtx;
2195
2215
  const displayName = projectName || path4.basename(projectPath);
2196
- const manager = new WorktreeManager(projectPath);
2216
+ const manager = new WorktreeManager(projectPath, projectName ?? void 0);
2197
2217
  const worktree = await manager.get(name);
2198
2218
  if (!worktree) {
2199
2219
  log.error(`Worktree '${name}' not found for '${displayName}'`);
@@ -2339,7 +2359,7 @@ function createBranchCommand(ctx) {
2339
2359
  }
2340
2360
  const { projectPath, projectName } = projectCtx;
2341
2361
  const displayName = projectName || path5.basename(projectPath);
2342
- const manager = new WorktreeManager(projectPath);
2362
+ const manager = new WorktreeManager(projectPath, projectName ?? void 0);
2343
2363
  const worktree = await manager.get(worktreeName);
2344
2364
  if (!worktree) {
2345
2365
  log.error(`Worktree '${worktreeName}' not found for '${displayName}'`);
@@ -2527,7 +2547,7 @@ async function runWorktreeOpen(projectCtx, worktreeName, options, ctx) {
2527
2547
  const { config, log } = ctx;
2528
2548
  const { projectPath, projectName, projectConfig, isRegistered } = projectCtx;
2529
2549
  const displayName = projectName || path6.basename(projectPath);
2530
- const manager = new WorktreeManager(projectPath);
2550
+ const manager = new WorktreeManager(projectPath, projectName ?? void 0);
2531
2551
  const worktree = await manager.get(worktreeName);
2532
2552
  if (!worktree) {
2533
2553
  log.error(`Worktree '${worktreeName}' not found for '${displayName}'`);
@@ -2555,6 +2575,7 @@ async function runWorktreeOpen(projectCtx, worktreeName, options, ctx) {
2555
2575
  hasClaudeEvent = !!events.claude;
2556
2576
  hasNpmEvent = !!events.npm;
2557
2577
  }
2578
+ const shouldAttach = options.attach !== false;
2558
2579
  if (isShellMode) {
2559
2580
  const shellCommands = await buildWorktreeShellCommands(
2560
2581
  project,
@@ -2571,10 +2592,20 @@ async function runWorktreeOpen(projectCtx, worktreeName, options, ctx) {
2571
2592
  hasClaudeEvent,
2572
2593
  hasNpmEvent
2573
2594
  });
2574
- await tmux.attachToSession(sessionName);
2575
- console.log(
2576
- chalk6.green(`Opened worktree '${worktreeName}' in tmux session '${sessionName}'`)
2577
- );
2595
+ if (shouldAttach) {
2596
+ await tmux.attachToSession(sessionName);
2597
+ console.log(
2598
+ chalk6.green(`Opened worktree '${worktreeName}' in tmux session '${sessionName}'`)
2599
+ );
2600
+ } else {
2601
+ console.log(
2602
+ chalk6.green(`
2603
+ Created tmux session '${sessionName}' for worktree '${worktreeName}'`)
2604
+ );
2605
+ console.log(`
2606
+ To attach to this session, run:`);
2607
+ console.log(chalk6.cyan(` tmux attach -t '${sessionName}'`));
2608
+ }
2578
2609
  } catch (error) {
2579
2610
  log.error(`Failed to create tmux session: ${error.message}`);
2580
2611
  process.exit(1);
@@ -2874,7 +2905,7 @@ async function initProject(defaultName, fromUser, ctx) {
2874
2905
  default: defaults?.base ? File5.from(defaults.base).join(name).path : name
2875
2906
  });
2876
2907
  let answerFile = File5.from(pathAnswer);
2877
- const defaultBase = defaults?.base ? File5.from(defaults.base) : File5.cwd();
2908
+ const defaultBase = defaults?.base ? File5.from(defaults.base).absolutify() : File5.cwd();
2878
2909
  if (!answerFile.isAbsolute()) {
2879
2910
  answerFile = defaultBase.join(answerFile.path);
2880
2911
  }
@@ -2888,7 +2919,8 @@ async function initProject(defaultName, fromUser, ctx) {
2888
2919
  } catch {
2889
2920
  answerFile = answerFile.absolutify();
2890
2921
  }
2891
- basePath = answerFile.relativize(defaultBase.path).path;
2922
+ const relPath = answerFile.relativize(defaultBase.path);
2923
+ basePath = relPath && !relPath.path.startsWith("..") ? relPath.path : answerFile.path;
2892
2924
  }
2893
2925
  const ide = await select4({
2894
2926
  message: "What is the IDE?",
@@ -3081,18 +3113,21 @@ async function createProjectManage(ctx) {
3081
3113
  return true;
3082
3114
  }
3083
3115
  });
3084
- const defaultPath = defaults?.base ? File5.from(defaults.base).join(name).path : name;
3116
+ const defaultPath = defaults?.base ? File5.from(defaults.base).absolutify().join(name).path : name;
3085
3117
  const pathInput = await input5({
3086
3118
  message: "Project path:",
3087
3119
  default: defaultPath
3088
3120
  });
3089
3121
  let relativePath = pathInput;
3090
3122
  if (defaults?.base) {
3091
- const baseDir = File5.from(defaults.base);
3123
+ const baseDir = File5.from(defaults.base).absolutify();
3092
3124
  const pathFile = File5.from(pathInput);
3093
3125
  try {
3094
3126
  if (pathFile.isAbsolute()) {
3095
- relativePath = pathFile.relativize(baseDir.path).path;
3127
+ const relPath = pathFile.relativize(baseDir.path);
3128
+ if (relPath && !relPath.path.startsWith("..")) {
3129
+ relativePath = relPath.path;
3130
+ }
3096
3131
  }
3097
3132
  } catch {
3098
3133
  relativePath = pathInput;
@@ -3153,11 +3188,14 @@ async function editProjectManage(ctx) {
3153
3188
  });
3154
3189
  let relativePath = pathInput;
3155
3190
  if (defaults?.base) {
3156
- const baseDir = File5.from(defaults.base);
3191
+ const baseDir = File5.from(defaults.base).absolutify();
3157
3192
  const pathFile = File5.from(pathInput);
3158
3193
  try {
3159
3194
  if (pathFile.isAbsolute()) {
3160
- relativePath = pathFile.relativize(baseDir.path).path;
3195
+ const relPath = pathFile.relativize(baseDir.path);
3196
+ if (relPath && !relPath.path.startsWith("..")) {
3197
+ relativePath = relPath.path;
3198
+ }
3161
3199
  }
3162
3200
  } catch {
3163
3201
  relativePath = pathInput;
@@ -3386,7 +3424,7 @@ async function manageWorktrees(projectName, ctx) {
3386
3424
  } else {
3387
3425
  projectPath = configPath.absolutify().path;
3388
3426
  }
3389
- const manager = new WorktreeManager(projectPath);
3427
+ const manager = new WorktreeManager(projectPath, projectName);
3390
3428
  if (!await manager.isGitRepository()) {
3391
3429
  log.error(`'${projectName}' is not a git repository`);
3392
3430
  return;
@@ -3535,7 +3573,7 @@ async function openWorktreeManage(projectName, manager, config, log) {
3535
3573
  }
3536
3574
  };
3537
3575
  const { runWorktreeOpen: runWorktreeOpen2 } = await Promise.resolve().then(() => (init_open(), open_exports));
3538
- await runWorktreeOpen2(projectCtx, worktreeName, {}, { config, log });
3576
+ await runWorktreeOpen2(projectCtx, worktreeName, { attach: false }, { config, log });
3539
3577
  }
3540
3578
  async function removeWorktreeManage(projectName, manager, log) {
3541
3579
  const worktrees = await manager.list();
@@ -3670,7 +3708,7 @@ async function manageWorktreesInteractive(projectCtx, ctx) {
3670
3708
  const { config, log } = ctx;
3671
3709
  const { projectPath, projectName } = projectCtx;
3672
3710
  const displayName = projectName || path7.basename(projectPath);
3673
- const manager = new WorktreeManager(projectPath);
3711
+ const manager = new WorktreeManager(projectPath, projectName ?? void 0);
3674
3712
  if (!await manager.isGitRepository()) {
3675
3713
  log.error(`'${displayName}' is not a git repository`);
3676
3714
  return;
@@ -4247,10 +4285,13 @@ async function createProject(ctx) {
4247
4285
  });
4248
4286
  let relativePath = pathInput;
4249
4287
  if (defaults?.base) {
4250
- const baseDir = File7.from(defaults.base);
4288
+ const baseDir = File7.from(defaults.base).absolutify();
4251
4289
  const pathFile = File7.from(pathInput);
4252
4290
  try {
4253
- relativePath = pathFile.relativize(baseDir.path).path;
4291
+ const relPath = pathFile.relativize(baseDir.path);
4292
+ if (relPath && !relPath.path.startsWith("..")) {
4293
+ relativePath = relPath.path;
4294
+ }
4254
4295
  } catch {
4255
4296
  relativePath = pathInput;
4256
4297
  }
@@ -4322,11 +4363,14 @@ async function editProject(ctx) {
4322
4363
  });
4323
4364
  let relativePath = pathInput;
4324
4365
  if (defaults?.base) {
4325
- const baseDir = File7.from(defaults.base);
4366
+ const baseDir = File7.from(defaults.base).absolutify();
4326
4367
  const pathFile = File7.from(pathInput);
4327
4368
  try {
4328
4369
  if (pathFile.isAbsolute()) {
4329
- relativePath = pathFile.relativize(baseDir.path).path;
4370
+ const relPath = pathFile.relativize(baseDir.path);
4371
+ if (relPath && !relPath.path.startsWith("..")) {
4372
+ relativePath = relPath.path;
4373
+ }
4330
4374
  }
4331
4375
  } catch {
4332
4376
  relativePath = pathInput;
@@ -4494,7 +4538,7 @@ async function addProject(pathArg, options, ctx) {
4494
4538
  log.debug(`IDE: ${ide}`);
4495
4539
  let relativePath = targetPath;
4496
4540
  if (defaults?.base) {
4497
- const baseDir = File8.from(defaults.base);
4541
+ const baseDir = File8.from(defaults.base).absolutify();
4498
4542
  try {
4499
4543
  const relPath = pathFile.relativize(baseDir.path);
4500
4544
  if (relPath && !relPath.path.startsWith("..")) {