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.mjs CHANGED
@@ -4463,14 +4463,14 @@ class UserConfig {
4463
4463
  */
4464
4464
 
4465
4465
 
4466
- const __filename$3 = fileURLToPath(import.meta.url);
4467
- const __dirname$3 = path.dirname(__filename$3);
4466
+ const __filename$4 = fileURLToPath(import.meta.url);
4467
+ const __dirname$4 = path.dirname(__filename$4);
4468
4468
 
4469
4469
  // Determine proto path based on whether we're running from dist or src
4470
- const inDist$3 = __dirname$3.includes('/dist') || __dirname$3.includes('\\dist');
4470
+ const inDist$3 = __dirname$4.includes('/dist') || __dirname$4.includes('\\dist');
4471
4471
  const protoDir$3 = inDist$3
4472
- ? path.join(__dirname$3, '..', 'src', 'protos')
4473
- : path.join(__dirname$3, '..', 'protos');
4472
+ ? path.join(__dirname$4, '..', 'src', 'protos')
4473
+ : path.join(__dirname$4, '..', 'protos');
4474
4474
 
4475
4475
  class JJCheckout {
4476
4476
  constructor(fs, dir) {
@@ -4545,14 +4545,14 @@ class JJCheckout {
4545
4545
  */
4546
4546
 
4547
4547
 
4548
- const __filename$2 = fileURLToPath(import.meta.url);
4549
- const __dirname$2 = path.dirname(__filename$2);
4548
+ const __filename$3 = fileURLToPath(import.meta.url);
4549
+ const __dirname$3 = path.dirname(__filename$3);
4550
4550
 
4551
4551
  // Determine proto path based on whether we're running from dist or src
4552
- const inDist$2 = __dirname$2.includes('/dist') || __dirname$2.includes('\\dist');
4552
+ const inDist$2 = __dirname$3.includes('/dist') || __dirname$3.includes('\\dist');
4553
4553
  const protoDir$2 = inDist$2
4554
- ? path.join(__dirname$2, '..', 'src', 'protos')
4555
- : path.join(__dirname$2, '..', 'protos');
4554
+ ? path.join(__dirname$3, '..', 'src', 'protos')
4555
+ : path.join(__dirname$3, '..', 'protos');
4556
4556
 
4557
4557
  class JJTreeState {
4558
4558
  constructor(fs, dir) {
@@ -4643,14 +4643,14 @@ class JJTreeState {
4643
4643
  */
4644
4644
 
4645
4645
 
4646
- const __filename$1 = fileURLToPath(import.meta.url);
4647
- const __dirname$1 = path.dirname(__filename$1);
4646
+ const __filename$2 = fileURLToPath(import.meta.url);
4647
+ const __dirname$2 = path.dirname(__filename$2);
4648
4648
 
4649
4649
  // Determine proto path based on whether we're running from dist or src
4650
- const inDist$1 = __dirname$1.includes('/dist') || __dirname$1.includes('\\dist');
4650
+ const inDist$1 = __dirname$2.includes('/dist') || __dirname$2.includes('\\dist');
4651
4651
  const protoDir$1 = inDist$1
4652
- ? path.join(__dirname$1, '..', 'src', 'protos')
4653
- : path.join(__dirname$1, '..', 'protos');
4652
+ ? path.join(__dirname$2, '..', 'src', 'protos')
4653
+ : path.join(__dirname$2, '..', 'protos');
4654
4654
 
4655
4655
  class JJOperationStore {
4656
4656
  constructor(fs, dir) {
@@ -4767,14 +4767,14 @@ class JJOperationStore {
4767
4767
  */
4768
4768
 
4769
4769
 
4770
- const __filename = fileURLToPath(import.meta.url);
4771
- const __dirname = path.dirname(__filename);
4770
+ const __filename$1 = fileURLToPath(import.meta.url);
4771
+ const __dirname$1 = path.dirname(__filename$1);
4772
4772
 
4773
4773
  // Determine proto path based on whether we're running from dist or src
4774
- const inDist = __dirname.includes('/dist') || __dirname.includes('\\dist');
4774
+ const inDist = __dirname$1.includes('/dist') || __dirname$1.includes('\\dist');
4775
4775
  const protoDir = inDist
4776
- ? path.join(__dirname, '..', 'src', 'protos')
4777
- : path.join(__dirname, '..', 'protos');
4776
+ ? path.join(__dirname$1, '..', 'src', 'protos')
4777
+ : path.join(__dirname$1, '..', 'protos');
4778
4778
 
4779
4779
  class JJViewStore {
4780
4780
  constructor(fs, dir) {
@@ -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
  /**
@@ -6620,6 +6619,11 @@ async function createJJ(options) {
6620
6619
  change.timestamp = new Date().toISOString();
6621
6620
  }
6622
6621
 
6622
+ // Update metadata
6623
+ if (args.metadata !== undefined) {
6624
+ change.metadata = args.metadata;
6625
+ }
6626
+
6623
6627
  // Only snapshot files if describing working copy
6624
6628
  if (isWorkingCopy) {
6625
6629
  // Snapshot current file contents for conflict detection
@@ -7224,10 +7228,11 @@ async function createJJ(options) {
7224
7228
  * @param {Object} [args={}] - Arguments
7225
7229
  * @param {string} [args.message] - Description for current change
7226
7230
  * @param {Object} [args.author] - Author for current change
7231
+ * @param {Object} [args.metadata] - Metadata to attach to the change
7227
7232
  * @returns {Promise<Object>} The newly created change
7228
7233
  */
7229
7234
  async commit(args = {}) {
7230
- await this.describe({ message: args.message, author: args.author });
7235
+ await this.describe({ message: args.message, author: args.author, metadata: args.metadata });
7231
7236
  return await this.new({ message: args.nextMessage });
7232
7237
  },
7233
7238
 
@@ -8051,7 +8056,7 @@ async function createJJ(options) {
8051
8056
  * });
8052
8057
  */
8053
8058
  async rebase(args) {
8054
- return await this._moveChange(args);
8059
+ return await this.moveChange(args);
8055
8060
  },
8056
8061
 
8057
8062
  /**