mini-figma-code-connect 0.1.1 → 0.1.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.
Files changed (61) hide show
  1. package/LICENSE +13 -9
  2. package/README.md +14 -3
  3. package/dist/build.mjs +228 -0
  4. package/dist/figma-mapping.mjs +618 -0
  5. package/dist/generate-registry.mjs +86 -0
  6. package/dist/install-skill.mjs +91 -0
  7. package/dist/main/code.d.ts +1 -0
  8. package/dist/main/code.js +388 -0
  9. package/dist/main/handle.d.ts +55 -0
  10. package/dist/main/handle.js +167 -0
  11. package/dist/main/mapping-table.generated.json +1 -0
  12. package/dist/main/messages.d.ts +71 -0
  13. package/dist/main/messages.js +1 -0
  14. package/dist/main/schema.d.ts +9 -0
  15. package/dist/main/schema.js +39 -0
  16. package/dist/main/validate.d.ts +16 -0
  17. package/dist/main/validate.js +40 -0
  18. package/dist/runtime/define.d.ts +3 -0
  19. package/dist/runtime/define.js +4 -0
  20. package/{src/runtime/index.ts → dist/runtime/index.d.ts} +3 -14
  21. package/dist/runtime/index.js +9 -0
  22. package/dist/runtime/registry.d.ts +5 -0
  23. package/dist/runtime/registry.generated.d.ts +3 -0
  24. package/dist/runtime/registry.generated.js +1 -0
  25. package/{src/runtime/registry.ts → dist/runtime/registry.js} +7 -8
  26. package/dist/runtime/render.d.ts +6 -0
  27. package/dist/runtime/render.js +27 -0
  28. package/dist/runtime/tagged.d.ts +6 -0
  29. package/dist/runtime/tagged.js +45 -0
  30. package/dist/runtime/types.d.ts +107 -0
  31. package/dist/runtime/types.js +7 -0
  32. package/dist/scaffold-manifest.mjs +87 -0
  33. package/dist/scaffold-plugin.mjs +468 -0
  34. package/dist/ui/ui.d.ts +84 -0
  35. package/dist/ui/ui.js +190 -0
  36. package/package.json +23 -18
  37. package/scripts/build-plugin.mjs +0 -132
  38. package/scripts/copy-mapping-table.mjs +0 -19
  39. package/scripts/figma-mapping/ai-generate.mjs +0 -102
  40. package/scripts/figma-mapping/index.mjs +0 -250
  41. package/scripts/figma-mapping/lib.mjs +0 -112
  42. package/scripts/figma-mapping/registry.mjs +0 -12
  43. package/scripts/figma-mapping/scaffold.mjs +0 -153
  44. package/scripts/generate-registry.mjs +0 -94
  45. package/scripts/install-skill.mjs +0 -79
  46. package/scripts/is-cli-entrypoint.mjs +0 -21
  47. package/scripts/scaffold-manifest.mjs +0 -84
  48. package/scripts/scaffold-plugin.mjs +0 -172
  49. package/src/dev/demo.ts +0 -207
  50. package/src/dev/export.ts +0 -28
  51. package/src/main/code.ts +0 -422
  52. package/src/main/handle.ts +0 -207
  53. package/src/main/messages.ts +0 -49
  54. package/src/main/schema.ts +0 -45
  55. package/src/main/validate.ts +0 -49
  56. package/src/runtime/define.ts +0 -6
  57. package/src/runtime/render.ts +0 -26
  58. package/src/runtime/tagged.ts +0 -46
  59. package/src/runtime/types.ts +0 -86
  60. package/src/ui/ui.ts +0 -271
  61. /package/{src → dist}/ui/ui.html +0 -0
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+
3
+ // scripts/scaffold-manifest.mjs
4
+ import fs from "node:fs";
5
+ import path2 from "node:path";
6
+
7
+ // scripts/is-cli-entrypoint.mjs
8
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
9
+ import path from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ function isCliEntrypoint(importMetaUrl, entryName) {
12
+ if (!process.argv[1]) return false;
13
+ try {
14
+ const argvPath = realpathSync(process.argv[1]);
15
+ const metaPath = realpathSync(fileURLToPath(importMetaUrl));
16
+ if (argvPath !== metaPath) return false;
17
+ if (entryName) {
18
+ return path.basename(argvPath).includes(entryName);
19
+ }
20
+ return true;
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+
26
+ // scripts/scaffold-manifest.mjs
27
+ function scaffoldManifest({ cwd, name = "Mini Code Connect", id, outDir = "dist" }) {
28
+ if (!id) throw new Error("scaffoldManifest: id \u5FC5\u586B");
29
+ const panel = {
30
+ name,
31
+ id,
32
+ api: "1.0.0",
33
+ main: `${outDir}/code.js`,
34
+ ui: `${outDir}/ui.html`,
35
+ editorType: ["figma", "dev"],
36
+ capabilities: ["inspect"],
37
+ documentAccess: "dynamic-page",
38
+ networkAccess: { allowedDomains: ["none"] }
39
+ };
40
+ const codegen = {
41
+ name: `${name} (Codegen)`,
42
+ id: `${id}-codegen`,
43
+ api: "1.0.0",
44
+ main: `${outDir}/code.js`,
45
+ ui: `${outDir}/ui.html`,
46
+ editorType: ["dev"],
47
+ capabilities: ["codegen"],
48
+ codegenLanguages: [{ label: "React", value: "react" }],
49
+ documentAccess: "dynamic-page",
50
+ networkAccess: { allowedDomains: ["none"] }
51
+ };
52
+ const results = [];
53
+ for (const [file, content] of [
54
+ ["manifest.json", panel],
55
+ ["manifest.codegen.json", codegen]
56
+ ]) {
57
+ const target = path2.join(cwd, file);
58
+ if (fs.existsSync(target)) {
59
+ results.push({ target, skipped: true });
60
+ continue;
61
+ }
62
+ fs.writeFileSync(target, JSON.stringify(content, null, 2) + "\n");
63
+ results.push({ target, skipped: false });
64
+ }
65
+ return { results };
66
+ }
67
+ if (isCliEntrypoint(import.meta.url, "scaffold-manifest")) {
68
+ const args = process.argv.slice(2);
69
+ const get = (flag) => {
70
+ const i = args.indexOf(flag);
71
+ return i === -1 ? void 0 : args[i + 1];
72
+ };
73
+ const name = get("--name") ?? "Mini Code Connect";
74
+ const id = get("--id");
75
+ const outDir = get("--out") ?? "dist";
76
+ if (!id) {
77
+ console.error('\u7528\u6CD5: node scripts/scaffold-manifest.mjs --id <manifest-id> [--name "<\u9762\u677F\u663E\u793A\u540D>"] [--out dist]');
78
+ process.exit(1);
79
+ }
80
+ const { results } = scaffoldManifest({ cwd: process.cwd(), name, id, outDir });
81
+ for (const r of results) {
82
+ console.log(r.skipped ? `[scaffold-manifest] \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\uFF1A${r.target}` : `[scaffold-manifest] \u5DF2\u5199\u5165\uFF1A${r.target}`);
83
+ }
84
+ }
85
+ export {
86
+ scaffoldManifest
87
+ };
@@ -0,0 +1,468 @@
1
+ #!/usr/bin/env node
2
+
3
+ // scripts/scaffold-plugin.mjs
4
+ import fs5 from "node:fs";
5
+ import path7 from "node:path";
6
+
7
+ // scripts/install-skill.mjs
8
+ import fs from "node:fs";
9
+ import path2 from "node:path";
10
+
11
+ // scripts/is-cli-entrypoint.mjs
12
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
13
+ import path from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ function isCliEntrypoint(importMetaUrl, entryName) {
16
+ if (!process.argv[1]) return false;
17
+ try {
18
+ const argvPath = realpathSync(process.argv[1]);
19
+ const metaPath = realpathSync(fileURLToPath(importMetaUrl));
20
+ if (argvPath !== metaPath) return false;
21
+ if (entryName) {
22
+ return path.basename(argvPath).includes(entryName);
23
+ }
24
+ return true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ function findPackageRoot(importMetaUrl) {
30
+ let dir = path.dirname(fileURLToPath(importMetaUrl));
31
+ while (true) {
32
+ const pkgPath = path.join(dir, "package.json");
33
+ if (existsSync(pkgPath)) {
34
+ try {
35
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
36
+ if (pkg.name === "mini-figma-code-connect") return dir;
37
+ } catch {
38
+ }
39
+ }
40
+ const parent = path.dirname(dir);
41
+ if (parent === dir) {
42
+ throw new Error("[mini-figma-code-connect] \u627E\u4E0D\u5230\u5305\u6839\u76EE\u5F55\uFF08package.json\uFF09");
43
+ }
44
+ dir = parent;
45
+ }
46
+ }
47
+
48
+ // scripts/install-skill.mjs
49
+ var PACKAGE_ROOT = findPackageRoot(import.meta.url);
50
+ var SKILL_SOURCE = path2.join(PACKAGE_ROOT, ".claude/skills/mini-code-connect");
51
+ var SKILL_NAME = "mini-code-connect";
52
+ function findRepoRoot(cwd) {
53
+ let dir = cwd;
54
+ while (true) {
55
+ if (fs.existsSync(path2.join(dir, ".git"))) return dir;
56
+ const parent = path2.dirname(dir);
57
+ if (parent === dir) return cwd;
58
+ dir = parent;
59
+ }
60
+ }
61
+ function installSkill({ cwd, root, force = false, claudeMirror = true }) {
62
+ const base = root ?? findRepoRoot(cwd);
63
+ const targets = [];
64
+ if (claudeMirror) targets.push(path2.join(base, ".claude/skills", SKILL_NAME));
65
+ if (fs.existsSync(path2.join(base, ".agents/skills"))) {
66
+ targets.push(path2.join(base, ".agents/skills", SKILL_NAME));
67
+ }
68
+ const results = [];
69
+ for (const target of targets) {
70
+ if (fs.existsSync(target) && !force) {
71
+ results.push({ target, skipped: true });
72
+ continue;
73
+ }
74
+ fs.rmSync(target, { recursive: true, force: true });
75
+ fs.cpSync(SKILL_SOURCE, target, { recursive: true });
76
+ results.push({ target, skipped: false });
77
+ }
78
+ return { results, root: base };
79
+ }
80
+ if (isCliEntrypoint(import.meta.url, "install-skill")) {
81
+ const args = process.argv.slice(2);
82
+ const force = args.includes("--force");
83
+ const noClaudeMirror = args.includes("--no-claude-mirror");
84
+ const rootIdx = args.indexOf("--root");
85
+ const root = rootIdx !== -1 ? path2.resolve(args[rootIdx + 1]) : void 0;
86
+ const { results, root: usedRoot } = installSkill({ cwd: process.cwd(), root, force, claudeMirror: !noClaudeMirror });
87
+ console.log(`[install-skill] \u9879\u76EE\u6839\u76EE\u5F55\uFF1A${usedRoot}`);
88
+ for (const r of results) {
89
+ console.log(r.skipped ? `[install-skill] \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\uFF08\u52A0 --force \u8986\u76D6\uFF09\uFF1A${r.target}` : `[install-skill] \u5DF2\u5199\u5165\uFF1A${r.target}`);
90
+ }
91
+ console.log('\n\u8DD1 /mini-code-connect\uFF08\u6216\u76F4\u63A5\u8BF4"\u5E2E\u6211\u6620\u5C04\u8FD9\u4E2A Figma \u7EC4\u4EF6"\uFF09\u89E6\u53D1 skill\u3002');
92
+ }
93
+
94
+ // scripts/scaffold-manifest.mjs
95
+ import fs2 from "node:fs";
96
+ import path3 from "node:path";
97
+ function scaffoldManifest({ cwd, name = "Mini Code Connect", id, outDir = "dist" }) {
98
+ if (!id) throw new Error("scaffoldManifest: id \u5FC5\u586B");
99
+ const panel = {
100
+ name,
101
+ id,
102
+ api: "1.0.0",
103
+ main: `${outDir}/code.js`,
104
+ ui: `${outDir}/ui.html`,
105
+ editorType: ["figma", "dev"],
106
+ capabilities: ["inspect"],
107
+ documentAccess: "dynamic-page",
108
+ networkAccess: { allowedDomains: ["none"] }
109
+ };
110
+ const codegen = {
111
+ name: `${name} (Codegen)`,
112
+ id: `${id}-codegen`,
113
+ api: "1.0.0",
114
+ main: `${outDir}/code.js`,
115
+ ui: `${outDir}/ui.html`,
116
+ editorType: ["dev"],
117
+ capabilities: ["codegen"],
118
+ codegenLanguages: [{ label: "React", value: "react" }],
119
+ documentAccess: "dynamic-page",
120
+ networkAccess: { allowedDomains: ["none"] }
121
+ };
122
+ const results = [];
123
+ for (const [file, content] of [
124
+ ["manifest.json", panel],
125
+ ["manifest.codegen.json", codegen]
126
+ ]) {
127
+ const target = path3.join(cwd, file);
128
+ if (fs2.existsSync(target)) {
129
+ results.push({ target, skipped: true });
130
+ continue;
131
+ }
132
+ fs2.writeFileSync(target, JSON.stringify(content, null, 2) + "\n");
133
+ results.push({ target, skipped: false });
134
+ }
135
+ return { results };
136
+ }
137
+ if (isCliEntrypoint(import.meta.url, "scaffold-manifest")) {
138
+ const args = process.argv.slice(2);
139
+ const get = (flag) => {
140
+ const i = args.indexOf(flag);
141
+ return i === -1 ? void 0 : args[i + 1];
142
+ };
143
+ const name = get("--name") ?? "Mini Code Connect";
144
+ const id = get("--id");
145
+ const outDir = get("--out") ?? "dist";
146
+ if (!id) {
147
+ console.error('\u7528\u6CD5: node scripts/scaffold-manifest.mjs --id <manifest-id> [--name "<\u9762\u677F\u663E\u793A\u540D>"] [--out dist]');
148
+ process.exit(1);
149
+ }
150
+ const { results } = scaffoldManifest({ cwd: process.cwd(), name, id, outDir });
151
+ for (const r of results) {
152
+ console.log(r.skipped ? `[scaffold-manifest] \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\uFF1A${r.target}` : `[scaffold-manifest] \u5DF2\u5199\u5165\uFF1A${r.target}`);
153
+ }
154
+ }
155
+
156
+ // scripts/build-plugin.mjs
157
+ import * as esbuild from "esbuild";
158
+ import { existsSync as existsSync2 } from "node:fs";
159
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
160
+ import path6 from "node:path";
161
+
162
+ // scripts/generate-registry.mjs
163
+ import fs3 from "node:fs";
164
+ import path4 from "node:path";
165
+ function parseGlob(glob) {
166
+ const starIdx = glob.indexOf("*");
167
+ if (starIdx === -1) throw new Error(`generate-registry: glob \u91CC\u6CA1\u6709 *\uFF1A"${glob}"`);
168
+ const dir = glob.slice(0, starIdx).replace(/\/$/, "");
169
+ const recursive = glob.includes("**");
170
+ return { dir, recursive };
171
+ }
172
+ function findFigmaFiles(dir, recursive) {
173
+ const results = [];
174
+ function walk(d) {
175
+ if (!fs3.existsSync(d)) return;
176
+ for (const entry of fs3.readdirSync(d, { withFileTypes: true })) {
177
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
178
+ const full = path4.join(d, entry.name);
179
+ if (entry.isDirectory()) {
180
+ if (recursive) walk(full);
181
+ } else if (entry.name.endsWith(".figma.ts")) {
182
+ results.push(full);
183
+ }
184
+ }
185
+ }
186
+ walk(dir);
187
+ return results.sort();
188
+ }
189
+ function identifierFor(filePath, index) {
190
+ const base = path4.basename(filePath).replace(/\.figma\.ts$/, "").replace(/[^a-zA-Z0-9]/g, "");
191
+ const safe = base && /^[a-zA-Z_]/.test(base) ? base : `M${base}`;
192
+ return `${safe || "Mapping"}_${index}`;
193
+ }
194
+ function generateRegistry({ cwd, mappingsGlob, outFile, typesImport = "./types" }) {
195
+ const { dir, recursive } = parseGlob(mappingsGlob);
196
+ const files = findFigmaFiles(path4.resolve(cwd, dir), recursive);
197
+ const outDir = path4.dirname(outFile);
198
+ const imports = files.map((f, i) => {
199
+ const id = identifierFor(f, i);
200
+ let rel = path4.relative(outDir, f).replace(/\.ts$/, "");
201
+ if (!rel.startsWith(".")) rel = `./${rel}`;
202
+ rel = rel.split(path4.sep).join("/");
203
+ return { id, importPath: rel };
204
+ });
205
+ const typeLine = typesImport == null ? "" : `import type { Template } from '${typesImport}'
206
+ `;
207
+ const templatesAnn = typesImport == null ? "" : ": Template[]";
208
+ 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
209
+ ` + 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(", ")}]
210
+ `;
211
+ fs3.mkdirSync(outDir, { recursive: true });
212
+ fs3.writeFileSync(outFile, body);
213
+ return { count: files.length, files };
214
+ }
215
+ if (isCliEntrypoint(import.meta.url, "generate-registry")) {
216
+ const [, , mappingsGlob, outFile] = process.argv;
217
+ if (!mappingsGlob || !outFile) {
218
+ console.error("\u7528\u6CD5: node scripts/generate-registry.mjs <mappingsGlob> <outFile>");
219
+ process.exit(1);
220
+ }
221
+ const result = generateRegistry({ cwd: process.cwd(), mappingsGlob, outFile: path4.resolve(outFile) });
222
+ console.log(`[generate-registry] \u5199\u5165 ${result.count} \u6761\u6620\u5C04\u5230 ${outFile}`);
223
+ }
224
+
225
+ // scripts/copy-mapping-table.mjs
226
+ import fs4 from "node:fs";
227
+ import path5 from "node:path";
228
+ function copyMappingTable({ cwd, mappingTablePath, outFile }) {
229
+ const src = path5.resolve(cwd, mappingTablePath);
230
+ const content = fs4.existsSync(src) ? fs4.readFileSync(src, "utf8") : "[]\n";
231
+ fs4.mkdirSync(path5.dirname(outFile), { recursive: true });
232
+ fs4.writeFileSync(outFile, content);
233
+ return { found: fs4.existsSync(src) };
234
+ }
235
+
236
+ // scripts/build-plugin.mjs
237
+ var PACKAGE_ROOT2 = findPackageRoot(import.meta.url);
238
+ function resolvePluginEntries(packageRoot) {
239
+ const main = path6.join(packageRoot, "dist/main/code.js");
240
+ const ui = path6.join(packageRoot, "dist/ui/ui.js");
241
+ const html = path6.join(packageRoot, "dist/ui/ui.html");
242
+ for (const file of [main, ui, html]) {
243
+ if (!existsSync2(file)) {
244
+ throw new Error(
245
+ `[build-plugin] \u7F3A\u5C11 ${file}\u3002\u8BF7\u5148\u5728\u5F15\u64CE\u5305\u76EE\u5F55\u6267\u884C npm run build\uFF08\u53EA\u4F7F\u7528 dist/\uFF0C\u4E0D\u56DE\u9000 src/\uFF09\u3002`
246
+ );
247
+ }
248
+ }
249
+ return { main, ui, html };
250
+ }
251
+ function consumerGeneratedPlugin(genDir) {
252
+ const registryFile = path6.join(genDir, "registry.generated.ts");
253
+ const mappingTableFile = path6.join(genDir, "mapping-table.generated.json");
254
+ return {
255
+ name: "consumer-generated",
256
+ setup(build2) {
257
+ build2.onResolve({ filter: /(?:^|[\\/])registry\.generated(?:\.(?:ts|js))?$/ }, () => ({
258
+ path: registryFile
259
+ }));
260
+ build2.onResolve({ filter: /(?:^|[\\/])mapping-table\.generated\.json$/ }, () => ({
261
+ path: mappingTableFile
262
+ }));
263
+ }
264
+ };
265
+ }
266
+ async function buildPlugin({
267
+ cwd,
268
+ mappingsGlob,
269
+ mappingTablePath = "figma-mapping-table.json",
270
+ outDir = "dist",
271
+ watch = false
272
+ }) {
273
+ const { main: mainEntry, ui: uiEntry, html: uiHtml } = resolvePluginEntries(PACKAGE_ROOT2);
274
+ const absOutDir = path6.resolve(cwd, outDir);
275
+ const genDir = path6.join(absOutDir, ".generated");
276
+ const registryOut = path6.join(genDir, "registry.generated.ts");
277
+ const mappingTableOut = path6.join(genDir, "mapping-table.generated.json");
278
+ const { count } = generateRegistry({
279
+ cwd,
280
+ mappingsGlob,
281
+ outFile: registryOut,
282
+ typesImport: null
283
+ });
284
+ console.log(`[build-plugin] \u626B\u5230 ${count} \u6761\u6620\u5C04\uFF08${mappingsGlob}\uFF09`);
285
+ const { found } = copyMappingTable({ cwd, mappingTablePath, outFile: mappingTableOut });
286
+ if (!found) console.log(`[build-plugin] \u6CA1\u627E\u5230 ${mappingTablePath}\uFF0C\u4E0B\u8F7D\u6309\u94AE\u4F1A\u7ED9\u7A7A\u6570\u7EC4`);
287
+ const generatedPlugin = consumerGeneratedPlugin(genDir);
288
+ const inlineUi = {
289
+ name: "inline-ui",
290
+ setup(build2) {
291
+ build2.onEnd(async (res) => {
292
+ if (res.errors.length) return;
293
+ const js = res.outputFiles ? res.outputFiles[0].text : await readFile(path6.join(absOutDir, ".ui.tmp.js"), "utf8");
294
+ const html = await readFile(uiHtml, "utf8");
295
+ await mkdir(absOutDir, { recursive: true });
296
+ await writeFile(path6.join(absOutDir, "ui.html"), html.replace("/*__UI_JS__*/", () => js));
297
+ console.log(`[inline-ui] ${path6.join(outDir, "ui.html")} \u5DF2\u66F4\u65B0`);
298
+ });
299
+ }
300
+ };
301
+ const codeOpts = {
302
+ entryPoints: [mainEntry],
303
+ bundle: true,
304
+ format: "iife",
305
+ target: "es2020",
306
+ outfile: path6.join(absOutDir, "code.js"),
307
+ logLevel: "info",
308
+ plugins: [generatedPlugin]
309
+ };
310
+ const uiOpts = {
311
+ entryPoints: [uiEntry],
312
+ bundle: true,
313
+ format: "iife",
314
+ target: "es2020",
315
+ outfile: path6.join(absOutDir, ".ui.tmp.js"),
316
+ logLevel: "warning",
317
+ plugins: [inlineUi, generatedPlugin]
318
+ };
319
+ if (watch) {
320
+ const a = await esbuild.context(codeOpts);
321
+ const b = await esbuild.context(uiOpts);
322
+ await Promise.all([a.watch(), b.watch()]);
323
+ console.log("[build-plugin] watching...");
324
+ return { watching: true };
325
+ }
326
+ await esbuild.build(codeOpts);
327
+ await esbuild.build(uiOpts);
328
+ return { watching: false };
329
+ }
330
+ if (isCliEntrypoint(import.meta.url, "build")) {
331
+ const args = process.argv.slice(2);
332
+ const watch = args.includes("--watch");
333
+ const outIdx = args.indexOf("--out");
334
+ const outDir = outIdx !== -1 ? args[outIdx + 1] : "dist";
335
+ const mappingsGlob = args.find((a, i) => !a.startsWith("--") && args[i - 1] !== "--out");
336
+ if (!mappingsGlob) {
337
+ console.error("\u7528\u6CD5: node scripts/build-plugin.mjs <mappingsGlob> [--out dist] [--watch]");
338
+ process.exit(1);
339
+ }
340
+ await buildPlugin({ cwd: process.cwd(), mappingsGlob, outDir, watch });
341
+ }
342
+
343
+ // scripts/scaffold-plugin.mjs
344
+ async function scaffoldPlugin({
345
+ cwd,
346
+ name = "Mini Code Connect",
347
+ id,
348
+ outDir = "figma-plugin-dist",
349
+ componentsDir = "components",
350
+ mappingsGlob = "figma-mappings/**/*.figma.ts",
351
+ mappingTablePath = "figma-mapping-table.json",
352
+ root,
353
+ force = false
354
+ }) {
355
+ if (!id) throw new Error("scaffoldPlugin: id \u5FC5\u586B");
356
+ const steps = [];
357
+ const skill = installSkill({ cwd, root, force });
358
+ steps.push({ step: "install-skill", ...skill });
359
+ const configPath = path7.join(cwd, "figma-mapping.config.json");
360
+ if (fs5.existsSync(configPath) && !force) {
361
+ steps.push({ step: "config", target: configPath, skipped: true });
362
+ } else {
363
+ const config = {
364
+ codeConnect: {
365
+ paths: { [path7.basename(componentsDir)]: componentsDir },
366
+ importPaths: { [`${componentsDir}/*`]: `@/${componentsDir}/*` }
367
+ }
368
+ };
369
+ fs5.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
370
+ steps.push({ step: "config", target: configPath, skipped: false });
371
+ }
372
+ const manifest = scaffoldManifest({ cwd, name, id, outDir });
373
+ steps.push({ step: "manifest", ...manifest });
374
+ const buildScriptPath = path7.join(cwd, "scripts/build-figma-plugin.mjs");
375
+ if (fs5.existsSync(buildScriptPath) && !force) {
376
+ steps.push({ step: "build-script", target: buildScriptPath, skipped: true });
377
+ } else {
378
+ const buildScript = `#!/usr/bin/env node
379
+ import { buildPlugin } from 'mini-figma-code-connect/build'
380
+ import path from 'node:path'
381
+ import { fileURLToPath } from 'node:url'
382
+
383
+ await buildPlugin({
384
+ cwd: path.resolve(fileURLToPath(import.meta.url), '../..'),
385
+ mappingsGlob: ${JSON.stringify(mappingsGlob)},
386
+ mappingTablePath: ${JSON.stringify(mappingTablePath)},
387
+ outDir: ${JSON.stringify(outDir)},
388
+ watch: process.argv.includes('--watch'),
389
+ })
390
+ `;
391
+ fs5.mkdirSync(path7.dirname(buildScriptPath), { recursive: true });
392
+ fs5.writeFileSync(buildScriptPath, buildScript);
393
+ steps.push({ step: "build-script", target: buildScriptPath, skipped: false });
394
+ }
395
+ const pkgPath = path7.join(cwd, "package.json");
396
+ if (!fs5.existsSync(pkgPath)) {
397
+ steps.push({ step: "package-json-scripts", target: pkgPath, skipped: true, reason: "package.json \u4E0D\u5B58\u5728" });
398
+ } else {
399
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf8"));
400
+ pkg.scripts ??= {};
401
+ const already = pkg.scripts["figma:build"] !== void 0 || pkg.scripts["figma:watch"] !== void 0;
402
+ if (already && !force) {
403
+ steps.push({ step: "package-json-scripts", target: pkgPath, skipped: true, reason: "figma:build/figma:watch \u5DF2\u5B58\u5728" });
404
+ } else {
405
+ pkg.scripts["figma:build"] = "node scripts/build-figma-plugin.mjs";
406
+ pkg.scripts["figma:watch"] = "node scripts/build-figma-plugin.mjs --watch";
407
+ fs5.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
408
+ steps.push({ step: "package-json-scripts", target: pkgPath, skipped: false });
409
+ }
410
+ }
411
+ const gitignorePath = path7.join(cwd, ".gitignore");
412
+ const ignoreEntry = `/${outDir}`;
413
+ const existing = fs5.existsSync(gitignorePath) ? fs5.readFileSync(gitignorePath, "utf8") : "";
414
+ const alreadyIgnored = existing.split("\n").some((l) => l.trim() === ignoreEntry || l.trim() === outDir);
415
+ if (alreadyIgnored) {
416
+ steps.push({ step: "gitignore", target: gitignorePath, skipped: true });
417
+ } else {
418
+ const sep = existing === "" || existing.endsWith("\n") ? "" : "\n";
419
+ fs5.writeFileSync(gitignorePath, `${existing}${sep}
420
+ # figma code connect \u63D2\u4EF6\u6784\u5EFA\u4EA7\u7269
421
+ ${ignoreEntry}
422
+ `);
423
+ steps.push({ step: "gitignore", target: gitignorePath, skipped: false });
424
+ }
425
+ await buildPlugin({ cwd, mappingsGlob, mappingTablePath, outDir });
426
+ steps.push({ step: "build", target: path7.join(cwd, outDir), skipped: false });
427
+ return { steps };
428
+ }
429
+ if (isCliEntrypoint(import.meta.url, "scaffold-plugin")) {
430
+ const args = process.argv.slice(2);
431
+ const get = (flag, fallback) => {
432
+ const i = args.indexOf(flag);
433
+ return i === -1 ? fallback : args[i + 1];
434
+ };
435
+ const name = get("--name") ?? "Mini Code Connect";
436
+ const id = get("--id");
437
+ if (!id) {
438
+ console.error(
439
+ '\u7528\u6CD5: node scripts/scaffold-plugin.mjs --id <manifest-id> [--name "<\u9762\u677F\u663E\u793A\u540D>"] [--out figma-plugin-dist] [--components components] [--force]'
440
+ );
441
+ process.exit(1);
442
+ }
443
+ const { steps } = await scaffoldPlugin({
444
+ cwd: process.cwd(),
445
+ name,
446
+ id,
447
+ outDir: get("--out", "figma-plugin-dist"),
448
+ componentsDir: get("--components", "components"),
449
+ force: args.includes("--force")
450
+ });
451
+ for (const s of steps) {
452
+ if (s.results) {
453
+ for (const r of s.results) {
454
+ console.log(r.skipped ? `[scaffold-plugin] [${s.step}] \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\uFF1A${r.target}` : `[scaffold-plugin] [${s.step}] \u5DF2\u5199\u5165\uFF1A${r.target}`);
455
+ }
456
+ } else {
457
+ console.log(
458
+ s.skipped ? `[scaffold-plugin] [${s.step}] \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\uFF1A${s.target}${s.reason ? `\uFF08${s.reason}\uFF09` : ""}` : `[scaffold-plugin] [${s.step}] \u5DF2\u5199\u5165\uFF1A${s.target}`
459
+ );
460
+ }
461
+ }
462
+ console.log(
463
+ "\n\u5DF2\u7ECF\u53EF\u4EE5\u5BFC\u5165 Figma \u4E86\uFF08Plugins \u2192 Development \u2192 Import plugin from manifest\u2026 \u9009 manifest.json\uFF09\u3002\n\u5019\u9009\u7EC4\u4EF6\u76EE\u5F55\u4E0D\u662F\u9ED8\u8BA4\u7684 components/ \u7684\u8BDD\uFF0C\u7F16\u8F91 figma-mapping.config.json \u518D\u6539\uFF1B\n\u5EFA\u597D\u771F\u5B9E\u6620\u5C04\u540E\u7528 `pnpm run figma:build`\uFF08\u6216 `figma:watch`\uFF09\u91CD\u65B0\u6253\u5305\u3002"
464
+ );
465
+ }
466
+ export {
467
+ scaffoldPlugin
468
+ };
@@ -0,0 +1,84 @@
1
+ type Level = 'error' | 'warn';
2
+ type Finding = {
3
+ level: Level;
4
+ text: string;
5
+ };
6
+ type PropertyDef = {
7
+ name: string;
8
+ type: string;
9
+ variantOptions?: string[];
10
+ };
11
+ type Schema = {
12
+ componentId: string;
13
+ componentKey: string;
14
+ componentName: string;
15
+ properties: PropertyDef[];
16
+ };
17
+ type AccessorCall = {
18
+ depth: number;
19
+ prop: string;
20
+ method: string;
21
+ ok: boolean;
22
+ note?: string;
23
+ };
24
+ type Candidate = {
25
+ id: string;
26
+ name: string;
27
+ mapped: boolean;
28
+ };
29
+ type Msg = {
30
+ type: 'state';
31
+ state: 'empty' | 'unmapped' | 'error';
32
+ message: string;
33
+ schema?: Schema;
34
+ figmaUrl?: string | null;
35
+ candidates?: Candidate[];
36
+ } | {
37
+ type: 'state';
38
+ state: 'candidates';
39
+ message: string;
40
+ candidates: Candidate[];
41
+ } | {
42
+ type: 'state';
43
+ state: 'scan';
44
+ message: string;
45
+ groups: {
46
+ componentKey: string;
47
+ componentName: string;
48
+ count: number;
49
+ sampleNodeId: string;
50
+ mapped: boolean;
51
+ codeComponent: string | null;
52
+ }[];
53
+ } | {
54
+ type: 'state';
55
+ state: 'ok';
56
+ schema: Schema;
57
+ template: {
58
+ id: string;
59
+ meta: {
60
+ url: string;
61
+ source: string;
62
+ component: string;
63
+ };
64
+ };
65
+ snippet: string;
66
+ imports: string[];
67
+ findings: Finding[];
68
+ calls: AccessorCall[];
69
+ candidates?: Candidate[];
70
+ } | {
71
+ type: 'download';
72
+ filename: string;
73
+ json: string;
74
+ };
75
+ declare const body: HTMLElement;
76
+ declare const send: (type: string, extra?: Record<string, unknown>) => void;
77
+ declare const esc: (s: string) => string;
78
+ declare function schemaTable(schema: Schema, calls: AccessorCall[]): string;
79
+ declare function backBar(candidates?: Candidate[]): string;
80
+ declare function wireBack(): void;
81
+ declare const exportSchemaBtn: HTMLButtonElement;
82
+ declare let inCandidatesMode: boolean;
83
+ declare function syncExportButton(state: string, count: number): void;
84
+ declare function render(msg: Msg): void;