byuckchon-frontend-cli 1.0.2 → 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/README.md CHANGED
@@ -1,2 +1,26 @@
1
1
  # byuckchon-frontend-cli
2
- byuckchon frontend CLI for creating React and Next.js projects
2
+
3
+ [byuckchon](https://www.byuckchon.com) 컨벤션에 맞게 React(Vite) 또는 Next.js(App Router) TypeScript 프로젝트를 생성하는 CLI.
4
+
5
+ ## 요구 사항
6
+
7
+ - [Node.js](https://nodejs.org/) (LTS 권장)
8
+
9
+ ## 사용법
10
+
11
+ ```bash
12
+ npx byuckchon-frontend-cli
13
+ ```
14
+
15
+ 프로젝트 이름과 프레임워크를 묻는 프롬프트에 따라 답하면, 현재 디렉터리에 새 폴더가 만들어집니다.
16
+
17
+ 생성이 끝나면 안내에 따라 다음을 실행하세요.
18
+
19
+ ```bash
20
+ cd <프로젝트-이름>
21
+ npm run dev
22
+ ```
23
+
24
+ ## 라이선스
25
+
26
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "byuckchon-frontend-cli",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "byuckchon frontend CLI for creating React and Next.js projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,37 +1,41 @@
1
- import chalk from 'chalk';
1
+ import chalk from "chalk";
2
2
 
3
- import { createProject } from '../generators/createProject.js';
4
- import { askInitQuestions } from '../prompts/initPrompts.js';
3
+ import { createProject } from "../generators/createProject.js";
4
+ import { askInitQuestions } from "../prompts/initPrompts.js";
5
5
 
6
6
  export async function initCommand() {
7
- console.log(chalk.bold.cyan('\n cli-test — 프로젝트 생성기\n'));
7
+ console.log(
8
+ chalk.bold.cyan("\n byuckchon-frontend-cli — 프로젝트 생성기\n")
9
+ );
8
10
 
9
11
  try {
10
12
  const answers = await askInitQuestions();
11
13
 
12
14
  console.log(
13
15
  chalk.dim(
14
- `\n ${answers.framework} 프로젝트를 생성하는 중... (${answers.projectName})\n`,
15
- ),
16
+ `\n ${answers.framework} 프로젝트를 생성하는 중... (${answers.projectName})\n`
17
+ )
16
18
  );
17
19
 
18
20
  await createProject({ ...answers, typescript: true });
19
21
 
20
22
  console.log(
21
- chalk.bold.green(`\n ✓ ${answers.projectName} 프로젝트가 생성되었습니다!\n`),
23
+ chalk.bold.green(
24
+ `\n ✓ ${answers.projectName} 프로젝트가 생성되었습니다!\n`
25
+ )
22
26
  );
23
- console.log(chalk.yellow(' 다음 명령어로 시작하세요:\n'));
27
+ console.log(chalk.yellow(" 다음 명령어로 시작하세요:\n"));
24
28
  console.log(chalk.white(` cd ${answers.projectName}`));
25
- console.log(chalk.white(' npm run dev\n'));
29
+ console.log(chalk.white(" npm run dev\n"));
26
30
  } catch (error) {
27
- if (error.code === 'EEXIST') {
31
+ if (error.code === "EEXIST") {
28
32
  console.error(
29
- chalk.red(`\n 오류: '${error.path}' 폴더가 이미 존재합니다.\n`),
33
+ chalk.red(`\n 오류: '${error.path}' 폴더가 이미 존재합니다.\n`)
30
34
  );
31
35
  } else {
32
36
  console.error(
33
- chalk.red('\n 프로젝트 생성 중 오류가 발생했습니다:'),
34
- error.message,
37
+ chalk.red("\n 프로젝트 생성 중 오류가 발생했습니다:"),
38
+ error.message
35
39
  );
36
40
  }
37
41
  process.exit(1);
@@ -1,34 +1,114 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
3
 
4
4
  async function write(filePath, content) {
5
- await fs.writeFile(filePath, content, 'utf-8');
5
+ await fs.writeFile(filePath, content, "utf-8");
6
6
  }
7
7
 
8
+ const TOKEN_CONFIG_JS = `import StyleDictionary from "style-dictionary";
9
+
10
+ // kebab-case 변환
11
+ StyleDictionary.registerTransform({
12
+ name: "name/kebab",
13
+ type: "name",
14
+ transform: (token) =>
15
+ token.path
16
+ .join("-")
17
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
18
+ .toLowerCase(),
19
+ });
20
+
21
+ // color는 Tailwind 유틸리티로, typography는 .text-* 클래스로 생성
22
+ StyleDictionary.registerFormat({
23
+ name: "css/tailwind-theme",
24
+ format: ({ dictionary }) => {
25
+ let css = "";
26
+ const withPx = (value) =>
27
+ typeof value === "string" && /^\\d+(\\.\\d+)?$/.test(value)
28
+ ? \`\${value}px\`
29
+ : value;
30
+
31
+ css += "@theme {\\n";
32
+ dictionary.allTokens.forEach((token) => {
33
+ if (token.$type === "color") {
34
+ css += \` --color-\${token.name}: \${token.$value};\\n\`;
35
+ }
36
+ });
37
+ css += "}\\n\\n";
38
+
39
+ css += "@layer components {\\n";
40
+ dictionary.allTokens.forEach((token) => {
41
+ if (token.$type === "typography" && token.$value) {
42
+ const typo = token.$value;
43
+ css += \` .text-\${token.name} {\\n\`;
44
+ if (typo.fontSize) {
45
+ css += \` font-size: \${withPx(typo.fontSize)};\\n\`;
46
+ }
47
+ if (typo.lineHeight) {
48
+ css += \` line-height: \${withPx(typo.lineHeight)};\\n\`;
49
+ }
50
+ if (typo.letterSpacing) {
51
+ css += \` letter-spacing: \${typo.letterSpacing};\\n\`;
52
+ }
53
+ if (typo.fontWeight) {
54
+ css += \` font-weight: \${typo.fontWeight};\\n\`;
55
+ }
56
+ if (typo.fontFamily) {
57
+ css += \` font-family: \${typo.fontFamily};\\n\`;
58
+ }
59
+ css += " }\\n";
60
+ }
61
+ });
62
+ css += "}\\n";
63
+
64
+ return css;
65
+ },
66
+ });
67
+
68
+ export default {
69
+ source: ["src/tokens.json"],
70
+ platforms: {
71
+ css: {
72
+ transforms: ["name/kebab"], // 일단 attribute/cti 제거
73
+ buildPath: "src/",
74
+ files: [
75
+ {
76
+ destination: "tokens.css",
77
+ format: "css/tailwind-theme",
78
+ },
79
+ ],
80
+ },
81
+ },
82
+ };
83
+ `;
84
+
8
85
  // ─── 공통 설정 파일 ────────────────────────────────────────────────────────────
9
86
 
10
87
  async function createPrettierConfig(rootDir) {
11
88
  const config = {
12
89
  semi: true,
13
- trailingComma: 'all',
90
+ trailingComma: "all",
14
91
  singleQuote: true,
15
92
  tabWidth: 2,
16
93
  useTabs: false,
17
94
  printWidth: 80,
18
95
  plugins: [
19
- '@trivago/prettier-plugin-sort-imports',
20
- 'prettier-plugin-tailwindcss',
96
+ "@trivago/prettier-plugin-sort-imports",
97
+ "prettier-plugin-tailwindcss",
21
98
  ],
22
- importOrder: ['^@core/(.*)$', '^@server/(.*)$', '^@ui/(.*)$', '^[./]'],
99
+ importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
23
100
  importOrderSeparation: true,
24
101
  importOrderSortSpecifiers: true,
25
102
  };
26
- await write(path.join(rootDir, '.prettierrc'), JSON.stringify(config, null, 2));
103
+ await write(
104
+ path.join(rootDir, ".prettierrc"),
105
+ JSON.stringify(config, null, 2)
106
+ );
27
107
  }
28
108
 
29
109
  async function createEslintConfig(rootDir) {
30
110
  await write(
31
- path.join(rootDir, '.eslintrc.cjs'),
111
+ path.join(rootDir, ".eslintrc.cjs"),
32
112
  `module.exports = {
33
113
  env: {
34
114
  browser: true,
@@ -62,13 +142,13 @@ async function createEslintConfig(rootDir) {
62
142
  },
63
143
  },
64
144
  };
65
- `,
145
+ `
66
146
  );
67
147
  }
68
148
 
69
149
  async function createNextEslintConfig(rootDir) {
70
150
  await write(
71
- path.join(rootDir, '.eslintrc.cjs'),
151
+ path.join(rootDir, ".eslintrc.cjs"),
72
152
  `// 현 파일이 eslint config type 을 따른다는 선언
73
153
  /** @type {import("eslint").Linter.Config} */
74
154
 
@@ -116,7 +196,7 @@ module.exports = {
116
196
  // 정렬 제외 파일 목록
117
197
  ignorePatterns: ["node_modules/", ".next/", "out/", "build/", "next-env.d.ts"],
118
198
  };
119
- `,
199
+ `
120
200
  );
121
201
  }
122
202
 
@@ -164,31 +244,34 @@ dist-ssr
164
244
  out/
165
245
  `;
166
246
 
167
- await write(path.join(rootDir, '.gitignore'), base + (framework === 'next' ? nextExtra : ''));
247
+ await write(
248
+ path.join(rootDir, ".gitignore"),
249
+ base + (framework === "next" ? nextExtra : "")
250
+ );
168
251
  }
169
252
 
170
253
  async function createVscodeSettings(rootDir) {
171
- await fs.mkdir(path.join(rootDir, '.vscode'), { recursive: true });
254
+ await fs.mkdir(path.join(rootDir, ".vscode"), { recursive: true });
172
255
  await write(
173
- path.join(rootDir, '.vscode/settings.json'),
256
+ path.join(rootDir, ".vscode/settings.json"),
174
257
  JSON.stringify(
175
258
  {
176
- 'editor.defaultFormatter': 'esbenp.prettier-vscode',
177
- 'editor.formatOnSave': true,
178
- 'eslint.validate': [
179
- 'javascript',
180
- 'typescript',
181
- 'javascriptreact',
182
- 'typescriptreact',
259
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
260
+ "editor.formatOnSave": true,
261
+ "eslint.validate": [
262
+ "javascript",
263
+ "typescript",
264
+ "javascriptreact",
265
+ "typescriptreact",
183
266
  ],
184
- 'editor.codeActionsOnSave': {
185
- 'source.organizeImports': 'always',
186
- 'source.fixAll.eslint': 'always',
267
+ "editor.codeActionsOnSave": {
268
+ "source.organizeImports": "always",
269
+ "source.fixAll.eslint": "always",
187
270
  },
188
271
  },
189
272
  null,
190
- 2,
191
- ),
273
+ 2
274
+ )
192
275
  );
193
276
  }
194
277
 
@@ -197,7 +280,7 @@ async function createVscodeSettings(rootDir) {
197
280
  async function createReactBaseFiles(rootDir, config) {
198
281
  // index.html
199
282
  await write(
200
- path.join(rootDir, 'index.html'),
283
+ path.join(rootDir, "index.html"),
201
284
  `<!doctype html>
202
285
  <html lang="ko">
203
286
  <head>
@@ -210,13 +293,16 @@ async function createReactBaseFiles(rootDir, config) {
210
293
  <script type="module" src="/src/main.tsx"></script>
211
294
  </body>
212
295
  </html>
213
- `,
296
+ `
297
+ );
298
+ await write(
299
+ path.join(rootDir, "public/robots.txt"),
300
+ `User-agent: *\nDisallow: /\n`
214
301
  );
215
- await write(path.join(rootDir, 'public/robots.txt'), `User-agent: *\nDisallow: /\n`);
216
302
 
217
303
  // vite.config.ts
218
304
  await write(
219
- path.join(rootDir, 'vite.config.ts'),
305
+ path.join(rootDir, "vite.config.ts"),
220
306
  `import tailwindcss from '@tailwindcss/vite';
221
307
  import react from '@vitejs/plugin-react';
222
308
  import { defineConfig } from 'vite';
@@ -241,74 +327,77 @@ export default defineConfig({
241
327
  },
242
328
  },
243
329
  });
244
- `,
330
+ `
245
331
  );
246
332
 
247
333
  // tsconfig.json
248
334
  await write(
249
- path.join(rootDir, 'tsconfig.json'),
335
+ path.join(rootDir, "tsconfig.json"),
250
336
  JSON.stringify(
251
337
  {
252
338
  files: [],
253
- references: [{ path: './tsconfig.app.json' }, { path: './tsconfig.node.json' }],
339
+ references: [
340
+ { path: "./tsconfig.app.json" },
341
+ { path: "./tsconfig.node.json" },
342
+ ],
254
343
  },
255
344
  null,
256
- 2,
257
- ),
345
+ 2
346
+ )
258
347
  );
259
348
 
260
349
  // tsconfig.app.json
261
350
  await write(
262
- path.join(rootDir, 'tsconfig.app.json'),
351
+ path.join(rootDir, "tsconfig.app.json"),
263
352
  JSON.stringify(
264
353
  {
265
354
  compilerOptions: {
266
- tsBuildInfoFile: './node_modules/.tmp/tsconfig.app.tsbuildinfo',
267
- target: 'ES2020',
355
+ tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
356
+ target: "ES2020",
268
357
  useDefineForClassFields: true,
269
- lib: ['ES2020', 'DOM', 'DOM.Iterable'],
270
- module: 'ESNext',
358
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
359
+ module: "ESNext",
271
360
  skipLibCheck: true,
272
- moduleResolution: 'bundler',
361
+ moduleResolution: "bundler",
273
362
  allowImportingTsExtensions: true,
274
363
  isolatedModules: true,
275
- moduleDetection: 'force',
364
+ moduleDetection: "force",
276
365
  noEmit: true,
277
- jsx: 'react-jsx',
366
+ jsx: "react-jsx",
278
367
  strict: true,
279
368
  noUnusedLocals: true,
280
369
  noUnusedParameters: true,
281
370
  noFallthroughCasesInSwitch: true,
282
371
  noUncheckedSideEffectImports: true,
283
- baseUrl: '.',
372
+ baseUrl: ".",
284
373
  paths: {
285
- '@/*': ['src/*'],
286
- '@icons/*': ['src/assets/icons/*'],
287
- '@images/*': ['src/assets/images/*'],
374
+ "@/*": ["src/*"],
375
+ "@icons/*": ["src/assets/icons/*"],
376
+ "@images/*": ["src/assets/images/*"],
288
377
  },
289
378
  },
290
- include: ['src', 'src/svg.d.ts'],
379
+ include: ["src", "src/svg.d.ts"],
291
380
  },
292
381
  null,
293
- 2,
294
- ),
382
+ 2
383
+ )
295
384
  );
296
385
 
297
386
  // tsconfig.node.json
298
387
  await write(
299
- path.join(rootDir, 'tsconfig.node.json'),
388
+ path.join(rootDir, "tsconfig.node.json"),
300
389
  JSON.stringify(
301
390
  {
302
391
  compilerOptions: {
303
- tsBuildInfoFile: './node_modules/.tmp/tsconfig.node.tsbuildinfo',
304
- target: 'ES2022',
305
- lib: ['ES2023'],
306
- module: 'ESNext',
392
+ tsBuildInfoFile: "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
393
+ target: "ES2022",
394
+ lib: ["ES2023"],
395
+ module: "ESNext",
307
396
  skipLibCheck: true,
308
- moduleResolution: 'bundler',
397
+ moduleResolution: "bundler",
309
398
  allowImportingTsExtensions: true,
310
399
  isolatedModules: true,
311
- moduleDetection: 'force',
400
+ moduleDetection: "force",
312
401
  noEmit: true,
313
402
  strict: true,
314
403
  noUnusedLocals: true,
@@ -316,125 +405,47 @@ export default defineConfig({
316
405
  noFallthroughCasesInSwitch: true,
317
406
  noUncheckedSideEffectImports: true,
318
407
  },
319
- include: ['vite.config.ts'],
408
+ include: ["vite.config.ts"],
320
409
  },
321
410
  null,
322
- 2,
323
- ),
411
+ 2
412
+ )
324
413
  );
325
414
 
326
415
  await createPrettierConfig(rootDir);
327
416
  await createEslintConfig(rootDir);
328
- await createGitignore(rootDir, 'react');
417
+ await createGitignore(rootDir, "react");
329
418
  await createVscodeSettings(rootDir);
330
- await write(
331
- path.join(rootDir, 'token.config.js'),
332
- `import StyleDictionary from "style-dictionary";
333
-
334
- // kebab-case 변환
335
- StyleDictionary.registerTransform({
336
- name: "name/kebab",
337
- type: "name",
338
- transform: (token) =>
339
- token.path
340
- .join("-")
341
- .replace(/([a-z])([A-Z])/g, "$1-$2")
342
- .toLowerCase(),
343
- });
344
-
345
- // color는 Tailwind 유틸리티로, typography는 .text-* 클래스로 생성
346
- StyleDictionary.registerFormat({
347
- name: "css/tailwind-theme",
348
- format: ({ dictionary }) => {
349
- let css = "";
350
- const withPx = (value) =>
351
- typeof value === "string" && /^\\d+(\\.\\d+)?$/.test(value)
352
- ? \`\${value}px\`
353
- : value;
354
-
355
- css += "@theme {\\n";
356
- dictionary.allTokens.forEach((token) => {
357
- if (token.$type === "color") {
358
- css += \` --color-\${token.name}: \${token.$value};\\n\`;
359
- }
360
- });
361
- css += "}\\n\\n";
362
-
363
- css += "@layer components {\\n";
364
- dictionary.allTokens.forEach((token) => {
365
- if (token.$type === "typography" && token.$value) {
366
- const typo = token.$value;
367
- css += \` .text-\${token.name} {\\n\`;
368
- if (typo.fontSize) {
369
- css += \` font-size: \${withPx(typo.fontSize)};\\n\`;
370
- }
371
- if (typo.lineHeight) {
372
- css += \` line-height: \${withPx(typo.lineHeight)};\\n\`;
373
- }
374
- if (typo.letterSpacing) {
375
- css += \` letter-spacing: \${typo.letterSpacing};\\n\`;
376
- }
377
- if (typo.fontWeight) {
378
- css += \` font-weight: \${typo.fontWeight};\\n\`;
379
- }
380
- if (typo.fontFamily) {
381
- css += \` font-family: \${typo.fontFamily};\\n\`;
382
- }
383
- css += " }\\n";
384
- }
385
- });
386
- css += "}\\n";
387
-
388
- return css;
389
- },
390
- });
391
-
392
- export default {
393
- source: ["src/tokens.json"],
394
- platforms: {
395
- css: {
396
- transforms: ["name/kebab"], // 일단 attribute/cti 제거
397
- buildPath: "src/",
398
- files: [
399
- {
400
- destination: "tokens.css",
401
- format: "css/tailwind-theme",
402
- },
403
- ],
404
- },
405
- },
406
- };
407
- `,
408
- );
419
+ await write(path.join(rootDir, "token.config.js"), TOKEN_CONFIG_JS);
409
420
 
410
421
  // src/App.css
411
422
  await write(
412
- path.join(rootDir, 'src/App.css'),
423
+ path.join(rootDir, "src/App.css"),
413
424
  `@import "./tokens.css";
414
425
  @import 'tailwindcss';
415
- `,
426
+ `
416
427
  );
417
- await write(path.join(rootDir, 'src/tokens.css'), '');
418
- await write(path.join(rootDir, 'src/tokens.json'), '{}\n');
428
+ await write(path.join(rootDir, "src/tokens.css"), "");
429
+ await write(path.join(rootDir, "src/tokens.json"), "{}\n");
419
430
 
420
431
  // src/main.tsx
421
432
  await write(
422
- path.join(rootDir, 'src/main.tsx'),
433
+ path.join(rootDir, "src/main.tsx"),
423
434
  `import { createRoot } from 'react-dom/client';
424
435
 
425
436
  import App from './App.tsx';
426
437
 
427
438
  createRoot(document.getElementById('root')!).render(<App />);
428
- `,
439
+ `
429
440
  );
430
441
 
431
442
  await write(
432
- path.join(rootDir, 'src/vite-env.d.ts'),
433
- `/// <reference types="vite/client" />\n`,
443
+ path.join(rootDir, "src/vite-env.d.ts"),
444
+ `/// <reference types="vite/client" />\n`
434
445
  );
435
446
 
436
447
  await write(
437
- path.join(rootDir, 'src/global.d.ts'),
448
+ path.join(rootDir, "src/global.d.ts"),
438
449
  `declare module '*.svg' {
439
450
  import React from 'react';
440
451
  export const ReactComponent: React.FunctionComponent<
@@ -456,12 +467,12 @@ declare module '*.webp' {
456
467
  const value: any;
457
468
  export = value;
458
469
  }
459
- `,
470
+ `
460
471
  );
461
472
 
462
473
  // src/App.tsx
463
474
  await write(
464
- path.join(rootDir, 'src/App.tsx'),
475
+ path.join(rootDir, "src/App.tsx"),
465
476
  `import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
466
477
 
467
478
  import './App.css';
@@ -487,7 +498,7 @@ function App() {
487
498
  }
488
499
 
489
500
  export default App;
490
- `,
501
+ `
491
502
  );
492
503
  }
493
504
 
@@ -496,7 +507,7 @@ export default App;
496
507
  async function createNextBaseFiles(rootDir, config) {
497
508
  // next.config.ts
498
509
  await write(
499
- path.join(rootDir, 'next.config.ts'),
510
+ path.join(rootDir, "next.config.ts"),
500
511
  `import type { NextConfig } from "next";
501
512
 
502
513
  const nextConfig: NextConfig = {
@@ -520,73 +531,83 @@ const nextConfig: NextConfig = {
520
531
  };
521
532
 
522
533
  export default nextConfig;
523
- `,
534
+ `
535
+ );
536
+ await write(
537
+ path.join(rootDir, "public/robots.txt"),
538
+ `User-agent: *\nDisallow: /\n`
524
539
  );
525
- await write(path.join(rootDir, 'public/robots.txt'), `User-agent: *\nDisallow: /\n`);
526
540
 
527
541
  // tsconfig.json (Next.js)
528
542
  await write(
529
- path.join(rootDir, 'tsconfig.json'),
543
+ path.join(rootDir, "tsconfig.json"),
530
544
  JSON.stringify(
531
545
  {
532
546
  compilerOptions: {
533
- target: 'ES2017',
534
- lib: ['dom', 'dom.iterable', 'esnext'],
547
+ target: "ES2017",
548
+ lib: ["dom", "dom.iterable", "esnext"],
535
549
  allowJs: true,
536
550
  skipLibCheck: true,
537
551
  strict: true,
538
552
  noEmit: true,
539
553
  esModuleInterop: true,
540
- module: 'esnext',
541
- moduleResolution: 'bundler',
554
+ module: "esnext",
555
+ moduleResolution: "bundler",
542
556
  resolveJsonModule: true,
543
557
  isolatedModules: true,
544
- jsx: 'preserve',
558
+ jsx: "preserve",
545
559
  incremental: true,
546
- plugins: [{ name: 'next' }],
547
- paths: { '@/*': ['./src/*'] },
560
+ plugins: [{ name: "next" }],
561
+ paths: { "@/*": ["./src/*"] },
548
562
  },
549
- include: ['next-env.d.ts', '**/*.ts', '**/*.tsx', '.next/types/**/*.ts'],
550
- exclude: ['node_modules'],
563
+ include: [
564
+ "next-env.d.ts",
565
+ "**/*.ts",
566
+ "**/*.tsx",
567
+ ".next/types/**/*.ts",
568
+ ],
569
+ exclude: ["node_modules"],
551
570
  },
552
571
  null,
553
- 2,
554
- ),
572
+ 2
573
+ )
555
574
  );
556
575
 
557
576
  await createPrettierConfig(rootDir);
558
577
  await createNextEslintConfig(rootDir);
559
- await createGitignore(rootDir, 'next');
578
+ await createGitignore(rootDir, "next");
560
579
  await createVscodeSettings(rootDir);
561
580
  await write(
562
- path.join(rootDir, 'postcss.config.mjs'),
581
+ path.join(rootDir, "postcss.config.mjs"),
563
582
  `const config = {
564
583
  plugins: ["@tailwindcss/postcss"],
565
584
  };
566
585
 
567
586
  export default config;
568
- `,
587
+ `
569
588
  );
570
589
 
571
590
  // src/app/globals.css
572
591
  await write(
573
- path.join(rootDir, 'src/app/globals.css'),
574
- `@import "../src/tokens.css";
592
+ path.join(rootDir, "src/app/globals.css"),
593
+ `@import "../tokens.css";
575
594
  @import 'tailwindcss';
576
- `,
595
+ `
577
596
  );
578
- await write(path.join(rootDir, 'src/tokens.css'), '');
597
+ await write(path.join(rootDir, "token.config.js"), TOKEN_CONFIG_JS);
598
+ await write(path.join(rootDir, "src/tokens.css"), "");
599
+ await write(path.join(rootDir, "src/tokens.json"), "{}\n");
579
600
 
580
601
  // src/app/layout.tsx
581
602
  await write(
582
- path.join(rootDir, 'src/app/layout.tsx'),
603
+ path.join(rootDir, "src/app/layout.tsx"),
583
604
  `import type { Metadata } from 'next';
584
605
 
585
606
  import './globals.css';
586
607
 
587
608
  export const metadata: Metadata = {
588
609
  title: '${config.projectName}',
589
- description: 'Generated by cli-test',
610
+ description: 'Generated by byuckchon-frontend-cli',
590
611
  };
591
612
 
592
613
  export default function RootLayout({
@@ -603,12 +624,12 @@ export default function RootLayout({
603
624
  </html>
604
625
  );
605
626
  }
606
- `,
627
+ `
607
628
  );
608
629
 
609
630
  // src/app/page.tsx
610
631
  await write(
611
- path.join(rootDir, 'src/app/page.tsx'),
632
+ path.join(rootDir, "src/app/page.tsx"),
612
633
  `export default function MainPage() {
613
634
  return (
614
635
  <main className="flex min-h-screen items-center justify-center bg-gray-50">
@@ -622,29 +643,29 @@ export default function RootLayout({
622
643
  </main>
623
644
  );
624
645
  }
625
- `,
646
+ `
626
647
  );
627
648
 
628
649
  await write(
629
- path.join(rootDir, 'src/app/error.tsx'),
650
+ path.join(rootDir, "src/app/error.tsx"),
630
651
  `'use client';
631
652
 
632
653
  export default function Error() {
633
654
  return <div>Something went wrong.</div>;
634
655
  }
635
- `,
656
+ `
636
657
  );
637
658
 
638
659
  await write(
639
- path.join(rootDir, 'src/app/not-found.tsx'),
660
+ path.join(rootDir, "src/app/not-found.tsx"),
640
661
  `export default function NotFound() {
641
662
  return <div>Page not found.</div>;
642
663
  }
643
- `,
664
+ `
644
665
  );
645
666
 
646
667
  await write(
647
- path.join(rootDir, 'src/global.d.ts'),
668
+ path.join(rootDir, "src/global.d.ts"),
648
669
  `declare module '*.svg' {
649
670
  import React from 'react';
650
671
  export const ReactComponent: React.FunctionComponent<
@@ -666,26 +687,26 @@ declare module '*.webp' {
666
687
  const value: any;
667
688
  export = value;
668
689
  }
669
- `,
690
+ `
670
691
  );
671
692
 
672
693
  await write(
673
- path.join(rootDir, 'src/types.d.ts'),
694
+ path.join(rootDir, "src/types.d.ts"),
674
695
  `declare module "*.svg" {
675
696
  import React from "react";
676
697
  const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
677
698
  export default ReactComponent;
678
699
  }
679
- `,
700
+ `
680
701
  );
681
702
  }
682
703
 
683
704
  // ─── 진입점 ───────────────────────────────────────────────────────────────────
684
705
 
685
706
  export async function createBaseFiles(rootDir, config) {
686
- if (config.framework === 'react') {
707
+ if (config.framework === "react") {
687
708
  await createReactBaseFiles(rootDir, config);
688
- } else if (config.framework === 'next') {
709
+ } else if (config.framework === "next") {
689
710
  await createNextBaseFiles(rootDir, config);
690
711
  }
691
712
  }
@@ -10,7 +10,7 @@ export async function createPackageJson(rootDir, config) {
10
10
  name: config.projectName,
11
11
  version: '0.0.1',
12
12
  private: true,
13
- ...(isReact ? { type: 'module' } : {}),
13
+ type: 'module',
14
14
  scripts: isReact
15
15
  ? {
16
16
  dev: 'vite',