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.
- package/bin/isojj.js +262 -0
- 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.
|
|
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",
|