isomorphic-jj 1.2.0 → 1.4.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/index.cjs +141 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +161 -156
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/api/repository.js +141 -136
- package/src/types.d.ts +166 -2
package/dist/index.cjs
CHANGED
|
@@ -6294,69 +6294,116 @@ async function createJJ(options) {
|
|
|
6294
6294
|
* @param {string[]} [args.paths] - Paths to move between changes (for history operations)
|
|
6295
6295
|
* @returns {Promise<Object>} Returns change object for history operations, file info for file operations
|
|
6296
6296
|
*/
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6297
|
+
/**
|
|
6298
|
+
* Move a file in the working copy
|
|
6299
|
+
*
|
|
6300
|
+
* @param {Object} args - Move file arguments
|
|
6301
|
+
* @param {string} args.from - Source file path
|
|
6302
|
+
* @param {string} args.to - Destination file path
|
|
6303
|
+
* @returns {Promise<Object>} Operation result including from, to paths and file stats
|
|
6304
|
+
*/
|
|
6305
|
+
async moveFile(args) {
|
|
6306
|
+
if (!args.from || typeof args.from !== 'string') {
|
|
6307
|
+
throw new JJError('INVALID_ARGUMENT', 'Missing or invalid from path', {
|
|
6308
|
+
suggestion: 'Provide a valid source file path',
|
|
6309
|
+
provided: { from: args.from, type: typeof args.from },
|
|
6301
6310
|
});
|
|
6302
6311
|
}
|
|
6303
6312
|
|
|
6304
|
-
|
|
6305
|
-
|
|
6313
|
+
if (!args.to || typeof args.to !== 'string') {
|
|
6314
|
+
throw new JJError('INVALID_ARGUMENT', 'Missing or invalid to path', {
|
|
6315
|
+
suggestion: 'Provide a valid destination file path',
|
|
6316
|
+
provided: { to: args.to, type: typeof args.to },
|
|
6317
|
+
});
|
|
6318
|
+
}
|
|
6306
6319
|
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6320
|
+
// Prevent moving to the same location
|
|
6321
|
+
if (args.from === args.to) {
|
|
6322
|
+
throw new JJError('INVALID_OPERATION', 'Source and destination paths are the same', {
|
|
6323
|
+
path: args.from,
|
|
6324
|
+
suggestion: 'Specify a different destination path',
|
|
6325
|
+
});
|
|
6311
6326
|
}
|
|
6312
|
-
},
|
|
6313
6327
|
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
*/
|
|
6321
|
-
_detectHistoryOperation(args) {
|
|
6322
|
-
// Explicit history operation indicators
|
|
6323
|
-
if (args.paths || args.changeId || args.newParent) {
|
|
6324
|
-
return true;
|
|
6328
|
+
// Prevent absolute paths
|
|
6329
|
+
if (args.from.startsWith('/') || args.to.startsWith('/')) {
|
|
6330
|
+
throw new JJError('INVALID_PATH', 'Absolute paths are not allowed', {
|
|
6331
|
+
suggestion: 'Use paths relative to the repository root',
|
|
6332
|
+
provided: { from: args.from, to: args.to },
|
|
6333
|
+
});
|
|
6325
6334
|
}
|
|
6326
6335
|
|
|
6327
|
-
//
|
|
6328
|
-
if (args.from
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6336
|
+
// Prevent parent directory traversal
|
|
6337
|
+
if (args.from.includes('..') || args.to.includes('..')) {
|
|
6338
|
+
throw new JJError('INVALID_PATH', 'Parent directory traversal (..) is not allowed', {
|
|
6339
|
+
suggestion: 'Use paths within the repository',
|
|
6340
|
+
provided: { from: args.from, to: args.to },
|
|
6341
|
+
});
|
|
6342
|
+
}
|
|
6333
6343
|
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
// Users should use explicit { changeId, newParent } for clarity
|
|
6337
|
-
if (fromLooksLikeChangeId && toLooksLikeChangeId) {
|
|
6338
|
-
// This is ambiguous - throw a helpful error
|
|
6339
|
-
throw new JJError('AMBIGUOUS_OPERATION',
|
|
6340
|
-
'Ambiguous move operation: arguments look like change IDs', {
|
|
6341
|
-
suggestion: 'Use { changeId, newParent } for history operations or ensure file paths don\'t match change ID pattern',
|
|
6342
|
-
args,
|
|
6343
|
-
});
|
|
6344
|
-
}
|
|
6344
|
+
const fromPath = `${dir}/${args.from}`;
|
|
6345
|
+
const toPath = `${dir}/${args.to}`;
|
|
6345
6346
|
|
|
6346
|
-
|
|
6347
|
+
// Check if source file exists
|
|
6348
|
+
try {
|
|
6349
|
+
await fs.promises.stat(fromPath);
|
|
6350
|
+
} catch (error) {
|
|
6351
|
+
throw new JJError('FILE_NOT_FOUND', `Source file not found: ${args.from}`, {
|
|
6352
|
+
path: args.from,
|
|
6353
|
+
suggestion: 'Check that the source file exists',
|
|
6354
|
+
});
|
|
6347
6355
|
}
|
|
6348
6356
|
|
|
6349
|
-
|
|
6357
|
+
// Ensure destination directory exists
|
|
6358
|
+
const toDir = toPath.substring(0, toPath.lastIndexOf('/'));
|
|
6359
|
+
try {
|
|
6360
|
+
await fs.promises.mkdir(toDir, { recursive: true });
|
|
6361
|
+
} catch (error) {
|
|
6362
|
+
throw new JJError('FILE_SYSTEM_ERROR', `Failed to create destination directory: ${error.message}`, {
|
|
6363
|
+
path: toDir,
|
|
6364
|
+
error: error.message,
|
|
6365
|
+
});
|
|
6366
|
+
}
|
|
6367
|
+
|
|
6368
|
+
// Move the file
|
|
6369
|
+
try {
|
|
6370
|
+
await fs.promises.rename(fromPath, toPath);
|
|
6371
|
+
} catch (error) {
|
|
6372
|
+
throw new JJError('FILE_SYSTEM_ERROR', `Failed to move file: ${error.message}`, {
|
|
6373
|
+
from: args.from,
|
|
6374
|
+
to: args.to,
|
|
6375
|
+
error: error.message,
|
|
6376
|
+
});
|
|
6377
|
+
}
|
|
6378
|
+
|
|
6379
|
+
// Update working copy tracking
|
|
6380
|
+
await workingCopy.load();
|
|
6381
|
+
await workingCopy.untrackFile(args.from);
|
|
6382
|
+
const stats = await fs.promises.stat(toPath);
|
|
6383
|
+
await workingCopy.trackFile(args.to, {
|
|
6384
|
+
mtime: stats.mtime,
|
|
6385
|
+
size: stats.size,
|
|
6386
|
+
mode: stats.mode,
|
|
6387
|
+
});
|
|
6388
|
+
|
|
6389
|
+
return {
|
|
6390
|
+
from: args.from,
|
|
6391
|
+
to: args.to,
|
|
6392
|
+
size: stats.size,
|
|
6393
|
+
mode: stats.mode,
|
|
6394
|
+
mtime: stats.mtime,
|
|
6395
|
+
};
|
|
6350
6396
|
},
|
|
6351
6397
|
|
|
6352
6398
|
/**
|
|
6353
|
-
* Move
|
|
6399
|
+
* Move (rebase) a change to a new parent
|
|
6354
6400
|
*
|
|
6355
|
-
* @
|
|
6356
|
-
* @param {
|
|
6401
|
+
* @param {Object} args - Move change arguments
|
|
6402
|
+
* @param {string} args.changeId - Change ID to move (also accepts 'from' or 'source')
|
|
6403
|
+
* @param {string} args.newParent - New parent change ID (also accepts 'to' or 'destination')
|
|
6357
6404
|
* @returns {Promise<Object>} Updated change object
|
|
6358
6405
|
*/
|
|
6359
|
-
async
|
|
6406
|
+
async moveChange(args) {
|
|
6360
6407
|
await graph.load();
|
|
6361
6408
|
|
|
6362
6409
|
// Support multiple parameter names for flexibility
|
|
@@ -6444,110 +6491,62 @@ async function createJJ(options) {
|
|
|
6444
6491
|
},
|
|
6445
6492
|
|
|
6446
6493
|
/**
|
|
6447
|
-
*
|
|
6448
|
-
*
|
|
6449
|
-
* @private
|
|
6450
|
-
* @param {Object} args - Move arguments
|
|
6451
|
-
* @returns {Promise<Object>} Operation result including from, to paths and file stats
|
|
6494
|
+
* @deprecated Use moveFile() for files or moveChange() for history operations
|
|
6495
|
+
* Move operation - delegates to moveFile or moveChange based on arguments
|
|
6452
6496
|
*/
|
|
6453
|
-
async
|
|
6454
|
-
if (!args
|
|
6455
|
-
throw new JJError('INVALID_ARGUMENT', 'Missing or invalid
|
|
6456
|
-
suggestion: '
|
|
6457
|
-
provided: { from: args.from, type: typeof args.from },
|
|
6458
|
-
});
|
|
6459
|
-
}
|
|
6460
|
-
|
|
6461
|
-
if (!args.to || typeof args.to !== 'string') {
|
|
6462
|
-
throw new JJError('INVALID_ARGUMENT', 'Missing or invalid to path', {
|
|
6463
|
-
suggestion: 'Provide a valid destination file path',
|
|
6464
|
-
provided: { to: args.to, type: typeof args.to },
|
|
6497
|
+
async move(args) {
|
|
6498
|
+
if (!args || typeof args !== 'object') {
|
|
6499
|
+
throw new JJError('INVALID_ARGUMENT', 'Missing or invalid arguments', {
|
|
6500
|
+
suggestion: 'Use moveFile({ from, to }) for files or moveChange({ changeId, newParent }) for history',
|
|
6465
6501
|
});
|
|
6466
6502
|
}
|
|
6467
6503
|
|
|
6468
|
-
//
|
|
6469
|
-
|
|
6470
|
-
throw new JJError('INVALID_OPERATION', 'Source and destination paths are the same', {
|
|
6471
|
-
path: args.from,
|
|
6472
|
-
suggestion: 'Specify a different destination path',
|
|
6473
|
-
});
|
|
6474
|
-
}
|
|
6504
|
+
// Detect operation type based on arguments
|
|
6505
|
+
const isHistoryOp = this._detectHistoryOperation(args);
|
|
6475
6506
|
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
provided: { from: args.from, to: args.to },
|
|
6481
|
-
});
|
|
6507
|
+
if (isHistoryOp) {
|
|
6508
|
+
return await this.moveChange(args);
|
|
6509
|
+
} else {
|
|
6510
|
+
return await this.moveFile(args);
|
|
6482
6511
|
}
|
|
6512
|
+
},
|
|
6483
6513
|
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6514
|
+
/**
|
|
6515
|
+
* Detect if move operation is for history (rebase) or files
|
|
6516
|
+
*
|
|
6517
|
+
* @private
|
|
6518
|
+
* @param {Object} args - Move arguments
|
|
6519
|
+
* @returns {boolean} True if history operation, false if file operation
|
|
6520
|
+
*/
|
|
6521
|
+
_detectHistoryOperation(args) {
|
|
6522
|
+
// Explicit history operation indicators
|
|
6523
|
+
if (args.paths || args.changeId || args.newParent) {
|
|
6524
|
+
return true;
|
|
6490
6525
|
}
|
|
6491
6526
|
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
} catch (error) {
|
|
6499
|
-
throw new JJError('FILE_NOT_FOUND', `Source file not found: ${args.from}`, {
|
|
6500
|
-
path: args.from,
|
|
6501
|
-
fullPath: fromPath,
|
|
6502
|
-
suggestion: 'Check that the file exists',
|
|
6503
|
-
});
|
|
6504
|
-
}
|
|
6527
|
+
// If only from/to are present, check if they look like change IDs
|
|
6528
|
+
if (args.from && args.to && !args.changeId && !args.newParent && !args.paths) {
|
|
6529
|
+
// Change IDs are 32-character hex strings
|
|
6530
|
+
const changeIdPattern = /^[0-9a-f]{32}$/;
|
|
6531
|
+
const fromLooksLikeChangeId = changeIdPattern.test(args.from);
|
|
6532
|
+
const toLooksLikeChangeId = changeIdPattern.test(args.to);
|
|
6505
6533
|
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
`Failed to create destination directory: ${dirPath}`, {
|
|
6516
|
-
directory: dirPath,
|
|
6517
|
-
originalError: error.message,
|
|
6534
|
+
// If both look like change IDs, it's likely a history operation
|
|
6535
|
+
// However, we err on the side of file operations to be safe
|
|
6536
|
+
// Users should use explicit { changeId, newParent } for clarity
|
|
6537
|
+
if (fromLooksLikeChangeId && toLooksLikeChangeId) {
|
|
6538
|
+
// This is ambiguous - throw a helpful error
|
|
6539
|
+
throw new JJError('AMBIGUOUS_OPERATION',
|
|
6540
|
+
'Ambiguous move operation: arguments look like change IDs', {
|
|
6541
|
+
suggestion: 'Use { changeId, newParent } for history operations or ensure file paths don\'t match change ID pattern',
|
|
6542
|
+
args,
|
|
6518
6543
|
});
|
|
6519
6544
|
}
|
|
6520
|
-
}
|
|
6521
6545
|
|
|
6522
|
-
|
|
6523
|
-
try {
|
|
6524
|
-
await fs.promises.rename(fromPath, toPath);
|
|
6525
|
-
} catch (error) {
|
|
6526
|
-
throw new JJError('FILE_MOVE_FAILED', `Failed to move file: ${error.message}`, {
|
|
6527
|
-
from: args.from,
|
|
6528
|
-
to: args.to,
|
|
6529
|
-
originalError: error.message,
|
|
6530
|
-
});
|
|
6546
|
+
return false; // Treat as file operation
|
|
6531
6547
|
}
|
|
6532
6548
|
|
|
6533
|
-
|
|
6534
|
-
await workingCopy.load();
|
|
6535
|
-
await workingCopy.untrackFile(args.from);
|
|
6536
|
-
const stats = await fs.promises.stat(toPath);
|
|
6537
|
-
await workingCopy.trackFile(args.to, {
|
|
6538
|
-
mtime: stats.mtime,
|
|
6539
|
-
size: stats.size,
|
|
6540
|
-
mode: stats.mode,
|
|
6541
|
-
});
|
|
6542
|
-
|
|
6543
|
-
// Return useful information about the move operation
|
|
6544
|
-
return {
|
|
6545
|
-
from: args.from,
|
|
6546
|
-
to: args.to,
|
|
6547
|
-
size: stats.size,
|
|
6548
|
-
mode: stats.mode,
|
|
6549
|
-
mtime: stats.mtime,
|
|
6550
|
-
};
|
|
6549
|
+
return false;
|
|
6551
6550
|
},
|
|
6552
6551
|
|
|
6553
6552
|
/**
|
|
@@ -6623,6 +6622,11 @@ async function createJJ(options) {
|
|
|
6623
6622
|
change.timestamp = new Date().toISOString();
|
|
6624
6623
|
}
|
|
6625
6624
|
|
|
6625
|
+
// Update metadata
|
|
6626
|
+
if (args.metadata !== undefined) {
|
|
6627
|
+
change.metadata = args.metadata;
|
|
6628
|
+
}
|
|
6629
|
+
|
|
6626
6630
|
// Only snapshot files if describing working copy
|
|
6627
6631
|
if (isWorkingCopy) {
|
|
6628
6632
|
// Snapshot current file contents for conflict detection
|
|
@@ -7227,10 +7231,11 @@ async function createJJ(options) {
|
|
|
7227
7231
|
* @param {Object} [args={}] - Arguments
|
|
7228
7232
|
* @param {string} [args.message] - Description for current change
|
|
7229
7233
|
* @param {Object} [args.author] - Author for current change
|
|
7234
|
+
* @param {Object} [args.metadata] - Metadata to attach to the change
|
|
7230
7235
|
* @returns {Promise<Object>} The newly created change
|
|
7231
7236
|
*/
|
|
7232
7237
|
async commit(args = {}) {
|
|
7233
|
-
await this.describe({ message: args.message, author: args.author });
|
|
7238
|
+
await this.describe({ message: args.message, author: args.author, metadata: args.metadata });
|
|
7234
7239
|
return await this.new({ message: args.nextMessage });
|
|
7235
7240
|
},
|
|
7236
7241
|
|
|
@@ -8054,7 +8059,7 @@ async function createJJ(options) {
|
|
|
8054
8059
|
* });
|
|
8055
8060
|
*/
|
|
8056
8061
|
async rebase(args) {
|
|
8057
|
-
return await this.
|
|
8062
|
+
return await this.moveChange(args);
|
|
8058
8063
|
},
|
|
8059
8064
|
|
|
8060
8065
|
/**
|