Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 54 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,54 @@ jobs:
fail_ci_if_error: false
verbose: true

schema-check:
name: Schema Check
runs-on: ubuntu-latest
needs: setup
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'

- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
id: pnpm-cache

- name: Restore dependencies
uses: actions/cache@v4
with:
path: |
${{ steps.pnpm-cache.outputs.STORE_PATH }}
node_modules
apps/*/node_modules
packages/*/node_modules
key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}

- name: Install dependencies (if cache miss)
run: pnpm install --frozen-lockfile

- name: Check for schema drift
working-directory: packages/db
run: |
timeout 30 npx drizzle-kit generate 2>&1 || true
if [ -n "$(git status --porcelain drizzle/)" ]; then
echo "::error::Schema drift detected! The Drizzle schema has changes that are not reflected in migrations."
echo "Run: pnpm --filter @switchflag/db db:generate"
echo "Then commit the generated migration file."
git diff drizzle/
exit 1
fi
echo "Schema and migrations are in sync."

build:
name: Build
runs-on: ubuntu-latest
Expand Down Expand Up @@ -262,6 +310,9 @@ jobs:

- name: Build
run: pnpm turbo build
env:
SKIP_ENV_VALIDATION: true
BETTER_AUTH_SECRET: ci-build-dummy-secret-that-is-long-enough

- name: Upload build artifacts
uses: actions/upload-artifact@v4
Expand All @@ -277,15 +328,16 @@ jobs:
ci-success:
name: CI Success
runs-on: ubuntu-latest
needs: [lint, typecheck, test, build]
needs: [lint, typecheck, test, build, schema-check]
if: always()
steps:
- name: Check all jobs passed
run: |
if [[ "${{ needs.lint.result }}" != "success" ]] || \
[[ "${{ needs.typecheck.result }}" != "success" ]] || \
[[ "${{ needs.test.result }}" != "success" ]] || \
[[ "${{ needs.build.result }}" != "success" ]]; then
[[ "${{ needs.build.result }}" != "success" ]] || \
[[ "${{ needs.schema-check.result }}" != "success" ]]; then
echo "One or more jobs failed"
exit 1
fi
Expand Down
6 changes: 6 additions & 0 deletions apps/dashboard/e2e/helpers/seed.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ export const TEST_ORG = {
slug: 'e2e-test-team',
}

export const TEST_PROJECT = {
id: 'e2e-project-000000000001',
name: 'E2E Test Project',
slug: 'e2e-test-project',
}

export type Persona = {
id: string
email: string
Expand Down
37 changes: 35 additions & 2 deletions apps/dashboard/e2e/seed-db.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import crypto from 'node:crypto'
import { createClient } from '@libsql/client'
import { hashPassword } from 'better-auth/crypto'
import { PERSONAS, TEST_ORG } from './helpers/seed.constants'
import { PERSONAS, TEST_ORG, TEST_PROJECT } from './helpers/seed.constants'

function hashApiKey(apiKey: string): string {
return crypto.createHash('sha256').update(apiKey).digest('hex')
}

const personaIds = Object.values(PERSONAS)
.map((p) => `'${p.id}'`)
Expand All @@ -12,6 +17,8 @@ export async function seedDatabase(dbUrl: string, authToken?: string) {

// Clean existing seed data (idempotent)
await client.executeMultiple(`
DELETE FROM environments WHERE project_id = '${TEST_PROJECT.id}';
DELETE FROM projects WHERE id = '${TEST_PROJECT.id}';
DELETE FROM member WHERE organization_id = '${TEST_ORG.id}';
DELETE FROM organization WHERE id = '${TEST_ORG.id}';
DELETE FROM account WHERE user_id IN (${personaIds});
Expand Down Expand Up @@ -45,8 +52,34 @@ export async function seedDatabase(dbUrl: string, authToken?: string) {
})
}

// Create a default project so the onboarding wizard doesn't trigger for the owner
await client.execute({
sql: 'INSERT INTO projects (id, organization_id, name, slug, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)',
args: [TEST_PROJECT.id, TEST_ORG.id, TEST_PROJECT.name, TEST_PROJECT.slug, now, now],
})

// Create default environments with hashed API keys
const envs = ['development', 'staging', 'production']
for (const envName of envs) {
const apiKey = `sf_${envName.slice(0, 3)}_e2e${envName}key000000000`
const apiKeyHashValue = hashApiKey(apiKey)
const apiKeyPrefixValue = `sf_${envName.slice(0, 3)}_****`

await client.execute({
sql: 'INSERT INTO environments (id, project_id, name, api_key_hash, api_key_prefix, created_at) VALUES (?, ?, ?, ?, ?, ?)',
args: [
`e2e-env-${envName}`,
TEST_PROJECT.id,
envName,
apiKeyHashValue,
apiKeyPrefixValue,
now,
],
})
}

client.close()
console.log('Seed complete — created org + %d personas', Object.keys(PERSONAS).length)
console.log('Seed complete — created org + project + %d personas', Object.keys(PERSONAS).length)
}

// CLI entry point: tsx e2e/seed-db.ts
Expand Down
Loading
Loading