figma-cache-toolchain 1.4.2 → 1.4.4

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.
@@ -0,0 +1,233 @@
1
+ /* eslint-disable no-console */
2
+
3
+ function isTodoLike(value) {
4
+ return /TODO/i.test(String(value || ""));
5
+ }
6
+
7
+ function getManifestFilesMap(cacheKey, item, errors, deps) {
8
+ const { resolveMaybeAbsolutePath, safeReadJson, normalizeSlash, path, fs } = deps;
9
+ if (!item || !item.paths || !item.paths.meta) {
10
+ errors.push(`${cacheKey}: source=figma-mcp 但缺少 paths.meta,无法定位 mcp-raw`);
11
+ return null;
12
+ }
13
+ const metaAbs = resolveMaybeAbsolutePath(item.paths.meta);
14
+ const nodeDir = path.dirname(metaAbs);
15
+ const mcpRawDir = path.join(nodeDir, "mcp-raw");
16
+ const manifestAbs = path.join(mcpRawDir, "mcp-raw-manifest.json");
17
+ const manifest = safeReadJson(manifestAbs);
18
+ if (!manifest || typeof manifest !== "object") {
19
+ errors.push(`${cacheKey}: source=figma-mcp 但缺少 mcp-raw/mcp-raw-manifest.json`);
20
+ return null;
21
+ }
22
+ if (!manifest.files || typeof manifest.files !== "object") {
23
+ errors.push(`${cacheKey}: mcp-raw-manifest.json 缺少 files 映射`);
24
+ return null;
25
+ }
26
+ Object.entries(manifest.files).forEach(([toolName, fileName]) => {
27
+ if (!fileName) {
28
+ errors.push(`${cacheKey}: mcp-raw-manifest.json 中 ${toolName} 未关联文件`);
29
+ return;
30
+ }
31
+ const fileAbs = path.join(mcpRawDir, String(fileName));
32
+ if (!fs.existsSync(fileAbs)) {
33
+ errors.push(`${cacheKey}: 缺少 MCP 原始文件 ${normalizeSlash(fileAbs)}`);
34
+ }
35
+ });
36
+ return manifest.files;
37
+ }
38
+
39
+ function collectMissingToolEvidence(completeness, filesMap, normalizeCompletenessList, toolRequirements) {
40
+ const missing = [];
41
+ normalizeCompletenessList(completeness).forEach((dimension) => {
42
+ const groups = toolRequirements[dimension];
43
+ if (!Array.isArray(groups) || !groups.length) {
44
+ return;
45
+ }
46
+ groups.forEach((alternatives) => {
47
+ const hit = alternatives.some((toolName) =>
48
+ Object.prototype.hasOwnProperty.call(filesMap, toolName)
49
+ );
50
+ if (!hit) {
51
+ missing.push({
52
+ dimension,
53
+ alternatives,
54
+ });
55
+ }
56
+ });
57
+ });
58
+ return missing;
59
+ }
60
+
61
+ function validateMcpRawEvidence(cacheKey, item, completeness, options, deps) {
62
+ const errors = [];
63
+ if (options && options.allowSkeletonWithFigmaMcp) {
64
+ return errors;
65
+ }
66
+
67
+ const filesMap = getManifestFilesMap(cacheKey, item, errors, deps);
68
+ if (!filesMap) {
69
+ return errors;
70
+ }
71
+
72
+ const missing = collectMissingToolEvidence(
73
+ completeness,
74
+ filesMap,
75
+ deps.normalizeCompletenessList,
76
+ deps.completenessToolRequirements
77
+ );
78
+ missing.forEach(({ dimension, alternatives }) => {
79
+ errors.push(
80
+ `${cacheKey}: completeness=${dimension} 缺少 MCP 原始证据(需包含 ${alternatives.join(" 或 ")})`
81
+ );
82
+ });
83
+ return errors;
84
+ }
85
+
86
+ function validateCompletenessEvidence(cacheKey, item, deps) {
87
+ const errors = [];
88
+ const covered = deps.normalizeCompletenessList(item.completeness);
89
+ if (!covered.length) {
90
+ return errors;
91
+ }
92
+ if (!item.paths || !item.paths.raw) {
93
+ errors.push(`${cacheKey}: completeness 非空但缺少 paths.raw`);
94
+ return errors;
95
+ }
96
+
97
+ const rawAbs = deps.resolveMaybeAbsolutePath(item.paths.raw);
98
+ const raw = deps.safeReadJson(rawAbs);
99
+ if (!raw || typeof raw !== "object") {
100
+ errors.push(`${cacheKey}: raw.json 不可读,无法校验 completeness 证据`);
101
+ return errors;
102
+ }
103
+
104
+ const coverageSummary =
105
+ raw.coverageSummary && typeof raw.coverageSummary === "object"
106
+ ? raw.coverageSummary
107
+ : null;
108
+ const evidence =
109
+ coverageSummary && coverageSummary.evidence && typeof coverageSummary.evidence === "object"
110
+ ? coverageSummary.evidence
111
+ : null;
112
+ if (!evidence) {
113
+ errors.push(`${cacheKey}: raw.json 缺少 coverageSummary.evidence`);
114
+ return errors;
115
+ }
116
+
117
+ covered.forEach((dimension) => {
118
+ const list = Array.isArray(evidence[dimension])
119
+ ? evidence[dimension].filter((x) => typeof x === "string" && String(x).trim())
120
+ : [];
121
+ if (!list.length) {
122
+ errors.push(`${cacheKey}: completeness=${dimension} 但缺少 evidence`);
123
+ }
124
+ });
125
+
126
+ if (item.source === "figma-mcp") {
127
+ ["interactions", "states", "accessibility"].forEach((dimension) => {
128
+ if (!covered.includes(dimension)) {
129
+ return;
130
+ }
131
+ const section = raw[dimension] && typeof raw[dimension] === "object" ? raw[dimension] : null;
132
+ const notes = section ? String(section.notes || "") : "";
133
+ if (!notes || isTodoLike(notes)) {
134
+ errors.push(`${cacheKey}: ${dimension} 仍为占位内容(TODO),请补充可执行证据`);
135
+ }
136
+ });
137
+ }
138
+
139
+ return errors;
140
+ }
141
+
142
+ function validateIndex(index, deps) {
143
+ const errors = [];
144
+ const normalized = deps.normalizeIndexShape(index);
145
+ const keys = Object.keys(normalized.items || {});
146
+
147
+ keys.forEach((cacheKey) => {
148
+ const item = normalized.items[cacheKey];
149
+ const required = [
150
+ "fileKey",
151
+ "scope",
152
+ "url",
153
+ "originalUrls",
154
+ "normalizationVersion",
155
+ "paths",
156
+ "syncedAt",
157
+ "completeness",
158
+ ];
159
+
160
+ required.forEach((field) => {
161
+ if (item[field] === undefined || item[field] === null) {
162
+ errors.push(`${cacheKey}: 缺少字段 ${field}`);
163
+ }
164
+ });
165
+
166
+ if (item.scope === "node" && !item.nodeId) {
167
+ errors.push(`${cacheKey}: node 作用域必须包含 nodeId`);
168
+ }
169
+
170
+ errors.push(...validateCompletenessEvidence(cacheKey, item, deps));
171
+ if (item.source === "figma-mcp") {
172
+ errors.push(
173
+ ...validateMcpRawEvidence(cacheKey, item, item.completeness, {
174
+ allowSkeletonWithFigmaMcp: false,
175
+ }, deps)
176
+ );
177
+ }
178
+ });
179
+
180
+ const flowKeys = Object.keys(normalized.flows || {});
181
+ flowKeys.forEach((flowId) => {
182
+ const flow = normalized.flows[flowId];
183
+ if (!flow || typeof flow !== "object") {
184
+ errors.push(`flow ${flowId}: 非法结构`);
185
+ return;
186
+ }
187
+ if (!flow.id || flow.id !== flowId) {
188
+ errors.push(`flow ${flowId}: id 字段缺失或不一致`);
189
+ }
190
+ if (!Array.isArray(flow.nodes)) {
191
+ errors.push(`flow ${flowId}: nodes 必须是数组`);
192
+ }
193
+ if (!Array.isArray(flow.edges)) {
194
+ errors.push(`flow ${flowId}: edges 必须是数组`);
195
+ }
196
+
197
+ if (Array.isArray(flow.edges)) {
198
+ flow.edges.forEach((edge, idx) => {
199
+ if (!edge || typeof edge !== "object") {
200
+ errors.push(`flow ${flowId}: edge[${idx}] 非法`);
201
+ return;
202
+ }
203
+ if (!edge.from || !edge.to) {
204
+ errors.push(`flow ${flowId}: edge[${idx}] 缺少 from/to`);
205
+ }
206
+ if (!edge.type) {
207
+ errors.push(`flow ${flowId}: edge[${idx}] 缺少 type`);
208
+ }
209
+ if (edge.from && !normalized.items[edge.from]) {
210
+ errors.push(`flow ${flowId}: edge[${idx}] from 不存在于 items: ${edge.from}`);
211
+ }
212
+ if (edge.to && !normalized.items[edge.to]) {
213
+ errors.push(`flow ${flowId}: edge[${idx}] to 不存在于 items: ${edge.to}`);
214
+ }
215
+ });
216
+ }
217
+
218
+ if (Array.isArray(flow.nodes)) {
219
+ flow.nodes.forEach((nodeCacheKey) => {
220
+ if (!normalized.items[nodeCacheKey]) {
221
+ errors.push(`flow ${flowId}: nodes 引用不存在于 items: ${nodeCacheKey}`);
222
+ }
223
+ });
224
+ }
225
+ });
226
+
227
+ return errors;
228
+ }
229
+
230
+ module.exports = {
231
+ validateMcpRawEvidence,
232
+ validateIndex,
233
+ };
package/package.json CHANGED
@@ -1,54 +1,72 @@
1
- {
2
- "name": "figma-cache-toolchain",
3
- "version": "1.4.2",
4
- "description": "Figma link normalization, local cache index, validation, and Node CLI (framework-agnostic core).",
5
- "homepage": "https://www.npmjs.com/package/figma-cache-toolchain",
6
- "keywords": [
7
- "figma",
8
- "cache",
9
- "cli",
10
- "design-tokens",
11
- "mcp"
12
- ],
13
- "license": "MIT",
14
- "engines": {
15
- "node": ">=16.20.0"
16
- },
17
- "bin": {
18
- "figma-cache": "bin/figma-cache.js"
19
- },
20
- "files": [
21
- "LICENSE",
22
- "bin",
23
- "cursor-bootstrap",
24
- "figma-cache/figma-cache.js",
25
- "figma-cache/*.md"
26
- ],
27
- "publishConfig": {
28
- "registry": "https://registry.npmjs.org/"
29
- },
30
- "scripts": {
31
- "test": "node tests/smoke.js",
32
- "prepack": "node bin/figma-cache.js validate",
33
- "figma:cache:normalize": "node bin/figma-cache.js normalize",
34
- "figma:cache:get": "node bin/figma-cache.js get",
35
- "figma:cache:upsert": "node bin/figma-cache.js upsert",
36
- "figma:cache:ensure": "node bin/figma-cache.js ensure",
37
- "figma:cache:validate": "node bin/figma-cache.js validate",
38
- "figma:cache:stale": "node bin/figma-cache.js stale",
39
- "figma:cache:budget": "node bin/figma-cache.js budget --mcp-only",
40
- "figma:cache:backfill": "node bin/figma-cache.js backfill",
41
- "figma:cache:init": "node bin/figma-cache.js init",
42
- "figma:cache:config": "node bin/figma-cache.js config",
43
- "figma:cache:flow:init": "node bin/figma-cache.js flow init",
44
- "figma:cache:flow:add-node": "node bin/figma-cache.js flow add-node",
45
- "figma:cache:flow:link": "node bin/figma-cache.js flow link",
46
- "figma:cache:flow:chain": "node bin/figma-cache.js flow chain",
47
- "figma:cache:flow:show": "node bin/figma-cache.js flow show",
48
- "figma:cache:flow:mermaid": "node bin/figma-cache.js flow mermaid",
49
- "figma:cache:cursor:init": "node bin/figma-cache.js cursor init"
50
- },
51
- "volta": {
52
- "node": "16.20.2"
53
- }
54
- }
1
+ {
2
+ "name": "figma-cache-toolchain",
3
+ "version": "1.4.4",
4
+ "description": "Figma link normalization, local cache index, validation, and Node CLI (framework-agnostic core).",
5
+ "homepage": "https://github.com/907086379/figma-cache-toolchain#readme",
6
+ "keywords": [
7
+ "figma",
8
+ "cache",
9
+ "cli",
10
+ "design-tokens",
11
+ "mcp"
12
+ ],
13
+ "license": "MIT",
14
+ "engines": {
15
+ "node": ">=16.20.0"
16
+ },
17
+ "bin": {
18
+ "figma-cache": "bin/figma-cache.js"
19
+ },
20
+ "files": [
21
+ "LICENSE",
22
+ "bin",
23
+ "cursor-bootstrap",
24
+ "figma-cache/figma-cache.js",
25
+ "figma-cache/js/flow-cli.js",
26
+ "figma-cache/js/validate-cli.js",
27
+ "figma-cache/js/budget-cli.js",
28
+ "figma-cache/js/index-store.js",
29
+ "figma-cache/js/cursor-bootstrap-cli.js",
30
+ "figma-cache/js/entry-files.js",
31
+ "figma-cache/js/backfill-cli.js",
32
+ "figma-cache/js/project-config.js",
33
+ "figma-cache/js/upsert-core.js",
34
+ "figma-cache/docs/*.md"
35
+ ],
36
+ "publishConfig": {
37
+ "registry": "https://registry.npmjs.org/"
38
+ },
39
+ "scripts": {
40
+ "test": "npm run docs:encoding:check && node tests/smoke.js",
41
+ "prepack": "npm run docs:encoding:check && node bin/figma-cache.js validate",
42
+ "figma:cache:normalize": "node bin/figma-cache.js normalize",
43
+ "figma:cache:get": "node bin/figma-cache.js get",
44
+ "figma:cache:upsert": "node bin/figma-cache.js upsert",
45
+ "figma:cache:ensure": "node bin/figma-cache.js ensure",
46
+ "figma:cache:validate": "node bin/figma-cache.js validate",
47
+ "figma:cache:stale": "node bin/figma-cache.js stale",
48
+ "figma:cache:budget": "node bin/figma-cache.js budget --mcp-only",
49
+ "figma:cache:backfill": "node bin/figma-cache.js backfill",
50
+ "figma:cache:init": "node bin/figma-cache.js init",
51
+ "figma:cache:config": "node bin/figma-cache.js config",
52
+ "figma:cache:flow:init": "node bin/figma-cache.js flow init",
53
+ "figma:cache:flow:add-node": "node bin/figma-cache.js flow add-node",
54
+ "figma:cache:flow:link": "node bin/figma-cache.js flow link",
55
+ "figma:cache:flow:chain": "node bin/figma-cache.js flow chain",
56
+ "figma:cache:flow:show": "node bin/figma-cache.js flow show",
57
+ "figma:cache:flow:mermaid": "node bin/figma-cache.js flow mermaid",
58
+ "figma:cache:cursor:init": "node bin/figma-cache.js cursor init",
59
+ "docs:encoding:check": "node scripts/check-doc-encoding.js",
60
+ "figma:cache:mobile:spec": "node scripts/mobile/generate-mobile-spec.js"
61
+ },
62
+ "volta": {
63
+ "node": "16.20.2"
64
+ },
65
+ "repository": {
66
+ "type": "git",
67
+ "url": "git+https://github.com/907086379/figma-cache-toolchain.git"
68
+ },
69
+ "bugs": {
70
+ "url": "https://github.com/907086379/figma-cache-toolchain/issues"
71
+ }
72
+ }