@solaqua/gji 0.4.1 → 0.5.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/dist/back.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { type HistoryEntry } from './history.js';
2
+ export declare const BACK_OUTPUT_FILE_ENV = "GJI_BACK_OUTPUT_FILE";
3
+ export interface BackCommandOptions {
4
+ cwd: string;
5
+ home?: string;
6
+ n?: number;
7
+ print?: boolean;
8
+ stderr: (chunk: string) => void;
9
+ stdout: (chunk: string) => void;
10
+ }
11
+ export declare function runBackCommand(options: BackCommandOptions): Promise<number>;
12
+ export declare function formatHistoryList(history: HistoryEntry[], cwd: string): string;
13
+ export declare function formatAge(timestamp: number): string;
package/dist/back.js ADDED
@@ -0,0 +1,74 @@
1
+ import { access } from 'node:fs/promises';
2
+ import { basename } from 'node:path';
3
+ import { loadEffectiveConfig } from './config.js';
4
+ import { extractHooks, runHook } from './hooks.js';
5
+ import { appendHistory, loadHistory } from './history.js';
6
+ import { detectRepository } from './repo.js';
7
+ import { writeShellOutput } from './shell-handoff.js';
8
+ export const BACK_OUTPUT_FILE_ENV = 'GJI_BACK_OUTPUT_FILE';
9
+ export async function runBackCommand(options) {
10
+ const history = await loadHistory(options.home);
11
+ const steps = options.n ?? 1;
12
+ if (steps < 1) {
13
+ options.stderr('gji back: step count must be at least 1\n');
14
+ return 1;
15
+ }
16
+ let found = 0;
17
+ let target;
18
+ for (const entry of history) {
19
+ if (entry.path === options.cwd)
20
+ continue;
21
+ try {
22
+ await access(entry.path);
23
+ found++;
24
+ if (found === steps) {
25
+ target = entry;
26
+ break;
27
+ }
28
+ }
29
+ catch {
30
+ // Path no longer exists — skip to the next entry
31
+ }
32
+ }
33
+ if (!target) {
34
+ options.stderr('gji back: no previous worktree in history\n');
35
+ options.stderr("Hint: Use 'gji go', 'gji new', or 'gji pr' to navigate between worktrees\n");
36
+ return 1;
37
+ }
38
+ try {
39
+ const repository = await detectRepository(target.path);
40
+ const config = await loadEffectiveConfig(repository.repoRoot, options.home, options.stderr);
41
+ const hooks = extractHooks(config);
42
+ await runHook(hooks.afterEnter, target.path, { branch: target.branch ?? undefined, path: target.path, repo: basename(repository.repoRoot) }, options.stderr);
43
+ }
44
+ catch {
45
+ // Not in a git repo or hooks unavailable — proceed without hook
46
+ }
47
+ await appendHistory(target.path, target.branch, options.home);
48
+ await writeShellOutput(BACK_OUTPUT_FILE_ENV, target.path, options.stdout);
49
+ return 0;
50
+ }
51
+ export function formatHistoryList(history, cwd) {
52
+ const branchWidth = Math.max('BRANCH'.length, ...history.map((e) => (e.branch ?? '(detached)').length));
53
+ const lines = [' ' + 'BRANCH'.padEnd(branchWidth) + ' WHEN PATH'];
54
+ for (const entry of history) {
55
+ const isCurrent = entry.path === cwd;
56
+ const branch = (entry.branch ?? '(detached)').padEnd(branchWidth);
57
+ const when = formatAge(entry.timestamp).padEnd(10);
58
+ lines.push(`${isCurrent ? '*' : ' '} ${branch} ${when} ${entry.path}`);
59
+ }
60
+ return lines.join('\n') + '\n';
61
+ }
62
+ export function formatAge(timestamp) {
63
+ const seconds = Math.floor((Date.now() - timestamp) / 1000);
64
+ if (seconds < 60)
65
+ return 'just now';
66
+ const minutes = Math.floor(seconds / 60);
67
+ if (minutes < 60)
68
+ return `${minutes}m ago`;
69
+ const hours = Math.floor(minutes / 60);
70
+ if (hours < 24)
71
+ return `${hours}h ago`;
72
+ const days = Math.floor(hours / 24);
73
+ return `${days}d ago`;
74
+ }
package/dist/cli.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { Command } from 'commander';
3
3
  import updateNotifier from 'update-notifier';
4
+ import { runBackCommand } from './back.js';
4
5
  import { runCleanCommand } from './clean.js';
6
+ import { runHistoryCommand } from './history-command.js';
5
7
  import { runCompletionCommand } from './completion.js';
6
8
  import { runConfigCommand } from './config-command.js';
7
9
  import { runGoCommand } from './go.js';
@@ -116,6 +118,16 @@ function registerCommands(program) {
116
118
  .option('--dry-run', 'show what would be created without executing any git commands or writing files')
117
119
  .option('--json', 'emit JSON on success or error instead of human-readable output')
118
120
  .action(notImplemented('pr'));
121
+ program
122
+ .command('back [n]')
123
+ .description('navigate to the previously visited worktree, optionally N steps back')
124
+ .option('--print', 'print the resolved worktree path explicitly')
125
+ .action(notImplemented('back'));
126
+ program
127
+ .command('history')
128
+ .description('show navigation history')
129
+ .option('--json', 'print history as JSON')
130
+ .action(notImplemented('history'));
119
131
  program
120
132
  .command('go [branch]')
121
133
  .description('print or select a worktree path')
@@ -223,6 +235,37 @@ function attachCommandActions(program, options) {
223
235
  throw commanderExit(exitCode);
224
236
  }
225
237
  });
238
+ program.commands
239
+ .find((command) => command.name() === 'back')
240
+ ?.action(async (n, commandOptions) => {
241
+ if (n !== undefined && !/^\d+$/.test(n)) {
242
+ options.stderr(`gji back: invalid step count: ${n}\n`);
243
+ throw commanderExit(1);
244
+ }
245
+ const steps = n !== undefined ? parseInt(n, 10) : undefined;
246
+ const exitCode = await runBackCommand({
247
+ cwd: options.cwd,
248
+ n: steps,
249
+ print: commandOptions.print,
250
+ stderr: options.stderr,
251
+ stdout: options.stdout,
252
+ });
253
+ if (exitCode !== 0) {
254
+ throw commanderExit(exitCode);
255
+ }
256
+ });
257
+ program.commands
258
+ .find((command) => command.name() === 'history')
259
+ ?.action(async (commandOptions) => {
260
+ const exitCode = await runHistoryCommand({
261
+ cwd: options.cwd,
262
+ json: commandOptions.json,
263
+ stdout: options.stdout,
264
+ });
265
+ if (exitCode !== 0) {
266
+ throw commanderExit(exitCode);
267
+ }
268
+ });
226
269
  program.commands
227
270
  .find((command) => command.name() === 'go')
228
271
  ?.action(async (branch, commandOptions) => {