From 1f739c879a46e1293b9db0584ff8e6043805f488 Mon Sep 17 00:00:00 2001 From: Lucas Santana Date: Wed, 11 Mar 2026 14:55:55 -0300 Subject: [PATCH 1/2] feat(ci): bootstrap limit-aware actions workflows for new projects --- CHANGELOG.md | 23 + README.md | 69 ++- docs/guides/actions-limits-strategy.md | 99 ++++ docs/guides/organization-setup.md | 520 ++---------------- scripts/bootstrap/actions-org-setup.sh | 213 +++++++ scripts/bootstrap/project.sh | 257 ++++++++- .../workflows/limit-aware/ci-nextjs.yml.tpl | 183 ++++++ .../workflows/limit-aware/ci-node.yml.tpl | 179 ++++++ .../workflows/limit-aware/ci-python.yml.tpl | 180 ++++++ .../security-nightly-nextjs.yml.tpl | 37 ++ .../limit-aware/security-nightly-node.yml.tpl | 37 ++ .../security-nightly-python.yml.tpl | 37 ++ 12 files changed, 1311 insertions(+), 523 deletions(-) create mode 100644 docs/guides/actions-limits-strategy.md create mode 100755 scripts/bootstrap/actions-org-setup.sh mode change 100644 => 100755 scripts/bootstrap/project.sh create mode 100644 scripts/bootstrap/templates/workflows/limit-aware/ci-nextjs.yml.tpl create mode 100644 scripts/bootstrap/templates/workflows/limit-aware/ci-node.yml.tpl create mode 100644 scripts/bootstrap/templates/workflows/limit-aware/ci-python.yml.tpl create mode 100644 scripts/bootstrap/templates/workflows/limit-aware/security-nightly-nextjs.yml.tpl create mode 100644 scripts/bootstrap/templates/workflows/limit-aware/security-nightly-node.yml.tpl create mode 100644 scripts/bootstrap/templates/workflows/limit-aware/security-nightly-python.yml.tpl diff --git a/CHANGELOG.md b/CHANGELOG.md index 74f0990..840d42d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ ## [Unreleased] +### Added +- **Limit-aware bootstrap templates** — Added workflow templates for `node`, + `nextjs`, and `python` projects under + `scripts/bootstrap/templates/workflows/limit-aware/`, including: + - PR CI templates with baseline required checks and conditional heavy jobs + - Nightly security workflows that always run heavy scans +- **Actions org setup helper** — Added `scripts/bootstrap/actions-org-setup.sh` + to validate org/repo Actions endpoints, query billing usage, and upsert: + - `ACTIONS_MONTHLY_CAP_MINUTES` + - `ACTIONS_WARN_PCT` + - `ACTIONS_DEGRADE_PCT` +- **Actions limits documentation** — Added + `docs/guides/actions-limits-strategy.md` and refreshed + `docs/guides/organization-setup.md` for the `.github` canonical workflow + source. + ### Fixed - **IDP init import side effects** — Package root imports no longer trigger `forge-init` writes. `initProject` now lives in side-effect-free `patterns/idp/init/project.ts`, CLI execution in @@ -7,6 +23,13 @@ loading at import time. ### Changed +- **Bootstrap contract (new projects)** — `scripts/bootstrap/project.sh` now + defaults to `--ci-profile=limit-aware`, requires `--org` and + `--actions-cap-minutes` for that profile, and generates limit-aware CI + workflows from templates. +- **Bootstrap path resolution** — Project bootstrapping now resolves source + files from repository-root absolute paths, avoiding relative path failures + during project generation. - **TypeScript ESLint alignment** — Synchronized `@typescript-eslint/eslint-plugin`, `@typescript-eslint/parser`, and `typescript-eslint` to `8.57.0` to keep peer dependencies compatible in CI installs. diff --git a/README.md b/README.md index 0c5bddb..5cc10b6 100644 --- a/README.md +++ b/README.md @@ -134,51 +134,46 @@ npx forge-audit --json --threshold 60 # CI gate mode ## � GitHub Workflows Optimization -### Organization-Level Reusable Workflows +### Canonical Workflow Source -Forge-Space Core now provides **centralized reusable workflows** that eliminate duplication across the Forge Space ecosystem: +Reusable workflows are now centralized in **`Forge-Space/.github`**. This +repository (`core`) provides bootstrap orchestration and templates for new +projects. -- **95% reduction** in maintenance overhead -- **Single source of truth** for all CI/CD logic -- **Organization-level sharing** via GitHub Actions -- **Zero duplicated files** across projects +### Limit-Aware CI (New Orgs + New Projects) -### Available Reusable Workflows +New projects generated by `scripts/bootstrap/project.sh` default to the +`limit-aware` CI profile: -#### Core CI/CD Workflows -- **ci-base.yml** - Unified base CI pipeline with configurable inputs -- **security-scan.yml** - Comprehensive security scanning and validation -- **branch-protection.yml** - Automated branch protection and validation -- **dependency-management.yml** - Centralized dependency updates and auditing -- **release-publish.yml** - Automated release publishing with version management +- baseline checks always run: lint, typecheck, unit tests, build, secret scan +- heavy jobs degrade when Actions usage crosses configured thresholds: + - Docker build + - E2E + - Semgrep + - Trivy + - CodeQL on PR +- nightly security workflow still runs heavy scans regardless of degrade mode -#### Usage Examples +Bootstrap example: -```yaml -# In your project's .github/workflows/ci.yml -jobs: - ci: - uses: Forge-Space/core/.github/workflows/reusable/ci-base.yml@ - with: - project-type: 'gateway' # or 'mcp', 'webapp', 'patterns' - node-version: '22' - python-version: '3.12' - enable-docker: true - enable-security: true - enable-coverage: true +```bash +./scripts/bootstrap/project.sh my-service node \ + --org Forge-Space \ + --actions-cap-minutes 20000 ``` -### Integration Benefits -- **Instant Updates**: Change once, apply everywhere -- **Consistency**: Standardized patterns across all projects -- **Maintenance**: Single point of update for workflow improvements -- **Quality**: Centralized testing and validation of workflows - -### Quick Integration -1. **Configure Repository Access**: Enable organization access to Forge-Space/core workflows -2. **Update Workflow References**: Replace local copies with organization references -3. **Remove Duplicated Files**: Delete any local `-shared.yml` files -4. **Test and Validate**: Ensure workflows run correctly with new references +Initialize Actions budget variables for an org: + +```bash +./scripts/bootstrap/actions-org-setup.sh \ + --org Forge-Space \ + --actions-cap-minutes 20000 +``` + +### Related Guides + +1. [Organization Setup](docs/guides/organization-setup.md) +2. [Actions Limits Strategy](docs/guides/actions-limits-strategy.md) ## � Documentation diff --git a/docs/guides/actions-limits-strategy.md b/docs/guides/actions-limits-strategy.md new file mode 100644 index 0000000..4e91bbe --- /dev/null +++ b/docs/guides/actions-limits-strategy.md @@ -0,0 +1,99 @@ +# Actions Limits Strategy (v1) + +This guide defines the first-pass GitHub Actions limits strategy for new +organizations and newly bootstrapped projects. + +## Goals + +- Reduce risk of hosted-runner exhaustion +- Keep required CI checks running under budget pressure +- Degrade non-required heavy jobs when usage is high +- Keep full security coverage via nightly scans + +## Guard Behavior + +Reusable workflow source: + +- `Forge-Space/.github/.github/workflows/reusable-actions-budget-guard.yml` + +Inputs: + +- `org` +- `monthly_cap_minutes` +- `warn_pct` (default `70`) +- `degrade_pct` (default `85`) + +Outputs: + +- `usage_pct` +- `warn_mode` +- `degrade_mode` +- `summary` + +Semantics: + +- fail-open on billing API/read failures (`degrade_mode=false`) +- warn mode emits summary signal only +- degrade mode skips heavy non-required jobs on PR CI + +## CI Policy Split + +Always run (baseline required checks): + +- lint +- typecheck +- unit tests +- build +- secret scan + +Conditionally skipped when `degrade_mode=true`: + +- Docker build +- E2E +- Semgrep +- Trivy +- CodeQL on PR + +Always run nightly (schedule + manual dispatch): + +- Semgrep +- Trivy +- CodeQL + +## New Project Bootstrap + +`limit-aware` is the default CI profile. + +```bash +./scripts/bootstrap/project.sh my-app node \ + --org Forge-Space \ + --actions-cap-minutes 20000 +``` + +Optional thresholds: + +```bash +./scripts/bootstrap/project.sh my-app nextjs \ + --org Forge-Space \ + --actions-cap-minutes 20000 \ + --actions-warn-pct 70 \ + --actions-degrade-pct 85 +``` + +## Organization Setup + +Before bootstrapping projects, initialize org/repo variables: + +```bash +./scripts/bootstrap/actions-org-setup.sh \ + --org Forge-Space \ + --actions-cap-minutes 20000 +``` + +## Validation Checklist + +- reusable guard workflow resolves from `ORG/.github` +- budget summary appears in workflow summary +- degrade mode skips heavy PR jobs only +- required checks remain green-capable under degrade mode +- nightly security workflow runs heavy scans independent of degrade mode diff --git a/docs/guides/organization-setup.md b/docs/guides/organization-setup.md index ae2b036..bc81b0e 100644 --- a/docs/guides/organization-setup.md +++ b/docs/guides/organization-setup.md @@ -1,501 +1,103 @@ # Organization Setup Guide -This guide provides step-by-step instructions for configuring the Forge-Space -organization to enable GitHub Actions workflow sharing across all repositories. +This guide configures a new Forge-Space organization to consume reusable workflows +from `Forge-Space/.github` and enable limit-aware CI defaults for newly +bootstrapped projects. -## 🎯 Overview +## Scope -Forge-Space/core uses GitHub Actions organization-level workflow sharing to -provide centralized reusable workflows. This requires proper organization -configuration to allow repositories to access and use these workflows. +- Canonical reusable workflow source: `Forge-Space/.github` +- New organization and new project onboarding only +- Hosted runner guardrails (no self-hosted runner rollout in this phase) -## 📋 Prerequisites +## Prerequisites -### Required Permissions +- Organization owner or admin access +- `gh` CLI authenticated with admin scope +- `jq` installed -- **Organization Owner** or **Admin** access to Forge-Space organization -- **Repository Admin** access to target repositories -- **GitHub Actions** enabled for the organization +## 1. Configure Reusable Workflow Source -### Required Tools +Open the workflow source repository: -- GitHub CLI (`gh`) - for command-line operations -- Web browser - for GitHub UI configuration +- [https://github.com/Forge-Space/.github/settings/actions](https://github.com/Forge-Space/.github/settings/actions) -## 🔧 Step 1: Configure Forge-Space/core Repository +Set: -### Enable Organization Access +- Actions permissions: allow GitHub Actions for the repository +- Reusable workflow access: accessible from repositories in the organization -1. **Navigate to Forge-Space/core** - - Go to - [https://github.com/Forge-Space/core](https://github.com/Forge-Space/core) - - Ensure you're logged in with appropriate permissions - -2. **Configure Actions Settings** - - Click **Settings** tab - - Click **Actions** in the left sidebar - - Scroll to **Workflow permissions** - - Select **Allow all actions** OR **Allow select actions** - - Under **Access**, check **Accessible from repositories in 'Forge-Space' - organization** - -3. **Save Settings** - - Click **Save** to apply changes - - Verify settings are saved successfully - -### Verify Configuration - -```bash -# Check repository permissions -gh api repos/Forge-Space/core/actions/permissions - -# Verify organization access -gh api orgs/Forge-Space -``` - -## 🔧 Step 2: Configure Target Repositories - -### Enable Actions for Each Repository - -For each repository that will use Forge-Space/core workflows: - -1. **Navigate to Repository** - - Go to the repository settings page - - Example: https://github.com/Forge-Space/mcp-gateway - -2. **Configure Actions Settings** - - Click **Settings** tab - - Click **Actions** in the left sidebar - - Under **Workflow permissions**, select: - - **Allow all actions** (recommended) OR - - **Allow select actions** and add Forge-Space/core - -3. **Enable Required Permissions** - - Ensure **Allow GitHub Actions to create and approve pull requests** is - checked - - Enable **Allow GitHub Actions to run workflows** if needed - -### Batch Configuration (Advanced) - -For multiple repositories, use the GitHub CLI: +Verify from CLI: ```bash -#!/bin/bash -# Configure multiple repositories for workflow access - -REPOSITORIES=( - "Forge-Space/mcp-gateway" - "Forge-Space/uiforge-mcp" - "Forge-Space/uiforge-webapp" -) - -for repo in "${REPOSITORIES[@]}"; do - echo "Configuring $repo..." - gh api repos/$repo/actions/permissions \ - --method PATCH \ - --field enabled=true \ - --field allowed_actions=select \ - --field selected_actions=Forge-Space/core -done +gh api repos/Forge-Space/.github/actions/permissions +gh api repos/Forge-Space/.github/actions/permissions/access ``` -## 🔧 Step 3: Verify Workflow Access - -### Test Workflow Access - -Create a test workflow in one of your repositories: - -```yaml -# .github/workflows/test-access.yml -name: Test Workflow Access - -on: - workflow_dispatch: +## 2. Configure Actions Budget Variables -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Test Forge-Space/core access - uses: Forge-Space/core/.github/workflows/reusable/ci-base.yml@main - with: - project-type: 'patterns' - node-version: '22' - python-version: '3.12' - enable-docker: false - enable-security: false - enable-coverage: false -``` - -### Run Test Workflow +Use the helper script in this repository: ```bash -# Trigger the test workflow -gh workflow run test-access --repo your-username/your-repo - -# Check the results -gh run list --repo your-username/your-repo +./scripts/bootstrap/actions-org-setup.sh \ + --org Forge-Space \ + --actions-cap-minutes 20000 \ + --warn-pct 70 \ + --degrade-pct 85 ``` -## 🔧 Step 4: Configure Organization Policies - -### Workflow Permissions Policy - -1. **Navigate to Organization Settings** - - Go to - [https://github.com/organizations/Forge-Space](https://github.com/organizations/Forge-Space) - - Click **Settings** - -2. **Configure Member Privileges** - - Under **Member privileges**, ensure: - - **Can update organization repository settings** is checked for admins - - **Can manage organization billing settings** is checked for owners - -3. **Repository Permissions** - - Under **Repository permissions**, ensure: - - **Members can create repositories** is set appropriately - - **Members can create internal repositories** is set if needed +What this does: -### Security Considerations +- validates org Actions permissions endpoints +- checks reusable-workflow accessibility on `ORG/.github` +- queries billing usage endpoint (`/orgs/{org}/settings/billing/usage`) +- upserts org and repo variables: + - `ACTIONS_MONTHLY_CAP_MINUTES` + - `ACTIONS_WARN_PCT` + - `ACTIONS_DEGRADE_PCT` -- **Review Access Regularly**: Periodically review who has access to - Forge-Space/core -- **Use Principle of Least Privilege**: Only grant necessary permissions -- **Monitor Workflow Usage**: Track which repositories use centralized workflows - -## 🔧 Step 5: Configure GitHub Actions Secrets - -### Organization-Level Secrets - -For shared secrets across all repositories: +Dry-run example: ```bash -# Add organization-wide secrets -gh secret set --org Forge-Space SNYK_TOKEN "your-snyk-token" -gh secret set --org Forge-Space CODECOV_TOKEN "your-codecov-token" +./scripts/bootstrap/actions-org-setup.sh \ + --org Forge-Space \ + --actions-cap-minutes 20000 \ + --dry-run ``` -### Repository-Level Secrets +## 3. Bootstrap New Projects With Limit-Aware CI -For repository-specific secrets: +Default profile is `limit-aware` and requires budget inputs: ```bash -# Add repository-specific secrets -gh secret set --repo Forge-Space/mcp-gateway DOCKER_PASSWORD "your-docker-password" -gh secret set --repo Forge-Space/uiforge-mcp NPM_TOKEN "your-npm-token" -``` - -## 🔧 Step 6: Configure Self-Hosted Runners (Optional) - -If using self-hosted runners: - -### Configure Runner Groups - -1. **Navigate to Organization Settings** - - Go to **Settings** → **Actions** → **Runners** - - Click **New runner group** - -2. **Create Runner Group** - - Name: `forge-workflows` - - Visibility: **All repositories** or **Selected repositories** - - Add Forge-Space/core to allowed repositories - -3. **Configure Runner Settings** - - Set appropriate labels - - Configure runner image and resources - - Set up runner scaling - -### Runner Group Example - -```yaml -# .github/workflows/ci.yml -jobs: - ci: - runs-on: - group: forge-workflows - labels: [ubuntu-latest, self-hosted] - uses: Forge-Space/core/.github/workflows/reusable/ci-base.yml@main -``` - -## 🔧 Step 7: Configure Monitoring and Notifications - -### Organization-Level Monitoring - -Set up monitoring for workflow failures: - -```yaml -# .github/workflows/monitor.yml -name: Organization Workflow Monitor - -on: - workflow_run: - types: [completed] - workflows: ['ci-base', 'security-scan'] - -jobs: - monitor: - runs-on: ubuntu-latest - if: github.event.workflow_run.conclusion == 'failure' - steps: - - name: Notify on failure - run: | - echo "Workflow failed in ${{ github.repository }}" - # Add Slack/Teams notifications here +./scripts/bootstrap/project.sh my-service node \ + --org Forge-Space \ + --actions-cap-minutes 20000 ``` -### Repository-Specific Monitoring - -For individual repository monitoring: - -```yaml -# .github/workflows/notify.yml -name: Repository Notifications +The generated workflows include: -on: - workflow_run: - types: [completed] +- concurrency controls +- docs/meta `paths-ignore` +- baseline required jobs (lint, typecheck, unit tests, build, secret scan) +- conditional heavy jobs (Docker, E2E, Semgrep, Trivy, CodeQL on PR) +- nightly security workflow that always runs heavy scans -jobs: - notify: - runs-on: ubuntu-latest - if: github.event.workflow_run.conclusion == 'failure' - steps: - - name: Send notification - uses: 8398a7/action-slack@v3 - with: - status: failure - channel: '#ci-cd' - webhook_url: ${{ secrets.SLACK_WEBHOOK }} -``` - -## 🔧 Step 8: Validate Configuration +## 4. Validate Setup -### Comprehensive Validation Script +From a generated project: ```bash -#!/bin/bash -# Validate Forge-Space organization setup - -echo "🔍 Validating Forge-Space organization setup..." - -# Check organization access -echo "📋 Checking organization access..." -gh api orgs/Forge-Space || { - echo "❌ Cannot access Forge-Space organization" - exit 1 -} - -# Check Forge-Space/core repository -echo "📋 Checking Forge-Space/core repository..." -gh api repos/Forge-Space/core || { - echo "❌ Cannot access Forge-Space/core repository" - exit 1 -} - -# Check reusable workflows -echo "📋 Checking reusable workflows..." -WORKFLOWS=("ci-base" "security-scan" "branch-protection" "dependency-management" "release-publish") - -for workflow in "${WORKFLOWS[@]}"; do - echo " Checking $workflow.yml..." - gh api repos/Forge-Space/core/contents/.github/workflows/reusable/$workflow.yml || { - echo "❌ Missing workflow: $workflow.yml" - exit 1 - } -done - -# Check workflow_call trigger -echo "📋 Checking workflow_call triggers..." -for workflow in "${WORKFLOWS[@]}"; do - if gh api repos/Forge-Space/core/contents/.github/workflows/reusable/$workflow.yml | grep -q "workflow_call:"; then - echo " ✅ $workflow.yml has workflow_call trigger" - else - echo " ❌ $workflow.yml missing workflow_call trigger" - fi -done - -# Test repository access -echo "📋 Testing repository access..." -TEST_REPOS=("mcp-gateway" "uiforge-mcp" "uiforge-webapp") - -for repo in "${TEST_REPOS[@]}"; do - echo " Testing $repo..." - if gh api repos/Forge-Space/$repo/actions/permissions; then - echo " ✅ $repo has Actions permissions" - else - echo " ❌ $repo missing Actions permissions" - fi -done - -echo "✅ Validation complete!" -``` - -## 🔧 Step 9: Create Documentation - -### Update Repository READMEs - -Update each repository's README.md to include workflow integration instructions: - -````markdown -## 🔄 GitHub Actions Integration - -This repository uses Forge-Space/core reusable workflows for CI/CD. - -### Quick Start - -1. Ensure your repository has Actions permissions -2. Update `.github/workflows/ci.yml`: - ```yaml - jobs: - ci: - uses: Forge-Space/core/.github/workflows/reusable/ci-base.yml@main - with: - project-type: 'gateway' - node-version: '22' - python-version: '3.12' - ``` -```` - -3. Remove any duplicated workflow files -4. Test the workflow integration - -### Documentation - -- [Workflow Optimization Guide](https://github.com/Forge-Space/core/blob/main/docs/guides/github-workflows-optimization.md) -- [Reusable Workflow Reference](https://github.com/Forge-Space/core/blob/main/docs/guides/reusable-workflows-reference.md) -- [Troubleshooting Guide](https://github.com/Forge-Space/core/blob/main/docs/guides/workflow-integration-troubleshooting.md) - +bash -n .github/workflows/ci.yml +bash -n .github/workflows/security-nightly.yml ``` -## 🔧 Step 10: Training and Onboarding - -### Team Training Materials - -Create training materials for team members: - -1. **Workflow Overview Presentation** - - Explain benefits of centralized workflows - - Show before/after comparison - - Demonstrate integration process - -2. **Hands-On Workshop** - - Practice workflow integration - - Troubleshooting common issues - - Best practices and tips - -3. **Documentation Access** - - Link to all relevant guides - - Create quick reference cards - - Set up help channels - -### Communication Channels - -Set up communication channels for support: - -1. **Slack/Teams Channel**: `#github-workflows` -2. **Email Distribution List**: workflows@forge-space.com -3. **Regular Meetings**: Monthly workflow review sessions - -## 🔍 Troubleshooting Common Issues - -### Issue: Repository Not Found - -**Error**: `Repository not found: Forge-Space/core` - -**Solution**: -1. Verify organization name is correct: `Forge-Space` -2. Check repository exists: `gh api repos/Forge-Space/core` -3. Ensure you have access permissions +Run a PR and confirm: -### Issue: Workflow Not Found +- baseline jobs run every time +- heavy jobs skip when `degrade_mode=true` +- workflow summary includes budget mode status -**Error**: `Workflow not found: .github/workflows/reusable/ci-base.yml` +## Related Guide -**Solution**: -1. Check workflow path: `gh api repos/Forge-Space/core/contents/.github/workflows/reusable` -2. Verify workflow exists: `gh api repos/Forge-Space/core/contents/.github/workflows/reusable/ci-base.yml` -3. Check workflow has `workflow_call:` trigger - -### Issue: Permission Denied - -**Error**: `Permission denied: cannot access Forge-Space/core` - -**Solution**: -1. Configure Forge-Space/core repository access -2. Check organization membership: `gh api orgs/Forge-Space/memberships/your-username` -3. Verify Actions permissions in your repository - -### Issue: Workflow Timeout - -**Error**: `The job exceeded the maximum timeout` - -**Solution**: -1. Increase timeout in workflow call -2. Optimize workflow performance -3. Check for infinite loops or long-running processes - -## 📊 Success Metrics - -### Configuration Completion Checklist - -- [ ] Forge-Space/core repository configured for organization access -- [ ] Target repositories have Actions permissions enabled -- [ ] Organization policies configured for workflow sharing -- [ ] Secrets configured for shared tools -- [ ] Self-hosted runners configured (if needed) -- [ ] Monitoring and notifications set up -- [ ] Documentation updated -- [ ] Team training completed -- [ ] Validation tests passing - -### Performance Metrics - -- **Workflow Success Rate**: Target >95% -- **Average Execution Time**: Monitor and optimize -- **Repository Coverage**: All target repositories using centralized workflows -- **Maintenance Reduction**: Measure 95% reduction goal - -## 🚀 Next Steps - -### Immediate Actions - -1. **Complete Configuration**: Finish all setup steps -2. **Test Integration**: Validate workflow access in all repositories -3. **Update Documentation**: Ensure all repositories have updated READMEs -4. **Train Team**: Conduct training sessions - -### Ongoing Maintenance - -1. **Monthly Review**: Review organization configuration -2. **Quarterly Audit**: Audit workflow usage and permissions -3. **Annual Update**: Update to latest workflow versions -4. **Continuous Improvement**: Gather feedback and optimize processes - -## 📞 Support - -### Getting Help - -- **GitHub Issues**: Create issues on [Forge-Space/core](https://github.com/Forge-Space/core/issues) -- **Discussions**: Start discussions in [Forge-Space/core](https://github.com/Forge-Space/core/discussions) -- **Documentation**: Review all available guides in Forge-Space/core -- **Community**: Join the Forge-Space community for support - -### Emergency Contacts - -- **Organization Admin**: For permission and access issues -- **Repository Admin**: For repository-specific problems -- **DevOps Team**: For workflow and CI/CD issues - ---- - -## 📚 Additional Resources - -- [GitHub Actions Documentation](https://docs.github.com/en/actions) -- [Reusable Workflows Guide](https://docs.github.com/en/actions/using-workflows/reusing-workflows) -- [Organization Settings](https://docs.github.com/en/organizations/managing-organizations/settings-for-organizations) -- [GitHub CLI Documentation](https://cli.github.com/manual/) - ---- - -*Last updated: February 2026* -``` +- [Actions Limits Strategy](./actions-limits-strategy.md) diff --git a/scripts/bootstrap/actions-org-setup.sh b/scripts/bootstrap/actions-org-setup.sh new file mode 100755 index 0000000..8dccd83 --- /dev/null +++ b/scripts/bootstrap/actions-org-setup.sh @@ -0,0 +1,213 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat << USAGE +Usage: $0 --org --actions-cap-minutes [options] + +Options: + --warn-pct <1-99> Warning threshold percentage (default: 70) + --degrade-pct <1-100> Degrade threshold percentage (default: 85) + --repo Target repo variable scope (repeatable) + --dry-run Print actions without writing variables + -h, --help Show this help +USAGE +} + +is_positive_integer() { + [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -gt 0 ] +} + +is_percentage() { + [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 100 ] +} + +require_commands() { + local missing=() + for cmd in gh jq; do + if ! command -v "$cmd" >/dev/null 2>&1; then + missing+=("$cmd") + fi + done + + if [ ${#missing[@]} -gt 0 ]; then + echo "❌ Missing required commands: ${missing[*]}" + exit 1 + fi +} + +ORG="" +ACTIONS_CAP_MINUTES="" +WARN_PCT="70" +DEGRADE_PCT="85" +DRY_RUN="false" +declare -a REPOS=() + +while [ $# -gt 0 ]; do + case "$1" in + --org) + ORG="${2:-}" + shift 2 + ;; + --actions-cap-minutes) + ACTIONS_CAP_MINUTES="${2:-}" + shift 2 + ;; + --warn-pct) + WARN_PCT="${2:-}" + shift 2 + ;; + --degrade-pct) + DEGRADE_PCT="${2:-}" + shift 2 + ;; + --repo) + REPOS+=("${2:-}") + shift 2 + ;; + --dry-run) + DRY_RUN="true" + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "❌ Unknown option: $1" + usage + exit 1 + ;; + esac +done + +if [ -z "$ORG" ]; then + echo "❌ --org is required" + usage + exit 1 +fi + +if ! is_positive_integer "$ACTIONS_CAP_MINUTES"; then + echo "❌ --actions-cap-minutes must be a positive integer" + exit 1 +fi + +if ! is_percentage "$WARN_PCT" || ! is_percentage "$DEGRADE_PCT"; then + echo "❌ --warn-pct and --degrade-pct must be between 1 and 100" + exit 1 +fi + +if [ "$WARN_PCT" -ge "$DEGRADE_PCT" ]; then + echo "❌ --warn-pct must be lower than --degrade-pct" + exit 1 +fi + +if [ ${#REPOS[@]} -eq 0 ]; then + REPOS+=("${ORG}/.github") +fi + +require_commands + +if ! gh auth status >/dev/null 2>&1; then + echo "❌ GitHub CLI is not authenticated. Run: gh auth login" + exit 1 +fi + +echo "🔎 Validating Actions settings for org: $ORG" + +if ! gh api "orgs/${ORG}/actions/permissions" >/dev/null 2>&1; then + echo "❌ Failed to read org Actions permissions for ${ORG}" + exit 1 +fi + +echo "✅ Org Actions permissions endpoint is reachable" + +if gh api "repos/${ORG}/.github/actions/permissions" >/dev/null 2>&1; then + echo "✅ ${ORG}/.github Actions permissions are reachable" +else + echo "⚠️ Could not read ${ORG}/.github Actions permissions" +fi + +if access_json=$(gh api "repos/${ORG}/.github/actions/permissions/access" 2>/dev/null); then + access_level=$(jq -r '.access_level // "unknown"' <<< "$access_json") + echo "ℹ️ Reusable workflow access for ${ORG}/.github: ${access_level}" +else + echo "⚠️ Could not read reusable workflow access for ${ORG}/.github" +fi + +if usage_json=$(gh api "orgs/${ORG}/settings/billing/usage" 2>/dev/null); then + month=$(date -u +%Y-%m) + used_minutes=$(jq -r --arg month "$month" ' + [ + (.usageItems // [])[] + | select(.product == "actions") + | select(.unitType == "Minutes") + | select((.date // "") | startswith($month)) + | (.quantity // 0) + ] | add // 0 + ' <<< "$usage_json") + echo "ℹ️ Current month Actions minutes (UTC): $used_minutes" +else + echo "⚠️ Failed to query billing usage endpoint (continuing)." +fi + +upsert_org_var() { + local name="$1" + local value="$2" + if [ "$DRY_RUN" = "true" ]; then + echo "[dry-run] org var ${name}=${value}" + return + fi + + if gh api "orgs/${ORG}/actions/variables/${name}" >/dev/null 2>&1; then + gh api --method PATCH "orgs/${ORG}/actions/variables/${name}" \ + -f name="$name" -f value="$value" >/dev/null + else + gh api --method POST "orgs/${ORG}/actions/variables" \ + -f name="$name" -f value="$value" >/dev/null + fi +} + +upsert_repo_var() { + local repo="$1" + local name="$2" + local value="$3" + if [ "$DRY_RUN" = "true" ]; then + echo "[dry-run] repo var ${repo} ${name}=${value}" + return + fi + + if gh api "repos/${repo}/actions/variables/${name}" >/dev/null 2>&1; then + gh api --method PATCH "repos/${repo}/actions/variables/${name}" \ + -f name="$name" -f value="$value" >/dev/null + else + gh api --method POST "repos/${repo}/actions/variables" \ + -f name="$name" -f value="$value" >/dev/null + fi +} + +echo "🛠️ Writing Actions budget variables" + +upsert_org_var "ACTIONS_MONTHLY_CAP_MINUTES" "$ACTIONS_CAP_MINUTES" +upsert_org_var "ACTIONS_WARN_PCT" "$WARN_PCT" +upsert_org_var "ACTIONS_DEGRADE_PCT" "$DEGRADE_PCT" + +for repo in "${REPOS[@]}"; do + if [[ ! "$repo" =~ ^[^/]+/[^/]+$ ]]; then + echo "⚠️ Skipping invalid repo slug: $repo" + continue + fi + + if gh api "repos/${repo}" >/dev/null 2>&1 || [ "$DRY_RUN" = "true" ]; then + upsert_repo_var "$repo" "ACTIONS_MONTHLY_CAP_MINUTES" "$ACTIONS_CAP_MINUTES" + upsert_repo_var "$repo" "ACTIONS_WARN_PCT" "$WARN_PCT" + upsert_repo_var "$repo" "ACTIONS_DEGRADE_PCT" "$DEGRADE_PCT" + else + echo "⚠️ Skipping unavailable repo: $repo" + fi +done + +echo "✅ Actions limit-aware setup complete for ${ORG}." +if [ "$DRY_RUN" = "true" ]; then + echo "ℹ️ Dry-run mode: no variables were changed." +fi diff --git a/scripts/bootstrap/project.sh b/scripts/bootstrap/project.sh old mode 100644 new mode 100755 index 3404f1f..a78d05d --- a/scripts/bootstrap/project.sh +++ b/scripts/bootstrap/project.sh @@ -2,19 +2,182 @@ # scripts/bootstrap/project.sh set -euo pipefail -echo "🚀 Bootstrapping new UIForge project..." +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +PATTERNS_DIR="${REPO_ROOT}/patterns" +TEMPLATE_DIR="${SCRIPT_DIR}/templates/workflows/limit-aware" + +usage() { + cat << EOF +Usage: $0 [project-type] [options] + +Project types: + node | nextjs | python + +Options: + --ci-profile CI profile (default: limit-aware) + --org Required when ci-profile is limit-aware + --actions-cap-minutes Required when ci-profile is limit-aware + --actions-warn-pct <1-99> Default: 70 + --actions-degrade-pct <1-100> Default: 85 + -h, --help Show this help message +EOF +} -PROJECT_NAME=$1 -PROJECT_TYPE=${2:-"node"} +is_positive_integer() { + [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -gt 0 ] +} + +is_percentage() { + [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 100 ] +} + +render_limit_aware_workflow() { + local template_file="$1" + local output_file="$2" + + sed \ + -e "s|__ORG__|${ORG}|g" \ + -e "s|__PROJECT_NAME__|${PROJECT_NAME}|g" \ + -e "s|__ACTIONS_MONTHLY_CAP_MINUTES__|${ACTIONS_CAP_MINUTES}|g" \ + -e "s|__ACTIONS_WARN_PCT__|${ACTIONS_WARN_PCT}|g" \ + -e "s|__ACTIONS_DEGRADE_PCT__|${ACTIONS_DEGRADE_PCT}|g" \ + "$template_file" > "$output_file" +} + +generate_limit_aware_workflows() { + if [ "$CI_PROFILE" != "limit-aware" ]; then + return + fi + + local ci_template + local nightly_template + case "$PROJECT_TYPE" in + node) + ci_template="${TEMPLATE_DIR}/ci-node.yml.tpl" + nightly_template="${TEMPLATE_DIR}/security-nightly-node.yml.tpl" + ;; + nextjs) + ci_template="${TEMPLATE_DIR}/ci-nextjs.yml.tpl" + nightly_template="${TEMPLATE_DIR}/security-nightly-nextjs.yml.tpl" + ;; + python) + ci_template="${TEMPLATE_DIR}/ci-python.yml.tpl" + nightly_template="${TEMPLATE_DIR}/security-nightly-python.yml.tpl" + ;; + *) + echo "❌ Unsupported project type for limit-aware CI profile: $PROJECT_TYPE" + exit 1 + ;; + esac + + if [ ! -f "$ci_template" ] || [ ! -f "$nightly_template" ]; then + echo "❌ Missing workflow templates for project type '$PROJECT_TYPE' in $TEMPLATE_DIR" + exit 1 + fi + + mkdir -p .github/workflows + render_limit_aware_workflow "$ci_template" .github/workflows/ci.yml + render_limit_aware_workflow "$nightly_template" .github/workflows/security-nightly.yml +} + +PROJECT_NAME="" +PROJECT_TYPE="node" +CI_PROFILE="limit-aware" +ORG="" +ACTIONS_CAP_MINUTES="" +ACTIONS_WARN_PCT="70" +ACTIONS_DEGRADE_PCT="85" + +while [ $# -gt 0 ]; do + case "$1" in + --ci-profile) + CI_PROFILE="${2:-}" + shift 2 + ;; + --org) + ORG="${2:-}" + shift 2 + ;; + --actions-cap-minutes) + ACTIONS_CAP_MINUTES="${2:-}" + shift 2 + ;; + --actions-warn-pct) + ACTIONS_WARN_PCT="${2:-}" + shift 2 + ;; + --actions-degrade-pct) + ACTIONS_DEGRADE_PCT="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + --*) + echo "❌ Unknown option: $1" + usage + exit 1 + ;; + *) + if [ -z "$PROJECT_NAME" ]; then + PROJECT_NAME="$1" + elif [ "$PROJECT_TYPE" = "node" ] && [[ "$1" =~ ^(node|nextjs|python)$ ]]; then + PROJECT_TYPE="$1" + else + echo "❌ Unexpected argument: $1" + usage + exit 1 + fi + shift + ;; + esac +done if [ -z "$PROJECT_NAME" ]; then echo "❌ Project name is required" - echo "Usage: $0 [project-type]" - echo "Project types: node, python, nextjs" + usage + exit 1 +fi + +if ! [[ "$PROJECT_TYPE" =~ ^(node|nextjs|python)$ ]]; then + echo "❌ Invalid project type: $PROJECT_TYPE" + usage + exit 1 +fi + +if ! [[ "$CI_PROFILE" =~ ^(limit-aware|legacy)$ ]]; then + echo "❌ Invalid ci profile: $CI_PROFILE" + usage exit 1 fi -echo "Creating project: $PROJECT_NAME (type: $PROJECT_TYPE)" +if [ "$CI_PROFILE" = "limit-aware" ]; then + if [ -z "$ORG" ]; then + echo "❌ --org is required when --ci-profile=limit-aware" + exit 1 + fi + if ! is_positive_integer "$ACTIONS_CAP_MINUTES"; then + echo "❌ --actions-cap-minutes must be a positive integer" + exit 1 + fi + if ! is_percentage "$ACTIONS_WARN_PCT"; then + echo "❌ --actions-warn-pct must be between 1 and 100" + exit 1 + fi + if ! is_percentage "$ACTIONS_DEGRADE_PCT"; then + echo "❌ --actions-degrade-pct must be between 1 and 100" + exit 1 + fi + if [ "$ACTIONS_WARN_PCT" -ge "$ACTIONS_DEGRADE_PCT" ]; then + echo "❌ --actions-warn-pct must be lower than --actions-degrade-pct" + exit 1 + fi +fi + +echo "🚀 Bootstrapping new UIForge project..." +echo "Creating project: $PROJECT_NAME (type: $PROJECT_TYPE, ci-profile: $CI_PROFILE)" # Create project directory mkdir -p "$PROJECT_NAME" @@ -31,41 +194,68 @@ echo "📋 Copying patterns from uiforge-patterns..." # Copy ESLint config if [ "$PROJECT_TYPE" = "node" ] || [ "$PROJECT_TYPE" = "nextjs" ]; then - cp ../patterns/code-quality/eslint/base.config.js .eslintrc.js - cp ../patterns/code-quality/prettier/base.config.json .prettierrc.json + cp "${PATTERNS_DIR}/code-quality/eslint/base.config.js" .eslintrc.js + cp "${PATTERNS_DIR}/code-quality/prettier/base.config.json" .prettierrc.json fi # Copy Jest config for TypeScript projects if [ "$PROJECT_TYPE" = "node" ] || [ "$PROJECT_TYPE" = "nextjs" ]; then - cp ../patterns/coverage/jest.config.template.js jest.config.js + if [ -f "${PATTERNS_DIR}/coverage/jest.config.template.js" ]; then + cp "${PATTERNS_DIR}/coverage/jest.config.template.js" jest.config.js + else + cat > jest.config.js << 'EOF' +module.exports = { + testEnvironment: 'node', + transform: { + '^.+\\.tsx?$': ['ts-jest', {}], + }, + testMatch: ['**/__tests__/**/*.test.ts'], +}; +EOF + fi fi # Copy Codecov config -cp ../patterns/coverage/codecov.template.yml .codecov.yml +if [ -f "${PATTERNS_DIR}/coverage/codecov.template.yml" ]; then + cp "${PATTERNS_DIR}/coverage/codecov.template.yml" .codecov.yml +else + cat > .codecov.yml << 'EOF' +coverage: + status: + project: + default: + target: 80% + threshold: 1% +EOF +fi # Copy Git hooks mkdir -p .git/hooks -cp ../patterns/git/pre-commit/base.sh .git/hooks/pre-commit -cp ../patterns/git/commit-msg/conventional.sh .git/hooks/commit-msg +cp "${PATTERNS_DIR}/git/pre-commit/base.sh" .git/hooks/pre-commit +cp "${PATTERNS_DIR}/git/commit-msg/conventional.sh" .git/hooks/commit-msg chmod +x .git/hooks/pre-commit .git/hooks/commit-msg # Copy Docker patterns echo "🐳 Adding Docker patterns..." -cp ../patterns/docker/Dockerfile.node.template Dockerfile -cp ../patterns/docker/docker-compose.dev.yml docker-compose.yml -cp ../patterns/docker/docker-compose.prod.yml docker-compose.prod.yml -cp ../patterns/docker/.dockerignore .dockerignore +cp "${PATTERNS_DIR}/docker/Dockerfile.node.template" Dockerfile +cp "${PATTERNS_DIR}/docker/docker-compose.dev.yml" docker-compose.yml +cp "${PATTERNS_DIR}/docker/docker-compose.prod.yml" docker-compose.prod.yml +cp "${PATTERNS_DIR}/docker/.dockerignore" .dockerignore # Copy centralized feature toggle patterns echo "🎛️ Adding centralized feature toggle patterns..." mkdir -p feature-toggles mkdir -p feature-toggles/libraries mkdir -p feature-toggles/libraries/nodejs -cp ../patterns/feature-toggles/README.md feature-toggles/ -cp ../patterns/feature-toggles/libraries/nodejs/index.js feature-toggles/libraries/nodejs/ 2>/dev/null || echo "# Feature toggle library will be added here" > feature-toggles/libraries/nodejs/index.js +cp "${PATTERNS_DIR}/feature-toggles/README.md" feature-toggles/ +cp "${PATTERNS_DIR}/feature-toggles/libraries/nodejs/index.js" \ + feature-toggles/libraries/nodejs/ 2>/dev/null || \ + echo "# Feature toggle library will be added here" > \ + feature-toggles/libraries/nodejs/index.js # Copy forge-features CLI tool -cp ../scripts/forge-features scripts/ 2>/dev/null || echo "# Forge Features CLI will be added here" > scripts/forge-features +cp "${REPO_ROOT}/scripts/forge-features" scripts/ 2>/dev/null || \ + echo "# Forge Features CLI will be added here" > scripts/forge-features chmod +x scripts/forge-features # Create package.json for Node.js projects @@ -100,6 +290,7 @@ if [ "$PROJECT_TYPE" = "node" ] || [ "$PROJECT_TYPE" = "nextjs" ]; then "eslint-config-prettier": "^9.0.0", "jest": "^29.5.0", "prettier": "^3.0.0", + "ts-jest": "^29.4.1", "tsx": "^4.0.0", "typescript": "^5.0.0" }, @@ -358,13 +549,17 @@ npm run dev:features EOF if [ "$PROJECT_TYPE" = "node" ] || [ "$PROJECT_TYPE" = "nextjs" ]; then - echo "- Node.js 22+" >> README.md - echo "- npm 9+" >> README.md - echo "- Docker & Docker Compose" >> README.md + { + echo "- Node.js 22+" + echo "- npm 9+" + echo "- Docker & Docker Compose" + } >> README.md elif [ "$PROJECT_TYPE" = "python" ]; then - echo "- Python 3.12+" >> README.md - echo "- pip" >> README.md - echo "- Docker & Docker Compose" >> README.md + { + echo "- Python 3.12+" + echo "- pip" + echo "- Docker & Docker Compose" + } >> README.md fi cat >> README.md << EOF @@ -477,11 +672,19 @@ def test_hello_custom(): EOF fi +generate_limit_aware_workflows + echo "✅ Project $PROJECT_NAME created successfully!" echo "" echo "Next steps:" echo "1. cd $PROJECT_NAME" echo "2. Install dependencies" echo "3. Run tests to verify setup" -echo "4. Start development with: docker-compose up -d" -echo "5. Happy coding! 🚀" +if [ "$CI_PROFILE" = "limit-aware" ]; then + echo "4. Add org variables with scripts/bootstrap/actions-org-setup.sh" + echo "5. Start development with: docker-compose up -d" + echo "6. Happy coding! 🚀" +else + echo "4. Start development with: docker-compose up -d" + echo "5. Happy coding! 🚀" +fi diff --git a/scripts/bootstrap/templates/workflows/limit-aware/ci-nextjs.yml.tpl b/scripts/bootstrap/templates/workflows/limit-aware/ci-nextjs.yml.tpl new file mode 100644 index 0000000..3500cb0 --- /dev/null +++ b/scripts/bootstrap/templates/workflows/limit-aware/ci-nextjs.yml.tpl @@ -0,0 +1,183 @@ +name: CI + +on: + push: + branches: [main, dev, 'release/*', 'feature/*', 'feat/*'] + paths-ignore: + - '**/*.md' + - 'docs/**' + - '.gitignore' + - 'LICENSE' + - '.editorconfig' + pull_request: + branches: [main, dev, 'release/*'] + paths-ignore: + - '**/*.md' + - 'docs/**' + - '.gitignore' + - 'LICENSE' + - '.editorconfig' + workflow_dispatch: + +permissions: + contents: read + security-events: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + actions-budget-guard: + name: Actions Budget Guard + uses: __ORG__/.github/.github/workflows/reusable-actions-budget-guard.yml@main + with: + org: '__ORG__' + monthly_cap_minutes: ${{ vars.ACTIONS_MONTHLY_CAP_MINUTES || '__ACTIONS_MONTHLY_CAP_MINUTES__' }} + warn_pct: ${{ vars.ACTIONS_WARN_PCT || '__ACTIONS_WARN_PCT__' }} + degrade_pct: ${{ vars.ACTIONS_DEGRADE_PCT || '__ACTIONS_DEGRADE_PCT__' }} + secrets: + github_token: ${{ secrets.GITHUB_TOKEN }} + + lint: + name: Lint + runs-on: ubuntu-latest + needs: [actions-budget-guard] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + - run: npm ci + - run: npm run lint --if-present + + typecheck: + name: Type Check + runs-on: ubuntu-latest + needs: [actions-budget-guard] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + - run: npm ci + - run: npm run type-check --if-present + + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + needs: [actions-budget-guard] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + - run: npm ci + - run: npm test --if-present + + build: + name: Build + runs-on: ubuntu-latest + needs: [actions-budget-guard] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + - run: npm ci + - run: npm run build --if-present + + secret-scan: + name: Secret Scan + needs: [actions-budget-guard] + uses: __ORG__/.github/.github/workflows/reusable-secret-scan.yml@main + + docker-build: + name: Docker Build + continue-on-error: true + runs-on: ubuntu-latest + needs: [actions-budget-guard] + if: needs.actions-budget-guard.outputs.degrade_mode != 'true' && github.actor != 'dependabot[bot]' + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - name: Build image when Dockerfile exists + run: | + if [ -f Dockerfile ]; then + docker build -t __PROJECT_NAME__:ci . + else + echo "No Dockerfile found, skipping Docker build." + fi + + e2e: + name: E2E + continue-on-error: true + runs-on: ubuntu-latest + needs: [actions-budget-guard] + if: needs.actions-budget-guard.outputs.degrade_mode != 'true' && github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + - run: npm ci + - name: Run E2E tests when available + run: | + if npm run | grep -q "test:e2e"; then + npm run test:e2e + elif npm run | grep -q "e2e"; then + npm run e2e + else + echo "No E2E script configured" + fi + + semgrep: + name: Semgrep Scan + continue-on-error: true + if: needs.actions-budget-guard.outputs.degrade_mode != 'true' + needs: [actions-budget-guard] + uses: __ORG__/.github/.github/workflows/reusable-semgrep.yml@main + + trivy: + name: Trivy Scan + continue-on-error: true + if: needs.actions-budget-guard.outputs.degrade_mode != 'true' + needs: [actions-budget-guard] + uses: __ORG__/.github/.github/workflows/reusable-trivy.yml@main + with: + severity: HIGH,CRITICAL + scan-type: fs + + codeql-pr: + name: CodeQL (PR) + continue-on-error: true + runs-on: ubuntu-latest + needs: [actions-budget-guard] + if: github.event_name == 'pull_request' && needs.actions-budget-guard.outputs.degrade_mode != 'true' + steps: + - uses: actions/checkout@v4 + - uses: github/codeql-action/init@v3 + with: + languages: javascript-typescript + - uses: github/codeql-action/autobuild@v3 + - uses: github/codeql-action/analyze@v3 + + budget-summary: + name: Budget Summary + runs-on: ubuntu-latest + needs: [actions-budget-guard] + if: always() + steps: + - run: | + echo "${{ needs.actions-budget-guard.outputs.summary }}" >> "$GITHUB_STEP_SUMMARY" + if [ "${{ needs.actions-budget-guard.outputs.warn_mode }}" = "true" ]; then + echo "Budget guard warning mode is active." >> "$GITHUB_STEP_SUMMARY" + fi + if [ "${{ needs.actions-budget-guard.outputs.degrade_mode }}" = "true" ]; then + echo "Budget guard degrade mode is active; heavy jobs are intentionally skipped." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/scripts/bootstrap/templates/workflows/limit-aware/ci-node.yml.tpl b/scripts/bootstrap/templates/workflows/limit-aware/ci-node.yml.tpl new file mode 100644 index 0000000..5c3f493 --- /dev/null +++ b/scripts/bootstrap/templates/workflows/limit-aware/ci-node.yml.tpl @@ -0,0 +1,179 @@ +name: CI + +on: + push: + branches: [main, dev, 'release/*', 'feature/*', 'feat/*'] + paths-ignore: + - '**/*.md' + - 'docs/**' + - '.gitignore' + - 'LICENSE' + - '.editorconfig' + pull_request: + branches: [main, dev, 'release/*'] + paths-ignore: + - '**/*.md' + - 'docs/**' + - '.gitignore' + - 'LICENSE' + - '.editorconfig' + workflow_dispatch: + +permissions: + contents: read + security-events: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + actions-budget-guard: + name: Actions Budget Guard + uses: __ORG__/.github/.github/workflows/reusable-actions-budget-guard.yml@main + with: + org: '__ORG__' + monthly_cap_minutes: ${{ vars.ACTIONS_MONTHLY_CAP_MINUTES || '__ACTIONS_MONTHLY_CAP_MINUTES__' }} + warn_pct: ${{ vars.ACTIONS_WARN_PCT || '__ACTIONS_WARN_PCT__' }} + degrade_pct: ${{ vars.ACTIONS_DEGRADE_PCT || '__ACTIONS_DEGRADE_PCT__' }} + secrets: + github_token: ${{ secrets.GITHUB_TOKEN }} + + lint: + name: Lint + runs-on: ubuntu-latest + needs: [actions-budget-guard] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + - run: npm ci + - run: npm run lint + + typecheck: + name: Type Check + runs-on: ubuntu-latest + needs: [actions-budget-guard] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + - run: npm ci + - run: npm run type-check + + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + needs: [actions-budget-guard] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + - run: npm ci + - run: npm test + + build: + name: Build + runs-on: ubuntu-latest + needs: [actions-budget-guard] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + - run: npm ci + - run: npm run build + + secret-scan: + name: Secret Scan + needs: [actions-budget-guard] + uses: __ORG__/.github/.github/workflows/reusable-secret-scan.yml@main + + docker-build: + name: Docker Build + continue-on-error: true + runs-on: ubuntu-latest + needs: [actions-budget-guard] + if: needs.actions-budget-guard.outputs.degrade_mode != 'true' && github.actor != 'dependabot[bot]' + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/build-push-action@v6 + with: + context: . + push: false + tags: __PROJECT_NAME__:ci + + e2e: + name: E2E + continue-on-error: true + runs-on: ubuntu-latest + needs: [actions-budget-guard] + if: needs.actions-budget-guard.outputs.degrade_mode != 'true' && github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + - run: npm ci + - name: Run E2E tests when available + run: | + if npm run | grep -q "test:e2e"; then + npm run test:e2e + else + echo "No test:e2e script configured" + fi + + semgrep: + name: Semgrep Scan + continue-on-error: true + if: needs.actions-budget-guard.outputs.degrade_mode != 'true' + needs: [actions-budget-guard] + uses: __ORG__/.github/.github/workflows/reusable-semgrep.yml@main + + trivy: + name: Trivy Scan + continue-on-error: true + if: needs.actions-budget-guard.outputs.degrade_mode != 'true' + needs: [actions-budget-guard] + uses: __ORG__/.github/.github/workflows/reusable-trivy.yml@main + with: + severity: HIGH,CRITICAL + scan-type: fs + + codeql-pr: + name: CodeQL (PR) + continue-on-error: true + runs-on: ubuntu-latest + needs: [actions-budget-guard] + if: github.event_name == 'pull_request' && needs.actions-budget-guard.outputs.degrade_mode != 'true' + steps: + - uses: actions/checkout@v4 + - uses: github/codeql-action/init@v3 + with: + languages: javascript-typescript + - uses: github/codeql-action/autobuild@v3 + - uses: github/codeql-action/analyze@v3 + + budget-summary: + name: Budget Summary + runs-on: ubuntu-latest + needs: [actions-budget-guard] + if: always() + steps: + - run: | + echo "${{ needs.actions-budget-guard.outputs.summary }}" >> "$GITHUB_STEP_SUMMARY" + if [ "${{ needs.actions-budget-guard.outputs.warn_mode }}" = "true" ]; then + echo "Budget guard warning mode is active." >> "$GITHUB_STEP_SUMMARY" + fi + if [ "${{ needs.actions-budget-guard.outputs.degrade_mode }}" = "true" ]; then + echo "Budget guard degrade mode is active; heavy jobs are intentionally skipped." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/scripts/bootstrap/templates/workflows/limit-aware/ci-python.yml.tpl b/scripts/bootstrap/templates/workflows/limit-aware/ci-python.yml.tpl new file mode 100644 index 0000000..3845f6f --- /dev/null +++ b/scripts/bootstrap/templates/workflows/limit-aware/ci-python.yml.tpl @@ -0,0 +1,180 @@ +name: CI + +on: + push: + branches: [main, dev, 'release/*', 'feature/*', 'feat/*'] + paths-ignore: + - '**/*.md' + - 'docs/**' + - '.gitignore' + - 'LICENSE' + - '.editorconfig' + pull_request: + branches: [main, dev, 'release/*'] + paths-ignore: + - '**/*.md' + - 'docs/**' + - '.gitignore' + - 'LICENSE' + - '.editorconfig' + workflow_dispatch: + +permissions: + contents: read + security-events: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + actions-budget-guard: + name: Actions Budget Guard + uses: __ORG__/.github/.github/workflows/reusable-actions-budget-guard.yml@main + with: + org: '__ORG__' + monthly_cap_minutes: ${{ vars.ACTIONS_MONTHLY_CAP_MINUTES || '__ACTIONS_MONTHLY_CAP_MINUTES__' }} + warn_pct: ${{ vars.ACTIONS_WARN_PCT || '__ACTIONS_WARN_PCT__' }} + degrade_pct: ${{ vars.ACTIONS_DEGRADE_PCT || '__ACTIONS_DEGRADE_PCT__' }} + secrets: + github_token: ${{ secrets.GITHUB_TOKEN }} + + lint: + name: Lint + runs-on: ubuntu-latest + needs: [actions-budget-guard] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: python -m pip install --upgrade pip + - run: python -m pip install -e .[dev] + - run: ruff check . + + typecheck: + name: Type Check + runs-on: ubuntu-latest + needs: [actions-budget-guard] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: python -m pip install --upgrade pip + - run: python -m pip install -e .[dev] + - run: mypy src + + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + needs: [actions-budget-guard] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: python -m pip install --upgrade pip + - run: python -m pip install -e .[dev] + - run: pytest -q + + build: + name: Build + runs-on: ubuntu-latest + needs: [actions-budget-guard] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: python -m pip install --upgrade pip + - run: python -m pip install build + - run: python -m build + + secret-scan: + name: Secret Scan + needs: [actions-budget-guard] + uses: __ORG__/.github/.github/workflows/reusable-secret-scan.yml@main + + docker-build: + name: Docker Build + continue-on-error: true + runs-on: ubuntu-latest + needs: [actions-budget-guard] + if: needs.actions-budget-guard.outputs.degrade_mode != 'true' && github.actor != 'dependabot[bot]' + steps: + - uses: actions/checkout@v4 + - name: Build image when Dockerfile exists + run: | + if [ -f Dockerfile ]; then + docker build -t __PROJECT_NAME__:ci . + else + echo "No Dockerfile found, skipping Docker build." + fi + + e2e: + name: E2E + continue-on-error: true + runs-on: ubuntu-latest + needs: [actions-budget-guard] + if: needs.actions-budget-guard.outputs.degrade_mode != 'true' && github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: python -m pip install --upgrade pip + - run: python -m pip install -e .[dev] + - name: Run E2E tests when available + run: | + if [ -f tests/e2e/test_e2e.py ]; then + pytest tests/e2e -q + else + echo "No Python E2E test suite configured" + fi + + semgrep: + name: Semgrep Scan + continue-on-error: true + if: needs.actions-budget-guard.outputs.degrade_mode != 'true' + needs: [actions-budget-guard] + uses: __ORG__/.github/.github/workflows/reusable-semgrep.yml@main + + trivy: + name: Trivy Scan + continue-on-error: true + if: needs.actions-budget-guard.outputs.degrade_mode != 'true' + needs: [actions-budget-guard] + uses: __ORG__/.github/.github/workflows/reusable-trivy.yml@main + with: + severity: HIGH,CRITICAL + scan-type: fs + + codeql-pr: + name: CodeQL (PR) + continue-on-error: true + runs-on: ubuntu-latest + needs: [actions-budget-guard] + if: github.event_name == 'pull_request' && needs.actions-budget-guard.outputs.degrade_mode != 'true' + steps: + - uses: actions/checkout@v4 + - uses: github/codeql-action/init@v3 + with: + languages: python + - uses: github/codeql-action/autobuild@v3 + - uses: github/codeql-action/analyze@v3 + + budget-summary: + name: Budget Summary + runs-on: ubuntu-latest + needs: [actions-budget-guard] + if: always() + steps: + - run: | + echo "${{ needs.actions-budget-guard.outputs.summary }}" >> "$GITHUB_STEP_SUMMARY" + if [ "${{ needs.actions-budget-guard.outputs.warn_mode }}" = "true" ]; then + echo "Budget guard warning mode is active." >> "$GITHUB_STEP_SUMMARY" + fi + if [ "${{ needs.actions-budget-guard.outputs.degrade_mode }}" = "true" ]; then + echo "Budget guard degrade mode is active; heavy jobs are intentionally skipped." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/scripts/bootstrap/templates/workflows/limit-aware/security-nightly-nextjs.yml.tpl b/scripts/bootstrap/templates/workflows/limit-aware/security-nightly-nextjs.yml.tpl new file mode 100644 index 0000000..6a2c3fe --- /dev/null +++ b/scripts/bootstrap/templates/workflows/limit-aware/security-nightly-nextjs.yml.tpl @@ -0,0 +1,37 @@ +name: Security Nightly + +on: + schedule: + - cron: '30 3 * * *' + workflow_dispatch: + +permissions: + contents: read + security-events: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + semgrep: + name: Semgrep Scan + uses: __ORG__/.github/.github/workflows/reusable-semgrep.yml@main + + trivy: + name: Trivy Scan + uses: __ORG__/.github/.github/workflows/reusable-trivy.yml@main + with: + severity: HIGH,CRITICAL + scan-type: fs + + codeql-nightly: + name: CodeQL Nightly + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: github/codeql-action/init@v3 + with: + languages: javascript-typescript + - uses: github/codeql-action/autobuild@v3 + - uses: github/codeql-action/analyze@v3 diff --git a/scripts/bootstrap/templates/workflows/limit-aware/security-nightly-node.yml.tpl b/scripts/bootstrap/templates/workflows/limit-aware/security-nightly-node.yml.tpl new file mode 100644 index 0000000..6a2c3fe --- /dev/null +++ b/scripts/bootstrap/templates/workflows/limit-aware/security-nightly-node.yml.tpl @@ -0,0 +1,37 @@ +name: Security Nightly + +on: + schedule: + - cron: '30 3 * * *' + workflow_dispatch: + +permissions: + contents: read + security-events: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + semgrep: + name: Semgrep Scan + uses: __ORG__/.github/.github/workflows/reusable-semgrep.yml@main + + trivy: + name: Trivy Scan + uses: __ORG__/.github/.github/workflows/reusable-trivy.yml@main + with: + severity: HIGH,CRITICAL + scan-type: fs + + codeql-nightly: + name: CodeQL Nightly + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: github/codeql-action/init@v3 + with: + languages: javascript-typescript + - uses: github/codeql-action/autobuild@v3 + - uses: github/codeql-action/analyze@v3 diff --git a/scripts/bootstrap/templates/workflows/limit-aware/security-nightly-python.yml.tpl b/scripts/bootstrap/templates/workflows/limit-aware/security-nightly-python.yml.tpl new file mode 100644 index 0000000..6aa572e --- /dev/null +++ b/scripts/bootstrap/templates/workflows/limit-aware/security-nightly-python.yml.tpl @@ -0,0 +1,37 @@ +name: Security Nightly + +on: + schedule: + - cron: '30 3 * * *' + workflow_dispatch: + +permissions: + contents: read + security-events: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + semgrep: + name: Semgrep Scan + uses: __ORG__/.github/.github/workflows/reusable-semgrep.yml@main + + trivy: + name: Trivy Scan + uses: __ORG__/.github/.github/workflows/reusable-trivy.yml@main + with: + severity: HIGH,CRITICAL + scan-type: fs + + codeql-nightly: + name: CodeQL Nightly + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: github/codeql-action/init@v3 + with: + languages: python + - uses: github/codeql-action/autobuild@v3 + - uses: github/codeql-action/analyze@v3 From ac136d022cded72b73e9a3e54f2ec65fd1f16afd Mon Sep 17 00:00:00 2001 From: Lucas Santana Date: Wed, 11 Mar 2026 14:59:01 -0300 Subject: [PATCH 2/2] fix(docs): format organization setup guide --- docs/guides/organization-setup.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guides/organization-setup.md b/docs/guides/organization-setup.md index bc81b0e..71fe628 100644 --- a/docs/guides/organization-setup.md +++ b/docs/guides/organization-setup.md @@ -1,8 +1,8 @@ # Organization Setup Guide -This guide configures a new Forge-Space organization to consume reusable workflows -from `Forge-Space/.github` and enable limit-aware CI defaults for newly -bootstrapped projects. +This guide configures a new Forge-Space organization to consume reusable +workflows from `Forge-Space/.github` and enable limit-aware CI defaults for +newly bootstrapped projects. ## Scope