인턴으로 있는 기간동안 많은 것들을 경험할 수 있도록 도와주셔서 늘 감사한 마음으로..
이번에는 git action을 사용해서 CI/CD 를 구축해볼 수 있는 시간을 갖게 되었다.
해당 velog 글을 참고하였다!
깃허브에 repo 하나 생성 후,
빈 폴더 하나를 만들고, 터미널창에 npm init -y을 입력하여 "package.json"생성
package.json에 아래처럼 작성(일단 script 부분은 비워두기)
{
"name": "ci-cd-test",
"version": "1.0.0",
"description": "ci-cd-test",
"main": "index.js",
"scripts": {},
"repository": {
"type": "git",
"url": "git+https://github.com/kimLrLr/node-ci-cd-test.git"
},
"author": "kimLrLr",
"license": "MIT",
"bugs": {
"url": "https://github.com/kimLrLr/node-ci-cd-test/issues"
},
"homepage": "https://github.com/kimLrLr/node-ci-cd-test#readme"
}

실제 기능을 담은 js파일, 이를 테스트할 파일
이렇게 두 가지의 파일을 분리시켜주는데,
test할 파일을 정리하기 위한 폴더 구조에는 크게 2가지가 있다.
보통 React에서 해당 방식을 많이 사용한다고 하고,
해당 경우에는 아래와 같은 폴더 구조를 갖는다.
├── src
│ ├── index.js
│ └── index.test.js
해당 경우에는 아래와 같은 폴더 구조를 갖는다.
├── src
│ └── index.js
├── test
│ ├── integration
│ │ └── index.test.js
│ └── unit
src 폴더에는 실제 기능을 담은 js파일을 두고,
test 폴더에는 integration 폴더와 unit 폴더를 둔다.
여기서 integration 폴더에는 통합 테스트를 진행하는 테스트 코드 파일을,
unit 폴더에는 단위 테스트를 진행하는 테스트 코드 파일을 넣을 수 있게 구조를 설정할 수 있다.
├── src
│ ├── functions
│ │ ├── add.js
│ │ └── sub.js
│ └── index.js
├── test
│ ├── integration
│ │ └── index.test.js
│ ├── unit
│ │ ├── add.test.js
│ │ └── sub.test.js

아래는 add.js로, a와 b의 값을 더해 return해주는 코드
export const add = (a, b) => {
return a + b;
};
아래는 sub.js로, a와 b의 값을 빼서 return해주는 코드
export const sub = (a, b) => {
return a - b;
};
아래는 index.js로, a와 b를 더한 값 + a와 b를 뺀 값을 return해주는 코드
export const main = (a, b) => {
return add(a, b) + sub(a, b);
};
코드 스타일을 체크하기 위한 라이브러리인 ESLint를 프로젝트에 적용해보자.
npm install eslint --save-dev
.gitignore를 하나 만들어 "node_modules"폴더가 git에 올라가지 않도록 설정node_modules/

npm add eslint@8.0.1
=> eslint 9버전부터 eslintrc가 아니라 완전 바뀌었길래 8버전 사용을 위해 입력
npx eslint --init

설정은 해당 velog를 참고하며 진행하였다.
.eslintrc.json파일이 생성되면 아래처럼 작성되어있다.
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 15,
"sourceType": "module"
},
"rules": {
}
}

.eslintrc.json에서 몇 가지 셋팅을 해준 코드!{
"env": {
"browser": true,
"es2021": true
},
"extends": "standard",
"overrides": [],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
"semi": [2, "always"],
"no-unused-vars": "warn"
}
}
변경사항은 standard를 extend해주고,
no-unused-vars로 사용되지 않는 변수들이 존재하는 경우에 error처리가 아닌 warn으로 표시되도록 설정..!
앞에서 ESLint로 코드 스타일을 체크해줬다면,
Prettier로 코드 포맷팅 설정을 해주자..!
프리티어는 기본적으로 프로젝트의 root에 있는 .prettierrc파일에 적힌 룰에 의해 동작한다.
.prettierrc파일을 생성해준 뒤, 아래처럼 설정해주자
{
"bracketSpacing": false,
"jsxBracketSameLine": true,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "avoid",
"endOfLine": "auto",
"tabWidth": 2
}
npm install prettier --save-dev
npm install eslint-config-prettier --save-dev
eslintrc.json 파일을 아래와 같이 수정!{
"env": {
"browser": true,
"es2021": true
},
"extends": ["standard", "prettier"],
"overrides": [],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
"semi": [2, "always"],
"no-unused-vars": "warn"
}
}

이까지 큰 오류없이 잘 되었으면 자동화 프로세스를 설정할 수 있다!
.github/workflows디렉토리 생성 후,ci.yml파일을 생성하여 넣어줌
아래는 ci.yml파일의 기본 포맷
name: Node.js CI
# 구독할 이벤트
on:
push:
branches: [main]
pull_request:
branches: [main]
# jobs 단위로 개별 서버(정확히는 Docker 컨테이너 단위라고 한다.)에서 작업이 수행된다.
# 각 작업은 병렬로 실행 된다고 하는데, needs: build와 같이 표시해서 기다릴 수도 있다.
jobs:
build:
# Ubuntu, Windows, MacOS를 지원한다.
runs-on: ubuntu-latest
# node-version 과 같이 배열로 돼있으면, 해당 원소를 순회하면서 작업이 반복해서 실행된다.
# 응용해서 runs-on에 여러 OS에서 돌릴 수도 있다.
strategy:
matrix:
node-version: [14.x] # 템플릿 기본값: [10.x, 12.x, 14.x]
# uses 개념은 다른 사람이 작성한 내용을 실행하는 개념이다.
# actions/checkout: GitHub의 마지막 커밋으로 Checkout 한다.
# actions/setup-node: Node.js를 설치한다.
# run 개념은 명령어를 실행한다. 셸 스크립트와 동일하다.
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
# npm ci는 npm install과 같은 기능을 수행한다.
- run: npm ci
# --if-present 옵션은 npm 스크립트가 존재할 때만 실행시키라는 의미이다.
# 만약 build 스크립트가 없는 경우, 오류 없이 지나간다.
- run: npm run build --if-present
- run: npm test
package.json을 아래처럼 수정해준다.{
"name": "ci-cd-test",
"version": "1.0.0",
"description": "ci-cd-test",
"main": "index.js",
"scripts": {
"lint": "./node_modules/.bin/eslint ."
},
"repository": {
"type": "git",
"url": "git+https://github.com/kimLrLr/node-ci-cd-test.git"
},
"author": "kimLrLr",
"license": "MIT",
"bugs": {
"url": "https://github.com/kimLrLr/node-ci-cd-test/issues"
},
"homepage": "https://github.com/kimLrLr/node-ci-cd-test#readme",
"devDependencies": {
"@eslint/js": "^9.10.0",
"eslint": "^9.10.0",
"eslint-config-prettier": "^9.1.0",
"globals": "^15.9.0",
"prettier": "^3.3.3"
}
}

ci.yml에 아래 명령어를 steps에 추가- run: npm run lint
아래는 현재 yml파일의 전체 코드
name: Node.js CI
# 구독할 이벤트
on:
push:
branches: [main]
pull_request:
branches: [main]
# jobs 단위로 개별 서버(정확히는 Docker 컨테이너 단위라고 한다.)에서 작업이 수행된다.
# 각 작업은 병렬로 실행 된다고 하는데, needs: build와 같이 표시해서 기다릴 수도 있다.
jobs:
build:
# Ubuntu, Windows, MacOS를 지원한다.
runs-on: ubuntu-latest
# node-version 과 같이 배열로 돼있으면, 해당 원소를 순회하면서 작업이 반복해서 실행된다.
# 응용해서 runs-on에 여러 OS에서 돌릴 수도 있다.
strategy:
matrix:
node-version: [14.x] # 템플릿 기본값: [10.x, 12.x, 14.x]
# uses 개념은 다른 사람이 작성한 내용을 실행하는 개념이다.
# actions/checkout: GitHub의 마지막 커밋으로 Checkout 한다.
# actions/setup-node: Node.js를 설치한다.
# run 개념은 명령어를 실행한다. 셸 스크립트와 동일하다.
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
# npm ci는 npm install과 같은 기능을 수행한다.
- run: npm ci
# --if-present 옵션은 npm 스크립트가 존재할 때만 실행시키라는 의미이다.
# 만약 build 스크립트가 없는 경우, 오류 없이 지나간다.
- run: npm run build --if-present
- run: npm run lint
- run: npm test
추가된 곳은 아래 사진 참고하면 쉽게 찾을 수 있다!

npm install jest --save-dev
package.json의 script에 아래 코드 추가!"test": "jest"

npm i --save-dev eslint-plugin-jest
eslintrc.yml파일을 생성하여 셋팅 코드 작성env:
jest: true # Jest 글로벌
plugins:
- jest # Jest 테스트를 위해 플러그인이 필요하다.
rules:
# Jest Eslint 옵션은 0,1,2 (off, warn, error) 만 옵션으로 사용 가능하다.
jest/no-disabled-tests:
- warn
jest/no-focused-tests:
- error
jest/no-identical-title:
- error
jest/prefer-to-have-length:
- warn
jest/valid-expect:
- error

드디어 test code를 작성해볼 수 있다!
아래처럼 test code를 작성해보자.
아래는 add.test.js의 코드
import {add} from '../../src/func/add';
test('add 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
아래는 sub.test.js의 코드
import {sub} from '../../src/func/sub';
test('sub 2 - 1 to equal 1', () => {
expect(sub(2, 1)).toBe(1);
});
아래는 index.test.js의 코드
import {main} from '../../src/index';
test('main 1 , 2 to equal 2', () => {
expect(main(1, 2)).toBe(2);
});
GitHub의 Branch Protection Rule이라는 기능을 이용해 build 전에 merge를 할 수 없도록 설정해주자.
레포지토리 > Setting탭 > Branches탭 > Branch protection rules탭 > Add classic branch protection rule 버튼 클릭 아래와 같이 설정

설정 끝났으면 아래에 "Create" 클릭하여 만들어주자!

index.js에서 add와 sub를 import해준다.
위 함수의 경우 jest에서 사용되는 명령어이기 때문에 몇 가지 설정을 해줘야 한다.
eslintrc.json에서 env 설정- babel 설정
아래에 하나씩 작성~!
eslintrc.json에서 env 설정eslintrc.json에서 env 설정에 jest 설정을 추가해줘야
jest 명령어를 ESLint와 충돌없이 사용할 수 있다고 한다.
"env"에 아래 코드 추가
"jest": true

jest가 ES6를 지원하지 않기 때문에 ES6의 문법인 import문을 사용하면 jest에서 파싱이 불가하다.
따라서 babel을 사용해 jest가 내부적으로 babel을 통해 translation을 하여 test를 실행할 수 있도록 설정해줄 것..!
@babel/core와 @babel/preset-env를 설치npm install @babel/core @babel/preset-env --save-dev
babel.config.js파일을 추가하고 아래처럼 코드 작성module.exports = {
presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
};

+) 이후 정말 오류가 와.. 정말 너무 많이 떠서..
고친 것들만 몇몇 적어보겠다..!
node버전을 20.x를 사용중이라
ci.yml에 버전 수정

일단 eslint 버전때문에 오류가 떴고,
eslint 8로 버전을 다운그레이드 하고나니
npm uninstall eslint
npm install eslint@8 --save-dev
위처럼 eslint를 8버전으로 다시 다운받으니
eslint/js랑 버전이 맞지 않아 오류가 떴다.
지금은 딱히 사용하고 있는 곳이 없어서 삭제해두고
npm uninstall @eslint/js
eslint-config-standard를 사용하기 위해
npm install --save-dev eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise
위 명령어로 "eslint-plugin-import", "eslint-plugin-node", "eslint-plugin-promise"를 함께 설치해줘야함..!
+) eslint-plugin-promise 버전을 맞추기 위해
npm install --save-dev eslint-plugin-promise@6