๐Ÿ“ฆ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ƒ์„ฑ๊ธฐ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž

์ž‘์—…ํ•˜๋Š” ํ™˜๊ฒฝ์ด ์–ด๋””๊ฑด ์ž์ฃผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ํ™˜๊ฒฝ์„ค์ •ํ•˜๋Š”๋ฐ ์˜ค๋ž˜๊ฑธ๋ฆฌ๋Š” ์ฝ”๋“œ๋“ค์ด ์กด์žฌํ•œ๋‹ค. ๋‚˜์˜ ๊ฒฝ์šฐ๋Š” ์›นํŒฉ์ด ๊ทธ๋žฌ๋‹ค.

ํ•˜๋‚˜์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•  ๋•Œ ํ•œ๋ฒˆ ์„ค์ •ํ•˜๋ฉด ๊ทธ์™ธ์—๋Š” ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š” ๋ถ€๋ถ„์ด์ง€๋งŒ, ๊ฐ„๋‹จํ•˜๊ฒŒ JS๋‚˜ TS ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์„œ ๋Œ๋ ค๋ณด๊ณ  ์‹ถ์€๋ฐ ํฌ๋กฌ๊ฐœ๋ฐœ์ž๋„๊ตฌ ์ฝ˜์†”์ฐฝ์—์„œ๋Š” ํ•œ๊ณ„๊ฐ€ ์žˆ์„ ๋•Œ๋งˆ๋‹ค ์˜ˆ์ „ ์›นํŒฉ ์„ค์ •์„ ํด๋ก ํ•ด์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ฑฐ๋‚˜ ์ฝ”๋“œ๋ฅผ ์ผ์ผํžˆ ๋ณต๋ถ™ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณคํ–ˆ๋‹ค.

์š”๋Ÿฐ ๋ฐฉ์‹์€ ๋„ˆ๋ฌด ๋น„ํšจ์œจ์ ์ด๊ณ , ์•ž์œผ๋กœ๋„ ์ด๋Ÿฐ ๊ฒฝ์šฐ๊ฐ€ ์ž์ฃผ ์ƒ๊ธธ ๊ฒƒ ๊ฐ™์•„์„œ ์ง์ ‘ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ƒ์„ฑ๊ธฐ๋ฅผ ๋งŒ๋“ค์–ด๋ณด๊ธฐ๋กœ ํ•˜์˜€๋‹ค!!

1. ํ”„๋กœ์ ํŠธ ์„ค์ •

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๋Š” ์ด๋ ‡๊ฒŒ ์„ค์ •ํ–ˆ๋‹ค.

boiler1

  • bin : ํ”„๋กœ์ ํŠธ ์„ค์น˜, ์‹คํ–‰์‹œ ์‹คํ–‰๋  ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋“ค์–ด๊ฐˆ ๋””๋ ‰ํ† ๋ฆฌ
  • dist : ๋นŒ๋“œ๋œ ํ”„๋กœ๊ทธ๋žจ ๋””๋ ‰ํ† ๋ฆฌ
  • lib : ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๋“ค
  • src : ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ƒ์„ฑ๊ธฐ ์†Œ์Šค

๋จผ์ € package.json์˜ bin ์—, ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•  ๋•Œ, ์‹คํ–‰๋  ์Šคํฌ๋ฆฝํŠธ ๊ฒฝ๋กœ๋ฅผ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

boiler2

๊ทธ๋ฆฌ๊ณ  run ์Šคํฌ๋ฆฝํŠธ์—์„œ๋Š” ์‹ค์ œ ์‹คํ–‰๋  ํŒŒ์ผ์„ ์‹คํ–‰์‹œ์ผœ์ฃผ๋„๋ก ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์—ˆ๋‹ค.

#!/usr/bin/env node

require("../dist")().run();

์ด ๋•Œ, run ์Šคํฌ๋ฆฝํŠธ๋Š” ์–ด๋– ํ•œ ์‚ฌ์šฉ์ž๋„ ๋ชจ๋‘ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ถŒํ•œ์„ ๋ณ€๊ฒฝํ•ด์ฃผ์—ˆ๋‹ค. (๊ถŒํ•œ ์„ค์ •์„ ํ•˜์ง€ ์•Š์„๊ฒฝ์šฐ, ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰์‹œ, permission ์˜ค๋ฅ˜๊ฐ€ ๋œฌ๋‹ค.)

chmod 755 run
chmod 755 run.cmd

๊ทธ๋ฆฌ๊ณ  typescript์™€ directory๋ฅผ ๋ณต์‚ฌํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ fs-extra ํŒจํ‚ค์ง€, ์˜ˆ์œ ํ„ฐ๋ฏธ๋„์ž…์ถœ๋ ฅ์„ ๋„์™€์ค„ terminal-kit ํŒจํ‚ค์ง€๋ฅผ ์ถ”๊ฐ€๋กœ ์„ค์น˜ํ•ด์ฃผ์—ˆ๋‹ค.

boiler3

2. index.ts

์—”ํŠธ๋ฆฌํฌ์ธํŠธ์ธ 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 ์„ ์‚ฌ์šฉํ–ˆ๋‹ค.

3. data.ts

๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ •๋ณด๋Š” type, dirName, description ์„ธ๊ฐœ์˜ key๋ฅผ ๊ฐ–๊ฒŒ ํ•˜์˜€๋‹ค.

  • type : ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ์ธ์ง€, quit์ธ์ง€์— ๋Œ€ํ•œ ์ •๋ณด (โ€˜boiler-plateโ€™ | โ€˜quitโ€™)
  • dirName : ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ์˜ ๊ฒฝ์šฐ ๋””๋ ‰ํ† ๋ฆฌ์ด๋ฆ„
  • description : ํ„ฐ๋ฏธ๋„์— ์ถœ๋ ฅ๋  ๋ฌธ์ž์—ด

๊ทธ๋ฆฌ๊ณ  ๊ทธ ์™ธ ์ž…์ถœ๋ ฅ๋ฉ”์‹œ์ง€๋‚˜, ๋งŒ๋“ค์–ด์งˆ 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'

ํ”„๋กœ๊ทธ๋žจ ๋กœ์ง๊ณผ ๋ฐ์ดํ„ฐ(์™€ ์ƒ์ˆ˜๋“ค)์€ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒŒ ์ข‹์„ ๊ฒƒ ๊ฐ™์•„์„œ ๋”ฐ๋กœ ๋ถ„๋ฆฌํ•ด์ฃผ์—ˆ๋‹ค.

4. types.ts

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์ด ๊ผญ ํ•„์š”ํ•œ ํ”„๋กœ๊ทธ๋žจ์€ ์•„๋‹์ˆ˜๋„ ์žˆ์ง€๋งŒ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๊ณต๋ถ€๊ฒธ ๊ฒธ์‚ฌ๊ฒธ์‚ฌ ํƒ€์ž…๋“ค๋„ ์ง€์ •ํ•ด์ฃผ์—ˆ๋‹ค. ใ…‹ใ…‹; ๊ทธ๋ž˜๋„ ํƒ€์ž…์„ ์ง€์ •ํ•ด์ฃผ๋‹ˆ๊นŒ ์˜คํžˆ๋ ค ์ž‘์—…ํ•˜๋ฉด์„œ๋„ ์–ด๋–ค ์ธํ„ฐํŽ˜์ด์Šค์ธ์ง€ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•ด์„œ ๊ฐœ๋ฐœ๋„ ๋น ๋ฅด๊ฒŒ ํ•  ์ˆ˜ ์žˆ๊ณ  ์ข€๋” ์•ˆ์ •์ ์ผ ๊ฒƒ์ด๋ผ๋Š” ์ƒ๊ฐ์— ํ”„๋กœ๊ทธ๋žจ์— ์• ์ •๋„ ๋” ์ƒ๊ฒผ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

5. prompt.ts

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)
}

6. ๋ฐฐํฌ

npm run dev ๋กœ ์ƒ์„ฑ๊ธฐ๊ฐ€ ์˜๋„๋Œ€๋กœ ์ž˜ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ , version์„ ์˜ฌ๋ ค์ค€ ๋’ค, npm publish ๋กœ ๋ฐฐํฌํ•ด์ฃผ์—ˆ๋‹ค.

{
  "name": "@taenykim/generator",
  "version": "0.0.3",
  "scripts": {
    "dev": "tsc && ./bin/run",
    "build": "tsc"
  }
}

7. ์‚ฌ์šฉ

์‚ฌ์šฉ์€ npx @taenykim/generator ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

npx๋Š” node_module ๋กœ ํŒจํ‚ค์ง€๊ฐ€ ์ €์žฅ๋˜์ง€ ์•Š๊ณ , ํŒจํ‚ค์ง€ ์‹คํ–‰ ํ›„, ์ œ๊ฑฐ๋œ๋‹ค.

8. ํ›„๊ธฐ

๋ฏธ๋ฃจ๊ณ ๋ฏธ๋ฃจ๋‹ค๊ฐ€ ํ›„๋”ฑ ๋งŒ๋“ค์–ด์„œ ๋ฐฐํฌ๊นŒ์ง€ ํ•ด๋ดค๋Š”๋ฐ ์•ž์œผ๋กœ ์œ ์šฉํ•˜๊ฒŒ ๋งŽ์ด ์“ธ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค. nodeJS fs ๋ชจ๋“ˆ๊ณผ npm๊ณผ ์ชผ๋”๋” ์นœํ•ด์ง„ ๊ฒƒ ๊ฐ™์•„์„œ ๋„ˆ๋ฌด ์ข‹์•˜๊ณ  ์•ž์œผ๋กœ ๋กค์—… ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๋‚˜ ๋ฆฌ์•กํŠธ ๋“ฑ๋“ฑ ๋” ์ถ”๊ฐ€ํ•ด๋‚˜๊ฐ€ ๋ณด์•„์•ผ๊ฒ ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์‹œ๊ฐ„์ด๋œ๋‹ค๋ฉด ๋ฆฌํŒฉํ† ๋ง๋„ ์ข€ ํ•˜๊ณ ,, ๋‹ค์–‘ํ•œ ์—๋Ÿฌ์ฒ˜๋ฆฌ๋„ ํ•ด๋ด์•ผ๊ฒ ๋‹ค.


Written by@taenyKim
๋ฐฐ์šฐ๋ฉฐ ์„ฑ์žฅํ•˜๊ณ  ๊ธฐ๋กํ•˜๊ธฐ #FE #UI #๊ฐœ๋ฐœ #life

GitHubFacebook