@camaradesuk/git-worktree-tools 1.0.5 ā 1.1.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/README.md +7 -0
- package/dist/cli/cleanpr.d.ts +1 -6
- package/dist/cli/cleanpr.d.ts.map +1 -1
- package/dist/cli/cleanpr.js +110 -302
- package/dist/cli/cleanpr.js.map +1 -1
- package/dist/cli/cleanpr.test.d.ts +2 -0
- package/dist/cli/cleanpr.test.d.ts.map +1 -0
- package/dist/cli/cleanpr.test.js +225 -0
- package/dist/cli/cleanpr.test.js.map +1 -0
- package/dist/cli/lswt.d.ts +1 -4
- package/dist/cli/lswt.d.ts.map +1 -1
- package/dist/cli/lswt.js +32 -251
- package/dist/cli/lswt.js.map +1 -1
- package/dist/cli/lswt.test.d.ts +2 -0
- package/dist/cli/lswt.test.d.ts.map +1 -0
- package/dist/cli/lswt.test.js +165 -0
- package/dist/cli/lswt.test.js.map +1 -0
- package/dist/cli/newpr.d.ts +1 -4
- package/dist/cli/newpr.d.ts.map +1 -1
- package/dist/cli/newpr.js +98 -493
- package/dist/cli/newpr.js.map +1 -1
- package/dist/cli/newpr.test.d.ts +2 -0
- package/dist/cli/newpr.test.d.ts.map +1 -0
- package/dist/cli/newpr.test.js +329 -0
- package/dist/cli/newpr.test.js.map +1 -0
- package/dist/cli/wtlink.test.d.ts +2 -0
- package/dist/cli/wtlink.test.d.ts.map +1 -0
- package/dist/cli/wtlink.test.js +148 -0
- package/dist/cli/wtlink.test.js.map +1 -0
- package/dist/e2e/cli.e2e.test.js +44 -1
- package/dist/e2e/cli.e2e.test.js.map +1 -1
- package/dist/lib/cleanpr/args.d.ts +14 -0
- package/dist/lib/cleanpr/args.d.ts.map +1 -0
- package/dist/lib/cleanpr/args.js +82 -0
- package/dist/lib/cleanpr/args.js.map +1 -0
- package/dist/lib/cleanpr/args.test.d.ts +2 -0
- package/dist/lib/cleanpr/args.test.d.ts.map +1 -0
- package/dist/lib/cleanpr/args.test.js +192 -0
- package/dist/lib/cleanpr/args.test.js.map +1 -0
- package/dist/lib/cleanpr/cleanup.d.ts +61 -0
- package/dist/lib/cleanpr/cleanup.d.ts.map +1 -0
- package/dist/lib/cleanpr/cleanup.js +97 -0
- package/dist/lib/cleanpr/cleanup.js.map +1 -0
- package/dist/lib/cleanpr/cleanup.test.d.ts +2 -0
- package/dist/lib/cleanpr/cleanup.test.d.ts.map +1 -0
- package/dist/lib/cleanpr/cleanup.test.js +264 -0
- package/dist/lib/cleanpr/cleanup.test.js.map +1 -0
- package/dist/lib/cleanpr/index.d.ts +10 -0
- package/dist/lib/cleanpr/index.d.ts.map +1 -0
- package/dist/lib/cleanpr/index.js +8 -0
- package/dist/lib/cleanpr/index.js.map +1 -0
- package/dist/lib/cleanpr/types.d.ts +49 -0
- package/dist/lib/cleanpr/types.d.ts.map +1 -0
- package/dist/lib/cleanpr/types.js +5 -0
- package/dist/lib/cleanpr/types.js.map +1 -0
- package/dist/lib/cleanpr/worktree-info.d.ts +27 -0
- package/dist/lib/cleanpr/worktree-info.d.ts.map +1 -0
- package/dist/lib/cleanpr/worktree-info.js +95 -0
- package/dist/lib/cleanpr/worktree-info.js.map +1 -0
- package/dist/lib/cleanpr/worktree-info.test.d.ts +2 -0
- package/dist/lib/cleanpr/worktree-info.test.d.ts.map +1 -0
- package/dist/lib/cleanpr/worktree-info.test.js +160 -0
- package/dist/lib/cleanpr/worktree-info.test.js.map +1 -0
- package/dist/lib/colors.test.js +73 -0
- package/dist/lib/colors.test.js.map +1 -1
- package/dist/lib/config.test.js +79 -2
- package/dist/lib/config.test.js.map +1 -1
- package/dist/lib/git.d.ts.map +1 -1
- package/dist/lib/git.js +4 -3
- package/dist/lib/git.js.map +1 -1
- package/dist/lib/git.test.js +7 -7
- package/dist/lib/git.test.js.map +1 -1
- package/dist/lib/github.d.ts.map +1 -1
- package/dist/lib/github.js +23 -30
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/github.test.js +49 -12
- package/dist/lib/github.test.js.map +1 -1
- package/dist/lib/lswt/args.d.ts +14 -0
- package/dist/lib/lswt/args.d.ts.map +1 -0
- package/dist/lib/lswt/args.js +67 -0
- package/dist/lib/lswt/args.js.map +1 -0
- package/dist/lib/lswt/args.test.d.ts +2 -0
- package/dist/lib/lswt/args.test.d.ts.map +1 -0
- package/dist/lib/lswt/args.test.js +135 -0
- package/dist/lib/lswt/args.test.js.map +1 -0
- package/dist/lib/lswt/formatters.d.ts +29 -0
- package/dist/lib/lswt/formatters.d.ts.map +1 -0
- package/dist/lib/lswt/formatters.js +113 -0
- package/dist/lib/lswt/formatters.js.map +1 -0
- package/dist/lib/lswt/formatters.test.d.ts +2 -0
- package/dist/lib/lswt/formatters.test.d.ts.map +1 -0
- package/dist/lib/lswt/formatters.test.js +233 -0
- package/dist/lib/lswt/formatters.test.js.map +1 -0
- package/dist/lib/lswt/index.d.ts +9 -0
- package/dist/lib/lswt/index.d.ts.map +1 -0
- package/dist/lib/lswt/index.js +9 -0
- package/dist/lib/lswt/index.js.map +1 -0
- package/dist/lib/lswt/types.d.ts +44 -0
- package/dist/lib/lswt/types.d.ts.map +1 -0
- package/dist/lib/lswt/types.js +5 -0
- package/dist/lib/lswt/types.js.map +1 -0
- package/dist/lib/lswt/worktree-info.d.ts +23 -0
- package/dist/lib/lswt/worktree-info.d.ts.map +1 -0
- package/dist/lib/lswt/worktree-info.js +81 -0
- package/dist/lib/lswt/worktree-info.js.map +1 -0
- package/dist/lib/lswt/worktree-info.test.d.ts +2 -0
- package/dist/lib/lswt/worktree-info.test.d.ts.map +1 -0
- package/dist/lib/lswt/worktree-info.test.js +190 -0
- package/dist/lib/lswt/worktree-info.test.js.map +1 -0
- package/dist/lib/newpr/actions.d.ts +56 -0
- package/dist/lib/newpr/actions.d.ts.map +1 -0
- package/dist/lib/newpr/actions.js +130 -0
- package/dist/lib/newpr/actions.js.map +1 -0
- package/dist/lib/newpr/actions.test.d.ts +2 -0
- package/dist/lib/newpr/actions.test.d.ts.map +1 -0
- package/dist/lib/newpr/actions.test.js +254 -0
- package/dist/lib/newpr/actions.test.js.map +1 -0
- package/dist/lib/newpr/args.d.ts +17 -0
- package/dist/lib/newpr/args.d.ts.map +1 -0
- package/dist/lib/newpr/args.js +123 -0
- package/dist/lib/newpr/args.js.map +1 -0
- package/dist/lib/newpr/args.test.d.ts +2 -0
- package/dist/lib/newpr/args.test.d.ts.map +1 -0
- package/dist/lib/newpr/args.test.js +271 -0
- package/dist/lib/newpr/args.test.js.map +1 -0
- package/dist/lib/newpr/index.d.ts +10 -0
- package/dist/lib/newpr/index.d.ts.map +1 -0
- package/dist/lib/newpr/index.js +8 -0
- package/dist/lib/newpr/index.js.map +1 -0
- package/dist/lib/newpr/scenario-handler.d.ts +40 -0
- package/dist/lib/newpr/scenario-handler.d.ts.map +1 -0
- package/dist/lib/newpr/scenario-handler.js +261 -0
- package/dist/lib/newpr/scenario-handler.js.map +1 -0
- package/dist/lib/newpr/scenario-handler.test.d.ts +2 -0
- package/dist/lib/newpr/scenario-handler.test.d.ts.map +1 -0
- package/dist/lib/newpr/scenario-handler.test.js +250 -0
- package/dist/lib/newpr/scenario-handler.test.js.map +1 -0
- package/dist/lib/newpr/types.d.ts +76 -0
- package/dist/lib/newpr/types.d.ts.map +1 -0
- package/dist/lib/newpr/types.js +5 -0
- package/dist/lib/newpr/types.js.map +1 -0
- package/dist/lib/state-detection.d.ts.map +1 -1
- package/dist/lib/state-detection.js +12 -3
- package/dist/lib/state-detection.js.map +1 -1
- package/dist/lib/state-detection.test.js +253 -2
- package/dist/lib/state-detection.test.js.map +1 -1
- package/dist/lib/wtlink/link-configs.d.ts +40 -0
- package/dist/lib/wtlink/link-configs.d.ts.map +1 -1
- package/dist/lib/wtlink/link-configs.js +126 -11
- package/dist/lib/wtlink/link-configs.js.map +1 -1
- package/dist/lib/wtlink/link-configs.test.js +237 -84
- package/dist/lib/wtlink/link-configs.test.js.map +1 -1
- package/dist/lib/wtlink/manage-manifest.d.ts +86 -0
- package/dist/lib/wtlink/manage-manifest.d.ts.map +1 -1
- package/dist/lib/wtlink/manage-manifest.js +16 -15
- package/dist/lib/wtlink/manage-manifest.js.map +1 -1
- package/dist/lib/wtlink/manage-manifest.test.d.ts +2 -0
- package/dist/lib/wtlink/manage-manifest.test.d.ts.map +1 -0
- package/dist/lib/wtlink/manage-manifest.test.js +383 -0
- package/dist/lib/wtlink/manage-manifest.test.js.map +1 -0
- package/dist/lib/wtlink/validate-manifest.d.ts +27 -0
- package/dist/lib/wtlink/validate-manifest.d.ts.map +1 -1
- package/dist/lib/wtlink/validate-manifest.js +103 -28
- package/dist/lib/wtlink/validate-manifest.js.map +1 -1
- package/dist/lib/wtlink/validate-manifest.test.js +170 -65
- package/dist/lib/wtlink/validate-manifest.test.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/newpr.js
CHANGED
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* newpr - Create or setup a PR with a dedicated worktree
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* newpr "feature description" Create new branch + PR + worktree
|
|
7
|
-
* newpr --pr <NUMBER> Setup worktree for existing PR
|
|
8
|
-
* newpr --branch <NAME> Create PR for existing branch + worktree
|
|
5
|
+
* CLI thin wrapper - orchestration and side effects only
|
|
9
6
|
*/
|
|
10
7
|
import path from 'path';
|
|
11
8
|
import fs from 'fs';
|
|
@@ -14,137 +11,28 @@ import * as github from '../lib/github.js';
|
|
|
14
11
|
import * as colors from '../lib/colors.js';
|
|
15
12
|
import { promptChoiceIndex } from '../lib/prompts.js';
|
|
16
13
|
import { loadConfig, generateBranchName, generateWorktreePath, } from '../lib/config.js';
|
|
17
|
-
import { analyzeGitState, detectScenario
|
|
14
|
+
import { analyzeGitState, detectScenario } from '../lib/state-detection.js';
|
|
15
|
+
import { parseArgs, getHelpText, getScenarioContext, isPrWorktreeScenario, isExistingBranchAction, executeStateAction, getBranchPoint, getScenarioMessageLevel, } from '../lib/newpr/index.js';
|
|
18
16
|
/**
|
|
19
|
-
*
|
|
17
|
+
* Create action dependencies using real git operations
|
|
20
18
|
*/
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
openEditor: false,
|
|
28
|
-
runWtlink: true,
|
|
19
|
+
function createActionDeps(cwd) {
|
|
20
|
+
return {
|
|
21
|
+
gitAdd: (addPath, cwdPath) => git.add(addPath, cwdPath ?? cwd),
|
|
22
|
+
gitStash: (options, cwdPath) => git.stash({ message: options.message, keepIndex: options.keepIndex }, cwdPath ?? cwd),
|
|
23
|
+
gitPush: (options, cwdPath) => git.push({ remote: options.remote, branch: options.branch, setUpstream: options.setUpstream }, cwdPath ?? cwd),
|
|
24
|
+
gitCommit: (options, cwdPath) => git.commit({ message: options.message, allowEmpty: options.allowEmpty }, cwdPath ?? cwd),
|
|
29
25
|
};
|
|
30
|
-
let i = 0;
|
|
31
|
-
while (i < args.length) {
|
|
32
|
-
const arg = args[i];
|
|
33
|
-
switch (arg) {
|
|
34
|
-
case '-h':
|
|
35
|
-
case '--help':
|
|
36
|
-
printHelp();
|
|
37
|
-
process.exit(0);
|
|
38
|
-
break;
|
|
39
|
-
case '--pr':
|
|
40
|
-
case '-p':
|
|
41
|
-
options.mode = 'pr';
|
|
42
|
-
i++;
|
|
43
|
-
if (!args[i] || args[i].startsWith('-')) {
|
|
44
|
-
console.error(colors.error('--pr requires a PR number'));
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
options.prNumber = parseInt(args[i], 10);
|
|
48
|
-
if (isNaN(options.prNumber)) {
|
|
49
|
-
console.error(colors.error('PR number must be numeric'));
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
break;
|
|
53
|
-
case '--branch':
|
|
54
|
-
case '-B':
|
|
55
|
-
options.mode = 'branch';
|
|
56
|
-
i++;
|
|
57
|
-
if (!args[i] || args[i].startsWith('-')) {
|
|
58
|
-
console.error(colors.error('--branch requires a branch name'));
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
options.branchName = args[i];
|
|
62
|
-
break;
|
|
63
|
-
case '-b':
|
|
64
|
-
case '--base':
|
|
65
|
-
i++;
|
|
66
|
-
if (!args[i] || args[i].startsWith('-')) {
|
|
67
|
-
console.error(colors.error('--base requires a branch name'));
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
|
-
options.baseBranch = args[i];
|
|
71
|
-
break;
|
|
72
|
-
case '-i':
|
|
73
|
-
case '--install':
|
|
74
|
-
options.installDeps = true;
|
|
75
|
-
break;
|
|
76
|
-
case '-c':
|
|
77
|
-
case '--code':
|
|
78
|
-
options.openEditor = true;
|
|
79
|
-
break;
|
|
80
|
-
case '-r':
|
|
81
|
-
case '--ready':
|
|
82
|
-
options.draft = false;
|
|
83
|
-
break;
|
|
84
|
-
case '--no-wtlink':
|
|
85
|
-
options.runWtlink = false;
|
|
86
|
-
break;
|
|
87
|
-
default:
|
|
88
|
-
if (arg.startsWith('-')) {
|
|
89
|
-
console.error(colors.error(`Unknown option: ${arg}`));
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
// Positional argument = description
|
|
93
|
-
if (!options.description && options.mode === 'new') {
|
|
94
|
-
options.description = arg;
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
console.error(colors.error(`Unexpected argument: ${arg}`));
|
|
98
|
-
process.exit(1);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
i++;
|
|
102
|
-
}
|
|
103
|
-
// Validate
|
|
104
|
-
if (options.mode === 'new' && !options.description) {
|
|
105
|
-
console.error(colors.error('Description required. Usage: newpr "feature description"'));
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
return options;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Print help message
|
|
112
|
-
*/
|
|
113
|
-
function printHelp() {
|
|
114
|
-
console.log(`
|
|
115
|
-
${colors.bold('newpr')} - Create or setup a PR with a dedicated worktree
|
|
116
|
-
|
|
117
|
-
${colors.bold('Usage:')}
|
|
118
|
-
newpr "description" Create new branch + PR + worktree
|
|
119
|
-
newpr --pr <NUMBER> Setup worktree for existing PR
|
|
120
|
-
newpr --branch <NAME> Create PR for existing branch + worktree
|
|
121
|
-
|
|
122
|
-
${colors.bold('Options:')}
|
|
123
|
-
-b, --base BRANCH Base branch for PR (default: main)
|
|
124
|
-
-i, --install Install dependencies after setup
|
|
125
|
-
-c, --code Open editor to the new worktree
|
|
126
|
-
-r, --ready Create PR as ready for review (default: draft)
|
|
127
|
-
--no-wtlink Skip wtlink config sync
|
|
128
|
-
-h, --help Show this help message
|
|
129
|
-
|
|
130
|
-
${colors.bold('Examples:')}
|
|
131
|
-
newpr "Add user authentication"
|
|
132
|
-
newpr "Fix login bug" --install --code
|
|
133
|
-
newpr --pr 1234
|
|
134
|
-
newpr --branch feat/my-feature
|
|
135
|
-
`);
|
|
136
26
|
}
|
|
137
27
|
/**
|
|
138
28
|
* Check prerequisites
|
|
139
29
|
*/
|
|
140
30
|
function checkPrerequisites() {
|
|
141
31
|
console.log(colors.info('Checking prerequisites...'));
|
|
142
|
-
// Check gh CLI
|
|
143
32
|
if (!github.isGhInstalled()) {
|
|
144
33
|
console.error(colors.error('GitHub CLI (gh) is required. See: https://cli.github.com'));
|
|
145
34
|
process.exit(1);
|
|
146
35
|
}
|
|
147
|
-
// Check gh auth
|
|
148
36
|
if (!github.isAuthenticated()) {
|
|
149
37
|
console.error(colors.error('GitHub CLI not authenticated. Run: gh auth login'));
|
|
150
38
|
process.exit(1);
|
|
@@ -205,330 +93,82 @@ function showUnstagedChanges(cwd) {
|
|
|
205
93
|
/**
|
|
206
94
|
* Handle scenario and return action to take
|
|
207
95
|
*/
|
|
208
|
-
async function handleScenario(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
"Cancel - I'll make some changes first",
|
|
224
|
-
]);
|
|
225
|
-
if (choice === 1) {
|
|
226
|
-
return { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false };
|
|
227
|
-
}
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
case 'main_staged_same': {
|
|
231
|
-
// Scenario 2a: On main, same as origin/main, staged changes only
|
|
232
|
-
console.log(colors.info('You have staged changes ready to commit:'));
|
|
233
|
-
showStagedChanges();
|
|
234
|
-
const choice = await promptChoiceIndex('How would you like to proceed?', [
|
|
235
|
-
'Commit staged changes to the new PR branch',
|
|
236
|
-
'Leave changes here and continue with empty initial commit',
|
|
237
|
-
'Cancel',
|
|
238
|
-
]);
|
|
239
|
-
switch (choice) {
|
|
240
|
-
case 1:
|
|
241
|
-
return { action: 'commit_staged', branchFrom: 'origin_main', stashUnstaged: false };
|
|
242
|
-
case 2:
|
|
243
|
-
return { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false };
|
|
244
|
-
default:
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
case 'main_unstaged_same': {
|
|
249
|
-
// Scenario 2b: On main, same as origin/main, unstaged changes only
|
|
250
|
-
console.log(colors.info('You have unstaged changes:'));
|
|
251
|
-
showUncommittedChanges();
|
|
252
|
-
const choice = await promptChoiceIndex('How would you like to proceed?', [
|
|
253
|
-
'Stage all and commit to the new PR branch',
|
|
254
|
-
'Leave changes here and continue with empty initial commit',
|
|
255
|
-
'Stash changes (will restore after)',
|
|
256
|
-
'Cancel',
|
|
257
|
-
]);
|
|
258
|
-
switch (choice) {
|
|
259
|
-
case 1:
|
|
260
|
-
return { action: 'commit_all', branchFrom: 'origin_main', stashUnstaged: false };
|
|
261
|
-
case 2:
|
|
262
|
-
return { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false };
|
|
263
|
-
case 3:
|
|
264
|
-
return { action: 'stash_and_empty', branchFrom: 'origin_main', stashUnstaged: false };
|
|
265
|
-
default:
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
case 'main_both_same': {
|
|
270
|
-
// Scenario 2c: On main, same as origin/main, both staged and unstaged
|
|
271
|
-
console.log(colors.info('You have both staged and unstaged changes:'));
|
|
272
|
-
showStagedChanges();
|
|
273
|
-
showUnstagedChanges();
|
|
274
|
-
const choice = await promptChoiceIndex('How would you like to proceed?', [
|
|
275
|
-
'Commit staged to PR branch, move unstaged to new worktree',
|
|
276
|
-
'Stage all and commit everything to the new PR branch',
|
|
277
|
-
'Leave all changes here and continue with empty initial commit',
|
|
278
|
-
'Stash all changes (will restore after)',
|
|
279
|
-
'Cancel',
|
|
280
|
-
]);
|
|
281
|
-
switch (choice) {
|
|
282
|
-
case 1:
|
|
283
|
-
return { action: 'commit_staged', branchFrom: 'origin_main', stashUnstaged: true };
|
|
284
|
-
case 2:
|
|
285
|
-
return { action: 'commit_all', branchFrom: 'origin_main', stashUnstaged: false };
|
|
286
|
-
case 3:
|
|
287
|
-
return { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false };
|
|
288
|
-
case 4:
|
|
289
|
-
return { action: 'stash_and_empty', branchFrom: 'origin_main', stashUnstaged: false };
|
|
290
|
-
default:
|
|
291
|
-
return null;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
case 'main_clean_ahead': {
|
|
295
|
-
// Scenario 3: On main, ahead of origin/main, clean
|
|
296
|
-
console.log(colors.info("You have local commits on 'main' not yet pushed:"));
|
|
297
|
-
showLocalCommits(baseBranch);
|
|
298
|
-
console.log();
|
|
299
|
-
console.log('These commits will NOT be included in the new PR branch by default.');
|
|
300
|
-
const choice = await promptChoiceIndex('How would you like to proceed?', [
|
|
301
|
-
'Use these commits for the PR (create branch from HEAD)',
|
|
302
|
-
'Push commits to origin/main first, then create PR branch',
|
|
303
|
-
'Start fresh from origin/main (ignore local commits)',
|
|
304
|
-
'Cancel',
|
|
305
|
-
]);
|
|
306
|
-
switch (choice) {
|
|
307
|
-
case 1:
|
|
308
|
-
return { action: 'use_commits', branchFrom: 'head', stashUnstaged: false };
|
|
309
|
-
case 2:
|
|
310
|
-
return { action: 'push_then_branch', branchFrom: 'origin_main', stashUnstaged: false };
|
|
311
|
-
case 3:
|
|
312
|
-
return { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false };
|
|
313
|
-
default:
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
case 'main_changes_ahead': {
|
|
318
|
-
// Scenario 4: On main, ahead of origin/main, has changes
|
|
319
|
-
console.log(colors.info('You have local commits AND uncommitted changes:'));
|
|
320
|
-
console.log();
|
|
321
|
-
console.log('Local commits (not pushed):');
|
|
322
|
-
showLocalCommits(baseBranch);
|
|
323
|
-
console.log();
|
|
324
|
-
console.log('Uncommitted changes:');
|
|
325
|
-
showUncommittedChanges();
|
|
326
|
-
const choice = await promptChoiceIndex('How would you like to proceed?', [
|
|
327
|
-
'Include commits + commit uncommitted changes to PR branch',
|
|
328
|
-
'Include commits only, stash uncommitted changes',
|
|
329
|
-
'Start fresh from origin/main (ignore all local work)',
|
|
330
|
-
'Cancel',
|
|
331
|
-
]);
|
|
332
|
-
switch (choice) {
|
|
333
|
-
case 1:
|
|
334
|
-
return { action: 'use_commits_and_commit_all', branchFrom: 'head', stashUnstaged: false };
|
|
335
|
-
case 2:
|
|
336
|
-
return { action: 'use_commits_and_stash', branchFrom: 'head', stashUnstaged: false };
|
|
337
|
-
case 3:
|
|
338
|
-
return { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false };
|
|
339
|
-
default:
|
|
340
|
-
return null;
|
|
341
|
-
}
|
|
96
|
+
async function handleScenario(state, baseBranch) {
|
|
97
|
+
let scenario = detectScenario(state);
|
|
98
|
+
// Handle pr_worktree scenario - re-analyze after warning
|
|
99
|
+
if (isPrWorktreeScenario(scenario)) {
|
|
100
|
+
console.log(colors.warning('You are in a PR worktree, not the main worktree.'));
|
|
101
|
+
console.log();
|
|
102
|
+
console.log('Creating a new PR is best done from the main worktree.');
|
|
103
|
+
const choice = await promptChoiceIndex('How would you like to proceed?', [
|
|
104
|
+
"Continue anyway (create PR from this worktree's state)",
|
|
105
|
+
"Cancel - I'll switch to the main worktree",
|
|
106
|
+
]);
|
|
107
|
+
if (choice === 1) {
|
|
108
|
+
// Re-analyze and get new scenario
|
|
109
|
+
const newState = analyzeGitState(baseBranch);
|
|
110
|
+
scenario = detectScenario(newState);
|
|
342
111
|
}
|
|
343
|
-
|
|
344
|
-
// Scenario 5: On different branch, same commit as main
|
|
345
|
-
const branch = state.currentBranch || 'unknown';
|
|
346
|
-
console.log(colors.warning(`Branch '${branch}' is at the same commit as main.`));
|
|
347
|
-
console.log();
|
|
348
|
-
console.log('No divergent commits detected. A PR requires at least one commit difference.');
|
|
349
|
-
const choice = await promptChoiceIndex('How would you like to proceed?', [
|
|
350
|
-
'Continue with empty initial commit (new branch from main)',
|
|
351
|
-
'Cancel',
|
|
352
|
-
]);
|
|
353
|
-
if (choice === 1) {
|
|
354
|
-
return { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false };
|
|
355
|
-
}
|
|
112
|
+
else {
|
|
356
113
|
return null;
|
|
357
114
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
115
|
+
}
|
|
116
|
+
const context = getScenarioContext(scenario, state, baseBranch);
|
|
117
|
+
if (!context) {
|
|
118
|
+
// Shouldn't happen if pr_worktree is handled above
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
// Display scenario message
|
|
122
|
+
const level = getScenarioMessageLevel(scenario);
|
|
123
|
+
if (level === 'warning') {
|
|
124
|
+
console.log(colors.warning(context.message));
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
console.log(colors.info(context.message));
|
|
128
|
+
}
|
|
129
|
+
if (context.subMessage) {
|
|
130
|
+
console.log();
|
|
131
|
+
console.log(context.subMessage);
|
|
132
|
+
}
|
|
133
|
+
// Show relevant changes based on scenario
|
|
134
|
+
if (scenario === 'main_staged_same') {
|
|
135
|
+
showStagedChanges();
|
|
136
|
+
}
|
|
137
|
+
else if (scenario === 'main_unstaged_same') {
|
|
138
|
+
showUncommittedChanges();
|
|
139
|
+
}
|
|
140
|
+
else if (scenario === 'main_both_same') {
|
|
141
|
+
showStagedChanges();
|
|
142
|
+
showUnstagedChanges();
|
|
143
|
+
}
|
|
144
|
+
else if (scenario === 'main_clean_ahead' || scenario === 'branch_divergent') {
|
|
145
|
+
showLocalCommits(baseBranch);
|
|
146
|
+
}
|
|
147
|
+
else if (scenario === 'main_changes_ahead') {
|
|
148
|
+
console.log();
|
|
149
|
+
console.log('Local commits (not pushed):');
|
|
150
|
+
showLocalCommits(baseBranch);
|
|
151
|
+
console.log();
|
|
152
|
+
console.log('Uncommitted changes:');
|
|
153
|
+
showUncommittedChanges();
|
|
154
|
+
}
|
|
155
|
+
else if (scenario === 'branch_with_changes') {
|
|
156
|
+
showUncommittedChanges();
|
|
157
|
+
if (state.localCommits.length > 0) {
|
|
363
158
|
console.log();
|
|
364
|
-
console.log(
|
|
365
|
-
console.log('Creating a PR would result in no changes.');
|
|
366
|
-
const choice = await promptChoiceIndex('How would you like to proceed?', [
|
|
367
|
-
'Continue with empty initial commit (new branch from main)',
|
|
368
|
-
"Cancel - I'll check the branch status first",
|
|
369
|
-
]);
|
|
370
|
-
if (choice === 1) {
|
|
371
|
-
return { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false };
|
|
372
|
-
}
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
|
-
case 'branch_divergent': {
|
|
376
|
-
// Scenario 7: On different branch, divergent commits
|
|
377
|
-
const branch = state.currentBranch || 'unknown';
|
|
378
|
-
console.log(colors.info(`You are on branch '${branch}' with commits not in main:`));
|
|
159
|
+
console.log('Branch also has commits not in main:');
|
|
379
160
|
showLocalCommits(baseBranch);
|
|
380
|
-
const choice = await promptChoiceIndex('How would you like to proceed?', [
|
|
381
|
-
`Create PR for THIS branch (${branch} ā main)`,
|
|
382
|
-
"Create NEW branch from main (ignore current branch's commits)",
|
|
383
|
-
'Cancel',
|
|
384
|
-
]);
|
|
385
|
-
switch (choice) {
|
|
386
|
-
case 1:
|
|
387
|
-
return { action: 'create_pr_for_branch', branchFrom: 'head', stashUnstaged: false };
|
|
388
|
-
case 2:
|
|
389
|
-
return { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false };
|
|
390
|
-
default:
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
case 'branch_with_changes': {
|
|
395
|
-
// Scenario 8: On different branch with uncommitted changes
|
|
396
|
-
const branch = state.currentBranch || 'unknown';
|
|
397
|
-
console.log(colors.info(`You are on branch '${branch}' with uncommitted changes:`));
|
|
398
|
-
showUncommittedChanges();
|
|
399
|
-
// Check if branch has divergent commits
|
|
400
|
-
const hasDivergent = state.localCommits.length > 0;
|
|
401
|
-
if (hasDivergent) {
|
|
402
|
-
console.log();
|
|
403
|
-
console.log('Branch also has commits not in main:');
|
|
404
|
-
showLocalCommits(baseBranch);
|
|
405
|
-
const choice = await promptChoiceIndex('How would you like to proceed?', [
|
|
406
|
-
'Create PR for THIS branch, commit changes first',
|
|
407
|
-
'Create PR for THIS branch, stash uncommitted changes',
|
|
408
|
-
'Create NEW branch from main (ignore current branch)',
|
|
409
|
-
'Cancel',
|
|
410
|
-
]);
|
|
411
|
-
switch (choice) {
|
|
412
|
-
case 1:
|
|
413
|
-
return { action: 'pr_for_branch_commit_all', branchFrom: 'head', stashUnstaged: false };
|
|
414
|
-
case 2:
|
|
415
|
-
return { action: 'pr_for_branch_stash', branchFrom: 'head', stashUnstaged: false };
|
|
416
|
-
case 3:
|
|
417
|
-
return { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false };
|
|
418
|
-
default:
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
else {
|
|
423
|
-
const choice = await promptChoiceIndex('How would you like to proceed?', [
|
|
424
|
-
'Stage all and commit to a new PR branch',
|
|
425
|
-
'Leave changes and continue with empty initial commit',
|
|
426
|
-
'Stash changes (will restore after)',
|
|
427
|
-
'Cancel',
|
|
428
|
-
]);
|
|
429
|
-
switch (choice) {
|
|
430
|
-
case 1:
|
|
431
|
-
return { action: 'commit_all', branchFrom: 'origin_main', stashUnstaged: false };
|
|
432
|
-
case 2:
|
|
433
|
-
return { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false };
|
|
434
|
-
case 3:
|
|
435
|
-
return { action: 'stash_and_empty', branchFrom: 'origin_main', stashUnstaged: false };
|
|
436
|
-
default:
|
|
437
|
-
return null;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
case 'detached_head': {
|
|
442
|
-
// Scenario 9: Detached HEAD
|
|
443
|
-
const shortSha = git.getShortCommit();
|
|
444
|
-
console.log(colors.warning(`You are in detached HEAD state at commit ${shortSha}.`));
|
|
445
|
-
const choice = await promptChoiceIndex('How would you like to proceed?', [
|
|
446
|
-
'Create branch from this commit',
|
|
447
|
-
'Create branch from origin/main',
|
|
448
|
-
'Cancel',
|
|
449
|
-
]);
|
|
450
|
-
switch (choice) {
|
|
451
|
-
case 1:
|
|
452
|
-
return { action: 'branch_from_detached', branchFrom: 'head', stashUnstaged: false };
|
|
453
|
-
case 2:
|
|
454
|
-
return { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false };
|
|
455
|
-
default:
|
|
456
|
-
return null;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
case 'pr_worktree': {
|
|
460
|
-
// Scenario 10: Running from PR worktree
|
|
461
|
-
console.log(colors.warning('You are in a PR worktree, not the main worktree.'));
|
|
462
|
-
console.log();
|
|
463
|
-
console.log('Creating a new PR is best done from the main worktree.');
|
|
464
|
-
const choice = await promptChoiceIndex('How would you like to proceed?', [
|
|
465
|
-
"Continue anyway (create PR from this worktree's state)",
|
|
466
|
-
"Cancel - I'll switch to the main worktree",
|
|
467
|
-
]);
|
|
468
|
-
if (choice === 1) {
|
|
469
|
-
// Analyze actual state and recurse
|
|
470
|
-
const newState = analyzeGitState(baseBranch);
|
|
471
|
-
const newScenario = detectScenario(newState);
|
|
472
|
-
return handleScenario(newScenario, newState, baseBranch);
|
|
473
|
-
}
|
|
474
|
-
return null;
|
|
475
161
|
}
|
|
476
|
-
default:
|
|
477
|
-
return defaultAction;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* Execute state action
|
|
482
|
-
*/
|
|
483
|
-
function executeStateAction(action, description, branchName, cwd) {
|
|
484
|
-
let stashRef = null;
|
|
485
|
-
switch (action.action) {
|
|
486
|
-
case 'empty_commit':
|
|
487
|
-
// No action needed before branch creation
|
|
488
|
-
break;
|
|
489
|
-
case 'commit_staged':
|
|
490
|
-
// Will commit staged changes after creating branch
|
|
491
|
-
break;
|
|
492
|
-
case 'commit_all':
|
|
493
|
-
console.log(colors.info('Staging all changes...'));
|
|
494
|
-
git.add('.', cwd);
|
|
495
|
-
break;
|
|
496
|
-
case 'stash_and_empty':
|
|
497
|
-
console.log(colors.info('Stashing all changes...'));
|
|
498
|
-
stashRef = git.stash({ message: `newpr: auto-stash before creating ${branchName}` }, cwd);
|
|
499
|
-
break;
|
|
500
|
-
case 'use_commits':
|
|
501
|
-
case 'branch_from_detached':
|
|
502
|
-
// Branch from HEAD instead of origin/main
|
|
503
|
-
break;
|
|
504
|
-
case 'use_commits_and_commit_all':
|
|
505
|
-
console.log(colors.info('Staging all uncommitted changes...'));
|
|
506
|
-
git.add('.', cwd);
|
|
507
|
-
break;
|
|
508
|
-
case 'use_commits_and_stash':
|
|
509
|
-
console.log(colors.info('Stashing uncommitted changes...'));
|
|
510
|
-
stashRef = git.stash({ message: `newpr: auto-stash before creating ${branchName}` }, cwd);
|
|
511
|
-
break;
|
|
512
|
-
case 'push_then_branch':
|
|
513
|
-
console.log(colors.info('Pushing local commits to origin/main...'));
|
|
514
|
-
git.push({ remote: 'origin', branch: 'main' }, cwd);
|
|
515
|
-
break;
|
|
516
|
-
case 'pr_for_branch_commit_all':
|
|
517
|
-
console.log(colors.info('Staging and committing all changes to current branch...'));
|
|
518
|
-
git.add('.', cwd);
|
|
519
|
-
git.commit({ message: 'chore: work in progress\n\nš¤ Committed with newpr' }, cwd);
|
|
520
|
-
break;
|
|
521
|
-
case 'pr_for_branch_stash':
|
|
522
|
-
console.log(colors.info('Stashing uncommitted changes...'));
|
|
523
|
-
stashRef = git.stash({ message: 'newpr: auto-stash before creating PR' }, cwd);
|
|
524
|
-
break;
|
|
525
162
|
}
|
|
526
|
-
|
|
163
|
+
// Prompt user for choice
|
|
164
|
+
const choiceLabels = context.choices.map((c) => c.label);
|
|
165
|
+
const choiceIndex = await promptChoiceIndex('How would you like to proceed?', choiceLabels);
|
|
166
|
+
return context.choices[choiceIndex].action;
|
|
527
167
|
}
|
|
528
168
|
/**
|
|
529
169
|
* Setup worktree (symlinks, wtlink, deps)
|
|
530
170
|
*/
|
|
531
|
-
async function setupWorktree(worktreePath, config,
|
|
171
|
+
async function setupWorktree(worktreePath, config, _options) {
|
|
532
172
|
const repoRoot = git.getRepoRoot();
|
|
533
173
|
const parentDir = path.dirname(repoRoot);
|
|
534
174
|
// Create symlinks for shared repos
|
|
@@ -556,12 +196,6 @@ async function setupWorktree(worktreePath, config, options) {
|
|
|
556
196
|
}
|
|
557
197
|
}
|
|
558
198
|
}
|
|
559
|
-
// TODO: Run wtlink if available
|
|
560
|
-
// if (options.runWtlink) { ... }
|
|
561
|
-
// TODO: Install dependencies if requested
|
|
562
|
-
// if (options.installDeps) { ... }
|
|
563
|
-
// TODO: Open editor if requested
|
|
564
|
-
// if (options.openEditor) { ... }
|
|
565
199
|
}
|
|
566
200
|
/**
|
|
567
201
|
* Print summary
|
|
@@ -588,7 +222,6 @@ async function modeExistingPr(prNumber, options) {
|
|
|
588
222
|
const repoRoot = git.getRepoRoot();
|
|
589
223
|
const repoName = git.getRepoName(repoRoot);
|
|
590
224
|
const config = loadConfig(repoRoot);
|
|
591
|
-
// Get PR info
|
|
592
225
|
const pr = github.getPr(prNumber);
|
|
593
226
|
if (!pr) {
|
|
594
227
|
console.error(colors.error(`Could not find PR #${prNumber}`));
|
|
@@ -598,16 +231,13 @@ async function modeExistingPr(prNumber, options) {
|
|
|
598
231
|
console.log(colors.warning(`PR #${prNumber} is ${pr.state}`));
|
|
599
232
|
}
|
|
600
233
|
console.log(colors.info(`PR branch: ${pr.headBranch}`));
|
|
601
|
-
// Generate worktree path
|
|
602
234
|
const worktreePath = generateWorktreePath(config, repoRoot, repoName, prNumber);
|
|
603
235
|
if (fs.existsSync(worktreePath)) {
|
|
604
236
|
console.error(colors.error(`Worktree already exists: ${worktreePath}`));
|
|
605
237
|
process.exit(1);
|
|
606
238
|
}
|
|
607
|
-
// Fetch the branch
|
|
608
239
|
console.log(colors.info('Fetching branch from origin...'));
|
|
609
240
|
git.fetch('origin');
|
|
610
|
-
// Create worktree
|
|
611
241
|
console.log(colors.info(`Creating worktree at ${worktreePath}...`));
|
|
612
242
|
try {
|
|
613
243
|
git.addWorktree(worktreePath, pr.headBranch, {
|
|
@@ -616,13 +246,10 @@ async function modeExistingPr(prNumber, options) {
|
|
|
616
246
|
});
|
|
617
247
|
}
|
|
618
248
|
catch {
|
|
619
|
-
// Branch might already exist locally
|
|
620
249
|
git.addWorktree(worktreePath, pr.headBranch);
|
|
621
250
|
}
|
|
622
251
|
console.log(colors.success(`Created worktree: ${worktreePath}`));
|
|
623
|
-
// Setup worktree
|
|
624
252
|
await setupWorktree(worktreePath, config, options);
|
|
625
|
-
// Print summary
|
|
626
253
|
printSummary(prNumber, pr.headBranch, worktreePath, pr.url);
|
|
627
254
|
}
|
|
628
255
|
/**
|
|
@@ -633,12 +260,9 @@ async function modeExistingBranch(branchName, options) {
|
|
|
633
260
|
const repoRoot = git.getRepoRoot();
|
|
634
261
|
const repoName = git.getRepoName(repoRoot);
|
|
635
262
|
const config = loadConfig(repoRoot);
|
|
636
|
-
// Fetch latest
|
|
637
263
|
console.log(colors.info('Fetching latest from origin...'));
|
|
638
264
|
git.fetch('origin');
|
|
639
|
-
// Check if branch exists on remote
|
|
640
265
|
if (!git.remoteBranchExists(branchName)) {
|
|
641
|
-
// Check if it exists locally
|
|
642
266
|
if (git.branchExists(branchName)) {
|
|
643
267
|
console.log(colors.info('Branch exists locally, pushing to origin...'));
|
|
644
268
|
git.push({ setUpstream: true, remote: 'origin', branch: branchName });
|
|
@@ -648,16 +272,13 @@ async function modeExistingBranch(branchName, options) {
|
|
|
648
272
|
process.exit(1);
|
|
649
273
|
}
|
|
650
274
|
}
|
|
651
|
-
// Check if PR already exists
|
|
652
275
|
const existingPr = github.getPrByBranch(branchName);
|
|
653
276
|
if (existingPr) {
|
|
654
277
|
console.log(colors.info(`PR #${existingPr.number} already exists for branch ${branchName}`));
|
|
655
278
|
await modeExistingPr(existingPr.number, options);
|
|
656
279
|
return;
|
|
657
280
|
}
|
|
658
|
-
// Create PR
|
|
659
281
|
console.log(colors.info('Creating pull request...'));
|
|
660
|
-
// Generate title from branch name
|
|
661
282
|
const title = branchName
|
|
662
283
|
.replace(/^(feat|fix|chore)\//, '')
|
|
663
284
|
.replace(/-/g, ' ')
|
|
@@ -683,9 +304,7 @@ PR created from existing branch: \`${branchName}\`
|
|
|
683
304
|
draft: options.draft,
|
|
684
305
|
});
|
|
685
306
|
console.log(colors.success(`Created PR #${pr.number}: ${pr.url}`));
|
|
686
|
-
// Generate worktree path
|
|
687
307
|
const worktreePath = generateWorktreePath(config, repoRoot, repoName, pr.number);
|
|
688
|
-
// Create worktree
|
|
689
308
|
console.log(colors.info(`Creating worktree at ${worktreePath}...`));
|
|
690
309
|
try {
|
|
691
310
|
git.addWorktree(worktreePath, branchName, {
|
|
@@ -697,9 +316,7 @@ PR created from existing branch: \`${branchName}\`
|
|
|
697
316
|
git.addWorktree(worktreePath, branchName);
|
|
698
317
|
}
|
|
699
318
|
console.log(colors.success(`Created worktree: ${worktreePath}`));
|
|
700
|
-
// Setup worktree
|
|
701
319
|
await setupWorktree(worktreePath, config, options);
|
|
702
|
-
// Print summary
|
|
703
320
|
printSummary(pr.number, branchName, worktreePath, pr.url);
|
|
704
321
|
}
|
|
705
322
|
/**
|
|
@@ -709,9 +326,7 @@ async function modeNewFeature(description, options) {
|
|
|
709
326
|
const repoRoot = git.getRepoRoot();
|
|
710
327
|
const repoName = git.getRepoName(repoRoot);
|
|
711
328
|
const config = loadConfig(repoRoot);
|
|
712
|
-
// Generate branch name
|
|
713
329
|
const branchName = generateBranchName(config, description);
|
|
714
|
-
// Fetch latest
|
|
715
330
|
console.log(colors.info('Fetching latest from origin...'));
|
|
716
331
|
try {
|
|
717
332
|
git.fetch('origin');
|
|
@@ -719,37 +334,29 @@ async function modeNewFeature(description, options) {
|
|
|
719
334
|
catch {
|
|
720
335
|
console.log(colors.warning('Could not fetch from origin (network unavailable?)'));
|
|
721
336
|
}
|
|
722
|
-
// Analyze git state
|
|
723
337
|
const state = analyzeGitState(options.baseBranch);
|
|
724
|
-
const
|
|
725
|
-
// Handle scenario and get action
|
|
726
|
-
const action = await handleScenario(scenario, state, options.baseBranch);
|
|
338
|
+
const action = await handleScenario(state, options.baseBranch);
|
|
727
339
|
if (!action) {
|
|
728
340
|
console.log(colors.error('Aborted by user.'));
|
|
729
341
|
process.exit(1);
|
|
730
342
|
}
|
|
731
343
|
// Handle special case: create PR for existing branch
|
|
732
|
-
if (action
|
|
733
|
-
action.action === 'pr_for_branch_commit_all' ||
|
|
734
|
-
action.action === 'pr_for_branch_stash') {
|
|
344
|
+
if (isExistingBranchAction(action)) {
|
|
735
345
|
const currentBranch = state.currentBranch;
|
|
736
346
|
if (!currentBranch) {
|
|
737
347
|
console.error(colors.error('Cannot determine current branch'));
|
|
738
348
|
process.exit(1);
|
|
739
349
|
}
|
|
740
|
-
|
|
741
|
-
executeStateAction(action, description, currentBranch);
|
|
742
|
-
// Push if needed
|
|
350
|
+
const deps = createActionDeps();
|
|
351
|
+
executeStateAction(action, description, currentBranch, deps);
|
|
743
352
|
if (!git.remoteBranchExists(currentBranch)) {
|
|
744
353
|
console.log(colors.info('Pushing branch to origin...'));
|
|
745
354
|
git.push({ setUpstream: true, remote: 'origin', branch: currentBranch });
|
|
746
355
|
}
|
|
747
|
-
// Delegate to existing branch mode
|
|
748
356
|
await modeExistingBranch(currentBranch, options);
|
|
749
357
|
return;
|
|
750
358
|
}
|
|
751
359
|
console.log(colors.info(`Creating feature branch: ${branchName}`));
|
|
752
|
-
// Check if branch already exists on remote
|
|
753
360
|
if (git.remoteBranchExists(branchName)) {
|
|
754
361
|
console.log(colors.warning(`Branch ${branchName} already exists on remote`));
|
|
755
362
|
const existingPr = github.getPrByBranch(branchName);
|
|
@@ -763,10 +370,13 @@ async function modeNewFeature(description, options) {
|
|
|
763
370
|
}
|
|
764
371
|
return;
|
|
765
372
|
}
|
|
766
|
-
// Save original branch
|
|
767
373
|
const originalBranch = git.getCurrentBranch() || 'main';
|
|
768
|
-
|
|
769
|
-
const
|
|
374
|
+
const deps = createActionDeps();
|
|
375
|
+
const actionResult = executeStateAction(action, description, branchName, deps);
|
|
376
|
+
if (!actionResult.success) {
|
|
377
|
+
console.error(colors.error(`Action failed: ${actionResult.message}`));
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
770
380
|
// Stash unstaged changes if needed
|
|
771
381
|
let unstagedStashRef = null;
|
|
772
382
|
if (action.stashUnstaged) {
|
|
@@ -777,31 +387,24 @@ async function modeNewFeature(description, options) {
|
|
|
777
387
|
});
|
|
778
388
|
}
|
|
779
389
|
try {
|
|
780
|
-
|
|
781
|
-
const branchFrom = action.branchFrom === 'head' ? 'HEAD' : `origin/${options.baseBranch}`;
|
|
390
|
+
const branchFrom = getBranchPoint(action, options.baseBranch);
|
|
782
391
|
console.log(colors.info(`Creating branch from ${branchFrom}...`));
|
|
783
|
-
// Create and checkout new branch
|
|
784
392
|
git.exec(['checkout', '-b', branchName, branchFrom]);
|
|
785
|
-
// Create initial commit
|
|
786
393
|
const stagedFiles = git.getStagedFiles();
|
|
787
394
|
if (stagedFiles.length > 0) {
|
|
788
395
|
console.log(colors.info('Committing staged changes...'));
|
|
789
396
|
git.commit({ message: `feat: ${description}\n\nš¤ Created with newpr` });
|
|
790
397
|
}
|
|
791
398
|
else if (action.branchFrom === 'origin_main') {
|
|
792
|
-
// No commits ahead and no staged changes - create empty commit
|
|
793
399
|
console.log(colors.info('Creating initial commit (required for PR creation)...'));
|
|
794
400
|
git.commit({
|
|
795
401
|
message: `chore: initialize ${branchName}\n\nBranch created for: ${description}\n\nš¤ Created with newpr`,
|
|
796
402
|
allowEmpty: true,
|
|
797
403
|
});
|
|
798
404
|
}
|
|
799
|
-
// Push branch
|
|
800
405
|
console.log(colors.info('Pushing branch to origin...'));
|
|
801
406
|
git.push({ setUpstream: true, remote: 'origin', branch: branchName });
|
|
802
|
-
// Switch back to original branch
|
|
803
407
|
git.checkout(originalBranch);
|
|
804
|
-
// Create PR
|
|
805
408
|
console.log(colors.info('Creating pull request...'));
|
|
806
409
|
const pr = github.createPr({
|
|
807
410
|
title: description,
|
|
@@ -824,13 +427,10 @@ ${description}
|
|
|
824
427
|
draft: options.draft,
|
|
825
428
|
});
|
|
826
429
|
console.log(colors.success(`Created PR #${pr.number}: ${pr.url}`));
|
|
827
|
-
// Generate worktree path
|
|
828
430
|
const worktreePath = generateWorktreePath(config, repoRoot, repoName, pr.number);
|
|
829
|
-
// Create worktree
|
|
830
431
|
console.log(colors.info(`Creating worktree at ${worktreePath}...`));
|
|
831
432
|
git.addWorktree(worktreePath, branchName);
|
|
832
433
|
console.log(colors.success(`Created worktree: ${worktreePath}`));
|
|
833
|
-
// Apply unstaged changes to worktree if we stashed them
|
|
834
434
|
if (unstagedStashRef) {
|
|
835
435
|
console.log(colors.info('Moving unstaged changes to worktree...'));
|
|
836
436
|
try {
|
|
@@ -843,17 +443,14 @@ ${description}
|
|
|
843
443
|
console.log(colors.warning("Run 'git stash pop' in main worktree to recover them."));
|
|
844
444
|
}
|
|
845
445
|
}
|
|
846
|
-
// Setup worktree
|
|
847
446
|
await setupWorktree(worktreePath, config, options);
|
|
848
|
-
// Print summary
|
|
849
447
|
printSummary(pr.number, branchName, worktreePath, pr.url);
|
|
850
448
|
}
|
|
851
449
|
catch (error) {
|
|
852
|
-
|
|
853
|
-
if (stashRef) {
|
|
450
|
+
if (actionResult.stashRef) {
|
|
854
451
|
console.log(colors.info('Restoring stashed changes...'));
|
|
855
452
|
try {
|
|
856
|
-
git.stashPop(stashRef);
|
|
453
|
+
git.stashPop(actionResult.stashRef);
|
|
857
454
|
}
|
|
858
455
|
catch {
|
|
859
456
|
console.log(colors.warning("Failed to restore stash. Run 'git stash pop' manually."));
|
|
@@ -866,8 +463,16 @@ ${description}
|
|
|
866
463
|
* Main entry point
|
|
867
464
|
*/
|
|
868
465
|
async function main() {
|
|
869
|
-
const
|
|
870
|
-
|
|
466
|
+
const result = parseArgs(process.argv.slice(2));
|
|
467
|
+
if (result.kind === 'help') {
|
|
468
|
+
console.log(getHelpText());
|
|
469
|
+
process.exit(0);
|
|
470
|
+
}
|
|
471
|
+
if (result.kind === 'error') {
|
|
472
|
+
console.error(colors.error(result.message));
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
const { options } = result;
|
|
871
476
|
checkPrerequisites();
|
|
872
477
|
switch (options.mode) {
|
|
873
478
|
case 'pr':
|