isomorphic-jj 1.1.2 → 1.2.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/README.md +48 -0
- package/dist/index.cjs +260 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +260 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/api/repository.js +151 -17
- package/src/core/bookmark-store.js +16 -4
- package/src/core/jj-checkout.js +7 -1
- package/src/core/jj-operation-store.js +7 -1
- package/src/core/jj-tree-state.js +7 -1
- package/src/core/jj-view-store.js +7 -1
- package/src/core/revset-engine.js +18 -0
- package/src/core/user-config.js +47 -2
package/dist/index.mjs
CHANGED
|
@@ -1309,6 +1309,7 @@ class BookmarkStore {
|
|
|
1309
1309
|
this.storage = storage;
|
|
1310
1310
|
this.local = new Map();
|
|
1311
1311
|
this.remote = new Map(); // remote → (bookmark → changeId)
|
|
1312
|
+
this.tracking = {}; // bookmark → { remote, ref } tracking info (v0.35.0)
|
|
1312
1313
|
}
|
|
1313
1314
|
|
|
1314
1315
|
/**
|
|
@@ -1351,6 +1352,7 @@ class BookmarkStore {
|
|
|
1351
1352
|
|
|
1352
1353
|
this.local.clear();
|
|
1353
1354
|
this.remote.clear();
|
|
1355
|
+
this.tracking = data.tracked || {}; // Load tracking info (v0.35.0)
|
|
1354
1356
|
|
|
1355
1357
|
// Load local bookmarks
|
|
1356
1358
|
for (const [name, target] of Object.entries(data.local)) {
|
|
@@ -1388,7 +1390,7 @@ class BookmarkStore {
|
|
|
1388
1390
|
version: 1,
|
|
1389
1391
|
local,
|
|
1390
1392
|
remote,
|
|
1391
|
-
tracked: {},
|
|
1393
|
+
tracked: this.tracking || {}, // Save tracking info (v0.35.0)
|
|
1392
1394
|
});
|
|
1393
1395
|
}
|
|
1394
1396
|
|
|
@@ -1501,13 +1503,23 @@ class BookmarkStore {
|
|
|
1501
1503
|
async list() {
|
|
1502
1504
|
const result = [];
|
|
1503
1505
|
|
|
1504
|
-
// Add local bookmarks
|
|
1506
|
+
// Add local bookmarks with tracking info (v0.35.0)
|
|
1505
1507
|
for (const [name, target] of this.local.entries()) {
|
|
1506
|
-
|
|
1508
|
+
const bookmark = {
|
|
1507
1509
|
name,
|
|
1508
1510
|
changeId: target,
|
|
1509
1511
|
remote: null,
|
|
1510
|
-
}
|
|
1512
|
+
};
|
|
1513
|
+
|
|
1514
|
+
// Include tracking info if available
|
|
1515
|
+
if (this.tracking && this.tracking[name]) {
|
|
1516
|
+
bookmark.tracking = {
|
|
1517
|
+
remote: this.tracking[name].remote,
|
|
1518
|
+
ref: this.tracking[name].remoteName || name,
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
result.push(bookmark);
|
|
1511
1523
|
}
|
|
1512
1524
|
|
|
1513
1525
|
// Add remote bookmarks
|
|
@@ -1829,6 +1841,24 @@ class RevsetEngine {
|
|
|
1829
1841
|
}
|
|
1830
1842
|
}
|
|
1831
1843
|
|
|
1844
|
+
// v0.36.0: visible() - non-abandoned changes
|
|
1845
|
+
if (trimmed === 'visible()') {
|
|
1846
|
+
await this.graph.load();
|
|
1847
|
+
const all = this.graph.getAll();
|
|
1848
|
+
return all
|
|
1849
|
+
.filter(c => !c.abandoned)
|
|
1850
|
+
.map(c => c.changeId);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// v0.36.0: hidden() - abandoned changes
|
|
1854
|
+
if (trimmed === 'hidden()') {
|
|
1855
|
+
await this.graph.load();
|
|
1856
|
+
const all = this.graph.getAll();
|
|
1857
|
+
return all
|
|
1858
|
+
.filter(c => c.abandoned === true)
|
|
1859
|
+
.map(c => c.changeId);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1832
1862
|
// ancestors(changeId) - all ancestors including the change itself
|
|
1833
1863
|
const ancestorsMatch = trimmed.match(/^ancestors\(([0-9a-f]{32})\)$/);
|
|
1834
1864
|
if (ancestorsMatch) {
|
|
@@ -4276,9 +4306,15 @@ class UserConfig {
|
|
|
4276
4306
|
}
|
|
4277
4307
|
|
|
4278
4308
|
/**
|
|
4279
|
-
* Load config from storage
|
|
4309
|
+
* Load config from storage and/or programmatic object
|
|
4310
|
+
* v0.35.0: Also loads workspace-specific config from workspace-config.json
|
|
4311
|
+
* v0.36.0: Accepts optional config object for programmatic configuration
|
|
4312
|
+
*
|
|
4313
|
+
* @param {Object} [opts] - Optional config options
|
|
4314
|
+
* @param {Object} [opts.workspace] - Workspace-specific config to merge (highest priority)
|
|
4315
|
+
* @param {Object} [opts.override] - Config object to merge over loaded config
|
|
4280
4316
|
*/
|
|
4281
|
-
async load() {
|
|
4317
|
+
async load(opts = {}) {
|
|
4282
4318
|
try {
|
|
4283
4319
|
const data = await this.storage.read('config.json');
|
|
4284
4320
|
if (data) {
|
|
@@ -4288,6 +4324,45 @@ class UserConfig {
|
|
|
4288
4324
|
// No config file yet, use defaults
|
|
4289
4325
|
await this.init();
|
|
4290
4326
|
}
|
|
4327
|
+
|
|
4328
|
+
// Merge programmatic override config if provided
|
|
4329
|
+
if (opts.override) {
|
|
4330
|
+
this.config = this._deepMerge(this.config, opts.override);
|
|
4331
|
+
}
|
|
4332
|
+
|
|
4333
|
+
// Load workspace-specific config from file (v0.35.0)
|
|
4334
|
+
try {
|
|
4335
|
+
const workspaceData = await this.storage.read('workspace-config.json');
|
|
4336
|
+
if (workspaceData) {
|
|
4337
|
+
// Merge workspace config over global config (workspace takes precedence)
|
|
4338
|
+
this.config = this._deepMerge(this.config, workspaceData);
|
|
4339
|
+
}
|
|
4340
|
+
} catch (error) {
|
|
4341
|
+
// No workspace config, that's fine
|
|
4342
|
+
}
|
|
4343
|
+
|
|
4344
|
+
// Merge programmatic workspace config if provided (highest priority)
|
|
4345
|
+
if (opts.workspace) {
|
|
4346
|
+
this.config = this._deepMerge(this.config, opts.workspace);
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4350
|
+
/**
|
|
4351
|
+
* Deep merge two config objects (workspace overrides global)
|
|
4352
|
+
* @private
|
|
4353
|
+
*/
|
|
4354
|
+
_deepMerge(target, source) {
|
|
4355
|
+
const result = { ...target };
|
|
4356
|
+
|
|
4357
|
+
for (const key in source) {
|
|
4358
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
4359
|
+
result[key] = this._deepMerge(target[key] || {}, source[key]);
|
|
4360
|
+
} else {
|
|
4361
|
+
result[key] = source[key];
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
|
|
4365
|
+
return result;
|
|
4291
4366
|
}
|
|
4292
4367
|
|
|
4293
4368
|
/**
|
|
@@ -4391,11 +4466,17 @@ class UserConfig {
|
|
|
4391
4466
|
const __filename$3 = fileURLToPath(import.meta.url);
|
|
4392
4467
|
const __dirname$3 = path.dirname(__filename$3);
|
|
4393
4468
|
|
|
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');
|
|
4471
|
+
const protoDir$3 = inDist$3
|
|
4472
|
+
? path.join(__dirname$3, '..', 'src', 'protos')
|
|
4473
|
+
: path.join(__dirname$3, '..', 'protos');
|
|
4474
|
+
|
|
4394
4475
|
class JJCheckout {
|
|
4395
4476
|
constructor(fs, dir) {
|
|
4396
4477
|
this.fs = fs;
|
|
4397
4478
|
this.dir = dir;
|
|
4398
|
-
this.protoPath = path.join(
|
|
4479
|
+
this.protoPath = path.join(protoDir$3, 'local_working_copy.proto');
|
|
4399
4480
|
}
|
|
4400
4481
|
|
|
4401
4482
|
/**
|
|
@@ -4467,11 +4548,17 @@ class JJCheckout {
|
|
|
4467
4548
|
const __filename$2 = fileURLToPath(import.meta.url);
|
|
4468
4549
|
const __dirname$2 = path.dirname(__filename$2);
|
|
4469
4550
|
|
|
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');
|
|
4553
|
+
const protoDir$2 = inDist$2
|
|
4554
|
+
? path.join(__dirname$2, '..', 'src', 'protos')
|
|
4555
|
+
: path.join(__dirname$2, '..', 'protos');
|
|
4556
|
+
|
|
4470
4557
|
class JJTreeState {
|
|
4471
4558
|
constructor(fs, dir) {
|
|
4472
4559
|
this.fs = fs;
|
|
4473
4560
|
this.dir = dir;
|
|
4474
|
-
this.protoPath = path.join(
|
|
4561
|
+
this.protoPath = path.join(protoDir$2, 'local_working_copy.proto');
|
|
4475
4562
|
}
|
|
4476
4563
|
|
|
4477
4564
|
/**
|
|
@@ -4559,11 +4646,17 @@ class JJTreeState {
|
|
|
4559
4646
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
4560
4647
|
const __dirname$1 = path.dirname(__filename$1);
|
|
4561
4648
|
|
|
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');
|
|
4651
|
+
const protoDir$1 = inDist$1
|
|
4652
|
+
? path.join(__dirname$1, '..', 'src', 'protos')
|
|
4653
|
+
: path.join(__dirname$1, '..', 'protos');
|
|
4654
|
+
|
|
4562
4655
|
class JJOperationStore {
|
|
4563
4656
|
constructor(fs, dir) {
|
|
4564
4657
|
this.fs = fs;
|
|
4565
4658
|
this.dir = dir;
|
|
4566
|
-
this.protoPath = path.join(
|
|
4659
|
+
this.protoPath = path.join(protoDir$1, 'simple_op_store.proto');
|
|
4567
4660
|
}
|
|
4568
4661
|
|
|
4569
4662
|
/**
|
|
@@ -4677,11 +4770,17 @@ class JJOperationStore {
|
|
|
4677
4770
|
const __filename = fileURLToPath(import.meta.url);
|
|
4678
4771
|
const __dirname = path.dirname(__filename);
|
|
4679
4772
|
|
|
4773
|
+
// Determine proto path based on whether we're running from dist or src
|
|
4774
|
+
const inDist = __dirname.includes('/dist') || __dirname.includes('\\dist');
|
|
4775
|
+
const protoDir = inDist
|
|
4776
|
+
? path.join(__dirname, '..', 'src', 'protos')
|
|
4777
|
+
: path.join(__dirname, '..', 'protos');
|
|
4778
|
+
|
|
4680
4779
|
class JJViewStore {
|
|
4681
4780
|
constructor(fs, dir) {
|
|
4682
4781
|
this.fs = fs;
|
|
4683
4782
|
this.dir = dir;
|
|
4684
|
-
this.protoPath = path.join(
|
|
4783
|
+
this.protoPath = path.join(protoDir, 'simple_op_store.proto');
|
|
4685
4784
|
}
|
|
4686
4785
|
|
|
4687
4786
|
/**
|
|
@@ -6990,7 +7089,8 @@ async function createJJ(options) {
|
|
|
6990
7089
|
* @param {Object} args - Arguments
|
|
6991
7090
|
* @param {string} [args.revset='all()'] - Revset expression to filter changes
|
|
6992
7091
|
* @param {number} [args.limit] - Maximum number of changes to return
|
|
6993
|
-
* @
|
|
7092
|
+
* @param {boolean} [args.count=false] - If true, return count instead of changes (v0.36.0)
|
|
7093
|
+
* @returns {Promise<Array|number>} Array of changes or count if args.count is true
|
|
6994
7094
|
*/
|
|
6995
7095
|
async log(args = {}) {
|
|
6996
7096
|
await graph.load();
|
|
@@ -6999,6 +7099,11 @@ async function createJJ(options) {
|
|
|
6999
7099
|
const revsetExpr = args.revset || 'all()';
|
|
7000
7100
|
const changeIds = await revset.evaluate(revsetExpr);
|
|
7001
7101
|
|
|
7102
|
+
// If count mode, return count early (v0.36.0 feature)
|
|
7103
|
+
if (args.count) {
|
|
7104
|
+
return changeIds.length;
|
|
7105
|
+
}
|
|
7106
|
+
|
|
7002
7107
|
let changes = [];
|
|
7003
7108
|
for (const changeId of changeIds) {
|
|
7004
7109
|
const change = await graph.getChange(changeId);
|
|
@@ -8019,9 +8124,18 @@ async function createJJ(options) {
|
|
|
8019
8124
|
* @returns {Promise<Object>} The new backout change including changeId, description, and backedOut field
|
|
8020
8125
|
*/
|
|
8021
8126
|
async backout(args) {
|
|
8127
|
+
// Normalize: accept common parameter aliases
|
|
8128
|
+
if (args && args.change && !args.revision) {
|
|
8129
|
+
args = { ...args, revision: args.change };
|
|
8130
|
+
} else if (args && args.changeId && !args.revision) {
|
|
8131
|
+
args = { ...args, revision: args.changeId };
|
|
8132
|
+
} else if (args && args.target && !args.revision) {
|
|
8133
|
+
args = { ...args, revision: args.target };
|
|
8134
|
+
}
|
|
8135
|
+
|
|
8022
8136
|
if (!args || !args.revision) {
|
|
8023
8137
|
throw new JJError('INVALID_ARGUMENT', 'Missing revision argument', {
|
|
8024
|
-
suggestion: 'Provide the change ID to back out: { revision: "abc123..." }',
|
|
8138
|
+
suggestion: 'Provide the change ID to back out: { revision: "abc123..." } (or use change/changeId/target)',
|
|
8025
8139
|
});
|
|
8026
8140
|
}
|
|
8027
8141
|
|
|
@@ -9056,6 +9170,15 @@ async function createJJ(options) {
|
|
|
9056
9170
|
* @param {string[]} [args.paths] - Specific paths to include in first split (full implementation planned for future)
|
|
9057
9171
|
*/
|
|
9058
9172
|
async split(args) {
|
|
9173
|
+
// Normalize: accept common parameter aliases
|
|
9174
|
+
if (args && args.change && !args.changeId) {
|
|
9175
|
+
args = { ...args, changeId: args.change };
|
|
9176
|
+
} else if (args && args.revision && !args.changeId) {
|
|
9177
|
+
args = { ...args, changeId: args.revision };
|
|
9178
|
+
} else if (args && args.target && !args.changeId) {
|
|
9179
|
+
args = { ...args, changeId: args.target };
|
|
9180
|
+
}
|
|
9181
|
+
|
|
9059
9182
|
// Check for interactive mode (not supported)
|
|
9060
9183
|
if (args.interactive) {
|
|
9061
9184
|
throw new JJError(
|
|
@@ -10767,9 +10890,21 @@ async function createJJ(options) {
|
|
|
10767
10890
|
* @returns {Promise<Object>} Bookmark info
|
|
10768
10891
|
*/
|
|
10769
10892
|
async set(args) {
|
|
10893
|
+
// Normalize: accept common parameter aliases
|
|
10894
|
+
if (args) {
|
|
10895
|
+
if (args.target && !args.changeId) {
|
|
10896
|
+
args = { ...args, changeId: args.target };
|
|
10897
|
+
} else if (args.change && !args.changeId) {
|
|
10898
|
+
args = { ...args, changeId: args.change };
|
|
10899
|
+
} else if (args.revision && !args.changeId) {
|
|
10900
|
+
args = { ...args, changeId: args.revision };
|
|
10901
|
+
}
|
|
10902
|
+
}
|
|
10903
|
+
|
|
10770
10904
|
if (!args || !args.name || !args.changeId) {
|
|
10771
10905
|
throw new JJError('INVALID_ARGUMENT', 'Missing name or changeId', {
|
|
10772
|
-
|
|
10906
|
+
args,
|
|
10907
|
+
suggestion: 'Provide both: { name: "main", changeId: "abc123..." } (or use target/change/revision instead of changeId)',
|
|
10773
10908
|
});
|
|
10774
10909
|
}
|
|
10775
10910
|
|
|
@@ -10805,6 +10940,17 @@ async function createJJ(options) {
|
|
|
10805
10940
|
* @returns {Promise<Object>} Bookmark info
|
|
10806
10941
|
*/
|
|
10807
10942
|
async create(args) {
|
|
10943
|
+
// Normalize: accept common parameter aliases
|
|
10944
|
+
if (args) {
|
|
10945
|
+
if (args.target && !args.changeId) {
|
|
10946
|
+
args = { ...args, changeId: args.target };
|
|
10947
|
+
} else if (args.change && !args.changeId) {
|
|
10948
|
+
args = { ...args, changeId: args.change };
|
|
10949
|
+
} else if (args.revision && !args.changeId) {
|
|
10950
|
+
args = { ...args, changeId: args.revision };
|
|
10951
|
+
}
|
|
10952
|
+
}
|
|
10953
|
+
|
|
10808
10954
|
if (!args || !args.name) {
|
|
10809
10955
|
throw new JJError('INVALID_ARGUMENT', 'Missing name argument', {
|
|
10810
10956
|
suggestion: 'Provide: { name: "main", changeId: "abc123..." } or { name: "main" } for working copy',
|
|
@@ -10857,9 +11003,23 @@ async function createJJ(options) {
|
|
|
10857
11003
|
* @returns {Promise<Object>} Move result
|
|
10858
11004
|
*/
|
|
10859
11005
|
async move(args) {
|
|
11006
|
+
// Normalize: accept common parameter aliases
|
|
11007
|
+
if (args) {
|
|
11008
|
+
if (args.target && !args.to) {
|
|
11009
|
+
args = { ...args, to: args.target };
|
|
11010
|
+
} else if (args.changeId && !args.to) {
|
|
11011
|
+
args = { ...args, to: args.changeId };
|
|
11012
|
+
} else if (args.change && !args.to) {
|
|
11013
|
+
args = { ...args, to: args.change };
|
|
11014
|
+
} else if (args.revision && !args.to) {
|
|
11015
|
+
args = { ...args, to: args.revision };
|
|
11016
|
+
}
|
|
11017
|
+
}
|
|
11018
|
+
|
|
10860
11019
|
if (!args || !args.name || !args.to) {
|
|
10861
11020
|
throw new JJError('INVALID_ARGUMENT', 'Missing name or to', {
|
|
10862
|
-
|
|
11021
|
+
args,
|
|
11022
|
+
suggestion: 'Provide both: { name: "main", to: "abc123..." } (or use target/changeId/change/revision instead of to)',
|
|
10863
11023
|
});
|
|
10864
11024
|
}
|
|
10865
11025
|
|
|
@@ -11260,21 +11420,13 @@ async function createJJ(options) {
|
|
|
11260
11420
|
});
|
|
11261
11421
|
}
|
|
11262
11422
|
|
|
11263
|
-
|
|
11264
|
-
|
|
11265
|
-
|
|
11266
|
-
const parts = key.split('.');
|
|
11267
|
-
let value = userConfig.config;
|
|
11268
|
-
|
|
11269
|
-
for (const part of parts) {
|
|
11270
|
-
if (value && typeof value === 'object') {
|
|
11271
|
-
value = value[part];
|
|
11272
|
-
} else {
|
|
11273
|
-
return null;
|
|
11274
|
-
}
|
|
11423
|
+
// Ensure config is loaded at least once
|
|
11424
|
+
if (!userConfig.config) {
|
|
11425
|
+
await userConfig.load();
|
|
11275
11426
|
}
|
|
11276
11427
|
|
|
11277
|
-
|
|
11428
|
+
// Use userConfig.get() to preserve programmatic config
|
|
11429
|
+
return userConfig.get(key);
|
|
11278
11430
|
},
|
|
11279
11431
|
|
|
11280
11432
|
/**
|
|
@@ -11323,6 +11475,87 @@ async function createJJ(options) {
|
|
|
11323
11475
|
await userConfig.load();
|
|
11324
11476
|
return userConfig.config;
|
|
11325
11477
|
},
|
|
11478
|
+
|
|
11479
|
+
/**
|
|
11480
|
+
* Reload configuration (including workspace-specific config)
|
|
11481
|
+
* v0.35.0: Loads workspace-config.json if present
|
|
11482
|
+
* v0.36.0: Accepts optional config object for programmatic configuration
|
|
11483
|
+
*
|
|
11484
|
+
* @param {Object} [opts] - Optional config options
|
|
11485
|
+
* @param {Object} [opts.workspace] - Workspace-specific config to merge (highest priority)
|
|
11486
|
+
* @param {Object} [opts.override] - Config object to merge over loaded config
|
|
11487
|
+
* @returns {Promise<void>}
|
|
11488
|
+
*
|
|
11489
|
+
* @example
|
|
11490
|
+
* // Load from files only
|
|
11491
|
+
* await repo.config.load();
|
|
11492
|
+
*
|
|
11493
|
+
* @example
|
|
11494
|
+
* // Programmatic workspace config (no file I/O needed)
|
|
11495
|
+
* await repo.config.load({
|
|
11496
|
+
* workspace: { user: { email: 'test@example.com' } }
|
|
11497
|
+
* });
|
|
11498
|
+
*
|
|
11499
|
+
* @example
|
|
11500
|
+
* // Override global config programmatically
|
|
11501
|
+
* await repo.config.load({
|
|
11502
|
+
* override: { test: { value: 'override' } }
|
|
11503
|
+
* });
|
|
11504
|
+
*/
|
|
11505
|
+
async load(opts) {
|
|
11506
|
+
await userConfig.load(opts);
|
|
11507
|
+
},
|
|
11508
|
+
},
|
|
11509
|
+
|
|
11510
|
+
/**
|
|
11511
|
+
* Template helper functions (v0.36.0)
|
|
11512
|
+
*
|
|
11513
|
+
* Utilities for displaying and formatting JJ data
|
|
11514
|
+
*/
|
|
11515
|
+
template: {
|
|
11516
|
+
/**
|
|
11517
|
+
* Get files modified in a change
|
|
11518
|
+
*
|
|
11519
|
+
* @param {string} changeId - Change ID
|
|
11520
|
+
* @returns {Promise<string[]>} Array of file paths
|
|
11521
|
+
*/
|
|
11522
|
+
async files(changeId) {
|
|
11523
|
+
await graph.load();
|
|
11524
|
+
const change = await graph.getChange(changeId);
|
|
11525
|
+
if (!change || !change.fileSnapshot) {
|
|
11526
|
+
return [];
|
|
11527
|
+
}
|
|
11528
|
+
return Object.keys(change.fileSnapshot);
|
|
11529
|
+
},
|
|
11530
|
+
|
|
11531
|
+
/**
|
|
11532
|
+
* Join array elements with a separator
|
|
11533
|
+
*
|
|
11534
|
+
* @param {string[]} array - Array of strings
|
|
11535
|
+
* @param {string} separator - Separator string
|
|
11536
|
+
* @returns {string} Joined string
|
|
11537
|
+
*/
|
|
11538
|
+
join(array, separator) {
|
|
11539
|
+
if (!Array.isArray(array)) {
|
|
11540
|
+
return '';
|
|
11541
|
+
}
|
|
11542
|
+
return array.join(separator);
|
|
11543
|
+
},
|
|
11544
|
+
|
|
11545
|
+
/**
|
|
11546
|
+
* Format path for display
|
|
11547
|
+
*
|
|
11548
|
+
* @param {string} path - File path
|
|
11549
|
+
* @returns {string} Formatted path
|
|
11550
|
+
*/
|
|
11551
|
+
format_path(path) {
|
|
11552
|
+
if (!path) {
|
|
11553
|
+
return '';
|
|
11554
|
+
}
|
|
11555
|
+
// Simple formatting: just return the path as-is for now
|
|
11556
|
+
// In real JJ, this might apply colors, truncation, etc.
|
|
11557
|
+
return path;
|
|
11558
|
+
},
|
|
11326
11559
|
},
|
|
11327
11560
|
|
|
11328
11561
|
/**
|