프로그래밍 언어/NODE JS

간단한 콘솔 명령어 만들기(5)

· 코딩마이데이

이를 위해 CLI 프로그램이 사용자가 원하는 것을 단계별로 질문하게 하겠습니다. 먼저 템플릿을 생성할지를 묻고, 그 다음에 파일명과 경로를 물어봅니다. cli 명령어만 입력했을 때는 단계적으로 질문하도록 만들어보겠습니다.

 

template.js

#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const readline = require("readline");

let rl;
let type = process.argv[2];
let name = process.argv[3];
let directory = process.argv[4] || ".";

const htmlTemplate = `
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Template</title>
  </head>
  <body>
    <h1>Hello</h1>
    <p>CLI</p>
  </body>
</html>
`;

const routerTemplate = `
const express = require('express');
const router = express.Router();
 
router.get('/', (req, res, next) => {
   try {
     res.send('ok');
   } catch (error) {
     console.error(error);
     next(error);
   }
});
 
module.exports = router;
`;

const exist = (dir) => {
  // 폴더 존제 확인 함수
  try {
    fs.accessSync(
      dir,
      fs.constants.F_OK | fs.constants.R_OK | fs.constants.W_OK,
    );
    return true;
  } catch (e) {
    return false;
  }
};

const mkdirp = (dir) => {
  // 경로 생성 함수
  const dirname = path
    .relative(".", path.normalize(dir))
    .split(path.sep)
    .filter((p) => !!p);
  dirname.forEach((d, idx) => {
    const pathBuilder = dirname.slice(0, idx + 1).join(path.sep);
    if (!exist(pathBuilder)) {
      fs.mkdirSync(pathBuilder);
    }
  });
};

const makeTemplate = () => {
  // 템플릿 생성 함수
  mkdirp(directory);
  if (type === "html") {
    const pathToFile = path.join(directory, `${name}.html`);
    if (exist(pathToFile)) {
      console.error("이미 해당 파일이 존재합니다");
    } else {
      fs.writeFileSync(pathToFile, htmlTemplate);
      console.log(pathToFile, "생성 완료");
    }
  } else if (type === "express-router") {
    const pathToFile = path.join(directory, `${name}.js`);
    if (exist(pathToFile)) {
      console.error("이미 해당 파일이 존재합니다");
    } else {
      fs.writeFileSync(pathToFile, routerTemplate);
      console.log(pathToFile, "생성 완료");
    }
  } else {
    console.error("html 또는 express-router 둘 중 하나를 입력하세요.");
  }
};

// ❶
const dirAnswer = (answer) => {
  // 경로 설명
  directory = (answer && answer.trim()) || ".";
  rl.close();
  makeTemplate();
};

const nameAnswer = (answer) => {
  // 파일명 설명
  if (!answer || !answer.trim()) {
    console.clear();
    console.log("name을 반드시 입력하셔야 합니다.");
    return rl.question("파일명을 설정하세요.", nameAnswer);
  }
  name = answer;
  return rl.question(
    "저장할 경로를 설정하세요. (설정하지 않으면 현재 경로) :",
    dirAnswer,
  );
};

const typeAnswer = (answer) => {
  // 템플릿 종류 설정
  if (answer != "html" && answer !== "express-router") {
    console.clear();
    console.log("html 또는 express-router만 지원합니다.");
    return rl.question("어떤 템플릿이 필요하십니까? ", typeAnswer);
  }
  type = answer;
  return rl.question("파일명을 설정하세요. ", nameAnswer);
};

// ❷
const program = () => {
  if (!type || !name) {
    rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });
    console.clear();
    rl.question("어떤 템플릿이 필요하십니까? ", typeAnswer);
  } else {
    makeTemplate();
  }
};

program(); // 프로그램 실행부

 

다시 readline 모듈을 사용합니다. question 메서드가 비동기 방식으로 동작하므로 새로운 함수들을 만들었습니다.

 

dirAnswer, nameAnswer, TypeAnswer는 각각 디렉터리, 파일명, 템플릿 종류에 대해 사용자 입력을 받는 함수입니다. 코드의 순서가 역순으로 되어 있으므로 typeAnswer, nameAnswer,  dirAnswer가 실질적인 실행 순서라고 생각하면 됩니다.

 

명령어에서 템플릿 종류(type)나 파일명(name)을 입력하지 않았을 때 상호작용할 수 있는 입력 창의 띄우는 부분입니다.

 

콘솔에서 직접 이 프로그램을 사용해봅니다.

$ npx cli
어떤 템플릿이 필요하십니까? html
파일명을 설정하세요. test                                                                                               
저장할 경로를 설정하세요. (설정하지 않으면 현재 경로) :public
public\test.html 생성 완료