mini-figma-code-connect 0.1.0 → 0.1.3
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/LICENSE +13 -9
- package/README.md +52 -130
- package/dist/build.mjs +222 -0
- package/dist/figma-mapping.mjs +611 -0
- package/dist/generate-registry.mjs +79 -0
- package/dist/install-skill.mjs +85 -0
- package/dist/main/code.d.ts +1 -0
- package/dist/main/code.js +388 -0
- package/dist/main/handle.d.ts +55 -0
- package/dist/main/handle.js +167 -0
- package/dist/main/mapping-table.generated.json +1 -0
- package/dist/main/messages.d.ts +71 -0
- package/dist/main/messages.js +1 -0
- package/dist/main/schema.d.ts +9 -0
- package/dist/main/schema.js +39 -0
- package/dist/main/validate.d.ts +16 -0
- package/dist/main/validate.js +40 -0
- package/dist/runtime/define.d.ts +3 -0
- package/dist/runtime/define.js +4 -0
- package/{src/runtime/index.ts → dist/runtime/index.d.ts} +3 -14
- package/dist/runtime/index.js +9 -0
- package/dist/runtime/registry.d.ts +5 -0
- package/dist/runtime/registry.generated.d.ts +3 -0
- package/dist/runtime/registry.generated.js +1 -0
- package/{src/runtime/registry.ts → dist/runtime/registry.js} +7 -8
- package/dist/runtime/render.d.ts +6 -0
- package/dist/runtime/render.js +27 -0
- package/dist/runtime/tagged.d.ts +6 -0
- package/dist/runtime/tagged.js +45 -0
- package/dist/runtime/types.d.ts +107 -0
- package/dist/runtime/types.js +7 -0
- package/dist/scaffold-manifest.mjs +80 -0
- package/dist/scaffold-plugin.mjs +462 -0
- package/dist/ui/ui.d.ts +84 -0
- package/dist/ui/ui.js +190 -0
- package/package.json +23 -18
- package/scripts/build-plugin.mjs +0 -132
- package/scripts/copy-mapping-table.mjs +0 -19
- package/scripts/figma-mapping/ai-generate.mjs +0 -102
- package/scripts/figma-mapping/index.mjs +0 -250
- package/scripts/figma-mapping/lib.mjs +0 -112
- package/scripts/figma-mapping/registry.mjs +0 -12
- package/scripts/figma-mapping/scaffold.mjs +0 -153
- package/scripts/generate-registry.mjs +0 -94
- package/scripts/install-skill.mjs +0 -79
- package/scripts/is-cli-entrypoint.mjs +0 -21
- package/scripts/scaffold-manifest.mjs +0 -84
- package/scripts/scaffold-plugin.mjs +0 -172
- package/src/dev/demo.ts +0 -207
- package/src/dev/export.ts +0 -28
- package/src/main/code.ts +0 -422
- package/src/main/handle.ts +0 -207
- package/src/main/messages.ts +0 -49
- package/src/main/schema.ts +0 -45
- package/src/main/validate.ts +0 -49
- package/src/runtime/define.ts +0 -6
- package/src/runtime/render.ts +0 -26
- package/src/runtime/tagged.ts +0 -46
- package/src/runtime/types.ts +0 -86
- package/src/ui/ui.ts +0 -271
- /package/{src → dist}/ui/ui.html +0 -0
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// scripts/figma-mapping/index.mjs
|
|
4
|
+
import fs4 from "node:fs";
|
|
5
|
+
import path4 from "node:path";
|
|
6
|
+
import readline from "node:readline/promises";
|
|
7
|
+
import { stdin, stdout } from "node:process";
|
|
8
|
+
|
|
9
|
+
// scripts/figma-mapping/lib.mjs
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
var ROOT = process.cwd();
|
|
13
|
+
var CONFIG_PATH = path.join(ROOT, "figma-mapping.config.json");
|
|
14
|
+
var MAPPINGS_DIR = path.join(ROOT, "figma-mappings");
|
|
15
|
+
function readConfig() {
|
|
16
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
17
|
+
console.warn(
|
|
18
|
+
`[figma-mapping] \u5F53\u524D\u76EE\u5F55\uFF08${ROOT}\uFF09\u6CA1\u6709 figma-mapping.config.json \u2014\u2014 \u626B\u4E0D\u5230\u4EFB\u4F55\u5019\u9009\u7EC4\u4EF6\u76EE\u5F55\u3002
|
|
19
|
+
\u6284\u5F15\u64CE\u5305\u91CC\u7684 figma-mapping.config.example.json\uFF0C\u5728\u8FD9\u4E2A\u9879\u76EE\u6839\u76EE\u5F55\u5EFA\u4E00\u4EFD\uFF0C\u6539\u6210\u81EA\u5DF1\u7684\u7EC4\u4EF6\u76EE\u5F55\u3002`
|
|
20
|
+
);
|
|
21
|
+
return { paths: {}, importPaths: {} };
|
|
22
|
+
}
|
|
23
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8")).codeConnect;
|
|
24
|
+
}
|
|
25
|
+
function listCandidateFiles() {
|
|
26
|
+
const { paths } = readConfig();
|
|
27
|
+
const dirs = Object.values(paths ?? {});
|
|
28
|
+
const results = [];
|
|
29
|
+
function walk(dir) {
|
|
30
|
+
if (!fs.existsSync(dir)) return;
|
|
31
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
32
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
33
|
+
const full = path.join(dir, entry.name);
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
walk(full);
|
|
36
|
+
} else if (/\.tsx?$/.test(entry.name) && !/\.figma\.tsx?$/.test(entry.name)) {
|
|
37
|
+
results.push(path.relative(ROOT, full));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const d of dirs) walk(path.resolve(ROOT, d));
|
|
42
|
+
return results;
|
|
43
|
+
}
|
|
44
|
+
function extractComponentInfo(relPath) {
|
|
45
|
+
const source = fs.readFileSync(path.join(ROOT, relPath), "utf8");
|
|
46
|
+
const nameMatches = [
|
|
47
|
+
...source.matchAll(/export\s+(?:declare\s+)?(?:const|function|class)\s+([A-Z][A-Za-z0-9]*)/g)
|
|
48
|
+
].map((m) => m[1]);
|
|
49
|
+
const propsBlocks = [
|
|
50
|
+
...source.matchAll(/(?:interface|type)\s+([A-Za-z0-9]*Props)\b[^{]*\{([\s\S]*?)\n\}/g)
|
|
51
|
+
].map((m) => ({ typeName: m[1], body: m[2] }));
|
|
52
|
+
const propNames = /* @__PURE__ */ new Set();
|
|
53
|
+
for (const block of propsBlocks) {
|
|
54
|
+
for (const m of block.body.matchAll(/^\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\??:/gm)) {
|
|
55
|
+
propNames.add(m[1]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { relPath, names: nameMatches, propsBlocks, propNames: [...propNames], source };
|
|
59
|
+
}
|
|
60
|
+
function normalize(s) {
|
|
61
|
+
return s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
62
|
+
}
|
|
63
|
+
function tokenOverlap(a, b) {
|
|
64
|
+
const na = normalize(a);
|
|
65
|
+
const nb = normalize(b);
|
|
66
|
+
if (!na || !nb) return 0;
|
|
67
|
+
if (na === nb) return 1;
|
|
68
|
+
if (na.includes(nb) || nb.includes(na)) return 0.7;
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
function scoreCandidate(schema, info) {
|
|
72
|
+
const fileBase = path.basename(info.relPath).replace(/\.tsx?$/, "");
|
|
73
|
+
const nameCandidates = [...info.names, fileBase];
|
|
74
|
+
const nameScore = Math.max(0, ...nameCandidates.map((n) => tokenOverlap(schema.componentName, n)));
|
|
75
|
+
const schemaProps = schema.properties.map((p) => normalize(p.name)).filter(Boolean);
|
|
76
|
+
const codeProps = info.propNames.map(normalize);
|
|
77
|
+
const overlapCount = schemaProps.filter((p) => codeProps.some((c) => c === p || c.includes(p) || p.includes(c))).length;
|
|
78
|
+
const propScore = schemaProps.length ? overlapCount / schemaProps.length : 0;
|
|
79
|
+
const score = nameScore * 0.6 + propScore * 0.4;
|
|
80
|
+
return { score, nameScore, propScore, overlapCount, fileBase };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// scripts/figma-mapping/scaffold.mjs
|
|
84
|
+
function normalize2(s) {
|
|
85
|
+
return s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
86
|
+
}
|
|
87
|
+
function findUnionLiterals(propsBody, propName) {
|
|
88
|
+
const line = propsBody.split("\n").find((l) => new RegExp(`^\\s*${propName}\\??:`).test(l));
|
|
89
|
+
if (!line) return null;
|
|
90
|
+
const literals = [...line.matchAll(/"([^"]+)"|'([^']+)'/g)].map((m) => m[1] ?? m[2]);
|
|
91
|
+
return literals.length ? literals : null;
|
|
92
|
+
}
|
|
93
|
+
function bestPropMatch(schemaPropName, codePropNames) {
|
|
94
|
+
const target = normalize2(schemaPropName);
|
|
95
|
+
if (!target) return null;
|
|
96
|
+
let best = null;
|
|
97
|
+
for (const name of codePropNames) {
|
|
98
|
+
const n = normalize2(name);
|
|
99
|
+
if (n === target || n.includes(target) || target.includes(n)) {
|
|
100
|
+
if (!best || n.length < normalize2(best).length) best = name;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return best;
|
|
104
|
+
}
|
|
105
|
+
function varName(p, i, seen) {
|
|
106
|
+
const cleaned = p.name.replace(/[^a-zA-Z0-9]/g, "");
|
|
107
|
+
const base = cleaned || `prop${i + 1}`;
|
|
108
|
+
const camel = base[0].toLowerCase() + base.slice(1);
|
|
109
|
+
let name = /^[0-9]/.test(camel) ? `_${camel}` : camel;
|
|
110
|
+
if (seen.has(name)) {
|
|
111
|
+
let n = 2;
|
|
112
|
+
while (seen.has(`${name}${n}`)) n++;
|
|
113
|
+
name = `${name}${n}`;
|
|
114
|
+
}
|
|
115
|
+
seen.add(name);
|
|
116
|
+
return name;
|
|
117
|
+
}
|
|
118
|
+
function idFrom(name) {
|
|
119
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "") || "template";
|
|
120
|
+
}
|
|
121
|
+
function propLine(p, i, codePropNames, propsBody, seen, claimedCodeProps) {
|
|
122
|
+
const v = varName(p, i, seen);
|
|
123
|
+
const guess = bestPropMatch(p.name, codePropNames);
|
|
124
|
+
const matched = guess && !claimedCodeProps.has(guess) ? guess : null;
|
|
125
|
+
if (matched) claimedCodeProps.add(matched);
|
|
126
|
+
switch (p.type) {
|
|
127
|
+
case "TEXT":
|
|
128
|
+
return { line: `const ${v} = instance.getString('${p.name}')`, jsxAttr: matched ? `${matched}={${v}}` : null };
|
|
129
|
+
case "BOOLEAN":
|
|
130
|
+
return { line: `const ${v} = instance.getBoolean('${p.name}')`, jsxAttr: matched ? `${matched}={${v}}` : null };
|
|
131
|
+
case "VARIANT": {
|
|
132
|
+
const literals = matched && propsBody ? findUnionLiterals(propsBody, matched) : null;
|
|
133
|
+
const opts = p.variantOptions ?? [];
|
|
134
|
+
const mapping = opts.map((o) => {
|
|
135
|
+
const guess2 = literals?.find((l) => normalize2(l) === normalize2(o)) ?? literals?.[0];
|
|
136
|
+
const value = guess2 ? JSON.stringify(guess2) : `'TODO'`;
|
|
137
|
+
return ` ${JSON.stringify(o)}: ${value},`;
|
|
138
|
+
}).join("\n");
|
|
139
|
+
return {
|
|
140
|
+
line: `const ${v} = instance.getEnum('${p.name}', {
|
|
141
|
+
${mapping}
|
|
142
|
+
})`,
|
|
143
|
+
jsxAttr: matched ? `${matched}="\${${v}}"` : null
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
case "INSTANCE_SWAP":
|
|
147
|
+
return {
|
|
148
|
+
line: `const ${v} = instance.getInstanceSwap('${p.name}')
|
|
149
|
+
let ${v}Code
|
|
150
|
+
if (${v} && ${v}.type === 'INSTANCE') {
|
|
151
|
+
${v}Code = ${v}.executeTemplate()?.example
|
|
152
|
+
}`,
|
|
153
|
+
jsxAttr: matched ? `${matched}={\${${v}Code}}` : null
|
|
154
|
+
};
|
|
155
|
+
default:
|
|
156
|
+
return { line: `// TODO: \u672A\u77E5\u5C5E\u6027\u7C7B\u578B ${p.type} \u2014 ${p.name}`, jsxAttr: null };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function buildMatchedScaffold({ schema, componentFile, componentName, importSpecifier, propsBlock }) {
|
|
160
|
+
const propsBody = propsBlock?.body ?? "";
|
|
161
|
+
const codePropNames = propsBlock ? [...propsBody.matchAll(/^\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\??:/gm)].map((m) => m[1]) : [];
|
|
162
|
+
const seen = /* @__PURE__ */ new Set();
|
|
163
|
+
const claimedCodeProps = /* @__PURE__ */ new Set();
|
|
164
|
+
const parts = schema.properties.map((p, i) => propLine(p, i, codePropNames, propsBody, seen, claimedCodeProps));
|
|
165
|
+
const lines = parts.map((p) => ` ${p.line}`).join("\n\n");
|
|
166
|
+
const jsxAttrs = parts.map((p) => p.jsxAttr).filter(Boolean).map((a) => `
|
|
167
|
+
${a}`).join("");
|
|
168
|
+
const unmatchedCount = schema.properties.length - parts.filter((p) => p.jsxAttr).length;
|
|
169
|
+
const id = idFrom(schema.componentName);
|
|
170
|
+
return `// url=https://www.figma.com/design/REPLACE_FILE_KEY/Design-System?node-id=REPLACE
|
|
171
|
+
// source=${componentFile}
|
|
172
|
+
// component=${componentName}
|
|
173
|
+
import { defineTemplate } from '../runtime/define'
|
|
174
|
+
import { code } from '../runtime/tagged'
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* \u7531 figma-mapping CLI \u751F\u6210\uFF1AFigma "${schema.componentName}" \u2194 \u4EE3\u7801 ${componentName}\uFF08${componentFile}\uFF09
|
|
178
|
+
* ${unmatchedCount > 0 ? `${unmatchedCount} \u4E2A\u5C5E\u6027\u6CA1\u81EA\u52A8\u914D\u4E0A\u5BF9\u5E94\u7684\u4EE3\u7801 prop\uFF0C\u4EBA\u5DE5\u786E\u8BA4` : "\u6BCF\u4E2A\u5C5E\u6027\u90FD\u81EA\u52A8\u914D\u4E0A\u4E86\u5BF9\u5E94\u7684\u4EE3\u7801 prop\uFF0C\u4ECD\u5EFA\u8BAE\u4EBA\u5DE5\u786E\u8BA4\u4E00\u904D"}
|
|
179
|
+
*/
|
|
180
|
+
export default defineTemplate({
|
|
181
|
+
meta: {
|
|
182
|
+
url: 'https://www.figma.com/design/REPLACE_FILE_KEY/Design-System?node-id=REPLACE',
|
|
183
|
+
source: '${componentFile}',
|
|
184
|
+
component: '${componentName}',
|
|
185
|
+
},
|
|
186
|
+
id: '${id}',
|
|
187
|
+
match: { componentName: '${schema.componentName}' },
|
|
188
|
+
|
|
189
|
+
render(instance) {
|
|
190
|
+
${lines || " // \u8FD9\u4E2A\u7EC4\u4EF6\u6CA1\u6709\u53EF\u8BFB\u7684\u5C5E\u6027"}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
example: code\`
|
|
194
|
+
<${componentName}${jsxAttrs}
|
|
195
|
+
/>
|
|
196
|
+
\`,
|
|
197
|
+
imports: ['import { ${componentName} } from "${importSpecifier}"'],
|
|
198
|
+
id: '${id}',
|
|
199
|
+
metadata: { nestable: true },
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
})
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
function toImportSpecifier(relPath, importPaths) {
|
|
206
|
+
for (const [glob, spec] of Object.entries(importPaths ?? {})) {
|
|
207
|
+
const prefix = glob.replace(/\*$/, "");
|
|
208
|
+
if (relPath.startsWith(prefix)) {
|
|
209
|
+
const rest = relPath.slice(prefix.length).replace(/\.tsx?$/, "");
|
|
210
|
+
return spec.endsWith("*") ? spec.replace(/\*$/, rest) : spec;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return "./" + relPath.replace(/\.tsx?$/, "");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// scripts/figma-mapping/registry.mjs
|
|
217
|
+
import fs3 from "node:fs";
|
|
218
|
+
import path3 from "node:path";
|
|
219
|
+
|
|
220
|
+
// scripts/generate-registry.mjs
|
|
221
|
+
import fs2 from "node:fs";
|
|
222
|
+
import path2 from "node:path";
|
|
223
|
+
|
|
224
|
+
// scripts/is-cli-entrypoint.mjs
|
|
225
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
226
|
+
import { fileURLToPath } from "node:url";
|
|
227
|
+
function isCliEntrypoint(importMetaUrl) {
|
|
228
|
+
if (!process.argv[1]) return false;
|
|
229
|
+
try {
|
|
230
|
+
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(importMetaUrl));
|
|
231
|
+
} catch {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// scripts/generate-registry.mjs
|
|
237
|
+
function parseGlob(glob) {
|
|
238
|
+
const starIdx = glob.indexOf("*");
|
|
239
|
+
if (starIdx === -1) throw new Error(`generate-registry: glob \u91CC\u6CA1\u6709 *\uFF1A"${glob}"`);
|
|
240
|
+
const dir = glob.slice(0, starIdx).replace(/\/$/, "");
|
|
241
|
+
const recursive = glob.includes("**");
|
|
242
|
+
return { dir, recursive };
|
|
243
|
+
}
|
|
244
|
+
function findFigmaFiles(dir, recursive) {
|
|
245
|
+
const results = [];
|
|
246
|
+
function walk(d) {
|
|
247
|
+
if (!fs2.existsSync(d)) return;
|
|
248
|
+
for (const entry of fs2.readdirSync(d, { withFileTypes: true })) {
|
|
249
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
250
|
+
const full = path2.join(d, entry.name);
|
|
251
|
+
if (entry.isDirectory()) {
|
|
252
|
+
if (recursive) walk(full);
|
|
253
|
+
} else if (entry.name.endsWith(".figma.ts")) {
|
|
254
|
+
results.push(full);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
walk(dir);
|
|
259
|
+
return results.sort();
|
|
260
|
+
}
|
|
261
|
+
function identifierFor(filePath, index) {
|
|
262
|
+
const base = path2.basename(filePath).replace(/\.figma\.ts$/, "").replace(/[^a-zA-Z0-9]/g, "");
|
|
263
|
+
const safe = base && /^[a-zA-Z_]/.test(base) ? base : `M${base}`;
|
|
264
|
+
return `${safe || "Mapping"}_${index}`;
|
|
265
|
+
}
|
|
266
|
+
function generateRegistry({ cwd, mappingsGlob, outFile, typesImport = "./types" }) {
|
|
267
|
+
const { dir, recursive } = parseGlob(mappingsGlob);
|
|
268
|
+
const files = findFigmaFiles(path2.resolve(cwd, dir), recursive);
|
|
269
|
+
const outDir = path2.dirname(outFile);
|
|
270
|
+
const imports = files.map((f, i) => {
|
|
271
|
+
const id = identifierFor(f, i);
|
|
272
|
+
let rel = path2.relative(outDir, f).replace(/\.ts$/, "");
|
|
273
|
+
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
274
|
+
rel = rel.split(path2.sep).join("/");
|
|
275
|
+
return { id, importPath: rel };
|
|
276
|
+
});
|
|
277
|
+
const typeLine = typesImport == null ? "" : `import type { Template } from '${typesImport}'
|
|
278
|
+
`;
|
|
279
|
+
const templatesAnn = typesImport == null ? "" : ": Template[]";
|
|
280
|
+
const body = `// \u81EA\u52A8\u751F\u6210\uFF0C\u4E0D\u8981\u624B\u6539 \u2014\u2014 \u7531 scripts/generate-registry.mjs \u626B .figma.ts \u751F\u6210\uFF0C\u6BCF\u6B21 build \u524D\u91CD\u8DD1
|
|
281
|
+
` + typeLine + imports.map((i) => `import ${i.id} from '${i.importPath}'`).join("\n") + (imports.length ? "\n\n" : "\n") + `export const templates${templatesAnn} = [${imports.map((i) => i.id).join(", ")}]
|
|
282
|
+
`;
|
|
283
|
+
fs2.mkdirSync(outDir, { recursive: true });
|
|
284
|
+
fs2.writeFileSync(outFile, body);
|
|
285
|
+
return { count: files.length, files };
|
|
286
|
+
}
|
|
287
|
+
if (isCliEntrypoint(import.meta.url)) {
|
|
288
|
+
const [, , mappingsGlob, outFile] = process.argv;
|
|
289
|
+
if (!mappingsGlob || !outFile) {
|
|
290
|
+
console.error("\u7528\u6CD5: node scripts/generate-registry.mjs <mappingsGlob> <outFile>");
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
const result = generateRegistry({ cwd: process.cwd(), mappingsGlob, outFile: path2.resolve(outFile) });
|
|
294
|
+
console.log(`[generate-registry] \u5199\u5165 ${result.count} \u6761\u6620\u5C04\u5230 ${outFile}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// scripts/figma-mapping/registry.mjs
|
|
298
|
+
function refreshRegistry({
|
|
299
|
+
cwd,
|
|
300
|
+
mappingsGlob = "figma-mappings/**/*.figma.ts",
|
|
301
|
+
outFile = path3.join(cwd, "figma-plugin-dist/.generated/registry.generated.ts")
|
|
302
|
+
}) {
|
|
303
|
+
fs3.mkdirSync(path3.dirname(outFile), { recursive: true });
|
|
304
|
+
const result = generateRegistry({ cwd, mappingsGlob, outFile });
|
|
305
|
+
return { count: result.count };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// scripts/figma-mapping/ai-generate.mjs
|
|
309
|
+
import { execFileSync } from "node:child_process";
|
|
310
|
+
var EXAMPLE_TEMPLATE = `import { defineTemplate, code } from 'mini-figma-code-connect'
|
|
311
|
+
|
|
312
|
+
export default defineTemplate({
|
|
313
|
+
meta: {
|
|
314
|
+
url: 'https://www.figma.com/design/FILE_KEY/Design-System?node-id=NODE_ID',
|
|
315
|
+
source: '../path/to/components/button.tsx',
|
|
316
|
+
component: 'Button',
|
|
317
|
+
},
|
|
318
|
+
id: 'button',
|
|
319
|
+
match: { componentName: 'Button' },
|
|
320
|
+
|
|
321
|
+
render(instance) {
|
|
322
|
+
const label = instance.getString('Label')
|
|
323
|
+
const disabled = instance.getBoolean('Disabled')
|
|
324
|
+
const size = instance.getEnum('Size', { Large: 'large', Medium: 'medium', Small: 'small' })
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
example: code\`<Button size="\${size}"\${disabled ? code\` disabled\` : ''}>\${label}</Button>\`,
|
|
328
|
+
imports: ['import { Button } from "@/components/button"'],
|
|
329
|
+
id: 'button',
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
})
|
|
333
|
+
`;
|
|
334
|
+
function buildPrompt({ schema, candidates, importPaths }) {
|
|
335
|
+
const candidateBlocks = candidates.map(
|
|
336
|
+
(c) => `### \u5019\u9009\u6587\u4EF6: ${c.relPath}
|
|
337
|
+
\`\`\`
|
|
338
|
+
${c.source}
|
|
339
|
+
\`\`\`
|
|
340
|
+
`
|
|
341
|
+
).join("\n");
|
|
342
|
+
return `\u4F60\u662F Figma Code Connect \u6620\u5C04\u751F\u6210\u5668\u3002\u4EFB\u52A1\uFF1A\u5224\u65AD\u4E0B\u9762\u8FD9\u4E2A Figma \u7EC4\u4EF6\u8BE5\u63A5\u5230\u54EA\u4E2A\u5019\u9009\u4EE3\u7801\u7EC4\u4EF6\uFF0C\u5982\u679C\u6709\u5408\u9002\u7684\u5C31\u751F\u6210\u6620\u5C04\u6587\u4EF6\u5185\u5BB9\u3002
|
|
343
|
+
|
|
344
|
+
## Figma \u7EC4\u4EF6 schema
|
|
345
|
+
\`\`\`json
|
|
346
|
+
${JSON.stringify(schema, null, 2)}
|
|
347
|
+
\`\`\`
|
|
348
|
+
|
|
349
|
+
## \u5019\u9009\u4EE3\u7801\u7EC4\u4EF6\uFF08\u6309\u8DEF\u5F84\u6392\u5217\uFF0C\u53EF\u80FD\u4E00\u4E2A\u90FD\u4E0D\u5408\u9002\uFF09
|
|
350
|
+
${candidateBlocks}
|
|
351
|
+
|
|
352
|
+
## \u76EE\u6807\u6587\u4EF6\u683C\u5F0F\uFF08\u8FD9\u662F\u4E00\u4EFD\u5DF2\u6709\u7684\u793A\u4F8B\uFF0C\u7167\u8FD9\u4E2A\u683C\u5F0F\u548C accessor API \u5199\uFF0C\u4E0D\u8981\u7528\u522B\u7684\u683C\u5F0F\uFF09
|
|
353
|
+
\`\`\`ts
|
|
354
|
+
${EXAMPLE_TEMPLATE}
|
|
355
|
+
\`\`\`
|
|
356
|
+
|
|
357
|
+
## import \u8DEF\u5F84\u6362\u7B97\u89C4\u5219
|
|
358
|
+
\u5019\u9009\u6587\u4EF6\u8DEF\u5F84\u524D\u7F00 \u2192 \u751F\u6210\u6587\u4EF6\u91CC imports \u7528\u7684 specifier\uFF1A
|
|
359
|
+
${JSON.stringify(importPaths, null, 2)}
|
|
360
|
+
|
|
361
|
+
## \u8981\u6C42
|
|
362
|
+
1. \u4ECE\u5019\u9009\u6587\u4EF6\u91CC\u9009\u4E00\u4E2A\u6700\u5408\u9002\u7684\uFF08\u7EC4\u4EF6\u540D\u3001Props \u8BED\u4E49\u90FD\u8981\u5BF9\u5F97\u4E0A\uFF0C\u4E0D\u662F\u5B57\u7B26\u4E32\u50CF\u4E0D\u50CF\uFF09\u3002\u4E00\u4E2A\u90FD\u4E0D\u5408\u9002\u5C31\u9009 NONE\u3002
|
|
363
|
+
2. Figma \u5C5E\u6027\u540D\u53EF\u80FD\u662F\u4E2D\u6587\u3001\u53EF\u80FD\u8DDF\u4EE3\u7801 prop \u4E0D\u662F\u5B57\u9762\u4E00\u81F4\uFF08\u6BD4\u5982"\u72B6\u6001"\u91CC\u7684 disabled/hover/loading \u9700\u8981\u62C6\u6210 disabled/loading \u4E24\u4E2A prop\uFF0Chover/pressed \u662F CSS \u72B6\u6001\u4E0D\u662F prop\uFF0C\u4E0D\u80FD\u786C\u585E\uFF09\u3002VARIANT \u7684\u5B57\u5178\u8981\u8986\u76D6 schema \u91CC\u7ED9\u7684\u6BCF\u4E00\u4E2A\u9009\u9879\uFF0C\u914D\u4E0D\u4E0A\u771F\u5B9E\u503C\u7684\u5FC5\u987B\u663E\u5F0F\u7559 'TODO' \u4E0D\u8981\u778E\u7F16\u4E00\u4E2A\u80FD\u7F16\u8BD1\u4F46\u8BED\u4E49\u9519\u7684\u503C\u3002
|
|
364
|
+
3. \u4E0D\u786E\u5B9A\u7684\u5224\u65AD\uFF08\u6BD4\u5982\u6CA1\u6709\u7CBE\u786E\u5BF9\u5E94\u7684 variant \u503C\u8BE5\u731C\u54EA\u4E2A\uFF09\uFF0C\u7167\u6837\u505A\u51FA\u9009\u62E9\uFF0C\u4F46\u5728\u751F\u6210\u6587\u4EF6\u9876\u90E8\u6CE8\u91CA\u91CC\u660E\u786E\u5199\u51FA"\u8FD9\u662F\u731C\u7684\uFF0C\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4"\uFF0C\u522B\u4E0D\u58F0\u4E0D\u54CD\u3002
|
|
365
|
+
4. \u4E25\u7981\u8C03\u7528\u4EFB\u4F55\u5DE5\u5177/\u547D\u4EE4\uFF0C\u4E25\u7981\u8BFB\u5199\u6587\u4EF6\u7CFB\u7EDF \u2014\u2014 \u4F60\u73B0\u5728\u6CA1\u6709\u8FD9\u4E9B\u6743\u9650\uFF0C\u53EA\u9700\u8981\u57FA\u4E8E\u4E0A\u9762\u7ED9\u7684\u4FE1\u606F\u76F4\u63A5\u751F\u6210\u6587\u672C\u3002
|
|
366
|
+
5. \u4E25\u7981\u7F16\u9020\u4EE3\u7801\u91CC\u4E0D\u5B58\u5728\u7684 prop \u540D\u3002
|
|
367
|
+
|
|
368
|
+
## \u8F93\u51FA\u683C\u5F0F\uFF08\u4E25\u683C\u9075\u5B88\uFF0C\u4E0D\u8981\u8F93\u51FA\u5176\u5B83\u4EFB\u4F55\u6587\u5B57\uFF09
|
|
369
|
+
\u7B2C\u4E00\u884C\uFF1A\`MATCH: <\u5019\u9009\u6587\u4EF6\u7684 relPath>\` \u6216 \`MATCH: NONE\`
|
|
370
|
+
\u5982\u679C MATCH \u4E0D\u662F NONE\uFF0C\u7B2C\u4E8C\u884C\u5F00\u59CB\u662F\u4E00\u6761\u5206\u9694\u7EBF \`---\`\uFF0C\u4E4B\u540E\u662F\u5B8C\u6574\u7684 .figma.ts \u6587\u4EF6\u5185\u5BB9\uFF08\u4E0D\u8981\u7528 markdown \u4EE3\u7801\u5757\u5305\u88F9\uFF0C\u76F4\u63A5\u662F\u53EF\u4EE5\u539F\u6837\u5199\u5165\u6587\u4EF6\u7684 TypeScript \u6E90\u7801\uFF09\u3002
|
|
371
|
+
\u5982\u679C MATCH \u662F NONE\uFF0C\u7B2C\u4E8C\u884C\u5F00\u59CB\u7B80\u77ED\u8BF4\u660E\u4E3A\u4EC0\u4E48\u6CA1\u6709\u5408\u9002\u7684\u5019\u9009\uFF08\u4E00\u4E24\u53E5\u8BDD\uFF09\u3002`;
|
|
372
|
+
}
|
|
373
|
+
function aiGenerateMapping({ schema, candidates, importPaths }) {
|
|
374
|
+
const prompt = buildPrompt({ schema, candidates, importPaths });
|
|
375
|
+
const raw = execFileSync(
|
|
376
|
+
"claude",
|
|
377
|
+
[
|
|
378
|
+
"-p",
|
|
379
|
+
prompt,
|
|
380
|
+
"--output-format",
|
|
381
|
+
"text",
|
|
382
|
+
"--disallowedTools",
|
|
383
|
+
"Read,Write,Edit,Bash,Glob,Grep,WebFetch,WebSearch,Task,NotebookEdit"
|
|
384
|
+
],
|
|
385
|
+
{ encoding: "utf8", maxBuffer: 32 * 1024 * 1024 }
|
|
386
|
+
);
|
|
387
|
+
const firstLineEnd = raw.indexOf("\n");
|
|
388
|
+
const firstLine = (firstLineEnd === -1 ? raw : raw.slice(0, firstLineEnd)).trim();
|
|
389
|
+
const rest = firstLineEnd === -1 ? "" : raw.slice(firstLineEnd + 1);
|
|
390
|
+
const m = firstLine.match(/^MATCH:\s*(.+)$/);
|
|
391
|
+
if (!m || m[1].trim() === "NONE") {
|
|
392
|
+
return { matched: false, reason: rest.trim() || firstLine };
|
|
393
|
+
}
|
|
394
|
+
const relPath = m[1].trim();
|
|
395
|
+
const content = rest.replace(/^---\s*\n?/, "");
|
|
396
|
+
return { matched: true, relPath, content };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// scripts/figma-mapping/index.mjs
|
|
400
|
+
var rl = readline.createInterface({ input: stdin, output: stdout });
|
|
401
|
+
var ask = (q) => rl.question(q);
|
|
402
|
+
function printHelp() {
|
|
403
|
+
console.log(`\u7528\u6CD5:
|
|
404
|
+
node scripts/figma-mapping/index.mjs <schema.json> [--ai]
|
|
405
|
+
|
|
406
|
+
schema.json \u4ECE\u63D2\u4EF6\u9762\u677F\u62FF\uFF1A
|
|
407
|
+
- \u5355\u4E2A\u7EC4\u4EF6\uFF1A"\u5BFC\u51FA schema.json"\u6309\u94AE\uFF08\u672A\u6620\u5C04\u72B6\u6001\u4E0B\u624D\u6709\uFF09
|
|
408
|
+
- \u4E00\u6574\u6279\uFF1A\u5019\u9009\u5217\u8868\u91CC"\u5BFC\u51FA\u5168\u90E8 schema.json"\u6309\u94AE\uFF0C\u4F1A\u662F\u4E00\u4E2A schema \u6570\u7EC4\uFF0C\u811A\u672C\u6328\u4E2A\u8DD1\u5B8C
|
|
409
|
+
|
|
410
|
+
\u4E0D\u5E26 --ai\uFF1A\u672C\u5730\u6B63\u5219\u6253\u5206\u6392\u5019\u9009\uFF0C\u4F60\u81EA\u5DF1\u6311\u3001\u81EA\u5DF1\u5224\u65AD\u8981\u4E0D\u8981\u63A5\u54EA\u4E2A prop\u3002
|
|
411
|
+
\u5E26 --ai\uFF1A\u8C03\u672C\u673A claude CLI\uFF08\u65E0\u5934\u6A21\u5F0F\uFF0C\u7981\u6389\u6240\u6709\u5DE5\u5177\u6743\u9650\uFF09\u8BFB schema + \u5019\u9009\u6E90\u7801\uFF0C\u76F4\u63A5\u505A\u771F\u5224\u65AD\u3001
|
|
412
|
+
\u751F\u6210\u5B8C\u6574\u6620\u5C04\u5185\u5BB9\uFF0C\u4F60\u53EA\u9700\u8981\u786E\u8BA4\u8981\u4E0D\u8981\u91C7\u7EB3\u2014\u2014\u5224\u65AD\u529B\u6BD4\u6B63\u5219\u5F3A\uFF0C\u4F46\u4E5F\u53EF\u80FD\u5224\u65AD\u9519\uFF0C\u4E00\u6837\u8981\u770B\u4E00\u773C\u3002
|
|
413
|
+
|
|
414
|
+
\u4E24\u79CD\u6A21\u5F0F\u90FD\u4F1A\u628A .figma.ts \u751F\u6210\u5230\u5F53\u524D\u76EE\u5F55\uFF08\u8FD0\u884C\u8FD9\u4E2A\u547D\u4EE4\u7684\u9879\u76EE\u6839\u76EE\u5F55\uFF09\u7684 figma-mappings/\uFF0C\u5E76\u91CD\u65B0\u751F\u6210
|
|
415
|
+
registry.generated.ts\uFF08\u81EA\u52A8\u626B\u5168\u90E8 .figma.ts\uFF0C\u4E0D\u9700\u8981\u624B\u52A8\u52A0 import\uFF09\u3002`);
|
|
416
|
+
}
|
|
417
|
+
function rankFiles(files, schema) {
|
|
418
|
+
return files.map((f) => {
|
|
419
|
+
const info = extractComponentInfo(f);
|
|
420
|
+
const s = scoreCandidate(schema, info);
|
|
421
|
+
return { file: f, info, ...s };
|
|
422
|
+
}).sort((a, b) => b.score - a.score);
|
|
423
|
+
}
|
|
424
|
+
async function main(schemaPath2, useAi) {
|
|
425
|
+
if (!schemaPath2 || !fs4.existsSync(schemaPath2)) {
|
|
426
|
+
console.error(`\u627E\u4E0D\u5230 schema \u6587\u4EF6: ${schemaPath2}
|
|
427
|
+
\u5148\u5728\u63D2\u4EF6\u9762\u677F\u91CC\u5BF9\u7740\u672A\u6620\u5C04\u7684\u5B9E\u4F8B\u70B9"\u5BFC\u51FA schema.json"\u3002`);
|
|
428
|
+
process.exitCode = 1;
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const parsed = JSON.parse(fs4.readFileSync(schemaPath2, "utf8"));
|
|
432
|
+
const queue = Array.isArray(parsed) ? parsed : [parsed];
|
|
433
|
+
console.log(`
|
|
434
|
+
\u5171 ${queue.length} \u4E2A Figma \u7EC4\u4EF6\u5F85\u5904\u7406\u3002${useAi ? "\uFF08AI \u6A21\u5F0F\uFF09" : ""}
|
|
435
|
+
`);
|
|
436
|
+
await processQueue(queue, useAi);
|
|
437
|
+
rl.close();
|
|
438
|
+
}
|
|
439
|
+
async function processQueue(queue, useAi) {
|
|
440
|
+
if (queue.length === 0) {
|
|
441
|
+
console.log("\n\u5168\u90E8\u5904\u7406\u5B8C\u3002");
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const [schema, ...rest] = queue;
|
|
445
|
+
console.log(`
|
|
446
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 [\u8FD8\u5269 ${queue.length} \u4E2A] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
447
|
+
console.log(`\u76EE\u6807 Figma \u7EC4\u4EF6: ${schema.componentName}`);
|
|
448
|
+
console.log(`\u5C5E\u6027: ${schema.properties.map((p) => `${p.name}(${p.type})`).join(", ") || "\uFF08\u65E0\uFF09"}
|
|
449
|
+
`);
|
|
450
|
+
const allFiles = listCandidateFiles();
|
|
451
|
+
if (useAi) {
|
|
452
|
+
return aiFlow(schema, rankFiles(allFiles, schema), rest);
|
|
453
|
+
}
|
|
454
|
+
await pickLoop(schema, rankFiles(allFiles, schema), allFiles, rest);
|
|
455
|
+
}
|
|
456
|
+
async function aiFlow(schema, ranked, rest) {
|
|
457
|
+
const top = ranked.slice(0, 6);
|
|
458
|
+
if (top.length === 0) {
|
|
459
|
+
console.log("\u6CA1\u6709\u5019\u9009\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u8FD9\u4E2A\u7EC4\u4EF6\u3002");
|
|
460
|
+
return processQueue(rest, true);
|
|
461
|
+
}
|
|
462
|
+
console.log(`\u628A\u8FD9 ${top.length} \u4E2A\u5019\u9009\u8FDE\u540C\u6E90\u7801\u4EA4\u7ED9 claude \u5224\u65AD:`);
|
|
463
|
+
top.forEach((r) => console.log(` - ${r.file}`));
|
|
464
|
+
console.log("\n\u8C03\u7528\u4E2D\uFF08\u53EF\u80FD\u8981\u51E0\u5341\u79D2\uFF09...\n");
|
|
465
|
+
const config = readConfig();
|
|
466
|
+
const candidates = top.map((r) => ({ relPath: r.file, source: r.info.source }));
|
|
467
|
+
let result;
|
|
468
|
+
try {
|
|
469
|
+
result = aiGenerateMapping({ schema, candidates, importPaths: config.importPaths });
|
|
470
|
+
} catch (err) {
|
|
471
|
+
console.log(`\u8C03\u7528 claude CLI \u5931\u8D25\uFF1A${err.stderr || err.stdout || err.message}
|
|
472
|
+
`);
|
|
473
|
+
const fallback = (await ask("\u8981\u4E0D\u8981\u9000\u56DE\u672C\u5730\u6B63\u5219\u6253\u5206\u624B\u52A8\u6311\u4E00\u4E2A\uFF1F(y/N): ")).trim().toLowerCase();
|
|
474
|
+
if (fallback === "y") {
|
|
475
|
+
const allFiles = listCandidateFiles();
|
|
476
|
+
return pickLoop(schema, ranked, allFiles, rest, true);
|
|
477
|
+
}
|
|
478
|
+
return processQueue(rest, true);
|
|
479
|
+
}
|
|
480
|
+
if (!result.matched) {
|
|
481
|
+
console.log(`AI \u5224\u65AD\u6CA1\u6709\u5408\u9002\u7684\u5019\u9009\uFF1A${result.reason}
|
|
482
|
+
`);
|
|
483
|
+
const fallback = (await ask("\u8981\u4E0D\u8981\u9000\u56DE\u672C\u5730\u6B63\u5219\u6253\u5206\u624B\u52A8\u6311\u4E00\u4E2A\uFF1F(y/N): ")).trim().toLowerCase();
|
|
484
|
+
if (fallback === "y") {
|
|
485
|
+
const allFiles = listCandidateFiles();
|
|
486
|
+
return pickLoop(schema, ranked, allFiles, rest, true);
|
|
487
|
+
}
|
|
488
|
+
return processQueue(rest, true);
|
|
489
|
+
}
|
|
490
|
+
console.log(`AI \u9009\u4E86: ${result.relPath}
|
|
491
|
+
`);
|
|
492
|
+
console.log("\u2500".repeat(60));
|
|
493
|
+
console.log(result.content);
|
|
494
|
+
console.log("\u2500".repeat(60));
|
|
495
|
+
const confirm = (await ask('\n\u786E\u8BA4\u91C7\u7EB3\u8FD9\u4EFD\u751F\u6210\u7ED3\u679C\u5417\uFF1F\u8F93\u5165 "good" \u786E\u8BA4\uFF0C\u5176\u4ED6\u4EFB\u610F\u952E\u653E\u5F03: ')).trim();
|
|
496
|
+
if (confirm !== "good") {
|
|
497
|
+
console.log("\u653E\u5F03\uFF0C\u8DF3\u8FC7\u8FD9\u4E2A\u7EC4\u4EF6\u3002\n");
|
|
498
|
+
return processQueue(rest, true);
|
|
499
|
+
}
|
|
500
|
+
return writeMappingFile(schema, result.content, rest, true);
|
|
501
|
+
}
|
|
502
|
+
async function pickLoop(schema, ranked, allFiles, rest, useAi) {
|
|
503
|
+
const top = ranked.slice(0, 8);
|
|
504
|
+
if (top.length === 0) {
|
|
505
|
+
console.log("\u6CA1\u6709\u5019\u9009\u6587\u4EF6\u4E86\uFF0C\u8DF3\u8FC7\u8FD9\u4E2A\u7EC4\u4EF6\u3002");
|
|
506
|
+
return processQueue(rest, useAi);
|
|
507
|
+
}
|
|
508
|
+
console.log("\u5019\u9009\u6392\u540D\uFF08\u5206\u6570\u8D8A\u9AD8\u8D8A\u50CF\uFF09:\n");
|
|
509
|
+
top.forEach((r, i) => {
|
|
510
|
+
const reason = `\u540D\u5B57\u76F8\u4F3C\u5EA6 ${r.nameScore.toFixed(2)} \xB7 \u5C5E\u6027\u91CD\u5408 ${r.overlapCount}/${schema.properties.length}`;
|
|
511
|
+
console.log(` ${i + 1}. ${r.file} [${r.score.toFixed(2)}] (${reason})`);
|
|
512
|
+
});
|
|
513
|
+
const answer = (await ask('\n\u8F93\u5165\u5E8F\u53F7\u9009\u4E00\u4E2A / \u8F93\u5165\u5173\u952E\u5B57\u91CD\u65B0\u7B5B\u9009\u6587\u4EF6 / "m" \u624B\u52A8\u8F93\u5165\u8DEF\u5F84 / "s" \u8DF3\u8FC7\u8FD9\u4E2A\u7EC4\u4EF6 / "q" \u5168\u90E8\u9000\u51FA: ')).trim();
|
|
514
|
+
if (answer === "q") return;
|
|
515
|
+
if (answer === "s") return processQueue(rest, useAi);
|
|
516
|
+
if (answer === "m") {
|
|
517
|
+
const rel = (await ask("\u8F93\u5165\u8DEF\u5F84\uFF08\u76F8\u5BF9\u9879\u76EE\u6839\u76EE\u5F55\uFF0C\u6216\u7EDD\u5BF9\u8DEF\u5F84\uFF09: ")).trim();
|
|
518
|
+
if (!fs4.existsSync(path4.resolve(ROOT, rel))) {
|
|
519
|
+
console.log("\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u91CD\u6765\u3002\n");
|
|
520
|
+
return pickLoop(schema, ranked, allFiles, rest, useAi);
|
|
521
|
+
}
|
|
522
|
+
return confirmAndGenerate(schema, path4.relative(ROOT, path4.resolve(ROOT, rel)), rest, useAi);
|
|
523
|
+
}
|
|
524
|
+
const idx = Number(answer);
|
|
525
|
+
if (Number.isInteger(idx) && idx >= 1 && idx <= top.length) {
|
|
526
|
+
return confirmAndGenerate(schema, top[idx - 1].file, rest, useAi);
|
|
527
|
+
}
|
|
528
|
+
const filtered = allFiles.filter((f) => f.toLowerCase().includes(answer.toLowerCase()));
|
|
529
|
+
if (filtered.length === 0) {
|
|
530
|
+
console.log("\u6CA1\u5339\u914D\u5230\u6587\u4EF6\uFF0C\u91CD\u6765\u3002\n");
|
|
531
|
+
return pickLoop(schema, ranked, allFiles, rest, useAi);
|
|
532
|
+
}
|
|
533
|
+
console.log("");
|
|
534
|
+
return pickLoop(schema, rankFiles(filtered, schema), allFiles, rest, useAi);
|
|
535
|
+
}
|
|
536
|
+
async function confirmAndGenerate(schema, relPath, rest, useAi) {
|
|
537
|
+
const info = extractComponentInfo(relPath);
|
|
538
|
+
const componentName = info.names.find((n) => n.toLowerCase() === path4.basename(relPath, ".ts").toLowerCase()) ?? info.names[0] ?? path4.basename(relPath, ".ts");
|
|
539
|
+
console.log(`
|
|
540
|
+
\u9009\u4E2D: ${relPath}`);
|
|
541
|
+
console.log(`\u8BC6\u522B\u5230\u7684\u5BFC\u51FA: ${info.names.join(", ") || "\uFF08\u6CA1\u627E\u5230 export \u7684\u7EC4\u4EF6\u540D\uFF0C\u4F1A\u7528\u6587\u4EF6\u540D\u515C\u5E95\uFF09"}`);
|
|
542
|
+
console.log(`\u9ED8\u8BA4\u53D6: ${componentName}\uFF08\u4E0D\u5BF9\u7684\u8BDD\u4E0B\u4E00\u6B65\u53EF\u4EE5\u6539\uFF09`);
|
|
543
|
+
if (info.propsBlocks.length) {
|
|
544
|
+
console.log(`Props \u5B9A\u4E49:`);
|
|
545
|
+
for (const b of info.propsBlocks) {
|
|
546
|
+
const names = [...b.body.matchAll(/^\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\??:/gm)].map((m) => m[1]);
|
|
547
|
+
console.log(` ${b.typeName} { ${names.join(", ")} }`);
|
|
548
|
+
}
|
|
549
|
+
} else {
|
|
550
|
+
console.log("\u6CA1\u627E\u5230 Props type/interface\uFF08\u53EF\u80FD\u662F\u5185\u8054\u7C7B\u578B\uFF0C\u751F\u6210\u540E\u81EA\u5DF1\u6838\u5BF9\uFF09");
|
|
551
|
+
}
|
|
552
|
+
const nameInput = (await ask(`
|
|
553
|
+
\u4EE3\u7801\u7EC4\u4EF6\u540D [\u56DE\u8F66\u7528 "${componentName}"]: `)).trim();
|
|
554
|
+
const finalName = nameInput || componentName;
|
|
555
|
+
const confirm = (await ask(`
|
|
556
|
+
\u786E\u8BA4\u751F\u6210\u6620\u5C04\u5417\uFF1F\u8F93\u5165 "good" \u786E\u8BA4\uFF0C\u5176\u4ED6\u4EFB\u610F\u952E\u653E\u5F03: `)).trim();
|
|
557
|
+
if (confirm !== "good") {
|
|
558
|
+
console.log("\u653E\u5F03\uFF0C\u56DE\u5230\u5217\u8868\u3002\n");
|
|
559
|
+
const allFiles = listCandidateFiles();
|
|
560
|
+
return pickLoop(schema, rankFiles(allFiles, schema), allFiles, rest, useAi);
|
|
561
|
+
}
|
|
562
|
+
const config = readConfig();
|
|
563
|
+
const importSpecifier = toImportSpecifier(relPath, config.importPaths);
|
|
564
|
+
const propsBlock = info.propsBlocks[0];
|
|
565
|
+
const content = buildMatchedScaffold({
|
|
566
|
+
schema,
|
|
567
|
+
componentFile: relPath,
|
|
568
|
+
componentName: finalName,
|
|
569
|
+
importSpecifier,
|
|
570
|
+
propsBlock
|
|
571
|
+
});
|
|
572
|
+
return writeMappingFile(schema, content, rest, useAi);
|
|
573
|
+
}
|
|
574
|
+
async function writeMappingFile(schema, content, rest, useAi) {
|
|
575
|
+
const safeName = schema.componentName.replace(/[^a-zA-Z0-9]+/g, "") || "Component";
|
|
576
|
+
const defaultOut = path4.relative(ROOT, path4.join(MAPPINGS_DIR, `${safeName}.figma.ts`));
|
|
577
|
+
const outInput = (await ask(`\u4FDD\u5B58\u5230\u54EA [\u56DE\u8F66\u7528 "${defaultOut}"]: `)).trim();
|
|
578
|
+
const outRel = outInput || defaultOut;
|
|
579
|
+
const outAbs = path4.resolve(ROOT, outRel);
|
|
580
|
+
if (!outAbs.startsWith(ROOT + path4.sep)) {
|
|
581
|
+
console.log(`\u62D2\u7EDD\u5199\u5165\uFF1A${outAbs} \u5728\u9879\u76EE\u6839\u76EE\u5F55\u4E4B\u5916\uFF0C\u8FD9\u4E2A\u811A\u672C\u53EA\u5141\u8BB8\u5F80 ${ROOT} \u91CC\u5199\u3002\u8DF3\u8FC7\u8FD9\u4E2A\u7EC4\u4EF6\u3002
|
|
582
|
+
`);
|
|
583
|
+
return processQueue(rest, useAi);
|
|
584
|
+
}
|
|
585
|
+
if (fs4.existsSync(outAbs)) {
|
|
586
|
+
const overwrite = (await ask(`${outRel} \u5DF2\u5B58\u5728\uFF0C\u8986\u76D6\uFF1F(y/N): `)).trim().toLowerCase();
|
|
587
|
+
if (overwrite !== "y") {
|
|
588
|
+
console.log("\u53D6\u6D88\u5199\u5165\uFF0C\u8DF3\u8FC7\u8FD9\u4E2A\u7EC4\u4EF6\u3002\n");
|
|
589
|
+
return processQueue(rest, useAi);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
fs4.mkdirSync(path4.dirname(outAbs), { recursive: true });
|
|
593
|
+
fs4.writeFileSync(outAbs, content);
|
|
594
|
+
console.log(`
|
|
595
|
+
\u5DF2\u5199\u5165 ${outRel}`);
|
|
596
|
+
const { count } = refreshRegistry({ cwd: ROOT });
|
|
597
|
+
console.log(`registry.generated.ts \u5DF2\u91CD\u65B0\u751F\u6210\uFF08${count} \u6761\u6620\u5C04\uFF09`);
|
|
598
|
+
return processQueue(rest, useAi);
|
|
599
|
+
}
|
|
600
|
+
var args = process.argv.slice(2);
|
|
601
|
+
var useAiFlag = args.includes("--ai");
|
|
602
|
+
var schemaPath = args.find((a) => !a.startsWith("--"));
|
|
603
|
+
if (!schemaPath) {
|
|
604
|
+
printHelp();
|
|
605
|
+
rl.close();
|
|
606
|
+
} else {
|
|
607
|
+
await main(schemaPath, useAiFlag);
|
|
608
|
+
console.log(`
|
|
609
|
+
\u5168\u90E8\u8DD1\u5B8C\uFF0C\u56DE\u5230\u9879\u76EE\u91CC\u8DD1\u4E00\u904D\u7C7B\u578B\u68C0\u67E5 + \u63D2\u4EF6\u6784\u5EFA\uFF08\u6BD4\u5982 rexy \u662F "pnpm run figma:build"\uFF09\uFF0C\u786E\u8BA4\u7F16\u5F97\u8FC7\uFF0C\u518D\u56DE Figma \u91CD\u65B0\u8BFB\u53D6\u9A8C\u8BC1\u3002`);
|
|
610
|
+
rl.close();
|
|
611
|
+
}
|