@saga-ai/cli 0.1.3 → 0.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 +21 -0
- package/dist/cli.cjs +1597 -50
- package/package.json +12 -12
package/dist/cli.cjs
CHANGED
|
@@ -3037,8 +3037,8 @@ var {
|
|
|
3037
3037
|
} = import_index.default;
|
|
3038
3038
|
|
|
3039
3039
|
// src/cli.ts
|
|
3040
|
-
var
|
|
3041
|
-
var
|
|
3040
|
+
var import_node_path5 = require("node:path");
|
|
3041
|
+
var import_node_fs5 = require("node:fs");
|
|
3042
3042
|
|
|
3043
3043
|
// src/commands/init.ts
|
|
3044
3044
|
var import_node_path2 = require("node:path");
|
|
@@ -3206,8 +3206,1546 @@ async function initCommand(options) {
|
|
|
3206
3206
|
|
|
3207
3207
|
// src/commands/implement.ts
|
|
3208
3208
|
var import_node_child_process = require("node:child_process");
|
|
3209
|
-
var
|
|
3209
|
+
var import_node_path4 = require("node:path");
|
|
3210
|
+
var import_node_fs4 = require("node:fs");
|
|
3211
|
+
|
|
3212
|
+
// src/utils/finder.ts
|
|
3210
3213
|
var import_node_fs3 = require("node:fs");
|
|
3214
|
+
var import_node_path3 = require("node:path");
|
|
3215
|
+
|
|
3216
|
+
// node_modules/.pnpm/fuse.js@7.1.0/node_modules/fuse.js/dist/fuse.mjs
|
|
3217
|
+
function isArray(value) {
|
|
3218
|
+
return !Array.isArray ? getTag(value) === "[object Array]" : Array.isArray(value);
|
|
3219
|
+
}
|
|
3220
|
+
var INFINITY = 1 / 0;
|
|
3221
|
+
function baseToString(value) {
|
|
3222
|
+
if (typeof value == "string") {
|
|
3223
|
+
return value;
|
|
3224
|
+
}
|
|
3225
|
+
let result = value + "";
|
|
3226
|
+
return result == "0" && 1 / value == -INFINITY ? "-0" : result;
|
|
3227
|
+
}
|
|
3228
|
+
function toString(value) {
|
|
3229
|
+
return value == null ? "" : baseToString(value);
|
|
3230
|
+
}
|
|
3231
|
+
function isString(value) {
|
|
3232
|
+
return typeof value === "string";
|
|
3233
|
+
}
|
|
3234
|
+
function isNumber(value) {
|
|
3235
|
+
return typeof value === "number";
|
|
3236
|
+
}
|
|
3237
|
+
function isBoolean(value) {
|
|
3238
|
+
return value === true || value === false || isObjectLike(value) && getTag(value) == "[object Boolean]";
|
|
3239
|
+
}
|
|
3240
|
+
function isObject(value) {
|
|
3241
|
+
return typeof value === "object";
|
|
3242
|
+
}
|
|
3243
|
+
function isObjectLike(value) {
|
|
3244
|
+
return isObject(value) && value !== null;
|
|
3245
|
+
}
|
|
3246
|
+
function isDefined(value) {
|
|
3247
|
+
return value !== void 0 && value !== null;
|
|
3248
|
+
}
|
|
3249
|
+
function isBlank(value) {
|
|
3250
|
+
return !value.trim().length;
|
|
3251
|
+
}
|
|
3252
|
+
function getTag(value) {
|
|
3253
|
+
return value == null ? value === void 0 ? "[object Undefined]" : "[object Null]" : Object.prototype.toString.call(value);
|
|
3254
|
+
}
|
|
3255
|
+
var INCORRECT_INDEX_TYPE = "Incorrect 'index' type";
|
|
3256
|
+
var LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = (key) => `Invalid value for key ${key}`;
|
|
3257
|
+
var PATTERN_LENGTH_TOO_LARGE = (max) => `Pattern length exceeds max of ${max}.`;
|
|
3258
|
+
var MISSING_KEY_PROPERTY = (name) => `Missing ${name} property in key`;
|
|
3259
|
+
var INVALID_KEY_WEIGHT_VALUE = (key) => `Property 'weight' in key '${key}' must be a positive integer`;
|
|
3260
|
+
var hasOwn = Object.prototype.hasOwnProperty;
|
|
3261
|
+
var KeyStore = class {
|
|
3262
|
+
constructor(keys) {
|
|
3263
|
+
this._keys = [];
|
|
3264
|
+
this._keyMap = {};
|
|
3265
|
+
let totalWeight = 0;
|
|
3266
|
+
keys.forEach((key) => {
|
|
3267
|
+
let obj = createKey(key);
|
|
3268
|
+
this._keys.push(obj);
|
|
3269
|
+
this._keyMap[obj.id] = obj;
|
|
3270
|
+
totalWeight += obj.weight;
|
|
3271
|
+
});
|
|
3272
|
+
this._keys.forEach((key) => {
|
|
3273
|
+
key.weight /= totalWeight;
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
get(keyId) {
|
|
3277
|
+
return this._keyMap[keyId];
|
|
3278
|
+
}
|
|
3279
|
+
keys() {
|
|
3280
|
+
return this._keys;
|
|
3281
|
+
}
|
|
3282
|
+
toJSON() {
|
|
3283
|
+
return JSON.stringify(this._keys);
|
|
3284
|
+
}
|
|
3285
|
+
};
|
|
3286
|
+
function createKey(key) {
|
|
3287
|
+
let path = null;
|
|
3288
|
+
let id = null;
|
|
3289
|
+
let src = null;
|
|
3290
|
+
let weight = 1;
|
|
3291
|
+
let getFn = null;
|
|
3292
|
+
if (isString(key) || isArray(key)) {
|
|
3293
|
+
src = key;
|
|
3294
|
+
path = createKeyPath(key);
|
|
3295
|
+
id = createKeyId(key);
|
|
3296
|
+
} else {
|
|
3297
|
+
if (!hasOwn.call(key, "name")) {
|
|
3298
|
+
throw new Error(MISSING_KEY_PROPERTY("name"));
|
|
3299
|
+
}
|
|
3300
|
+
const name = key.name;
|
|
3301
|
+
src = name;
|
|
3302
|
+
if (hasOwn.call(key, "weight")) {
|
|
3303
|
+
weight = key.weight;
|
|
3304
|
+
if (weight <= 0) {
|
|
3305
|
+
throw new Error(INVALID_KEY_WEIGHT_VALUE(name));
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
path = createKeyPath(name);
|
|
3309
|
+
id = createKeyId(name);
|
|
3310
|
+
getFn = key.getFn;
|
|
3311
|
+
}
|
|
3312
|
+
return { path, id, weight, src, getFn };
|
|
3313
|
+
}
|
|
3314
|
+
function createKeyPath(key) {
|
|
3315
|
+
return isArray(key) ? key : key.split(".");
|
|
3316
|
+
}
|
|
3317
|
+
function createKeyId(key) {
|
|
3318
|
+
return isArray(key) ? key.join(".") : key;
|
|
3319
|
+
}
|
|
3320
|
+
function get(obj, path) {
|
|
3321
|
+
let list = [];
|
|
3322
|
+
let arr = false;
|
|
3323
|
+
const deepGet = (obj2, path2, index) => {
|
|
3324
|
+
if (!isDefined(obj2)) {
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3327
|
+
if (!path2[index]) {
|
|
3328
|
+
list.push(obj2);
|
|
3329
|
+
} else {
|
|
3330
|
+
let key = path2[index];
|
|
3331
|
+
const value = obj2[key];
|
|
3332
|
+
if (!isDefined(value)) {
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3335
|
+
if (index === path2.length - 1 && (isString(value) || isNumber(value) || isBoolean(value))) {
|
|
3336
|
+
list.push(toString(value));
|
|
3337
|
+
} else if (isArray(value)) {
|
|
3338
|
+
arr = true;
|
|
3339
|
+
for (let i = 0, len = value.length; i < len; i += 1) {
|
|
3340
|
+
deepGet(value[i], path2, index + 1);
|
|
3341
|
+
}
|
|
3342
|
+
} else if (path2.length) {
|
|
3343
|
+
deepGet(value, path2, index + 1);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
};
|
|
3347
|
+
deepGet(obj, isString(path) ? path.split(".") : path, 0);
|
|
3348
|
+
return arr ? list : list[0];
|
|
3349
|
+
}
|
|
3350
|
+
var MatchOptions = {
|
|
3351
|
+
// Whether the matches should be included in the result set. When `true`, each record in the result
|
|
3352
|
+
// set will include the indices of the matched characters.
|
|
3353
|
+
// These can consequently be used for highlighting purposes.
|
|
3354
|
+
includeMatches: false,
|
|
3355
|
+
// When `true`, the matching function will continue to the end of a search pattern even if
|
|
3356
|
+
// a perfect match has already been located in the string.
|
|
3357
|
+
findAllMatches: false,
|
|
3358
|
+
// Minimum number of characters that must be matched before a result is considered a match
|
|
3359
|
+
minMatchCharLength: 1
|
|
3360
|
+
};
|
|
3361
|
+
var BasicOptions = {
|
|
3362
|
+
// When `true`, the algorithm continues searching to the end of the input even if a perfect
|
|
3363
|
+
// match is found before the end of the same input.
|
|
3364
|
+
isCaseSensitive: false,
|
|
3365
|
+
// When `true`, the algorithm will ignore diacritics (accents) in comparisons
|
|
3366
|
+
ignoreDiacritics: false,
|
|
3367
|
+
// When true, the matching function will continue to the end of a search pattern even if
|
|
3368
|
+
includeScore: false,
|
|
3369
|
+
// List of properties that will be searched. This also supports nested properties.
|
|
3370
|
+
keys: [],
|
|
3371
|
+
// Whether to sort the result list, by score
|
|
3372
|
+
shouldSort: true,
|
|
3373
|
+
// Default sort function: sort by ascending score, ascending index
|
|
3374
|
+
sortFn: (a, b) => a.score === b.score ? a.idx < b.idx ? -1 : 1 : a.score < b.score ? -1 : 1
|
|
3375
|
+
};
|
|
3376
|
+
var FuzzyOptions = {
|
|
3377
|
+
// Approximately where in the text is the pattern expected to be found?
|
|
3378
|
+
location: 0,
|
|
3379
|
+
// At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match
|
|
3380
|
+
// (of both letters and location), a threshold of '1.0' would match anything.
|
|
3381
|
+
threshold: 0.6,
|
|
3382
|
+
// Determines how close the match must be to the fuzzy location (specified above).
|
|
3383
|
+
// An exact letter match which is 'distance' characters away from the fuzzy location
|
|
3384
|
+
// would score as a complete mismatch. A distance of '0' requires the match be at
|
|
3385
|
+
// the exact location specified, a threshold of '1000' would require a perfect match
|
|
3386
|
+
// to be within 800 characters of the fuzzy location to be found using a 0.8 threshold.
|
|
3387
|
+
distance: 100
|
|
3388
|
+
};
|
|
3389
|
+
var AdvancedOptions = {
|
|
3390
|
+
// When `true`, it enables the use of unix-like search commands
|
|
3391
|
+
useExtendedSearch: false,
|
|
3392
|
+
// The get function to use when fetching an object's properties.
|
|
3393
|
+
// The default will search nested paths *ie foo.bar.baz*
|
|
3394
|
+
getFn: get,
|
|
3395
|
+
// When `true`, search will ignore `location` and `distance`, so it won't matter
|
|
3396
|
+
// where in the string the pattern appears.
|
|
3397
|
+
// More info: https://fusejs.io/concepts/scoring-theory.html#fuzziness-score
|
|
3398
|
+
ignoreLocation: false,
|
|
3399
|
+
// When `true`, the calculation for the relevance score (used for sorting) will
|
|
3400
|
+
// ignore the field-length norm.
|
|
3401
|
+
// More info: https://fusejs.io/concepts/scoring-theory.html#field-length-norm
|
|
3402
|
+
ignoreFieldNorm: false,
|
|
3403
|
+
// The weight to determine how much field length norm effects scoring.
|
|
3404
|
+
fieldNormWeight: 1
|
|
3405
|
+
};
|
|
3406
|
+
var Config = {
|
|
3407
|
+
...BasicOptions,
|
|
3408
|
+
...MatchOptions,
|
|
3409
|
+
...FuzzyOptions,
|
|
3410
|
+
...AdvancedOptions
|
|
3411
|
+
};
|
|
3412
|
+
var SPACE = /[^ ]+/g;
|
|
3413
|
+
function norm(weight = 1, mantissa = 3) {
|
|
3414
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3415
|
+
const m = Math.pow(10, mantissa);
|
|
3416
|
+
return {
|
|
3417
|
+
get(value) {
|
|
3418
|
+
const numTokens = value.match(SPACE).length;
|
|
3419
|
+
if (cache.has(numTokens)) {
|
|
3420
|
+
return cache.get(numTokens);
|
|
3421
|
+
}
|
|
3422
|
+
const norm2 = 1 / Math.pow(numTokens, 0.5 * weight);
|
|
3423
|
+
const n = parseFloat(Math.round(norm2 * m) / m);
|
|
3424
|
+
cache.set(numTokens, n);
|
|
3425
|
+
return n;
|
|
3426
|
+
},
|
|
3427
|
+
clear() {
|
|
3428
|
+
cache.clear();
|
|
3429
|
+
}
|
|
3430
|
+
};
|
|
3431
|
+
}
|
|
3432
|
+
var FuseIndex = class {
|
|
3433
|
+
constructor({
|
|
3434
|
+
getFn = Config.getFn,
|
|
3435
|
+
fieldNormWeight = Config.fieldNormWeight
|
|
3436
|
+
} = {}) {
|
|
3437
|
+
this.norm = norm(fieldNormWeight, 3);
|
|
3438
|
+
this.getFn = getFn;
|
|
3439
|
+
this.isCreated = false;
|
|
3440
|
+
this.setIndexRecords();
|
|
3441
|
+
}
|
|
3442
|
+
setSources(docs = []) {
|
|
3443
|
+
this.docs = docs;
|
|
3444
|
+
}
|
|
3445
|
+
setIndexRecords(records = []) {
|
|
3446
|
+
this.records = records;
|
|
3447
|
+
}
|
|
3448
|
+
setKeys(keys = []) {
|
|
3449
|
+
this.keys = keys;
|
|
3450
|
+
this._keysMap = {};
|
|
3451
|
+
keys.forEach((key, idx) => {
|
|
3452
|
+
this._keysMap[key.id] = idx;
|
|
3453
|
+
});
|
|
3454
|
+
}
|
|
3455
|
+
create() {
|
|
3456
|
+
if (this.isCreated || !this.docs.length) {
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
this.isCreated = true;
|
|
3460
|
+
if (isString(this.docs[0])) {
|
|
3461
|
+
this.docs.forEach((doc, docIndex) => {
|
|
3462
|
+
this._addString(doc, docIndex);
|
|
3463
|
+
});
|
|
3464
|
+
} else {
|
|
3465
|
+
this.docs.forEach((doc, docIndex) => {
|
|
3466
|
+
this._addObject(doc, docIndex);
|
|
3467
|
+
});
|
|
3468
|
+
}
|
|
3469
|
+
this.norm.clear();
|
|
3470
|
+
}
|
|
3471
|
+
// Adds a doc to the end of the index
|
|
3472
|
+
add(doc) {
|
|
3473
|
+
const idx = this.size();
|
|
3474
|
+
if (isString(doc)) {
|
|
3475
|
+
this._addString(doc, idx);
|
|
3476
|
+
} else {
|
|
3477
|
+
this._addObject(doc, idx);
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
// Removes the doc at the specified index of the index
|
|
3481
|
+
removeAt(idx) {
|
|
3482
|
+
this.records.splice(idx, 1);
|
|
3483
|
+
for (let i = idx, len = this.size(); i < len; i += 1) {
|
|
3484
|
+
this.records[i].i -= 1;
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
getValueForItemAtKeyId(item, keyId) {
|
|
3488
|
+
return item[this._keysMap[keyId]];
|
|
3489
|
+
}
|
|
3490
|
+
size() {
|
|
3491
|
+
return this.records.length;
|
|
3492
|
+
}
|
|
3493
|
+
_addString(doc, docIndex) {
|
|
3494
|
+
if (!isDefined(doc) || isBlank(doc)) {
|
|
3495
|
+
return;
|
|
3496
|
+
}
|
|
3497
|
+
let record = {
|
|
3498
|
+
v: doc,
|
|
3499
|
+
i: docIndex,
|
|
3500
|
+
n: this.norm.get(doc)
|
|
3501
|
+
};
|
|
3502
|
+
this.records.push(record);
|
|
3503
|
+
}
|
|
3504
|
+
_addObject(doc, docIndex) {
|
|
3505
|
+
let record = { i: docIndex, $: {} };
|
|
3506
|
+
this.keys.forEach((key, keyIndex) => {
|
|
3507
|
+
let value = key.getFn ? key.getFn(doc) : this.getFn(doc, key.path);
|
|
3508
|
+
if (!isDefined(value)) {
|
|
3509
|
+
return;
|
|
3510
|
+
}
|
|
3511
|
+
if (isArray(value)) {
|
|
3512
|
+
let subRecords = [];
|
|
3513
|
+
const stack = [{ nestedArrIndex: -1, value }];
|
|
3514
|
+
while (stack.length) {
|
|
3515
|
+
const { nestedArrIndex, value: value2 } = stack.pop();
|
|
3516
|
+
if (!isDefined(value2)) {
|
|
3517
|
+
continue;
|
|
3518
|
+
}
|
|
3519
|
+
if (isString(value2) && !isBlank(value2)) {
|
|
3520
|
+
let subRecord = {
|
|
3521
|
+
v: value2,
|
|
3522
|
+
i: nestedArrIndex,
|
|
3523
|
+
n: this.norm.get(value2)
|
|
3524
|
+
};
|
|
3525
|
+
subRecords.push(subRecord);
|
|
3526
|
+
} else if (isArray(value2)) {
|
|
3527
|
+
value2.forEach((item, k) => {
|
|
3528
|
+
stack.push({
|
|
3529
|
+
nestedArrIndex: k,
|
|
3530
|
+
value: item
|
|
3531
|
+
});
|
|
3532
|
+
});
|
|
3533
|
+
} else ;
|
|
3534
|
+
}
|
|
3535
|
+
record.$[keyIndex] = subRecords;
|
|
3536
|
+
} else if (isString(value) && !isBlank(value)) {
|
|
3537
|
+
let subRecord = {
|
|
3538
|
+
v: value,
|
|
3539
|
+
n: this.norm.get(value)
|
|
3540
|
+
};
|
|
3541
|
+
record.$[keyIndex] = subRecord;
|
|
3542
|
+
}
|
|
3543
|
+
});
|
|
3544
|
+
this.records.push(record);
|
|
3545
|
+
}
|
|
3546
|
+
toJSON() {
|
|
3547
|
+
return {
|
|
3548
|
+
keys: this.keys,
|
|
3549
|
+
records: this.records
|
|
3550
|
+
};
|
|
3551
|
+
}
|
|
3552
|
+
};
|
|
3553
|
+
function createIndex(keys, docs, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) {
|
|
3554
|
+
const myIndex = new FuseIndex({ getFn, fieldNormWeight });
|
|
3555
|
+
myIndex.setKeys(keys.map(createKey));
|
|
3556
|
+
myIndex.setSources(docs);
|
|
3557
|
+
myIndex.create();
|
|
3558
|
+
return myIndex;
|
|
3559
|
+
}
|
|
3560
|
+
function parseIndex(data, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) {
|
|
3561
|
+
const { keys, records } = data;
|
|
3562
|
+
const myIndex = new FuseIndex({ getFn, fieldNormWeight });
|
|
3563
|
+
myIndex.setKeys(keys);
|
|
3564
|
+
myIndex.setIndexRecords(records);
|
|
3565
|
+
return myIndex;
|
|
3566
|
+
}
|
|
3567
|
+
function computeScore$1(pattern, {
|
|
3568
|
+
errors = 0,
|
|
3569
|
+
currentLocation = 0,
|
|
3570
|
+
expectedLocation = 0,
|
|
3571
|
+
distance = Config.distance,
|
|
3572
|
+
ignoreLocation = Config.ignoreLocation
|
|
3573
|
+
} = {}) {
|
|
3574
|
+
const accuracy = errors / pattern.length;
|
|
3575
|
+
if (ignoreLocation) {
|
|
3576
|
+
return accuracy;
|
|
3577
|
+
}
|
|
3578
|
+
const proximity = Math.abs(expectedLocation - currentLocation);
|
|
3579
|
+
if (!distance) {
|
|
3580
|
+
return proximity ? 1 : accuracy;
|
|
3581
|
+
}
|
|
3582
|
+
return accuracy + proximity / distance;
|
|
3583
|
+
}
|
|
3584
|
+
function convertMaskToIndices(matchmask = [], minMatchCharLength = Config.minMatchCharLength) {
|
|
3585
|
+
let indices = [];
|
|
3586
|
+
let start = -1;
|
|
3587
|
+
let end = -1;
|
|
3588
|
+
let i = 0;
|
|
3589
|
+
for (let len = matchmask.length; i < len; i += 1) {
|
|
3590
|
+
let match = matchmask[i];
|
|
3591
|
+
if (match && start === -1) {
|
|
3592
|
+
start = i;
|
|
3593
|
+
} else if (!match && start !== -1) {
|
|
3594
|
+
end = i - 1;
|
|
3595
|
+
if (end - start + 1 >= minMatchCharLength) {
|
|
3596
|
+
indices.push([start, end]);
|
|
3597
|
+
}
|
|
3598
|
+
start = -1;
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
if (matchmask[i - 1] && i - start >= minMatchCharLength) {
|
|
3602
|
+
indices.push([start, i - 1]);
|
|
3603
|
+
}
|
|
3604
|
+
return indices;
|
|
3605
|
+
}
|
|
3606
|
+
var MAX_BITS = 32;
|
|
3607
|
+
function search(text, pattern, patternAlphabet, {
|
|
3608
|
+
location = Config.location,
|
|
3609
|
+
distance = Config.distance,
|
|
3610
|
+
threshold = Config.threshold,
|
|
3611
|
+
findAllMatches = Config.findAllMatches,
|
|
3612
|
+
minMatchCharLength = Config.minMatchCharLength,
|
|
3613
|
+
includeMatches = Config.includeMatches,
|
|
3614
|
+
ignoreLocation = Config.ignoreLocation
|
|
3615
|
+
} = {}) {
|
|
3616
|
+
if (pattern.length > MAX_BITS) {
|
|
3617
|
+
throw new Error(PATTERN_LENGTH_TOO_LARGE(MAX_BITS));
|
|
3618
|
+
}
|
|
3619
|
+
const patternLen = pattern.length;
|
|
3620
|
+
const textLen = text.length;
|
|
3621
|
+
const expectedLocation = Math.max(0, Math.min(location, textLen));
|
|
3622
|
+
let currentThreshold = threshold;
|
|
3623
|
+
let bestLocation = expectedLocation;
|
|
3624
|
+
const computeMatches = minMatchCharLength > 1 || includeMatches;
|
|
3625
|
+
const matchMask = computeMatches ? Array(textLen) : [];
|
|
3626
|
+
let index;
|
|
3627
|
+
while ((index = text.indexOf(pattern, bestLocation)) > -1) {
|
|
3628
|
+
let score = computeScore$1(pattern, {
|
|
3629
|
+
currentLocation: index,
|
|
3630
|
+
expectedLocation,
|
|
3631
|
+
distance,
|
|
3632
|
+
ignoreLocation
|
|
3633
|
+
});
|
|
3634
|
+
currentThreshold = Math.min(score, currentThreshold);
|
|
3635
|
+
bestLocation = index + patternLen;
|
|
3636
|
+
if (computeMatches) {
|
|
3637
|
+
let i = 0;
|
|
3638
|
+
while (i < patternLen) {
|
|
3639
|
+
matchMask[index + i] = 1;
|
|
3640
|
+
i += 1;
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
bestLocation = -1;
|
|
3645
|
+
let lastBitArr = [];
|
|
3646
|
+
let finalScore = 1;
|
|
3647
|
+
let binMax = patternLen + textLen;
|
|
3648
|
+
const mask = 1 << patternLen - 1;
|
|
3649
|
+
for (let i = 0; i < patternLen; i += 1) {
|
|
3650
|
+
let binMin = 0;
|
|
3651
|
+
let binMid = binMax;
|
|
3652
|
+
while (binMin < binMid) {
|
|
3653
|
+
const score2 = computeScore$1(pattern, {
|
|
3654
|
+
errors: i,
|
|
3655
|
+
currentLocation: expectedLocation + binMid,
|
|
3656
|
+
expectedLocation,
|
|
3657
|
+
distance,
|
|
3658
|
+
ignoreLocation
|
|
3659
|
+
});
|
|
3660
|
+
if (score2 <= currentThreshold) {
|
|
3661
|
+
binMin = binMid;
|
|
3662
|
+
} else {
|
|
3663
|
+
binMax = binMid;
|
|
3664
|
+
}
|
|
3665
|
+
binMid = Math.floor((binMax - binMin) / 2 + binMin);
|
|
3666
|
+
}
|
|
3667
|
+
binMax = binMid;
|
|
3668
|
+
let start = Math.max(1, expectedLocation - binMid + 1);
|
|
3669
|
+
let finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen;
|
|
3670
|
+
let bitArr = Array(finish + 2);
|
|
3671
|
+
bitArr[finish + 1] = (1 << i) - 1;
|
|
3672
|
+
for (let j = finish; j >= start; j -= 1) {
|
|
3673
|
+
let currentLocation = j - 1;
|
|
3674
|
+
let charMatch = patternAlphabet[text.charAt(currentLocation)];
|
|
3675
|
+
if (computeMatches) {
|
|
3676
|
+
matchMask[currentLocation] = +!!charMatch;
|
|
3677
|
+
}
|
|
3678
|
+
bitArr[j] = (bitArr[j + 1] << 1 | 1) & charMatch;
|
|
3679
|
+
if (i) {
|
|
3680
|
+
bitArr[j] |= (lastBitArr[j + 1] | lastBitArr[j]) << 1 | 1 | lastBitArr[j + 1];
|
|
3681
|
+
}
|
|
3682
|
+
if (bitArr[j] & mask) {
|
|
3683
|
+
finalScore = computeScore$1(pattern, {
|
|
3684
|
+
errors: i,
|
|
3685
|
+
currentLocation,
|
|
3686
|
+
expectedLocation,
|
|
3687
|
+
distance,
|
|
3688
|
+
ignoreLocation
|
|
3689
|
+
});
|
|
3690
|
+
if (finalScore <= currentThreshold) {
|
|
3691
|
+
currentThreshold = finalScore;
|
|
3692
|
+
bestLocation = currentLocation;
|
|
3693
|
+
if (bestLocation <= expectedLocation) {
|
|
3694
|
+
break;
|
|
3695
|
+
}
|
|
3696
|
+
start = Math.max(1, 2 * expectedLocation - bestLocation);
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
const score = computeScore$1(pattern, {
|
|
3701
|
+
errors: i + 1,
|
|
3702
|
+
currentLocation: expectedLocation,
|
|
3703
|
+
expectedLocation,
|
|
3704
|
+
distance,
|
|
3705
|
+
ignoreLocation
|
|
3706
|
+
});
|
|
3707
|
+
if (score > currentThreshold) {
|
|
3708
|
+
break;
|
|
3709
|
+
}
|
|
3710
|
+
lastBitArr = bitArr;
|
|
3711
|
+
}
|
|
3712
|
+
const result = {
|
|
3713
|
+
isMatch: bestLocation >= 0,
|
|
3714
|
+
// Count exact matches (those with a score of 0) to be "almost" exact
|
|
3715
|
+
score: Math.max(1e-3, finalScore)
|
|
3716
|
+
};
|
|
3717
|
+
if (computeMatches) {
|
|
3718
|
+
const indices = convertMaskToIndices(matchMask, minMatchCharLength);
|
|
3719
|
+
if (!indices.length) {
|
|
3720
|
+
result.isMatch = false;
|
|
3721
|
+
} else if (includeMatches) {
|
|
3722
|
+
result.indices = indices;
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
return result;
|
|
3726
|
+
}
|
|
3727
|
+
function createPatternAlphabet(pattern) {
|
|
3728
|
+
let mask = {};
|
|
3729
|
+
for (let i = 0, len = pattern.length; i < len; i += 1) {
|
|
3730
|
+
const char = pattern.charAt(i);
|
|
3731
|
+
mask[char] = (mask[char] || 0) | 1 << len - i - 1;
|
|
3732
|
+
}
|
|
3733
|
+
return mask;
|
|
3734
|
+
}
|
|
3735
|
+
var stripDiacritics = String.prototype.normalize ? (str) => str.normalize("NFD").replace(/[\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08D3-\u08E1\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u09FE\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C00-\u0C04\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0D00-\u0D03\u0D3B\u0D3C\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F\u109A-\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u180B-\u180D\u1885\u1886\u18A9\u1920-\u192B\u1930-\u193B\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F\u1AB0-\u1ABE\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BE6-\u1BF3\u1C24-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF7-\u1CF9\u1DC0-\u1DF9\u1DFB-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880\uA881\uA8B4-\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9E5\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F]/g, "") : (str) => str;
|
|
3736
|
+
var BitapSearch = class {
|
|
3737
|
+
constructor(pattern, {
|
|
3738
|
+
location = Config.location,
|
|
3739
|
+
threshold = Config.threshold,
|
|
3740
|
+
distance = Config.distance,
|
|
3741
|
+
includeMatches = Config.includeMatches,
|
|
3742
|
+
findAllMatches = Config.findAllMatches,
|
|
3743
|
+
minMatchCharLength = Config.minMatchCharLength,
|
|
3744
|
+
isCaseSensitive = Config.isCaseSensitive,
|
|
3745
|
+
ignoreDiacritics = Config.ignoreDiacritics,
|
|
3746
|
+
ignoreLocation = Config.ignoreLocation
|
|
3747
|
+
} = {}) {
|
|
3748
|
+
this.options = {
|
|
3749
|
+
location,
|
|
3750
|
+
threshold,
|
|
3751
|
+
distance,
|
|
3752
|
+
includeMatches,
|
|
3753
|
+
findAllMatches,
|
|
3754
|
+
minMatchCharLength,
|
|
3755
|
+
isCaseSensitive,
|
|
3756
|
+
ignoreDiacritics,
|
|
3757
|
+
ignoreLocation
|
|
3758
|
+
};
|
|
3759
|
+
pattern = isCaseSensitive ? pattern : pattern.toLowerCase();
|
|
3760
|
+
pattern = ignoreDiacritics ? stripDiacritics(pattern) : pattern;
|
|
3761
|
+
this.pattern = pattern;
|
|
3762
|
+
this.chunks = [];
|
|
3763
|
+
if (!this.pattern.length) {
|
|
3764
|
+
return;
|
|
3765
|
+
}
|
|
3766
|
+
const addChunk = (pattern2, startIndex) => {
|
|
3767
|
+
this.chunks.push({
|
|
3768
|
+
pattern: pattern2,
|
|
3769
|
+
alphabet: createPatternAlphabet(pattern2),
|
|
3770
|
+
startIndex
|
|
3771
|
+
});
|
|
3772
|
+
};
|
|
3773
|
+
const len = this.pattern.length;
|
|
3774
|
+
if (len > MAX_BITS) {
|
|
3775
|
+
let i = 0;
|
|
3776
|
+
const remainder = len % MAX_BITS;
|
|
3777
|
+
const end = len - remainder;
|
|
3778
|
+
while (i < end) {
|
|
3779
|
+
addChunk(this.pattern.substr(i, MAX_BITS), i);
|
|
3780
|
+
i += MAX_BITS;
|
|
3781
|
+
}
|
|
3782
|
+
if (remainder) {
|
|
3783
|
+
const startIndex = len - MAX_BITS;
|
|
3784
|
+
addChunk(this.pattern.substr(startIndex), startIndex);
|
|
3785
|
+
}
|
|
3786
|
+
} else {
|
|
3787
|
+
addChunk(this.pattern, 0);
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
searchIn(text) {
|
|
3791
|
+
const { isCaseSensitive, ignoreDiacritics, includeMatches } = this.options;
|
|
3792
|
+
text = isCaseSensitive ? text : text.toLowerCase();
|
|
3793
|
+
text = ignoreDiacritics ? stripDiacritics(text) : text;
|
|
3794
|
+
if (this.pattern === text) {
|
|
3795
|
+
let result2 = {
|
|
3796
|
+
isMatch: true,
|
|
3797
|
+
score: 0
|
|
3798
|
+
};
|
|
3799
|
+
if (includeMatches) {
|
|
3800
|
+
result2.indices = [[0, text.length - 1]];
|
|
3801
|
+
}
|
|
3802
|
+
return result2;
|
|
3803
|
+
}
|
|
3804
|
+
const {
|
|
3805
|
+
location,
|
|
3806
|
+
distance,
|
|
3807
|
+
threshold,
|
|
3808
|
+
findAllMatches,
|
|
3809
|
+
minMatchCharLength,
|
|
3810
|
+
ignoreLocation
|
|
3811
|
+
} = this.options;
|
|
3812
|
+
let allIndices = [];
|
|
3813
|
+
let totalScore = 0;
|
|
3814
|
+
let hasMatches = false;
|
|
3815
|
+
this.chunks.forEach(({ pattern, alphabet, startIndex }) => {
|
|
3816
|
+
const { isMatch, score, indices } = search(text, pattern, alphabet, {
|
|
3817
|
+
location: location + startIndex,
|
|
3818
|
+
distance,
|
|
3819
|
+
threshold,
|
|
3820
|
+
findAllMatches,
|
|
3821
|
+
minMatchCharLength,
|
|
3822
|
+
includeMatches,
|
|
3823
|
+
ignoreLocation
|
|
3824
|
+
});
|
|
3825
|
+
if (isMatch) {
|
|
3826
|
+
hasMatches = true;
|
|
3827
|
+
}
|
|
3828
|
+
totalScore += score;
|
|
3829
|
+
if (isMatch && indices) {
|
|
3830
|
+
allIndices = [...allIndices, ...indices];
|
|
3831
|
+
}
|
|
3832
|
+
});
|
|
3833
|
+
let result = {
|
|
3834
|
+
isMatch: hasMatches,
|
|
3835
|
+
score: hasMatches ? totalScore / this.chunks.length : 1
|
|
3836
|
+
};
|
|
3837
|
+
if (hasMatches && includeMatches) {
|
|
3838
|
+
result.indices = allIndices;
|
|
3839
|
+
}
|
|
3840
|
+
return result;
|
|
3841
|
+
}
|
|
3842
|
+
};
|
|
3843
|
+
var BaseMatch = class {
|
|
3844
|
+
constructor(pattern) {
|
|
3845
|
+
this.pattern = pattern;
|
|
3846
|
+
}
|
|
3847
|
+
static isMultiMatch(pattern) {
|
|
3848
|
+
return getMatch(pattern, this.multiRegex);
|
|
3849
|
+
}
|
|
3850
|
+
static isSingleMatch(pattern) {
|
|
3851
|
+
return getMatch(pattern, this.singleRegex);
|
|
3852
|
+
}
|
|
3853
|
+
search() {
|
|
3854
|
+
}
|
|
3855
|
+
};
|
|
3856
|
+
function getMatch(pattern, exp) {
|
|
3857
|
+
const matches = pattern.match(exp);
|
|
3858
|
+
return matches ? matches[1] : null;
|
|
3859
|
+
}
|
|
3860
|
+
var ExactMatch = class extends BaseMatch {
|
|
3861
|
+
constructor(pattern) {
|
|
3862
|
+
super(pattern);
|
|
3863
|
+
}
|
|
3864
|
+
static get type() {
|
|
3865
|
+
return "exact";
|
|
3866
|
+
}
|
|
3867
|
+
static get multiRegex() {
|
|
3868
|
+
return /^="(.*)"$/;
|
|
3869
|
+
}
|
|
3870
|
+
static get singleRegex() {
|
|
3871
|
+
return /^=(.*)$/;
|
|
3872
|
+
}
|
|
3873
|
+
search(text) {
|
|
3874
|
+
const isMatch = text === this.pattern;
|
|
3875
|
+
return {
|
|
3876
|
+
isMatch,
|
|
3877
|
+
score: isMatch ? 0 : 1,
|
|
3878
|
+
indices: [0, this.pattern.length - 1]
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
};
|
|
3882
|
+
var InverseExactMatch = class extends BaseMatch {
|
|
3883
|
+
constructor(pattern) {
|
|
3884
|
+
super(pattern);
|
|
3885
|
+
}
|
|
3886
|
+
static get type() {
|
|
3887
|
+
return "inverse-exact";
|
|
3888
|
+
}
|
|
3889
|
+
static get multiRegex() {
|
|
3890
|
+
return /^!"(.*)"$/;
|
|
3891
|
+
}
|
|
3892
|
+
static get singleRegex() {
|
|
3893
|
+
return /^!(.*)$/;
|
|
3894
|
+
}
|
|
3895
|
+
search(text) {
|
|
3896
|
+
const index = text.indexOf(this.pattern);
|
|
3897
|
+
const isMatch = index === -1;
|
|
3898
|
+
return {
|
|
3899
|
+
isMatch,
|
|
3900
|
+
score: isMatch ? 0 : 1,
|
|
3901
|
+
indices: [0, text.length - 1]
|
|
3902
|
+
};
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
var PrefixExactMatch = class extends BaseMatch {
|
|
3906
|
+
constructor(pattern) {
|
|
3907
|
+
super(pattern);
|
|
3908
|
+
}
|
|
3909
|
+
static get type() {
|
|
3910
|
+
return "prefix-exact";
|
|
3911
|
+
}
|
|
3912
|
+
static get multiRegex() {
|
|
3913
|
+
return /^\^"(.*)"$/;
|
|
3914
|
+
}
|
|
3915
|
+
static get singleRegex() {
|
|
3916
|
+
return /^\^(.*)$/;
|
|
3917
|
+
}
|
|
3918
|
+
search(text) {
|
|
3919
|
+
const isMatch = text.startsWith(this.pattern);
|
|
3920
|
+
return {
|
|
3921
|
+
isMatch,
|
|
3922
|
+
score: isMatch ? 0 : 1,
|
|
3923
|
+
indices: [0, this.pattern.length - 1]
|
|
3924
|
+
};
|
|
3925
|
+
}
|
|
3926
|
+
};
|
|
3927
|
+
var InversePrefixExactMatch = class extends BaseMatch {
|
|
3928
|
+
constructor(pattern) {
|
|
3929
|
+
super(pattern);
|
|
3930
|
+
}
|
|
3931
|
+
static get type() {
|
|
3932
|
+
return "inverse-prefix-exact";
|
|
3933
|
+
}
|
|
3934
|
+
static get multiRegex() {
|
|
3935
|
+
return /^!\^"(.*)"$/;
|
|
3936
|
+
}
|
|
3937
|
+
static get singleRegex() {
|
|
3938
|
+
return /^!\^(.*)$/;
|
|
3939
|
+
}
|
|
3940
|
+
search(text) {
|
|
3941
|
+
const isMatch = !text.startsWith(this.pattern);
|
|
3942
|
+
return {
|
|
3943
|
+
isMatch,
|
|
3944
|
+
score: isMatch ? 0 : 1,
|
|
3945
|
+
indices: [0, text.length - 1]
|
|
3946
|
+
};
|
|
3947
|
+
}
|
|
3948
|
+
};
|
|
3949
|
+
var SuffixExactMatch = class extends BaseMatch {
|
|
3950
|
+
constructor(pattern) {
|
|
3951
|
+
super(pattern);
|
|
3952
|
+
}
|
|
3953
|
+
static get type() {
|
|
3954
|
+
return "suffix-exact";
|
|
3955
|
+
}
|
|
3956
|
+
static get multiRegex() {
|
|
3957
|
+
return /^"(.*)"\$$/;
|
|
3958
|
+
}
|
|
3959
|
+
static get singleRegex() {
|
|
3960
|
+
return /^(.*)\$$/;
|
|
3961
|
+
}
|
|
3962
|
+
search(text) {
|
|
3963
|
+
const isMatch = text.endsWith(this.pattern);
|
|
3964
|
+
return {
|
|
3965
|
+
isMatch,
|
|
3966
|
+
score: isMatch ? 0 : 1,
|
|
3967
|
+
indices: [text.length - this.pattern.length, text.length - 1]
|
|
3968
|
+
};
|
|
3969
|
+
}
|
|
3970
|
+
};
|
|
3971
|
+
var InverseSuffixExactMatch = class extends BaseMatch {
|
|
3972
|
+
constructor(pattern) {
|
|
3973
|
+
super(pattern);
|
|
3974
|
+
}
|
|
3975
|
+
static get type() {
|
|
3976
|
+
return "inverse-suffix-exact";
|
|
3977
|
+
}
|
|
3978
|
+
static get multiRegex() {
|
|
3979
|
+
return /^!"(.*)"\$$/;
|
|
3980
|
+
}
|
|
3981
|
+
static get singleRegex() {
|
|
3982
|
+
return /^!(.*)\$$/;
|
|
3983
|
+
}
|
|
3984
|
+
search(text) {
|
|
3985
|
+
const isMatch = !text.endsWith(this.pattern);
|
|
3986
|
+
return {
|
|
3987
|
+
isMatch,
|
|
3988
|
+
score: isMatch ? 0 : 1,
|
|
3989
|
+
indices: [0, text.length - 1]
|
|
3990
|
+
};
|
|
3991
|
+
}
|
|
3992
|
+
};
|
|
3993
|
+
var FuzzyMatch = class extends BaseMatch {
|
|
3994
|
+
constructor(pattern, {
|
|
3995
|
+
location = Config.location,
|
|
3996
|
+
threshold = Config.threshold,
|
|
3997
|
+
distance = Config.distance,
|
|
3998
|
+
includeMatches = Config.includeMatches,
|
|
3999
|
+
findAllMatches = Config.findAllMatches,
|
|
4000
|
+
minMatchCharLength = Config.minMatchCharLength,
|
|
4001
|
+
isCaseSensitive = Config.isCaseSensitive,
|
|
4002
|
+
ignoreDiacritics = Config.ignoreDiacritics,
|
|
4003
|
+
ignoreLocation = Config.ignoreLocation
|
|
4004
|
+
} = {}) {
|
|
4005
|
+
super(pattern);
|
|
4006
|
+
this._bitapSearch = new BitapSearch(pattern, {
|
|
4007
|
+
location,
|
|
4008
|
+
threshold,
|
|
4009
|
+
distance,
|
|
4010
|
+
includeMatches,
|
|
4011
|
+
findAllMatches,
|
|
4012
|
+
minMatchCharLength,
|
|
4013
|
+
isCaseSensitive,
|
|
4014
|
+
ignoreDiacritics,
|
|
4015
|
+
ignoreLocation
|
|
4016
|
+
});
|
|
4017
|
+
}
|
|
4018
|
+
static get type() {
|
|
4019
|
+
return "fuzzy";
|
|
4020
|
+
}
|
|
4021
|
+
static get multiRegex() {
|
|
4022
|
+
return /^"(.*)"$/;
|
|
4023
|
+
}
|
|
4024
|
+
static get singleRegex() {
|
|
4025
|
+
return /^(.*)$/;
|
|
4026
|
+
}
|
|
4027
|
+
search(text) {
|
|
4028
|
+
return this._bitapSearch.searchIn(text);
|
|
4029
|
+
}
|
|
4030
|
+
};
|
|
4031
|
+
var IncludeMatch = class extends BaseMatch {
|
|
4032
|
+
constructor(pattern) {
|
|
4033
|
+
super(pattern);
|
|
4034
|
+
}
|
|
4035
|
+
static get type() {
|
|
4036
|
+
return "include";
|
|
4037
|
+
}
|
|
4038
|
+
static get multiRegex() {
|
|
4039
|
+
return /^'"(.*)"$/;
|
|
4040
|
+
}
|
|
4041
|
+
static get singleRegex() {
|
|
4042
|
+
return /^'(.*)$/;
|
|
4043
|
+
}
|
|
4044
|
+
search(text) {
|
|
4045
|
+
let location = 0;
|
|
4046
|
+
let index;
|
|
4047
|
+
const indices = [];
|
|
4048
|
+
const patternLen = this.pattern.length;
|
|
4049
|
+
while ((index = text.indexOf(this.pattern, location)) > -1) {
|
|
4050
|
+
location = index + patternLen;
|
|
4051
|
+
indices.push([index, location - 1]);
|
|
4052
|
+
}
|
|
4053
|
+
const isMatch = !!indices.length;
|
|
4054
|
+
return {
|
|
4055
|
+
isMatch,
|
|
4056
|
+
score: isMatch ? 0 : 1,
|
|
4057
|
+
indices
|
|
4058
|
+
};
|
|
4059
|
+
}
|
|
4060
|
+
};
|
|
4061
|
+
var searchers = [
|
|
4062
|
+
ExactMatch,
|
|
4063
|
+
IncludeMatch,
|
|
4064
|
+
PrefixExactMatch,
|
|
4065
|
+
InversePrefixExactMatch,
|
|
4066
|
+
InverseSuffixExactMatch,
|
|
4067
|
+
SuffixExactMatch,
|
|
4068
|
+
InverseExactMatch,
|
|
4069
|
+
FuzzyMatch
|
|
4070
|
+
];
|
|
4071
|
+
var searchersLen = searchers.length;
|
|
4072
|
+
var SPACE_RE = / +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/;
|
|
4073
|
+
var OR_TOKEN = "|";
|
|
4074
|
+
function parseQuery(pattern, options = {}) {
|
|
4075
|
+
return pattern.split(OR_TOKEN).map((item) => {
|
|
4076
|
+
let query = item.trim().split(SPACE_RE).filter((item2) => item2 && !!item2.trim());
|
|
4077
|
+
let results = [];
|
|
4078
|
+
for (let i = 0, len = query.length; i < len; i += 1) {
|
|
4079
|
+
const queryItem = query[i];
|
|
4080
|
+
let found = false;
|
|
4081
|
+
let idx = -1;
|
|
4082
|
+
while (!found && ++idx < searchersLen) {
|
|
4083
|
+
const searcher = searchers[idx];
|
|
4084
|
+
let token = searcher.isMultiMatch(queryItem);
|
|
4085
|
+
if (token) {
|
|
4086
|
+
results.push(new searcher(token, options));
|
|
4087
|
+
found = true;
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
if (found) {
|
|
4091
|
+
continue;
|
|
4092
|
+
}
|
|
4093
|
+
idx = -1;
|
|
4094
|
+
while (++idx < searchersLen) {
|
|
4095
|
+
const searcher = searchers[idx];
|
|
4096
|
+
let token = searcher.isSingleMatch(queryItem);
|
|
4097
|
+
if (token) {
|
|
4098
|
+
results.push(new searcher(token, options));
|
|
4099
|
+
break;
|
|
4100
|
+
}
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
return results;
|
|
4104
|
+
});
|
|
4105
|
+
}
|
|
4106
|
+
var MultiMatchSet = /* @__PURE__ */ new Set([FuzzyMatch.type, IncludeMatch.type]);
|
|
4107
|
+
var ExtendedSearch = class {
|
|
4108
|
+
constructor(pattern, {
|
|
4109
|
+
isCaseSensitive = Config.isCaseSensitive,
|
|
4110
|
+
ignoreDiacritics = Config.ignoreDiacritics,
|
|
4111
|
+
includeMatches = Config.includeMatches,
|
|
4112
|
+
minMatchCharLength = Config.minMatchCharLength,
|
|
4113
|
+
ignoreLocation = Config.ignoreLocation,
|
|
4114
|
+
findAllMatches = Config.findAllMatches,
|
|
4115
|
+
location = Config.location,
|
|
4116
|
+
threshold = Config.threshold,
|
|
4117
|
+
distance = Config.distance
|
|
4118
|
+
} = {}) {
|
|
4119
|
+
this.query = null;
|
|
4120
|
+
this.options = {
|
|
4121
|
+
isCaseSensitive,
|
|
4122
|
+
ignoreDiacritics,
|
|
4123
|
+
includeMatches,
|
|
4124
|
+
minMatchCharLength,
|
|
4125
|
+
findAllMatches,
|
|
4126
|
+
ignoreLocation,
|
|
4127
|
+
location,
|
|
4128
|
+
threshold,
|
|
4129
|
+
distance
|
|
4130
|
+
};
|
|
4131
|
+
pattern = isCaseSensitive ? pattern : pattern.toLowerCase();
|
|
4132
|
+
pattern = ignoreDiacritics ? stripDiacritics(pattern) : pattern;
|
|
4133
|
+
this.pattern = pattern;
|
|
4134
|
+
this.query = parseQuery(this.pattern, this.options);
|
|
4135
|
+
}
|
|
4136
|
+
static condition(_, options) {
|
|
4137
|
+
return options.useExtendedSearch;
|
|
4138
|
+
}
|
|
4139
|
+
searchIn(text) {
|
|
4140
|
+
const query = this.query;
|
|
4141
|
+
if (!query) {
|
|
4142
|
+
return {
|
|
4143
|
+
isMatch: false,
|
|
4144
|
+
score: 1
|
|
4145
|
+
};
|
|
4146
|
+
}
|
|
4147
|
+
const { includeMatches, isCaseSensitive, ignoreDiacritics } = this.options;
|
|
4148
|
+
text = isCaseSensitive ? text : text.toLowerCase();
|
|
4149
|
+
text = ignoreDiacritics ? stripDiacritics(text) : text;
|
|
4150
|
+
let numMatches = 0;
|
|
4151
|
+
let allIndices = [];
|
|
4152
|
+
let totalScore = 0;
|
|
4153
|
+
for (let i = 0, qLen = query.length; i < qLen; i += 1) {
|
|
4154
|
+
const searchers2 = query[i];
|
|
4155
|
+
allIndices.length = 0;
|
|
4156
|
+
numMatches = 0;
|
|
4157
|
+
for (let j = 0, pLen = searchers2.length; j < pLen; j += 1) {
|
|
4158
|
+
const searcher = searchers2[j];
|
|
4159
|
+
const { isMatch, indices, score } = searcher.search(text);
|
|
4160
|
+
if (isMatch) {
|
|
4161
|
+
numMatches += 1;
|
|
4162
|
+
totalScore += score;
|
|
4163
|
+
if (includeMatches) {
|
|
4164
|
+
const type = searcher.constructor.type;
|
|
4165
|
+
if (MultiMatchSet.has(type)) {
|
|
4166
|
+
allIndices = [...allIndices, ...indices];
|
|
4167
|
+
} else {
|
|
4168
|
+
allIndices.push(indices);
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4171
|
+
} else {
|
|
4172
|
+
totalScore = 0;
|
|
4173
|
+
numMatches = 0;
|
|
4174
|
+
allIndices.length = 0;
|
|
4175
|
+
break;
|
|
4176
|
+
}
|
|
4177
|
+
}
|
|
4178
|
+
if (numMatches) {
|
|
4179
|
+
let result = {
|
|
4180
|
+
isMatch: true,
|
|
4181
|
+
score: totalScore / numMatches
|
|
4182
|
+
};
|
|
4183
|
+
if (includeMatches) {
|
|
4184
|
+
result.indices = allIndices;
|
|
4185
|
+
}
|
|
4186
|
+
return result;
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
return {
|
|
4190
|
+
isMatch: false,
|
|
4191
|
+
score: 1
|
|
4192
|
+
};
|
|
4193
|
+
}
|
|
4194
|
+
};
|
|
4195
|
+
var registeredSearchers = [];
|
|
4196
|
+
function register(...args) {
|
|
4197
|
+
registeredSearchers.push(...args);
|
|
4198
|
+
}
|
|
4199
|
+
function createSearcher(pattern, options) {
|
|
4200
|
+
for (let i = 0, len = registeredSearchers.length; i < len; i += 1) {
|
|
4201
|
+
let searcherClass = registeredSearchers[i];
|
|
4202
|
+
if (searcherClass.condition(pattern, options)) {
|
|
4203
|
+
return new searcherClass(pattern, options);
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
return new BitapSearch(pattern, options);
|
|
4207
|
+
}
|
|
4208
|
+
var LogicalOperator = {
|
|
4209
|
+
AND: "$and",
|
|
4210
|
+
OR: "$or"
|
|
4211
|
+
};
|
|
4212
|
+
var KeyType = {
|
|
4213
|
+
PATH: "$path",
|
|
4214
|
+
PATTERN: "$val"
|
|
4215
|
+
};
|
|
4216
|
+
var isExpression = (query) => !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]);
|
|
4217
|
+
var isPath = (query) => !!query[KeyType.PATH];
|
|
4218
|
+
var isLeaf = (query) => !isArray(query) && isObject(query) && !isExpression(query);
|
|
4219
|
+
var convertToExplicit = (query) => ({
|
|
4220
|
+
[LogicalOperator.AND]: Object.keys(query).map((key) => ({
|
|
4221
|
+
[key]: query[key]
|
|
4222
|
+
}))
|
|
4223
|
+
});
|
|
4224
|
+
function parse2(query, options, { auto = true } = {}) {
|
|
4225
|
+
const next = (query2) => {
|
|
4226
|
+
let keys = Object.keys(query2);
|
|
4227
|
+
const isQueryPath = isPath(query2);
|
|
4228
|
+
if (!isQueryPath && keys.length > 1 && !isExpression(query2)) {
|
|
4229
|
+
return next(convertToExplicit(query2));
|
|
4230
|
+
}
|
|
4231
|
+
if (isLeaf(query2)) {
|
|
4232
|
+
const key = isQueryPath ? query2[KeyType.PATH] : keys[0];
|
|
4233
|
+
const pattern = isQueryPath ? query2[KeyType.PATTERN] : query2[key];
|
|
4234
|
+
if (!isString(pattern)) {
|
|
4235
|
+
throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key));
|
|
4236
|
+
}
|
|
4237
|
+
const obj = {
|
|
4238
|
+
keyId: createKeyId(key),
|
|
4239
|
+
pattern
|
|
4240
|
+
};
|
|
4241
|
+
if (auto) {
|
|
4242
|
+
obj.searcher = createSearcher(pattern, options);
|
|
4243
|
+
}
|
|
4244
|
+
return obj;
|
|
4245
|
+
}
|
|
4246
|
+
let node = {
|
|
4247
|
+
children: [],
|
|
4248
|
+
operator: keys[0]
|
|
4249
|
+
};
|
|
4250
|
+
keys.forEach((key) => {
|
|
4251
|
+
const value = query2[key];
|
|
4252
|
+
if (isArray(value)) {
|
|
4253
|
+
value.forEach((item) => {
|
|
4254
|
+
node.children.push(next(item));
|
|
4255
|
+
});
|
|
4256
|
+
}
|
|
4257
|
+
});
|
|
4258
|
+
return node;
|
|
4259
|
+
};
|
|
4260
|
+
if (!isExpression(query)) {
|
|
4261
|
+
query = convertToExplicit(query);
|
|
4262
|
+
}
|
|
4263
|
+
return next(query);
|
|
4264
|
+
}
|
|
4265
|
+
function computeScore(results, { ignoreFieldNorm = Config.ignoreFieldNorm }) {
|
|
4266
|
+
results.forEach((result) => {
|
|
4267
|
+
let totalScore = 1;
|
|
4268
|
+
result.matches.forEach(({ key, norm: norm2, score }) => {
|
|
4269
|
+
const weight = key ? key.weight : null;
|
|
4270
|
+
totalScore *= Math.pow(
|
|
4271
|
+
score === 0 && weight ? Number.EPSILON : score,
|
|
4272
|
+
(weight || 1) * (ignoreFieldNorm ? 1 : norm2)
|
|
4273
|
+
);
|
|
4274
|
+
});
|
|
4275
|
+
result.score = totalScore;
|
|
4276
|
+
});
|
|
4277
|
+
}
|
|
4278
|
+
function transformMatches(result, data) {
|
|
4279
|
+
const matches = result.matches;
|
|
4280
|
+
data.matches = [];
|
|
4281
|
+
if (!isDefined(matches)) {
|
|
4282
|
+
return;
|
|
4283
|
+
}
|
|
4284
|
+
matches.forEach((match) => {
|
|
4285
|
+
if (!isDefined(match.indices) || !match.indices.length) {
|
|
4286
|
+
return;
|
|
4287
|
+
}
|
|
4288
|
+
const { indices, value } = match;
|
|
4289
|
+
let obj = {
|
|
4290
|
+
indices,
|
|
4291
|
+
value
|
|
4292
|
+
};
|
|
4293
|
+
if (match.key) {
|
|
4294
|
+
obj.key = match.key.src;
|
|
4295
|
+
}
|
|
4296
|
+
if (match.idx > -1) {
|
|
4297
|
+
obj.refIndex = match.idx;
|
|
4298
|
+
}
|
|
4299
|
+
data.matches.push(obj);
|
|
4300
|
+
});
|
|
4301
|
+
}
|
|
4302
|
+
function transformScore(result, data) {
|
|
4303
|
+
data.score = result.score;
|
|
4304
|
+
}
|
|
4305
|
+
function format(results, docs, {
|
|
4306
|
+
includeMatches = Config.includeMatches,
|
|
4307
|
+
includeScore = Config.includeScore
|
|
4308
|
+
} = {}) {
|
|
4309
|
+
const transformers = [];
|
|
4310
|
+
if (includeMatches) transformers.push(transformMatches);
|
|
4311
|
+
if (includeScore) transformers.push(transformScore);
|
|
4312
|
+
return results.map((result) => {
|
|
4313
|
+
const { idx } = result;
|
|
4314
|
+
const data = {
|
|
4315
|
+
item: docs[idx],
|
|
4316
|
+
refIndex: idx
|
|
4317
|
+
};
|
|
4318
|
+
if (transformers.length) {
|
|
4319
|
+
transformers.forEach((transformer) => {
|
|
4320
|
+
transformer(result, data);
|
|
4321
|
+
});
|
|
4322
|
+
}
|
|
4323
|
+
return data;
|
|
4324
|
+
});
|
|
4325
|
+
}
|
|
4326
|
+
var Fuse = class {
|
|
4327
|
+
constructor(docs, options = {}, index) {
|
|
4328
|
+
this.options = { ...Config, ...options };
|
|
4329
|
+
if (this.options.useExtendedSearch && false) {
|
|
4330
|
+
throw new Error(EXTENDED_SEARCH_UNAVAILABLE);
|
|
4331
|
+
}
|
|
4332
|
+
this._keyStore = new KeyStore(this.options.keys);
|
|
4333
|
+
this.setCollection(docs, index);
|
|
4334
|
+
}
|
|
4335
|
+
setCollection(docs, index) {
|
|
4336
|
+
this._docs = docs;
|
|
4337
|
+
if (index && !(index instanceof FuseIndex)) {
|
|
4338
|
+
throw new Error(INCORRECT_INDEX_TYPE);
|
|
4339
|
+
}
|
|
4340
|
+
this._myIndex = index || createIndex(this.options.keys, this._docs, {
|
|
4341
|
+
getFn: this.options.getFn,
|
|
4342
|
+
fieldNormWeight: this.options.fieldNormWeight
|
|
4343
|
+
});
|
|
4344
|
+
}
|
|
4345
|
+
add(doc) {
|
|
4346
|
+
if (!isDefined(doc)) {
|
|
4347
|
+
return;
|
|
4348
|
+
}
|
|
4349
|
+
this._docs.push(doc);
|
|
4350
|
+
this._myIndex.add(doc);
|
|
4351
|
+
}
|
|
4352
|
+
remove(predicate = () => false) {
|
|
4353
|
+
const results = [];
|
|
4354
|
+
for (let i = 0, len = this._docs.length; i < len; i += 1) {
|
|
4355
|
+
const doc = this._docs[i];
|
|
4356
|
+
if (predicate(doc, i)) {
|
|
4357
|
+
this.removeAt(i);
|
|
4358
|
+
i -= 1;
|
|
4359
|
+
len -= 1;
|
|
4360
|
+
results.push(doc);
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
return results;
|
|
4364
|
+
}
|
|
4365
|
+
removeAt(idx) {
|
|
4366
|
+
this._docs.splice(idx, 1);
|
|
4367
|
+
this._myIndex.removeAt(idx);
|
|
4368
|
+
}
|
|
4369
|
+
getIndex() {
|
|
4370
|
+
return this._myIndex;
|
|
4371
|
+
}
|
|
4372
|
+
search(query, { limit = -1 } = {}) {
|
|
4373
|
+
const {
|
|
4374
|
+
includeMatches,
|
|
4375
|
+
includeScore,
|
|
4376
|
+
shouldSort,
|
|
4377
|
+
sortFn,
|
|
4378
|
+
ignoreFieldNorm
|
|
4379
|
+
} = this.options;
|
|
4380
|
+
let results = isString(query) ? isString(this._docs[0]) ? this._searchStringList(query) : this._searchObjectList(query) : this._searchLogical(query);
|
|
4381
|
+
computeScore(results, { ignoreFieldNorm });
|
|
4382
|
+
if (shouldSort) {
|
|
4383
|
+
results.sort(sortFn);
|
|
4384
|
+
}
|
|
4385
|
+
if (isNumber(limit) && limit > -1) {
|
|
4386
|
+
results = results.slice(0, limit);
|
|
4387
|
+
}
|
|
4388
|
+
return format(results, this._docs, {
|
|
4389
|
+
includeMatches,
|
|
4390
|
+
includeScore
|
|
4391
|
+
});
|
|
4392
|
+
}
|
|
4393
|
+
_searchStringList(query) {
|
|
4394
|
+
const searcher = createSearcher(query, this.options);
|
|
4395
|
+
const { records } = this._myIndex;
|
|
4396
|
+
const results = [];
|
|
4397
|
+
records.forEach(({ v: text, i: idx, n: norm2 }) => {
|
|
4398
|
+
if (!isDefined(text)) {
|
|
4399
|
+
return;
|
|
4400
|
+
}
|
|
4401
|
+
const { isMatch, score, indices } = searcher.searchIn(text);
|
|
4402
|
+
if (isMatch) {
|
|
4403
|
+
results.push({
|
|
4404
|
+
item: text,
|
|
4405
|
+
idx,
|
|
4406
|
+
matches: [{ score, value: text, norm: norm2, indices }]
|
|
4407
|
+
});
|
|
4408
|
+
}
|
|
4409
|
+
});
|
|
4410
|
+
return results;
|
|
4411
|
+
}
|
|
4412
|
+
_searchLogical(query) {
|
|
4413
|
+
const expression = parse2(query, this.options);
|
|
4414
|
+
const evaluate = (node, item, idx) => {
|
|
4415
|
+
if (!node.children) {
|
|
4416
|
+
const { keyId, searcher } = node;
|
|
4417
|
+
const matches = this._findMatches({
|
|
4418
|
+
key: this._keyStore.get(keyId),
|
|
4419
|
+
value: this._myIndex.getValueForItemAtKeyId(item, keyId),
|
|
4420
|
+
searcher
|
|
4421
|
+
});
|
|
4422
|
+
if (matches && matches.length) {
|
|
4423
|
+
return [
|
|
4424
|
+
{
|
|
4425
|
+
idx,
|
|
4426
|
+
item,
|
|
4427
|
+
matches
|
|
4428
|
+
}
|
|
4429
|
+
];
|
|
4430
|
+
}
|
|
4431
|
+
return [];
|
|
4432
|
+
}
|
|
4433
|
+
const res = [];
|
|
4434
|
+
for (let i = 0, len = node.children.length; i < len; i += 1) {
|
|
4435
|
+
const child = node.children[i];
|
|
4436
|
+
const result = evaluate(child, item, idx);
|
|
4437
|
+
if (result.length) {
|
|
4438
|
+
res.push(...result);
|
|
4439
|
+
} else if (node.operator === LogicalOperator.AND) {
|
|
4440
|
+
return [];
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
return res;
|
|
4444
|
+
};
|
|
4445
|
+
const records = this._myIndex.records;
|
|
4446
|
+
const resultMap = {};
|
|
4447
|
+
const results = [];
|
|
4448
|
+
records.forEach(({ $: item, i: idx }) => {
|
|
4449
|
+
if (isDefined(item)) {
|
|
4450
|
+
let expResults = evaluate(expression, item, idx);
|
|
4451
|
+
if (expResults.length) {
|
|
4452
|
+
if (!resultMap[idx]) {
|
|
4453
|
+
resultMap[idx] = { idx, item, matches: [] };
|
|
4454
|
+
results.push(resultMap[idx]);
|
|
4455
|
+
}
|
|
4456
|
+
expResults.forEach(({ matches }) => {
|
|
4457
|
+
resultMap[idx].matches.push(...matches);
|
|
4458
|
+
});
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
});
|
|
4462
|
+
return results;
|
|
4463
|
+
}
|
|
4464
|
+
_searchObjectList(query) {
|
|
4465
|
+
const searcher = createSearcher(query, this.options);
|
|
4466
|
+
const { keys, records } = this._myIndex;
|
|
4467
|
+
const results = [];
|
|
4468
|
+
records.forEach(({ $: item, i: idx }) => {
|
|
4469
|
+
if (!isDefined(item)) {
|
|
4470
|
+
return;
|
|
4471
|
+
}
|
|
4472
|
+
let matches = [];
|
|
4473
|
+
keys.forEach((key, keyIndex) => {
|
|
4474
|
+
matches.push(
|
|
4475
|
+
...this._findMatches({
|
|
4476
|
+
key,
|
|
4477
|
+
value: item[keyIndex],
|
|
4478
|
+
searcher
|
|
4479
|
+
})
|
|
4480
|
+
);
|
|
4481
|
+
});
|
|
4482
|
+
if (matches.length) {
|
|
4483
|
+
results.push({
|
|
4484
|
+
idx,
|
|
4485
|
+
item,
|
|
4486
|
+
matches
|
|
4487
|
+
});
|
|
4488
|
+
}
|
|
4489
|
+
});
|
|
4490
|
+
return results;
|
|
4491
|
+
}
|
|
4492
|
+
_findMatches({ key, value, searcher }) {
|
|
4493
|
+
if (!isDefined(value)) {
|
|
4494
|
+
return [];
|
|
4495
|
+
}
|
|
4496
|
+
let matches = [];
|
|
4497
|
+
if (isArray(value)) {
|
|
4498
|
+
value.forEach(({ v: text, i: idx, n: norm2 }) => {
|
|
4499
|
+
if (!isDefined(text)) {
|
|
4500
|
+
return;
|
|
4501
|
+
}
|
|
4502
|
+
const { isMatch, score, indices } = searcher.searchIn(text);
|
|
4503
|
+
if (isMatch) {
|
|
4504
|
+
matches.push({
|
|
4505
|
+
score,
|
|
4506
|
+
key,
|
|
4507
|
+
value: text,
|
|
4508
|
+
idx,
|
|
4509
|
+
norm: norm2,
|
|
4510
|
+
indices
|
|
4511
|
+
});
|
|
4512
|
+
}
|
|
4513
|
+
});
|
|
4514
|
+
} else {
|
|
4515
|
+
const { v: text, n: norm2 } = value;
|
|
4516
|
+
const { isMatch, score, indices } = searcher.searchIn(text);
|
|
4517
|
+
if (isMatch) {
|
|
4518
|
+
matches.push({ score, key, value: text, norm: norm2, indices });
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
return matches;
|
|
4522
|
+
}
|
|
4523
|
+
};
|
|
4524
|
+
Fuse.version = "7.1.0";
|
|
4525
|
+
Fuse.createIndex = createIndex;
|
|
4526
|
+
Fuse.parseIndex = parseIndex;
|
|
4527
|
+
Fuse.config = Config;
|
|
4528
|
+
{
|
|
4529
|
+
Fuse.parseQuery = parse2;
|
|
4530
|
+
}
|
|
4531
|
+
{
|
|
4532
|
+
register(ExtendedSearch);
|
|
4533
|
+
}
|
|
4534
|
+
|
|
4535
|
+
// src/utils/finder.ts
|
|
4536
|
+
var FUZZY_THRESHOLD = 0.3;
|
|
4537
|
+
var MATCH_THRESHOLD = 0.6;
|
|
4538
|
+
var SCORE_SIMILARITY_THRESHOLD = 0.1;
|
|
4539
|
+
function parseFrontmatter(content) {
|
|
4540
|
+
if (!content || !content.startsWith("---")) {
|
|
4541
|
+
return { frontmatter: {}, body: content };
|
|
4542
|
+
}
|
|
4543
|
+
const endIndex = content.indexOf("\n---", 3);
|
|
4544
|
+
if (endIndex === -1) {
|
|
4545
|
+
return { frontmatter: {}, body: content };
|
|
4546
|
+
}
|
|
4547
|
+
const frontmatterBlock = content.slice(4, endIndex);
|
|
4548
|
+
const body = content.slice(endIndex + 4).trim();
|
|
4549
|
+
const frontmatter = {};
|
|
4550
|
+
for (const line of frontmatterBlock.split("\n")) {
|
|
4551
|
+
const trimmed = line.trim();
|
|
4552
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
4553
|
+
const colonIndex = trimmed.indexOf(":");
|
|
4554
|
+
if (colonIndex === -1) continue;
|
|
4555
|
+
const key = trimmed.slice(0, colonIndex).trim();
|
|
4556
|
+
let value = trimmed.slice(colonIndex + 1).trim();
|
|
4557
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
4558
|
+
value = value.slice(1, -1);
|
|
4559
|
+
}
|
|
4560
|
+
frontmatter[key] = value;
|
|
4561
|
+
}
|
|
4562
|
+
return { frontmatter, body };
|
|
4563
|
+
}
|
|
4564
|
+
function extractContext(body, maxLength = 300) {
|
|
4565
|
+
const contextMatch = body.match(/##\s*Context\s*\n+([\s\S]*?)(?=\n##|\Z|$)/i);
|
|
4566
|
+
if (!contextMatch) {
|
|
4567
|
+
return "";
|
|
4568
|
+
}
|
|
4569
|
+
let context = contextMatch[1].trim();
|
|
4570
|
+
if (context.length > maxLength) {
|
|
4571
|
+
return context.slice(0, maxLength - 3) + "...";
|
|
4572
|
+
}
|
|
4573
|
+
return context;
|
|
4574
|
+
}
|
|
4575
|
+
function normalize(str) {
|
|
4576
|
+
return str.toLowerCase().replace(/[-_]/g, " ");
|
|
4577
|
+
}
|
|
4578
|
+
function findEpic(projectPath, query) {
|
|
4579
|
+
const epicsDir = (0, import_node_path3.join)(projectPath, ".saga", "epics");
|
|
4580
|
+
if (!(0, import_node_fs3.existsSync)(epicsDir)) {
|
|
4581
|
+
return {
|
|
4582
|
+
found: false,
|
|
4583
|
+
error: "No .saga/epics/ directory found"
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
const epicSlugs = (0, import_node_fs3.readdirSync)(epicsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
4587
|
+
if (epicSlugs.length === 0) {
|
|
4588
|
+
return {
|
|
4589
|
+
found: false,
|
|
4590
|
+
error: `No epic found matching '${query}'`
|
|
4591
|
+
};
|
|
4592
|
+
}
|
|
4593
|
+
const queryNormalized = query.toLowerCase().replace(/_/g, "-");
|
|
4594
|
+
for (const slug of epicSlugs) {
|
|
4595
|
+
if (slug.toLowerCase() === queryNormalized) {
|
|
4596
|
+
return {
|
|
4597
|
+
found: true,
|
|
4598
|
+
data: { slug }
|
|
4599
|
+
};
|
|
4600
|
+
}
|
|
4601
|
+
}
|
|
4602
|
+
const epics = epicSlugs.map((slug) => ({ slug }));
|
|
4603
|
+
const fuse = new Fuse(epics, {
|
|
4604
|
+
keys: ["slug"],
|
|
4605
|
+
threshold: MATCH_THRESHOLD,
|
|
4606
|
+
includeScore: true
|
|
4607
|
+
});
|
|
4608
|
+
const results = fuse.search(query);
|
|
4609
|
+
if (results.length === 0) {
|
|
4610
|
+
return {
|
|
4611
|
+
found: false,
|
|
4612
|
+
error: `No epic found matching '${query}'`
|
|
4613
|
+
};
|
|
4614
|
+
}
|
|
4615
|
+
if (results.length === 1) {
|
|
4616
|
+
return {
|
|
4617
|
+
found: true,
|
|
4618
|
+
data: results[0].item
|
|
4619
|
+
};
|
|
4620
|
+
}
|
|
4621
|
+
const bestScore = results[0].score ?? 0;
|
|
4622
|
+
const similarMatches = results.filter(
|
|
4623
|
+
(r) => (r.score ?? 0) - bestScore <= SCORE_SIMILARITY_THRESHOLD
|
|
4624
|
+
);
|
|
4625
|
+
if (similarMatches.length > 1) {
|
|
4626
|
+
return {
|
|
4627
|
+
found: false,
|
|
4628
|
+
matches: similarMatches.map((r) => r.item)
|
|
4629
|
+
};
|
|
4630
|
+
}
|
|
4631
|
+
if (bestScore <= FUZZY_THRESHOLD) {
|
|
4632
|
+
return {
|
|
4633
|
+
found: true,
|
|
4634
|
+
data: results[0].item
|
|
4635
|
+
};
|
|
4636
|
+
}
|
|
4637
|
+
return {
|
|
4638
|
+
found: false,
|
|
4639
|
+
matches: results.map((r) => r.item)
|
|
4640
|
+
};
|
|
4641
|
+
}
|
|
4642
|
+
function findStory(projectPath, query) {
|
|
4643
|
+
const worktreesDir = (0, import_node_path3.join)(projectPath, ".saga", "worktrees");
|
|
4644
|
+
if (!(0, import_node_fs3.existsSync)(worktreesDir)) {
|
|
4645
|
+
return {
|
|
4646
|
+
found: false,
|
|
4647
|
+
error: "No .saga/worktrees/ directory found. Run /generate-stories first."
|
|
4648
|
+
};
|
|
4649
|
+
}
|
|
4650
|
+
const allStories = [];
|
|
4651
|
+
const epicDirs = (0, import_node_fs3.readdirSync)(worktreesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
4652
|
+
for (const epicSlug of epicDirs) {
|
|
4653
|
+
const epicWorktreeDir = (0, import_node_path3.join)(worktreesDir, epicSlug);
|
|
4654
|
+
const storyDirs = (0, import_node_fs3.readdirSync)(epicWorktreeDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
4655
|
+
for (const storySlugFromDir of storyDirs) {
|
|
4656
|
+
const worktreePath = (0, import_node_path3.join)(epicWorktreeDir, storySlugFromDir);
|
|
4657
|
+
const storyPath = (0, import_node_path3.join)(
|
|
4658
|
+
worktreePath,
|
|
4659
|
+
".saga",
|
|
4660
|
+
"epics",
|
|
4661
|
+
epicSlug,
|
|
4662
|
+
"stories",
|
|
4663
|
+
storySlugFromDir,
|
|
4664
|
+
"story.md"
|
|
4665
|
+
);
|
|
4666
|
+
if (!(0, import_node_fs3.existsSync)(storyPath)) {
|
|
4667
|
+
continue;
|
|
4668
|
+
}
|
|
4669
|
+
try {
|
|
4670
|
+
const content = (0, import_node_fs3.readFileSync)(storyPath, "utf-8");
|
|
4671
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
4672
|
+
const storySlug = frontmatter.id || frontmatter.slug || storySlugFromDir;
|
|
4673
|
+
const title = frontmatter.title || "";
|
|
4674
|
+
const status = frontmatter.status || "";
|
|
4675
|
+
allStories.push({
|
|
4676
|
+
slug: storySlug,
|
|
4677
|
+
title,
|
|
4678
|
+
status,
|
|
4679
|
+
context: extractContext(body),
|
|
4680
|
+
epicSlug,
|
|
4681
|
+
storyPath,
|
|
4682
|
+
worktreePath
|
|
4683
|
+
});
|
|
4684
|
+
} catch {
|
|
4685
|
+
continue;
|
|
4686
|
+
}
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
if (allStories.length === 0) {
|
|
4690
|
+
return {
|
|
4691
|
+
found: false,
|
|
4692
|
+
error: `No story found matching '${query}'`
|
|
4693
|
+
};
|
|
4694
|
+
}
|
|
4695
|
+
const queryNormalized = normalize(query);
|
|
4696
|
+
for (const story of allStories) {
|
|
4697
|
+
if (normalize(story.slug) === queryNormalized) {
|
|
4698
|
+
return {
|
|
4699
|
+
found: true,
|
|
4700
|
+
data: story
|
|
4701
|
+
};
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
const fuse = new Fuse(allStories, {
|
|
4705
|
+
keys: [
|
|
4706
|
+
{ name: "slug", weight: 2 },
|
|
4707
|
+
// Prioritize slug matches
|
|
4708
|
+
{ name: "title", weight: 1 }
|
|
4709
|
+
],
|
|
4710
|
+
threshold: MATCH_THRESHOLD,
|
|
4711
|
+
includeScore: true
|
|
4712
|
+
});
|
|
4713
|
+
const results = fuse.search(query);
|
|
4714
|
+
if (results.length === 0) {
|
|
4715
|
+
return {
|
|
4716
|
+
found: false,
|
|
4717
|
+
error: `No story found matching '${query}'`
|
|
4718
|
+
};
|
|
4719
|
+
}
|
|
4720
|
+
if (results.length === 1) {
|
|
4721
|
+
return {
|
|
4722
|
+
found: true,
|
|
4723
|
+
data: results[0].item
|
|
4724
|
+
};
|
|
4725
|
+
}
|
|
4726
|
+
const bestScore = results[0].score ?? 0;
|
|
4727
|
+
const similarMatches = results.filter(
|
|
4728
|
+
(r) => (r.score ?? 0) - bestScore <= SCORE_SIMILARITY_THRESHOLD
|
|
4729
|
+
);
|
|
4730
|
+
if (similarMatches.length > 1) {
|
|
4731
|
+
return {
|
|
4732
|
+
found: false,
|
|
4733
|
+
matches: similarMatches.map((r) => r.item)
|
|
4734
|
+
};
|
|
4735
|
+
}
|
|
4736
|
+
if (bestScore <= FUZZY_THRESHOLD) {
|
|
4737
|
+
return {
|
|
4738
|
+
found: true,
|
|
4739
|
+
data: results[0].item
|
|
4740
|
+
};
|
|
4741
|
+
}
|
|
4742
|
+
return {
|
|
4743
|
+
found: false,
|
|
4744
|
+
matches: results.map((r) => r.item)
|
|
4745
|
+
};
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
// src/commands/implement.ts
|
|
3211
4749
|
var DEFAULT_MAX_CYCLES = 10;
|
|
3212
4750
|
var DEFAULT_MAX_TIME = 60;
|
|
3213
4751
|
var DEFAULT_MODEL = "opus";
|
|
@@ -3231,43 +4769,23 @@ var WORKER_OUTPUT_SCHEMA = {
|
|
|
3231
4769
|
},
|
|
3232
4770
|
required: ["status", "summary"]
|
|
3233
4771
|
};
|
|
3234
|
-
function
|
|
3235
|
-
const
|
|
3236
|
-
if (!
|
|
4772
|
+
function findStory2(projectPath, storySlug) {
|
|
4773
|
+
const result = findStory(projectPath, storySlug);
|
|
4774
|
+
if (!result.found) {
|
|
3237
4775
|
return null;
|
|
3238
4776
|
}
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
}
|
|
3246
|
-
const storyPath = (0, import_node_path3.join)(
|
|
3247
|
-
storyWorktree,
|
|
3248
|
-
".saga",
|
|
3249
|
-
"epics",
|
|
3250
|
-
epicSlug,
|
|
3251
|
-
"stories",
|
|
3252
|
-
storySlug,
|
|
3253
|
-
"story.md"
|
|
3254
|
-
);
|
|
3255
|
-
if ((0, import_node_fs3.existsSync)(storyPath)) {
|
|
3256
|
-
return {
|
|
3257
|
-
epicSlug,
|
|
3258
|
-
storySlug,
|
|
3259
|
-
storyPath,
|
|
3260
|
-
worktreePath: storyWorktree
|
|
3261
|
-
};
|
|
3262
|
-
}
|
|
3263
|
-
}
|
|
3264
|
-
return null;
|
|
4777
|
+
return {
|
|
4778
|
+
epicSlug: result.data.epicSlug,
|
|
4779
|
+
storySlug: result.data.slug,
|
|
4780
|
+
storyPath: result.data.storyPath,
|
|
4781
|
+
worktreePath: result.data.worktreePath
|
|
4782
|
+
};
|
|
3265
4783
|
}
|
|
3266
4784
|
function computeStoryPath(worktree, epicSlug, storySlug) {
|
|
3267
|
-
return (0,
|
|
4785
|
+
return (0, import_node_path4.join)(worktree, ".saga", "epics", epicSlug, "stories", storySlug, "story.md");
|
|
3268
4786
|
}
|
|
3269
4787
|
function validateStoryFiles(worktree, epicSlug, storySlug) {
|
|
3270
|
-
if (!(0,
|
|
4788
|
+
if (!(0, import_node_fs4.existsSync)(worktree)) {
|
|
3271
4789
|
return {
|
|
3272
4790
|
valid: false,
|
|
3273
4791
|
error: `Worktree not found at ${worktree}
|
|
@@ -3280,7 +4798,7 @@ To create the worktree, use: /task-resume ${storySlug}`
|
|
|
3280
4798
|
};
|
|
3281
4799
|
}
|
|
3282
4800
|
const storyPath = computeStoryPath(worktree, epicSlug, storySlug);
|
|
3283
|
-
if (!(0,
|
|
4801
|
+
if (!(0, import_node_fs4.existsSync)(storyPath)) {
|
|
3284
4802
|
return {
|
|
3285
4803
|
valid: false,
|
|
3286
4804
|
error: `story.md not found in worktree.
|
|
@@ -3294,7 +4812,7 @@ This may indicate an incomplete story setup.`
|
|
|
3294
4812
|
return { valid: true };
|
|
3295
4813
|
}
|
|
3296
4814
|
function getSkillRoot(pluginRoot) {
|
|
3297
|
-
return (0,
|
|
4815
|
+
return (0, import_node_path4.join)(pluginRoot, "skills", "execute-story");
|
|
3298
4816
|
}
|
|
3299
4817
|
function checkCommandExists(command) {
|
|
3300
4818
|
try {
|
|
@@ -3334,8 +4852,8 @@ function runDryRun(storyInfo, projectPath, pluginRoot) {
|
|
|
3334
4852
|
if (!claudeCheck.exists) allPassed = false;
|
|
3335
4853
|
if (pluginRoot) {
|
|
3336
4854
|
const skillRoot = getSkillRoot(pluginRoot);
|
|
3337
|
-
const workerPromptPath = (0,
|
|
3338
|
-
if ((0,
|
|
4855
|
+
const workerPromptPath = (0, import_node_path4.join)(skillRoot, WORKER_PROMPT_RELATIVE);
|
|
4856
|
+
if ((0, import_node_fs4.existsSync)(workerPromptPath)) {
|
|
3339
4857
|
checks.push({
|
|
3340
4858
|
name: "Worker prompt",
|
|
3341
4859
|
path: workerPromptPath,
|
|
@@ -3356,7 +4874,7 @@ function runDryRun(storyInfo, projectPath, pluginRoot) {
|
|
|
3356
4874
|
path: `${storyInfo.storySlug} (epic: ${storyInfo.epicSlug})`,
|
|
3357
4875
|
passed: true
|
|
3358
4876
|
});
|
|
3359
|
-
if ((0,
|
|
4877
|
+
if ((0, import_node_fs4.existsSync)(storyInfo.worktreePath)) {
|
|
3360
4878
|
checks.push({
|
|
3361
4879
|
name: "Worktree exists",
|
|
3362
4880
|
path: storyInfo.worktreePath,
|
|
@@ -3371,13 +4889,13 @@ function runDryRun(storyInfo, projectPath, pluginRoot) {
|
|
|
3371
4889
|
});
|
|
3372
4890
|
allPassed = false;
|
|
3373
4891
|
}
|
|
3374
|
-
if ((0,
|
|
4892
|
+
if ((0, import_node_fs4.existsSync)(storyInfo.worktreePath)) {
|
|
3375
4893
|
const storyMdPath = computeStoryPath(
|
|
3376
4894
|
storyInfo.worktreePath,
|
|
3377
4895
|
storyInfo.epicSlug,
|
|
3378
4896
|
storyInfo.storySlug
|
|
3379
4897
|
);
|
|
3380
|
-
if ((0,
|
|
4898
|
+
if ((0, import_node_fs4.existsSync)(storyMdPath)) {
|
|
3381
4899
|
checks.push({
|
|
3382
4900
|
name: "story.md in worktree",
|
|
3383
4901
|
path: storyMdPath,
|
|
@@ -3427,11 +4945,11 @@ function printDryRunResults(result) {
|
|
|
3427
4945
|
}
|
|
3428
4946
|
function loadWorkerPrompt(pluginRoot) {
|
|
3429
4947
|
const skillRoot = getSkillRoot(pluginRoot);
|
|
3430
|
-
const promptPath = (0,
|
|
3431
|
-
if (!(0,
|
|
4948
|
+
const promptPath = (0, import_node_path4.join)(skillRoot, WORKER_PROMPT_RELATIVE);
|
|
4949
|
+
if (!(0, import_node_fs4.existsSync)(promptPath)) {
|
|
3432
4950
|
throw new Error(`Worker prompt not found at ${promptPath}`);
|
|
3433
4951
|
}
|
|
3434
|
-
return (0,
|
|
4952
|
+
return (0, import_node_fs4.readFileSync)(promptPath, "utf-8");
|
|
3435
4953
|
}
|
|
3436
4954
|
function buildScopeSettings() {
|
|
3437
4955
|
const hookCommand = "npx @saga-ai/cli scope-validator";
|
|
@@ -3505,7 +5023,7 @@ function spawnWorker(prompt, model, settings, workingDir) {
|
|
|
3505
5023
|
return result.stdout || "";
|
|
3506
5024
|
}
|
|
3507
5025
|
function runLoop(epicSlug, storySlug, maxCycles, maxTime, model, projectDir, pluginRoot) {
|
|
3508
|
-
const worktree = (0,
|
|
5026
|
+
const worktree = (0, import_node_path4.join)(projectDir, ".saga", "worktrees", epicSlug, storySlug);
|
|
3509
5027
|
const validation = validateStoryFiles(worktree, epicSlug, storySlug);
|
|
3510
5028
|
if (!validation.valid) {
|
|
3511
5029
|
return {
|
|
@@ -3607,11 +5125,11 @@ async function implementCommand(storySlug, options) {
|
|
|
3607
5125
|
console.error(`Error: ${error.message}`);
|
|
3608
5126
|
process.exit(1);
|
|
3609
5127
|
}
|
|
3610
|
-
const storyInfo =
|
|
5128
|
+
const storyInfo = findStory2(projectPath, storySlug);
|
|
3611
5129
|
if (!storyInfo) {
|
|
3612
5130
|
console.error(`Error: Story '${storySlug}' not found in SAGA project.`);
|
|
3613
5131
|
console.error(`
|
|
3614
|
-
Searched in: ${(0,
|
|
5132
|
+
Searched in: ${(0, import_node_path4.join)(projectPath, ".saga", "worktrees")}`);
|
|
3615
5133
|
console.error("\nMake sure the story worktree exists and has a story.md file.");
|
|
3616
5134
|
console.error("Run /generate-stories to create story worktrees.");
|
|
3617
5135
|
process.exit(1);
|
|
@@ -3622,7 +5140,7 @@ Searched in: ${(0, import_node_path3.join)(projectPath, ".saga", "worktrees")}`)
|
|
|
3622
5140
|
printDryRunResults(dryRunResult);
|
|
3623
5141
|
process.exit(dryRunResult.success ? 0 : 1);
|
|
3624
5142
|
}
|
|
3625
|
-
if (!(0,
|
|
5143
|
+
if (!(0, import_node_fs4.existsSync)(storyInfo.worktreePath)) {
|
|
3626
5144
|
console.error(`Error: Worktree not found at ${storyInfo.worktreePath}`);
|
|
3627
5145
|
console.error("\nThe story worktree has not been created yet.");
|
|
3628
5146
|
console.error("Make sure the story was properly generated with /generate-stories.");
|
|
@@ -3767,9 +5285,31 @@ async function scopeValidatorCommand() {
|
|
|
3767
5285
|
process.exit(0);
|
|
3768
5286
|
}
|
|
3769
5287
|
|
|
5288
|
+
// src/commands/find.ts
|
|
5289
|
+
async function findCommand(query, options) {
|
|
5290
|
+
let projectPath;
|
|
5291
|
+
try {
|
|
5292
|
+
projectPath = resolveProjectPath(options.path);
|
|
5293
|
+
} catch (error) {
|
|
5294
|
+
console.log(JSON.stringify({ found: false, error: error.message }));
|
|
5295
|
+
process.exit(1);
|
|
5296
|
+
}
|
|
5297
|
+
const type = options.type ?? "story";
|
|
5298
|
+
let result;
|
|
5299
|
+
if (type === "epic") {
|
|
5300
|
+
result = findEpic(projectPath, query);
|
|
5301
|
+
} else {
|
|
5302
|
+
result = findStory(projectPath, query);
|
|
5303
|
+
}
|
|
5304
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5305
|
+
if (!result.found) {
|
|
5306
|
+
process.exit(1);
|
|
5307
|
+
}
|
|
5308
|
+
}
|
|
5309
|
+
|
|
3770
5310
|
// src/cli.ts
|
|
3771
|
-
var packageJsonPath = (0,
|
|
3772
|
-
var packageJson = JSON.parse((0,
|
|
5311
|
+
var packageJsonPath = (0, import_node_path5.join)(__dirname, "..", "package.json");
|
|
5312
|
+
var packageJson = JSON.parse((0, import_node_fs5.readFileSync)(packageJsonPath, "utf-8"));
|
|
3773
5313
|
var program2 = new Command();
|
|
3774
5314
|
program2.name("saga").description("CLI for SAGA - Structured Autonomous Goal Achievement").version(packageJson.version).addHelpCommand("help [command]", "Display help for a command");
|
|
3775
5315
|
program2.option("-p, --path <dir>", "Path to SAGA project directory (overrides auto-discovery)");
|
|
@@ -3787,6 +5327,13 @@ program2.command("implement <story-slug>").description("Run story implementation
|
|
|
3787
5327
|
dryRun: options.dryRun
|
|
3788
5328
|
});
|
|
3789
5329
|
});
|
|
5330
|
+
program2.command("find <query>").description("Find an epic or story by slug/title").option("--type <type>", "Type to search for: epic or story (default: story)").action(async (query, options) => {
|
|
5331
|
+
const globalOpts = program2.opts();
|
|
5332
|
+
await findCommand(query, {
|
|
5333
|
+
path: globalOpts.path,
|
|
5334
|
+
type: options.type
|
|
5335
|
+
});
|
|
5336
|
+
});
|
|
3790
5337
|
program2.command("dashboard").description("Start the dashboard server").option("--port <n>", "Port to run the server on (default: 3847)", parseInt).action(async (options) => {
|
|
3791
5338
|
const globalOpts = program2.opts();
|
|
3792
5339
|
await dashboardCommand({
|