isomorphic-jj 1.0.0 → 1.1.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 (2) hide show
  1. package/bin/isojj.js +262 -0
  2. package/package.json +5 -1
package/bin/isojj.js ADDED
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * isojj - CLI wrapper for isomorphic-jj
5
+ *
6
+ * Thin shell that translates CLI arguments to isomorphic-jj API calls.
7
+ * Similar to isogit for isomorphic-git.
8
+ */
9
+
10
+ import { createJJ } from '../src/api/repository.js';
11
+ import fs from 'fs';
12
+
13
+ // Parse command line arguments
14
+ function parseArgs(argv) {
15
+ const args = argv.slice(2);
16
+ const command = args[0];
17
+ const opts = {};
18
+
19
+ for (let i = 1; i < args.length; i++) {
20
+ const arg = args[i];
21
+
22
+ if (arg.startsWith('--')) {
23
+ const key = arg.slice(2);
24
+ const nextArg = args[i + 1];
25
+
26
+ // Handle --flag and --key=value
27
+ if (arg.includes('=')) {
28
+ const [k, v] = arg.slice(2).split('=');
29
+ opts[k] = v;
30
+ } else if (nextArg && !nextArg.startsWith('-')) {
31
+ opts[key] = nextArg;
32
+ i++;
33
+ } else {
34
+ opts[key] = true;
35
+ }
36
+ } else if (arg.startsWith('-')) {
37
+ // Handle short flags like -m "message"
38
+ const key = arg.slice(1);
39
+ const nextArg = args[i + 1];
40
+
41
+ if (nextArg && !nextArg.startsWith('-')) {
42
+ opts[key] = nextArg;
43
+ i++;
44
+ } else {
45
+ opts[key] = true;
46
+ }
47
+ } else {
48
+ // Positional argument
49
+ if (!opts._positional) opts._positional = [];
50
+ opts._positional.push(arg);
51
+ }
52
+ }
53
+
54
+ return { command, opts };
55
+ }
56
+
57
+ // Format output for different commands
58
+ function formatOutput(command, result) {
59
+ if (result === undefined || result === null) {
60
+ return;
61
+ }
62
+
63
+ // Handle special output formatting for common commands
64
+ if (command === 'log') {
65
+ if (Array.isArray(result)) {
66
+ console.log('\nChange History:\n');
67
+ result.forEach((change, idx) => {
68
+ const shortId = change.changeId.slice(0, 12);
69
+ const desc = change.description || '(no description)';
70
+ const author = change.author?.name || 'unknown';
71
+ const timestamp = change.timestamp ? new Date(change.timestamp).toLocaleString() : '';
72
+
73
+ console.log(` ${shortId} ${desc}`);
74
+ console.log(` ${author} ${timestamp}`);
75
+ if (idx < result.length - 1) console.log();
76
+ });
77
+ return;
78
+ }
79
+ }
80
+
81
+ if (command === 'status') {
82
+ if (result.workingCopy) {
83
+ console.log(`Working copy change: ${result.workingCopy.changeId.slice(0, 12)}`);
84
+ console.log(`Parent change(s): ${result.workingCopy.parents.map(p => p.slice(0, 12)).join(', ')}`);
85
+ console.log();
86
+ }
87
+
88
+ if (result.modified?.length > 0) {
89
+ console.log('Modified files:');
90
+ result.modified.forEach(file => console.log(` M ${file}`));
91
+ }
92
+
93
+ if (result.added?.length > 0) {
94
+ console.log('Added files:');
95
+ result.added.forEach(file => console.log(` A ${file}`));
96
+ }
97
+
98
+ if (result.removed?.length > 0) {
99
+ console.log('Removed files:');
100
+ result.removed.forEach(file => console.log(` D ${file}`));
101
+ }
102
+
103
+ if (result.conflicts?.length > 0) {
104
+ console.log('Conflicted files:');
105
+ result.conflicts.forEach(file => console.log(` C ${file}`));
106
+ }
107
+
108
+ return;
109
+ }
110
+
111
+ if (command === 'diff') {
112
+ if (typeof result === 'string') {
113
+ console.log(result);
114
+ return;
115
+ }
116
+ }
117
+
118
+ // Default: pretty-print JSON
119
+ console.log(JSON.stringify(result, null, 2));
120
+ }
121
+
122
+ // Main CLI handler
123
+ async function main() {
124
+ try {
125
+ const { command, opts } = parseArgs(process.argv);
126
+
127
+ if (!command || command === 'help' || command === '--help' || command === '-h') {
128
+ console.log(`
129
+ isojj - CLI for isomorphic-jj
130
+
131
+ Usage: isojj <command> [options]
132
+
133
+ Common commands:
134
+ init Initialize a new repository
135
+ log [--revset <expr>] Show change history
136
+ status Show working copy status
137
+ describe -m <message> Set change description
138
+ new -m <message> Create new change
139
+ diff [--changeId <id>] Show changes
140
+
141
+ bookmark.set --name <name> --changeId <id>
142
+ bookmark.list
143
+
144
+ absorb [--dryRun] Absorb working copy changes
145
+
146
+ bisect.start --good <id> --bad <id>
147
+ bisect.good
148
+ bisect.bad
149
+ bisect.status
150
+ bisect.reset
151
+
152
+ Options:
153
+ --dir <path> Repository directory (default: current directory)
154
+ --json Output raw JSON (skip formatting)
155
+
156
+ Examples:
157
+ isojj init
158
+ isojj log
159
+ isojj describe -m "Fix bug"
160
+ isojj new -m "Start new feature"
161
+ isojj bookmark.set --name main --changeId abc123
162
+ isojj bisect.start --good abc123 --bad def456
163
+ `);
164
+ process.exit(0);
165
+ }
166
+
167
+ // Get repository directory
168
+ const dir = opts.dir || process.cwd();
169
+
170
+ // Initialize repository instance
171
+ const jj = await createJJ({ fs, dir });
172
+
173
+ // If not initializing, load existing repository state
174
+ if (command !== 'init') {
175
+ try {
176
+ // Check if repository exists by checking for .jj directory
177
+ await fs.promises.stat(`${dir}/.jj`);
178
+
179
+ // Load repository state
180
+ await jj.graph.load();
181
+ await jj.workingCopy.load();
182
+ await jj.bookmarks.load();
183
+ } catch (error) {
184
+ if (error.code === 'ENOENT') {
185
+ throw new Error(`Not a jj repository (or any parent up to mount point /)\nStopped at: ${dir}\nRun 'isojj init' to initialize a repository`);
186
+ }
187
+ throw error;
188
+ }
189
+ }
190
+
191
+ // Handle nested commands (e.g., bisect.start, bookmark.set)
192
+ const parts = command.split('.');
193
+ let method = jj;
194
+
195
+ for (const part of parts) {
196
+ method = method[part];
197
+ if (!method) {
198
+ throw new Error(`Unknown command: ${command}`);
199
+ }
200
+ }
201
+
202
+ if (typeof method !== 'function') {
203
+ throw new Error(`${command} is not a valid command`);
204
+ }
205
+
206
+ // Map common CLI flags to API options
207
+ const apiOpts = {};
208
+
209
+ // Common mappings
210
+ if (opts.m || opts.message) apiOpts.message = opts.m || opts.message;
211
+ if (opts.changeId) apiOpts.changeId = opts.changeId;
212
+ if (opts.revset) apiOpts.revset = opts.revset;
213
+ if (opts.name) apiOpts.name = opts.name;
214
+ if (opts.path) apiOpts.path = opts.path;
215
+ if (opts.good) apiOpts.good = opts.good;
216
+ if (opts.bad) apiOpts.bad = opts.bad;
217
+ if (opts.dryRun) apiOpts.dryRun = true;
218
+ if (opts.parents) {
219
+ apiOpts.parents = opts.parents.includes(',')
220
+ ? opts.parents.split(',')
221
+ : [opts.parents];
222
+ }
223
+
224
+ // Add positional arguments if any
225
+ if (opts._positional?.length > 0) {
226
+ // For commands like 'read <path>', use first positional as path
227
+ if (command === 'read' || command === 'remove') {
228
+ apiOpts.path = opts._positional[0];
229
+ }
230
+ }
231
+
232
+ // Execute the command
233
+ const result = await method.call(jj, apiOpts);
234
+
235
+ // Format and output result
236
+ if (opts.json) {
237
+ console.log(JSON.stringify(result, null, 2));
238
+ } else {
239
+ formatOutput(command, result);
240
+ }
241
+
242
+ process.exit(0);
243
+ } catch (error) {
244
+ console.error(`Error: ${error.message}`);
245
+
246
+ if (error.code) {
247
+ console.error(`Code: ${error.code}`);
248
+ }
249
+
250
+ if (error.context?.suggestion) {
251
+ console.error(`Suggestion: ${error.context.suggestion}`);
252
+ }
253
+
254
+ if (process.env.DEBUG) {
255
+ console.error(error.stack);
256
+ }
257
+
258
+ process.exit(1);
259
+ }
260
+ }
261
+
262
+ main();
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "isomorphic-jj",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A pure-JavaScript library that brings Jujutsu (jj) version control semantics to Node.js and browsers",
5
5
  "type": "module",
6
+ "bin": {
7
+ "isojj": "./bin/isojj.js"
8
+ },
6
9
  "main": "./dist/index.cjs",
7
10
  "module": "./dist/index.mjs",
8
11
  "types": "./src/types.d.ts",
@@ -18,6 +21,7 @@
18
21
  }
19
22
  },
20
23
  "files": [
24
+ "bin",
21
25
  "dist",
22
26
  "src",
23
27
  "README.md",