isomorphic-jj 1.1.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/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 +1 -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.cjs
CHANGED
|
@@ -269,10 +269,26 @@ function validateChangeId(changeId) {
|
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Normalize a file path by stripping leading slashes
|
|
274
|
+
*
|
|
275
|
+
* @param {string} path - File path to normalize
|
|
276
|
+
* @returns {string} Normalized path (without leading slash)
|
|
277
|
+
*/
|
|
278
|
+
function normalizePath(path) {
|
|
279
|
+
if (typeof path !== 'string') {
|
|
280
|
+
return path;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Strip leading slashes for compatibility with REST APIs and absolute paths
|
|
284
|
+
return path.replace(/^\/+/, '');
|
|
285
|
+
}
|
|
286
|
+
|
|
272
287
|
/**
|
|
273
288
|
* Validate a file path
|
|
274
|
-
*
|
|
289
|
+
*
|
|
275
290
|
* @param {string} path - File path to validate
|
|
291
|
+
* @returns {string} Normalized path
|
|
276
292
|
* @throws {JJError} If path is invalid or contains path traversal
|
|
277
293
|
*/
|
|
278
294
|
function validatePath(path) {
|
|
@@ -282,27 +298,25 @@ function validatePath(path) {
|
|
|
282
298
|
});
|
|
283
299
|
}
|
|
284
300
|
|
|
285
|
-
|
|
301
|
+
// Auto-normalize: strip leading slashes (common with REST APIs)
|
|
302
|
+
const normalizedPath = normalizePath(path);
|
|
303
|
+
|
|
304
|
+
if (normalizedPath.includes('..')) {
|
|
286
305
|
throw new JJError('INVALID_PATH', 'Path traversal (..) not allowed', {
|
|
287
|
-
path,
|
|
306
|
+
path: normalizedPath,
|
|
288
307
|
suggestion: 'Use relative paths without .. components',
|
|
289
308
|
});
|
|
290
309
|
}
|
|
291
310
|
|
|
292
|
-
if (
|
|
293
|
-
throw new JJError('INVALID_PATH', 'Absolute paths not allowed', {
|
|
294
|
-
path,
|
|
295
|
-
suggestion: 'Use relative paths from repository root',
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (path.length > 4096) {
|
|
311
|
+
if (normalizedPath.length > 4096) {
|
|
300
312
|
throw new JJError('INVALID_PATH', 'Path exceeds maximum length', {
|
|
301
|
-
path,
|
|
313
|
+
path: normalizedPath,
|
|
302
314
|
maxLength: 4096,
|
|
303
315
|
suggestion: 'Use shorter file paths',
|
|
304
316
|
});
|
|
305
317
|
}
|
|
318
|
+
|
|
319
|
+
return normalizedPath;
|
|
306
320
|
}
|
|
307
321
|
|
|
308
322
|
/**
|
|
@@ -776,7 +790,7 @@ class WorkingCopy {
|
|
|
776
790
|
* @param {Object} fileState - File state (mtime, size, mode, hash)
|
|
777
791
|
*/
|
|
778
792
|
async trackFile(path, fileState) {
|
|
779
|
-
validatePath(path);
|
|
793
|
+
path = validatePath(path); // Normalize path
|
|
780
794
|
|
|
781
795
|
if (!this.state) {
|
|
782
796
|
await this.load();
|
|
@@ -788,11 +802,11 @@ class WorkingCopy {
|
|
|
788
802
|
|
|
789
803
|
/**
|
|
790
804
|
* Untrack a file from the working copy
|
|
791
|
-
*
|
|
805
|
+
*
|
|
792
806
|
* @param {string} path - File path to untrack
|
|
793
807
|
*/
|
|
794
808
|
async untrackFile(path) {
|
|
795
|
-
validatePath(path);
|
|
809
|
+
path = validatePath(path); // Normalize path
|
|
796
810
|
|
|
797
811
|
if (!this.state) {
|
|
798
812
|
await this.load();
|
|
@@ -1579,7 +1593,7 @@ class TagStore {
|
|
|
1579
1593
|
*/
|
|
1580
1594
|
async load() {
|
|
1581
1595
|
try {
|
|
1582
|
-
const data = await this.fs.readFile(this.tagsFile, 'utf8');
|
|
1596
|
+
const data = await this.fs.promises.readFile(this.tagsFile, 'utf8');
|
|
1583
1597
|
return JSON.parse(data);
|
|
1584
1598
|
} catch (error) {
|
|
1585
1599
|
if (error.code === 'ENOENT') {
|
|
@@ -1595,7 +1609,14 @@ class TagStore {
|
|
|
1595
1609
|
* @returns {Promise<void>}
|
|
1596
1610
|
*/
|
|
1597
1611
|
async save(tags) {
|
|
1598
|
-
|
|
1612
|
+
// Ensure the store directory exists
|
|
1613
|
+
const storeDir = `${this.jjDir}/store`;
|
|
1614
|
+
try {
|
|
1615
|
+
await this.fs.promises.mkdir(storeDir, { recursive: true });
|
|
1616
|
+
} catch (err) {
|
|
1617
|
+
// Directory might already exist, ignore
|
|
1618
|
+
}
|
|
1619
|
+
await this.fs.promises.writeFile(this.tagsFile, JSON.stringify(tags, null, 2));
|
|
1599
1620
|
}
|
|
1600
1621
|
|
|
1601
1622
|
/**
|
|
@@ -6239,8 +6260,9 @@ async function createJJ(options) {
|
|
|
6239
6260
|
async _moveChange(args) {
|
|
6240
6261
|
await graph.load();
|
|
6241
6262
|
|
|
6242
|
-
|
|
6243
|
-
const
|
|
6263
|
+
// Support multiple parameter names for flexibility
|
|
6264
|
+
const changeId = args.changeId || args.from || args.source;
|
|
6265
|
+
const newParent = args.newParent || args.to || args.destination;
|
|
6244
6266
|
|
|
6245
6267
|
if (!changeId || typeof changeId !== 'string') {
|
|
6246
6268
|
throw new JJError('INVALID_ARGUMENT', 'Missing or invalid changeId', {
|
|
@@ -6580,11 +6602,14 @@ async function createJJ(options) {
|
|
|
6580
6602
|
/**
|
|
6581
6603
|
* Edit change metadata without modifying content
|
|
6582
6604
|
*
|
|
6583
|
-
* Allows updating author, committer
|
|
6605
|
+
* Allows updating author, committer, description, and optionally regenerating the change ID.
|
|
6584
6606
|
* The file content and snapshot remain unchanged.
|
|
6585
6607
|
*
|
|
6586
6608
|
* @param {Object} [args={}] - Arguments
|
|
6587
6609
|
* @param {string} [args.revision] - Change ID to edit (defaults to working copy @)
|
|
6610
|
+
* @param {string} [args.change] - Alias for revision
|
|
6611
|
+
* @param {string} [args.description] - Update the change description
|
|
6612
|
+
* @param {string} [args.message] - Alias for description
|
|
6588
6613
|
* @param {Object} [args.author] - Author information to update
|
|
6589
6614
|
* @param {string} [args.author.name] - Author name
|
|
6590
6615
|
* @param {string} [args.author.email] - Author email
|
|
@@ -6595,6 +6620,16 @@ async function createJJ(options) {
|
|
|
6595
6620
|
* @returns {Promise<Object>} Updated change object
|
|
6596
6621
|
*/
|
|
6597
6622
|
async metaedit(args = {}) {
|
|
6623
|
+
// Normalize: accept 'change' as alias for 'revision'
|
|
6624
|
+
if (args.change && !args.revision) {
|
|
6625
|
+
args = { ...args, revision: args.change };
|
|
6626
|
+
}
|
|
6627
|
+
|
|
6628
|
+
// Normalize: accept 'message' as alias for 'description'
|
|
6629
|
+
if (args.message && !args.description) {
|
|
6630
|
+
args = { ...args, description: args.message };
|
|
6631
|
+
}
|
|
6632
|
+
|
|
6598
6633
|
await graph.load();
|
|
6599
6634
|
await workingCopy.load();
|
|
6600
6635
|
await userConfig.load();
|
|
@@ -6612,13 +6647,14 @@ async function createJJ(options) {
|
|
|
6612
6647
|
const hasAuthor = args.author && (args.author.name || args.author.email);
|
|
6613
6648
|
const hasCommitter = args.committer && (args.committer.name || args.committer.email);
|
|
6614
6649
|
const hasResetChangeId = args.resetChangeId === true;
|
|
6650
|
+
const hasDescription = args.description !== undefined;
|
|
6615
6651
|
|
|
6616
|
-
if (!hasAuthor && !hasCommitter && !hasResetChangeId) {
|
|
6652
|
+
if (!hasAuthor && !hasCommitter && !hasResetChangeId && !hasDescription) {
|
|
6617
6653
|
throw new JJError(
|
|
6618
6654
|
'INVALID_ARGUMENT',
|
|
6619
6655
|
'No metadata provided to update',
|
|
6620
6656
|
{
|
|
6621
|
-
suggestion: 'Provide author, committer, or resetChangeId: true',
|
|
6657
|
+
suggestion: 'Provide author, committer, description, or resetChangeId: true',
|
|
6622
6658
|
}
|
|
6623
6659
|
);
|
|
6624
6660
|
}
|
|
@@ -6663,6 +6699,11 @@ async function createJJ(options) {
|
|
|
6663
6699
|
change.committer.timestamp = new Date().toISOString();
|
|
6664
6700
|
}
|
|
6665
6701
|
|
|
6702
|
+
// Update description if provided
|
|
6703
|
+
if (args.description !== undefined) {
|
|
6704
|
+
change.description = args.description;
|
|
6705
|
+
}
|
|
6706
|
+
|
|
6666
6707
|
// Regenerate change ID if requested
|
|
6667
6708
|
if (args.resetChangeId) {
|
|
6668
6709
|
// For now, resetChangeId is not fully implemented as it requires complex graph operations
|
|
@@ -7096,9 +7137,14 @@ async function createJJ(options) {
|
|
|
7096
7137
|
* @returns {Promise<Object>} Information about the edited change including changeId, description, and file count
|
|
7097
7138
|
*/
|
|
7098
7139
|
async edit(args) {
|
|
7140
|
+
// Normalize: accept both 'change' and 'changeId' parameters
|
|
7141
|
+
if (args && args.change && !args.changeId) {
|
|
7142
|
+
args = { ...args, changeId: args.change };
|
|
7143
|
+
}
|
|
7144
|
+
|
|
7099
7145
|
if (!args || !args.changeId) {
|
|
7100
7146
|
throw new JJError('INVALID_ARGUMENT', 'Missing changeId argument', {
|
|
7101
|
-
suggestion: 'Provide a changeId to edit',
|
|
7147
|
+
suggestion: 'Provide a changeId (or change) to edit',
|
|
7102
7148
|
});
|
|
7103
7149
|
}
|
|
7104
7150
|
|
|
@@ -8940,6 +8986,23 @@ async function createJJ(options) {
|
|
|
8940
8986
|
* @returns {Promise<Object>} The un-abandoned change object including changeId, description, and abandoned flag (now false)
|
|
8941
8987
|
*/
|
|
8942
8988
|
async unabandon(args) {
|
|
8989
|
+
// Normalize: accept both 'change' and 'changeId' parameters
|
|
8990
|
+
if (args && args.change && !args.changeId) {
|
|
8991
|
+
args = { ...args, changeId: args.change };
|
|
8992
|
+
}
|
|
8993
|
+
|
|
8994
|
+
if (!args || !args.changeId) {
|
|
8995
|
+
throw new JJError('INVALID_ARGUMENT', 'Missing changeId argument', {
|
|
8996
|
+
suggestion: 'Provide a changeId (or change) to unabandon',
|
|
8997
|
+
});
|
|
8998
|
+
}
|
|
8999
|
+
|
|
9000
|
+
if (typeof args.changeId !== 'string') {
|
|
9001
|
+
throw new JJError('INVALID_ARGUMENT', 'Change ID must be a string', {
|
|
9002
|
+
changeId: args.changeId,
|
|
9003
|
+
});
|
|
9004
|
+
}
|
|
9005
|
+
|
|
8943
9006
|
await graph.load();
|
|
8944
9007
|
await userConfig.load();
|
|
8945
9008
|
|
|
@@ -10304,6 +10367,13 @@ async function createJJ(options) {
|
|
|
10304
10367
|
*/
|
|
10305
10368
|
async add(args) {
|
|
10306
10369
|
await workspaces.load();
|
|
10370
|
+
|
|
10371
|
+
// If no changeId specified, default to current working copy change
|
|
10372
|
+
if (!args.changeId) {
|
|
10373
|
+
const currentChangeId = workingCopy.getCurrentChangeId();
|
|
10374
|
+
args = { ...args, changeId: currentChangeId };
|
|
10375
|
+
}
|
|
10376
|
+
|
|
10307
10377
|
const workspace = await workspaces.add(args);
|
|
10308
10378
|
|
|
10309
10379
|
// If a change was specified, check it out in the new workspace
|