on
CLI 프로그램 만들기
CLI 프로그램 만들기
간단한 콘솔 명령어 만들기
CLI
CLI(Command Line Interface) 기반 노드 프로그램을 제작해보기
콘솔 창을 통해서 프로그램을 수행하는 환경
반대 개념으로는 GUI(그래픽 유저 인터페이스)가 있음
리눅스의 셸이나 브라우저 콘솔, 명령 프롬프트 등이 대표적인 CLI 방식 소프트웨어
개발자에게는 CLI 툴이 더 효율적일 때가 많음
콘솔 명령어
노드 파일을 실행할 때 node [ 파일명 ] 명령어를 콘솔에 입력함
node나 npm. nodemon처럼 콘솔에서 입력하여 어떠한 동작을 수행하는 명령어를 콘솔 명령어라고 부름.
node와 npm 명령어는 노드를 설치해야만 사용할 수 있음
nodemon, rimraf같은 명령어는 npm i –g 옵션으로 설치하면 명령어로 사용 가능
패키지 명과 콘솔 명령어를 다르게 만들 수도 있음(sequelize-cli는 sequelize 명령어 사용)
이러한 명령어를 만드는 게 이 장의 목표
프로젝트 시작하기
node-cli 폴더 안에 package.json 과 index.js 생성
package.json // index.js #!usr/bin/env node console.log('Hello CLI')
index.js 첫 줄의 주석에 주목(윈도에서는 의미 없음)
리눅스나 맥 같은 유닉스 기반 운영체제에서는 /usr/bin/env에 등록된 node 명령어로 이 파일을 실행하라는 뜻
CLI 프로그램으로 만들기
package.json 에 다음 줄을 추가
package.json
bin 속성이 콘솔 명령어와 해당 명령어 호출 시 실행 파일을 설정하는 객체
콘솔 명령어는 cli, 실행 파일은 index.js
콘솔 명령어 사용하기
npm i -g 로 설치 후 cli 로 실행
보통 전역 설치할 때는 패키지 명을 입력하지만 현재 패키지를 전역 설치할 때는 적지 않음
콘솔
리눅스나 맥에서는 명령어 앞에 sudo를 붙여야 할 수도 있음
전역 설치한 것이기 때문에 node_modules가 생기지 않음
명령어에 옵션 붙이기
process.argv 로 명령어에 어떤 옵션이 주어졌는지 확인 가능 ( 배열로 표시 )
// index.js #!usr/bin/env node console.log('Hello CLI', process.argv);
코드가 바뀔 때마다 전역 설치할 필요는 없음
package.json 내용이 바뀌면 다시 전역 설치해야 함
배열의 첫 요소는 노드의 경로, 두 번째 요소는 cli 명령어의 경로, 나머지는 옵션
사용자로부터 입력 받기
노드 내장 모듈 readline 사용
createInterface 메서드로 rl 객체를 만듦
process.stdin, process.stdout은 각각 콘솔을 통해 입력받고 출력한다는 의미
question 메서드로 질문을 표시하고 답변이 들어오면 콜백 함수가 실행됨
답변은 answer 매개변수에 담김
콘솔 내용 지우기
console.clear 로 콘솔 내용 지우기
프로그램 시작 시와, 잘못된 답변 후에 콘솔 지움\
// index.js 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);
템플릿을 만들어주는 명령어 만들기
// 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 = ` Template Hello CLI `; 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(); // 프로그램 실행부
위 코드는 밑의 단계적 명령어까지 합친 것( 바로 밑의 설명과 단계적 명령어 설명과 같이 이해 )
디렉토리가 존재하는지 확인하는 exist 함수와 디렉토리를 생성하는 mkdirp 함수를 만듦
program 이라는 함수는 template.js 의 실행부 , makeTemplate 은 옵션을 읽어서 알맞은 템플릿을 작성해주는 함수
옵션에 따라 다른 동작을 하도록 분기 처리
package.json 의 명령어를 바꿔주고 전역 재설치
단계적 명령어 만들기
옵 션을 입력하지 않는 경우 readline 모듈로 단계적으로 질문을 해 옵션을 외울 필요가 없도록 함
옵션을 입력하는 경우 예전과 마찬가지로 동작
from http://jhg3410.tistory.com/35 by ccl(A) rewrite - 2021-12-05 03:00:38