@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
|
@@ -0,0 +1,178 @@
|
|
|
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
|
+
});
|
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
} = require("../lib/pencil-lock");
|
|
18
18
|
|
|
19
19
|
const fixturePath = path.join(__dirname, "fixtures", "complex-sample.pen");
|
|
20
|
+
const mockPencilPath = path.join(__dirname, "fixtures", "mock-pencil.js");
|
|
20
21
|
|
|
21
22
|
function runTest(name, fn) {
|
|
22
23
|
try {
|
|
@@ -65,7 +66,8 @@ runTest("writePenFromPayloadFiles writes and reopens a complex sample", () => {
|
|
|
65
66
|
nodesFile,
|
|
66
67
|
variablesFile,
|
|
67
68
|
version: fixture.version,
|
|
68
|
-
verifyWithPencil: true
|
|
69
|
+
verifyWithPencil: true,
|
|
70
|
+
pencilBin: mockPencilPath
|
|
69
71
|
});
|
|
70
72
|
|
|
71
73
|
const written = JSON.parse(fs.readFileSync(outputPath, "utf8"));
|
|
@@ -82,7 +84,8 @@ runTest("snapshotPenFile round-trips a complex sample", () => {
|
|
|
82
84
|
const result = snapshotPenFile({
|
|
83
85
|
inputPath: fixturePath,
|
|
84
86
|
outputPath,
|
|
85
|
-
verifyWithPencil: true
|
|
87
|
+
verifyWithPencil: true,
|
|
88
|
+
pencilBin: mockPencilPath
|
|
86
89
|
});
|
|
87
90
|
|
|
88
91
|
const written = JSON.parse(fs.readFileSync(outputPath, "utf8"));
|
|
@@ -97,7 +100,8 @@ runTest("ensurePenFile seeds a missing project-local .pen and state", () => {
|
|
|
97
100
|
|
|
98
101
|
const result = ensurePenFile({
|
|
99
102
|
outputPath,
|
|
100
|
-
verifyWithPencil: true
|
|
103
|
+
verifyWithPencil: true,
|
|
104
|
+
pencilBin: mockPencilPath
|
|
101
105
|
});
|
|
102
106
|
|
|
103
107
|
const written = JSON.parse(fs.readFileSync(outputPath, "utf8"));
|