mini-figma-code-connect 0.1.1 → 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.
Files changed (61) hide show
  1. package/LICENSE +13 -9
  2. package/README.md +10 -2
  3. package/dist/build.mjs +222 -0
  4. package/dist/figma-mapping.mjs +611 -0
  5. package/dist/generate-registry.mjs +79 -0
  6. package/dist/install-skill.mjs +85 -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 +80 -0
  33. package/dist/scaffold-plugin.mjs +462 -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
package/LICENSE CHANGED
@@ -1,16 +1,18 @@
1
- MIT License
1
+ Proprietary License
2
2
 
3
3
  Copyright (c) 2026 elaine.ma
4
4
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
5
+ All rights reserved.
11
6
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
7
+ This software and associated documentation files (the "Software") are the
8
+ proprietary property of the copyright holder.
9
+
10
+ Permission is granted to install and use the Software via the published npm
11
+ package for your own projects, subject to the npm package terms.
12
+
13
+ You may not copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software source code, except as allowed by applicable law or
15
+ with prior written permission from the copyright holder.
14
16
 
15
17
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
18
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -19,3 +21,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
21
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
22
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
23
  SOFTWARE.
24
+
25
+ Issues and feedback: https://github.com/elainema0215/mini-figma-code-connect/issues
package/README.md CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  **Local Code Connect for Figma Professional** — 无 Org / Enterprise 也能做确定性的 `f(instance) → snippet`。
4
4
 
5
- 面向 Pro 档团队的轻量替代引擎:Figma 插件 + 将设计组件映射到消费方真实代码库的工作流。
5
+ > 源码未开源。npm 仅分发编译产物(`dist/`)。问题与反馈请到 [GitHub Issues](https://github.com/elainema0215/mini-figma-code-connect/issues)。
6
6
 
7
+ 面向 Pro 档团队的轻量替代引擎:Figma 插件 + 将设计组件映射到消费方真实代码库的工作流。
7
8
  官方 Code Connect 与 Builder.io Design System Intelligence 均要求 Organization / Enterprise 订阅;团队当前为 Professional,相关 MCP 绑定工具不可用。本引擎在本地实现等价的「组件绑定 + 属性映射」:映射进消费方仓库、执行在 Figma 插件,不依赖云端 publish。插件沙箱无法读写本地代码,因此映射生成在沙箱外(agent / CLI)完成,再经构建打包进插件。
8
9
 
9
10
  ---
@@ -107,7 +108,7 @@ pnpm exec mini-code-connect-scaffold-manifest --id <manifest-id>
107
108
 
108
109
  无 agent 可用时,`pnpm exec figma-mapping <schema.json>` 提供本地正则打分作为兜底方案(同样支持批量 schema 数组与 `--ai` 模式),判断准确度低于 agent 现场读取代码,但落盘规则保持一致。
109
110
 
110
- ### 常见问题
111
+ ## 常见问题
111
112
 
112
113
  1. **Q:** 命令找不到 / `EACCES: spawn …`
113
114
  **A:** 先在已安装依赖的目录执行 `pnpm install`,刷新 `node_modules/.bin`。若仍报错,多半是引擎包脚本权限问题,反馈给引擎仓库。
@@ -120,3 +121,10 @@ pnpm exec mini-code-connect-scaffold-manifest --id <manifest-id>
120
121
 
121
122
  4. **Q:** 改了 manifest,Figma 没变化?
122
123
  **A:** `editorType` / `capabilities` 在导入时缓存。需在 Figma 里删掉该开发插件后重新导入。`figma-plugin-dist/` 下的 `code.js` / `ui.html` 每次运行都会重读,改构建产物不必重导。
124
+
125
+ 5. **Q:** 导入/运行插件时报 `EPERM: operation not permitted, open '…/figma-plugin-dist/code.js'`,或提示 *Unable to load code* / *loading the plugin environment*?
126
+ **A:** 这通常不是包本身坏了,而是 **macOS 隐私权限拦住了 Figma 读取本地文件**(项目在 `Desktop` / `Documents` 下时很常见)。处理:
127
+ 1. **系统设置 → 隐私与安全性 → 文件与文件夹**(必要时再开「完全磁盘访问权限」),给 **Figma** 勾选桌面 / 文稿等对应目录。
128
+ 2. 完全退出并重启 Figma。
129
+ 3. 在 Development 里删掉旧插件后,重新 **Import plugin from manifest…**。
130
+ 也可把消费方仓库挪出受保护目录(例如 `~/Projects/`)再导入。
package/dist/build.mjs ADDED
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env node
2
+
3
+ // scripts/build-plugin.mjs
4
+ import * as esbuild from "esbuild";
5
+ import { existsSync as existsSync2 } from "node:fs";
6
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
7
+ import path4 from "node:path";
8
+
9
+ // scripts/is-cli-entrypoint.mjs
10
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
11
+ import path from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ function isCliEntrypoint(importMetaUrl) {
14
+ if (!process.argv[1]) return false;
15
+ try {
16
+ return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(importMetaUrl));
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+ function findPackageRoot(importMetaUrl) {
22
+ let dir = path.dirname(fileURLToPath(importMetaUrl));
23
+ while (true) {
24
+ const pkgPath = path.join(dir, "package.json");
25
+ if (existsSync(pkgPath)) {
26
+ try {
27
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
28
+ if (pkg.name === "mini-figma-code-connect") return dir;
29
+ } catch {
30
+ }
31
+ }
32
+ const parent = path.dirname(dir);
33
+ if (parent === dir) {
34
+ throw new Error("[mini-figma-code-connect] \u627E\u4E0D\u5230\u5305\u6839\u76EE\u5F55\uFF08package.json\uFF09");
35
+ }
36
+ dir = parent;
37
+ }
38
+ }
39
+
40
+ // scripts/generate-registry.mjs
41
+ import fs from "node:fs";
42
+ import path2 from "node:path";
43
+ function parseGlob(glob) {
44
+ const starIdx = glob.indexOf("*");
45
+ if (starIdx === -1) throw new Error(`generate-registry: glob \u91CC\u6CA1\u6709 *\uFF1A"${glob}"`);
46
+ const dir = glob.slice(0, starIdx).replace(/\/$/, "");
47
+ const recursive = glob.includes("**");
48
+ return { dir, recursive };
49
+ }
50
+ function findFigmaFiles(dir, recursive) {
51
+ const results = [];
52
+ function walk(d) {
53
+ if (!fs.existsSync(d)) return;
54
+ for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
55
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
56
+ const full = path2.join(d, entry.name);
57
+ if (entry.isDirectory()) {
58
+ if (recursive) walk(full);
59
+ } else if (entry.name.endsWith(".figma.ts")) {
60
+ results.push(full);
61
+ }
62
+ }
63
+ }
64
+ walk(dir);
65
+ return results.sort();
66
+ }
67
+ function identifierFor(filePath, index) {
68
+ const base = path2.basename(filePath).replace(/\.figma\.ts$/, "").replace(/[^a-zA-Z0-9]/g, "");
69
+ const safe = base && /^[a-zA-Z_]/.test(base) ? base : `M${base}`;
70
+ return `${safe || "Mapping"}_${index}`;
71
+ }
72
+ function generateRegistry({ cwd, mappingsGlob, outFile, typesImport = "./types" }) {
73
+ const { dir, recursive } = parseGlob(mappingsGlob);
74
+ const files = findFigmaFiles(path2.resolve(cwd, dir), recursive);
75
+ const outDir = path2.dirname(outFile);
76
+ const imports = files.map((f, i) => {
77
+ const id = identifierFor(f, i);
78
+ let rel = path2.relative(outDir, f).replace(/\.ts$/, "");
79
+ if (!rel.startsWith(".")) rel = `./${rel}`;
80
+ rel = rel.split(path2.sep).join("/");
81
+ return { id, importPath: rel };
82
+ });
83
+ const typeLine = typesImport == null ? "" : `import type { Template } from '${typesImport}'
84
+ `;
85
+ const templatesAnn = typesImport == null ? "" : ": Template[]";
86
+ 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
87
+ ` + 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(", ")}]
88
+ `;
89
+ fs.mkdirSync(outDir, { recursive: true });
90
+ fs.writeFileSync(outFile, body);
91
+ return { count: files.length, files };
92
+ }
93
+ if (isCliEntrypoint(import.meta.url)) {
94
+ const [, , mappingsGlob, outFile] = process.argv;
95
+ if (!mappingsGlob || !outFile) {
96
+ console.error("\u7528\u6CD5: node scripts/generate-registry.mjs <mappingsGlob> <outFile>");
97
+ process.exit(1);
98
+ }
99
+ const result = generateRegistry({ cwd: process.cwd(), mappingsGlob, outFile: path2.resolve(outFile) });
100
+ console.log(`[generate-registry] \u5199\u5165 ${result.count} \u6761\u6620\u5C04\u5230 ${outFile}`);
101
+ }
102
+
103
+ // scripts/copy-mapping-table.mjs
104
+ import fs2 from "node:fs";
105
+ import path3 from "node:path";
106
+ function copyMappingTable({ cwd, mappingTablePath, outFile }) {
107
+ const src = path3.resolve(cwd, mappingTablePath);
108
+ const content = fs2.existsSync(src) ? fs2.readFileSync(src, "utf8") : "[]\n";
109
+ fs2.mkdirSync(path3.dirname(outFile), { recursive: true });
110
+ fs2.writeFileSync(outFile, content);
111
+ return { found: fs2.existsSync(src) };
112
+ }
113
+
114
+ // scripts/build-plugin.mjs
115
+ var PACKAGE_ROOT = findPackageRoot(import.meta.url);
116
+ function resolvePluginEntries(packageRoot) {
117
+ const main = path4.join(packageRoot, "dist/main/code.js");
118
+ const ui = path4.join(packageRoot, "dist/ui/ui.js");
119
+ const html = path4.join(packageRoot, "dist/ui/ui.html");
120
+ for (const file of [main, ui, html]) {
121
+ if (!existsSync2(file)) {
122
+ throw new Error(
123
+ `[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`
124
+ );
125
+ }
126
+ }
127
+ return { main, ui, html };
128
+ }
129
+ function consumerGeneratedPlugin(genDir) {
130
+ const registryFile = path4.join(genDir, "registry.generated.ts");
131
+ const mappingTableFile = path4.join(genDir, "mapping-table.generated.json");
132
+ return {
133
+ name: "consumer-generated",
134
+ setup(build2) {
135
+ build2.onResolve({ filter: /(?:^|[\\/])registry\.generated(?:\.(?:ts|js))?$/ }, () => ({
136
+ path: registryFile
137
+ }));
138
+ build2.onResolve({ filter: /(?:^|[\\/])mapping-table\.generated\.json$/ }, () => ({
139
+ path: mappingTableFile
140
+ }));
141
+ }
142
+ };
143
+ }
144
+ async function buildPlugin({
145
+ cwd,
146
+ mappingsGlob,
147
+ mappingTablePath = "figma-mapping-table.json",
148
+ outDir = "dist",
149
+ watch = false
150
+ }) {
151
+ const { main: mainEntry, ui: uiEntry, html: uiHtml } = resolvePluginEntries(PACKAGE_ROOT);
152
+ const absOutDir = path4.resolve(cwd, outDir);
153
+ const genDir = path4.join(absOutDir, ".generated");
154
+ const registryOut = path4.join(genDir, "registry.generated.ts");
155
+ const mappingTableOut = path4.join(genDir, "mapping-table.generated.json");
156
+ const { count } = generateRegistry({
157
+ cwd,
158
+ mappingsGlob,
159
+ outFile: registryOut,
160
+ typesImport: null
161
+ });
162
+ console.log(`[build-plugin] \u626B\u5230 ${count} \u6761\u6620\u5C04\uFF08${mappingsGlob}\uFF09`);
163
+ const { found } = copyMappingTable({ cwd, mappingTablePath, outFile: mappingTableOut });
164
+ if (!found) console.log(`[build-plugin] \u6CA1\u627E\u5230 ${mappingTablePath}\uFF0C\u4E0B\u8F7D\u6309\u94AE\u4F1A\u7ED9\u7A7A\u6570\u7EC4`);
165
+ const generatedPlugin = consumerGeneratedPlugin(genDir);
166
+ const inlineUi = {
167
+ name: "inline-ui",
168
+ setup(build2) {
169
+ build2.onEnd(async (res) => {
170
+ if (res.errors.length) return;
171
+ const js = res.outputFiles ? res.outputFiles[0].text : await readFile(path4.join(absOutDir, ".ui.tmp.js"), "utf8");
172
+ const html = await readFile(uiHtml, "utf8");
173
+ await mkdir(absOutDir, { recursive: true });
174
+ await writeFile(path4.join(absOutDir, "ui.html"), html.replace("/*__UI_JS__*/", () => js));
175
+ console.log(`[inline-ui] ${path4.join(outDir, "ui.html")} \u5DF2\u66F4\u65B0`);
176
+ });
177
+ }
178
+ };
179
+ const codeOpts = {
180
+ entryPoints: [mainEntry],
181
+ bundle: true,
182
+ format: "iife",
183
+ target: "es2020",
184
+ outfile: path4.join(absOutDir, "code.js"),
185
+ logLevel: "info",
186
+ plugins: [generatedPlugin]
187
+ };
188
+ const uiOpts = {
189
+ entryPoints: [uiEntry],
190
+ bundle: true,
191
+ format: "iife",
192
+ target: "es2020",
193
+ outfile: path4.join(absOutDir, ".ui.tmp.js"),
194
+ logLevel: "warning",
195
+ plugins: [inlineUi, generatedPlugin]
196
+ };
197
+ if (watch) {
198
+ const a = await esbuild.context(codeOpts);
199
+ const b = await esbuild.context(uiOpts);
200
+ await Promise.all([a.watch(), b.watch()]);
201
+ console.log("[build-plugin] watching...");
202
+ return { watching: true };
203
+ }
204
+ await esbuild.build(codeOpts);
205
+ await esbuild.build(uiOpts);
206
+ return { watching: false };
207
+ }
208
+ if (isCliEntrypoint(import.meta.url)) {
209
+ const args = process.argv.slice(2);
210
+ const watch = args.includes("--watch");
211
+ const outIdx = args.indexOf("--out");
212
+ const outDir = outIdx !== -1 ? args[outIdx + 1] : "dist";
213
+ const mappingsGlob = args.find((a, i) => !a.startsWith("--") && args[i - 1] !== "--out");
214
+ if (!mappingsGlob) {
215
+ console.error("\u7528\u6CD5: node scripts/build-plugin.mjs <mappingsGlob> [--out dist] [--watch]");
216
+ process.exit(1);
217
+ }
218
+ await buildPlugin({ cwd: process.cwd(), mappingsGlob, outDir, watch });
219
+ }
220
+ export {
221
+ buildPlugin
222
+ };