본문 바로가기
개발/Git, GitHub

깃허브 액션 - 리포지토리의 폴더 정보 저장하기 (Chonky File Map)

by 피로물든딸기 2024. 3. 16.
반응형

깃허브 데스크탑으로 프로젝트 관리하기 강의 오픈!! (인프런 바로가기)

 

Git / GitHub 전체 링크

리액트 전체 링크

Node JS 전체 링크

 

참고

- RESTful API로 파일 읽기

- RESTful API로 파일 쓰기

- 로또 번호 수집해서 json으로 저장하기

 

- 파일 브라우저 만들기
- chonky 기본 설정 확인하기
- 액션 추가하고 다크 모드 구현하기
커스텀 액션 추가하기
- Chonky 파일 맵 만들기
- 리포지토리의 폴더 정보 저장하기
- 깃허브 리포지토리를 파일 브라우저로 불러오기
useNavigate로 Toast UI Editor 이동하기

 

이전 글에서 구현한 Chonky File Map깃허브 액션을 이용해서 리포지토리에 저장해 보자.

 

Chonky 브라우저깃허브의 리포지토리에 대한 정보를 불러오려고 한다.

 

GitHub의 RESTful API를 이용하면 설정한 경로의 파일 목록을 알 수 있지만,

해당 경로에 대해서만 알 수 있기 때문에, 하위 내용에 대해서는 다시 API를 호출해야 한다.

폴더 구성이 복잡할수록 API 호출 횟수가 많아지기 때문에 Chonky File Map을 리포지토리에 저장하고,

해당 파일을 이용하여 Chonky Browser를 렌더링하는 것이 낫다.

 

이제 깃허브 액션을 이용해 actions/myfiles(= root) 폴더가 변경되면, chonky_map.json이 변경되도록 구현하자.


make_dir_map.js 수정

 

이전 글에서 만든 make_dir_map.js로또 번호를 수집한 방법대로 write API를 추가하자

 

1) API 사용을 위한 Octokit, fetch 추가

2) path.resolve를 이용하여 리포지토리 절대 경로dirPath에 저장

3) 최종 결과물 json을 file write → 저장소에 write

const fs = require("fs");
const path = require("path");
const fetch = require("node-fetch");

const { Octokit } = require("@octokit/core");

const octokit = new Octokit({
  auth: process.env.GH_TOKEN,
  request: {
    fetch: fetch,
  },
});

const dirPath = path.resolve("./actions/myfiles");
console.log({ dirPath });

...

makeChonkyMap(dirPath);

const getSHA = async (path) => {
  try {
    const result = await octokit.request(
      `GET /repos/bloodstrawberry/auto-test/contents/${path}`,
      {
        owner: "bloodstrawberry",
        repo: "auto-test",
        path,
      }
    );

    return result.data.sha;
  } catch (e) {
    console.log("error : ", e);
    return undefined;
  }
};

const fileWrite = async (path, contents) => {
  const currentSHA = await getSHA(path);
  const result = await octokit.request(
    `PUT /repos/bloodstrawberry/auto-test/contents/${path}`,
    {
      owner: "bloodstrawberry",
      repo: "auto-test",
      path,
      message: `Update ${path}`,
      sha: currentSHA,
      committer: {
        name: "bloodstrawberry",
        email: "bloodstrawberry@github.com",
      },
      content: `${Buffer.from(contents).toString("base64")}`, // or `${btoa(contents)}`      
      headers: {
        "X-GitHub-Api-Version": "2022-11-28",
      },
    }
  );

  return result.status;
};

const updateChonkyMap = async (json) => {
  const filePath = "actions/config/chonky_map.json";
  try {
    let response = await fileWrite(filePath, json);    
    console.log(response);
  } catch (err) {
    console.error("error : ", err);
    process.exit(1);
  }
};

let json = JSON.stringify({ rootFolderId, fileMap }, null, 4);
// fs.writeFileSync("dir_map.json", json, "utf-8");

console.log(json);

updateChonkyMap(json);

 

이제 깃허브 액션을 위한 chonky-map.yml을 작성한다.

actions/myfilespush 이벤트가 발생하면 chonky-map.yml이 실행된다. 

그러나 액션을 즉시 실행할 경우, 리포지토리 정보가 최신이 아닐 수 있으므로, sleep을 추가하였다.

name: Make Chonky Map

on:
  push:
    paths:
      - actions/myfiles/**

jobs:
  make_chonky_map:
    runs-on: ubuntu-latest

    # Secret에 설정한 변수를 환경 변수로 설정
    env:
      GH_TOKEN: ${{ secrets.MY_TOKEN }}

    steps:
      - name: Wait for 5 minutes
        run: sleep 300 # 300 seconds : 5 minutes

      - name: Checkout code
        uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '16'

      # library 설치
      - name: Install dependencies
        run: |
          npm install @octokit/rest
          npm install node-fetch@2

      # make_dir_map.js 실행
      - name: node make_dir_map.js 
        run: node ./actions/make_dir_map.js

 

리포지토리의 myfiles의 폴더에 파일을 삭제하거나 추가하면 Action이 실행된다.

 

액션 로그를 확인해 보면, 정상적으로 실행된 것을 알 수 있다.

 

그리고 액션에 의해 chonky_map.json이 변경된 것을 알 수 있다.

 

전체 코드는 다음과 같다.

// make_dir_map.js 

const fs = require("fs");
const path = require("path");
const fetch = require("node-fetch");

const { Octokit } = require("@octokit/core");

const octokit = new Octokit({
  auth: process.env.GH_TOKEN,
  request: {
    fetch: fetch,
  },
});

const dirPath = path.resolve("./actions/myfiles");
console.log({ dirPath });

let pathIdMap = {};
let id_counter = 0;

const makePathIdMap = (dir) => {
  pathIdMap[dir] = `id_${id_counter++}`;
  let items = fs.readdirSync(dir);
  for (let item of items) {
    let itemPath = path.join(dir, item);
    let stats = fs.statSync(itemPath);

    if (stats.isDirectory()) {
      makePathIdMap(itemPath);
    } else if (stats.isFile()) {
      pathIdMap[itemPath] = `id_${id_counter++}`;
    }
  }
};

const getChildrenCount = (dir) => {
  let items = fs.readdirSync(dir);
  return items.length;
};

const getChildrenIds = (dir) => {
  let ret = [];
  let items = fs.readdirSync(dir);
  for (let item of items) {
    let itemPath = path.join(dir, item);
    ret.push(pathIdMap[itemPath]);
  }

  return ret;
};

let makeChonkyMap = (dir) => {
  let items = fs.readdirSync(dir);

  for (let item of items) {
    let itemPath = path.join(dir, item);
    let stats = fs.statSync(itemPath);

    // 현재 폴더에 있는 폴더와 파일을 먼저 처리
    if (stats.isDirectory()) {
      let obj = {
        id: pathIdMap[itemPath],
        name: item,
        isDir: true,
        modDate: fs.statSync(itemPath).mtime,
        childrenIds: getChildrenIds(itemPath),
        childrenCount: getChildrenCount(itemPath),
        parentId: pathIdMap[dir],
      };

      fileMap[pathIdMap[itemPath]] = obj;
      makeChonkyMap(itemPath);
    } else if (stats.isFile()) {
      let obj = {
        id: pathIdMap[itemPath],
        name: item,
        size: fs.statSync(itemPath).size,
        modDate: fs.statSync(itemPath).mtime,
        parentId: pathIdMap[dir],
      };

      fileMap[pathIdMap[itemPath]] = obj;
    }
  }
};

makePathIdMap(dirPath);

let rootFolderId = `id_0`;
let initCount = getChildrenCount(dirPath);
let initChildrenIds = getChildrenIds(dirPath);

let fileMap = {
  id_0: {
    id: rootFolderId,
    name: "myfiles",
    isDir: true,
    childrenIds: initChildrenIds,
    childrenCount: initCount,
  },
};

makeChonkyMap(dirPath);

const getSHA = async (path) => {
  try {
    const result = await octokit.request(
      `GET /repos/bloodstrawberry/auto-test/contents/${path}`,
      {
        owner: "bloodstrawberry",
        repo: "auto-test",
        path,
      }
    );

    return result.data.sha;
  } catch (e) {
    console.log("error : ", e);
    return undefined;
  }
};

const fileWrite = async (path, contents) => {
  const currentSHA = await getSHA(path);
  const result = await octokit.request(
    `PUT /repos/bloodstrawberry/auto-test/contents/${path}`,
    {
      owner: "bloodstrawberry",
      repo: "auto-test",
      path,
      message: `Update ${path}`,
      sha: currentSHA,
      committer: {
        name: "bloodstrawberry",
        email: "bloodstrawberry@github.com",
      },
      content: `${Buffer.from(contents).toString("base64")}`, // or `${btoa(contents)}`     
      headers: {
        "X-GitHub-Api-Version": "2022-11-28",
      },
    }
  );

  return result.status;
};

const updateChonkyMap = async (json) => {
  const filePath = "actions/config/chonky_map.json";
  try {
    let response = await fileWrite(filePath, json);    
    console.log(response);
  } catch (err) {
    console.error("error : ", err);
    process.exit(1);
  }
};

let json = JSON.stringify({ rootFolderId, fileMap }, null, 4);
// fs.writeFileSync("dir_map.json", json, "utf-8");

console.log(json);

updateChonkyMap(json);
반응형

댓글