From fc5ce12ac30c60d0cc1f0b08c5cdf0dbd482fc86 Mon Sep 17 00:00:00 2001 From: admin-raintree <277948009+admin-raintree@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:32:24 -0700 Subject: [PATCH 1/8] feat(ci): add reusable Raintree CI workflow with exact-pin enforcement Frozen install (bun/pnpm/npm) -> pin check -> biome -> typecheck -> test -> build -> gitleaks (full history) -> Socket (when API key secret exists). Script-dependent steps skip cleanly so libraries and workers share the same workflow. All actions pinned to commit SHAs. Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 160 ++++++++++++++++++++++++++++++++++ scripts/check-pinned-deps.mjs | 69 +++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 scripts/check-pinned-deps.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5763c81 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,160 @@ +# Raintree Technology — reusable CI +# +# Called by every repo in the org: +# +# jobs: +# ci: +# uses: raintree-technology/.github/.github/workflows/ci.yml@ +# with: +# package-manager: bun # bun | pnpm | npm +# secrets: inherit +# +# Pipeline: frozen install → pin check → biome → typecheck → test → build +# → gitleaks (full history) → Socket (only when an API key secret exists). +# Steps that depend on a script (`typecheck`, `test`, `build`) skip cleanly when +# the script is absent, so libraries and workers can call the same workflow. +name: Raintree CI + +on: + workflow_call: + inputs: + package-manager: + description: "bun | pnpm | npm" + type: string + default: bun + node-version: + description: "Node version for pnpm/npm repos (and tooling)" + type: string + default: "22" + bun-version: + description: "Bun version for bun repos" + type: string + default: "1.3.11" + working-directory: + description: "Root of the package being checked" + type: string + default: "." + run-build: + description: "Run the build script (disable for repos whose build needs deploy credentials)" + type: boolean + default: true + secrets: + SOCKET_SECURITY_API_KEY: + required: false + +permissions: + contents: read + +env: + GITLEAKS_VERSION: "8.30.0" + +jobs: + ci: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ inputs.working-directory }} + steps: + - name: Checkout (full history for secret scan) + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup Bun + if: inputs.package-manager == 'bun' + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: ${{ inputs.bun-version }} + + - name: Setup pnpm + if: inputs.package-manager == 'pnpm' + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + + - name: Setup Node + if: inputs.package-manager != 'bun' + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: ${{ inputs.node-version }} + + - name: Install (frozen lockfile) + run: | + case "${{ inputs.package-manager }}" in + bun) bun install --frozen-lockfile ;; + pnpm) pnpm install --frozen-lockfile ;; + npm) npm ci ;; + *) echo "unknown package-manager: ${{ inputs.package-manager }}" >&2; exit 1 ;; + esac + + - name: Verify exact-pinned dependencies + run: | + curl -fsSL "https://raw.githubusercontent.com/raintree-technology/.github/main/scripts/check-pinned-deps.mjs" -o /tmp/check-pinned-deps.mjs + node /tmp/check-pinned-deps.mjs + + - name: Biome check + run: | + if [ -f biome.json ] || [ -f biome.jsonc ]; then + if [ -x node_modules/.bin/biome ]; then + node_modules/.bin/biome ci . + else + echo "::warning::biome config present but @biomejs/biome is not a devDependency; skipping" + fi + else + echo "no biome config; skipping" + fi + + - name: Typecheck + run: | + if node -e "process.exit(require('./package.json').scripts?.typecheck ? 0 : 1)"; then + case "${{ inputs.package-manager }}" in + bun) bun run typecheck ;; + *) npm run typecheck ;; + esac + else + echo "no typecheck script; skipping" + fi + + - name: Test + env: + CI: "true" + run: | + if node -e "process.exit(require('./package.json').scripts?.test ? 0 : 1)"; then + case "${{ inputs.package-manager }}" in + bun) bun run test ;; + *) npm run test ;; + esac + else + echo "no test script; skipping" + fi + + - name: Build + if: inputs.run-build + run: | + if node -e "process.exit(require('./package.json').scripts?.build ? 0 : 1)"; then + case "${{ inputs.package-manager }}" in + bun) bun run build ;; + *) npm run build ;; + esac + else + echo "no build script; skipping" + fi + + - name: Secret scan (gitleaks, full history) + working-directory: ${{ github.workspace }} + run: | + curl -fsSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" -o /tmp/gitleaks.tar.gz + tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks + /tmp/gitleaks detect --source . --redact --exit-code 1 + + - name: Socket supply-chain scan + env: + SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_SECURITY_API_KEY }} + run: | + if [ -z "$SOCKET_SECURITY_API_KEY" ]; then + echo "::notice::SOCKET_SECURITY_API_KEY not configured; Socket scan skipped (PR gating via the Socket GitHub App still applies once installed)" + exit 0 + fi + npx -y socket@latest scan create --no-interactive . || { + echo "::error::Socket scan failed" + exit 1 + } diff --git a/scripts/check-pinned-deps.mjs b/scripts/check-pinned-deps.mjs new file mode 100644 index 0000000..3cea9ed --- /dev/null +++ b/scripts/check-pinned-deps.mjs @@ -0,0 +1,69 @@ +#!/usr/bin/env node +// Fails if any dependency in package.json (root + workspace packages) uses a +// range specifier. Exact versions only — `workspace:`, `catalog:`, `npm:` with +// an exact version, file/link/git pins are allowed. +import { readFileSync, existsSync, readdirSync, statSync } from "node:fs"; +import { join, dirname } from "node:path"; + +const SECTIONS = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]; +const EXACT = /^\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/; + +function isAllowed(spec) { + if (typeof spec !== "string") return false; + if (EXACT.test(spec)) return true; + if (spec.startsWith("workspace:") || spec.startsWith("catalog:")) return true; + if (spec.startsWith("file:") || spec.startsWith("link:") || spec.startsWith("portal:")) return true; + if (spec.startsWith("git+") || spec.startsWith("github:")) return spec.includes("#"); + if (spec.startsWith("npm:")) { + const at = spec.lastIndexOf("@"); + return at > 4 && EXACT.test(spec.slice(at + 1)); + } + return false; +} + +function* packageJsonFiles(root) { + const rootPkg = join(root, "package.json"); + if (existsSync(rootPkg)) yield rootPkg; + // workspace globs: check conventional dirs (apps/*, packages/*) plus declared workspaces + const declared = (() => { + try { + const p = JSON.parse(readFileSync(rootPkg, "utf8")); + const w = Array.isArray(p.workspaces) ? p.workspaces : p.workspaces?.packages; + return Array.isArray(w) ? w : []; + } catch { + return []; + } + })(); + const dirs = new Set( + ["apps", "packages", ...declared.map((g) => g.split("/")[0])].filter( + (d) => d && !d.includes("*") && existsSync(join(root, d)), + ), + ); + for (const d of dirs) { + for (const sub of readdirSync(join(root, d))) { + const pkg = join(root, d, sub, "package.json"); + if (existsSync(pkg) && statSync(pkg).isFile()) yield pkg; + } + } +} + +let bad = 0; +for (const file of packageJsonFiles(process.cwd())) { + const pkg = JSON.parse(readFileSync(file, "utf8")); + for (const section of SECTIONS) { + for (const [name, spec] of Object.entries(pkg[section] ?? {})) { + // peerDependencies legitimately use ranges for libraries + if (section === "peerDependencies") continue; + if (!isAllowed(spec)) { + console.error(`UNPINNED ${file} ${section}.${name} = "${spec}"`); + bad++; + } + } + } +} + +if (bad > 0) { + console.error(`\n${bad} unpinned dependency specifier(s). Use exact versions (save-exact).`); + process.exit(1); +} +console.log("All dependency specifiers exact-pinned."); From 1b1b5e3b7d3905527f81f899d82564f1a83e2983 Mon Sep 17 00:00:00 2001 From: admin-raintree <277948009+admin-raintree@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:32:24 -0700 Subject: [PATCH 2/8] feat(ci): add reusable drift-check workflow + engine Scheduled per repo; fails when a repo stops meeting the standard: missing README/STATUS badge, committed .env, unpinned deps, multiple lockfiles, missing biome config or engines.node, unpinned action refs. Co-Authored-By: Claude Fable 5 --- .github/workflows/drift-check.yml | 44 ++++++++++++++++++ scripts/drift-check.sh | 76 +++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 .github/workflows/drift-check.yml create mode 100644 scripts/drift-check.sh diff --git a/.github/workflows/drift-check.yml b/.github/workflows/drift-check.yml new file mode 100644 index 0000000..b15ba1c --- /dev/null +++ b/.github/workflows/drift-check.yml @@ -0,0 +1,44 @@ +# Raintree Technology — reusable standard-drift check +# +# Called by every repo on a schedule (and on PRs touching config): +# +# on: +# schedule: [{cron: "17 6 * * 1"}] # weekly +# workflow_dispatch: +# jobs: +# drift: +# uses: raintree-technology/.github/.github/workflows/drift-check.yml@ +# +# Fails when the repo stops meeting the Raintree standard: missing files, +# unpinned deps, unpinned action refs, stale/absent central-CI wiring. +name: Raintree drift check + +on: + workflow_call: {} + +permissions: + contents: read + +jobs: + drift: + runs-on: ubuntu-latest + steps: + - name: Checkout repo under test + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Checkout org standard + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + repository: raintree-technology/.github + path: .raintree-standard + persist-credentials: false + + - name: Setup Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22" + + - name: Run drift check + run: bash .raintree-standard/scripts/drift-check.sh diff --git a/scripts/drift-check.sh b/scripts/drift-check.sh new file mode 100644 index 0000000..2e3bcd0 --- /dev/null +++ b/scripts/drift-check.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Raintree standard drift check. Run from the root of the repo under test with +# the org .github repo checked out at .raintree-standard/ (or set STANDARD_DIR). +# Checks apply conditionally by repo type; each violation is one FAIL line. +set -u +STANDARD_DIR="${STANDARD_DIR:-.raintree-standard}" +FAILURES=0 +fail() { echo "FAIL: $1"; FAILURES=$((FAILURES + 1)); } +warn() { echo "WARN: $1"; } +ok() { echo "ok: $1"; } + +# ---- universal checks ------------------------------------------------------- +[ -f README.md ] || fail "README.md missing" +if [ -f README.md ]; then + grep -qE 'img\.shields\.io/badge/status-' README.md \ + && ok "README has STATUS badge" \ + || fail "README missing STATUS badge (live/WIP/archived)" +fi + +if git ls-files --error-unmatch .env >/dev/null 2>&1; then + fail ".env is committed to git" +else + ok "no committed .env" +fi + +# every workflow `uses:` must be pinned to a 40-char SHA (local ./ refs and +# docker:// refs excluded) +if [ -d .github/workflows ]; then + UNPINNED=$(grep -rhoE 'uses:\s*[^ ]+@[^ #]+' .github/workflows/ 2>/dev/null \ + | grep -vE '@[0-9a-f]{40}$' \ + | grep -vE 'uses:\s*(\./|docker://)' || true) + if [ -n "$UNPINNED" ]; then + fail "workflow actions not pinned to commit SHAs:"$'\n'"$UNPINNED" + else + ok "all workflow actions SHA-pinned" + fi + grep -rq 'raintree-technology/.github/.github/workflows/ci.yml@' .github/workflows/ 2>/dev/null \ + && ok "central reusable CI wired" \ + || warn "repo does not call the central reusable CI (fine for Python/Swift/shell repos with bespoke CI)" +else + fail "no .github/workflows directory" +fi + +# ---- JS/TS repos ------------------------------------------------------------ +if [ -f package.json ]; then + node "$STANDARD_DIR/scripts/check-pinned-deps.mjs" || fail "unpinned dependencies (see above)" + + LOCKS=0 + for f in bun.lock bun.lockb pnpm-lock.yaml package-lock.json; do + [ -f "$f" ] && LOCKS=$((LOCKS + 1)) + done + [ "$LOCKS" -eq 1 ] && ok "exactly one lockfile" || fail "expected exactly 1 lockfile, found $LOCKS" + + if [ -f biome.json ] || [ -f biome.jsonc ]; then + ok "biome config present" + else + fail "no biome.json/biome.jsonc" + fi + + node -e "process.exit(require('./package.json').engines?.node ? 0 : 1)" 2>/dev/null \ + && ok "engines.node declared" \ + || fail "package.json missing engines.node" +fi + +# ---- Python repos ----------------------------------------------------------- +if [ -f pyproject.toml ]; then + [ -f uv.lock ] || [ -f poetry.lock ] || fail "pyproject.toml without lockfile (uv.lock/poetry.lock)" +fi + +# ---- summary ---------------------------------------------------------------- +echo "" +if [ "$FAILURES" -gt 0 ]; then + echo "DRIFT: $FAILURES violation(s) of the Raintree standard." + exit 1 +fi +echo "No drift detected." From ea4187d1cdc1e7219d0e0b2beef85c60158074cf Mon Sep 17 00:00:00 2001 From: admin-raintree <277948009+admin-raintree@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:32:24 -0700 Subject: [PATCH 3/8] feat(configs): add canonical biome, tsconfig, and renovate bases Vendored per repo and extended locally. Biome base enforces invariants (VCS, recommended lint, organized imports) leaving formatter style per repo; tsconfig base is strict + noUncheckedIndexedAccess; renovate base pins everything with a 7-day minimumReleaseAge cooldown. Co-Authored-By: Claude Fable 5 --- configs/biome.base.jsonc | 32 ++++++++++++++++++++++++++++++++ configs/renovate-base.json | 25 +++++++++++++++++++++++++ configs/tsconfig.base.json | 15 +++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 configs/biome.base.jsonc create mode 100644 configs/renovate-base.json create mode 100644 configs/tsconfig.base.json diff --git a/configs/biome.base.jsonc b/configs/biome.base.jsonc new file mode 100644 index 0000000..313fad1 --- /dev/null +++ b/configs/biome.base.jsonc @@ -0,0 +1,32 @@ +// Raintree Technology — canonical Biome base. +// Vendored into each repo as biome.base.jsonc; the repo's biome.json extends it: +// { "extends": ["./biome.base.jsonc"], ... } +// This base enforces org invariants (VCS integration, recommended lint rules, +// organized imports). Formatter style (tabs/spaces, quotes, line width) is a +// per-repo choice and intentionally not set here. +{ + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": true + }, + "formatter": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "assist": { + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/configs/renovate-base.json b/configs/renovate-base.json new file mode 100644 index 0000000..561070e --- /dev/null +++ b/configs/renovate-base.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "description": "Raintree Technology shared Renovate preset. Per-repo renovate.json: { \"extends\": [\"github>raintree-technology/.github//configs/renovate-base.json\"] }", + "extends": ["config:recommended", ":pinAllExceptPeerDependencies"], + "minimumReleaseAge": "7 days", + "rangeStrategy": "pin", + "schedule": ["before 9am on monday"], + "prHourlyLimit": 4, + "packageRules": [ + { + "description": "Group non-major updates into one weekly PR", + "matchUpdateTypes": ["minor", "patch"], + "groupName": "non-major dependencies" + }, + { + "description": "Pin GitHub Action digests", + "matchManagers": ["github-actions"], + "pinDigests": true + } + ], + "vulnerabilityAlerts": { + "enabled": true, + "minimumReleaseAge": null + } +} diff --git a/configs/tsconfig.base.json b/configs/tsconfig.base.json new file mode 100644 index 0000000..45323a1 --- /dev/null +++ b/configs/tsconfig.base.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "_comment": "Raintree Technology canonical strict TypeScript base. Vendored into each repo as tsconfig.base.json and extended by the repo tsconfig: { \"extends\": \"./tsconfig.base.json\" }. Module/target/jsx/paths stay per-repo.", + "compilerOptions": { + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "isolatedModules": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noUncheckedIndexedAccess": true, + "noFallthroughCasesInSwitch": true + } +} From edb34377d7531d14ab5209c85d1f0c7a7fc9f9a2 Mon Sep 17 00:00:00 2001 From: admin-raintree <277948009+admin-raintree@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:32:24 -0700 Subject: [PATCH 4/8] docs: add README template + CODEOWNERS, document the Raintree standard Co-Authored-By: Claude Fable 5 --- CODEOWNERS | 4 +++ README.md | 50 ++++++++++++++++++++++++++++++++ templates/README.template.md | 56 ++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 CODEOWNERS create mode 100644 templates/README.template.md diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..dd4b5b9 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,4 @@ +# Default owner for the org-standard repo. CODEOWNERS is not inherited from +# the .github repo — each repo carries its own copy (vendored by the +# standardization pass). +* @admin-raintree diff --git a/README.md b/README.md index 9e4b60a..2ab1208 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,56 @@ Organization-wide GitHub configuration for [Raintree Technology](https://raintre | `SECURITY.md` | Default security policy and vulnerability reporting process | | `PULL_REQUEST_TEMPLATE.md` | Default PR template with checklist | | `ISSUE_TEMPLATE/` | Bug report and feature request templates | +| `CODEOWNERS` | Owner of this repo (CODEOWNERS is **not** inherited — each repo vendors its own) | +| `.github/workflows/ci.yml` | **Reusable CI** every repo calls: frozen install → pin check → biome → typecheck → test → build → gitleaks → Socket | +| `.github/workflows/drift-check.yml` | **Reusable drift check** run on a schedule per repo; fails when a repo stops meeting the standard | +| `scripts/check-pinned-deps.mjs` | Fails on any `^`/`~` range in package.json (root + workspaces) | +| `scripts/drift-check.sh` | The drift-check engine (files, pinning, SHA-pinned actions, CI wiring) | +| `configs/biome.base.jsonc` | Canonical Biome base — vendored per repo, extended by the repo's `biome.json` | +| `configs/tsconfig.base.json` | Canonical strict TypeScript base — vendored per repo | +| `configs/renovate-base.json` | Shared Renovate preset: pin everything, 7-day `minimumReleaseAge`, weekly grouped PRs | +| `templates/README.template.md` | README template (STATUS badge, stack, setup, env vars, scripts, deploy, license) | + +## The Raintree standard (per repo) + +- Exact-pinned dependencies, one lockfile, frozen installs in CI (`save-exact=true`) +- Biome lint+format extending the vendored canonical base +- Strict TypeScript extending the vendored canonical base +- CI calls the reusable workflow here, pinned to a commit SHA +- All GitHub Actions pinned to full commit SHAs — never floating tags +- Renovate/Dependabot with a 7-day cooldown so freshly published (possibly malicious) versions never land same-day +- Zod-validated env module + committed `.env.example`; secrets only in Vercel env / a secret manager +- README from the template with a STATUS badge (live / WIP / archived) +- Branch protection on `main`: PR required, checks required, no force-push, linear history + +### Calling the reusable CI + +```yaml +# .github/workflows/ci.yml in any repo +name: CI +on: + push: {branches: [main]} + pull_request: +jobs: + ci: + uses: raintree-technology/.github/.github/workflows/ci.yml@ + with: + package-manager: bun # bun | pnpm | npm + secrets: inherit +``` + +### Calling the drift check + +```yaml +# .github/workflows/drift-check.yml in any repo +name: Drift check +on: + schedule: [{cron: "17 6 * * 1"}] + workflow_dispatch: +jobs: + drift: + uses: raintree-technology/.github/.github/workflows/drift-check.yml@ +``` ## How it works diff --git a/templates/README.template.md b/templates/README.template.md new file mode 100644 index 0000000..dcec71d --- /dev/null +++ b/templates/README.template.md @@ -0,0 +1,56 @@ + + +# {{REPO_NAME}} + +![status](https://img.shields.io/badge/status-{{live|WIP|archived}}-{{brightgreen|yellow|lightgrey}}) +![Raintree Technology](https://img.shields.io/badge/Raintree-Technology-1a7f37) + +{{One-line description: what this is and who it's for.}} + +## Stack + +{{e.g. Next.js 16 (App Router) · TypeScript (strict) · Bun · Neon (Postgres) · Drizzle · Better Auth · Vercel · Biome}} + +## Setup + +```bash +{{bun install}} +cp .env.example .env.local # then fill in values +{{bun dev}} +``` + +## Environment variables + +Validated at boot by {{`lib/env.ts`}} (Zod). Real values live in Vercel env / a +secret manager — never in the repo. + +| Variable | Purpose | +| --- | --- | +| `{{DATABASE_URL}}` | {{Neon Postgres connection string}} | + +## Scripts + +| Script | What it does | +| --- | --- | +| `dev` | local dev server | +| `build` | production build | +| `check` | Biome lint + format check | +| `typecheck` | `tsc --noEmit` | +| `test` | test suite | + +## Deploy + +{{Vercel project `{{name}}`; pushes to `main` deploy via PR merge only.}} + +## License + +{{MIT — see [LICENSE](LICENSE) | Proprietary — © FinSync LLC (dba Raintree Technology)}} + +--- + +Built by [Raintree Technology](https://raintree.technology) · [hello@raintree.technology](mailto:hello@raintree.technology) From 326f815e920df16bafa8234b236e2de4b78ee660 Mon Sep 17 00:00:00 2001 From: admin-raintree <277948009+admin-raintree@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:33:40 -0700 Subject: [PATCH 5/8] fix(ci): resolve standard scripts at the called workflow's own SHA github.job_workflow_sha keeps check-pinned-deps.mjs and the drift-check engine locked to the exact workflow commit the caller pinned, instead of floating on main. Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 6 +++++- .github/workflows/drift-check.yml | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5763c81..94df114 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,8 +87,12 @@ jobs: esac - name: Verify exact-pinned dependencies + env: + # SHA of this reusable workflow's own commit — keeps the script + # version locked to the workflow version the caller pinned. + STANDARD_SHA: ${{ github.job_workflow_sha || 'main' }} run: | - curl -fsSL "https://raw.githubusercontent.com/raintree-technology/.github/main/scripts/check-pinned-deps.mjs" -o /tmp/check-pinned-deps.mjs + curl -fsSL "https://raw.githubusercontent.com/raintree-technology/.github/${STANDARD_SHA}/scripts/check-pinned-deps.mjs" -o /tmp/check-pinned-deps.mjs node /tmp/check-pinned-deps.mjs - name: Biome check diff --git a/.github/workflows/drift-check.yml b/.github/workflows/drift-check.yml index b15ba1c..b0f0bd5 100644 --- a/.github/workflows/drift-check.yml +++ b/.github/workflows/drift-check.yml @@ -32,6 +32,8 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: raintree-technology/.github + # same commit as the reusable workflow the caller pinned + ref: ${{ github.job_workflow_sha }} path: .raintree-standard persist-credentials: false From a99e879574108241a5fc7e96479512b135248d2a Mon Sep 17 00:00:00 2001 From: admin-raintree <277948009+admin-raintree@users.noreply.github.com> Date: Thu, 11 Jun 2026 23:33:33 -0700 Subject: [PATCH 6/8] fix(ci): pass standard script ref explicitly --- .github/workflows/ci.yml | 11 +++++++---- .github/workflows/drift-check.yml | 12 +++++++++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94df114..547f1e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,7 @@ # uses: raintree-technology/.github/.github/workflows/ci.yml@ # with: # package-manager: bun # bun | pnpm | npm +# standard-ref: # same SHA as the reusable workflow ref # secrets: inherit # # Pipeline: frozen install → pin check → biome → typecheck → test → build @@ -34,6 +35,10 @@ on: description: "Root of the package being checked" type: string default: "." + standard-ref: + description: "Ref in raintree-technology/.github to fetch standard scripts from; pin to the same SHA as this reusable workflow." + type: string + default: main run-build: description: "Run the build script (disable for repos whose build needs deploy credentials)" type: boolean @@ -88,11 +93,9 @@ jobs: - name: Verify exact-pinned dependencies env: - # SHA of this reusable workflow's own commit — keeps the script - # version locked to the workflow version the caller pinned. - STANDARD_SHA: ${{ github.job_workflow_sha || 'main' }} + STANDARD_REF: ${{ inputs.standard-ref }} run: | - curl -fsSL "https://raw.githubusercontent.com/raintree-technology/.github/${STANDARD_SHA}/scripts/check-pinned-deps.mjs" -o /tmp/check-pinned-deps.mjs + curl -fsSL "https://raw.githubusercontent.com/raintree-technology/.github/${STANDARD_REF}/scripts/check-pinned-deps.mjs" -o /tmp/check-pinned-deps.mjs node /tmp/check-pinned-deps.mjs - name: Biome check diff --git a/.github/workflows/drift-check.yml b/.github/workflows/drift-check.yml index b0f0bd5..6220374 100644 --- a/.github/workflows/drift-check.yml +++ b/.github/workflows/drift-check.yml @@ -8,13 +8,20 @@ # jobs: # drift: # uses: raintree-technology/.github/.github/workflows/drift-check.yml@ +# with: +# standard-ref: # same SHA as the reusable workflow ref # # Fails when the repo stops meeting the Raintree standard: missing files, # unpinned deps, unpinned action refs, stale/absent central-CI wiring. name: Raintree drift check on: - workflow_call: {} + workflow_call: + inputs: + standard-ref: + description: "Ref in raintree-technology/.github to check out standard scripts from; pin to the same SHA as this reusable workflow." + type: string + default: main permissions: contents: read @@ -32,8 +39,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: raintree-technology/.github - # same commit as the reusable workflow the caller pinned - ref: ${{ github.job_workflow_sha }} + ref: ${{ inputs.standard-ref }} path: .raintree-standard persist-credentials: false From 87c460a4be6d320112ccd82e094c4137c6444d81 Mon Sep 17 00:00:00 2001 From: admin-raintree <277948009+admin-raintree@users.noreply.github.com> Date: Fri, 12 Jun 2026 00:40:04 -0700 Subject: [PATCH 7/8] fix(ci): avoid external pnpm setup action --- .github/workflows/ci.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 547f1e5..03a4158 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,10 @@ on: description: "Node version for pnpm/npm repos (and tooling)" type: string default: "22" + pnpm-version: + description: "pnpm version activated through Corepack for pnpm repos" + type: string + default: "10.25.0" bun-version: description: "Bun version for bun repos" type: string @@ -72,16 +76,19 @@ jobs: with: bun-version: ${{ inputs.bun-version }} - - name: Setup pnpm - if: inputs.package-manager == 'pnpm' - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - name: Setup Node if: inputs.package-manager != 'bun' uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: ${{ inputs.node-version }} + - name: Setup pnpm through Corepack + if: inputs.package-manager == 'pnpm' + run: | + corepack enable + corepack prepare "pnpm@${{ inputs.pnpm-version }}" --activate + pnpm --version + - name: Install (frozen lockfile) run: | case "${{ inputs.package-manager }}" in From 3256afba6d5c060f8b00f1bbf96887d253b0e9e1 Mon Sep 17 00:00:00 2001 From: admin-raintree <277948009+admin-raintree@users.noreply.github.com> Date: Fri, 12 Jun 2026 00:44:05 -0700 Subject: [PATCH 8/8] docs(ci): clarify standard-ref and public repo secrets --- .github/workflows/ci.yml | 2 +- README.md | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03a4158..bb1b27f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ # with: # package-manager: bun # bun | pnpm | npm # standard-ref: # same SHA as the reusable workflow ref -# secrets: inherit +# secrets: inherit # private repos; public repos may omit when no optional org secrets are needed # # Pipeline: frozen install → pin check → biome → typecheck → test → build # → gitleaks (full history) → Socket (only when an API key secret exists). diff --git a/README.md b/README.md index 2ab1208..68596ba 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,8 @@ jobs: uses: raintree-technology/.github/.github/workflows/ci.yml@ with: package-manager: bun # bun | pnpm | npm - secrets: inherit + standard-ref: + secrets: inherit # private repos; public repos may omit this when no optional org secrets are needed ``` ### Calling the drift check @@ -60,6 +61,8 @@ on: jobs: drift: uses: raintree-technology/.github/.github/workflows/drift-check.yml@ + with: + standard-ref: ``` ## How it works