Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Raintree Technology — reusable CI
#
# Called by every repo in the org:
#
# jobs:
# ci:
# uses: raintree-technology/.github/.github/workflows/ci.yml@<commit-sha>
# with:
# package-manager: bun # bun | pnpm | npm
# standard-ref: <commit-sha> # same SHA as the reusable workflow ref
# 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).
# 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"
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
default: "1.3.11"
working-directory:
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
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 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
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
env:
STANDARD_REF: ${{ inputs.standard-ref }}
run: |
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
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
}
52 changes: 52 additions & 0 deletions .github/workflows/drift-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 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@<commit-sha>
# with:
# standard-ref: <commit-sha> # 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:
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

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
ref: ${{ inputs.standard-ref }}
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
4 changes: 4 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -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
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,59 @@ 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@<commit-sha>
with:
package-manager: bun # bun | pnpm | npm
standard-ref: <commit-sha>
secrets: inherit # private repos; public repos may omit this when no optional org secrets are needed
```

### 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@<commit-sha>
with:
standard-ref: <commit-sha>
```

## How it works

Expand Down
32 changes: 32 additions & 0 deletions configs/biome.base.jsonc
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
25 changes: 25 additions & 0 deletions configs/renovate-base.json
Original file line number Diff line number Diff line change
@@ -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
}
}
15 changes: 15 additions & 0 deletions configs/tsconfig.base.json
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading