@solaqua/gji 0.6.2 → 0.7.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 +26 -1
- package/dist/back.d.ts +1 -1
- package/dist/back.js +23 -17
- package/dist/clean.d.ts +1 -1
- package/dist/clean.js +44 -35
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +264 -164
- package/dist/completion.js +3 -3
- package/dist/config-command.js +5 -5
- package/dist/config.js +41 -35
- package/dist/conflict.d.ts +1 -1
- package/dist/conflict.js +14 -6
- package/dist/editor.js +29 -9
- package/dist/file-sync.d.ts +1 -0
- package/dist/file-sync.js +15 -11
- package/dist/git.d.ts +1 -1
- package/dist/git.js +21 -19
- package/dist/gji-bundle.mjs +1624 -819
- package/dist/go.d.ts +2 -2
- package/dist/go.js +39 -26
- package/dist/headless.js +1 -1
- package/dist/history-command.js +3 -3
- package/dist/history.js +12 -12
- package/dist/hooks.js +16 -16
- package/dist/index.js +13 -9
- package/dist/init.d.ts +2 -2
- package/dist/init.js +106 -94
- package/dist/install-prompt.d.ts +3 -3
- package/dist/install-prompt.js +46 -28
- package/dist/ls.d.ts +2 -2
- package/dist/ls.js +29 -29
- package/dist/new.d.ts +2 -2
- package/dist/new.js +96 -81
- package/dist/open.d.ts +2 -2
- package/dist/open.js +24 -21
- package/dist/package-manager.js +96 -45
- package/dist/pr.d.ts +2 -2
- package/dist/pr.js +47 -34
- package/dist/remove.d.ts +1 -1
- package/dist/remove.js +39 -27
- package/dist/repo-registry.js +14 -14
- package/dist/repo.js +29 -28
- package/dist/root.js +3 -3
- package/dist/shell-completion.d.ts +1 -1
- package/dist/shell-completion.js +65 -37
- package/dist/shell-handoff.js +2 -2
- package/dist/shell.d.ts +1 -1
- package/dist/shell.js +4 -4
- package/dist/status.d.ts +5 -5
- package/dist/status.js +23 -23
- package/dist/sync-files-command.d.ts +10 -0
- package/dist/sync-files-command.js +137 -0
- package/dist/sync.js +23 -15
- package/dist/trigger-hook.js +9 -5
- package/dist/warp.js +37 -33
- package/dist/worktree-info.d.ts +9 -9
- package/dist/worktree-info.js +31 -29
- package/dist/worktree-management.d.ts +1 -1
- package/dist/worktree-management.js +26 -11
- package/dist/worktree-prompts.js +5 -5
- 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-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +1 -1
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +1 -1
- package/man/man1/gji-open.1 +1 -1
- package/man/man1/gji-pr.1 +1 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +23 -0
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-trigger-hook.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +5 -1
- package/package.json +8 -2
package/dist/new.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { promisify } from
|
|
5
|
-
import { isCancel, text } from
|
|
6
|
-
import { loadEffectiveConfig, resolveConfigString } from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { detectRepository, resolveWorktreePath, validateBranchName } from
|
|
15
|
-
import { writeShellOutput } from
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname } from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { isCancel, text } from "@clack/prompts";
|
|
6
|
+
import { loadEffectiveConfig, resolveConfigString } from "./config.js";
|
|
7
|
+
import { pathExists, promptForPathConflict, } from "./conflict.js";
|
|
8
|
+
import { defaultSpawnEditor, EDITORS } from "./editor.js";
|
|
9
|
+
import { syncFiles } from "./file-sync.js";
|
|
10
|
+
import { isHeadless } from "./headless.js";
|
|
11
|
+
import { appendHistory } from "./history.js";
|
|
12
|
+
import { extractHooks, runHook } from "./hooks.js";
|
|
13
|
+
import { maybeRunInstallPrompt, } from "./install-prompt.js";
|
|
14
|
+
import { detectRepository, resolveWorktreePath, validateBranchName, } from "./repo.js";
|
|
15
|
+
import { writeShellOutput } from "./shell-handoff.js";
|
|
16
16
|
const execFileAsync = promisify(execFile);
|
|
17
|
-
const NEW_OUTPUT_FILE_ENV =
|
|
17
|
+
const NEW_OUTPUT_FILE_ENV = "GJI_NEW_OUTPUT_FILE";
|
|
18
18
|
export function createNewCommand(dependencies = {}) {
|
|
19
19
|
const createBranchPlaceholder = dependencies.createBranchPlaceholder ?? generateBranchPlaceholder;
|
|
20
20
|
const promptForBranch = dependencies.promptForBranch ?? defaultPromptForBranch;
|
|
@@ -25,10 +25,12 @@ export function createNewCommand(dependencies = {}) {
|
|
|
25
25
|
const config = await loadEffectiveConfig(repository.repoRoot, undefined, options.stderr);
|
|
26
26
|
const usesGeneratedDetachedName = options.detached && options.branch === undefined;
|
|
27
27
|
if (options.editor && !options.open) {
|
|
28
|
-
options.stderr(
|
|
28
|
+
options.stderr("gji new: --editor has no effect without --open\n");
|
|
29
29
|
}
|
|
30
|
-
if (!options.detached &&
|
|
31
|
-
|
|
30
|
+
if (!options.detached &&
|
|
31
|
+
!options.branch &&
|
|
32
|
+
(options.json || isHeadless())) {
|
|
33
|
+
const message = "branch argument is required";
|
|
32
34
|
if (options.json) {
|
|
33
35
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
34
36
|
}
|
|
@@ -38,14 +40,14 @@ export function createNewCommand(dependencies = {}) {
|
|
|
38
40
|
return 1;
|
|
39
41
|
}
|
|
40
42
|
const rawBranch = options.detached
|
|
41
|
-
? options.branch ?? createBranchPlaceholder()
|
|
42
|
-
: options.branch ?? await promptForBranch(createBranchPlaceholder());
|
|
43
|
+
? (options.branch ?? createBranchPlaceholder())
|
|
44
|
+
: (options.branch ?? (await promptForBranch(createBranchPlaceholder())));
|
|
43
45
|
if (!rawBranch) {
|
|
44
46
|
if (options.json) {
|
|
45
|
-
options.stderr(`${JSON.stringify({ error:
|
|
47
|
+
options.stderr(`${JSON.stringify({ error: "Aborted" }, null, 2)}\n`);
|
|
46
48
|
}
|
|
47
49
|
else {
|
|
48
|
-
options.stderr(
|
|
50
|
+
options.stderr("Aborted\n");
|
|
49
51
|
}
|
|
50
52
|
return 1;
|
|
51
53
|
}
|
|
@@ -61,19 +63,21 @@ export function createNewCommand(dependencies = {}) {
|
|
|
61
63
|
return 1;
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
|
-
const rawBasePath = resolveConfigString(config,
|
|
65
|
-
const configuredBasePath = rawBasePath?.startsWith(
|
|
66
|
+
const rawBasePath = resolveConfigString(config, "worktreePath");
|
|
67
|
+
const configuredBasePath = rawBasePath?.startsWith("/") || rawBasePath?.startsWith("~")
|
|
68
|
+
? rawBasePath
|
|
69
|
+
: undefined;
|
|
66
70
|
const worktreeName = options.detached
|
|
67
71
|
? rawBranch
|
|
68
72
|
: applyConfiguredBranchPrefix(rawBranch, config.branchPrefix);
|
|
69
73
|
const worktreePath = usesGeneratedDetachedName
|
|
70
74
|
? await resolveUniqueDetachedWorktreePath(repository.repoRoot, worktreeName, configuredBasePath)
|
|
71
75
|
: resolveWorktreePath(repository.repoRoot, worktreeName, configuredBasePath);
|
|
72
|
-
if (!usesGeneratedDetachedName && await pathExists(worktreePath)) {
|
|
76
|
+
if (!usesGeneratedDetachedName && (await pathExists(worktreePath))) {
|
|
73
77
|
if (options.force) {
|
|
74
78
|
if (!options.dryRun) {
|
|
75
79
|
try {
|
|
76
|
-
await execFileAsync(
|
|
80
|
+
await execFileAsync("git", ["worktree", "remove", "--force", worktreePath], { cwd: repository.repoRoot });
|
|
77
81
|
}
|
|
78
82
|
catch (err) {
|
|
79
83
|
if (!isNotRegisteredWorktreeError(err)) {
|
|
@@ -88,7 +92,9 @@ export function createNewCommand(dependencies = {}) {
|
|
|
88
92
|
}
|
|
89
93
|
if (!options.detached) {
|
|
90
94
|
try {
|
|
91
|
-
await execFileAsync(
|
|
95
|
+
await execFileAsync("git", ["branch", "-D", worktreeName], {
|
|
96
|
+
cwd: repository.repoRoot,
|
|
97
|
+
});
|
|
92
98
|
}
|
|
93
99
|
catch {
|
|
94
100
|
// Branch may not exist; proceed anyway.
|
|
@@ -110,7 +116,7 @@ export function createNewCommand(dependencies = {}) {
|
|
|
110
116
|
}
|
|
111
117
|
else {
|
|
112
118
|
const choice = await prompt(worktreePath);
|
|
113
|
-
if (choice ===
|
|
119
|
+
if (choice === "reuse") {
|
|
114
120
|
appendHistory(worktreePath, worktreeName).catch(() => undefined);
|
|
115
121
|
await writeOutput(worktreePath, options.stdout);
|
|
116
122
|
return 0;
|
|
@@ -125,23 +131,25 @@ export function createNewCommand(dependencies = {}) {
|
|
|
125
131
|
}
|
|
126
132
|
else {
|
|
127
133
|
const resolvedEditor = options.open
|
|
128
|
-
? (options.editor ?? resolveConfigString(config,
|
|
134
|
+
? (options.editor ?? resolveConfigString(config, "editor"))
|
|
129
135
|
: undefined;
|
|
130
|
-
const openNote = resolvedEditor
|
|
136
|
+
const openNote = resolvedEditor
|
|
137
|
+
? `, then open in ${resolvedEditor}`
|
|
138
|
+
: "";
|
|
131
139
|
options.stdout(`Would create worktree at ${worktreePath} (branch: ${worktreeName}${openNote})\n`);
|
|
132
140
|
}
|
|
133
141
|
return 0;
|
|
134
142
|
}
|
|
135
143
|
await mkdir(dirname(worktreePath), { recursive: true });
|
|
136
144
|
const gitArgs = options.detached
|
|
137
|
-
? [
|
|
138
|
-
: await localBranchExists(repository.repoRoot, worktreeName)
|
|
139
|
-
? [
|
|
140
|
-
: [
|
|
141
|
-
await execFileAsync(
|
|
145
|
+
? ["worktree", "add", "--detach", worktreePath]
|
|
146
|
+
: (await localBranchExists(repository.repoRoot, worktreeName))
|
|
147
|
+
? ["worktree", "add", worktreePath, worktreeName]
|
|
148
|
+
: ["worktree", "add", "-b", worktreeName, worktreePath];
|
|
149
|
+
await execFileAsync("git", gitArgs, { cwd: repository.repoRoot });
|
|
142
150
|
// Sync files from main worktree before afterCreate so synced files are available to install scripts.
|
|
143
151
|
const syncPatterns = Array.isArray(config.syncFiles)
|
|
144
|
-
? config.syncFiles.filter((p) => typeof p ===
|
|
152
|
+
? config.syncFiles.filter((p) => typeof p === "string")
|
|
145
153
|
: [];
|
|
146
154
|
for (const pattern of syncPatterns) {
|
|
147
155
|
try {
|
|
@@ -153,7 +161,11 @@ export function createNewCommand(dependencies = {}) {
|
|
|
153
161
|
}
|
|
154
162
|
await maybeRunInstallPrompt(worktreePath, repository.repoRoot, config, options.stderr, dependencies, !!options.json);
|
|
155
163
|
const hooks = extractHooks(config);
|
|
156
|
-
await runHook(hooks.afterCreate, worktreePath, {
|
|
164
|
+
await runHook(hooks.afterCreate, worktreePath, {
|
|
165
|
+
branch: worktreeName,
|
|
166
|
+
path: worktreePath,
|
|
167
|
+
repo: basename(repository.repoRoot),
|
|
168
|
+
}, options.stderr);
|
|
157
169
|
if (options.json) {
|
|
158
170
|
options.stdout(`${JSON.stringify({ branch: worktreeName, path: worktreePath }, null, 2)}\n`);
|
|
159
171
|
}
|
|
@@ -162,7 +174,7 @@ export function createNewCommand(dependencies = {}) {
|
|
|
162
174
|
await writeOutput(worktreePath, options.stdout);
|
|
163
175
|
}
|
|
164
176
|
if (options.open) {
|
|
165
|
-
const resolvedEditor = options.editor ?? resolveConfigString(config,
|
|
177
|
+
const resolvedEditor = options.editor ?? resolveConfigString(config, "editor");
|
|
166
178
|
await openWorktree(worktreePath, resolvedEditor, spawnEditor, options.stderr);
|
|
167
179
|
}
|
|
168
180
|
return 0;
|
|
@@ -171,48 +183,48 @@ export function createNewCommand(dependencies = {}) {
|
|
|
171
183
|
export const runNewCommand = createNewCommand();
|
|
172
184
|
export function generateBranchPlaceholder(random = Math.random) {
|
|
173
185
|
const roots = [
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
186
|
+
"socrates",
|
|
187
|
+
"prometheus",
|
|
188
|
+
"beethoven",
|
|
189
|
+
"ada",
|
|
190
|
+
"turing",
|
|
191
|
+
"hypatia",
|
|
192
|
+
"tesla",
|
|
193
|
+
"curie",
|
|
194
|
+
"diogenes",
|
|
195
|
+
"plato",
|
|
196
|
+
"hephaestus",
|
|
197
|
+
"athena",
|
|
198
|
+
"archimedes",
|
|
199
|
+
"euclid",
|
|
200
|
+
"heraclitus",
|
|
201
|
+
"galileo",
|
|
202
|
+
"newton",
|
|
203
|
+
"lovelace",
|
|
204
|
+
"nietzsche",
|
|
205
|
+
"kafka",
|
|
194
206
|
];
|
|
195
207
|
const antics = [
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
"borrowed-a-bike",
|
|
209
|
+
"brought-snacks",
|
|
210
|
+
"missed-the-bus",
|
|
211
|
+
"lost-the-keys",
|
|
212
|
+
"spilled-the-coffee",
|
|
213
|
+
"forgot-the-umbrella",
|
|
214
|
+
"walked-the-dog",
|
|
215
|
+
"missed-the-train",
|
|
216
|
+
"wrote-a-poem",
|
|
217
|
+
"burned-the-toast",
|
|
218
|
+
"fed-the-pigeons",
|
|
219
|
+
"watered-the-plants",
|
|
220
|
+
"washed-the-dishes",
|
|
221
|
+
"folded-the-laundry",
|
|
222
|
+
"took-a-nap",
|
|
211
223
|
];
|
|
212
224
|
return `${pickRandom(roots, random)}-${pickRandom(antics, random)}`;
|
|
213
225
|
}
|
|
214
226
|
function applyConfiguredBranchPrefix(branch, branchPrefix) {
|
|
215
|
-
if (typeof branchPrefix !==
|
|
227
|
+
if (typeof branchPrefix !== "string" || branchPrefix.length === 0) {
|
|
216
228
|
return branch;
|
|
217
229
|
}
|
|
218
230
|
if (branch.startsWith(branchPrefix)) {
|
|
@@ -225,7 +237,7 @@ async function resolveUniqueDetachedWorktreePath(repoRoot, baseName, basePath) {
|
|
|
225
237
|
while (true) {
|
|
226
238
|
const candidateName = attempt === 1 ? baseName : `${baseName}-${attempt}`;
|
|
227
239
|
const candidatePath = resolveWorktreePath(repoRoot, candidateName, basePath);
|
|
228
|
-
if (!await pathExists(candidatePath)) {
|
|
240
|
+
if (!(await pathExists(candidatePath))) {
|
|
229
241
|
return candidatePath;
|
|
230
242
|
}
|
|
231
243
|
attempt += 1;
|
|
@@ -234,7 +246,7 @@ async function resolveUniqueDetachedWorktreePath(repoRoot, baseName, basePath) {
|
|
|
234
246
|
async function defaultPromptForBranch(placeholder) {
|
|
235
247
|
const choice = await text({
|
|
236
248
|
defaultValue: placeholder,
|
|
237
|
-
message:
|
|
249
|
+
message: "Name the new branch",
|
|
238
250
|
placeholder,
|
|
239
251
|
validate: (value) => {
|
|
240
252
|
const trimmed = value.trim();
|
|
@@ -252,7 +264,7 @@ function pickRandom(values, random) {
|
|
|
252
264
|
}
|
|
253
265
|
async function localBranchExists(repoRoot, branchName) {
|
|
254
266
|
try {
|
|
255
|
-
await execFileAsync(
|
|
267
|
+
await execFileAsync("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`], { cwd: repoRoot });
|
|
256
268
|
return true;
|
|
257
269
|
}
|
|
258
270
|
catch {
|
|
@@ -264,17 +276,20 @@ async function writeOutput(worktreePath, stdout) {
|
|
|
264
276
|
}
|
|
265
277
|
function isNotRegisteredWorktreeError(error) {
|
|
266
278
|
const stderr = hasExecStderr(error) ? error.stderr : String(error);
|
|
267
|
-
return stderr.includes(
|
|
279
|
+
return (stderr.includes("is not a working tree") ||
|
|
280
|
+
stderr.includes("not a linked working tree"));
|
|
268
281
|
}
|
|
269
282
|
function hasExecStderr(error) {
|
|
270
|
-
return error instanceof Error &&
|
|
283
|
+
return (error instanceof Error &&
|
|
284
|
+
"stderr" in error &&
|
|
285
|
+
typeof error.stderr === "string");
|
|
271
286
|
}
|
|
272
287
|
function toExecMessage(error) {
|
|
273
288
|
return hasExecStderr(error) ? error.stderr.trim() : String(error);
|
|
274
289
|
}
|
|
275
290
|
async function openWorktree(worktreePath, editorCli, spawnFn, stderr) {
|
|
276
291
|
if (!editorCli) {
|
|
277
|
-
stderr(
|
|
292
|
+
stderr("gji new: --open requires --editor <cli> or a saved editor in config\n");
|
|
278
293
|
return;
|
|
279
294
|
}
|
|
280
295
|
const editorDef = EDITORS.find((e) => e.cli === editorCli);
|
package/dist/open.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type EditorDefinition } from
|
|
2
|
-
import { type WorktreeEntry } from
|
|
1
|
+
import { type EditorDefinition } from "./editor.js";
|
|
2
|
+
import { type WorktreeEntry } from "./repo.js";
|
|
3
3
|
export type { EditorDefinition };
|
|
4
4
|
export interface OpenCommandOptions {
|
|
5
5
|
branch?: string;
|
package/dist/open.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { execFile } from
|
|
2
|
-
import { access, writeFile } from
|
|
3
|
-
import { join } from
|
|
4
|
-
import { promisify } from
|
|
5
|
-
import { isCancel, select } from
|
|
6
|
-
import { loadEffectiveConfig, resolveConfigString, updateGlobalConfigKey } from
|
|
7
|
-
import { defaultSpawnEditor, EDITORS } from
|
|
8
|
-
import { isHeadless } from
|
|
9
|
-
import { detectRepository, listWorktrees, sortByCurrentFirst } from
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { access, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { isCancel, select } from "@clack/prompts";
|
|
6
|
+
import { loadEffectiveConfig, resolveConfigString, updateGlobalConfigKey, } from "./config.js";
|
|
7
|
+
import { defaultSpawnEditor, EDITORS, } from "./editor.js";
|
|
8
|
+
import { isHeadless } from "./headless.js";
|
|
9
|
+
import { detectRepository, listWorktrees, sortByCurrentFirst, } from "./repo.js";
|
|
10
10
|
const execFileAsync = promisify(execFile);
|
|
11
11
|
export function createOpenCommand(dependencies = {}) {
|
|
12
12
|
const detectEditors = dependencies.detectEditors ?? detectInstalledEditors;
|
|
@@ -35,14 +35,14 @@ export function createOpenCommand(dependencies = {}) {
|
|
|
35
35
|
else {
|
|
36
36
|
const chosen = await promptForWorktree(sortByCurrentFirst(worktrees));
|
|
37
37
|
if (!chosen) {
|
|
38
|
-
options.stderr(
|
|
38
|
+
options.stderr("Aborted\n");
|
|
39
39
|
return 1;
|
|
40
40
|
}
|
|
41
41
|
targetPath = chosen;
|
|
42
42
|
}
|
|
43
43
|
// Resolve which editor to use.
|
|
44
44
|
const config = await loadEffectiveConfig(repository.repoRoot, undefined, options.stderr);
|
|
45
|
-
const savedEditor = resolveConfigString(config,
|
|
45
|
+
const savedEditor = resolveConfigString(config, "editor");
|
|
46
46
|
let editorCli;
|
|
47
47
|
if (options.editor) {
|
|
48
48
|
editorCli = options.editor;
|
|
@@ -53,7 +53,7 @@ export function createOpenCommand(dependencies = {}) {
|
|
|
53
53
|
else {
|
|
54
54
|
const installed = await detectEditors();
|
|
55
55
|
if (installed.length === 0) {
|
|
56
|
-
options.stderr(
|
|
56
|
+
options.stderr("gji open: no supported editor detected. Use --editor <code|cursor|zed|...> to specify one.\n");
|
|
57
57
|
return 1;
|
|
58
58
|
}
|
|
59
59
|
if (installed.length === 1 || isHeadless()) {
|
|
@@ -62,7 +62,7 @@ export function createOpenCommand(dependencies = {}) {
|
|
|
62
62
|
else {
|
|
63
63
|
const chosen = await promptForEditor(installed);
|
|
64
64
|
if (!chosen) {
|
|
65
|
-
options.stderr(
|
|
65
|
+
options.stderr("Aborted\n");
|
|
66
66
|
return 1;
|
|
67
67
|
}
|
|
68
68
|
editorCli = chosen;
|
|
@@ -70,7 +70,7 @@ export function createOpenCommand(dependencies = {}) {
|
|
|
70
70
|
}
|
|
71
71
|
// Persist editor choice when requested.
|
|
72
72
|
if (options.save && editorCli !== savedEditor) {
|
|
73
|
-
await updateGlobalConfigKey(
|
|
73
|
+
await updateGlobalConfigKey("editor", editorCli);
|
|
74
74
|
const displayName = EDITORS.find((e) => e.cli === editorCli)?.name ?? editorCli;
|
|
75
75
|
options.stdout(`Saved editor "${displayName}" to global config\n`);
|
|
76
76
|
}
|
|
@@ -106,12 +106,15 @@ export function createOpenCommand(dependencies = {}) {
|
|
|
106
106
|
}
|
|
107
107
|
export const runOpenCommand = createOpenCommand();
|
|
108
108
|
async function detectInstalledEditors() {
|
|
109
|
-
const results = await Promise.all(EDITORS.map(async (editor) => ({
|
|
109
|
+
const results = await Promise.all(EDITORS.map(async (editor) => ({
|
|
110
|
+
editor,
|
|
111
|
+
available: await isCommandAvailable(editor.cli),
|
|
112
|
+
})));
|
|
110
113
|
return results.filter((r) => r.available).map((r) => r.editor);
|
|
111
114
|
}
|
|
112
115
|
async function isCommandAvailable(command) {
|
|
113
116
|
try {
|
|
114
|
-
await execFileAsync(
|
|
117
|
+
await execFileAsync("which", [command]);
|
|
115
118
|
return true;
|
|
116
119
|
}
|
|
117
120
|
catch {
|
|
@@ -120,10 +123,10 @@ async function isCommandAvailable(command) {
|
|
|
120
123
|
}
|
|
121
124
|
async function defaultPromptForWorktree(worktrees) {
|
|
122
125
|
const choice = await select({
|
|
123
|
-
message:
|
|
126
|
+
message: "Choose a worktree to open",
|
|
124
127
|
options: worktrees.map((w) => ({
|
|
125
128
|
value: w.path,
|
|
126
|
-
label: w.branch ??
|
|
129
|
+
label: w.branch ?? "(detached)",
|
|
127
130
|
hint: w.isCurrent ? `${w.path} (current)` : w.path,
|
|
128
131
|
})),
|
|
129
132
|
});
|
|
@@ -133,7 +136,7 @@ async function defaultPromptForWorktree(worktrees) {
|
|
|
133
136
|
}
|
|
134
137
|
async function defaultPromptForEditor(editors) {
|
|
135
138
|
const choice = await select({
|
|
136
|
-
message:
|
|
139
|
+
message: "Choose an editor",
|
|
137
140
|
options: editors.map((e) => ({ value: e.cli, label: e.name })),
|
|
138
141
|
});
|
|
139
142
|
if (isCancel(choice))
|
|
@@ -149,7 +152,7 @@ async function ensureWorkspaceFile(worktreePath, repoName) {
|
|
|
149
152
|
catch {
|
|
150
153
|
// File doesn't exist yet — create it.
|
|
151
154
|
}
|
|
152
|
-
const workspace = { folders: [{ path:
|
|
153
|
-
await writeFile(workspacePath, `${JSON.stringify(workspace, null, 2)}\n`,
|
|
155
|
+
const workspace = { folders: [{ path: "." }], settings: {} };
|
|
156
|
+
await writeFile(workspacePath, `${JSON.stringify(workspace, null, 2)}\n`, "utf8");
|
|
154
157
|
return workspacePath;
|
|
155
158
|
}
|
package/dist/package-manager.js
CHANGED
|
@@ -1,70 +1,121 @@
|
|
|
1
|
-
import { access, readdir } from
|
|
2
|
-
import { join } from
|
|
1
|
+
import { access, readdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
3
|
const ENTRIES = [
|
|
4
4
|
// JavaScript / TypeScript
|
|
5
|
-
{ name:
|
|
6
|
-
{ name:
|
|
7
|
-
{ name:
|
|
8
|
-
{ name:
|
|
9
|
-
{ name:
|
|
5
|
+
{ name: "pnpm", signals: ["pnpm-lock.yaml"], command: "pnpm install" },
|
|
6
|
+
{ name: "yarn", signals: ["yarn.lock"], command: "yarn install" },
|
|
7
|
+
{ name: "bun", signals: ["bun.lockb"], command: "bun install" },
|
|
8
|
+
{ name: "npm", signals: ["package-lock.json"], command: "npm install" },
|
|
9
|
+
{ name: "deno", signals: ["deno.json", "deno.jsonc"], command: "deno cache" },
|
|
10
10
|
// Python
|
|
11
|
-
{ name:
|
|
12
|
-
{ name:
|
|
13
|
-
{ name:
|
|
14
|
-
{ name:
|
|
15
|
-
{
|
|
16
|
-
|
|
11
|
+
{ name: "poetry", signals: ["poetry.lock"], command: "poetry install" },
|
|
12
|
+
{ name: "uv", signals: ["uv.lock"], command: "uv sync" },
|
|
13
|
+
{ name: "pipenv", signals: ["Pipfile.lock"], command: "pipenv install" },
|
|
14
|
+
{ name: "pdm", signals: ["pdm.lock"], command: "pdm install" },
|
|
15
|
+
{
|
|
16
|
+
name: "conda-lock",
|
|
17
|
+
signals: ["conda-lock.yml"],
|
|
18
|
+
command: "conda-lock install",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "conda",
|
|
22
|
+
signals: ["environment.yml"],
|
|
23
|
+
command: "conda env update --file environment.yml",
|
|
24
|
+
},
|
|
17
25
|
// R
|
|
18
|
-
{
|
|
26
|
+
{
|
|
27
|
+
name: "renv",
|
|
28
|
+
signals: ["renv.lock"],
|
|
29
|
+
command: "Rscript -e 'renv::restore()'",
|
|
30
|
+
},
|
|
19
31
|
// Rust
|
|
20
|
-
{ name:
|
|
32
|
+
{ name: "cargo", signals: ["Cargo.lock"], command: "cargo build" },
|
|
21
33
|
// Go
|
|
22
|
-
{ name:
|
|
34
|
+
{ name: "go", signals: ["go.sum"], command: "go mod download" },
|
|
23
35
|
// Ruby
|
|
24
|
-
{ name:
|
|
36
|
+
{ name: "bundler", signals: ["Gemfile.lock"], command: "bundle install" },
|
|
25
37
|
// PHP
|
|
26
|
-
{ name:
|
|
38
|
+
{ name: "composer", signals: ["composer.lock"], command: "composer install" },
|
|
27
39
|
// Elixir / Erlang
|
|
28
|
-
{ name:
|
|
29
|
-
{ name:
|
|
40
|
+
{ name: "mix", signals: ["mix.lock"], command: "mix deps.get" },
|
|
41
|
+
{ name: "rebar3", signals: ["rebar.lock"], command: "rebar3 deps" },
|
|
30
42
|
// Dart / Flutter
|
|
31
|
-
{ name:
|
|
43
|
+
{ name: "dart", signals: ["pubspec.lock"], command: "dart pub get" },
|
|
32
44
|
// Java / Kotlin / Scala
|
|
33
|
-
{ name:
|
|
34
|
-
{ name:
|
|
35
|
-
{
|
|
36
|
-
|
|
45
|
+
{ name: "maven", signals: ["pom.xml"], command: "mvn install" },
|
|
46
|
+
{ name: "gradle", signals: ["gradlew"], command: "./gradlew build" },
|
|
47
|
+
{
|
|
48
|
+
name: "gradle",
|
|
49
|
+
signals: ["build.gradle", "build.gradle.kts"],
|
|
50
|
+
command: "gradle build",
|
|
51
|
+
},
|
|
52
|
+
{ name: "sbt", signals: ["build.sbt"], command: "sbt compile" },
|
|
37
53
|
// .NET (C# / F# / VB)
|
|
38
|
-
{
|
|
54
|
+
{
|
|
55
|
+
name: "dotnet",
|
|
56
|
+
signals: ["*.sln", "*.csproj", "*.fsproj", "*.vbproj"],
|
|
57
|
+
command: "dotnet restore",
|
|
58
|
+
glob: true,
|
|
59
|
+
},
|
|
39
60
|
// Swift
|
|
40
|
-
{
|
|
61
|
+
{
|
|
62
|
+
name: "swift",
|
|
63
|
+
signals: ["Package.swift"],
|
|
64
|
+
command: "swift package resolve",
|
|
65
|
+
},
|
|
41
66
|
// Haskell
|
|
42
|
-
{ name:
|
|
43
|
-
{
|
|
44
|
-
|
|
67
|
+
{ name: "stack", signals: ["stack.yaml"], command: "stack build" },
|
|
68
|
+
{
|
|
69
|
+
name: "cabal",
|
|
70
|
+
signals: ["cabal.project"],
|
|
71
|
+
command: "cabal install --only-dependencies",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "cabal",
|
|
75
|
+
signals: ["*.cabal"],
|
|
76
|
+
command: "cabal install --only-dependencies",
|
|
77
|
+
glob: true,
|
|
78
|
+
},
|
|
45
79
|
// Clojure
|
|
46
|
-
{ name:
|
|
47
|
-
{ name:
|
|
80
|
+
{ name: "clojure", signals: ["deps.edn"], command: "clojure -P" },
|
|
81
|
+
{ name: "leiningen", signals: ["project.clj"], command: "lein deps" },
|
|
48
82
|
// OCaml
|
|
49
|
-
{ name:
|
|
83
|
+
{ name: "dune", signals: ["dune-project"], command: "dune build" },
|
|
50
84
|
// Julia
|
|
51
|
-
{
|
|
85
|
+
{
|
|
86
|
+
name: "julia",
|
|
87
|
+
signals: ["Manifest.toml"],
|
|
88
|
+
command: "julia --project -e 'using Pkg; Pkg.instantiate()'",
|
|
89
|
+
},
|
|
52
90
|
// Nim
|
|
53
|
-
{
|
|
91
|
+
{
|
|
92
|
+
name: "nimble",
|
|
93
|
+
signals: ["*.nimble"],
|
|
94
|
+
command: "nimble install",
|
|
95
|
+
glob: true,
|
|
96
|
+
},
|
|
54
97
|
// Crystal
|
|
55
|
-
{ name:
|
|
98
|
+
{ name: "shards", signals: ["shard.yml"], command: "shards install" },
|
|
56
99
|
// Perl
|
|
57
|
-
{ name:
|
|
100
|
+
{ name: "cpanm", signals: ["cpanfile"], command: "cpanm --installdeps ." },
|
|
58
101
|
// Zig
|
|
59
|
-
{ name:
|
|
102
|
+
{ name: "zig", signals: ["build.zig.zon"], command: "zig build" },
|
|
60
103
|
// C / C++
|
|
61
|
-
{ name:
|
|
62
|
-
{
|
|
104
|
+
{ name: "vcpkg", signals: ["vcpkg.json"], command: "vcpkg install" },
|
|
105
|
+
{
|
|
106
|
+
name: "conan",
|
|
107
|
+
signals: ["conanfile.py", "conanfile.txt"],
|
|
108
|
+
command: "conan install .",
|
|
109
|
+
},
|
|
63
110
|
// Nix
|
|
64
|
-
{ name:
|
|
65
|
-
{ name:
|
|
111
|
+
{ name: "nix", signals: ["flake.nix"], command: "nix develop" },
|
|
112
|
+
{ name: "nix-shell", signals: ["shell.nix"], command: "nix-shell" },
|
|
66
113
|
// Terraform / OpenTofu
|
|
67
|
-
{
|
|
114
|
+
{
|
|
115
|
+
name: "terraform",
|
|
116
|
+
signals: ["terraform.lock.hcl"],
|
|
117
|
+
command: "terraform init",
|
|
118
|
+
},
|
|
68
119
|
];
|
|
69
120
|
export async function detectPackageManager(repoRoot) {
|
|
70
121
|
for (const entry of ENTRIES) {
|
|
@@ -102,7 +153,7 @@ async function matchesGlob(repoRoot, patterns) {
|
|
|
102
153
|
}
|
|
103
154
|
function patternToRegex(pattern) {
|
|
104
155
|
const escaped = pattern
|
|
105
|
-
.replace(/[.+^${}()|[\]\\]/g,
|
|
106
|
-
.replace(/\*/g,
|
|
156
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
157
|
+
.replace(/\*/g, "[^/]*");
|
|
107
158
|
return new RegExp(`^${escaped}$`);
|
|
108
159
|
}
|
package/dist/pr.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type PathConflictChoice } from
|
|
2
|
-
import { type InstallPromptDependencies } from
|
|
1
|
+
import { type PathConflictChoice } from "./conflict.js";
|
|
2
|
+
import { type InstallPromptDependencies } from "./install-prompt.js";
|
|
3
3
|
export type { PathConflictChoice };
|
|
4
4
|
export interface PrCommandOptions {
|
|
5
5
|
cwd: string;
|