@solaqua/gji 0.8.1 → 0.10.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 +29 -33
- package/dist/browser.d.ts +7 -0
- package/dist/browser.js +37 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +63 -4
- package/dist/doctor.d.ts +28 -0
- package/dist/doctor.js +454 -0
- package/dist/gji-bundle.mjs +2507 -359
- package/dist/go.d.ts +2 -1
- package/dist/go.js +4 -1
- package/dist/index.js +12 -6
- package/dist/init.d.ts +15 -0
- package/dist/init.js +232 -16
- package/dist/new.d.ts +1 -0
- package/dist/new.js +24 -1
- package/dist/open.d.ts +3 -1
- package/dist/open.js +16 -3
- package/dist/pr-open.d.ts +19 -0
- package/dist/pr-open.js +290 -0
- package/dist/pull-requests.d.ts +33 -0
- package/dist/pull-requests.js +384 -0
- package/dist/repo-registry.d.ts +5 -0
- package/dist/repo-registry.js +87 -14
- package/dist/shell-completion.js +284 -14
- package/dist/shell-setup.d.ts +9 -0
- package/dist/shell-setup.js +56 -0
- package/dist/warp.d.ts +3 -0
- package/dist/warp.js +8 -3
- package/dist/worktree-picker.d.ts +12 -1
- package/dist/worktree-picker.js +84 -6
- package/man/man1/gji-back.1 +1 -1
- package/man/man1/gji-clean.1 +1 -1
- package/man/man1/gji-completion.1 +1 -1
- package/man/man1/gji-config.1 +1 -1
- package/man/man1/gji-doctor.1 +19 -0
- package/man/man1/gji-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +6 -3
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +4 -1
- package/man/man1/gji-open.1 +4 -1
- package/man/man1/gji-pr.1 +5 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-run-hook.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +6 -2
- package/package.json +1 -1
package/dist/go.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type WorktreePromptEntry } from "./worktree-picker.js";
|
|
1
|
+
import { type QueryWorktreePullRequests, type WorktreePromptEntry } from "./worktree-picker.js";
|
|
2
2
|
export interface GoCommandOptions {
|
|
3
3
|
branch?: string;
|
|
4
4
|
cwd: string;
|
|
@@ -8,6 +8,7 @@ export interface GoCommandOptions {
|
|
|
8
8
|
}
|
|
9
9
|
export interface GoCommandDependencies {
|
|
10
10
|
promptForWorktree: (worktrees: WorktreePromptEntry[]) => Promise<string | null>;
|
|
11
|
+
queryPullRequests: QueryWorktreePullRequests;
|
|
11
12
|
}
|
|
12
13
|
export declare function createGoCommand(dependencies?: Partial<GoCommandDependencies>): (options: GoCommandOptions) => Promise<number>;
|
|
13
14
|
export declare const runGoCommand: (options: GoCommandOptions) => Promise<number>;
|
package/dist/go.js
CHANGED
|
@@ -40,12 +40,15 @@ export function createGoCommand(dependencies = {}) {
|
|
|
40
40
|
return 1;
|
|
41
41
|
}
|
|
42
42
|
const promptSources = worktrees.map((worktree) => ({
|
|
43
|
+
repoRoot: repository.repoRoot,
|
|
43
44
|
repoName: repository.repoName,
|
|
44
45
|
worktree,
|
|
45
46
|
}));
|
|
46
47
|
const promptEntries = options.branch
|
|
47
48
|
? []
|
|
48
|
-
: await buildWorktreePromptEntries(promptSources
|
|
49
|
+
: await buildWorktreePromptEntries(promptSources, {
|
|
50
|
+
queryPullRequests: dependencies.queryPullRequests,
|
|
51
|
+
});
|
|
49
52
|
const queried = options.branch
|
|
50
53
|
? resolveWorktreeQuery(promptSources, options.branch)
|
|
51
54
|
: null;
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { runCli } from "./cli.js";
|
|
4
4
|
import { loadGlobalConfig } from "./config.js";
|
|
5
|
+
import { isHeadless } from "./headless.js";
|
|
5
6
|
async function main() {
|
|
6
7
|
try {
|
|
7
8
|
const argv = process.argv.slice(2);
|
|
8
9
|
// Warn once (until fixed) when shell integration hasn't been set up.
|
|
9
|
-
// Only shown in interactive terminals — suppressed in pipes and after gji init
|
|
10
|
+
// Only shown in interactive terminals — suppressed in pipes and after gji init.
|
|
10
11
|
const isMetaArg = argv[0] === "init" ||
|
|
11
12
|
argv[0] === "--version" ||
|
|
12
13
|
argv[0] === "-V" ||
|
|
@@ -31,15 +32,20 @@ async function warnIfMissingShellIntegration() {
|
|
|
31
32
|
try {
|
|
32
33
|
const { config } = await loadGlobalConfig(homedir());
|
|
33
34
|
if (!config.shellIntegration) {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
process.stderr.write(`gji: shell integration not set up — run \`gji init${shellArg} --write\` to enable automatic cd.\n`);
|
|
35
|
+
const setupCommand = isHeadless()
|
|
36
|
+
? legacyShellSetupCommand()
|
|
37
|
+
: "gji init";
|
|
38
|
+
process.stderr.write(`gji: shell integration not set up — run \`${setupCommand}\` to enable automatic cd.\n`);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
catch {
|
|
42
42
|
// best-effort; never block the command
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
function legacyShellSetupCommand() {
|
|
46
|
+
const shell = (process.env.SHELL ?? "").split("/").at(-1);
|
|
47
|
+
return shell && ["bash", "fish", "zsh"].includes(shell)
|
|
48
|
+
? `gji init ${shell} --write`
|
|
49
|
+
: "gji init <shell> --write";
|
|
50
|
+
}
|
|
45
51
|
void main();
|
package/dist/init.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type SupportedShell } from "./shell.js";
|
|
2
2
|
export type InstallSaveTarget = "local" | "global";
|
|
3
|
+
export type ShellIntegrationChoice = "existing" | "install" | "skip";
|
|
3
4
|
export interface SetupWizardResult {
|
|
4
5
|
branchPrefix?: string;
|
|
5
6
|
hooks?: {
|
|
@@ -10,9 +11,22 @@ export interface SetupWizardResult {
|
|
|
10
11
|
installSaveTarget: InstallSaveTarget;
|
|
11
12
|
worktreePath?: string;
|
|
12
13
|
}
|
|
14
|
+
export interface InitOnboardingResult {
|
|
15
|
+
editor?: string;
|
|
16
|
+
installCompletion: boolean;
|
|
17
|
+
shellIntegration: ShellIntegrationChoice;
|
|
18
|
+
shell: SupportedShell;
|
|
19
|
+
}
|
|
20
|
+
interface InitOnboardingContext {
|
|
21
|
+
detectedShell: SupportedShell | null;
|
|
22
|
+
home: string;
|
|
23
|
+
}
|
|
13
24
|
export interface InitCommandOptions {
|
|
14
25
|
cwd: string;
|
|
15
26
|
home?: string;
|
|
27
|
+
interactive?: boolean;
|
|
28
|
+
json?: boolean;
|
|
29
|
+
promptForOnboarding?: (context: InitOnboardingContext) => Promise<InitOnboardingResult | null>;
|
|
16
30
|
promptForSetup?: () => Promise<SetupWizardResult | null>;
|
|
17
31
|
shell?: string;
|
|
18
32
|
stderr?: (chunk: string) => void;
|
|
@@ -22,3 +36,4 @@ export interface InitCommandOptions {
|
|
|
22
36
|
export declare function runInitCommand(options: InitCommandOptions): Promise<number>;
|
|
23
37
|
export declare function renderShellIntegration(shell: SupportedShell): string;
|
|
24
38
|
export declare function upsertShellIntegration(existingConfig: string, script: string): string;
|
|
39
|
+
export {};
|
package/dist/init.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname
|
|
4
|
-
import { intro, isCancel, outro, select, text } from "@clack/prompts";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { confirm, intro, isCancel, log, outro, select, text, } from "@clack/prompts";
|
|
5
6
|
import { loadConfig, loadGlobalConfig, saveGlobalConfig, saveLocalConfig, updateGlobalConfigKey, } from "./config.js";
|
|
7
|
+
import { EDITORS } from "./editor.js";
|
|
8
|
+
import { isHeadless } from "./headless.js";
|
|
6
9
|
import { resolveSupportedShell } from "./shell.js";
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
import { renderShellCompletion } from "./shell-completion.js";
|
|
11
|
+
import { SHELL_INTEGRATION_END_MARKER as END_MARKER, executableExists, hasShellIntegration, ONBOARDING_SHELL_INTEGRATION_END_MARKER as ONBOARDING_END_MARKER, ONBOARDING_SHELL_INTEGRATION_START_MARKER as ONBOARDING_START_MARKER, resolveCompletionPath, resolveShellConfigPath, SHELL_INTEGRATION_START_MARKER as START_MARKER, } from "./shell-setup.js";
|
|
12
|
+
const ZSH_COMPLETION_PATH_LINE = "fpath=(~/.zsh/completions $fpath)";
|
|
9
13
|
const SHELL_WRAPPED_COMMANDS = [
|
|
10
14
|
{
|
|
11
15
|
bypassOptions: ["--help"],
|
|
@@ -58,6 +62,228 @@ const SHELL_WRAPPED_COMMANDS = [
|
|
|
58
62
|
},
|
|
59
63
|
];
|
|
60
64
|
export async function runInitCommand(options) {
|
|
65
|
+
if (options.shell === undefined) {
|
|
66
|
+
return runOnboardingInitCommand(options);
|
|
67
|
+
}
|
|
68
|
+
return runLegacyInitCommand(options);
|
|
69
|
+
}
|
|
70
|
+
async function runOnboardingInitCommand(options) {
|
|
71
|
+
if (options.json || isHeadless() || !canRunOnboarding(options)) {
|
|
72
|
+
return writeNonInteractiveInitError(options);
|
|
73
|
+
}
|
|
74
|
+
const home = options.home ?? homedir();
|
|
75
|
+
const context = {
|
|
76
|
+
detectedShell: resolveSupportedShell(undefined, process.env.SHELL),
|
|
77
|
+
home,
|
|
78
|
+
};
|
|
79
|
+
if (options.promptForOnboarding === undefined) {
|
|
80
|
+
return runDefaultOnboarding(context);
|
|
81
|
+
}
|
|
82
|
+
const result = await options.promptForOnboarding(context);
|
|
83
|
+
if (!result) {
|
|
84
|
+
options.stderr?.("Aborted.\n");
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
await applyOnboardingResult(result, home);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
function canRunOnboarding(options) {
|
|
91
|
+
return (options.interactive ??
|
|
92
|
+
(options.promptForOnboarding !== undefined ||
|
|
93
|
+
(process.stdin.isTTY === true && process.stdout.isTTY === true)));
|
|
94
|
+
}
|
|
95
|
+
function writeNonInteractiveInitError(options) {
|
|
96
|
+
const error = "run `gji init <shell> --write` in non-interactive mode";
|
|
97
|
+
options.stderr?.(options.json ? `${JSON.stringify({ error })}\n` : `${error}\n`);
|
|
98
|
+
return 1;
|
|
99
|
+
}
|
|
100
|
+
async function runDefaultOnboarding(context) {
|
|
101
|
+
intro("gji init");
|
|
102
|
+
const shell = await select({
|
|
103
|
+
message: "Which shell should gji configure?",
|
|
104
|
+
initialValue: context.detectedShell ?? "zsh",
|
|
105
|
+
options: [
|
|
106
|
+
{ value: "zsh", label: "zsh" },
|
|
107
|
+
{ value: "bash", label: "bash" },
|
|
108
|
+
{ value: "fish", label: "fish" },
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
if (isCancel(shell))
|
|
112
|
+
return abortDefaultOnboarding();
|
|
113
|
+
const rcPath = resolveShellConfigPath(shell, context.home);
|
|
114
|
+
const currentConfig = await readExistingConfig(rcPath);
|
|
115
|
+
const shellIntegration = await promptForShellIntegration(currentConfig, rcPath, shell);
|
|
116
|
+
if (shellIntegration === null)
|
|
117
|
+
return abortDefaultOnboarding();
|
|
118
|
+
const completionPath = resolveCompletionPath(shell, context.home);
|
|
119
|
+
const completionAlreadyInstalled = await fileExists(completionPath);
|
|
120
|
+
await applyShellIntegration(shell, shellIntegration, context.home);
|
|
121
|
+
const installCompletion = await promptForCompletion(shell, completionPath);
|
|
122
|
+
if (installCompletion === null)
|
|
123
|
+
return abortDefaultOnboarding();
|
|
124
|
+
if (installCompletion) {
|
|
125
|
+
await mkdir(dirname(completionPath), { recursive: true });
|
|
126
|
+
await writeFile(completionPath, renderShellCompletion(shell), "utf8");
|
|
127
|
+
}
|
|
128
|
+
if (shell === "zsh" && (completionAlreadyInstalled || installCompletion)) {
|
|
129
|
+
await ensureZshCompletionPath(rcPath);
|
|
130
|
+
}
|
|
131
|
+
const editor = await promptForEditor();
|
|
132
|
+
if (editor === null)
|
|
133
|
+
return abortDefaultOnboarding();
|
|
134
|
+
if (editor) {
|
|
135
|
+
await updateGlobalConfigKey("editor", editor, context.home);
|
|
136
|
+
}
|
|
137
|
+
outro(`Setup complete. Restart your shell or run: source ${rcPath}\nVerify with: gji doctor`);
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
async function applyShellIntegration(shell, choice, home) {
|
|
141
|
+
if (choice === "install") {
|
|
142
|
+
await installOnboardingShellIntegration(shell, home);
|
|
143
|
+
}
|
|
144
|
+
if (choice !== "skip") {
|
|
145
|
+
await updateGlobalConfigKey("shellIntegration", true, home);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function abortDefaultOnboarding() {
|
|
149
|
+
outro("Aborted.");
|
|
150
|
+
return 1;
|
|
151
|
+
}
|
|
152
|
+
async function promptForShellIntegration(currentConfig, rcPath, shell) {
|
|
153
|
+
if (currentConfig.includes(ONBOARDING_START_MARKER) &&
|
|
154
|
+
currentConfig.includes(ONBOARDING_END_MARKER)) {
|
|
155
|
+
log.success(`Shell integration marker found in ${rcPath}; refreshing it.`);
|
|
156
|
+
return "install";
|
|
157
|
+
}
|
|
158
|
+
if (hasShellIntegration(currentConfig, shell)) {
|
|
159
|
+
log.success(`Shell integration already installed in ${rcPath} ✓`);
|
|
160
|
+
return "existing";
|
|
161
|
+
}
|
|
162
|
+
const confirmed = await confirm({
|
|
163
|
+
message: `Install shell integration in ${rcPath}?`,
|
|
164
|
+
initialValue: true,
|
|
165
|
+
});
|
|
166
|
+
if (isCancel(confirmed))
|
|
167
|
+
return null;
|
|
168
|
+
return confirmed ? "install" : "skip";
|
|
169
|
+
}
|
|
170
|
+
async function promptForCompletion(shell, completionPath) {
|
|
171
|
+
if (await fileExists(completionPath)) {
|
|
172
|
+
log.success(`${shell} completion already installed at ${completionPath} ✓`);
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
const confirmed = await confirm({
|
|
176
|
+
message: `Install ${shell} completion at ${completionPath}?`,
|
|
177
|
+
initialValue: true,
|
|
178
|
+
});
|
|
179
|
+
if (isCancel(confirmed))
|
|
180
|
+
return null;
|
|
181
|
+
return confirmed;
|
|
182
|
+
}
|
|
183
|
+
async function promptForEditor() {
|
|
184
|
+
const availableEditors = await findAvailableEditors();
|
|
185
|
+
if (availableEditors.length === 0) {
|
|
186
|
+
log.info("No supported editor CLIs found on PATH; skipping editor setup.");
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
const skipValue = "__gji_skip_editor__";
|
|
190
|
+
const selected = await select({
|
|
191
|
+
message: "Which editor should gji use?",
|
|
192
|
+
options: [
|
|
193
|
+
...availableEditors.map(({ cli, name }) => ({
|
|
194
|
+
value: cli,
|
|
195
|
+
label: name,
|
|
196
|
+
hint: cli,
|
|
197
|
+
})),
|
|
198
|
+
{ value: skipValue, label: "Skip" },
|
|
199
|
+
],
|
|
200
|
+
});
|
|
201
|
+
if (isCancel(selected))
|
|
202
|
+
return null;
|
|
203
|
+
return selected === skipValue ? undefined : selected;
|
|
204
|
+
}
|
|
205
|
+
async function findAvailableEditors() {
|
|
206
|
+
const availableEditors = [];
|
|
207
|
+
for (const editor of EDITORS) {
|
|
208
|
+
if (await executableExists(editor.cli)) {
|
|
209
|
+
availableEditors.push(editor);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return availableEditors;
|
|
213
|
+
}
|
|
214
|
+
async function applyOnboardingResult(result, home) {
|
|
215
|
+
const rcPath = resolveShellConfigPath(result.shell, home);
|
|
216
|
+
const completionPath = resolveCompletionPath(result.shell, home);
|
|
217
|
+
const completionAlreadyInstalled = await fileExists(completionPath);
|
|
218
|
+
await applyShellIntegration(result.shell, result.shellIntegration, home);
|
|
219
|
+
if (result.installCompletion) {
|
|
220
|
+
await mkdir(dirname(completionPath), { recursive: true });
|
|
221
|
+
await writeFile(completionPath, renderShellCompletion(result.shell), "utf8");
|
|
222
|
+
}
|
|
223
|
+
if (result.shell === "zsh" &&
|
|
224
|
+
(completionAlreadyInstalled || result.installCompletion)) {
|
|
225
|
+
await ensureZshCompletionPath(rcPath);
|
|
226
|
+
}
|
|
227
|
+
if (result.editor) {
|
|
228
|
+
await updateGlobalConfigKey("editor", result.editor, home);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async function installOnboardingShellIntegration(shell, home) {
|
|
232
|
+
const rcPath = resolveShellConfigPath(shell, home);
|
|
233
|
+
await mkdir(dirname(rcPath), { recursive: true });
|
|
234
|
+
const current = await readExistingConfig(rcPath);
|
|
235
|
+
if (shell === "fish") {
|
|
236
|
+
await writeFile(rcPath, upsertShellIntegration(current, renderShellIntegration(shell)), "utf8");
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
await writeFile(rcPath, upsertOnboardingShellIntegration(current, shell), "utf8");
|
|
240
|
+
}
|
|
241
|
+
function upsertOnboardingShellIntegration(existingConfig, shell) {
|
|
242
|
+
const script = renderOnboardingShellIntegration(shell).trimEnd();
|
|
243
|
+
const blockPattern = new RegExp(`${escapeForRegExp(ONBOARDING_START_MARKER)}[\\s\\S]*?${escapeForRegExp(ONBOARDING_END_MARKER)}\\n?`, "m");
|
|
244
|
+
if (blockPattern.test(existingConfig)) {
|
|
245
|
+
return ensureTrailingNewline(existingConfig.replace(blockPattern, `${script}\n`));
|
|
246
|
+
}
|
|
247
|
+
const prefix = existingConfig.trimEnd();
|
|
248
|
+
if (prefix.length === 0)
|
|
249
|
+
return ensureTrailingNewline(script);
|
|
250
|
+
return ensureTrailingNewline(`${prefix}\n\n${script}`);
|
|
251
|
+
}
|
|
252
|
+
function renderOnboardingShellIntegration(shell) {
|
|
253
|
+
return `${ONBOARDING_START_MARKER}
|
|
254
|
+
eval "$(gji init ${shell})"
|
|
255
|
+
${ONBOARDING_END_MARKER}
|
|
256
|
+
`;
|
|
257
|
+
}
|
|
258
|
+
async function ensureZshCompletionPath(rcPath) {
|
|
259
|
+
const current = await readExistingConfig(rcPath);
|
|
260
|
+
const next = upsertZshCompletionPath(current);
|
|
261
|
+
if (next !== current) {
|
|
262
|
+
await writeFile(rcPath, next, "utf8");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function upsertZshCompletionPath(existingConfig) {
|
|
266
|
+
const configWithoutCompletionPath = existingConfig
|
|
267
|
+
.replace(/# >>> gji zsh completion path >>>[\s\S]*?# <<< gji zsh completion path <<<\n?/m, "")
|
|
268
|
+
.replace(new RegExp(`^[\\t ]*${escapeForRegExp(ZSH_COMPLETION_PATH_LINE)}[\\t ]*\\n?`, "gm"), "");
|
|
269
|
+
const suffix = configWithoutCompletionPath
|
|
270
|
+
.replace(/^(?:\r?\n)+/, "")
|
|
271
|
+
.trimEnd();
|
|
272
|
+
// Frameworks can invoke compinit from a sourced file, so this must come first.
|
|
273
|
+
return ensureTrailingNewline(suffix.length === 0
|
|
274
|
+
? ZSH_COMPLETION_PATH_LINE
|
|
275
|
+
: `${ZSH_COMPLETION_PATH_LINE}\n\n${suffix}`);
|
|
276
|
+
}
|
|
277
|
+
async function fileExists(path) {
|
|
278
|
+
try {
|
|
279
|
+
await access(path, constants.F_OK);
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async function runLegacyInitCommand(options) {
|
|
61
287
|
const shell = resolveSupportedShell(options.shell, process.env.SHELL);
|
|
62
288
|
const home = options.home ?? homedir();
|
|
63
289
|
if (!shell) {
|
|
@@ -79,7 +305,7 @@ export async function runInitCommand(options) {
|
|
|
79
305
|
const { config: globalConfig } = await loadGlobalConfig(home);
|
|
80
306
|
const alreadyConfigured = "shellIntegration" in globalConfig || "installSaveTarget" in globalConfig;
|
|
81
307
|
const hasCustomPrompt = options.promptForSetup !== undefined;
|
|
82
|
-
const canPrompt = hasCustomPrompt || process.stdout.isTTY === true;
|
|
308
|
+
const canPrompt = !isHeadless() && (hasCustomPrompt || process.stdout.isTTY === true);
|
|
83
309
|
if (!alreadyConfigured && canPrompt) {
|
|
84
310
|
const prompt = options.promptForSetup ?? defaultPromptForSetup;
|
|
85
311
|
const result = await prompt();
|
|
@@ -154,16 +380,6 @@ async function saveWizardConfig(result, cwd, home) {
|
|
|
154
380
|
await saveGlobalConfig({ ...existing, ...values }, home);
|
|
155
381
|
}
|
|
156
382
|
}
|
|
157
|
-
function resolveShellConfigPath(shell, home) {
|
|
158
|
-
switch (shell) {
|
|
159
|
-
case "bash":
|
|
160
|
-
return join(home, ".bashrc");
|
|
161
|
-
case "fish":
|
|
162
|
-
return join(home, ".config", "fish", "config.fish");
|
|
163
|
-
case "zsh":
|
|
164
|
-
return join(home, ".zshrc");
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
383
|
async function readExistingConfig(path) {
|
|
168
384
|
try {
|
|
169
385
|
return await readFile(path, "utf8");
|
package/dist/new.d.ts
CHANGED
package/dist/new.js
CHANGED
|
@@ -21,6 +21,16 @@ export function createNewCommand(dependencies = {}) {
|
|
|
21
21
|
const prompt = dependencies.promptForPathConflict ?? promptForPathConflict;
|
|
22
22
|
const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
|
|
23
23
|
return async function runNewCommand(options) {
|
|
24
|
+
if (options.detached && options.fromCurrent) {
|
|
25
|
+
const message = "--from-current cannot be used with --detached";
|
|
26
|
+
if (options.json) {
|
|
27
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
options.stderr(`gji new: ${message}\n`);
|
|
31
|
+
}
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
24
34
|
const repository = await detectRepository(options.cwd);
|
|
25
35
|
const config = await loadEffectiveConfig(repository.repoRoot, undefined, options.stderr);
|
|
26
36
|
const usesGeneratedDetachedName = options.detached && options.branch === undefined;
|
|
@@ -141,11 +151,21 @@ export function createNewCommand(dependencies = {}) {
|
|
|
141
151
|
return 0;
|
|
142
152
|
}
|
|
143
153
|
await mkdir(dirname(worktreePath), { recursive: true });
|
|
154
|
+
const startPoint = options.fromCurrent && !options.detached
|
|
155
|
+
? await resolveCurrentWorktreeHead(options.cwd)
|
|
156
|
+
: undefined;
|
|
144
157
|
const gitArgs = options.detached
|
|
145
158
|
? ["worktree", "add", "--detach", worktreePath]
|
|
146
159
|
: (await localBranchExists(repository.repoRoot, worktreeName))
|
|
147
160
|
? ["worktree", "add", worktreePath, worktreeName]
|
|
148
|
-
: [
|
|
161
|
+
: [
|
|
162
|
+
"worktree",
|
|
163
|
+
"add",
|
|
164
|
+
"-b",
|
|
165
|
+
worktreeName,
|
|
166
|
+
worktreePath,
|
|
167
|
+
...(startPoint ? [startPoint] : []),
|
|
168
|
+
];
|
|
149
169
|
await execFileAsync("git", gitArgs, { cwd: repository.repoRoot });
|
|
150
170
|
// Sync files from main worktree before afterCreate so synced files are available to install scripts.
|
|
151
171
|
const syncPatterns = Array.isArray(config.syncFiles)
|
|
@@ -333,6 +353,9 @@ async function localBranchExists(repoRoot, branchName) {
|
|
|
333
353
|
return false;
|
|
334
354
|
}
|
|
335
355
|
}
|
|
356
|
+
async function resolveCurrentWorktreeHead(cwd) {
|
|
357
|
+
return execFileAsync("git", ["rev-parse", "--verify", "HEAD"], { cwd }).then(({ stdout }) => stdout.trim());
|
|
358
|
+
}
|
|
336
359
|
async function writeOutput(worktreePath, stdout) {
|
|
337
360
|
await writeShellOutput(NEW_OUTPUT_FILE_ENV, worktreePath, stdout);
|
|
338
361
|
}
|
package/dist/open.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type EditorDefinition } from "./editor.js";
|
|
2
|
-
import { type WorktreePromptEntry } from "./worktree-picker.js";
|
|
2
|
+
import { type QueryWorktreePullRequests, type WorktreePromptEntry } from "./worktree-picker.js";
|
|
3
3
|
export type { EditorDefinition };
|
|
4
4
|
export interface OpenCommandOptions {
|
|
5
5
|
branch?: string;
|
|
6
6
|
cwd: string;
|
|
7
7
|
editor?: string;
|
|
8
8
|
save?: boolean;
|
|
9
|
+
select?: boolean;
|
|
9
10
|
stderr: (chunk: string) => void;
|
|
10
11
|
stdout: (chunk: string) => void;
|
|
11
12
|
workspace?: boolean;
|
|
@@ -14,6 +15,7 @@ export interface OpenCommandDependencies {
|
|
|
14
15
|
detectEditors: () => Promise<EditorDefinition[]>;
|
|
15
16
|
promptForEditor: (editors: EditorDefinition[]) => Promise<string | null>;
|
|
16
17
|
promptForWorktree: (worktrees: WorktreePromptEntry[]) => Promise<string | null>;
|
|
18
|
+
queryPullRequests: QueryWorktreePullRequests;
|
|
17
19
|
spawnEditor: (cli: string, args: string[]) => Promise<void>;
|
|
18
20
|
}
|
|
19
21
|
export declare function createOpenCommand(dependencies?: Partial<OpenCommandDependencies>): (options: OpenCommandOptions) => Promise<number>;
|
package/dist/open.js
CHANGED
|
@@ -16,6 +16,14 @@ export function createOpenCommand(dependencies = {}) {
|
|
|
16
16
|
const promptForWorktree = dependencies.promptForWorktree ?? defaultPromptForWorktree;
|
|
17
17
|
const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
|
|
18
18
|
return async function runOpenCommand(options) {
|
|
19
|
+
if (options.select && options.branch !== undefined) {
|
|
20
|
+
options.stderr("gji open: --select cannot be used with a branch\n");
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
if (options.select && isHeadless()) {
|
|
24
|
+
options.stderr("gji open --select: selector is unavailable in non-interactive mode (GJI_NO_TUI=1)\n");
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
19
27
|
const [worktrees, repository] = await Promise.all([
|
|
20
28
|
listWorktrees(options.cwd),
|
|
21
29
|
detectRepository(options.cwd),
|
|
@@ -36,15 +44,20 @@ export function createOpenCommand(dependencies = {}) {
|
|
|
36
44
|
targetPath = match.worktree.path;
|
|
37
45
|
targetWorktree = match.worktree;
|
|
38
46
|
}
|
|
39
|
-
else if (
|
|
47
|
+
else if (!options.select) {
|
|
40
48
|
targetWorktree = worktrees.find((w) => w.isCurrent);
|
|
41
|
-
|
|
49
|
+
if (!targetWorktree) {
|
|
50
|
+
options.stderr("gji open: unable to identify the current worktree\n");
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
targetPath = targetWorktree.path;
|
|
42
54
|
}
|
|
43
55
|
else {
|
|
44
56
|
const entries = await buildWorktreePromptEntries(worktrees.map((worktree) => ({
|
|
57
|
+
repoRoot: repository.repoRoot,
|
|
45
58
|
repoName: repository.repoName,
|
|
46
59
|
worktree,
|
|
47
|
-
})));
|
|
60
|
+
})), { queryPullRequests: dependencies.queryPullRequests });
|
|
48
61
|
const chosen = await promptForWorktree(entries);
|
|
49
62
|
if (!chosen) {
|
|
50
63
|
options.stderr("Aborted\n");
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type PullRequestInfo } from "./pull-requests.js";
|
|
2
|
+
import { type QueryRepositoryPullRequests, type QueryWorktreePullRequests, type WorktreePromptEntry } from "./worktree-picker.js";
|
|
3
|
+
export interface PrOpenCommandOptions {
|
|
4
|
+
cwd: string;
|
|
5
|
+
stderr: (chunk: string) => void;
|
|
6
|
+
stdout: (chunk: string) => void;
|
|
7
|
+
select?: boolean;
|
|
8
|
+
target?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface PrOpenCommandDependencies {
|
|
11
|
+
findOpenPullRequest: (repoRoot: string, number: number) => Promise<PullRequestInfo | null>;
|
|
12
|
+
openBrowser: (url: string) => Promise<void>;
|
|
13
|
+
promptForPullRequest: (pullRequests: PullRequestInfo[]) => Promise<PullRequestInfo | null>;
|
|
14
|
+
promptForWorktree: (worktrees: WorktreePromptEntry[]) => Promise<string | null>;
|
|
15
|
+
queryPullRequests: QueryWorktreePullRequests;
|
|
16
|
+
queryRepositoryPullRequests?: QueryRepositoryPullRequests;
|
|
17
|
+
}
|
|
18
|
+
export declare function createPrOpenCommand(dependencies?: Partial<PrOpenCommandDependencies>): (options: PrOpenCommandOptions) => Promise<number>;
|
|
19
|
+
export declare const runPrOpenCommand: (options: PrOpenCommandOptions) => Promise<number>;
|