diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..f257ad4 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,36 @@ +name: Deploy ๐Ÿš€ + +on: + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: deploy-production + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + + - uses: oven-sh/setup-bun@v2 + + - run: bun install --frozen-lockfile + + - run: bun run build + + - name: Write secrets file + env: + GITHUB_CLIENT_SECRET: ${{ secrets.APP_CLIENT_SECRET }} + run: printf 'GITHUB_CLIENT_SECRET=%s\n' "$GITHUB_CLIENT_SECRET" > "$RUNNER_TEMP/secrets.env" + + - name: Deploy + uses: cloudflare/wrangler-action@v4 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + packageManager: bun + command: deploy --secrets-file ${{ runner.temp }}/secrets.env diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 0000000..9235b79 --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,77 @@ +name: Preview ๐Ÿ” + +on: + pull_request: + types: [opened, synchronize, reopened, closed] + +concurrency: + group: preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + preview: + # ํฌํฌ PR์—๋Š” secret์ด ์ „๋‹ฌ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๊ฐ™์€ ์ €์žฅ์†Œ์˜ PR๋งŒ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ๋งŒ๋“ ๋‹ค + if: > + github.event.action != 'closed' && + github.event.pull_request.head.repo.full_name == github.repository && + github.actor != 'dependabot[bot]' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v7 + + - uses: oven-sh/setup-bun@v2 + + - run: bun install --frozen-lockfile + + # ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ผ๋ฆฌ ํ•จ๊ป˜ ์“ฐ๋Š” D1(feedback-preview)์— ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๋จผ์ € ์ ์šฉํ•œ๋‹ค + - name: Migrate preview D1 + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + run: bun run db:migrate:preview + + - run: bun run build + + - name: Write secrets file + env: + GITHUB_CLIENT_SECRET: ${{ secrets.APP_CLIENT_SECRET }} + run: printf 'GITHUB_CLIENT_SECRET=%s\n' "$GITHUB_CLIENT_SECRET" > "$RUNNER_TEMP/secrets.env" + + - name: Deploy preview + id: preview + uses: cloudflare/wrangler-action@v4 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + packageManager: bun + command: preview --name pr-${{ github.event.pull_request.number }} --secrets-file ${{ runner.temp }}/secrets.env + + - name: Comment + if: always() + uses: marocchino/sticky-pull-request-comment@v3 + with: + header: cloudflare-workers + message: | + ${{ steps.preview.outcome == 'success' && 'โœ…' || 'โŒ' }} ๋ฏธ๋ฆฌ๋ณด๊ธฐ: https://pr-${{ github.event.pull_request.number }}.feedback-preview.dalestudy.com ยท [์‹คํ–‰ ๋กœ๊ทธ](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + + cleanup: + if: > + github.event.action == 'closed' && + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + + - uses: oven-sh/setup-bun@v2 + + - run: bun install --frozen-lockfile + + - name: Delete preview + uses: cloudflare/wrangler-action@v4 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + packageManager: bun + command: preview delete --name pr-${{ github.event.pull_request.number }} -y diff --git a/AGENTS.md b/AGENTS.md index cacf52c..0624b10 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,26 +20,25 @@ bun run db:seed:local # ์„ค๋ฌธ JSON(ํŽธ์ง‘์žยท๋ฌธํ•ญ)์„ ๋กœ์ปฌ D1 ์— bunx drizzle-kit generate --name # ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ํ›„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ bun run db:migrate:remote # ํ”„๋กœ๋•์…˜ D1 ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (์•„๋ž˜ Gotchas ์˜ ์ˆœ์„œ๋ฅผ ์ง€ํ‚จ๋‹ค) bun run db:migrate:preview # ๋ฏธ๋ฆฌ๋ณด๊ธฐ D1 ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ -bun run deploy:preview # ์ง€๊ธˆ ๋ธŒ๋žœ์น˜์˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ˆ˜๋™ ๋ฐฐํฌ (๋ณดํ†ต์€ push ๋•Œ Workers Builds ๊ฐ€ ํ•œ๋‹ค) +bun run deploy:preview # ์ง€๊ธˆ ๋ธŒ๋žœ์น˜์˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ˆ˜๋™ ๋ฐฐํฌ (๋ณดํ†ต์€ PR ๋•Œ GitHub Actions ๊ฐ€ ํ•œ๋‹ค) ``` ## Local setup -- ๋กœ์ปฌ ์‹œํฌ๋ฆฟ์€ `.dev.vars` (git ์— ์˜ฌ๋ผ๊ฐ€์ง€ ์•Š์Œ): `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET` โ€” DaleStudy GitHub App ์˜ Client IDยทsecret. ์—†์œผ๋ฉด ๋กœ๊ทธ์ธ ๋Œ€์‹  `users`ยท`sessions` ์— ํ–‰์„ ์ง์ ‘ ๋„ฃ๊ณ  `Cookie: __Host-session=` ๋กœ ์š”์ฒญํ•œ๋‹ค. ์‹ค์ œ GitHub ๋กœ๊ทธ์ธ์€ PR ๋ฏธ๋ฆฌ๋ณด๊ธฐ์—์„œ ๋œ๋‹ค. +- ๋กœ์ปฌ ์‹œํฌ๋ฆฟ์€ `.dev.vars` (git ์— ์˜ฌ๋ผ๊ฐ€์ง€ ์•Š์Œ): `GITHUB_CLIENT_SECRET` โ€” DaleStudy GitHub App ์˜ client secret (Client ID ๋Š” ๊ณต๊ฐœ ๊ฐ’์ด๋ผ `wrangler.jsonc` ์˜ `vars` ์— ์žˆ๋‹ค). ์—†์œผ๋ฉด ๋กœ๊ทธ์ธ ๋Œ€์‹  `users`ยท`sessions` ์— ํ–‰์„ ์ง์ ‘ ๋„ฃ๊ณ  `Cookie: __Host-session=` ๋กœ ์š”์ฒญํ•œ๋‹ค. ์‹ค์ œ GitHub ๋กœ๊ทธ์ธ์€ PR ๋ฏธ๋ฆฌ๋ณด๊ธฐ์—์„œ ๋œ๋‹ค. - `__Host-` ์ ‘๋‘์‚ฌ ์ฟ ํ‚ค๋Š” Secure ๊ฐ€ ํ•„์ˆ˜๋ผ, localhost ๋ฅผ secure context ๋กœ ์ทจ๊ธ‰ํ•˜๋Š” Chrome/Firefox ์—์„œ ๊ฐœ๋ฐœํ•œ๋‹ค. - seed JSON(`seed/*.json`, ํ˜•์‹์€ `seed/example.json`)์€ ๋กœ์ปฌ D1 ์šฉ์ด๋‹ค. ํ”„๋กœ๋•์…˜ ์„ค๋ฌธ์€ ํ™”๋ฉด์—์„œ ๋งŒ๋“ค๊ฑฐ๋‚˜ ๋ณต์ œํ•œ๋‹ค. `editors` ๊ฐ€ ํŽธ์ง‘์ž, `survey.id` ๋ฅผ ๋นผ๋ฉด nanoid ๋กœ ๋งŒ๋“ ๋‹ค. ๊ฐ™์€ id ๊ฐ€ ์žˆ์œผ๋ฉด ์‹คํŒจํ•œ๋‹ค. ๋ฌธํ•ญ์€ `{ "common": "", "required"? }`(๊ณตํ†ต ๋ฌธํ•ญ, ๋ฌธ๊ตฌ๋Š” `vars` ๋กœ ์ฑ„์›€) ๋˜๋Š” `{ "type", "label", "required"?, "config"? }`(์ด ์„ค๋ฌธ๋งŒ์˜ ๋ฌธํ•ญ). ์œ ํ˜•๋ณ„ ์ €์žฅ๊ฐ’ยท๊ฒ€์ฆยท`config` ๋Š” `docs/questions.md`. ## Deployment - ๋ณ€๊ฒฝ์€ ๋ธŒ๋žœ์น˜ โ†’ PR โ†’ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์—์„œ ํ™•์ธ โ†’ squash merge. `main` ์— ์ง์ ‘ pushยทforce push ํ•˜์ง€ ์•Š๋Š”๋‹ค โ€” ๋‹ค๋ฅธ ์‚ฌ๋žŒ๊ณผ ํ•จ๊ป˜ ์“ฐ๋Š” ์ €์žฅ์†Œ๊ฐ€ ๋๊ณ , ๋จธ์ง€ ๊ธฐ๋ก์ด ๊ณง ๋ฐฐํฌ ๊ธฐ๋ก์ด๋‹ค. -- `main` ์— ๋จธ์ง€๋˜๋ฉด Cloudflare Workers Builds ๊ฐ€ `bun run build` โ†’ `npx wrangler deploy`. ๋นŒ๋“œ ๊ฒฐ๊ณผ๋Š” ์ปค๋ฐ‹์˜ check run `Workers Builds: feedback` ์œผ๋กœ ํ™•์ธํ•œ๋‹ค. `bun run deploy` ๋Š” ์ˆ˜๋™ fallback. -- **๋ฏธ๋ฆฌ๋ณด๊ธฐ**: `main` ์ด ์•„๋‹Œ ๋ธŒ๋žœ์น˜๋ฅผ push ํ•˜๋ฉด Workers Builds ๊ฐ€ `bun run db:migrate:preview && bun run deploy:preview`(Workers Previews, `wrangler preview`)๋กœ `https://<๋ธŒ๋žœ์น˜>.feedback-preview.dalestudy.com` ์„ ๋งŒ๋“ ๋‹ค. ๋ชจ๋“  ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋Š” D1 `feedback-preview` ๋ฅผ ํ•จ๊ป˜ ์“ด๋‹ค โ€” ํ”„๋กœ๋•์…˜ ์‘๋‹ต์„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š” ๊ฒŒ ๋ชฉ์ ์ด๋‹ค. +- `main` ์— ๋จธ์ง€๋˜๋ฉด GitHub Actions(`.github/workflows/deploy.yml`)๊ฐ€ `bun run build` โ†’ `wrangler deploy --secrets-file` ๋กœ ๋ฐฐํฌํ•œ๋‹ค. `GITHUB_CLIENT_SECRET` ์€ org secret `APP_CLIENT_SECRET` ์—์„œ ๋ฐฐํฌ์™€ ํ•จ๊ป˜ ๋„ฃ๋Š”๋‹ค(๋”ฐ๋กœ `wrangler secret put` ํ•˜์ง€ ์•Š๋Š”๋‹ค โ€” ๋‹ค์Œ ๋ฐฐํฌ ๋•Œ GitHub ๊ฐ’์œผ๋กœ ๋ฎ์ธ๋‹ค). `bun run deploy` ๋Š” ์ˆ˜๋™ fallback. +- **๋ฏธ๋ฆฌ๋ณด๊ธฐ**: ๊ฐ™์€ ์ €์žฅ์†Œ์˜ PR ์„ ์—ด๊ฑฐ๋‚˜ push ํ•˜๋ฉด GitHub Actions(`.github/workflows/preview.yml`)๊ฐ€ `bun run db:migrate:preview` โ†’ `wrangler preview --name pr-<๋ฒˆํ˜ธ> --secrets-file` ๋กœ `https://pr-<๋ฒˆํ˜ธ>.feedback-preview.dalestudy.com` ์„ ๋งŒ๋“ค๊ณ  PR ๋Œ“๊ธ€๋กœ ์•Œ๋ฆฐ๋‹ค. PR ์ด ๋‹ซํžˆ๋ฉด ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ์ง€์šด๋‹ค. ๋ชจ๋“  ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋Š” D1 `feedback-preview` ๋ฅผ ํ•จ๊ป˜ ์“ด๋‹ค โ€” ํ”„๋กœ๋•์…˜ ์‘๋‹ต์„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š” ๊ฒŒ ๋ชฉ์ ์ด๋‹ค. - `wrangler.jsonc` ์˜ `previews` ๋ธ”๋ก์— D1 ์„ ๋‹ค์‹œ ์„ ์–ธํ•œ๋‹ค(์ƒ์†๋˜์ง€ ์•Š๋Š”๋‹ค). ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ „์šฉ ํ™˜๊ฒฝ `env.migrate-preview` ๋กœ ํ•œ๋‹ค โ€” `previews` ๋ธ”๋ก์˜ D1 ์€ `d1 migrations apply` ๊ฐ€ ์ฐพ์ง€ ๋ชปํ•˜๊ณ , `preview_database_id` ๋ฅผ ์“ฐ๋ฉด ๋กœ์ปฌ ๊ฐœ๋ฐœ๊นŒ์ง€ ๊ทธ id ์˜ ๋นˆ DB ๋ฅผ ์“ฐ๊ฒŒ ๋œ๋‹ค. - `versions upload` ์˜ ๋ฒ„์ „ URL ์€ ํ”„๋กœ๋•์…˜ D1 ์„ ์จ์„œ ์“ฐ์ง€ ์•Š๋Š”๋‹ค(`preview_urls` ๋„ ๋”). - - ๋ธŒ๋žœ์น˜๋ณ„ ํŠธ๋ฆฌ๊ฑฐ์™€ ์‹œํฌ๋ฆฟ์€ ๋ธŒ๋žœ์น˜๊ฐ€ ์ฒ˜์Œ ๋นŒ๋“œ๋  ๋•Œ์˜ base config ๋ฅผ ๋ณต์‚ฌํ•œ๋‹ค. ๋Œ€์‹œ๋ณด๋“œ์˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ช…๋ น์ด๋‚˜ `wrangler preview base-config secret put` ์„ ๋ฐ”๊พผ ๋’ค์—๋Š” ๊ธฐ์กด ๋ธŒ๋žœ์น˜์— ๋”ฐ๋กœ ๋„ฃ๋Š”๋‹ค(`wrangler preview secret put --name <๋ธŒ๋žœ์น˜>`). ์ฆ์ƒ์€ GitHub ๋กœ `client_id=undefined` ๊ฐ€ ๊ฐ€๋Š” ๊ฒƒ. -- ์ด๋ฏธ ์„ค์ •๋œ ๊ฒƒ: D1 `feedback`ยท`feedback-preview`(APAC, id ๋Š” `wrangler.jsonc`), ์‹œํฌ๋ฆฟ `GITHUB_CLIENT_ID`ยท`GITHUB_CLIENT_SECRET`, ์ปค์Šคํ…€ ๋„๋ฉ”์ธ(`wrangler.jsonc` ์˜ `routes`), ๋นŒ๋“œ ๋ณ€์ˆ˜ `BUN_VERSION=1.4.0`(๋นŒ๋“œ ์ด๋ฏธ์ง€ ๊ธฐ๋ณธ bun ์€ lockfile v2 ๋ฅผ ๋ชป ์ฝ๋Š”๋‹ค. ๋กœ์ปฌ bun ์„ ์˜ฌ๋ฆฌ๋ฉด ๊ฐ™์ด ์˜ฌ๋ฆฐ๋‹ค). + - ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์‹œํฌ๋ฆฟ๋„ ๋ฐฐํฌํ•  ๋•Œ๋งˆ๋‹ค `--secrets-file` ๋กœ ๋„ฃ์œผ๋ฏ€๋กœ Previews base config ์—๋Š” ์‹œํฌ๋ฆฟ์„ ๋‘์ง€ ์•Š๋Š”๋‹ค. +- ์ด๋ฏธ ์„ค์ •๋œ ๊ฒƒ: D1 `feedback`ยท`feedback-preview`(APAC, id ๋Š” `wrangler.jsonc`), org secret `CLOUDFLARE_API_TOKEN`ยท`APP_CLIENT_SECRET`, ์ปค์Šคํ…€ ๋„๋ฉ”์ธ(`wrangler.jsonc` ์˜ `routes`). - GitHub App ์˜ Callback URL ์€ localhost, `https://feedback.dalestudy.com/auth/callback`, `https://feedback-preview.dalestudy.com/auth/callback`(์™€์ผ๋“œ์นด๋“œ) ์…‹. ์•ฑ์€ **๊ณต๊ฐœ(public)** ์—ฌ์•ผ ํ•œ๋‹ค โ€” ๋น„๊ณต๊ฐœ ์•ฑ์€ ์†Œ์œ  ์กฐ์ง ๋ฉค๋ฒ„๋งŒ authorize ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์กฐ์ง ๋ฐ– ์ฐธ๊ฐ€์ž๊ฐ€ GitHub ์˜ authorize ํŽ˜์ด์ง€์—์„œ 404 ๋ฅผ ๋ณธ๋‹ค(์šฐ๋ฆฌ ๋กœ๊ทธ์—๋Š” ์•ˆ ๋‚จ๋Š”๋‹ค). -- ์ €์žฅ์†Œ๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค๋ฉด Workers Builds ์—ฐ๊ฒฐ์ด ๋Š๊ธด๋‹ค(๋Œ€์‹œ๋ณด๋“œ์—๋Š” ์ด๋ฆ„์ด ๊ทธ๋Œ€๋กœ ๋ณด์—ฌ๋„). ๋Œ€์‹œ๋ณด๋“œ์—์„œ Disconnect โ†’ Connect ๋กœ ๋‹ค์‹œ ์ž‡๋Š”๋‹ค. ## Architecture diff --git a/src/env.d.ts b/src/env.d.ts index 4689db8..d5c7a8c 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -1,7 +1,6 @@ -// wrangler secret put ์œผ๋กœ ๋“ฑ๋กํ•˜๋Š” ๊ฐ’๋“ค. wrangler types ๋Š” ์‹œํฌ๋ฆฟ์„ ๋ชจ๋ฅด๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ ๋ณด๊ฐ•ํ•œ๋‹ค. +// ๋ฐฐํฌํ•  ๋•Œ GitHub secret์—์„œ ๋„ฃ๋Š” ๊ฐ’. wrangler types ๋Š” ์‹œํฌ๋ฆฟ์„ ๋ชจ๋ฅด๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ ๋ณด๊ฐ•ํ•œ๋‹ค. declare namespace Cloudflare { interface Env { - GITHUB_CLIENT_ID: string GITHUB_CLIENT_SECRET: string } } diff --git a/wrangler.jsonc b/wrangler.jsonc index 0b1a270..e14ac3f 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -7,6 +7,8 @@ "main": "@tanstack/react-start/server-entry", "observability": { "enabled": true }, "upload_source_maps": true, + // GitHub App(dalestudy)์˜ Client ID. ๋กœ๊ทธ์ธ URL์— ๊ทธ๋Œ€๋กœ ๋“œ๋Ÿฌ๋‚˜๋Š” ๊ณต๊ฐœ ๊ฐ’์ด๋‹ค. + "vars": { "GITHUB_CLIENT_ID": "Iv23liwu6Ob75w8NcAX9" }, "routes": [ { "pattern": "feedback.dalestudy.com", "custom_domain": true }, // ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ „์šฉ ๋„๋ฉ”์ธ: ํ”„๋กœ๋•์…˜ ํŠธ๋ž˜ํ”ฝ์€ ๋ฐ›์ง€ ์•Š๋Š”๋‹ค(enabled: false) @@ -23,6 +25,7 @@ // PR ๋ฏธ๋ฆฌ๋ณด๊ธฐ(Workers Previews). ๋ธŒ๋žœ์น˜๋งˆ๋‹ค <๋ธŒ๋žœ์น˜>.feedback-preview.dalestudy.com ์— ๋œฌ๋‹ค. // D1 ์€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ผ๋ฆฌ ํ•จ๊ป˜ ์“ฐ๋Š” feedback-preview ๋กœ โ€” ํ”„๋กœ๋•์…˜ ์‘๋‹ต์„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ฒŒ. "previews": { + "vars": { "GITHUB_CLIENT_ID": "Iv23liwu6Ob75w8NcAX9" }, "d1_databases": [ { "binding": "DB", @@ -36,6 +39,7 @@ // `preview_database_id` ๋กœ ํ•˜๋ฉด ๋กœ์ปฌ ๊ฐœ๋ฐœ๋„ ๊ทธ id ์˜ ๋นˆ DB ๋ฅผ ์“ฐ๊ฒŒ ๋ผ์„œ ์ด๋ ‡๊ฒŒ ๋‘”๋‹ค. ๋ฐฐํฌ์—๋Š” ์“ฐ์ง€ ์•Š๋Š”๋‹ค. "env": { "migrate-preview": { + "vars": { "GITHUB_CLIENT_ID": "Iv23liwu6Ob75w8NcAX9" }, "d1_databases": [ { "binding": "DB",