worktree-flow 0.0.7 → 0.0.8

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 CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/worktree-flow)](https://www.npmjs.com/package/worktree-flow)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/worktree-flow)](https://www.npmjs.com/package/worktree-flow)
7
+ [![Build](https://github.com/simonpratt/worktree-flow/actions/workflows/build.yml/badge.svg)](https://github.com/simonpratt/worktree-flow/actions/workflows/build.yml)
7
8
  [![license](https://img.shields.io/npm/l/worktree-flow)](https://github.com/simonpratt/worktree-flow/blob/master/LICENSE)
8
9
 
9
10
  Stop juggling branches across repos. `flow` creates isolated workspaces with git worktrees from multiple repositories, all on the same branch, with a single command.
@@ -85,6 +86,12 @@ Remove a workspace and all its worktrees. Fetches latest, checks for uncommitted
85
86
 
86
87
  Remove stale workspaces in bulk. Finds workspaces where all worktrees are clean, fully merged, and haven't been committed to in over 7 days.
87
88
 
89
+ ### `flow tmux resume`
90
+
91
+ Create tmux sessions for all workspaces that don't already have one. Each session is created with split panes (one for the workspace root and one for each worktree) using a tiled layout. Skips workspaces that already have active sessions.
92
+
93
+ Requires `tmux` to be installed and the `tmux` config option to be enabled.
94
+
88
95
  ### `flow config set <key> <value>`
89
96
 
90
97
  Configure flow settings. See [Configuration](#configuration) below.
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ import { registerRemoveCommand } from './commands/remove.js';
10
10
  import { registerStatusCommand } from './commands/status.js';
11
11
  import { registerPruneCommand } from './commands/prune.js';
12
12
  import { registerFetchCommand } from './commands/fetch.js';
13
+ import { registerTmuxCommand } from './commands/tmux.js';
13
14
  const program = new Command();
14
15
  program
15
16
  .name('flow')
@@ -25,4 +26,5 @@ registerRemoveCommand(program);
25
26
  registerStatusCommand(program);
26
27
  registerPruneCommand(program);
27
28
  registerFetchCommand(program);
29
+ registerTmuxCommand(program);
28
30
  program.parse();
@@ -45,6 +45,16 @@ export function registerConfigCommand(program) {
45
45
  const displayValue = isDefault ? chalk.gray(value) : chalk.green(value);
46
46
  services.console.log(` ${chalk.cyan(key)}: ${displayValue}`);
47
47
  }
48
+ // Display per-repo post-checkout commands
49
+ const perRepoCommands = displayConfig.perRepoPostCheckout;
50
+ if (Object.keys(perRepoCommands).length > 0) {
51
+ services.console.log('');
52
+ services.console.log(chalk.bold('Per-repo post-checkout commands:'));
53
+ services.console.log('');
54
+ for (const [repo, command] of Object.entries(perRepoCommands)) {
55
+ services.console.log(` ${chalk.cyan(repo)}: ${chalk.green(command)}`);
56
+ }
57
+ }
48
58
  services.console.log('');
49
59
  });
50
60
  }
@@ -0,0 +1,45 @@
1
+ import chalk from 'chalk';
2
+ import { createServices } from '../lib/services.js';
3
+ import { createUseCases } from '../usecases/usecases.js';
4
+ export async function runTmuxResume(useCases, services) {
5
+ const { destPath } = services.config.getRequired();
6
+ const result = await useCases.resumeTmuxSessions.execute({ destPath });
7
+ if (result.totalWorkspaces === 0) {
8
+ services.console.log('No workspaces found.');
9
+ return;
10
+ }
11
+ // Display results
12
+ if (result.sessionsCreated > 0) {
13
+ services.console.log(chalk.green(`✓ Created ${result.sessionsCreated} tmux session${result.sessionsCreated === 1 ? '' : 's'}`));
14
+ }
15
+ if (result.sessionsSkipped > 0) {
16
+ services.console.log(chalk.blue(`○ Skipped ${result.sessionsSkipped} existing session${result.sessionsSkipped === 1 ? '' : 's'}`));
17
+ }
18
+ if (result.errors.length > 0) {
19
+ services.console.log(chalk.red('\nErrors:'));
20
+ for (const error of result.errors) {
21
+ services.console.log(chalk.red(` ${error.workspace}: ${error.error}`));
22
+ }
23
+ }
24
+ // Summary
25
+ services.console.log(chalk.dim(`\nTotal: ${result.totalWorkspaces} workspace${result.totalWorkspaces === 1 ? '' : 's'}`));
26
+ }
27
+ export function registerTmuxCommand(program) {
28
+ const tmuxCommand = program
29
+ .command('tmux')
30
+ .description('Manage tmux sessions for workspaces');
31
+ tmuxCommand
32
+ .command('resume')
33
+ .description('Create tmux sessions for all workspaces that don\'t have one')
34
+ .action(async () => {
35
+ const services = createServices();
36
+ const useCases = createUseCases(services);
37
+ try {
38
+ await runTmuxResume(useCases, services);
39
+ }
40
+ catch (error) {
41
+ services.console.error(error.message);
42
+ services.process.exit(1);
43
+ }
44
+ });
45
+ }
@@ -142,6 +142,7 @@ export class ConfigService {
142
142
  value: raw['fetch-cache-ttl-seconds'] ?? `${config.fetchCacheTtlSeconds} (default)`,
143
143
  isDefault: !raw['fetch-cache-ttl-seconds'],
144
144
  },
145
+ perRepoPostCheckout: config.perRepoPostCheckout,
145
146
  };
146
147
  }
147
148
  }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Use case for resuming tmux sessions across all workspaces.
3
+ * Creates sessions for workspaces that don't already have one.
4
+ */
5
+ export class ResumeTmuxSessionsUseCase {
6
+ workspaceDir;
7
+ tmux;
8
+ constructor(workspaceDir, tmux) {
9
+ this.workspaceDir = workspaceDir;
10
+ this.tmux = tmux;
11
+ }
12
+ async execute(params) {
13
+ // 1. List all workspaces
14
+ const workspaces = this.workspaceDir.listWorkspaces(params.destPath);
15
+ let sessionsCreated = 0;
16
+ let sessionsSkipped = 0;
17
+ const errors = [];
18
+ // 2. Try to create tmux session for each workspace
19
+ for (const workspace of workspaces) {
20
+ try {
21
+ const worktreeDirs = this.workspaceDir.getWorktreeDirs(workspace.path);
22
+ // Try to create session - will throw on duplicate session
23
+ await this.tmux.createSession(workspace.path, workspace.name, worktreeDirs);
24
+ sessionsCreated++;
25
+ }
26
+ catch (error) {
27
+ // Check if session already exists
28
+ if (error.message?.includes('duplicate session')) {
29
+ sessionsSkipped++;
30
+ }
31
+ else {
32
+ // Other error
33
+ errors.push({
34
+ workspace: workspace.name,
35
+ error: error.message || 'Unknown error',
36
+ });
37
+ }
38
+ }
39
+ }
40
+ return {
41
+ totalWorkspaces: workspaces.length,
42
+ sessionsCreated,
43
+ sessionsSkipped,
44
+ errors,
45
+ };
46
+ }
47
+ }
@@ -9,6 +9,7 @@ import { ListWorkspacesWithStatusUseCase } from './listWorkspacesWithStatus.js';
9
9
  import { FetchAllReposUseCase } from './fetchAllRepos.js';
10
10
  import { FetchWorkspaceReposUseCase } from './fetchWorkspaceRepos.js';
11
11
  import { FetchUsedReposUseCase } from './fetchUsedRepos.js';
12
+ import { ResumeTmuxSessionsUseCase } from './resumeTmuxSessions.js';
12
13
  /**
13
14
  * Factory function for creating all use cases with their service dependencies.
14
15
  * Use cases orchestrate workflows by coordinating multiple services.
@@ -26,5 +27,6 @@ export function createUseCases(services) {
26
27
  checkWorkspaceStatus: new CheckWorkspaceStatusUseCase(services.workspaceDir, services.status),
27
28
  discoverPrunableWorkspaces: new DiscoverPrunableWorkspacesUseCase(services.workspaceDir, services.status, services.git),
28
29
  listWorkspacesWithStatus: new ListWorkspacesWithStatusUseCase(services.workspaceDir, services.status),
30
+ resumeTmuxSessions: new ResumeTmuxSessionsUseCase(services.workspaceDir, services.tmux),
29
31
  };
30
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worktree-flow",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Manage git worktrees across a poly-repo environment",
5
5
  "type": "module",
6
6
  "bin": {