[Tree 라이브러리 제작] (2) npm 배포

더 개발하기 전에 배포 먼저 해두고, 이후 개발에 진행됨에 따라 버전업 해서 업데이트 하기로 함.

패키지 이름
@hyeonwoo/js-tree

최종 폴더 구조

├── LICENSE
├── README.md
├── .gitignore
├── .npmignore
├── package.json
├── .github
│   └── workflows
│       └── release.yml
├── index.d.ts
└── src
    ├── components
    │   └── BSTree.js
    └── index.js

TypeScript 사용자를 위한 타입 정의 파일(.d.ts) 작성

BSTree, BStreeNode 클래스에 대한 타입과 docs 작성

index.d.t

declare module "@hyeonwoo/js-tree" {
  /** the data to use when initialize the tree  */
  export type TTreeInitialData<T = any> = {
    key: number;
    left: TTreeInitialData<T> | null;
    right: TTreeInitialData<T> | null;
    data: T | null;
  };

  /** the order way to traverse the tree
   * @pre pre-order
   * @in in-order (asc)
   * @out in-order (desc)
   * @post post-order
   */
  export type TBSTreeTraverseOrderWay = "pre" | "in" | "out" | "post";

  export class BSTreeNode<T = any> {
    key: number;
    left: BSTreeNode<T> | null;
    right: BSTreeNode<T> | null;
    data: T | null;

    /** use internally when insert a new node */
    redirect(newKey: number): {
      node: BSTreeNode<T>;
      direction: -1 | 0 | 1;
      redirect: boolean;
    };

    /** get the left leaf node
     * @param `node` a start node to find the left leaf node (default: `this`)
     */
    getLeftLeaf(node?: BSTreeNode<T>): BSTreeNode<T>;

    /** get the right leaf node
     * @param `node` a start node to find the right leaf node (default: `this`)
     */
    getRightLeaf(node?: BSTreeNode<T>): BSTreeNode<T>;
  }

  export class BSTree<TData = any> {
    root: BSTreeNode<TData> | null;

    /** insert a new node
     * @returns `depth` the depth of inserted node
     */
    insert: (key: number, data?: TData) => { depth: number };

    /** traverse the tree
     * @returns `result` the array of key
     */
    traverse: (
      orderWay: TBSTreeTraverseOrderWay,
      cb?: (node: BSTreeNode<TData>) => void
    ) => BSTreeNode<TData>[];

    /** search a node by key
     * @returns `node` the found node
     * @returns `parentNode` the parent node of the found node
     * @returns `direction` which direction the found node is on the parent. `0` means the found node is ROOT. `-1` means the found node is on the left of the parent. `1` means the found node is on the right of the parent. `null` means that not found.
     * @returns `depth` the depth of the found node
     */
    search: (key: number) => {
      node: BSTreeNode<TData> | null;
      parentNode: BSTreeNode<TData> | null;
      direction: -1 | 0 | 1 | null;
      depth: number;
    };

    /** delete a node by key
     * @returns `success` whether the node is deleted successfully
     * @param `options.cascade` whether to delete the node and all its children (default: `false`)
     */
    deleteByKey: (key: number, options?: { cascade?: boolean }) => boolean;
  }
}

ignore 파일 작성

소스 형상관리에서 제외할 파일을 .gitignore에, npm에 배포 시 포함시키지 않을 파일을 .npmignore에 작성

.gitignore

example

.npmignore

.github
test

배포 전략

  1. npm version <major | minor | patch> 명령 실행 -> package.json의 'version' 값이 1 증가하도록 수정되고, git 커밋과 태그 생성까지 자동으로 해줌. major 버전, minor 버전, patch 버전 중 올리고 싶은 버전에 따라 인자를 다르게 넘김.
  2. github에 커밋 과 태그 푸시
  3. GitHub Actions에서 태그 푸시 감지하여 자동으로 npm에 배포

package.json에 패키지 정보 작성

이 패키지의 이름, 설명, 버전, 홈페이지 및 저장소, 작성자, 키워드, 라이선스 등 작성.

npm에 배포하기 위한 스크립트 3가지(pub:1, pub:2, pub:3)도 추가.

배포 스크립트
pub:1: major 버전 증가시킨 후 github에 푸시
pub:2: minor 버전 증가시킨 후 github에 푸시
pub:3: patch 버전 증가시킨 후 github에 푸시

package.json

{
  "name": "@hyeonwoo/js-tree",
  "description": "JavaScript Tree Library",
  "version": "0.1.8",
  "homepage": "https://github.com/hyeon-wooo/js-tree#readme",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/hyeon-wooo/js-tree.git"
  },
  "deprecated": false,
  "keywords": [
    "javascript",
    "tree",
    "javascript tree",
    "bst",
    "binary search tree"
  ],
  "main": "src/index.js",
  "scripts": {
    "test": "echo \"Test Script Executed!\"",
    "pub:1": "npm version major && git push origin main --tags",
    "pub:2": "npm version minor && git push origin main --tags",
    "pub:3": "npm version patch && git push origin main --tags"
  },
  "author": {
    "name": "Hyeonwoo Kim",
    "url": "https://github.com/hyeon-wooo"
  },
  "license": "MIT"
}

npm 토큰 생성

  1. npm 로그인 후, 우측 상단의 프로필 아이콘 클릭 -> Access Tokens 클릭

  2. Granular Access Token 생성

  3. name, description, 만료일 등 입력해주고, Permission은 아래와 같이 @hyeonwoo scope에 read & write 가능하도록 설정

  4. 토큰 생성이 완료되며, 토큰을 복사해서 GitHub의 secret 변수로 입력

(1) github 로그인 후 프로젝트 페이지 진입
(2) settings로 이동
(3) 좌측 메뉴 중 Security - Secrets and variables - Actions 클릭
(4) New repository secret 클릭
(5) Name: NPM_TOKEN, Secret: 복사한 토큰 입력 후 'Add secret' 클릭

GitHub Actions 배포 자동화

  1. .github/workflows/release.yml 파일 생성 후 아래와 같이 작성
name: Publish Package

# workflow 중복실행 방지
concurrency:
  group: npm-publish
  cancel-in-progress: true

# 'v...' 형식의 태그가 push되면 workflow 실행
on:
  push:
    tags:
      - "v*"

jobs:
  publish:
    runs-on: ubuntu-latest

    steps:
      # 커밋 checkout
      - name: Checkout
        uses: actions/checkout@v4

      # node v22 세팅
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: "https://registry.npmjs.org"

      # CI. 현재는 의미 없음.
      - name: Run tests
        run: npm test

      # npm에 public package로 배포
      - name: Publish to npm
        run: npm publish --access=public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

'Run tests' 단계는 없어도 되지만, 추후 기능이 많아지고 CI가 필요하다고 판단되면 테스트 추가 예정

  1. github에 push

마무리

workflow까지 github에 푸시한 이후,
로컬에서 파일 수정 -> 커밋 추가 -> npm run pub:3 등의 명령어 실행하면 버전이 한단계 올라가면서 자동으로 배포되는 것을 확인할 수 있음.