@xenonbyte/da-vinci-workflow 0.1.21 → 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 +17 -0
- package/README.md +19 -0
- package/README.zh-CN.md +19 -0
- package/docs/constraint-files.md +109 -0
- package/docs/dv-command-reference.md +17 -0
- 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 +17 -0
- 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 +9 -0
- package/examples/greenfield-spec-markupflow/README.md +7 -0
- package/examples/greenfield-spec-markupflow/pencil-design.md +5 -0
- package/lib/cli.js +188 -1
- package/lib/icon-aliases.js +165 -0
- package/lib/icon-search.js +370 -0
- package/lib/icon-sync.js +361 -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-pen-persistence.js +7 -3
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>]",
|
|
@@ -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 = "";
|
|
@@ -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
|
+
};
|