@xenonbyte/da-vinci-workflow 0.1.25 → 0.2.1
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 +37 -0
- package/README.md +48 -67
- package/README.zh-CN.md +36 -66
- package/SKILL.md +3 -0
- package/commands/claude/dv/continue.md +5 -0
- package/commands/claude/dv/design.md +1 -0
- package/commands/codex/prompts/dv-continue.md +6 -1
- package/commands/codex/prompts/dv-design.md +1 -0
- package/commands/gemini/dv/continue.toml +5 -0
- package/commands/gemini/dv/design.toml +1 -0
- package/commands/templates/dv-continue.shared.md +33 -0
- package/docs/dv-command-reference.md +45 -2
- package/docs/execution-chain-migration.md +46 -0
- package/docs/execution-chain-plan.md +125 -0
- package/docs/pencil-rendering-workflow.md +9 -7
- package/docs/prompt-entrypoints.md +6 -0
- package/docs/prompt-presets/README.md +4 -0
- package/docs/visual-assist-presets/README.md +4 -0
- package/docs/workflow-examples.md +23 -11
- package/docs/workflow-overview.md +27 -0
- package/docs/zh-CN/dv-command-reference.md +45 -2
- package/docs/zh-CN/execution-chain-migration.md +46 -0
- package/docs/zh-CN/pencil-rendering-workflow.md +9 -7
- package/docs/zh-CN/prompt-entrypoints.md +6 -0
- package/docs/zh-CN/prompt-presets/README.md +5 -1
- package/docs/zh-CN/visual-assist-presets/README.md +5 -1
- package/docs/zh-CN/workflow-examples.md +23 -11
- package/docs/zh-CN/workflow-overview.md +27 -0
- package/examples/greenfield-spec-markupflow/README.md +6 -1
- package/lib/artifact-parsers.js +120 -0
- package/lib/async-offload-worker.js +26 -0
- package/lib/async-offload.js +82 -0
- package/lib/audit-parsers.js +152 -32
- package/lib/audit.js +145 -23
- package/lib/cli.js +1068 -437
- package/lib/diff-spec.js +242 -0
- package/lib/execution-signals.js +136 -0
- package/lib/fs-safety.js +1 -4
- package/lib/icon-aliases.js +7 -7
- package/lib/icon-search.js +21 -14
- package/lib/icon-sync.js +220 -41
- package/lib/install.js +128 -60
- package/lib/lint-bindings.js +143 -0
- package/lib/lint-spec.js +408 -0
- package/lib/lint-tasks.js +176 -0
- package/lib/mcp-runtime-gate.js +4 -7
- package/lib/pen-persistence.js +318 -46
- package/lib/pencil-lock.js +237 -25
- package/lib/pencil-preflight.js +233 -12
- package/lib/pencil-session.js +216 -36
- package/lib/planning-parsers.js +567 -0
- package/lib/scaffold.js +193 -0
- package/lib/scope-check.js +603 -0
- package/lib/sidecars.js +369 -0
- package/lib/supervisor-review.js +82 -35
- package/lib/utils.js +129 -0
- package/lib/verify.js +652 -0
- package/lib/workflow-bootstrap.js +255 -0
- package/lib/workflow-contract.js +107 -0
- package/lib/workflow-persisted-state.js +297 -0
- package/lib/workflow-state.js +785 -0
- package/package.json +21 -3
- package/references/artifact-templates.md +26 -0
- package/references/checkpoints.md +16 -0
- package/references/design-inputs.md +2 -0
- package/references/modes.md +10 -0
- package/references/pencil-design-to-code.md +2 -0
- package/scripts/fixtures/complex-sample.pen +0 -295
- package/scripts/fixtures/mock-pencil.js +0 -49
- package/scripts/test-audit-context-delta.js +0 -446
- package/scripts/test-audit-design-supervisor.js +0 -691
- package/scripts/test-audit-safety.js +0 -92
- package/scripts/test-icon-aliases.js +0 -96
- package/scripts/test-icon-search.js +0 -77
- package/scripts/test-icon-sync.js +0 -178
- package/scripts/test-mcp-runtime-gate.js +0 -287
- package/scripts/test-mode-consistency.js +0 -344
- package/scripts/test-pen-persistence.js +0 -403
- package/scripts/test-pencil-lock.js +0 -130
- package/scripts/test-pencil-preflight.js +0 -169
- package/scripts/test-pencil-session.js +0 -192
- package/scripts/test-persistence-flows.js +0 -345
- package/scripts/test-supervisor-review-cli.js +0 -619
- package/scripts/test-supervisor-review-integration.js +0 -115
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
const assert = require("assert/strict");
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const os = require("os");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
|
|
6
|
-
const { auditProject } = require("../lib/audit");
|
|
7
|
-
|
|
8
|
-
function runTest(name, fn) {
|
|
9
|
-
try {
|
|
10
|
-
fn();
|
|
11
|
-
console.log(`PASS ${name}`);
|
|
12
|
-
} catch (error) {
|
|
13
|
-
console.error(`FAIL ${name}`);
|
|
14
|
-
throw error;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function writeText(filePath, text) {
|
|
19
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
20
|
-
fs.writeFileSync(filePath, text);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function setupProject(name) {
|
|
24
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), `da-vinci-audit-safety-${name}-`));
|
|
25
|
-
const daVinciDir = path.join(root, ".da-vinci");
|
|
26
|
-
const designsDir = path.join(daVinciDir, "designs");
|
|
27
|
-
const designRegistryPath = path.join(daVinciDir, "design-registry.md");
|
|
28
|
-
|
|
29
|
-
writeText(path.join(root, "DA-VINCI.md"), "# DA-VINCI\n");
|
|
30
|
-
writeText(path.join(daVinciDir, "project-inventory.md"), "# Inventory\n");
|
|
31
|
-
writeText(path.join(daVinciDir, "page-map.md"), "# Page Map\n");
|
|
32
|
-
fs.mkdirSync(designsDir, { recursive: true });
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
root,
|
|
36
|
-
daVinciDir,
|
|
37
|
-
designsDir,
|
|
38
|
-
designRegistryPath
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
runTest("integrity audit warns when registry contains out-of-root .pen reference", () => {
|
|
43
|
-
const project = setupProject("escaped-registry");
|
|
44
|
-
writeText(path.join(project.designsDir, "main.pen"), "{}");
|
|
45
|
-
writeText(
|
|
46
|
-
project.designRegistryPath,
|
|
47
|
-
[
|
|
48
|
-
"# Registry",
|
|
49
|
-
"- Preferred .pen: .da-vinci/designs/main.pen",
|
|
50
|
-
"- Legacy .pen: .da-vinci/designs/../../../outside.pen",
|
|
51
|
-
""
|
|
52
|
-
].join("\n")
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
const result = auditProject(project.root, {
|
|
56
|
-
mode: "integrity"
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
assert.equal(result.failures.length, 0);
|
|
60
|
-
assert.match(result.warnings.join("\n"), /escapes project root and will be ignored/i);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
runTest("integrity audit surfaces traversal truncation warnings on deep trees", () => {
|
|
64
|
-
const project = setupProject("truncated-scan");
|
|
65
|
-
writeText(path.join(project.designsDir, "main.pen"), "{}");
|
|
66
|
-
writeText(
|
|
67
|
-
project.designRegistryPath,
|
|
68
|
-
[
|
|
69
|
-
"# Registry",
|
|
70
|
-
"- Preferred .pen: .da-vinci/designs/main.pen",
|
|
71
|
-
""
|
|
72
|
-
].join("\n")
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
let current = project.designsDir;
|
|
76
|
-
for (let depth = 0; depth < 30; depth += 1) {
|
|
77
|
-
current = path.join(current, `deep-${depth}`);
|
|
78
|
-
fs.mkdirSync(current, { recursive: true });
|
|
79
|
-
}
|
|
80
|
-
writeText(path.join(current, "deep-tree.pen"), "{}");
|
|
81
|
-
|
|
82
|
-
const result = auditProject(project.root, {
|
|
83
|
-
mode: "integrity"
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
assert.match(
|
|
87
|
-
result.warnings.join("\n"),
|
|
88
|
-
/File scan truncated under \.da-vinci[\/\\]designs/i
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
console.log("All audit safety tests passed.");
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
const assert = require("assert/strict");
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const os = require("os");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
const {
|
|
6
|
-
DEFAULT_ALIASES,
|
|
7
|
-
getDefaultAliasPath,
|
|
8
|
-
resolveAliasPath,
|
|
9
|
-
loadIconAliases,
|
|
10
|
-
expandQueryWithAliases,
|
|
11
|
-
normalizeAliasMap
|
|
12
|
-
} = require("../lib/icon-aliases");
|
|
13
|
-
|
|
14
|
-
function runTest(name, fn) {
|
|
15
|
-
try {
|
|
16
|
-
fn();
|
|
17
|
-
console.log(`PASS ${name}`);
|
|
18
|
-
} catch (error) {
|
|
19
|
-
console.error(`FAIL ${name}`);
|
|
20
|
-
throw error;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
runTest("default aliases include vault semantics", () => {
|
|
25
|
-
assert.ok(Array.isArray(DEFAULT_ALIASES["保险箱"]));
|
|
26
|
-
assert.ok(DEFAULT_ALIASES["保险箱"].includes("vault"));
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
runTest("normalizeAliasMap normalizes keys and values", () => {
|
|
30
|
-
const map = normalizeAliasMap({
|
|
31
|
-
" 设 置 ": [" settings ", "tune"],
|
|
32
|
-
"": ["ignored"]
|
|
33
|
-
});
|
|
34
|
-
assert.ok(map["设 置"]);
|
|
35
|
-
assert.deepEqual(map["设 置"], ["settings", "tune"]);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
runTest("default alias path resolves under home", () => {
|
|
39
|
-
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-alias-home-"));
|
|
40
|
-
const aliasPath = getDefaultAliasPath(tempHome);
|
|
41
|
-
assert.match(aliasPath, /\.da-vinci[\/\\]icon-aliases\.json$/);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
runTest("loadIconAliases merges user file over defaults", () => {
|
|
45
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-alias-file-"));
|
|
46
|
-
const aliasPath = path.join(tempDir, "icon-aliases.json");
|
|
47
|
-
fs.writeFileSync(
|
|
48
|
-
aliasPath,
|
|
49
|
-
JSON.stringify(
|
|
50
|
-
{
|
|
51
|
-
aliases: {
|
|
52
|
-
"保险箱": ["vault", "safe-box-custom"],
|
|
53
|
-
"工单": ["ticket", "message-square"]
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
null,
|
|
57
|
-
2
|
|
58
|
-
)
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const loaded = loadIconAliases({
|
|
62
|
-
aliasPath
|
|
63
|
-
});
|
|
64
|
-
assert.equal(loaded.loaded, true);
|
|
65
|
-
assert.equal(loaded.source, "file");
|
|
66
|
-
assert.ok(loaded.aliases["保险箱"].includes("safe-box-custom"));
|
|
67
|
-
assert.ok(loaded.aliases["工单"].includes("ticket"));
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
runTest("expandQueryWithAliases adds mapped extra tokens", () => {
|
|
71
|
-
const expansion = expandQueryWithAliases("保险箱 解密", {
|
|
72
|
-
...normalizeAliasMap(DEFAULT_ALIASES),
|
|
73
|
-
工单: ["ticket"]
|
|
74
|
-
});
|
|
75
|
-
assert.ok(expansion.extraTokens.includes("vault"));
|
|
76
|
-
assert.ok(expansion.extraTokens.includes("unlock"));
|
|
77
|
-
assert.ok(expansion.matchedAliases.length >= 2);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
runTest("alias expansion normalizes diacritics in user query", () => {
|
|
81
|
-
const expansion = expandQueryWithAliases("SéTTings", normalizeAliasMap({
|
|
82
|
-
...DEFAULT_ALIASES,
|
|
83
|
-
settings: ["settings", "tune"]
|
|
84
|
-
}));
|
|
85
|
-
assert.ok(expansion.extraTokens.includes("settings"));
|
|
86
|
-
assert.ok(expansion.extraTokens.includes("tune"));
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
runTest("resolveAliasPath returns absolute path", () => {
|
|
90
|
-
const resolved = resolveAliasPath({
|
|
91
|
-
aliasPath: "./tmp/icon-aliases.json"
|
|
92
|
-
});
|
|
93
|
-
assert.ok(path.isAbsolute(resolved));
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
console.log("All icon-aliases tests passed.");
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
const assert = require("assert/strict");
|
|
2
|
-
const {
|
|
3
|
-
searchIconLibrary,
|
|
4
|
-
formatIconSearchReport
|
|
5
|
-
} = require("../lib/icon-search");
|
|
6
|
-
|
|
7
|
-
function runTest(name, fn) {
|
|
8
|
-
try {
|
|
9
|
-
fn();
|
|
10
|
-
console.log(`PASS ${name}`);
|
|
11
|
-
} catch (error) {
|
|
12
|
-
console.error(`FAIL ${name}`);
|
|
13
|
-
throw error;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
runTest("exact settings query ranks material rounded first", () => {
|
|
18
|
-
const result = searchIconLibrary("settings", { top: 5 });
|
|
19
|
-
assert.ok(result.matches.length > 0);
|
|
20
|
-
assert.equal(result.matches[0].family, "Material Symbols Rounded");
|
|
21
|
-
assert.equal(result.matches[0].name, "settings");
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
runTest("chinese query can resolve settings candidates", () => {
|
|
25
|
-
const result = searchIconLibrary("设置", { top: 5 });
|
|
26
|
-
assert.ok(result.matches.some((match) => match.name === "settings"));
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
runTest("accented latin query is normalized consistently", () => {
|
|
30
|
-
const result = searchIconLibrary("séttings", { top: 5 });
|
|
31
|
-
assert.ok(result.matches.some((match) => match.name === "settings"));
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
runTest("family filter returns only the selected family", () => {
|
|
35
|
-
const result = searchIconLibrary("lock", { family: "lucide", top: 6 });
|
|
36
|
-
assert.ok(result.matches.length > 0);
|
|
37
|
-
for (const match of result.matches) {
|
|
38
|
-
assert.equal(match.family, "lucide");
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
runTest("material family alias expands to all material variants", () => {
|
|
43
|
-
const result = searchIconLibrary("home", { family: "material", top: 6 });
|
|
44
|
-
const families = new Set(result.matches.map((match) => match.family));
|
|
45
|
-
assert.ok(families.has("Material Symbols Rounded"));
|
|
46
|
-
assert.ok(families.has("Material Symbols Outlined"));
|
|
47
|
-
assert.ok(families.has("Material Symbols Sharp"));
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
runTest("unknown family filter throws a clear error", () => {
|
|
51
|
-
assert.throws(() => searchIconLibrary("lock", { family: "unknown-family" }), /Unknown icon family filter/i);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
runTest("formatted report contains node payload hints", () => {
|
|
55
|
-
const result = searchIconLibrary("vault", { top: 3 });
|
|
56
|
-
const report = formatIconSearchReport(result);
|
|
57
|
-
assert.match(report, /Icon Search/);
|
|
58
|
-
assert.match(report, /node:/);
|
|
59
|
-
assert.match(report, /iconFontFamily/);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
runTest("external catalog records are merged into search candidates", () => {
|
|
63
|
-
const result = searchIconLibrary("launch rocket", {
|
|
64
|
-
top: 5,
|
|
65
|
-
catalog: [
|
|
66
|
-
{
|
|
67
|
-
family: "lucide",
|
|
68
|
-
name: "rocket",
|
|
69
|
-
semantic: "rocket",
|
|
70
|
-
tags: ["launch"]
|
|
71
|
-
}
|
|
72
|
-
]
|
|
73
|
-
});
|
|
74
|
-
assert.ok(result.matches.some((match) => match.family === "lucide" && match.name === "rocket"));
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
console.log("All icon-search tests passed.");
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
const assert = require("assert/strict");
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const os = require("os");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
const {
|
|
6
|
-
MATERIAL_METADATA_URL,
|
|
7
|
-
LUCIDE_TREE_URL,
|
|
8
|
-
FEATHER_TREE_URL,
|
|
9
|
-
PHOSPHOR_TREE_URL,
|
|
10
|
-
getDefaultCatalogPath,
|
|
11
|
-
resolveCatalogPath,
|
|
12
|
-
loadIconCatalog,
|
|
13
|
-
syncIconCatalog,
|
|
14
|
-
formatIconSyncReport,
|
|
15
|
-
parseGoogleMaterialMetadata,
|
|
16
|
-
parseGitHubTreeIcons,
|
|
17
|
-
dedupeIconRecords,
|
|
18
|
-
summarizeSourceResults
|
|
19
|
-
} = require("../lib/icon-sync");
|
|
20
|
-
|
|
21
|
-
async function runTest(name, fn) {
|
|
22
|
-
try {
|
|
23
|
-
await fn();
|
|
24
|
-
console.log(`PASS ${name}`);
|
|
25
|
-
} catch (error) {
|
|
26
|
-
console.error(`FAIL ${name}`);
|
|
27
|
-
throw error;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function createMockFetch({ failLucide = false } = {}) {
|
|
32
|
-
return async (url) => {
|
|
33
|
-
if (url === MATERIAL_METADATA_URL) {
|
|
34
|
-
return `)]}'\n{"icons":[{"name":"settings","unsupported_families":[]}]}`;
|
|
35
|
-
}
|
|
36
|
-
if (url === LUCIDE_TREE_URL) {
|
|
37
|
-
if (failLucide) {
|
|
38
|
-
throw new Error("mock lucide outage");
|
|
39
|
-
}
|
|
40
|
-
return JSON.stringify({
|
|
41
|
-
tree: [{ path: "icons/settings.json" }]
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
if (url === FEATHER_TREE_URL) {
|
|
45
|
-
return JSON.stringify({
|
|
46
|
-
tree: [{ path: "icons/lock.svg" }]
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
if (url === PHOSPHOR_TREE_URL) {
|
|
50
|
-
return JSON.stringify({
|
|
51
|
-
tree: [{ path: "assets/regular/vault.svg" }]
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
throw new Error(`Unexpected URL: ${url}`);
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
(async () => {
|
|
59
|
-
await runTest("google metadata parser extracts material variants", () => {
|
|
60
|
-
const raw = `)]}'\n{"icons":[{"name":"settings","unsupported_families":[]},{"name":"lock_open","unsupported_families":["Material Icons Sharp"]}]}`;
|
|
61
|
-
const records = parseGoogleMaterialMetadata(raw);
|
|
62
|
-
const keys = new Set(records.map((record) => `${record.family}/${record.name}`));
|
|
63
|
-
assert.ok(keys.has("Material Symbols Rounded/settings"));
|
|
64
|
-
assert.ok(keys.has("Material Symbols Outlined/settings"));
|
|
65
|
-
assert.ok(keys.has("Material Symbols Sharp/settings"));
|
|
66
|
-
assert.ok(keys.has("Material Symbols Rounded/lock_open"));
|
|
67
|
-
assert.equal(keys.has("Material Symbols Sharp/lock_open"), false);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
await runTest("github tree parser extracts icon names from paths", () => {
|
|
71
|
-
const raw = JSON.stringify({
|
|
72
|
-
tree: [
|
|
73
|
-
{ path: "icons/settings.json" },
|
|
74
|
-
{ path: "icons/lock-open.json" },
|
|
75
|
-
{ path: "icons/nested/skip.json" },
|
|
76
|
-
{ path: "readme.md" }
|
|
77
|
-
]
|
|
78
|
-
});
|
|
79
|
-
const records = parseGitHubTreeIcons(raw, {
|
|
80
|
-
family: "lucide",
|
|
81
|
-
prefix: "icons/",
|
|
82
|
-
suffix: ".json"
|
|
83
|
-
});
|
|
84
|
-
const names = records.map((record) => record.name);
|
|
85
|
-
assert.deepEqual(names, ["settings", "lock-open"]);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
await runTest("dedupe keeps first unique family/name pair", () => {
|
|
89
|
-
const deduped = dedupeIconRecords([
|
|
90
|
-
{ family: "lucide", name: "settings" },
|
|
91
|
-
{ family: "lucide", name: "settings" },
|
|
92
|
-
{ family: "feather", name: "settings" }
|
|
93
|
-
]);
|
|
94
|
-
assert.equal(deduped.length, 2);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
await runTest("catalog path resolves to home .da-vinci directory by default", () => {
|
|
98
|
-
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-icon-home-"));
|
|
99
|
-
const resolved = getDefaultCatalogPath(tempHome);
|
|
100
|
-
assert.match(resolved, /\.da-vinci[\/\\]icon-catalog\.json$/);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
await runTest("load icon catalog reads valid schema", () => {
|
|
104
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-icon-catalog-"));
|
|
105
|
-
const catalogPath = path.join(tempDir, "icon-catalog.json");
|
|
106
|
-
fs.writeFileSync(
|
|
107
|
-
catalogPath,
|
|
108
|
-
JSON.stringify(
|
|
109
|
-
{
|
|
110
|
-
schema: 1,
|
|
111
|
-
generatedAt: "2026-03-29T00:00:00.000Z",
|
|
112
|
-
iconCount: 1,
|
|
113
|
-
icons: [{ family: "lucide", name: "settings", semantic: "settings", tags: [] }]
|
|
114
|
-
},
|
|
115
|
-
null,
|
|
116
|
-
2
|
|
117
|
-
)
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
const loaded = loadIconCatalog({
|
|
121
|
-
catalogPath
|
|
122
|
-
});
|
|
123
|
-
assert.ok(loaded.catalog);
|
|
124
|
-
assert.equal(loaded.catalog.icons.length, 1);
|
|
125
|
-
assert.equal(resolveCatalogPath({ catalogPath }), path.resolve(catalogPath));
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
await runTest("source summary marks degraded when at least one source errors", () => {
|
|
129
|
-
const summary = summarizeSourceResults({
|
|
130
|
-
material: { status: "ok" },
|
|
131
|
-
lucide: { status: "error" },
|
|
132
|
-
feather: { status: "ok" }
|
|
133
|
-
});
|
|
134
|
-
assert.deepEqual(summary, {
|
|
135
|
-
total: 3,
|
|
136
|
-
okCount: 2,
|
|
137
|
-
errorCount: 1,
|
|
138
|
-
degraded: true
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
await runTest("icon sync keeps flow non-blocking by default on partial source failure", async () => {
|
|
143
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-icon-sync-"));
|
|
144
|
-
const outputPath = path.join(tempDir, "catalog.json");
|
|
145
|
-
const result = await syncIconCatalog({
|
|
146
|
-
outputPath,
|
|
147
|
-
fetchText: createMockFetch({ failLucide: true })
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
assert.equal(result.catalog.syncStatus, "degraded");
|
|
151
|
-
assert.equal(result.catalog.sourceResults.lucide.status, "error");
|
|
152
|
-
assert.ok(result.catalog.iconCount > 0);
|
|
153
|
-
assert.equal(fs.existsSync(outputPath), true);
|
|
154
|
-
assert.match(formatIconSyncReport(result), /Status: DEGRADED/i);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
await runTest("icon sync strict mode fails on partial source failure and does not write partial catalog", async () => {
|
|
158
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-icon-sync-strict-"));
|
|
159
|
-
const outputPath = path.join(tempDir, "catalog.json");
|
|
160
|
-
|
|
161
|
-
await assert.rejects(
|
|
162
|
-
() =>
|
|
163
|
-
syncIconCatalog({
|
|
164
|
-
outputPath,
|
|
165
|
-
strict: true,
|
|
166
|
-
fetchText: createMockFetch({ failLucide: true })
|
|
167
|
-
}),
|
|
168
|
-
/strict mode failed/i
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
assert.equal(fs.existsSync(outputPath), false);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
console.log("All icon-sync tests passed.");
|
|
175
|
-
})().catch((error) => {
|
|
176
|
-
console.error(error.stack || error.message);
|
|
177
|
-
process.exit(1);
|
|
178
|
-
});
|