byuckchon-frontend-cli 1.9.6 → 1.10.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.
@@ -1,201 +1,77 @@
1
1
  import fs from "fs/promises";
2
+ import { createRequire } from "node:module";
2
3
  import path from "path";
3
4
 
5
+ // JSON import attributes 는 Node 22+ 라서, Node 18 지원을 위해 createRequire 로 읽는다.
6
+ const require = createRequire(import.meta.url);
7
+ const vscodeSettings = require("@byuckchon-frontend/settings/vscode");
8
+
9
+ import { readSettingsAsset } from "../utils/settingsAssets.js";
10
+
4
11
  async function write(filePath, content) {
5
12
  await fs.writeFile(filePath, content, "utf-8");
6
13
  }
7
14
 
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
- };
15
+ const TOKEN_CONFIG_JS = `/**
16
+ * 디자이너가 넘긴 src/tokens.json 을 src/tokens.css 로 변환하는 설정.
17
+ *
18
+ * npm run tokens:build
19
+ *
20
+ * 변환 규칙(color / typography / motion)은 @byuckchon-frontend/settings 가
21
+ * 관리한다. 규칙이 바뀌면 settings 버전만 올리면 되고 이 파일은 그대로 둔다.
22
+ * 프로젝트별 예외가 필요하면 defineTokenConfig({ ... }) 에 인자를 넘긴다.
23
+ */
24
+ import { defineTokenConfig } from "@byuckchon-frontend/settings/tokens";
25
+
26
+ export default defineTokenConfig();
83
27
  `;
84
28
 
85
29
  // ─── 공통 설정 파일 ────────────────────────────────────────────────────────────
86
30
 
87
31
  async function createPrettierConfig(rootDir) {
88
- const config = {
89
- semi: true,
90
- trailingComma: "all",
91
- singleQuote: true,
92
- tabWidth: 2,
93
- useTabs: false,
94
- printWidth: 80,
95
- plugins: [
96
- "@trivago/prettier-plugin-sort-imports",
97
- "prettier-plugin-tailwindcss",
98
- ],
99
- importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
100
- importOrderSeparation: true,
101
- importOrderSortSpecifiers: true,
102
- };
32
+ // 규칙 본체는 @byuckchon-frontend/settings 가 관리한다.
33
+ // 프로젝트는 참조만 하므로, 팀 표준이 바뀌면 settings 버전만 올리면 된다.
103
34
  await write(
104
- path.join(rootDir, ".prettierrc"),
105
- JSON.stringify(config, null, 2)
35
+ path.join(rootDir, "prettier.config.js"),
36
+ `import byuckchon from '@byuckchon-frontend/settings/prettier';
37
+
38
+ /** 프로젝트 예외가 필요하면 펼쳐서 덮어쓰세요. (예: printWidth: 100) */
39
+ export default byuckchon;
40
+ `
106
41
  );
107
42
  }
108
43
 
109
44
  async function createEslintConfig(rootDir) {
110
45
  await write(
111
- path.join(rootDir, ".eslintrc.cjs"),
112
- `module.exports = {
113
- env: {
114
- browser: true,
115
- es2022: true,
116
- node: true,
117
- },
118
- extends: ['expo', 'eslint:recommended'],
119
- plugins: ['unused-imports'],
120
- rules: {
121
- 'unused-imports/no-unused-imports': 'error',
122
- 'unused-imports/no-unused-vars': [
123
- 'warn',
124
- {
125
- vars: 'all',
126
- varsIgnorePattern: '^_',
127
- args: 'after-used',
128
- argsIgnorePattern: '^_',
129
- },
130
- ],
131
- 'react/self-closing-comp': [
132
- 'warn',
133
- {
134
- component: true,
135
- html: true,
136
- },
137
- ],
138
- },
139
- settings: {
140
- 'import/resolver': {
141
- typescript: {},
142
- },
143
- },
144
- };
46
+ path.join(rootDir, "eslint.config.js"),
47
+ `import byuckchon from '@byuckchon-frontend/settings/eslint/react';
48
+
49
+ /**
50
+ * 규칙 본체는 @byuckchon-frontend/settings 가 관리합니다.
51
+ * 프로젝트 예외는 뒤에 이어붙이세요.
52
+ *
53
+ * export default [...byuckchon, { rules: { 'import/order': 'off' } }];
54
+ */
55
+ export default byuckchon;
145
56
  `
146
57
  );
147
58
  }
148
59
 
149
60
  async function createNextEslintConfig(rootDir) {
61
+ // next/core-web-vitals 는 설치된 next 버전과 짝을 이뤄야 해서 settings 가 들고 있지 않다.
62
+ // 프로젝트 쪽에서 합친다.
150
63
  await write(
151
- path.join(rootDir, ".eslintrc.cjs"),
152
- `// 현 파일이 eslint config type 을 따른다는 선언
153
- /** @type {import("eslint").Linter.Config} */
154
-
155
- module.exports = {
156
- root: true,
64
+ path.join(rootDir, "eslint.config.mjs"),
65
+ `import { FlatCompat } from '@eslint/eslintrc';
157
66
 
158
- // next.js 공식 eslint 규칙 적용
159
- extends: ["next/core-web-vitals", "next/typescript"],
67
+ import byuckchon from '@byuckchon-frontend/settings/eslint/next';
160
68
 
161
- // import 문 자동정렬, 유효성 검사, 경로 오류 방지
162
- plugins: ["import"],
163
-
164
- rules: {
165
- "@typescript-eslint/no-explicit-any": "off",
166
- "import/order": [
167
- "error",
168
- {
169
- // builtin: node 내장 모듈, external: npm 패키지, internal: 프로젝트 내 모듈, parent: 상위 경로, sibling: 형제 경로, index: 인덱스 파일
170
- groups: ["builtin", "external", "internal", "parent", "sibling", "index"],
171
- // 특정 패턴 그룹에 속하는 모듈 순서 지정
172
- pathGroups: [
173
- {
174
- pattern: "react",
175
- group: "external",
176
- position: "before",
177
- },
178
- {
179
- pattern: "next/**",
180
- group: "external",
181
- position: "before",
182
- },
183
- {
184
- pattern: "@/**",
185
- group: "internal",
186
- },
187
- ],
188
- // 중복 정렬 방지
189
- pathGroupsExcludedImportTypes: ["react"],
190
- // 알파벳 순서대로 오름차순 정렬
191
- alphabetize: { order: "asc", caseInsensitive: true },
192
- },
193
- ],
194
- },
69
+ const compat = new FlatCompat({ baseDirectory: import.meta.dirname });
195
70
 
196
- // 정렬 제외 파일 목록
197
- ignorePatterns: ["node_modules/", ".next/", "out/", "build/", "next-env.d.ts"],
198
- };
71
+ export default [
72
+ ...compat.extends('next/core-web-vitals', 'next/typescript'),
73
+ ...byuckchon,
74
+ ];
199
75
  `
200
76
  );
201
77
  }
@@ -228,17 +104,13 @@ Thumbs.db
228
104
  npm-debug.log*
229
105
  yarn-error.log*
230
106
 
231
- # Additional ignores
232
- node_modules
233
- dist
234
- dist-ssr
235
- *.local
236
- *.md
237
- !README.md
107
+ # bc CLI
238
108
  .bc/
239
- .env
240
- .env.production
241
109
  .history
110
+
111
+ # Misc
112
+ dist-ssr/
113
+ *.local
242
114
  `;
243
115
 
244
116
  const nextExtra = `
@@ -254,27 +126,14 @@ out/
254
126
  }
255
127
 
256
128
  async function createVscodeSettings(rootDir) {
129
+ // settings.json 은 extends 가 없어서 참조가 불가능하다. 실제 파일이 있어야 한다.
130
+ // 그래서 settings 가 들고 있는 값을 "복사"하되, 나중에 팀 표준이 바뀌면
131
+ // npx byuckchon-settings-sync vscode
132
+ // 로 다시 맞출 수 있게 한다. 프로젝트가 값을 고치는 것은 자유.
257
133
  await fs.mkdir(path.join(rootDir, ".vscode"), { recursive: true });
258
134
  await write(
259
135
  path.join(rootDir, ".vscode/settings.json"),
260
- JSON.stringify(
261
- {
262
- "editor.defaultFormatter": "esbenp.prettier-vscode",
263
- "editor.formatOnSave": true,
264
- "eslint.validate": [
265
- "javascript",
266
- "typescript",
267
- "javascriptreact",
268
- "typescriptreact",
269
- ],
270
- "editor.codeActionsOnSave": {
271
- "source.organizeImports": "always",
272
- "source.fixAll.eslint": "always",
273
- },
274
- },
275
- null,
276
- 2
277
- )
136
+ JSON.stringify(vscodeSettings, null, 2) + "\n"
278
137
  );
279
138
  }
280
139
 
@@ -333,45 +192,27 @@ export default defineConfig({
333
192
  `
334
193
  );
335
194
 
336
- // tsconfig.json
195
+ // tsconfig — 공통 옵션은 @byuckchon-frontend/settings 가 관리한다.
196
+ // 프로젝트에는 경로 alias 처럼 이 프로젝트에만 해당하는 것만 남긴다.
337
197
  await write(
338
198
  path.join(rootDir, "tsconfig.json"),
339
199
  JSON.stringify(
340
200
  {
341
201
  files: [],
342
- references: [
343
- { path: "./tsconfig.app.json" },
344
- { path: "./tsconfig.node.json" },
345
- ],
202
+ references: [{ path: "./tsconfig.app.json" }, { path: "./tsconfig.node.json" }],
346
203
  },
347
204
  null,
348
205
  2
349
- )
206
+ ) + "\n"
350
207
  );
351
208
 
352
- // tsconfig.app.json
353
209
  await write(
354
210
  path.join(rootDir, "tsconfig.app.json"),
355
211
  JSON.stringify(
356
212
  {
213
+ extends: "@byuckchon-frontend/settings/tsconfig/react.json",
357
214
  compilerOptions: {
358
215
  tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
359
- target: "ES2020",
360
- useDefineForClassFields: true,
361
- lib: ["ES2020", "DOM", "DOM.Iterable"],
362
- module: "ESNext",
363
- skipLibCheck: true,
364
- moduleResolution: "bundler",
365
- allowImportingTsExtensions: true,
366
- isolatedModules: true,
367
- moduleDetection: "force",
368
- noEmit: true,
369
- jsx: "react-jsx",
370
- strict: true,
371
- noUnusedLocals: true,
372
- noUnusedParameters: true,
373
- noFallthroughCasesInSwitch: true,
374
- noUncheckedSideEffectImports: true,
375
216
  baseUrl: ".",
376
217
  paths: {
377
218
  "@/*": ["src/*"],
@@ -379,42 +220,32 @@ export default defineConfig({
379
220
  "@images/*": ["src/assets/images/*"],
380
221
  },
381
222
  },
382
- include: ["src", "src/svg.d.ts"],
223
+ include: ["src"],
383
224
  },
384
225
  null,
385
226
  2
386
- )
227
+ ) + "\n"
387
228
  );
388
229
 
389
- // tsconfig.node.json
390
230
  await write(
391
231
  path.join(rootDir, "tsconfig.node.json"),
392
232
  JSON.stringify(
393
233
  {
234
+ extends: "@byuckchon-frontend/settings/tsconfig/node.json",
394
235
  compilerOptions: {
395
236
  tsBuildInfoFile: "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
396
- target: "ES2022",
397
- lib: ["ES2023"],
398
237
  module: "ESNext",
399
- skipLibCheck: true,
400
- moduleResolution: "bundler",
401
- allowImportingTsExtensions: true,
402
- isolatedModules: true,
403
- moduleDetection: "force",
404
- noEmit: true,
405
- strict: true,
406
- noUnusedLocals: true,
407
- noUnusedParameters: true,
408
- noFallthroughCasesInSwitch: true,
409
- noUncheckedSideEffectImports: true,
238
+ moduleResolution: "Bundler",
410
239
  },
411
240
  include: ["vite.config.ts"],
412
241
  },
413
242
  null,
414
243
  2
415
- )
244
+ ) + "\n"
416
245
  );
417
246
 
247
+ await write(path.join(rootDir, ".nvmrc"), await readSettingsAsset("project/nvmrc"));
248
+
418
249
  await createPrettierConfig(rootDir);
419
250
  await createEslintConfig(rootDir);
420
251
  await createGitignore(rootDir, "react");
@@ -424,8 +255,9 @@ export default defineConfig({
424
255
  // src/App.css
425
256
  await write(
426
257
  path.join(rootDir, "src/App.css"),
427
- `@import "./tokens.css";
428
- @import 'tailwindcss';
258
+ `@import 'tailwindcss';
259
+ @import "@byuckchon-frontend/settings/motion";
260
+ @import "./tokens.css";
429
261
  `
430
262
  );
431
263
  await write(path.join(rootDir, "src/tokens.css"), "");
@@ -449,27 +281,7 @@ createRoot(document.getElementById('root')!).render(<App />);
449
281
 
450
282
  await write(
451
283
  path.join(rootDir, "src/global.d.ts"),
452
- `declare module '*.svg' {
453
- import React from 'react';
454
- export const ReactComponent: React.FunctionComponent<
455
- React.SVGProps<SVGSVGElement>
456
- >;
457
- const src: string;
458
-
459
- export default src;
460
- }
461
-
462
- declare module '*.svg?react' {
463
- import React from 'react';
464
- const Component: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
465
-
466
- export default Component;
467
- }
468
-
469
- declare module '*.webp' {
470
- const value: any;
471
- export = value;
472
- }
284
+ `/// <reference types="@byuckchon-frontend/settings/types/svg-vite" />
473
285
  `
474
286
  );
475
287
 
@@ -541,41 +353,26 @@ export default nextConfig;
541
353
  `User-agent: *\nDisallow: /\n`
542
354
  );
543
355
 
544
- // tsconfig.json (Next.js)
356
+ // tsconfig (Next.js) — 공통 옵션은 settings 가 관리한다.
545
357
  await write(
546
358
  path.join(rootDir, "tsconfig.json"),
547
359
  JSON.stringify(
548
360
  {
361
+ extends: "@byuckchon-frontend/settings/tsconfig/next.json",
549
362
  compilerOptions: {
550
- target: "ES2017",
551
- lib: ["dom", "dom.iterable", "esnext"],
552
- allowJs: true,
553
- skipLibCheck: true,
554
- strict: true,
555
- noEmit: true,
556
- esModuleInterop: true,
557
- module: "esnext",
558
- moduleResolution: "bundler",
559
- resolveJsonModule: true,
560
- isolatedModules: true,
561
- jsx: "preserve",
562
- incremental: true,
563
- plugins: [{ name: "next" }],
363
+ baseUrl: ".",
564
364
  paths: { "@/*": ["./src/*"] },
565
365
  },
566
- include: [
567
- "next-env.d.ts",
568
- "**/*.ts",
569
- "**/*.tsx",
570
- ".next/types/**/*.ts",
571
- ],
366
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
572
367
  exclude: ["node_modules"],
573
368
  },
574
369
  null,
575
370
  2
576
- )
371
+ ) + "\n"
577
372
  );
578
373
 
374
+ await write(path.join(rootDir, ".nvmrc"), await readSettingsAsset("project/nvmrc"));
375
+
579
376
  await createPrettierConfig(rootDir);
580
377
  await createNextEslintConfig(rootDir);
581
378
  await createGitignore(rootDir, "next");
@@ -593,8 +390,9 @@ export default config;
593
390
  // src/app/globals.css
594
391
  await write(
595
392
  path.join(rootDir, "src/app/globals.css"),
596
- `@import "../tokens.css";
597
- @import 'tailwindcss';
393
+ `@import 'tailwindcss';
394
+ @import "@byuckchon-frontend/settings/motion";
395
+ @import "../tokens.css";
598
396
  `
599
397
  );
600
398
  await write(path.join(rootDir, "token.config.js"), TOKEN_CONFIG_JS);
@@ -669,39 +467,11 @@ export default function Error() {
669
467
 
670
468
  await write(
671
469
  path.join(rootDir, "src/global.d.ts"),
672
- `declare module '*.svg' {
673
- import React from 'react';
674
- export const ReactComponent: React.FunctionComponent<
675
- React.SVGProps<SVGSVGElement>
676
- >;
677
- const src: string;
678
-
679
- export default src;
680
- }
681
-
682
- declare module '*.svg?react' {
683
- import React from 'react';
684
- const Component: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
685
-
686
- export default Component;
687
- }
688
-
689
- declare module '*.webp' {
690
- const value: any;
691
- export = value;
692
- }
470
+ `/// <reference types="@byuckchon-frontend/settings/types/svg-next" />
693
471
  `
694
472
  );
695
473
 
696
- await write(
697
- path.join(rootDir, "src/types.d.ts"),
698
- `declare module "*.svg" {
699
- import React from "react";
700
- const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
701
- export default ReactComponent;
702
- }
703
- `
704
- );
474
+ // src/types.d.ts 는 global.d.ts 와 *.svg 선언이 충돌해서 제거했다.
705
475
  }
706
476
 
707
477
  // ─── 진입점 ───────────────────────────────────────────────────────────────────
@@ -3,7 +3,7 @@ import path from 'path';
3
3
 
4
4
  /**
5
5
  * React 폴더 구조
6
- * lib → store → api → hooks → context → components → layout → page
6
+ * lib → store → api → hooks → context → components → layouts → pages
7
7
  * assets는 모든 레이어에서 참조 가능
8
8
  */
9
9
  const REACT_FOLDERS = [
@@ -12,13 +12,14 @@ const REACT_FOLDERS = [
12
12
  'src/assets/icons',
13
13
  'src/assets/images',
14
14
  'src/lib',
15
+ 'src/lib/utils',
15
16
  'src/store',
16
17
  'src/api',
17
18
  'src/hooks',
18
19
  'src/context',
19
20
  'src/components',
20
- 'src/layout',
21
- 'src/page',
21
+ 'src/layouts',
22
+ 'src/pages',
22
23
  ];
23
24
 
24
25
  /**
@@ -34,6 +35,7 @@ const NEXT_FOLDERS = [
34
35
  'src/constant',
35
36
  'src/hooks',
36
37
  'src/lib',
38
+ 'src/lib/utils',
37
39
  'src/providers',
38
40
  ];
39
41
 
@@ -50,7 +52,21 @@ export async function createFolders(rootDir, config) {
50
52
  }
51
53
 
52
54
  // 각 레이어에 placeholder 파일 생성
53
- await writeFile(path.join(rootDir, 'src/lib/index.ts'), '// 유틸리티 함수, 상수, 헬퍼\n');
55
+ await writeFile(
56
+ path.join(rootDir, 'src/lib/utils/cn.ts'),
57
+ `import { clsx, type ClassValue } from 'clsx';
58
+ import { twMerge } from 'tailwind-merge';
59
+
60
+ export function cn(...inputs: ClassValue[]) {
61
+ return twMerge(clsx(inputs));
62
+ }
63
+ `,
64
+ );
65
+ await writeFile(
66
+ path.join(rootDir, 'src/lib/utils/index.ts'),
67
+ "export { cn } from './cn';\n",
68
+ );
69
+ await writeFile(path.join(rootDir, 'src/lib/index.ts'), "export * from './utils';\n");
54
70
  await writeFile(path.join(rootDir, 'src/hooks/index.ts'), '// 커스텀 훅\n');
55
71
  await writeFile(path.join(rootDir, 'src/components/index.ts'), '// 재사용 가능한 UI 컴포넌트\n');
56
72
 
@@ -58,8 +74,8 @@ export async function createFolders(rootDir, config) {
58
74
  await writeFile(path.join(rootDir, 'src/store/index.ts'), '// Zustand 스토어\n');
59
75
  await writeFile(path.join(rootDir, 'src/api/index.ts'), '// Axios API 호출\n');
60
76
  await writeFile(path.join(rootDir, 'src/context/index.tsx'), '// React Context\n');
61
- await writeFile(path.join(rootDir, 'src/layout/index.tsx'), '// 레이아웃 컴포넌트\n');
62
- await writeFile(path.join(rootDir, 'src/page/index.tsx'), '// 페이지 컴포넌트\n');
77
+ await writeFile(path.join(rootDir, 'src/layouts/index.tsx'), '// 레이아웃 컴포넌트\n');
78
+ await writeFile(path.join(rootDir, 'src/pages/index.tsx'), '// 페이지 컴포넌트\n');
63
79
  } else {
64
80
  await writeFile(path.join(rootDir, 'src/constant/index.ts'), '// 상수 정의\n');
65
81
  await writeFile(path.join(rootDir, 'src/providers/index.tsx'), '// 전역 Provider\n');