14.1 간단한 콘솔 명령어 만들기
1. CLI
⇒ CLI(Command Line Interface) 기반 노드 프로그램을 제작해보기
- 콘솔 창을 통해 프로그램을 수행하는 환경을 구현하며 반대 개념은 GUI(그래픽 유저 인터페이스)이다.
- 리눅스의 셸이나 브라우저 콘솔, 명령 프롬프트 등이 대표적인 CLI 방식 소프트웨어이다.
2. 콘솔 명령어
⇒ 노드 파일을 실행할 때 node[파일명] 명령어를 콘솔에 입력한다.
- node나 npm, nodemon처럼 콘솔에서 입력해 어떠한 동작을 수행하는 명령어를 콘솔 명령어라고 한다.
- nodemon, rimraf같인 명렁어는 npm i -g 옵션으로 설치하면 명령어로 사용이 가능하다.
- 패키지 명과 콘솔 명령어를 다르게 만들 수도 있다.(sequelize-cli는 sequelize 명령어 사용)
3. 프로젝트 시작하기
⇒ node-cli 폴더 안 packate.json과 index.js 파일을 생성한다.
// package.json
{
"name": "cli",
"version": "0.0.1",
"description": "nodejs cli program",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"bin": {
"cli": "./index.js"
},
}
#!/usr/bin/env node
// cli linux나 max에서 node로 index.js를 실행해라라는 문법
//Hello CLI [
// 'C:\\Program Files\\nodejs\\node.exe', node 명령어의 경로
// 'C:\\Users\\user\\AppData\\Local\\npm-cache\\_npx\\9a59ee6893e4192e\\node_modules\\cli\\index.js' 실제 파일의 경로
// ]
console.log('Hello CLI', process.argv);
6. 명령어에 옵션 붙이기
⇒ process.argv로 명령어에 어떤 옵션이 주어졌는지 확인이 가능하다.(배열로 표시)
- 코드가 바뀔때마다 전역 설치할 필요는 없으나 package.json 내용이 바뀌면 다시 전역 설치해야 한다.
- 배열의 첫요소는 노드의 경로, 두 번째 요소는 cli명령어의 경로, 나머지는 옵션
C:\nodejs\cli>cli one two three four
Hello CLI [
'C:\\Program Files\\nodejs\\node.exe',
'C:\\Users\\user\\AppData\\Roaming\\npm\\node_modules\\cli\\index.js',
'one',
'two',
'three',
'four'
]
7. 사용자로부터 입력 받기
⇒ 노드 내장 모듈 readline을 사용한다.
- createInterface 메서드로 rl 객체를 선언하여 입출력을 설정한다.(process.stdin, process.stdout : 콘솔기본입력)
- question 메서드로 질문을 표시하고 답변을 받아 콜백 함수를 실행한다.
- 답변은 answer 매개변수에 담기며 마지막에 readline을 close처리해야 한다.
#!/usr/bin/env node
const readline = require('readline');
// 터미널의 input과 output을 그대로 쓰겠다는 의미
const rl = readline.createInterface({
input : process.stdin,
output : process.stdout,
});
console.clear();
const answerCallback = (answer) => {
if(answer.toUpperCase() === 'Y') {
console.log('감사합니다.');
rl.close();
} else if(answer.toUpperCase() === 'N') {
console.log('죄송합니다.');
rl.close();
} else {
console.log('Y 또는 N만 입력하세요.');
rl.question('예제가 재미있습니까? (y/n)', answerCallback);
}
};
rl.question('예제가 재미있습니까? (y/n)', answerCallback);
$ npx cli
예제가 재미있습니까? (y/n)y
감사합니다.
10. 템플릿을 만들어주는 명령어 만들기
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const readline = require('readline');
// npx cli html main .
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(error) {
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.log('html 또는 express-router 둘 중 하나를 입력하세요.');
}
};
const directoryAnswer = (answer) => {
directory = answer?.trim() || '.';
rl.close();
makeTemplate();
}
const nameAnswer = (answer) => {
if(answer === '') {
console.clear();
console.log('파일명은 반드시 입력해야 합니다.');
return rl.question('어떤 파일명으로 하실건가요?', nameAnswer);
} else {
name = answer;
return rl.question('파일의 디렉토리 경로를 입력하세요.', directoryAnswer);
}
}
const typeAnswer = (answer) => {
if(answer !== 'html' && answer !== 'express-router') {
console.clear();
console.log('html 또는 express-router 둘 중 하나를 입력하세요.');
return rl.question('어떤 템플릿이 필요 하십니까?', typeAnswer);
} else {
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();
14.2 Commander, Inquirer 사용하기
1. 패키지로 쉽게 CLI 프로그램 만들기
⇒ npm에는 CLI 프로그램을 위한 라이브러리가 많이 준비되어 있다.
- commander(CLI)와 inquirer(사용자와 상호작용), chalk(콘솔에 컬러)를 사용해서 예제생성
2. commander 사용하기
⇒ command.js 파일 작성
- version : 프로그램의 버전 설정(--version 또는 -v로 확인가능)
- usage : 프로그램 사용 방법을 기입한다(--help 또는 -h로 확인)
- command : 명령어를 등록한다.(template <type> 등)
- description : 명령어에 대한 설명을 설정할 수 있다.
- option : 명령어에 대한 옵션을 등록한다.(--옵션 [값] 또는 --옵션 <값> 형식, 두번째 인자는 설명, 세 번째 인자는 기본값)
- Action : 명령어가 실행될 때 수행할 동작등록
- parse : process.argv를 파싱하여 옵션을 등록한다.
6. inquirer 사용하기
⇒ 옵션들을 외워 사용하는 것이 불편하기 때문에 상호작용을 위한 inquirer 패키지를 설치하여 사용한다.
#!/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(error) {
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');
// <type> : 필수, [filename] : 선택
program
.command('template <type>')
.alias('tmpl')
.usage('<type> --filename [filename] --path [path]')
.description('템플릿을 생성합니다.')
.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.error(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.hex('#123fff')('터미널을 종료합니다.'));
}
});
}
})
program.parse(process.argv);
'BackEnd > Node' 카테고리의 다른 글
[노드교과서] 섹션 15. 16장 AWS 서버리스(S3+Lambda) 사용하기 (0) | 2024.02.05 |
---|---|
[노드교과서] 섹션 14. 15장 AWS에 배포해보기 (2) | 2024.02.02 |
[노드교과서] 섹션 12. 13장 실시간 경매 시스템 만들기(서버센트이벤트, 스케줄링) (2) | 2024.01.31 |
[노드교과서] 섹션 11. 12장 실시간 GIF 채팅방 만들기(웹소켓, Socket.IO) (0) | 2024.01.28 |
[노드교과서] 섹션 10. 11장 테스트 해보기(단위, 통합, 부하) (0) | 2024.01.25 |