Skip to content

Appendix D: Centralised Configuration Repository Structure Reference

This appendix defines the recommended structure for the organization-level .github configuration repository described in Section 8. The repository serves as the authoritative source for workflow templates, shared configuration standards, and governance artifacts consumed by all Salesforce project repositories in the organization.

Repository Overview

The configuration repository is created as a repository named .github within the GitHub organization. Starter workflows placed in workflow-templates/ are surfaced to all repositories in the organization via the Actions UI, and reusable workflows stored in .github/workflows/ are callable via workflow_call from any repository in the organization.

The repository should be treated as a product: changes follow the same PR-based process as application code, the CODEOWNERS file restricts modifications to the governance team (@<my-org>/admin-team), and releases are tagged following Conventional Commits and SemVer conventions.

The structure below mirrors the reference implementation in Appendix A. In a consuming project repository, .github/actions/ and .github/workflows/ contain the composite actions and reusable workflows described there. At the organization-level .github repository, those same files appear under the inner .github/ directory and become callable from every repository in the organization.

.github/                                               # Organization-level configuration repository
│
├── .github/                                           # GitHub configuration for the template repo itself
│   ├── ISSUE_TEMPLATE/                                # Issue templates surfaced when opening an issue
│   │   ├── bug_report.md                              # Bug report template with steps to reproduce, environment, logs
│   │   └── feature_request.md                         # Feature request template for new workflows, actions, gates
│   │
│   ├── PULL_REQUEST_TEMPLATE.md                       # Default PR description template (summary, test plan, Apex tests to run)
│   │
│   ├── actions/                                       # Composite actions (reusable steps within a job)
│   │   ├── apex-list/
│   │   │   └── action.yml                             # Retrieves all ApexClass and ApexTrigger names from an org
│   │   ├── apex-test-report/
│   │   │   └── action.yml                             # Parses sf apex run test JSON into job summary + PR markdown
│   │   ├── post-pr-comment/
│   │   │   └── action.yml                             # Posts or updates a structured PR comment (finds by marker)
│   │   ├── setup-sf/
│   │   │   └── action.yml                             # Installs Node, npm ci, SF CLI, Code Analyzer, sfdx-git-delta
│   │   ├── sf-auth-jwt/
│   │   │   └── action.yml                             # Authenticates to an org via JWT bearer flow
│   │   ├── sf-auth-sfdx-url/
│   │   │   └── action.yml                             # Authenticates to an org via SFDX auth URL
│   │   ├── sf-code-analyzer-report/
│   │   │   └── action.yml                             # Parses Code Analyzer JSON into job summary + PR markdown
│   │   └── sf-validation-report/
│   │       └── action.yml                             # Parses deployment validation JSON into report markdown
│   │
│   ├── workflows/                                     # Caller workflows (triggered) and reusable workflows (workflow_call)
│   │   │
│   │   │ ── Caller workflows (one per trigger event) ──
│   │   ├── ci.yml                                     # Push baseline: runs on every push; triggers release-please on main
│   │   ├── pr-prod.yml                                # PR gates for pull requests targeting main (production auth)
│   │   ├── pr-pipeline.yml                            # PR gates for PRs targeting staging, qa, and int
│   │   ├── pr-risk-tier.yml                           # Re-runs risk tier classification when auto-merge is enabled
│   │   ├── scheduled-project-scan.yml                 # Weekly project scan (drift, compilation, full Code Analyzer scan)
│   │   ├── scheduled-test-runner.yml                  # Nightly test runner (full-org Apex tests, all Jest tests)
│   │   ├── manual-code-analyzer-scan.yml              # On demand: Code Analyzer scan of the whole repository
│   │   ├── dependabot-rename.yml                      # Gives a Dependabot PR a Conventional Commit title
│   │   │
│   │   │ ── Reusable workflows (workflow_call targets) ──
│   │   ├── release-please.yml                         # Release Please changelog + tagged release management
│   │   ├── sf-apex-compilation.yml                    # Dry-run deploys all Apex, to check that it compiles
│   │   ├── sf-apex-tests.yml                          # Full-org RunLocalTests, creates issue on failure
│   │   ├── sf-apex-test-selection.yml                 # Chooses the Apex tests for a PR; the validation runs them
│   │   ├── sf-changelog-check.yml                     # Fails the PR that changes CHANGELOG.md, unless it is the release PR
│   │   ├── sf-code-analyzer.yml                       # sf code-analyzer run; pr, ci, or full mode
│   │   ├── sf-destructive-check.yml                   # Detects destructiveChanges.xml and labels the PR
│   │   ├── sf-org-scan.yml                            # Scans one org: drift detection (org retrieve against repo state via git diff) and compilation
│   │   ├── sf-generate-delta.yml                      # sfdx-git-delta delta package, uploaded as artifact
│   │   ├── sf-jest-tests.yml                          # LWC Jest with coverage, uploads to Codecov
│   │   ├── sf-notify-slack.yml                        # Slack incoming webhook notification
│   │   ├── sf-prohibited-metadata.yml                 # Fails the PR on any Tier 0 (prohibited) metadata path
│   │   ├── sf-risk-tier.yml                           # Labels the PR by risk tier; disables auto-merge on Tier 1
│   │   ├── sf-prettier-verify.yml                     # prettier --check on source files
│   │   ├── sf-secret-scan.yml                         # Placeholder for the chosen scanner
│   │   ├── sf-semver-check.yml                        # Enforces Conventional Commits on PR title
│   │   └── sf-validate-deployment.yml                 # Validates the delta with --dry-run and runs the PR's Apex tests
│   │
│   ├── CODEOWNERS                                     # Restricts governance files to admin-team
│   ├── codecov.yml                                    # Codecov configuration (coverage flags, thresholds)
│   ├── renovate.json                                  # One line: extends the central renovate.jsonc beside it
│   └── renovate.jsonc                                 # The central Renovate file; the renovate.json of every
│                                                      # repository extends it (see Version Control)
│
├── workflow-templates/                                # Starter workflows surfaced in GitHub Actions UI
│   ├── sf-pr-checks.yml                               # Starter: full PR quality gate chain
│   ├── sf-pr-checks.properties.json
│   ├── sf-scheduled-checks.yml                        # Starter: weekly drift detection and full-org tests
│   ├── sf-scheduled-checks.properties.json
│   ├── sf-deploy-sandbox.yml                          # Starter: sandbox deployment workflow
│   ├── sf-deploy-sandbox.properties.json
│   ├── sf-deploy-production.yml                       # Starter: production deployment workflow
│   ├── sf-deploy-production.properties.json
│   ├── sf-notify-slack.yml                            # Starter: Slack notification workflow
│   └── sf-notify-slack.properties.json
│
├── config/                                            # Shared configuration standards
│   │                                                  # This directory is the canonical source. Where each
│   │                                                  # file sits in a consuming repository is on the Version
│   │                                                  # Control page: in .github/ when its tool finds it there
│   │                                                  # or only a workflow reads it, in config/pmd/ for the
│   │                                                  # PMD files, and at the root only when its tool looks
│   │                                                  # nowhere else.
│   ├── code-analyzer.yml                              # Canonical Code Analyzer configuration
│   ├── codecov.yml                                    # Canonical Codecov configuration
│   ├── metadata-risk-tiers.yml                        # Canonical machine-readable risk matrix (Appendix B)
│   ├── pmd/                                           # The PMD files, kept together (see Appendix F)
│   │   ├── pmd-stock-rules.xml                        # Canonical stock PMD rules, with priorities (see Appendix F.1)
│   │   ├── pmd-stock-rules-javascript.xml             # Canonical stock PMD rules for JavaScript (see Appendix F.1)
│   │   ├── pmd-custom-rules-apex.xml                  # Canonical PMD custom ruleset for Apex (see Appendix F.2)
│   │   ├── pmd-custom-rules-markup.xml                # Canonical PMD custom ruleset for markup (see Appendix F.2)
│   │   ├── pmd-custom-rules-metadata.xml              # Canonical PMD custom ruleset for metadata (see Appendix F.2)
│   │   ├── pmd-custom-rules-flow.xml                  # Canonical PMD custom ruleset for Flows (see Appendix F.2)
│   │   ├── pmd-custom-rules-javascript.xml            # Canonical PMD custom ruleset for JavaScript (see Appendix F.2)
│   │   └── pmd-java-rules-1.0.0.jar                   # Canonical custom Java PMD rules, built as a JAR; the
│   │                                                  # ruleset rulesets/devsecops-java.xml is inside it
│   │                                                  # (see Appendix F.3)
│   ├── eslint.config.js                               # Canonical ESLint flat config for LWC and Aura
│   ├── jest.config.js                                 # Canonical Jest configuration with coverage thresholds
│   ├── prettier.config.mjs                            # Canonical Prettier configuration, in JavaScript so that
│   │                                                  # a repository can import the npm-published one and extend it
│   └── secret-scan-config                             # Canonical secret scanning configuration
│                                                      # (e.g. .gitleaks.toml for Gitleaks, or equivalent)
│
├── pmd-java-rules/                                    # Maven project for the custom Java PMD rules (see Appendix F.3)
│   ├── pom.xml                                        # mvn package runs the rule tests and builds the JAR
│   └── src/
│       ├── main/java/                                 # One class per rule
│       ├── main/resources/rulesets/devsecops-java.xml # The ruleset that names the classes
│       └── test/                                      # PMD's rule-test harness, and the test cases for each rule
│
├── templates/                                         # Project scaffolding templates
│   ├── CODEOWNERS.template                            # The default line, the governance block (.github/,
│   │                                                  # config/, and root files), the block of the files that a
│   │                                                  # release changes, then the Tier 1 block (Appendix B)
│   ├── .gitignore.template                            # SFDX default, plus the Tier 0 entries (Appendix B)
│   ├── .forceignore.template                          # SFDX default, plus the Tier 0 entries (Appendix B)
│   ├── sfdx-project.json.template                     # SFDX default, plus the organisation's settings (see Version Control)
│   ├── README.md.template                             # The organisation's default README, with the sections each
│   │                                                  # project keeps current
│   ├── package.json.template                          # SFDX default, plus the organisation's npm packages as the
│   │                                                  # starters of every repository: the pipeline tools and the
│   │                                                  # shared configurations (see Version Control)
│   ├── .prettierignore.template                       # SFDX default
│   ├── .husky/
│   │   └── pre-commit.template                        # The checks that run before a commit, through lint-staged
│   ├── .vscode/
│   │   ├── extensions.json.template                   # SFDX default, plus the extensions the standards recommend
│   │   └── settings.json.template                     # SFDX default, plus the organisation's settings; a developer's
│   │                                                  # own go in a VS Code profile (see Version Control)
│   ├── config/
│   │   └── project-scratch-def.json.template          # SFDX default; each project team adapts it to the project's
│   │                                                  # needs, and an architect approves the change like any other
│   ├── release-please-config.json.template            # Release Please config: release type and tracked paths
│   └── .release-please-manifest.json.template         # Release Please manifest: current version per package.
│                                                      # Must be correctly initialised at project start
│
├── rulesets/                                          # Exported GitHub Ruleset configurations
│   ├── int-branch-protection.json
│   ├── qa-staging-branch-protection.json
│   ├── production-branch-protection.json
│   └── required-workflows.json
│
├── docs/
│   ├── onboarding.md                                  # Guide for new projects adopting this configuration repository
│   ├── contributing.md                                # Contribution process for project repository delivery teams
│   ├── versioning.md                                  # Versioning and upgrade policy
│   └── changelog.md                                   # Record of changes to standards and workflows
│
├── CODE_OF_CONDUCT.md                                 # Community standards and enforcement policy
├── CONTRIBUTING.md                                    # Guidelines for contributing to this configuration repository
├── SECURITY.md                                        # Vulnerability disclosure and security policy
├── SUPPORT.md                                         # How to get help and report issues
└── README.md

The .github/workflows/ directory contains two categories of files. Caller workflows (ci.yml, pr-prod.yml, pr-pipeline.yml, scheduled-*.yml) are the trigger-bound entry points; they respond to GitHub events (push, pull_request, schedule) and compose reusable workflows into a pipeline. Reusable workflows (sf-*.yml, release-please.yml) declare on: workflow_call and implement exactly one quality gate or concern. Each reusable workflow lives in its own file, with the naming convention sf-<concern>.yml. This split keeps caller workflows thin and event-focused, while the gate logic remains testable and versionable in isolation.

Key Design Decisions

Starter workflows vs. reusable workflows serve distinct purposes. Starter workflows in workflow-templates/ are discovery and onboarding mechanisms - they appear in the GitHub Actions UI when creating a new workflow. Reusable workflows in .github/workflows/ are the actual callable implementations. A starter workflow is typically a thin wrapper that calls one or more reusable workflows, giving new projects a complete working pipeline immediately while keeping the implementation centralised.

Composite actions in .github/actions/ handle cross-cutting setup and utility concerns shared across multiple reusable workflows. The setup-sf action consolidates Node, SF CLI, Code Analyzer plugin, and sfdx-git-delta installation into a single step; the sf-auth-jwt action is called by every workflow requiring org connectivity, and the sf-auth-sfdx-url action is an alternative that no workflow of the reference calls; the post-pr-comment action standardises PR comment posting and in-place update logic. Rather than duplicating these steps across every reusable workflow, the logic lives in one place and is referenced by all.

Configuration files in config/ are the canonical versions of all quality gate configuration. Consuming repositories reference these files directly where tooling supports remote configuration, or copy them into the project repository at onboarding and track updates via Renovate.

GitHub configuration repository as a complement: Maintaining a dedicated GitHub configuration repository - a separate repository marked as a template in GitHub's settings - allows new projects to be created from it with a single click, copying all scaffolding files automatically. The templates/ directory in the .github repository is the authoritative reviewed source; the GitHub configuration repository reflects its current state for consumption. As changes are made in templates/, those changes could be mirrored to the configuration repository - but this synchronisation automation is advanced-level and should be addressed only once the manual pattern is stable.

CODEOWNERS configuration: The configuration repository's CODEOWNERS file assigns every governance-critical path to the admin team, and every other file to the architect team, so that no file is without a reviewer:

# Every file that no later line names goes to the architect team for review.
# This line comes first: in CODEOWNERS the last matching pattern takes
# precedence, so the lines below override it for their own paths.
*                           @<my-org>/architect-team

/.github/workflows/         @<my-org>/admin-team
/.github/actions/           @<my-org>/admin-team
/workflow-templates/        @<my-org>/admin-team
/config/                    @<my-org>/admin-team
/pmd-java-rules/            @<my-org>/admin-team
/templates/                 @<my-org>/admin-team
/rulesets/                  @<my-org>/admin-team
/docs/                      @<my-org>/admin-team
/README.md                  @<my-org>/admin-team
/.github/renovate.jsonc     @<my-org>/admin-team

Project repository CODEOWNERS: Every consuming project repository needs the same protection for its copies of these files, so templates/CODEOWNERS.template carries a governance block. The configuration files are sensitive wherever they live: a change to code-analyzer.yml or metadata-risk-tiers.yml can quietly switch off a quality gate or reclassify high-risk metadata as low risk. The block therefore covers the whole .github/ folder, which holds the workflows, the risk matrix, and CODEOWNERS itself; the whole config/ folder, which holds the PMD rulesets and the JAR of the Java rules; the configuration files that sit at the repository root because their tools look there, a secret scanning configuration among them; package.json, its lock file, and the .husky/ folder; the list of recommended editor extensions; and the Tier 0 ignore files. The npm packages of a Salesforce project are the tools of the pipeline, such as Prettier, ESLint, Jest, Husky, and lint-staged, so a new version of one can change what a gate accepts: the governance team reviews every change to them, and an upgrade comes as a pull request from Renovate or Dependabot and not from a developer's own hand (Version Control: Repositories). Nobody can then remove the protection without the governance team's approval. Where each file sits is in Repository Structure. Before the block comes one default line: any file that no later line names is reviewed by the architect team, which is how the source that will be deployed to the org gets its reviewers:

# Every file that no later line names goes to the architect team for review.
# This line comes first: in CODEOWNERS the last matching pattern takes
# precedence, so each block below overrides it for its own paths.
*                           @<my-org>/architect-team

# Governance configuration. The governance team must approve every change.
# The file list is an example: include every configuration file your
# repositories use. The Tier 1 block goes after this block, because in
# CODEOWNERS the last matching pattern takes precedence.

# .github/ holds the workflows, this file, the risk matrix, and the
# configuration of each tool that reads it there. config/pmd/ holds the PMD
# rulesets and the JAR of the custom Java rules. The JAR is code that runs
# in the pipeline, so it needs the same protection as the files that
# configure it.
/.github/                   @<my-org>/admin-team
/config/                    @<my-org>/admin-team

# The three files that every release pull request of Release Please changes.

# Org-based repository: a release needs the approval
# of a release manager, an architect, or a member of the governance team,
# and one approval from any team on a line meets the code owner rule.

# In a package-based repository anyone releases: keep the three paths, and
# name no team after them, which leaves a file with no owner.
/CHANGELOG.md                           @<my-org>/release-managers @<my-org>/architect-team @<my-org>/admin-team
/sfdx-project.json                      @<my-org>/release-managers @<my-org>/architect-team @<my-org>/admin-team

# Because it is later in the file than /.github/ above, this line takes precedence
/.github/.release-please-manifest.json  @<my-org>/release-managers @<my-org>/architect-team @<my-org>/admin-team

# Configuration files that their tools look for at the repository root
/code-analyzer.yml          @<my-org>/admin-team
/eslint.config.js           @<my-org>/admin-team
/jest.config.js             @<my-org>/admin-team
/prettier.config.mjs        @<my-org>/admin-team
/.gitleaks.toml             @<my-org>/admin-team

# The npm packages are the tools of the pipeline, and the commit hook runs
# them. A new version can change what a gate accepts, so the governance team
# reviews every change. Renovate and Dependabot open the pull requests for
# upgrades.
/package.json               @<my-org>/admin-team
/package-lock.json          @<my-org>/admin-team
/.husky/                    @<my-org>/admin-team

# The editor extensions the repository recommends to every developer
/.vscode/extensions.json    @<my-org>/admin-team

# Tier 0 enforcement layers
/.forceignore               @<my-org>/admin-team
/.gitignore                 @<my-org>/admin-team

Code owner review is required by the fully protected branch rulesets (Appendix C), so a configuration change can reach the int branch with a single peer review but cannot reach qa, staging, or main without the governance team's approval. The default line does the same for everything else: no change reaches those branches without the architect team's approval. Three files have a block of their own, because every release pull request of Release Please changes them: CHANGELOG.md, sfdx-project.json, and the manifest. In an org-based repository the block names the release managers beside the two other teams, so that any one of the three can approve a release; in a package-based repository the three files have no owner, and anyone releases (Version Control).

Versioning and Upgrade Policy

The configuration repository follows Semantic Versioning driven by Conventional Commits and automated via release-please (GitHub). Patch releases address bug fixes that do not change gate behaviour; minor releases introduce new optional gates or backward-compatible enhancements; major releases introduce breaking changes requiring coordinated updates in consuming repositories.

Consuming repositories pin every reusable workflow and composite action reference to an immutable release tag and take updates through Renovate PRs when new versions are published (Appendix G). The configuration files copied from templates/ are not dependencies, so Renovate does not track them on its own: Vendir can declare their upstream source, and Renovate can then be configured to open a PR whenever vendir sync would change them, as explained in a blog post on the pattern.

If template configuration files are bundled into a npm package, then Renovate can check for the package in an npm (public or private) registry and open updating pull requests as needed. A rule for those packages goes into the central Renovate file, renovate.jsonc, which every repository extends (Version Control: Renovate and Dependabot); replace my-org with the scope of the organisation's packages.

{
  "packageRules": [
    {
      "matchManagers": ["npm"],
      "matchPackageNames": ["@my-org/**"],
      "groupName": "Internal packages",
      "automerge": false
    }
  ]
}

Setting automerge: false for internal template updates is intentional - template version upgrades may introduce new required inputs or changed gate behaviour and should be reviewed by the project technical lead before adoption.

The release-please-config.json template defines the release type and the paths Release Please should track. The .release-please-manifest.json maintains current version state per package and must be correctly initialised at project kickstart - an incorrect initial version entry is the most common cause of first-release failures and can produce unexpected version bumps that are difficult to reverse once tags have been pushed.

Repository Bootstrap Script

Rather than relying on manual steps to set up a new project repository, these operations can be codified into a bootstrap script that a delivery team runs once at project kickstart:

#!/bin/bash
# Bootstrap a new Salesforce project repository
# Usage: ./bootstrap.sh <new-repo-name> <github-org>

REPO_NAME=$1
ORG=$2
TEMPLATE_REPO=".github"

# Create repository from template
gh repo create "$ORG/$REPO_NAME" \
  --template "$ORG/$TEMPLATE_REPO" \
  --private

# Apply organization Rulesets
for ruleset in rulesets/*.json; do
  gh api \
    --method POST \
    -H "Accept: application/vnd.github+json" \
    "/orgs/$ORG/rulesets" \
    --input "$ruleset"
done

# Create branch topology
for branch in int qa staging; do
  gh api \
    --method POST \
    -H "Accept: application/vnd.github+json" \
    "/repos/$ORG/$REPO_NAME/git/refs" \
    -f ref="refs/heads/$branch" \
    -f sha="$(gh api /repos/$ORG/$REPO_NAME/git/ref/heads/main --jq '.object.sha')"
done

# Create GitHub Environments
for env in qa staging production; do
  gh api \
    --method PUT \
    -H "Accept: application/vnd.github+json" \
    "/repos/$ORG/$REPO_NAME/environments/$env"
done

echo "Repository $REPO_NAME bootstrapped successfully."
echo "Next steps:"
echo "  1. Add Salesforce org credentials to each GitHub Environment"
echo "  2. Update sfdx-project.json with package structure"
echo "  3. Configure Renovate on the new repository"
echo "  4. Run initial scratch org creation to validate pipeline"

GitHub Environments. The script makes an environment for each org that receives a release: qa, staging, and production, which is the org of the main branch. Store the credentials of each org in its environment, and not as a repository secret (Section 7). An environment has three advantages (Deployments and environments):

  • Only a job that names the environment can read its secrets, so the credentials of production are out of reach of every other job.
  • The environment can require the approval of a named person or team before such a job runs.
  • The environment can limit which branches deploy to it, so that only main reaches production.

Some of these depend on the GitHub plan. A job names its environment with the environment: key; the sample workflows in Appendix A leave it out, to stay short.

This script is a starting point. Production-grade bootstrap scripts for consulting organisations typically extend this pattern to include org authentication validation, Slack notifications, Jira project linking, and population of environment secrets from a vault. The bootstrap script itself should be stored in the configuration repository - versioned and maintained with the same discipline as the workflows and configuration files it deploys. The target structure for the reusable workflow refactoring described in Appendix A is the .github/workflows/ directory defined above - each job in the monolithic PR workflow maps directly to a named reusable workflow file callable via workflow_call.