@camaradesuk/git-worktree-tools 1.7.0 → 1.9.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/README.md +69 -8
- package/dist/cli/cleanpr.js +11 -5
- package/dist/cli/cleanpr.js.map +1 -1
- package/dist/cli/cleanpr.test.js +12 -1
- package/dist/cli/cleanpr.test.js.map +1 -1
- package/dist/cli/newpr.js +302 -37
- package/dist/cli/newpr.js.map +1 -1
- package/dist/cli/newpr.test.js +336 -9
- package/dist/cli/newpr.test.js.map +1 -1
- package/dist/cli/prs.d.ts +22 -0
- package/dist/cli/prs.d.ts.map +1 -0
- package/dist/cli/prs.js +275 -0
- package/dist/cli/prs.js.map +1 -0
- package/dist/cli/prs.test.d.ts +8 -0
- package/dist/cli/prs.test.d.ts.map +1 -0
- package/dist/cli/prs.test.js +410 -0
- package/dist/cli/prs.test.js.map +1 -0
- package/dist/cli/wt/interactive-menu.d.ts +2 -0
- package/dist/cli/wt/interactive-menu.d.ts.map +1 -1
- package/dist/cli/wt/interactive-menu.js +17 -0
- package/dist/cli/wt/interactive-menu.js.map +1 -1
- package/dist/cli/wt/interactive-menu.test.js +28 -0
- package/dist/cli/wt/interactive-menu.test.js.map +1 -1
- package/dist/cli/wt/prs.d.ts +21 -0
- package/dist/cli/wt/prs.d.ts.map +1 -0
- package/dist/cli/wt/prs.js +251 -0
- package/dist/cli/wt/prs.js.map +1 -0
- package/dist/cli/wt/prs.test.d.ts +5 -0
- package/dist/cli/wt/prs.test.d.ts.map +1 -0
- package/dist/cli/wt/prs.test.js +410 -0
- package/dist/cli/wt/prs.test.js.map +1 -0
- package/dist/cli/wt.d.ts +1 -0
- package/dist/cli/wt.d.ts.map +1 -1
- package/dist/cli/wt.js +5 -0
- package/dist/cli/wt.js.map +1 -1
- package/dist/cli/wtconfig.d.ts +1 -0
- package/dist/cli/wtconfig.d.ts.map +1 -1
- package/dist/cli/wtconfig.js +115 -0
- package/dist/cli/wtconfig.js.map +1 -1
- package/dist/cli/wtconfig.test.js +6 -0
- package/dist/cli/wtconfig.test.js.map +1 -1
- package/dist/cli/wtlink.d.ts +2 -1
- package/dist/cli/wtlink.d.ts.map +1 -1
- package/dist/cli/wtlink.js +60 -2
- package/dist/cli/wtlink.js.map +1 -1
- package/dist/e2e/cli.e2e.test.js +6 -2
- package/dist/e2e/cli.e2e.test.js.map +1 -1
- package/dist/e2e/helpers/cli-runner.d.ts.map +1 -1
- package/dist/e2e/helpers/cli-runner.js +1 -0
- package/dist/e2e/helpers/cli-runner.js.map +1 -1
- package/dist/e2e/helpers/gh-mock.d.ts.map +1 -1
- package/dist/e2e/helpers/gh-mock.js +55 -1
- package/dist/e2e/helpers/gh-mock.js.map +1 -1
- package/dist/e2e/helpers/pty-wrapper.d.ts +15 -0
- package/dist/e2e/helpers/pty-wrapper.d.ts.map +1 -1
- package/dist/e2e/helpers/pty-wrapper.js +65 -0
- package/dist/e2e/helpers/pty-wrapper.js.map +1 -1
- package/dist/e2e/newpr-full-flow.e2e.test.js +1 -0
- package/dist/e2e/newpr-full-flow.e2e.test.js.map +1 -1
- package/dist/e2e/prs/prs.e2e.test.d.ts +7 -0
- package/dist/e2e/prs/prs.e2e.test.d.ts.map +1 -0
- package/dist/e2e/prs/prs.e2e.test.js +606 -0
- package/dist/e2e/prs/prs.e2e.test.js.map +1 -0
- package/dist/e2e/wt/interactive-menu.e2e.test.d.ts +8 -0
- package/dist/e2e/wt/interactive-menu.e2e.test.d.ts.map +1 -0
- package/dist/e2e/wt/interactive-menu.e2e.test.js +583 -0
- package/dist/e2e/wt/interactive-menu.e2e.test.js.map +1 -0
- package/dist/e2e/wt/wt.e2e.test.js +217 -4
- package/dist/e2e/wt/wt.e2e.test.js.map +1 -1
- package/dist/integration/prs.integration.test.d.ts +8 -0
- package/dist/integration/prs.integration.test.d.ts.map +1 -0
- package/dist/integration/prs.integration.test.js +478 -0
- package/dist/integration/prs.integration.test.js.map +1 -0
- package/dist/lib/ai/types.d.ts +12 -0
- package/dist/lib/ai/types.d.ts.map +1 -1
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/cleanpr/worktree-info.d.ts.map +1 -1
- package/dist/lib/cleanpr/worktree-info.js +1 -6
- package/dist/lib/cleanpr/worktree-info.js.map +1 -1
- package/dist/lib/cleanpr/worktree-info.test.js +10 -13
- package/dist/lib/cleanpr/worktree-info.test.js.map +1 -1
- package/dist/lib/config-editor.d.ts.map +1 -1
- package/dist/lib/config-editor.js.map +1 -1
- package/dist/lib/config-migration/detector.d.ts +25 -0
- package/dist/lib/config-migration/detector.d.ts.map +1 -0
- package/dist/lib/config-migration/detector.js +372 -0
- package/dist/lib/config-migration/detector.js.map +1 -0
- package/dist/lib/config-migration/detector.test.d.ts +5 -0
- package/dist/lib/config-migration/detector.test.d.ts.map +1 -0
- package/dist/lib/config-migration/detector.test.js +201 -0
- package/dist/lib/config-migration/detector.test.js.map +1 -0
- package/dist/lib/config-migration/index.d.ts +29 -0
- package/dist/lib/config-migration/index.d.ts.map +1 -0
- package/dist/lib/config-migration/index.js +33 -0
- package/dist/lib/config-migration/index.js.map +1 -0
- package/dist/lib/config-migration/reporter.d.ts +53 -0
- package/dist/lib/config-migration/reporter.d.ts.map +1 -0
- package/dist/lib/config-migration/reporter.js +257 -0
- package/dist/lib/config-migration/reporter.js.map +1 -0
- package/dist/lib/config-migration/reporter.test.d.ts +5 -0
- package/dist/lib/config-migration/reporter.test.d.ts.map +1 -0
- package/dist/lib/config-migration/reporter.test.js +305 -0
- package/dist/lib/config-migration/reporter.test.js.map +1 -0
- package/dist/lib/config-migration/runner.d.ts +46 -0
- package/dist/lib/config-migration/runner.d.ts.map +1 -0
- package/dist/lib/config-migration/runner.js +364 -0
- package/dist/lib/config-migration/runner.js.map +1 -0
- package/dist/lib/config-migration/runner.test.d.ts +5 -0
- package/dist/lib/config-migration/runner.test.d.ts.map +1 -0
- package/dist/lib/config-migration/runner.test.js +235 -0
- package/dist/lib/config-migration/runner.test.js.map +1 -0
- package/dist/lib/config-migration/types.d.ts +120 -0
- package/dist/lib/config-migration/types.d.ts.map +1 -0
- package/dist/lib/config-migration/types.js +70 -0
- package/dist/lib/config-migration/types.js.map +1 -0
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config-validation.js +62 -0
- package/dist/lib/config-validation.js.map +1 -1
- package/dist/lib/config-validation.test.js +25 -0
- package/dist/lib/config-validation.test.js.map +1 -1
- package/dist/lib/config.d.ts +65 -7
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +12 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/git.d.ts +32 -0
- package/dist/lib/git.d.ts.map +1 -1
- package/dist/lib/git.js +95 -1
- package/dist/lib/git.js.map +1 -1
- package/dist/lib/git.test.js +118 -1
- package/dist/lib/git.test.js.map +1 -1
- package/dist/lib/github.d.ts +41 -0
- package/dist/lib/github.d.ts.map +1 -1
- package/dist/lib/github.js +109 -0
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/global-check.d.ts +2 -2
- package/dist/lib/global-check.js +3 -3
- package/dist/lib/global-check.js.map +1 -1
- package/dist/lib/global-check.test.js +3 -6
- package/dist/lib/global-check.test.js.map +1 -1
- package/dist/lib/hooks/confirmation.d.ts +49 -0
- package/dist/lib/hooks/confirmation.d.ts.map +1 -0
- package/dist/lib/hooks/confirmation.js +147 -0
- package/dist/lib/hooks/confirmation.js.map +1 -0
- package/dist/lib/hooks/confirmation.test.d.ts +7 -0
- package/dist/lib/hooks/confirmation.test.d.ts.map +1 -0
- package/dist/lib/hooks/confirmation.test.js +300 -0
- package/dist/lib/hooks/confirmation.test.js.map +1 -0
- package/dist/lib/hooks/executor.d.ts +16 -1
- package/dist/lib/hooks/executor.d.ts.map +1 -1
- package/dist/lib/hooks/executor.js +53 -4
- package/dist/lib/hooks/executor.js.map +1 -1
- package/dist/lib/hooks/index.d.ts +4 -2
- package/dist/lib/hooks/index.d.ts.map +1 -1
- package/dist/lib/hooks/index.js +3 -2
- package/dist/lib/hooks/index.js.map +1 -1
- package/dist/lib/hooks/types.d.ts +16 -0
- package/dist/lib/hooks/types.d.ts.map +1 -1
- package/dist/lib/hooks/types.js +12 -0
- package/dist/lib/hooks/types.js.map +1 -1
- package/dist/lib/json-output.d.ts +1 -0
- package/dist/lib/json-output.d.ts.map +1 -1
- package/dist/lib/json-output.js +2 -0
- package/dist/lib/json-output.js.map +1 -1
- package/dist/lib/lswt/action-executors.d.ts +2 -0
- package/dist/lib/lswt/action-executors.d.ts.map +1 -1
- package/dist/lib/lswt/action-executors.js +6 -4
- package/dist/lib/lswt/action-executors.js.map +1 -1
- package/dist/lib/lswt/action-executors.test.js +8 -0
- package/dist/lib/lswt/action-executors.test.js.map +1 -1
- package/dist/lib/lswt/actions.d.ts +1 -0
- package/dist/lib/lswt/actions.d.ts.map +1 -1
- package/dist/lib/lswt/actions.js +38 -10
- package/dist/lib/lswt/actions.js.map +1 -1
- package/dist/lib/lswt/actions.test.js +34 -22
- package/dist/lib/lswt/actions.test.js.map +1 -1
- package/dist/lib/lswt/environment.d.ts +21 -2
- package/dist/lib/lswt/environment.d.ts.map +1 -1
- package/dist/lib/lswt/environment.js +73 -32
- package/dist/lib/lswt/environment.js.map +1 -1
- package/dist/lib/lswt/environment.test.js +79 -1
- package/dist/lib/lswt/environment.test.js.map +1 -1
- package/dist/lib/lswt/interactive.js +8 -8
- package/dist/lib/lswt/interactive.js.map +1 -1
- package/dist/lib/lswt/worktree-info.d.ts.map +1 -1
- package/dist/lib/lswt/worktree-info.js +1 -6
- package/dist/lib/lswt/worktree-info.js.map +1 -1
- package/dist/lib/lswt/worktree-info.test.js +5 -17
- package/dist/lib/lswt/worktree-info.test.js.map +1 -1
- package/dist/lib/newpr/args.d.ts.map +1 -1
- package/dist/lib/newpr/args.js +15 -1
- package/dist/lib/newpr/args.js.map +1 -1
- package/dist/lib/newpr/hook-runner.d.ts +11 -0
- package/dist/lib/newpr/hook-runner.d.ts.map +1 -1
- package/dist/lib/newpr/hook-runner.js +49 -1
- package/dist/lib/newpr/hook-runner.js.map +1 -1
- package/dist/lib/newpr/hook-runner.test.js +121 -0
- package/dist/lib/newpr/hook-runner.test.js.map +1 -1
- package/dist/lib/newpr/plan-generator.d.ts +121 -0
- package/dist/lib/newpr/plan-generator.d.ts.map +1 -0
- package/dist/lib/newpr/plan-generator.js +185 -0
- package/dist/lib/newpr/plan-generator.js.map +1 -0
- package/dist/lib/newpr/plan-generator.test.d.ts +7 -0
- package/dist/lib/newpr/plan-generator.test.d.ts.map +1 -0
- package/dist/lib/newpr/plan-generator.test.js +387 -0
- package/dist/lib/newpr/plan-generator.test.js.map +1 -0
- package/dist/lib/newpr/types.d.ts +6 -0
- package/dist/lib/newpr/types.d.ts.map +1 -1
- package/dist/lib/prompts.d.ts.map +1 -1
- package/dist/lib/prompts.js +16 -12
- package/dist/lib/prompts.js.map +1 -1
- package/dist/lib/prs/actions.d.ts +74 -0
- package/dist/lib/prs/actions.d.ts.map +1 -0
- package/dist/lib/prs/actions.js +446 -0
- package/dist/lib/prs/actions.js.map +1 -0
- package/dist/lib/prs/actions.test.d.ts +5 -0
- package/dist/lib/prs/actions.test.d.ts.map +1 -0
- package/dist/lib/prs/actions.test.js +356 -0
- package/dist/lib/prs/actions.test.js.map +1 -0
- package/dist/lib/prs/data.d.ts +48 -0
- package/dist/lib/prs/data.d.ts.map +1 -0
- package/dist/lib/prs/data.js +171 -0
- package/dist/lib/prs/data.js.map +1 -0
- package/dist/lib/prs/data.test.d.ts +5 -0
- package/dist/lib/prs/data.test.d.ts.map +1 -0
- package/dist/lib/prs/data.test.js +417 -0
- package/dist/lib/prs/data.test.js.map +1 -0
- package/dist/lib/prs/details.d.ts +57 -0
- package/dist/lib/prs/details.d.ts.map +1 -0
- package/dist/lib/prs/details.js +246 -0
- package/dist/lib/prs/details.js.map +1 -0
- package/dist/lib/prs/details.test.d.ts +5 -0
- package/dist/lib/prs/details.test.d.ts.map +1 -0
- package/dist/lib/prs/details.test.js +325 -0
- package/dist/lib/prs/details.test.js.map +1 -0
- package/dist/lib/prs/filters.d.ts +56 -0
- package/dist/lib/prs/filters.d.ts.map +1 -0
- package/dist/lib/prs/filters.js +171 -0
- package/dist/lib/prs/filters.js.map +1 -0
- package/dist/lib/prs/filters.test.d.ts +5 -0
- package/dist/lib/prs/filters.test.d.ts.map +1 -0
- package/dist/lib/prs/filters.test.js +312 -0
- package/dist/lib/prs/filters.test.js.map +1 -0
- package/dist/lib/prs/formatters.d.ts +83 -0
- package/dist/lib/prs/formatters.d.ts.map +1 -0
- package/dist/lib/prs/formatters.js +389 -0
- package/dist/lib/prs/formatters.js.map +1 -0
- package/dist/lib/prs/formatters.test.d.ts +2 -0
- package/dist/lib/prs/formatters.test.d.ts.map +1 -0
- package/dist/lib/prs/formatters.test.js +387 -0
- package/dist/lib/prs/formatters.test.js.map +1 -0
- package/dist/lib/prs/interactive.d.ts +50 -0
- package/dist/lib/prs/interactive.d.ts.map +1 -0
- package/dist/lib/prs/interactive.js +453 -0
- package/dist/lib/prs/interactive.js.map +1 -0
- package/dist/lib/prs/interactive.test.d.ts +5 -0
- package/dist/lib/prs/interactive.test.d.ts.map +1 -0
- package/dist/lib/prs/interactive.test.js +364 -0
- package/dist/lib/prs/interactive.test.js.map +1 -0
- package/dist/lib/prs/types.d.ts +152 -0
- package/dist/lib/prs/types.d.ts.map +1 -0
- package/dist/lib/prs/types.js +17 -0
- package/dist/lib/prs/types.js.map +1 -0
- package/dist/lib/wtconfig/environment.d.ts +18 -1
- package/dist/lib/wtconfig/environment.d.ts.map +1 -1
- package/dist/lib/wtconfig/environment.js +60 -24
- package/dist/lib/wtconfig/environment.js.map +1 -1
- package/dist/lib/wtconfig/environment.test.js +45 -1
- package/dist/lib/wtconfig/environment.test.js.map +1 -1
- package/dist/lib/wtlink/config-manifest.d.ts +101 -0
- package/dist/lib/wtlink/config-manifest.d.ts.map +1 -0
- package/dist/lib/wtlink/config-manifest.js +219 -0
- package/dist/lib/wtlink/config-manifest.js.map +1 -0
- package/dist/lib/wtlink/config-manifest.test.d.ts +2 -0
- package/dist/lib/wtlink/config-manifest.test.d.ts.map +1 -0
- package/dist/lib/wtlink/config-manifest.test.js +486 -0
- package/dist/lib/wtlink/config-manifest.test.js.map +1 -0
- package/dist/lib/wtlink/link-configs.d.ts.map +1 -1
- package/dist/lib/wtlink/link-configs.js +36 -11
- package/dist/lib/wtlink/link-configs.js.map +1 -1
- package/dist/lib/wtlink/main-menu.d.ts.map +1 -1
- package/dist/lib/wtlink/main-menu.js +58 -50
- package/dist/lib/wtlink/main-menu.js.map +1 -1
- package/dist/lib/wtlink/main-menu.test.js +42 -40
- package/dist/lib/wtlink/main-menu.test.js.map +1 -1
- package/dist/lib/wtlink/manage-manifest.d.ts +9 -0
- package/dist/lib/wtlink/manage-manifest.d.ts.map +1 -1
- package/dist/lib/wtlink/manage-manifest.js +346 -25
- package/dist/lib/wtlink/manage-manifest.js.map +1 -1
- package/dist/lib/wtlink/manage-manifest.test.js +52 -1
- package/dist/lib/wtlink/manage-manifest.test.js.map +1 -1
- package/dist/lib/wtlink/validate-manifest.d.ts.map +1 -1
- package/dist/lib/wtlink/validate-manifest.js +27 -6
- package/dist/lib/wtlink/validate-manifest.js.map +1 -1
- package/package.json +2 -1
- package/schemas/worktreerc.schema.json +49 -1
package/dist/cli/newpr.test.js
CHANGED
|
@@ -3,11 +3,14 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
3
3
|
vi.mock('../lib/git.js', () => ({
|
|
4
4
|
getRepoRoot: vi.fn(),
|
|
5
5
|
getRepoName: vi.fn(),
|
|
6
|
+
getMainWorktreeRoot: vi.fn(),
|
|
6
7
|
fetch: vi.fn(),
|
|
8
|
+
fetchAsync: vi.fn().mockResolvedValue(undefined),
|
|
7
9
|
getCurrentBranch: vi.fn(),
|
|
8
10
|
checkout: vi.fn(),
|
|
9
11
|
exec: vi.fn(),
|
|
10
12
|
push: vi.fn(),
|
|
13
|
+
pushAsync: vi.fn().mockResolvedValue(undefined),
|
|
11
14
|
commit: vi.fn(),
|
|
12
15
|
add: vi.fn(),
|
|
13
16
|
stash: vi.fn(),
|
|
@@ -15,6 +18,7 @@ vi.mock('../lib/git.js', () => ({
|
|
|
15
18
|
stashDrop: vi.fn(),
|
|
16
19
|
stashPop: vi.fn(),
|
|
17
20
|
addWorktree: vi.fn(),
|
|
21
|
+
addWorktreeAsync: vi.fn().mockResolvedValue(undefined),
|
|
18
22
|
getStagedFiles: vi.fn(),
|
|
19
23
|
getUnstagedFiles: vi.fn(),
|
|
20
24
|
getStatusOutput: vi.fn(),
|
|
@@ -33,6 +37,10 @@ vi.mock('../lib/github.js', () => ({
|
|
|
33
37
|
}));
|
|
34
38
|
vi.mock('../lib/prompts.js', () => ({
|
|
35
39
|
promptChoiceIndex: vi.fn(),
|
|
40
|
+
promptConfirm: vi.fn(),
|
|
41
|
+
withSpinner: vi.fn(async (message, fn) => {
|
|
42
|
+
return await fn();
|
|
43
|
+
}),
|
|
36
44
|
}));
|
|
37
45
|
vi.mock('../lib/config.js', () => ({
|
|
38
46
|
loadConfig: vi.fn(),
|
|
@@ -80,6 +88,12 @@ vi.mock('fs', () => ({
|
|
|
80
88
|
existsSync: vi.fn(),
|
|
81
89
|
symlinkSync: vi.fn(),
|
|
82
90
|
}));
|
|
91
|
+
vi.mock('../lib/wtlink/config-manifest.js', () => ({
|
|
92
|
+
getEnabledFiles: vi.fn(),
|
|
93
|
+
}));
|
|
94
|
+
vi.mock('../lib/wtlink/link-configs.js', () => ({
|
|
95
|
+
run: vi.fn(),
|
|
96
|
+
}));
|
|
83
97
|
// Import after mocking
|
|
84
98
|
import * as git from '../lib/git.js';
|
|
85
99
|
import * as github from '../lib/github.js';
|
|
@@ -88,18 +102,22 @@ import { loadConfig, generateBranchNameAsync, generateWorktreePath, generatePRCo
|
|
|
88
102
|
import { analyzeGitState, detectScenario } from '../lib/state-detection.js';
|
|
89
103
|
import * as newpr from '../lib/newpr/index.js';
|
|
90
104
|
import fs from 'fs';
|
|
105
|
+
import { getEnabledFiles } from '../lib/wtlink/config-manifest.js';
|
|
106
|
+
import { run as runWtlink } from '../lib/wtlink/link-configs.js';
|
|
91
107
|
describe('cli/newpr', () => {
|
|
92
108
|
let mockConsoleLog;
|
|
93
109
|
let mockConsoleError;
|
|
94
110
|
let mockProcessExit;
|
|
95
111
|
let originalArgv;
|
|
96
112
|
const defaultConfig = {
|
|
113
|
+
configVersion: 1,
|
|
97
114
|
baseBranch: 'main',
|
|
98
115
|
worktreePattern: '{repo}.pr{number}',
|
|
99
116
|
worktreeParent: '..',
|
|
100
117
|
draftPr: false,
|
|
101
118
|
sharedRepos: [],
|
|
102
119
|
branchPrefix: 'feature',
|
|
120
|
+
previewLabel: 'preview',
|
|
103
121
|
syncPatterns: [],
|
|
104
122
|
preferredEditor: 'auto',
|
|
105
123
|
ai: { provider: 'none' },
|
|
@@ -110,6 +128,8 @@ describe('cli/newpr', () => {
|
|
|
110
128
|
integrations: {},
|
|
111
129
|
logging: { level: 'info', timestamps: true },
|
|
112
130
|
global: { warnNotGlobal: true },
|
|
131
|
+
wtlink: { enabled: [], disabled: [] },
|
|
132
|
+
linkConfigFiles: undefined,
|
|
113
133
|
};
|
|
114
134
|
const defaultOptions = {
|
|
115
135
|
baseBranch: 'main',
|
|
@@ -146,6 +166,14 @@ describe('cli/newpr', () => {
|
|
|
146
166
|
});
|
|
147
167
|
beforeEach(() => {
|
|
148
168
|
vi.resetAllMocks();
|
|
169
|
+
// Reset async git mocks (resetAllMocks clears implementations)
|
|
170
|
+
vi.mocked(git.fetchAsync).mockResolvedValue(undefined);
|
|
171
|
+
vi.mocked(git.pushAsync).mockResolvedValue(undefined);
|
|
172
|
+
vi.mocked(git.addWorktreeAsync).mockResolvedValue(undefined);
|
|
173
|
+
// Reset withSpinner mock (resetAllMocks clears the implementation)
|
|
174
|
+
vi.mocked(prompts.withSpinner).mockImplementation(async (message, fn) => {
|
|
175
|
+
return await fn();
|
|
176
|
+
});
|
|
149
177
|
// Reset the hook runner mock to allow all hooks to pass
|
|
150
178
|
mockHookRunner.runHook.mockResolvedValue(true);
|
|
151
179
|
mockHookRunner.runCleanup.mockResolvedValue(undefined);
|
|
@@ -170,6 +198,12 @@ describe('cli/newpr', () => {
|
|
|
170
198
|
});
|
|
171
199
|
vi.mocked(git.getChangedFiles).mockReturnValue([]);
|
|
172
200
|
vi.mocked(git.getCommitMessages).mockReturnValue([]);
|
|
201
|
+
// Default mocks for auto-link feature (throw by default to skip auto-link)
|
|
202
|
+
vi.mocked(git.getMainWorktreeRoot).mockImplementation(() => {
|
|
203
|
+
throw new Error('Mock: getMainWorktreeRoot not configured');
|
|
204
|
+
});
|
|
205
|
+
vi.mocked(getEnabledFiles).mockReturnValue([]);
|
|
206
|
+
vi.mocked(runWtlink).mockResolvedValue(undefined);
|
|
173
207
|
mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
174
208
|
mockConsoleError = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
175
209
|
// @ts-expect-error - process.exit mock type is complex
|
|
@@ -186,7 +220,8 @@ describe('cli/newpr', () => {
|
|
|
186
220
|
async function runCli(args = []) {
|
|
187
221
|
process.argv = ['node', 'newpr', ...args];
|
|
188
222
|
await import('./newpr.js');
|
|
189
|
-
|
|
223
|
+
// Allow time for all async operations to complete
|
|
224
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
190
225
|
}
|
|
191
226
|
describe('help option', () => {
|
|
192
227
|
it('prints help and exits 0 on --help', async () => {
|
|
@@ -246,7 +281,8 @@ describe('cli/newpr', () => {
|
|
|
246
281
|
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
247
282
|
await runCli(['--pr', '123']);
|
|
248
283
|
// Verify wiring: worktree path, branch, and options are correctly passed through
|
|
249
|
-
|
|
284
|
+
// Non-JSON mode uses addWorktreeAsync
|
|
285
|
+
expect(git.addWorktreeAsync).toHaveBeenCalledWith('/repo.pr123', // path from generateWorktreePath
|
|
250
286
|
'feature-123', // branch from PR info
|
|
251
287
|
expect.objectContaining({
|
|
252
288
|
createBranch: true,
|
|
@@ -292,8 +328,8 @@ describe('cli/newpr', () => {
|
|
|
292
328
|
head: 'my-feature',
|
|
293
329
|
base: 'main', // from config
|
|
294
330
|
}));
|
|
295
|
-
// Verify wiring:
|
|
296
|
-
expect(git.
|
|
331
|
+
// Verify wiring: addWorktreeAsync receives correct path, branch, and options (non-JSON mode)
|
|
332
|
+
expect(git.addWorktreeAsync).toHaveBeenCalledWith('/repo.pr456', // path from generateWorktreePath
|
|
297
333
|
'my-feature', // the branch name
|
|
298
334
|
expect.objectContaining({
|
|
299
335
|
createBranch: true,
|
|
@@ -362,22 +398,24 @@ describe('cli/newpr', () => {
|
|
|
362
398
|
aiGenerated: false,
|
|
363
399
|
});
|
|
364
400
|
await runCli(['Add new feature']);
|
|
365
|
-
// Verify wiring: checkout uses generated branch name and branch point
|
|
401
|
+
// Verify wiring: checkout uses generated branch name and branch point with repoRoot
|
|
366
402
|
expect(git.exec).toHaveBeenCalledWith([
|
|
367
403
|
'checkout',
|
|
368
404
|
'-b',
|
|
369
405
|
'feature/add-new-feature', // from generateBranchNameAsync
|
|
370
406
|
'origin/main', // from getBranchPoint
|
|
371
|
-
])
|
|
407
|
+
], { cwd: '/repo' } // repoRoot from getRepoRoot()
|
|
408
|
+
);
|
|
372
409
|
// Verify wiring: createPr receives correct branch and title (from generatePRContentAsync)
|
|
373
410
|
expect(github.createPr).toHaveBeenCalledWith(expect.objectContaining({
|
|
374
411
|
head: 'feature/add-new-feature',
|
|
375
412
|
base: 'main',
|
|
376
413
|
title: 'Add new feature',
|
|
377
414
|
}));
|
|
378
|
-
// Verify wiring:
|
|
379
|
-
expect(git.
|
|
380
|
-
'feature/add-new-feature' // the branch name
|
|
415
|
+
// Verify wiring: addWorktreeAsync receives correct path, branch, and cwd option
|
|
416
|
+
expect(git.addWorktreeAsync).toHaveBeenCalledWith('/repo.pr100', // path from generateWorktreePath
|
|
417
|
+
'feature/add-new-feature', // the branch name
|
|
418
|
+
{ cwd: '/repo' } // repoRoot from getRepoRoot()
|
|
381
419
|
);
|
|
382
420
|
});
|
|
383
421
|
it('exits 1 when user cancels', async () => {
|
|
@@ -851,5 +889,294 @@ describe('cli/newpr', () => {
|
|
|
851
889
|
);
|
|
852
890
|
});
|
|
853
891
|
});
|
|
892
|
+
describe('Bug fix: git operations must use repoRoot (empty commit worktree bug)', () => {
|
|
893
|
+
// These tests verify the fix for the bug where creating a PR worktree
|
|
894
|
+
// with an empty commit failed because git.checkout() didn't switch back
|
|
895
|
+
// to main (missing repoRoot parameter), causing git.addWorktree() to fail
|
|
896
|
+
// with "branch already checked out" error.
|
|
897
|
+
it('git.checkout after push must use repoRoot parameter', async () => {
|
|
898
|
+
const repoRoot = '/repo';
|
|
899
|
+
vi.mocked(newpr.parseArgs).mockReturnValue({
|
|
900
|
+
kind: 'success',
|
|
901
|
+
options: { mode: 'new', description: 'Test feature', ...defaultOptions },
|
|
902
|
+
});
|
|
903
|
+
vi.mocked(github.isGhInstalled).mockReturnValue(true);
|
|
904
|
+
vi.mocked(github.isAuthenticated).mockReturnValue(true);
|
|
905
|
+
vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
|
|
906
|
+
vi.mocked(git.getRepoName).mockReturnValue('repo');
|
|
907
|
+
vi.mocked(loadConfig).mockReturnValue(defaultConfig);
|
|
908
|
+
vi.mocked(generateBranchNameAsync).mockResolvedValue('feat/test-feature');
|
|
909
|
+
vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
|
|
910
|
+
vi.mocked(detectScenario).mockReturnValue('main_clean_same');
|
|
911
|
+
vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
|
|
912
|
+
vi.mocked(newpr.getScenarioContext).mockReturnValue({
|
|
913
|
+
message: 'No changes detected',
|
|
914
|
+
choices: [
|
|
915
|
+
{
|
|
916
|
+
label: 'Continue with empty initial commit',
|
|
917
|
+
action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
|
|
918
|
+
},
|
|
919
|
+
{ label: 'Cancel', action: null },
|
|
920
|
+
],
|
|
921
|
+
});
|
|
922
|
+
vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
|
|
923
|
+
vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1);
|
|
924
|
+
vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
|
|
925
|
+
vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
|
|
926
|
+
vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
|
|
927
|
+
vi.mocked(git.remoteBranchExists).mockReturnValue(false);
|
|
928
|
+
vi.mocked(git.getCurrentBranch).mockReturnValue('main');
|
|
929
|
+
vi.mocked(git.getStagedFiles).mockReturnValue([]);
|
|
930
|
+
vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
|
|
931
|
+
vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
|
|
932
|
+
await runCli(['Test feature']);
|
|
933
|
+
// CRITICAL: git.checkout must be called with repoRoot to switch back to main
|
|
934
|
+
// Without this, the branch remains checked out and worktree creation fails
|
|
935
|
+
expect(git.checkout).toHaveBeenCalledWith('main', repoRoot);
|
|
936
|
+
});
|
|
937
|
+
it('git.addWorktreeAsync must use { cwd: repoRoot } option', async () => {
|
|
938
|
+
const repoRoot = '/repo';
|
|
939
|
+
vi.mocked(newpr.parseArgs).mockReturnValue({
|
|
940
|
+
kind: 'success',
|
|
941
|
+
options: { mode: 'new', description: 'Test feature', ...defaultOptions },
|
|
942
|
+
});
|
|
943
|
+
vi.mocked(github.isGhInstalled).mockReturnValue(true);
|
|
944
|
+
vi.mocked(github.isAuthenticated).mockReturnValue(true);
|
|
945
|
+
vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
|
|
946
|
+
vi.mocked(git.getRepoName).mockReturnValue('repo');
|
|
947
|
+
vi.mocked(loadConfig).mockReturnValue(defaultConfig);
|
|
948
|
+
vi.mocked(generateBranchNameAsync).mockResolvedValue('feat/test-feature');
|
|
949
|
+
vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
|
|
950
|
+
vi.mocked(detectScenario).mockReturnValue('main_clean_same');
|
|
951
|
+
vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
|
|
952
|
+
vi.mocked(newpr.getScenarioContext).mockReturnValue({
|
|
953
|
+
message: 'No changes detected',
|
|
954
|
+
choices: [
|
|
955
|
+
{
|
|
956
|
+
label: 'Continue with empty initial commit',
|
|
957
|
+
action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
|
|
958
|
+
},
|
|
959
|
+
{ label: 'Cancel', action: null },
|
|
960
|
+
],
|
|
961
|
+
});
|
|
962
|
+
vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
|
|
963
|
+
vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1);
|
|
964
|
+
vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
|
|
965
|
+
vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
|
|
966
|
+
vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
|
|
967
|
+
vi.mocked(git.remoteBranchExists).mockReturnValue(false);
|
|
968
|
+
vi.mocked(git.getCurrentBranch).mockReturnValue('main');
|
|
969
|
+
vi.mocked(git.getStagedFiles).mockReturnValue([]);
|
|
970
|
+
vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
|
|
971
|
+
vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
|
|
972
|
+
await runCli(['Test feature']);
|
|
973
|
+
// CRITICAL: git.addWorktreeAsync must be called with { cwd: repoRoot }
|
|
974
|
+
// to ensure worktree is created from the correct directory
|
|
975
|
+
expect(git.addWorktreeAsync).toHaveBeenCalledWith('/repo.pr100', 'feat/test-feature', {
|
|
976
|
+
cwd: repoRoot,
|
|
977
|
+
});
|
|
978
|
+
});
|
|
979
|
+
it('git.pushAsync must use repoRoot parameter', async () => {
|
|
980
|
+
const repoRoot = '/repo';
|
|
981
|
+
vi.mocked(newpr.parseArgs).mockReturnValue({
|
|
982
|
+
kind: 'success',
|
|
983
|
+
options: { mode: 'new', description: 'Test feature', ...defaultOptions },
|
|
984
|
+
});
|
|
985
|
+
vi.mocked(github.isGhInstalled).mockReturnValue(true);
|
|
986
|
+
vi.mocked(github.isAuthenticated).mockReturnValue(true);
|
|
987
|
+
vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
|
|
988
|
+
vi.mocked(git.getRepoName).mockReturnValue('repo');
|
|
989
|
+
vi.mocked(loadConfig).mockReturnValue(defaultConfig);
|
|
990
|
+
vi.mocked(generateBranchNameAsync).mockResolvedValue('feat/test-feature');
|
|
991
|
+
vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
|
|
992
|
+
vi.mocked(detectScenario).mockReturnValue('main_clean_same');
|
|
993
|
+
vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
|
|
994
|
+
vi.mocked(newpr.getScenarioContext).mockReturnValue({
|
|
995
|
+
message: 'No changes detected',
|
|
996
|
+
choices: [
|
|
997
|
+
{
|
|
998
|
+
label: 'Continue with empty initial commit',
|
|
999
|
+
action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
|
|
1000
|
+
},
|
|
1001
|
+
{ label: 'Cancel', action: null },
|
|
1002
|
+
],
|
|
1003
|
+
});
|
|
1004
|
+
vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
|
|
1005
|
+
vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1);
|
|
1006
|
+
vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
|
|
1007
|
+
vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
|
|
1008
|
+
vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
|
|
1009
|
+
vi.mocked(git.remoteBranchExists).mockReturnValue(false);
|
|
1010
|
+
vi.mocked(git.getCurrentBranch).mockReturnValue('main');
|
|
1011
|
+
vi.mocked(git.getStagedFiles).mockReturnValue([]);
|
|
1012
|
+
vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
|
|
1013
|
+
vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
|
|
1014
|
+
await runCli(['Test feature']);
|
|
1015
|
+
// git.pushAsync must be called with repoRoot to push from correct directory
|
|
1016
|
+
expect(git.pushAsync).toHaveBeenCalledWith({ setUpstream: true, remote: 'origin', branch: 'feat/test-feature' }, repoRoot);
|
|
1017
|
+
});
|
|
1018
|
+
it('git.exec for branch checkout must use { cwd: repoRoot } option', async () => {
|
|
1019
|
+
const repoRoot = '/repo';
|
|
1020
|
+
vi.mocked(newpr.parseArgs).mockReturnValue({
|
|
1021
|
+
kind: 'success',
|
|
1022
|
+
options: { mode: 'new', description: 'Test feature', ...defaultOptions },
|
|
1023
|
+
});
|
|
1024
|
+
vi.mocked(github.isGhInstalled).mockReturnValue(true);
|
|
1025
|
+
vi.mocked(github.isAuthenticated).mockReturnValue(true);
|
|
1026
|
+
vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
|
|
1027
|
+
vi.mocked(git.getRepoName).mockReturnValue('repo');
|
|
1028
|
+
vi.mocked(loadConfig).mockReturnValue(defaultConfig);
|
|
1029
|
+
vi.mocked(generateBranchNameAsync).mockResolvedValue('feat/test-feature');
|
|
1030
|
+
vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
|
|
1031
|
+
vi.mocked(detectScenario).mockReturnValue('main_clean_same');
|
|
1032
|
+
vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
|
|
1033
|
+
vi.mocked(newpr.getScenarioContext).mockReturnValue({
|
|
1034
|
+
message: 'No changes detected',
|
|
1035
|
+
choices: [
|
|
1036
|
+
{
|
|
1037
|
+
label: 'Continue with empty initial commit',
|
|
1038
|
+
action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
|
|
1039
|
+
},
|
|
1040
|
+
{ label: 'Cancel', action: null },
|
|
1041
|
+
],
|
|
1042
|
+
});
|
|
1043
|
+
vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
|
|
1044
|
+
vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1);
|
|
1045
|
+
vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
|
|
1046
|
+
vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
|
|
1047
|
+
vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
|
|
1048
|
+
vi.mocked(git.remoteBranchExists).mockReturnValue(false);
|
|
1049
|
+
vi.mocked(git.getCurrentBranch).mockReturnValue('main');
|
|
1050
|
+
vi.mocked(git.getStagedFiles).mockReturnValue([]);
|
|
1051
|
+
vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
|
|
1052
|
+
vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
|
|
1053
|
+
await runCli(['Test feature']);
|
|
1054
|
+
// git.exec for branch creation must use cwd option to ensure
|
|
1055
|
+
// branch is created in the correct worktree
|
|
1056
|
+
expect(git.exec).toHaveBeenCalledWith(['checkout', '-b', 'feat/test-feature', 'origin/main'], { cwd: repoRoot });
|
|
1057
|
+
});
|
|
1058
|
+
});
|
|
1059
|
+
describe('auto-link config files (linkConfigFiles feature)', () => {
|
|
1060
|
+
// Helper to set up all the mocks for a successful PR creation
|
|
1061
|
+
const setupPrCreationMocks = (configOverrides = {}) => {
|
|
1062
|
+
vi.mocked(newpr.parseArgs).mockReturnValue({
|
|
1063
|
+
kind: 'success',
|
|
1064
|
+
options: { mode: 'pr', prNumber: 123, ...defaultOptions },
|
|
1065
|
+
});
|
|
1066
|
+
vi.mocked(github.isGhInstalled).mockReturnValue(true);
|
|
1067
|
+
vi.mocked(github.isAuthenticated).mockReturnValue(true);
|
|
1068
|
+
vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
|
|
1069
|
+
vi.mocked(git.getRepoName).mockReturnValue('repo');
|
|
1070
|
+
vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/main-repo');
|
|
1071
|
+
vi.mocked(loadConfig).mockReturnValue({ ...defaultConfig, ...configOverrides });
|
|
1072
|
+
vi.mocked(github.getPr).mockReturnValue(makePrInfo());
|
|
1073
|
+
vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr123');
|
|
1074
|
+
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
1075
|
+
};
|
|
1076
|
+
it('auto-links config files when linkConfigFiles is true', async () => {
|
|
1077
|
+
setupPrCreationMocks({ linkConfigFiles: true });
|
|
1078
|
+
vi.mocked(getEnabledFiles).mockReturnValue(['.env.local', '.vscode/settings.json']);
|
|
1079
|
+
vi.mocked(runWtlink).mockResolvedValue(undefined);
|
|
1080
|
+
await runCli(['--pr', '123']);
|
|
1081
|
+
expect(getEnabledFiles).toHaveBeenCalledWith('/main-repo');
|
|
1082
|
+
expect(runWtlink).toHaveBeenCalledWith(expect.objectContaining({
|
|
1083
|
+
source: '/main-repo',
|
|
1084
|
+
destination: '/repo.pr123',
|
|
1085
|
+
dryRun: false,
|
|
1086
|
+
yes: true,
|
|
1087
|
+
}));
|
|
1088
|
+
expect(prompts.promptConfirm).not.toHaveBeenCalled();
|
|
1089
|
+
});
|
|
1090
|
+
it('skips linking when linkConfigFiles is false', async () => {
|
|
1091
|
+
setupPrCreationMocks({ linkConfigFiles: false });
|
|
1092
|
+
vi.mocked(getEnabledFiles).mockReturnValue(['.env.local', '.vscode/settings.json']);
|
|
1093
|
+
await runCli(['--pr', '123']);
|
|
1094
|
+
expect(getEnabledFiles).toHaveBeenCalledWith('/main-repo');
|
|
1095
|
+
expect(runWtlink).not.toHaveBeenCalled();
|
|
1096
|
+
expect(prompts.promptConfirm).not.toHaveBeenCalled();
|
|
1097
|
+
});
|
|
1098
|
+
it('prompts user when linkConfigFiles is undefined in interactive mode', async () => {
|
|
1099
|
+
setupPrCreationMocks({ linkConfigFiles: undefined });
|
|
1100
|
+
vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
|
|
1101
|
+
vi.mocked(prompts.promptConfirm).mockResolvedValue(true);
|
|
1102
|
+
vi.mocked(runWtlink).mockResolvedValue(undefined);
|
|
1103
|
+
await runCli(['--pr', '123']);
|
|
1104
|
+
expect(getEnabledFiles).toHaveBeenCalledWith('/main-repo');
|
|
1105
|
+
expect(prompts.promptConfirm).toHaveBeenCalledWith(expect.stringContaining('Link these config files'), true);
|
|
1106
|
+
expect(runWtlink).toHaveBeenCalled();
|
|
1107
|
+
});
|
|
1108
|
+
it('skips linking when user declines the prompt', async () => {
|
|
1109
|
+
setupPrCreationMocks({ linkConfigFiles: undefined });
|
|
1110
|
+
vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
|
|
1111
|
+
vi.mocked(prompts.promptConfirm).mockResolvedValue(false);
|
|
1112
|
+
await runCli(['--pr', '123']);
|
|
1113
|
+
expect(prompts.promptConfirm).toHaveBeenCalled();
|
|
1114
|
+
expect(runWtlink).not.toHaveBeenCalled();
|
|
1115
|
+
});
|
|
1116
|
+
it('defaults to linking in non-interactive mode when linkConfigFiles is undefined', async () => {
|
|
1117
|
+
vi.mocked(newpr.parseArgs).mockReturnValue({
|
|
1118
|
+
kind: 'success',
|
|
1119
|
+
options: { mode: 'pr', prNumber: 123, ...defaultOptions, nonInteractive: true },
|
|
1120
|
+
});
|
|
1121
|
+
vi.mocked(github.isGhInstalled).mockReturnValue(true);
|
|
1122
|
+
vi.mocked(github.isAuthenticated).mockReturnValue(true);
|
|
1123
|
+
vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
|
|
1124
|
+
vi.mocked(git.getRepoName).mockReturnValue('repo');
|
|
1125
|
+
vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/main-repo');
|
|
1126
|
+
vi.mocked(loadConfig).mockReturnValue({ ...defaultConfig, linkConfigFiles: undefined });
|
|
1127
|
+
vi.mocked(github.getPr).mockReturnValue(makePrInfo());
|
|
1128
|
+
vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr123');
|
|
1129
|
+
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
1130
|
+
vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
|
|
1131
|
+
vi.mocked(runWtlink).mockResolvedValue(undefined);
|
|
1132
|
+
await runCli(['--pr', '123', '--non-interactive']);
|
|
1133
|
+
expect(prompts.promptConfirm).not.toHaveBeenCalled();
|
|
1134
|
+
expect(runWtlink).toHaveBeenCalled();
|
|
1135
|
+
});
|
|
1136
|
+
it('defaults to linking in JSON mode when linkConfigFiles is undefined', async () => {
|
|
1137
|
+
vi.mocked(newpr.parseArgs).mockReturnValue({
|
|
1138
|
+
kind: 'success',
|
|
1139
|
+
options: { mode: 'pr', prNumber: 123, ...defaultOptions, json: true },
|
|
1140
|
+
});
|
|
1141
|
+
vi.mocked(github.isGhInstalled).mockReturnValue(true);
|
|
1142
|
+
vi.mocked(github.isAuthenticated).mockReturnValue(true);
|
|
1143
|
+
vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
|
|
1144
|
+
vi.mocked(git.getRepoName).mockReturnValue('repo');
|
|
1145
|
+
vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/main-repo');
|
|
1146
|
+
vi.mocked(loadConfig).mockReturnValue({ ...defaultConfig, linkConfigFiles: undefined });
|
|
1147
|
+
vi.mocked(github.getPr).mockReturnValue(makePrInfo());
|
|
1148
|
+
vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr123');
|
|
1149
|
+
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
1150
|
+
vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
|
|
1151
|
+
vi.mocked(runWtlink).mockResolvedValue(undefined);
|
|
1152
|
+
await runCli(['--pr', '123', '--json']);
|
|
1153
|
+
expect(prompts.promptConfirm).not.toHaveBeenCalled();
|
|
1154
|
+
expect(runWtlink).toHaveBeenCalled();
|
|
1155
|
+
});
|
|
1156
|
+
it('does not link when there are no enabled files', async () => {
|
|
1157
|
+
setupPrCreationMocks({ linkConfigFiles: true });
|
|
1158
|
+
vi.mocked(getEnabledFiles).mockReturnValue([]);
|
|
1159
|
+
await runCli(['--pr', '123']);
|
|
1160
|
+
expect(runWtlink).not.toHaveBeenCalled();
|
|
1161
|
+
expect(prompts.promptConfirm).not.toHaveBeenCalled();
|
|
1162
|
+
});
|
|
1163
|
+
it('handles linking errors gracefully', async () => {
|
|
1164
|
+
setupPrCreationMocks({ linkConfigFiles: true });
|
|
1165
|
+
vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
|
|
1166
|
+
vi.mocked(runWtlink).mockRejectedValue(new Error('Permission denied'));
|
|
1167
|
+
await runCli(['--pr', '123']);
|
|
1168
|
+
// Should not crash, just warn about the failure
|
|
1169
|
+
expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('Failed to link config files'));
|
|
1170
|
+
});
|
|
1171
|
+
it('skips config linking when main worktree cannot be determined', async () => {
|
|
1172
|
+
setupPrCreationMocks({ linkConfigFiles: true });
|
|
1173
|
+
vi.mocked(git.getMainWorktreeRoot).mockImplementation(() => {
|
|
1174
|
+
throw new Error('Not in a git worktree');
|
|
1175
|
+
});
|
|
1176
|
+
await runCli(['--pr', '123']);
|
|
1177
|
+
expect(getEnabledFiles).not.toHaveBeenCalled();
|
|
1178
|
+
expect(runWtlink).not.toHaveBeenCalled();
|
|
1179
|
+
});
|
|
1180
|
+
});
|
|
854
1181
|
});
|
|
855
1182
|
//# sourceMappingURL=newpr.test.js.map
|