@seed-design/cli 0.0.0-alpha-20241014082441 → 0.0.0-alpha-20241014090450
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/bin/index.mjs +502 -2
- package/package.json +1 -1
- package/src/commands/add.ts +14 -14
- package/src/schema.ts +39 -12
- package/src/test/add-relative-components.test.ts +7 -12
- package/src/utils/add-relative-components.ts +3 -3
- package/src/utils/get-metadata.ts +7 -10
package/bin/index.mjs
CHANGED
|
@@ -1,3 +1,503 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
// src/utils/get-config.ts
|
|
4
|
+
import { cosmiconfig } from "cosmiconfig";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
var MODULE_NAME = "seed-design";
|
|
8
|
+
var explorer = cosmiconfig(MODULE_NAME, {
|
|
9
|
+
searchPlaces: [`${MODULE_NAME}.json`]
|
|
10
|
+
});
|
|
11
|
+
var rawConfigSchema = z.object({
|
|
12
|
+
$schema: z.string().optional(),
|
|
13
|
+
rsc: z.coerce.boolean().default(false),
|
|
14
|
+
tsx: z.coerce.boolean().default(true),
|
|
15
|
+
css: z.coerce.boolean().default(true),
|
|
16
|
+
path: z.string()
|
|
17
|
+
}).strict();
|
|
18
|
+
var configSchema = rawConfigSchema.extend({
|
|
19
|
+
resolvedUIPaths: z.string()
|
|
20
|
+
});
|
|
21
|
+
async function getConfig(cwd) {
|
|
22
|
+
const config = await getRawConfig(cwd);
|
|
23
|
+
if (!config) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return await resolveConfigPaths(cwd, config);
|
|
27
|
+
}
|
|
28
|
+
async function resolveConfigPaths(cwd, config) {
|
|
29
|
+
const seedComponentRootPath = path.resolve(cwd, config.path);
|
|
30
|
+
return configSchema.parse({
|
|
31
|
+
...config,
|
|
32
|
+
resolvedUIPaths: path.join(seedComponentRootPath, "ui")
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async function getRawConfig(cwd) {
|
|
36
|
+
try {
|
|
37
|
+
const configResult = await explorer.search(cwd);
|
|
38
|
+
if (!configResult) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return rawConfigSchema.parse(configResult.config);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.log(error);
|
|
44
|
+
throw new Error(`Invalid configuration found in ${cwd}/seed-design.json.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/schema.ts
|
|
49
|
+
import { z as z2 } from "zod";
|
|
50
|
+
var registryComponentItemSchema = z2.object({
|
|
51
|
+
/**
|
|
52
|
+
* @description 컴포넌트 이름
|
|
53
|
+
* @example chip-tabs, alert-dialog
|
|
54
|
+
*/
|
|
55
|
+
name: z2.string(),
|
|
56
|
+
description: z2.string().optional(),
|
|
57
|
+
/**
|
|
58
|
+
* @description 컴포넌트 의존성
|
|
59
|
+
* @example @seed-design/react-tabs
|
|
60
|
+
*/
|
|
61
|
+
dependencies: z2.array(z2.string()).optional(),
|
|
62
|
+
/**
|
|
63
|
+
* @description 컴포넌트 개발 의존성
|
|
64
|
+
*/
|
|
65
|
+
devDependencies: z2.array(z2.string()).optional(),
|
|
66
|
+
/**
|
|
67
|
+
* @description 컴포넌트 내부의 Seed Design 컴포넌트 의존성
|
|
68
|
+
* @example action-button
|
|
69
|
+
*/
|
|
70
|
+
innerDependencies: z2.array(z2.string()).optional(),
|
|
71
|
+
/**
|
|
72
|
+
* @description 컴포넌트 코드 스니펫 경로, 여러 파일이 될 수 있어서 배열로 정의
|
|
73
|
+
* @example component/alert-dialog.tsx
|
|
74
|
+
*/
|
|
75
|
+
files: z2.array(z2.string())
|
|
76
|
+
});
|
|
77
|
+
var registryComponentSchema = z2.array(registryComponentItemSchema);
|
|
78
|
+
var omittedRegistryComponentSchema = registryComponentItemSchema.omit({ files: true });
|
|
79
|
+
var registryComponentItemMachineGeneratedSchema = omittedRegistryComponentSchema.extend({
|
|
80
|
+
registries: z2.array(
|
|
81
|
+
z2.object({
|
|
82
|
+
name: z2.string(),
|
|
83
|
+
content: z2.string()
|
|
84
|
+
})
|
|
85
|
+
)
|
|
86
|
+
});
|
|
87
|
+
var registryComponentMachineGeneratedSchema = z2.array(
|
|
88
|
+
registryComponentItemMachineGeneratedSchema
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// src/utils/get-metadata.ts
|
|
92
|
+
var BASE_URL = false ? "https://component.seed-design.io" : "http://localhost:3000";
|
|
93
|
+
async function fetchRegistryComponentItem(fileNames) {
|
|
94
|
+
try {
|
|
95
|
+
const results = await Promise.all(
|
|
96
|
+
fileNames.map(async (fileName) => {
|
|
97
|
+
const response = await fetch(`${BASE_URL}/__registry__/component/${fileName}.json`);
|
|
98
|
+
return await response.json();
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
return results;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.log(error);
|
|
104
|
+
throw new Error(`Failed to fetch registry from ${BASE_URL}.`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function getRegistryComponentIndex() {
|
|
108
|
+
try {
|
|
109
|
+
const [result] = await fetchRegistryComponentItem(["index"]);
|
|
110
|
+
return registryComponentSchema.parse(result);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.log(error);
|
|
113
|
+
throw new Error(`Failed to fetch components from ${BASE_URL}.`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/utils/get-package-manager.ts
|
|
118
|
+
import { detect } from "@antfu/ni";
|
|
119
|
+
async function getPackageManager(targetDir) {
|
|
120
|
+
const packageManager = await detect({ programmatic: true, cwd: targetDir });
|
|
121
|
+
if (packageManager === "yarn@berry")
|
|
122
|
+
return "yarn";
|
|
123
|
+
if (packageManager === "pnpm@6")
|
|
124
|
+
return "pnpm";
|
|
125
|
+
if (packageManager === "bun")
|
|
126
|
+
return "bun";
|
|
127
|
+
return packageManager ?? "npm";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/utils/transformers/index.ts
|
|
131
|
+
import { promises as fs } from "fs";
|
|
132
|
+
import { tmpdir } from "os";
|
|
133
|
+
import path2 from "path";
|
|
134
|
+
|
|
135
|
+
// src/utils/transformers/transform-jsx.ts
|
|
136
|
+
import { transformFromAstSync } from "@babel/core";
|
|
137
|
+
import transformTypescript from "@babel/plugin-transform-typescript";
|
|
138
|
+
import * as recast from "recast";
|
|
139
|
+
import { parse as parse2 } from "@babel/parser";
|
|
140
|
+
var PARSE_OPTIONS = {
|
|
141
|
+
sourceType: "module",
|
|
142
|
+
allowImportExportEverywhere: true,
|
|
143
|
+
allowReturnOutsideFunction: true,
|
|
144
|
+
startLine: 1,
|
|
145
|
+
tokens: true,
|
|
146
|
+
plugins: [
|
|
147
|
+
"asyncGenerators",
|
|
148
|
+
"bigInt",
|
|
149
|
+
"classPrivateMethods",
|
|
150
|
+
"classPrivateProperties",
|
|
151
|
+
"classProperties",
|
|
152
|
+
"classStaticBlock",
|
|
153
|
+
"decimal",
|
|
154
|
+
"decorators-legacy",
|
|
155
|
+
"doExpressions",
|
|
156
|
+
"dynamicImport",
|
|
157
|
+
"exportDefaultFrom",
|
|
158
|
+
"exportNamespaceFrom",
|
|
159
|
+
"functionBind",
|
|
160
|
+
"functionSent",
|
|
161
|
+
"importAssertions",
|
|
162
|
+
"importMeta",
|
|
163
|
+
"nullishCoalescingOperator",
|
|
164
|
+
"numericSeparator",
|
|
165
|
+
"objectRestSpread",
|
|
166
|
+
"optionalCatchBinding",
|
|
167
|
+
"optionalChaining",
|
|
168
|
+
[
|
|
169
|
+
"pipelineOperator",
|
|
170
|
+
{
|
|
171
|
+
proposal: "minimal"
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
[
|
|
175
|
+
"recordAndTuple",
|
|
176
|
+
{
|
|
177
|
+
syntaxType: "hash"
|
|
178
|
+
}
|
|
179
|
+
],
|
|
180
|
+
"throwExpressions",
|
|
181
|
+
"topLevelAwait",
|
|
182
|
+
"v8intrinsic",
|
|
183
|
+
"typescript",
|
|
184
|
+
"jsx"
|
|
185
|
+
]
|
|
186
|
+
};
|
|
187
|
+
var transformJsx = async ({ sourceFile, config }) => {
|
|
188
|
+
const output = sourceFile.getFullText();
|
|
189
|
+
if (config.tsx) {
|
|
190
|
+
return output;
|
|
191
|
+
}
|
|
192
|
+
const ast = recast.parse(output, {
|
|
193
|
+
parser: {
|
|
194
|
+
parse: (code) => {
|
|
195
|
+
return parse2(code, PARSE_OPTIONS);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
const result = transformFromAstSync(ast, output, {
|
|
200
|
+
cloneInputAst: false,
|
|
201
|
+
code: false,
|
|
202
|
+
ast: true,
|
|
203
|
+
plugins: [transformTypescript],
|
|
204
|
+
configFile: false
|
|
205
|
+
});
|
|
206
|
+
if (!result || !result.ast) {
|
|
207
|
+
throw new Error("Failed to transform JSX");
|
|
208
|
+
}
|
|
209
|
+
return recast.print(result.ast).code;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// src/utils/transformers/transform-rsc.ts
|
|
213
|
+
import { SyntaxKind } from "ts-morph";
|
|
214
|
+
var transformRsc = async ({ sourceFile, config }) => {
|
|
215
|
+
if (config.rsc) {
|
|
216
|
+
return sourceFile;
|
|
217
|
+
}
|
|
218
|
+
const first = sourceFile.getFirstChildByKind(SyntaxKind.ExpressionStatement);
|
|
219
|
+
if (first?.getText() === `"use client";`) {
|
|
220
|
+
first.remove();
|
|
221
|
+
}
|
|
222
|
+
return sourceFile;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// src/utils/transformers/transform-css.ts
|
|
226
|
+
var transformCSS = async ({ sourceFile, config }) => {
|
|
227
|
+
if (config.css) {
|
|
228
|
+
return sourceFile;
|
|
229
|
+
}
|
|
230
|
+
const imports = sourceFile.getImportDeclarations();
|
|
231
|
+
const cssImports = imports.filter((i) => i.getModuleSpecifierValue().endsWith(".css"));
|
|
232
|
+
for (const cssImport of cssImports) {
|
|
233
|
+
cssImport.remove();
|
|
234
|
+
}
|
|
235
|
+
return sourceFile;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// src/utils/transformers/index.ts
|
|
239
|
+
import { Project, ScriptKind } from "ts-morph";
|
|
240
|
+
var transformers = [transformRsc, transformCSS];
|
|
241
|
+
var project = new Project({
|
|
242
|
+
compilerOptions: {}
|
|
243
|
+
});
|
|
244
|
+
async function createTempSourceFile(filename) {
|
|
245
|
+
const dir = await fs.mkdtemp(path2.join(tmpdir(), "seed-deisgn-"));
|
|
246
|
+
return path2.join(dir, filename);
|
|
247
|
+
}
|
|
248
|
+
async function transform(opts) {
|
|
249
|
+
const tempFile = await createTempSourceFile(opts.filename);
|
|
250
|
+
const sourceFile = project.createSourceFile(tempFile, opts.raw, {
|
|
251
|
+
scriptKind: ScriptKind.TSX
|
|
252
|
+
});
|
|
253
|
+
for (const transformer of transformers) {
|
|
254
|
+
transformer({ sourceFile, ...opts });
|
|
255
|
+
}
|
|
256
|
+
return await transformJsx({
|
|
257
|
+
sourceFile,
|
|
258
|
+
...opts
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/commands/add.ts
|
|
263
|
+
import * as p from "@clack/prompts";
|
|
264
|
+
import { execa } from "execa";
|
|
265
|
+
import fs2 from "fs-extra";
|
|
266
|
+
import path3 from "path";
|
|
267
|
+
import color from "picocolors";
|
|
268
|
+
import { z as z3 } from "zod";
|
|
269
|
+
|
|
270
|
+
// src/utils/add-relative-components.ts
|
|
271
|
+
function addRelativeComponents(userSelects, registryIndex) {
|
|
272
|
+
const selectedComponents = /* @__PURE__ */ new Set();
|
|
273
|
+
function addSeedDependencies(componentName) {
|
|
274
|
+
if (selectedComponents.has(componentName))
|
|
275
|
+
return;
|
|
276
|
+
selectedComponents.add(componentName);
|
|
277
|
+
const component = registryIndex.find((c) => c.name === componentName);
|
|
278
|
+
if (!component)
|
|
279
|
+
return;
|
|
280
|
+
if (component.innerDependencies) {
|
|
281
|
+
for (const dep of component.innerDependencies) {
|
|
282
|
+
addSeedDependencies(dep);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (const componentName of userSelects) {
|
|
287
|
+
addSeedDependencies(componentName);
|
|
288
|
+
}
|
|
289
|
+
return Array.from(selectedComponents);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/commands/add.ts
|
|
293
|
+
var addOptionsSchema = z3.object({
|
|
294
|
+
components: z3.array(z3.string()).optional(),
|
|
295
|
+
cwd: z3.string(),
|
|
296
|
+
all: z3.boolean()
|
|
297
|
+
// yes: z.boolean(),
|
|
298
|
+
// overwrite: z.boolean(),
|
|
299
|
+
// path: z.string().optional(),
|
|
300
|
+
});
|
|
301
|
+
var addCommand = (cli) => {
|
|
302
|
+
cli.command("add [...components]", "add component").option("-a, --all", "Add all components", {
|
|
303
|
+
default: false
|
|
304
|
+
}).option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", {
|
|
305
|
+
default: process.cwd()
|
|
306
|
+
}).example("seed-design add box-button").example("seed-design add alert-dialog").action(async (components, opts) => {
|
|
307
|
+
const options = addOptionsSchema.parse({
|
|
308
|
+
components,
|
|
309
|
+
...opts
|
|
310
|
+
});
|
|
311
|
+
const highlight = (text2) => color.cyan(text2);
|
|
312
|
+
const cwd = options.cwd;
|
|
313
|
+
if (!fs2.existsSync(cwd)) {
|
|
314
|
+
p.log.error(`The path ${cwd} does not exist. Please try again.`);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
const registryComponentIndex = await getRegistryComponentIndex();
|
|
318
|
+
let selectedComponents = options.all ? registryComponentIndex.map((registry) => registry.name) : options.components;
|
|
319
|
+
if (!options.components?.length && !options.all) {
|
|
320
|
+
const selects = await p.multiselect({
|
|
321
|
+
message: "Select all components to add",
|
|
322
|
+
options: registryComponentIndex.map((metadata) => {
|
|
323
|
+
return {
|
|
324
|
+
label: metadata.name,
|
|
325
|
+
value: metadata.name,
|
|
326
|
+
hint: metadata.description
|
|
327
|
+
};
|
|
328
|
+
})
|
|
329
|
+
});
|
|
330
|
+
if (p.isCancel(selects)) {
|
|
331
|
+
p.log.error("Aborted.");
|
|
332
|
+
process.exit(0);
|
|
333
|
+
}
|
|
334
|
+
selectedComponents = selects;
|
|
335
|
+
}
|
|
336
|
+
if (!selectedComponents?.length) {
|
|
337
|
+
p.log.error("No components found.");
|
|
338
|
+
process.exit(0);
|
|
339
|
+
}
|
|
340
|
+
const allComponents = addRelativeComponents(selectedComponents, registryComponentIndex);
|
|
341
|
+
const addedComponents = allComponents.filter((c) => !selectedComponents.includes(c));
|
|
342
|
+
const config = await getConfig(cwd);
|
|
343
|
+
const registryComponentItems = await fetchRegistryComponentItem(allComponents);
|
|
344
|
+
p.log.message(`Selection: ${highlight(selectedComponents.join(", "))}`);
|
|
345
|
+
if (addedComponents.length) {
|
|
346
|
+
p.log.message(
|
|
347
|
+
`Inner Dependencies: ${highlight(addedComponents.join(", "))} will be also added.`
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
for (const component of registryComponentItems) {
|
|
351
|
+
for (const registry of component.registries) {
|
|
352
|
+
const UIFolderPath = config.resolvedUIPaths;
|
|
353
|
+
if (!fs2.existsSync(UIFolderPath)) {
|
|
354
|
+
await fs2.mkdir(UIFolderPath, { recursive: true });
|
|
355
|
+
}
|
|
356
|
+
let filePath = path3.resolve(UIFolderPath, registry.name);
|
|
357
|
+
const content = await transform({
|
|
358
|
+
filename: registry.name,
|
|
359
|
+
config,
|
|
360
|
+
raw: registry.content
|
|
361
|
+
});
|
|
362
|
+
if (!config.tsx) {
|
|
363
|
+
filePath = filePath.replace(/\.tsx$/, ".jsx");
|
|
364
|
+
filePath = filePath.replace(/\.ts$/, ".js");
|
|
365
|
+
}
|
|
366
|
+
await fs2.writeFile(filePath, content);
|
|
367
|
+
const relativePath = path3.relative(cwd, filePath);
|
|
368
|
+
p.log.info(`Added ${highlight(registry.name)} to ${highlight(relativePath)}`);
|
|
369
|
+
}
|
|
370
|
+
const packageManager = await getPackageManager(cwd);
|
|
371
|
+
const { start, stop } = p.spinner();
|
|
372
|
+
if (component.dependencies?.length) {
|
|
373
|
+
start(color.gray("Installing dependencies"));
|
|
374
|
+
const result = await execa(
|
|
375
|
+
packageManager,
|
|
376
|
+
[packageManager === "npm" ? "install" : "add", ...component.dependencies],
|
|
377
|
+
{
|
|
378
|
+
cwd
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
if (result.failed) {
|
|
382
|
+
console.error(result.all);
|
|
383
|
+
process.exit(1);
|
|
384
|
+
} else {
|
|
385
|
+
for (const deps of component.dependencies) {
|
|
386
|
+
p.log.info(`- ${deps}`);
|
|
387
|
+
}
|
|
388
|
+
stop("Dependencies installed.");
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (component.devDependencies?.length) {
|
|
392
|
+
start(color.gray("Installing devDependencies"));
|
|
393
|
+
const result = await execa(
|
|
394
|
+
packageManager,
|
|
395
|
+
[packageManager === "npm" ? "install" : "add", "-D", ...component.devDependencies],
|
|
396
|
+
{
|
|
397
|
+
cwd
|
|
398
|
+
}
|
|
399
|
+
);
|
|
400
|
+
if (result.failed) {
|
|
401
|
+
console.error(result.all);
|
|
402
|
+
process.exit(1);
|
|
403
|
+
} else {
|
|
404
|
+
for (const deps of component.devDependencies) {
|
|
405
|
+
p.log.info(`- ${deps}`);
|
|
406
|
+
}
|
|
407
|
+
stop("Dependencies installed.");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
p.outro("Components added.");
|
|
412
|
+
});
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// src/utils/get-package-info.ts
|
|
416
|
+
import findup from "findup-sync";
|
|
417
|
+
import fs3 from "fs-extra";
|
|
418
|
+
var PACKAGE_JSON = "package.json";
|
|
419
|
+
function getPackagePath() {
|
|
420
|
+
const packageJsonPath = findup(PACKAGE_JSON);
|
|
421
|
+
if (!packageJsonPath) {
|
|
422
|
+
throw new Error("No package.json file found in the project.");
|
|
423
|
+
}
|
|
424
|
+
return packageJsonPath;
|
|
425
|
+
}
|
|
426
|
+
function getPackageInfo() {
|
|
427
|
+
return fs3.readJSONSync(getPackagePath());
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/index.ts
|
|
431
|
+
import { cac } from "cac";
|
|
432
|
+
|
|
433
|
+
// src/commands/init.ts
|
|
434
|
+
import * as p2 from "@clack/prompts";
|
|
435
|
+
import fs4 from "fs-extra";
|
|
436
|
+
import path4 from "path";
|
|
437
|
+
import color2 from "picocolors";
|
|
438
|
+
import { z as z4 } from "zod";
|
|
439
|
+
var initOptionsSchema = z4.object({
|
|
440
|
+
cwd: z4.string()
|
|
441
|
+
});
|
|
442
|
+
var initCommand = (cli) => {
|
|
443
|
+
cli.command("init", "initialize seed-design.json").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", {
|
|
444
|
+
default: process.cwd()
|
|
445
|
+
}).action(async (opts) => {
|
|
446
|
+
const options = initOptionsSchema.parse({ ...opts });
|
|
447
|
+
const highlight = (text2) => color2.cyan(text2);
|
|
448
|
+
const group2 = await p2.group(
|
|
449
|
+
{
|
|
450
|
+
tsx: () => p2.confirm({
|
|
451
|
+
message: `Would you like to use ${highlight("TypeScript")} (recommended)?`,
|
|
452
|
+
initialValue: true
|
|
453
|
+
}),
|
|
454
|
+
rsc: () => p2.confirm({
|
|
455
|
+
message: `Are you using ${highlight("React Server Components")}?`,
|
|
456
|
+
initialValue: false
|
|
457
|
+
}),
|
|
458
|
+
css: () => p2.confirm({
|
|
459
|
+
message: `Would you like to use ${highlight("CSS Modules")}? (If true, CSS import will be added in components)`,
|
|
460
|
+
initialValue: true
|
|
461
|
+
}),
|
|
462
|
+
path: () => p2.text({
|
|
463
|
+
message: `Enter the path to your ${highlight("seed-design directory")}`,
|
|
464
|
+
initialValue: "./seed-design",
|
|
465
|
+
defaultValue: "./seed-design",
|
|
466
|
+
placeholder: "./seed-design"
|
|
467
|
+
})
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
onCancel: () => {
|
|
471
|
+
p2.cancel("Operation cancelled.");
|
|
472
|
+
process.exit(0);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
);
|
|
476
|
+
const config = {
|
|
477
|
+
rsc: group2.rsc,
|
|
478
|
+
tsx: group2.tsx,
|
|
479
|
+
css: group2.css,
|
|
480
|
+
path: group2.path
|
|
481
|
+
};
|
|
482
|
+
const { start, stop } = p2.spinner();
|
|
483
|
+
start("Writing seed-design.json...");
|
|
484
|
+
const targetPath = path4.resolve(options.cwd, "seed-design.json");
|
|
485
|
+
await fs4.writeFile(targetPath, `${JSON.stringify(config, null, 2)}
|
|
486
|
+
`, "utf-8");
|
|
487
|
+
const relativePath = path4.relative(process.cwd(), targetPath);
|
|
488
|
+
stop(`seed-design.json written to ${highlight(relativePath)}`);
|
|
489
|
+
});
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
// src/index.ts
|
|
493
|
+
var NAME = "seed-design";
|
|
494
|
+
var CLI = cac(NAME);
|
|
495
|
+
async function main() {
|
|
496
|
+
const packageInfo = getPackageInfo();
|
|
497
|
+
addCommand(CLI);
|
|
498
|
+
initCommand(CLI);
|
|
499
|
+
CLI.version(packageInfo.version || "1.0.0", "-v, --version");
|
|
500
|
+
CLI.help();
|
|
501
|
+
CLI.parse();
|
|
502
|
+
}
|
|
503
|
+
main();
|
package/package.json
CHANGED
package/src/commands/add.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getConfig } from "@/src/utils/get-config";
|
|
2
|
-
import {
|
|
2
|
+
import { fetchRegistryComponentItem, getRegistryComponentIndex } from "@/src/utils/get-metadata";
|
|
3
3
|
import { getPackageManager } from "@/src/utils/get-package-manager";
|
|
4
4
|
import { transform } from "@/src/utils/transformers";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
@@ -45,10 +45,10 @@ export const addCommand = (cli: CAC) => {
|
|
|
45
45
|
process.exit(1);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
const
|
|
48
|
+
const registryComponentIndex = await getRegistryComponentIndex();
|
|
49
49
|
|
|
50
50
|
let selectedComponents: string[] = options.all
|
|
51
|
-
?
|
|
51
|
+
? registryComponentIndex.map((registry) => registry.name)
|
|
52
52
|
: options.components;
|
|
53
53
|
|
|
54
54
|
if (!options.components?.length && !options.all) {
|
|
@@ -57,7 +57,7 @@ export const addCommand = (cli: CAC) => {
|
|
|
57
57
|
string
|
|
58
58
|
>({
|
|
59
59
|
message: "Select all components to add",
|
|
60
|
-
options:
|
|
60
|
+
options: registryComponentIndex.map((metadata) => {
|
|
61
61
|
return {
|
|
62
62
|
label: metadata.name,
|
|
63
63
|
value: metadata.name,
|
|
@@ -79,10 +79,10 @@ export const addCommand = (cli: CAC) => {
|
|
|
79
79
|
process.exit(0);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
const allComponents = addRelativeComponents(selectedComponents,
|
|
82
|
+
const allComponents = addRelativeComponents(selectedComponents, registryComponentIndex);
|
|
83
83
|
const addedComponents = allComponents.filter((c) => !selectedComponents.includes(c));
|
|
84
84
|
const config = await getConfig(cwd);
|
|
85
|
-
const
|
|
85
|
+
const registryComponentItems = await fetchRegistryComponentItem(allComponents);
|
|
86
86
|
|
|
87
87
|
p.log.message(`Selection: ${highlight(selectedComponents.join(", "))}`);
|
|
88
88
|
if (addedComponents.length) {
|
|
@@ -91,8 +91,8 @@ export const addCommand = (cli: CAC) => {
|
|
|
91
91
|
);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
for (const
|
|
95
|
-
for (const registry of
|
|
94
|
+
for (const component of registryComponentItems) {
|
|
95
|
+
for (const registry of component.registries) {
|
|
96
96
|
const UIFolderPath = config.resolvedUIPaths;
|
|
97
97
|
|
|
98
98
|
if (!fs.existsSync(UIFolderPath)) {
|
|
@@ -122,12 +122,12 @@ export const addCommand = (cli: CAC) => {
|
|
|
122
122
|
const { start, stop } = p.spinner();
|
|
123
123
|
|
|
124
124
|
// Install dependencies.
|
|
125
|
-
if (
|
|
125
|
+
if (component.dependencies?.length) {
|
|
126
126
|
start(color.gray("Installing dependencies"));
|
|
127
127
|
|
|
128
128
|
const result = await execa(
|
|
129
129
|
packageManager,
|
|
130
|
-
[packageManager === "npm" ? "install" : "add", ...
|
|
130
|
+
[packageManager === "npm" ? "install" : "add", ...component.dependencies],
|
|
131
131
|
{
|
|
132
132
|
cwd,
|
|
133
133
|
},
|
|
@@ -137,7 +137,7 @@ export const addCommand = (cli: CAC) => {
|
|
|
137
137
|
console.error(result.all);
|
|
138
138
|
process.exit(1);
|
|
139
139
|
} else {
|
|
140
|
-
for (const deps of
|
|
140
|
+
for (const deps of component.dependencies) {
|
|
141
141
|
p.log.info(`- ${deps}`);
|
|
142
142
|
}
|
|
143
143
|
stop("Dependencies installed.");
|
|
@@ -145,12 +145,12 @@ export const addCommand = (cli: CAC) => {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
// Install devDependencies.
|
|
148
|
-
if (
|
|
148
|
+
if (component.devDependencies?.length) {
|
|
149
149
|
start(color.gray("Installing devDependencies"));
|
|
150
150
|
|
|
151
151
|
const result = await execa(
|
|
152
152
|
packageManager,
|
|
153
|
-
[packageManager === "npm" ? "install" : "add", "-D", ...
|
|
153
|
+
[packageManager === "npm" ? "install" : "add", "-D", ...component.devDependencies],
|
|
154
154
|
{
|
|
155
155
|
cwd,
|
|
156
156
|
},
|
|
@@ -160,7 +160,7 @@ export const addCommand = (cli: CAC) => {
|
|
|
160
160
|
console.error(result.all);
|
|
161
161
|
process.exit(1);
|
|
162
162
|
} else {
|
|
163
|
-
for (const deps of
|
|
163
|
+
for (const deps of component.devDependencies) {
|
|
164
164
|
p.log.info(`- ${deps}`);
|
|
165
165
|
}
|
|
166
166
|
stop("Dependencies installed.");
|
package/src/schema.ts
CHANGED
|
@@ -2,21 +2,43 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
// TODO: Extract this to a shared package.
|
|
4
4
|
// INFO: also used in component-docs
|
|
5
|
-
export const
|
|
5
|
+
export const registryComponentItemSchema = z.object({
|
|
6
|
+
/**
|
|
7
|
+
* @description 컴포넌트 이름
|
|
8
|
+
* @example chip-tabs, alert-dialog
|
|
9
|
+
*/
|
|
6
10
|
name: z.string(),
|
|
11
|
+
|
|
7
12
|
description: z.string().optional(),
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @description 컴포넌트 의존성
|
|
16
|
+
* @example @seed-design/react-tabs
|
|
17
|
+
*/
|
|
8
18
|
dependencies: z.array(z.string()).optional(),
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @description 컴포넌트 개발 의존성
|
|
22
|
+
*/
|
|
9
23
|
devDependencies: z.array(z.string()).optional(),
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @description 컴포넌트 내부의 Seed Design 컴포넌트 의존성
|
|
27
|
+
* @example action-button
|
|
28
|
+
*/
|
|
10
29
|
innerDependencies: z.array(z.string()).optional(),
|
|
11
|
-
snippets: z.array(z.string()),
|
|
12
|
-
type: z.enum(["component"]),
|
|
13
|
-
});
|
|
14
30
|
|
|
15
|
-
|
|
31
|
+
/**
|
|
32
|
+
* @description 컴포넌트 코드 스니펫 경로, 여러 파일이 될 수 있어서 배열로 정의
|
|
33
|
+
* @example component/alert-dialog.tsx
|
|
34
|
+
*/
|
|
35
|
+
files: z.array(z.string()),
|
|
36
|
+
});
|
|
16
37
|
|
|
17
|
-
const
|
|
38
|
+
export const registryComponentSchema = z.array(registryComponentItemSchema);
|
|
18
39
|
|
|
19
|
-
|
|
40
|
+
const omittedRegistryComponentSchema = registryComponentItemSchema.omit({ files: true });
|
|
41
|
+
export const registryComponentItemMachineGeneratedSchema = omittedRegistryComponentSchema.extend({
|
|
20
42
|
registries: z.array(
|
|
21
43
|
z.object({
|
|
22
44
|
name: z.string(),
|
|
@@ -25,10 +47,15 @@ export const componentMetadataSchemaWithRegistry = omittedComponentMetadataIndex
|
|
|
25
47
|
),
|
|
26
48
|
});
|
|
27
49
|
|
|
28
|
-
export const
|
|
50
|
+
export const registryComponentMachineGeneratedSchema = z.array(
|
|
51
|
+
registryComponentItemMachineGeneratedSchema,
|
|
52
|
+
);
|
|
29
53
|
|
|
30
|
-
export type
|
|
31
|
-
export type
|
|
32
|
-
export type
|
|
33
|
-
typeof
|
|
54
|
+
export type RegistryComponentItem = z.infer<typeof registryComponentItemSchema>;
|
|
55
|
+
export type RegistryComponent = z.infer<typeof registryComponentSchema>;
|
|
56
|
+
export type RegistryComponentItemMachineGenerated = z.infer<
|
|
57
|
+
typeof registryComponentItemMachineGeneratedSchema
|
|
58
|
+
>;
|
|
59
|
+
export type RegistryComponentMachineGenerated = z.infer<
|
|
60
|
+
typeof registryComponentMachineGeneratedSchema
|
|
34
61
|
>;
|
|
@@ -1,36 +1,31 @@
|
|
|
1
1
|
import { describe, expect, test } from "vitest";
|
|
2
2
|
import { addRelativeComponents } from "../utils/add-relative-components";
|
|
3
|
-
import type {
|
|
3
|
+
import type { RegistryComponent } from "@/src/schema";
|
|
4
4
|
|
|
5
|
-
const config:
|
|
5
|
+
const config: RegistryComponent = [
|
|
6
6
|
{
|
|
7
7
|
name: "a",
|
|
8
|
-
|
|
9
|
-
type: "component",
|
|
8
|
+
files: ["a.tsx"],
|
|
10
9
|
},
|
|
11
10
|
{
|
|
12
11
|
name: "b",
|
|
13
12
|
innerDependencies: ["a"],
|
|
14
|
-
|
|
15
|
-
type: "component",
|
|
13
|
+
files: ["b.tsx"],
|
|
16
14
|
},
|
|
17
15
|
{
|
|
18
16
|
name: "c",
|
|
19
17
|
innerDependencies: ["b"],
|
|
20
|
-
|
|
21
|
-
type: "component",
|
|
18
|
+
files: ["c.tsx"],
|
|
22
19
|
},
|
|
23
20
|
{
|
|
24
21
|
name: "d",
|
|
25
22
|
innerDependencies: ["a", "b"],
|
|
26
|
-
|
|
27
|
-
type: "component",
|
|
23
|
+
files: ["d.tsx"],
|
|
28
24
|
},
|
|
29
25
|
{
|
|
30
26
|
name: "e",
|
|
31
27
|
innerDependencies: ["d"],
|
|
32
|
-
|
|
33
|
-
type: "component",
|
|
28
|
+
files: ["d.tsx"],
|
|
34
29
|
},
|
|
35
30
|
];
|
|
36
31
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RegistryComponentMachineGenerated } from "@/src/schema";
|
|
2
2
|
|
|
3
3
|
export function addRelativeComponents(
|
|
4
4
|
userSelects: string[],
|
|
5
|
-
|
|
5
|
+
registryIndex: RegistryComponentMachineGenerated,
|
|
6
6
|
) {
|
|
7
7
|
const selectedComponents = new Set<string>();
|
|
8
8
|
|
|
@@ -11,7 +11,7 @@ export function addRelativeComponents(
|
|
|
11
11
|
|
|
12
12
|
selectedComponents.add(componentName);
|
|
13
13
|
|
|
14
|
-
const component =
|
|
14
|
+
const component = registryIndex.find((c) => c.name === componentName);
|
|
15
15
|
if (!component) return;
|
|
16
16
|
|
|
17
17
|
if (component.innerDependencies) {
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
componentMetadataIndexSchema,
|
|
3
|
-
type ComponentMetadataWithRegistrySchema,
|
|
4
|
-
} from "@/src/schema";
|
|
1
|
+
import { registryComponentSchema, type RegistryComponentMachineGenerated } from "@/src/schema";
|
|
5
2
|
|
|
6
3
|
const BASE_URL =
|
|
7
4
|
process.env.NODE_ENV === "prod" ? "https://component.seed-design.io" : "http://localhost:3000";
|
|
8
5
|
|
|
9
|
-
export async function
|
|
6
|
+
export async function fetchRegistryComponentItem(
|
|
10
7
|
fileNames?: string[],
|
|
11
|
-
): Promise<
|
|
8
|
+
): Promise<RegistryComponentMachineGenerated> {
|
|
12
9
|
try {
|
|
13
10
|
const results = await Promise.all(
|
|
14
11
|
fileNames.map(async (fileName) => {
|
|
15
|
-
const response = await fetch(`${BASE_URL}/
|
|
12
|
+
const response = await fetch(`${BASE_URL}/__registry__/component/${fileName}.json`);
|
|
16
13
|
return await response.json();
|
|
17
14
|
}),
|
|
18
15
|
);
|
|
@@ -24,11 +21,11 @@ export async function fetchComponentMetadatas(
|
|
|
24
21
|
}
|
|
25
22
|
}
|
|
26
23
|
|
|
27
|
-
export async function
|
|
24
|
+
export async function getRegistryComponentIndex() {
|
|
28
25
|
try {
|
|
29
|
-
const [result] = await
|
|
26
|
+
const [result] = await fetchRegistryComponentItem(["index"]);
|
|
30
27
|
|
|
31
|
-
return
|
|
28
|
+
return registryComponentSchema.parse(result);
|
|
32
29
|
} catch (error) {
|
|
33
30
|
console.log(error);
|
|
34
31
|
throw new Error(`Failed to fetch components from ${BASE_URL}.`);
|