@hanzogui/get 7.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +42 -0
- package/dist/cli.js +1703 -0
- package/package.json +39 -0
- package/readme.md +46 -0
- package/src/cli.ts +67 -0
- package/src/commands/add.ts +23 -0
- package/src/commands/init.ts +18 -0
- package/src/commands/list.ts +19 -0
- package/src/commands/pick.ts +61 -0
- package/src/constants.ts +1 -0
- package/src/lib/ansi.ts +12 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1703 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, {
|
|
23
|
+
get: all[name],
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
30
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
31
|
+
|
|
32
|
+
// src/lib/ansi.ts
|
|
33
|
+
var ESC = "\x1B[", isTTY, wrap = (open, close) => (s) => isTTY ? `${ESC}${open}m${s}${ESC}${close}m` : s, bold, dim, red, green, yellow, cyan, gray;
|
|
34
|
+
var init_ansi = __esm(() => {
|
|
35
|
+
isTTY = process.stdout.isTTY === true;
|
|
36
|
+
bold = wrap(1, 22);
|
|
37
|
+
dim = wrap(2, 22);
|
|
38
|
+
red = wrap(31, 39);
|
|
39
|
+
green = wrap(32, 39);
|
|
40
|
+
yellow = wrap(33, 39);
|
|
41
|
+
cyan = wrap(36, 39);
|
|
42
|
+
gray = wrap(90, 39);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// package.json
|
|
46
|
+
var require_package = __commonJS((exports, module) => {
|
|
47
|
+
module.exports = {
|
|
48
|
+
name: "@hanzogui/get",
|
|
49
|
+
version: "7.3.0",
|
|
50
|
+
description: "CLI installer for Hanzo GUI recipes.",
|
|
51
|
+
license: "BSD-3-Clause",
|
|
52
|
+
author: "Hanzo AI <dev@hanzo.ai>",
|
|
53
|
+
homepage: "https://gui.hanzo.ai",
|
|
54
|
+
repository: {
|
|
55
|
+
type: "git",
|
|
56
|
+
url: "https://github.com/hanzoai/gui.git",
|
|
57
|
+
directory: "pkgs/get"
|
|
58
|
+
},
|
|
59
|
+
bin: {
|
|
60
|
+
"hanzo-gui-get": "./dist/cli.js"
|
|
61
|
+
},
|
|
62
|
+
type: "module",
|
|
63
|
+
main: "./dist/cli.js",
|
|
64
|
+
source: "src/cli.ts",
|
|
65
|
+
files: [
|
|
66
|
+
"src/**/*.ts",
|
|
67
|
+
"!src/**/*.test.ts",
|
|
68
|
+
"dist"
|
|
69
|
+
],
|
|
70
|
+
publishConfig: {
|
|
71
|
+
access: "public"
|
|
72
|
+
},
|
|
73
|
+
scripts: {
|
|
74
|
+
build: "bun run build.ts",
|
|
75
|
+
clean: "rm -rf dist",
|
|
76
|
+
test: "bun test"
|
|
77
|
+
},
|
|
78
|
+
dependencies: {
|
|
79
|
+
"@hanzogui/recipes": "workspace:*",
|
|
80
|
+
"fuse.js": "7.1.0"
|
|
81
|
+
},
|
|
82
|
+
engines: {
|
|
83
|
+
node: ">=20"
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ../../node_modules/fuse.js/dist/fuse.mjs
|
|
89
|
+
function isArray(value) {
|
|
90
|
+
return !Array.isArray ? getTag(value) === "[object Array]" : Array.isArray(value);
|
|
91
|
+
}
|
|
92
|
+
function baseToString(value) {
|
|
93
|
+
if (typeof value == "string") {
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
let result = value + "";
|
|
97
|
+
return result == "0" && 1 / value == -INFINITY ? "-0" : result;
|
|
98
|
+
}
|
|
99
|
+
function toString(value) {
|
|
100
|
+
return value == null ? "" : baseToString(value);
|
|
101
|
+
}
|
|
102
|
+
function isString(value) {
|
|
103
|
+
return typeof value === "string";
|
|
104
|
+
}
|
|
105
|
+
function isNumber(value) {
|
|
106
|
+
return typeof value === "number";
|
|
107
|
+
}
|
|
108
|
+
function isBoolean(value) {
|
|
109
|
+
return value === true || value === false || isObjectLike(value) && getTag(value) == "[object Boolean]";
|
|
110
|
+
}
|
|
111
|
+
function isObject(value) {
|
|
112
|
+
return typeof value === "object";
|
|
113
|
+
}
|
|
114
|
+
function isObjectLike(value) {
|
|
115
|
+
return isObject(value) && value !== null;
|
|
116
|
+
}
|
|
117
|
+
function isDefined(value) {
|
|
118
|
+
return value !== undefined && value !== null;
|
|
119
|
+
}
|
|
120
|
+
function isBlank(value) {
|
|
121
|
+
return !value.trim().length;
|
|
122
|
+
}
|
|
123
|
+
function getTag(value) {
|
|
124
|
+
return value == null ? value === undefined ? "[object Undefined]" : "[object Null]" : Object.prototype.toString.call(value);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
class KeyStore {
|
|
128
|
+
constructor(keys) {
|
|
129
|
+
this._keys = [];
|
|
130
|
+
this._keyMap = {};
|
|
131
|
+
let totalWeight = 0;
|
|
132
|
+
keys.forEach((key) => {
|
|
133
|
+
let obj = createKey(key);
|
|
134
|
+
this._keys.push(obj);
|
|
135
|
+
this._keyMap[obj.id] = obj;
|
|
136
|
+
totalWeight += obj.weight;
|
|
137
|
+
});
|
|
138
|
+
this._keys.forEach((key) => {
|
|
139
|
+
key.weight /= totalWeight;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
get(keyId) {
|
|
143
|
+
return this._keyMap[keyId];
|
|
144
|
+
}
|
|
145
|
+
keys() {
|
|
146
|
+
return this._keys;
|
|
147
|
+
}
|
|
148
|
+
toJSON() {
|
|
149
|
+
return JSON.stringify(this._keys);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function createKey(key) {
|
|
153
|
+
let path = null;
|
|
154
|
+
let id = null;
|
|
155
|
+
let src = null;
|
|
156
|
+
let weight = 1;
|
|
157
|
+
let getFn = null;
|
|
158
|
+
if (isString(key) || isArray(key)) {
|
|
159
|
+
src = key;
|
|
160
|
+
path = createKeyPath(key);
|
|
161
|
+
id = createKeyId(key);
|
|
162
|
+
} else {
|
|
163
|
+
if (!hasOwn.call(key, "name")) {
|
|
164
|
+
throw new Error(MISSING_KEY_PROPERTY("name"));
|
|
165
|
+
}
|
|
166
|
+
const name = key.name;
|
|
167
|
+
src = name;
|
|
168
|
+
if (hasOwn.call(key, "weight")) {
|
|
169
|
+
weight = key.weight;
|
|
170
|
+
if (weight <= 0) {
|
|
171
|
+
throw new Error(INVALID_KEY_WEIGHT_VALUE(name));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
path = createKeyPath(name);
|
|
175
|
+
id = createKeyId(name);
|
|
176
|
+
getFn = key.getFn;
|
|
177
|
+
}
|
|
178
|
+
return { path, id, weight, src, getFn };
|
|
179
|
+
}
|
|
180
|
+
function createKeyPath(key) {
|
|
181
|
+
return isArray(key) ? key : key.split(".");
|
|
182
|
+
}
|
|
183
|
+
function createKeyId(key) {
|
|
184
|
+
return isArray(key) ? key.join(".") : key;
|
|
185
|
+
}
|
|
186
|
+
function get(obj, path) {
|
|
187
|
+
let list = [];
|
|
188
|
+
let arr = false;
|
|
189
|
+
const deepGet = (obj2, path2, index) => {
|
|
190
|
+
if (!isDefined(obj2)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (!path2[index]) {
|
|
194
|
+
list.push(obj2);
|
|
195
|
+
} else {
|
|
196
|
+
let key = path2[index];
|
|
197
|
+
const value = obj2[key];
|
|
198
|
+
if (!isDefined(value)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (index === path2.length - 1 && (isString(value) || isNumber(value) || isBoolean(value))) {
|
|
202
|
+
list.push(toString(value));
|
|
203
|
+
} else if (isArray(value)) {
|
|
204
|
+
arr = true;
|
|
205
|
+
for (let i = 0, len = value.length;i < len; i += 1) {
|
|
206
|
+
deepGet(value[i], path2, index + 1);
|
|
207
|
+
}
|
|
208
|
+
} else if (path2.length) {
|
|
209
|
+
deepGet(value, path2, index + 1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
deepGet(obj, isString(path) ? path.split(".") : path, 0);
|
|
214
|
+
return arr ? list : list[0];
|
|
215
|
+
}
|
|
216
|
+
function norm(weight = 1, mantissa = 3) {
|
|
217
|
+
const cache = new Map;
|
|
218
|
+
const m = Math.pow(10, mantissa);
|
|
219
|
+
return {
|
|
220
|
+
get(value) {
|
|
221
|
+
const numTokens = value.match(SPACE).length;
|
|
222
|
+
if (cache.has(numTokens)) {
|
|
223
|
+
return cache.get(numTokens);
|
|
224
|
+
}
|
|
225
|
+
const norm2 = 1 / Math.pow(numTokens, 0.5 * weight);
|
|
226
|
+
const n = parseFloat(Math.round(norm2 * m) / m);
|
|
227
|
+
cache.set(numTokens, n);
|
|
228
|
+
return n;
|
|
229
|
+
},
|
|
230
|
+
clear() {
|
|
231
|
+
cache.clear();
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
class FuseIndex {
|
|
237
|
+
constructor({
|
|
238
|
+
getFn = Config.getFn,
|
|
239
|
+
fieldNormWeight = Config.fieldNormWeight
|
|
240
|
+
} = {}) {
|
|
241
|
+
this.norm = norm(fieldNormWeight, 3);
|
|
242
|
+
this.getFn = getFn;
|
|
243
|
+
this.isCreated = false;
|
|
244
|
+
this.setIndexRecords();
|
|
245
|
+
}
|
|
246
|
+
setSources(docs = []) {
|
|
247
|
+
this.docs = docs;
|
|
248
|
+
}
|
|
249
|
+
setIndexRecords(records = []) {
|
|
250
|
+
this.records = records;
|
|
251
|
+
}
|
|
252
|
+
setKeys(keys = []) {
|
|
253
|
+
this.keys = keys;
|
|
254
|
+
this._keysMap = {};
|
|
255
|
+
keys.forEach((key, idx) => {
|
|
256
|
+
this._keysMap[key.id] = idx;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
create() {
|
|
260
|
+
if (this.isCreated || !this.docs.length) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
this.isCreated = true;
|
|
264
|
+
if (isString(this.docs[0])) {
|
|
265
|
+
this.docs.forEach((doc, docIndex) => {
|
|
266
|
+
this._addString(doc, docIndex);
|
|
267
|
+
});
|
|
268
|
+
} else {
|
|
269
|
+
this.docs.forEach((doc, docIndex) => {
|
|
270
|
+
this._addObject(doc, docIndex);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
this.norm.clear();
|
|
274
|
+
}
|
|
275
|
+
add(doc) {
|
|
276
|
+
const idx = this.size();
|
|
277
|
+
if (isString(doc)) {
|
|
278
|
+
this._addString(doc, idx);
|
|
279
|
+
} else {
|
|
280
|
+
this._addObject(doc, idx);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
removeAt(idx) {
|
|
284
|
+
this.records.splice(idx, 1);
|
|
285
|
+
for (let i = idx, len = this.size();i < len; i += 1) {
|
|
286
|
+
this.records[i].i -= 1;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
getValueForItemAtKeyId(item, keyId) {
|
|
290
|
+
return item[this._keysMap[keyId]];
|
|
291
|
+
}
|
|
292
|
+
size() {
|
|
293
|
+
return this.records.length;
|
|
294
|
+
}
|
|
295
|
+
_addString(doc, docIndex) {
|
|
296
|
+
if (!isDefined(doc) || isBlank(doc)) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
let record = {
|
|
300
|
+
v: doc,
|
|
301
|
+
i: docIndex,
|
|
302
|
+
n: this.norm.get(doc)
|
|
303
|
+
};
|
|
304
|
+
this.records.push(record);
|
|
305
|
+
}
|
|
306
|
+
_addObject(doc, docIndex) {
|
|
307
|
+
let record = { i: docIndex, $: {} };
|
|
308
|
+
this.keys.forEach((key, keyIndex) => {
|
|
309
|
+
let value = key.getFn ? key.getFn(doc) : this.getFn(doc, key.path);
|
|
310
|
+
if (!isDefined(value)) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (isArray(value)) {
|
|
314
|
+
let subRecords = [];
|
|
315
|
+
const stack = [{ nestedArrIndex: -1, value }];
|
|
316
|
+
while (stack.length) {
|
|
317
|
+
const { nestedArrIndex, value: value2 } = stack.pop();
|
|
318
|
+
if (!isDefined(value2)) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (isString(value2) && !isBlank(value2)) {
|
|
322
|
+
let subRecord = {
|
|
323
|
+
v: value2,
|
|
324
|
+
i: nestedArrIndex,
|
|
325
|
+
n: this.norm.get(value2)
|
|
326
|
+
};
|
|
327
|
+
subRecords.push(subRecord);
|
|
328
|
+
} else if (isArray(value2)) {
|
|
329
|
+
value2.forEach((item, k) => {
|
|
330
|
+
stack.push({
|
|
331
|
+
nestedArrIndex: k,
|
|
332
|
+
value: item
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
} else
|
|
336
|
+
;
|
|
337
|
+
}
|
|
338
|
+
record.$[keyIndex] = subRecords;
|
|
339
|
+
} else if (isString(value) && !isBlank(value)) {
|
|
340
|
+
let subRecord = {
|
|
341
|
+
v: value,
|
|
342
|
+
n: this.norm.get(value)
|
|
343
|
+
};
|
|
344
|
+
record.$[keyIndex] = subRecord;
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
this.records.push(record);
|
|
348
|
+
}
|
|
349
|
+
toJSON() {
|
|
350
|
+
return {
|
|
351
|
+
keys: this.keys,
|
|
352
|
+
records: this.records
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function createIndex(keys, docs, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) {
|
|
357
|
+
const myIndex = new FuseIndex({ getFn, fieldNormWeight });
|
|
358
|
+
myIndex.setKeys(keys.map(createKey));
|
|
359
|
+
myIndex.setSources(docs);
|
|
360
|
+
myIndex.create();
|
|
361
|
+
return myIndex;
|
|
362
|
+
}
|
|
363
|
+
function parseIndex(data, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) {
|
|
364
|
+
const { keys, records } = data;
|
|
365
|
+
const myIndex = new FuseIndex({ getFn, fieldNormWeight });
|
|
366
|
+
myIndex.setKeys(keys);
|
|
367
|
+
myIndex.setIndexRecords(records);
|
|
368
|
+
return myIndex;
|
|
369
|
+
}
|
|
370
|
+
function computeScore$1(pattern, {
|
|
371
|
+
errors = 0,
|
|
372
|
+
currentLocation = 0,
|
|
373
|
+
expectedLocation = 0,
|
|
374
|
+
distance = Config.distance,
|
|
375
|
+
ignoreLocation = Config.ignoreLocation
|
|
376
|
+
} = {}) {
|
|
377
|
+
const accuracy = errors / pattern.length;
|
|
378
|
+
if (ignoreLocation) {
|
|
379
|
+
return accuracy;
|
|
380
|
+
}
|
|
381
|
+
const proximity = Math.abs(expectedLocation - currentLocation);
|
|
382
|
+
if (!distance) {
|
|
383
|
+
return proximity ? 1 : accuracy;
|
|
384
|
+
}
|
|
385
|
+
return accuracy + proximity / distance;
|
|
386
|
+
}
|
|
387
|
+
function convertMaskToIndices(matchmask = [], minMatchCharLength = Config.minMatchCharLength) {
|
|
388
|
+
let indices = [];
|
|
389
|
+
let start = -1;
|
|
390
|
+
let end = -1;
|
|
391
|
+
let i = 0;
|
|
392
|
+
for (let len = matchmask.length;i < len; i += 1) {
|
|
393
|
+
let match = matchmask[i];
|
|
394
|
+
if (match && start === -1) {
|
|
395
|
+
start = i;
|
|
396
|
+
} else if (!match && start !== -1) {
|
|
397
|
+
end = i - 1;
|
|
398
|
+
if (end - start + 1 >= minMatchCharLength) {
|
|
399
|
+
indices.push([start, end]);
|
|
400
|
+
}
|
|
401
|
+
start = -1;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (matchmask[i - 1] && i - start >= minMatchCharLength) {
|
|
405
|
+
indices.push([start, i - 1]);
|
|
406
|
+
}
|
|
407
|
+
return indices;
|
|
408
|
+
}
|
|
409
|
+
function search(text, pattern, patternAlphabet, {
|
|
410
|
+
location = Config.location,
|
|
411
|
+
distance = Config.distance,
|
|
412
|
+
threshold = Config.threshold,
|
|
413
|
+
findAllMatches = Config.findAllMatches,
|
|
414
|
+
minMatchCharLength = Config.minMatchCharLength,
|
|
415
|
+
includeMatches = Config.includeMatches,
|
|
416
|
+
ignoreLocation = Config.ignoreLocation
|
|
417
|
+
} = {}) {
|
|
418
|
+
if (pattern.length > MAX_BITS) {
|
|
419
|
+
throw new Error(PATTERN_LENGTH_TOO_LARGE(MAX_BITS));
|
|
420
|
+
}
|
|
421
|
+
const patternLen = pattern.length;
|
|
422
|
+
const textLen = text.length;
|
|
423
|
+
const expectedLocation = Math.max(0, Math.min(location, textLen));
|
|
424
|
+
let currentThreshold = threshold;
|
|
425
|
+
let bestLocation = expectedLocation;
|
|
426
|
+
const computeMatches = minMatchCharLength > 1 || includeMatches;
|
|
427
|
+
const matchMask = computeMatches ? Array(textLen) : [];
|
|
428
|
+
let index;
|
|
429
|
+
while ((index = text.indexOf(pattern, bestLocation)) > -1) {
|
|
430
|
+
let score = computeScore$1(pattern, {
|
|
431
|
+
currentLocation: index,
|
|
432
|
+
expectedLocation,
|
|
433
|
+
distance,
|
|
434
|
+
ignoreLocation
|
|
435
|
+
});
|
|
436
|
+
currentThreshold = Math.min(score, currentThreshold);
|
|
437
|
+
bestLocation = index + patternLen;
|
|
438
|
+
if (computeMatches) {
|
|
439
|
+
let i = 0;
|
|
440
|
+
while (i < patternLen) {
|
|
441
|
+
matchMask[index + i] = 1;
|
|
442
|
+
i += 1;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
bestLocation = -1;
|
|
447
|
+
let lastBitArr = [];
|
|
448
|
+
let finalScore = 1;
|
|
449
|
+
let binMax = patternLen + textLen;
|
|
450
|
+
const mask = 1 << patternLen - 1;
|
|
451
|
+
for (let i = 0;i < patternLen; i += 1) {
|
|
452
|
+
let binMin = 0;
|
|
453
|
+
let binMid = binMax;
|
|
454
|
+
while (binMin < binMid) {
|
|
455
|
+
const score2 = computeScore$1(pattern, {
|
|
456
|
+
errors: i,
|
|
457
|
+
currentLocation: expectedLocation + binMid,
|
|
458
|
+
expectedLocation,
|
|
459
|
+
distance,
|
|
460
|
+
ignoreLocation
|
|
461
|
+
});
|
|
462
|
+
if (score2 <= currentThreshold) {
|
|
463
|
+
binMin = binMid;
|
|
464
|
+
} else {
|
|
465
|
+
binMax = binMid;
|
|
466
|
+
}
|
|
467
|
+
binMid = Math.floor((binMax - binMin) / 2 + binMin);
|
|
468
|
+
}
|
|
469
|
+
binMax = binMid;
|
|
470
|
+
let start = Math.max(1, expectedLocation - binMid + 1);
|
|
471
|
+
let finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen;
|
|
472
|
+
let bitArr = Array(finish + 2);
|
|
473
|
+
bitArr[finish + 1] = (1 << i) - 1;
|
|
474
|
+
for (let j = finish;j >= start; j -= 1) {
|
|
475
|
+
let currentLocation = j - 1;
|
|
476
|
+
let charMatch = patternAlphabet[text.charAt(currentLocation)];
|
|
477
|
+
if (computeMatches) {
|
|
478
|
+
matchMask[currentLocation] = +!!charMatch;
|
|
479
|
+
}
|
|
480
|
+
bitArr[j] = (bitArr[j + 1] << 1 | 1) & charMatch;
|
|
481
|
+
if (i) {
|
|
482
|
+
bitArr[j] |= (lastBitArr[j + 1] | lastBitArr[j]) << 1 | 1 | lastBitArr[j + 1];
|
|
483
|
+
}
|
|
484
|
+
if (bitArr[j] & mask) {
|
|
485
|
+
finalScore = computeScore$1(pattern, {
|
|
486
|
+
errors: i,
|
|
487
|
+
currentLocation,
|
|
488
|
+
expectedLocation,
|
|
489
|
+
distance,
|
|
490
|
+
ignoreLocation
|
|
491
|
+
});
|
|
492
|
+
if (finalScore <= currentThreshold) {
|
|
493
|
+
currentThreshold = finalScore;
|
|
494
|
+
bestLocation = currentLocation;
|
|
495
|
+
if (bestLocation <= expectedLocation) {
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
start = Math.max(1, 2 * expectedLocation - bestLocation);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const score = computeScore$1(pattern, {
|
|
503
|
+
errors: i + 1,
|
|
504
|
+
currentLocation: expectedLocation,
|
|
505
|
+
expectedLocation,
|
|
506
|
+
distance,
|
|
507
|
+
ignoreLocation
|
|
508
|
+
});
|
|
509
|
+
if (score > currentThreshold) {
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
lastBitArr = bitArr;
|
|
513
|
+
}
|
|
514
|
+
const result = {
|
|
515
|
+
isMatch: bestLocation >= 0,
|
|
516
|
+
score: Math.max(0.001, finalScore)
|
|
517
|
+
};
|
|
518
|
+
if (computeMatches) {
|
|
519
|
+
const indices = convertMaskToIndices(matchMask, minMatchCharLength);
|
|
520
|
+
if (!indices.length) {
|
|
521
|
+
result.isMatch = false;
|
|
522
|
+
} else if (includeMatches) {
|
|
523
|
+
result.indices = indices;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return result;
|
|
527
|
+
}
|
|
528
|
+
function createPatternAlphabet(pattern) {
|
|
529
|
+
let mask = {};
|
|
530
|
+
for (let i = 0, len = pattern.length;i < len; i += 1) {
|
|
531
|
+
const char = pattern.charAt(i);
|
|
532
|
+
mask[char] = (mask[char] || 0) | 1 << len - i - 1;
|
|
533
|
+
}
|
|
534
|
+
return mask;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
class BitapSearch {
|
|
538
|
+
constructor(pattern, {
|
|
539
|
+
location = Config.location,
|
|
540
|
+
threshold = Config.threshold,
|
|
541
|
+
distance = Config.distance,
|
|
542
|
+
includeMatches = Config.includeMatches,
|
|
543
|
+
findAllMatches = Config.findAllMatches,
|
|
544
|
+
minMatchCharLength = Config.minMatchCharLength,
|
|
545
|
+
isCaseSensitive = Config.isCaseSensitive,
|
|
546
|
+
ignoreDiacritics = Config.ignoreDiacritics,
|
|
547
|
+
ignoreLocation = Config.ignoreLocation
|
|
548
|
+
} = {}) {
|
|
549
|
+
this.options = {
|
|
550
|
+
location,
|
|
551
|
+
threshold,
|
|
552
|
+
distance,
|
|
553
|
+
includeMatches,
|
|
554
|
+
findAllMatches,
|
|
555
|
+
minMatchCharLength,
|
|
556
|
+
isCaseSensitive,
|
|
557
|
+
ignoreDiacritics,
|
|
558
|
+
ignoreLocation
|
|
559
|
+
};
|
|
560
|
+
pattern = isCaseSensitive ? pattern : pattern.toLowerCase();
|
|
561
|
+
pattern = ignoreDiacritics ? stripDiacritics(pattern) : pattern;
|
|
562
|
+
this.pattern = pattern;
|
|
563
|
+
this.chunks = [];
|
|
564
|
+
if (!this.pattern.length) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const addChunk = (pattern2, startIndex) => {
|
|
568
|
+
this.chunks.push({
|
|
569
|
+
pattern: pattern2,
|
|
570
|
+
alphabet: createPatternAlphabet(pattern2),
|
|
571
|
+
startIndex
|
|
572
|
+
});
|
|
573
|
+
};
|
|
574
|
+
const len = this.pattern.length;
|
|
575
|
+
if (len > MAX_BITS) {
|
|
576
|
+
let i = 0;
|
|
577
|
+
const remainder = len % MAX_BITS;
|
|
578
|
+
const end = len - remainder;
|
|
579
|
+
while (i < end) {
|
|
580
|
+
addChunk(this.pattern.substr(i, MAX_BITS), i);
|
|
581
|
+
i += MAX_BITS;
|
|
582
|
+
}
|
|
583
|
+
if (remainder) {
|
|
584
|
+
const startIndex = len - MAX_BITS;
|
|
585
|
+
addChunk(this.pattern.substr(startIndex), startIndex);
|
|
586
|
+
}
|
|
587
|
+
} else {
|
|
588
|
+
addChunk(this.pattern, 0);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
searchIn(text) {
|
|
592
|
+
const { isCaseSensitive, ignoreDiacritics, includeMatches } = this.options;
|
|
593
|
+
text = isCaseSensitive ? text : text.toLowerCase();
|
|
594
|
+
text = ignoreDiacritics ? stripDiacritics(text) : text;
|
|
595
|
+
if (this.pattern === text) {
|
|
596
|
+
let result2 = {
|
|
597
|
+
isMatch: true,
|
|
598
|
+
score: 0
|
|
599
|
+
};
|
|
600
|
+
if (includeMatches) {
|
|
601
|
+
result2.indices = [[0, text.length - 1]];
|
|
602
|
+
}
|
|
603
|
+
return result2;
|
|
604
|
+
}
|
|
605
|
+
const {
|
|
606
|
+
location,
|
|
607
|
+
distance,
|
|
608
|
+
threshold,
|
|
609
|
+
findAllMatches,
|
|
610
|
+
minMatchCharLength,
|
|
611
|
+
ignoreLocation
|
|
612
|
+
} = this.options;
|
|
613
|
+
let allIndices = [];
|
|
614
|
+
let totalScore = 0;
|
|
615
|
+
let hasMatches = false;
|
|
616
|
+
this.chunks.forEach(({ pattern, alphabet, startIndex }) => {
|
|
617
|
+
const { isMatch, score, indices } = search(text, pattern, alphabet, {
|
|
618
|
+
location: location + startIndex,
|
|
619
|
+
distance,
|
|
620
|
+
threshold,
|
|
621
|
+
findAllMatches,
|
|
622
|
+
minMatchCharLength,
|
|
623
|
+
includeMatches,
|
|
624
|
+
ignoreLocation
|
|
625
|
+
});
|
|
626
|
+
if (isMatch) {
|
|
627
|
+
hasMatches = true;
|
|
628
|
+
}
|
|
629
|
+
totalScore += score;
|
|
630
|
+
if (isMatch && indices) {
|
|
631
|
+
allIndices = [...allIndices, ...indices];
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
let result = {
|
|
635
|
+
isMatch: hasMatches,
|
|
636
|
+
score: hasMatches ? totalScore / this.chunks.length : 1
|
|
637
|
+
};
|
|
638
|
+
if (hasMatches && includeMatches) {
|
|
639
|
+
result.indices = allIndices;
|
|
640
|
+
}
|
|
641
|
+
return result;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
class BaseMatch {
|
|
646
|
+
constructor(pattern) {
|
|
647
|
+
this.pattern = pattern;
|
|
648
|
+
}
|
|
649
|
+
static isMultiMatch(pattern) {
|
|
650
|
+
return getMatch(pattern, this.multiRegex);
|
|
651
|
+
}
|
|
652
|
+
static isSingleMatch(pattern) {
|
|
653
|
+
return getMatch(pattern, this.singleRegex);
|
|
654
|
+
}
|
|
655
|
+
search() {}
|
|
656
|
+
}
|
|
657
|
+
function getMatch(pattern, exp) {
|
|
658
|
+
const matches = pattern.match(exp);
|
|
659
|
+
return matches ? matches[1] : null;
|
|
660
|
+
}
|
|
661
|
+
function parseQuery(pattern, options = {}) {
|
|
662
|
+
return pattern.split(OR_TOKEN).map((item) => {
|
|
663
|
+
let query = item.trim().split(SPACE_RE).filter((item2) => item2 && !!item2.trim());
|
|
664
|
+
let results = [];
|
|
665
|
+
for (let i = 0, len = query.length;i < len; i += 1) {
|
|
666
|
+
const queryItem = query[i];
|
|
667
|
+
let found = false;
|
|
668
|
+
let idx = -1;
|
|
669
|
+
while (!found && ++idx < searchersLen) {
|
|
670
|
+
const searcher = searchers[idx];
|
|
671
|
+
let token = searcher.isMultiMatch(queryItem);
|
|
672
|
+
if (token) {
|
|
673
|
+
results.push(new searcher(token, options));
|
|
674
|
+
found = true;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (found) {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
idx = -1;
|
|
681
|
+
while (++idx < searchersLen) {
|
|
682
|
+
const searcher = searchers[idx];
|
|
683
|
+
let token = searcher.isSingleMatch(queryItem);
|
|
684
|
+
if (token) {
|
|
685
|
+
results.push(new searcher(token, options));
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return results;
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
class ExtendedSearch {
|
|
695
|
+
constructor(pattern, {
|
|
696
|
+
isCaseSensitive = Config.isCaseSensitive,
|
|
697
|
+
ignoreDiacritics = Config.ignoreDiacritics,
|
|
698
|
+
includeMatches = Config.includeMatches,
|
|
699
|
+
minMatchCharLength = Config.minMatchCharLength,
|
|
700
|
+
ignoreLocation = Config.ignoreLocation,
|
|
701
|
+
findAllMatches = Config.findAllMatches,
|
|
702
|
+
location = Config.location,
|
|
703
|
+
threshold = Config.threshold,
|
|
704
|
+
distance = Config.distance
|
|
705
|
+
} = {}) {
|
|
706
|
+
this.query = null;
|
|
707
|
+
this.options = {
|
|
708
|
+
isCaseSensitive,
|
|
709
|
+
ignoreDiacritics,
|
|
710
|
+
includeMatches,
|
|
711
|
+
minMatchCharLength,
|
|
712
|
+
findAllMatches,
|
|
713
|
+
ignoreLocation,
|
|
714
|
+
location,
|
|
715
|
+
threshold,
|
|
716
|
+
distance
|
|
717
|
+
};
|
|
718
|
+
pattern = isCaseSensitive ? pattern : pattern.toLowerCase();
|
|
719
|
+
pattern = ignoreDiacritics ? stripDiacritics(pattern) : pattern;
|
|
720
|
+
this.pattern = pattern;
|
|
721
|
+
this.query = parseQuery(this.pattern, this.options);
|
|
722
|
+
}
|
|
723
|
+
static condition(_, options) {
|
|
724
|
+
return options.useExtendedSearch;
|
|
725
|
+
}
|
|
726
|
+
searchIn(text) {
|
|
727
|
+
const query = this.query;
|
|
728
|
+
if (!query) {
|
|
729
|
+
return {
|
|
730
|
+
isMatch: false,
|
|
731
|
+
score: 1
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
const { includeMatches, isCaseSensitive, ignoreDiacritics } = this.options;
|
|
735
|
+
text = isCaseSensitive ? text : text.toLowerCase();
|
|
736
|
+
text = ignoreDiacritics ? stripDiacritics(text) : text;
|
|
737
|
+
let numMatches = 0;
|
|
738
|
+
let allIndices = [];
|
|
739
|
+
let totalScore = 0;
|
|
740
|
+
for (let i = 0, qLen = query.length;i < qLen; i += 1) {
|
|
741
|
+
const searchers2 = query[i];
|
|
742
|
+
allIndices.length = 0;
|
|
743
|
+
numMatches = 0;
|
|
744
|
+
for (let j = 0, pLen = searchers2.length;j < pLen; j += 1) {
|
|
745
|
+
const searcher = searchers2[j];
|
|
746
|
+
const { isMatch, indices, score } = searcher.search(text);
|
|
747
|
+
if (isMatch) {
|
|
748
|
+
numMatches += 1;
|
|
749
|
+
totalScore += score;
|
|
750
|
+
if (includeMatches) {
|
|
751
|
+
const type = searcher.constructor.type;
|
|
752
|
+
if (MultiMatchSet.has(type)) {
|
|
753
|
+
allIndices = [...allIndices, ...indices];
|
|
754
|
+
} else {
|
|
755
|
+
allIndices.push(indices);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
} else {
|
|
759
|
+
totalScore = 0;
|
|
760
|
+
numMatches = 0;
|
|
761
|
+
allIndices.length = 0;
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (numMatches) {
|
|
766
|
+
let result = {
|
|
767
|
+
isMatch: true,
|
|
768
|
+
score: totalScore / numMatches
|
|
769
|
+
};
|
|
770
|
+
if (includeMatches) {
|
|
771
|
+
result.indices = allIndices;
|
|
772
|
+
}
|
|
773
|
+
return result;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return {
|
|
777
|
+
isMatch: false,
|
|
778
|
+
score: 1
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function register(...args) {
|
|
783
|
+
registeredSearchers.push(...args);
|
|
784
|
+
}
|
|
785
|
+
function createSearcher(pattern, options) {
|
|
786
|
+
for (let i = 0, len = registeredSearchers.length;i < len; i += 1) {
|
|
787
|
+
let searcherClass = registeredSearchers[i];
|
|
788
|
+
if (searcherClass.condition(pattern, options)) {
|
|
789
|
+
return new searcherClass(pattern, options);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return new BitapSearch(pattern, options);
|
|
793
|
+
}
|
|
794
|
+
function parse(query, options, { auto = true } = {}) {
|
|
795
|
+
const next = (query2) => {
|
|
796
|
+
let keys = Object.keys(query2);
|
|
797
|
+
const isQueryPath = isPath(query2);
|
|
798
|
+
if (!isQueryPath && keys.length > 1 && !isExpression(query2)) {
|
|
799
|
+
return next(convertToExplicit(query2));
|
|
800
|
+
}
|
|
801
|
+
if (isLeaf(query2)) {
|
|
802
|
+
const key = isQueryPath ? query2[KeyType.PATH] : keys[0];
|
|
803
|
+
const pattern = isQueryPath ? query2[KeyType.PATTERN] : query2[key];
|
|
804
|
+
if (!isString(pattern)) {
|
|
805
|
+
throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key));
|
|
806
|
+
}
|
|
807
|
+
const obj = {
|
|
808
|
+
keyId: createKeyId(key),
|
|
809
|
+
pattern
|
|
810
|
+
};
|
|
811
|
+
if (auto) {
|
|
812
|
+
obj.searcher = createSearcher(pattern, options);
|
|
813
|
+
}
|
|
814
|
+
return obj;
|
|
815
|
+
}
|
|
816
|
+
let node = {
|
|
817
|
+
children: [],
|
|
818
|
+
operator: keys[0]
|
|
819
|
+
};
|
|
820
|
+
keys.forEach((key) => {
|
|
821
|
+
const value = query2[key];
|
|
822
|
+
if (isArray(value)) {
|
|
823
|
+
value.forEach((item) => {
|
|
824
|
+
node.children.push(next(item));
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
return node;
|
|
829
|
+
};
|
|
830
|
+
if (!isExpression(query)) {
|
|
831
|
+
query = convertToExplicit(query);
|
|
832
|
+
}
|
|
833
|
+
return next(query);
|
|
834
|
+
}
|
|
835
|
+
function computeScore(results, { ignoreFieldNorm = Config.ignoreFieldNorm }) {
|
|
836
|
+
results.forEach((result) => {
|
|
837
|
+
let totalScore = 1;
|
|
838
|
+
result.matches.forEach(({ key, norm: norm2, score }) => {
|
|
839
|
+
const weight = key ? key.weight : null;
|
|
840
|
+
totalScore *= Math.pow(score === 0 && weight ? Number.EPSILON : score, (weight || 1) * (ignoreFieldNorm ? 1 : norm2));
|
|
841
|
+
});
|
|
842
|
+
result.score = totalScore;
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
function transformMatches(result, data) {
|
|
846
|
+
const matches = result.matches;
|
|
847
|
+
data.matches = [];
|
|
848
|
+
if (!isDefined(matches)) {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
matches.forEach((match) => {
|
|
852
|
+
if (!isDefined(match.indices) || !match.indices.length) {
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
const { indices, value } = match;
|
|
856
|
+
let obj = {
|
|
857
|
+
indices,
|
|
858
|
+
value
|
|
859
|
+
};
|
|
860
|
+
if (match.key) {
|
|
861
|
+
obj.key = match.key.src;
|
|
862
|
+
}
|
|
863
|
+
if (match.idx > -1) {
|
|
864
|
+
obj.refIndex = match.idx;
|
|
865
|
+
}
|
|
866
|
+
data.matches.push(obj);
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
function transformScore(result, data) {
|
|
870
|
+
data.score = result.score;
|
|
871
|
+
}
|
|
872
|
+
function format(results, docs, {
|
|
873
|
+
includeMatches = Config.includeMatches,
|
|
874
|
+
includeScore = Config.includeScore
|
|
875
|
+
} = {}) {
|
|
876
|
+
const transformers = [];
|
|
877
|
+
if (includeMatches)
|
|
878
|
+
transformers.push(transformMatches);
|
|
879
|
+
if (includeScore)
|
|
880
|
+
transformers.push(transformScore);
|
|
881
|
+
return results.map((result) => {
|
|
882
|
+
const { idx } = result;
|
|
883
|
+
const data = {
|
|
884
|
+
item: docs[idx],
|
|
885
|
+
refIndex: idx
|
|
886
|
+
};
|
|
887
|
+
if (transformers.length) {
|
|
888
|
+
transformers.forEach((transformer) => {
|
|
889
|
+
transformer(result, data);
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
return data;
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
class Fuse {
|
|
897
|
+
constructor(docs, options = {}, index) {
|
|
898
|
+
this.options = { ...Config, ...options };
|
|
899
|
+
if (this.options.useExtendedSearch && false) {}
|
|
900
|
+
this._keyStore = new KeyStore(this.options.keys);
|
|
901
|
+
this.setCollection(docs, index);
|
|
902
|
+
}
|
|
903
|
+
setCollection(docs, index) {
|
|
904
|
+
this._docs = docs;
|
|
905
|
+
if (index && !(index instanceof FuseIndex)) {
|
|
906
|
+
throw new Error(INCORRECT_INDEX_TYPE);
|
|
907
|
+
}
|
|
908
|
+
this._myIndex = index || createIndex(this.options.keys, this._docs, {
|
|
909
|
+
getFn: this.options.getFn,
|
|
910
|
+
fieldNormWeight: this.options.fieldNormWeight
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
add(doc) {
|
|
914
|
+
if (!isDefined(doc)) {
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
this._docs.push(doc);
|
|
918
|
+
this._myIndex.add(doc);
|
|
919
|
+
}
|
|
920
|
+
remove(predicate = () => false) {
|
|
921
|
+
const results = [];
|
|
922
|
+
for (let i = 0, len = this._docs.length;i < len; i += 1) {
|
|
923
|
+
const doc = this._docs[i];
|
|
924
|
+
if (predicate(doc, i)) {
|
|
925
|
+
this.removeAt(i);
|
|
926
|
+
i -= 1;
|
|
927
|
+
len -= 1;
|
|
928
|
+
results.push(doc);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return results;
|
|
932
|
+
}
|
|
933
|
+
removeAt(idx) {
|
|
934
|
+
this._docs.splice(idx, 1);
|
|
935
|
+
this._myIndex.removeAt(idx);
|
|
936
|
+
}
|
|
937
|
+
getIndex() {
|
|
938
|
+
return this._myIndex;
|
|
939
|
+
}
|
|
940
|
+
search(query, { limit = -1 } = {}) {
|
|
941
|
+
const {
|
|
942
|
+
includeMatches,
|
|
943
|
+
includeScore,
|
|
944
|
+
shouldSort,
|
|
945
|
+
sortFn,
|
|
946
|
+
ignoreFieldNorm
|
|
947
|
+
} = this.options;
|
|
948
|
+
let results = isString(query) ? isString(this._docs[0]) ? this._searchStringList(query) : this._searchObjectList(query) : this._searchLogical(query);
|
|
949
|
+
computeScore(results, { ignoreFieldNorm });
|
|
950
|
+
if (shouldSort) {
|
|
951
|
+
results.sort(sortFn);
|
|
952
|
+
}
|
|
953
|
+
if (isNumber(limit) && limit > -1) {
|
|
954
|
+
results = results.slice(0, limit);
|
|
955
|
+
}
|
|
956
|
+
return format(results, this._docs, {
|
|
957
|
+
includeMatches,
|
|
958
|
+
includeScore
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
_searchStringList(query) {
|
|
962
|
+
const searcher = createSearcher(query, this.options);
|
|
963
|
+
const { records } = this._myIndex;
|
|
964
|
+
const results = [];
|
|
965
|
+
records.forEach(({ v: text, i: idx, n: norm2 }) => {
|
|
966
|
+
if (!isDefined(text)) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const { isMatch, score, indices } = searcher.searchIn(text);
|
|
970
|
+
if (isMatch) {
|
|
971
|
+
results.push({
|
|
972
|
+
item: text,
|
|
973
|
+
idx,
|
|
974
|
+
matches: [{ score, value: text, norm: norm2, indices }]
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
return results;
|
|
979
|
+
}
|
|
980
|
+
_searchLogical(query) {
|
|
981
|
+
const expression = parse(query, this.options);
|
|
982
|
+
const evaluate = (node, item, idx) => {
|
|
983
|
+
if (!node.children) {
|
|
984
|
+
const { keyId, searcher } = node;
|
|
985
|
+
const matches = this._findMatches({
|
|
986
|
+
key: this._keyStore.get(keyId),
|
|
987
|
+
value: this._myIndex.getValueForItemAtKeyId(item, keyId),
|
|
988
|
+
searcher
|
|
989
|
+
});
|
|
990
|
+
if (matches && matches.length) {
|
|
991
|
+
return [
|
|
992
|
+
{
|
|
993
|
+
idx,
|
|
994
|
+
item,
|
|
995
|
+
matches
|
|
996
|
+
}
|
|
997
|
+
];
|
|
998
|
+
}
|
|
999
|
+
return [];
|
|
1000
|
+
}
|
|
1001
|
+
const res = [];
|
|
1002
|
+
for (let i = 0, len = node.children.length;i < len; i += 1) {
|
|
1003
|
+
const child = node.children[i];
|
|
1004
|
+
const result = evaluate(child, item, idx);
|
|
1005
|
+
if (result.length) {
|
|
1006
|
+
res.push(...result);
|
|
1007
|
+
} else if (node.operator === LogicalOperator.AND) {
|
|
1008
|
+
return [];
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return res;
|
|
1012
|
+
};
|
|
1013
|
+
const records = this._myIndex.records;
|
|
1014
|
+
const resultMap = {};
|
|
1015
|
+
const results = [];
|
|
1016
|
+
records.forEach(({ $: item, i: idx }) => {
|
|
1017
|
+
if (isDefined(item)) {
|
|
1018
|
+
let expResults = evaluate(expression, item, idx);
|
|
1019
|
+
if (expResults.length) {
|
|
1020
|
+
if (!resultMap[idx]) {
|
|
1021
|
+
resultMap[idx] = { idx, item, matches: [] };
|
|
1022
|
+
results.push(resultMap[idx]);
|
|
1023
|
+
}
|
|
1024
|
+
expResults.forEach(({ matches }) => {
|
|
1025
|
+
resultMap[idx].matches.push(...matches);
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
return results;
|
|
1031
|
+
}
|
|
1032
|
+
_searchObjectList(query) {
|
|
1033
|
+
const searcher = createSearcher(query, this.options);
|
|
1034
|
+
const { keys, records } = this._myIndex;
|
|
1035
|
+
const results = [];
|
|
1036
|
+
records.forEach(({ $: item, i: idx }) => {
|
|
1037
|
+
if (!isDefined(item)) {
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
let matches = [];
|
|
1041
|
+
keys.forEach((key, keyIndex) => {
|
|
1042
|
+
matches.push(...this._findMatches({
|
|
1043
|
+
key,
|
|
1044
|
+
value: item[keyIndex],
|
|
1045
|
+
searcher
|
|
1046
|
+
}));
|
|
1047
|
+
});
|
|
1048
|
+
if (matches.length) {
|
|
1049
|
+
results.push({
|
|
1050
|
+
idx,
|
|
1051
|
+
item,
|
|
1052
|
+
matches
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
return results;
|
|
1057
|
+
}
|
|
1058
|
+
_findMatches({ key, value, searcher }) {
|
|
1059
|
+
if (!isDefined(value)) {
|
|
1060
|
+
return [];
|
|
1061
|
+
}
|
|
1062
|
+
let matches = [];
|
|
1063
|
+
if (isArray(value)) {
|
|
1064
|
+
value.forEach(({ v: text, i: idx, n: norm2 }) => {
|
|
1065
|
+
if (!isDefined(text)) {
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
const { isMatch, score, indices } = searcher.searchIn(text);
|
|
1069
|
+
if (isMatch) {
|
|
1070
|
+
matches.push({
|
|
1071
|
+
score,
|
|
1072
|
+
key,
|
|
1073
|
+
value: text,
|
|
1074
|
+
idx,
|
|
1075
|
+
norm: norm2,
|
|
1076
|
+
indices
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
} else {
|
|
1081
|
+
const { v: text, n: norm2 } = value;
|
|
1082
|
+
const { isMatch, score, indices } = searcher.searchIn(text);
|
|
1083
|
+
if (isMatch) {
|
|
1084
|
+
matches.push({ score, key, value: text, norm: norm2, indices });
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return matches;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
var INFINITY, INCORRECT_INDEX_TYPE = "Incorrect 'index' type", LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = (key) => `Invalid value for key ${key}`, PATTERN_LENGTH_TOO_LARGE = (max) => `Pattern length exceeds max of ${max}.`, MISSING_KEY_PROPERTY = (name) => `Missing ${name} property in key`, INVALID_KEY_WEIGHT_VALUE = (key) => `Property 'weight' in key '${key}' must be a positive integer`, hasOwn, MatchOptions, BasicOptions, FuzzyOptions, AdvancedOptions, Config, SPACE, MAX_BITS = 32, stripDiacritics, ExactMatch, InverseExactMatch, PrefixExactMatch, InversePrefixExactMatch, SuffixExactMatch, InverseSuffixExactMatch, FuzzyMatch, IncludeMatch, searchers, searchersLen, SPACE_RE, OR_TOKEN = "|", MultiMatchSet, registeredSearchers, LogicalOperator, KeyType, isExpression = (query) => !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]), isPath = (query) => !!query[KeyType.PATH], isLeaf = (query) => !isArray(query) && isObject(query) && !isExpression(query), convertToExplicit = (query) => ({
|
|
1091
|
+
[LogicalOperator.AND]: Object.keys(query).map((key) => ({
|
|
1092
|
+
[key]: query[key]
|
|
1093
|
+
}))
|
|
1094
|
+
});
|
|
1095
|
+
var init_fuse = __esm(() => {
|
|
1096
|
+
INFINITY = 1 / 0;
|
|
1097
|
+
hasOwn = Object.prototype.hasOwnProperty;
|
|
1098
|
+
MatchOptions = {
|
|
1099
|
+
includeMatches: false,
|
|
1100
|
+
findAllMatches: false,
|
|
1101
|
+
minMatchCharLength: 1
|
|
1102
|
+
};
|
|
1103
|
+
BasicOptions = {
|
|
1104
|
+
isCaseSensitive: false,
|
|
1105
|
+
ignoreDiacritics: false,
|
|
1106
|
+
includeScore: false,
|
|
1107
|
+
keys: [],
|
|
1108
|
+
shouldSort: true,
|
|
1109
|
+
sortFn: (a, b) => a.score === b.score ? a.idx < b.idx ? -1 : 1 : a.score < b.score ? -1 : 1
|
|
1110
|
+
};
|
|
1111
|
+
FuzzyOptions = {
|
|
1112
|
+
location: 0,
|
|
1113
|
+
threshold: 0.6,
|
|
1114
|
+
distance: 100
|
|
1115
|
+
};
|
|
1116
|
+
AdvancedOptions = {
|
|
1117
|
+
useExtendedSearch: false,
|
|
1118
|
+
getFn: get,
|
|
1119
|
+
ignoreLocation: false,
|
|
1120
|
+
ignoreFieldNorm: false,
|
|
1121
|
+
fieldNormWeight: 1
|
|
1122
|
+
};
|
|
1123
|
+
Config = {
|
|
1124
|
+
...BasicOptions,
|
|
1125
|
+
...MatchOptions,
|
|
1126
|
+
...FuzzyOptions,
|
|
1127
|
+
...AdvancedOptions
|
|
1128
|
+
};
|
|
1129
|
+
SPACE = /[^ ]+/g;
|
|
1130
|
+
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;
|
|
1131
|
+
ExactMatch = class ExactMatch extends BaseMatch {
|
|
1132
|
+
constructor(pattern) {
|
|
1133
|
+
super(pattern);
|
|
1134
|
+
}
|
|
1135
|
+
static get type() {
|
|
1136
|
+
return "exact";
|
|
1137
|
+
}
|
|
1138
|
+
static get multiRegex() {
|
|
1139
|
+
return /^="(.*)"$/;
|
|
1140
|
+
}
|
|
1141
|
+
static get singleRegex() {
|
|
1142
|
+
return /^=(.*)$/;
|
|
1143
|
+
}
|
|
1144
|
+
search(text) {
|
|
1145
|
+
const isMatch = text === this.pattern;
|
|
1146
|
+
return {
|
|
1147
|
+
isMatch,
|
|
1148
|
+
score: isMatch ? 0 : 1,
|
|
1149
|
+
indices: [0, this.pattern.length - 1]
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
InverseExactMatch = class InverseExactMatch extends BaseMatch {
|
|
1154
|
+
constructor(pattern) {
|
|
1155
|
+
super(pattern);
|
|
1156
|
+
}
|
|
1157
|
+
static get type() {
|
|
1158
|
+
return "inverse-exact";
|
|
1159
|
+
}
|
|
1160
|
+
static get multiRegex() {
|
|
1161
|
+
return /^!"(.*)"$/;
|
|
1162
|
+
}
|
|
1163
|
+
static get singleRegex() {
|
|
1164
|
+
return /^!(.*)$/;
|
|
1165
|
+
}
|
|
1166
|
+
search(text) {
|
|
1167
|
+
const index = text.indexOf(this.pattern);
|
|
1168
|
+
const isMatch = index === -1;
|
|
1169
|
+
return {
|
|
1170
|
+
isMatch,
|
|
1171
|
+
score: isMatch ? 0 : 1,
|
|
1172
|
+
indices: [0, text.length - 1]
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
PrefixExactMatch = class PrefixExactMatch extends BaseMatch {
|
|
1177
|
+
constructor(pattern) {
|
|
1178
|
+
super(pattern);
|
|
1179
|
+
}
|
|
1180
|
+
static get type() {
|
|
1181
|
+
return "prefix-exact";
|
|
1182
|
+
}
|
|
1183
|
+
static get multiRegex() {
|
|
1184
|
+
return /^\^"(.*)"$/;
|
|
1185
|
+
}
|
|
1186
|
+
static get singleRegex() {
|
|
1187
|
+
return /^\^(.*)$/;
|
|
1188
|
+
}
|
|
1189
|
+
search(text) {
|
|
1190
|
+
const isMatch = text.startsWith(this.pattern);
|
|
1191
|
+
return {
|
|
1192
|
+
isMatch,
|
|
1193
|
+
score: isMatch ? 0 : 1,
|
|
1194
|
+
indices: [0, this.pattern.length - 1]
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
InversePrefixExactMatch = class InversePrefixExactMatch extends BaseMatch {
|
|
1199
|
+
constructor(pattern) {
|
|
1200
|
+
super(pattern);
|
|
1201
|
+
}
|
|
1202
|
+
static get type() {
|
|
1203
|
+
return "inverse-prefix-exact";
|
|
1204
|
+
}
|
|
1205
|
+
static get multiRegex() {
|
|
1206
|
+
return /^!\^"(.*)"$/;
|
|
1207
|
+
}
|
|
1208
|
+
static get singleRegex() {
|
|
1209
|
+
return /^!\^(.*)$/;
|
|
1210
|
+
}
|
|
1211
|
+
search(text) {
|
|
1212
|
+
const isMatch = !text.startsWith(this.pattern);
|
|
1213
|
+
return {
|
|
1214
|
+
isMatch,
|
|
1215
|
+
score: isMatch ? 0 : 1,
|
|
1216
|
+
indices: [0, text.length - 1]
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
SuffixExactMatch = class SuffixExactMatch extends BaseMatch {
|
|
1221
|
+
constructor(pattern) {
|
|
1222
|
+
super(pattern);
|
|
1223
|
+
}
|
|
1224
|
+
static get type() {
|
|
1225
|
+
return "suffix-exact";
|
|
1226
|
+
}
|
|
1227
|
+
static get multiRegex() {
|
|
1228
|
+
return /^"(.*)"\$$/;
|
|
1229
|
+
}
|
|
1230
|
+
static get singleRegex() {
|
|
1231
|
+
return /^(.*)\$$/;
|
|
1232
|
+
}
|
|
1233
|
+
search(text) {
|
|
1234
|
+
const isMatch = text.endsWith(this.pattern);
|
|
1235
|
+
return {
|
|
1236
|
+
isMatch,
|
|
1237
|
+
score: isMatch ? 0 : 1,
|
|
1238
|
+
indices: [text.length - this.pattern.length, text.length - 1]
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
InverseSuffixExactMatch = class InverseSuffixExactMatch extends BaseMatch {
|
|
1243
|
+
constructor(pattern) {
|
|
1244
|
+
super(pattern);
|
|
1245
|
+
}
|
|
1246
|
+
static get type() {
|
|
1247
|
+
return "inverse-suffix-exact";
|
|
1248
|
+
}
|
|
1249
|
+
static get multiRegex() {
|
|
1250
|
+
return /^!"(.*)"\$$/;
|
|
1251
|
+
}
|
|
1252
|
+
static get singleRegex() {
|
|
1253
|
+
return /^!(.*)\$$/;
|
|
1254
|
+
}
|
|
1255
|
+
search(text) {
|
|
1256
|
+
const isMatch = !text.endsWith(this.pattern);
|
|
1257
|
+
return {
|
|
1258
|
+
isMatch,
|
|
1259
|
+
score: isMatch ? 0 : 1,
|
|
1260
|
+
indices: [0, text.length - 1]
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
FuzzyMatch = class FuzzyMatch extends BaseMatch {
|
|
1265
|
+
constructor(pattern, {
|
|
1266
|
+
location = Config.location,
|
|
1267
|
+
threshold = Config.threshold,
|
|
1268
|
+
distance = Config.distance,
|
|
1269
|
+
includeMatches = Config.includeMatches,
|
|
1270
|
+
findAllMatches = Config.findAllMatches,
|
|
1271
|
+
minMatchCharLength = Config.minMatchCharLength,
|
|
1272
|
+
isCaseSensitive = Config.isCaseSensitive,
|
|
1273
|
+
ignoreDiacritics = Config.ignoreDiacritics,
|
|
1274
|
+
ignoreLocation = Config.ignoreLocation
|
|
1275
|
+
} = {}) {
|
|
1276
|
+
super(pattern);
|
|
1277
|
+
this._bitapSearch = new BitapSearch(pattern, {
|
|
1278
|
+
location,
|
|
1279
|
+
threshold,
|
|
1280
|
+
distance,
|
|
1281
|
+
includeMatches,
|
|
1282
|
+
findAllMatches,
|
|
1283
|
+
minMatchCharLength,
|
|
1284
|
+
isCaseSensitive,
|
|
1285
|
+
ignoreDiacritics,
|
|
1286
|
+
ignoreLocation
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
static get type() {
|
|
1290
|
+
return "fuzzy";
|
|
1291
|
+
}
|
|
1292
|
+
static get multiRegex() {
|
|
1293
|
+
return /^"(.*)"$/;
|
|
1294
|
+
}
|
|
1295
|
+
static get singleRegex() {
|
|
1296
|
+
return /^(.*)$/;
|
|
1297
|
+
}
|
|
1298
|
+
search(text) {
|
|
1299
|
+
return this._bitapSearch.searchIn(text);
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
IncludeMatch = class IncludeMatch extends BaseMatch {
|
|
1303
|
+
constructor(pattern) {
|
|
1304
|
+
super(pattern);
|
|
1305
|
+
}
|
|
1306
|
+
static get type() {
|
|
1307
|
+
return "include";
|
|
1308
|
+
}
|
|
1309
|
+
static get multiRegex() {
|
|
1310
|
+
return /^'"(.*)"$/;
|
|
1311
|
+
}
|
|
1312
|
+
static get singleRegex() {
|
|
1313
|
+
return /^'(.*)$/;
|
|
1314
|
+
}
|
|
1315
|
+
search(text) {
|
|
1316
|
+
let location = 0;
|
|
1317
|
+
let index;
|
|
1318
|
+
const indices = [];
|
|
1319
|
+
const patternLen = this.pattern.length;
|
|
1320
|
+
while ((index = text.indexOf(this.pattern, location)) > -1) {
|
|
1321
|
+
location = index + patternLen;
|
|
1322
|
+
indices.push([index, location - 1]);
|
|
1323
|
+
}
|
|
1324
|
+
const isMatch = !!indices.length;
|
|
1325
|
+
return {
|
|
1326
|
+
isMatch,
|
|
1327
|
+
score: isMatch ? 0 : 1,
|
|
1328
|
+
indices
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
searchers = [
|
|
1333
|
+
ExactMatch,
|
|
1334
|
+
IncludeMatch,
|
|
1335
|
+
PrefixExactMatch,
|
|
1336
|
+
InversePrefixExactMatch,
|
|
1337
|
+
InverseSuffixExactMatch,
|
|
1338
|
+
SuffixExactMatch,
|
|
1339
|
+
InverseExactMatch,
|
|
1340
|
+
FuzzyMatch
|
|
1341
|
+
];
|
|
1342
|
+
searchersLen = searchers.length;
|
|
1343
|
+
SPACE_RE = / +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/;
|
|
1344
|
+
MultiMatchSet = new Set([FuzzyMatch.type, IncludeMatch.type]);
|
|
1345
|
+
registeredSearchers = [];
|
|
1346
|
+
LogicalOperator = {
|
|
1347
|
+
AND: "$and",
|
|
1348
|
+
OR: "$or"
|
|
1349
|
+
};
|
|
1350
|
+
KeyType = {
|
|
1351
|
+
PATH: "$path",
|
|
1352
|
+
PATTERN: "$val"
|
|
1353
|
+
};
|
|
1354
|
+
Fuse.version = "7.1.0";
|
|
1355
|
+
Fuse.createIndex = createIndex;
|
|
1356
|
+
Fuse.parseIndex = parseIndex;
|
|
1357
|
+
Fuse.config = Config;
|
|
1358
|
+
{
|
|
1359
|
+
Fuse.parseQuery = parse;
|
|
1360
|
+
}
|
|
1361
|
+
{
|
|
1362
|
+
register(ExtendedSearch);
|
|
1363
|
+
}
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
// ../recipes/dist/index.js
|
|
1367
|
+
import { existsSync, promises as fs } from "node:fs";
|
|
1368
|
+
import path from "node:path";
|
|
1369
|
+
import { existsSync as existsSync2, promises as fs2 } from "node:fs";
|
|
1370
|
+
import path2 from "node:path";
|
|
1371
|
+
function resolveBaseUrl(override) {
|
|
1372
|
+
return override ?? process.env.HANZO_BASE_URL ?? DEFAULT_BASE_URL;
|
|
1373
|
+
}
|
|
1374
|
+
async function findProjectRoot(start = process.cwd()) {
|
|
1375
|
+
let dir = start;
|
|
1376
|
+
while (dir !== path.parse(dir).root) {
|
|
1377
|
+
const pkgPath = path.join(dir, "package.json");
|
|
1378
|
+
if (existsSync(pkgPath)) {
|
|
1379
|
+
try {
|
|
1380
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
|
|
1381
|
+
if (pkg.workspaces || existsSync(path.join(dir, "pnpm-workspace.yaml")))
|
|
1382
|
+
return dir;
|
|
1383
|
+
} catch {}
|
|
1384
|
+
}
|
|
1385
|
+
if (existsSync(path.join(dir, ".git")))
|
|
1386
|
+
return dir;
|
|
1387
|
+
dir = path.dirname(dir);
|
|
1388
|
+
}
|
|
1389
|
+
return start;
|
|
1390
|
+
}
|
|
1391
|
+
async function readConfig(root) {
|
|
1392
|
+
for (const name of DEFAULT_CONFIG_FILENAMES) {
|
|
1393
|
+
const p = path.join(root, name);
|
|
1394
|
+
if (existsSync(p)) {
|
|
1395
|
+
try {
|
|
1396
|
+
return JSON.parse(await fs.readFile(p, "utf8"));
|
|
1397
|
+
} catch {}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return {};
|
|
1401
|
+
}
|
|
1402
|
+
function resolveInstallDir(root, config) {
|
|
1403
|
+
return path.join(root, config.installDir ?? DEFAULT_INSTALL_DIR);
|
|
1404
|
+
}
|
|
1405
|
+
async function getInstallDir(opts) {
|
|
1406
|
+
if (opts.installDir)
|
|
1407
|
+
return opts.installDir;
|
|
1408
|
+
const root = await findProjectRoot(opts.cwd);
|
|
1409
|
+
return resolveInstallDir(root, await readConfig(root));
|
|
1410
|
+
}
|
|
1411
|
+
async function installRecipe(recipe, opts = {}) {
|
|
1412
|
+
const installDir = await getInstallDir(opts);
|
|
1413
|
+
const written = [];
|
|
1414
|
+
for (const file of recipe.files) {
|
|
1415
|
+
const dest = path2.join(installDir, file.path);
|
|
1416
|
+
const dir = path2.dirname(dest);
|
|
1417
|
+
if (!existsSync2(dir))
|
|
1418
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
1419
|
+
await fs2.writeFile(dest, file.source);
|
|
1420
|
+
written.push(dest);
|
|
1421
|
+
}
|
|
1422
|
+
return written;
|
|
1423
|
+
}
|
|
1424
|
+
async function installPlan(plan, opts = {}) {
|
|
1425
|
+
const written = [];
|
|
1426
|
+
for (const r of [plan.recipe, ...plan.transitiveRecipes]) {
|
|
1427
|
+
written.push(...await installRecipe(r, opts));
|
|
1428
|
+
}
|
|
1429
|
+
return written;
|
|
1430
|
+
}
|
|
1431
|
+
async function getJSON(url) {
|
|
1432
|
+
const res = await fetch(url, { headers: { Accept: "application/json" } });
|
|
1433
|
+
if (!res.ok) {
|
|
1434
|
+
throw Object.assign(new Error(`Base request failed: ${res.status} ${res.statusText}`), {
|
|
1435
|
+
status: res.status
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
return await res.json();
|
|
1439
|
+
}
|
|
1440
|
+
function records(baseUrl) {
|
|
1441
|
+
return `${resolveBaseUrl(baseUrl)}/v1/base/collections/${RECIPES_COLLECTION}/records`;
|
|
1442
|
+
}
|
|
1443
|
+
async function listRecipes(opts = {}) {
|
|
1444
|
+
const url = `${records(opts.baseUrl)}?perPage=200&sort=name&fields=id,slug,name,description,category`;
|
|
1445
|
+
const data = await getJSON(url);
|
|
1446
|
+
return data.items;
|
|
1447
|
+
}
|
|
1448
|
+
async function getRecipe(slug, opts = {}) {
|
|
1449
|
+
const filter = encodeURIComponent(`slug="${slug}"`);
|
|
1450
|
+
const url = `${records(opts.baseUrl)}?perPage=1&filter=${filter}`;
|
|
1451
|
+
const data = await getJSON(url);
|
|
1452
|
+
const recipe = data.items[0];
|
|
1453
|
+
if (!recipe) {
|
|
1454
|
+
throw Object.assign(new Error(`Recipe not found: ${slug}`), { status: 404 });
|
|
1455
|
+
}
|
|
1456
|
+
return recipe;
|
|
1457
|
+
}
|
|
1458
|
+
async function resolveRecipe(slug, opts = {}) {
|
|
1459
|
+
const seen = new Map;
|
|
1460
|
+
const queue = [slug];
|
|
1461
|
+
while (queue.length) {
|
|
1462
|
+
const next = queue.shift();
|
|
1463
|
+
if (seen.has(next))
|
|
1464
|
+
continue;
|
|
1465
|
+
const recipe2 = await getRecipe(next, opts);
|
|
1466
|
+
seen.set(next, recipe2);
|
|
1467
|
+
for (const dep of recipe2.recipeDeps ?? []) {
|
|
1468
|
+
if (!seen.has(dep))
|
|
1469
|
+
queue.push(dep);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
const installDir = opts.installDir ?? await (async () => {
|
|
1473
|
+
const root = await findProjectRoot(opts.cwd);
|
|
1474
|
+
return resolveInstallDir(root, await readConfig(root));
|
|
1475
|
+
})();
|
|
1476
|
+
const recipe = seen.get(slug);
|
|
1477
|
+
const transitiveRecipes = Array.from(seen.values()).filter((r) => r.slug !== slug);
|
|
1478
|
+
const path3 = await import("node:path");
|
|
1479
|
+
const targetPaths = [recipe, ...transitiveRecipes].flatMap((r) => r.files.map((f) => path3.join(installDir, f.path)));
|
|
1480
|
+
return { recipe, transitiveRecipes, targetPaths };
|
|
1481
|
+
}
|
|
1482
|
+
var DEFAULT_BASE_URL = "https://base.hanzo.ai", RECIPES_COLLECTION = "gui_recipes", DEFAULT_INSTALL_DIR = "src/gui", DEFAULT_CONFIG_FILENAMES;
|
|
1483
|
+
var init_dist = __esm(() => {
|
|
1484
|
+
DEFAULT_CONFIG_FILENAMES = ["gui.config.json", ".guirc.json"];
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
// src/commands/add.ts
|
|
1488
|
+
var exports_add = {};
|
|
1489
|
+
__export(exports_add, {
|
|
1490
|
+
add: () => add
|
|
1491
|
+
});
|
|
1492
|
+
async function add(slug) {
|
|
1493
|
+
const plan = await resolveRecipe(slug);
|
|
1494
|
+
const paths = await installPlan(plan);
|
|
1495
|
+
process.stdout.write(green(`Installed "${plan.recipe.name}"`) + `
|
|
1496
|
+
`);
|
|
1497
|
+
if (plan.transitiveRecipes.length > 0) {
|
|
1498
|
+
process.stdout.write(gray(`+ ${plan.transitiveRecipes.length} dependent recipe${plan.transitiveRecipes.length === 1 ? "" : "s"}: ${plan.transitiveRecipes.map((r) => r.slug).join(", ")}
|
|
1499
|
+
`));
|
|
1500
|
+
}
|
|
1501
|
+
for (const p of paths)
|
|
1502
|
+
process.stdout.write(` ${p}
|
|
1503
|
+
`);
|
|
1504
|
+
if (plan.recipe.npmDeps.length > 0) {
|
|
1505
|
+
process.stdout.write(`
|
|
1506
|
+
` + gray("Install these npm deps:") + `
|
|
1507
|
+
`);
|
|
1508
|
+
process.stdout.write(` ${bold("npm i " + plan.recipe.npmDeps.join(" "))}
|
|
1509
|
+
`);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
var init_add = __esm(() => {
|
|
1513
|
+
init_dist();
|
|
1514
|
+
init_ansi();
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
// src/commands/pick.ts
|
|
1518
|
+
var exports_pick = {};
|
|
1519
|
+
__export(exports_pick, {
|
|
1520
|
+
pick: () => pick
|
|
1521
|
+
});
|
|
1522
|
+
import * as readline from "node:readline/promises";
|
|
1523
|
+
async function pick() {
|
|
1524
|
+
const recipes = await listRecipes();
|
|
1525
|
+
if (recipes.length === 0) {
|
|
1526
|
+
process.stdout.write(`No recipes published yet.
|
|
1527
|
+
`);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
const fuse = new Fuse(recipes, {
|
|
1531
|
+
keys: ["name", "slug", "category", "description"],
|
|
1532
|
+
threshold: 0.4
|
|
1533
|
+
});
|
|
1534
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1535
|
+
try {
|
|
1536
|
+
const query = (await rl.question(bold("Search recipes") + " (empty = show all): ")).trim();
|
|
1537
|
+
const hits = query ? fuse.search(query).map((h) => h.item) : recipes;
|
|
1538
|
+
if (hits.length === 0) {
|
|
1539
|
+
process.stdout.write(dim(`No matches.
|
|
1540
|
+
`));
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
process.stdout.write(`
|
|
1544
|
+
`);
|
|
1545
|
+
hits.slice(0, 20).forEach((r, i) => {
|
|
1546
|
+
process.stdout.write(` ${cyan(String(i + 1).padStart(2))} ${bold(r.name)} ${gray(r.slug)}
|
|
1547
|
+
`);
|
|
1548
|
+
process.stdout.write(` ${gray(r.description)}
|
|
1549
|
+
`);
|
|
1550
|
+
});
|
|
1551
|
+
process.stdout.write(`
|
|
1552
|
+
`);
|
|
1553
|
+
const pickRaw = (await rl.question("Pick a number (or paste a slug): ")).trim();
|
|
1554
|
+
if (!pickRaw)
|
|
1555
|
+
return;
|
|
1556
|
+
let slug;
|
|
1557
|
+
const asNum = Number.parseInt(pickRaw, 10);
|
|
1558
|
+
if (!Number.isNaN(asNum) && asNum >= 1 && asNum <= hits.length) {
|
|
1559
|
+
slug = hits[asNum - 1].slug;
|
|
1560
|
+
} else if (recipes.find((r) => r.slug === pickRaw)) {
|
|
1561
|
+
slug = pickRaw;
|
|
1562
|
+
}
|
|
1563
|
+
if (!slug) {
|
|
1564
|
+
process.stdout.write(dim(`No recipe matches "${pickRaw}".
|
|
1565
|
+
`));
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
process.stdout.write(`
|
|
1569
|
+
`);
|
|
1570
|
+
await add(slug);
|
|
1571
|
+
} finally {
|
|
1572
|
+
rl.close();
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
var init_pick = __esm(() => {
|
|
1576
|
+
init_fuse();
|
|
1577
|
+
init_dist();
|
|
1578
|
+
init_ansi();
|
|
1579
|
+
init_add();
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
// src/commands/list.ts
|
|
1583
|
+
var exports_list = {};
|
|
1584
|
+
__export(exports_list, {
|
|
1585
|
+
list: () => list
|
|
1586
|
+
});
|
|
1587
|
+
async function list() {
|
|
1588
|
+
const recipes = await listRecipes();
|
|
1589
|
+
if (recipes.length === 0) {
|
|
1590
|
+
process.stdout.write(yellow(`No recipes published yet.
|
|
1591
|
+
`));
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
const byCategory = {};
|
|
1595
|
+
for (const r of recipes)
|
|
1596
|
+
(byCategory[r.category || "misc"] ||= []).push(r);
|
|
1597
|
+
for (const [cat, items] of Object.entries(byCategory)) {
|
|
1598
|
+
process.stdout.write(`
|
|
1599
|
+
${bold(cat)}
|
|
1600
|
+
`);
|
|
1601
|
+
for (const r of items) {
|
|
1602
|
+
process.stdout.write(` ${cyan(r.slug.padEnd(24))} ${gray(r.description)}
|
|
1603
|
+
`);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
process.stdout.write(`
|
|
1607
|
+
`);
|
|
1608
|
+
}
|
|
1609
|
+
var init_list = __esm(() => {
|
|
1610
|
+
init_dist();
|
|
1611
|
+
init_ansi();
|
|
1612
|
+
});
|
|
1613
|
+
|
|
1614
|
+
// src/commands/init.ts
|
|
1615
|
+
var exports_init = {};
|
|
1616
|
+
__export(exports_init, {
|
|
1617
|
+
init: () => init
|
|
1618
|
+
});
|
|
1619
|
+
import { existsSync as existsSync3, promises as fs3 } from "node:fs";
|
|
1620
|
+
import path3 from "node:path";
|
|
1621
|
+
async function init() {
|
|
1622
|
+
const root = await findProjectRoot();
|
|
1623
|
+
const dest = path3.join(root, "gui.config.json");
|
|
1624
|
+
if (existsSync3(dest)) {
|
|
1625
|
+
process.stdout.write(yellow(`gui.config.json already exists at ${dest}
|
|
1626
|
+
`));
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
await fs3.writeFile(dest, JSON.stringify({ installDir: DEFAULT_INSTALL_DIR }, null, 2) + `
|
|
1630
|
+
`);
|
|
1631
|
+
process.stdout.write(green(`Wrote ${dest}
|
|
1632
|
+
`));
|
|
1633
|
+
}
|
|
1634
|
+
var init_init = __esm(() => {
|
|
1635
|
+
init_dist();
|
|
1636
|
+
init_ansi();
|
|
1637
|
+
});
|
|
1638
|
+
|
|
1639
|
+
// src/constants.ts
|
|
1640
|
+
var CLI_NAME = "hanzo-gui-get";
|
|
1641
|
+
|
|
1642
|
+
// src/cli.ts
|
|
1643
|
+
init_ansi();
|
|
1644
|
+
var HELP = `${CLI_NAME} — install Hanzo GUI recipes into your project.
|
|
1645
|
+
|
|
1646
|
+
Usage:
|
|
1647
|
+
${CLI_NAME} Interactive picker.
|
|
1648
|
+
${CLI_NAME} add <slug> Install a single recipe.
|
|
1649
|
+
${CLI_NAME} list List all available recipes.
|
|
1650
|
+
${CLI_NAME} init Write gui.config.json.
|
|
1651
|
+
${CLI_NAME} --help Show this message.
|
|
1652
|
+
${CLI_NAME} --version Print version.
|
|
1653
|
+
|
|
1654
|
+
Recipes are fetched from Hanzo Base at $HANZO_BASE_URL (default https://base.hanzo.ai).
|
|
1655
|
+
Recipe files vendor into src/gui/ by default; override via gui.config.json.
|
|
1656
|
+
`;
|
|
1657
|
+
async function main() {
|
|
1658
|
+
const [, , cmd, ...rest] = process.argv;
|
|
1659
|
+
if (cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
1660
|
+
process.stdout.write(HELP);
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
if (cmd === "--version" || cmd === "-v") {
|
|
1664
|
+
const pkg = await Promise.resolve().then(() => __toESM(require_package(), 1));
|
|
1665
|
+
process.stdout.write((pkg.default?.version ?? "unknown") + `
|
|
1666
|
+
`);
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
if (!cmd) {
|
|
1670
|
+
const { pick: pick2 } = await Promise.resolve().then(() => (init_pick(), exports_pick));
|
|
1671
|
+
await pick2();
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
if (cmd === "add") {
|
|
1675
|
+
if (!rest[0]) {
|
|
1676
|
+
process.stderr.write(`Usage: ${CLI_NAME} add <recipe-slug>
|
|
1677
|
+
`);
|
|
1678
|
+
process.exit(1);
|
|
1679
|
+
}
|
|
1680
|
+
const { add: add2 } = await Promise.resolve().then(() => (init_add(), exports_add));
|
|
1681
|
+
await add2(rest[0]);
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
if (cmd === "list") {
|
|
1685
|
+
const { list: list2 } = await Promise.resolve().then(() => (init_list(), exports_list));
|
|
1686
|
+
await list2();
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
if (cmd === "init") {
|
|
1690
|
+
const { init: init2 } = await Promise.resolve().then(() => (init_init(), exports_init));
|
|
1691
|
+
await init2();
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
process.stderr.write(`Unknown command: ${cmd}
|
|
1695
|
+
|
|
1696
|
+
${HELP}`);
|
|
1697
|
+
process.exit(1);
|
|
1698
|
+
}
|
|
1699
|
+
main().catch((e) => {
|
|
1700
|
+
process.stderr.write(red(e instanceof Error ? e.message : String(e)) + `
|
|
1701
|
+
`);
|
|
1702
|
+
process.exit(1);
|
|
1703
|
+
});
|