March 01, 2021
์์ ํ๋ ํ๊ฒฝ์ด ์ด๋๊ฑด ์์ฃผ ์ฌ์ฉํ๊ฑฐ๋, ํ๊ฒฝ์ค์ ํ๋๋ฐ ์ค๋๊ฑธ๋ฆฌ๋ ์ฝ๋๋ค์ด ์กด์ฌํ๋ค. ๋์ ๊ฒฝ์ฐ๋ ์นํฉ์ด ๊ทธ๋ฌ๋ค.
ํ๋์ ํ๋ก์ ํธ๋ฅผ ์์ํ ๋ ํ๋ฒ ์ค์ ํ๋ฉด ๊ทธ์ธ์๋ ๋ณ๊ฒฝํ์ง ์๋ ๋ถ๋ถ์ด์ง๋ง, ๊ฐ๋จํ๊ฒ JS๋ TS ์ฝ๋๋ฅผ ์์ฑํด์ ๋๋ ค๋ณด๊ณ ์ถ์๋ฐ ํฌ๋กฌ๊ฐ๋ฐ์๋๊ตฌ ์ฝ์์ฐฝ์์๋ ํ๊ณ๊ฐ ์์ ๋๋ง๋ค ์์ ์นํฉ ์ค์ ์ ํด๋ก ํด์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค๊ฑฐ๋ ์ฝ๋๋ฅผ ์ผ์ผํ ๋ณต๋ถํด์ ์ฌ์ฉํ๊ณคํ๋ค.
์๋ฐ ๋ฐฉ์์ ๋๋ฌด ๋นํจ์จ์ ์ด๊ณ , ์์ผ๋ก๋ ์ด๋ฐ ๊ฒฝ์ฐ๊ฐ ์์ฃผ ์๊ธธ ๊ฒ ๊ฐ์์ ์ง์ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์์ฑ๊ธฐ๋ฅผ ๋ง๋ค์ด๋ณด๊ธฐ๋ก ํ์๋ค!!
ํ๋ก์ ํธ ๊ตฌ์กฐ๋ ์ด๋ ๊ฒ ์ค์ ํ๋ค.
๋จผ์ package.json์ bin ์, ํจํค์ง๋ฅผ ์ค์นํ ๋, ์คํ๋ ์คํฌ๋ฆฝํธ ๊ฒฝ๋ก๋ฅผ ๋ฃ์ด์ฃผ์๋ค.
๊ทธ๋ฆฌ๊ณ run
์คํฌ๋ฆฝํธ์์๋ ์ค์ ์คํ๋ ํ์ผ์ ์คํ์์ผ์ฃผ๋๋ก ์ฝ๋๋ฅผ ์์ฑํด์ฃผ์๋ค.
#!/usr/bin/env node
require("../dist")().run();
์ด ๋, run
์คํฌ๋ฆฝํธ๋ ์ด๋ ํ ์ฌ์ฉ์๋ ๋ชจ๋ ์คํํ ์ ์๋๋ก ๊ถํ์ ๋ณ๊ฒฝํด์ฃผ์๋ค. (๊ถํ ์ค์ ์ ํ์ง ์์๊ฒฝ์ฐ, ์คํฌ๋ฆฝํธ ์คํ์, permission ์ค๋ฅ๊ฐ ๋ฌ๋ค.)
chmod 755 run
chmod 755 run.cmd
๊ทธ๋ฆฌ๊ณ typescript์ directory๋ฅผ ๋ณต์ฌํ๋ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ fs-extra
ํจํค์ง, ์์ ํฐ๋ฏธ๋์
์ถ๋ ฅ์ ๋์์ค terminal-kit
ํจํค์ง๋ฅผ ์ถ๊ฐ๋ก ์ค์นํด์ฃผ์๋ค.
์ํธ๋ฆฌํฌ์ธํธ์ธ index.ts
์์๋ run ๋ฉ์๋๋ง ๊ฐ์ง๋ ๋ชจ๋์ ํํ๋ก ์์ฑํด์ฃผ์๋ค.
๊ทธ๋ฆฌ๊ณ ๋ณ๊ฒฝ๊ฐ๋ฅํ ๋ฐ์ดํฐ(๋ณด์ผ๋ฌํ๋ ์ดํธ ์ ๋ณด, ์
์ถ๋ ฅ, ์๋ฌ๋ฉ์์ง ๋ฑ)์ ๋ชจ๋ ๋ฐ๋ก ๋นผ๊ณ , ๋ฐ์ดํฐ๋ฅผ createPrompt
๋ผ๋ ํจ์์ ์ ๋ฌํ๋ ์ญํ ๋ง ์ฃผ์๋ค.
const run = () => {
const selectItemMap: SelectItemMap = new Map(selectItems)
const options: PromptOptions = {
DEFAULT_DEST_DIR_NAME,
QUESTION_MESSAGE1,
QUESTION_MESSAGE2,
}
createPrompt(selectItemMap, options)
}
module.exports = () => {
return {
run,
}
}
๋ฐ์ดํฐ ๊ตฌ์กฐ๋ ์์๋ฅผ ๊ฐ๋ ๊ฐ์ฒด์ ํํ๋ฅผ ์ํด Map
์ ์ฌ์ฉํ๋ค.
๋ณด์ผ๋ฌํ๋ ์ดํธ ์ ๋ณด๋ type, dirName, description ์ธ๊ฐ์ key๋ฅผ ๊ฐ๊ฒ ํ์๋ค.
๊ทธ๋ฆฌ๊ณ ๊ทธ ์ธ ์ ์ถ๋ ฅ๋ฉ์์ง๋, ๋ง๋ค์ด์ง default ๋๋ ํ ๋ฆฌ๋ช , ์๋ฌ๋ฉ์์ง ๋ฑ๋ ์ ์ด์ฃผ์๋ค.
export const selectItems: SelectItems = [
[
0,
{
type: 'boiler-plate',
dirName: 'ts-webpack',
description: '- TypeScript + Webpack',
},
],
[
1,
{
type: 'boiler-plate',
dirName: 'js-webpack',
description: '- JavaScript + Webpack',
},
],
[
2,
{
type: 'quit',
description: '- quit',
},
],
]
export const defaultDestDirName = 'my-app'
export const QUESTION_MESSAGE1 =
'์์ฑํ ํ๋ก์ ํธ๋ช
์ ์
๋ ฅํ์ธ์(default : my-app, ํ์ฌ์์น: . ) > '
export const QUESTION_MESSAGE2 = '\n\n์์ฑํ ๋ณด์ผ๋ฌ ํ๋ ์ดํธ๋ฅผ ์ ํํด์ฃผ์ธ์.\n'
export const SUCCESS_MESSAGE = '\n์์ฑ๋์์ต๋๋ค!\n'
export const FAILURE_MESSAGE = '\n์๋ฌด์ผ๋ ์ผ์ด๋์ง ์์์ต๋๋ค!\n'
export const QUIT_MESSAGE = '\n์ข
๋ฃ๋์์ต๋๋ค!\n'
export const EXIST_DEST_ERROR_MESSAGE = '\n๋๋ ํ ๋ฆฌ๊ฐ ์กด์ฌํฉ๋๋ค.\n"'
export const EXIST_TARGET_ERROR_MESSAGE =
'\nํ์ฌ ๋๋ ํ ๋ฆฌ์ ํ์ผ๋ค์ด ์กด์ฌํฉ๋๋ค\n'
ํ๋ก๊ทธ๋จ ๋ก์ง๊ณผ ๋ฐ์ดํฐ(์ ์์๋ค)์ ๋ถ๋ฆฌํ๋ ๊ฒ ์ข์ ๊ฒ ๊ฐ์์ ๋ฐ๋ก ๋ถ๋ฆฌํด์ฃผ์๋ค.
types.ts
์์๋ ๋ฐ์ดํฐ์ ๋ค์ด๊ฐ QuitSelectItem (quit ์ ๋ณด), BoilerSelectItem (๋ณด์ผ๋ฌํ๋ ์ดํธ ์ ๋ณด) ์ options์ ๋ค์ด๊ฐ ์ ๋ณด์ ๋ํ ํ์
์ ์ง์ ํด์ฃผ์๋ค.
interface QuitSelectItem {
type: 'quit'
description: string
}
interface BoilerSelectItem {
type: 'boiler-plate'
dirName: string
description: string
}
export type SelectItems = Array<[number, QuitSelectItem | BoilerSelectItem]>
export type SelectItemMap = Map<number, QuitSelectItem | BoilerSelectItem>
export interface PromptOptions {
defaultDestDirName?: string
QUESTION_MESSAGE1?: string
QUESTION_MESSAGE2?: string
SUCCESS_MESSAGE?: string
FAILURE_MESSAGE?: string
QUIT_MESSAGE?: string
EXIST_DEST_ERROR_MESSAGE?: string
EXIST_TARGET_ERROR_MESSAGE?: string
}
export type DefaultPromptOptions = Required<PromptOptions>
์ฌ์ค type์ด ๊ผญ ํ์ํ ํ๋ก๊ทธ๋จ์ ์๋์๋ ์์ง๋ง ํ์ ์คํฌ๋ฆฝํธ ๊ณต๋ถ๊ฒธ ๊ฒธ์ฌ๊ฒธ์ฌ ํ์ ๋ค๋ ์ง์ ํด์ฃผ์๋ค. ใ ใ ; ๊ทธ๋๋ ํ์ ์ ์ง์ ํด์ฃผ๋๊น ์คํ๋ ค ์์ ํ๋ฉด์๋ ์ด๋ค ์ธํฐํ์ด์ค์ธ์ง ํ์ธ์ด ๊ฐ๋ฅํด์ ๊ฐ๋ฐ๋ ๋น ๋ฅด๊ฒ ํ ์ ์๊ณ ์ข๋ ์์ ์ ์ผ ๊ฒ์ด๋ผ๋ ์๊ฐ์ ํ๋ก๊ทธ๋จ์ ์ ์ ๋ ๋ ์๊ฒผ๋ ๊ฒ ๊ฐ๋ค.
prompt.ts
์์๋ ์ค์ ์์ฑ๊ธฐ์์ ๋์ํ ๋ด์ฉ๋ค์ ๋ด์์ฃผ์๋ค.
๋จผ์ ์ธ์๋ก ๋์ด์จ options์ ํค๊ฐ์ด ์์ ๊ฒฝ์ฐ์๋ default๋ก ์ ์ฉํ๊ณ ํค๊ฐ์ด ์์ผ๋ฉด ํด๋น ํค๊ฐ์ ์ฌ์ฉํ๋๋ก ์ค์ ํด์ฃผ์๋ค.
const {
defaultDestDirName,
QUESTION_MESSAGE1,
QUESTION_MESSAGE2,
SUCCESS_MESSAGE,
FAILURE_MESSAGE,
QUIT_MESSAGE,
EXIST_DEST_ERROR_MESSAGE,
EXIST_TARGET_ERROR_MESSAGE,
} = { ...defaultOptions, ...options }
๊ทธ๋ฆฌ๊ณ getDestDirName
ํจ์์ getSelectItemType
ํจ์๋ฅผ ๋ง๋ค์ด ์ฌ์ฉ์๋ก๋ถํฐ ์
๋ ฅ์ ๋ฐ๊ณ
term.cyan(QUESTION_MESSAGE1)
const destDirName = await getDestDirName(defaultDestDirName)
const selectItemValues = selectItemMap.values()
const descriptions = Array.from(selectItemValues).map(
value => value.description
)
term.cyan(QUESTION_MESSAGE2)
const selectedItem = await getSelectItemType(descriptions)
๋ฐ์ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก createDirectory
ํจ์๋ฅผ ์คํํด ๋ณด์ผ๋ฌํ๋ ์ดํธ๋ฅผ ๋ง๋ค๋๋ก ํด์ฃผ์๋ค.
const selectedItemType = await createDirectory(
selectItemMap,
selectedItem.selectedIndex,
destDirName
)
๊ทธ๋ฆฌ๊ณ ์ ์ ์ฉ์ด ๋์๋์ง, ์๋ฌ๊ฐ ์์๋์ง์ ๋ํ ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์๋ค.
if (selectedItemType === 'exist-dest') {
term.red(EXIST_DEST_ERROR_MESSAGE)
createPrompt(selectItemMap, options)
}
if (selectedItemType === 'exist-target') {
term.red(EXIST_TARGET_ERROR_MESSAGE)
createPrompt(selectItemMap, options)
}
if (selectedItemType === 'boiler-plate') {
term.cyan(SUCCESS_MESSAGE)
process.exit(0)
}
if (selectedItemType === 'quit') {
term.red(FAILURE_MESSAGE)
process.exit(0)
}
if (!selectedItemType) {
term.white(QUIT_MESSAGE)
process.exit(0)
}
npm run dev
๋ก ์์ฑ๊ธฐ๊ฐ ์๋๋๋ก ์ ์๋ํ๋์ง ํ์ธํ๊ณ , version์ ์ฌ๋ ค์ค ๋ค, npm publish
๋ก ๋ฐฐํฌํด์ฃผ์๋ค.
{
"name": "@taenykim/generator",
"version": "0.0.3",
"scripts": {
"dev": "tsc && ./bin/run",
"build": "tsc"
}
}
์ฌ์ฉ์ npx @taenykim/generator
๋ก ์ฌ์ฉํ ์ ์๋ค.
npx๋
node_module
๋ก ํจํค์ง๊ฐ ์ ์ฅ๋์ง ์๊ณ , ํจํค์ง ์คํ ํ, ์ ๊ฑฐ๋๋ค.
๋ฏธ๋ฃจ๊ณ ๋ฏธ๋ฃจ๋ค๊ฐ ํ๋ฑ ๋ง๋ค์ด์ ๋ฐฐํฌ๊น์ง ํด๋ดค๋๋ฐ ์์ผ๋ก ์ ์ฉํ๊ฒ ๋ง์ด ์ธ ์ ์์ ๊ฒ ๊ฐ๋ค. nodeJS fs ๋ชจ๋๊ณผ npm๊ณผ ์ชผ๋๋ ์นํด์ง ๊ฒ ๊ฐ์์ ๋๋ฌด ์ข์๊ณ ์์ผ๋ก ๋กค์ ๋ณด์ผ๋ฌํ๋ ์ดํธ๋ ๋ฆฌ์กํธ ๋ฑ๋ฑ ๋ ์ถ๊ฐํด๋๊ฐ ๋ณด์์ผ๊ฒ ๋ค.
๊ทธ๋ฆฌ๊ณ ์๊ฐ์ด๋๋ค๋ฉด ๋ฆฌํฉํ ๋ง๋ ์ข ํ๊ณ ,, ๋ค์ํ ์๋ฌ์ฒ๋ฆฌ๋ ํด๋ด์ผ๊ฒ ๋ค.