Skip to content

Commit 2f4b091

Browse files
authored
Merge pull request #139 from NaverPayDev/feature/cli
[code-style-cli] 패키지 설치 및 설정 파일 생성 CLI 도구 추가
2 parents 5f11fc5 + 8f6112c commit 2f4b091

7 files changed

Lines changed: 659 additions & 2 deletions

File tree

.changeset/tangy-streets-hide.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@naverpay/code-style-cli": minor
3+
---
4+
5+
[code-style-cli] 패키지 설치 및 설정 파일 생성 CLI 도구 추가
6+
7+
PR: [[code-style-cli] 패키지 설치 및 설정 파일 생성 CLI 도구 추가](https://github.com/NaverPayDev/code-style/pull/139)

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
네이버페이의 일관된 코드 스타일을 유지하기 위한 레포입니다.
44
해당 레포는 모노레포 구조로 여러 패키지를 포함하며, 필요에 맞게 선택하여 사용하실 수 있습니다.
55

6-
- [@naverpay/eslint-config](./packages/eslint-config/README.md): ESLint config 를 제공하는 패키지입니다.
6+
- [@naverpay/code-style-cli](./packages/code-style-cli/README.md): 패키지 설치 및 설정 파일 생성을 위한 CLI 도구입니다.
7+
- [@naverpay/eslint-config](./packages/eslint-config/README.md): ESLint config를 제공하는 패키지입니다.
78
- [@naverpay/eslint-plugin](./packages/eslint-plugin/README.md): ESLint plugin 을 제공하는 패키지입니다.
89
- [@naverpay/oxlint-config](./packages/oxlint-config/README.md): oxlint config 를 제공하는 패키지입니다.
910
- [@naverpay/prettier-config](./packages/prettier-config/README.md): Prettier config 를 제공하는 패키지입니다.
10-
- [@naverpay/stylelint-config](./packages/stylelint-config/README.md): Stylelint config 를 제공하는 패키지 입니다.
11+
- [@naverpay/biome-config](./packages/biome-config/README.md): Biome config 를 제공하는 패키지입니다.
12+
- [@naverpay/stylelint-config](./packages/stylelint-config/README.md): Stylelint config 를 제공하는 패키지입니다.
1113
- [@naverpay/editorconfig](./packages/editorconfig/README.md): IDE 일관된 코딩 스타일로 작성할 수 있도록 `.editorconfig` 를 제공하는 패키지입니다.
1214
- [@naverpay/markdown-lint](./packages/markdown-lint/README.md): 마크다운(Markdown) 파일의 스타일을 검사하는 패키지 입니다.

packages/code-style-cli/README.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# @naverpay/code-style-cli
2+
3+
네이버페이 코드 스타일 패키지를 쉽게 설치하고 설정할 수 있는 CLI 도구입니다.
4+
5+
> pnpm v10+에서는 보안상의 이유로 postinstall 스크립트가 기본적으로 실행되지 않습니다. 이 CLI를 사용하면 패키지 설치와 설정 파일 생성을 한 번에 처리할 수 있습니다.
6+
7+
## 사용 방법
8+
9+
프로젝트 루트에서 실행합니다.
10+
11+
```bash
12+
npx @naverpay/code-style-cli
13+
```
14+
15+
1. 패키지 매니저를 자동으로 감지합니다. (npm, yarn, pnpm)
16+
2. 설치할 패키지를 선택합니다.
17+
3. 선택한 패키지가 설치되고 설정 파일이 자동으로 생성됩니다.
18+
19+
## 지원 패키지
20+
21+
| 패키지 | 설정 파일 |
22+
|--------|-----------|
23+
| [@naverpay/eslint-config](../eslint-config/README.md) | - |
24+
| [@naverpay/eslint-plugin](../eslint-plugin/README.md) | - |
25+
| [@naverpay/prettier-config](../prettier-config/README.md) | `.prettierrc` |
26+
| [@naverpay/stylelint-config](../stylelint-config/README.md) | `stylelint.config.mjs` |
27+
| [@naverpay/markdown-lint](../markdown-lint/README.md) | `.markdownlint.jsonc` |
28+
| [@naverpay/editorconfig](../editorconfig/README.md) | `.editorconfig` |
29+
| [@naverpay/oxlint-config](../oxlint-config/README.md) | `.oxlintrc.json` |
30+
| [@naverpay/biome-config](../biome-config/README.md) | `biome.json` |
31+
| [oxfmt](#oxfmt-가이드) | `.oxfmtrc.json` |
32+
33+
> eslint-config, eslint-plugin은 설정이 복잡하여 설정 파일을 자동 생성하지 않습니다.
34+
35+
## 설치 후 설정
36+
37+
CLI는 기본 설정 파일만 생성합니다. 추가 설정이 필요한 경우:
38+
39+
- **설정 변경**: 생성된 설정 파일을 직접 수정하세요.
40+
- **CLI 명령어**: 각 패키지 README의 "CLI" 섹션을 참고하세요.
41+
- **IDE 설정**: 각 패키지 README의 "Integrating with IDE" 섹션을 참고하세요.
42+
43+
## 주의 사항
44+
45+
- 프로젝트 루트에 `package.json`이 있어야 합니다.
46+
- 이미 설정 파일이 존재하는 경우 덮어쓰지 않습니다.
47+
48+
---
49+
50+
## oxfmt 가이드
51+
52+
> oxfmt는 Prettier 호환 포매터로, Rust로 작성되어 빠른 속도를 제공합니다.
53+
>
54+
> **Note:** oxfmt는 현재 **alpha** 단계입니다. VSCode Extension 지원도 experimental 상태입니다.
55+
56+
oxfmt는 현재 `extends` 옵션을 지원하지 않아 별도 config 패키지가 없습니다. CLI에서 네이버페이 권장 설정이 포함된 `.oxfmtrc.json`을 생성합니다.
57+
58+
### 설정
59+
60+
필요에 따라 `ignorePatterns`를 추가합니다.
61+
62+
```json
63+
{
64+
"$schema": "./node_modules/oxfmt/configuration_schema.json",
65+
"singleQuote": true,
66+
"semi": false,
67+
"useTabs": false,
68+
"tabWidth": 4,
69+
"endOfLine": "lf",
70+
"bracketSpacing": false,
71+
"arrowParens": "always",
72+
"bracketSameLine": false,
73+
"printWidth": 120,
74+
"trailingComma": "all",
75+
"ignorePatterns": ["dist", "pnpm-lock.yaml", ".github"]
76+
}
77+
```
78+
79+
### CLI
80+
81+
package.json에 스크립트를 추가하여 format 검사를 할 수 있습니다.
82+
83+
```json
84+
{
85+
"scripts": {
86+
"format": "oxfmt",
87+
"format:check": "oxfmt --check"
88+
}
89+
}
90+
```
91+
92+
### Integrating with IDE
93+
94+
#### VSCode
95+
96+
> **Warning:** oxfmt의 VSCode 지원은 현재 **experimental** 단계입니다.
97+
98+
1. [oxc Extension](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode)을 설치합니다.
99+
2. `settings.json`에 아래 설정을 추가합니다.
100+
101+
```json
102+
{
103+
"oxc.enable": true,
104+
"oxc.fmt.experimental": true,
105+
"editor.defaultFormatter": "oxc.oxc-vscode",
106+
"editor.formatOnSave": true,
107+
"[typescript]": {
108+
"editor.defaultFormatter": "oxc.oxc-vscode"
109+
},
110+
"[javascript]": {
111+
"editor.defaultFormatter": "oxc.oxc-vscode"
112+
}
113+
}
114+
```
115+
116+
#### WebStorm
117+
118+
[oxc-intellij-plugin](https://plugins.jetbrains.com/plugin/27061-oxc) (v0.0.21 이상)을 설치하여 사용할 수 있습니다.

packages/code-style-cli/cli.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env node
2+
/* eslint-disable no-console */
3+
4+
import {execSync} from 'child_process'
5+
import fs from 'fs'
6+
7+
import {checkbox, confirm} from '@inquirer/prompts'
8+
9+
import {PACKAGE_MANAGERS, TOOLS, TOOLS_MAP} from './configs.js'
10+
11+
// 1. package.json 존재 확인
12+
if (!fs.existsSync('package.json')) {
13+
console.error('❌ package.json이 없습니다. 프로젝트 루트에서 실행해주세요.')
14+
process.exit(1)
15+
}
16+
17+
// 2. 패키지 매니저 감지
18+
function detectPackageManager() {
19+
for (const [name, {lockFile}] of Object.entries(PACKAGE_MANAGERS)) {
20+
if (fs.existsSync(lockFile)) return name
21+
}
22+
return 'npm'
23+
}
24+
25+
const pm = detectPackageManager()
26+
console.log(`📦 패키지 매니저: ${pm}`)
27+
28+
// 3. 설치된 패키지 확인
29+
const pkgJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'))
30+
const installedPkgs = {...pkgJson.dependencies, ...pkgJson.devDependencies}
31+
32+
// 4. 패키지 선택 UI
33+
const choices = TOOLS.map((tool) => {
34+
const isInstalled = tool.packages.some((pkg) => installedPkgs[pkg])
35+
return {
36+
...tool,
37+
name: isInstalled ? `${tool.value} (설치됨)` : tool.value,
38+
}
39+
})
40+
41+
const selected = await checkbox({
42+
message: '설치할 패키지를 선택하세요 (이미 설치된 패키지 선택 시 업데이트)',
43+
choices,
44+
pageSize: choices.length,
45+
})
46+
47+
if (selected.length === 0) {
48+
console.log('⚠️ 선택된 패키지가 없습니다.')
49+
process.exit(0)
50+
}
51+
52+
const selectedTools = selected.map((value) => TOOLS_MAP[value])
53+
console.log('✅ 선택된 패키지:', selected)
54+
55+
// 5. 패키지 설치
56+
const packagesToInstall = selectedTools.flatMap((tool) => tool.packages)
57+
58+
if (packagesToInstall.length > 0) {
59+
console.log('\n📥 패키지 설치 중...')
60+
try {
61+
execSync(`${PACKAGE_MANAGERS[pm].installCmd} ${packagesToInstall.join(' ')}`, {stdio: 'inherit'})
62+
console.log('✅ 패키지 설치 완료!')
63+
} catch {
64+
console.error('❌ 패키지 설치 실패. 네트워크 연결을 확인하거나 수동으로 설치해주세요.')
65+
process.exit(1)
66+
}
67+
}
68+
69+
// 6. 설정 파일 생성
70+
for (const tool of selectedTools) {
71+
if (!tool.configFile) continue
72+
73+
if (fs.existsSync(tool.configFile)) {
74+
const overwrite = await confirm({
75+
message: `${tool.configFile} 이미 존재합니다. 덮어쓰시겠습니까?`,
76+
default: false,
77+
})
78+
if (!overwrite) {
79+
console.log(`⏭️ ${tool.configFile} 스킵`)
80+
continue
81+
}
82+
}
83+
84+
if (tool.copyFrom) {
85+
if (!fs.existsSync(tool.copyFrom)) {
86+
console.error(`❌ ${tool.copyFrom} 파일을 찾을 수 없습니다.`)
87+
continue
88+
}
89+
const content = fs.readFileSync(tool.copyFrom, 'utf-8')
90+
fs.writeFileSync(tool.configFile, content)
91+
} else if (tool.getContent) {
92+
const content = tool.getContent()
93+
if (!content) {
94+
console.error(`❌ ${tool.configFile} 생성에 필요한 정보를 찾을 수 없습니다.`)
95+
continue
96+
}
97+
fs.writeFileSync(tool.configFile, content)
98+
} else if (tool.configContent) {
99+
fs.writeFileSync(tool.configFile, tool.configContent)
100+
}
101+
console.log(`✅ ${tool.configFile} 생성 완료`)
102+
}
103+
104+
console.log('\n🎉 완료!')

packages/code-style-cli/configs.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import fs from 'fs'
2+
3+
// 설정 파일 내용
4+
const PRETTIERRC_CONTENT = '"@naverpay/prettier-config"'
5+
6+
const STYLELINT_CONTENT = `/** @type {import('stylelint').Config} */
7+
const config = {
8+
extends: ['@naverpay/stylelint-config'],
9+
defaultSeverity: 'error',
10+
rules: {},
11+
}
12+
13+
export default config
14+
`
15+
16+
const MARKDOWNLINT_CONTENT = JSON.stringify(
17+
{
18+
extends: '@naverpay/markdown-lint',
19+
},
20+
null,
21+
4,
22+
)
23+
24+
const OXLINTRC_CONTENT = JSON.stringify(
25+
{
26+
$schema: './node_modules/oxlint/configuration_schema.json',
27+
extends: ['./node_modules/@naverpay/oxlint-config/node/.oxlintrc.json'],
28+
},
29+
null,
30+
4,
31+
)
32+
33+
function getBiomeContent() {
34+
const pkgJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'))
35+
const version = pkgJson.devDependencies?.['@biomejs/biome']?.replace(/[\^~]/, '')
36+
if (!version) return null
37+
return JSON.stringify(
38+
{
39+
$schema: `https://biomejs.dev/schemas/${version}/schema.json`,
40+
extends: ['@naverpay/biome-config'],
41+
},
42+
null,
43+
4,
44+
)
45+
}
46+
47+
const OXFMTRC_CONTENT = JSON.stringify(
48+
{
49+
$schema: './node_modules/oxfmt/configuration_schema.json',
50+
singleQuote: true,
51+
semi: false,
52+
useTabs: false,
53+
tabWidth: 4,
54+
endOfLine: 'lf',
55+
bracketSpacing: false,
56+
arrowParens: 'always',
57+
bracketSameLine: false,
58+
printWidth: 120,
59+
trailingComma: 'all',
60+
},
61+
null,
62+
4,
63+
)
64+
65+
// 패키지 목록
66+
export const TOOLS = [
67+
{value: 'eslint-config', packages: ['@naverpay/eslint-config']},
68+
{value: 'eslint-plugin', packages: ['@naverpay/eslint-plugin']},
69+
{
70+
value: 'prettier-config',
71+
packages: ['@naverpay/prettier-config'],
72+
configFile: '.prettierrc',
73+
configContent: PRETTIERRC_CONTENT,
74+
},
75+
{
76+
value: 'stylelint-config',
77+
packages: ['@naverpay/stylelint-config', 'stylelint'],
78+
configFile: 'stylelint.config.mjs',
79+
configContent: STYLELINT_CONTENT,
80+
},
81+
{
82+
value: 'markdown-lint',
83+
packages: ['@naverpay/markdown-lint'],
84+
configFile: '.markdownlint.jsonc',
85+
configContent: MARKDOWNLINT_CONTENT,
86+
},
87+
{
88+
value: 'editorconfig',
89+
packages: ['@naverpay/editorconfig'],
90+
configFile: '.editorconfig',
91+
copyFrom: 'node_modules/@naverpay/editorconfig/.editorconfig',
92+
},
93+
{
94+
value: 'oxlint-config',
95+
packages: ['@naverpay/oxlint-config', 'oxlint'],
96+
configFile: '.oxlintrc.json',
97+
configContent: OXLINTRC_CONTENT,
98+
},
99+
{
100+
value: 'biome-config',
101+
packages: ['@naverpay/biome-config', '@biomejs/biome'],
102+
configFile: 'biome.json',
103+
getContent: getBiomeContent,
104+
},
105+
{value: 'oxfmt', packages: ['oxfmt'], configFile: '.oxfmtrc.json', configContent: OXFMTRC_CONTENT},
106+
]
107+
108+
export const TOOLS_MAP = Object.fromEntries(TOOLS.map((t) => [t.value, t]))
109+
110+
export const PACKAGE_MANAGERS = {
111+
npm: {lockFile: 'package-lock.json', installCmd: 'npm install -D'},
112+
yarn: {lockFile: 'yarn.lock', installCmd: 'yarn add -D'},
113+
pnpm: {lockFile: 'pnpm-lock.yaml', installCmd: 'pnpm add -D'},
114+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "@naverpay/code-style-cli",
3+
"version": "0.0.1",
4+
"description": "NaverPay code-style CLI tool",
5+
"keywords": [
6+
"code-style",
7+
"cli",
8+
"naverpay"
9+
],
10+
"homepage": "https://github.com/NaverPayDev/code-style",
11+
"repository": {
12+
"type": "git",
13+
"url": "https://github.com/NaverPayDev/code-style.git",
14+
"directory": "packages/code-style-cli"
15+
},
16+
"license": "MIT",
17+
"author": "@NaverPayDev/frontend",
18+
"type": "module",
19+
"bin": {
20+
"code-style": "cli.js"
21+
},
22+
"dependencies": {
23+
"@inquirer/prompts": "^8.1.0"
24+
}
25+
}

0 commit comments

Comments
 (0)