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