@solaqua/gji 0.1.0 → 0.2.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 +163 -164
- package/dist/clean.d.ts +2 -0
- package/dist/clean.js +52 -6
- package/dist/cli.js +19 -5
- package/dist/config.d.ts +2 -0
- package/dist/config.js +14 -0
- package/dist/file-sync.d.ts +9 -0
- package/dist/file-sync.js +52 -0
- package/dist/go.js +12 -3
- package/dist/headless.d.ts +6 -0
- package/dist/headless.js +8 -0
- package/dist/install-prompt.d.ts +10 -0
- package/dist/install-prompt.js +99 -0
- package/dist/new.d.ts +4 -1
- package/dist/new.js +58 -2
- package/dist/package-manager.d.ts +5 -0
- package/dist/package-manager.js +108 -0
- package/dist/pr.d.ts +4 -1
- package/dist/pr.js +58 -4
- package/dist/remove.d.ts +2 -0
- package/dist/remove.js +49 -5
- package/dist/sync.d.ts +1 -0
- package/dist/sync.js +45 -7
- package/package.json +1 -1
package/dist/remove.js
CHANGED
|
@@ -2,6 +2,7 @@ import { basename } from 'node:path';
|
|
|
2
2
|
import { confirm, isCancel, select } from '@clack/prompts';
|
|
3
3
|
import { loadEffectiveConfig } from './config.js';
|
|
4
4
|
import { extractHooks, runHook } from './hooks.js';
|
|
5
|
+
import { isHeadless } from './headless.js';
|
|
5
6
|
import { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isWorktreeDirtyError, loadLinkedWorktrees, removeWorktree, } from './worktree-management.js';
|
|
6
7
|
import { defaultConfirmForceDeleteBranch, defaultConfirmForceRemoveWorktree } from './worktree-prompts.js';
|
|
7
8
|
import { writeShellOutput } from './shell-handoff.js';
|
|
@@ -14,7 +15,17 @@ export function createRemoveCommand(dependencies = {}) {
|
|
|
14
15
|
return async function runRemoveCommand(options) {
|
|
15
16
|
const { linkedWorktrees, repository } = await loadLinkedWorktrees(options.cwd);
|
|
16
17
|
if (linkedWorktrees.length === 0) {
|
|
17
|
-
options
|
|
18
|
+
emitError(options, 'No linked worktrees to finish');
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
if (!options.branch && (options.json || isHeadless())) {
|
|
22
|
+
const message = 'branch argument is required';
|
|
23
|
+
if (options.json) {
|
|
24
|
+
emitError(options, message);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
options.stderr(`gji remove: ${message} in non-interactive mode (GJI_NO_TUI=1)\n`);
|
|
28
|
+
}
|
|
18
29
|
return 1;
|
|
19
30
|
}
|
|
20
31
|
const selection = options.branch ?? (await promptForWorktree(linkedWorktrees));
|
|
@@ -24,13 +35,33 @@ export function createRemoveCommand(dependencies = {}) {
|
|
|
24
35
|
}
|
|
25
36
|
const worktree = linkedWorktrees.find((entry) => entry.branch === selection || entry.path === selection);
|
|
26
37
|
if (!worktree) {
|
|
27
|
-
options
|
|
38
|
+
emitError(options, `No linked worktree found for branch: ${selection}`);
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
if (!options.dryRun && !options.force && (options.json || isHeadless())) {
|
|
42
|
+
const message = '--force is required';
|
|
43
|
+
if (options.json) {
|
|
44
|
+
emitError(options, message);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
options.stderr(`gji remove: ${message} in non-interactive mode (GJI_NO_TUI=1)\n`);
|
|
48
|
+
}
|
|
28
49
|
return 1;
|
|
29
50
|
}
|
|
30
|
-
if (!options.force && !(await confirmRemoval(worktree))) {
|
|
51
|
+
if (!options.dryRun && !options.force && !(await confirmRemoval(worktree))) {
|
|
31
52
|
options.stderr('Aborted\n');
|
|
32
53
|
return 1;
|
|
33
54
|
}
|
|
55
|
+
if (options.dryRun) {
|
|
56
|
+
if (options.json) {
|
|
57
|
+
options.stdout(`${JSON.stringify({ branch: worktree.branch, path: worktree.path, dryRun: true }, null, 2)}\n`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const desc = worktree.branch ? `branch: ${worktree.branch}` : 'detached';
|
|
61
|
+
options.stdout(`Would remove worktree at ${worktree.path} (${desc})\n`);
|
|
62
|
+
}
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
34
65
|
const config = await loadEffectiveConfig(repository.repoRoot);
|
|
35
66
|
const hooks = extractHooks(config);
|
|
36
67
|
await runHook(hooks.beforeRemove, worktree.path, { branch: worktree.branch ?? undefined, path: worktree.path, repo: basename(repository.repoRoot) }, options.stderr);
|
|
@@ -49,7 +80,7 @@ export function createRemoveCommand(dependencies = {}) {
|
|
|
49
80
|
await forceRemoveWorktree(repository.repoRoot, worktree.path);
|
|
50
81
|
}
|
|
51
82
|
catch (forceError) {
|
|
52
|
-
options
|
|
83
|
+
emitError(options, `Failed to remove worktree at ${worktree.path}: ${toMessage(forceError)}`);
|
|
53
84
|
return 1;
|
|
54
85
|
}
|
|
55
86
|
}
|
|
@@ -74,7 +105,12 @@ export function createRemoveCommand(dependencies = {}) {
|
|
|
74
105
|
}
|
|
75
106
|
}
|
|
76
107
|
}
|
|
77
|
-
|
|
108
|
+
if (options.json) {
|
|
109
|
+
options.stdout(`${JSON.stringify({ branch: worktree.branch, path: worktree.path, deleted: true }, null, 2)}\n`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
await writeOutput(repository.repoRoot, options.stdout);
|
|
113
|
+
}
|
|
78
114
|
return 0;
|
|
79
115
|
};
|
|
80
116
|
}
|
|
@@ -104,6 +140,14 @@ async function defaultConfirmRemoval(worktree) {
|
|
|
104
140
|
async function writeOutput(repoRoot, stdout) {
|
|
105
141
|
await writeShellOutput(REMOVE_OUTPUT_FILE_ENV, repoRoot, stdout);
|
|
106
142
|
}
|
|
143
|
+
function emitError(options, message) {
|
|
144
|
+
if (options.json) {
|
|
145
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
options.stderr(`${message}\n`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
107
151
|
function toMessage(error) {
|
|
108
152
|
return error instanceof Error ? error.message : String(error);
|
|
109
153
|
}
|
package/dist/sync.d.ts
CHANGED
package/dist/sync.js
CHANGED
|
@@ -7,24 +7,47 @@ export async function runSyncCommand(options) {
|
|
|
7
7
|
const config = await loadEffectiveConfig(repository.repoRoot);
|
|
8
8
|
const worktrees = await listWorktrees(options.cwd);
|
|
9
9
|
const remote = resolveConfiguredString(config.syncRemote) ?? 'origin';
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
let defaultBranch;
|
|
11
|
+
try {
|
|
12
|
+
defaultBranch = resolveConfiguredString(config.syncDefaultBranch)
|
|
13
|
+
?? await resolveDefaultBranch(repository.repoRoot, remote);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
emitError(options, `Unable to reach remote '${remote}'`);
|
|
17
|
+
if (!options.json) {
|
|
18
|
+
options.stderr(`Hint: Add the remote with: git remote add ${remote} <url>\n`);
|
|
19
|
+
}
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
12
22
|
if (!defaultBranch) {
|
|
13
|
-
options
|
|
23
|
+
emitError(options, 'Unable to determine the default branch for sync.');
|
|
24
|
+
if (!options.json) {
|
|
25
|
+
options.stderr(`Hint: Add the remote with: git remote add ${remote} <url>\n`);
|
|
26
|
+
}
|
|
14
27
|
return 1;
|
|
15
28
|
}
|
|
16
29
|
const targetWorktrees = selectTargetWorktrees(worktrees, repository.currentRoot, options.all);
|
|
17
30
|
if (targetWorktrees === 'detached') {
|
|
18
|
-
options
|
|
31
|
+
emitError(options, `Cannot sync detached worktree: ${repository.currentRoot}`);
|
|
19
32
|
return 1;
|
|
20
33
|
}
|
|
21
34
|
for (const worktree of targetWorktrees) {
|
|
22
35
|
if (await isDirtyWorktree(worktree.path)) {
|
|
23
|
-
options
|
|
36
|
+
emitError(options, `Cannot sync dirty worktree: ${worktree.path}`);
|
|
24
37
|
return 1;
|
|
25
38
|
}
|
|
26
39
|
}
|
|
27
|
-
|
|
40
|
+
try {
|
|
41
|
+
await runGit(repository.repoRoot, ['fetch', '--prune', remote]);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
emitError(options, `Failed to fetch from remote '${remote}'`);
|
|
45
|
+
if (!options.json) {
|
|
46
|
+
options.stderr(`Hint: Add the remote with: git remote add ${remote} <url>\n`);
|
|
47
|
+
}
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
const updatedWorktrees = [];
|
|
28
51
|
for (const worktree of targetWorktrees) {
|
|
29
52
|
if (worktree.branch === defaultBranch) {
|
|
30
53
|
await runGit(worktree.path, ['merge', '--ff-only', `${remote}/${defaultBranch}`]);
|
|
@@ -32,10 +55,25 @@ export async function runSyncCommand(options) {
|
|
|
32
55
|
else {
|
|
33
56
|
await runGit(worktree.path, ['rebase', `${remote}/${defaultBranch}`]);
|
|
34
57
|
}
|
|
35
|
-
|
|
58
|
+
updatedWorktrees.push(worktree);
|
|
59
|
+
if (!options.json) {
|
|
60
|
+
options.stdout(`${worktree.path}\n`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (options.json) {
|
|
64
|
+
const updated = updatedWorktrees.map((w) => ({ branch: w.branch, path: w.path }));
|
|
65
|
+
options.stdout(`${JSON.stringify({ updated }, null, 2)}\n`);
|
|
36
66
|
}
|
|
37
67
|
return 0;
|
|
38
68
|
}
|
|
69
|
+
function emitError(options, message) {
|
|
70
|
+
if (options.json) {
|
|
71
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
options.stderr(`${message}\n`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
39
77
|
function selectTargetWorktrees(worktrees, currentRoot, all) {
|
|
40
78
|
if (all) {
|
|
41
79
|
return worktrees
|