Skip to content

Figma-plugin-refactor#269

Merged
whdgur5717 merged 9 commits into
devfrom
figma-plugin-refactor
Jan 17, 2026
Merged

Figma-plugin-refactor#269
whdgur5717 merged 9 commits into
devfrom
figma-plugin-refactor

Conversation

@whdgur5717

@whdgur5717 whdgur5717 commented Jan 17, 2026

Copy link
Copy Markdown
Contributor

📝 설명

figma plugin 일부 기능 변경

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 컴포넌트 디자인 문서 추가: 버튼, 입력필드, 다이얼로그, 메뉴, 진행률, 아이콘 등 주요 UI 컴포넌트에 대한 상세한 Storybook 문서 및 사용 예제 제공
    • 향상된 스타일 시스템: 글꼴, 테마, 레이아웃 유틸리티를 포함한 새로운 기본 CSS 정의
  • 스타일

    • 글로벌 스타일 재구성으로 더 일관된 디자인 시스템 구현
  • Chores

    • 의존성 업데이트: @modelcontextprotocol/sdk 및 es-toolkit 추가

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 17, 2026

Copy link
Copy Markdown

Walkthrough

이 PR은 Figma 플러그인의 노드 변환 파이프라인을 재구성하며, 타입 정의, 추출 유틸리티, 변수 바인딩 시스템을 추가합니다. 또한 여러 컴포넌트 Storybook 문서와 디자인 규칙을 신규 생성합니다.

Changes

컴포넌트 / 파일 변경 사항
패키지 및 설정 변경
package.json, shared/figma-plugin/package.json @modelcontextprotocol/sdk 버전 업데이트 (^1.24), es-toolkit 의존성 추가 (^1.39.10), unplugged flag 추가
shared/figma-plugin/tsconfig.json TypeScript lib 설정 ES2020 → ES2022로 변경
shared/figma-plugin/tsup.config.js 빌드 타임에 규칙 로드 기능 추가, __RULES__ 전역 변수 주입
타입 정의
shared/figma-plugin/src/types/figmaNode.ts 노드 스타일, 텍스트 타이포그래피, 컴포넌트 속성, 변수 바인딩, React 호환 노드 타입 정의 (+224줄)
Figma 노드 변환 파이프라인
shared/figma-plugin/src/convert/index.ts 비동기 노드 변환 라우터: 노드 타입별 빌더 선택, 재귀적 자식 처리
shared/figma-plugin/src/convert/boundVariables.ts 바인딩된 변수 참조 수집 유틸리티
shared/figma-plugin/src/convert/children.ts 가시 상태의 자식 노드 필터링 유틸리티
shared/figma-plugin/src/convert/node/*.ts (text, rectangle, frame, group, instance, generic) 노드 타입별 React 노드 빌더 함수 6개 (+155줄)
스타일 및 텍스트 추출
shared/figma-plugin/src/extract/style.ts 차원, 레이아웃, 자동 레이아웃, 채우기, 스트로크, 모서리 반지름 추출 (+368줄)
shared/figma-plugin/src/extract/text.ts 텍스트 세그먼트, 타이포그래피 기본값 추출 (+118줄)
shared/figma-plugin/src/extract/css.ts CSS 수집 유틸리티 (+23줄)
변수 관리
shared/figma-plugin/src/utils/variables/variableCollector.ts 바인딩된 변수 참조 수집, 인덱싱, 사용 분석 (+146줄)
shared/figma-plugin/src/utils/variables/variableUsage.ts React 노드 트리에서 변수 사용 분석 및 포맷팅 (+83줄)
Figma 클라이언트 및 서버
shared/figma-plugin/src/figmaClient.ts WebSocket 기반 Figma 클라이언트: 연결 관리, 요청/응답 추적, 타임아웃 처리 (+155줄)
shared/figma-plugin/src/server.ts 설계 규칙 지원, 자원 기반 인터페이스 추가, MCP 서버 재구성 (±258줄)
shared/figma-plugin/src/main.ts figmaNodeToReactNode 통합, 변수 해석 로직 단순화, 불필요한 유틸리티 제거 (±365줄)
스타일 및 마크업
shared/figma-plugin/src/styles.css 글로벌 타이포그래피, 테마 변수, 기본 리셋 추가 (+51줄)
shared/figma-plugin/src/App.css 전역 및 기초 스타일 제거 (-49줄)
shared/figma-plugin/src/ui.tsx styles.css 임포트, App.tsx → app.tsx 경로 수정
유틸리티
shared/figma-plugin/src/utils/mappings/map.ts Figma → CSS 매핑 18개, 색상/픽셀 변환 함수 (+244줄)
shared/figma-plugin/src/utils/node.ts 삭제: 기존 노드 변환 로직 제거 (-228줄)
shared/figma-plugin/src/utils/serialize/convertNodeToXML.ts SceneNode 배열을 XML로 변환하는 유틸리티 (+155줄)
Storybook 문서 및 규칙
shared/figma-plugin/rules/*.stories.mdx (button, dialog, gameCreate, icon, input, menu, playerStatus, progress, question, stickyActionBar, textField, upload) 컴포넌트별 Storybook 문서 추가: 메타 구성, 스토리 변형, 접근성 설명 (+2,856줄)
shared/figma-plugin/rules/accordion.mdx 아코디언 사용 금지 규칙 문서 (+3줄)
shared/design/convertStoryToMdx.ts MDX 생성 파이프라인: 디렉터리 순회, 코드 펜스 자동 계산, 프론트매터 생성 (+158줄)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 분

변환 파이프라인 재구성, 다양한 유틸리티 함수, 광범위한 타입 정의, 수많은 Storybook 문서 추가로 인해 검토 범위가 크고 이질적입니다. 특히 노드 변환 로직, 변수 바인딩 시스템, 기존 코드 삭제 부분의 검증이 필요합니다.

Possibly related PRs

Suggested labels

🪄refactor, ✨feature, 📃docs

Suggested reviewers

  • kimnamheeee

Poem

🐰 노드를 변환하는 마법사,
스타일을 추출하는 모험꾼,
변수를 엮어내는 장인의 손길,
아름다운 문서로 그려낸 이야기,
플러그인의 새로운 세계가 펼쳐진다네! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive PR 제목 'Figma-plugin-refactor'는 변경사항의 주요 내용을 명확히 반영하지 못함. 구체적인 기능 변경, 구조 개편, 의존성 업데이트 등 실질적인 변경 사항이 반영되지 않음. 더 구체적인 제목으로 변경 권장 (예: 'Refactor Figma plugin with new type system and MDX conversion pipeline' 또는 'Figma plugin: restructure node conversion with typed React nodes and design rules integration')
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown

Deploy preview for re-creation ready!

✅ Preview
https://re-creation-nqpbyhtyv-whdgur5717s-projects.vercel.app

Built with commit 02c7b7d.
This pull request is being automatically deployed with vercel-action

@whdgur5717 whdgur5717 merged commit ebf756c into dev Jan 17, 2026
1 of 2 checks passed
@whdgur5717 whdgur5717 deleted the figma-plugin-refactor branch January 17, 2026 15:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
shared/figma-plugin/src/server.ts (1)

167-189: get_selection 도구가 하드코딩된 "test"를 반환합니다.

sendCommandToFigma("get_selection") 호출 결과를 무시하고 하드코딩된 "test" 문자열을 반환하고 있습니다. 이것이 디버그 코드라면 실제 결과를 반환하도록 수정해야 합니다.

🔧 실제 결과를 반환하도록 수정
  async () => {
    try {
      const result = await sendCommandToFigma("get_selection")
      return {
        content: [
          {
            type: "text",
-            text: "test",
+            text: JSON.stringify(result),
          },
        ],
      }
🤖 Fix all issues with AI agents
In `@shared/figma-plugin/rules/question.stories.mdx`:
- Around line 240-248: The story object LastRequiredQuestion has a typo in its
args.title string ("최소 하나의 문제은 필요합니다")—update args.title to "최소 하나의 문제는 필요합니다"
so the Korean grammar is correct; locate the LastRequiredQuestion export and
edit the title field accordingly.

In `@shared/figma-plugin/src/convert/node/instance.ts`:
- Around line 20-22: 현재 instance.ts에서 extractNodeStyle(node)와 collectCss(node)
사이에 남아있는 디버그 출력(console.log(node.componentProperties))이 플러그인 런타임에 민감한 디자인 데이터를
노출하고 로그 노이즈를 증가시킵니다; 해당 console.log 호출을 삭제하거나(권장) 환경/디버그 플래그로 제어되는 로거로 대체하도록
수정하고, 참조 대상인 extractNodeStyle, collectCss, node.componentProperties 및 문제의
console.log 호출을 찾아 제거 또는 안전한 로깅으로 변경하세요.

In `@shared/figma-plugin/src/figmaClient.ts`:
- Around line 64-79: The current response-matching condition ignores responses
that have errors or falsy results because it requires myResponse.result to be
truthy; change the outer if to only check myResponse.id and
pendingRequests.has(myResponse.id) so every response with a matching id is
handled. Inside that block keep clearing the timeout and retrieving the request
(pendingRequests.get(myResponse.id)), then if myResponse.error call logger.error
and request.reject(new Error(...)), else call request.resolve(myResponse.result)
even when result is falsy, and finally pendingRequests.delete(myResponse.id).
- Around line 43-52: The connectToFigma function currently only checks
ws.readyState === WebSocket.OPEN before returning, allowing a new WebSocket to
be created when ws is in WebSocket.CONNECTING; update connectToFigma to treat
both OPEN and CONNECTING as "already connecting/connected" and return early
(i.e., check ws && (ws.readyState === WebSocket.OPEN || ws.readyState ===
WebSocket.CONNECTING)) to prevent duplicate socket creation and resource leaks
while leaving existing ws variable and logging intact.

In `@shared/figma-plugin/tsup.config.js`:
- Around line 5-16: The loadRules function currently reads every entry in
rulesDir and blindly calls readFileSync, which can throw on subdirectories or
non-.mdx files and produce non-deterministic output; update loadRules to: list
entries from rulesDir, filter to regular files only (use fs.lstatSync or
fs.statSync + isFile()) and filter by extension (e.g., path.extname(file) ===
".mdx"), sort the filtered file list (e.g., files.sort()) for deterministic
order, then call readFileSync for each remaining file and populate the rules
object; reference symbols: loadRules, rulesDir, files, readFileSync,
path.extname, fs.lstatSync/fs.statSync.
🧹 Nitpick comments (11)
package.json (1)

37-41: dependenciesMeta 버전 고정 범위 확인이 필요합니다.
현재 키가 @modelcontextprotocol/sdk@1.24.3로 고정되어 있어 실제 해석 버전이 다르면 unplugged가 적용되지 않을 수 있습니다. 해석 버전을 확인한 뒤, 필요하면 패키지명만 지정해 범위를 넓히는 쪽이 안전합니다.

🔧 제안 변경
-  "dependenciesMeta": {
-    "@modelcontextprotocol/sdk@1.24.3": {
-      "unplugged": true
-    }
-  }
+  "dependenciesMeta": {
+    "@modelcontextprotocol/sdk": {
+      "unplugged": true
+    }
+  }
shared/figma-plugin/src/convert/boundVariables.ts (1)

3-4: 불필요한 래퍼 함수 제거 권장

withBoundVariablescollectNodeVariableReferences를 그대로 호출하는 것 외에 추가 로직이 없습니다. 직접 호출하는 것이 더 간결합니다.

♻️ 간소화 제안
 import { collectNodeVariableReferences } from "../utils/variables/variableCollector"
 
-const withBoundVariables = (node: SceneNode) =>
-  collectNodeVariableReferences(node)
-
 export const ensureBoundVariables = (
   node: SceneNode,
 ): ReturnType<typeof collectNodeVariableReferences> | undefined => {
-  const references = withBoundVariables(node)
+  const references = collectNodeVariableReferences(node)
   return references.length > 0 ? references : undefined
 }
shared/figma-plugin/src/styles.css (1)

1-5: :root 선언이 중복되어 있습니다.

:root 블록이 두 번 정의되어 있습니다 (1-5줄, 38-51줄). 단일 :root 블록으로 통합하는 것이 유지보수에 좋습니다.

♻️ 통합 제안
 :root {
   font-family: Inter, system-ui, Helvetica, Arial, sans-serif;
-  font-display: optional;
   font-size: 16px;
-}
-
-/* ... other styles ... */
-
-:root {
   --radius-full: 9999px;
   --radius-large: 0.8125rem;
   --radius-medium: 0.3125rem;
   --radius-none: 0;
   --radius-small: 0.125rem;
   --spacer-0: 0;
   --spacer-1: 0.25rem;
   --spacer-2: 0.5rem;
   --spacer-3: 1rem;
   --spacer-4: 1.5rem;
   --spacer-5: 2rem;
   --spacer-6: 2.5rem;
 }

참고: font-display: optional은 일반적으로 @font-face 규칙 내에서 사용됩니다. :root에서의 사용이 의도된 것인지 확인해 주세요.

Also applies to: 38-51

shared/figma-plugin/rules/icon.stories.mdx (1)

16-29: 아이콘 네이밍 컨벤션을 확인해주세요.

대부분의 아이콘은 PascalCase(Add, Arrow, Cross)를 따르지만, placeholder 아이콘들은 underscore를 사용합니다(Iconplaceholder_16px). 일관성을 위해 IconPlaceholder16px 같은 형식으로 통일하는 것이 좋을 수 있습니다.

shared/figma-plugin/src/utils/serialize/convertNodeToXML.ts (3)

3-8: 불필요한 async 선언입니다.

이 함수는 await를 사용하지 않으므로 동기 함수로 변경할 수 있습니다. js2xml은 동기 함수입니다.

♻️ 권장 수정안
-export async function convertNodeToXML(
+export function convertNodeToXML(
   selectedNodes: readonly SceneNode[],
-): Promise<string> {
+): string {

10-16: any 타입 사용을 줄이는 것을 고려해주세요.

processNode의 반환 타입과 attributes 객체에 any가 사용되고 있습니다. xml-js의 Element 타입이나 더 구체적인 타입을 사용하면 타입 안전성이 향상됩니다.

♻️ 타입 개선 제안
+import type { Element } from "xml-js"
+
+type XmlAttributes = Record<string, string | number | boolean>

-  const processNode = (node: SceneNode): any => {
+  const processNode = (node: SceneNode): Element | null => {
     if (!node) {
       return null
     }

-    const attributes: any = {}
+    const attributes: XmlAttributes = {}

119-124: COMPONENT와 INSTANCE 타입 제외 이유를 문서화해주세요.

COMPONENTINSTANCE 타입의 children을 제외하는 이유가 명확하지 않습니다. 주석을 추가하면 향후 유지보수에 도움이 됩니다.

📝 주석 추가 제안
+    // COMPONENT와 INSTANCE 타입은 children을 별도로 처리하거나
+    // 재귀적으로 포함하지 않아야 하는 경우가 있어 제외합니다
     const children =
       "children" in node &&
       node.type !== "COMPONENT" &&
       node.type !== "INSTANCE"
         ? node.children.map(processNode).filter((child) => child !== null)
         : []
shared/design/convertStoryToMdx.ts (2)

156-158: 모듈 임포트 시 자동 실행 방지를 고려해주세요.

main()이 무조건 실행되어 이 모듈을 다른 곳에서 임포트하면 의도치 않게 실행됩니다. 직접 실행 여부를 체크하는 것이 좋습니다.

♻️ 권장 수정안
-// Run if invoked directly
-
-main()
+// Run if invoked directly
+const isMain =
+  import.meta.url === `file://${process.argv[1]}` ||
+  process.argv[1]?.endsWith("convertStoryToMdx.ts")
+
+if (isMain) {
+  main()
+}

또는 ES 모듈에서 더 간단하게:

+if (import.meta.url.endsWith(process.argv[1]?.split(/[\\/]/).pop() ?? "")) {
+  main()
+}

78-85: fileExists 함수가 모든 에러를 무시합니다.

권한 문제나 다른 파일 시스템 에러도 false로 반환됩니다. ENOENT 에러만 처리하는 것이 더 안전합니다.

♻️ 에러 처리 개선안
 async function fileExists(p: string) {
   try {
     await fs.stat(p)
     return true
-  } catch {
+  } catch (err) {
+    if ((err as NodeJS.ErrnoException).code === "ENOENT") {
+      return false
+    }
+    throw err
-    return false
   }
 }
shared/figma-plugin/src/utils/mappings/map.ts (1)

200-211: 일관성을 위해 누락된 타입 별칭 추가를 고려하세요.

STROKE_CAP_TO_CSSSTROKE_JOIN_TO_CSS 매핑에 대한 타입 별칭(FigmaStrokeCap, FigmaStrokeJoin)이 정의되어 있지만, 다른 매핑들과 달리 export되지 않았습니다. 또한 FigmaLineHeightUnitFigmaLetterSpacingUnit 타입도 누락되어 있습니다.

♻️ 누락된 타입 별칭 추가
 export type FigmaBlendMode = keyof typeof BLEND_MODE_TO_CSS
 export type FigmaStrokeAlign = keyof typeof STROKE_ALIGN_TO_CSS
+export type FigmaStrokeCap = keyof typeof STROKE_CAP_TO_CSS
+export type FigmaStrokeJoin = keyof typeof STROKE_JOIN_TO_CSS
+export type FigmaLineHeightUnit = keyof typeof LINE_HEIGHT_UNIT_TO_CSS
+export type FigmaLetterSpacingUnit = keyof typeof LETTER_SPACING_UNIT_TO_CSS
shared/figma-plugin/src/convert/index.ts (1)

24-29: COMPONENT/COMPONENT_SET 캐스팅 대신 타입 확장 권장

FrameNode로 강제 캐스팅하면 타입 불일치가 가려질 수 있습니다. buildFrameNode 시그니처를 확장해 캐스팅을 제거하는 편이 안전합니다.

♻️ 제안 수정
-      baseNode = await buildFrameNode(node as FrameNode)
+      baseNode = await buildFrameNode(node)
// shared/figma-plugin/src/convert/node/frame.ts
-export const buildFrameNode = async (
-  node: FrameNode,
-): Promise<FrameReactNode> => {
+export const buildFrameNode = async (
+  node: FrameNode | ComponentNode | ComponentSetNode,
+): Promise<FrameReactNode> => {

Comment on lines +240 to +248
export const LastRequiredQuestion: Story = {
args: {
state: "default",
index: 1,
title: "최소 하나의 문제은 필요합니다",
canDelete: false,
},
render: Template,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

오타 수정이 필요합니다.

Line 244에서 "문제은"은 "문제는"으로 수정해야 합니다.

✏️ 수정안
-    title: "최소 하나의 문제은 필요합니다",
+    title: "최소 하나의 문제는 필요합니다",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const LastRequiredQuestion: Story = {
args: {
state: "default",
index: 1,
title: "최소 하나의 문제은 필요합니다",
canDelete: false,
},
render: Template,
}
export const LastRequiredQuestion: Story = {
args: {
state: "default",
index: 1,
title: "최소 하나의 문제는 필요합니다",
canDelete: false,
},
render: Template,
}
🤖 Prompt for AI Agents
In `@shared/figma-plugin/rules/question.stories.mdx` around lines 240 - 248, The
story object LastRequiredQuestion has a typo in its args.title string ("최소 하나의
문제은 필요합니다")—update args.title to "최소 하나의 문제는 필요합니다" so the Korean grammar is
correct; locate the LastRequiredQuestion export and edit the title field
accordingly.

Comment on lines +20 to +22
const style = extractNodeStyle(node)
console.log(node.componentProperties)
const css = await collectCss(node)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

디버그 로그 제거 권장

플러그인 런타임 로그는 디자인 데이터 노출/노이즈의 원인이 될 수 있습니다.

🧹 제안 수정
-  console.log(node.componentProperties)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const style = extractNodeStyle(node)
console.log(node.componentProperties)
const css = await collectCss(node)
const style = extractNodeStyle(node)
const css = await collectCss(node)
🤖 Prompt for AI Agents
In `@shared/figma-plugin/src/convert/node/instance.ts` around lines 20 - 22, 현재
instance.ts에서 extractNodeStyle(node)와 collectCss(node) 사이에 남아있는 디버그
출력(console.log(node.componentProperties))이 플러그인 런타임에 민감한 디자인 데이터를 노출하고 로그 노이즈를
증가시킵니다; 해당 console.log 호출을 삭제하거나(권장) 환경/디버그 플래그로 제어되는 로거로 대체하도록 수정하고, 참조 대상인
extractNodeStyle, collectCss, node.componentProperties 및 문제의 console.log 호출을 찾아
제거 또는 안전한 로깅으로 변경하세요.

Comment on lines +43 to +52
function connectToFigma(port = 3055) {
if (ws && ws.readyState === WebSocket.OPEN) {
logger.info("Already connected to Figma")
return
}

const wsUrl = serverUrl === "localhost" ? `${wsUrlBase}:${port}` : wsUrlBase
logger.info(`Connecting to Figma socket server at ${wsUrl}...`)
ws = new WebSocket(wsUrl)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the full figmaClient.ts file to understand context
cat shared/figma-plugin/src/figmaClient.ts

Repository: JECT-Study/JECT-5-FE

Length of output: 184


🌐 Web query:

WebSocket readyState CONNECTING OPEN values JavaScript

💡 Result:

In JavaScript WebSocket.readyState is one of four numeric constants:

  • WebSocket.CONNECTING = 0 — connection not yet open.
  • WebSocket.OPEN = 1 — connection open and ready to communicate.
  • WebSocket.CLOSING = 2 — connection is closing.
  • WebSocket.CLOSED = 3 — connection closed or failed to open.

Example: if (socket.readyState === WebSocket.OPEN) { /* send */ }. [1][2]

Sources:
[1] MDN WebSocket.readyState.
[2] WebSocket API reference.


연결 중(CONNECTING) 상태에서도 새 소켓을 열 수 있어 리소스 누수가 발생합니다.

ws.readyState 값이 WebSocket.CONNECTING (0)일 때도 새 소켓이 생성되므로, 중복 연결 시도를 방지하려면 OPEN 상태뿐만 아니라 CONNECTING 상태도 함께 확인해야 합니다.

수정 제안
-    if (ws && ws.readyState === WebSocket.OPEN) {
-      logger.info("Already connected to Figma")
-      return
-    }
+    if (
+      ws &&
+      (ws.readyState === WebSocket.OPEN ||
+        ws.readyState === WebSocket.CONNECTING)
+    ) {
+      logger.info("Already connected or connecting to Figma")
+      return
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function connectToFigma(port = 3055) {
if (ws && ws.readyState === WebSocket.OPEN) {
logger.info("Already connected to Figma")
return
}
const wsUrl = serverUrl === "localhost" ? `${wsUrlBase}:${port}` : wsUrlBase
logger.info(`Connecting to Figma socket server at ${wsUrl}...`)
ws = new WebSocket(wsUrl)
function connectToFigma(port = 3055) {
if (
ws &&
(ws.readyState === WebSocket.OPEN ||
ws.readyState === WebSocket.CONNECTING)
) {
logger.info("Already connected or connecting to Figma")
return
}
const wsUrl = serverUrl === "localhost" ? `${wsUrlBase}:${port}` : wsUrlBase
logger.info(`Connecting to Figma socket server at ${wsUrl}...`)
ws = new WebSocket(wsUrl)
🤖 Prompt for AI Agents
In `@shared/figma-plugin/src/figmaClient.ts` around lines 43 - 52, The
connectToFigma function currently only checks ws.readyState === WebSocket.OPEN
before returning, allowing a new WebSocket to be created when ws is in
WebSocket.CONNECTING; update connectToFigma to treat both OPEN and CONNECTING as
"already connecting/connected" and return early (i.e., check ws &&
(ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) to
prevent duplicate socket creation and resource leaks while leaving existing ws
variable and logging intact.

Comment on lines +64 to +79
if (
myResponse.id &&
pendingRequests.has(myResponse.id) &&
myResponse.result
) {
const request = pendingRequests.get(myResponse.id)!
clearTimeout(request.timeout)

if (myResponse.error) {
logger.error(`Error from Figma: ${myResponse.error}`)
request.reject(new Error(myResponse.error))
} else if (myResponse.result) {
request.resolve(myResponse.result)
}

pendingRequests.delete(myResponse.id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

응답 매칭 조건 때문에 에러/빈 결과가 타임아웃됩니다.

현재 myResponse.result가 truthy일 때만 pending 요청을 처리해서, 에러 응답이나 0/false/"" 같은 결과가 무시됩니다. 결과적으로 타임아웃으로만 종료될 수 있으니 id 매칭만으로 처리하도록 수정이 필요합니다.

🐛 수정 제안
-        if (
-          myResponse.id &&
-          pendingRequests.has(myResponse.id) &&
-          myResponse.result
-        ) {
+        if (myResponse.id && pendingRequests.has(myResponse.id)) {
           const request = pendingRequests.get(myResponse.id)!
           clearTimeout(request.timeout)

           if (myResponse.error) {
             logger.error(`Error from Figma: ${myResponse.error}`)
             request.reject(new Error(myResponse.error))
-          } else if (myResponse.result) {
-            request.resolve(myResponse.result)
-          }
+          } else {
+            request.resolve(myResponse.result)
+          }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (
myResponse.id &&
pendingRequests.has(myResponse.id) &&
myResponse.result
) {
const request = pendingRequests.get(myResponse.id)!
clearTimeout(request.timeout)
if (myResponse.error) {
logger.error(`Error from Figma: ${myResponse.error}`)
request.reject(new Error(myResponse.error))
} else if (myResponse.result) {
request.resolve(myResponse.result)
}
pendingRequests.delete(myResponse.id)
if (myResponse.id && pendingRequests.has(myResponse.id)) {
const request = pendingRequests.get(myResponse.id)!
clearTimeout(request.timeout)
if (myResponse.error) {
logger.error(`Error from Figma: ${myResponse.error}`)
request.reject(new Error(myResponse.error))
} else {
request.resolve(myResponse.result)
}
pendingRequests.delete(myResponse.id)
🤖 Prompt for AI Agents
In `@shared/figma-plugin/src/figmaClient.ts` around lines 64 - 79, The current
response-matching condition ignores responses that have errors or falsy results
because it requires myResponse.result to be truthy; change the outer if to only
check myResponse.id and pendingRequests.has(myResponse.id) so every response
with a matching id is handled. Inside that block keep clearing the timeout and
retrieving the request (pendingRequests.get(myResponse.id)), then if
myResponse.error call logger.error and request.reject(new Error(...)), else call
request.resolve(myResponse.result) even when result is falsy, and finally
pendingRequests.delete(myResponse.id).

Comment on lines +5 to +16
// 빌드 시점에 rules 파일들을 읽어서 객체로 만듦
function loadRules() {
const rulesDir = path.join(process.cwd(), "rules")
const files = fs.readdirSync(rulesDir)
const rules = {}

for (const file of files) {
const content = fs.readFileSync(path.join(rulesDir, file), "utf-8")
rules[file] = content
}

return rules

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

rules 로딩에서 디렉터리/비‑MDX 파일 처리 누락은 빌드 실패로 이어질 수 있습니다.
규칙 폴더에 하위 폴더나 다른 파일이 생기면 readFileSync가 예외를 던질 수 있고, 파일 순서도 OS에 따라 달라져 빌드 산출물이 비결정적일 수 있습니다. 파일 필터링과 정렬을 추가하는 편이 안전합니다.

🛠️ 제안 변경
 function loadRules() {
   const rulesDir = path.join(process.cwd(), "rules")
-  const files = fs.readdirSync(rulesDir)
+  const files = fs
+    .readdirSync(rulesDir, { withFileTypes: true })
+    .filter((entry) => entry.isFile())
+    .map((entry) => entry.name)
+    .filter((name) => name.endsWith(".mdx"))
+    .sort()
   const rules = {}
 
   for (const file of files) {
     const content = fs.readFileSync(path.join(rulesDir, file), "utf-8")
     rules[file] = content
   }
🤖 Prompt for AI Agents
In `@shared/figma-plugin/tsup.config.js` around lines 5 - 16, The loadRules
function currently reads every entry in rulesDir and blindly calls readFileSync,
which can throw on subdirectories or non-.mdx files and produce
non-deterministic output; update loadRules to: list entries from rulesDir,
filter to regular files only (use fs.lstatSync or fs.statSync + isFile()) and
filter by extension (e.g., path.extname(file) === ".mdx"), sort the filtered
file list (e.g., files.sort()) for deterministic order, then call readFileSync
for each remaining file and populate the rules object; reference symbols:
loadRules, rulesDir, files, readFileSync, path.extname,
fs.lstatSync/fs.statSync.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant