isomorphic-jj 1.2.0 → 1.3.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 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
- async move(args) {
6298
- if (!args || typeof args !== 'object') {
6299
- throw new JJError('INVALID_ARGUMENT', 'Missing or invalid arguments', {
6300
- suggestion: 'Use file.move({ from, to }) for files or rebase({ changeId, newParent }) for history',
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
- // Detect operation type based on arguments
6305
- const isHistoryOp = this._detectHistoryOperation(args);
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
- if (isHistoryOp) {
6308
- return await this._moveChange(args);
6309
- } else {
6310
- return await this._moveFile(args);
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
- * Detect if move operation is for history (rebase) or files
6316
- *
6317
- * @private
6318
- * @param {Object} args - Move arguments
6319
- * @returns {boolean} True if history operation, false if file operation
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
- // If only from/to are present, check if they look like change IDs
6328
- if (args.from && args.to && !args.changeId && !args.newParent && !args.paths) {
6329
- // Change IDs are 32-character hex strings
6330
- const changeIdPattern = /^[0-9a-f]{32}$/;
6331
- const fromLooksLikeChangeId = changeIdPattern.test(args.from);
6332
- const toLooksLikeChangeId = changeIdPattern.test(args.to);
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
- // If both look like change IDs, it's likely a history operation
6335
- // However, we err on the side of file operations to be safe
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
- return false; // Treat as file operation
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
- return false;
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/rebase a change to a new parent (history operation)
6399
+ * Move (rebase) a change to a new parent
6354
6400
  *
6355
- * @private
6356
- * @param {Object} args - Move arguments
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 _moveChange(args) {
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
- * Move/rename a file in the working copy (file operation)
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 _moveFile(args) {
6454
- if (!args.from || typeof args.from !== 'string') {
6455
- throw new JJError('INVALID_ARGUMENT', 'Missing or invalid from path', {
6456
- suggestion: 'Provide a valid source file path',
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
- // Prevent moving to the same location
6469
- if (args.from === args.to) {
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
- // Prevent absolute paths
6477
- if (args.from.startsWith('/') || args.to.startsWith('/')) {
6478
- throw new JJError('INVALID_PATH', 'Absolute paths are not allowed', {
6479
- suggestion: 'Use paths relative to the repository root',
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
- // Prevent parent directory traversal
6485
- if (args.from.includes('..') || args.to.includes('..')) {
6486
- throw new JJError('INVALID_PATH', 'Parent directory traversal (..) is not allowed', {
6487
- suggestion: 'Use paths within the repository',
6488
- provided: { from: args.from, to: args.to },
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
- const fromPath = `${dir}/${args.from}`;
6493
- const toPath = `${dir}/${args.to}`;
6494
-
6495
- // Check if source file exists
6496
- try {
6497
- await fs.promises.access(fromPath);
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
- // Ensure destination directory exists
6507
- const pathParts = args.to.split('/');
6508
- if (pathParts.length > 1) {
6509
- const dirPath = pathParts.slice(0, -1).join('/');
6510
- const fullDirPath = `${dir}/${dirPath}`;
6511
- try {
6512
- await fs.promises.mkdir(fullDirPath, { recursive: true });
6513
- } catch (error) {
6514
- throw new JJError('DIRECTORY_CREATE_FAILED',
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
- // Move the file
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
- // Update working copy tracking
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
  /**
@@ -8054,7 +8053,7 @@ async function createJJ(options) {
8054
8053
  * });
8055
8054
  */
8056
8055
  async rebase(args) {
8057
- return await this._moveChange(args);
8056
+ return await this.moveChange(args);
8058
8057
  },
8059
8058
 
8060
8059
  /**