palette-mcp 1.1.1 → 1.2.0

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/dist/server.d.ts CHANGED
@@ -5,10 +5,25 @@
5
5
  */
6
6
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
7
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
8
+ import { type SyncConfig } from './services/design-system.js';
8
9
  export interface ServerConfig {
9
10
  figmaAccessToken?: string;
10
11
  githubToken?: string;
11
12
  figmaMcpServerUrl?: string;
13
+ /**
14
+ * Figma Desktop MCP 클라이언트 사용 여부
15
+ * Remote 모드(Smithery)에서는 false로 설정해야 함 (로컬호스트 접근 불가)
16
+ */
17
+ useFigmaMcp?: boolean;
18
+ /**
19
+ * 디자인 시스템 컴포넌트 동기화 설정
20
+ */
21
+ syncConfig?: SyncConfig;
22
+ /**
23
+ * 인증 건너뛰기 (로컬 개발용)
24
+ * true로 설정하면 GitHub 조직 멤버십 확인을 건너뜁니다.
25
+ */
26
+ skipAuth?: boolean;
12
27
  }
13
28
  export declare const tools: Tool[];
14
29
  /**
package/dist/server.js CHANGED
@@ -4,35 +4,43 @@
4
4
  * Local(stdio) 모드와 Remote(Smithery) 모드에서 공유되는 핵심 기능을 정의합니다.
5
5
  */
6
6
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
- import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
7
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
8
8
  import { readFile } from 'fs/promises';
9
9
  import { FigmaService } from './services/figma.js';
10
10
  import { DesignSystemService } from './services/design-system.js';
11
11
  import { CodeGenerator } from './services/code-generator.js';
12
- // Tools 정의
12
+ import { validateAccess } from './services/auth.js';
13
+ // Tools 정의 (annotations 포함)
13
14
  export const tools = [
14
15
  {
15
16
  name: 'convert_figma_to_react',
16
- description: 'Figma 디자인을 디자인 시스템을 사용하여 React 컴포넌트로 변환합니다',
17
+ description: 'Figma 디자인을 @dealicious/design-system-react 컴포넌트를 사용하여 React 컴포넌트로 변환합니다. Figma 파일 URL과 컴포넌트 이름을 제공하면, 디자인 시스템 컴포넌트(Button, Text, Tag, Check, Chip 등)를 활용한 React TSX 코드를 생성합니다.',
18
+ annotations: {
19
+ title: 'Figma to React Converter',
20
+ readOnlyHint: false,
21
+ destructiveHint: false,
22
+ idempotentHint: true,
23
+ openWorldHint: true,
24
+ },
17
25
  inputSchema: {
18
26
  type: 'object',
19
27
  properties: {
20
28
  figmaUrl: {
21
29
  type: 'string',
22
- description: 'Figma 파일 URL 또는 파일 ID',
30
+ description: 'Figma 파일의 전체 URL (예: https://www.figma.com/file/ABC123/Design?node-id=1-2) 또는 파일 ID',
23
31
  },
24
32
  nodeId: {
25
33
  type: 'string',
26
- description: '변환할 특정 노드 ID (선택사항)',
34
+ description: '변환할 특정 Figma 노드의 ID. URL에 node-id가 포함되어 있으면 생략 가능합니다.',
27
35
  },
28
36
  componentName: {
29
37
  type: 'string',
30
- description: '생성할 컴포넌트 이름',
38
+ description: '생성될 React 컴포넌트의 이름 (예: ProductCard, LoginForm)',
31
39
  },
32
40
  previewType: {
33
41
  type: 'string',
34
42
  enum: ['html', 'image', 'both'],
35
- description: '미리보기 타입: "html"은 HTML 파일, "image"는 PNG 이미지, "both"는 둘 다 (기본값: "both")',
43
+ description: '미리보기 생성 옵션: "html"은 브라우저에서 볼 수 있는 HTML 파일, "image"는 PNG 스크린샷, "both"는 둘 다 생성 (기본값: "both")',
36
44
  default: 'both',
37
45
  },
38
46
  },
@@ -41,26 +49,33 @@ export const tools = [
41
49
  },
42
50
  {
43
51
  name: 'convert_figma_to_vue',
44
- description: 'Figma 디자인을 디자인 시스템을 사용하여 Vue 컴포넌트로 변환합니다',
52
+ description: 'Figma 디자인을 @dealicious/design-system Vue 컴포넌트를 사용하여 Vue 3 Single File Component로 변환합니다. Figma 파일 URL과 컴포넌트 이름을 제공하면, 디자인 시스템 컴포넌트를 활용한 Vue SFC 코드를 생성합니다.',
53
+ annotations: {
54
+ title: 'Figma to Vue Converter',
55
+ readOnlyHint: false,
56
+ destructiveHint: false,
57
+ idempotentHint: true,
58
+ openWorldHint: true,
59
+ },
45
60
  inputSchema: {
46
61
  type: 'object',
47
62
  properties: {
48
63
  figmaUrl: {
49
64
  type: 'string',
50
- description: 'Figma 파일 URL 또는 파일 ID',
65
+ description: 'Figma 파일의 전체 URL (예: https://www.figma.com/file/ABC123/Design?node-id=1-2) 또는 파일 ID',
51
66
  },
52
67
  nodeId: {
53
68
  type: 'string',
54
- description: '변환할 특정 노드 ID (선택사항)',
69
+ description: '변환할 특정 Figma 노드의 ID. URL에 node-id가 포함되어 있으면 생략 가능합니다.',
55
70
  },
56
71
  componentName: {
57
72
  type: 'string',
58
- description: '생성할 컴포넌트 이름',
73
+ description: '생성될 Vue 컴포넌트의 이름 (예: ProductCard, LoginForm)',
59
74
  },
60
75
  previewType: {
61
76
  type: 'string',
62
77
  enum: ['html', 'image', 'both'],
63
- description: '미리보기 타입: "html"은 HTML 파일, "image"는 PNG 이미지, "both"는 둘 다 (기본값: "both")',
78
+ description: '미리보기 생성 옵션: "html"은 브라우저에서 볼 수 있는 HTML 파일, "image"는 PNG 스크린샷, "both"는 둘 다 생성 (기본값: "both")',
64
79
  default: 'both',
65
80
  },
66
81
  },
@@ -69,14 +84,21 @@ export const tools = [
69
84
  },
70
85
  {
71
86
  name: 'list_design_system_components',
72
- description: '디자인 시스템에서 사용 가능한 컴포넌트 목록을 조회합니다',
87
+ description: '@dealicious/design-system에서 사용 가능한 모든 UI 컴포넌트 목록을 조회합니다. 각 컴포넌트의 이름, 설명, import 경로, 사용 가능한 props를 반환합니다.',
88
+ annotations: {
89
+ title: 'Design System Component List',
90
+ readOnlyHint: true,
91
+ destructiveHint: false,
92
+ idempotentHint: true,
93
+ openWorldHint: false,
94
+ },
73
95
  inputSchema: {
74
96
  type: 'object',
75
97
  properties: {
76
98
  framework: {
77
99
  type: 'string',
78
100
  enum: ['react', 'vue'],
79
- description: '컴포넌트를 조회할 프레임워크',
101
+ description: '컴포넌트를 조회할 프레임워크: "react"는 design-system-react, "vue"는 design-system 패키지',
80
102
  },
81
103
  },
82
104
  required: ['framework'],
@@ -84,19 +106,76 @@ export const tools = [
84
106
  },
85
107
  {
86
108
  name: 'analyze_figma_file',
87
- description: 'Figma 파일 구조와 사용 가능한 컴포넌트를 분석합니다',
109
+ description: 'Figma 파일의 구조를 분석하여 페이지, 프레임, 컴포넌트 계층을 파악합니다. 변환하기 전에 파일 구조를 이해하는 유용합니다.',
110
+ annotations: {
111
+ title: 'Figma File Analyzer',
112
+ readOnlyHint: true,
113
+ destructiveHint: false,
114
+ idempotentHint: true,
115
+ openWorldHint: true,
116
+ },
88
117
  inputSchema: {
89
118
  type: 'object',
90
119
  properties: {
91
120
  figmaUrl: {
92
121
  type: 'string',
93
- description: 'Figma 파일 URL 또는 파일 ID',
122
+ description: '분석할 Figma 파일의 전체 URL 또는 파일 ID',
94
123
  },
95
124
  },
96
125
  required: ['figmaUrl'],
97
126
  },
98
127
  },
99
128
  ];
129
+ // Prompts 정의
130
+ const prompts = [
131
+ {
132
+ name: 'convert-design-to-component',
133
+ description: 'Figma 디자인을 React 또는 Vue 컴포넌트로 변환하는 가이드 프롬프트',
134
+ arguments: [
135
+ {
136
+ name: 'figmaUrl',
137
+ description: 'Figma 파일 URL',
138
+ required: true,
139
+ },
140
+ {
141
+ name: 'framework',
142
+ description: '프레임워크 선택 (react 또는 vue)',
143
+ required: true,
144
+ },
145
+ {
146
+ name: 'componentName',
147
+ description: '컴포넌트 이름',
148
+ required: true,
149
+ },
150
+ ],
151
+ },
152
+ {
153
+ name: 'explore-design-system',
154
+ description: '디자인 시스템 컴포넌트 탐색 가이드',
155
+ arguments: [
156
+ {
157
+ name: 'framework',
158
+ description: '프레임워크 선택 (react 또는 vue)',
159
+ required: false,
160
+ },
161
+ ],
162
+ },
163
+ ];
164
+ // Resources 정의
165
+ const resources = [
166
+ {
167
+ uri: 'palette://design-system/react/components',
168
+ name: 'React Design System Components',
169
+ description: '@dealicious/design-system-react에서 사용 가능한 컴포넌트 목록',
170
+ mimeType: 'application/json',
171
+ },
172
+ {
173
+ uri: 'palette://design-system/vue/components',
174
+ name: 'Vue Design System Components',
175
+ description: '@dealicious/design-system에서 사용 가능한 컴포넌트 목록',
176
+ mimeType: 'application/json',
177
+ },
178
+ ];
100
179
  /**
101
180
  * MCP 서버를 생성하고 핸들러를 등록합니다.
102
181
  * Local 모드와 Remote 모드에서 공통으로 사용됩니다.
@@ -112,23 +191,59 @@ export function createPaletteServer(config = {}) {
112
191
  if (config.figmaMcpServerUrl) {
113
192
  process.env.FIGMA_MCP_SERVER_URL = config.figmaMcpServerUrl;
114
193
  }
115
- // MCP 서버 초기화
194
+ // MCP 서버 초기화 (모든 capabilities 활성화)
116
195
  const server = new Server({
117
196
  name: 'palette',
118
197
  version: '1.0.0',
198
+ }, {
199
+ capabilities: {
200
+ tools: {}, // tools 기능 활성화
201
+ prompts: {}, // prompts 기능 활성화
202
+ resources: {}, // resources 기능 활성화
203
+ },
119
204
  });
120
205
  // 서비스 초기화
121
- const figmaService = new FigmaService();
122
- const designSystemService = new DesignSystemService();
206
+ // Remote 모드에서는 Figma Desktop MCP 클라이언트를 사용하지 않음 (로컬호스트 접근 불가)
207
+ const useFigmaMcp = config.useFigmaMcp !== undefined ? config.useFigmaMcp : true;
208
+ const figmaService = new FigmaService(useFigmaMcp, config.figmaMcpServerUrl);
209
+ const designSystemService = new DesignSystemService(config.syncConfig);
123
210
  const codeGenerator = new CodeGenerator(designSystemService);
124
- // 도구 목록 핸들러
211
+ // 인증 상태 캐시
212
+ let authResult = null;
213
+ /**
214
+ * 도구 실행 전 인증 확인
215
+ * skipAuth가 true이면 인증을 건너뜁니다.
216
+ */
217
+ async function ensureAuthenticated() {
218
+ // 로컬 개발 모드에서는 인증 건너뛰기
219
+ if (config.skipAuth) {
220
+ console.error('[Palette Auth] ⚠️ 인증 건너뛰기 모드 (skipAuth=true)');
221
+ return;
222
+ }
223
+ // 이미 인증되었으면 건너뛰기
224
+ if (authResult?.authorized) {
225
+ return;
226
+ }
227
+ // 인증 수행
228
+ const githubToken = config.githubToken || process.env.GITHUB_TOKEN;
229
+ authResult = await validateAccess(githubToken);
230
+ if (!authResult.authorized) {
231
+ throw new Error(authResult.error || '인증에 실패했습니다.');
232
+ }
233
+ }
234
+ // 디자인 시스템 컴포넌트 비동기 초기화 (백그라운드에서 실행)
235
+ designSystemService.initialize().catch((error) => {
236
+ console.error('[Palette] 디자인 시스템 초기화 실패:', error);
237
+ });
238
+ // ===== Tools 핸들러 =====
125
239
  server.setRequestHandler(ListToolsRequestSchema, async () => {
126
240
  return { tools };
127
241
  });
128
- // 도구 실행 핸들러
129
242
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
130
243
  const { name, arguments: args } = request.params;
131
244
  try {
245
+ // 🔐 도구 실행 전 인증 확인
246
+ await ensureAuthenticated();
132
247
  switch (name) {
133
248
  case 'convert_figma_to_react': {
134
249
  const { figmaUrl, nodeId, componentName, previewType = 'both' } = args;
@@ -242,5 +357,83 @@ export function createPaletteServer(config = {}) {
242
357
  };
243
358
  }
244
359
  });
360
+ // ===== Prompts 핸들러 =====
361
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
362
+ return { prompts };
363
+ });
364
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
365
+ const { name, arguments: args } = request.params;
366
+ switch (name) {
367
+ case 'convert-design-to-component': {
368
+ const figmaUrl = args?.figmaUrl || '<FIGMA_URL>';
369
+ const framework = args?.framework || 'react';
370
+ const componentName = args?.componentName || 'MyComponent';
371
+ return {
372
+ description: 'Figma 디자인을 컴포넌트로 변환하는 프롬프트',
373
+ messages: [
374
+ {
375
+ role: 'user',
376
+ content: {
377
+ type: 'text',
378
+ text: `Figma 디자인을 ${framework} 컴포넌트로 변환해주세요.\n\n- Figma URL: ${figmaUrl}\n- 컴포넌트 이름: ${componentName}\n\n디자인 시스템 컴포넌트를 최대한 활용해주세요.`,
379
+ },
380
+ },
381
+ ],
382
+ };
383
+ }
384
+ case 'explore-design-system': {
385
+ const framework = args?.framework || 'react';
386
+ return {
387
+ description: '디자인 시스템 탐색 프롬프트',
388
+ messages: [
389
+ {
390
+ role: 'user',
391
+ content: {
392
+ type: 'text',
393
+ text: `@dealicious/design-system${framework === 'react' ? '-react' : ''} 패키지에서 사용 가능한 컴포넌트 목록을 보여주세요. 각 컴포넌트의 사용법과 예제도 함께 알려주세요.`,
394
+ },
395
+ },
396
+ ],
397
+ };
398
+ }
399
+ default:
400
+ throw new Error(`Unknown prompt: ${name}`);
401
+ }
402
+ });
403
+ // ===== Resources 핸들러 =====
404
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
405
+ return { resources };
406
+ });
407
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
408
+ const { uri } = request.params;
409
+ switch (uri) {
410
+ case 'palette://design-system/react/components': {
411
+ const components = await designSystemService.getAvailableComponents('react');
412
+ return {
413
+ contents: [
414
+ {
415
+ uri,
416
+ mimeType: 'application/json',
417
+ text: JSON.stringify(components, null, 2),
418
+ },
419
+ ],
420
+ };
421
+ }
422
+ case 'palette://design-system/vue/components': {
423
+ const components = await designSystemService.getAvailableComponents('vue');
424
+ return {
425
+ contents: [
426
+ {
427
+ uri,
428
+ mimeType: 'application/json',
429
+ text: JSON.stringify(components, null, 2),
430
+ },
431
+ ],
432
+ };
433
+ }
434
+ default:
435
+ throw new Error(`Unknown resource: ${uri}`);
436
+ }
437
+ });
245
438
  return server;
246
439
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 인증 및 권한 검증 서비스
3
+ *
4
+ * dealicious-inc 조직 멤버십을 확인하여 서비스 접근을 제어합니다.
5
+ */
6
+ export interface AuthResult {
7
+ authorized: boolean;
8
+ username?: string;
9
+ organization?: string;
10
+ error?: string;
11
+ }
12
+ /**
13
+ * GitHub 조직 멤버십을 확인하여 서비스 접근 권한을 검증합니다.
14
+ *
15
+ * @param githubToken GitHub Personal Access Token
16
+ * @returns 인증 결과
17
+ */
18
+ export declare function validateAccess(githubToken?: string): Promise<AuthResult>;
19
+ /**
20
+ * 인증 캐시 초기화 (테스트용)
21
+ */
22
+ export declare function clearAuthCache(): void;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * 인증 및 권한 검증 서비스
3
+ *
4
+ * dealicious-inc 조직 멤버십을 확인하여 서비스 접근을 제어합니다.
5
+ */
6
+ import { Octokit } from '@octokit/rest';
7
+ // 허용된 GitHub 조직 목록
8
+ const ALLOWED_ORGANIZATIONS = ['dealicious-inc'];
9
+ // 인증 상태 캐시 (토큰별로 검증 결과를 캐시)
10
+ const authCache = new Map();
11
+ // 캐시 유효 시간 (1시간)
12
+ const CACHE_TTL_MS = 60 * 60 * 1000;
13
+ /**
14
+ * GitHub 토큰으로 사용자 정보 조회
15
+ */
16
+ async function getAuthenticatedUser(octokit) {
17
+ const { data } = await octokit.users.getAuthenticated();
18
+ return { login: data.login, email: data.email };
19
+ }
20
+ /**
21
+ * 사용자가 허용된 조직의 멤버인지 확인
22
+ */
23
+ async function checkOrganizationMembership(octokit, org) {
24
+ try {
25
+ await octokit.orgs.getMembershipForAuthenticatedUser({ org });
26
+ return true;
27
+ }
28
+ catch (error) {
29
+ // 404: 멤버가 아님, 403: 권한 없음
30
+ if (error.status === 404 || error.status === 403) {
31
+ return false;
32
+ }
33
+ throw error;
34
+ }
35
+ }
36
+ /**
37
+ * 캐시된 인증 결과 확인
38
+ */
39
+ function getCachedAuth(githubToken) {
40
+ const cached = authCache.get(githubToken);
41
+ if (!cached)
42
+ return null;
43
+ // 캐시 만료 확인
44
+ const now = new Date();
45
+ if (now.getTime() - cached.checkedAt.getTime() > CACHE_TTL_MS) {
46
+ authCache.delete(githubToken);
47
+ return null;
48
+ }
49
+ return { valid: cached.valid, username: cached.username };
50
+ }
51
+ /**
52
+ * 인증 결과 캐시 저장
53
+ */
54
+ function setCachedAuth(githubToken, valid, username) {
55
+ authCache.set(githubToken, { valid, username, checkedAt: new Date() });
56
+ }
57
+ /**
58
+ * GitHub 조직 멤버십을 확인하여 서비스 접근 권한을 검증합니다.
59
+ *
60
+ * @param githubToken GitHub Personal Access Token
61
+ * @returns 인증 결과
62
+ */
63
+ export async function validateAccess(githubToken) {
64
+ // GITHUB_TOKEN이 없으면 거부
65
+ if (!githubToken) {
66
+ return {
67
+ authorized: false,
68
+ error: 'GITHUB_TOKEN이 필요합니다. dealicious-inc 조직 멤버만 Palette MCP를 사용할 수 있습니다.',
69
+ };
70
+ }
71
+ // 캐시 확인
72
+ const cached = getCachedAuth(githubToken);
73
+ if (cached) {
74
+ if (cached.valid) {
75
+ return {
76
+ authorized: true,
77
+ username: cached.username,
78
+ organization: ALLOWED_ORGANIZATIONS[0],
79
+ };
80
+ }
81
+ else {
82
+ return {
83
+ authorized: false,
84
+ username: cached.username,
85
+ error: `사용자 '${cached.username}'은(는) dealicious-inc 조직 멤버가 아닙니다.`,
86
+ };
87
+ }
88
+ }
89
+ // GitHub API로 검증
90
+ const octokit = new Octokit({ auth: githubToken });
91
+ try {
92
+ // 사용자 정보 조회
93
+ const user = await getAuthenticatedUser(octokit);
94
+ // 조직 멤버십 확인
95
+ for (const org of ALLOWED_ORGANIZATIONS) {
96
+ const isMember = await checkOrganizationMembership(octokit, org);
97
+ if (isMember) {
98
+ // 캐시 저장
99
+ setCachedAuth(githubToken, true, user.login);
100
+ console.error(`[Palette Auth] ✅ 인증 성공: ${user.login} (${org})`);
101
+ return {
102
+ authorized: true,
103
+ username: user.login,
104
+ organization: org,
105
+ };
106
+ }
107
+ }
108
+ // 어떤 조직에도 속하지 않음
109
+ setCachedAuth(githubToken, false, user.login);
110
+ console.error(`[Palette Auth] ❌ 인증 실패: ${user.login} - 허용된 조직 멤버 아님`);
111
+ return {
112
+ authorized: false,
113
+ username: user.login,
114
+ error: `사용자 '${user.login}'은(는) dealicious-inc 조직 멤버가 아닙니다. 조직 관리자에게 문의하세요.`,
115
+ };
116
+ }
117
+ catch (error) {
118
+ // 토큰 유효성 검사 실패
119
+ if (error.status === 401) {
120
+ return {
121
+ authorized: false,
122
+ error: 'GITHUB_TOKEN이 유효하지 않습니다. 토큰을 확인해주세요.',
123
+ };
124
+ }
125
+ // 기타 오류
126
+ console.error('[Palette Auth] 인증 중 오류 발생:', error.message);
127
+ return {
128
+ authorized: false,
129
+ error: `인증 중 오류가 발생했습니다: ${error.message}`,
130
+ };
131
+ }
132
+ }
133
+ /**
134
+ * 인증 캐시 초기화 (테스트용)
135
+ */
136
+ export function clearAuthCache() {
137
+ authCache.clear();
138
+ }
@@ -24,6 +24,7 @@ export declare class CodeGenerator {
24
24
  generateReactComponent(figmaData: FigmaFile, componentName: string): Promise<string>;
25
25
  /**
26
26
  * HTML을 렌더링하여 이미지로 변환
27
+ * puppeteer가 없으면 빈 문자열 반환
27
28
  */
28
29
  private generateImagePreview;
29
30
  /**
@@ -1,5 +1,23 @@
1
1
  import { saveFile, saveBinaryFile, saveMetadata, generateRequestId, getRequestFolderPath } from '../utils/request-manager.js';
2
- import puppeteer from 'puppeteer';
2
+ // puppeteer optional dependency로, 없으면 이미지 프리뷰 기능이 비활성화됨
3
+ let puppeteer = null;
4
+ let puppeteerLoaded = false;
5
+ /**
6
+ * puppeteer를 lazy loading으로 로드
7
+ */
8
+ async function loadPuppeteer() {
9
+ if (puppeteerLoaded)
10
+ return puppeteer;
11
+ try {
12
+ puppeteer = (await import('puppeteer')).default;
13
+ }
14
+ catch (e) {
15
+ console.warn('puppeteer를 로드할 수 없습니다. 이미지 프리뷰 기능이 비활성화됩니다.');
16
+ puppeteer = null;
17
+ }
18
+ puppeteerLoaded = true;
19
+ return puppeteer;
20
+ }
3
21
  export class CodeGenerator {
4
22
  designSystemService;
5
23
  constructor(designSystemService) {
@@ -44,9 +62,17 @@ export class CodeGenerator {
44
62
  }
45
63
  /**
46
64
  * HTML을 렌더링하여 이미지로 변환
65
+ * puppeteer가 없으면 빈 문자열 반환
47
66
  */
48
67
  async generateImagePreview(htmlContent, componentName, requestId) {
49
- const browser = await puppeteer.launch({
68
+ // puppeteer를 lazy loading으로 로드
69
+ const pptr = await loadPuppeteer();
70
+ // puppeteer가 없으면 이미지 생성 불가
71
+ if (!pptr) {
72
+ console.warn('puppeteer가 설치되지 않아 이미지 프리뷰를 생성할 수 없습니다.');
73
+ return '';
74
+ }
75
+ const browser = await pptr.launch({
50
76
  headless: true,
51
77
  args: ['--no-sandbox', '--disable-setuid-sandbox']
52
78
  });
@@ -1,3 +1,4 @@
1
+ import { type SyncResult } from '../sync/index.js';
1
2
  export interface DesignSystemComponent {
2
3
  name: string;
3
4
  description: string;
@@ -19,10 +20,51 @@ export interface ComponentExample {
19
20
  code: string;
20
21
  description: string;
21
22
  }
23
+ export interface SyncConfig {
24
+ /** 서버 시작 시 자동 동기화 여부 (기본값: true) */
25
+ autoSync?: boolean;
26
+ /** 동기화 상세 로그 출력 여부 */
27
+ verbose?: boolean;
28
+ }
22
29
  export declare class DesignSystemService {
23
30
  private reactComponents;
24
31
  private vueComponents;
25
- constructor();
32
+ private syncService;
33
+ private syncConfig;
34
+ private initialized;
35
+ private lastSyncResult;
36
+ constructor(syncConfig?: SyncConfig);
37
+ /**
38
+ * 서비스 초기화 (비동기)
39
+ * 서버 시작 시 호출하여 GitHub에서 최신 컴포넌트 동기화
40
+ */
41
+ initialize(): Promise<void>;
42
+ /**
43
+ * 수동 동기화 실행
44
+ */
45
+ syncComponents(force?: boolean): Promise<SyncResult>;
46
+ /**
47
+ * 마지막 동기화 결과 조회
48
+ */
49
+ getLastSyncResult(): SyncResult | null;
50
+ /**
51
+ * 캐시 상태 조회
52
+ */
53
+ getCacheStatus(): Promise<{
54
+ exists: boolean;
55
+ isValid: boolean;
56
+ metadata: {
57
+ version: string;
58
+ lastSyncedAt: string;
59
+ commitSha: string;
60
+ reactComponentCount: number;
61
+ vueComponentCount: number;
62
+ } | null;
63
+ }>;
64
+ /**
65
+ * 캐시 삭제
66
+ */
67
+ clearCache(): Promise<void>;
26
68
  /**
27
69
  * 디자인 시스템(React, Vue) 컴포넌트 메타데이터 제공 필요.
28
70
  * 1. 메타데이터 구조 정의