@seed-design/cli 0.0.0-alpha-20241113031935 → 0.0.0-alpha-20250210081704

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.
@@ -0,0 +1,182 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { addRelativeRegistries } from "../utils/add-relative-registries";
3
+ import type { RegistryLib, RegistryUI } from "@/src/schema";
4
+
5
+ const libConfig: RegistryLib = [
6
+ {
7
+ name: "a",
8
+ files: ["a.tsx"],
9
+ },
10
+ ];
11
+
12
+ const uiConfig: RegistryUI = [
13
+ {
14
+ name: "a",
15
+ files: ["a.tsx"],
16
+ },
17
+ {
18
+ name: "b",
19
+ innerDependencies: ["ui:a"],
20
+ files: ["b.tsx"],
21
+ },
22
+ {
23
+ name: "c",
24
+ innerDependencies: ["ui:b"],
25
+ files: ["c.tsx"],
26
+ },
27
+ {
28
+ name: "d",
29
+ innerDependencies: ["ui:a", "ui:b"],
30
+ files: ["d.tsx"],
31
+ },
32
+ {
33
+ name: "e",
34
+ innerDependencies: ["ui:d"],
35
+ files: ["e.tsx"],
36
+ },
37
+ {
38
+ name: "f",
39
+ innerDependencies: ["lib:a"],
40
+ files: ["f.tsx"],
41
+ },
42
+ ];
43
+
44
+ describe("addRelativeRegistries", () => {
45
+ test("4 deps test", () => {
46
+ const userSelects = ["e"];
47
+ const result = addRelativeRegistries({
48
+ userSelects,
49
+ uiRegistryIndex: uiConfig,
50
+ libRegistryIndex: [],
51
+ });
52
+ expect(result).toEqual(
53
+ expect.arrayContaining([
54
+ {
55
+ type: "ui",
56
+ name: "e",
57
+ },
58
+ {
59
+ type: "ui",
60
+ name: "d",
61
+ },
62
+ {
63
+ type: "ui",
64
+ name: "a",
65
+ },
66
+ {
67
+ type: "ui",
68
+ name: "b",
69
+ },
70
+ ]),
71
+ );
72
+ });
73
+
74
+ test("3 deps test", () => {
75
+ const userSelects = ["d"];
76
+ const result = addRelativeRegistries({
77
+ userSelects,
78
+ uiRegistryIndex: uiConfig,
79
+ libRegistryIndex: [],
80
+ });
81
+ expect(result).toEqual(
82
+ expect.arrayContaining([
83
+ {
84
+ type: "ui",
85
+ name: "d",
86
+ },
87
+ {
88
+ type: "ui",
89
+ name: "a",
90
+ },
91
+ {
92
+ type: "ui",
93
+ name: "b",
94
+ },
95
+ ]),
96
+ );
97
+ });
98
+
99
+ test("3 deps test", () => {
100
+ const userSelects = ["c"];
101
+ const result = addRelativeRegistries({
102
+ userSelects,
103
+ uiRegistryIndex: uiConfig,
104
+ libRegistryIndex: [],
105
+ });
106
+ expect(result).toEqual(
107
+ expect.arrayContaining([
108
+ {
109
+ type: "ui",
110
+ name: "c",
111
+ },
112
+ {
113
+ type: "ui",
114
+ name: "b",
115
+ },
116
+ {
117
+ type: "ui",
118
+ name: "a",
119
+ },
120
+ ]),
121
+ );
122
+ });
123
+
124
+ test("2 deps test", () => {
125
+ const userSelects = ["b"];
126
+ const result = addRelativeRegistries({
127
+ userSelects,
128
+ uiRegistryIndex: uiConfig,
129
+ libRegistryIndex: [],
130
+ });
131
+ expect(result).toEqual(
132
+ expect.arrayContaining([
133
+ {
134
+ type: "ui",
135
+ name: "b",
136
+ },
137
+ {
138
+ type: "ui",
139
+ name: "a",
140
+ },
141
+ ]),
142
+ );
143
+ });
144
+
145
+ test("1 deps test", () => {
146
+ const userSelects = ["a"];
147
+ const result = addRelativeRegistries({
148
+ userSelects,
149
+ uiRegistryIndex: uiConfig,
150
+ libRegistryIndex: [],
151
+ });
152
+ expect(result).toEqual(
153
+ expect.arrayContaining([
154
+ {
155
+ type: "ui",
156
+ name: "a",
157
+ },
158
+ ]),
159
+ );
160
+ });
161
+
162
+ test("lib deps test", () => {
163
+ const userSelects = ["f"];
164
+ const result = addRelativeRegistries({
165
+ userSelects,
166
+ uiRegistryIndex: uiConfig,
167
+ libRegistryIndex: libConfig,
168
+ });
169
+ expect(result).toEqual(
170
+ expect.arrayContaining([
171
+ {
172
+ type: "ui",
173
+ name: "f",
174
+ },
175
+ {
176
+ type: "lib",
177
+ name: "a",
178
+ },
179
+ ]),
180
+ );
181
+ });
182
+ });
@@ -0,0 +1,43 @@
1
+ import type { RegistryLibMachineGenerated, RegistryUIMachineGenerated } from "@/src/schema";
2
+
3
+ interface AddRelativeComponentsProps {
4
+ userSelects: string[];
5
+ uiRegistryIndex: RegistryUIMachineGenerated;
6
+ libRegistryIndex: RegistryLibMachineGenerated;
7
+ }
8
+
9
+ export function addRelativeRegistries({
10
+ userSelects,
11
+ uiRegistryIndex,
12
+ libRegistryIndex,
13
+ }: AddRelativeComponentsProps) {
14
+ const selectedComponents: { type: "ui" | "lib"; name: string }[] = [];
15
+
16
+ function addSeedDependencies({
17
+ registryName,
18
+ type,
19
+ }: { registryName: string; type: "ui" | "lib" }) {
20
+ if (selectedComponents.some((c) => c.name === registryName && c.type === type)) return;
21
+
22
+ selectedComponents.push({ type, name: registryName });
23
+
24
+ const registry =
25
+ type === "ui"
26
+ ? uiRegistryIndex.find((c) => c.name === registryName)
27
+ : libRegistryIndex.find((c) => c.name === registryName);
28
+ if (!registry) return;
29
+
30
+ if (registry.innerDependencies) {
31
+ for (const dep of registry.innerDependencies) {
32
+ const [depType, depName] = dep.split(":");
33
+ addSeedDependencies({ registryName: depName, type: depType as "ui" | "lib" });
34
+ }
35
+ }
36
+ }
37
+
38
+ for (const registryName of userSelects) {
39
+ addSeedDependencies({ registryName, type: "ui" });
40
+ }
41
+
42
+ return Array.from(selectedComponents);
43
+ }
@@ -0,0 +1,3 @@
1
+ import color from "picocolors";
2
+
3
+ export const highlight = (text: string) => color.cyan(text);
@@ -1,8 +1,11 @@
1
+ import * as p from "@clack/prompts";
1
2
  import { cosmiconfig } from "cosmiconfig";
3
+ import { execa } from "execa";
4
+ import fs from "fs";
2
5
  import path from "path";
3
- import * as p from "@clack/prompts";
4
6
  import { z } from "zod";
5
- import color from "picocolors";
7
+ import { highlight } from "./color";
8
+ import { getPackageManager } from "./get-package-manager";
6
9
 
7
10
  const MODULE_NAME = "seed-design";
8
11
 
@@ -24,8 +27,7 @@ export type RawConfig = z.infer<typeof rawConfigSchema>;
24
27
 
25
28
  export const configSchema = rawConfigSchema.extend({
26
29
  resolvedUIPaths: z.string(),
27
- resolbedHookPaths: z.string(),
28
- resolvedUtilPaths: z.string(),
30
+ resolvedLibPaths: z.string(),
29
31
  });
30
32
 
31
33
  export async function getConfig(cwd: string) {
@@ -43,26 +45,43 @@ export type Config = z.infer<typeof configSchema>;
43
45
  export async function resolveConfigPaths(cwd: string, config: RawConfig) {
44
46
  const seedComponentRootPath = path.resolve(cwd, config.path);
45
47
 
48
+ if (!fs.existsSync(seedComponentRootPath)) {
49
+ fs.mkdirSync(seedComponentRootPath, { recursive: true });
50
+ }
51
+
52
+ const resolvedUIPaths = path.join(seedComponentRootPath, "ui");
53
+ const resolvedLibPaths = path.join(seedComponentRootPath, "lib");
54
+
55
+ if (!fs.existsSync(resolvedUIPaths)) {
56
+ fs.mkdirSync(resolvedUIPaths, { recursive: true });
57
+ }
58
+
59
+ if (!fs.existsSync(resolvedLibPaths)) {
60
+ fs.mkdirSync(resolvedLibPaths, { recursive: true });
61
+ }
62
+
46
63
  return configSchema.parse({
47
64
  ...config,
48
- resolvedUIPaths: path.join(seedComponentRootPath, "ui"),
49
- resolbedHookPaths: path.join(seedComponentRootPath, "hook"),
50
- resolvedUtilPaths: path.join(seedComponentRootPath, "util"),
65
+ resolvedUIPaths,
66
+ resolvedLibPaths,
51
67
  });
52
68
  }
53
69
 
54
70
  export async function getRawConfig(cwd: string): Promise<RawConfig | null> {
55
71
  try {
56
72
  const configResult = await explorer.search(cwd);
73
+ return rawConfigSchema.parse(configResult.config);
74
+ } catch {
75
+ p.log.error("프로젝트 루트 경로에 `seed-design.json` 파일이 없어요.");
57
76
 
58
- if (!configResult) {
59
- p.log.message(color.red(`${cwd} 경로에 seed-design.json 파일이 없습니다.`));
60
- return null;
77
+ const isConfirm = await p.confirm({ message: "seed-design.json 파일을 생성하시겠어요?" });
78
+ if (isConfirm === true) {
79
+ const packageManager = await getPackageManager(cwd);
80
+ await execa(packageManager, ["seed-design", "init", "--default"], { cwd });
81
+ p.log.message("seed-design.json 파일이 생성됐어요.");
82
+ } else {
83
+ p.outro(highlight("작업이 취소됐어요."));
84
+ process.exit(1);
61
85
  }
62
-
63
- return rawConfigSchema.parse(configResult.config);
64
- } catch (error) {
65
- console.log(error);
66
- throw new Error(`${cwd} 경로에 seed-design.json 파일을 읽을 수 없습니다.`);
67
86
  }
68
87
  }
@@ -1,33 +1,75 @@
1
+ import * as p from "@clack/prompts";
1
2
  import { registryUISchema, type RegistryUIMachineGenerated } from "@/src/schema";
2
3
 
3
- const BASE_URL =
4
- process.env.NODE_ENV === "prod" ? "https://v3.seed-design.io" : "http://localhost:3000";
5
-
6
- export async function fetchRegistryUIItem(
4
+ export async function fetchRegistryItems(
7
5
  fileNames?: string[],
6
+ baseUrl?: string,
7
+ type: "ui" | "lib" = "ui",
8
8
  ): Promise<RegistryUIMachineGenerated> {
9
- try {
10
- const results = await Promise.all(
11
- fileNames.map(async (fileName) => {
12
- const response = await fetch(`${BASE_URL}/__registry__/ui/${fileName}.json`);
9
+ const results = await Promise.all(
10
+ fileNames.map(async (fileName) => {
11
+ try {
12
+ const response = await fetch(`${baseUrl}/__registry__/${type}/${fileName}.json`);
13
13
  return await response.json();
14
- }),
15
- );
14
+ } catch (error) {
15
+ const index = await fetch(`${baseUrl}/__registry__/${type}/index.json`).then((res) =>
16
+ res.json(),
17
+ );
18
+ const parsedIndex = registryUISchema.parse(index);
19
+ const availableComponents = parsedIndex.map((component) => component.name);
16
20
 
17
- return results;
18
- } catch (error) {
19
- console.log(error);
20
- throw new Error(`Failed to fetch registry from ${BASE_URL}.`);
21
- }
21
+ p.log.error(`${type}:${fileName} 컴포넌트는 레지스트리에 존재하지 않아요.`);
22
+ p.log.info(`사용 가능한 컴포넌트: ${availableComponents.join(", ")}`);
23
+ p.log.info(
24
+ JSON.stringify(
25
+ {
26
+ baseUrl,
27
+ error: error.toString(),
28
+ },
29
+ null,
30
+ 2,
31
+ ),
32
+ );
33
+ process.exit(1);
34
+ }
35
+ }),
36
+ );
37
+
38
+ return results;
22
39
  }
23
40
 
24
- export async function getRegistryUIIndex() {
41
+ export async function fetchRegistryItem(
42
+ fileName: string,
43
+ baseUrl: string,
44
+ type: "ui" | "lib" = "ui",
45
+ ) {
46
+ const [result] = await fetchRegistryItems([fileName], baseUrl, type);
47
+ return result;
48
+ }
49
+
50
+ export async function getRegistryUIIndex(baseUrl?: string) {
25
51
  try {
26
- const [result] = await fetchRegistryUIItem(["index"]);
52
+ const [result] = await fetchRegistryItems(["index"], baseUrl, "ui");
27
53
 
28
54
  return registryUISchema.parse(result);
29
55
  } catch (error) {
30
- console.log(error);
31
- throw new Error(`Failed to fetch components from ${BASE_URL}.`);
56
+ p.log.error("레지스트리 인덱스를 가져오는 데 실패했어요.");
57
+ p.log.info(
58
+ JSON.stringify(
59
+ {
60
+ baseUrl,
61
+ error: error.toString(),
62
+ },
63
+ null,
64
+ 2,
65
+ ),
66
+ );
67
+ process.exit(1);
32
68
  }
33
69
  }
70
+
71
+ export async function getRegistryLibIndex(baseUrl?: string) {
72
+ const [result] = await fetchRegistryItems(["index"], baseUrl, "lib");
73
+
74
+ return registryUISchema.parse(result);
75
+ }
@@ -13,5 +13,6 @@ function getPackagePath() {
13
13
  }
14
14
 
15
15
  export function getPackageInfo() {
16
- return fs.readJSONSync(getPackagePath()) as PackageJson;
16
+ const packageJsonPath = getPackagePath();
17
+ return fs.readJSONSync(packageJsonPath) as PackageJson;
17
18
  }
@@ -2,12 +2,12 @@ import { detect } from "@antfu/ni";
2
2
 
3
3
  export async function getPackageManager(
4
4
  targetDir: string,
5
- ): Promise<"yarn" | "pnpm" | "bun" | "npm"> {
5
+ ): Promise<"yarn" | "pnpm" | "bun" | "npm" | "deno"> {
6
6
  const packageManager = await detect({ programmatic: true, cwd: targetDir });
7
7
 
8
8
  if (packageManager === "yarn@berry") return "yarn";
9
9
  if (packageManager === "pnpm@6") return "pnpm";
10
10
  if (packageManager === "bun") return "bun";
11
-
11
+ if (packageManager === "deno") return "deno";
12
12
  return packageManager ?? "npm";
13
13
  }
@@ -0,0 +1,53 @@
1
+ import * as p from "@clack/prompts";
2
+ import { execa } from "execa";
3
+ import color from "picocolors";
4
+ import { getPackageManager } from "./get-package-manager";
5
+ import { getPackageInfo } from "./get-package-info";
6
+
7
+ interface InstallDependenciesProps {
8
+ cwd: string;
9
+ deps: string[];
10
+ dev?: boolean;
11
+ }
12
+
13
+ export async function installDependencies({ cwd, deps, dev = false }: InstallDependenciesProps) {
14
+ const { start, stop } = p.spinner();
15
+ const packageManager = await getPackageManager(cwd);
16
+ const packageInfo = getPackageInfo();
17
+
18
+ // 이미 설치된 의존성 필터링
19
+ const existingDeps = {
20
+ ...packageInfo.dependencies,
21
+ ...packageInfo.devDependencies,
22
+ };
23
+
24
+ const depsToInstall = deps.filter((dep) => !existingDeps[dep]);
25
+ const filteredDeps = deps.filter((dep) => existingDeps[dep]);
26
+
27
+ if (!depsToInstall.length) {
28
+ return {
29
+ installed: new Set(),
30
+ filtered: new Set(),
31
+ };
32
+ }
33
+
34
+ start(color.gray("의존성 설치중..."));
35
+
36
+ const isDev = dev ? "-D" : null;
37
+ const addCommand = packageManager === "npm" ? "install" : "add";
38
+ const command = [addCommand, isDev, ...depsToInstall].filter(Boolean);
39
+
40
+ try {
41
+ await execa(packageManager, command, { cwd });
42
+ } catch (error) {
43
+ console.error(`의존성 설치 실패: ${error}`);
44
+ process.exit(1);
45
+ }
46
+
47
+ stop("의존성 설치 완료.");
48
+
49
+ return {
50
+ installed: depsToInstall,
51
+ filtered: filteredDeps,
52
+ };
53
+ }