byuckchon-frontend-cli 1.9.7 → 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.
Files changed (24) hide show
  1. package/README.md +39 -5
  2. package/package.json +8 -2
  3. package/src/commands/adopt.js +12 -3
  4. package/src/constants/versions.js +12 -46
  5. package/src/generators/createApp.js +13 -29
  6. package/src/generators/createBaseFiles.js +79 -311
  7. package/src/generators/createFolders.js +17 -1
  8. package/src/generators/createMonorepo.js +49 -236
  9. package/src/generators/createPackageJson.js +10 -11
  10. package/src/generators/scaffoldReviewAutomation.js +41 -31
  11. package/src/utils/ensureRequiredDependencies.js +35 -9
  12. package/src/utils/settingsAssets.js +27 -0
  13. package/templates/review-automation/github/workflows/eslint-convention-review.monorepo.yml +8 -6
  14. package/templates/review-automation/github/workflows/eslint-convention-review.single.yml +6 -6
  15. package/templates/review-automation/github/workflows/pr-check.monorepo.yml +51 -0
  16. package/templates/review-automation/github/workflows/pr-check.single.yml +47 -0
  17. package/templates/review-automation/tools/review.config.mjs +9 -0
  18. package/templates/review-automation/tools/eslint-rules/internal-blocking-conventions.js +0 -1242
  19. package/templates/review-automation/tools/eslint-rules/internal-plugin.cjs +0 -8
  20. package/templates/review-automation/tools/eslint-rules/internal-rdjson-formatter.js +0 -60
  21. package/templates/review-automation/tools/eslint-rules/internal-warning-conventions.js +0 -57
  22. package/templates/review-automation/tools/eslint-rules/package.json +0 -3
  23. package/templates/review-automation/tools/eslint-rules/review.config.mjs +0 -22
  24. package/templates/review-automation/tools/post-eslint-review-comments.cjs +0 -309
@@ -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");
@@ -450,27 +281,7 @@ createRoot(document.getElementById('root')!).render(<App />);
450
281
 
451
282
  await write(
452
283
  path.join(rootDir, "src/global.d.ts"),
453
- `declare module '*.svg' {
454
- import React from 'react';
455
- export const ReactComponent: React.FunctionComponent<
456
- React.SVGProps<SVGSVGElement>
457
- >;
458
- const src: string;
459
-
460
- export default src;
461
- }
462
-
463
- declare module '*.svg?react' {
464
- import React from 'react';
465
- const Component: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
466
-
467
- export default Component;
468
- }
469
-
470
- declare module '*.webp' {
471
- const value: any;
472
- export = value;
473
- }
284
+ `/// <reference types="@byuckchon-frontend/settings/types/svg-vite" />
474
285
  `
475
286
  );
476
287
 
@@ -542,41 +353,26 @@ export default nextConfig;
542
353
  `User-agent: *\nDisallow: /\n`
543
354
  );
544
355
 
545
- // tsconfig.json (Next.js)
356
+ // tsconfig (Next.js) — 공통 옵션은 settings 가 관리한다.
546
357
  await write(
547
358
  path.join(rootDir, "tsconfig.json"),
548
359
  JSON.stringify(
549
360
  {
361
+ extends: "@byuckchon-frontend/settings/tsconfig/next.json",
550
362
  compilerOptions: {
551
- target: "ES2017",
552
- lib: ["dom", "dom.iterable", "esnext"],
553
- allowJs: true,
554
- skipLibCheck: true,
555
- strict: true,
556
- noEmit: true,
557
- esModuleInterop: true,
558
- module: "esnext",
559
- moduleResolution: "bundler",
560
- resolveJsonModule: true,
561
- isolatedModules: true,
562
- jsx: "preserve",
563
- incremental: true,
564
- plugins: [{ name: "next" }],
363
+ baseUrl: ".",
565
364
  paths: { "@/*": ["./src/*"] },
566
365
  },
567
- include: [
568
- "next-env.d.ts",
569
- "**/*.ts",
570
- "**/*.tsx",
571
- ".next/types/**/*.ts",
572
- ],
366
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
573
367
  exclude: ["node_modules"],
574
368
  },
575
369
  null,
576
370
  2
577
- )
371
+ ) + "\n"
578
372
  );
579
373
 
374
+ await write(path.join(rootDir, ".nvmrc"), await readSettingsAsset("project/nvmrc"));
375
+
580
376
  await createPrettierConfig(rootDir);
581
377
  await createNextEslintConfig(rootDir);
582
378
  await createGitignore(rootDir, "next");
@@ -671,39 +467,11 @@ export default function Error() {
671
467
 
672
468
  await write(
673
469
  path.join(rootDir, "src/global.d.ts"),
674
- `declare module '*.svg' {
675
- import React from 'react';
676
- export const ReactComponent: React.FunctionComponent<
677
- React.SVGProps<SVGSVGElement>
678
- >;
679
- const src: string;
680
-
681
- export default src;
682
- }
683
-
684
- declare module '*.svg?react' {
685
- import React from 'react';
686
- const Component: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
687
-
688
- export default Component;
689
- }
690
-
691
- declare module '*.webp' {
692
- const value: any;
693
- export = value;
694
- }
470
+ `/// <reference types="@byuckchon-frontend/settings/types/svg-next" />
695
471
  `
696
472
  );
697
473
 
698
- await write(
699
- path.join(rootDir, "src/types.d.ts"),
700
- `declare module "*.svg" {
701
- import React from "react";
702
- const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
703
- export default ReactComponent;
704
- }
705
- `
706
- );
474
+ // src/types.d.ts 는 global.d.ts 와 *.svg 선언이 충돌해서 제거했다.
707
475
  }
708
476
 
709
477
  // ─── 진입점 ───────────────────────────────────────────────────────────────────
@@ -12,6 +12,7 @@ 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',
@@ -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