byuckchon-frontend-cli 1.9.2 → 1.9.4

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,211 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { versions } from '../constants/versions.js';
5
+ import { createFolders } from './createFolders.js';
6
+ import { createBaseFiles } from './createBaseFiles.js';
7
+ import { createBcConfig } from './createBcConfig.js';
8
+ import { scaffoldApiConventionDoc } from './apiConventionDoc.js';
9
+
10
+ /**
11
+ * 모노레포(pnpm workspace) 안의 apps/<name> 으로 React/Next 앱을 하나 만든다.
12
+ *
13
+ * 단일 프로젝트용 createProject 와 달리:
14
+ * - package.json name 이 `@<scope>/<app>` 로 스코프됨
15
+ * - 공유 프리셋(@<scope>/config-eslint, @<scope>/config-typescript)을 workspace 로 참조
16
+ * - eslint / tsconfig 를 모노레포 프리셋을 extends 하도록 재작성
17
+ * - .gitignore / .vscode / .prettierrc 는 루트가 담당하므로 앱에서는 제거
18
+ * - 의존성 설치(pnpm install)는 호출자가 루트에서 일괄 수행
19
+ *
20
+ * @param {object} args
21
+ * @param {string} args.appDir 앱 절대경로 (…/apps/<name>)
22
+ * @param {object} args.config { projectName, framework, aiModel, figmaUrl, openapiUrl }
23
+ * @param {string} args.scope npm scope (예: 'marketd' → @marketd/<app>)
24
+ */
25
+ export async function createApp({ appDir, config, scope }) {
26
+ await fs.mkdir(appDir, { recursive: true });
27
+
28
+ await createFolders(appDir, config);
29
+ await createBaseFiles(appDir, config);
30
+ await scaffoldApiConventionDoc({ projectRoot: appDir, framework: config.framework });
31
+ await createBcConfig(appDir, config);
32
+
33
+ await createAppPackageJson(appDir, config, scope);
34
+ await applyMonorepoConventions(appDir, config, scope);
35
+ }
36
+
37
+ async function write(filePath, content) {
38
+ await fs.writeFile(filePath, content, 'utf-8');
39
+ }
40
+
41
+ async function rm(target) {
42
+ await fs.rm(target, { recursive: true, force: true });
43
+ }
44
+
45
+ /**
46
+ * 워크스페이스 앱용 package.json. 모노레포에서는 React 버전을 하나로 통일하려고
47
+ * (pnpm hoist 충돌 방지) react/react-dom 모두 19 계열로 맞춘다.
48
+ */
49
+ async function createAppPackageJson(appDir, config, scope) {
50
+ const isReact = config.framework === 'react';
51
+ const name = `@${scope}/${config.projectName}`;
52
+
53
+ const scripts = isReact
54
+ ? {
55
+ dev: 'vite',
56
+ build: 'vite build',
57
+ typecheck: 'tsc --noEmit',
58
+ lint: 'eslint .',
59
+ preview: 'vite preview',
60
+ 'tokens:build': 'style-dictionary build --config token.config.js',
61
+ clean: 'rm -rf dist node_modules .turbo',
62
+ }
63
+ : {
64
+ dev: 'next dev',
65
+ build: 'next build',
66
+ start: 'next start',
67
+ typecheck: 'tsc --noEmit',
68
+ lint: 'eslint .',
69
+ 'tokens:build': 'style-dictionary build --config token.config.js',
70
+ clean: 'rm -rf .next node_modules .turbo',
71
+ };
72
+
73
+ const pkg = {
74
+ name,
75
+ version: '0.0.0',
76
+ private: true,
77
+ type: 'module',
78
+ scripts,
79
+ dependencies: {
80
+ react: versions['next-react'],
81
+ 'react-dom': versions['next-react-dom'],
82
+ ...(isReact ? {} : { next: versions.next }),
83
+ zustand: versions.zustand,
84
+ ...(isReact
85
+ ? {
86
+ axios: versions.axios,
87
+ '@tanstack/react-query': versions['@tanstack/react-query'],
88
+ }
89
+ : {}),
90
+ zod: versions.zod,
91
+ },
92
+ devDependencies: {
93
+ [`@${scope}/config-eslint`]: 'workspace:*',
94
+ [`@${scope}/config-typescript`]: 'workspace:*',
95
+ '@types/react': versions['@types/react'],
96
+ '@types/react-dom': versions['@types/react-dom'],
97
+ '@types/node': versions['@types/node'],
98
+ // 모노레포 앱은 flat config(@scope/config-eslint) 를 쓰므로 ESLint 9 필요.
99
+ // (단일 프로젝트의 eslintrc + eslint 8 과 별개)
100
+ eslint: '^9.18.0',
101
+ 'eslint-plugin-unused-imports': versions['eslint-plugin-unused-imports'],
102
+ prettier: versions.prettier,
103
+ 'prettier-plugin-tailwindcss': versions['prettier-plugin-tailwindcss'],
104
+ 'style-dictionary': versions['style-dictionary'],
105
+ tailwindcss: versions.tailwindcss,
106
+ typescript: versions.typescript,
107
+ ...(isReact
108
+ ? {
109
+ '@tailwindcss/vite': versions['@tailwindcss/vite'],
110
+ '@vitejs/plugin-react': versions['@vitejs/plugin-react'],
111
+ vite: versions.vite,
112
+ 'vite-plugin-svgr': versions['vite-plugin-svgr'],
113
+ }
114
+ : {
115
+ '@tailwindcss/postcss': versions['@tailwindcss/postcss'],
116
+ '@svgr/webpack': versions['@svgr/webpack'],
117
+ }),
118
+ },
119
+ };
120
+
121
+ await write(path.join(appDir, 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
122
+ }
123
+
124
+ /**
125
+ * createBaseFiles 가 깔아둔 단일 프로젝트용 설정 파일을 모노레포 규칙으로 교체한다.
126
+ */
127
+ async function applyMonorepoConventions(appDir, config, scope) {
128
+ const isReact = config.framework === 'react';
129
+
130
+ // 루트에서 관리하는 설정들은 앱 레벨에서 제거.
131
+ await rm(path.join(appDir, '.eslintrc.cjs'));
132
+ await rm(path.join(appDir, '.prettierrc'));
133
+ await rm(path.join(appDir, '.gitignore'));
134
+ await rm(path.join(appDir, '.vscode'));
135
+
136
+ // 공유 ESLint 프리셋을 extends 하는 flat config.
137
+ await write(
138
+ path.join(appDir, 'eslint.config.mjs'),
139
+ `import { reactConfig } from '@${scope}/config-eslint/react';
140
+ import unusedImports from 'eslint-plugin-unused-imports';
141
+
142
+ export default [
143
+ ...reactConfig,
144
+ {
145
+ plugins: { 'unused-imports': unusedImports },
146
+ rules: {
147
+ 'no-unused-vars': 'off',
148
+ '@typescript-eslint/no-unused-vars': 'off',
149
+ 'unused-imports/no-unused-imports': 'error',
150
+ 'unused-imports/no-unused-vars': [
151
+ 'warn',
152
+ { vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' },
153
+ ],
154
+ },
155
+ },
156
+ ];
157
+ `,
158
+ );
159
+
160
+ if (isReact) {
161
+ // createBaseFiles 의 프로젝트 레퍼런스형 tsconfig 3종을 공유 프리셋 extends 로 교체.
162
+ await rm(path.join(appDir, 'tsconfig.app.json'));
163
+ await rm(path.join(appDir, 'tsconfig.node.json'));
164
+ await write(
165
+ path.join(appDir, 'tsconfig.json'),
166
+ JSON.stringify(
167
+ {
168
+ extends: `@${scope}/config-typescript/react.json`,
169
+ compilerOptions: {
170
+ tsBuildInfoFile: './node_modules/.tmp/tsconfig.tsbuildinfo',
171
+ noUnusedLocals: true,
172
+ noUnusedParameters: true,
173
+ noUncheckedSideEffectImports: true,
174
+ moduleDetection: 'force',
175
+ baseUrl: '.',
176
+ paths: {
177
+ '@/*': ['src/*'],
178
+ '@icons/*': ['src/assets/icons/*'],
179
+ '@images/*': ['src/assets/images/*'],
180
+ },
181
+ },
182
+ include: ['src', 'vite.config.ts'],
183
+ },
184
+ null,
185
+ 2,
186
+ ) + '\n',
187
+ );
188
+ } else {
189
+ await write(
190
+ path.join(appDir, 'tsconfig.json'),
191
+ JSON.stringify(
192
+ {
193
+ extends: `@${scope}/config-typescript/next.json`,
194
+ compilerOptions: {
195
+ baseUrl: '.',
196
+ paths: { '@/*': ['./src/*'] },
197
+ },
198
+ include: [
199
+ 'next-env.d.ts',
200
+ '**/*.ts',
201
+ '**/*.tsx',
202
+ '.next/types/**/*.ts',
203
+ ],
204
+ exclude: ['node_modules'],
205
+ },
206
+ null,
207
+ 2,
208
+ ) + '\n',
209
+ );
210
+ }
211
+ }