@camaradesuk/git-worktree-tools 1.6.0 → 1.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.
Files changed (190) hide show
  1. package/README.md +307 -418
  2. package/dist/cli/cleanpr.test.js +2 -0
  3. package/dist/cli/cleanpr.test.js.map +1 -1
  4. package/dist/cli/newpr.js +48 -11
  5. package/dist/cli/newpr.js.map +1 -1
  6. package/dist/cli/newpr.test.js +33 -15
  7. package/dist/cli/newpr.test.js.map +1 -1
  8. package/dist/cli/wt/clean.d.ts +1 -0
  9. package/dist/cli/wt/clean.d.ts.map +1 -1
  10. package/dist/cli/wt/clean.js +11 -1
  11. package/dist/cli/wt/clean.js.map +1 -1
  12. package/dist/cli/wt/config.d.ts +1 -1
  13. package/dist/cli/wt/config.d.ts.map +1 -1
  14. package/dist/cli/wt/config.js +148 -13
  15. package/dist/cli/wt/config.js.map +1 -1
  16. package/dist/cli/wt/config.test.d.ts +5 -0
  17. package/dist/cli/wt/config.test.d.ts.map +1 -0
  18. package/dist/cli/wt/config.test.js +260 -0
  19. package/dist/cli/wt/config.test.js.map +1 -0
  20. package/dist/cli/wt/entry.test.js +11 -8
  21. package/dist/cli/wt/entry.test.js.map +1 -1
  22. package/dist/cli/wt/init.d.ts +14 -0
  23. package/dist/cli/wt/init.d.ts.map +1 -0
  24. package/dist/cli/wt/init.js +209 -0
  25. package/dist/cli/wt/init.js.map +1 -0
  26. package/dist/cli/wt/init.test.d.ts +5 -0
  27. package/dist/cli/wt/init.test.d.ts.map +1 -0
  28. package/dist/cli/wt/init.test.js +165 -0
  29. package/dist/cli/wt/init.test.js.map +1 -0
  30. package/dist/cli/wt/init.unit.test.d.ts +5 -0
  31. package/dist/cli/wt/init.unit.test.d.ts.map +1 -0
  32. package/dist/cli/wt/init.unit.test.js +432 -0
  33. package/dist/cli/wt/init.unit.test.js.map +1 -0
  34. package/dist/cli/wt/interactive-menu.d.ts +41 -0
  35. package/dist/cli/wt/interactive-menu.d.ts.map +1 -0
  36. package/dist/cli/wt/interactive-menu.js +639 -0
  37. package/dist/cli/wt/interactive-menu.js.map +1 -0
  38. package/dist/cli/wt/interactive-menu.test.d.ts +10 -0
  39. package/dist/cli/wt/interactive-menu.test.d.ts.map +1 -0
  40. package/dist/cli/wt/interactive-menu.test.js +711 -0
  41. package/dist/cli/wt/interactive-menu.test.js.map +1 -0
  42. package/dist/cli/wt/link.d.ts +9 -0
  43. package/dist/cli/wt/link.d.ts.map +1 -1
  44. package/dist/cli/wt/link.js +27 -0
  45. package/dist/cli/wt/link.js.map +1 -1
  46. package/dist/cli/wt/list.d.ts +2 -2
  47. package/dist/cli/wt/list.d.ts.map +1 -1
  48. package/dist/cli/wt/list.js +17 -17
  49. package/dist/cli/wt/list.js.map +1 -1
  50. package/dist/cli/wt/new.d.ts +7 -1
  51. package/dist/cli/wt/new.d.ts.map +1 -1
  52. package/dist/cli/wt/new.js +58 -6
  53. package/dist/cli/wt/new.js.map +1 -1
  54. package/dist/cli/wt/wt.test.js +159 -16
  55. package/dist/cli/wt/wt.test.js.map +1 -1
  56. package/dist/cli/wt.d.ts +2 -1
  57. package/dist/cli/wt.d.ts.map +1 -1
  58. package/dist/cli/wt.js +98 -3
  59. package/dist/cli/wt.js.map +1 -1
  60. package/dist/cli/wt.unit.test.d.ts +7 -0
  61. package/dist/cli/wt.unit.test.d.ts.map +1 -0
  62. package/dist/cli/wt.unit.test.js +182 -0
  63. package/dist/cli/wt.unit.test.js.map +1 -0
  64. package/dist/cli/wtconfig.js +18 -4
  65. package/dist/cli/wtconfig.js.map +1 -1
  66. package/dist/cli/wtconfig.test.js +18 -16
  67. package/dist/cli/wtconfig.test.js.map +1 -1
  68. package/dist/e2e/newpr/scenarios.e2e.test.js +7 -7
  69. package/dist/e2e/newpr/scenarios.e2e.test.js.map +1 -1
  70. package/dist/e2e/wt/wt.e2e.test.d.ts +9 -0
  71. package/dist/e2e/wt/wt.e2e.test.d.ts.map +1 -0
  72. package/dist/e2e/wt/wt.e2e.test.js +384 -0
  73. package/dist/e2e/wt/wt.e2e.test.js.map +1 -0
  74. package/dist/lib/ai/base-provider.d.ts +22 -0
  75. package/dist/lib/ai/base-provider.d.ts.map +1 -1
  76. package/dist/lib/ai/base-provider.js +180 -99
  77. package/dist/lib/ai/base-provider.js.map +1 -1
  78. package/dist/lib/ai/base-provider.test.js +13 -14
  79. package/dist/lib/ai/base-provider.test.js.map +1 -1
  80. package/dist/lib/ai/cli-provider.d.ts +11 -7
  81. package/dist/lib/ai/cli-provider.d.ts.map +1 -1
  82. package/dist/lib/ai/cli-provider.js +19 -49
  83. package/dist/lib/ai/cli-provider.js.map +1 -1
  84. package/dist/lib/ai/cli-provider.test.js +47 -49
  85. package/dist/lib/ai/cli-provider.test.js.map +1 -1
  86. package/dist/lib/ai/index.d.ts +2 -1
  87. package/dist/lib/ai/index.d.ts.map +1 -1
  88. package/dist/lib/ai/index.js +2 -0
  89. package/dist/lib/ai/index.js.map +1 -1
  90. package/dist/lib/ai/provider-manager.js +2 -2
  91. package/dist/lib/ai/provider-manager.js.map +1 -1
  92. package/dist/lib/ai/repo-docs.d.ts +43 -0
  93. package/dist/lib/ai/repo-docs.d.ts.map +1 -0
  94. package/dist/lib/ai/repo-docs.js +274 -0
  95. package/dist/lib/ai/repo-docs.js.map +1 -0
  96. package/dist/lib/ai/repo-docs.test.d.ts +5 -0
  97. package/dist/lib/ai/repo-docs.test.d.ts.map +1 -0
  98. package/dist/lib/ai/repo-docs.test.js +357 -0
  99. package/dist/lib/ai/repo-docs.test.js.map +1 -0
  100. package/dist/lib/ai/types.d.ts +18 -2
  101. package/dist/lib/ai/types.d.ts.map +1 -1
  102. package/dist/lib/ai/types.js.map +1 -1
  103. package/dist/lib/config-editor.d.ts +21 -0
  104. package/dist/lib/config-editor.d.ts.map +1 -0
  105. package/dist/lib/config-editor.js +729 -0
  106. package/dist/lib/config-editor.js.map +1 -0
  107. package/dist/lib/config-editor.test.d.ts +11 -0
  108. package/dist/lib/config-editor.test.d.ts.map +1 -0
  109. package/dist/lib/config-editor.test.js +526 -0
  110. package/dist/lib/config-editor.test.js.map +1 -0
  111. package/dist/lib/config-validation.d.ts +28 -0
  112. package/dist/lib/config-validation.d.ts.map +1 -0
  113. package/dist/lib/config-validation.js +534 -0
  114. package/dist/lib/config-validation.js.map +1 -0
  115. package/dist/lib/config-validation.test.d.ts +5 -0
  116. package/dist/lib/config-validation.test.d.ts.map +1 -0
  117. package/dist/lib/config-validation.test.js +398 -0
  118. package/dist/lib/config-validation.test.js.map +1 -0
  119. package/dist/lib/config.d.ts +115 -6
  120. package/dist/lib/config.d.ts.map +1 -1
  121. package/dist/lib/config.js +251 -55
  122. package/dist/lib/config.js.map +1 -1
  123. package/dist/lib/config.test.js +2 -1
  124. package/dist/lib/config.test.js.map +1 -1
  125. package/dist/lib/constants.d.ts +50 -1
  126. package/dist/lib/constants.d.ts.map +1 -1
  127. package/dist/lib/constants.js +67 -1
  128. package/dist/lib/constants.js.map +1 -1
  129. package/dist/lib/constants.test.d.ts +5 -0
  130. package/dist/lib/constants.test.d.ts.map +1 -0
  131. package/dist/lib/constants.test.js +121 -0
  132. package/dist/lib/constants.test.js.map +1 -0
  133. package/dist/lib/git.d.ts +12 -0
  134. package/dist/lib/git.d.ts.map +1 -1
  135. package/dist/lib/git.js +26 -0
  136. package/dist/lib/git.js.map +1 -1
  137. package/dist/lib/global-check.d.ts +38 -0
  138. package/dist/lib/global-check.d.ts.map +1 -0
  139. package/dist/lib/global-check.js +135 -0
  140. package/dist/lib/global-check.js.map +1 -0
  141. package/dist/lib/global-check.test.d.ts +5 -0
  142. package/dist/lib/global-check.test.d.ts.map +1 -0
  143. package/dist/lib/global-check.test.js +153 -0
  144. package/dist/lib/global-check.test.js.map +1 -0
  145. package/dist/lib/global-config.d.ts +102 -0
  146. package/dist/lib/global-config.d.ts.map +1 -0
  147. package/dist/lib/global-config.js +234 -0
  148. package/dist/lib/global-config.js.map +1 -0
  149. package/dist/lib/global-config.test.d.ts +5 -0
  150. package/dist/lib/global-config.test.d.ts.map +1 -0
  151. package/dist/lib/global-config.test.js +282 -0
  152. package/dist/lib/global-config.test.js.map +1 -0
  153. package/dist/lib/logger.d.ts +175 -0
  154. package/dist/lib/logger.d.ts.map +1 -0
  155. package/dist/lib/logger.js +475 -0
  156. package/dist/lib/logger.js.map +1 -0
  157. package/dist/lib/logger.test.d.ts +5 -0
  158. package/dist/lib/logger.test.d.ts.map +1 -0
  159. package/dist/lib/logger.test.js +292 -0
  160. package/dist/lib/logger.test.js.map +1 -0
  161. package/dist/lib/lswt/action-executors.test.js +2 -0
  162. package/dist/lib/lswt/action-executors.test.js.map +1 -1
  163. package/dist/lib/newpr/args.d.ts.map +1 -1
  164. package/dist/lib/newpr/args.js +9 -2
  165. package/dist/lib/newpr/args.js.map +1 -1
  166. package/dist/lib/newpr/args.test.js +1 -1
  167. package/dist/lib/newpr/args.test.js.map +1 -1
  168. package/dist/lib/newpr/types.d.ts +2 -0
  169. package/dist/lib/newpr/types.d.ts.map +1 -1
  170. package/dist/lib/prompts.d.ts +6 -0
  171. package/dist/lib/prompts.d.ts.map +1 -1
  172. package/dist/lib/prompts.js +31 -9
  173. package/dist/lib/prompts.js.map +1 -1
  174. package/dist/lib/prompts.test.js +72 -1
  175. package/dist/lib/prompts.test.js.map +1 -1
  176. package/dist/lib/schema.test.d.ts +10 -0
  177. package/dist/lib/schema.test.d.ts.map +1 -0
  178. package/dist/lib/schema.test.js +309 -0
  179. package/dist/lib/schema.test.js.map +1 -0
  180. package/dist/lib/wtconfig/environment.d.ts.map +1 -1
  181. package/dist/lib/wtconfig/environment.js +6 -4
  182. package/dist/lib/wtconfig/environment.js.map +1 -1
  183. package/dist/lib/wtconfig/environment.test.js +2 -7
  184. package/dist/lib/wtconfig/environment.test.js.map +1 -1
  185. package/dist/lib/wtconfig/types.d.ts +3 -1
  186. package/dist/lib/wtconfig/types.d.ts.map +1 -1
  187. package/dist/lib/wtlink/manage-manifest.test.js +145 -7
  188. package/dist/lib/wtlink/manage-manifest.test.js.map +1 -1
  189. package/package.json +2 -1
  190. package/schemas/worktreerc.schema.json +416 -0
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Tests for wt init command
3
+ */
4
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import os from 'os';
8
+ import { spawnSync } from 'child_process';
9
+ // Path to the compiled CLI
10
+ const CLI_DIR = path.resolve(__dirname, '../../../dist/cli');
11
+ // Helper to run the wt CLI command
12
+ function runWt(args = [], options = {}) {
13
+ const scriptPath = path.join(CLI_DIR, 'wt.js');
14
+ const result = spawnSync('node', [scriptPath, ...args], {
15
+ cwd: options.cwd || process.cwd(),
16
+ encoding: 'utf-8',
17
+ input: options.input,
18
+ timeout: options.timeout || 30000,
19
+ env: { ...process.env, FORCE_COLOR: '0', GWT_ALLOW_LOCAL: '1' },
20
+ });
21
+ return {
22
+ stdout: result.stdout || '',
23
+ stderr: result.stderr || '',
24
+ exitCode: result.status ?? 1,
25
+ };
26
+ }
27
+ describe('wt init command', () => {
28
+ let tempDir;
29
+ let repoDir;
30
+ beforeEach(() => {
31
+ // Check if CLI is built
32
+ const wtPath = path.join(CLI_DIR, 'wt.js');
33
+ if (!fs.existsSync(wtPath)) {
34
+ throw new Error('CLI not built. Run "npm run build" before running tests.');
35
+ }
36
+ // Create temp directory structure
37
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'wt-init-test-'));
38
+ repoDir = path.join(tempDir, 'test-repo');
39
+ // Initialize git repo
40
+ fs.mkdirSync(repoDir);
41
+ const { execSync } = require('child_process');
42
+ execSync('git init', { cwd: repoDir, stdio: 'ignore' });
43
+ execSync('git config user.email "test@test.com"', { cwd: repoDir, stdio: 'ignore' });
44
+ execSync('git config user.name "Test User"', { cwd: repoDir, stdio: 'ignore' });
45
+ fs.writeFileSync(path.join(repoDir, 'README.md'), '# Test');
46
+ execSync('git add .', { cwd: repoDir, stdio: 'ignore' });
47
+ execSync('git commit -m "Initial commit"', { cwd: repoDir, stdio: 'ignore' });
48
+ });
49
+ afterEach(() => {
50
+ if (tempDir && fs.existsSync(tempDir)) {
51
+ fs.rmSync(tempDir, { recursive: true, force: true });
52
+ }
53
+ });
54
+ describe('help output', () => {
55
+ it('shows help with --help flag', () => {
56
+ const result = runWt(['init', '--help']);
57
+ expect(result.exitCode).toBe(0);
58
+ expect(result.stdout).toContain('init');
59
+ expect(result.stdout).toContain('--local');
60
+ expect(result.stdout).toContain('--global');
61
+ expect(result.stdout).toContain('--force');
62
+ });
63
+ it('shows init as a command in main help', () => {
64
+ const result = runWt(['--help']);
65
+ expect(result.exitCode).toBe(0);
66
+ expect(result.stdout).toContain('init');
67
+ });
68
+ });
69
+ describe('--local flag', () => {
70
+ it('creates local config file with --local', () => {
71
+ // Provide "n" to skip logging configuration prompt
72
+ const result = runWt(['init', '--local'], { cwd: repoDir, input: 'n\n' });
73
+ expect(result.exitCode).toBe(0);
74
+ // Check that .worktreerc.local was created
75
+ const localConfigPath = path.join(repoDir, '.worktreerc.local');
76
+ expect(fs.existsSync(localConfigPath)).toBe(true);
77
+ });
78
+ it('local config includes $schema', () => {
79
+ runWt(['init', '--local'], { cwd: repoDir, input: 'n\n' });
80
+ const localConfigPath = path.join(repoDir, '.worktreerc.local');
81
+ const content = JSON.parse(fs.readFileSync(localConfigPath, 'utf8'));
82
+ expect(content.$schema).toContain('unpkg.com');
83
+ });
84
+ it('updates .gitignore with local config patterns', () => {
85
+ runWt(['init', '--local'], { cwd: repoDir, input: 'n\n' });
86
+ const gitignorePath = path.join(repoDir, '.gitignore');
87
+ expect(fs.existsSync(gitignorePath)).toBe(true);
88
+ const content = fs.readFileSync(gitignorePath, 'utf8');
89
+ expect(content).toContain('.worktreerc.local');
90
+ });
91
+ it('fails outside git repo', () => {
92
+ const result = runWt(['init', '--local'], { cwd: tempDir, input: 'n\n' });
93
+ expect(result.exitCode).toBe(1);
94
+ expect(result.stderr.toLowerCase()).toMatch(/git|repository/);
95
+ });
96
+ it('warns if local config already exists', () => {
97
+ // Create local config first
98
+ fs.writeFileSync(path.join(repoDir, '.worktreerc.local'), '{}');
99
+ const result = runWt(['init', '--local'], { cwd: repoDir, input: 'n\n' });
100
+ // Should succeed but warn
101
+ expect(result.stdout.toLowerCase()).toMatch(/already exists|use --force/);
102
+ });
103
+ it('overwrites with --force', () => {
104
+ // Create local config with specific content
105
+ fs.writeFileSync(path.join(repoDir, '.worktreerc.local'), '{"baseBranch": "old"}');
106
+ // Provide "n" to skip logging configuration prompt
107
+ const result = runWt(['init', '--local', '--force'], { cwd: repoDir, input: 'n\n' });
108
+ expect(result.exitCode).toBe(0);
109
+ const content = JSON.parse(fs.readFileSync(path.join(repoDir, '.worktreerc.local'), 'utf8'));
110
+ // Should have $schema from new creation
111
+ expect(content.$schema).toBeDefined();
112
+ });
113
+ });
114
+ describe('--global flag', () => {
115
+ it('creates global config with --global', () => {
116
+ // Use a custom HOME to avoid polluting real config
117
+ const customHome = path.join(tempDir, 'home');
118
+ fs.mkdirSync(customHome, { recursive: true });
119
+ const result = runWt(['init', '--global'], {
120
+ cwd: repoDir,
121
+ });
122
+ // Just check it runs without error - actual path varies by OS
123
+ // We can't easily test global config creation without mocking HOME
124
+ expect(result.exitCode === 0 || result.stdout.includes('already exists')).toBe(true);
125
+ });
126
+ });
127
+ describe('non-interactive mode', () => {
128
+ it('--local completes with stdin input', () => {
129
+ const result = runWt(['init', '--local'], {
130
+ cwd: repoDir,
131
+ input: 'n\n', // Skip logging configuration prompt
132
+ timeout: 5000,
133
+ });
134
+ // Should complete with provided input
135
+ expect(result.exitCode).toBe(0);
136
+ });
137
+ });
138
+ describe('output messages', () => {
139
+ it('shows success message after creating local config', () => {
140
+ const result = runWt(['init', '--local'], { cwd: repoDir, input: 'n\n' });
141
+ expect(result.exitCode).toBe(0);
142
+ expect(result.stdout.toLowerCase()).toMatch(/created|success|✓/);
143
+ });
144
+ it('shows path to created config', () => {
145
+ const result = runWt(['init', '--local'], { cwd: repoDir, input: 'n\n' });
146
+ expect(result.stdout).toContain('.worktreerc.local');
147
+ });
148
+ it('mentions .gitignore when updated', () => {
149
+ const result = runWt(['init', '--local'], { cwd: repoDir, input: 'n\n' });
150
+ expect(result.stdout.toLowerCase()).toMatch(/gitignore|updated/);
151
+ });
152
+ });
153
+ });
154
+ describe('wt init unit tests', () => {
155
+ // These test the logic without running the full CLI
156
+ it('LOCAL_CONFIG_FILE_NAMES includes .worktreerc.local', async () => {
157
+ const { LOCAL_CONFIG_FILE_NAMES } = await import('../../lib/constants.js');
158
+ expect(LOCAL_CONFIG_FILE_NAMES).toContain('.worktreerc.local');
159
+ });
160
+ it('CONFIG_FILE_NAMES includes .worktreerc', async () => {
161
+ const { CONFIG_FILE_NAMES } = await import('../../lib/constants.js');
162
+ expect(CONFIG_FILE_NAMES).toContain('.worktreerc');
163
+ });
164
+ });
165
+ //# sourceMappingURL=init.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.test.js","sourceRoot":"","sources":["../../../src/cli/wt/init.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Unit tests for wt init command functions
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=init.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.unit.test.d.ts","sourceRoot":"","sources":["../../../src/cli/wt/init.unit.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,432 @@
1
+ /**
2
+ * Unit tests for wt init command functions
3
+ */
4
+ import { describe, it, expect, beforeEach, afterEach, vi, } from 'vitest';
5
+ // Helper to create valid yargs argv for tests
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ function createArgv(args) {
8
+ return {
9
+ _: [],
10
+ $0: 'wt',
11
+ ...args,
12
+ };
13
+ }
14
+ // Mock dependencies before importing the module
15
+ vi.mock('../../lib/git.js', () => ({
16
+ getRepoRoot: vi.fn(),
17
+ }));
18
+ vi.mock('../../lib/colors.js', () => ({
19
+ error: vi.fn((s) => s),
20
+ dim: vi.fn((s) => s),
21
+ success: vi.fn((s) => s),
22
+ info: vi.fn((s) => s),
23
+ bold: vi.fn((s) => s),
24
+ warning: vi.fn((s) => s),
25
+ }));
26
+ vi.mock('../../lib/global-config.js', () => ({
27
+ initializeLocalConfig: vi.fn(() => ({
28
+ configPath: '/repo/.worktreerc.local',
29
+ gitignoreUpdated: true,
30
+ })),
31
+ saveGlobalConfig: vi.fn(),
32
+ getConfigSummary: vi.fn(() => ({
33
+ global: false,
34
+ repo: false,
35
+ local: false,
36
+ paths: {
37
+ global: {
38
+ path: '/home/user/.config/git-worktree-tools/config.json',
39
+ level: 'global',
40
+ exists: false,
41
+ },
42
+ repo: { path: '/repo/.worktreerc', level: 'repo', exists: false },
43
+ local: { path: '/repo/.worktreerc.local', level: 'local', exists: false },
44
+ },
45
+ })),
46
+ ensureLocalConfigInGitignore: vi.fn(() => true),
47
+ getGlobalConfigPath: vi.fn(() => '/home/user/.config/git-worktree-tools/config.json'),
48
+ }));
49
+ vi.mock('../../lib/prompts.js', () => ({
50
+ promptChoice: vi.fn(() => 'cancel'),
51
+ promptConfirm: vi.fn(() => false),
52
+ promptInput: vi.fn(() => 'main'),
53
+ }));
54
+ import { initCommand } from './init.js';
55
+ import * as git from '../../lib/git.js';
56
+ import * as globalConfig from '../../lib/global-config.js';
57
+ import * as prompts from '../../lib/prompts.js';
58
+ describe('wt init command', () => {
59
+ // Using 'any' for spies since vitest's MockInstance typing is complex for these methods
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ let consoleLogSpy;
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ let consoleErrorSpy;
64
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
+ let processExitSpy;
66
+ beforeEach(() => {
67
+ vi.clearAllMocks();
68
+ consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
69
+ consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
70
+ processExitSpy = vi.spyOn(process, 'exit').mockImplementation((() => { }));
71
+ });
72
+ afterEach(() => {
73
+ consoleLogSpy.mockRestore();
74
+ consoleErrorSpy.mockRestore();
75
+ processExitSpy.mockRestore();
76
+ });
77
+ describe('command structure', () => {
78
+ it('has correct command name', () => {
79
+ expect(initCommand.command).toBe('init');
80
+ });
81
+ it('has description', () => {
82
+ expect(initCommand.describe).toBeDefined();
83
+ expect(initCommand.describe).toContain('Initialize');
84
+ });
85
+ it('has handler function', () => {
86
+ expect(typeof initCommand.handler).toBe('function');
87
+ });
88
+ it('builder configures options', () => {
89
+ const mockYargs = {
90
+ option: vi.fn().mockReturnThis(),
91
+ example: vi.fn().mockReturnThis(),
92
+ };
93
+ if (typeof initCommand.builder === 'function') {
94
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
+ initCommand.builder(mockYargs);
96
+ }
97
+ expect(mockYargs.option).toHaveBeenCalledWith('local', expect.any(Object));
98
+ expect(mockYargs.option).toHaveBeenCalledWith('global', expect.any(Object));
99
+ expect(mockYargs.option).toHaveBeenCalledWith('force', expect.any(Object));
100
+ });
101
+ });
102
+ describe('handler - --local flag', () => {
103
+ it('creates local config when in git repo', async () => {
104
+ git.getRepoRoot.mockReturnValue('/repo');
105
+ prompts.promptConfirm.mockResolvedValue(false);
106
+ await initCommand.handler(createArgv({ local: true, global: false, force: false }));
107
+ expect(globalConfig.initializeLocalConfig).toHaveBeenCalledWith('/repo', expect.any(Object));
108
+ });
109
+ it('includes logging config when user confirms', async () => {
110
+ git.getRepoRoot.mockReturnValue('/repo');
111
+ prompts.promptConfirm.mockResolvedValue(true);
112
+ prompts.promptChoice.mockResolvedValue('debug');
113
+ await initCommand.handler(createArgv({ local: true, global: false, force: false }));
114
+ expect(globalConfig.initializeLocalConfig).toHaveBeenCalledWith('/repo', expect.objectContaining({
115
+ logging: { level: 'debug' },
116
+ }));
117
+ });
118
+ it('exits with error when not in git repo', async () => {
119
+ git.getRepoRoot.mockImplementation(() => {
120
+ throw new Error('Not a git repo');
121
+ });
122
+ await initCommand.handler(createArgv({ local: true, global: false, force: false }));
123
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Not in a git repository.');
124
+ expect(processExitSpy).toHaveBeenCalledWith(1);
125
+ });
126
+ it('warns when local config already exists without --force', async () => {
127
+ git.getRepoRoot.mockReturnValue('/repo');
128
+ globalConfig.getConfigSummary.mockReturnValue({
129
+ global: false,
130
+ repo: false,
131
+ local: true,
132
+ paths: {
133
+ global: {
134
+ path: '/home/user/.config/git-worktree-tools/config.json',
135
+ level: 'global',
136
+ exists: false,
137
+ },
138
+ repo: { path: '/repo/.worktreerc', level: 'repo', exists: false },
139
+ local: { path: '/repo/.worktreerc.local', level: 'local', exists: true },
140
+ },
141
+ });
142
+ await initCommand.handler(createArgv({ local: true, global: false, force: false }));
143
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('already exists'));
144
+ expect(globalConfig.initializeLocalConfig).not.toHaveBeenCalled();
145
+ });
146
+ it('overwrites when --force is provided', async () => {
147
+ git.getRepoRoot.mockReturnValue('/repo');
148
+ globalConfig.getConfigSummary.mockReturnValue({
149
+ global: false,
150
+ repo: false,
151
+ local: true,
152
+ paths: {
153
+ global: {
154
+ path: '/home/user/.config/git-worktree-tools/config.json',
155
+ level: 'global',
156
+ exists: false,
157
+ },
158
+ repo: { path: '/repo/.worktreerc', level: 'repo', exists: false },
159
+ local: { path: '/repo/.worktreerc.local', level: 'local', exists: true },
160
+ },
161
+ });
162
+ prompts.promptConfirm.mockResolvedValue(false);
163
+ await initCommand.handler(createArgv({ local: true, global: false, force: true }));
164
+ expect(globalConfig.initializeLocalConfig).toHaveBeenCalled();
165
+ });
166
+ it('shows success message after creation', async () => {
167
+ git.getRepoRoot.mockReturnValue('/repo');
168
+ prompts.promptConfirm.mockResolvedValue(false);
169
+ // Ensure config summary shows no local config exists
170
+ globalConfig.getConfigSummary.mockReturnValue({
171
+ global: false,
172
+ repo: false,
173
+ local: false,
174
+ paths: {
175
+ global: {
176
+ path: '/home/user/.config/git-worktree-tools/config.json',
177
+ level: 'global',
178
+ exists: false,
179
+ },
180
+ repo: { path: '/repo/.worktreerc', level: 'repo', exists: false },
181
+ local: { path: '/repo/.worktreerc.local', level: 'local', exists: false },
182
+ },
183
+ });
184
+ await initCommand.handler(createArgv({ local: true, global: false, force: false }));
185
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Created local config'));
186
+ });
187
+ it('shows gitignore update message when updated', async () => {
188
+ git.getRepoRoot.mockReturnValue('/repo');
189
+ prompts.promptConfirm.mockResolvedValue(false);
190
+ // Ensure config summary shows no local config exists
191
+ globalConfig.getConfigSummary.mockReturnValue({
192
+ global: false,
193
+ repo: false,
194
+ local: false,
195
+ paths: {
196
+ global: {
197
+ path: '/home/user/.config/git-worktree-tools/config.json',
198
+ level: 'global',
199
+ exists: false,
200
+ },
201
+ repo: { path: '/repo/.worktreerc', level: 'repo', exists: false },
202
+ local: { path: '/repo/.worktreerc.local', level: 'local', exists: false },
203
+ },
204
+ });
205
+ globalConfig.initializeLocalConfig.mockReturnValue({
206
+ configPath: '/repo/.worktreerc.local',
207
+ gitignoreUpdated: true,
208
+ });
209
+ await initCommand.handler(createArgv({ local: true, global: false, force: false }));
210
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('gitignore'));
211
+ });
212
+ });
213
+ describe('handler - --global flag', () => {
214
+ it('creates global config', async () => {
215
+ git.getRepoRoot.mockReturnValue('/repo');
216
+ prompts.promptConfirm.mockResolvedValue(true);
217
+ prompts.promptInput.mockResolvedValue('main');
218
+ prompts.promptChoice.mockResolvedValue('vscode');
219
+ await initCommand.handler(createArgv({ local: false, global: true, force: false }));
220
+ expect(globalConfig.saveGlobalConfig).toHaveBeenCalled();
221
+ });
222
+ it('warns when global config already exists without --force', async () => {
223
+ git.getRepoRoot.mockReturnValue('/repo');
224
+ globalConfig.getConfigSummary.mockReturnValue({
225
+ global: true,
226
+ repo: false,
227
+ local: false,
228
+ paths: {
229
+ global: {
230
+ path: '/home/user/.config/git-worktree-tools/config.json',
231
+ level: 'global',
232
+ exists: true,
233
+ },
234
+ repo: { path: '/repo/.worktreerc', level: 'repo', exists: false },
235
+ local: { path: '/repo/.worktreerc.local', level: 'local', exists: false },
236
+ },
237
+ });
238
+ await initCommand.handler(createArgv({ local: false, global: true, force: false }));
239
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('already exists'));
240
+ expect(globalConfig.saveGlobalConfig).not.toHaveBeenCalled();
241
+ });
242
+ it('includes warnNotGlobal setting based on prompt', async () => {
243
+ git.getRepoRoot.mockReturnValue('/repo');
244
+ // Ensure global config doesn't exist
245
+ globalConfig.getConfigSummary.mockReturnValue({
246
+ global: false,
247
+ repo: false,
248
+ local: false,
249
+ paths: {
250
+ global: {
251
+ path: '/home/user/.config/git-worktree-tools/config.json',
252
+ level: 'global',
253
+ exists: false,
254
+ },
255
+ repo: { path: '/repo/.worktreerc', level: 'repo', exists: false },
256
+ local: { path: '/repo/.worktreerc.local', level: 'local', exists: false },
257
+ },
258
+ });
259
+ prompts.promptConfirm.mockResolvedValue(true);
260
+ prompts.promptInput.mockResolvedValue('main');
261
+ prompts.promptChoice.mockResolvedValue('vscode');
262
+ await initCommand.handler(createArgv({ local: false, global: true, force: false }));
263
+ expect(globalConfig.saveGlobalConfig).toHaveBeenCalledWith(expect.objectContaining({
264
+ global: { warnNotGlobal: true },
265
+ }));
266
+ });
267
+ it('includes preferredEditor when not vscode', async () => {
268
+ git.getRepoRoot.mockReturnValue('/repo');
269
+ // Ensure global config doesn't exist
270
+ globalConfig.getConfigSummary.mockReturnValue({
271
+ global: false,
272
+ repo: false,
273
+ local: false,
274
+ paths: {
275
+ global: {
276
+ path: '/home/user/.config/git-worktree-tools/config.json',
277
+ level: 'global',
278
+ exists: false,
279
+ },
280
+ repo: { path: '/repo/.worktreerc', level: 'repo', exists: false },
281
+ local: { path: '/repo/.worktreerc.local', level: 'local', exists: false },
282
+ },
283
+ });
284
+ prompts.promptConfirm.mockResolvedValue(true);
285
+ prompts.promptInput.mockResolvedValue('main');
286
+ prompts.promptChoice.mockResolvedValue('cursor');
287
+ await initCommand.handler(createArgv({ local: false, global: true, force: false }));
288
+ expect(globalConfig.saveGlobalConfig).toHaveBeenCalledWith(expect.objectContaining({
289
+ preferredEditor: 'cursor',
290
+ }));
291
+ });
292
+ it('includes baseBranch when not main', async () => {
293
+ git.getRepoRoot.mockReturnValue('/repo');
294
+ // Ensure global config doesn't exist
295
+ globalConfig.getConfigSummary.mockReturnValue({
296
+ global: false,
297
+ repo: false,
298
+ local: false,
299
+ paths: {
300
+ global: {
301
+ path: '/home/user/.config/git-worktree-tools/config.json',
302
+ level: 'global',
303
+ exists: false,
304
+ },
305
+ repo: { path: '/repo/.worktreerc', level: 'repo', exists: false },
306
+ local: { path: '/repo/.worktreerc.local', level: 'local', exists: false },
307
+ },
308
+ });
309
+ prompts.promptConfirm.mockResolvedValue(true);
310
+ prompts.promptInput.mockResolvedValue('develop');
311
+ prompts.promptChoice.mockResolvedValue('vscode');
312
+ await initCommand.handler(createArgv({ local: false, global: true, force: false }));
313
+ expect(globalConfig.saveGlobalConfig).toHaveBeenCalledWith(expect.objectContaining({
314
+ baseBranch: 'develop',
315
+ }));
316
+ });
317
+ });
318
+ describe('handler - interactive mode (no flags)', () => {
319
+ it('shows interactive menu when no flags specified', async () => {
320
+ git.getRepoRoot.mockReturnValue('/repo');
321
+ prompts.promptChoice.mockResolvedValue('cancel');
322
+ await initCommand.handler(createArgv({ local: false, global: false, force: false }));
323
+ expect(prompts.promptChoice).toHaveBeenCalled();
324
+ });
325
+ it('handles global choice in interactive mode', async () => {
326
+ git.getRepoRoot.mockReturnValue('/repo');
327
+ prompts.promptChoice
328
+ .mockResolvedValueOnce('global')
329
+ .mockResolvedValueOnce('vscode');
330
+ prompts.promptConfirm.mockResolvedValue(true);
331
+ prompts.promptInput.mockResolvedValue('main');
332
+ await initCommand.handler(createArgv({ local: false, global: false, force: false }));
333
+ expect(globalConfig.saveGlobalConfig).toHaveBeenCalled();
334
+ });
335
+ it('handles local choice in interactive mode', async () => {
336
+ git.getRepoRoot.mockReturnValue('/repo');
337
+ prompts.promptChoice.mockResolvedValueOnce('local');
338
+ prompts.promptConfirm.mockResolvedValue(false);
339
+ await initCommand.handler(createArgv({ local: false, global: false, force: false }));
340
+ expect(globalConfig.initializeLocalConfig).toHaveBeenCalled();
341
+ });
342
+ it('handles gitignore choice in interactive mode', async () => {
343
+ git.getRepoRoot.mockReturnValue('/repo');
344
+ globalConfig.getConfigSummary.mockReturnValue({
345
+ global: false,
346
+ repo: false,
347
+ local: true,
348
+ paths: {
349
+ global: {
350
+ path: '/home/user/.config/git-worktree-tools/config.json',
351
+ level: 'global',
352
+ exists: false,
353
+ },
354
+ repo: null,
355
+ local: { path: '/repo/.worktreerc.local', level: 'local', exists: true },
356
+ },
357
+ });
358
+ prompts.promptChoice.mockResolvedValueOnce('gitignore');
359
+ await initCommand.handler(createArgv({ local: false, global: false, force: false }));
360
+ expect(globalConfig.ensureLocalConfigInGitignore).toHaveBeenCalledWith('/repo');
361
+ });
362
+ it('handles cancel choice in interactive mode', async () => {
363
+ git.getRepoRoot.mockReturnValue('/repo');
364
+ prompts.promptChoice.mockResolvedValue('cancel');
365
+ await initCommand.handler(createArgv({ local: false, global: false, force: false }));
366
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Cancelled'));
367
+ });
368
+ it('shows all set message when configs already exist', async () => {
369
+ git.getRepoRoot.mockReturnValue('/repo');
370
+ globalConfig.getConfigSummary.mockReturnValue({
371
+ global: true,
372
+ repo: true,
373
+ local: true,
374
+ paths: {
375
+ global: {
376
+ path: '/home/user/.config/git-worktree-tools/config.json',
377
+ level: 'global',
378
+ exists: true,
379
+ },
380
+ repo: { path: '/repo/.worktreerc', level: 'repo', exists: true },
381
+ local: { path: '/repo/.worktreerc.local', level: 'local', exists: true },
382
+ },
383
+ });
384
+ await initCommand.handler(createArgv({ local: false, global: false, force: false }));
385
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('already set up'));
386
+ });
387
+ it('shows status when not in git repo', async () => {
388
+ git.getRepoRoot.mockImplementation(() => {
389
+ throw new Error('Not a git repo');
390
+ });
391
+ globalConfig.getConfigSummary.mockReturnValue({
392
+ global: false,
393
+ repo: null,
394
+ local: null,
395
+ paths: {
396
+ global: {
397
+ path: '/home/user/.config/git-worktree-tools/config.json',
398
+ level: 'global',
399
+ exists: false,
400
+ },
401
+ repo: null,
402
+ local: null,
403
+ },
404
+ });
405
+ prompts.promptChoice.mockResolvedValue('cancel');
406
+ await initCommand.handler(createArgv({ local: false, global: false, force: false }));
407
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Not in a git repository'));
408
+ });
409
+ it('shows gitignore no changes message when already up to date', async () => {
410
+ git.getRepoRoot.mockReturnValue('/repo');
411
+ globalConfig.getConfigSummary.mockReturnValue({
412
+ global: false,
413
+ repo: false,
414
+ local: true,
415
+ paths: {
416
+ global: {
417
+ path: '/home/user/.config/git-worktree-tools/config.json',
418
+ level: 'global',
419
+ exists: false,
420
+ },
421
+ repo: null,
422
+ local: { path: '/repo/.worktreerc.local', level: 'local', exists: true },
423
+ },
424
+ });
425
+ prompts.promptChoice.mockResolvedValueOnce('gitignore');
426
+ globalConfig.ensureLocalConfigInGitignore.mockReturnValue(false);
427
+ await initCommand.handler(createArgv({ local: false, global: false, force: false }));
428
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('No changes needed'));
429
+ });
430
+ });
431
+ });
432
+ //# sourceMappingURL=init.unit.test.js.map