isomorphic-jj 1.0.0 → 1.1.2
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/dist/index.cjs +93 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +93 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
- package/src/api/repository.js +55 -6
- package/src/core/tag-store.js +9 -2
- package/src/core/working-copy.js +3 -3
- package/src/utils/validation.js +26 -12
package/dist/index.mjs
CHANGED
|
@@ -266,10 +266,26 @@ function validateChangeId(changeId) {
|
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Normalize a file path by stripping leading slashes
|
|
271
|
+
*
|
|
272
|
+
* @param {string} path - File path to normalize
|
|
273
|
+
* @returns {string} Normalized path (without leading slash)
|
|
274
|
+
*/
|
|
275
|
+
function normalizePath(path) {
|
|
276
|
+
if (typeof path !== 'string') {
|
|
277
|
+
return path;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Strip leading slashes for compatibility with REST APIs and absolute paths
|
|
281
|
+
return path.replace(/^\/+/, '');
|
|
282
|
+
}
|
|
283
|
+
|
|
269
284
|
/**
|
|
270
285
|
* Validate a file path
|
|
271
|
-
*
|
|
286
|
+
*
|
|
272
287
|
* @param {string} path - File path to validate
|
|
288
|
+
* @returns {string} Normalized path
|
|
273
289
|
* @throws {JJError} If path is invalid or contains path traversal
|
|
274
290
|
*/
|
|
275
291
|
function validatePath(path) {
|
|
@@ -279,27 +295,25 @@ function validatePath(path) {
|
|
|
279
295
|
});
|
|
280
296
|
}
|
|
281
297
|
|
|
282
|
-
|
|
298
|
+
// Auto-normalize: strip leading slashes (common with REST APIs)
|
|
299
|
+
const normalizedPath = normalizePath(path);
|
|
300
|
+
|
|
301
|
+
if (normalizedPath.includes('..')) {
|
|
283
302
|
throw new JJError('INVALID_PATH', 'Path traversal (..) not allowed', {
|
|
284
|
-
path,
|
|
303
|
+
path: normalizedPath,
|
|
285
304
|
suggestion: 'Use relative paths without .. components',
|
|
286
305
|
});
|
|
287
306
|
}
|
|
288
307
|
|
|
289
|
-
if (
|
|
290
|
-
throw new JJError('INVALID_PATH', 'Absolute paths not allowed', {
|
|
291
|
-
path,
|
|
292
|
-
suggestion: 'Use relative paths from repository root',
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (path.length > 4096) {
|
|
308
|
+
if (normalizedPath.length > 4096) {
|
|
297
309
|
throw new JJError('INVALID_PATH', 'Path exceeds maximum length', {
|
|
298
|
-
path,
|
|
310
|
+
path: normalizedPath,
|
|
299
311
|
maxLength: 4096,
|
|
300
312
|
suggestion: 'Use shorter file paths',
|
|
301
313
|
});
|
|
302
314
|
}
|
|
315
|
+
|
|
316
|
+
return normalizedPath;
|
|
303
317
|
}
|
|
304
318
|
|
|
305
319
|
/**
|
|
@@ -773,7 +787,7 @@ class WorkingCopy {
|
|
|
773
787
|
* @param {Object} fileState - File state (mtime, size, mode, hash)
|
|
774
788
|
*/
|
|
775
789
|
async trackFile(path, fileState) {
|
|
776
|
-
validatePath(path);
|
|
790
|
+
path = validatePath(path); // Normalize path
|
|
777
791
|
|
|
778
792
|
if (!this.state) {
|
|
779
793
|
await this.load();
|
|
@@ -785,11 +799,11 @@ class WorkingCopy {
|
|
|
785
799
|
|
|
786
800
|
/**
|
|
787
801
|
* Untrack a file from the working copy
|
|
788
|
-
*
|
|
802
|
+
*
|
|
789
803
|
* @param {string} path - File path to untrack
|
|
790
804
|
*/
|
|
791
805
|
async untrackFile(path) {
|
|
792
|
-
validatePath(path);
|
|
806
|
+
path = validatePath(path); // Normalize path
|
|
793
807
|
|
|
794
808
|
if (!this.state) {
|
|
795
809
|
await this.load();
|
|
@@ -1576,7 +1590,7 @@ class TagStore {
|
|
|
1576
1590
|
*/
|
|
1577
1591
|
async load() {
|
|
1578
1592
|
try {
|
|
1579
|
-
const data = await this.fs.readFile(this.tagsFile, 'utf8');
|
|
1593
|
+
const data = await this.fs.promises.readFile(this.tagsFile, 'utf8');
|
|
1580
1594
|
return JSON.parse(data);
|
|
1581
1595
|
} catch (error) {
|
|
1582
1596
|
if (error.code === 'ENOENT') {
|
|
@@ -1592,7 +1606,14 @@ class TagStore {
|
|
|
1592
1606
|
* @returns {Promise<void>}
|
|
1593
1607
|
*/
|
|
1594
1608
|
async save(tags) {
|
|
1595
|
-
|
|
1609
|
+
// Ensure the store directory exists
|
|
1610
|
+
const storeDir = `${this.jjDir}/store`;
|
|
1611
|
+
try {
|
|
1612
|
+
await this.fs.promises.mkdir(storeDir, { recursive: true });
|
|
1613
|
+
} catch (err) {
|
|
1614
|
+
// Directory might already exist, ignore
|
|
1615
|
+
}
|
|
1616
|
+
await this.fs.promises.writeFile(this.tagsFile, JSON.stringify(tags, null, 2));
|
|
1596
1617
|
}
|
|
1597
1618
|
|
|
1598
1619
|
/**
|
|
@@ -6236,8 +6257,9 @@ async function createJJ(options) {
|
|
|
6236
6257
|
async _moveChange(args) {
|
|
6237
6258
|
await graph.load();
|
|
6238
6259
|
|
|
6239
|
-
|
|
6240
|
-
const
|
|
6260
|
+
// Support multiple parameter names for flexibility
|
|
6261
|
+
const changeId = args.changeId || args.from || args.source;
|
|
6262
|
+
const newParent = args.newParent || args.to || args.destination;
|
|
6241
6263
|
|
|
6242
6264
|
if (!changeId || typeof changeId !== 'string') {
|
|
6243
6265
|
throw new JJError('INVALID_ARGUMENT', 'Missing or invalid changeId', {
|
|
@@ -6577,11 +6599,14 @@ async function createJJ(options) {
|
|
|
6577
6599
|
/**
|
|
6578
6600
|
* Edit change metadata without modifying content
|
|
6579
6601
|
*
|
|
6580
|
-
* Allows updating author, committer
|
|
6602
|
+
* Allows updating author, committer, description, and optionally regenerating the change ID.
|
|
6581
6603
|
* The file content and snapshot remain unchanged.
|
|
6582
6604
|
*
|
|
6583
6605
|
* @param {Object} [args={}] - Arguments
|
|
6584
6606
|
* @param {string} [args.revision] - Change ID to edit (defaults to working copy @)
|
|
6607
|
+
* @param {string} [args.change] - Alias for revision
|
|
6608
|
+
* @param {string} [args.description] - Update the change description
|
|
6609
|
+
* @param {string} [args.message] - Alias for description
|
|
6585
6610
|
* @param {Object} [args.author] - Author information to update
|
|
6586
6611
|
* @param {string} [args.author.name] - Author name
|
|
6587
6612
|
* @param {string} [args.author.email] - Author email
|
|
@@ -6592,6 +6617,16 @@ async function createJJ(options) {
|
|
|
6592
6617
|
* @returns {Promise<Object>} Updated change object
|
|
6593
6618
|
*/
|
|
6594
6619
|
async metaedit(args = {}) {
|
|
6620
|
+
// Normalize: accept 'change' as alias for 'revision'
|
|
6621
|
+
if (args.change && !args.revision) {
|
|
6622
|
+
args = { ...args, revision: args.change };
|
|
6623
|
+
}
|
|
6624
|
+
|
|
6625
|
+
// Normalize: accept 'message' as alias for 'description'
|
|
6626
|
+
if (args.message && !args.description) {
|
|
6627
|
+
args = { ...args, description: args.message };
|
|
6628
|
+
}
|
|
6629
|
+
|
|
6595
6630
|
await graph.load();
|
|
6596
6631
|
await workingCopy.load();
|
|
6597
6632
|
await userConfig.load();
|
|
@@ -6609,13 +6644,14 @@ async function createJJ(options) {
|
|
|
6609
6644
|
const hasAuthor = args.author && (args.author.name || args.author.email);
|
|
6610
6645
|
const hasCommitter = args.committer && (args.committer.name || args.committer.email);
|
|
6611
6646
|
const hasResetChangeId = args.resetChangeId === true;
|
|
6647
|
+
const hasDescription = args.description !== undefined;
|
|
6612
6648
|
|
|
6613
|
-
if (!hasAuthor && !hasCommitter && !hasResetChangeId) {
|
|
6649
|
+
if (!hasAuthor && !hasCommitter && !hasResetChangeId && !hasDescription) {
|
|
6614
6650
|
throw new JJError(
|
|
6615
6651
|
'INVALID_ARGUMENT',
|
|
6616
6652
|
'No metadata provided to update',
|
|
6617
6653
|
{
|
|
6618
|
-
suggestion: 'Provide author, committer, or resetChangeId: true',
|
|
6654
|
+
suggestion: 'Provide author, committer, description, or resetChangeId: true',
|
|
6619
6655
|
}
|
|
6620
6656
|
);
|
|
6621
6657
|
}
|
|
@@ -6660,6 +6696,11 @@ async function createJJ(options) {
|
|
|
6660
6696
|
change.committer.timestamp = new Date().toISOString();
|
|
6661
6697
|
}
|
|
6662
6698
|
|
|
6699
|
+
// Update description if provided
|
|
6700
|
+
if (args.description !== undefined) {
|
|
6701
|
+
change.description = args.description;
|
|
6702
|
+
}
|
|
6703
|
+
|
|
6663
6704
|
// Regenerate change ID if requested
|
|
6664
6705
|
if (args.resetChangeId) {
|
|
6665
6706
|
// For now, resetChangeId is not fully implemented as it requires complex graph operations
|
|
@@ -7093,9 +7134,14 @@ async function createJJ(options) {
|
|
|
7093
7134
|
* @returns {Promise<Object>} Information about the edited change including changeId, description, and file count
|
|
7094
7135
|
*/
|
|
7095
7136
|
async edit(args) {
|
|
7137
|
+
// Normalize: accept both 'change' and 'changeId' parameters
|
|
7138
|
+
if (args && args.change && !args.changeId) {
|
|
7139
|
+
args = { ...args, changeId: args.change };
|
|
7140
|
+
}
|
|
7141
|
+
|
|
7096
7142
|
if (!args || !args.changeId) {
|
|
7097
7143
|
throw new JJError('INVALID_ARGUMENT', 'Missing changeId argument', {
|
|
7098
|
-
suggestion: 'Provide a changeId to edit',
|
|
7144
|
+
suggestion: 'Provide a changeId (or change) to edit',
|
|
7099
7145
|
});
|
|
7100
7146
|
}
|
|
7101
7147
|
|
|
@@ -8937,6 +8983,23 @@ async function createJJ(options) {
|
|
|
8937
8983
|
* @returns {Promise<Object>} The un-abandoned change object including changeId, description, and abandoned flag (now false)
|
|
8938
8984
|
*/
|
|
8939
8985
|
async unabandon(args) {
|
|
8986
|
+
// Normalize: accept both 'change' and 'changeId' parameters
|
|
8987
|
+
if (args && args.change && !args.changeId) {
|
|
8988
|
+
args = { ...args, changeId: args.change };
|
|
8989
|
+
}
|
|
8990
|
+
|
|
8991
|
+
if (!args || !args.changeId) {
|
|
8992
|
+
throw new JJError('INVALID_ARGUMENT', 'Missing changeId argument', {
|
|
8993
|
+
suggestion: 'Provide a changeId (or change) to unabandon',
|
|
8994
|
+
});
|
|
8995
|
+
}
|
|
8996
|
+
|
|
8997
|
+
if (typeof args.changeId !== 'string') {
|
|
8998
|
+
throw new JJError('INVALID_ARGUMENT', 'Change ID must be a string', {
|
|
8999
|
+
changeId: args.changeId,
|
|
9000
|
+
});
|
|
9001
|
+
}
|
|
9002
|
+
|
|
8940
9003
|
await graph.load();
|
|
8941
9004
|
await userConfig.load();
|
|
8942
9005
|
|
|
@@ -10301,6 +10364,13 @@ async function createJJ(options) {
|
|
|
10301
10364
|
*/
|
|
10302
10365
|
async add(args) {
|
|
10303
10366
|
await workspaces.load();
|
|
10367
|
+
|
|
10368
|
+
// If no changeId specified, default to current working copy change
|
|
10369
|
+
if (!args.changeId) {
|
|
10370
|
+
const currentChangeId = workingCopy.getCurrentChangeId();
|
|
10371
|
+
args = { ...args, changeId: currentChangeId };
|
|
10372
|
+
}
|
|
10373
|
+
|
|
10304
10374
|
const workspace = await workspaces.add(args);
|
|
10305
10375
|
|
|
10306
10376
|
// If a change was specified, check it out in the new workspace
|