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.
- package/LICENSE +13 -9
- package/README.md +14 -3
- package/dist/build.mjs +228 -0
- package/dist/figma-mapping.mjs +618 -0
- package/dist/generate-registry.mjs +86 -0
- package/dist/install-skill.mjs +91 -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 +87 -0
- package/dist/scaffold-plugin.mjs +468 -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,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
|
+
};
|
package/dist/ui/ui.d.ts
ADDED
|
@@ -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;
|