From d09253c0d96e14bef4f3f4f67a6804c3d940d5f4 Mon Sep 17 00:00:00 2001 From: Gabor Demeter Date: Sun, 12 Jul 2026 18:35:49 +0200 Subject: [PATCH 1/6] feat: replace inline CI/CD with thin callers of shared reusable workflows - pr-validate.yml -> sf-validate.yml@v1: delta check-only deploy with tests against the Dev Hub (quick-deploy handoff), Code Analyzer on changed files, scratch-org validation, sticky PR comment, audit artifact - deploy.yml -> sf-deploy.yml@v1: production GitHub Environment gate, quick deploy of the PR-validated request (delta/full fallbacks), GitHub Deployment record, audit artifact; workflow_dispatch full-deploy bootstrap - delete validate-pr.yml / deploy-staging.yml / deploy-production.yml; STAGING_AUTH_URL and PRODUCTION_AUTH_URL secrets are obsolete - docs/CICD.md: setup, mermaid pipeline diagram, quick-deploy mechanics, audit trail + 90-day retention caveat (S3 export as enterprise path) - align README, CONTRIBUTING and deployment-devops reference with the trunk-based, single-gate model Co-Authored-By: Claude Fable 5 --- .claude/references/deployment-devops.md | 156 ++++++++++++------------ .github/workflows/deploy-production.yml | 42 ------- .github/workflows/deploy-staging.yml | 41 ------- .github/workflows/deploy.yml | 31 +++++ .github/workflows/pr-validate.yml | 23 ++++ .github/workflows/validate-pr.yml | 86 ------------- CONTRIBUTING.md | 39 +++--- README.md | 14 +-- docs/CICD.md | 132 ++++++++++++++++++++ 9 files changed, 291 insertions(+), 273 deletions(-) delete mode 100644 .github/workflows/deploy-production.yml delete mode 100644 .github/workflows/deploy-staging.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/pr-validate.yml delete mode 100644 .github/workflows/validate-pr.yml create mode 100644 docs/CICD.md diff --git a/.claude/references/deployment-devops.md b/.claude/references/deployment-devops.md index 72a422e..73b39aa 100644 --- a/.claude/references/deployment-devops.md +++ b/.claude/references/deployment-devops.md @@ -1,21 +1,29 @@ # GForce Deployment & DevOps +The pipeline implementation lives in +[shared-github-actions](https://github.com/Gforce-Innovation-Kft/shared-github-actions) +(`sf-validate.yml` / `sf-deploy.yml` reusable workflows); this repo only ships +thin callers. Operational setup guide: [docs/CICD.md](../../docs/CICD.md). +If the pipeline needs something the shared workflows lack, raise a change +request there — never copy workflow logic into this repo. + --- -## 1. Branching Strategy +## 1. Branching Strategy (trunk-based) ``` -main ← production-ready; protected; requires PR + approval + CI pass - └── develop ← integration branch; auto-deploys to staging - └── feature/REQ-001-account-enhancements ← short-lived, per-ticket - └── feature/REQ-002-lwc-account-form - └── hotfix/critical-bug-fix ← branches from main, merges to main + develop +main ← protected by ruleset; every merge deploys via the production gate + └── feature/REQ-001-account-enhancements ← short-lived, per-ticket + └── feature/REQ-002-lwc-account-form + └── hotfix/critical-bug-fix ← same flow, expedited review ``` -- `main` → production (deploy-production.yml, manual approval gate) -- `develop` → staging (deploy-staging.yml, auto on push) -- `feature/*` → scratch org only (validated by validate-pr.yml on PR to develop) -- `hotfix/*` → branches from `main`, merges to both `main` and `develop` +- `feature/*` → PR to `main` → validated by `pr-validate.yml` (delta check-only + deploy + scratch org + Code Analyzer) +- merge to `main` → `deploy.yml` → **production GitHub Environment gate** + (required reviewer) → quick deploy of the PR-validated request +- No long-lived `develop` branch. Additional environments (integration/uat) + are added as extra gated jobs in `deploy.yml`, not extra branches. --- @@ -36,104 +44,90 @@ Always reference the REQ number when one exists. Every PR must: -- Pass CI (validate-pr.yml): scratch org creation + push + Apex tests + PMD/Code Analyzer +- Pass CI (`pr-validate.yml` → shared `sf-validate.yml@v1`): delta check-only + deploy with tests + scratch org validation + Code Analyzer +- Be **up to date with `main`** before merge (ruleset-enforced — this keeps the + validated deploy request identical to what merges, which is what makes quick + deploy safe) - Have a completed PULL_REQUEST_TEMPLATE.md checklist - Link to the relevant REQ-NNN.yaml or ticket -- Be reviewed by at least one other developer before merge +- Be reviewed (CODEOWNERS enforced) - Not contain `System.debug` calls, hardcoded IDs, or `SeeAllData=true` - Have test coverage ≥ 85% for changed classes -Squash merge into `develop`. No merge commits on `main`. +Squash or merge commits only — rebase-merges rewrite SHAs and break the +quick-deploy lookup (the fallback delta deploy covers it, but tests re-run). --- -## 4. Scratch Org Validation (CI) - -The `validate-pr.yml` workflow: +## 4. PR Validation (CI) -1. Authenticates to Dev Hub via `DEVHUB_AUTH_URL` secret -2. Creates a scratch org using `config/scratch-orgs/ci.json` -3. Pushes all source (TestDataFactory is source-tracked in force-app — no package install needed) -4. Runs all Apex tests (`sf apex run test --test-level RunLocalTests --result-format json`) -5. Runs Salesforce Code Analyzer (PMD rules) -6. **Always** deletes the scratch org (even on failure) — prevents org leaks +`pr-validate.yml` calls the shared `sf-validate.yml`, which: -Scratch org lifespan in CI: 1 day max. Always pass `--duration-days 1`. +1. Generates a delta package between the PR base and head (sfdx-git-delta) +2. Runs a **check-only deploy of the delta with tests against the Dev Hub** + (auth via `DEVHUB_AUTH_URL`) — the resulting validated deploy request is the + quick-deploy handle, shipped in the `sf-validate-` artifact +3. Runs Salesforce Code Analyzer on changed files +4. Creates a scratch org from `config/scratch-orgs/ci.json` (`--duration-days 1`), + pushes source, assigns permission sets, runs all local tests, and **always** + deletes the org +5. Posts a sticky summary comment on the PR --- -## 5. Staging Promotion +## 5. Deployment (promotion) -Push to `develop` triggers `deploy-staging.yml`: +Merge to `main` triggers `deploy.yml` → shared `sf-deploy.yml`: -1. Authenticates via `STAGING_AUTH_URL` secret -2. Runs `sf project deploy start --target-org staging` (source format) -3. Runs smoke tests (manual or automated Apex test subset) +1. Waits at the **`production` GitHub Environment** for required-reviewer + approval +2. Creates a GitHub Deployment record (audit trail, links the Salesforce + deploy-request page) +3. **Quick deploys** the PR-validated request (no tests re-run); falls back to + a delta deploy, then a full deploy of all package directories +4. Uploads the audit artifact: delta manifest, deploy result JSON, JUnit test + results, quick-deploy decision -Staging = always green. If a deploy breaks staging, roll forward (fix) not roll back. +Bootstrap/re-baseline: `gh workflow run deploy.yml -f full-deploy=true`. +Production deployments: business hours only (08:00–17:00 CET). No Friday +deployments. --- -## 6. Production Promotion - -Push to `main` triggers `deploy-production.yml`: +## 6. Secret & Variable Management -1. Requires manual approval (GitHub environment protection: `production`) -2. Authenticates via `PRODUCTION_AUTH_URL` secret -3. Runs `sf project deploy start --target-org production` -4. Posts Slack notification on success/failure +| Name | Kind | Used by | Value source | +| ----------------- | -------------------------------------------- | -------------- | ------------------------------------------------------------------------------------- | +| `DEVHUB_AUTH_URL` | Repo secret | both workflows | `sf org display --target-org --verbose --json \| jq -r '.result.sfdxAuthUrl'` | +| `SF_ORG_ALIAS` | `production` environment variable (optional) | deploy | CLI alias, defaults to the environment name | -Production deployments: business hours only (08:00–17:00 CET). No Friday deployments. +Never commit auth URLs. For real client projects, move the auth URL to an +**environment secret** behind the gate, one per target org. --- -## 7. Secret Management - -| Secret | Used in | Value source | -| --------------------- | --------------------- | --------------------------------------------------------- | -| `DEVHUB_AUTH_URL` | validate-pr.yml | `sf org display --target-org devhub --verbose --json` | -| `STAGING_AUTH_URL` | deploy-staging.yml | `sf org display --target-org staging --verbose --json` | -| `PRODUCTION_AUTH_URL` | deploy-production.yml | `sf org display --target-org production --verbose --json` | - -Store in GitHub repo secrets (Settings → Secrets and variables → Actions). Never commit auth URLs. - -To generate an auth URL: - -```bash -sf org display --target-org --verbose --json | jq -r '.result.sfdxAuthUrl' -``` - ---- - -## 8. Package Installation in CI +## 7. Package Installation in CI TestDataFactory is source-tracked in `force-app/main/default/classes/` and deploys with the regular source push — no package install step is needed for it. -If the project adds unlocked packages, install them like this after the source push: - -```yaml -- name: Install packages - run: sf package install --package <04t-package-id> --target-org $SCRATCH_ORG_ALIAS --no-prompt --wait 10 -``` - -For more than one package, add them to a `scripts/install-packages.sh` and call it from all workflow jobs that need them. +If the project adds unlocked packages, install them after the source push via a +`scripts/install-packages.sh` invoked from the caller workflow (or raise a +change request to add package-install inputs to the shared workflows). --- -## 9. Code Analyzer (PMD) +## 8. Code Analyzer (PMD) -Run the Salesforce Code Analyzer on every PR, using the project ruleset -(`config/pmd-ruleset.xml` — all standard Apex categories, with naming -conventions adjusted for given_when_then test methods and fflib PascalCase -Application factories): +The shared `sf-validate.yml` runs Salesforce Code Analyzer on every PR with +changed-files-only gating (severity 1–2 block). The project ruleset for local +runs lives at `config/pmd-ruleset.xml`: ```bash sf scanner run --target force-app/ --pmdconfig config/pmd-ruleset.xml --format table --severity-threshold 2 ``` -Severity 1–2: block the PR. Severity 3: warn but allow merge with justification. - Key PMD rules enforced: - `ApexSOQLInjection` — all SOQL through selectors with bind vars @@ -143,12 +137,14 @@ Key PMD rules enforced: --- -## 10. Quality Gates Summary - -| Gate | Trigger | Must Pass | -| ------------------------ | -------------------- | --------------------- | -| Lint + Prettier | Pre-commit (Husky) | Yes | -| Apex tests (scratch org) | PR to develop/main | Yes (≥85% coverage) | -| PMD Code Analyzer | PR to develop/main | Severity 1–2 = block | -| Manual PR review | PR to develop/main | ≥1 approval | -| Manual approval | Deploy to production | Required (GitHub env) | +## 9. Quality Gates Summary + +| Gate | Trigger | Must Pass | +| --------------------------- | ------------------ | ------------------------------- | +| Lint + Prettier | Pre-commit (Husky) | Yes | +| Delta check-only deploy | PR to main | Yes | +| Apex tests (scratch org) | PR to main | Yes (≥85% coverage) | +| PMD Code Analyzer | PR to main | Severity 1–2 = block | +| Branch up to date with main | PR merge (ruleset) | Yes | +| Manual PR review | PR to main | CODEOWNERS approval | +| Manual approval | Deploy | `production` GitHub Environment | diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml deleted file mode 100644 index 0b6903f..0000000 --- a/.github/workflows/deploy-production.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Deploy to Production - -on: - push: - branches: - - main - -jobs: - deploy: - name: Deploy → Production - runs-on: ubuntu-latest - environment: production # requires manual approval in GitHub repo settings - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - - - name: Install Salesforce CLI - run: npm install -g @salesforce/cli --silent - - - name: Authenticate production org - run: | - echo "${{ secrets.PRODUCTION_AUTH_URL }}" > production-auth.txt - sf org login sfdx-url --sfdx-url-file production-auth.txt --alias production - rm production-auth.txt - - - name: Deploy source - run: sf project deploy start --target-org production - - - name: Run post-deploy tests - run: | - sf apex run test \ - --target-org production \ - --test-level RunLocalTests \ - --result-format human \ - --wait 20 diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml deleted file mode 100644 index 4b1a422..0000000 --- a/.github/workflows/deploy-staging.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Deploy to Staging - -on: - push: - branches: - - main - -jobs: - deploy: - name: Deploy → Staging - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - - - name: Install Salesforce CLI - run: npm install -g @salesforce/cli --silent - - - name: Authenticate staging org - run: | - echo "${{ secrets.STAGING_AUTH_URL }}" > staging-auth.txt - sf org login sfdx-url --sfdx-url-file staging-auth.txt --alias staging - rm staging-auth.txt - - - name: Deploy source - run: sf project deploy start --target-org staging - - - name: Run smoke tests - run: | - sf apex run test \ - --target-org staging \ - --test-level RunLocalTests \ - --result-format human \ - --wait 15 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..147c215 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,31 @@ +# Thin caller — all deploy logic lives in shared-github-actions. +# The `production` GitHub Environment holds the required-reviewer gate. +# Bootstrap a fresh org with: gh workflow run deploy.yml -f full-deploy=true +name: Deploy +on: + push: + branches: [main] + workflow_dispatch: + inputs: + full-deploy: + description: "Deploy every package directory (bootstrap / re-baseline)" + type: boolean + default: false + +concurrency: + group: deploy-main + cancel-in-progress: false + +permissions: + contents: read + deployments: write + actions: read + +jobs: + production: + uses: Gforce-Innovation-Kft/shared-github-actions/.github/workflows/sf-deploy.yml@v1 + with: + environment: production + full-deploy: ${{ github.event_name == 'workflow_dispatch' && inputs.full-deploy }} + secrets: + sfdx-auth-url: ${{ secrets.DEVHUB_AUTH_URL }} diff --git a/.github/workflows/pr-validate.yml b/.github/workflows/pr-validate.yml new file mode 100644 index 0000000..9d57e08 --- /dev/null +++ b/.github/workflows/pr-validate.yml @@ -0,0 +1,23 @@ +# Thin caller — all validation logic lives in shared-github-actions. +# See docs/CICD.md for the pipeline overview and required setup. +name: PR Validate +on: + pull_request: + branches: [main] + +concurrency: + group: pr-validate-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + actions: read + +jobs: + validate: + uses: Gforce-Innovation-Kft/shared-github-actions/.github/workflows/sf-validate.yml@v1 + with: + scratch-org-validation: true + secrets: + sfdx-auth-url: ${{ secrets.DEVHUB_AUTH_URL }} diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml deleted file mode 100644 index 13e4be9..0000000 --- a/.github/workflows/validate-pr.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Validate PR - -on: - pull_request: - branches: - - main - -concurrency: - group: validate-pr-${{ github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - validate: - name: Scratch Org Validation - runs-on: ubuntu-latest - env: - SCRATCH_ALIAS: ci-scratch-${{ github.run_id }} - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - - - name: Install Salesforce CLI - run: npm install -g @salesforce/cli --silent - - - name: Install npm dependencies - run: npm ci --silent - - - name: Authenticate Dev Hub - run: | - echo "${{ secrets.DEVHUB_AUTH_URL }}" > devhub-auth.txt - sf org login sfdx-url --sfdx-url-file devhub-auth.txt --alias devhub --set-default-dev-hub - rm devhub-auth.txt - - - name: Create scratch org - run: | - sf org create scratch \ - --definition-file config/scratch-orgs/ci.json \ - --alias $SCRATCH_ALIAS \ - --duration-days 1 \ - --target-dev-hub devhub - - - name: Push source - run: sf project deploy start --target-org $SCRATCH_ALIAS - - - name: Assign permission sets - run: | - for ps in force-app/main/default/permissionsets/*.permissionset-meta.xml; do - [ -e "$ps" ] || continue - sf org assign permset --name "$(basename "$ps" .permissionset-meta.xml)" --target-org $SCRATCH_ALIAS - done - - - name: Run Apex tests - run: | - sf apex run test \ - --target-org $SCRATCH_ALIAS \ - --test-level RunLocalTests \ - --result-format json \ - --output-dir test-results \ - --wait 20 - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v4 - with: - name: apex-test-results - path: test-results/ - - - name: Run Code Analyzer (PMD) - run: | - sf plugins install @salesforce/sfdx-scanner - sf scanner run \ - --target force-app/ \ - --pmdconfig config/pmd-ruleset.xml \ - --format table \ - --severity-threshold 2 - - - name: Delete scratch org - if: always() - run: sf org delete scratch --target-org $SCRATCH_ALIAS --no-prompt 2>/dev/null || true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 21ba848..c5eebc7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,41 +3,48 @@ This applies both to the template itself and to projects bootstrapped from it. Full details live in `.claude/references/deployment-devops.md`. -## Branch model +## Branch model (trunk-based) ``` -main ← production-ready; protected; PR + approval + CI pass required - └── develop ← integration branch; auto-deploys to staging - └── feature/REQ-001-short-description ← short-lived, per-ticket - └── hotfix/critical-bug-fix ← from main, merges to main + develop +main ← protected by ruleset; every merge deploys via the production gate + └── feature/REQ-001-short-description ← short-lived, per-ticket + └── hotfix/critical-bug-fix ← same flow, expedited review ``` Branch names: `feature/REQ-NNN-…`, `bugfix/REQ-NNN-…`, `hotfix/…`, `chore/…` — -always reference the REQ number when one exists. +always reference the REQ number when one exists. No long-lived `develop` +branch; additional environments are extra gated jobs in `deploy.yml`, not +extra branches (see [docs/CICD.md](docs/CICD.md)). ## Pull requests Every PR must: -- Pass CI (`validate-pr.yml`: scratch org + source push + Apex tests + Code Analyzer) +- Pass CI (`pr-validate.yml` → shared `sf-validate.yml@v1`: delta check-only + deploy with tests + scratch-org validation + Code Analyzer) +- Be up to date with `main` before merge (ruleset-enforced — keeps the + validated deploy request identical to what merges, enabling quick deploy) - Complete the `PULL_REQUEST_TEMPLATE.md` checklist - Link the relevant `docs/product/requirements/REQ-NNN.yaml` or ticket - Have ≥ 1 review approval - Contain no `System.debug`, hardcoded IDs, or `SeeAllData=true` - Keep test coverage ≥ 85% for changed classes -Squash-merge into `develop`. No merge commits on `main`. +Squash-merge or merge commit — never rebase-merge (rewritten SHAs break the +quick-deploy lookup). ## Quality gates -| Gate | Trigger | Must pass | -| ------------------------ | ------------------ | -------------------------- | -| Lint + Prettier | Pre-commit (Husky) | Yes | -| Apex tests (scratch org) | PR to develop/main | Yes (≥ 85% coverage) | -| Code Analyzer (PMD) | PR to develop/main | Severity 1–2 = block | -| Template contract tests | PR to develop/main | Yes (`npm run test:setup`) | -| Manual review | PR to develop/main | ≥ 1 approval | -| Manual approval | Production deploy | GitHub `production` env | +| Gate | Trigger | Must pass | +| --------------------------- | ------------------ | -------------------------- | +| Lint + Prettier | Pre-commit (Husky) | Yes | +| Delta check-only deploy | PR to main | Yes | +| Apex tests (scratch org) | PR to main | Yes (≥ 85% coverage) | +| Code Analyzer (PMD) | PR to main | Severity 1–2 = block | +| Template contract tests | PR to main | Yes (`npm run test:setup`) | +| Branch up to date with main | PR merge (ruleset) | Yes | +| Manual review | PR to main | ≥ 1 approval (CODEOWNERS) | +| Manual approval | Deploy | GitHub `production` env | ## Hard rules for code diff --git a/README.md b/README.md index 15081e6..5d2ace6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Reusable Salesforce DX scaffold for GForce Innovation client engagements. - **fflib enterprise patterns** — Application factory, Domain / Selector / Service / Unit of Work layers (fflib-apex-common + fflib-apex-mocks as git submodules) - **NebulaLogger** — structured logging everywhere, `System.debug` banned by convention and PMD - **AI pair-programming, ready on clone** — `CLAUDE.md` conventions, 89 vendored [sf-skills](https://github.com/forcedotcom/sf-skills) pinned by `skills-lock.json`, GForce custom skills, coding-rule references in `.claude/references/`, and a graphify knowledge graph -- **CI/CD** — scratch-org PR validation, staging auto-deploy, production deploy with manual approval, and a template self-verification workflow +- **CI/CD** — delta PR validation (check-only deploy + scratch org + Code Analyzer), gated production deploy with quick-deploy promotion and a full audit trail (GitHub Deployments + artifacts), plus a template self-verification workflow — all via [shared reusable workflows](https://github.com/Gforce-Innovation-Kft/shared-github-actions) - **Worked reference feature** — FX Invoice Conversion (`Invoice__c`, trigger → handler → domain → selector → service → UoW → gateway, LWC, tests). It demonstrates every layer end-to-end; strip or replace it once your real requirements land. See `docs/product/PRODUCT.md`. - **Test scaffolding** — TestDataFactory (source-tracked, no package install), Jest for LWC, contract tests that keep the template itself honest @@ -63,15 +63,13 @@ sf apex run test --test-level RunLocalTests ## CI/CD and required secrets -Workflows live in `.github/workflows/`. `Template Verify` needs **no secrets**; the other three need org auth URLs added under **Settings → Secrets and variables → Actions**: +The pipeline is two thin callers (`pr-validate.yml`, `deploy.yml`) over the reusable workflows in [shared-github-actions](https://github.com/Gforce-Innovation-Kft/shared-github-actions): PR = delta check-only deploy with tests + scratch-org validation + Code Analyzer + sticky PR comment; merge = required-reviewer gate on the `production` GitHub Environment, then a **quick deploy** of the PR-validated request. Every deploy leaves a GitHub Deployment record and an audit artifact. `Template Verify` needs **no secrets**; org access needs exactly one: -| Secret | Used by | How to generate | -| --------------------- | ----------------------- | ------------------------------------------------------------------------------------ | -| `DEVHUB_AUTH_URL` | `validate-pr.yml` | `sf org display --target-org devhub --verbose --json \| jq -r '.result.sfdxAuthUrl'` | -| `STAGING_AUTH_URL` | `deploy-staging.yml` | same, with `--target-org staging` | -| `PRODUCTION_AUTH_URL` | `deploy-production.yml` | same, with `--target-org production` | +| Secret | Used by | How to generate | +| ----------------- | ------------------------------- | ------------------------------------------------------------------------------------ | +| `DEVHUB_AUTH_URL` | `pr-validate.yml`, `deploy.yml` | `sf org display --target-org devhub --verbose --json \| jq -r '.result.sfdxAuthUrl'` | -Also add GitHub **environment protection** named `production` (manual approval) for production deploys. Branch model and quality gates are documented in [CONTRIBUTING.md](CONTRIBUTING.md). +Also add GitHub **environment protection** named `production` (required reviewer) and the branch ruleset on `main`. Full setup, quick-deploy mechanics, and the audit-trail story are in [docs/CICD.md](docs/CICD.md); branch model and quality gates in [CONTRIBUTING.md](CONTRIBUTING.md). ## Working with AI (Claude Code) diff --git a/docs/CICD.md b/docs/CICD.md new file mode 100644 index 0000000..7b83db0 --- /dev/null +++ b/docs/CICD.md @@ -0,0 +1,132 @@ +# CI/CD + +This repo's pipeline is two thin caller workflows (~25 lines each) over the +reusable Salesforce CI/CD layer in +[shared-github-actions](https://github.com/Gforce-Innovation-Kft/shared-github-actions) +— see its +[consumer guide](https://github.com/Gforce-Innovation-Kft/shared-github-actions/blob/main/docs/consuming-sf-cicd.md) +for the full workflow contracts. Everything below runs on the GitHub **free +tier** (public repo). + +## Pipeline + +```mermaid +flowchart LR + subgraph pr [Pull request] + A[PR opened / updated] --> B[Delta package
sfdx-git-delta] + B --> C[Check-only deploy + tests
against the org] + A --> D[Code Analyzer
changed files] + A --> E[Scratch org
create, push, test] + C --> F[Sticky PR comment
+ sf-validate artifact] + D --> F + E --> F + end + F --> G[Merge to main] + subgraph deploy [Deploy] + G --> H{{production environment
required reviewer gate}} + H --> I[Quick deploy of the
PR-validated request] + I -. fallback .-> J[Delta deploy] + J -. fallback .-> K[Full deploy] + I --> L[GitHub Deployment record
+ sf-deploy artifact] + J --> L + K --> L + end + style H fill:#f9e2af,stroke:#333 + style F fill:#cdf,stroke:#333 + style L fill:#cdf,stroke:#333 +``` + +The blue boxes are the **audit trail**: every PR and every deploy leaves a +GitHub Deployment record plus a downloadable artifact. + +## Workflows + +| File | Trigger | Calls | +| ----------------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| [`pr-validate.yml`](../.github/workflows/pr-validate.yml) | `pull_request` → main | [`sf-validate.yml@v1`](https://github.com/Gforce-Innovation-Kft/shared-github-actions/blob/main/.github/workflows/sf-validate.yml) | +| [`deploy.yml`](../.github/workflows/deploy.yml) | `push` → main, `workflow_dispatch` | [`sf-deploy.yml@v1`](https://github.com/Gforce-Innovation-Kft/shared-github-actions/blob/main/.github/workflows/sf-deploy.yml) | +| [`template-verify.yml`](../.github/workflows/template-verify.yml) | PR + push | (self-contained template plumbing — no org access) | + +If the pipeline needs a capability the shared workflows lack, raise a change +request on shared-github-actions — never copy workflow logic into this repo. + +## Required setup + +### 1. Secret + +| Secret | Scope | Value | +| ----------------- | ---------- | ------------------------------------------------------------------------------------- | +| `DEVHUB_AUTH_URL` | Repository | `sf org display --target-org --verbose --json \| jq -r '.result.sfdxAuthUrl'` | + +Used by both validation (check-only deploys + scratch org creation on the Dev +Hub) and deploy. Hardened variant for real client projects: move it to an +**environment secret** so it only exists behind the gate. + +### 2. GitHub Environment + +| Environment | Protection | Variables | +| ------------ | -------------------- | ----------------------------------------------------------------------- | +| `production` | Required reviewer(s) | `SF_ORG_ALIAS=production` (optional — defaults to the environment name) | + +```bash +gh api -X PUT repos///environments/production \ + --input - <<'JSON' +{ "reviewers": [{ "type": "User", "id": }] } +JSON +``` + +Every merge to main then **waits for approval** before touching the org. With +one org today, `production` maps to the Dev Hub; pointing it at a real +production org later is just replacing the secret — and adding `integration` +/ `uat` environments is one more thin job per environment in `deploy.yml`. + +### 3. Branch ruleset on `main` + +Require a pull request + the validation status checks, **with "require +branches to be up to date before merging"**. That strict setting is +load-bearing: it forces every PR to re-validate against the top of main, so +the check-only deploy request that quick deploy later consumes is guaranteed +to match what actually merges. + +CODEOWNERS review (@gambe94) is enforced on top of the ruleset. + +## Quick deploy mechanics + +1. The PR's check-only deploy runs the tests and leaves a **validated deploy + request** on the org; its id travels in `validation.json` inside the + `sf-validate-` artifact. +2. After merge + gate approval, `sf-deploy.yml` finds that artifact (merge + commit → PR → head SHA → run), verifies org id + SHA + the 10-day window, + and runs `sf project deploy quick` — **no tests re-run, seconds instead of + minutes**. +3. Any mismatch falls back to a delta deploy; an unusable delta base (force + push, bootstrap) falls back to a full deploy of all package directories. + The decision is recorded in `quick-deploy-decision.json`. + +Bootstrap a fresh org: `gh workflow run deploy.yml -f full-deploy=true` +(deploys `force-app` **and** the fflib/NebulaLogger submodule package +directories). + +## Audit trail + +_"What exactly went to prod on March 3rd, who approved it, and what tests +ran?"_ — answered from two places, no extra tooling: + +- **Deployments sidebar** (repo → Environments → production): every deploy + with its approver, timestamp, and a direct link to the Salesforce + deploy-request page. +- **Artifacts** per run: `sf-validate-` (delta manifest, validation + result, quick-deploy handoff) and `sf-deploy-production-` (delta + manifest, deploy result JSON, JUnit test results, quick-deploy decision). + +**Retention caveat:** artifacts live for 90 days (the free-tier maximum, +already configured). For regulated projects, export artifacts to S3/object +storage on a schedule as the long-term audit store — that is the enterprise +upgrade path; deliberately not built here. + +## Migration note (July 2026) + +The previous inline workflows (`validate-pr.yml`, `deploy-staging.yml`, +`deploy-production.yml`) and their `STAGING_AUTH_URL` / `PRODUCTION_AUTH_URL` +secrets are gone. `DEVHUB_AUTH_URL` is the only secret; environments replace +per-org secrets as the promotion mechanism. From 003eb0dc167d53c6005d25188a965b8686b8f3ca Mon Sep 17 00:00:00 2001 From: Gabor Demeter Date: Mon, 13 Jul 2026 00:26:06 +0200 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20two-workflow=20CI/CD=20=E2=80=94=20?= =?UTF-8?q?pr-validate=20+=20release=20with=20gated=20quick=20deploy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .github/workflows/pr-validate.yml | 6 +----- .github/workflows/{deploy.yml => release.yml} | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 15 deletions(-) rename .github/workflows/{deploy.yml => release.yml} (50%) diff --git a/.github/workflows/pr-validate.yml b/.github/workflows/pr-validate.yml index 9d57e08..28e29b4 100644 --- a/.github/workflows/pr-validate.yml +++ b/.github/workflows/pr-validate.yml @@ -11,13 +11,9 @@ concurrency: permissions: contents: read - pull-requests: write - actions: read jobs: validate: - uses: Gforce-Innovation-Kft/shared-github-actions/.github/workflows/sf-validate.yml@v1 - with: - scratch-org-validation: true + uses: Gforce-Innovation-Kft/shared-github-actions/.github/workflows/sf-pr-validate.yml@v1 secrets: sfdx-auth-url: ${{ secrets.DEVHUB_AUTH_URL }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/release.yml similarity index 50% rename from .github/workflows/deploy.yml rename to .github/workflows/release.yml index 147c215..5909a86 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,10 @@ -# Thin caller — all deploy logic lives in shared-github-actions. -# The `production` GitHub Environment holds the required-reviewer gate. -# Bootstrap a fresh org with: gh workflow run deploy.yml -f full-deploy=true -name: Deploy +# Thin caller — delta validation on PRs, gated quick deploy on merge. +# The `devhub` GitHub Environment holds the required-reviewer gate. +# Bootstrap a fresh org with: gh workflow run release.yml -f full-deploy=true +name: Release on: + pull_request: + branches: [main] push: branches: [main] workflow_dispatch: @@ -13,19 +15,18 @@ on: default: false concurrency: - group: deploy-main - cancel-in-progress: false + group: release-${{ github.event_name == 'pull_request' && github.event.pull_request.number || 'main' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} permissions: contents: read - deployments: write actions: read jobs: - production: - uses: Gforce-Innovation-Kft/shared-github-actions/.github/workflows/sf-deploy.yml@v1 + release: + uses: Gforce-Innovation-Kft/shared-github-actions/.github/workflows/sf-release.yml@v1 with: - environment: production + environment: devhub full-deploy: ${{ github.event_name == 'workflow_dispatch' && inputs.full-deploy }} secrets: sfdx-auth-url: ${{ secrets.DEVHUB_AUTH_URL }} From 349336718b7be576887d2fe3d27b47fe6d2098f1 Mon Sep 17 00:00:00 2001 From: Gabor Demeter Date: Mon, 13 Jul 2026 00:32:05 +0200 Subject: [PATCH 3/6] docs: CI/CD guide for the two-workflow pipeline (gated quick deploy) Co-Authored-By: Claude Fable 5 --- .claude/references/deployment-devops.md | 95 ++++++++------ CONTRIBUTING.md | 30 +++-- README.md | 12 +- docs/CICD.md | 165 ++++++++---------------- 4 files changed, 131 insertions(+), 171 deletions(-) diff --git a/.claude/references/deployment-devops.md b/.claude/references/deployment-devops.md index 73b39aa..f356ce1 100644 --- a/.claude/references/deployment-devops.md +++ b/.claude/references/deployment-devops.md @@ -2,8 +2,8 @@ The pipeline implementation lives in [shared-github-actions](https://github.com/Gforce-Innovation-Kft/shared-github-actions) -(`sf-validate.yml` / `sf-deploy.yml` reusable workflows); this repo only ships -thin callers. Operational setup guide: [docs/CICD.md](../../docs/CICD.md). +(`sf-pr-validate.yml` / `sf-release.yml` reusable workflows); this repo only +ships thin callers. Operational setup guide: [docs/CICD.md](../../docs/CICD.md). If the pipeline needs something the shared workflows lack, raise a change request there — never copy workflow logic into this repo. @@ -12,18 +12,19 @@ request there — never copy workflow logic into this repo. ## 1. Branching Strategy (trunk-based) ``` -main ← protected by ruleset; every merge deploys via the production gate +main ← protected by ruleset; every merge deploys via the devhub gate └── feature/REQ-001-account-enhancements ← short-lived, per-ticket └── feature/REQ-002-lwc-account-form └── hotfix/critical-bug-fix ← same flow, expedited review ``` -- `feature/*` → PR to `main` → validated by `pr-validate.yml` (delta check-only - deploy + scratch org + Code Analyzer) -- merge to `main` → `deploy.yml` → **production GitHub Environment gate** +- `feature/*` → PR to `main` → validated by `pr-validate.yml` (`jest` + + `scratch-org` deploy/test) and `release.yml`'s `validate` job (delta + check-only deploy with selected Apex tests against the Dev Hub) +- merge to `main` → `release.yml` → **`devhub` GitHub Environment gate** (required reviewer) → quick deploy of the PR-validated request - No long-lived `develop` branch. Additional environments (integration/uat) - are added as extra gated jobs in `deploy.yml`, not extra branches. + are added as extra gated jobs in `release.yml`, not extra branches. --- @@ -44,8 +45,10 @@ Always reference the REQ number when one exists. Every PR must: -- Pass CI (`pr-validate.yml` → shared `sf-validate.yml@v1`): delta check-only - deploy with tests + scratch org validation + Code Analyzer +- Pass CI: `pr-validate.yml` → shared `sf-pr-validate.yml@v1` (`jest` + + `scratch-org` deploy/test), and `release.yml` → shared `sf-release.yml@v1` + (`validate`: delta check-only deploy with selected Apex tests against the + Dev Hub) - Be **up to date with `main`** before merge (ruleset-enforced — this keeps the validated deploy request identical to what merges, which is what makes quick deploy safe) @@ -62,34 +65,45 @@ quick-deploy lookup (the fallback delta deploy covers it, but tests re-run). ## 4. PR Validation (CI) -`pr-validate.yml` calls the shared `sf-validate.yml`, which: +Two workflows run on every PR: -1. Generates a delta package between the PR base and head (sfdx-git-delta) -2. Runs a **check-only deploy of the delta with tests against the Dev Hub** - (auth via `DEVHUB_AUTH_URL`) — the resulting validated deploy request is the - quick-deploy handle, shipped in the `sf-validate-` artifact -3. Runs Salesforce Code Analyzer on changed files -4. Creates a scratch org from `config/scratch-orgs/ci.json` (`--duration-days 1`), - pushes source, assigns permission sets, runs all local tests, and **always** - deletes the org -5. Posts a sticky summary comment on the PR +`pr-validate.yml` calls the shared `sf-pr-validate.yml@v1`: + +1. `jest` — runs `npm test` (skips with a notice if no test script exists) +2. `scratch-org` — creates a scratch org from `config/scratch-orgs/ci.json` + (`--duration-days 1`), pushes source, assigns permission sets, runs all + local tests with coverage, and **always** deletes the org + +`release.yml` calls the shared `sf-release.yml@v1` (`validate` job): + +1. Generates a delta `package.xml` between the PR base and head + (sfdx-git-delta) +2. `sf-find-tests` selects the Apex tests covering the changed classes + (naming match + reference scan) +3. Runs a **check-only deploy of the delta against the Dev Hub** + (auth via `DEVHUB_AUTH_URL`) with `RunSpecifiedTests` — falls back to + `RunLocalTests` when Apex changed but no tests matched, and runs no tests + at all for metadata-only deltas. The resulting validated deploy request is + the quick-deploy handle, shipped in the `sf-release-` artifact --- ## 5. Deployment (promotion) -Merge to `main` triggers `deploy.yml` → shared `sf-deploy.yml`: +Merge to `main` triggers `release.yml` → shared `sf-release.yml@v1` +(`quick-deploy` job): -1. Waits at the **`production` GitHub Environment** for required-reviewer +1. Waits at the **`devhub` GitHub Environment** for required-reviewer approval 2. Creates a GitHub Deployment record (audit trail, links the Salesforce deploy-request page) 3. **Quick deploys** the PR-validated request (no tests re-run); falls back to - a delta deploy, then a full deploy of all package directories + a delta deploy (same recorded test plan), then a full deploy of all + package directories 4. Uploads the audit artifact: delta manifest, deploy result JSON, JUnit test results, quick-deploy decision -Bootstrap/re-baseline: `gh workflow run deploy.yml -f full-deploy=true`. +Bootstrap/re-baseline: `gh workflow run release.yml -f full-deploy=true`. Production deployments: business hours only (08:00–17:00 CET). No Friday deployments. @@ -97,10 +111,10 @@ deployments. ## 6. Secret & Variable Management -| Name | Kind | Used by | Value source | -| ----------------- | -------------------------------------------- | -------------- | ------------------------------------------------------------------------------------- | -| `DEVHUB_AUTH_URL` | Repo secret | both workflows | `sf org display --target-org --verbose --json \| jq -r '.result.sfdxAuthUrl'` | -| `SF_ORG_ALIAS` | `production` environment variable (optional) | deploy | CLI alias, defaults to the environment name | +| Name | Kind | Used by | Value source | +| ----------------- | ---------------------------------------- | -------------- | ------------------------------------------------------------------------------------- | +| `DEVHUB_AUTH_URL` | Repo secret | both workflows | `sf org display --target-org --verbose --json \| jq -r '.result.sfdxAuthUrl'` | +| `SF_ORG_ALIAS` | `devhub` environment variable (optional) | quick-deploy | CLI alias, defaults to the environment name | Never commit auth URLs. For real client projects, move the auth URL to an **environment secret** behind the gate, one per target org. @@ -118,11 +132,12 @@ change request to add package-install inputs to the shared workflows). --- -## 8. Code Analyzer (PMD) +## 8. Code Analyzer (PMD) — local use only -The shared `sf-validate.yml` runs Salesforce Code Analyzer on every PR with -changed-files-only gating (severity 1–2 block). The project ruleset for local -runs lives at `config/pmd-ruleset.xml`: +Code Analyzer is **not** wired into CI (dropped when the callers were rebuilt +on the shared `sf-pr-validate.yml` / `sf-release.yml` workflows — it is not +one of the PR gates). Run it locally before opening a PR — the project +ruleset lives at `config/pmd-ruleset.xml`: ```bash sf scanner run --target force-app/ --pmdconfig config/pmd-ruleset.xml --format table --severity-threshold 2 @@ -139,12 +154,12 @@ Key PMD rules enforced: ## 9. Quality Gates Summary -| Gate | Trigger | Must Pass | -| --------------------------- | ------------------ | ------------------------------- | -| Lint + Prettier | Pre-commit (Husky) | Yes | -| Delta check-only deploy | PR to main | Yes | -| Apex tests (scratch org) | PR to main | Yes (≥85% coverage) | -| PMD Code Analyzer | PR to main | Severity 1–2 = block | -| Branch up to date with main | PR merge (ruleset) | Yes | -| Manual PR review | PR to main | CODEOWNERS approval | -| Manual approval | Deploy | `production` GitHub Environment | +| Gate | Trigger | Must Pass | +| ------------------------------------ | ------------------ | --------------------------- | +| Lint + Prettier | Pre-commit (Husky) | Yes | +| `jest` | PR to main | Yes | +| Delta check-only deploy (`validate`) | PR to main | Yes | +| Apex tests (`scratch-org`) | PR to main | Yes (≥85% coverage) | +| Branch up to date with main | PR merge (ruleset) | Yes | +| Manual PR review | PR to main | CODEOWNERS approval | +| Manual approval | Deploy | `devhub` GitHub Environment | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c5eebc7..82fa947 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,22 +6,24 @@ Full details live in `.claude/references/deployment-devops.md`. ## Branch model (trunk-based) ``` -main ← protected by ruleset; every merge deploys via the production gate +main ← protected by ruleset; every merge deploys via the devhub gate └── feature/REQ-001-short-description ← short-lived, per-ticket └── hotfix/critical-bug-fix ← same flow, expedited review ``` Branch names: `feature/REQ-NNN-…`, `bugfix/REQ-NNN-…`, `hotfix/…`, `chore/…` — always reference the REQ number when one exists. No long-lived `develop` -branch; additional environments are extra gated jobs in `deploy.yml`, not +branch; additional environments are extra gated jobs in `release.yml`, not extra branches (see [docs/CICD.md](docs/CICD.md)). ## Pull requests Every PR must: -- Pass CI (`pr-validate.yml` → shared `sf-validate.yml@v1`: delta check-only - deploy with tests + scratch-org validation + Code Analyzer) +- Pass CI: `pr-validate.yml` → shared `sf-pr-validate.yml@v1` (`jest` + + scratch-org deploy/test), and `release.yml` → shared `sf-release.yml@v1` + (`validate`: delta check-only deploy with selected Apex tests against the + Dev Hub) - Be up to date with `main` before merge (ruleset-enforced — keeps the validated deploy request identical to what merges, enabling quick deploy) - Complete the `PULL_REQUEST_TEMPLATE.md` checklist @@ -35,16 +37,16 @@ quick-deploy lookup). ## Quality gates -| Gate | Trigger | Must pass | -| --------------------------- | ------------------ | -------------------------- | -| Lint + Prettier | Pre-commit (Husky) | Yes | -| Delta check-only deploy | PR to main | Yes | -| Apex tests (scratch org) | PR to main | Yes (≥ 85% coverage) | -| Code Analyzer (PMD) | PR to main | Severity 1–2 = block | -| Template contract tests | PR to main | Yes (`npm run test:setup`) | -| Branch up to date with main | PR merge (ruleset) | Yes | -| Manual review | PR to main | ≥ 1 approval (CODEOWNERS) | -| Manual approval | Deploy | GitHub `production` env | +| Gate | Trigger | Must pass | +| ------------------------------------ | ------------------ | -------------------------- | +| Lint + Prettier | Pre-commit (Husky) | Yes | +| `jest` | PR to main | Yes | +| Delta check-only deploy (`validate`) | PR to main | Yes | +| Apex tests (`scratch-org`) | PR to main | Yes (≥ 85% coverage) | +| Template contract tests | PR to main | Yes (`npm run test:setup`) | +| Branch up to date with main | PR merge (ruleset) | Yes | +| Manual review | PR to main | ≥ 1 approval (CODEOWNERS) | +| Manual approval | Deploy | GitHub `devhub` env | ## Hard rules for code diff --git a/README.md b/README.md index 5d2ace6..79ea112 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Reusable Salesforce DX scaffold for GForce Innovation client engagements. - **fflib enterprise patterns** — Application factory, Domain / Selector / Service / Unit of Work layers (fflib-apex-common + fflib-apex-mocks as git submodules) - **NebulaLogger** — structured logging everywhere, `System.debug` banned by convention and PMD - **AI pair-programming, ready on clone** — `CLAUDE.md` conventions, 89 vendored [sf-skills](https://github.com/forcedotcom/sf-skills) pinned by `skills-lock.json`, GForce custom skills, coding-rule references in `.claude/references/`, and a graphify knowledge graph -- **CI/CD** — delta PR validation (check-only deploy + scratch org + Code Analyzer), gated production deploy with quick-deploy promotion and a full audit trail (GitHub Deployments + artifacts), plus a template self-verification workflow — all via [shared reusable workflows](https://github.com/Gforce-Innovation-Kft/shared-github-actions) +- **CI/CD** — PR checks (`jest` + scratch-org deploy/test) plus a delta check-only validate against the Dev Hub, gated `devhub` deploy with quick-deploy promotion (delta → full fallback) and a full audit trail (GitHub Deployments + artifacts), plus a template self-verification workflow — all via [shared reusable workflows](https://github.com/Gforce-Innovation-Kft/shared-github-actions) - **Worked reference feature** — FX Invoice Conversion (`Invoice__c`, trigger → handler → domain → selector → service → UoW → gateway, LWC, tests). It demonstrates every layer end-to-end; strip or replace it once your real requirements land. See `docs/product/PRODUCT.md`. - **Test scaffolding** — TestDataFactory (source-tracked, no package install), Jest for LWC, contract tests that keep the template itself honest @@ -63,13 +63,13 @@ sf apex run test --test-level RunLocalTests ## CI/CD and required secrets -The pipeline is two thin callers (`pr-validate.yml`, `deploy.yml`) over the reusable workflows in [shared-github-actions](https://github.com/Gforce-Innovation-Kft/shared-github-actions): PR = delta check-only deploy with tests + scratch-org validation + Code Analyzer + sticky PR comment; merge = required-reviewer gate on the `production` GitHub Environment, then a **quick deploy** of the PR-validated request. Every deploy leaves a GitHub Deployment record and an audit artifact. `Template Verify` needs **no secrets**; org access needs exactly one: +The pipeline is two thin callers (`pr-validate.yml`, `release.yml`) over the reusable workflows in [shared-github-actions](https://github.com/Gforce-Innovation-Kft/shared-github-actions): PR = `jest` + scratch-org deploy/test (`pr-validate.yml`) and a delta check-only validate against the Dev Hub (`release.yml`); merge = required-reviewer gate on the `devhub` GitHub Environment, then a **quick deploy** of the PR-validated request (falls back to delta → full deploy). Every deploy leaves a GitHub Deployment record and an audit artifact. `Template Verify` needs **no secrets**; org access needs exactly one: -| Secret | Used by | How to generate | -| ----------------- | ------------------------------- | ------------------------------------------------------------------------------------ | -| `DEVHUB_AUTH_URL` | `pr-validate.yml`, `deploy.yml` | `sf org display --target-org devhub --verbose --json \| jq -r '.result.sfdxAuthUrl'` | +| Secret | Used by | How to generate | +| ----------------- | -------------------------------- | ------------------------------------------------------------------------------------ | +| `DEVHUB_AUTH_URL` | `pr-validate.yml`, `release.yml` | `sf org display --target-org devhub --verbose --json \| jq -r '.result.sfdxAuthUrl'` | -Also add GitHub **environment protection** named `production` (required reviewer) and the branch ruleset on `main`. Full setup, quick-deploy mechanics, and the audit-trail story are in [docs/CICD.md](docs/CICD.md); branch model and quality gates in [CONTRIBUTING.md](CONTRIBUTING.md). +Also add GitHub **environment protection** named `devhub` (required reviewer) and the branch ruleset on `main`. Full setup, quick-deploy mechanics, and the audit-trail story are in [docs/CICD.md](docs/CICD.md); branch model and quality gates in [CONTRIBUTING.md](CONTRIBUTING.md). ## Working with AI (Claude Code) diff --git a/docs/CICD.md b/docs/CICD.md index 7b83db0..7a6c0e7 100644 --- a/docs/CICD.md +++ b/docs/CICD.md @@ -1,132 +1,75 @@ -# CI/CD +# CI/CD Pipeline -This repo's pipeline is two thin caller workflows (~25 lines each) over the -reusable Salesforce CI/CD layer in +Trunk-based flow against a single Dev Hub (production) org. Two thin +workflows call the reusable layer in [shared-github-actions](https://github.com/Gforce-Innovation-Kft/shared-github-actions) -— see its -[consumer guide](https://github.com/Gforce-Innovation-Kft/shared-github-actions/blob/main/docs/consuming-sf-cicd.md) -for the full workflow contracts. Everything below runs on the GitHub **free -tier** (public repo). +at the `v1` release tag — all pipeline logic is versioned there. -## Pipeline +## Flow ```mermaid flowchart LR - subgraph pr [Pull request] - A[PR opened / updated] --> B[Delta package
sfdx-git-delta] - B --> C[Check-only deploy + tests
against the org] - A --> D[Code Analyzer
changed files] - A --> E[Scratch org
create, push, test] - C --> F[Sticky PR comment
+ sf-validate artifact] - D --> F - E --> F + subgraph PR["Pull request"] + J[jest\nnpm test] ~~~ S[scratch-org\ndeploy + RunLocalTests] + D[delta package] --> T[sf-find-tests\nrelevant Apex tests] --> V[check-only validate\nvs Dev Hub] + V -->|deploy request id| A[(sf-release artifact)] end - F --> G[Merge to main] - subgraph deploy [Deploy] - G --> H{{production environment
required reviewer gate}} - H --> I[Quick deploy of the
PR-validated request] - I -. fallback .-> J[Delta deploy] - J -. fallback .-> K[Full deploy] - I --> L[GitHub Deployment record
+ sf-deploy artifact] - J --> L - K --> L - end - style H fill:#f9e2af,stroke:#333 - style F fill:#cdf,stroke:#333 - style L fill:#cdf,stroke:#333 + PR -->|merge| G{devhub environment\nmanual approval} + G --> Q[quick deploy\nvalidated request] + A -.->|validation.json| Q + Q -->|stale / consumed| F[delta deploy → full deploy] ``` -The blue boxes are the **audit trail**: every PR and every deploy leaves a -GitHub Deployment record plus a downloadable artifact. - -## Workflows - -| File | Trigger | Calls | -| ----------------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -| [`pr-validate.yml`](../.github/workflows/pr-validate.yml) | `pull_request` → main | [`sf-validate.yml@v1`](https://github.com/Gforce-Innovation-Kft/shared-github-actions/blob/main/.github/workflows/sf-validate.yml) | -| [`deploy.yml`](../.github/workflows/deploy.yml) | `push` → main, `workflow_dispatch` | [`sf-deploy.yml@v1`](https://github.com/Gforce-Innovation-Kft/shared-github-actions/blob/main/.github/workflows/sf-deploy.yml) | -| [`template-verify.yml`](../.github/workflows/template-verify.yml) | PR + push | (self-contained template plumbing — no org access) | - -If the pipeline needs a capability the shared workflows lack, raise a change -request on shared-github-actions — never copy workflow logic into this repo. - -## Required setup - -### 1. Secret - -| Secret | Scope | Value | -| ----------------- | ---------- | ------------------------------------------------------------------------------------- | -| `DEVHUB_AUTH_URL` | Repository | `sf org display --target-org --verbose --json \| jq -r '.result.sfdxAuthUrl'` | +**PR Validate** (`pr-validate.yml` → `sf-pr-validate.yml@v1`) -Used by both validation (check-only deploys + scratch org creation on the Dev -Hub) and deploy. Hardened variant for real client projects: move it to an -**environment secret** so it only exists behind the gate. +- `jest` — runs `npm test` (skips with a notice if no test script exists). +- `scratch-org` — 1-day scratch org from `config/scratch-orgs/ci.json`: + deploy, assign permission sets, `RunLocalTests` with coverage, always + deleted. -### 2. GitHub Environment +**Release** (`release.yml` → `sf-release.yml@v1`) -| Environment | Protection | Variables | -| ------------ | -------------------- | ----------------------------------------------------------------------- | -| `production` | Required reviewer(s) | `SF_ORG_ALIAS=production` (optional — defaults to the environment name) | +- On PR: delta `package.xml` (sfdx-git-delta) → `sf-find-tests` selects the + Apex tests covering the changed classes (naming match + reference scan) → + check-only deploy against the Dev Hub (`RunSpecifiedTests`; falls back to + `RunLocalTests` when Apex changed but no tests matched; no tests for + metadata-only deltas). The deploy request id is saved in the + `sf-release-` artifact. +- On merge: the `quick-deploy` job waits for approval on the `devhub` + environment, then runs `sf project deploy quick` with the validated + request — no tests re-run, the org accepts the already-validated + package. Fallbacks: delta deploy (same recorded test plan) → full deploy + of every `packageDirectories` entry. Manual bootstrap: + `gh workflow run release.yml -f full-deploy=true`. -```bash -gh api -X PUT repos///environments/production \ - --input - <<'JSON' -{ "reviewers": [{ "type": "User", "id": }] } -JSON -``` - -Every merge to main then **waits for approval** before touching the org. With -one org today, `production` maps to the Dev Hub; pointing it at a real -production org later is just replacing the secret — and adding `integration` -/ `uat` environments is one more thin job per environment in `deploy.yml`. - -### 3. Branch ruleset on `main` - -Require a pull request + the validation status checks, **with "require -branches to be up to date before merging"**. That strict setting is -load-bearing: it forces every PR to re-validate against the top of main, so -the check-only deploy request that quick deploy later consumes is guaranteed -to match what actually merges. +## Why quick deploy is safe here -CODEOWNERS review (@gambe94) is enforced on top of the ruleset. +The `main` ruleset requires branches to be up to date before merging, so +the merged tree is identical to the validated PR head. The deploy job +additionally checks: same org id, same head SHA, validation younger than +10 days — otherwise it falls back to a real deploy. -## Quick deploy mechanics - -1. The PR's check-only deploy runs the tests and leaves a **validated deploy - request** on the org; its id travels in `validation.json` inside the - `sf-validate-` artifact. -2. After merge + gate approval, `sf-deploy.yml` finds that artifact (merge - commit → PR → head SHA → run), verifies org id + SHA + the 10-day window, - and runs `sf project deploy quick` — **no tests re-run, seconds instead of - minutes**. -3. Any mismatch falls back to a delta deploy; an unusable delta base (force - push, bootstrap) falls back to a full deploy of all package directories. - The decision is recorded in `quick-deploy-decision.json`. +## Required setup -Bootstrap a fresh org: `gh workflow run deploy.yml -f full-deploy=true` -(deploys `force-app` **and** the fflib/NebulaLogger submodule package -directories). +| Piece | Value | +| ----------------- | ----------------------------------------------------------------------------------------- | +| Repo secret | `DEVHUB_AUTH_URL` — SFDX auth URL of the Dev Hub | +| Environment | `devhub`, required reviewer = release manager | +| Ruleset on `main` | require PR, require `jest` / `scratch-org` / `validate` checks, require branch up to date | ## Audit trail -_"What exactly went to prod on March 3rd, who approved it, and what tests -ran?"_ — answered from two places, no extra tooling: - -- **Deployments sidebar** (repo → Environments → production): every deploy - with its approver, timestamp, and a direct link to the Salesforce - deploy-request page. -- **Artifacts** per run: `sf-validate-` (delta manifest, validation - result, quick-deploy handoff) and `sf-deploy-production-` (delta - manifest, deploy result JSON, JUnit test results, quick-deploy decision). - -**Retention caveat:** artifacts live for 90 days (the free-tier maximum, -already configured). For regulated projects, export artifacts to S3/object -storage on a schedule as the long-term audit store — that is the enterprise -upgrade path; deliberately not built here. +Every run leaves artifacts (90-day retention by default): the delta +manifest and generated source, the validate/deploy results, the selected +tests, and `quick-deploy-decision.json` recording why quick deploy was or +was not used. Deployments to `devhub` also appear in the repo's +Deployments sidebar (recorded automatically by the environment binding). +For longer retention, sync artifacts to external storage (e.g. S3) from a +scheduled workflow. -## Migration note (July 2026) +## Limitations -The previous inline workflows (`validate-pr.yml`, `deploy-staging.yml`, -`deploy-production.yml`) and their `STAGING_AUTH_URL` / `PRODUCTION_AUTH_URL` -secrets are gone. `DEVHUB_AUTH_URL` is the only secret; environments replace -per-org secrets as the promotion mechanism. +- Fork PRs fail validation (secrets are not exposed to forks) — use + same-repo branches. +- Approving a deploy more than 10 days after validation falls back to a + full delta redeploy (tests re-run). From e9b452b676dbbd83e8d28615914b73c3fd7cfe25 Mon Sep 17 00:00:00 2001 From: Gabor Demeter Date: Mon, 13 Jul 2026 00:55:30 +0200 Subject: [PATCH 4/6] fix(ci): use published sf-devcontainer image until sf-ci ships on Docker Hub Co-Authored-By: Claude Fable 5 --- .github/workflows/pr-validate.yml | 3 +++ .github/workflows/release.yml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/.github/workflows/pr-validate.yml b/.github/workflows/pr-validate.yml index 28e29b4..24febc1 100644 --- a/.github/workflows/pr-validate.yml +++ b/.github/workflows/pr-validate.yml @@ -15,5 +15,8 @@ permissions: jobs: validate: uses: Gforce-Innovation-Kft/shared-github-actions/.github/workflows/sf-pr-validate.yml@v1 + with: + # Temporary until gforceinnovation/sf-ci is published on Docker Hub + container-image: gforceinnovation/sf-devcontainer:latest secrets: sfdx-auth-url: ${{ secrets.DEVHUB_AUTH_URL }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5909a86..4cc6f85 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,6 +27,8 @@ jobs: uses: Gforce-Innovation-Kft/shared-github-actions/.github/workflows/sf-release.yml@v1 with: environment: devhub + # Temporary until gforceinnovation/sf-ci is published on Docker Hub + container-image: gforceinnovation/sf-devcontainer:latest full-deploy: ${{ github.event_name == 'workflow_dispatch' && inputs.full-deploy }} secrets: sfdx-auth-url: ${{ secrets.DEVHUB_AUTH_URL }} From 349f52ed99b3d876e8748e5cb822844b88edbd63 Mon Sep 17 00:00:00 2001 From: Gabor Demeter Date: Mon, 13 Jul 2026 01:54:18 +0200 Subject: [PATCH 5/6] chore: re-run checks against shared v1.2.1 (container user fix) Co-Authored-By: Claude Fable 5 From 9c8c463f571d765d1de72a6b1d1093f7cc6b66da Mon Sep 17 00:00:00 2001 From: Gabor Demeter Date: Mon, 13 Jul 2026 19:25:36 +0200 Subject: [PATCH 6/6] use gforceinnovation/sf-ci:latest image --- .github/workflows/pr-validate.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-validate.yml b/.github/workflows/pr-validate.yml index 24febc1..a9e639b 100644 --- a/.github/workflows/pr-validate.yml +++ b/.github/workflows/pr-validate.yml @@ -17,6 +17,6 @@ jobs: uses: Gforce-Innovation-Kft/shared-github-actions/.github/workflows/sf-pr-validate.yml@v1 with: # Temporary until gforceinnovation/sf-ci is published on Docker Hub - container-image: gforceinnovation/sf-devcontainer:latest + container-image: gforceinnovation/sf-ci:latest secrets: sfdx-auth-url: ${{ secrets.DEVHUB_AUTH_URL }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4cc6f85..6533adc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: with: environment: devhub # Temporary until gforceinnovation/sf-ci is published on Docker Hub - container-image: gforceinnovation/sf-devcontainer:latest + container-image: gforceinnovation/sf-ci:latest full-deploy: ${{ github.event_name == 'workflow_dispatch' && inputs.full-deploy }} secrets: sfdx-auth-url: ${{ secrets.DEVHUB_AUTH_URL }}