@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/init.js CHANGED
@@ -1,67 +1,67 @@
1
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
- import { homedir } from 'node:os';
3
- import { dirname, join } from 'node:path';
4
- import { intro, isCancel, outro, select, text } from '@clack/prompts';
5
- import { loadConfig, loadGlobalConfig, saveGlobalConfig, saveLocalConfig, updateGlobalConfigKey } from './config.js';
6
- import { resolveSupportedShell } from './shell.js';
7
- const START_MARKER = '# >>> gji init >>>';
8
- const END_MARKER = '# <<< gji init <<<';
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { intro, isCancel, outro, select, text } from "@clack/prompts";
5
+ import { loadConfig, loadGlobalConfig, saveGlobalConfig, saveLocalConfig, updateGlobalConfigKey, } from "./config.js";
6
+ import { resolveSupportedShell } from "./shell.js";
7
+ const START_MARKER = "# >>> gji init >>>";
8
+ const END_MARKER = "# <<< gji init <<<";
9
9
  const SHELL_WRAPPED_COMMANDS = [
10
10
  {
11
- bypassOptions: ['--help'],
12
- commandName: 'new',
13
- envVar: 'GJI_NEW_OUTPUT_FILE',
14
- names: ['new'],
15
- tempPrefix: 'gji-new',
11
+ bypassOptions: ["--help"],
12
+ commandName: "new",
13
+ envVar: "GJI_NEW_OUTPUT_FILE",
14
+ names: ["new"],
15
+ tempPrefix: "gji-new",
16
16
  },
17
17
  {
18
- bypassOptions: ['--help'],
19
- commandName: 'pr',
20
- envVar: 'GJI_PR_OUTPUT_FILE',
21
- names: ['pr'],
22
- tempPrefix: 'gji-pr',
18
+ bypassOptions: ["--help"],
19
+ commandName: "pr",
20
+ envVar: "GJI_PR_OUTPUT_FILE",
21
+ names: ["pr"],
22
+ tempPrefix: "gji-pr",
23
23
  },
24
24
  {
25
- bypassOptions: ['--print'],
26
- commandName: 'back',
27
- envVar: 'GJI_BACK_OUTPUT_FILE',
28
- names: ['back'],
29
- tempPrefix: 'gji-back',
25
+ bypassOptions: ["--print"],
26
+ commandName: "back",
27
+ envVar: "GJI_BACK_OUTPUT_FILE",
28
+ names: ["back"],
29
+ tempPrefix: "gji-back",
30
30
  },
31
31
  {
32
- bypassOptions: ['--print'],
33
- commandName: 'go',
34
- envVar: 'GJI_GO_OUTPUT_FILE',
35
- names: ['go', 'jump'],
36
- tempPrefix: 'gji-go',
32
+ bypassOptions: ["--print"],
33
+ commandName: "go",
34
+ envVar: "GJI_GO_OUTPUT_FILE",
35
+ names: ["go", "jump"],
36
+ tempPrefix: "gji-go",
37
37
  },
38
38
  {
39
- bypassOptions: ['--print'],
40
- commandName: 'root',
41
- envVar: 'GJI_ROOT_OUTPUT_FILE',
42
- names: ['root'],
43
- tempPrefix: 'gji-root',
39
+ bypassOptions: ["--print"],
40
+ commandName: "root",
41
+ envVar: "GJI_ROOT_OUTPUT_FILE",
42
+ names: ["root"],
43
+ tempPrefix: "gji-root",
44
44
  },
45
45
  {
46
- bypassOptions: ['--help'],
47
- commandName: 'remove',
48
- envVar: 'GJI_REMOVE_OUTPUT_FILE',
49
- names: ['remove', 'rm'],
50
- tempPrefix: 'gji-remove',
46
+ bypassOptions: ["--help"],
47
+ commandName: "remove",
48
+ envVar: "GJI_REMOVE_OUTPUT_FILE",
49
+ names: ["remove", "rm"],
50
+ tempPrefix: "gji-remove",
51
51
  },
52
52
  {
53
- bypassOptions: ['--print', '--json'],
54
- commandName: 'warp',
55
- envVar: 'GJI_WARP_OUTPUT_FILE',
56
- names: ['warp'],
57
- tempPrefix: 'gji-warp',
53
+ bypassOptions: ["--print", "--json"],
54
+ commandName: "warp",
55
+ envVar: "GJI_WARP_OUTPUT_FILE",
56
+ names: ["warp"],
57
+ tempPrefix: "gji-warp",
58
58
  },
59
59
  ];
60
60
  export async function runInitCommand(options) {
61
61
  const shell = resolveSupportedShell(options.shell, process.env.SHELL);
62
62
  const home = options.home ?? homedir();
63
63
  if (!shell) {
64
- options.stderr?.('Unable to detect a supported shell. Specify one explicitly: bash, fish, or zsh.\n');
64
+ options.stderr?.("Unable to detect a supported shell. Specify one explicitly: bash, fish, or zsh.\n");
65
65
  return 1;
66
66
  }
67
67
  const script = renderShellIntegration(shell);
@@ -73,29 +73,29 @@ export async function runInitCommand(options) {
73
73
  await mkdir(dirname(rcPath), { recursive: true });
74
74
  const current = await readExistingConfig(rcPath);
75
75
  const next = upsertShellIntegration(current, script);
76
- await writeFile(rcPath, next, 'utf8');
76
+ await writeFile(rcPath, next, "utf8");
77
77
  options.stdout(`${rcPath}\n`);
78
78
  // Run the setup wizard on the first-ever init (not on subsequent re-runs).
79
79
  const { config: globalConfig } = await loadGlobalConfig(home);
80
- const alreadyConfigured = 'shellIntegration' in globalConfig || 'installSaveTarget' in globalConfig;
80
+ const alreadyConfigured = "shellIntegration" in globalConfig || "installSaveTarget" in globalConfig;
81
81
  const hasCustomPrompt = options.promptForSetup !== undefined;
82
82
  const canPrompt = hasCustomPrompt || process.stdout.isTTY === true;
83
83
  if (!alreadyConfigured && canPrompt) {
84
84
  const prompt = options.promptForSetup ?? defaultPromptForSetup;
85
85
  const result = await prompt();
86
86
  if (result) {
87
- await updateGlobalConfigKey('installSaveTarget', result.installSaveTarget, home);
87
+ await updateGlobalConfigKey("installSaveTarget", result.installSaveTarget, home);
88
88
  await saveWizardConfig(result, options.cwd, home);
89
89
  }
90
90
  }
91
91
  // Mark shell integration as installed so the first-run nudge is suppressed.
92
- await updateGlobalConfigKey('shellIntegration', true, home);
92
+ await updateGlobalConfigKey("shellIntegration", true, home);
93
93
  return 0;
94
94
  }
95
95
  export function renderShellIntegration(shell) {
96
- const commandBlocks = SHELL_WRAPPED_COMMANDS.map((command) => shell === 'fish' ? renderFishWrapper(command) : renderPosixWrapper(command)).join('\n\n');
96
+ const commandBlocks = SHELL_WRAPPED_COMMANDS.map((command) => shell === "fish" ? renderFishWrapper(command) : renderPosixWrapper(command)).join("\n\n");
97
97
  switch (shell) {
98
- case 'fish':
98
+ case "fish":
99
99
  return `${START_MARKER}
100
100
  function gji --wraps gji --description 'gji shell integration'
101
101
  ${indentBlock(commandBlocks, 4)}
@@ -104,8 +104,8 @@ ${indentBlock(commandBlocks, 4)}
104
104
  end
105
105
  ${END_MARKER}
106
106
  `;
107
- case 'bash':
108
- case 'zsh':
107
+ case "bash":
108
+ case "zsh":
109
109
  return `${START_MARKER}
110
110
  gji() {
111
111
  ${indentBlock(commandBlocks, 2)}
@@ -118,7 +118,7 @@ ${END_MARKER}
118
118
  }
119
119
  export function upsertShellIntegration(existingConfig, script) {
120
120
  const trimmedScript = script.trimEnd();
121
- const blockPattern = new RegExp(`${escapeForRegExp(START_MARKER)}[\\s\\S]*?${escapeForRegExp(END_MARKER)}\\n?`, 'm');
121
+ const blockPattern = new RegExp(`${escapeForRegExp(START_MARKER)}[\\s\\S]*?${escapeForRegExp(END_MARKER)}\\n?`, "m");
122
122
  if (blockPattern.test(existingConfig)) {
123
123
  return ensureTrailingNewline(existingConfig.replace(blockPattern, `${trimmedScript}\n`));
124
124
  }
@@ -145,7 +145,7 @@ async function saveWizardConfig(result, cwd, home) {
145
145
  values.hooks = hooks;
146
146
  if (Object.keys(values).length === 0)
147
147
  return;
148
- if (result.installSaveTarget === 'local') {
148
+ if (result.installSaveTarget === "local") {
149
149
  const loaded = await loadConfig(cwd);
150
150
  await saveLocalConfig(cwd, { ...loaded.config, ...values });
151
151
  }
@@ -156,43 +156,43 @@ async function saveWizardConfig(result, cwd, home) {
156
156
  }
157
157
  function resolveShellConfigPath(shell, home) {
158
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');
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
165
  }
166
166
  }
167
167
  async function readExistingConfig(path) {
168
168
  try {
169
- return await readFile(path, 'utf8');
169
+ return await readFile(path, "utf8");
170
170
  }
171
171
  catch (error) {
172
172
  if (isMissingFileError(error)) {
173
- return '';
173
+ return "";
174
174
  }
175
175
  throw error;
176
176
  }
177
177
  }
178
178
  function ensureTrailingNewline(value) {
179
- return value.endsWith('\n') ? value : `${value}\n`;
179
+ return value.endsWith("\n") ? value : `${value}\n`;
180
180
  }
181
181
  function escapeForRegExp(value) {
182
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
182
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
183
183
  }
184
184
  function isMissingFileError(error) {
185
- return error instanceof Error && 'code' in error && error.code === 'ENOENT';
185
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
186
186
  }
187
187
  function renderFishWrapper(command) {
188
188
  const nameTests = command.names.map((name) => `test $argv[1] = ${name}`);
189
189
  const nameCondition = nameTests.length === 1
190
190
  ? nameTests[0]
191
- : `begin; ${nameTests.join('; or ')}; end`;
191
+ : `begin; ${nameTests.join("; or ")}; end`;
192
192
  const bypassTests = command.bypassOptions.map((opt) => `test $argv[1] = ${opt}`);
193
193
  const bypassCondition = bypassTests.length === 1
194
194
  ? bypassTests[0]
195
- : `begin; ${bypassTests.join('; or ')}; end`;
195
+ : `begin; ${bypassTests.join("; or ")}; end`;
196
196
  return `if test (count $argv) -gt 0; and ${nameCondition}
197
197
  set -e argv[1]
198
198
  if test (count $argv) -gt 0; and ${bypassCondition}
@@ -215,8 +215,12 @@ function renderFishWrapper(command) {
215
215
  end`;
216
216
  }
217
217
  function renderPosixWrapper(command) {
218
- const tests = command.names.map((name) => `[ "$1" = "${name}" ]`).join(' || ');
219
- const bypassTests = command.bypassOptions.map((opt) => `[ "\${1:-}" = "${opt}" ]`).join(' || ');
218
+ const tests = command.names
219
+ .map((name) => `[ "$1" = "${name}" ]`)
220
+ .join(" || ");
221
+ const bypassTests = command.bypassOptions
222
+ .map((opt) => `[ "\${1:-}" = "${opt}" ]`)
223
+ .join(" || ");
220
224
  return `if ${tests}; then
221
225
  shift
222
226
  if ${bypassTests}; then
@@ -235,66 +239,74 @@ function renderPosixWrapper(command) {
235
239
  fi`;
236
240
  }
237
241
  function indentBlock(value, spaces) {
238
- const prefix = ' '.repeat(spaces);
242
+ const prefix = " ".repeat(spaces);
239
243
  return value
240
- .split('\n')
241
- .map((line) => line.length === 0 ? '' : `${prefix}${line}`)
242
- .join('\n');
244
+ .split("\n")
245
+ .map((line) => (line.length === 0 ? "" : `${prefix}${line}`))
246
+ .join("\n");
243
247
  }
244
248
  async function defaultPromptForSetup() {
245
- intro('gji setup');
249
+ intro("gji setup");
246
250
  const installSaveTarget = await select({
247
- message: 'Where should preferences be saved?',
251
+ message: "Where should preferences be saved?",
248
252
  options: [
249
- { value: 'global', label: '~/.config/gji/config.json', hint: 'personal — never committed' },
250
- { value: 'local', label: '.gji.json', hint: 'repo — committed with the project' },
253
+ {
254
+ value: "global",
255
+ label: "~/.config/gji/config.json",
256
+ hint: "personal — never committed",
257
+ },
258
+ {
259
+ value: "local",
260
+ label: ".gji.json",
261
+ hint: "repo — committed with the project",
262
+ },
251
263
  ],
252
264
  });
253
265
  if (isCancel(installSaveTarget)) {
254
- outro('Setup skipped.');
266
+ outro("Setup skipped.");
255
267
  return null;
256
268
  }
257
269
  const branchPrefix = await text({
258
- message: 'Default branch prefix?',
259
- placeholder: 'e.g. feat/ or fix/ — leave blank to skip',
270
+ message: "Default branch prefix?",
271
+ placeholder: "e.g. feat/ or fix/ — leave blank to skip",
260
272
  });
261
273
  if (isCancel(branchPrefix)) {
262
- outro('Setup skipped.');
274
+ outro("Setup skipped.");
263
275
  return null;
264
276
  }
265
277
  const worktreePath = await text({
266
- message: 'Worktree base path?',
267
- placeholder: 'leave blank to use the default path',
278
+ message: "Worktree base path?",
279
+ placeholder: "leave blank to use the default path",
268
280
  });
269
281
  if (isCancel(worktreePath)) {
270
- outro('Setup skipped.');
282
+ outro("Setup skipped.");
271
283
  return null;
272
284
  }
273
285
  const afterCreate = await text({
274
- message: 'afterCreate hook — run after creating a worktree?',
275
- placeholder: 'e.g. pnpm install — leave blank to skip',
286
+ message: "afterCreate hook — run after creating a worktree?",
287
+ placeholder: "e.g. pnpm install — leave blank to skip",
276
288
  });
277
289
  if (isCancel(afterCreate)) {
278
- outro('Setup skipped.');
290
+ outro("Setup skipped.");
279
291
  return null;
280
292
  }
281
293
  const afterEnter = await text({
282
- message: 'afterEnter hook — run after entering a worktree?',
283
- placeholder: 'e.g. nvm use — leave blank to skip',
294
+ message: "afterEnter hook — run after entering a worktree?",
295
+ placeholder: "e.g. nvm use — leave blank to skip",
284
296
  });
285
297
  if (isCancel(afterEnter)) {
286
- outro('Setup skipped.');
298
+ outro("Setup skipped.");
287
299
  return null;
288
300
  }
289
301
  const beforeRemove = await text({
290
- message: 'beforeRemove hook — run before removing a worktree?',
291
- placeholder: 'leave blank to skip',
302
+ message: "beforeRemove hook — run before removing a worktree?",
303
+ placeholder: "leave blank to skip",
292
304
  });
293
305
  if (isCancel(beforeRemove)) {
294
- outro('Setup skipped.');
306
+ outro("Setup skipped.");
295
307
  return null;
296
308
  }
297
- outro('Setup complete!');
309
+ outro("Setup complete!");
298
310
  const hooks = {};
299
311
  if (afterCreate)
300
312
  hooks.afterCreate = afterCreate;
@@ -1,6 +1,6 @@
1
- import { type GjiConfig } from './config.js';
2
- import { type PackageManager } from './package-manager.js';
3
- export type InstallChoice = 'yes' | 'no' | 'always' | 'never';
1
+ import { type GjiConfig } from "./config.js";
2
+ import { type PackageManager } from "./package-manager.js";
3
+ export type InstallChoice = "yes" | "no" | "always" | "never";
4
4
  export interface InstallPromptDependencies {
5
5
  detectInstallPackageManager?: (root: string) => Promise<PackageManager | null>;
6
6
  promptForInstallChoice?: (pm: PackageManager) => Promise<InstallChoice | null>;
@@ -1,8 +1,8 @@
1
- import { spawn } from 'node:child_process';
2
- import { isCancel, select } from '@clack/prompts';
3
- import { loadConfig, loadGlobalConfig, updateGlobalRepoConfigKey, updateLocalConfigKey } from './config.js';
4
- import { isHeadless } from './headless.js';
5
- import { detectPackageManager } from './package-manager.js';
1
+ import { spawn } from "node:child_process";
2
+ import { isCancel, select } from "@clack/prompts";
3
+ import { loadConfig, loadGlobalConfig, updateGlobalRepoConfigKey, updateLocalConfigKey, } from "./config.js";
4
+ import { isHeadless } from "./headless.js";
5
+ import { detectPackageManager, } from "./package-manager.js";
6
6
  export async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dependencies = {}, nonInteractive = false) {
7
7
  // Skip in non-interactive mode — no prompt can be shown.
8
8
  if (isHeadless() || nonInteractive) {
@@ -24,10 +24,10 @@ export async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stde
24
24
  }
25
25
  const prompt = dependencies.promptForInstallChoice ?? defaultPromptForInstallChoice;
26
26
  const choice = await prompt(pm);
27
- if (!choice || choice === 'no') {
27
+ if (!choice || choice === "no") {
28
28
  return;
29
29
  }
30
- if (choice === 'yes' || choice === 'always') {
30
+ if (choice === "yes" || choice === "always") {
31
31
  const runner = dependencies.runInstallCommand ?? defaultRunInstallCommand;
32
32
  try {
33
33
  await runner(pm.installCommand, worktreePath, stderr);
@@ -36,34 +36,42 @@ export async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stde
36
36
  stderr(`gji: install command failed: ${error instanceof Error ? error.message : String(error)}\n`);
37
37
  }
38
38
  }
39
- const saveGlobal = config.installSaveTarget === 'global';
39
+ const saveGlobal = config.installSaveTarget === "global";
40
40
  const writeKey = dependencies.writeConfigKey ?? defaultWriteConfigKey;
41
41
  const writeGlobalKey = dependencies.writeGlobalRepoConfigKey ?? defaultWriteGlobalRepoConfigKey;
42
- if (choice === 'always') {
42
+ if (choice === "always") {
43
43
  try {
44
44
  if (saveGlobal) {
45
45
  // Deep-merge with any existing per-repo global hooks so other keys are preserved.
46
46
  const existingHooks = await loadExistingGlobalRepoHooks(repoRoot);
47
- await writeGlobalKey(repoRoot, 'hooks', { ...existingHooks, afterCreate: pm.installCommand });
47
+ await writeGlobalKey(repoRoot, "hooks", {
48
+ ...existingHooks,
49
+ afterCreate: pm.installCommand,
50
+ });
48
51
  }
49
52
  else {
50
53
  // Read local config hooks to deep-merge so other hook keys (e.g. afterEnter) are preserved.
51
54
  const { config: localConfig } = await loadConfig(repoRoot);
52
- const existingLocalHooks = isPlainObject(localConfig.hooks) ? localConfig.hooks : {};
53
- await writeKey(repoRoot, 'hooks', { ...existingLocalHooks, afterCreate: pm.installCommand });
55
+ const existingLocalHooks = isPlainObject(localConfig.hooks)
56
+ ? localConfig.hooks
57
+ : {};
58
+ await writeKey(repoRoot, "hooks", {
59
+ ...existingLocalHooks,
60
+ afterCreate: pm.installCommand,
61
+ });
54
62
  }
55
63
  }
56
64
  catch (error) {
57
65
  stderr(`gji: failed to save config: ${error instanceof Error ? error.message : String(error)}\n`);
58
66
  }
59
67
  }
60
- if (choice === 'never') {
68
+ if (choice === "never") {
61
69
  try {
62
70
  if (saveGlobal) {
63
- await writeGlobalKey(repoRoot, 'skipInstallPrompt', true);
71
+ await writeGlobalKey(repoRoot, "skipInstallPrompt", true);
64
72
  }
65
73
  else {
66
- await writeKey(repoRoot, 'skipInstallPrompt', true);
74
+ await writeKey(repoRoot, "skipInstallPrompt", true);
67
75
  }
68
76
  }
69
77
  catch (error) {
@@ -73,11 +81,15 @@ export async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stde
73
81
  }
74
82
  async function defaultRunInstallCommand(command, cwd, stderr) {
75
83
  await new Promise((resolve, reject) => {
76
- const child = spawn(command, { cwd, shell: true, stdio: ['ignore', 'inherit', 'pipe'] });
77
- child.stderr.on('data', (chunk) => {
84
+ const child = spawn(command, {
85
+ cwd,
86
+ shell: true,
87
+ stdio: ["ignore", "inherit", "pipe"],
88
+ });
89
+ child.stderr.on("data", (chunk) => {
78
90
  stderr(chunk.toString());
79
91
  });
80
- child.on('close', (code) => {
92
+ child.on("close", (code) => {
81
93
  if (code !== 0) {
82
94
  reject(new Error(`exited with code ${code}`));
83
95
  }
@@ -85,7 +97,7 @@ async function defaultRunInstallCommand(command, cwd, stderr) {
85
97
  resolve();
86
98
  }
87
99
  });
88
- child.on('error', (err) => {
100
+ child.on("error", (err) => {
89
101
  reject(err);
90
102
  });
91
103
  });
@@ -99,17 +111,23 @@ async function defaultWriteGlobalRepoConfigKey(repoRoot, key, value) {
99
111
  async function loadExistingGlobalRepoHooks(repoRoot) {
100
112
  const { config: globalConfig } = await loadGlobalConfig();
101
113
  const repos = isPlainObject(globalConfig.repos) ? globalConfig.repos : {};
102
- const perRepo = isPlainObject(repos[repoRoot]) ? repos[repoRoot] : {};
114
+ const perRepo = isPlainObject(repos[repoRoot])
115
+ ? repos[repoRoot]
116
+ : {};
103
117
  return isPlainObject(perRepo.hooks) ? perRepo.hooks : {};
104
118
  }
105
119
  async function defaultPromptForInstallChoice(pm) {
106
120
  const choice = await select({
107
121
  message: `Run \`${pm.installCommand}\` in the new worktree?`,
108
122
  options: [
109
- { value: 'yes', label: 'Yes', hint: 'run once' },
110
- { value: 'no', label: 'No', hint: 'skip this time' },
111
- { value: 'always', label: 'Always', hint: 'save as afterCreate hook' },
112
- { value: 'never', label: 'Never', hint: 'disable this prompt for this repo' },
123
+ { value: "yes", label: "Yes", hint: "run once" },
124
+ { value: "no", label: "No", hint: "skip this time" },
125
+ { value: "always", label: "Always", hint: "save as afterCreate hook" },
126
+ {
127
+ value: "never",
128
+ label: "Never",
129
+ hint: "disable this prompt for this repo",
130
+ },
113
131
  ],
114
132
  });
115
133
  if (isCancel(choice)) {
@@ -118,13 +136,13 @@ async function defaultPromptForInstallChoice(pm) {
118
136
  return choice;
119
137
  }
120
138
  function isPlainObject(value) {
121
- return typeof value === 'object' && value !== null && !Array.isArray(value);
139
+ return typeof value === "object" && value !== null && !Array.isArray(value);
122
140
  }
123
141
  function isConfiguredHookCommand(value) {
124
- if (typeof value === 'string')
142
+ if (typeof value === "string")
125
143
  return value.length > 0;
126
144
  return (Array.isArray(value) &&
127
145
  value.length > 0 &&
128
- value[0] !== '' &&
129
- value.every((item) => typeof item === 'string'));
146
+ value[0] !== "" &&
147
+ value.every((item) => typeof item === "string"));
130
148
  }
package/dist/ls.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { type WorktreeEntry } from './repo.js';
2
- import { type WorktreeInfo } from './worktree-info.js';
1
+ import { type WorktreeEntry } from "./repo.js";
2
+ import { type WorktreeInfo } from "./worktree-info.js";
3
3
  export interface LsCommandOptions {
4
4
  compact?: boolean;
5
5
  cwd: string;
package/dist/ls.js CHANGED
@@ -1,6 +1,6 @@
1
- import { listWorktrees } from './repo.js';
2
- import { comparePaths } from './paths.js';
3
- import { formatLastCommit, formatUpstreamState, readWorktreeInfos, } from './worktree-info.js';
1
+ import { comparePaths } from "./paths.js";
2
+ import { listWorktrees } from "./repo.js";
3
+ import { formatLastCommit, formatUpstreamState, readWorktreeInfos, } from "./worktree-info.js";
4
4
  export async function runLsCommand(options) {
5
5
  const worktrees = sortWorktrees(await listWorktrees(options.cwd));
6
6
  if (options.compact) {
@@ -21,50 +21,50 @@ export async function runLsCommand(options) {
21
21
  }
22
22
  export function formatDetailedWorktreeTable(worktrees) {
23
23
  const rows = worktrees.map((worktree) => ({
24
- branch: worktree.branch ?? '(detached)',
24
+ branch: worktree.branch ?? "(detached)",
25
25
  isCurrent: worktree.isCurrent,
26
26
  lastCommit: formatLastCommit(worktree.lastCommitTimestamp),
27
27
  path: worktree.path,
28
28
  status: worktree.status,
29
29
  upstream: formatUpstreamState(worktree.upstream),
30
30
  }));
31
- const branchWidth = Math.max('BRANCH'.length, ...rows.map((row) => row.branch.length));
32
- const statusWidth = Math.max('STATUS'.length, ...rows.map((row) => row.status.length));
33
- const upstreamWidth = Math.max('UPSTREAM'.length, ...rows.map((row) => row.upstream.length));
34
- const lastCommitWidth = Math.max('LAST'.length, ...rows.map((row) => row.lastCommit.length));
31
+ const branchWidth = Math.max("BRANCH".length, ...rows.map((row) => row.branch.length));
32
+ const statusWidth = Math.max("STATUS".length, ...rows.map((row) => row.status.length));
33
+ const upstreamWidth = Math.max("UPSTREAM".length, ...rows.map((row) => row.upstream.length));
34
+ const lastCommitWidth = Math.max("LAST".length, ...rows.map((row) => row.lastCommit.length));
35
35
  const lines = [
36
- ' '
37
- + 'BRANCH'.padEnd(branchWidth, ' ')
38
- + ' '
39
- + 'STATUS'.padEnd(statusWidth, ' ')
40
- + ' '
41
- + 'UPSTREAM'.padEnd(upstreamWidth, ' ')
42
- + ' '
43
- + 'LAST'.padEnd(lastCommitWidth, ' ')
44
- + ' PATH',
36
+ " " +
37
+ "BRANCH".padEnd(branchWidth, " ") +
38
+ " " +
39
+ "STATUS".padEnd(statusWidth, " ") +
40
+ " " +
41
+ "UPSTREAM".padEnd(upstreamWidth, " ") +
42
+ " " +
43
+ "LAST".padEnd(lastCommitWidth, " ") +
44
+ " PATH",
45
45
  ];
46
46
  for (const row of rows) {
47
- lines.push(`${row.isCurrent ? '*' : ' '} `
48
- + `${row.branch.padEnd(branchWidth, ' ')} `
49
- + `${row.status.padEnd(statusWidth, ' ')} `
50
- + `${row.upstream.padEnd(upstreamWidth, ' ')} `
51
- + `${row.lastCommit.padEnd(lastCommitWidth, ' ')} `
52
- + row.path);
47
+ lines.push(`${row.isCurrent ? "*" : " "} ` +
48
+ `${row.branch.padEnd(branchWidth, " ")} ` +
49
+ `${row.status.padEnd(statusWidth, " ")} ` +
50
+ `${row.upstream.padEnd(upstreamWidth, " ")} ` +
51
+ `${row.lastCommit.padEnd(lastCommitWidth, " ")} ` +
52
+ row.path);
53
53
  }
54
- return lines.join('\n');
54
+ return lines.join("\n");
55
55
  }
56
56
  export function formatWorktreeTable(worktrees) {
57
57
  const rows = worktrees.map((worktree) => ({
58
- branch: worktree.branch ?? '(detached)',
58
+ branch: worktree.branch ?? "(detached)",
59
59
  isCurrent: worktree.isCurrent,
60
60
  path: worktree.path,
61
61
  }));
62
- const branchWidth = Math.max('BRANCH'.length, ...rows.map((row) => row.branch.length));
63
- const lines = [' ' + 'BRANCH'.padEnd(branchWidth, ' ') + ' PATH'];
62
+ const branchWidth = Math.max("BRANCH".length, ...rows.map((row) => row.branch.length));
63
+ const lines = [" " + "BRANCH".padEnd(branchWidth, " ") + " PATH"];
64
64
  for (const row of rows) {
65
- lines.push(`${row.isCurrent ? '*' : ' '} ${row.branch.padEnd(branchWidth, ' ')} ${row.path}`);
65
+ lines.push(`${row.isCurrent ? "*" : " "} ${row.branch.padEnd(branchWidth, " ")} ${row.path}`);
66
66
  }
67
- return lines.join('\n');
67
+ return lines.join("\n");
68
68
  }
69
69
  function sortWorktrees(worktrees) {
70
70
  return [...worktrees].sort((left, right) => {
package/dist/new.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { type InstallPromptDependencies } from './install-prompt.js';
2
- import { type PathConflictChoice } from './conflict.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 NewCommandOptions {
5
5
  branch?: string;