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
package/LICENSE
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
Proprietary License
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2026 elaine.ma
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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,17 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
**Local Code Connect for Figma Professional** — 无 Org / Enterprise 也能做确定性的 `f(instance) → snippet`。
|
|
4
4
|
|
|
5
|
-
|
|
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
|
+
---
|
|
11
|
+
|
|
12
|
+
## 与官方 Code Connect 的差异
|
|
13
|
+
|
|
14
|
+
以下差异均为刻意设计,而非未实现的功能缺口:
|
|
15
|
+
|
|
16
|
+
| | 官方 Code Connect | 本实现 |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| 订阅计划要求 | Organization / Enterprise,需 Full 或 Dev 席位 | 无要求,任意计划均可运行 |
|
|
19
|
+
| 模板存储位置 | 发布至 Figma 服务端(CLI `publish` 或 MCP `add_code_connect_map`) | 构建时打包进插件 |
|
|
20
|
+
| 模板执行方 | Figma 侧沙箱,在 Dev Mode / MCP 读取时执行 | 插件主线程,选中实例时执行 |
|
|
21
|
+
| 模板入口 | 模块顶层 `const instance = figma.selectedInstance` | `render(instance)` 函数参数 |
|
|
22
|
+
| 三行绑定注释 | 构建时解析 `// url= // source= // component=` | 保留注释供人工阅读,同时在 `meta` 字段中显式声明 |
|
|
23
|
+
| 组件发布状态要求 | 必须已发布 | 不要求,未发布时退回按组件名匹配 |
|
|
24
|
+
| SLOT 属性 | 支持 `getSlot()` | 未实现,仅支持 TEXT / BOOLEAN / VARIANT / INSTANCE_SWAP |
|
|
25
|
+
| 多框架支持 | 通过 `label` 枚举,单一设计组件可挂载多条并行映射 | 仅支持 React 一条 |
|
|
26
|
+
| 穷举校验 | 不提供,缺值静默 | 提供,`validate()` 自动对账 |
|
|
27
|
+
| 绑定关系判定方 | Figma 服务端存储的映射,或人工在 Code Connect UI 中手动指定 | agent 现场读取代码判断(mini-code-connect skill),或本地正则打分兜底 |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 已知限制
|
|
32
|
+
|
|
33
|
+
- **尚未接入基于 REST API 与 Personal Access Token 的 schema 拉取能力**。该方案理论可行(通用 Figma REST API 不受 Code Connect 订阅限制),但目前唯一的数据来源仍是插件手动导出的 schema.json。
|
|
34
|
+
- **不提供跨消费方的数据新鲜度检查**。某个消费方代码库更新后,此前标记为 `guessed` / `unmatched` 的映射条目是否已可补全,需人工发起新一轮映射流程,引擎本身不做追踪。
|
|
35
|
+
|
|
36
|
+
> 构建时生成的 `registry.generated.ts` / `mapping-table.generated.json` 写在消费方 `outDir/.generated/`(默认 `figma-plugin-dist/.generated/`),经 esbuild 插件注入,不会改写 `node_modules` 内的包文件,多消费方可并行构建。
|
|
37
|
+
|
|
38
|
+
消费方项目自身映射数据的具体缺口(未映射的组件、置信度较低的属性推断),记录于消费方 `figma-mapping-table.json` 的 `unmappedNotes` / `note` 字段中,不属于本文档的追踪范围。
|
|
10
39
|
|
|
11
40
|
---
|
|
12
41
|
|
|
13
42
|
## 消费方接入
|
|
14
43
|
|
|
15
|
-
面向将 `mini-figma-code-connect` 接入自身项目、并开始建立 Figma
|
|
44
|
+
面向将 `mini-figma-code-connect` 接入自身项目、并开始建立 Figma 组件映射的使用者。所有命令默认在消费方项目目录(含 `package.json` 的目录)下执行;若为 monorepo,请进入已安装该依赖的子包目录,勿在仓库根目录执行。
|
|
16
45
|
|
|
17
46
|
### 前置条件
|
|
18
47
|
|
|
@@ -36,27 +65,15 @@ pnpm add -D mini-figma-code-connect
|
|
|
36
65
|
pnpm exec mini-code-connect-scaffold-plugin --id <manifest-id>
|
|
37
66
|
```
|
|
38
67
|
|
|
39
|
-
`--id`
|
|
40
|
-
|
|
41
|
-
该命令具备幂等性:已存在的文件不会被覆盖,重复执行不会影响后续的手动修改;如需强制重新生成,附加 `--force` 参数。执行内容如下:
|
|
68
|
+
`--id` 必填,同一 Figma 账号内不可与已导入插件重复。`--name` 可选(默认 `"Mini Code Connect"`),多插件并存时用于区分。
|
|
42
69
|
|
|
43
|
-
|
|
44
|
-
|---|---|---|
|
|
45
|
-
| 安装 skill | `<仓库根>/.claude/skills/mini-code-connect/`(若 `<仓库根>/.agents/skills/` 存在,同时写入一份) | 自动从当前目录向上查找 `.git` 所在的仓库根——pnpm monorepo 中子包目录并非仓库根,写入错误位置会导致 Claude Code 无法发现该 skill |
|
|
46
|
-
| 候选组件目录配置 | `<cwd>/figma-mapping.config.json` | 默认假设组件位于 `<cwd>/components`,可通过 `--components <dir>` 调整 |
|
|
47
|
-
| Manifest | `<cwd>/manifest.json` 与 `manifest.codegen.json` | `main`/`ui` 字段指向 `--out` 参数指定的目录(默认 `figma-plugin-dist`) |
|
|
48
|
-
| 构建脚本 | `<cwd>/scripts/build-figma-plugin.mjs` | 调用引擎包导出的 `buildPlugin()` |
|
|
49
|
-
| package.json | 新增 `scripts.figma:build` 与 `figma:watch` | 已存在的同名 script 不会被覆盖 |
|
|
50
|
-
| `.gitignore` | 新增构建产物目录的忽略规则 | 已存在同一条规则则跳过 |
|
|
51
|
-
| 首次构建 | `<cwd>/<outDir>/`(默认 `figma-plugin-dist/`) | 命令结束时自动执行一次构建,产出可直接导入 Figma 的插件——无需额外手动执行 `pnpm run figma:build` |
|
|
70
|
+
命令幂等:已有文件不覆盖;需重建时加 `--force`。会写入 skill、候选组件配置、manifest、构建脚本与 `package.json` scripts,并自动完成首次构建,之后可直接导入 Figma(步骤 4)。
|
|
52
71
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
如需单独执行其中某一步(例如仅重新生成 manifest),可使用以下更细粒度的命令——`scaffold-plugin` 内部即调用这两者:
|
|
72
|
+
仅需其中一步时:
|
|
56
73
|
|
|
57
74
|
```bash
|
|
58
|
-
pnpm exec mini-code-connect-install-skill
|
|
59
|
-
pnpm exec mini-code-connect-scaffold-manifest --id <manifest-id>
|
|
75
|
+
pnpm exec mini-code-connect-install-skill
|
|
76
|
+
pnpm exec mini-code-connect-scaffold-manifest --id <manifest-id>
|
|
60
77
|
```
|
|
61
78
|
|
|
62
79
|
#### 3. 按需调整候选组件目录
|
|
@@ -91,118 +108,23 @@ pnpm exec mini-code-connect-scaffold-manifest --id <manifest-id> # 仅执行 m
|
|
|
91
108
|
|
|
92
109
|
无 agent 可用时,`pnpm exec figma-mapping <schema.json>` 提供本地正则打分作为兜底方案(同样支持批量 schema 数组与 `--ai` 模式),判断准确度低于 agent 现场读取代码,但落盘规则保持一致。
|
|
93
110
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
| 命令 | 必填参数 | 作用 |
|
|
97
|
-
|---|---|---|
|
|
98
|
-
| `mini-code-connect-scaffold-plugin` | `--id` | 执行完整一次性脚手架(详见步骤 2) |
|
|
99
|
-
| `mini-code-connect-install-skill` | 无 | 仅将 skill 复制至项目。`--root <path>` 显式指定仓库根,`--no-claude-mirror` 仅写入 `.agents/skills/`,`--force` 覆盖已存在内容 |
|
|
100
|
-
| `mini-code-connect-scaffold-manifest` | `--id` | 仅生成 manifest.json 与 manifest.codegen.json。`--out <dir>` 指定产物目录 |
|
|
101
|
-
| `figma-mapping` | `<schema.json>` | 本地正则打分 CLI,agent 缺席时的兜底方案。`--ai` 调用本机 `claude` CLI 以无头模式执行判断 |
|
|
102
|
-
|
|
103
|
-
### 常见问题
|
|
104
|
-
|
|
105
|
-
#### `EACCES: spawn <command>`
|
|
106
|
-
|
|
107
|
-
通常由以下两种原因之一导致:
|
|
111
|
+
## 常见问题
|
|
108
112
|
|
|
109
|
-
1.
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
pnpm install
|
|
113
|
-
```
|
|
113
|
+
1. **Q:** 命令找不到 / `EACCES: spawn …`
|
|
114
|
+
**A:** 先在已安装依赖的目录执行 `pnpm install`,刷新 `node_modules/.bin`。若仍报错,多半是引擎包脚本权限问题,反馈给引擎仓库。
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
2. **Q:** `ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL`
|
|
117
|
+
**A:** 不要在 monorepo 根目录跑 `pnpm exec`;进入已安装 `mini-figma-code-connect` 的子包再执行。
|
|
116
118
|
|
|
117
|
-
|
|
119
|
+
3. **Q:** 和官方 Code Connect 会冲突吗?
|
|
120
|
+
**A:** 不会覆盖。本方案用 `mini-code-connect` skill 与 `figma-mapping.config.json`,官方用 `figma-code-connect` / `figma.config.json`;格式不兼容,勿混用。
|
|
118
121
|
|
|
119
|
-
|
|
122
|
+
4. **Q:** 改了 manifest,Figma 没变化?
|
|
123
|
+
**A:** `editorType` / `capabilities` 在导入时缓存。需在 Figma 里删掉该开发插件后重新导入。`figma-plugin-dist/` 下的 `code.js` / `ui.html` 每次运行都会重读,改构建产物不必重导。
|
|
120
124
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
## 项目结构
|
|
130
|
-
|
|
131
|
-
```
|
|
132
|
-
manifest.json Figma 插件清单(面板形态)——本仓库自身 demo 用,产物零映射
|
|
133
|
-
manifest.codegen.json Figma 插件清单(Dev Mode Code 区形态)——同上
|
|
134
|
-
figma-mapping.config.example.json 消费方接入时复制到自身项目根目录的配置模板
|
|
135
|
-
esbuild.config.mjs 本仓库以 buildPlugin() 自举打包自身(零映射,用于验证引擎可用性)
|
|
136
|
-
|
|
137
|
-
.claude/skills/mini-code-connect/
|
|
138
|
-
└── SKILL.md 工作流的完整步骤定义,agent 依此执行——写入目标是消费方项目,而非本仓库
|
|
139
|
-
|
|
140
|
-
scripts/figma-mapping/ 插件沙箱之外的工作流实现(CLI 以调用时的 cwd 作为 ROOT,即消费方项目根目录)
|
|
141
|
-
├── index.mjs CLI 入口:本地正则打分选取候选 + 交互确认,agent 缺席场景下的兜底方案
|
|
142
|
-
├── lib.mjs 读取消费方项目的 figma-mapping.config.json,扫描候选目录,提取组件信息并打分
|
|
143
|
-
├── scaffold.mjs 根据打分选中的候选生成 .figma.ts 内容
|
|
144
|
-
├── registry.mjs 重新生成 registry.generated.ts(薄封装,核心逻辑位于 scripts/generate-registry.mjs)
|
|
145
|
-
└── ai-generate.mjs --ai 模式:调用本机 claude CLI 进行判断(存在 OAuth 阻塞风险,不推荐;agent 在场时应直接使用 skill)
|
|
146
|
-
|
|
147
|
-
scripts/
|
|
148
|
-
├── build-plugin.mjs 导出 buildPlugin({cwd, mappingsGlob, mappingTablePath, outDir, watch})
|
|
149
|
-
├── generate-registry.mjs 导出 generateRegistry({cwd, mappingsGlob, outFile}),扫描 .figma.ts 生成 registry.generated.ts
|
|
150
|
-
├── copy-mapping-table.mjs 将消费方的 figma-mapping-table.json 复制至包自身 src/main/ 下,供 code.ts 静态引入
|
|
151
|
-
├── install-skill.mjs bin: mini-code-connect-install-skill
|
|
152
|
-
├── scaffold-manifest.mjs bin: mini-code-connect-scaffold-manifest
|
|
153
|
-
├── scaffold-plugin.mjs bin: mini-code-connect-scaffold-plugin —— 单条命令完成上述两项 + 候选目录配置 + 构建脚本 + package.json scripts
|
|
154
|
-
└── is-cli-entrypoint.mjs 内部工具:判定"是否被直接作为 CLI 执行"(process.argv[1] 与 import.meta.url 的朴素比较通常不成立,详见文件内注释)
|
|
155
|
-
|
|
156
|
-
src/
|
|
157
|
-
├── runtime/ 轻量级 Code Connect 运行时(与 Figma API 无关,可独立测试)——包的公开入口
|
|
158
|
-
│ ├── index.ts 导出 defineTemplate/code/类型定义,消费方通过 `import { defineTemplate, code } from 'mini-figma-code-connect'` 引入
|
|
159
|
-
│ ├── types.ts ResultSection / ComponentSchema / Template / InstanceLike
|
|
160
|
-
│ ├── tagged.ts figma.code`...` 的简化实现:切分 section,展平嵌套结构
|
|
161
|
-
│ ├── render.ts ResultSection[] 转字符串
|
|
162
|
-
│ ├── define.ts defineTemplate(),用于类型收窄
|
|
163
|
-
│ ├── registry.generated.ts 自动生成(已加入 .gitignore),由 scripts/generate-registry.mjs 扫描消费方 .figma.ts 产出;本地 build/typecheck 前生成
|
|
164
|
-
│ └── registry.ts componentKey / componentName 到模板的查找;templates 数组从 registry.generated 转出
|
|
165
|
-
├── main/ 插件主线程(可访问 figma.* API)
|
|
166
|
-
│ ├── code.ts 六步流水线与面板消息处理,详见核心实现.md
|
|
167
|
-
│ ├── schema.ts 组件到属性 schema 的提取
|
|
168
|
-
│ ├── handle.ts InstanceHandle:访问器层与递归执行
|
|
169
|
-
│ ├── validate.ts Step 6 自检的自动化实现
|
|
170
|
-
│ ├── mapping-table.generated.json 自动生成(已加入 .gitignore),构建时从消费方的 figma-mapping-table.json 复制
|
|
171
|
-
│ └── messages.ts 主线程与 UI 间的消息类型定义
|
|
172
|
-
├── ui/ 插件面板
|
|
173
|
-
│ ├── ui.html
|
|
174
|
-
│ └── ui.ts
|
|
175
|
-
└── dev/
|
|
176
|
-
├── demo.ts 独立示例模板,使用模拟实例渲染,不依赖 Figma 或任何消费方数据
|
|
177
|
-
└── export.ts 生成 codeconnect.json
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
---
|
|
181
|
-
|
|
182
|
-
## 与官方 Code Connect 的差异
|
|
183
|
-
|
|
184
|
-
以下差异均为刻意设计,而非未实现的功能缺口:
|
|
185
|
-
|
|
186
|
-
| | 官方 Code Connect | 本实现 |
|
|
187
|
-
|---|---|---|
|
|
188
|
-
| 订阅计划要求 | Organization / Enterprise,需 Full 或 Dev 席位 | 无要求,任意计划均可运行 |
|
|
189
|
-
| 模板存储位置 | 发布至 Figma 服务端(CLI `publish` 或 MCP `add_code_connect_map`) | 构建时打包进插件 |
|
|
190
|
-
| 模板执行方 | Figma 侧沙箱,在 Dev Mode / MCP 读取时执行 | 插件主线程,选中实例时执行 |
|
|
191
|
-
| 模板入口 | 模块顶层 `const instance = figma.selectedInstance` | `render(instance)` 函数参数 |
|
|
192
|
-
| 三行绑定注释 | 构建时解析 `// url= // source= // component=` | 保留注释供人工阅读,同时在 `meta` 字段中显式声明 |
|
|
193
|
-
| 组件发布状态要求 | 必须已发布 | 不要求,未发布时退回按组件名匹配 |
|
|
194
|
-
| SLOT 属性 | 支持 `getSlot()` | 未实现,仅支持 TEXT / BOOLEAN / VARIANT / INSTANCE_SWAP |
|
|
195
|
-
| 多框架支持 | 通过 `label` 枚举,单一设计组件可挂载多条并行映射 | 仅支持 React 一条 |
|
|
196
|
-
| 穷举校验 | 不提供,缺值静默 | 提供,`validate()` 自动对账 |
|
|
197
|
-
| 绑定关系判定方 | Figma 服务端存储的映射,或人工在 Code Connect UI 中手动指定 | agent 现场读取代码判断(mini-code-connect skill),或本地正则打分兜底 |
|
|
198
|
-
|
|
199
|
-
---
|
|
200
|
-
|
|
201
|
-
## 已知限制
|
|
202
|
-
|
|
203
|
-
- **尚未接入基于 REST API 与 Personal Access Token 的 schema 拉取能力**。该方案理论可行(通用 Figma REST API 不受 Code Connect 订阅限制),但目前唯一的数据来源仍是插件手动导出的 schema.json。
|
|
204
|
-
- **不提供跨消费方的数据新鲜度检查**。某个消费方代码库更新后,此前标记为 `guessed` / `unmatched` 的映射条目是否已可补全,需人工发起新一轮映射流程,引擎本身不做追踪。
|
|
205
|
-
|
|
206
|
-
> 构建时生成的 `registry.generated.ts` / `mapping-table.generated.json` 写在消费方 `outDir/.generated/`(默认 `figma-plugin-dist/.generated/`),经 esbuild 插件注入,不会改写 `node_modules` 内的包文件,多消费方可并行构建。
|
|
207
|
-
|
|
208
|
-
消费方项目自身映射数据的具体缺口(未映射的组件、置信度较低的属性推断),记录于消费方 `figma-mapping-table.json` 的 `unmappedNotes` / `note` 字段中,不属于本文档的追踪范围。
|
|
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
|
+
};
|