isomorphic-jj 1.1.0 → 1.1.3

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
@@ -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
- if (path.includes('..')) {
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 (path.startsWith('/')) {
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
- await this.fs.writeFile(this.tagsFile, JSON.stringify(tags, null, 2));
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
  /**
@@ -4373,11 +4394,17 @@ class UserConfig {
4373
4394
  const __filename$4 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
4374
4395
  const __dirname$4 = path.dirname(__filename$4);
4375
4396
 
4397
+ // Determine proto path based on whether we're running from dist or src
4398
+ const inDist$3 = __dirname$4.includes('/dist') || __dirname$4.includes('\\dist');
4399
+ const protoDir$3 = inDist$3
4400
+ ? path.join(__dirname$4, '..', 'src', 'protos')
4401
+ : path.join(__dirname$4, '..', 'protos');
4402
+
4376
4403
  class JJCheckout {
4377
4404
  constructor(fs, dir) {
4378
4405
  this.fs = fs;
4379
4406
  this.dir = dir;
4380
- this.protoPath = path.join(__dirname$4, '..', 'protos', 'local_working_copy.proto');
4407
+ this.protoPath = path.join(protoDir$3, 'local_working_copy.proto');
4381
4408
  }
4382
4409
 
4383
4410
  /**
@@ -4449,11 +4476,17 @@ class JJCheckout {
4449
4476
  const __filename$3 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
4450
4477
  const __dirname$3 = path.dirname(__filename$3);
4451
4478
 
4479
+ // Determine proto path based on whether we're running from dist or src
4480
+ const inDist$2 = __dirname$3.includes('/dist') || __dirname$3.includes('\\dist');
4481
+ const protoDir$2 = inDist$2
4482
+ ? path.join(__dirname$3, '..', 'src', 'protos')
4483
+ : path.join(__dirname$3, '..', 'protos');
4484
+
4452
4485
  class JJTreeState {
4453
4486
  constructor(fs, dir) {
4454
4487
  this.fs = fs;
4455
4488
  this.dir = dir;
4456
- this.protoPath = path.join(__dirname$3, '..', 'protos', 'local_working_copy.proto');
4489
+ this.protoPath = path.join(protoDir$2, 'local_working_copy.proto');
4457
4490
  }
4458
4491
 
4459
4492
  /**
@@ -4541,11 +4574,17 @@ class JJTreeState {
4541
4574
  const __filename$2 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
4542
4575
  const __dirname$2 = path.dirname(__filename$2);
4543
4576
 
4577
+ // Determine proto path based on whether we're running from dist or src
4578
+ const inDist$1 = __dirname$2.includes('/dist') || __dirname$2.includes('\\dist');
4579
+ const protoDir$1 = inDist$1
4580
+ ? path.join(__dirname$2, '..', 'src', 'protos')
4581
+ : path.join(__dirname$2, '..', 'protos');
4582
+
4544
4583
  class JJOperationStore {
4545
4584
  constructor(fs, dir) {
4546
4585
  this.fs = fs;
4547
4586
  this.dir = dir;
4548
- this.protoPath = path.join(__dirname$2, '..', 'protos', 'simple_op_store.proto');
4587
+ this.protoPath = path.join(protoDir$1, 'simple_op_store.proto');
4549
4588
  }
4550
4589
 
4551
4590
  /**
@@ -4659,11 +4698,17 @@ class JJOperationStore {
4659
4698
  const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
4660
4699
  const __dirname$1 = path.dirname(__filename$1);
4661
4700
 
4701
+ // Determine proto path based on whether we're running from dist or src
4702
+ const inDist = __dirname$1.includes('/dist') || __dirname$1.includes('\\dist');
4703
+ const protoDir = inDist
4704
+ ? path.join(__dirname$1, '..', 'src', 'protos')
4705
+ : path.join(__dirname$1, '..', 'protos');
4706
+
4662
4707
  class JJViewStore {
4663
4708
  constructor(fs, dir) {
4664
4709
  this.fs = fs;
4665
4710
  this.dir = dir;
4666
- this.protoPath = path.join(__dirname$1, '..', 'protos', 'simple_op_store.proto');
4711
+ this.protoPath = path.join(protoDir, 'simple_op_store.proto');
4667
4712
  }
4668
4713
 
4669
4714
  /**
@@ -6239,8 +6284,9 @@ async function createJJ(options) {
6239
6284
  async _moveChange(args) {
6240
6285
  await graph.load();
6241
6286
 
6242
- const changeId = args.changeId || args.from;
6243
- const newParent = args.newParent || args.to;
6287
+ // Support multiple parameter names for flexibility
6288
+ const changeId = args.changeId || args.from || args.source;
6289
+ const newParent = args.newParent || args.to || args.destination;
6244
6290
 
6245
6291
  if (!changeId || typeof changeId !== 'string') {
6246
6292
  throw new JJError('INVALID_ARGUMENT', 'Missing or invalid changeId', {
@@ -6580,11 +6626,14 @@ async function createJJ(options) {
6580
6626
  /**
6581
6627
  * Edit change metadata without modifying content
6582
6628
  *
6583
- * Allows updating author, committer information, and optionally regenerating the change ID.
6629
+ * Allows updating author, committer, description, and optionally regenerating the change ID.
6584
6630
  * The file content and snapshot remain unchanged.
6585
6631
  *
6586
6632
  * @param {Object} [args={}] - Arguments
6587
6633
  * @param {string} [args.revision] - Change ID to edit (defaults to working copy @)
6634
+ * @param {string} [args.change] - Alias for revision
6635
+ * @param {string} [args.description] - Update the change description
6636
+ * @param {string} [args.message] - Alias for description
6588
6637
  * @param {Object} [args.author] - Author information to update
6589
6638
  * @param {string} [args.author.name] - Author name
6590
6639
  * @param {string} [args.author.email] - Author email
@@ -6595,6 +6644,16 @@ async function createJJ(options) {
6595
6644
  * @returns {Promise<Object>} Updated change object
6596
6645
  */
6597
6646
  async metaedit(args = {}) {
6647
+ // Normalize: accept 'change' as alias for 'revision'
6648
+ if (args.change && !args.revision) {
6649
+ args = { ...args, revision: args.change };
6650
+ }
6651
+
6652
+ // Normalize: accept 'message' as alias for 'description'
6653
+ if (args.message && !args.description) {
6654
+ args = { ...args, description: args.message };
6655
+ }
6656
+
6598
6657
  await graph.load();
6599
6658
  await workingCopy.load();
6600
6659
  await userConfig.load();
@@ -6612,13 +6671,14 @@ async function createJJ(options) {
6612
6671
  const hasAuthor = args.author && (args.author.name || args.author.email);
6613
6672
  const hasCommitter = args.committer && (args.committer.name || args.committer.email);
6614
6673
  const hasResetChangeId = args.resetChangeId === true;
6674
+ const hasDescription = args.description !== undefined;
6615
6675
 
6616
- if (!hasAuthor && !hasCommitter && !hasResetChangeId) {
6676
+ if (!hasAuthor && !hasCommitter && !hasResetChangeId && !hasDescription) {
6617
6677
  throw new JJError(
6618
6678
  'INVALID_ARGUMENT',
6619
6679
  'No metadata provided to update',
6620
6680
  {
6621
- suggestion: 'Provide author, committer, or resetChangeId: true',
6681
+ suggestion: 'Provide author, committer, description, or resetChangeId: true',
6622
6682
  }
6623
6683
  );
6624
6684
  }
@@ -6663,6 +6723,11 @@ async function createJJ(options) {
6663
6723
  change.committer.timestamp = new Date().toISOString();
6664
6724
  }
6665
6725
 
6726
+ // Update description if provided
6727
+ if (args.description !== undefined) {
6728
+ change.description = args.description;
6729
+ }
6730
+
6666
6731
  // Regenerate change ID if requested
6667
6732
  if (args.resetChangeId) {
6668
6733
  // For now, resetChangeId is not fully implemented as it requires complex graph operations
@@ -7096,9 +7161,14 @@ async function createJJ(options) {
7096
7161
  * @returns {Promise<Object>} Information about the edited change including changeId, description, and file count
7097
7162
  */
7098
7163
  async edit(args) {
7164
+ // Normalize: accept both 'change' and 'changeId' parameters
7165
+ if (args && args.change && !args.changeId) {
7166
+ args = { ...args, changeId: args.change };
7167
+ }
7168
+
7099
7169
  if (!args || !args.changeId) {
7100
7170
  throw new JJError('INVALID_ARGUMENT', 'Missing changeId argument', {
7101
- suggestion: 'Provide a changeId to edit',
7171
+ suggestion: 'Provide a changeId (or change) to edit',
7102
7172
  });
7103
7173
  }
7104
7174
 
@@ -8940,6 +9010,23 @@ async function createJJ(options) {
8940
9010
  * @returns {Promise<Object>} The un-abandoned change object including changeId, description, and abandoned flag (now false)
8941
9011
  */
8942
9012
  async unabandon(args) {
9013
+ // Normalize: accept both 'change' and 'changeId' parameters
9014
+ if (args && args.change && !args.changeId) {
9015
+ args = { ...args, changeId: args.change };
9016
+ }
9017
+
9018
+ if (!args || !args.changeId) {
9019
+ throw new JJError('INVALID_ARGUMENT', 'Missing changeId argument', {
9020
+ suggestion: 'Provide a changeId (or change) to unabandon',
9021
+ });
9022
+ }
9023
+
9024
+ if (typeof args.changeId !== 'string') {
9025
+ throw new JJError('INVALID_ARGUMENT', 'Change ID must be a string', {
9026
+ changeId: args.changeId,
9027
+ });
9028
+ }
9029
+
8943
9030
  await graph.load();
8944
9031
  await userConfig.load();
8945
9032
 
@@ -10304,6 +10391,13 @@ async function createJJ(options) {
10304
10391
  */
10305
10392
  async add(args) {
10306
10393
  await workspaces.load();
10394
+
10395
+ // If no changeId specified, default to current working copy change
10396
+ if (!args.changeId) {
10397
+ const currentChangeId = workingCopy.getCurrentChangeId();
10398
+ args = { ...args, changeId: currentChangeId };
10399
+ }
10400
+
10307
10401
  const workspace = await workspaces.add(args);
10308
10402
 
10309
10403
  // If a change was specified, check it out in the new workspace
@@ -10700,9 +10794,21 @@ async function createJJ(options) {
10700
10794
  * @returns {Promise<Object>} Bookmark info
10701
10795
  */
10702
10796
  async set(args) {
10797
+ // Normalize: accept common parameter aliases
10798
+ if (args) {
10799
+ if (args.target && !args.changeId) {
10800
+ args = { ...args, changeId: args.target };
10801
+ } else if (args.change && !args.changeId) {
10802
+ args = { ...args, changeId: args.change };
10803
+ } else if (args.revision && !args.changeId) {
10804
+ args = { ...args, changeId: args.revision };
10805
+ }
10806
+ }
10807
+
10703
10808
  if (!args || !args.name || !args.changeId) {
10704
10809
  throw new JJError('INVALID_ARGUMENT', 'Missing name or changeId', {
10705
- suggestion: 'Provide both: { name: "main", changeId: "abc123..." }',
10810
+ args,
10811
+ suggestion: 'Provide both: { name: "main", changeId: "abc123..." } (or use target/change/revision instead of changeId)',
10706
10812
  });
10707
10813
  }
10708
10814
 
@@ -10738,6 +10844,17 @@ async function createJJ(options) {
10738
10844
  * @returns {Promise<Object>} Bookmark info
10739
10845
  */
10740
10846
  async create(args) {
10847
+ // Normalize: accept common parameter aliases
10848
+ if (args) {
10849
+ if (args.target && !args.changeId) {
10850
+ args = { ...args, changeId: args.target };
10851
+ } else if (args.change && !args.changeId) {
10852
+ args = { ...args, changeId: args.change };
10853
+ } else if (args.revision && !args.changeId) {
10854
+ args = { ...args, changeId: args.revision };
10855
+ }
10856
+ }
10857
+
10741
10858
  if (!args || !args.name) {
10742
10859
  throw new JJError('INVALID_ARGUMENT', 'Missing name argument', {
10743
10860
  suggestion: 'Provide: { name: "main", changeId: "abc123..." } or { name: "main" } for working copy',
@@ -10790,9 +10907,23 @@ async function createJJ(options) {
10790
10907
  * @returns {Promise<Object>} Move result
10791
10908
  */
10792
10909
  async move(args) {
10910
+ // Normalize: accept common parameter aliases
10911
+ if (args) {
10912
+ if (args.target && !args.to) {
10913
+ args = { ...args, to: args.target };
10914
+ } else if (args.changeId && !args.to) {
10915
+ args = { ...args, to: args.changeId };
10916
+ } else if (args.change && !args.to) {
10917
+ args = { ...args, to: args.change };
10918
+ } else if (args.revision && !args.to) {
10919
+ args = { ...args, to: args.revision };
10920
+ }
10921
+ }
10922
+
10793
10923
  if (!args || !args.name || !args.to) {
10794
10924
  throw new JJError('INVALID_ARGUMENT', 'Missing name or to', {
10795
- suggestion: 'Provide both: { name: "main", to: "abc123..." }',
10925
+ args,
10926
+ suggestion: 'Provide both: { name: "main", to: "abc123..." } (or use target/changeId/change/revision instead of to)',
10796
10927
  });
10797
10928
  }
10798
10929