diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 00000000..6b879738 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,37 @@ +name: 'Setup' +description: 'Common setup steps for Actions' + +runs: + using: composite + steps: + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - uses: pnpm/action-setup@v3 + name: Install pnpm + with: + version: 8 + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install Turborepo + shell: bash + run: pnpm add -g turbo + + - name: Install dependencies + shell: bash + run: pnpm install diff --git a/.github/workflows/main-ci.yml b/.github/workflows/main-ci.yml new file mode 100644 index 00000000..1add0a4a --- /dev/null +++ b/.github/workflows/main-ci.yml @@ -0,0 +1,89 @@ +name: Main CI +on: + push: + branches: + - main + +env: + # This enabled remote task caching using Turborepo + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ vars.TURBO_TEAM }} + +jobs: + format-lint-typecheck: + name: Format, Lint & Typecheck + runs-on: ubuntu-latest + + env: + # We don't need to validate the environment variables when we are checking the format, linting and typechecking + SKIP_ENV_VALIDATION: true + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Format check + run: pnpm format:check + + - name: Spell check + run: pnpm lint:spell + + - name: Lint markdown + run: pnpm lint:md + + - name: Lint & Typecheck + run: turbo lint typecheck + + build-marketing: + name: Build marketing website + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Build marketing website + run: turbo build --filter=marketing + + build-app: + name: Build web app + runs-on: ubuntu-latest + + environment: Production + + env: + # Database URL + DATABASE_URL: ${{ secrets.DATABASE_URL }} + + # Authentication + AUTH_GITHUB_ID: ${{ secrets.AUTH_GITHUB_ID }} + AUTH_GITHUB_SECRET: ${{ secrets.AUTH_GITHUB_SECRET }} + AUTH_GOOGLE_ID: ${{ secrets.AUTH_GOOGLE_ID }} + AUTH_GOOGLE_SECRET: ${{ secrets.AUTH_GOOGLE_SECRET }} + AUTH_GOOGLE_CODE_VERIFIER: ${{ secrets.AUTH_GOOGLE_CODE_VERIFIER }} + + # Uploadthing + UPLOADTHING_SECRET: ${{ secrets.UPLOADTHING_SECRET }} + UPLOADTHING_APP_ID: ${{ secrets.UPLOADTHING_APP_ID }} + + # Unkey + UNKEY_ROOT_KEY: ${{ secrets.UNKEY_ROOT_KEY }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Build + run: turbo build --filter=web diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml new file mode 100644 index 00000000..4ee55bb8 --- /dev/null +++ b/.github/workflows/pr-ci.yml @@ -0,0 +1,107 @@ +name: Pull Request CI +on: [pull_request] + +env: + # This enabled remote task caching using Turborepo + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ vars.TURBO_TEAM }} + +jobs: + format-lint-typecheck: + name: Format, Lint & Typecheck + runs-on: ubuntu-latest + + env: + # We don't need to validate the environment variables when we are checking the format, linting and typechecking + SKIP_ENV_VALIDATION: true + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Format check + run: pnpm format:check + + - name: Spell check + run: pnpm lint:spell + + - name: Lint markdown + run: pnpm lint:md + + - name: Lint & Typecheck + run: turbo lint typecheck + + build-marketing: + name: Build marketing website + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Build marketing website + run: turbo build --filter=marketing + + build-app: + name: Build web app + runs-on: ubuntu-latest + + env: + # Neon + NEON_DATABASE_USERNAME: ${{ secrets.NEON_DATABASE_USERNAME }} + NEON_API_KEY: ${{ secrets.NEON_API_KEY }} + NEON_PROJECT_ID: ${{ secrets.NEON_PROJECT_ID }} + + # Authentication + AUTH_GITHUB_ID: ${{ secrets.AUTH_GITHUB_ID }} + AUTH_GITHUB_SECRET: ${{ secrets.AUTH_GITHUB_SECRET }} + AUTH_GOOGLE_ID: ${{ secrets.AUTH_GOOGLE_ID }} + AUTH_GOOGLE_SECRET: ${{ secrets.AUTH_GOOGLE_SECRET }} + AUTH_GOOGLE_CODE_VERIFIER: ${{ secrets.AUTH_GOOGLE_CODE_VERIFIER }} + + # Uploadthing + UPLOADTHING_SECRET: ${{ secrets.UPLOADTHING_SECRET }} + UPLOADTHING_APP_ID: ${{ secrets.UPLOADTHING_APP_ID }} + + # Unkey + UNKEY_ROOT_KEY: ${{ secrets.UNKEY_ROOT_KEY }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Get branch name + id: branch_name + uses: tj-actions/branch-names@v8 + + - name: Create Neon Branch + id: create-branch + uses: neondatabase/create-branch-action@v4 + with: + project_id: ${{ env.NEON_PROJECT_ID }} + branch_name: web/pr-${{ github.event.number}}-${{ steps.branch_name.outputs.current_branch }} + username: ${{ env.NEON_DATABASE_USERNAME }} + api_key: ${{ env.NEON_API_KEY }} + + - name: Set DATABASE_URL + shell: bash + run: | + echo "DATABASE_URL=${{ steps.create-branch.outputs.db_url }}?sslmode=require" >> $GITHUB_ENV + + - name: Build + env: + DATABASE_URL: ${{ env.DATABASE_URL }} + # The build command will also apply schema migrations on the created database branch + run: turbo build --filter=web diff --git a/.github/workflows/pr-cleanup.yml b/.github/workflows/pr-cleanup.yml new file mode 100644 index 00000000..d2269937 --- /dev/null +++ b/.github/workflows/pr-cleanup.yml @@ -0,0 +1,15 @@ +name: Clean up after Pull Request +on: + pull_request: + types: [closed] + +jobs: + delete-db-branch: + runs-on: ubuntu-latest + steps: + - name: Delete Neon Branch + uses: neondatabase/delete-branch-action@v3.1.3 + with: + project_id: ${{ secrets.NEON_PROJECT_ID }} + branch_name: web/pr-${{ github.event.number}}-${{ steps.branch_name.outputs.current_branch }} + api_key: ${{ secrets.NEON_API_KEY }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e8f176da..1d6ec9f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,39 +13,14 @@ jobs: timeout-minutes: 15 runs-on: ubuntu-latest steps: - - name: checkout code repository - # https://github.com/actions/checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: - fetch-depth: 0 + fetch-depth: 2 - - name: setup node.js - # https://github.com/actions/setup-node - uses: actions/setup-node@v4 - - - name: install pnpm - uses: pnpm/action-setup@v3 - with: - run_install: false - - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - uses: actions/cache@v4 - name: Setup pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: install dependencies - run: pnpm install --frozen-lockfile + - name: Setup + uses: ./.github/actions/setup - name: create and publish versions - # https://github.com/changesets/action uses: changesets/action@v1 with: commit: 'chore: update versions' diff --git a/apps/web/.env.example b/apps/web/.env.example index 7e3c30dd..bf7f9d68 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -5,9 +5,9 @@ UPLOADTHING_APP_ID= UNKEY_ROOT_KEY= -GITHUB_ID= -GITHUB_SECRET= +AUTH_GITHUB_ID= +AUTH_GITHUB_SECRET= -GOOGLE_ID= -GOOGLE_SECRET= -GOOGLE_CODE_VERIFIER= \ No newline at end of file +AUTH_GOOGLE_ID= +AUTH_GOOGLE_SECRET= +AUTH_GOOGLE_CODE_VERIFIER= \ No newline at end of file diff --git a/cspell.config.yaml b/cspell.config.yaml index 2c0dea3e..3d14cbb6 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -6,6 +6,10 @@ ignorePaths: - .tsbuildinfo - .gitignore - dist + - storybook-static + - .next + - .astro + - drizzle words: - acst - aest @@ -64,3 +68,9 @@ words: - WITA - ixahmedxi - degit + - tada + - branchname + - codespaces + - codespace + - sslmode + - thollander diff --git a/packages/auth/src/providers/github.ts b/packages/auth/src/providers/github.ts index 0cd719b6..62a0c682 100644 --- a/packages/auth/src/providers/github.ts +++ b/packages/auth/src/providers/github.ts @@ -9,7 +9,7 @@ import { env } from '@orbitkit/env/web/server'; import { lucia } from '../lucia'; -const github = new GitHub(env.GITHUB_ID, env.GITHUB_SECRET); +const github = new GitHub(env.AUTH_GITHUB_ID, env.AUTH_GITHUB_SECRET); export async function createGithubAuthorizationURL(): Promise { const state = generateState(); diff --git a/packages/auth/src/providers/google.ts b/packages/auth/src/providers/google.ts index efdf5b1f..1c94e197 100644 --- a/packages/auth/src/providers/google.ts +++ b/packages/auth/src/providers/google.ts @@ -14,8 +14,8 @@ import { lucia } from '../lucia'; const baseUrl = getBaseUrl(); const google = new Google( - env.GOOGLE_ID, - env.GOOGLE_SECRET, + env.AUTH_GOOGLE_ID, + env.AUTH_GOOGLE_SECRET, `${baseUrl}/login/google/callback`, ); @@ -23,7 +23,7 @@ export async function createGoogleAuthorizationURL(): Promise { const state = generateState(); const url = await google.createAuthorizationURL( state, - env.GOOGLE_CODE_VERIFIER, + env.AUTH_GOOGLE_CODE_VERIFIER, { scopes: ['profile', 'email'], }, @@ -63,7 +63,7 @@ export async function validateGoogleCallback( try { const tokens = await google.validateAuthorizationCode( code, - env.GOOGLE_CODE_VERIFIER, + env.AUTH_GOOGLE_CODE_VERIFIER, ); const googleUserResponse = await fetch( 'https://openidconnect.googleapis.com/v1/userinfo', diff --git a/packages/env/src/web/server.ts b/packages/env/src/web/server.ts index ccd0be9a..5d5d73ce 100644 --- a/packages/env/src/web/server.ts +++ b/packages/env/src/web/server.ts @@ -15,12 +15,12 @@ export const env = createEnv({ UNKEY_ROOT_KEY: z.string().optional(), - GITHUB_ID: z.string(), - GITHUB_SECRET: z.string(), + AUTH_GITHUB_ID: z.string(), + AUTH_GITHUB_SECRET: z.string(), - GOOGLE_ID: z.string(), - GOOGLE_SECRET: z.string(), - GOOGLE_CODE_VERIFIER: z.string(), + AUTH_GOOGLE_ID: z.string(), + AUTH_GOOGLE_SECRET: z.string(), + AUTH_GOOGLE_CODE_VERIFIER: z.string(), }, runtimeEnv: { NODE_ENV: process.env['NODE_ENV'], @@ -33,12 +33,12 @@ export const env = createEnv({ UNKEY_ROOT_KEY: process.env['UNKEY_ROOT_KEY'], - GITHUB_ID: process.env['GITHUB_ID'], - GITHUB_SECRET: process.env['GITHUB_SECRET'], + AUTH_GITHUB_ID: process.env['AUTH_GITHUB_ID'], + AUTH_GITHUB_SECRET: process.env['AUTH_GITHUB_SECRET'], - GOOGLE_ID: process.env['GOOGLE_ID'], - GOOGLE_SECRET: process.env['GOOGLE_SECRET'], - GOOGLE_CODE_VERIFIER: process.env['GOOGLE_CODE_VERIFIER'], + AUTH_GOOGLE_ID: process.env['AUTH_GOOGLE_ID'], + AUTH_GOOGLE_SECRET: process.env['AUTH_GOOGLE_SECRET'], + AUTH_GOOGLE_CODE_VERIFIER: process.env['AUTH_GOOGLE_CODE_VERIFIER'], }, emptyStringAsUndefined: true, skipValidation: !!process.env['SKIP_ENV_VALIDATION'],