@axis-ds/cli 1.1.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/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/chunk-CGFESSCW.js +24 -0
- package/dist/chunk-D26GW2HO.js +873 -0
- package/dist/chunk-DYIVW3T5.js +873 -0
- package/dist/chunk-GCOWKKXG.js +202 -0
- package/dist/chunk-WRDOGNSG.js +173 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2501 -0
- package/dist/monet.d.ts +90 -0
- package/dist/monet.js +12 -0
- package/dist/registry-client-TPWFZ4HE.js +8 -0
- package/dist/sync/index.d.ts +339 -0
- package/dist/sync/index.js +24 -0
- package/dist/v0.d.ts +26 -0
- package/dist/v0.js +10 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2501 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
MonetClient
|
|
4
|
+
} from "./chunk-WRDOGNSG.js";
|
|
5
|
+
import {
|
|
6
|
+
V0Client
|
|
7
|
+
} from "./chunk-GCOWKKXG.js";
|
|
8
|
+
import {
|
|
9
|
+
registerSyncCommand
|
|
10
|
+
} from "./chunk-D26GW2HO.js";
|
|
11
|
+
import {
|
|
12
|
+
fetchTemplateDetail,
|
|
13
|
+
fetchTemplateIndex
|
|
14
|
+
} from "./chunk-CGFESSCW.js";
|
|
15
|
+
|
|
16
|
+
// src/index.ts
|
|
17
|
+
import { Command } from "commander";
|
|
18
|
+
import chalk8 from "chalk";
|
|
19
|
+
import ora6 from "ora";
|
|
20
|
+
import fs9 from "fs-extra";
|
|
21
|
+
import path9 from "path";
|
|
22
|
+
import prompts2 from "prompts";
|
|
23
|
+
|
|
24
|
+
// src/library/base-collector.ts
|
|
25
|
+
import crypto from "crypto";
|
|
26
|
+
var CATEGORY_RULES = [
|
|
27
|
+
{
|
|
28
|
+
category: "agentic",
|
|
29
|
+
patterns: [
|
|
30
|
+
/streaming/i,
|
|
31
|
+
/agent/i,
|
|
32
|
+
/approval/i,
|
|
33
|
+
/step[-_]?indicator/i,
|
|
34
|
+
/tool[-_]?call/i,
|
|
35
|
+
/run[-_]?container/i,
|
|
36
|
+
/surface/i,
|
|
37
|
+
/thinking/i
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
category: "form",
|
|
42
|
+
patterns: [
|
|
43
|
+
/input/i,
|
|
44
|
+
/select/i,
|
|
45
|
+
/checkbox/i,
|
|
46
|
+
/radio/i,
|
|
47
|
+
/switch/i,
|
|
48
|
+
/form/i,
|
|
49
|
+
/textarea/i,
|
|
50
|
+
/slider/i,
|
|
51
|
+
/date[-_]?picker/i,
|
|
52
|
+
/label/i
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
category: "layout",
|
|
57
|
+
patterns: [
|
|
58
|
+
/container/i,
|
|
59
|
+
/grid/i,
|
|
60
|
+
/flex/i,
|
|
61
|
+
/stack/i,
|
|
62
|
+
/spacer/i,
|
|
63
|
+
/divider/i,
|
|
64
|
+
/separator/i,
|
|
65
|
+
/aspect[-_]?ratio/i
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
category: "navigation",
|
|
70
|
+
patterns: [
|
|
71
|
+
/nav/i,
|
|
72
|
+
/menu/i,
|
|
73
|
+
/breadcrumb/i,
|
|
74
|
+
/tabs/i,
|
|
75
|
+
/pagination/i,
|
|
76
|
+
/sidebar/i,
|
|
77
|
+
/header/i,
|
|
78
|
+
/footer/i
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
category: "feedback",
|
|
83
|
+
patterns: [
|
|
84
|
+
/toast/i,
|
|
85
|
+
/alert/i,
|
|
86
|
+
/notification/i,
|
|
87
|
+
/progress/i,
|
|
88
|
+
/spinner/i,
|
|
89
|
+
/skeleton/i,
|
|
90
|
+
/loading/i
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
category: "overlay",
|
|
95
|
+
patterns: [
|
|
96
|
+
/modal/i,
|
|
97
|
+
/dialog/i,
|
|
98
|
+
/drawer/i,
|
|
99
|
+
/popover/i,
|
|
100
|
+
/tooltip/i,
|
|
101
|
+
/dropdown/i,
|
|
102
|
+
/sheet/i
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
category: "data-display",
|
|
107
|
+
patterns: [
|
|
108
|
+
/table/i,
|
|
109
|
+
/list/i,
|
|
110
|
+
/card/i,
|
|
111
|
+
/avatar/i,
|
|
112
|
+
/badge/i,
|
|
113
|
+
/tag/i,
|
|
114
|
+
/timeline/i,
|
|
115
|
+
/tree/i
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
category: "chart",
|
|
120
|
+
patterns: [
|
|
121
|
+
/chart/i,
|
|
122
|
+
/graph/i,
|
|
123
|
+
/pie/i,
|
|
124
|
+
/bar/i,
|
|
125
|
+
/line/i,
|
|
126
|
+
/area/i,
|
|
127
|
+
/scatter/i,
|
|
128
|
+
/radar/i
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
];
|
|
132
|
+
var TAG_PATTERNS = [
|
|
133
|
+
{ pattern: /useState|useReducer/, tag: "stateful" },
|
|
134
|
+
{ pattern: /useEffect|useLayoutEffect/, tag: "side-effects" },
|
|
135
|
+
{ pattern: /forwardRef/, tag: "forwardRef" },
|
|
136
|
+
{ pattern: /motion|animate|transition/, tag: "animated" },
|
|
137
|
+
{ pattern: /accessible|aria-/, tag: "accessible" },
|
|
138
|
+
{ pattern: /responsive|breakpoint|media/, tag: "responsive" }
|
|
139
|
+
];
|
|
140
|
+
var BaseCollector = class {
|
|
141
|
+
/**
|
|
142
|
+
* 전체 컴포넌트 수집
|
|
143
|
+
*/
|
|
144
|
+
async collectAll(options) {
|
|
145
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
146
|
+
const items = [];
|
|
147
|
+
const errors = [];
|
|
148
|
+
try {
|
|
149
|
+
let componentList = await this.listComponents();
|
|
150
|
+
if (options?.filter?.categories) {
|
|
151
|
+
componentList = componentList.filter(
|
|
152
|
+
(c) => c.category && options.filter.categories.includes(c.category)
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
for (const item of componentList) {
|
|
156
|
+
try {
|
|
157
|
+
const component = await this.collectComponent(item.id);
|
|
158
|
+
items.push({
|
|
159
|
+
id: component.id,
|
|
160
|
+
name: component.name,
|
|
161
|
+
status: "new",
|
|
162
|
+
// 증분 수집 시 로직 추가 필요
|
|
163
|
+
checksum: component.checksum
|
|
164
|
+
});
|
|
165
|
+
} catch (error) {
|
|
166
|
+
errors.push({
|
|
167
|
+
id: item.id,
|
|
168
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
169
|
+
recoverable: true
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
source: this.sourceType,
|
|
175
|
+
timestamp,
|
|
176
|
+
success: errors.length === 0,
|
|
177
|
+
collected: items.length,
|
|
178
|
+
updated: 0,
|
|
179
|
+
failed: errors.length,
|
|
180
|
+
items,
|
|
181
|
+
errors
|
|
182
|
+
};
|
|
183
|
+
} catch (error) {
|
|
184
|
+
return {
|
|
185
|
+
source: this.sourceType,
|
|
186
|
+
timestamp,
|
|
187
|
+
success: false,
|
|
188
|
+
collected: 0,
|
|
189
|
+
updated: 0,
|
|
190
|
+
failed: 1,
|
|
191
|
+
items: [],
|
|
192
|
+
errors: [
|
|
193
|
+
{
|
|
194
|
+
id: "root",
|
|
195
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
196
|
+
recoverable: false
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* 변경 감지
|
|
204
|
+
*/
|
|
205
|
+
async detectChanges(lastChecksum) {
|
|
206
|
+
const currentList = await this.listComponents();
|
|
207
|
+
const currentIds = new Set(currentList.map((c) => c.id));
|
|
208
|
+
const previousIds = new Set(Object.keys(lastChecksum));
|
|
209
|
+
const added = currentList.filter((c) => !previousIds.has(c.id)).map((c) => c.id);
|
|
210
|
+
const removed = [...previousIds].filter((id) => !currentIds.has(id));
|
|
211
|
+
const modified = [];
|
|
212
|
+
for (const item of currentList) {
|
|
213
|
+
if (previousIds.has(item.id)) {
|
|
214
|
+
try {
|
|
215
|
+
const component = await this.collectComponent(item.id);
|
|
216
|
+
if (component.checksum !== lastChecksum[item.id]) {
|
|
217
|
+
modified.push(item.id);
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
modified.push(item.id);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return { added, modified, removed };
|
|
225
|
+
}
|
|
226
|
+
// ============================================================================
|
|
227
|
+
// Protected Helpers
|
|
228
|
+
// ============================================================================
|
|
229
|
+
/**
|
|
230
|
+
* 카테고리 자동 분류
|
|
231
|
+
*/
|
|
232
|
+
classifyCategory(name, code) {
|
|
233
|
+
const searchText = `${name} ${code || ""}`.toLowerCase();
|
|
234
|
+
for (const rule of CATEGORY_RULES) {
|
|
235
|
+
for (const pattern of rule.patterns) {
|
|
236
|
+
if (pattern.test(searchText)) {
|
|
237
|
+
return rule.category;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return "ui";
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* 태그 자동 추출
|
|
245
|
+
*/
|
|
246
|
+
extractTags(name, code) {
|
|
247
|
+
const tags = [];
|
|
248
|
+
const searchText = code || "";
|
|
249
|
+
for (const { pattern, tag } of TAG_PATTERNS) {
|
|
250
|
+
if (pattern.test(searchText)) {
|
|
251
|
+
tags.push(tag);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const nameLower = name.toLowerCase();
|
|
255
|
+
if (nameLower.includes("button")) tags.push("interactive");
|
|
256
|
+
if (nameLower.includes("card")) tags.push("container");
|
|
257
|
+
if (nameLower.includes("input") || nameLower.includes("form")) tags.push("input");
|
|
258
|
+
return [...new Set(tags)];
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* 체크섬 계산
|
|
262
|
+
*/
|
|
263
|
+
calculateChecksum(content) {
|
|
264
|
+
return crypto.createHash("md5").update(content).digest("hex").slice(0, 8);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* 슬러그 생성
|
|
268
|
+
*/
|
|
269
|
+
generateSlug(name) {
|
|
270
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* ID 생성
|
|
274
|
+
*/
|
|
275
|
+
generateId(name) {
|
|
276
|
+
return `${this.sourceType}-${this.generateSlug(name)}`;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* 의존성 추출
|
|
280
|
+
*/
|
|
281
|
+
extractDependencies(code) {
|
|
282
|
+
const dependencies = [];
|
|
283
|
+
const devDependencies = [];
|
|
284
|
+
const registryDeps = [];
|
|
285
|
+
const importRegex = /from\s+['"]([^'"]+)['"]/g;
|
|
286
|
+
let match;
|
|
287
|
+
while ((match = importRegex.exec(code)) !== null) {
|
|
288
|
+
const pkg = match[1];
|
|
289
|
+
if (pkg.startsWith(".") || pkg.startsWith("@/")) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (pkg.startsWith("@axis") || pkg.includes("/ui/")) {
|
|
293
|
+
registryDeps.push(pkg);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const pkgName = pkg.startsWith("@") ? pkg.split("/").slice(0, 2).join("/") : pkg.split("/")[0];
|
|
297
|
+
if (pkgName === "react" || pkgName === "react-dom") {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (!dependencies.includes(pkgName)) {
|
|
301
|
+
dependencies.push(pkgName);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return { dependencies, devDependencies, registryDeps };
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* ComponentMeta 기본 구조 생성
|
|
308
|
+
*/
|
|
309
|
+
createBaseMeta(name, description, files, options = {}) {
|
|
310
|
+
const code = files.map((f) => f.content).join("\n");
|
|
311
|
+
const deps = this.extractDependencies(code);
|
|
312
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
313
|
+
return {
|
|
314
|
+
id: this.generateId(name),
|
|
315
|
+
slug: this.generateSlug(name),
|
|
316
|
+
name,
|
|
317
|
+
description,
|
|
318
|
+
source: {
|
|
319
|
+
type: this.sourceType,
|
|
320
|
+
registry: options.registry || `@${this.sourceType}`,
|
|
321
|
+
url: options.url || "",
|
|
322
|
+
version: options.version
|
|
323
|
+
},
|
|
324
|
+
category: this.classifyCategory(name, code),
|
|
325
|
+
tags: this.extractTags(name, code),
|
|
326
|
+
code: {
|
|
327
|
+
files,
|
|
328
|
+
dependencies: deps.dependencies,
|
|
329
|
+
devDependencies: deps.devDependencies,
|
|
330
|
+
registryDeps: deps.registryDeps
|
|
331
|
+
},
|
|
332
|
+
status: "active",
|
|
333
|
+
collectedAt: now,
|
|
334
|
+
updatedAt: now,
|
|
335
|
+
checksum: this.calculateChecksum(code)
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// src/library/shadcn-collector.ts
|
|
341
|
+
var ShadcnCollector = class extends BaseCollector {
|
|
342
|
+
sourceType = "shadcn";
|
|
343
|
+
baseUrl = "https://ui.shadcn.com";
|
|
344
|
+
registryUrl = "https://ui.shadcn.com/r";
|
|
345
|
+
constructor(options = {}) {
|
|
346
|
+
super();
|
|
347
|
+
if (options.baseUrl) {
|
|
348
|
+
this.baseUrl = options.baseUrl;
|
|
349
|
+
this.registryUrl = `${options.baseUrl}/r`;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* 컴포넌트 목록 조회
|
|
354
|
+
*/
|
|
355
|
+
async listComponents() {
|
|
356
|
+
try {
|
|
357
|
+
const response = await fetch(`${this.registryUrl}/index.json`);
|
|
358
|
+
if (!response.ok) {
|
|
359
|
+
return this.getDefaultComponentList();
|
|
360
|
+
}
|
|
361
|
+
const data = await response.json();
|
|
362
|
+
return data.items.map((item) => ({
|
|
363
|
+
id: `shadcn-${item.name}`,
|
|
364
|
+
name: item.name,
|
|
365
|
+
description: item.description || `shadcn/ui ${item.name} component`,
|
|
366
|
+
category: this.classifyCategory(item.name),
|
|
367
|
+
source: "shadcn"
|
|
368
|
+
}));
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.warn("shadcn \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC778\uB371\uC2A4 \uC870\uD68C \uC2E4\uD328, \uAE30\uBCF8 \uBAA9\uB85D \uC0AC\uC6A9:", error);
|
|
371
|
+
return this.getDefaultComponentList();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* 단일 컴포넌트 수집
|
|
376
|
+
*/
|
|
377
|
+
async collectComponent(id) {
|
|
378
|
+
const name = id.replace("shadcn-", "");
|
|
379
|
+
try {
|
|
380
|
+
const response = await fetch(`${this.registryUrl}/${name}.json`);
|
|
381
|
+
if (!response.ok) {
|
|
382
|
+
throw new Error(`Component not found: ${name}`);
|
|
383
|
+
}
|
|
384
|
+
const data = await response.json();
|
|
385
|
+
const files = data.files.map((f) => ({
|
|
386
|
+
path: f.target || f.path,
|
|
387
|
+
content: f.content,
|
|
388
|
+
type: this.getFileType(f.path)
|
|
389
|
+
}));
|
|
390
|
+
const meta = this.createBaseMeta(
|
|
391
|
+
data.name,
|
|
392
|
+
data.description || `shadcn/ui ${data.name} component`,
|
|
393
|
+
files,
|
|
394
|
+
{
|
|
395
|
+
registry: "@shadcn",
|
|
396
|
+
url: `${this.baseUrl}/docs/components/${name}`
|
|
397
|
+
}
|
|
398
|
+
);
|
|
399
|
+
if (data.dependencies) {
|
|
400
|
+
meta.code.dependencies = [
|
|
401
|
+
.../* @__PURE__ */ new Set([...meta.code.dependencies, ...data.dependencies])
|
|
402
|
+
];
|
|
403
|
+
}
|
|
404
|
+
if (data.devDependencies) {
|
|
405
|
+
meta.code.devDependencies = [
|
|
406
|
+
.../* @__PURE__ */ new Set([...meta.code.devDependencies, ...data.devDependencies])
|
|
407
|
+
];
|
|
408
|
+
}
|
|
409
|
+
if (data.registryDependencies) {
|
|
410
|
+
meta.code.registryDeps = [
|
|
411
|
+
.../* @__PURE__ */ new Set([...meta.code.registryDeps, ...data.registryDependencies])
|
|
412
|
+
];
|
|
413
|
+
}
|
|
414
|
+
return meta;
|
|
415
|
+
} catch (error) {
|
|
416
|
+
return this.createBaseMeta(name, `shadcn/ui ${name} component`, [], {
|
|
417
|
+
registry: "@shadcn",
|
|
418
|
+
url: `${this.baseUrl}/docs/components/${name}`
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* 기본 컴포넌트 목록 (레지스트리 인덱스 없을 때)
|
|
424
|
+
*/
|
|
425
|
+
getDefaultComponentList() {
|
|
426
|
+
const components = [
|
|
427
|
+
{ name: "accordion", desc: "Accordion component" },
|
|
428
|
+
{ name: "alert", desc: "Alert component" },
|
|
429
|
+
{ name: "alert-dialog", desc: "Alert dialog component" },
|
|
430
|
+
{ name: "aspect-ratio", desc: "Aspect ratio component" },
|
|
431
|
+
{ name: "avatar", desc: "Avatar component" },
|
|
432
|
+
{ name: "badge", desc: "Badge component" },
|
|
433
|
+
{ name: "breadcrumb", desc: "Breadcrumb component" },
|
|
434
|
+
{ name: "button", desc: "Button component" },
|
|
435
|
+
{ name: "calendar", desc: "Calendar component" },
|
|
436
|
+
{ name: "card", desc: "Card component" },
|
|
437
|
+
{ name: "carousel", desc: "Carousel component" },
|
|
438
|
+
{ name: "chart", desc: "Chart component" },
|
|
439
|
+
{ name: "checkbox", desc: "Checkbox component" },
|
|
440
|
+
{ name: "collapsible", desc: "Collapsible component" },
|
|
441
|
+
{ name: "command", desc: "Command component" },
|
|
442
|
+
{ name: "context-menu", desc: "Context menu component" },
|
|
443
|
+
{ name: "dialog", desc: "Dialog component" },
|
|
444
|
+
{ name: "drawer", desc: "Drawer component" },
|
|
445
|
+
{ name: "dropdown-menu", desc: "Dropdown menu component" },
|
|
446
|
+
{ name: "form", desc: "Form component" },
|
|
447
|
+
{ name: "hover-card", desc: "Hover card component" },
|
|
448
|
+
{ name: "input", desc: "Input component" },
|
|
449
|
+
{ name: "input-otp", desc: "Input OTP component" },
|
|
450
|
+
{ name: "label", desc: "Label component" },
|
|
451
|
+
{ name: "menubar", desc: "Menubar component" },
|
|
452
|
+
{ name: "navigation-menu", desc: "Navigation menu component" },
|
|
453
|
+
{ name: "pagination", desc: "Pagination component" },
|
|
454
|
+
{ name: "popover", desc: "Popover component" },
|
|
455
|
+
{ name: "progress", desc: "Progress component" },
|
|
456
|
+
{ name: "radio-group", desc: "Radio group component" },
|
|
457
|
+
{ name: "resizable", desc: "Resizable component" },
|
|
458
|
+
{ name: "scroll-area", desc: "Scroll area component" },
|
|
459
|
+
{ name: "select", desc: "Select component" },
|
|
460
|
+
{ name: "separator", desc: "Separator component" },
|
|
461
|
+
{ name: "sheet", desc: "Sheet component" },
|
|
462
|
+
{ name: "sidebar", desc: "Sidebar component" },
|
|
463
|
+
{ name: "skeleton", desc: "Skeleton component" },
|
|
464
|
+
{ name: "slider", desc: "Slider component" },
|
|
465
|
+
{ name: "sonner", desc: "Sonner toast component" },
|
|
466
|
+
{ name: "switch", desc: "Switch component" },
|
|
467
|
+
{ name: "table", desc: "Table component" },
|
|
468
|
+
{ name: "tabs", desc: "Tabs component" },
|
|
469
|
+
{ name: "textarea", desc: "Textarea component" },
|
|
470
|
+
{ name: "toast", desc: "Toast component" },
|
|
471
|
+
{ name: "toggle", desc: "Toggle component" },
|
|
472
|
+
{ name: "toggle-group", desc: "Toggle group component" },
|
|
473
|
+
{ name: "tooltip", desc: "Tooltip component" }
|
|
474
|
+
];
|
|
475
|
+
return components.map((c) => ({
|
|
476
|
+
id: `shadcn-${c.name}`,
|
|
477
|
+
name: c.name,
|
|
478
|
+
description: c.desc,
|
|
479
|
+
category: this.classifyCategory(c.name),
|
|
480
|
+
source: "shadcn"
|
|
481
|
+
}));
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* 파일 타입 추론
|
|
485
|
+
*/
|
|
486
|
+
getFileType(path10) {
|
|
487
|
+
if (path10.includes("hook") || path10.startsWith("use")) return "hook";
|
|
488
|
+
if (path10.includes("util") || path10.includes("lib")) return "util";
|
|
489
|
+
if (path10.endsWith(".css")) return "style";
|
|
490
|
+
if (path10.endsWith(".d.ts") || path10.includes("types")) return "type";
|
|
491
|
+
return "component";
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// src/library/monet-collector.ts
|
|
496
|
+
var MONET_CATEGORIES = [
|
|
497
|
+
{ id: "hero", name: "Hero Sections", count: 120, desc: "\uD788\uC5B4\uB85C \uC139\uC158 \uCEF4\uD3EC\uB10C\uD2B8" },
|
|
498
|
+
{ id: "feature", name: "Feature Sections", count: 191, desc: "\uAE30\uB2A5 \uC18C\uAC1C \uC139\uC158" },
|
|
499
|
+
{ id: "testimonial", name: "Testimonials", count: 47, desc: "\uACE0\uAC1D \uD6C4\uAE30 \uC139\uC158" },
|
|
500
|
+
{ id: "pricing", name: "Pricing Tables", count: 35, desc: "\uC694\uAE08\uC81C \uD14C\uC774\uBE14" },
|
|
501
|
+
{ id: "cta", name: "Call to Action", count: 42, desc: "CTA \uC139\uC158" },
|
|
502
|
+
{ id: "stats", name: "Statistics", count: 28, desc: "\uD1B5\uACC4 \uC139\uC158" },
|
|
503
|
+
{ id: "footer", name: "Footers", count: 38, desc: "\uD478\uD130 \uCEF4\uD3EC\uB10C\uD2B8" },
|
|
504
|
+
{ id: "header", name: "Headers/Navigation", count: 45, desc: "\uD5E4\uB354/\uB124\uBE44\uAC8C\uC774\uC158" },
|
|
505
|
+
{ id: "logo-cloud", name: "Logo Clouds", count: 22, desc: "\uB85C\uACE0 \uD074\uB77C\uC6B0\uB4DC" },
|
|
506
|
+
{ id: "team", name: "Team Sections", count: 31, desc: "\uD300 \uC18C\uAC1C \uC139\uC158" },
|
|
507
|
+
{ id: "faq", name: "FAQ Sections", count: 24, desc: "FAQ \uC139\uC158" },
|
|
508
|
+
{ id: "contact", name: "Contact Forms", count: 29, desc: "\uBB38\uC758 \uD3FC" },
|
|
509
|
+
{ id: "newsletter", name: "Newsletter", count: 18, desc: "\uB274\uC2A4\uB808\uD130 \uC139\uC158" },
|
|
510
|
+
{ id: "landing", name: "Full Landing Pages", count: 28, desc: "\uB79C\uB529 \uD398\uC774\uC9C0" }
|
|
511
|
+
];
|
|
512
|
+
var MONET_EXAMPLES = {
|
|
513
|
+
hero: [
|
|
514
|
+
{ id: "hero-with-image", name: "Hero with Image", desc: "\uC774\uBBF8\uC9C0 \uBC30\uACBD \uD788\uC5B4\uB85C" },
|
|
515
|
+
{ id: "hero-centered", name: "Hero Centered", desc: "\uC911\uC559 \uC815\uB82C \uD788\uC5B4\uB85C" },
|
|
516
|
+
{ id: "hero-split", name: "Hero Split", desc: "\uBD84\uD560 \uB808\uC774\uC544\uC6C3 \uD788\uC5B4\uB85C" },
|
|
517
|
+
{ id: "hero-gradient", name: "Hero Gradient", desc: "\uADF8\uB77C\uB370\uC774\uC158 \uD788\uC5B4\uB85C" }
|
|
518
|
+
],
|
|
519
|
+
feature: [
|
|
520
|
+
{ id: "feature-grid", name: "Feature Grid", desc: "\uADF8\uB9AC\uB4DC \uD615\uC2DD \uAE30\uB2A5 \uC18C\uAC1C" },
|
|
521
|
+
{ id: "feature-alternating", name: "Feature Alternating", desc: "\uAD50\uCC28 \uB808\uC774\uC544\uC6C3" },
|
|
522
|
+
{ id: "feature-icons", name: "Feature Icons", desc: "\uC544\uC774\uCF58 \uAE30\uBC18 \uAE30\uB2A5" },
|
|
523
|
+
{ id: "feature-cards", name: "Feature Cards", desc: "\uCE74\uB4DC \uD615\uC2DD \uAE30\uB2A5" }
|
|
524
|
+
],
|
|
525
|
+
pricing: [
|
|
526
|
+
{ id: "pricing-three-tiers", name: "Pricing Three Tiers", desc: "3\uB2E8 \uC694\uAE08\uC81C" },
|
|
527
|
+
{ id: "pricing-comparison", name: "Pricing Comparison", desc: "\uBE44\uAD50 \uD14C\uC774\uBE14" },
|
|
528
|
+
{ id: "pricing-toggle", name: "Pricing Toggle", desc: "\uC6D4/\uC5F0 \uD1A0\uAE00 \uC694\uAE08\uC81C" }
|
|
529
|
+
],
|
|
530
|
+
landing: [
|
|
531
|
+
{ id: "deepcon-ai-landing", name: "Deepcon AI Landing", desc: "AI SaaS \uB79C\uB529" },
|
|
532
|
+
{ id: "saas-landing", name: "SaaS Landing", desc: "SaaS \uC2A4\uD0C0\uD130 \uB79C\uB529" },
|
|
533
|
+
{ id: "agency-landing", name: "Agency Landing", desc: "\uC5D0\uC774\uC804\uC2DC \uB79C\uB529" }
|
|
534
|
+
],
|
|
535
|
+
testimonial: [
|
|
536
|
+
{ id: "testimonial-carousel", name: "Testimonial Carousel", desc: "\uCE90\uB7EC\uC140 \uD6C4\uAE30" },
|
|
537
|
+
{ id: "testimonial-grid", name: "Testimonial Grid", desc: "\uADF8\uB9AC\uB4DC \uD6C4\uAE30" },
|
|
538
|
+
{ id: "testimonial-quote", name: "Testimonial Quote", desc: "\uC778\uC6A9\uAD6C \uD6C4\uAE30" }
|
|
539
|
+
],
|
|
540
|
+
cta: [
|
|
541
|
+
{ id: "cta-simple", name: "CTA Simple", desc: "\uC2EC\uD50C CTA" },
|
|
542
|
+
{ id: "cta-with-image", name: "CTA with Image", desc: "\uC774\uBBF8\uC9C0 CTA" },
|
|
543
|
+
{ id: "cta-newsletter", name: "CTA Newsletter", desc: "\uB274\uC2A4\uB808\uD130 CTA" }
|
|
544
|
+
]
|
|
545
|
+
};
|
|
546
|
+
var MonetCollector = class extends BaseCollector {
|
|
547
|
+
sourceType = "monet";
|
|
548
|
+
baseUrl = "https://www.monet.design";
|
|
549
|
+
registryUrl = "https://registry.monet.design";
|
|
550
|
+
constructor(options = {}) {
|
|
551
|
+
super();
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* 컴포넌트 목록 조회
|
|
555
|
+
* Monet은 공개 API가 제한적이므로 정적 메타데이터 반환
|
|
556
|
+
*/
|
|
557
|
+
async listComponents() {
|
|
558
|
+
const items = [];
|
|
559
|
+
for (const category of MONET_CATEGORIES) {
|
|
560
|
+
const examples = MONET_EXAMPLES[category.id] || [];
|
|
561
|
+
for (const example of examples) {
|
|
562
|
+
items.push({
|
|
563
|
+
id: `monet-${example.id}`,
|
|
564
|
+
name: example.name,
|
|
565
|
+
description: example.desc,
|
|
566
|
+
category: this.mapMonetCategory(category.id),
|
|
567
|
+
source: "monet"
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
items.push({
|
|
571
|
+
id: `monet-${category.id}`,
|
|
572
|
+
name: category.name,
|
|
573
|
+
description: `${category.desc} (${category.count}\uAC1C \uCEF4\uD3EC\uB10C\uD2B8)`,
|
|
574
|
+
category: this.mapMonetCategory(category.id),
|
|
575
|
+
source: "monet"
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
return items;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* 단일 컴포넌트 수집
|
|
582
|
+
* Monet API 제한으로 메타데이터만 반환
|
|
583
|
+
*/
|
|
584
|
+
async collectComponent(id) {
|
|
585
|
+
const name = id.replace("monet-", "");
|
|
586
|
+
const category = MONET_CATEGORIES.find((c) => c.id === name);
|
|
587
|
+
let example;
|
|
588
|
+
for (const [catId, examples] of Object.entries(MONET_EXAMPLES)) {
|
|
589
|
+
const found = examples.find((e) => e.id === name);
|
|
590
|
+
if (found) {
|
|
591
|
+
example = found;
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const displayName = example?.name || category?.name || name;
|
|
596
|
+
const description = example?.desc || category?.desc || `Monet ${name} component`;
|
|
597
|
+
const monetCategory = category?.id || this.findCategoryForExample(name);
|
|
598
|
+
return this.createBaseMeta(displayName, description, [], {
|
|
599
|
+
registry: "@monet",
|
|
600
|
+
url: `${this.baseUrl}/c?category=${monetCategory || name}`
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Monet 카테고리를 AXIS 카테고리로 매핑
|
|
605
|
+
*/
|
|
606
|
+
mapMonetCategory(monetCategory) {
|
|
607
|
+
const mapping = {
|
|
608
|
+
hero: "layout",
|
|
609
|
+
feature: "data-display",
|
|
610
|
+
testimonial: "data-display",
|
|
611
|
+
pricing: "data-display",
|
|
612
|
+
cta: "ui",
|
|
613
|
+
stats: "data-display",
|
|
614
|
+
footer: "navigation",
|
|
615
|
+
header: "navigation",
|
|
616
|
+
"logo-cloud": "data-display",
|
|
617
|
+
team: "data-display",
|
|
618
|
+
faq: "data-display",
|
|
619
|
+
contact: "form",
|
|
620
|
+
newsletter: "form",
|
|
621
|
+
landing: "layout"
|
|
622
|
+
};
|
|
623
|
+
return mapping[monetCategory] || "ui";
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* 예시 컴포넌트의 카테고리 찾기
|
|
627
|
+
*/
|
|
628
|
+
findCategoryForExample(exampleId) {
|
|
629
|
+
for (const [catId, examples] of Object.entries(MONET_EXAMPLES)) {
|
|
630
|
+
if (examples.some((e) => e.id === exampleId)) {
|
|
631
|
+
return catId;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return void 0;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* 레지스트리 URL에서 컴포넌트 정보 조회 시도
|
|
638
|
+
*/
|
|
639
|
+
async tryFetchFromRegistry(name) {
|
|
640
|
+
try {
|
|
641
|
+
const response = await fetch(`${this.registryUrl}/${name}.json`);
|
|
642
|
+
if (response.ok) {
|
|
643
|
+
return await response.json();
|
|
644
|
+
}
|
|
645
|
+
} catch {
|
|
646
|
+
}
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* 카테고리 목록 반환
|
|
651
|
+
*/
|
|
652
|
+
getCategories() {
|
|
653
|
+
return MONET_CATEGORIES;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* 카테고리별 컴포넌트 수 반환
|
|
657
|
+
*/
|
|
658
|
+
getCategoryCount(categoryId) {
|
|
659
|
+
const category = MONET_CATEGORIES.find((c) => c.id === categoryId);
|
|
660
|
+
return category?.count || 0;
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
// src/library/v0-collector.ts
|
|
665
|
+
import fs from "fs-extra";
|
|
666
|
+
import path from "path";
|
|
667
|
+
var V0_PATTERNS = [
|
|
668
|
+
/@\/components\/ui\//,
|
|
669
|
+
/from ['"]@\/lib\/utils['"]/,
|
|
670
|
+
/from ['"]lucide-react['"]/
|
|
671
|
+
];
|
|
672
|
+
var V0Collector = class extends BaseCollector {
|
|
673
|
+
sourceType = "v0";
|
|
674
|
+
v0Directory;
|
|
675
|
+
convertToAxis;
|
|
676
|
+
constructor(options = {}) {
|
|
677
|
+
super();
|
|
678
|
+
this.v0Directory = options.directory || "./src/components/v0";
|
|
679
|
+
this.convertToAxis = options.convertToAxis ?? true;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* V0 디렉토리에서 컴포넌트 목록 조회
|
|
683
|
+
*/
|
|
684
|
+
async listComponents() {
|
|
685
|
+
const items = [];
|
|
686
|
+
if (!await fs.pathExists(this.v0Directory)) {
|
|
687
|
+
return items;
|
|
688
|
+
}
|
|
689
|
+
const files = await this.scanDirectory(this.v0Directory);
|
|
690
|
+
for (const file of files) {
|
|
691
|
+
const content = await fs.readFile(file, "utf-8");
|
|
692
|
+
if (this.isV0Code(content)) {
|
|
693
|
+
const name = path.basename(file, path.extname(file));
|
|
694
|
+
const relativePath = path.relative(this.v0Directory, file);
|
|
695
|
+
items.push({
|
|
696
|
+
id: `v0-${this.generateSlug(name)}`,
|
|
697
|
+
name: this.formatName(name),
|
|
698
|
+
description: this.extractDescription(content) || `V0 generated ${name} component`,
|
|
699
|
+
category: this.classifyCategory(name, content),
|
|
700
|
+
source: "v0"
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return items;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* 단일 컴포넌트 수집
|
|
708
|
+
*/
|
|
709
|
+
async collectComponent(id) {
|
|
710
|
+
const slug = id.replace("v0-", "");
|
|
711
|
+
const files = await this.findComponentFiles(slug);
|
|
712
|
+
if (files.length === 0) {
|
|
713
|
+
throw new Error(`V0 component not found: ${slug}`);
|
|
714
|
+
}
|
|
715
|
+
const codeFiles = [];
|
|
716
|
+
for (const filePath of files) {
|
|
717
|
+
let content = await fs.readFile(filePath, "utf-8");
|
|
718
|
+
if (this.convertToAxis) {
|
|
719
|
+
content = this.convertCode(content);
|
|
720
|
+
}
|
|
721
|
+
codeFiles.push({
|
|
722
|
+
path: path.relative(this.v0Directory, filePath),
|
|
723
|
+
content,
|
|
724
|
+
type: this.getFileType(filePath)
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
const mainContent = codeFiles.map((f) => f.content).join("\n");
|
|
728
|
+
const name = this.formatName(slug);
|
|
729
|
+
return this.createBaseMeta(
|
|
730
|
+
name,
|
|
731
|
+
this.extractDescription(mainContent) || `V0 generated ${name} component`,
|
|
732
|
+
codeFiles,
|
|
733
|
+
{
|
|
734
|
+
registry: "@v0",
|
|
735
|
+
url: "https://v0.app"
|
|
736
|
+
}
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* V0 코드인지 확인
|
|
741
|
+
*/
|
|
742
|
+
isV0Code(code) {
|
|
743
|
+
return V0_PATTERNS.some((pattern) => pattern.test(code));
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* V0 코드를 AXIS 스타일로 변환
|
|
747
|
+
*/
|
|
748
|
+
convertCode(code) {
|
|
749
|
+
let result = code;
|
|
750
|
+
const conversions = [
|
|
751
|
+
{ from: /@\/components\/ui\/button/g, to: "@axis-ds/ui-react/button" },
|
|
752
|
+
{ from: /@\/components\/ui\/card/g, to: "@axis-ds/ui-react/card" },
|
|
753
|
+
{ from: /@\/components\/ui\/input/g, to: "@axis-ds/ui-react/input" },
|
|
754
|
+
{ from: /@\/components\/ui\/badge/g, to: "@axis-ds/ui-react/badge" },
|
|
755
|
+
{ from: /@\/components\/ui\/dialog/g, to: "@axis-ds/ui-react/dialog" },
|
|
756
|
+
{ from: /@\/components\/ui\/select/g, to: "@axis-ds/ui-react/select" },
|
|
757
|
+
{ from: /@\/components\/ui\/tabs/g, to: "@axis-ds/ui-react/tabs" },
|
|
758
|
+
{ from: /@\/components\/ui\/label/g, to: "@axis-ds/ui-react/label" },
|
|
759
|
+
{ from: /@\/components\/ui\/separator/g, to: "@axis-ds/ui-react/separator" },
|
|
760
|
+
{ from: /@\/components\/ui\/toast/g, to: "@axis-ds/ui-react/toast" },
|
|
761
|
+
// 일반 패턴
|
|
762
|
+
{ from: /@\/components\/ui\/([a-z-]+)/g, to: "@axis-ds/ui-react/$1" }
|
|
763
|
+
];
|
|
764
|
+
for (const { from, to } of conversions) {
|
|
765
|
+
result = result.replace(from, to);
|
|
766
|
+
}
|
|
767
|
+
if (result.includes("cn(") && !result.includes('from "@/lib/utils"')) {
|
|
768
|
+
result = `import { cn } from "@/lib/utils";
|
|
769
|
+
${result}`;
|
|
770
|
+
}
|
|
771
|
+
return result;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* 디렉토리 스캔
|
|
775
|
+
*/
|
|
776
|
+
async scanDirectory(dir) {
|
|
777
|
+
const files = [];
|
|
778
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
779
|
+
for (const entry of entries) {
|
|
780
|
+
const fullPath = path.join(dir, entry.name);
|
|
781
|
+
if (entry.isDirectory()) {
|
|
782
|
+
if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
783
|
+
files.push(...await this.scanDirectory(fullPath));
|
|
784
|
+
}
|
|
785
|
+
} else if (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) {
|
|
786
|
+
files.push(fullPath);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return files;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* 컴포넌트 파일 찾기
|
|
793
|
+
*/
|
|
794
|
+
async findComponentFiles(slug) {
|
|
795
|
+
const files = [];
|
|
796
|
+
const possiblePaths = [
|
|
797
|
+
path.join(this.v0Directory, `${slug}.tsx`),
|
|
798
|
+
path.join(this.v0Directory, slug, `index.tsx`),
|
|
799
|
+
path.join(this.v0Directory, slug, `${slug}.tsx`)
|
|
800
|
+
];
|
|
801
|
+
for (const filePath of possiblePaths) {
|
|
802
|
+
if (await fs.pathExists(filePath)) {
|
|
803
|
+
files.push(filePath);
|
|
804
|
+
const dir = path.dirname(filePath);
|
|
805
|
+
if (dir !== this.v0Directory) {
|
|
806
|
+
const dirFiles = await fs.readdir(dir);
|
|
807
|
+
for (const f of dirFiles) {
|
|
808
|
+
if ((f.endsWith(".tsx") || f.endsWith(".ts")) && !files.includes(path.join(dir, f))) {
|
|
809
|
+
files.push(path.join(dir, f));
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
break;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return files;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* 설명 추출 (JSDoc 주석에서)
|
|
820
|
+
*/
|
|
821
|
+
extractDescription(code) {
|
|
822
|
+
const jsdocMatch = code.match(/\/\*\*\s*\n\s*\*\s*(.+)\n/);
|
|
823
|
+
if (jsdocMatch) {
|
|
824
|
+
return jsdocMatch[1].trim();
|
|
825
|
+
}
|
|
826
|
+
const commentMatch = code.match(/^\/\/\s*(.+)$/m);
|
|
827
|
+
if (commentMatch) {
|
|
828
|
+
return commentMatch[1].trim();
|
|
829
|
+
}
|
|
830
|
+
return void 0;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* 이름 포맷팅
|
|
834
|
+
*/
|
|
835
|
+
formatName(slug) {
|
|
836
|
+
return slug.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* 파일 타입 추론
|
|
840
|
+
*/
|
|
841
|
+
getFileType(filePath) {
|
|
842
|
+
const name = path.basename(filePath);
|
|
843
|
+
if (name.startsWith("use") || name.includes("hook")) return "hook";
|
|
844
|
+
if (name.includes("util") || name.includes("lib")) return "util";
|
|
845
|
+
if (name.endsWith(".css")) return "style";
|
|
846
|
+
if (name.endsWith(".d.ts") || name.includes("types")) return "type";
|
|
847
|
+
return "component";
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* V0 디렉토리 설정
|
|
851
|
+
*/
|
|
852
|
+
setDirectory(directory) {
|
|
853
|
+
this.v0Directory = directory;
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* AXIS 변환 설정
|
|
857
|
+
*/
|
|
858
|
+
setConvertToAxis(convert) {
|
|
859
|
+
this.convertToAxis = convert;
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
// src/library/axis-collector.ts
|
|
864
|
+
import fs2 from "fs-extra";
|
|
865
|
+
import path2 from "path";
|
|
866
|
+
var DEFAULT_PATHS = {
|
|
867
|
+
uiReact: "packages/axis-ui-react/src",
|
|
868
|
+
agenticUi: "packages/axis-agentic-ui/src",
|
|
869
|
+
webComponents: "apps/web/src/app/components",
|
|
870
|
+
webAgentic: "apps/web/src/app/agentic"
|
|
871
|
+
};
|
|
872
|
+
var AxisCollector = class extends BaseCollector {
|
|
873
|
+
sourceType = "axis";
|
|
874
|
+
rootDir;
|
|
875
|
+
paths;
|
|
876
|
+
constructor(options = {}) {
|
|
877
|
+
super();
|
|
878
|
+
this.rootDir = options.rootDir || process.cwd();
|
|
879
|
+
this.paths = { ...DEFAULT_PATHS, ...options.paths };
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* AXIS 컴포넌트 목록 조회
|
|
883
|
+
*/
|
|
884
|
+
async listComponents() {
|
|
885
|
+
const items = [];
|
|
886
|
+
items.push(...await this.scanPackage(this.paths.uiReact, "ui", "@axis-ds/ui-react"));
|
|
887
|
+
items.push(...await this.scanPackage(this.paths.agenticUi, "agentic", "@axis-ds/agentic-ui"));
|
|
888
|
+
items.push(...await this.scanWebDirectory(this.paths.webComponents, "ui"));
|
|
889
|
+
items.push(...await this.scanWebDirectory(this.paths.webAgentic, "agentic"));
|
|
890
|
+
const uniqueItems = /* @__PURE__ */ new Map();
|
|
891
|
+
for (const item of items) {
|
|
892
|
+
if (!uniqueItems.has(item.id)) {
|
|
893
|
+
uniqueItems.set(item.id, item);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return [...uniqueItems.values()];
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* 단일 컴포넌트 수집
|
|
900
|
+
*/
|
|
901
|
+
async collectComponent(id) {
|
|
902
|
+
const slug = id.replace("axis-", "");
|
|
903
|
+
const componentInfo = await this.findComponent(slug);
|
|
904
|
+
if (!componentInfo) {
|
|
905
|
+
throw new Error(`AXIS component not found: ${slug}`);
|
|
906
|
+
}
|
|
907
|
+
const codeFiles = [];
|
|
908
|
+
for (const filePath of componentInfo.files) {
|
|
909
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
910
|
+
codeFiles.push({
|
|
911
|
+
path: path2.relative(componentInfo.baseDir, filePath),
|
|
912
|
+
content,
|
|
913
|
+
type: this.getFileType(filePath)
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
const mainContent = codeFiles.map((f) => f.content).join("\n");
|
|
917
|
+
const meta = this.createBaseMeta(
|
|
918
|
+
componentInfo.name,
|
|
919
|
+
this.extractDescription(mainContent) || `AXIS ${componentInfo.name} component`,
|
|
920
|
+
codeFiles,
|
|
921
|
+
{
|
|
922
|
+
registry: "@axis",
|
|
923
|
+
url: this.getDocsUrl(slug, componentInfo.category)
|
|
924
|
+
}
|
|
925
|
+
);
|
|
926
|
+
meta.category = componentInfo.category;
|
|
927
|
+
return meta;
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* 패키지 디렉토리 스캔
|
|
931
|
+
*/
|
|
932
|
+
async scanPackage(packagePath, defaultCategory, registry) {
|
|
933
|
+
const items = [];
|
|
934
|
+
const fullPath = path2.join(this.rootDir, packagePath);
|
|
935
|
+
if (!await fs2.pathExists(fullPath)) {
|
|
936
|
+
return items;
|
|
937
|
+
}
|
|
938
|
+
const entries = await fs2.readdir(fullPath, { withFileTypes: true });
|
|
939
|
+
for (const entry of entries) {
|
|
940
|
+
if (entry.isDirectory()) {
|
|
941
|
+
const componentDir = path2.join(fullPath, entry.name);
|
|
942
|
+
const indexFile = path2.join(componentDir, "index.tsx");
|
|
943
|
+
if (await fs2.pathExists(indexFile)) {
|
|
944
|
+
const content = await fs2.readFile(indexFile, "utf-8");
|
|
945
|
+
const name = this.formatName(entry.name);
|
|
946
|
+
items.push({
|
|
947
|
+
id: `axis-${entry.name}`,
|
|
948
|
+
name,
|
|
949
|
+
description: this.extractDescription(content) || `AXIS ${name} component`,
|
|
950
|
+
category: this.classifyCategory(entry.name, content) || defaultCategory,
|
|
951
|
+
source: "axis"
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
} else if (entry.name.endsWith(".tsx") && entry.name !== "index.tsx") {
|
|
955
|
+
const name = entry.name.replace(".tsx", "");
|
|
956
|
+
const filePath = path2.join(fullPath, entry.name);
|
|
957
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
958
|
+
items.push({
|
|
959
|
+
id: `axis-${name}`,
|
|
960
|
+
name: this.formatName(name),
|
|
961
|
+
description: this.extractDescription(content) || `AXIS ${name} component`,
|
|
962
|
+
category: this.classifyCategory(name, content) || defaultCategory,
|
|
963
|
+
source: "axis"
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return items;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Web 디렉토리 스캔 (app router 구조)
|
|
971
|
+
*/
|
|
972
|
+
async scanWebDirectory(webPath, defaultCategory) {
|
|
973
|
+
const items = [];
|
|
974
|
+
const fullPath = path2.join(this.rootDir, webPath);
|
|
975
|
+
if (!await fs2.pathExists(fullPath)) {
|
|
976
|
+
return items;
|
|
977
|
+
}
|
|
978
|
+
const entries = await fs2.readdir(fullPath, { withFileTypes: true });
|
|
979
|
+
for (const entry of entries) {
|
|
980
|
+
if (entry.isDirectory()) {
|
|
981
|
+
const componentDir = path2.join(fullPath, entry.name);
|
|
982
|
+
const pageFile = path2.join(componentDir, "page.tsx");
|
|
983
|
+
if (await fs2.pathExists(pageFile)) {
|
|
984
|
+
const content = await fs2.readFile(pageFile, "utf-8");
|
|
985
|
+
const name = this.formatName(entry.name);
|
|
986
|
+
items.push({
|
|
987
|
+
id: `axis-${entry.name}`,
|
|
988
|
+
name,
|
|
989
|
+
description: this.extractDescription(content) || `AXIS ${name} component`,
|
|
990
|
+
category: this.classifyCategory(entry.name, content) || defaultCategory,
|
|
991
|
+
source: "axis"
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return items;
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* 컴포넌트 찾기
|
|
1000
|
+
*/
|
|
1001
|
+
async findComponent(slug) {
|
|
1002
|
+
const searchPaths = [
|
|
1003
|
+
{ base: this.paths.uiReact, category: "ui" },
|
|
1004
|
+
{ base: this.paths.agenticUi, category: "agentic" },
|
|
1005
|
+
{ base: this.paths.webComponents, category: "ui" },
|
|
1006
|
+
{ base: this.paths.webAgentic, category: "agentic" }
|
|
1007
|
+
];
|
|
1008
|
+
for (const { base, category } of searchPaths) {
|
|
1009
|
+
const fullBase = path2.join(this.rootDir, base);
|
|
1010
|
+
const dirPath = path2.join(fullBase, slug);
|
|
1011
|
+
if (await fs2.pathExists(dirPath)) {
|
|
1012
|
+
const files = await this.getComponentFiles(dirPath);
|
|
1013
|
+
if (files.length > 0) {
|
|
1014
|
+
return {
|
|
1015
|
+
name: this.formatName(slug),
|
|
1016
|
+
files,
|
|
1017
|
+
baseDir: dirPath,
|
|
1018
|
+
category
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
const filePath = path2.join(fullBase, `${slug}.tsx`);
|
|
1023
|
+
if (await fs2.pathExists(filePath)) {
|
|
1024
|
+
return {
|
|
1025
|
+
name: this.formatName(slug),
|
|
1026
|
+
files: [filePath],
|
|
1027
|
+
baseDir: fullBase,
|
|
1028
|
+
category
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* 컴포넌트 디렉토리에서 파일 목록 가져오기
|
|
1036
|
+
*/
|
|
1037
|
+
async getComponentFiles(dir) {
|
|
1038
|
+
const files = [];
|
|
1039
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
1040
|
+
for (const entry of entries) {
|
|
1041
|
+
if (entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts"))) {
|
|
1042
|
+
files.push(path2.join(dir, entry.name));
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return files;
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* 설명 추출
|
|
1049
|
+
*/
|
|
1050
|
+
extractDescription(code) {
|
|
1051
|
+
const jsdocMatch = code.match(/\/\*\*\s*\n\s*\*\s*(.+)\n/);
|
|
1052
|
+
if (jsdocMatch) {
|
|
1053
|
+
return jsdocMatch[1].trim();
|
|
1054
|
+
}
|
|
1055
|
+
const commentMatch = code.match(/^\/\/\s*(.+)$/m);
|
|
1056
|
+
if (commentMatch) {
|
|
1057
|
+
return commentMatch[1].trim();
|
|
1058
|
+
}
|
|
1059
|
+
return void 0;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* 이름 포맷팅
|
|
1063
|
+
*/
|
|
1064
|
+
formatName(slug) {
|
|
1065
|
+
return slug.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* 파일 타입 추론
|
|
1069
|
+
*/
|
|
1070
|
+
getFileType(filePath) {
|
|
1071
|
+
const name = path2.basename(filePath);
|
|
1072
|
+
if (name.startsWith("use") || name.includes("hook")) return "hook";
|
|
1073
|
+
if (name.includes("util") || name.includes("lib")) return "util";
|
|
1074
|
+
if (name.endsWith(".css")) return "style";
|
|
1075
|
+
if (name.endsWith(".d.ts") || name.includes("types")) return "type";
|
|
1076
|
+
return "component";
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* 문서 URL 생성
|
|
1080
|
+
*/
|
|
1081
|
+
getDocsUrl(slug, category) {
|
|
1082
|
+
const baseUrl = "https://axis.minu.best";
|
|
1083
|
+
return `${baseUrl}/${category}/${slug}`;
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
// src/library/publisher.ts
|
|
1088
|
+
import fs3 from "fs-extra";
|
|
1089
|
+
import path3 from "path";
|
|
1090
|
+
import chalk from "chalk";
|
|
1091
|
+
import ora from "ora";
|
|
1092
|
+
var Publisher = class {
|
|
1093
|
+
defaultOutputDir = "apps/web/public/library";
|
|
1094
|
+
/**
|
|
1095
|
+
* 라이브러리 인덱스를 사이트에 배치
|
|
1096
|
+
*/
|
|
1097
|
+
async publish(index, options = {}) {
|
|
1098
|
+
const outputDir = options.outputDir || this.defaultOutputDir;
|
|
1099
|
+
const files = [];
|
|
1100
|
+
const errors = [];
|
|
1101
|
+
const spinner = ora("\uB77C\uC774\uBE0C\uB7EC\uB9AC \uBC30\uCE58 \uC911...").start();
|
|
1102
|
+
try {
|
|
1103
|
+
await fs3.ensureDir(outputDir);
|
|
1104
|
+
const indexPath = path3.join(outputDir, "index.json");
|
|
1105
|
+
await this.writeJson(indexPath, this.createPublicIndex(index), options.minify);
|
|
1106
|
+
files.push(indexPath);
|
|
1107
|
+
const categoriesDir = path3.join(outputDir, "categories");
|
|
1108
|
+
await fs3.ensureDir(categoriesDir);
|
|
1109
|
+
for (const category of index.categories) {
|
|
1110
|
+
const categoryComponents = index.components.filter((c) => c.category === category.id);
|
|
1111
|
+
const categoryPath = path3.join(categoriesDir, `${category.id}.json`);
|
|
1112
|
+
await this.writeJson(
|
|
1113
|
+
categoryPath,
|
|
1114
|
+
{
|
|
1115
|
+
category: category.id,
|
|
1116
|
+
name: category.name,
|
|
1117
|
+
description: category.description,
|
|
1118
|
+
icon: category.icon,
|
|
1119
|
+
count: categoryComponents.length,
|
|
1120
|
+
components: categoryComponents.map((c) => this.createPublicComponent(c))
|
|
1121
|
+
},
|
|
1122
|
+
options.minify
|
|
1123
|
+
);
|
|
1124
|
+
files.push(categoryPath);
|
|
1125
|
+
}
|
|
1126
|
+
const componentsDir = path3.join(outputDir, "components");
|
|
1127
|
+
await fs3.ensureDir(componentsDir);
|
|
1128
|
+
for (const component of index.components) {
|
|
1129
|
+
const componentPath = path3.join(componentsDir, `${component.slug}.json`);
|
|
1130
|
+
await this.writeJson(componentPath, this.createPublicComponent(component), options.minify);
|
|
1131
|
+
files.push(componentPath);
|
|
1132
|
+
}
|
|
1133
|
+
let searchIndexSize = 0;
|
|
1134
|
+
if (options.generateSearchIndex !== false) {
|
|
1135
|
+
const searchIndex = this.generateSearchIndex(index);
|
|
1136
|
+
const searchPath = path3.join(outputDir, "search-index.json");
|
|
1137
|
+
await this.writeJson(searchPath, searchIndex, options.minify);
|
|
1138
|
+
files.push(searchPath);
|
|
1139
|
+
searchIndexSize = searchIndex.length;
|
|
1140
|
+
}
|
|
1141
|
+
const metaPath = path3.join(outputDir, "meta.json");
|
|
1142
|
+
await this.writeJson(
|
|
1143
|
+
metaPath,
|
|
1144
|
+
{
|
|
1145
|
+
version: index.version,
|
|
1146
|
+
updatedAt: index.updatedAt,
|
|
1147
|
+
stats: index.stats,
|
|
1148
|
+
categories: index.categories,
|
|
1149
|
+
tags: index.tags.slice(0, 50)
|
|
1150
|
+
// 상위 50개 태그만
|
|
1151
|
+
},
|
|
1152
|
+
options.minify
|
|
1153
|
+
);
|
|
1154
|
+
files.push(metaPath);
|
|
1155
|
+
spinner.succeed(`\uB77C\uC774\uBE0C\uB7EC\uB9AC \uBC30\uCE58 \uC644\uB8CC: ${files.length}\uAC1C \uD30C\uC77C`);
|
|
1156
|
+
return {
|
|
1157
|
+
success: true,
|
|
1158
|
+
outputDir,
|
|
1159
|
+
files,
|
|
1160
|
+
stats: {
|
|
1161
|
+
totalComponents: index.components.length,
|
|
1162
|
+
categoriesPublished: index.categories.length,
|
|
1163
|
+
searchIndexSize
|
|
1164
|
+
},
|
|
1165
|
+
errors
|
|
1166
|
+
};
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
spinner.fail("\uB77C\uC774\uBE0C\uB7EC\uB9AC \uBC30\uCE58 \uC2E4\uD328");
|
|
1169
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1170
|
+
errors.push(errorMessage);
|
|
1171
|
+
return {
|
|
1172
|
+
success: false,
|
|
1173
|
+
outputDir,
|
|
1174
|
+
files,
|
|
1175
|
+
stats: {
|
|
1176
|
+
totalComponents: 0,
|
|
1177
|
+
categoriesPublished: 0,
|
|
1178
|
+
searchIndexSize: 0
|
|
1179
|
+
},
|
|
1180
|
+
errors
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* 공개용 인덱스 생성 (코드 제외)
|
|
1186
|
+
*/
|
|
1187
|
+
createPublicIndex(index) {
|
|
1188
|
+
return {
|
|
1189
|
+
version: index.version,
|
|
1190
|
+
updatedAt: index.updatedAt,
|
|
1191
|
+
stats: index.stats,
|
|
1192
|
+
categories: index.categories,
|
|
1193
|
+
components: index.components.map((c) => ({
|
|
1194
|
+
id: c.id,
|
|
1195
|
+
slug: c.slug,
|
|
1196
|
+
name: c.name,
|
|
1197
|
+
description: c.description,
|
|
1198
|
+
category: c.category,
|
|
1199
|
+
source: c.source.type,
|
|
1200
|
+
tags: c.tags,
|
|
1201
|
+
status: c.status
|
|
1202
|
+
}))
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* 공개용 컴포넌트 생성
|
|
1207
|
+
*/
|
|
1208
|
+
createPublicComponent(component) {
|
|
1209
|
+
return {
|
|
1210
|
+
id: component.id,
|
|
1211
|
+
slug: component.slug,
|
|
1212
|
+
name: component.name,
|
|
1213
|
+
description: component.description,
|
|
1214
|
+
category: component.category,
|
|
1215
|
+
source: {
|
|
1216
|
+
type: component.source.type,
|
|
1217
|
+
registry: component.source.registry,
|
|
1218
|
+
url: component.source.url
|
|
1219
|
+
},
|
|
1220
|
+
tags: component.tags,
|
|
1221
|
+
code: component.code,
|
|
1222
|
+
preview: component.preview,
|
|
1223
|
+
dependencies: component.dependencies,
|
|
1224
|
+
status: component.status,
|
|
1225
|
+
updatedAt: component.updatedAt
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* 검색 인덱스 생성
|
|
1230
|
+
*/
|
|
1231
|
+
generateSearchIndex(index) {
|
|
1232
|
+
return index.components.map((c) => ({
|
|
1233
|
+
id: c.id,
|
|
1234
|
+
slug: c.slug,
|
|
1235
|
+
name: c.name,
|
|
1236
|
+
description: c.description,
|
|
1237
|
+
category: c.category,
|
|
1238
|
+
source: c.source.type,
|
|
1239
|
+
tags: c.tags,
|
|
1240
|
+
url: `/library/${c.category}/${c.slug}`
|
|
1241
|
+
}));
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* JSON 파일 쓰기
|
|
1245
|
+
*/
|
|
1246
|
+
async writeJson(filePath, data, minify) {
|
|
1247
|
+
const content = minify ? JSON.stringify(data) : JSON.stringify(data, null, 2);
|
|
1248
|
+
await fs3.writeFile(filePath, content, "utf-8");
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* 기존 배치 정리
|
|
1252
|
+
*/
|
|
1253
|
+
async clean(outputDir) {
|
|
1254
|
+
const dir = outputDir || this.defaultOutputDir;
|
|
1255
|
+
if (await fs3.pathExists(dir)) {
|
|
1256
|
+
await fs3.remove(dir);
|
|
1257
|
+
console.log(chalk.yellow(`\u2713 \uAE30\uC874 \uBC30\uCE58 \uC815\uB9AC: ${dir}`));
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* 배치 상태 확인
|
|
1262
|
+
*/
|
|
1263
|
+
async getStatus(outputDir) {
|
|
1264
|
+
const dir = outputDir || this.defaultOutputDir;
|
|
1265
|
+
const metaPath = path3.join(dir, "meta.json");
|
|
1266
|
+
if (!await fs3.pathExists(metaPath)) {
|
|
1267
|
+
return { exists: false };
|
|
1268
|
+
}
|
|
1269
|
+
try {
|
|
1270
|
+
const meta = await fs3.readJson(metaPath);
|
|
1271
|
+
return {
|
|
1272
|
+
exists: true,
|
|
1273
|
+
lastUpdated: meta.updatedAt,
|
|
1274
|
+
componentCount: meta.stats?.total
|
|
1275
|
+
};
|
|
1276
|
+
} catch {
|
|
1277
|
+
return { exists: false };
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
var publisher = new Publisher();
|
|
1282
|
+
|
|
1283
|
+
// src/library/index.ts
|
|
1284
|
+
import fs4 from "fs-extra";
|
|
1285
|
+
import path4 from "path";
|
|
1286
|
+
import chalk2 from "chalk";
|
|
1287
|
+
import ora2 from "ora";
|
|
1288
|
+
var DEFAULT_CATEGORIES = [
|
|
1289
|
+
{ id: "ui", name: "UI", description: "\uAE30\uBCF8 UI \uCEF4\uD3EC\uB10C\uD2B8", icon: "layers", order: 1 },
|
|
1290
|
+
{ id: "agentic", name: "Agentic", description: "Agentic UI \uCEF4\uD3EC\uB10C\uD2B8", icon: "bot", order: 2 },
|
|
1291
|
+
{ id: "form", name: "Form", description: "\uD3FC \uCEF4\uD3EC\uB10C\uD2B8", icon: "text-cursor-input", order: 3 },
|
|
1292
|
+
{ id: "layout", name: "Layout", description: "\uB808\uC774\uC544\uC6C3 \uCEF4\uD3EC\uB10C\uD2B8", icon: "layout", order: 4 },
|
|
1293
|
+
{ id: "navigation", name: "Navigation", description: "\uB124\uBE44\uAC8C\uC774\uC158", icon: "navigation", order: 5 },
|
|
1294
|
+
{ id: "feedback", name: "Feedback", description: "\uD53C\uB4DC\uBC31 \uCEF4\uD3EC\uB10C\uD2B8", icon: "bell", order: 6 },
|
|
1295
|
+
{ id: "overlay", name: "Overlay", description: "\uC624\uBC84\uB808\uC774 \uCEF4\uD3EC\uB10C\uD2B8", icon: "square-stack", order: 7 },
|
|
1296
|
+
{ id: "data-display", name: "Data Display", description: "\uB370\uC774\uD130 \uD45C\uC2DC", icon: "table", order: 8 },
|
|
1297
|
+
{ id: "chart", name: "Chart", description: "\uCC28\uD2B8/\uC2DC\uAC01\uD654", icon: "chart-bar", order: 9 },
|
|
1298
|
+
{ id: "utility", name: "Utility", description: "\uC720\uD2F8\uB9AC\uD2F0", icon: "wrench", order: 10 }
|
|
1299
|
+
];
|
|
1300
|
+
var LibraryCurator = class {
|
|
1301
|
+
collectors = /* @__PURE__ */ new Map();
|
|
1302
|
+
dataDir;
|
|
1303
|
+
constructor(options = {}) {
|
|
1304
|
+
this.dataDir = options.dataDir || ".claude/data/library";
|
|
1305
|
+
this.collectors.set("shadcn", new ShadcnCollector());
|
|
1306
|
+
this.collectors.set("monet", new MonetCollector());
|
|
1307
|
+
this.collectors.set("v0", new V0Collector());
|
|
1308
|
+
this.collectors.set("axis", new AxisCollector({ rootDir: options.rootDir }));
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* 전체 수집 실행
|
|
1312
|
+
*/
|
|
1313
|
+
async collectAll(sources, options) {
|
|
1314
|
+
const results = /* @__PURE__ */ new Map();
|
|
1315
|
+
const targetSources = sources || ["shadcn", "monet", "v0", "axis"];
|
|
1316
|
+
console.log(chalk2.blue("\n\u{1F4E6} Library Curator - \uCEF4\uD3EC\uB10C\uD2B8 \uC218\uC9D1 \uC2DC\uC791\n"));
|
|
1317
|
+
for (const source of targetSources) {
|
|
1318
|
+
const collector = this.collectors.get(source);
|
|
1319
|
+
if (!collector) {
|
|
1320
|
+
console.log(chalk2.yellow(`\u26A0\uFE0F ${source} collector not found, skipping`));
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
const spinner = ora2(`${source} \uC218\uC9D1 \uC911...`).start();
|
|
1324
|
+
try {
|
|
1325
|
+
const result = await collector.collectAll({
|
|
1326
|
+
incremental: options?.incremental
|
|
1327
|
+
});
|
|
1328
|
+
results.set(source, result);
|
|
1329
|
+
if (result.success) {
|
|
1330
|
+
spinner.succeed(
|
|
1331
|
+
`${source}: ${result.collected}\uAC1C \uC218\uC9D1 \uC644\uB8CC` + (result.failed > 0 ? chalk2.yellow(` (${result.failed}\uAC1C \uC2E4\uD328)`) : "")
|
|
1332
|
+
);
|
|
1333
|
+
} else {
|
|
1334
|
+
spinner.fail(`${source}: \uC218\uC9D1 \uC2E4\uD328 - ${result.errors[0]?.error}`);
|
|
1335
|
+
}
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
spinner.fail(`${source}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1338
|
+
results.set(source, {
|
|
1339
|
+
source,
|
|
1340
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1341
|
+
success: false,
|
|
1342
|
+
collected: 0,
|
|
1343
|
+
updated: 0,
|
|
1344
|
+
failed: 1,
|
|
1345
|
+
items: [],
|
|
1346
|
+
errors: [
|
|
1347
|
+
{
|
|
1348
|
+
id: "root",
|
|
1349
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1350
|
+
recoverable: false
|
|
1351
|
+
}
|
|
1352
|
+
]
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return results;
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* 라이브러리 인덱스 생성
|
|
1360
|
+
*/
|
|
1361
|
+
async generateIndex() {
|
|
1362
|
+
const components = [];
|
|
1363
|
+
for (const [source, collector] of this.collectors) {
|
|
1364
|
+
try {
|
|
1365
|
+
const list = await collector.listComponents();
|
|
1366
|
+
for (const item of list) {
|
|
1367
|
+
try {
|
|
1368
|
+
const meta = await collector.collectComponent(item.id);
|
|
1369
|
+
components.push(meta);
|
|
1370
|
+
} catch {
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
} catch {
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
const stats = this.calculateStats(components);
|
|
1377
|
+
const tags = this.aggregateTags(components);
|
|
1378
|
+
return {
|
|
1379
|
+
version: "1.0.0",
|
|
1380
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1381
|
+
stats,
|
|
1382
|
+
components,
|
|
1383
|
+
categories: DEFAULT_CATEGORIES,
|
|
1384
|
+
tags
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* 인덱스 저장
|
|
1389
|
+
*/
|
|
1390
|
+
async saveIndex(index) {
|
|
1391
|
+
await fs4.ensureDir(this.dataDir);
|
|
1392
|
+
const indexPath = path4.join(this.dataDir, "components.json");
|
|
1393
|
+
await fs4.writeJSON(indexPath, index, { spaces: 2 });
|
|
1394
|
+
const categoriesDir = path4.join(this.dataDir, "categories");
|
|
1395
|
+
await fs4.ensureDir(categoriesDir);
|
|
1396
|
+
for (const category of DEFAULT_CATEGORIES) {
|
|
1397
|
+
const categoryComponents = index.components.filter((c) => c.category === category.id);
|
|
1398
|
+
const categoryPath = path4.join(categoriesDir, `${category.id}.json`);
|
|
1399
|
+
await fs4.writeJSON(
|
|
1400
|
+
categoryPath,
|
|
1401
|
+
{
|
|
1402
|
+
category: category.id,
|
|
1403
|
+
count: categoryComponents.length,
|
|
1404
|
+
components: categoryComponents
|
|
1405
|
+
},
|
|
1406
|
+
{ spaces: 2 }
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
console.log(chalk2.green(`
|
|
1410
|
+
\u2713 \uC778\uB371\uC2A4 \uC800\uC7A5 \uC644\uB8CC: ${indexPath}`));
|
|
1411
|
+
console.log(chalk2.gray(` - \uC804\uCCB4: ${index.stats.total}\uAC1C \uCEF4\uD3EC\uB10C\uD2B8`));
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* 인덱스 로드
|
|
1415
|
+
*/
|
|
1416
|
+
async loadIndex() {
|
|
1417
|
+
const indexPath = path4.join(this.dataDir, "components.json");
|
|
1418
|
+
if (await fs4.pathExists(indexPath)) {
|
|
1419
|
+
return await fs4.readJSON(indexPath);
|
|
1420
|
+
}
|
|
1421
|
+
return null;
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* 통계 계산
|
|
1425
|
+
*/
|
|
1426
|
+
calculateStats(components) {
|
|
1427
|
+
const bySource = {};
|
|
1428
|
+
const byCategory = {};
|
|
1429
|
+
for (const comp of components) {
|
|
1430
|
+
bySource[comp.source.type] = (bySource[comp.source.type] || 0) + 1;
|
|
1431
|
+
byCategory[comp.category] = (byCategory[comp.category] || 0) + 1;
|
|
1432
|
+
}
|
|
1433
|
+
return {
|
|
1434
|
+
total: components.length,
|
|
1435
|
+
bySource,
|
|
1436
|
+
byCategory
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* 태그 집계
|
|
1441
|
+
*/
|
|
1442
|
+
aggregateTags(components) {
|
|
1443
|
+
const tagCounts = /* @__PURE__ */ new Map();
|
|
1444
|
+
for (const comp of components) {
|
|
1445
|
+
for (const tag of comp.tags) {
|
|
1446
|
+
tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
return [...tagCounts.entries()].map(([id, count]) => ({ id, name: id, count })).sort((a, b) => b.count - a.count);
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* 컴포넌트 검색
|
|
1453
|
+
*/
|
|
1454
|
+
async searchComponents(query, options) {
|
|
1455
|
+
const index = await this.loadIndex();
|
|
1456
|
+
if (!index) return [];
|
|
1457
|
+
const queryLower = query.toLowerCase();
|
|
1458
|
+
return index.components.filter((comp) => {
|
|
1459
|
+
if (options?.category && comp.category !== options.category) {
|
|
1460
|
+
return false;
|
|
1461
|
+
}
|
|
1462
|
+
if (options?.source && comp.source.type !== options.source) {
|
|
1463
|
+
return false;
|
|
1464
|
+
}
|
|
1465
|
+
return comp.name.toLowerCase().includes(queryLower) || comp.description.toLowerCase().includes(queryLower) || comp.tags.some((t) => t.toLowerCase().includes(queryLower));
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* 통계 출력
|
|
1470
|
+
*/
|
|
1471
|
+
async printStats() {
|
|
1472
|
+
const index = await this.loadIndex();
|
|
1473
|
+
if (!index) {
|
|
1474
|
+
console.log(chalk2.yellow("\n\u26A0\uFE0F \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC778\uB371\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
1475
|
+
console.log(chalk2.gray(" axis-cli library collect \uBA85\uB839\uC5B4\uB85C \uC218\uC9D1\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.\n"));
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
console.log(chalk2.blue("\n\u{1F4CA} Library Curator - \uD1B5\uACC4\n"));
|
|
1479
|
+
console.log(chalk2.bold("\uC804\uCCB4 \uCEF4\uD3EC\uB10C\uD2B8: ") + chalk2.cyan(`${index.stats.total}\uAC1C`));
|
|
1480
|
+
console.log(chalk2.gray(`\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8: ${index.updatedAt}
|
|
1481
|
+
`));
|
|
1482
|
+
console.log(chalk2.bold("\uC18C\uC2A4\uBCC4:"));
|
|
1483
|
+
for (const [source, count] of Object.entries(index.stats.bySource)) {
|
|
1484
|
+
console.log(` ${chalk2.cyan(source.padEnd(10))} ${count}\uAC1C`);
|
|
1485
|
+
}
|
|
1486
|
+
console.log(chalk2.bold("\n\uCE74\uD14C\uACE0\uB9AC\uBCC4:"));
|
|
1487
|
+
for (const [category, count] of Object.entries(index.stats.byCategory)) {
|
|
1488
|
+
console.log(` ${chalk2.cyan(category.padEnd(15))} ${count}\uAC1C`);
|
|
1489
|
+
}
|
|
1490
|
+
console.log(chalk2.bold("\n\uC778\uAE30 \uD0DC\uADF8:"));
|
|
1491
|
+
const topTags = index.tags.slice(0, 10);
|
|
1492
|
+
for (const tag of topTags) {
|
|
1493
|
+
console.log(` ${chalk2.cyan(tag.name.padEnd(15))} ${tag.count}\uAC1C`);
|
|
1494
|
+
}
|
|
1495
|
+
console.log();
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1498
|
+
var libraryCurator = new LibraryCurator();
|
|
1499
|
+
|
|
1500
|
+
// src/template/index.ts
|
|
1501
|
+
import chalk7 from "chalk";
|
|
1502
|
+
import ora5 from "ora";
|
|
1503
|
+
|
|
1504
|
+
// src/template/template-apply.ts
|
|
1505
|
+
import fs6 from "fs-extra";
|
|
1506
|
+
import path6 from "path";
|
|
1507
|
+
import chalk4 from "chalk";
|
|
1508
|
+
import ora3 from "ora";
|
|
1509
|
+
import prompts from "prompts";
|
|
1510
|
+
|
|
1511
|
+
// src/template/post-install.ts
|
|
1512
|
+
import fs5 from "fs-extra";
|
|
1513
|
+
import path5 from "path";
|
|
1514
|
+
import chalk3 from "chalk";
|
|
1515
|
+
async function runPostInstall(patches, baseDir, dryRun = false) {
|
|
1516
|
+
for (const patch of patches) {
|
|
1517
|
+
const filePath = path5.resolve(baseDir, patch.file);
|
|
1518
|
+
if (dryRun) {
|
|
1519
|
+
console.log(chalk3.gray(` [dry-run] patch ${patch.type}: ${patch.file}`));
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
if (!await fs5.pathExists(filePath)) {
|
|
1523
|
+
console.log(chalk3.yellow(` \uACBD\uACE0: \uD328\uCE58 \uB300\uC0C1 \uD30C\uC77C \uC5C6\uC74C - ${patch.file}`));
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
switch (patch.type) {
|
|
1527
|
+
case "replace": {
|
|
1528
|
+
if (!patch.search || patch.value === void 0) break;
|
|
1529
|
+
let content = await fs5.readFile(filePath, "utf-8");
|
|
1530
|
+
content = content.replace(patch.search, patch.value);
|
|
1531
|
+
await fs5.writeFile(filePath, content);
|
|
1532
|
+
console.log(chalk3.gray(` patch replace: ${patch.file}`));
|
|
1533
|
+
break;
|
|
1534
|
+
}
|
|
1535
|
+
case "append": {
|
|
1536
|
+
if (patch.value === void 0) break;
|
|
1537
|
+
const content = await fs5.readFile(filePath, "utf-8");
|
|
1538
|
+
await fs5.writeFile(filePath, content + patch.value);
|
|
1539
|
+
console.log(chalk3.gray(` patch append: ${patch.file}`));
|
|
1540
|
+
break;
|
|
1541
|
+
}
|
|
1542
|
+
case "prepend": {
|
|
1543
|
+
if (patch.value === void 0) break;
|
|
1544
|
+
const content = await fs5.readFile(filePath, "utf-8");
|
|
1545
|
+
await fs5.writeFile(filePath, patch.value + content);
|
|
1546
|
+
console.log(chalk3.gray(` patch prepend: ${patch.file}`));
|
|
1547
|
+
break;
|
|
1548
|
+
}
|
|
1549
|
+
case "json-merge": {
|
|
1550
|
+
if (!patch.merge) break;
|
|
1551
|
+
const raw = await fs5.readFile(filePath, "utf-8");
|
|
1552
|
+
const json = JSON.parse(raw);
|
|
1553
|
+
deepMerge(json, patch.merge);
|
|
1554
|
+
await fs5.writeJSON(filePath, json, { spaces: 2 });
|
|
1555
|
+
console.log(chalk3.gray(` patch json-merge: ${patch.file}`));
|
|
1556
|
+
break;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
function deepMerge(target, source) {
|
|
1562
|
+
for (const key of Object.keys(source)) {
|
|
1563
|
+
const srcVal = source[key];
|
|
1564
|
+
const tgtVal = target[key];
|
|
1565
|
+
if (srcVal && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal && typeof tgtVal === "object" && !Array.isArray(tgtVal)) {
|
|
1566
|
+
deepMerge(tgtVal, srcVal);
|
|
1567
|
+
} else {
|
|
1568
|
+
target[key] = srcVal;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// src/template/template-apply.ts
|
|
1574
|
+
async function applyTemplate(slug, options) {
|
|
1575
|
+
const spinner = ora3(`'${slug}' \uD15C\uD50C\uB9BF \uAC00\uC838\uC624\uB294 \uC911...`).start();
|
|
1576
|
+
let detail;
|
|
1577
|
+
try {
|
|
1578
|
+
detail = await fetchTemplateDetail(slug);
|
|
1579
|
+
spinner.succeed(`'${detail.name}' \uD15C\uD50C\uB9BF \uB85C\uB4DC \uC644\uB8CC (${detail.files.length}\uAC1C \uD30C\uC77C)`);
|
|
1580
|
+
} catch (err) {
|
|
1581
|
+
spinner.fail(err.message);
|
|
1582
|
+
process.exit(1);
|
|
1583
|
+
}
|
|
1584
|
+
const baseDir = path6.resolve(options.dir || ".");
|
|
1585
|
+
const conflicts = [];
|
|
1586
|
+
for (const file of detail.files) {
|
|
1587
|
+
const dest = path6.join(baseDir, file.path);
|
|
1588
|
+
if (await fs6.pathExists(dest)) {
|
|
1589
|
+
conflicts.push(file.path);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
if (conflicts.length > 0 && !options.yes && !options.dryRun) {
|
|
1593
|
+
console.log(chalk4.yellow(`
|
|
1594
|
+
\uAE30\uC874 \uD30C\uC77C\uACFC \uCDA9\uB3CC\uD558\uB294 ${conflicts.length}\uAC1C \uD30C\uC77C:`));
|
|
1595
|
+
for (const c of conflicts) {
|
|
1596
|
+
console.log(chalk4.gray(` - ${c}`));
|
|
1597
|
+
}
|
|
1598
|
+
const { proceed } = await prompts({
|
|
1599
|
+
type: "confirm",
|
|
1600
|
+
name: "proceed",
|
|
1601
|
+
message: "\uCDA9\uB3CC \uD30C\uC77C\uC744 \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
|
|
1602
|
+
initial: false
|
|
1603
|
+
});
|
|
1604
|
+
if (!proceed) {
|
|
1605
|
+
console.log(chalk4.gray("\uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
if (options.dryRun) {
|
|
1610
|
+
console.log(chalk4.blue("\n[\uBBF8\uB9AC\uBCF4\uAE30] \uC0DD\uC131\uB420 \uD30C\uC77C:"));
|
|
1611
|
+
for (const file of detail.files) {
|
|
1612
|
+
const dest = path6.join(baseDir, file.path);
|
|
1613
|
+
const exists = conflicts.includes(file.path);
|
|
1614
|
+
const label = exists ? chalk4.yellow("\uB36E\uC5B4\uC4F0\uAE30") : chalk4.green("\uC0DD\uC131");
|
|
1615
|
+
console.log(` ${label} ${dest}`);
|
|
1616
|
+
}
|
|
1617
|
+
if (detail.postInstall?.length) {
|
|
1618
|
+
console.log(chalk4.blue("\n[\uBBF8\uB9AC\uBCF4\uAE30] postInstall \uD328\uCE58:"));
|
|
1619
|
+
for (const p of detail.postInstall) {
|
|
1620
|
+
console.log(chalk4.gray(` ${p.type}: ${p.file}`));
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
const writeSpinner = ora3("\uD30C\uC77C \uC0DD\uC131 \uC911...").start();
|
|
1626
|
+
for (const file of detail.files) {
|
|
1627
|
+
const dest = path6.join(baseDir, file.path);
|
|
1628
|
+
await fs6.ensureDir(path6.dirname(dest));
|
|
1629
|
+
await fs6.writeFile(dest, file.content);
|
|
1630
|
+
}
|
|
1631
|
+
writeSpinner.succeed(`${detail.files.length}\uAC1C \uD30C\uC77C \uC0DD\uC131 \uC644\uB8CC`);
|
|
1632
|
+
if (detail.postInstall?.length) {
|
|
1633
|
+
console.log(chalk4.blue("\npostInstall \uD328\uCE58 \uC2E4\uD589 \uC911..."));
|
|
1634
|
+
await runPostInstall(detail.postInstall, baseDir);
|
|
1635
|
+
}
|
|
1636
|
+
if (!options.skipDeps) {
|
|
1637
|
+
const allDeps = { ...detail.dependencies, ...detail.devDependencies };
|
|
1638
|
+
const depList = Object.entries(allDeps);
|
|
1639
|
+
if (depList.length > 0) {
|
|
1640
|
+
console.log(chalk4.yellow("\n\uD544\uC694\uD55C \uC758\uC874\uC131:"));
|
|
1641
|
+
const deps = Object.entries(detail.dependencies || {}).map(([k, v]) => `${k}@${v}`).join(" ");
|
|
1642
|
+
const devDeps = Object.entries(detail.devDependencies || {}).map(([k, v]) => `${k}@${v}`).join(" ");
|
|
1643
|
+
if (deps) {
|
|
1644
|
+
console.log(chalk4.cyan(` pnpm add ${deps}`));
|
|
1645
|
+
}
|
|
1646
|
+
if (devDeps) {
|
|
1647
|
+
console.log(chalk4.cyan(` pnpm add -D ${devDeps}`));
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
console.log(chalk4.green(`
|
|
1652
|
+
'${detail.name}' \uD15C\uD50C\uB9BF \uC801\uC6A9 \uC644\uB8CC!`));
|
|
1653
|
+
}
|
|
1654
|
+
async function initTemplate(slug, options) {
|
|
1655
|
+
let selectedSlug = slug;
|
|
1656
|
+
if (!selectedSlug) {
|
|
1657
|
+
const { fetchTemplateIndex: fetchTemplateIndex2 } = await import("./registry-client-TPWFZ4HE.js");
|
|
1658
|
+
const index = await fetchTemplateIndex2();
|
|
1659
|
+
const { choice } = await prompts({
|
|
1660
|
+
type: "select",
|
|
1661
|
+
name: "choice",
|
|
1662
|
+
message: "\uC0AC\uC6A9\uD560 \uD15C\uD50C\uB9BF\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
|
|
1663
|
+
choices: index.templates.map((t) => ({
|
|
1664
|
+
title: `${t.name} (${t.category})`,
|
|
1665
|
+
description: t.description,
|
|
1666
|
+
value: t.slug
|
|
1667
|
+
}))
|
|
1668
|
+
});
|
|
1669
|
+
if (!choice) {
|
|
1670
|
+
console.log(chalk4.gray("\uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
selectedSlug = choice;
|
|
1674
|
+
}
|
|
1675
|
+
let targetDir = options.dir;
|
|
1676
|
+
if (!targetDir) {
|
|
1677
|
+
const { dir } = await prompts({
|
|
1678
|
+
type: "text",
|
|
1679
|
+
name: "dir",
|
|
1680
|
+
message: "\uD504\uB85C\uC81D\uD2B8 \uB514\uB809\uD1A0\uB9AC:",
|
|
1681
|
+
initial: selectedSlug
|
|
1682
|
+
});
|
|
1683
|
+
if (!dir) {
|
|
1684
|
+
console.log(chalk4.gray("\uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
targetDir = dir;
|
|
1688
|
+
}
|
|
1689
|
+
const resolved = path6.resolve(targetDir);
|
|
1690
|
+
if (await fs6.pathExists(resolved)) {
|
|
1691
|
+
const entries = await fs6.readdir(resolved);
|
|
1692
|
+
if (entries.length > 0) {
|
|
1693
|
+
const { proceed } = await prompts({
|
|
1694
|
+
type: "confirm",
|
|
1695
|
+
name: "proceed",
|
|
1696
|
+
message: `'${targetDir}'\uC5D0 \uC774\uBBF8 \uD30C\uC77C\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uACC4\uC18D\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?`,
|
|
1697
|
+
initial: false
|
|
1698
|
+
});
|
|
1699
|
+
if (!proceed) return;
|
|
1700
|
+
}
|
|
1701
|
+
} else {
|
|
1702
|
+
await fs6.ensureDir(resolved);
|
|
1703
|
+
}
|
|
1704
|
+
await applyTemplate(selectedSlug, { dir: targetDir, yes: true });
|
|
1705
|
+
const configPath = path6.join(resolved, "axis.config.json");
|
|
1706
|
+
if (!await fs6.pathExists(configPath)) {
|
|
1707
|
+
const axisConfig = {
|
|
1708
|
+
$schema: "https://ds.minu.best/schema.json",
|
|
1709
|
+
template: selectedSlug,
|
|
1710
|
+
componentsDir: "./components/ui",
|
|
1711
|
+
tailwindConfig: "./tailwind.config.ts",
|
|
1712
|
+
globalCss: "./globals.css"
|
|
1713
|
+
};
|
|
1714
|
+
await fs6.writeJSON(configPath, axisConfig, { spaces: 2 });
|
|
1715
|
+
console.log(chalk4.green("axis.config.json \uC0DD\uC131 \uC644\uB8CC"));
|
|
1716
|
+
}
|
|
1717
|
+
console.log(chalk4.blue(`
|
|
1718
|
+
\uB2E4\uC74C \uB2E8\uACC4:`));
|
|
1719
|
+
console.log(chalk4.cyan(` cd ${targetDir}`));
|
|
1720
|
+
console.log(chalk4.cyan(` pnpm install`));
|
|
1721
|
+
console.log(chalk4.cyan(` pnpm dev`));
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
// src/template/template-diff.ts
|
|
1725
|
+
import fs7 from "fs-extra";
|
|
1726
|
+
import path7 from "path";
|
|
1727
|
+
import chalk5 from "chalk";
|
|
1728
|
+
import ora4 from "ora";
|
|
1729
|
+
async function diffTemplate(slug, options) {
|
|
1730
|
+
const spinner = ora4(`'${slug}' \uD15C\uD50C\uB9BF \uAC00\uC838\uC624\uB294 \uC911...`).start();
|
|
1731
|
+
let detail;
|
|
1732
|
+
try {
|
|
1733
|
+
detail = await fetchTemplateDetail(slug);
|
|
1734
|
+
spinner.succeed(`'${detail.name}' \uB85C\uB4DC \uC644\uB8CC`);
|
|
1735
|
+
} catch (err) {
|
|
1736
|
+
spinner.fail(err.message);
|
|
1737
|
+
process.exit(1);
|
|
1738
|
+
}
|
|
1739
|
+
const baseDir = path7.resolve(options.dir || ".");
|
|
1740
|
+
const entries = [];
|
|
1741
|
+
for (const file of detail.files) {
|
|
1742
|
+
const localPath = path7.join(baseDir, file.path);
|
|
1743
|
+
if (!await fs7.pathExists(localPath)) {
|
|
1744
|
+
entries.push({ path: file.path, status: "added" });
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
const localContent = await fs7.readFile(localPath, "utf-8");
|
|
1748
|
+
if (localContent === file.content) {
|
|
1749
|
+
entries.push({ path: file.path, status: "unchanged" });
|
|
1750
|
+
} else {
|
|
1751
|
+
entries.push({
|
|
1752
|
+
path: file.path,
|
|
1753
|
+
status: "modified",
|
|
1754
|
+
localLines: localContent.split("\n").length,
|
|
1755
|
+
remoteLines: file.content.split("\n").length
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
if (options.verbose) {
|
|
1760
|
+
const templatePaths = new Set(detail.files.map((f) => f.path));
|
|
1761
|
+
const localFiles = await collectLocalFiles(baseDir);
|
|
1762
|
+
for (const lf of localFiles) {
|
|
1763
|
+
if (!templatePaths.has(lf)) {
|
|
1764
|
+
entries.push({ path: lf, status: "extra" });
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
console.log(chalk5.blue(`
|
|
1769
|
+
'${detail.name}' \uD15C\uD50C\uB9BF diff \uACB0\uACFC:
|
|
1770
|
+
`));
|
|
1771
|
+
const statusIcon = {
|
|
1772
|
+
added: chalk5.green("+ \uCD94\uAC00 \uD544\uC694"),
|
|
1773
|
+
modified: chalk5.yellow("~ \uBCC0\uACBD\uB428"),
|
|
1774
|
+
unchanged: chalk5.gray("= \uB3D9\uC77C"),
|
|
1775
|
+
extra: chalk5.cyan("? \uB85C\uCEEC \uC804\uC6A9")
|
|
1776
|
+
};
|
|
1777
|
+
let added = 0;
|
|
1778
|
+
let modified = 0;
|
|
1779
|
+
let unchanged = 0;
|
|
1780
|
+
let extra = 0;
|
|
1781
|
+
for (const entry of entries) {
|
|
1782
|
+
const icon = statusIcon[entry.status];
|
|
1783
|
+
console.log(` ${icon.padEnd(28)} ${entry.path}`);
|
|
1784
|
+
if (entry.status === "modified" && options.verbose) {
|
|
1785
|
+
console.log(
|
|
1786
|
+
chalk5.gray(
|
|
1787
|
+
` \uB85C\uCEEC ${entry.localLines}\uC904 / \uC6D0\uBCF8 ${entry.remoteLines}\uC904`
|
|
1788
|
+
)
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
switch (entry.status) {
|
|
1792
|
+
case "added":
|
|
1793
|
+
added++;
|
|
1794
|
+
break;
|
|
1795
|
+
case "modified":
|
|
1796
|
+
modified++;
|
|
1797
|
+
break;
|
|
1798
|
+
case "unchanged":
|
|
1799
|
+
unchanged++;
|
|
1800
|
+
break;
|
|
1801
|
+
case "extra":
|
|
1802
|
+
extra++;
|
|
1803
|
+
break;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
console.log(chalk5.blue("\n\uC694\uC57D:"));
|
|
1807
|
+
console.log(
|
|
1808
|
+
` \uCD94\uAC00 \uD544\uC694: ${chalk5.green(String(added))} | \uBCC0\uACBD\uB428: ${chalk5.yellow(String(modified))} | \uB3D9\uC77C: ${chalk5.gray(String(unchanged))}${extra > 0 ? ` | \uB85C\uCEEC \uC804\uC6A9: ${chalk5.cyan(String(extra))}` : ""}`
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
async function collectLocalFiles(dir, base) {
|
|
1812
|
+
const root = base || dir;
|
|
1813
|
+
const ignore = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", "dist"]);
|
|
1814
|
+
const results = [];
|
|
1815
|
+
let entries;
|
|
1816
|
+
try {
|
|
1817
|
+
entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
1818
|
+
} catch {
|
|
1819
|
+
return results;
|
|
1820
|
+
}
|
|
1821
|
+
for (const entry of entries) {
|
|
1822
|
+
if (ignore.has(entry.name)) continue;
|
|
1823
|
+
const fullPath = path7.join(dir, entry.name);
|
|
1824
|
+
if (entry.isDirectory()) {
|
|
1825
|
+
const nested = await collectLocalFiles(fullPath, root);
|
|
1826
|
+
results.push(...nested);
|
|
1827
|
+
} else {
|
|
1828
|
+
results.push(path7.relative(root, fullPath).replace(/\\/g, "/"));
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
return results;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// src/template/template-check.ts
|
|
1835
|
+
import fs8 from "fs-extra";
|
|
1836
|
+
import path8 from "path";
|
|
1837
|
+
import chalk6 from "chalk";
|
|
1838
|
+
var SEVERITY_ICON = {
|
|
1839
|
+
pass: chalk6.green("PASS"),
|
|
1840
|
+
warn: chalk6.yellow("WARN"),
|
|
1841
|
+
fail: chalk6.red("FAIL")
|
|
1842
|
+
};
|
|
1843
|
+
async function runCheck(dir) {
|
|
1844
|
+
const baseDir = path8.resolve(dir || ".");
|
|
1845
|
+
const results = [];
|
|
1846
|
+
results.push(await checkFileExists(baseDir, "axis.config.json", "axis.config.json"));
|
|
1847
|
+
results.push(await checkTailwindConfig(baseDir));
|
|
1848
|
+
results.push(await checkGlobalsCss(baseDir));
|
|
1849
|
+
results.push(await checkUtils(baseDir));
|
|
1850
|
+
results.push(await checkDependencies(baseDir));
|
|
1851
|
+
console.log(chalk6.blue("\nAXIS \uD504\uB85C\uC81D\uD2B8 \uAC80\uC99D \uACB0\uACFC:\n"));
|
|
1852
|
+
let passCount = 0;
|
|
1853
|
+
let warnCount = 0;
|
|
1854
|
+
let failCount = 0;
|
|
1855
|
+
for (const r of results) {
|
|
1856
|
+
console.log(` ${SEVERITY_ICON[r.severity]} ${r.message}`);
|
|
1857
|
+
switch (r.severity) {
|
|
1858
|
+
case "pass":
|
|
1859
|
+
passCount++;
|
|
1860
|
+
break;
|
|
1861
|
+
case "warn":
|
|
1862
|
+
warnCount++;
|
|
1863
|
+
break;
|
|
1864
|
+
case "fail":
|
|
1865
|
+
failCount++;
|
|
1866
|
+
break;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
console.log();
|
|
1870
|
+
console.log(
|
|
1871
|
+
` \uACB0\uACFC: ${chalk6.green(`${passCount} pass`)} / ${chalk6.yellow(`${warnCount} warn`)} / ${chalk6.red(`${failCount} fail`)}`
|
|
1872
|
+
);
|
|
1873
|
+
if (failCount > 0) {
|
|
1874
|
+
console.log(chalk6.red("\n\uC77C\uBD80 \uD544\uC218 \uD56D\uBAA9\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC704 \uC548\uB0B4\uB97C \uD655\uC778\uD558\uC138\uC694."));
|
|
1875
|
+
} else if (warnCount > 0) {
|
|
1876
|
+
console.log(chalk6.yellow("\n\uAD8C\uC7A5 \uD56D\uBAA9\uC744 \uD655\uC778\uD558\uC138\uC694."));
|
|
1877
|
+
} else {
|
|
1878
|
+
console.log(chalk6.green("\n\uBAA8\uB4E0 \uAC80\uC99D\uC744 \uD1B5\uACFC\uD588\uC2B5\uB2C8\uB2E4!"));
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
async function checkFileExists(baseDir, fileName, label) {
|
|
1882
|
+
const exists = await fs8.pathExists(path8.join(baseDir, fileName));
|
|
1883
|
+
return {
|
|
1884
|
+
name: label,
|
|
1885
|
+
severity: exists ? "pass" : "fail",
|
|
1886
|
+
message: exists ? `${label} \uD30C\uC77C\uC774 \uC874\uC7AC\uD569\uB2C8\uB2E4` : `${label} \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2192 'axis init'\uC744 \uC2E4\uD589\uD558\uC138\uC694`
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
async function checkTailwindConfig(baseDir) {
|
|
1890
|
+
const tsPath = path8.join(baseDir, "tailwind.config.ts");
|
|
1891
|
+
const jsPath = path8.join(baseDir, "tailwind.config.js");
|
|
1892
|
+
if (await fs8.pathExists(tsPath)) {
|
|
1893
|
+
const content = await fs8.readFile(tsPath, "utf-8");
|
|
1894
|
+
const hasTokens = content.includes("--background") || content.includes("hsl(var(");
|
|
1895
|
+
return {
|
|
1896
|
+
name: "tailwind.config",
|
|
1897
|
+
severity: hasTokens ? "pass" : "warn",
|
|
1898
|
+
message: hasTokens ? "tailwind.config.ts\uC5D0 AXIS \uD1A0\uD070\uC774 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4" : "tailwind.config.ts\uC5D0 AXIS \uD1A0\uD070 \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
if (await fs8.pathExists(jsPath)) {
|
|
1902
|
+
return {
|
|
1903
|
+
name: "tailwind.config",
|
|
1904
|
+
severity: "warn",
|
|
1905
|
+
message: "tailwind.config.js \uC874\uC7AC (TypeScript \uAD8C\uC7A5)"
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
return {
|
|
1909
|
+
name: "tailwind.config",
|
|
1910
|
+
severity: "fail",
|
|
1911
|
+
message: "tailwind.config \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
async function checkGlobalsCss(baseDir) {
|
|
1915
|
+
const candidates = [
|
|
1916
|
+
"globals.css",
|
|
1917
|
+
"app/globals.css",
|
|
1918
|
+
"src/app/globals.css",
|
|
1919
|
+
"src/globals.css",
|
|
1920
|
+
"styles/globals.css"
|
|
1921
|
+
];
|
|
1922
|
+
for (const candidate of candidates) {
|
|
1923
|
+
const fullPath = path8.join(baseDir, candidate);
|
|
1924
|
+
if (await fs8.pathExists(fullPath)) {
|
|
1925
|
+
const content = await fs8.readFile(fullPath, "utf-8");
|
|
1926
|
+
const hasTokens = content.includes("--background") && content.includes("--primary");
|
|
1927
|
+
return {
|
|
1928
|
+
name: "globals.css",
|
|
1929
|
+
severity: hasTokens ? "pass" : "warn",
|
|
1930
|
+
message: hasTokens ? `${candidate}\uC5D0 AXIS CSS \uBCC0\uC218\uAC00 \uC815\uC758\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4` : `${candidate}\uC5D0 AXIS CSS \uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4`
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
return {
|
|
1935
|
+
name: "globals.css",
|
|
1936
|
+
severity: "fail",
|
|
1937
|
+
message: "globals.css \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
async function checkUtils(baseDir) {
|
|
1941
|
+
const candidates = [
|
|
1942
|
+
"lib/utils.ts",
|
|
1943
|
+
"src/lib/utils.ts",
|
|
1944
|
+
"components/ui/utils.ts",
|
|
1945
|
+
"src/components/ui/utils.ts",
|
|
1946
|
+
"utils.ts"
|
|
1947
|
+
];
|
|
1948
|
+
for (const candidate of candidates) {
|
|
1949
|
+
const fullPath = path8.join(baseDir, candidate);
|
|
1950
|
+
if (await fs8.pathExists(fullPath)) {
|
|
1951
|
+
const content = await fs8.readFile(fullPath, "utf-8");
|
|
1952
|
+
const hasCn = content.includes("function cn");
|
|
1953
|
+
return {
|
|
1954
|
+
name: "utils.ts",
|
|
1955
|
+
severity: hasCn ? "pass" : "warn",
|
|
1956
|
+
message: hasCn ? `${candidate}\uC5D0 cn() \uD568\uC218\uAC00 \uC788\uC2B5\uB2C8\uB2E4` : `${candidate}\uC5D0 cn() \uD568\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4`
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
return {
|
|
1961
|
+
name: "utils.ts",
|
|
1962
|
+
severity: "warn",
|
|
1963
|
+
message: "utils.ts\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uCEF4\uD3EC\uB10C\uD2B8 \uC0AC\uC6A9 \uC2DC \uD544\uC694)"
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
async function checkDependencies(baseDir) {
|
|
1967
|
+
const pkgPath = path8.join(baseDir, "package.json");
|
|
1968
|
+
if (!await fs8.pathExists(pkgPath)) {
|
|
1969
|
+
return {
|
|
1970
|
+
name: "package.json",
|
|
1971
|
+
severity: "fail",
|
|
1972
|
+
message: "package.json \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
const pkg = await fs8.readJSON(pkgPath);
|
|
1976
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1977
|
+
const required = ["tailwindcss"];
|
|
1978
|
+
const recommended = ["clsx", "tailwind-merge"];
|
|
1979
|
+
const missingRequired = required.filter((d) => !allDeps[d]);
|
|
1980
|
+
const missingRecommended = recommended.filter((d) => !allDeps[d]);
|
|
1981
|
+
if (missingRequired.length > 0) {
|
|
1982
|
+
return {
|
|
1983
|
+
name: "dependencies",
|
|
1984
|
+
severity: "fail",
|
|
1985
|
+
message: `\uD544\uC218 \uC758\uC874\uC131 \uB204\uB77D: ${missingRequired.join(", ")}`
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
if (missingRecommended.length > 0) {
|
|
1989
|
+
return {
|
|
1990
|
+
name: "dependencies",
|
|
1991
|
+
severity: "warn",
|
|
1992
|
+
message: `\uAD8C\uC7A5 \uC758\uC874\uC131 \uB204\uB77D: ${missingRecommended.join(", ")}`
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
return {
|
|
1996
|
+
name: "dependencies",
|
|
1997
|
+
severity: "pass",
|
|
1998
|
+
message: "\uD544\uC218 \uBC0F \uAD8C\uC7A5 \uC758\uC874\uC131\uC774 \uBAA8\uB450 \uC124\uCE58\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4"
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
// src/template/index.ts
|
|
2003
|
+
function registerTemplateCommand(program2) {
|
|
2004
|
+
const tplCmd = program2.command("template").alias("tpl").description("\uD504\uB85C\uC81D\uD2B8 \uD15C\uD50C\uB9BF \uAD00\uB9AC");
|
|
2005
|
+
tplCmd.command("list").alias("ls").description("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD15C\uD50C\uB9BF \uBAA9\uB85D").option("--category <type>", "\uCE74\uD14C\uACE0\uB9AC \uD544\uD130").action(async (options) => {
|
|
2006
|
+
const spinner = ora5("\uD15C\uD50C\uB9BF \uBAA9\uB85D \uAC00\uC838\uC624\uB294 \uC911...").start();
|
|
2007
|
+
try {
|
|
2008
|
+
const index = await fetchTemplateIndex();
|
|
2009
|
+
spinner.stop();
|
|
2010
|
+
let templates = index.templates;
|
|
2011
|
+
if (options.category) {
|
|
2012
|
+
templates = templates.filter((t) => t.category === options.category);
|
|
2013
|
+
}
|
|
2014
|
+
if (templates.length === 0) {
|
|
2015
|
+
console.log(chalk7.yellow("\n\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD15C\uD50C\uB9BF\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
console.log(chalk7.blue(`
|
|
2019
|
+
\u{1F4E6} AXIS \uD15C\uD50C\uB9BF (${templates.length}\uAC1C)
|
|
2020
|
+
`));
|
|
2021
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
2022
|
+
for (const t of templates) {
|
|
2023
|
+
if (!byCategory.has(t.category)) {
|
|
2024
|
+
byCategory.set(t.category, []);
|
|
2025
|
+
}
|
|
2026
|
+
byCategory.get(t.category).push(t);
|
|
2027
|
+
}
|
|
2028
|
+
for (const [category, items] of byCategory) {
|
|
2029
|
+
console.log(chalk7.bold(` ${category}`));
|
|
2030
|
+
for (const t of items) {
|
|
2031
|
+
console.log(
|
|
2032
|
+
` ${chalk7.cyan(t.slug.padEnd(20))} ${chalk7.gray(t.description.slice(0, 50))}`
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
console.log();
|
|
2036
|
+
}
|
|
2037
|
+
console.log(chalk7.gray("\uC0AC\uC6A9\uBC95: axis template info <name>"));
|
|
2038
|
+
console.log(chalk7.gray(" axis template apply <name>\n"));
|
|
2039
|
+
} catch (err) {
|
|
2040
|
+
spinner.fail(err.message);
|
|
2041
|
+
process.exit(1);
|
|
2042
|
+
}
|
|
2043
|
+
});
|
|
2044
|
+
tplCmd.command("info <name>").description("\uD15C\uD50C\uB9BF \uC0C1\uC138 \uC815\uBCF4 \uBCF4\uAE30").action(async (name) => {
|
|
2045
|
+
const spinner = ora5(`'${name}' \uC815\uBCF4 \uAC00\uC838\uC624\uB294 \uC911...`).start();
|
|
2046
|
+
try {
|
|
2047
|
+
const detail = await fetchTemplateDetail(name);
|
|
2048
|
+
spinner.stop();
|
|
2049
|
+
console.log(chalk7.blue(`
|
|
2050
|
+
\u{1F4CB} ${detail.name}
|
|
2051
|
+
`));
|
|
2052
|
+
console.log(` ${chalk7.bold("\uC124\uBA85")}: ${detail.description}`);
|
|
2053
|
+
console.log(` ${chalk7.bold("\uCE74\uD14C\uACE0\uB9AC")}: ${detail.category}`);
|
|
2054
|
+
console.log(` ${chalk7.bold("\uBC84\uC804")}: ${detail.version}`);
|
|
2055
|
+
if (detail.tags.length > 0) {
|
|
2056
|
+
console.log(` ${chalk7.bold("\uD0DC\uADF8")}: ${detail.tags.join(", ")}`);
|
|
2057
|
+
}
|
|
2058
|
+
if (detail.features.length > 0) {
|
|
2059
|
+
console.log(chalk7.bold("\n \uAE30\uB2A5:"));
|
|
2060
|
+
for (const f of detail.features) {
|
|
2061
|
+
console.log(` - ${f}`);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
console.log(chalk7.bold(`
|
|
2065
|
+
\uD30C\uC77C (${detail.files.length}\uAC1C):`));
|
|
2066
|
+
for (const f of detail.files) {
|
|
2067
|
+
console.log(` ${chalk7.cyan(f.path)} ${chalk7.gray(`(${f.type})`)}`);
|
|
2068
|
+
}
|
|
2069
|
+
const deps = Object.keys(detail.dependencies || {});
|
|
2070
|
+
const devDeps = Object.keys(detail.devDependencies || {});
|
|
2071
|
+
if (deps.length > 0 || devDeps.length > 0) {
|
|
2072
|
+
console.log(chalk7.bold("\n \uC758\uC874\uC131:"));
|
|
2073
|
+
if (deps.length > 0) {
|
|
2074
|
+
console.log(` dependencies: ${deps.join(", ")}`);
|
|
2075
|
+
}
|
|
2076
|
+
if (devDeps.length > 0) {
|
|
2077
|
+
console.log(` devDependencies: ${devDeps.join(", ")}`);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
console.log(chalk7.gray(`
|
|
2081
|
+
\uC0AC\uC6A9\uBC95: axis template apply ${name}
|
|
2082
|
+
`));
|
|
2083
|
+
} catch (err) {
|
|
2084
|
+
spinner.fail(err.message);
|
|
2085
|
+
process.exit(1);
|
|
2086
|
+
}
|
|
2087
|
+
});
|
|
2088
|
+
tplCmd.command("apply <name>").description("\uD15C\uD50C\uB9BF\uC744 \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC801\uC6A9").option("-y, --yes", "\uD655\uC778 \uC5C6\uC774 \uB36E\uC5B4\uC4F0\uAE30").option("--dry-run", "\uC2E4\uC81C \uD30C\uC77C \uC0DD\uC131 \uC5C6\uC774 \uBBF8\uB9AC\uBCF4\uAE30").option("--skip-deps", "\uC758\uC874\uC131 \uC548\uB0B4 \uC0DD\uB7B5").option("-d, --dir <path>", "\uB300\uC0C1 \uB514\uB809\uD1A0\uB9AC").action(async (name, options) => {
|
|
2089
|
+
await applyTemplate(name, options);
|
|
2090
|
+
});
|
|
2091
|
+
tplCmd.command("init [name]").description("\uC0C8 \uD504\uB85C\uC81D\uD2B8\uB97C \uD15C\uD50C\uB9BF\uC73C\uB85C \uCD08\uAE30\uD654").option("-d, --dir <path>", "\uD504\uB85C\uC81D\uD2B8 \uB514\uB809\uD1A0\uB9AC").action(async (name, options) => {
|
|
2092
|
+
await initTemplate(name, options);
|
|
2093
|
+
});
|
|
2094
|
+
tplCmd.command("diff <name>").description("\uB85C\uCEEC \uD504\uB85C\uC81D\uD2B8\uC640 \uD15C\uD50C\uB9BF\uC758 \uCC28\uC774 \uBE44\uAD50").option("--verbose", "\uB85C\uCEEC \uC804\uC6A9 \uD30C\uC77C\uB3C4 \uD45C\uC2DC").option("-d, --dir <path>", "\uB300\uC0C1 \uB514\uB809\uD1A0\uB9AC").action(async (name, options) => {
|
|
2095
|
+
await diffTemplate(name, options);
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
function registerCheckCommand(program2) {
|
|
2099
|
+
program2.command("check").description("AXIS \uD504\uB85C\uC81D\uD2B8 \uC124\uC815 \uAC80\uC99D").option("-d, --dir <path>", "\uB300\uC0C1 \uB514\uB809\uD1A0\uB9AC").action(async (options) => {
|
|
2100
|
+
await runCheck(options.dir);
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
// src/index.ts
|
|
2105
|
+
var REGISTRY_URL = process.env.AXIS_REGISTRY_URL || "https://ds.minu.best/r";
|
|
2106
|
+
var program = new Command();
|
|
2107
|
+
program.name("axis").description("AXIS Design System CLI - shadcn \uD638\uD658 \uCEF4\uD3EC\uB10C\uD2B8 \uC124\uCE58 \uB3C4\uAD6C").version("0.1.0");
|
|
2108
|
+
program.command("init").description("\uD504\uB85C\uC81D\uD2B8\uC5D0 AXIS Design System \uCD08\uAE30\uD654").option("-y, --yes", "\uAE30\uBCF8\uAC12\uC73C\uB85C \uC790\uB3D9 \uC124\uC815").action(async (options) => {
|
|
2109
|
+
console.log(chalk8.blue("\n\u{1F3A8} AXIS Design System \uCD08\uAE30\uD654\n"));
|
|
2110
|
+
const config = options.yes ? {
|
|
2111
|
+
componentsDir: "./src/components/ui",
|
|
2112
|
+
tailwindConfig: "./tailwind.config.ts",
|
|
2113
|
+
globalCss: "./src/app/globals.css"
|
|
2114
|
+
} : await prompts2([
|
|
2115
|
+
{
|
|
2116
|
+
type: "text",
|
|
2117
|
+
name: "componentsDir",
|
|
2118
|
+
message: "\uCEF4\uD3EC\uB10C\uD2B8 \uB514\uB809\uD1A0\uB9AC:",
|
|
2119
|
+
initial: "./src/components/ui"
|
|
2120
|
+
},
|
|
2121
|
+
{
|
|
2122
|
+
type: "text",
|
|
2123
|
+
name: "tailwindConfig",
|
|
2124
|
+
message: "Tailwind \uC124\uC815 \uD30C\uC77C:",
|
|
2125
|
+
initial: "./tailwind.config.ts"
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
type: "text",
|
|
2129
|
+
name: "globalCss",
|
|
2130
|
+
message: "\uAE00\uB85C\uBC8C CSS \uD30C\uC77C:",
|
|
2131
|
+
initial: "./src/app/globals.css"
|
|
2132
|
+
}
|
|
2133
|
+
]);
|
|
2134
|
+
const axisConfig = {
|
|
2135
|
+
$schema: "https://ds.minu.best/schema.json",
|
|
2136
|
+
componentsDir: config.componentsDir,
|
|
2137
|
+
tailwindConfig: config.tailwindConfig,
|
|
2138
|
+
globalCss: config.globalCss,
|
|
2139
|
+
registry: REGISTRY_URL
|
|
2140
|
+
};
|
|
2141
|
+
await fs9.writeJSON("axis.config.json", axisConfig, { spaces: 2 });
|
|
2142
|
+
console.log(chalk8.green("\u2713 axis.config.json \uC0DD\uC131 \uC644\uB8CC"));
|
|
2143
|
+
const utilsPath = path9.join(config.componentsDir, "utils.ts");
|
|
2144
|
+
await fs9.ensureDir(path9.dirname(utilsPath));
|
|
2145
|
+
await fs9.writeFile(
|
|
2146
|
+
utilsPath,
|
|
2147
|
+
`import { type ClassValue, clsx } from "clsx";
|
|
2148
|
+
import { twMerge } from "tailwind-merge";
|
|
2149
|
+
|
|
2150
|
+
export function cn(...inputs: ClassValue[]) {
|
|
2151
|
+
return twMerge(clsx(inputs));
|
|
2152
|
+
}
|
|
2153
|
+
`
|
|
2154
|
+
);
|
|
2155
|
+
console.log(chalk8.green("\u2713 utils.ts \uC0DD\uC131 \uC644\uB8CC"));
|
|
2156
|
+
console.log(chalk8.blue("\n\uCD08\uAE30\uD654 \uC644\uB8CC! \uC774\uC81C \uB2E4\uC74C \uBA85\uB839\uC5B4\uB85C \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uCD94\uAC00\uD558\uC138\uC694:"));
|
|
2157
|
+
console.log(chalk8.cyan(" npx axis-cli add button\n"));
|
|
2158
|
+
});
|
|
2159
|
+
program.command("add <component>").description("\uCEF4\uD3EC\uB10C\uD2B8 \uCD94\uAC00").option("--agentic", "Agentic UI \uCEF4\uD3EC\uB10C\uD2B8 \uCD94\uAC00").option("-y, --yes", "\uD655\uC778 \uC5C6\uC774 \uB36E\uC5B4\uC4F0\uAE30").action(async (component, options) => {
|
|
2160
|
+
const spinner = ora6(`${component} \uCEF4\uD3EC\uB10C\uD2B8 \uAC00\uC838\uC624\uB294 \uC911...`).start();
|
|
2161
|
+
try {
|
|
2162
|
+
const configPath = path9.resolve("axis.config.json");
|
|
2163
|
+
if (!await fs9.pathExists(configPath)) {
|
|
2164
|
+
spinner.fail(
|
|
2165
|
+
"axis.config.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n \uD574\uACB0: \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0\uC11C 'npx @axis-ds/cli init'\uC744 \uC2E4\uD589\uD558\uC138\uC694.\n \uCC38\uACE0: https://axis.minu.best/docs/guides/quick-start"
|
|
2166
|
+
);
|
|
2167
|
+
process.exit(1);
|
|
2168
|
+
}
|
|
2169
|
+
const config = await fs9.readJSON(configPath);
|
|
2170
|
+
const category = options.agentic ? "agentic" : "ui";
|
|
2171
|
+
const componentInfo = await getComponentInfo(component, category);
|
|
2172
|
+
if (!componentInfo) {
|
|
2173
|
+
spinner.fail(
|
|
2174
|
+
`'${component}' \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
|
|
2175
|
+
\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uCEF4\uD3EC\uB10C\uD2B8 \uBAA9\uB85D: npx @axis-ds/cli list
|
|
2176
|
+
Agentic UI \uCEF4\uD3EC\uB10C\uD2B8\uB294 --agentic \uC635\uC158\uC744 \uCD94\uAC00\uD558\uC138\uC694.`
|
|
2177
|
+
);
|
|
2178
|
+
process.exit(1);
|
|
2179
|
+
}
|
|
2180
|
+
spinner.text = `${component} \uCEF4\uD3EC\uB10C\uD2B8 \uC124\uCE58 \uC911...`;
|
|
2181
|
+
for (const file of componentInfo.files) {
|
|
2182
|
+
const filePath = path9.join(config.componentsDir, file.path);
|
|
2183
|
+
if (await fs9.pathExists(filePath)) {
|
|
2184
|
+
if (!options.yes) {
|
|
2185
|
+
const { overwrite } = await prompts2({
|
|
2186
|
+
type: "confirm",
|
|
2187
|
+
name: "overwrite",
|
|
2188
|
+
message: `${file.path}\uC774(\uAC00) \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?`,
|
|
2189
|
+
initial: false
|
|
2190
|
+
});
|
|
2191
|
+
if (!overwrite) continue;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
await fs9.ensureDir(path9.dirname(filePath));
|
|
2195
|
+
await fs9.writeFile(filePath, file.content);
|
|
2196
|
+
}
|
|
2197
|
+
spinner.succeed(chalk8.green(`${component} \uCEF4\uD3EC\uB10C\uD2B8 \uCD94\uAC00 \uC644\uB8CC`));
|
|
2198
|
+
if (componentInfo.dependencies.length > 0) {
|
|
2199
|
+
console.log(chalk8.yellow("\n\uD544\uC694\uD55C \uC758\uC874\uC131:"));
|
|
2200
|
+
console.log(chalk8.cyan(` pnpm add ${componentInfo.dependencies.join(" ")}`));
|
|
2201
|
+
}
|
|
2202
|
+
} catch (error) {
|
|
2203
|
+
spinner.fail(
|
|
2204
|
+
`\uCEF4\uD3EC\uB10C\uD2B8 \uCD94\uAC00 \uC911 \uC624\uB958 \uBC1C\uC0DD: ${error.message}
|
|
2205
|
+
\uB124\uD2B8\uC6CC\uD06C \uBB38\uC81C\uC778 \uACBD\uC6B0 \uC778\uD130\uB137 \uC5F0\uACB0\uC744 \uD655\uC778\uD558\uC138\uC694.
|
|
2206
|
+
\uBB38\uC81C\uAC00 \uC9C0\uC18D\uB418\uBA74 GitHub \uC774\uC288\uB97C \uC0DD\uC131\uD574 \uC8FC\uC138\uC694: https://github.com/IDEA-on-Action/AXIS-Design-System/issues`
|
|
2207
|
+
);
|
|
2208
|
+
process.exit(1);
|
|
2209
|
+
}
|
|
2210
|
+
});
|
|
2211
|
+
program.command("list").description("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uCEF4\uD3EC\uB10C\uD2B8 \uBAA9\uB85D").option("--category <type>", "\uCE74\uD14C\uACE0\uB9AC \uD544\uD130 (ui, agentic)").action(async (options) => {
|
|
2212
|
+
console.log(chalk8.blue("\n\u{1F4E6} AXIS Design System \uCEF4\uD3EC\uB10C\uD2B8\n"));
|
|
2213
|
+
const components = {
|
|
2214
|
+
ui: [
|
|
2215
|
+
{ name: "button", description: "\uAE30\uBCF8 \uBC84\uD2BC \uCEF4\uD3EC\uB10C\uD2B8" },
|
|
2216
|
+
{ name: "input", description: "\uD14D\uC2A4\uD2B8 \uC785\uB825 \uD544\uB4DC" },
|
|
2217
|
+
{ name: "card", description: "\uCE74\uB4DC \uCEE8\uD14C\uC774\uB108" },
|
|
2218
|
+
{ name: "dialog", description: "\uBAA8\uB2EC \uB2E4\uC774\uC5BC\uB85C\uADF8" },
|
|
2219
|
+
{ name: "badge", description: "\uBC43\uC9C0/\uD0DC\uADF8" },
|
|
2220
|
+
{ name: "select", description: "\uC120\uD0DD \uB4DC\uB86D\uB2E4\uC6B4" },
|
|
2221
|
+
{ name: "tabs", description: "\uD0ED \uB124\uBE44\uAC8C\uC774\uC158" },
|
|
2222
|
+
{ name: "toast", description: "\uC54C\uB9BC \uD1A0\uC2A4\uD2B8" },
|
|
2223
|
+
{ name: "label", description: "\uD3FC \uB77C\uBCA8" },
|
|
2224
|
+
{ name: "separator", description: "\uAD6C\uBD84\uC120" }
|
|
2225
|
+
],
|
|
2226
|
+
agentic: [
|
|
2227
|
+
{ name: "run-progress", description: "\uC5D0\uC774\uC804\uD2B8 \uC2E4\uD589 \uC9C4\uD589\uB960" },
|
|
2228
|
+
{ name: "step-timeline", description: "\uB2E8\uACC4\uBCC4 \uD0C0\uC784\uB77C\uC778" },
|
|
2229
|
+
{ name: "approval-card", description: "\uC0AC\uC6A9\uC790 \uC2B9\uC778 \uC694\uCCAD" },
|
|
2230
|
+
{ name: "streaming-text", description: "\uC2E4\uC2DC\uAC04 \uD14D\uC2A4\uD2B8 \uC2A4\uD2B8\uB9AC\uBC0D" },
|
|
2231
|
+
{ name: "tool-call-card", description: "\uB3C4\uAD6C \uD638\uCD9C \uD45C\uC2DC" },
|
|
2232
|
+
{ name: "source-panel", description: "AI \uADFC\uAC70/\uCD9C\uCC98 \uD45C\uC2DC" },
|
|
2233
|
+
{ name: "thinking-indicator", description: "\uC0DD\uAC01 \uC911 \uD45C\uC2DC" },
|
|
2234
|
+
{ name: "recovery-banner", description: "\uC624\uB958 \uBCF5\uAD6C \uC548\uB0B4" },
|
|
2235
|
+
{ name: "agent-avatar", description: "\uC5D0\uC774\uC804\uD2B8 \uC544\uBC14\uD0C0" },
|
|
2236
|
+
{ name: "surface-renderer", description: "\uB3D9\uC801 Surface \uB80C\uB354\uB9C1" }
|
|
2237
|
+
]
|
|
2238
|
+
};
|
|
2239
|
+
const showCategory = (category, items) => {
|
|
2240
|
+
console.log(chalk8.bold(category === "ui" ? "Core UI" : "Agentic UI"));
|
|
2241
|
+
items.forEach((c) => {
|
|
2242
|
+
console.log(` ${chalk8.cyan(c.name.padEnd(20))} ${chalk8.gray(c.description)}`);
|
|
2243
|
+
});
|
|
2244
|
+
console.log();
|
|
2245
|
+
};
|
|
2246
|
+
if (!options.category || options.category === "ui") {
|
|
2247
|
+
showCategory("ui", components.ui);
|
|
2248
|
+
}
|
|
2249
|
+
if (!options.category || options.category === "agentic") {
|
|
2250
|
+
showCategory("agentic", components.agentic);
|
|
2251
|
+
}
|
|
2252
|
+
console.log(chalk8.gray("\uC0AC\uC6A9\uBC95: npx axis-cli add <component-name>"));
|
|
2253
|
+
console.log(chalk8.gray("Agentic: npx axis-cli add <component-name> --agentic\n"));
|
|
2254
|
+
});
|
|
2255
|
+
var monetCmd = program.command("monet").description("Monet Design \uCEF4\uD3EC\uB10C\uD2B8 \uAD00\uB9AC (https://monet.design)");
|
|
2256
|
+
monetCmd.command("list").alias("ls").description("Monet \uCE74\uD14C\uACE0\uB9AC \uBAA9\uB85D").action(async () => {
|
|
2257
|
+
const client = new MonetClient();
|
|
2258
|
+
await client.listCategories();
|
|
2259
|
+
});
|
|
2260
|
+
monetCmd.command("browse <category>").alias("b").description("\uCE74\uD14C\uACE0\uB9AC\uBCC4 \uCEF4\uD3EC\uB10C\uD2B8 \uBCF4\uAE30").action(async (category) => {
|
|
2261
|
+
const client = new MonetClient();
|
|
2262
|
+
await client.browseCategory(category);
|
|
2263
|
+
});
|
|
2264
|
+
monetCmd.command("search <query>").alias("s").description("\uCEF4\uD3EC\uB10C\uD2B8 \uAC80\uC0C9").action(async (query) => {
|
|
2265
|
+
const client = new MonetClient();
|
|
2266
|
+
await client.searchComponents(query);
|
|
2267
|
+
});
|
|
2268
|
+
monetCmd.command("import").alias("i").description("\uD074\uB9BD\uBCF4\uB4DC\uC5D0\uC11C \uCEF4\uD3EC\uB10C\uD2B8 \uAC00\uC838\uC624\uAE30").action(async () => {
|
|
2269
|
+
const client = new MonetClient();
|
|
2270
|
+
await client.importFromClipboard();
|
|
2271
|
+
});
|
|
2272
|
+
monetCmd.command("setup").description("Monet MCP \uC11C\uBC84 \uC124\uC815 \uC548\uB0B4").action(() => {
|
|
2273
|
+
console.log(chalk8.blue("\n\u{1F527} Monet MCP \uC11C\uBC84 \uC124\uC815\n"));
|
|
2274
|
+
console.log(chalk8.bold("1. API \uD0A4 \uBC1C\uAE09:"));
|
|
2275
|
+
console.log(chalk8.cyan(" https://monet.design/mcp\n"));
|
|
2276
|
+
console.log(chalk8.bold("2. \uD658\uACBD \uBCC0\uC218 \uC124\uC815:"));
|
|
2277
|
+
console.log(chalk8.gray(" .env \uD30C\uC77C\uC5D0 \uCD94\uAC00:"));
|
|
2278
|
+
console.log(chalk8.cyan(" MONET_API_KEY=your-api-key-here\n"));
|
|
2279
|
+
console.log(chalk8.bold("3. Claude Code MCP \uC124\uC815:"));
|
|
2280
|
+
console.log(chalk8.gray(" .claude/mcp.json\uC5D0\uC11C monet \uC11C\uBC84 \uD65C\uC131\uD654:\n"));
|
|
2281
|
+
console.log(
|
|
2282
|
+
chalk8.cyan(` "monet": {
|
|
2283
|
+
"url": "https://www.monet.design/api/remote/mcp",
|
|
2284
|
+
"headers": { "Authorization": "Bearer \${MONET_API_KEY}" },
|
|
2285
|
+
"disabled": false
|
|
2286
|
+
}
|
|
2287
|
+
`)
|
|
2288
|
+
);
|
|
2289
|
+
console.log(chalk8.bold("4. Claude Code \uC7AC\uC2DC\uC791 \uD6C4 \uC0AC\uC6A9:"));
|
|
2290
|
+
console.log(chalk8.gray(' "monet\uC5D0\uC11C hero \uCEF4\uD3EC\uB10C\uD2B8 \uCC3E\uC544\uC918"\n'));
|
|
2291
|
+
});
|
|
2292
|
+
var v0Cmd = program.command("v0").description("V0 (v0.app) \uCF54\uB4DC \uBCC0\uD658 \uBC0F \uD1B5\uD569");
|
|
2293
|
+
v0Cmd.command("convert <file>").alias("c").description("V0 \uCF54\uB4DC\uB97C AXIS \uC2A4\uD0C0\uC77C\uB85C \uBCC0\uD658").option("-o, --output <path>", "\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C").action(async (file, options) => {
|
|
2294
|
+
const client = new V0Client();
|
|
2295
|
+
await client.convertFile(file, options.output);
|
|
2296
|
+
});
|
|
2297
|
+
v0Cmd.command("import").alias("i").description("V0 URL/\uD074\uB9BD\uBCF4\uB4DC\uC5D0\uC11C \uAC00\uC838\uC624\uAE30").action(async () => {
|
|
2298
|
+
const client = new V0Client();
|
|
2299
|
+
await client.importFromUrl();
|
|
2300
|
+
});
|
|
2301
|
+
v0Cmd.command("guide").alias("g").description("V0 \u2192 AXIS \uBCC0\uD658 \uAC00\uC774\uB4DC").action(() => {
|
|
2302
|
+
const client = new V0Client();
|
|
2303
|
+
client.showGuide();
|
|
2304
|
+
});
|
|
2305
|
+
v0Cmd.command("setup").description("V0 GitHub \uC5F0\uB3D9 \uC548\uB0B4").action(async () => {
|
|
2306
|
+
const client = new V0Client();
|
|
2307
|
+
await client.setupGitHubSync();
|
|
2308
|
+
});
|
|
2309
|
+
var libraryCmd = program.command("library").alias("lib").description("\uB514\uC790\uC778 \uC2DC\uC2A4\uD15C \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC218\uC9D1/\uBD84\uB958/\uBC30\uCE58");
|
|
2310
|
+
libraryCmd.command("collect").alias("c").description("\uC678\uBD80 \uC18C\uC2A4\uC5D0\uC11C \uCEF4\uD3EC\uB10C\uD2B8 \uC218\uC9D1").option("-s, --source <source>", "\uC18C\uC2A4 \uC9C0\uC815 (shadcn, monet, v0, axis)").option("-i, --incremental", "\uC99D\uBD84 \uC218\uC9D1 (\uBCC0\uACBD\uBD84\uB9CC)").option("--dry-run", "\uC2E4\uC81C \uC800\uC7A5 \uC5C6\uC774 \uBBF8\uB9AC\uBCF4\uAE30").action(async (options) => {
|
|
2311
|
+
const curator = new LibraryCurator();
|
|
2312
|
+
const sources = options.source ? [options.source] : void 0;
|
|
2313
|
+
const results = await curator.collectAll(sources, {
|
|
2314
|
+
incremental: options.incremental,
|
|
2315
|
+
verbose: true
|
|
2316
|
+
});
|
|
2317
|
+
if (!options.dryRun) {
|
|
2318
|
+
const index = await curator.generateIndex();
|
|
2319
|
+
await curator.saveIndex(index);
|
|
2320
|
+
}
|
|
2321
|
+
let total = 0;
|
|
2322
|
+
let failed = 0;
|
|
2323
|
+
for (const [, result] of results) {
|
|
2324
|
+
total += result.collected;
|
|
2325
|
+
failed += result.failed;
|
|
2326
|
+
}
|
|
2327
|
+
console.log(chalk8.blue(`
|
|
2328
|
+
\u{1F4CA} \uC218\uC9D1 \uC644\uB8CC: ${total}\uAC1C \uCEF4\uD3EC\uB10C\uD2B8`));
|
|
2329
|
+
if (failed > 0) {
|
|
2330
|
+
console.log(chalk8.yellow(` \uC2E4\uD328: ${failed}\uAC1C`));
|
|
2331
|
+
}
|
|
2332
|
+
});
|
|
2333
|
+
libraryCmd.command("list").alias("ls").description("\uB77C\uC774\uBE0C\uB7EC\uB9AC \uCEF4\uD3EC\uB10C\uD2B8 \uBAA9\uB85D").option("-c, --category <category>", "\uCE74\uD14C\uACE0\uB9AC \uD544\uD130").option("-s, --source <source>", "\uC18C\uC2A4 \uD544\uD130").action(async (options) => {
|
|
2334
|
+
const curator = new LibraryCurator();
|
|
2335
|
+
const index = await curator.loadIndex();
|
|
2336
|
+
if (!index) {
|
|
2337
|
+
console.log(chalk8.yellow("\n\u26A0\uFE0F \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC778\uB371\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
2338
|
+
console.log(chalk8.gray(" \uD574\uACB0: npx @axis-ds/cli library collect \uBA85\uB839\uC5B4\uB85C \uC218\uC9D1\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.\n"));
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
let components = index.components;
|
|
2342
|
+
if (options.category) {
|
|
2343
|
+
components = components.filter((c) => c.category === options.category);
|
|
2344
|
+
}
|
|
2345
|
+
if (options.source) {
|
|
2346
|
+
components = components.filter((c) => c.source.type === options.source);
|
|
2347
|
+
}
|
|
2348
|
+
console.log(chalk8.blue(`
|
|
2349
|
+
\u{1F4E6} Library Components (${components.length}\uAC1C)
|
|
2350
|
+
`));
|
|
2351
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
2352
|
+
for (const comp of components) {
|
|
2353
|
+
const cat = comp.category;
|
|
2354
|
+
if (!byCategory.has(cat)) {
|
|
2355
|
+
byCategory.set(cat, []);
|
|
2356
|
+
}
|
|
2357
|
+
byCategory.get(cat).push(comp);
|
|
2358
|
+
}
|
|
2359
|
+
for (const [category, items] of byCategory) {
|
|
2360
|
+
console.log(chalk8.bold(`${category} (${items.length})`));
|
|
2361
|
+
for (const item of items.slice(0, 10)) {
|
|
2362
|
+
console.log(
|
|
2363
|
+
` ${chalk8.cyan(item.name.padEnd(25))} ${chalk8.gray(item.source.type.padEnd(8))} ${chalk8.gray(item.description.slice(0, 40))}`
|
|
2364
|
+
);
|
|
2365
|
+
}
|
|
2366
|
+
if (items.length > 10) {
|
|
2367
|
+
console.log(chalk8.gray(` ... \uC678 ${items.length - 10}\uAC1C`));
|
|
2368
|
+
}
|
|
2369
|
+
console.log();
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
|
+
libraryCmd.command("search <query>").alias("s").description("\uCEF4\uD3EC\uB10C\uD2B8 \uAC80\uC0C9").option("-c, --category <category>", "\uCE74\uD14C\uACE0\uB9AC \uD544\uD130").option("-s, --source <source>", "\uC18C\uC2A4 \uD544\uD130").action(async (query, options) => {
|
|
2373
|
+
const curator = new LibraryCurator();
|
|
2374
|
+
const results = await curator.searchComponents(query, {
|
|
2375
|
+
category: options.category,
|
|
2376
|
+
source: options.source
|
|
2377
|
+
});
|
|
2378
|
+
if (results.length === 0) {
|
|
2379
|
+
console.log(chalk8.yellow(`
|
|
2380
|
+
'${query}'\uC5D0 \uB300\uD55C \uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.
|
|
2381
|
+
`));
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
console.log(chalk8.blue(`
|
|
2385
|
+
\u{1F50D} '${query}' \uAC80\uC0C9 \uACB0\uACFC (${results.length}\uAC1C)
|
|
2386
|
+
`));
|
|
2387
|
+
for (const comp of results.slice(0, 20)) {
|
|
2388
|
+
console.log(
|
|
2389
|
+
` ${chalk8.cyan(comp.name.padEnd(25))} ${chalk8.gray(comp.source.type.padEnd(8))} ${chalk8.gray(comp.category.padEnd(12))} ${chalk8.gray(comp.description.slice(0, 30))}`
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
2392
|
+
if (results.length > 20) {
|
|
2393
|
+
console.log(chalk8.gray(`
|
|
2394
|
+
... \uC678 ${results.length - 20}\uAC1C`));
|
|
2395
|
+
}
|
|
2396
|
+
console.log();
|
|
2397
|
+
});
|
|
2398
|
+
libraryCmd.command("stats").description("\uB77C\uC774\uBE0C\uB7EC\uB9AC \uD1B5\uACC4").action(async () => {
|
|
2399
|
+
const curator = new LibraryCurator();
|
|
2400
|
+
await curator.printStats();
|
|
2401
|
+
});
|
|
2402
|
+
libraryCmd.command("publish").description("\uC0AC\uC774\uD2B8\uC5D0 \uB77C\uC774\uBE0C\uB7EC\uB9AC \uBC30\uCE58").option("-o, --output <dir>", "\uCD9C\uB825 \uB514\uB809\uD1A0\uB9AC", "apps/web/public/library").option("--minify", "JSON \uC555\uCD95").option("--clean", "\uAE30\uC874 \uBC30\uCE58 \uC815\uB9AC \uD6C4 \uC7AC\uBC30\uCE58").action(async (options) => {
|
|
2403
|
+
const curator = new LibraryCurator();
|
|
2404
|
+
const publisher2 = new Publisher();
|
|
2405
|
+
const index = await curator.loadIndex();
|
|
2406
|
+
if (!index) {
|
|
2407
|
+
console.log(chalk8.yellow("\n\u26A0\uFE0F \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC778\uB371\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
2408
|
+
console.log(chalk8.gray(" \uD574\uACB0: npx @axis-ds/cli library collect \uBA85\uB839\uC5B4\uB85C \uC218\uC9D1\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.\n"));
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
console.log(chalk8.blue("\n\u{1F4E4} Library \uBC30\uCE58\n"));
|
|
2412
|
+
console.log(chalk8.gray(`\uCD1D ${index.stats.total}\uAC1C \uCEF4\uD3EC\uB10C\uD2B8
|
|
2413
|
+
`));
|
|
2414
|
+
if (options.clean) {
|
|
2415
|
+
await publisher2.clean(options.output);
|
|
2416
|
+
}
|
|
2417
|
+
const result = await publisher2.publish(index, {
|
|
2418
|
+
outputDir: options.output,
|
|
2419
|
+
minify: options.minify,
|
|
2420
|
+
generateSearchIndex: true
|
|
2421
|
+
});
|
|
2422
|
+
if (result.success) {
|
|
2423
|
+
console.log(chalk8.green(`
|
|
2424
|
+
\u2713 \uBC30\uCE58 \uC644\uB8CC`));
|
|
2425
|
+
console.log(chalk8.gray(` - \uCD9C\uB825 \uB514\uB809\uD1A0\uB9AC: ${result.outputDir}`));
|
|
2426
|
+
console.log(chalk8.gray(` - \uC0DD\uC131\uB41C \uD30C\uC77C: ${result.files.length}\uAC1C`));
|
|
2427
|
+
console.log(chalk8.gray(` - \uCE74\uD14C\uACE0\uB9AC: ${result.stats.categoriesPublished}\uAC1C`));
|
|
2428
|
+
console.log(chalk8.gray(` - \uAC80\uC0C9 \uC778\uB371\uC2A4: ${result.stats.searchIndexSize}\uAC1C \uD56D\uBAA9`));
|
|
2429
|
+
} else {
|
|
2430
|
+
console.log(chalk8.red(`
|
|
2431
|
+
\u2717 \uBC30\uCE58 \uC2E4\uD328`));
|
|
2432
|
+
for (const error of result.errors) {
|
|
2433
|
+
console.log(chalk8.red(` - ${error}`));
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
console.log();
|
|
2437
|
+
});
|
|
2438
|
+
registerSyncCommand(program);
|
|
2439
|
+
registerTemplateCommand(program);
|
|
2440
|
+
registerCheckCommand(program);
|
|
2441
|
+
async function getComponentInfo(name, category) {
|
|
2442
|
+
const localComponents = {
|
|
2443
|
+
button: {
|
|
2444
|
+
name: "button",
|
|
2445
|
+
type: "ui",
|
|
2446
|
+
description: "Primary button component",
|
|
2447
|
+
dependencies: ["@radix-ui/react-slot", "class-variance-authority", "clsx", "tailwind-merge"],
|
|
2448
|
+
files: [
|
|
2449
|
+
{
|
|
2450
|
+
path: "button.tsx",
|
|
2451
|
+
content: `import * as React from "react"
|
|
2452
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
2453
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
2454
|
+
import { cn } from "./utils"
|
|
2455
|
+
|
|
2456
|
+
const buttonVariants = cva(
|
|
2457
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
|
2458
|
+
{
|
|
2459
|
+
variants: {
|
|
2460
|
+
variant: {
|
|
2461
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
2462
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
2463
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
2464
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
2465
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
2466
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
2467
|
+
},
|
|
2468
|
+
size: {
|
|
2469
|
+
sm: "h-8 px-3 text-xs",
|
|
2470
|
+
default: "h-10 px-4 py-2",
|
|
2471
|
+
lg: "h-12 px-6 text-base",
|
|
2472
|
+
icon: "h-10 w-10",
|
|
2473
|
+
},
|
|
2474
|
+
},
|
|
2475
|
+
defaultVariants: { variant: "default", size: "default" },
|
|
2476
|
+
}
|
|
2477
|
+
)
|
|
2478
|
+
|
|
2479
|
+
export interface ButtonProps
|
|
2480
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
2481
|
+
VariantProps<typeof buttonVariants> {
|
|
2482
|
+
asChild?: boolean
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
2486
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
2487
|
+
const Comp = asChild ? Slot : "button"
|
|
2488
|
+
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
|
|
2489
|
+
}
|
|
2490
|
+
)
|
|
2491
|
+
Button.displayName = "Button"
|
|
2492
|
+
|
|
2493
|
+
export { Button, buttonVariants }
|
|
2494
|
+
`
|
|
2495
|
+
}
|
|
2496
|
+
]
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
return localComponents[name] || null;
|
|
2500
|
+
}
|
|
2501
|
+
program.parse();
|