@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.
Files changed (80) hide show
  1. package/README.md +26 -1
  2. package/dist/back.d.ts +1 -1
  3. package/dist/back.js +23 -17
  4. package/dist/clean.d.ts +1 -1
  5. package/dist/clean.js +44 -35
  6. package/dist/cli.d.ts +1 -1
  7. package/dist/cli.js +264 -164
  8. package/dist/completion.js +3 -3
  9. package/dist/config-command.js +5 -5
  10. package/dist/config.js +41 -35
  11. package/dist/conflict.d.ts +1 -1
  12. package/dist/conflict.js +14 -6
  13. package/dist/editor.js +29 -9
  14. package/dist/file-sync.d.ts +1 -0
  15. package/dist/file-sync.js +15 -11
  16. package/dist/git.d.ts +1 -1
  17. package/dist/git.js +21 -19
  18. package/dist/gji-bundle.mjs +1624 -819
  19. package/dist/go.d.ts +2 -2
  20. package/dist/go.js +39 -26
  21. package/dist/headless.js +1 -1
  22. package/dist/history-command.js +3 -3
  23. package/dist/history.js +12 -12
  24. package/dist/hooks.js +16 -16
  25. package/dist/index.js +13 -9
  26. package/dist/init.d.ts +2 -2
  27. package/dist/init.js +106 -94
  28. package/dist/install-prompt.d.ts +3 -3
  29. package/dist/install-prompt.js +46 -28
  30. package/dist/ls.d.ts +2 -2
  31. package/dist/ls.js +29 -29
  32. package/dist/new.d.ts +2 -2
  33. package/dist/new.js +96 -81
  34. package/dist/open.d.ts +2 -2
  35. package/dist/open.js +24 -21
  36. package/dist/package-manager.js +96 -45
  37. package/dist/pr.d.ts +2 -2
  38. package/dist/pr.js +47 -34
  39. package/dist/remove.d.ts +1 -1
  40. package/dist/remove.js +39 -27
  41. package/dist/repo-registry.js +14 -14
  42. package/dist/repo.js +29 -28
  43. package/dist/root.js +3 -3
  44. package/dist/shell-completion.d.ts +1 -1
  45. package/dist/shell-completion.js +65 -37
  46. package/dist/shell-handoff.js +2 -2
  47. package/dist/shell.d.ts +1 -1
  48. package/dist/shell.js +4 -4
  49. package/dist/status.d.ts +5 -5
  50. package/dist/status.js +23 -23
  51. package/dist/sync-files-command.d.ts +10 -0
  52. package/dist/sync-files-command.js +137 -0
  53. package/dist/sync.js +23 -15
  54. package/dist/trigger-hook.js +9 -5
  55. package/dist/warp.js +37 -33
  56. package/dist/worktree-info.d.ts +9 -9
  57. package/dist/worktree-info.js +31 -29
  58. package/dist/worktree-management.d.ts +1 -1
  59. package/dist/worktree-management.js +26 -11
  60. package/dist/worktree-prompts.js +5 -5
  61. package/man/man1/gji-back.1 +1 -1
  62. package/man/man1/gji-clean.1 +1 -1
  63. package/man/man1/gji-completion.1 +1 -1
  64. package/man/man1/gji-config.1 +1 -1
  65. package/man/man1/gji-go.1 +1 -1
  66. package/man/man1/gji-history.1 +1 -1
  67. package/man/man1/gji-init.1 +1 -1
  68. package/man/man1/gji-ls.1 +1 -1
  69. package/man/man1/gji-new.1 +1 -1
  70. package/man/man1/gji-open.1 +1 -1
  71. package/man/man1/gji-pr.1 +1 -1
  72. package/man/man1/gji-remove.1 +1 -1
  73. package/man/man1/gji-root.1 +1 -1
  74. package/man/man1/gji-status.1 +1 -1
  75. package/man/man1/gji-sync-files.1 +23 -0
  76. package/man/man1/gji-sync.1 +1 -1
  77. package/man/man1/gji-trigger-hook.1 +1 -1
  78. package/man/man1/gji-warp.1 +1 -1
  79. package/man/man1/gji.1 +5 -1
  80. package/package.json +8 -2
package/dist/new.js CHANGED
@@ -1,20 +1,20 @@
1
- import { mkdir } from 'node:fs/promises';
2
- import { basename, dirname } from 'node:path';
3
- import { execFile } from 'node:child_process';
4
- import { promisify } from 'node:util';
5
- import { isCancel, text } from '@clack/prompts';
6
- import { loadEffectiveConfig, resolveConfigString } from './config.js';
7
- import { defaultSpawnEditor, EDITORS } from './editor.js';
8
- import { syncFiles } from './file-sync.js';
9
- import { extractHooks, runHook } from './hooks.js';
10
- import { appendHistory } from './history.js';
11
- import { isHeadless } from './headless.js';
12
- import { maybeRunInstallPrompt } from './install-prompt.js';
13
- import { pathExists, promptForPathConflict } from './conflict.js';
14
- import { detectRepository, resolveWorktreePath, validateBranchName } from './repo.js';
15
- import { writeShellOutput } from './shell-handoff.js';
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 = 'GJI_NEW_OUTPUT_FILE';
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('gji new: --editor has no effect without --open\n');
28
+ options.stderr("gji new: --editor has no effect without --open\n");
29
29
  }
30
- if (!options.detached && !options.branch && (options.json || isHeadless())) {
31
- const message = 'branch argument is required';
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: 'Aborted' }, null, 2)}\n`);
47
+ options.stderr(`${JSON.stringify({ error: "Aborted" }, null, 2)}\n`);
46
48
  }
47
49
  else {
48
- options.stderr('Aborted\n');
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, 'worktreePath');
65
- const configuredBasePath = rawBasePath?.startsWith('/') || rawBasePath?.startsWith('~') ? rawBasePath : undefined;
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('git', ['worktree', 'remove', '--force', worktreePath], { cwd: repository.repoRoot });
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('git', ['branch', '-D', worktreeName], { cwd: repository.repoRoot });
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 === 'reuse') {
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, 'editor'))
134
+ ? (options.editor ?? resolveConfigString(config, "editor"))
129
135
  : undefined;
130
- const openNote = resolvedEditor ? `, then open in ${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
- ? ['worktree', 'add', '--detach', worktreePath]
138
- : await localBranchExists(repository.repoRoot, worktreeName)
139
- ? ['worktree', 'add', worktreePath, worktreeName]
140
- : ['worktree', 'add', '-b', worktreeName, worktreePath];
141
- await execFileAsync('git', gitArgs, { cwd: repository.repoRoot });
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 === 'string')
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, { branch: worktreeName, path: worktreePath, repo: basename(repository.repoRoot) }, options.stderr);
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, 'editor');
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
- 'socrates',
175
- 'prometheus',
176
- 'beethoven',
177
- 'ada',
178
- 'turing',
179
- 'hypatia',
180
- 'tesla',
181
- 'curie',
182
- 'diogenes',
183
- 'plato',
184
- 'hephaestus',
185
- 'athena',
186
- 'archimedes',
187
- 'euclid',
188
- 'heraclitus',
189
- 'galileo',
190
- 'newton',
191
- 'lovelace',
192
- 'nietzsche',
193
- 'kafka',
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
- 'borrowed-a-bike',
197
- 'brought-snacks',
198
- 'missed-the-bus',
199
- 'lost-the-keys',
200
- 'spilled-the-coffee',
201
- 'forgot-the-umbrella',
202
- 'walked-the-dog',
203
- 'missed-the-train',
204
- 'wrote-a-poem',
205
- 'burned-the-toast',
206
- 'fed-the-pigeons',
207
- 'watered-the-plants',
208
- 'washed-the-dishes',
209
- 'folded-the-laundry',
210
- 'took-a-nap',
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 !== 'string' || branchPrefix.length === 0) {
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: 'Name the new branch',
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('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branchName}`], { cwd: repoRoot });
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('is not a working tree') || stderr.includes('not a linked working tree');
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 && 'stderr' in error && typeof error.stderr === 'string';
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('gji new: --open requires --editor <cli> or a saved editor in config\n');
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 './editor.js';
2
- import { type WorktreeEntry } from './repo.js';
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 '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';
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('Aborted\n');
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, 'editor');
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('gji open: no supported editor detected. Use --editor <code|cursor|zed|...> to specify one.\n');
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('Aborted\n');
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('editor', editorCli);
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) => ({ editor, available: await isCommandAvailable(editor.cli) })));
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('which', [command]);
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: 'Choose a worktree to open',
126
+ message: "Choose a worktree to open",
124
127
  options: worktrees.map((w) => ({
125
128
  value: w.path,
126
- label: w.branch ?? '(detached)',
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: 'Choose an editor',
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: '.' }], settings: {} };
153
- await writeFile(workspacePath, `${JSON.stringify(workspace, null, 2)}\n`, 'utf8');
155
+ const workspace = { folders: [{ path: "." }], settings: {} };
156
+ await writeFile(workspacePath, `${JSON.stringify(workspace, null, 2)}\n`, "utf8");
154
157
  return workspacePath;
155
158
  }
@@ -1,70 +1,121 @@
1
- import { access, readdir } from 'node:fs/promises';
2
- import { join } from 'node:path';
1
+ import { access, readdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
3
  const ENTRIES = [
4
4
  // JavaScript / TypeScript
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' },
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: '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
- { name: 'conda-lock', signals: ['conda-lock.yml'], command: 'conda-lock install' },
16
- { name: 'conda', signals: ['environment.yml'], command: 'conda env update --file environment.yml' },
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
- { name: 'renv', signals: ['renv.lock'], command: "Rscript -e 'renv::restore()'" },
26
+ {
27
+ name: "renv",
28
+ signals: ["renv.lock"],
29
+ command: "Rscript -e 'renv::restore()'",
30
+ },
19
31
  // Rust
20
- { name: 'cargo', signals: ['Cargo.lock'], command: 'cargo build' },
32
+ { name: "cargo", signals: ["Cargo.lock"], command: "cargo build" },
21
33
  // Go
22
- { name: 'go', signals: ['go.sum'], command: 'go mod download' },
34
+ { name: "go", signals: ["go.sum"], command: "go mod download" },
23
35
  // Ruby
24
- { name: 'bundler', signals: ['Gemfile.lock'], command: 'bundle install' },
36
+ { name: "bundler", signals: ["Gemfile.lock"], command: "bundle install" },
25
37
  // PHP
26
- { name: 'composer', signals: ['composer.lock'], command: 'composer install' },
38
+ { name: "composer", signals: ["composer.lock"], command: "composer install" },
27
39
  // Elixir / Erlang
28
- { name: 'mix', signals: ['mix.lock'], command: 'mix deps.get' },
29
- { name: 'rebar3', signals: ['rebar.lock'], command: 'rebar3 deps' },
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: 'dart', signals: ['pubspec.lock'], command: 'dart pub get' },
43
+ { name: "dart", signals: ["pubspec.lock"], command: "dart pub get" },
32
44
  // Java / Kotlin / Scala
33
- { name: 'maven', signals: ['pom.xml'], command: 'mvn install' },
34
- { name: 'gradle', signals: ['gradlew'], command: './gradlew build' },
35
- { name: 'gradle', signals: ['build.gradle', 'build.gradle.kts'], command: 'gradle build' },
36
- { name: 'sbt', signals: ['build.sbt'], command: 'sbt compile' },
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
- { name: 'dotnet', signals: ['*.sln', '*.csproj', '*.fsproj', '*.vbproj'], command: 'dotnet restore', glob: true },
54
+ {
55
+ name: "dotnet",
56
+ signals: ["*.sln", "*.csproj", "*.fsproj", "*.vbproj"],
57
+ command: "dotnet restore",
58
+ glob: true,
59
+ },
39
60
  // Swift
40
- { name: 'swift', signals: ['Package.swift'], command: 'swift package resolve' },
61
+ {
62
+ name: "swift",
63
+ signals: ["Package.swift"],
64
+ command: "swift package resolve",
65
+ },
41
66
  // Haskell
42
- { name: 'stack', signals: ['stack.yaml'], command: 'stack build' },
43
- { name: 'cabal', signals: ['cabal.project'], command: 'cabal install --only-dependencies' },
44
- { name: 'cabal', signals: ['*.cabal'], command: 'cabal install --only-dependencies', glob: true },
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: 'clojure', signals: ['deps.edn'], command: 'clojure -P' },
47
- { name: 'leiningen', signals: ['project.clj'], command: 'lein deps' },
80
+ { name: "clojure", signals: ["deps.edn"], command: "clojure -P" },
81
+ { name: "leiningen", signals: ["project.clj"], command: "lein deps" },
48
82
  // OCaml
49
- { name: 'dune', signals: ['dune-project'], command: 'dune build' },
83
+ { name: "dune", signals: ["dune-project"], command: "dune build" },
50
84
  // Julia
51
- { name: 'julia', signals: ['Manifest.toml'], command: "julia --project -e 'using Pkg; Pkg.instantiate()'" },
85
+ {
86
+ name: "julia",
87
+ signals: ["Manifest.toml"],
88
+ command: "julia --project -e 'using Pkg; Pkg.instantiate()'",
89
+ },
52
90
  // Nim
53
- { name: 'nimble', signals: ['*.nimble'], command: 'nimble install', glob: true },
91
+ {
92
+ name: "nimble",
93
+ signals: ["*.nimble"],
94
+ command: "nimble install",
95
+ glob: true,
96
+ },
54
97
  // Crystal
55
- { name: 'shards', signals: ['shard.yml'], command: 'shards install' },
98
+ { name: "shards", signals: ["shard.yml"], command: "shards install" },
56
99
  // Perl
57
- { name: 'cpanm', signals: ['cpanfile'], command: 'cpanm --installdeps .' },
100
+ { name: "cpanm", signals: ["cpanfile"], command: "cpanm --installdeps ." },
58
101
  // Zig
59
- { name: 'zig', signals: ['build.zig.zon'], command: 'zig build' },
102
+ { name: "zig", signals: ["build.zig.zon"], command: "zig build" },
60
103
  // C / C++
61
- { name: 'vcpkg', signals: ['vcpkg.json'], command: 'vcpkg install' },
62
- { name: 'conan', signals: ['conanfile.py', 'conanfile.txt'], command: 'conan install .' },
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: 'nix', signals: ['flake.nix'], command: 'nix develop' },
65
- { name: 'nix-shell', signals: ['shell.nix'], command: 'nix-shell' },
111
+ { name: "nix", signals: ["flake.nix"], command: "nix develop" },
112
+ { name: "nix-shell", signals: ["shell.nix"], command: "nix-shell" },
66
113
  // Terraform / OpenTofu
67
- { name: 'terraform', signals: ['terraform.lock.hcl'], command: 'terraform init' },
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 './conflict.js';
2
- import { type InstallPromptDependencies } from './install-prompt.js';
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;