Files
superwork/vscode/src/figma/localExtensions.ts
T
cruldra 142af57d9d ✨ feat(vscode): 工单挂原型插件,详情面板一键导入 Figma
- prototypeFile 走通 state → Issue → webview 全链路(gitea/youtrack 两个
  loader、两份 KNOWN_STATE_FIELDS、issue/patch、webview types),按 planFile
  同款当共享字段;路径校验换成 .json 版,允许绝对路径
- 详情面板「计划文件」下加「原型插件路径」行:双击开 manifest.json,
  行尾按钮「在 Figma 中导入并打开」;没挂插件时只显示空态
- src/figma/:把 manifest 写进 Figma 桌面版的 localFileExtensions 再拉起
  Figma。坑是 Figma 退出会用内存状态覆盖 settings.json,所以写之前先
  osascript 优雅退出并轮询等它真没了(10s 等不到就让用户手动关,不强杀);
  写前备份 settings.json.bak-<时间戳>;已导入过就跳过写入直接打开;
  非 macOS 只把路径复制到剪贴板
- webhook 里 spec/plan 的 marker 同步改成表驱动,顺带认第三种 prototype
- 提示词:prototype.md 要求画完挂 marker;brainstorm-continue.md 新增
  {prototypeRef} 占位符,有原型插件时让续聊先读它别从零重画,没有时
  连同整段抹掉

Claude-Session: https://claude.ai/code/session_01Kgmb1LvrPmFX3NoUDB4zZC
2026-09-15 00:11:49 +08:00

117 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Figma 桌面版「已导入的开发插件」清单的纯逻辑:算新 id、查重、造条目。
*
* 清单落在 `~/Library/Application Support/Figma/settings.json` 的
* `localFileExtensions` 数组里,一条 manifest 条目长这样:
*
* ```json
* { "id": 9, "manifestPath": "/abs/.../manifest.json",
* "lastKnownName": "Xxx Design Lab", "lastKnownPluginId": "",
* "fileMetadata": { "type": "manifest", "codeFileId": 10, "uiFileIds": [] } }
* ```
*
* 坑:id 是**整个数组共享**的递增计数器,manifest 条目占一个 id,Figma 之后
* 给它的 code.js 建条目时占 `fileMetadata.codeFileId` 那个 id。所以算下一个
* id 时不能只看已有条目的 id,还得把被 codeFileId / manifestFileId 预定掉的
* 号算进去,否则新插件会和 Figma 迟早要建的 code 条目撞号。
*
* 这里不碰文件系统,好单测;读写与关 Figma 在 importPlugin.ts。
*/
export interface FigmaFileMetadata {
type: 'manifest' | 'code' | string
/** manifest 条目专有:预留给同插件 code.js 条目的 id。 */
codeFileId?: number
/** code 条目专有:反指所属 manifest 条目的 id。 */
manifestFileId?: number
uiFileIds?: number[]
}
export interface FigmaLocalFileExtension {
id: number
manifestPath: string
lastKnownName?: string
lastKnownPluginId?: string
fileMetadata?: FigmaFileMetadata
[key: string]: unknown
}
/** 数组里混着 Figma 写的杂项,只把「有数字 id + 字符串 manifestPath」的当条目。 */
export function asExtensionEntries(value: unknown): FigmaLocalFileExtension[] {
if (!Array.isArray(value))
return []
return value.filter((e): e is FigmaLocalFileExtension =>
!!e
&& typeof e === 'object'
&& typeof (e as FigmaLocalFileExtension).id === 'number'
&& typeof (e as FigmaLocalFileExtension).manifestPath === 'string',
)
}
/**
* 下一个可用 id:已有条目 id 与被 codeFileId / manifestFileId 预定的 id 取最大再 +1。
* 空清单从 1 开始(Figma 自己也是从 1 起)。
*/
export function nextExtensionId(entries: FigmaLocalFileExtension[]): number {
let max = 0
for (const e of entries) {
for (const n of [e.id, e.fileMetadata?.codeFileId, e.fileMetadata?.manifestFileId]) {
if (typeof n === 'number' && Number.isFinite(n) && n > max)
max = n
}
}
return max + 1
}
/** 按 manifestPath 精确查重;已导入过就返回那条,用来跳过写入。 */
export function findManifestEntry(
entries: FigmaLocalFileExtension[],
manifestPath: string,
): FigmaLocalFileExtension | undefined {
return entries.find(e => e.manifestPath === manifestPath)
}
/** 造一条 manifest 条目;code 条目留给 Figma 自己建,这里只把 id 号占住。 */
export function buildManifestEntry(opts: {
id: number
manifestPath: string
name: string
}): FigmaLocalFileExtension {
return {
id: opts.id,
manifestPath: opts.manifestPath,
lastKnownName: opts.name,
lastKnownPluginId: '',
fileMetadata: {
type: 'manifest',
codeFileId: opts.id + 1,
uiFileIds: [],
},
}
}
export interface AppendResult {
/** 追加后的完整条目数组;已存在时原样返回。 */
entries: FigmaLocalFileExtension[]
/** false = 已导入过,调用方跳过写文件直接拉起 Figma。 */
added: boolean
/** 这条插件在 Figma 里的显示名(新建用传入名,已存在用清单里记的名)。 */
name: string
}
/** 查重 → 算 id → 追加,一次算完,写文件的那端只管落盘。 */
export function appendManifestEntry(
entries: FigmaLocalFileExtension[],
opts: { manifestPath: string, name: string },
): AppendResult {
const existing = findManifestEntry(entries, opts.manifestPath)
if (existing)
return { entries, added: false, name: existing.lastKnownName || opts.name }
const entry = buildManifestEntry({
id: nextExtensionId(entries),
manifestPath: opts.manifestPath,
name: opts.name,
})
return { entries: [...entries, entry], added: true, name: opts.name }
}