[Node.js 교과서] 섹션 14- CLI 프로그램 만들기

2024. 12. 15. 14:07·Node.js

간단한 콘솔 명령어 만들기


  • package.json
{
  "name": "node-cli",
  "version": "0.0.1",
  "description": "nodejs cli program",
  "main": "index.js",
  ...
  "bin": {
    "cli": "./index.js"
  },

}
  • index.js
#!/usr/bin/env node
console.log('Hello CLI', process.argv);

`#!/usr/bin/env node`는 맥과 리눅스에서 /usr/bin/env에 등록된 node 명령어로 이 파일을 실행하라는 의미다.

process.argv는 실행한 명령어의 인자들을 볼 수 있다.

  • 해당 모듈 전역 설치
npx i -g
  • 모듈 실행
npx cli

CLI로 입력받기

  • index.js
#!/usr/bin/env node
// console.log("Hello CLI", process.argv);
const readline = require("readline");

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

console.clear();

const answerCallback = (answer) => {
  if (answer === "y") {
    console.log("감사링~");
  } else if (answer === "n") {
    console.log("아쉬운거죵");
  } else {
    console.clear();
    console.log("y 또는 n만 입력하세요.");
    rl.question("예제가 재밌습니까? (y/n)", answerCallback);
  }
  rl.close();
};

rl.question("예제가 재밌습니까? (y/n)", answerCallback);

`readline`은 노드의 내장 모듈 input과 output을 콘솔 기본 입출력 (stdin, stdout)으로 설정한다.

`console.clear` 명령어는 콘솔창을 내용을 지운다.

`rl.question`의 명령어로 출력와 입력을 받는다.

  • 템플릿을 만드는 template.js
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const readline = require("readline");

// npx cli html main .
let li;
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?.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) {
    console.error("사용 방법: cli html|express-router 파일명 [생성 경로]");
    rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });
    console.clear();
    rl.question("어떤 템플릿이 필요하십니까?", typeAnswer);
  } else {
    makeTemplate();
  }
};

program();

Commander, Inquirer 모듈로 CLI 프로그램 만들기


  • command.js <- commander 적용
#!/usr/bin/env node
const { program } = require("commander");
const fs = require("fs");
const path = require("path");

...

program.version("0.0.1", "-v, --version").name("cli");

program
  .command("template <type>")
  .usage("<type> --filename [filename] --path [path]")
  .description("템플릿을 생성합니다.")
  .alias("tmpl")
  .option("-f --filename [filename]", "파일명을 입력하세요", "index")
  .option("-d --directory [path]", "생성 경로를 입력하세요", ".")
  .action((type, options, command) => {
    console.log(type, options.filename, options.directory);
    makeTemplate(type, options.filename, options.directory);
  });

program.action((options, command) => {
  if (command.args.length !== 0) {
    console.log("해당 명령어를 찾을 수 없습니다.");
    program.help();
  } else {
  ...
  }
});

program.parse(process.argv);

`version`: 프로그램의  버전을 설정한다.(-v --version)

`name`: 명령어의 이름을 등록한다.

`usage`: 명령어의 사용법을 적는다(-h, --help)

`command`: 명령어를 설정한다. <>는 필수라는 의미이고 []는 선택사항이다.

`description`: 명령어에 대한 설명을 설정한다.

`alias`: 명령어의 별칭을 설정한다.

`option`: 명령어에 대한 부가적인 옵션을 설정한다. 첫번째 인수는 명령어 옵션이고, 두번째 인수는 옵션에 대한 설명, 세번째 인수는 기본 값이다.

`action`: 명령어의 실제 동작을 정의하는 메서드다. 필수 옵션을 제외한 부가전인 옵션은 options 객체에 들어있다. command 객체에는 명령어에 대한 내용이 담겨있다.

`help`: 해당 모듈의 설명서를 보여주는 매서드다.

`parse`: program 객체의 마지막에 붙이는 매서드다. 인수를 받아 파싱한다.

  • commander.js <- inquirer 적용
#!/usr/bin/env node
const { program } = require("commander");
const fs = require("fs");
const path = require("path");
const inquirer = require("inquirer");
...
program.action((options, command) => {
  if (command.args.length !== 0) {
    console.log("해당 명령어를 찾을 수 없습니다.");
    program.help();
  } else {
    inquirer
      .prompt([
        {
          type: "list",
          name: "type",
          message: "템플릿 종류를 선택하세요.",
          choices: ["html", "express-router"],
        },
        {
          type: "input",
          name: "name",
          message: "파일의 이름을 입력하세요.",
          default: "index",
        },
        {
          type: "input",
          name: "directory",
          message: "파일이 위치할 폴더의 경로를 입력하세요,",
          default: ".",
        },
        {
          type: "confrirm",
          name: "confirm",
          message: "생성하시겠습니까?",
        },
      ])
      .then((answers) => {
        if (answers.confirm) {
          makeTemplate(answers.type, answers.name, answers.directory);
          console.log(chalk.hex("#123fff")("터미널을 종료합니다."));
        }
      });
  }
});

program.parse(process.argv);

`type`: 질문의 종류

`name`: 질문의 이름

`message`: 사용자에게 보이는 문자열

`choices`: 선택지

`default`: 기본값

  • commander.js <- chalk 적용
#!/usr/bin/env node
const { program } = require("commander");
const fs = require("fs");
const path = require("path");
const inquirer = require("inquirer");
const chalk = require("chalk");
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 = (type, name, directory) => {
  mkdirp(directory);
  if (type === "html") {
    const pathToFile = path.join(directory, `${name}.html`);
    if (exist(pathToFile)) {
      console.error(chalk.bold.red("이미 해당 파일이 존재합니다."));
    } else {
      fs.writeFileSync(pathToFile, htmlTemplate);
      console.log(chalk.bold.red(pathToFile, " 생성 완료"));
    }
  } else if (type === "express-router") {
    const pathToFile = path.join(directory, `${name}.js`);
    if (exist(pathToFile)) {
      console.error(chalk.bold.red("이미 해당 파일이 존재합니다."));
    } else {
      fs.writeFileSync(pathToFile, routerTemplate);
      console.log(chalk.green(pathToFile, " 생성 완료"));
    }
  } else {
    console.error(
      chalk.bold.red,
      "html 또는 express-router 둘 중 하나를 입력하세요."
    );
  }
};

program.version("0.0.1", "-v, --version").name("cli");

program
  .command("template <type>")
  .usage("<type> --filename [filename] --path [path]")
  .description("템플릿을 생성합니다.")
  .alias("tmpl")
  .option("-f --filename [filename]", "파일명을 입력하세요", "index")
  .option("-d --directory [path]", "생성 경로를 입력하세요", ".")
  .action((type, options, command) => {
    console.log(type, options.filename, options.directory);
    makeTemplate(type, options.filename, options.directory);
  });

program.action((options, command) => {
  if (command.args.length !== 0) {
    console.log("해당 명령어를 찾을 수 없습니다.");
    program.help();
  } else {
    inquirer
      .prompt([
        {
          type: "list",
          name: "type",
          message: "템플릿 종류를 선택하세요.",
          choices: ["html", "express-router"],
        },
        {
          type: "input",
          name: "name",
          message: "파일의 이름을 입력하세요.",
          default: "index",
        },
        {
          type: "input",
          name: "directory",
          message: "파일이 위치할 폴더의 경로를 입력하세요,",
          default: ".",
        },
        {
          type: "confrirm",
          name: "confirm",
          message: "생성하시겠습니까?",
        },
      ])
      .then((answers) => {
        if (answers.confirm) {
          makeTemplate(answers.type, answers.name, answers.directory);
          console.log(chalk.hex("#123fff")("터미널을 종료합니다."));
        }
      });
  }
});

program.parse(process.argv);

참고


 

 

Node.js 교과서 : 네이버 도서

네이버 도서 상세정보를 제공합니다.

search.shopping.naver.com

 

 

[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지 강의 | 제로초(조현영) - 인프런

제로초(조현영) | 노드가 무엇인지부터, 자바스크립트 최신 문법, 노드의 API, npm, 모듈 시스템, 데이터베이스, 테스팅 등을 배우고 5가지 실전 예제로 프로젝트를 만들어 나갑니다. 클라우드에 서

www.inflearn.com

'Node.js' 카테고리의 다른 글

[Node.js 교과서] 섹션 16 - 서버리스 노드 개발  (0) 2025.01.11
[Node.js 교과서]섹션 15 - AWS에 배포해보기  (0) 2025.01.07
[Node.js 교과서] 섹션 13- 실시간 경매 시스템 만들기  (0) 2024.12.13
[Node.js 교과서] 섹션 12 - 웹 소켓으로 실시간 데이터 전송하기  (0) 2024.12.08
[Node.js 교과서] 섹션 11 - 노드 서비스 테스트 하기  (3) 2024.12.05
'Node.js' 카테고리의 다른 글
  • [Node.js 교과서] 섹션 16 - 서버리스 노드 개발
  • [Node.js 교과서]섹션 15 - AWS에 배포해보기
  • [Node.js 교과서] 섹션 13- 실시간 경매 시스템 만들기
  • [Node.js 교과서] 섹션 12 - 웹 소켓으로 실시간 데이터 전송하기
bearn_soo
bearn_soo
추상적으로 받아들이되 구체적으로 이해하라
  • bearn_soo
    초밥구이
    bearn_soo
  • 전체
    오늘
    어제
    • 분류 전체보기 (78)
      • Javascript (16)
      • Node.js (18)
      • 알고리즘 (8)
      • 네트워크 (2)
      • 데이터베이스 (10)
      • 운영체제 (11)
      • 자료구조 (6)
      • 공부기록 (7)
  • 인기 글

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
bearn_soo
[Node.js 교과서] 섹션 14- CLI 프로그램 만들기
상단으로

티스토리툴바