Notion Database 와 Github Pages 연동하기
카테고리 | 기술 |
---|---|
태그 | |
수정일 | Jan 3, 2024 |
작성일 | Dec 2, 2023 |
Intro
요즘은 다 fork 떠서 만들긴 하던데 간만에 삽질도 좀 해볼 겸 직접 해보았다.
준비
- Notion 템플릿 작성
- API 토큰 생성
- Notion
프라이빗 API 통합 시크릿
생성하기 🔗 https://www.notion.so/my-integrations -
Notion database ID
🔗 https://developers.notion.com/reference/retrieve-a-database
-
Github Token 🔗 https://github.com/settings/tokens
Generate New Token(Classic)
선택 후repo
,workflow
,admin:repo_hook
세가지를 체크하여 토큰 생성.
💡나는 로컬에서 테스트하기 위해.env.local
에다가 저장했다.# .env.local NOTION_TOKEN=***** DATABASE_ID=***** COMMIT_TOKEN=*****
- Notion
환경 설치
-
brew install rbenv nvm
💡rbenv
: Ruby 버전을 관리하기 위한 패키지nvm
: Node 버전을 관리하기 위한 패키지 -
Node 18
설치nvm install 18 nvm use 18
-
Ruby 3.1.0
설치# ruby 3.1.0 설치 rbenv install 3.1.0 rbenv rehash rbenv global 3.1.0
💡rbenv
는 여러 루비 버전을 관리하기 위한 패키지이다. -
bundler
(루비 패키지 관리 프로그램) 설치# Permission 문제로 sudo 이용하여 실행. sudo gem install bundler
설정파일
-
package.json
생성# yarn 사용 시 yarn init # npm 사용 시 npm init
프로젝트 상세 내용은 알아서 작성
-
jekyll
설치bundler add jekyll bundler exec jekyll new . --skip-bundle --force
-
설정파일 생성
-
/package.json
: 프로젝트 정보 파일{ "name": "croot-dev.github.io", "version": "1.0.0", "main": "index.js", "repository": "https://github.com/croot-dev/croot-dev.github.io.git", "author": "croot <croot.dev@gmail.com>", "private": true, "scripts": { "generate": "cross-env NODE_ENV=local ENV_PATH=.env.local node ./_scripts/notionPageImporter", "build": "bundle exec jekyll build", "serve": "bundle exec jekyll serve" }, "devDependencies": { "@notionhq/client": "^1.0.4", "@types/node-fetch": "^2.6.2", "cross-env": "^7.0.3", "dayjs": "^1.11.10", "dotenv": "^16.3.1", "notion-to-md": "^2.5.5" }, "dependencies": { "axios": "^1.6.2" } }
-
/Gemfile
: Ruby 의존성 정의 파일source "https://rubygems.org" gem "jekyll", "~> 3.9.3" # gem "minima", "~> 2.5" gem "github-pages", "~> 228", group: :jekyll_plugins group :jekyll_plugins do gem "jekyll-paginate-v2" gem "jekyll-sitemap" end platforms :mingw, :x64_mingw, :mswin, :jruby do gem "tzinfo", ">= 1", "< 3" gem "tzinfo-data" end gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] gem "webrick", "~> 1.8" gem "html-proofer", "~> 5.0"
-
/_scripts/notionPageImporter.ts
: 노션 페이지를 마크다운으로 변환하는 스크립트// notionPageImporter.ts const { Client } = require("@notionhq/client"); const { NotionToMarkdown } = require("notion-to-md"); const dayjs = require("dayjs"); const path = require("path"); const fs = require("fs"); const axios = require("axios"); // 노션 데이터베이스 속성명 const PROPERTY = { PUBLISH: '공개', // 타입: 체크박스(checkbox) TITLE: '게시물', // 타입: 제목(plain_text) CATEGORY: '카테고리', TAGS: '태그', // 타입: 다중선택(multi_select) } const DEFAULT_CATEGORY_NAME = '기타'; // 카테고리 없을 시 기본으로 적용할 카테고리 명 // Using dotenv in local if (process.env.NODE_ENV === 'local') { require('dotenv').config({ path: process.env.ENV_PATH, override: true }); } const notion = new Client({ auth: process.env.NOTION_TOKEN, }); const rootDir = "_posts"; fs.mkdirSync(rootDir, { recursive: true }); (async () => { const databaseId = process.env.DATABASE_ID; // TODO has_more const response = await notion.databases.query({ database_id: databaseId, filter: { property: PROPERTY.PUBLISH, checkbox: { equals: true, }, }, }); for (const { id, properties, created_time, last_edited_time } of response.results) { // date const createdDate = dayjs(created_time).format("YYYY-MM-DD"); const updatedDate = dayjs(last_edited_time).format("YYYY-MM-DD"); // title const tempTitle = properties?.[PROPERTY.TITLE]?.["title"]; const title = tempTitle.length > 0? tempTitle[0]?.["plain_text"] : id; // category const category = properties?.[PROPERTY.CATEGORY]?.["select"]?.name || DEFAULT_CATEGORY_NAME; // tags const tagList = properties?.[PROPERTY.TAGS]?.["multi_select"] || []; const tags = tagList.map((tag) => `${tag['name']}`); // frontmatter const frontmatter = `--- layout: post title: "${title}" created: ${createdDate} edited: ${updatedDate} category: [${category}] tags: [${tags.join(',')}] --- `; // passing notion client to the option const n2m = new NotionToMarkdown({ notionClient: notion }); const blocks = await n2m.pageToMarkdown(id); const markdown = n2m.toMarkdownString(blocks)["parent"]; const fileTitle = `${createdDate}-${title.replaceAll(" ", "-")}.md`; if(!markdown) { continue; } let imageIndex = 0; const edited_markdown = markdown.replace( /!\[(.*?)\]\((.*?)\)/g, (match, p1, p2, p3) => { const dirname = path.join("assets/img", fileTitle); if (!fs.existsSync(dirname)) { fs.mkdirSync(dirname, { recursive: true }); } const filename = path.join(dirname, `${imageIndex}.png`); axios.get(p2, { responseType: "stream" }) .then(function (response) { let file = fs.createWriteStream(`${filename}`); response.data.pipe(file); }) .catch(function (error) { console.log(error); }); const res = (p1 === '')? '' : `_${p1}_`; return `![${imageIndex++}]` + `(/${filename})` + `${res}`; }); // Write file fs.writeFile(path.join(rootDir, fileTitle), frontmatter + edited_markdown, (err) => { if (err) { console.log(err); } }); } })();
-
/.github/workflows/pages-deploy.yaml
: github action 스크립트name: "Build and Deploy" on: repository_dispatch: types: [RUN_WORKFLOW_DISPATCH] permissions: contents: write pages: write id-token: write # Allow one concurrent deployment concurrency: group: "pages" cancel-in-progress: true jobs: importer: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Clean Directory run: | for file in assets/img/* do if [[ $file != "assets/img/favicons" ]] then rm -rf "$file" fi done rm -rf _site - uses: actions/setup-node@v2 with: node-version: "17" - run: npm install - run: node _scripts/notionPageImporter.js env: NOTION_TOKEN: $ DATABASE_ID: $ - uses: stefanzweifel/git-auto-commit-action@v4 env: GITHUB_TOKEN: $ with: commit_message: "[배포] Notion 변경 사항 저장" branch: main commit_user_name: importer-bot 🤖 commit_user_email: actions@github.com commit_author: importer-bot 🤖 <actions@github.com> build: needs: importer runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: ref: main fetch-depth: 1 # submodules: true # If using the 'assets' git submodule from Chirpy Starter, uncomment above # (See: https://github.com/cotes2020/chirpy-starter/tree/main/assets) - name: Setup Pages id: pages uses: actions/configure-pages@v1 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: ruby-version: '3.1' # reads from a '.ruby-version' or '.tools-version' file if 'ruby-version' is omitted bundler-cache: true - name: Build site run: bundle exec jekyll b -d "_site" env: JEKYLL_ENV: "production" - name: Test site run: | bundle exec htmlproofer _site --disable-external --checks --allow_hash_href - name: Upload site artifact uses: actions/upload-pages-artifact@v1 with: path: "_site" deploy: environment: name: github-pages url: $ runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1
-
.env.local
(준비 > API 토큰 생성 단계 참고하여 작성)NOTION_TOKEN=#YOUR_NOTION_TOKEN DATABASE_ID=#YOUR_DATABASE_ID COMMIT_TOKEN=#YOUR_COMMIT_TOKEN
-
테스트
yarn test
yarn serve
http://localhost:4000 접속하여 확인 가능.
배포
-
Repository secrets 추가
https://github.com/[Github ID]/[Repository Name]/settings/secrets/actions
위 주소로 접근하여 아래와 같이 값들을 넣어준다. (위에 생성한
.env.local
참고)github secrets actions
-
배포 요청 API 호출하는 HTML 생성 (
COMMIT_TOKEN
수정 입력 필요)<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <button id="triggerButton" class="trigger-button">갱신</button> <div id="message" class="message"></div> <script> document.getElementById("triggerButton").addEventListener("click", function() { var messageElement = document.getElementById("message"); messageElement.textContent = "요청 전송 중..."; var xhr = new XMLHttpRequest(); xhr.open("POST", "https://api.github.com/repos/croot-dev/croot-dev.github.io/dispatches", true); xhr.setRequestHeader("Accept", "application/vnd.github.v3+json"); xhr.setRequestHeader("Authorization", "Bearer [YOUR_COMMIT_TOKEN]"); xhr.setRequestHeader("Content-Type", "application/json"); xhr.onload = function() { if (xhr.status === 204) { messageElement.textContent = "요청이 성공적으로 전송되었습니다." + xhr.status; } else { messageElement.textContent = "요청 전송에 실패했습니다.<br>상태 코드: " + xhr.status; } }; xhr.onerror = function() { messageElement.textContent = "요청 전송 중 알 수 없는 오류가 발생했습니다."; }; xhr.send(JSON.stringify({"event_type": "RUN_WORKFLOW_DISPATCH"})); }); </script> </body> </html>
💡로컬에서 HTML 파일을 열어서 버튼을 누르면 github action이 실행된다.
확인
http://[Github ID].github.io 주소로 접근하여 배포 확인.
Troubleshooting
Cause: 로컬과 Github Actions 플랫폼 불일치로 인한 오류
> bundle install
/opt/hostedtoolcache/Ruby/3.1.4/x64/bin/bundle config --local path /home/runner/work/croot-dev.github.io/croot-dev.github.io/vendor/bundle
/opt/hostedtoolcache/Ruby/3.1.4/x64/bin/bundle config --local deployment true
Cache key: setup-ruby-bundler-cache-v6-ubuntu-22.04-x64-ruby-3.1.4-wd-/home/runner/work/croot-dev.github.io/croot-dev.github.io-with--without--only--Gemfile.lock-073fe0d893d3d26402573dc722ed2240f3b54e2043dff09f2f61ef058a7aecd1
/opt/hostedtoolcache/Ruby/3.1.4/x64/bin/bundle install --jobs 4
Your bundle only supports platforms ["x86_64-darwin-22"] but your local platform
is x86_64-linux. Add the current platform to the lockfile with
`bundle lock --add-platform x86_64-linux` and try again.
Error: The process '/opt/hostedtoolcache/Ruby/3.1.4/x64/bin/bundle' failed with exit code 16
Resolve: Github Actions 맞추어 lockfile 수정.
bundle lock --add-platform x86_64-linux
References
https://aymanbagabas.com/blog/2022/03/29/import-notion-pages-to-jekyll.html
https://lourcode.kr/posts/Jekyll-기반-Github-Pages와-Notion-Page-연동/
« husky script
Ruby gems 삭제하기 »