@xenonbyte/da-vinci-workflow 0.1.20 → 0.1.22
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/CHANGELOG.md +29 -0
- package/README.md +40 -54
- package/README.zh-CN.md +40 -54
- package/commands/claude/dv/build.md +6 -0
- package/commands/claude/dv/continue.md +1 -0
- package/commands/codex/prompts/dv-build.md +4 -0
- package/commands/codex/prompts/dv-continue.md +1 -0
- package/commands/gemini/dv/build.toml +4 -0
- package/commands/gemini/dv/continue.toml +1 -0
- package/docs/constraint-files.md +109 -0
- package/docs/dv-command-reference.md +24 -0
- package/docs/visual-adapters.md +24 -80
- package/docs/visual-assist-presets/desktop-app.md +20 -68
- package/docs/visual-assist-presets/mobile-app.md +20 -68
- package/docs/visual-assist-presets/tablet-app.md +20 -68
- package/docs/visual-assist-presets/web-app.md +20 -68
- package/docs/workflow-examples.md +29 -13
- package/docs/workflow-overview.md +2 -0
- package/docs/zh-CN/constraint-files.md +111 -0
- package/docs/zh-CN/dv-command-reference.md +24 -0
- package/docs/zh-CN/visual-adapters.md +24 -80
- package/docs/zh-CN/visual-assist-presets/desktop-app.md +20 -68
- package/docs/zh-CN/visual-assist-presets/mobile-app.md +20 -68
- package/docs/zh-CN/visual-assist-presets/tablet-app.md +20 -68
- package/docs/zh-CN/visual-assist-presets/web-app.md +20 -68
- package/docs/zh-CN/workflow-examples.md +29 -13
- package/docs/zh-CN/workflow-overview.md +2 -0
- package/examples/greenfield-spec-markupflow/DA-VINCI.md +13 -13
- package/examples/greenfield-spec-markupflow/README.md +7 -0
- package/examples/greenfield-spec-markupflow/pencil-design.md +5 -0
- package/lib/cli.js +194 -2
- package/lib/icon-aliases.js +165 -0
- package/lib/icon-search.js +370 -0
- package/lib/icon-sync.js +361 -0
- package/lib/pencil-session.js +6 -0
- package/package.json +5 -2
- package/references/artifact-templates.md +24 -0
- package/references/icon-aliases.example.json +12 -0
- package/scripts/fixtures/mock-pencil.js +49 -0
- package/scripts/test-icon-aliases.js +87 -0
- package/scripts/test-icon-search.js +72 -0
- package/scripts/test-icon-sync.js +178 -0
- package/scripts/test-mode-consistency.js +50 -0
- package/scripts/test-pen-persistence.js +7 -3
- package/scripts/test-pencil-session.js +40 -0
- package/scripts/test-persistence-flows.js +31 -1
|
@@ -46,6 +46,13 @@ Visual adapter used for this forward test:
|
|
|
46
46
|
- runtime declaration: explicit before first anchor pass
|
|
47
47
|
- fallback: `native-da-vinci` if no local adapter were available
|
|
48
48
|
|
|
49
|
+
Icon library workflow used for this forward test:
|
|
50
|
+
|
|
51
|
+
- sync command: `da-vinci icon-sync`
|
|
52
|
+
- alias template: `cp references/icon-aliases.example.json ~/.da-vinci/icon-aliases.json`
|
|
53
|
+
- lookup command example: `da-vinci icon-search --query "settings publish workflow" --family material --top 6`
|
|
54
|
+
- gate policy: icon lookup remains advisory by default; use `--strict` only for explicit CI hard gates
|
|
55
|
+
|
|
49
56
|
Important note:
|
|
50
57
|
|
|
51
58
|
- the `.pen` source was authored in a live Pencil session and the example records `.da-vinci/designs/project-baseline.pen` as the intended project-local persisted path
|
|
@@ -41,6 +41,11 @@
|
|
|
41
41
|
- keep the structural emphasis on three core capabilities on page one
|
|
42
42
|
- keep the import, annotate, publish pipeline readable on page two
|
|
43
43
|
|
|
44
|
+
## Icon Notes
|
|
45
|
+
- functional icons are expected to use `icon_font` instead of placeholder geometry
|
|
46
|
+
- preferred family for this pass: `Material Symbols Rounded`
|
|
47
|
+
- when icon intent is ambiguous, resolve candidates via `da-vinci icon-search` before writing `batch_design`
|
|
48
|
+
|
|
44
49
|
## Validation Notes
|
|
45
50
|
- both screens were reviewed in Pencil during authoring
|
|
46
51
|
- no obvious clipping, overlap, or alignment failures were observed in the design pass
|
package/lib/cli.js
CHANGED
|
@@ -29,6 +29,19 @@ const {
|
|
|
29
29
|
endPencilSession,
|
|
30
30
|
getPencilSessionStatus
|
|
31
31
|
} = require("./pencil-session");
|
|
32
|
+
const {
|
|
33
|
+
searchIconLibrary,
|
|
34
|
+
formatIconSearchReport
|
|
35
|
+
} = require("./icon-search");
|
|
36
|
+
const {
|
|
37
|
+
syncIconCatalog,
|
|
38
|
+
loadIconCatalog,
|
|
39
|
+
formatIconSyncReport
|
|
40
|
+
} = require("./icon-sync");
|
|
41
|
+
const {
|
|
42
|
+
loadIconAliases,
|
|
43
|
+
expandQueryWithAliases
|
|
44
|
+
} = require("./icon-aliases");
|
|
32
45
|
|
|
33
46
|
function getOption(args, name) {
|
|
34
47
|
const direct = args.find((arg) => arg.startsWith(`${name}=`));
|
|
@@ -90,6 +103,8 @@ function printHelp() {
|
|
|
90
103
|
" da-vinci status",
|
|
91
104
|
" da-vinci validate-assets",
|
|
92
105
|
" da-vinci audit [project-path]",
|
|
106
|
+
" da-vinci icon-sync [--output <path>] [--timeout-ms <value>] [--strict]",
|
|
107
|
+
" da-vinci icon-search --query <text> [--family <value>] [--top <value>] [--catalog <path>] [--aliases <path>] [--json]",
|
|
93
108
|
" da-vinci preflight-pencil --ops-file <path>",
|
|
94
109
|
" da-vinci ensure-pen --output <path>",
|
|
95
110
|
" da-vinci write-pen --output <path> --nodes-file <path> [--variables-file <path>]",
|
|
@@ -100,7 +115,7 @@ function printHelp() {
|
|
|
100
115
|
" da-vinci pencil-lock status",
|
|
101
116
|
" da-vinci pencil-session begin --project <path> --pen <path>",
|
|
102
117
|
" da-vinci pencil-session persist --project <path> --pen <path> --nodes-file <path> [--variables-file <path>]",
|
|
103
|
-
" da-vinci pencil-session end --project <path> --pen <path>
|
|
118
|
+
" da-vinci pencil-session end --project <path> --pen <path> --nodes-file <path> [--variables-file <path>]",
|
|
104
119
|
" da-vinci pencil-session status --project <path>",
|
|
105
120
|
" da-vinci --version",
|
|
106
121
|
"",
|
|
@@ -108,6 +123,14 @@ function printHelp() {
|
|
|
108
123
|
" --platform <value> codex, claude, gemini, or all",
|
|
109
124
|
" --home <path> override HOME for installation targets",
|
|
110
125
|
" --project <path> override project path for audit",
|
|
126
|
+
" --catalog <path> icon catalog path for icon-search/icon-sync (default: ~/.da-vinci/icon-catalog.json)",
|
|
127
|
+
" --aliases <path> icon alias mapping file for icon-search (default: ~/.da-vinci/icon-aliases.json)",
|
|
128
|
+
" --query <text> icon-search query text",
|
|
129
|
+
" --family <value> icon-search family filter: all, material, rounded, outlined, sharp, lucide, feather, phosphor",
|
|
130
|
+
" --top <value> icon-search result count (1-50, default 8)",
|
|
131
|
+
" --timeout-ms <value> network timeout for icon-sync requests",
|
|
132
|
+
" --strict fail icon-sync when any upstream source request fails",
|
|
133
|
+
" --json print structured JSON for icon-search",
|
|
111
134
|
" --pen <path> registered .pen path for sync checks",
|
|
112
135
|
" --ops-file <path> Pencil batch operations file for preflight",
|
|
113
136
|
" --input <path> input .pen file for snapshot-pen",
|
|
@@ -128,7 +151,28 @@ function printHelp() {
|
|
|
128
151
|
async function runCli(argv) {
|
|
129
152
|
const [command] = argv;
|
|
130
153
|
const homeDir = getOption(argv, "--home");
|
|
131
|
-
const positionalArgs = getPositionalArgs(argv.slice(1), [
|
|
154
|
+
const positionalArgs = getPositionalArgs(argv.slice(1), [
|
|
155
|
+
"--home",
|
|
156
|
+
"--platform",
|
|
157
|
+
"--project",
|
|
158
|
+
"--mode",
|
|
159
|
+
"--change",
|
|
160
|
+
"--query",
|
|
161
|
+
"--family",
|
|
162
|
+
"--top",
|
|
163
|
+
"--catalog",
|
|
164
|
+
"--aliases",
|
|
165
|
+
"--timeout-ms",
|
|
166
|
+
"--ops-file",
|
|
167
|
+
"--input",
|
|
168
|
+
"--output",
|
|
169
|
+
"--pen",
|
|
170
|
+
"--nodes-file",
|
|
171
|
+
"--variables-file",
|
|
172
|
+
"--version",
|
|
173
|
+
"--owner",
|
|
174
|
+
"--wait-ms"
|
|
175
|
+
]);
|
|
132
176
|
|
|
133
177
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
134
178
|
printHelp();
|
|
@@ -184,6 +228,149 @@ async function runCli(argv) {
|
|
|
184
228
|
return;
|
|
185
229
|
}
|
|
186
230
|
|
|
231
|
+
if (command === "icon-sync") {
|
|
232
|
+
const outputPath = getOption(argv, "--output") || getOption(argv, "--catalog");
|
|
233
|
+
const timeoutMs = getOption(argv, "--timeout-ms");
|
|
234
|
+
const strict = argv.includes("--strict");
|
|
235
|
+
|
|
236
|
+
const result = await syncIconCatalog({
|
|
237
|
+
outputPath,
|
|
238
|
+
timeoutMs,
|
|
239
|
+
strict,
|
|
240
|
+
homeDir
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
console.log(formatIconSyncReport(result));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (command === "icon-search") {
|
|
248
|
+
const family = getOption(argv, "--family") || "all";
|
|
249
|
+
const topRaw = getOption(argv, "--top");
|
|
250
|
+
const queryOption = getOption(argv, "--query");
|
|
251
|
+
const catalogPath = getOption(argv, "--catalog");
|
|
252
|
+
const aliasesPath = getOption(argv, "--aliases");
|
|
253
|
+
const iconPositional = getPositionalArgs(argv.slice(1), [
|
|
254
|
+
"--home",
|
|
255
|
+
"--query",
|
|
256
|
+
"--family",
|
|
257
|
+
"--top",
|
|
258
|
+
"--catalog",
|
|
259
|
+
"--aliases"
|
|
260
|
+
]);
|
|
261
|
+
const query = queryOption || iconPositional.join(" ").trim();
|
|
262
|
+
|
|
263
|
+
if (!query) {
|
|
264
|
+
throw new Error("`icon-search` requires `--query <text>` or positional query text.");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let top;
|
|
268
|
+
if (topRaw !== undefined) {
|
|
269
|
+
top = Number.parseInt(topRaw, 10);
|
|
270
|
+
if (!Number.isFinite(top) || top < 1 || top > 50) {
|
|
271
|
+
throw new Error("`icon-search --top` must be an integer between 1 and 50.");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let loadedCatalog = null;
|
|
276
|
+
let loadedCatalogPath = null;
|
|
277
|
+
let catalogLoadError = null;
|
|
278
|
+
let loadedAliases = null;
|
|
279
|
+
let loadedAliasesPath = null;
|
|
280
|
+
let aliasesLoadError = null;
|
|
281
|
+
let aliasExpansion = {
|
|
282
|
+
extraTokens: [],
|
|
283
|
+
matchedAliases: []
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const loaded = loadIconCatalog({
|
|
288
|
+
catalogPath,
|
|
289
|
+
homeDir
|
|
290
|
+
});
|
|
291
|
+
loadedCatalog = loaded.catalog;
|
|
292
|
+
loadedCatalogPath = loaded.catalogPath;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
catalogLoadError = error.message || String(error);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
const loaded = loadIconAliases({
|
|
299
|
+
aliasPath: aliasesPath,
|
|
300
|
+
homeDir
|
|
301
|
+
});
|
|
302
|
+
loadedAliases = loaded;
|
|
303
|
+
loadedAliasesPath = loaded.aliasPath;
|
|
304
|
+
aliasExpansion = expandQueryWithAliases(query, loaded.aliases);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
aliasesLoadError = error.message || String(error);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const result = searchIconLibrary(query, {
|
|
310
|
+
family,
|
|
311
|
+
top,
|
|
312
|
+
catalog: loadedCatalog ? loadedCatalog.icons : [],
|
|
313
|
+
extraQueryTokens: aliasExpansion.extraTokens
|
|
314
|
+
});
|
|
315
|
+
const jsonOutput = argv.includes("--json");
|
|
316
|
+
|
|
317
|
+
const resultWithMeta = {
|
|
318
|
+
...result,
|
|
319
|
+
catalog: {
|
|
320
|
+
path: loadedCatalogPath || "(unresolved)",
|
|
321
|
+
loaded: Boolean(loadedCatalog),
|
|
322
|
+
iconCount: loadedCatalog ? loadedCatalog.iconCount : 0,
|
|
323
|
+
generatedAt: loadedCatalog ? loadedCatalog.generatedAt : null,
|
|
324
|
+
error: catalogLoadError
|
|
325
|
+
},
|
|
326
|
+
aliases: {
|
|
327
|
+
path: loadedAliasesPath || "(unresolved)",
|
|
328
|
+
loaded: loadedAliases ? Boolean(loadedAliases.loaded) : false,
|
|
329
|
+
available: Boolean(loadedAliases),
|
|
330
|
+
source: loadedAliases ? loadedAliases.source : null,
|
|
331
|
+
matched: aliasExpansion.matchedAliases.length,
|
|
332
|
+
extraTokens: aliasExpansion.extraTokens,
|
|
333
|
+
error: aliasesLoadError
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
if (jsonOutput) {
|
|
338
|
+
console.log(JSON.stringify(resultWithMeta, null, 2));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (resultWithMeta.catalog.loaded) {
|
|
343
|
+
console.log(
|
|
344
|
+
`Icon catalog: ${resultWithMeta.catalog.path} (${resultWithMeta.catalog.iconCount} icons, ${resultWithMeta.catalog.generatedAt})`
|
|
345
|
+
);
|
|
346
|
+
} else if (resultWithMeta.catalog.error) {
|
|
347
|
+
console.log(
|
|
348
|
+
`Icon catalog: ${resultWithMeta.catalog.path} (load failed: ${resultWithMeta.catalog.error}; using built-in fallback index)`
|
|
349
|
+
);
|
|
350
|
+
} else {
|
|
351
|
+
console.log(
|
|
352
|
+
`Icon catalog: ${resultWithMeta.catalog.path} (not found; using built-in fallback index; run \`da-vinci icon-sync\`)`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (resultWithMeta.aliases.available) {
|
|
357
|
+
console.log(
|
|
358
|
+
`Icon aliases: ${resultWithMeta.aliases.path} (${resultWithMeta.aliases.source}, matched ${resultWithMeta.aliases.matched})`
|
|
359
|
+
);
|
|
360
|
+
} else if (resultWithMeta.aliases.error) {
|
|
361
|
+
console.log(
|
|
362
|
+
`Icon aliases: ${resultWithMeta.aliases.path} (load failed: ${resultWithMeta.aliases.error})`
|
|
363
|
+
);
|
|
364
|
+
} else {
|
|
365
|
+
console.log(
|
|
366
|
+
`Icon aliases: ${resultWithMeta.aliases.path} (not found; using built-in defaults only)`
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
console.log(formatIconSearchReport(result));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
187
374
|
if (command === "preflight-pencil") {
|
|
188
375
|
const opsFile = getOption(argv, "--ops-file");
|
|
189
376
|
let operations = "";
|
|
@@ -456,6 +643,11 @@ async function runCli(argv) {
|
|
|
456
643
|
if (!penPath) {
|
|
457
644
|
throw new Error("`pencil-session end` requires `--pen <path>`.");
|
|
458
645
|
}
|
|
646
|
+
if (!nodesFile && !force) {
|
|
647
|
+
throw new Error(
|
|
648
|
+
"`pencil-session end` requires `--nodes-file <path>` (and `--variables-file <path>` when available). Use `--force` only for emergency lock release."
|
|
649
|
+
);
|
|
650
|
+
}
|
|
459
651
|
|
|
460
652
|
const result = endPencilSession({
|
|
461
653
|
projectPath,
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
const DEFAULT_ALIASES = {
|
|
6
|
+
"保险箱": ["vault", "safe box", "archive", "inventory_2"],
|
|
7
|
+
"金库": ["vault", "safe box", "lock", "inventory_2"],
|
|
8
|
+
"解密": ["unlock", "lock_open", "key", "verified_user"],
|
|
9
|
+
"加密": ["lock", "shield", "key", "fingerprint"],
|
|
10
|
+
"重试": ["refresh", "retry", "sync", "rotate-cw", "arrow-clockwise"],
|
|
11
|
+
"警告": ["warning", "alert-triangle", "triangle-alert"],
|
|
12
|
+
"错误": ["error", "x-circle", "circle-x"],
|
|
13
|
+
"成功": ["check_circle", "check-circle", "circle-check"],
|
|
14
|
+
"设置": ["settings", "tune", "sliders", "sliders-horizontal"],
|
|
15
|
+
"首页": ["home", "house"],
|
|
16
|
+
"用户": ["person", "user", "account", "profile"],
|
|
17
|
+
"账户": ["person", "user", "account", "profile"],
|
|
18
|
+
"通知": ["notifications", "bell"],
|
|
19
|
+
"上传": ["upload", "cloud_upload", "cloud-upload", "upload-cloud"],
|
|
20
|
+
"下载": ["download", "cloud_download", "cloud-download", "download-cloud"],
|
|
21
|
+
"搜索": ["search", "magnifying-glass"],
|
|
22
|
+
"返回": ["chevron_left", "chevron-left", "caret-left", "arrow-left"],
|
|
23
|
+
"关闭": ["close", "x", "x-circle"],
|
|
24
|
+
"删除": ["delete", "trash", "trash-2"],
|
|
25
|
+
"确认": ["check", "done", "check_circle", "check-circle"],
|
|
26
|
+
"客服": ["headset", "support_agent", "message-circle", "chat"],
|
|
27
|
+
"客服消息": ["headset", "support_agent", "message-circle", "chat"]
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function normalize(text) {
|
|
31
|
+
return String(text || "")
|
|
32
|
+
.normalize("NFKD")
|
|
33
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.replace(/[_/]+/g, " ")
|
|
36
|
+
.replace(/[^\p{L}\p{N}\s-]+/gu, " ")
|
|
37
|
+
.replace(/\s+/g, " ")
|
|
38
|
+
.trim();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function tokenize(text) {
|
|
42
|
+
const normalized = normalize(text);
|
|
43
|
+
if (!normalized) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
return normalized
|
|
47
|
+
.split(/[\s-]+/)
|
|
48
|
+
.map((token) => token.trim())
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function unique(values) {
|
|
53
|
+
return Array.from(new Set((values || []).filter(Boolean)));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getDefaultAliasPath(homeDir) {
|
|
57
|
+
const root = homeDir ? path.resolve(homeDir) : os.homedir();
|
|
58
|
+
return path.join(root, ".da-vinci", "icon-aliases.json");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveAliasPath(options = {}) {
|
|
62
|
+
if (options.aliasPath) {
|
|
63
|
+
return path.resolve(options.aliasPath);
|
|
64
|
+
}
|
|
65
|
+
return path.resolve(getDefaultAliasPath(options.homeDir));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeAliasMap(rawMap) {
|
|
69
|
+
const normalized = {};
|
|
70
|
+
for (const [key, values] of Object.entries(rawMap || {})) {
|
|
71
|
+
const normalizedKey = normalize(key);
|
|
72
|
+
if (!normalizedKey) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const normalizedValues = unique(
|
|
76
|
+
(Array.isArray(values) ? values : [values]).flatMap((value) =>
|
|
77
|
+
String(value || "")
|
|
78
|
+
.split(",")
|
|
79
|
+
.map((entry) => entry.trim())
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
if (normalizedValues.length === 0) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
normalized[normalizedKey] = normalizedValues;
|
|
86
|
+
}
|
|
87
|
+
return normalized;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function loadIconAliases(options = {}) {
|
|
91
|
+
const aliasPath = resolveAliasPath(options);
|
|
92
|
+
const mergedBase = normalizeAliasMap(DEFAULT_ALIASES);
|
|
93
|
+
|
|
94
|
+
if (!fs.existsSync(aliasPath)) {
|
|
95
|
+
return {
|
|
96
|
+
aliasPath,
|
|
97
|
+
aliases: mergedBase,
|
|
98
|
+
loaded: false,
|
|
99
|
+
source: "default"
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const text = fs.readFileSync(aliasPath, "utf8");
|
|
104
|
+
const parsed = JSON.parse(text);
|
|
105
|
+
const userAliases = normalizeAliasMap(parsed.aliases || parsed);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
aliasPath,
|
|
109
|
+
aliases: {
|
|
110
|
+
...mergedBase,
|
|
111
|
+
...userAliases
|
|
112
|
+
},
|
|
113
|
+
loaded: true,
|
|
114
|
+
source: "file"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function expandQueryWithAliases(query, aliases) {
|
|
119
|
+
const normalizedQuery = normalize(query);
|
|
120
|
+
const queryTokens = tokenize(normalizedQuery);
|
|
121
|
+
const matched = new Map();
|
|
122
|
+
|
|
123
|
+
if (!normalizedQuery) {
|
|
124
|
+
return {
|
|
125
|
+
extraTokens: [],
|
|
126
|
+
matchedAliases: []
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const [aliasKey, aliasValues] of Object.entries(aliases || {})) {
|
|
131
|
+
if (!aliasKey || !Array.isArray(aliasValues) || aliasValues.length === 0) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const tokenHit = queryTokens.includes(aliasKey);
|
|
136
|
+
const phraseHit = normalizedQuery === aliasKey || normalizedQuery.includes(aliasKey);
|
|
137
|
+
if (!tokenHit && !phraseHit) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
matched.set(aliasKey, aliasValues);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const extraTokens = unique(
|
|
144
|
+
Array.from(matched.values()).flatMap((aliasValues) =>
|
|
145
|
+
aliasValues.flatMap((value) => tokenize(value))
|
|
146
|
+
)
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
extraTokens,
|
|
151
|
+
matchedAliases: Array.from(matched.entries()).map(([key, values]) => ({
|
|
152
|
+
key,
|
|
153
|
+
values
|
|
154
|
+
}))
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
DEFAULT_ALIASES,
|
|
160
|
+
getDefaultAliasPath,
|
|
161
|
+
resolveAliasPath,
|
|
162
|
+
loadIconAliases,
|
|
163
|
+
expandQueryWithAliases,
|
|
164
|
+
normalizeAliasMap
|
|
165
|
+
};
|