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.
- package/README.md +39 -5
- package/package.json +8 -2
- package/src/commands/adopt.js +12 -3
- package/src/constants/versions.js +12 -46
- package/src/generators/createApp.js +13 -29
- package/src/generators/createBaseFiles.js +79 -311
- package/src/generators/createFolders.js +17 -1
- package/src/generators/createMonorepo.js +49 -236
- package/src/generators/createPackageJson.js +10 -11
- package/src/generators/scaffoldReviewAutomation.js +41 -31
- package/src/utils/ensureRequiredDependencies.js +35 -9
- package/src/utils/settingsAssets.js +27 -0
- package/templates/review-automation/github/workflows/eslint-convention-review.monorepo.yml +8 -6
- package/templates/review-automation/github/workflows/eslint-convention-review.single.yml +6 -6
- package/templates/review-automation/github/workflows/pr-check.monorepo.yml +51 -0
- package/templates/review-automation/github/workflows/pr-check.single.yml +47 -0
- package/templates/review-automation/tools/review.config.mjs +9 -0
- package/templates/review-automation/tools/eslint-rules/internal-blocking-conventions.js +0 -1242
- package/templates/review-automation/tools/eslint-rules/internal-plugin.cjs +0 -8
- package/templates/review-automation/tools/eslint-rules/internal-rdjson-formatter.js +0 -60
- package/templates/review-automation/tools/eslint-rules/internal-warning-conventions.js +0 -57
- package/templates/review-automation/tools/eslint-rules/package.json +0 -3
- package/templates/review-automation/tools/eslint-rules/review.config.mjs +0 -22
- package/templates/review-automation/tools/post-eslint-review-comments.cjs +0 -309
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
+
import { versions } from '../constants/versions.js';
|
|
5
|
+
import { readSettingsAsset } from '../utils/settingsAssets.js';
|
|
4
6
|
import { createApp } from './createApp.js';
|
|
5
7
|
import { scaffoldReviewAutomation } from './scaffoldReviewAutomation.js';
|
|
6
8
|
|
|
@@ -82,6 +84,8 @@ async function writeRootFiles(root, config, scope) {
|
|
|
82
84
|
[config.appName]: `pnpm --filter @${scope}/${config.appName} dev`,
|
|
83
85
|
},
|
|
84
86
|
devDependencies: {
|
|
87
|
+
// 루트의 prettier.config.mjs 와 tsconfig.base.json 이 직접 참조한다.
|
|
88
|
+
'@byuckchon-frontend/settings': versions['@byuckchon-frontend/settings'],
|
|
85
89
|
'@types/node': '^22.0.0',
|
|
86
90
|
eslint: '^9.18.0',
|
|
87
91
|
'eslint-config-prettier': '^10.1.8',
|
|
@@ -102,58 +106,22 @@ packages:
|
|
|
102
106
|
`,
|
|
103
107
|
);
|
|
104
108
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
globalDependencies: ['tsconfig.base.json', '.env', '.env.*', '!.env*.local'],
|
|
109
|
-
globalEnv: ['NODE_ENV', 'CI'],
|
|
110
|
-
tasks: {
|
|
111
|
-
build: {
|
|
112
|
-
dependsOn: ['^build'],
|
|
113
|
-
outputs: ['dist/**', 'build/**', '.next/**', '!.next/cache/**', 'out/**'],
|
|
114
|
-
inputs: [
|
|
115
|
-
'$TURBO_DEFAULT$',
|
|
116
|
-
'!**/*.md',
|
|
117
|
-
'!**/*.test.ts',
|
|
118
|
-
'!**/*.test.tsx',
|
|
119
|
-
'!**/*.spec.ts',
|
|
120
|
-
'!**/*.spec.tsx',
|
|
121
|
-
],
|
|
122
|
-
},
|
|
123
|
-
dev: { cache: false, persistent: true },
|
|
124
|
-
lint: { dependsOn: ['^build'], outputs: [] },
|
|
125
|
-
typecheck: { dependsOn: ['^build'], outputs: ['.tsbuildinfo', '**/*.tsbuildinfo'] },
|
|
126
|
-
'tokens:build': { outputs: ['src/tokens.css'] },
|
|
127
|
-
clean: { cache: false },
|
|
128
|
-
},
|
|
129
|
-
});
|
|
109
|
+
// turbo / .npmrc / .nvmrc 는 참조 문법이 없어서 내용을 복사한다.
|
|
110
|
+
// 이후 settings 가 바뀌면 `npx byuckchon-settings-sync` 로 갱신한다.
|
|
111
|
+
await write(path.join(root, 'turbo.json'), await readSettingsAsset('project/turbo.json'));
|
|
130
112
|
|
|
113
|
+
// 공통 옵션은 @byuckchon-frontend/settings 가 관리한다.
|
|
114
|
+
// 이 파일은 모노레포 전용 예외를 얹는 자리로만 남긴다.
|
|
131
115
|
await writeJson(path.join(root, 'tsconfig.base.json'), {
|
|
132
116
|
$schema: 'https://json.schemastore.org/tsconfig',
|
|
133
|
-
display: `${
|
|
117
|
+
display: `${config.projectName} Base`,
|
|
118
|
+
extends: '@byuckchon-frontend/settings/tsconfig/base.json',
|
|
134
119
|
compilerOptions: {
|
|
135
|
-
target: 'ES2022',
|
|
136
|
-
lib: ['ES2022'],
|
|
137
|
-
module: 'ESNext',
|
|
138
|
-
moduleResolution: 'Bundler',
|
|
139
|
-
strict: true,
|
|
140
120
|
noUncheckedIndexedAccess: true,
|
|
141
121
|
noImplicitOverride: true,
|
|
142
|
-
noFallthroughCasesInSwitch: true,
|
|
143
|
-
useUnknownInCatchVariables: true,
|
|
144
|
-
exactOptionalPropertyTypes: false,
|
|
145
|
-
esModuleInterop: true,
|
|
146
122
|
allowSyntheticDefaultImports: true,
|
|
147
|
-
forceConsistentCasingInFileNames: true,
|
|
148
|
-
resolveJsonModule: true,
|
|
149
|
-
isolatedModules: true,
|
|
150
|
-
verbatimModuleSyntax: false,
|
|
151
|
-
skipLibCheck: true,
|
|
152
123
|
incremental: true,
|
|
153
|
-
composite: false,
|
|
154
|
-
types: [],
|
|
155
124
|
},
|
|
156
|
-
exclude: ['node_modules', 'dist', 'build', '.turbo', '.next', 'coverage'],
|
|
157
125
|
});
|
|
158
126
|
|
|
159
127
|
await writeJson(path.join(root, 'tsconfig.json'), {
|
|
@@ -162,42 +130,19 @@ packages:
|
|
|
162
130
|
include: [],
|
|
163
131
|
});
|
|
164
132
|
|
|
165
|
-
await write(
|
|
166
|
-
path.join(root, '.npmrc'),
|
|
167
|
-
`# pnpm 동작 설정
|
|
168
|
-
# React 버전 통일 및 (향후 Expo/RN 도입 대비) hoisted linker 사용.
|
|
169
|
-
node-linker=hoisted
|
|
170
|
-
|
|
171
|
-
public-hoist-pattern[]=*react*
|
|
172
|
-
public-hoist-pattern[]=*@types/*
|
|
173
|
-
public-hoist-pattern[]=*eslint*
|
|
174
|
-
public-hoist-pattern[]=*prettier*
|
|
175
|
-
|
|
176
|
-
strict-peer-dependencies=false
|
|
177
|
-
auto-install-peers=true
|
|
178
|
-
save-exact=false
|
|
179
|
-
save-prefix=^
|
|
180
|
-
prefer-frozen-lockfile=true
|
|
181
|
-
`,
|
|
182
|
-
);
|
|
133
|
+
await write(path.join(root, '.npmrc'), await readSettingsAsset('project/npmrc'));
|
|
183
134
|
|
|
184
|
-
await write(path.join(root, '.nvmrc'), '
|
|
135
|
+
await write(path.join(root, '.nvmrc'), await readSettingsAsset('project/nvmrc'));
|
|
185
136
|
|
|
186
137
|
await write(
|
|
187
138
|
path.join(root, 'prettier.config.mjs'),
|
|
188
|
-
|
|
139
|
+
`// 포맷 규칙은 @byuckchon-frontend/settings 가 관리합니다.
|
|
140
|
+
// 이 모노레포 전용 예외가 필요하면 펼쳐서 덮어쓰세요.
|
|
141
|
+
import byuckchon from '@byuckchon-frontend/settings/prettier';
|
|
142
|
+
|
|
143
|
+
/** @type {import("prettier").Config} */
|
|
189
144
|
export default {
|
|
190
|
-
|
|
191
|
-
singleQuote: true,
|
|
192
|
-
trailingComma: 'all',
|
|
193
|
-
tabWidth: 2,
|
|
194
|
-
printWidth: 100,
|
|
195
|
-
arrowParens: 'always',
|
|
196
|
-
endOfLine: 'lf',
|
|
197
|
-
bracketSpacing: true,
|
|
198
|
-
bracketSameLine: false,
|
|
199
|
-
jsxSingleQuote: false,
|
|
200
|
-
plugins: ['prettier-plugin-tailwindcss'],
|
|
145
|
+
...byuckchon,
|
|
201
146
|
overrides: [
|
|
202
147
|
{
|
|
203
148
|
files: ['*.json', '*.md', '*.yml', '*.yaml'],
|
|
@@ -205,7 +150,7 @@ export default {
|
|
|
205
150
|
},
|
|
206
151
|
],
|
|
207
152
|
};
|
|
208
|
-
|
|
153
|
+
`
|
|
209
154
|
);
|
|
210
155
|
|
|
211
156
|
await write(
|
|
@@ -313,106 +258,54 @@ async function writeConfigTypescript(root, scope) {
|
|
|
313
258
|
const dir = path.join(root, 'packages', 'config-typescript');
|
|
314
259
|
await fs.mkdir(dir, { recursive: true });
|
|
315
260
|
|
|
261
|
+
// 프리셋 본체는 @byuckchon-frontend/settings 가 관리한다.
|
|
262
|
+
// 이 패키지는 모노레포 전용 예외를 얹을 자리로만 남긴다.
|
|
316
263
|
await writeJson(path.join(dir, 'package.json'), {
|
|
317
264
|
name: `@${scope}/config-typescript`,
|
|
318
265
|
version: '0.0.0',
|
|
319
266
|
private: true,
|
|
320
|
-
description:
|
|
267
|
+
description: 'settings 의 TypeScript 프리셋을 이 모노레포용으로 감싼 패키지',
|
|
321
268
|
files: ['base.json', 'library.json', 'react.json', 'next.json', 'node.json'],
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
await writeJson(path.join(dir, 'base.json'), {
|
|
325
|
-
$schema: 'https://json.schemastore.org/tsconfig',
|
|
326
|
-
display: 'Base',
|
|
327
|
-
extends: '../../tsconfig.base.json',
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
await writeJson(path.join(dir, 'react.json'), {
|
|
331
|
-
$schema: 'https://json.schemastore.org/tsconfig',
|
|
332
|
-
display: 'React Web (Vite)',
|
|
333
|
-
extends: './base.json',
|
|
334
|
-
compilerOptions: {
|
|
335
|
-
lib: ['ES2022', 'DOM', 'DOM.Iterable'],
|
|
336
|
-
jsx: 'react-jsx',
|
|
337
|
-
moduleResolution: 'Bundler',
|
|
338
|
-
module: 'ESNext',
|
|
339
|
-
noEmit: true,
|
|
340
|
-
allowImportingTsExtensions: true,
|
|
341
|
-
useDefineForClassFields: true,
|
|
342
|
-
types: ['vite/client'],
|
|
343
|
-
noUncheckedIndexedAccess: false,
|
|
344
|
-
},
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
await writeJson(path.join(dir, 'next.json'), {
|
|
348
|
-
$schema: 'https://json.schemastore.org/tsconfig',
|
|
349
|
-
display: 'Next.js (App Router)',
|
|
350
|
-
extends: './base.json',
|
|
351
|
-
compilerOptions: {
|
|
352
|
-
lib: ['ES2022', 'DOM', 'DOM.Iterable'],
|
|
353
|
-
jsx: 'preserve',
|
|
354
|
-
module: 'ESNext',
|
|
355
|
-
moduleResolution: 'Bundler',
|
|
356
|
-
noEmit: true,
|
|
357
|
-
allowJs: true,
|
|
358
|
-
incremental: true,
|
|
359
|
-
plugins: [{ name: 'next' }],
|
|
360
|
-
noUncheckedIndexedAccess: false,
|
|
269
|
+
dependencies: {
|
|
270
|
+
'@byuckchon-frontend/settings': versions['@byuckchon-frontend/settings'],
|
|
361
271
|
},
|
|
362
272
|
});
|
|
363
273
|
|
|
364
|
-
|
|
274
|
+
const preset = (display, name) => ({
|
|
365
275
|
$schema: 'https://json.schemastore.org/tsconfig',
|
|
366
|
-
display
|
|
367
|
-
extends:
|
|
368
|
-
compilerOptions: {
|
|
369
|
-
declaration: true,
|
|
370
|
-
declarationMap: true,
|
|
371
|
-
sourceMap: true,
|
|
372
|
-
outDir: 'dist',
|
|
373
|
-
rootDir: 'src',
|
|
374
|
-
composite: true,
|
|
375
|
-
},
|
|
276
|
+
display,
|
|
277
|
+
extends: `@byuckchon-frontend/settings/tsconfig/${name}.json`,
|
|
376
278
|
});
|
|
377
279
|
|
|
378
|
-
await writeJson(path.join(dir, '
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
lib: ['ES2022'],
|
|
384
|
-
module: 'NodeNext',
|
|
385
|
-
moduleResolution: 'NodeNext',
|
|
386
|
-
types: ['node'],
|
|
387
|
-
},
|
|
388
|
-
});
|
|
280
|
+
await writeJson(path.join(dir, 'base.json'), preset('Base', 'base'));
|
|
281
|
+
await writeJson(path.join(dir, 'react.json'), preset('React Web (Vite)', 'react'));
|
|
282
|
+
await writeJson(path.join(dir, 'next.json'), preset('Next.js (App Router)', 'next'));
|
|
283
|
+
await writeJson(path.join(dir, 'library.json'), preset('Library (packages/*)', 'library'));
|
|
284
|
+
await writeJson(path.join(dir, 'node.json'), preset('Node (scripts)', 'node'));
|
|
389
285
|
}
|
|
390
286
|
|
|
391
287
|
async function writeConfigEslint(root, scope) {
|
|
392
288
|
const dir = path.join(root, 'packages', 'config-eslint');
|
|
393
289
|
await fs.mkdir(dir, { recursive: true });
|
|
394
290
|
|
|
291
|
+
// 규칙 본체는 @byuckchon-frontend/settings 가 관리한다.
|
|
292
|
+
// 이 패키지는 모노레포 전용 예외를 얹을 자리로만 남긴다.
|
|
395
293
|
await writeJson(path.join(dir, 'package.json'), {
|
|
396
294
|
name: `@${scope}/config-eslint`,
|
|
397
295
|
version: '0.0.0',
|
|
398
296
|
private: true,
|
|
399
297
|
type: 'module',
|
|
400
|
-
description:
|
|
298
|
+
description: 'settings 의 ESLint 프리셋을 이 모노레포용으로 감싼 패키지',
|
|
401
299
|
main: './base.js',
|
|
402
300
|
exports: {
|
|
403
301
|
'.': './base.js',
|
|
404
302
|
'./base': './base.js',
|
|
405
303
|
'./react': './react.js',
|
|
304
|
+
'./next': './next.js',
|
|
406
305
|
},
|
|
407
|
-
files: ['base.js', 'react.js'],
|
|
306
|
+
files: ['base.js', 'react.js', 'next.js'],
|
|
408
307
|
dependencies: {
|
|
409
|
-
'@
|
|
410
|
-
'eslint-config-prettier': '^10.1.8',
|
|
411
|
-
'eslint-plugin-import': '^2.32.0',
|
|
412
|
-
'eslint-plugin-react': '^7.37.4',
|
|
413
|
-
'eslint-plugin-react-hooks': '^5.1.0',
|
|
414
|
-
globals: '^15.14.0',
|
|
415
|
-
'typescript-eslint': '^8.59.1',
|
|
308
|
+
'@byuckchon-frontend/settings': versions['@byuckchon-frontend/settings'],
|
|
416
309
|
},
|
|
417
310
|
peerDependencies: {
|
|
418
311
|
eslint: '^9.0.0',
|
|
@@ -420,97 +313,17 @@ async function writeConfigEslint(root, scope) {
|
|
|
420
313
|
},
|
|
421
314
|
});
|
|
422
315
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
import js from '@eslint/js';
|
|
427
|
-
import tseslint from 'typescript-eslint';
|
|
428
|
-
import prettier from 'eslint-config-prettier';
|
|
429
|
-
import importPlugin from 'eslint-plugin-import';
|
|
430
|
-
import globals from 'globals';
|
|
316
|
+
const reexport = (name, named) => `// 규칙 본체는 @byuckchon-frontend/settings 가 관리합니다.
|
|
317
|
+
// 이 모노레포에만 해당하는 예외는 아래 배열에 이어붙이세요.
|
|
318
|
+
import byuckchon from '@byuckchon-frontend/settings/eslint/${name}';
|
|
431
319
|
|
|
432
|
-
/**
|
|
433
|
-
|
|
434
|
-
* @type {import("eslint").Linter.Config[]}
|
|
435
|
-
*/
|
|
436
|
-
export const baseConfig = [
|
|
437
|
-
{
|
|
438
|
-
ignores: [
|
|
439
|
-
'**/node_modules/**',
|
|
440
|
-
'**/dist/**',
|
|
441
|
-
'**/build/**',
|
|
442
|
-
'**/out/**',
|
|
443
|
-
'**/.next/**',
|
|
444
|
-
'**/.turbo/**',
|
|
445
|
-
'**/coverage/**',
|
|
446
|
-
'**/*.d.ts',
|
|
447
|
-
],
|
|
448
|
-
},
|
|
449
|
-
js.configs.recommended,
|
|
450
|
-
...tseslint.configs.recommended,
|
|
451
|
-
{
|
|
452
|
-
languageOptions: {
|
|
453
|
-
ecmaVersion: 2022,
|
|
454
|
-
sourceType: 'module',
|
|
455
|
-
globals: { ...globals.es2022 },
|
|
456
|
-
},
|
|
457
|
-
plugins: { import: importPlugin },
|
|
458
|
-
rules: {
|
|
459
|
-
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
|
460
|
-
'@typescript-eslint/consistent-type-imports': [
|
|
461
|
-
'warn',
|
|
462
|
-
{ prefer: 'type-imports', fixStyle: 'inline-type-imports' },
|
|
463
|
-
],
|
|
464
|
-
'@typescript-eslint/no-unused-vars': [
|
|
465
|
-
'warn',
|
|
466
|
-
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
|
467
|
-
],
|
|
468
|
-
},
|
|
469
|
-
},
|
|
470
|
-
prettier,
|
|
471
|
-
];
|
|
472
|
-
|
|
473
|
-
export default baseConfig;
|
|
474
|
-
`,
|
|
475
|
-
);
|
|
476
|
-
|
|
477
|
-
await write(
|
|
478
|
-
path.join(dir, 'react.js'),
|
|
479
|
-
`// @ts-check
|
|
480
|
-
import react from 'eslint-plugin-react';
|
|
481
|
-
import reactHooks from 'eslint-plugin-react-hooks';
|
|
482
|
-
import globals from 'globals';
|
|
320
|
+
/** @type {import("eslint").Linter.Config[]} */
|
|
321
|
+
export const ${named} = [...byuckchon];
|
|
483
322
|
|
|
484
|
-
|
|
323
|
+
export default ${named};
|
|
324
|
+
`;
|
|
485
325
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
*/
|
|
490
|
-
export const reactConfig = [
|
|
491
|
-
...baseConfig,
|
|
492
|
-
{
|
|
493
|
-
files: ['**/*.{js,jsx,ts,tsx}'],
|
|
494
|
-
languageOptions: {
|
|
495
|
-
globals: { ...globals.browser, ...globals.es2022 },
|
|
496
|
-
parserOptions: { ecmaFeatures: { jsx: true } },
|
|
497
|
-
},
|
|
498
|
-
plugins: { react, 'react-hooks': reactHooks },
|
|
499
|
-
settings: { react: { version: 'detect' } },
|
|
500
|
-
rules: {
|
|
501
|
-
...react.configs.recommended.rules,
|
|
502
|
-
...react.configs['jsx-runtime'].rules,
|
|
503
|
-
...reactHooks.configs.recommended.rules,
|
|
504
|
-
'react/prop-types': 'off',
|
|
505
|
-
'react/react-in-jsx-scope': 'off',
|
|
506
|
-
'react/no-unescaped-entities': 'off',
|
|
507
|
-
'react-hooks/rules-of-hooks': 'warn',
|
|
508
|
-
'@typescript-eslint/no-explicit-any': 'off',
|
|
509
|
-
},
|
|
510
|
-
},
|
|
511
|
-
];
|
|
512
|
-
|
|
513
|
-
export default reactConfig;
|
|
514
|
-
`,
|
|
515
|
-
);
|
|
326
|
+
await write(path.join(dir, 'base.js'), reexport('base', 'baseConfig'));
|
|
327
|
+
await write(path.join(dir, 'react.js'), reexport('react', 'reactConfig'));
|
|
328
|
+
await write(path.join(dir, 'next.js'), reexport('next', 'nextConfig'));
|
|
516
329
|
}
|
|
@@ -16,6 +16,8 @@ export async function createPackageJson(rootDir, config) {
|
|
|
16
16
|
dev: 'vite',
|
|
17
17
|
build: 'tsc -b && vite build',
|
|
18
18
|
'tokens:build': 'style-dictionary build --config token.config.js',
|
|
19
|
+
// PR Check workflow 가 lint / typecheck / build 를 각각 호출한다.
|
|
20
|
+
typecheck: 'tsc -b',
|
|
19
21
|
lint: 'eslint . --ext ts,tsx',
|
|
20
22
|
preview: 'vite preview',
|
|
21
23
|
format:
|
|
@@ -27,8 +29,9 @@ export async function createPackageJson(rootDir, config) {
|
|
|
27
29
|
dev: 'next dev',
|
|
28
30
|
build: 'next build',
|
|
29
31
|
'tokens:build': 'style-dictionary build --config token.config.js',
|
|
32
|
+
typecheck: 'tsc --noEmit',
|
|
30
33
|
start: 'next start',
|
|
31
|
-
lint: '
|
|
34
|
+
lint: 'eslint .',
|
|
32
35
|
format:
|
|
33
36
|
'prettier --write "src/**/*.{ts,tsx,css}" "tools/**/*.{js,cjs,json,md}"',
|
|
34
37
|
'format:check':
|
|
@@ -47,6 +50,8 @@ export async function createPackageJson(rootDir, config) {
|
|
|
47
50
|
}
|
|
48
51
|
: {}),
|
|
49
52
|
zod: versions.zod,
|
|
53
|
+
clsx: versions.clsx,
|
|
54
|
+
'tailwind-merge': versions['tailwind-merge'],
|
|
50
55
|
},
|
|
51
56
|
devDependencies: {
|
|
52
57
|
'@types/react': versions['@types/react'],
|
|
@@ -54,14 +59,10 @@ export async function createPackageJson(rootDir, config) {
|
|
|
54
59
|
'@types/node': versions['@types/node'],
|
|
55
60
|
'@trivago/prettier-plugin-sort-imports':
|
|
56
61
|
versions['@trivago/prettier-plugin-sort-imports'],
|
|
62
|
+
// eslint/prettier/tsconfig/tokens 설정과 motion CSS 가 모두 이 패키지를 참조한다.
|
|
63
|
+
// (ESLint 플러그인도 이 패키지가 의존성으로 들고 온다)
|
|
64
|
+
'@byuckchon-frontend/settings': versions['@byuckchon-frontend/settings'],
|
|
57
65
|
eslint: versions.eslint,
|
|
58
|
-
'eslint-config-expo': versions['eslint-config-expo'],
|
|
59
|
-
'eslint-import-resolver-typescript':
|
|
60
|
-
versions['eslint-import-resolver-typescript'],
|
|
61
|
-
'eslint-plugin-import': versions['eslint-plugin-import'],
|
|
62
|
-
'eslint-plugin-react': versions['eslint-plugin-react'],
|
|
63
|
-
'eslint-plugin-react-hooks': versions['eslint-plugin-react-hooks'],
|
|
64
|
-
'eslint-plugin-unused-imports': versions['eslint-plugin-unused-imports'],
|
|
65
66
|
prettier: versions.prettier,
|
|
66
67
|
'prettier-plugin-tailwindcss': versions['prettier-plugin-tailwindcss'],
|
|
67
68
|
'style-dictionary': versions['style-dictionary'],
|
|
@@ -70,9 +71,6 @@ export async function createPackageJson(rootDir, config) {
|
|
|
70
71
|
...(isReact
|
|
71
72
|
? {
|
|
72
73
|
'@tailwindcss/vite': versions['@tailwindcss/vite'],
|
|
73
|
-
'@typescript-eslint/eslint-plugin':
|
|
74
|
-
versions['@typescript-eslint/eslint-plugin'],
|
|
75
|
-
'@typescript-eslint/parser': versions['@typescript-eslint/parser'],
|
|
76
74
|
'@vitejs/plugin-react': versions['@vitejs/plugin-react'],
|
|
77
75
|
vite: versions.vite,
|
|
78
76
|
'vite-plugin-svgr': versions['vite-plugin-svgr'],
|
|
@@ -81,6 +79,7 @@ export async function createPackageJson(rootDir, config) {
|
|
|
81
79
|
'@tailwindcss/postcss': versions['@tailwindcss/postcss'],
|
|
82
80
|
'@svgr/webpack': versions['@svgr/webpack'],
|
|
83
81
|
'eslint-config-next': versions['eslint-config-next'],
|
|
82
|
+
'@eslint/eslintrc': versions['@eslint/eslintrc'],
|
|
84
83
|
}),
|
|
85
84
|
},
|
|
86
85
|
};
|
|
@@ -7,10 +7,37 @@ const TEMPLATE_ROOT = path.resolve(
|
|
|
7
7
|
'../../templates/review-automation',
|
|
8
8
|
);
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
const PROJECT_TYPES = ['single', 'monorepo'];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 워크플로 템플릿 이름 규칙
|
|
14
|
+
* `<이름>.single.yml` / `<이름>.monorepo.yml` → 유형이 맞는 쪽만 `<이름>.yml` 로 복사
|
|
15
|
+
* `<이름>.yml` → 유형과 무관하게 항상 복사
|
|
16
|
+
*
|
|
17
|
+
* @param {string[]} fileNames 템플릿 디렉터리의 파일 목록
|
|
18
|
+
* @param {'single' | 'monorepo'} projectType
|
|
19
|
+
* @returns {Array<{ source: string, target: string }>}
|
|
20
|
+
*/
|
|
21
|
+
function selectWorkflows(fileNames, projectType, exclude = []) {
|
|
22
|
+
const selected = [];
|
|
23
|
+
|
|
24
|
+
for (const fileName of fileNames) {
|
|
25
|
+
if (!fileName.endsWith('.yml')) continue;
|
|
26
|
+
|
|
27
|
+
const withoutExt = fileName.slice(0, -'.yml'.length);
|
|
28
|
+
const suffix = PROJECT_TYPES.find((type) => withoutExt.endsWith(`.${type}`));
|
|
29
|
+
const baseName = suffix
|
|
30
|
+
? withoutExt.slice(0, -(suffix.length + 1))
|
|
31
|
+
: withoutExt;
|
|
32
|
+
|
|
33
|
+
if (exclude.includes(baseName)) continue;
|
|
34
|
+
if (suffix && suffix !== projectType) continue;
|
|
35
|
+
|
|
36
|
+
selected.push({ source: fileName, target: `${baseName}.yml` });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return selected;
|
|
40
|
+
}
|
|
14
41
|
|
|
15
42
|
function shouldCopyTemplateFile(source) {
|
|
16
43
|
return path.basename(source) !== '.DS_Store';
|
|
@@ -38,23 +65,24 @@ async function copyFileIfAllowed(source, target, overwrite) {
|
|
|
38
65
|
* 프로젝트 루트에 PR 리뷰 자동화 파일을 생성한다.
|
|
39
66
|
*
|
|
40
67
|
* - tools/ 는 ESLint convention rule 및 PR 댓글 게시 스크립트를 제공한다.
|
|
41
|
-
* - .github/workflows/ 는 프로젝트 유형에 맞는
|
|
42
|
-
*
|
|
68
|
+
* - .github/workflows/ 는 프로젝트 유형에 맞는 워크플로를 제공한다.
|
|
69
|
+
* 템플릿 이름이 `<이름>.single.yml` / `<이름>.monorepo.yml` 이면 유형이 맞는 쪽만
|
|
70
|
+
* `<이름>.yml` 로 복사되고, 접미사가 없으면 유형과 무관하게 복사된다.
|
|
43
71
|
*
|
|
44
72
|
* @param {object} args
|
|
45
73
|
* @param {string} args.projectRoot 새로 생성한 프로젝트의 절대 경로
|
|
46
74
|
* @param {'single' | 'monorepo'} args.projectType 생성할 프로젝트 유형
|
|
47
75
|
* @param {boolean} [args.overwrite=true] 기존 파일을 템플릿으로 덮어쓸지
|
|
76
|
+
* @param {string[]} [args.exclude=[]] 제외할 워크플로 이름 (확장자·유형 접미사 제외)
|
|
48
77
|
* @returns {Promise<{ toolsDir: string, workflows: string[] }>}
|
|
49
78
|
*/
|
|
50
79
|
export async function scaffoldReviewAutomation({
|
|
51
80
|
projectRoot,
|
|
52
81
|
projectType,
|
|
53
82
|
overwrite = true,
|
|
83
|
+
exclude = [],
|
|
54
84
|
}) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!eslintWorkflow) {
|
|
85
|
+
if (!PROJECT_TYPES.includes(projectType)) {
|
|
58
86
|
throw new Error(`지원하지 않는 프로젝트 유형입니다: ${projectType}`);
|
|
59
87
|
}
|
|
60
88
|
|
|
@@ -72,33 +100,15 @@ export async function scaffoldReviewAutomation({
|
|
|
72
100
|
await fs.mkdir(workflowsTargetDir, { recursive: true });
|
|
73
101
|
|
|
74
102
|
const workflowFiles = await fs.readdir(workflowsTemplateDir);
|
|
75
|
-
const commonWorkflows = workflowFiles.filter(
|
|
76
|
-
(fileName) =>
|
|
77
|
-
fileName.endsWith('.yml') &&
|
|
78
|
-
!fileName.startsWith('eslint-convention-review.'),
|
|
79
|
-
);
|
|
80
103
|
const copiedWorkflows = [];
|
|
81
104
|
|
|
82
|
-
for (const
|
|
105
|
+
for (const { source, target } of selectWorkflows(workflowFiles, projectType, exclude)) {
|
|
83
106
|
const copied = await copyFileIfAllowed(
|
|
84
|
-
path.join(workflowsTemplateDir,
|
|
85
|
-
path.join(workflowsTargetDir,
|
|
107
|
+
path.join(workflowsTemplateDir, source),
|
|
108
|
+
path.join(workflowsTargetDir, target),
|
|
86
109
|
overwrite,
|
|
87
110
|
);
|
|
88
|
-
if (copied) copiedWorkflows.push(
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const eslintWorkflowTarget = path.join(
|
|
92
|
-
workflowsTargetDir,
|
|
93
|
-
'eslint-convention-review.yml',
|
|
94
|
-
);
|
|
95
|
-
const copiedEslintWorkflow = await copyFileIfAllowed(
|
|
96
|
-
path.join(workflowsTemplateDir, eslintWorkflow),
|
|
97
|
-
eslintWorkflowTarget,
|
|
98
|
-
overwrite,
|
|
99
|
-
);
|
|
100
|
-
if (copiedEslintWorkflow) {
|
|
101
|
-
copiedWorkflows.push('eslint-convention-review.yml');
|
|
111
|
+
if (copied) copiedWorkflows.push(target);
|
|
102
112
|
}
|
|
103
113
|
|
|
104
114
|
return { toolsDir: toolsTargetDir, workflows: copiedWorkflows };
|
|
@@ -7,6 +7,13 @@ const execFile = promisify(execFileCallback);
|
|
|
7
7
|
|
|
8
8
|
const COMMON_DEPENDENCIES = ['@tanstack/react-query', 'zod'];
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* 스캐폴딩되는 설정 파일들이 참조하는 패키지.
|
|
12
|
+
* tools/review.config.mjs, eslint.config, prettier.config, tsconfig 가 모두
|
|
13
|
+
* @byuckchon-frontend/settings 를 import/extends 하므로 없으면 lint·build 가 실패한다.
|
|
14
|
+
*/
|
|
15
|
+
const COMMON_DEV_DEPENDENCIES = ['@byuckchon-frontend/settings'];
|
|
16
|
+
|
|
10
17
|
const installCommands = {
|
|
11
18
|
npm: ['npm', ['install']],
|
|
12
19
|
pnpm: ['pnpm', ['add']],
|
|
@@ -14,6 +21,14 @@ const installCommands = {
|
|
|
14
21
|
bun: ['bun', ['add']],
|
|
15
22
|
};
|
|
16
23
|
|
|
24
|
+
/** devDependencies 로 설치할 때 붙이는 플래그 */
|
|
25
|
+
const devFlags = {
|
|
26
|
+
npm: '--save-dev',
|
|
27
|
+
pnpm: '-D',
|
|
28
|
+
yarn: '-D',
|
|
29
|
+
bun: '-d',
|
|
30
|
+
};
|
|
31
|
+
|
|
17
32
|
export function hasDependency(pkg, packageName) {
|
|
18
33
|
return Boolean(
|
|
19
34
|
pkg?.dependencies?.[packageName] ||
|
|
@@ -36,11 +51,11 @@ export async function ensureRequiredDependencies({
|
|
|
36
51
|
packageManager,
|
|
37
52
|
run = execFile,
|
|
38
53
|
}) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
);
|
|
54
|
+
const notInstalled = (packageName) => !hasDependency(pkg, packageName);
|
|
55
|
+
const missing = requiredDependenciesForFramework(framework).filter(notInstalled);
|
|
56
|
+
const missingDev = COMMON_DEV_DEPENDENCIES.filter(notInstalled);
|
|
42
57
|
|
|
43
|
-
if (!missing.length) {
|
|
58
|
+
if (!missing.length && !missingDev.length) {
|
|
44
59
|
return { installed: [], packageManager };
|
|
45
60
|
}
|
|
46
61
|
|
|
@@ -48,11 +63,22 @@ export async function ensureRequiredDependencies({
|
|
|
48
63
|
? packageManager
|
|
49
64
|
: 'npm';
|
|
50
65
|
const [command, baseArgs] = installCommands[selectedPackageManager];
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
);
|
|
66
|
+
const spec = (packageName) =>
|
|
67
|
+
versions[packageName] ? `${packageName}@${versions[packageName]}` : packageName;
|
|
54
68
|
|
|
55
|
-
|
|
69
|
+
if (missing.length) {
|
|
70
|
+
await run(command, [...baseArgs, ...missing.map(spec)], { cwd });
|
|
71
|
+
}
|
|
72
|
+
if (missingDev.length) {
|
|
73
|
+
await run(
|
|
74
|
+
command,
|
|
75
|
+
[...baseArgs, devFlags[selectedPackageManager], ...missingDev.map(spec)],
|
|
76
|
+
{ cwd },
|
|
77
|
+
);
|
|
78
|
+
}
|
|
56
79
|
|
|
57
|
-
return {
|
|
80
|
+
return {
|
|
81
|
+
installed: [...missing, ...missingDev],
|
|
82
|
+
packageManager: selectedPackageManager,
|
|
83
|
+
};
|
|
58
84
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @byuckchon-frontend/settings 가 관리하는 "파일로 존재해야 하는" 설정을 읽는다.
|
|
3
|
+
*
|
|
4
|
+
* eslint / prettier / tsconfig 는 프로젝트가 import·extends 로 참조하므로 여기 없다.
|
|
5
|
+
* 아래 파일들은 참조 문법이 없어서 생성 시점에 내용을 복사해야 한다.
|
|
6
|
+
* 이후 settings 가 바뀌면 프로젝트에서 `npx byuckchon-settings-sync` 로 갱신한다.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs/promises';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
|
|
14
|
+
/** settings 패키지 안의 파일 경로를 해석한다. */
|
|
15
|
+
export function resolveSettingsAsset(subpath) {
|
|
16
|
+
return require.resolve(`@byuckchon-frontend/settings/${subpath}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** settings 가 들고 있는 파일 내용을 문자열로 읽는다. */
|
|
20
|
+
export async function readSettingsAsset(subpath) {
|
|
21
|
+
return fs.readFile(resolveSettingsAsset(subpath), 'utf8');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** CLI 가 함께 배포되는 settings 의 실제 버전 (생성 프로젝트가 참조할 버전) */
|
|
25
|
+
export function settingsVersion() {
|
|
26
|
+
return require('@byuckchon-frontend/settings/package.json').version;
|
|
27
|
+
}
|