@solaqua/gji 0.1.0-beta.1 → 0.1.0-beta.3
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/dist/cli.js +13 -3
- package/dist/go.d.ts +1 -0
- package/dist/go.js +48 -1
- package/dist/init.js +2 -2
- package/dist/new.d.ts +1 -0
- package/dist/new.js +26 -5
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
1
2
|
import { Command } from 'commander';
|
|
2
3
|
import { runCleanCommand } from './clean.js';
|
|
3
4
|
import { runConfigCommand } from './config-command.js';
|
|
@@ -12,14 +13,21 @@ import { runStatusCommand } from './status.js';
|
|
|
12
13
|
import { runSyncCommand } from './sync.js';
|
|
13
14
|
export function createProgram() {
|
|
14
15
|
const program = new Command();
|
|
16
|
+
const packageVersion = readPackageVersion();
|
|
15
17
|
program
|
|
16
18
|
.name('gji')
|
|
17
19
|
.description('Context switching without the mess.')
|
|
20
|
+
.version(packageVersion)
|
|
18
21
|
.showHelpAfterError()
|
|
19
22
|
.showSuggestionAfterError();
|
|
20
23
|
registerCommands(program);
|
|
21
24
|
return program;
|
|
22
25
|
}
|
|
26
|
+
function readPackageVersion() {
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
28
|
+
const packageJson = require('../package.json');
|
|
29
|
+
return typeof packageJson.version === 'string' ? packageJson.version : '0.0.0';
|
|
30
|
+
}
|
|
23
31
|
export async function runCli(argv, options = {}) {
|
|
24
32
|
const program = createProgram();
|
|
25
33
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -49,7 +57,8 @@ export async function runCli(argv, options = {}) {
|
|
|
49
57
|
function registerCommands(program) {
|
|
50
58
|
program
|
|
51
59
|
.command('new [branch]')
|
|
52
|
-
.description('create a new branch
|
|
60
|
+
.description('create a new branch or detached linked worktree')
|
|
61
|
+
.option('--detached', 'create a detached worktree without a branch')
|
|
53
62
|
.action(notImplemented('new'));
|
|
54
63
|
program
|
|
55
64
|
.command('init [shell]')
|
|
@@ -90,6 +99,7 @@ function registerCommands(program) {
|
|
|
90
99
|
.action(notImplemented('clean'));
|
|
91
100
|
program
|
|
92
101
|
.command('remove [branch]')
|
|
102
|
+
.alias('rm')
|
|
93
103
|
.description('remove a linked worktree and delete its branch when present')
|
|
94
104
|
.action(notImplemented('remove'));
|
|
95
105
|
const configCommand = program
|
|
@@ -112,8 +122,8 @@ function registerCommands(program) {
|
|
|
112
122
|
function attachCommandActions(program, options) {
|
|
113
123
|
program.commands
|
|
114
124
|
.find((command) => command.name() === 'new')
|
|
115
|
-
?.action(async (branch) => {
|
|
116
|
-
const exitCode = await runNewCommand({ ...options, branch });
|
|
125
|
+
?.action(async (branch, commandOptions) => {
|
|
126
|
+
const exitCode = await runNewCommand({ ...options, branch, detached: commandOptions.detached });
|
|
117
127
|
if (exitCode !== 0) {
|
|
118
128
|
throw commanderExit(exitCode);
|
|
119
129
|
}
|
package/dist/go.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface GoCommandOptions {
|
|
|
7
7
|
stdout: (chunk: string) => void;
|
|
8
8
|
}
|
|
9
9
|
export interface GoCommandDependencies {
|
|
10
|
+
promptForCapturedOutputWorktree: (worktrees: WorktreeEntry[]) => Promise<string | null>;
|
|
10
11
|
promptForWorktree: (worktrees: WorktreeEntry[]) => Promise<string | null>;
|
|
11
12
|
}
|
|
12
13
|
export declare function createGoCommand(dependencies?: Partial<GoCommandDependencies>): (options: GoCommandOptions) => Promise<number>;
|
package/dist/go.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { SelectPrompt, isCancel as isCoreCancel } from '@clack/core';
|
|
1
2
|
import { isCancel, select } from '@clack/prompts';
|
|
2
3
|
import { listWorktrees } from './repo.js';
|
|
4
|
+
const GO_TTY_PROMPT_ENV = 'GJI_GO_TTY_PROMPT';
|
|
3
5
|
export function createGoCommand(dependencies = {}) {
|
|
6
|
+
const promptForCapturedOutput = dependencies.promptForCapturedOutputWorktree ?? promptForCapturedOutputWorktree;
|
|
4
7
|
const prompt = dependencies.promptForWorktree ?? promptForWorktree;
|
|
5
8
|
return async function runGoCommand(options) {
|
|
6
9
|
const worktrees = await listWorktrees(options.cwd);
|
|
@@ -13,7 +16,9 @@ export function createGoCommand(dependencies = {}) {
|
|
|
13
16
|
options.stdout(`${worktree.path}\n`);
|
|
14
17
|
return 0;
|
|
15
18
|
}
|
|
16
|
-
const chosenPath =
|
|
19
|
+
const chosenPath = shouldUseCapturedOutputPrompt(options)
|
|
20
|
+
? await promptForCapturedOutput(worktrees)
|
|
21
|
+
: await prompt(worktrees);
|
|
17
22
|
if (!chosenPath) {
|
|
18
23
|
options.stderr('Aborted\n');
|
|
19
24
|
return 1;
|
|
@@ -37,3 +42,45 @@ async function promptForWorktree(worktrees) {
|
|
|
37
42
|
}
|
|
38
43
|
return choice;
|
|
39
44
|
}
|
|
45
|
+
async function promptForCapturedOutputWorktree(worktrees) {
|
|
46
|
+
const options = worktrees.map((worktree) => ({
|
|
47
|
+
value: worktree.path,
|
|
48
|
+
label: worktree.branch ?? '(detached)',
|
|
49
|
+
hint: worktree.path,
|
|
50
|
+
}));
|
|
51
|
+
const prompt = new SelectPrompt({
|
|
52
|
+
input: process.stdin,
|
|
53
|
+
options,
|
|
54
|
+
output: process.stderr,
|
|
55
|
+
render() {
|
|
56
|
+
const lines = ['Choose a worktree'];
|
|
57
|
+
switch (this.state) {
|
|
58
|
+
case 'submit': {
|
|
59
|
+
const selected = this.options[this.cursor];
|
|
60
|
+
lines.push(`> ${selected.label} (${selected.hint})`);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case 'cancel':
|
|
64
|
+
lines.push('> canceled');
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
lines.push(...this.options.map((option, index) => {
|
|
68
|
+
const prefix = index === this.cursor ? '> ' : ' ';
|
|
69
|
+
return `${prefix}${option.label} (${option.hint})`;
|
|
70
|
+
}));
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
return `${lines.join('\n')}\n`;
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
const choice = await prompt.prompt();
|
|
77
|
+
if (isCoreCancel(choice)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return choice;
|
|
81
|
+
}
|
|
82
|
+
function shouldUseCapturedOutputPrompt(options) {
|
|
83
|
+
return (!options.branch &&
|
|
84
|
+
options.print === true &&
|
|
85
|
+
process.env[GO_TTY_PROMPT_ENV] === '1');
|
|
86
|
+
}
|
package/dist/init.js
CHANGED
|
@@ -34,7 +34,7 @@ function gji --wraps gji --description 'gji shell integration'
|
|
|
34
34
|
return $status
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
set -l target (command gji go --print $argv)
|
|
37
|
+
set -l target (env GJI_GO_TTY_PROMPT=1 command gji go --print $argv)
|
|
38
38
|
or return $status
|
|
39
39
|
cd $target
|
|
40
40
|
return $status
|
|
@@ -56,7 +56,7 @@ gji() {
|
|
|
56
56
|
fi
|
|
57
57
|
|
|
58
58
|
local target
|
|
59
|
-
target="$(command gji go --print "$@")" || return $?
|
|
59
|
+
target="$(GJI_GO_TTY_PROMPT=1 command gji go --print "$@")" || return $?
|
|
60
60
|
cd "$target" || return $?
|
|
61
61
|
return 0
|
|
62
62
|
fi
|
package/dist/new.d.ts
CHANGED
package/dist/new.js
CHANGED
|
@@ -14,14 +14,21 @@ export function createNewCommand(dependencies = {}) {
|
|
|
14
14
|
return async function runNewCommand(options) {
|
|
15
15
|
const repository = await detectRepository(options.cwd);
|
|
16
16
|
const config = await loadEffectiveConfig(repository.repoRoot);
|
|
17
|
-
const
|
|
17
|
+
const usesGeneratedDetachedName = options.detached && options.branch === undefined;
|
|
18
|
+
const rawBranch = options.detached
|
|
19
|
+
? options.branch ?? createBranchPlaceholder()
|
|
20
|
+
: options.branch ?? await promptForBranch(createBranchPlaceholder());
|
|
18
21
|
if (!rawBranch) {
|
|
19
22
|
options.stderr('Aborted\n');
|
|
20
23
|
return 1;
|
|
21
24
|
}
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
const worktreeName = options.detached
|
|
26
|
+
? rawBranch
|
|
27
|
+
: applyConfiguredBranchPrefix(rawBranch, config.branchPrefix);
|
|
28
|
+
const worktreePath = usesGeneratedDetachedName
|
|
29
|
+
? await resolveUniqueDetachedWorktreePath(repository.repoRoot, worktreeName)
|
|
30
|
+
: resolveWorktreePath(repository.repoRoot, worktreeName);
|
|
31
|
+
if (!usesGeneratedDetachedName && await pathExists(worktreePath)) {
|
|
25
32
|
const choice = await prompt(worktreePath);
|
|
26
33
|
if (choice === 'reuse') {
|
|
27
34
|
options.stdout(`${worktreePath}\n`);
|
|
@@ -31,7 +38,10 @@ export function createNewCommand(dependencies = {}) {
|
|
|
31
38
|
return 1;
|
|
32
39
|
}
|
|
33
40
|
await mkdir(dirname(worktreePath), { recursive: true });
|
|
34
|
-
|
|
41
|
+
const gitArgs = options.detached
|
|
42
|
+
? ['worktree', 'add', '--detach', worktreePath]
|
|
43
|
+
: ['worktree', 'add', '-b', worktreeName, worktreePath];
|
|
44
|
+
await execFileAsync('git', gitArgs, { cwd: repository.repoRoot });
|
|
35
45
|
options.stdout(`${worktreePath}\n`);
|
|
36
46
|
return 0;
|
|
37
47
|
};
|
|
@@ -97,6 +107,17 @@ function applyConfiguredBranchPrefix(branch, branchPrefix) {
|
|
|
97
107
|
}
|
|
98
108
|
return `${branchPrefix}${branch}`;
|
|
99
109
|
}
|
|
110
|
+
async function resolveUniqueDetachedWorktreePath(repoRoot, baseName) {
|
|
111
|
+
let attempt = 1;
|
|
112
|
+
while (true) {
|
|
113
|
+
const candidateName = attempt === 1 ? baseName : `${baseName}-${attempt}`;
|
|
114
|
+
const candidatePath = resolveWorktreePath(repoRoot, candidateName);
|
|
115
|
+
if (!await pathExists(candidatePath)) {
|
|
116
|
+
return candidatePath;
|
|
117
|
+
}
|
|
118
|
+
attempt += 1;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
100
121
|
async function promptForPathConflict(path) {
|
|
101
122
|
const choice = await select({
|
|
102
123
|
message: `Target path already exists: ${path}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solaqua/gji",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.3",
|
|
4
4
|
"description": "Git worktree CLI for fast context switching.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "sjquant",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"node": ">=18"
|
|
35
35
|
},
|
|
36
36
|
"bin": {
|
|
37
|
-
"gji": "
|
|
37
|
+
"gji": "dist/index.js"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "node scripts/clean-dist.mjs && tsc -p tsconfig.build.json",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"test:watch": "vitest"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
+
"@clack/core": "0.5.0",
|
|
46
47
|
"@clack/prompts": "^0.11.0",
|
|
47
48
|
"commander": "^14.0.1"
|
|
48
49
|
},
|