This document describes Portal's continuous integration and deployment setup.
Runs on every pull request and push to main branch.
Jobs:
- lint: Runs Biome linting and formatting checks
- type-check: Runs TypeScript type checking
- build: Builds the Next.js application
- test: Runs test suite with coverage reporting
Validates that pull request titles follow Conventional Commits format.
Features:
- Enforces conventional commit types (feat, fix, docs, etc.)
- Validates subject format (no uppercase start)
- Blocks merge if title doesn't match convention
- Supports
[WIP]prefix for work-in-progress PRs - Works with fork-based workflows (uses
pull_request_target) - Helps ensure semantic-release can properly analyze PRs
Valid PR Title Examples:
feat: add user profile pagefix(auth): resolve session expiration issuedocs: update API documentationrefactor(db): optimize query performancefeat!: breaking change(use!for breaking changes in PR titles)[WIP] feat: work in progress(skips validation, keeps check pending)
Configuration:
- Uses
pull_request_targetevent for better security and fork support - Validates on: opened, reopened, edited, synchronize
- Requires PR titles to match commitlint configuration
If present, a workflow such as .github/workflows/dependency-review.yml can automatically review dependency changes in pull requests.
Features:
- Scans for security vulnerabilities in new dependencies
- Checks for problematic licenses (GPL, AGPL)
- Fails PRs with moderate or higher severity vulnerabilities
- Provides security insights before merging
Configuration:
- Fails on: Moderate severity or higher
- Denied licenses: GPL-2.0, GPL-3.0, AGPL-1.0, AGPL-3.0
Coverage:
- Coverage reports are uploaded to Codecov (if
CODECOV_TOKENis configured) - Coverage thresholds: 40% minimum (lines, functions, branches, statements)
- Target: 70%+ for new code
Automatically creates pull requests for dependency updates using Renovate.
Features:
- Weekly dependency updates (Monday mornings)
- Groups updates by type (production, development, GitHub Actions, Docker)
- Auto-merges minor/patch updates when CI passes
- Manual review required for major updates
- Uses conventional commits
- Dependency dashboard for tracking updates
Configuration:
- Config file:
renovate.json - Schedule: Monday before 10am UTC
- Auto-merge: Enabled for minor/patch updates
- Reviewers: AllThingsLinux team
- Labels: Applied automatically based on update type
If Renovate is used and a workflow such as .github/workflows/renovate-auto-merge.yml is present, it can automatically test and merge Renovate PRs when all checks pass.
Features:
- Runs full CI suite on dependency updates
- Auto-merges when checks pass and PR has
automergelabel - Only runs for Renovate PRs
Automated version management and GitHub releases using semantic-release.
Trigger:
- Runs as part of CI workflow on push to
mainbranch - Only runs after all CI checks pass (lint, type-check, build, test)
- Skips if commit message contains
[skip ci] - Does not run on pull requests
Features:
- Analyzes commits since last release
- Determines next semantic version (major/minor/patch)
- Generates changelog
- Creates GitHub release
- Updates
package.jsonversion - Commits changelog and version updates
Release Types:
fix:→ Patch release (1.0.0 → 1.0.1)feat:→ Minor release (1.0.0 → 1.1.0)BREAKING CHANGE:→ Major release (1.0.0 → 2.0.0)
Configuration:
- Config file:
.releaserc.json - Uses conventional commits (enforced by commitlint)
- Creates
CHANGELOG.mdautomatically - Commits
package.jsonversion andCHANGELOG.mdupdates (via@semantic-release/git)
Note on Committing Changes:
This project commits both package.json version and CHANGELOG.md during releases. While this adds complexity (requires proper branch protection configuration), it's beneficial for:
- Visibility: Contributors can see the current version in the repository
- Documentation:
CHANGELOG.mdprovides a local reference for changes - GitHub-only releases: Since we're not publishing to npm, committing these files provides better visibility
See semantic-release FAQ for more details on the trade-offs.
Plugins:
@semantic-release/commit-analyzer- Analyzes conventional commits (default plugin)@semantic-release/release-notes-generator- Generates release notes (default plugin)@semantic-release/changelog- CreatesCHANGELOG.md(installed as dev dependency)@semantic-release/git- Commits changelog and version updates (installed as dev dependency)@semantic-release/exec- Creates and finalizes Sentry releases (installed as dev dependency)- Creates Sentry release during prepare step
- Finalizes release and associates commits during publish step
- Non-blocking: fails gracefully if Sentry is not configured
@semantic-release/github- Creates GitHub releases (default plugin)semantic-release-major-tag- Creates/updates major version tags (installed as dev dependency)- Creates
v2tag when releasingv2.0.0 - Updates
v2tag to point to latest2.x.xrelease - Useful for quick reference to latest version in a major line
- Creates
Job Dependencies:
The release job depends on all other CI jobs:
lint- Must passtype-check- Must passbuild- Must passtest- Must pass
This ensures releases only happen when all quality gates pass.
Manual deployment workflow for staging and production environments.
Usage:
- Trigger via GitHub Actions UI: "Run workflow"
- Select environment:
stagingorproduction - Or push to
mainbranch (deploys to staging)
Steps:
- Install dependencies
- Type check
- Build application
- Deploy to Vercel (if configured)
- Run database migrations (production only)
Configure in GitHub repository settings: Settings > Branches > Branch protection rules
Recommended Settings:
-
Require status checks to pass before merging
- Required checks:
linttype-checkbuildtestvalidate-pr-title(PR title validation)dependency-review(dependency security review)
- Note: Do not include
releasein required checks, as it only runs on pushes tomain(not on pull requests)
- Required checks:
-
Require pull request reviews before merging
- Required reviewers: 1
- Dismiss stale reviews when new commits are pushed: ✅
-
Require conversation resolution before merging: ✅
-
Require linear history: Optional
-
Do not allow bypassing the above settings: ✅ (for admins)
Setup Instructions:
- Go to repository Settings
- Navigate to "Branches"
- Click "Add rule" or edit existing rule for
mainbranch - Configure the settings above
- Save changes
Note on Branch Protection and Releases:
The release job uses the automatically populated GITHUB_TOKEN to commit changes (changelog and version updates). If branch protection is enabled, ensure that:
- The
GITHUB_TOKENhas sufficient permissions (configured via workflowpermissions) - The release job is not included in required status checks (it only runs on pushes to
main, not on PRs) - Branch protection may need to allow the
GITHUB_TOKENto bypass restrictions for automated commits - Pre-commit hooks (like commitlint via husky) should be disabled for automated commits to avoid conflicts
- If you need to use a Personal Access Token instead, set
persist-credentials: falsein the checkout step and use a custom token (see semantic-release GitHub Actions docs for details)
Important: Committing during releases adds complexity. See semantic-release FAQ for details on the trade-offs.
Coverage thresholds are enforced in vitest.config.ts:
thresholds: {
lines: 40,
functions: 40,
branches: 40,
statements: 40,
}For New Code:
- Target: 70%+ coverage
- Enforced via code review
- Use
pnpm test:coverageto check locally
- Trigger: Push to
mainbranch or manual workflow dispatch - Environment: Staging
- URL: Configured in Vercel project settings
- Trigger: Manual workflow dispatch only
- Environment: Production
- Prerequisites:
- All CI checks passing
- Code review approved
- Database migrations reviewed
Required secrets in GitHub repository settings:
For CI:
CODECOV_TOKEN(optional): Codecov token for coverage reporting
For Releases:
GITHUB_TOKEN(automatic): Automatically populated by GitHub Actions for repository accessSENTRY_ORG(optional): Sentry organization slug for release trackingSENTRY_PROJECT(optional): Sentry project slug for release trackingSENTRY_AUTH_TOKEN(optional): Sentry authentication token for release API
Note: The GITHUB_TOKEN is automatically provided by GitHub Actions and has the necessary permissions configured in the workflow. No manual setup required.
For Deployment:
VERCEL_TOKEN: Vercel deployment tokenVERCEL_ORG_ID: Vercel organization IDVERCEL_PROJECT_ID: Vercel project ID
Environment-specific variables are configured in Vercel dashboard:
- Staging environment variables
- Production environment variables
Before Production Deployment:
- Review migration files in
drizzle/directory - Test migrations on staging database
- Backup production database
- Run migrations:
pnpm db:migrate - Verify migration success
Migration Strategy:
- Migrations run automatically in deploy workflow (production)
- Or run manually via deployment platform's CLI
- Always test migrations on staging first
# Run all CI checks
pnpm check # Lint
pnpm type-check # Type check
pnpm build # Build
pnpm test:coverage # Tests with coverageHusky runs pre-commit hooks automatically:
- Linting and formatting (via lint-staged)
- Commit message validation (via commitlint)
Lint Failures:
pnpm fix # Auto-fix issues
pnpm check # Verify fixesType Check Failures:
pnpm type-check # See detailed errorsBuild Failures:
pnpm build # Test build locallyTest Failures:
pnpm test # Run tests locally
pnpm test:watch # Watch mode for debuggingLow Coverage:
- Add tests for uncovered code
- Focus on critical paths (auth, API routes, integrations)
- Use
pnpm test:coverageto see detailed report
Coverage Threshold Failures:
- Review coverage report:
coverage/index.html - Add tests to increase coverage
- Adjust thresholds if needed (in
vitest.config.ts)
- Always run checks locally before pushing
- Keep PRs small for faster CI runs
- Fix CI failures immediately - don't merge broken code
- Review coverage reports before merging
- Test migrations on staging before production
- Monitor deployments after pushing to production
Portal uses semantic-release for automated version management and GitHub releases.
How It Works:
- Commits follow Conventional Commits format (enforced by commitlint)
- On push to
main, semantic-release:- Analyzes commits since last release
- Determines next version number
- Generates changelog
- Creates Sentry release (if configured)
- Creates GitHub release
- Updates
package.jsonand commits changes - Finalizes Sentry release and associates commits (if configured)
Commit Message Format:
<type>(<scope>): <subject>
[optional body]
[optional footer(s)]
Examples:
fix(auth): resolve session expiration issue→ Patch releasefeat(api): add user profile endpoint→ Minor releasefeat(api): redesign authentication flow+BREAKING CHANGE: ...→ Major release
Skipping Releases:
Add [skip ci] to commit message to skip release:
chore: update dependencies [skip ci]
Excluding Commits from Release Analysis:
To exclude commits from version analysis (they won't affect the release type), add [skip release] or [release skip] to the commit message:
chore: update dependencies [skip release]
docs: update README [release skip]
Note: These commits will still be included in the release notes, but won't affect whether it's a patch, minor, or major release.
Previewing Releases:
To preview what version would be released without actually publishing, use the --dry-run option:
npx semantic-release@25 --dry-runThis will print the next version and release notes to the console without creating a release or committing changes.
If you need to create a release manually:
- Create a tag:
git tag v1.0.0 - Push tag:
git push origin v1.0.0 - Create GitHub release from tag
Note: Manual releases bypass semantic-release. Use automated releases for consistency.
If you're setting up semantic-release for the first time:
- Current version in
package.jsonis0.1.0 - First release will be determined by the first commit that triggers a release
- No initial tag needed - semantic-release will start from the current version
If you had previous releases before setting up semantic-release, ensure the last release commit is tagged with v{version} (e.g., v0.1.0) matching the format in .releaserc.json (tagFormat: "v${version}").
Note on Initial Version:
Semantic-release follows Semantic Versioning and does not support starting at 0.0.1. The first release will be 1.0.0 (or higher based on commit types). If your project is under heavy development with frequent breaking changes, consider using pre-releases instead.
Currently configured for a simple single-branch workflow:
- Release branch:
main- All releases are published from this branch - Distribution channel: Default (GitHub releases)
Future Extensibility:
The workflow can be extended to support more complex release strategies:
-
Pre-release branches: Add
betaoralphabranches for pre-releases (e.g.,2.0.0-beta.1)- Useful for testing major releases before general availability
- See Publishing pre-releases recipe for detailed examples
-
Maintenance branches: Add version-specific branches (e.g.,
1.x,1.0.x) for maintaining older versions- Useful for backporting fixes to older versions
- See Publishing maintenance releases recipe for detailed examples
-
Multiple release branches: Add a
nextbranch for experimental releases on a separate distribution channel- Useful for making breaking changes available to early adopters
- See Publishing on distribution channels recipe for detailed examples
Example Extended Configuration:
{
"branches": [
"main",
{ "name": "beta", "prerelease": true },
{ "name": "alpha", "prerelease": true },
"next",
"+([0-9])?(.{+([0-9]),x}).x"
]
}This configuration would enable:
- Regular releases from
main(default channel) - Pre-releases from
betaandalphabranches - Experimental releases from
nextbranch - Maintenance releases from version-specific branches (e.g.,
1.x,1.0.x)
See semantic-release workflow configuration for detailed documentation.
PR titles are automatically validated to ensure they follow Conventional Commits format. This helps:
- Ensure semantic-release can properly analyze merged PRs
- Maintain consistency across the project
- Prevent merge of PRs with invalid titles
Configuration: .github/workflows/pr-title.yml
While not enforced automatically, the project follows a branch naming convention:
feat/- New featuresfix/- Bug fixesdocs/- Documentation changesrefactor/- Code refactoringtest/- Test additions/updateschore/- Maintenance tasksci/- CI/CD changes
See CONTRIBUTING.md for more details.
This project uses semantic-release instead of alternatives like Changesets or release-please because:
- Semantic-release analyzes commit messages directly (no manual changeset files needed)
- Works seamlessly with conventional commits (already enforced via commitlint)
- Automatically determines version based on commit types
- Simpler workflow: just write conventional commits, releases happen automatically
- Better for projects that want fully automated releases without manual intervention
Changesets would require:
- Manual creation of changeset files for each PR
- Additional PR step to add changeset
- More overhead for contributors
release-please would require:
- Manual version bump PRs
- Less automation than semantic-release
- More manual intervention