상세 컨텐츠

본문 제목

[Node.js] 14장 CLI 프로그램 만들기

21-22/21-22 Node.js

by Kimpeep 2022. 1. 24. 13:00

본문

728x90
CLI(Command Line Interface - 명령줄 인터페이스) : 콘솔 창을 통해 프로그램을 수행하는 환경
     ex) 리눅스의 셸, 브라우저의 콘솔, 명령 프롬프트
GUI(Graphic User Interface - 그래픽 사용자 인터페이스) : 그래픽을 통해 프로그램을 수행하는 환경
     ex) 윈도나 맥 운영체제, 웹 어플리케이션

 

1. 간단한 콘솔 명령어 만들기

콘솔 명령어 : 콘솔에서 입력하여 어떠한 동작을 수행하는 문장
                   ex) node, npm, nodemon

 1) node-cli 폴더 생성 후, package.json 생성

{
  "name": "node-cli",
  "version": "0.0.1",
  "description": "nodejs cli program",
  "main": "index.js",
  "author": "ZeroCho",
  "license": "ISC",
  "bin": {
    "cli": "./index.js"
  }
}

 

2) index.js 생성

#!/usr/bin/env node
console.log("Hello CLI");

 

3) 콘솔에서 현재 패키지 전역 설치

npm i -g

 

4) 콘솔에 cli 입력하여 실행

cli
  • 결과 화면

 

5) index.js 코드 수정

#!/usr/bin/env node
console.log("Hello CLI", process.argv);

 

6) 콘솔에 명령어 입력

cli one two three four
  • 결과 화면

 

[사용자로부터 입력 받기]

7) index.js 코드 수정

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

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

rl.question("예제가 재미있습니까? (y/n) ", (answer) => {
  if (answer === "y") {
    console.log("감사합니다!");
  } else if (answer === "n") {
    console.log("죄송합니다!");
  } else {
    console.log("y 또는 n만 입력하세요.");
  }
  rl.close();
});

 

8) 프로그램 실행

 

[코드 리팩토링]

9) index.js 코드 수정

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

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

console.clear();
const answerCallback = (answer) => {
  if (answer === "y") {
    console.log("감사합니다!");
    rl.close();
  } else if (answer === "n") {
    console.log("죄송합니다!");
    rl.close();
  } else {
    console.clear();
    console.log("y 또는 n만 입력하세요.");
    rl.question("예제가 재미있습니까? (y/n) ", answerCallback);
  }
};

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

 

10) 프로그램 실행

 

[html 또는 익스프레스 라우터 파일 템플릿 만들어주는 예제]

11) public, html 폴더 만들기

12) 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(); // 프로그램 실행부

 

13) package.json 코드 수정

{
	...
	"bin": {
      	"cli": "./template.js"
    	}
}

 

14) 전역 설치

npm i -g

 

15) 프로그램 실행

 

 

2. commander, inquirer 사용하기

commander 라이브러리 : npm에서 제공하는 CLI 프로그램을 위한 라이브러리
inquirer 패키지 : CLI 프로그램과 사용자 간의 상호작용을 돕는 패키지
chalk 패키지 : 콘솔 텍스트에 스타일을 추가하는 패키지

1) commander, inquirer, chalk 설치

npm i commander inquirer chalk

 

2) command.js 파일 생성 (commander 사용법 익히기)

[program 객체의 메서드]
- version(버전, 버전을 보여줄 옵션) : 프로그램의 버전을 설정
- usage(명령어 사용법) : 명령어의 사용법 설정
- command(명령어) : 명령어를 설정
- description(명령어 설명) : 명령어에 대한 설명을 설정
- alias(명령어 별칭) : 명령어의 별칭 설정
- option(옵션 명령어, 옵션에 대한 설명, 옵션 기본값) : 명령어에 대한 부가적인 옵션을 설정
- action(함수) : 명령어에 대한 실제 동작을 정의
- help : 설명서를 보여주는 옵션
- parse(process.argv) : program 객체의 마지막에 붙이는 메서드로, process.env를 인수로 받아서 명령어와 옵션을 파싱
#!/usr/bin/env node
const { program } = require("commander");

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) => {
    console.log(type, options.filename, options.directory);
  });

program.command("*", { noHelp: true }).action(() => {
  console.log("해당 명령어를 찾을 수 없습니다.");
  program.help();
});

program.parse(process.argv);

 

3) package.json 코드 수정

{
	...
	"bin": {
    	"cli": "./command.js"
  	},
}

 

4) 전역 설치

npm i -g

 

5) 프로그램 실행

 

6) command.js 파일 수정

#!/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 chart="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.green(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) => {
    makeTemplate(type, options.filename, options.directory);
  });

program
  .action((cmd, args) => {
    if (args) {
      console.log(chalk.bold.red('해당 명령어를 찾을 수 없습니다.'));
      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: 'confirm',
        name: 'confirm',
        message: '생성하시겠습니까?',
      }])
        .then((answers) => {
          if (answers.confirm) {
            makeTemplate(answers.type, answers.name, answers.directory);
            console.log(chalk.rgb(128, 128, 128)('터미널을 종료합니다.'));
          }
        });
    }
  })
  .parse(process.argv);
[inquirer 객체의 prompt 메서드 질문 객체 속성]
- type : 질문의 종류 / input(평범한 답변), list(다중택일), confirm(Yes 또는 No)
- name : 질문의 이름 -> 답변 객체가 속성명으로 질문의 이름을, 속성값으로 질문의 답을 가지게 됨
- message : 사용자에게 표시되는 문자열, 실제 질문을 적으면 됨
- choices : type이 checkbox, list인 경우 선택지를 넣는 곳, 배열로 넣으면 됨
- default : 답을 적지 않았을 경우 적용되는 기본값

 

7) 프로그램 실행

728x90

관련글 더보기