Skip to content
Closed
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
24 changes: 24 additions & 0 deletions .github/file-size-defaults.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Org-wide file-size defaults. Consumed by .github/workflows/file-size.yml,
# which is referenced as a Required Workflow by terraform-github's
# github_organization_ruleset.org_file_size_check.
#
# Per-repo overrides land in the consuming repo at .github/file-size.yml
# (preferred) or .file-size.yml at the root (legacy, deprecation warning).

defaults:
# Bytes. Files larger than `warn` emit ::warning::; files larger than
# `error` emit ::error:: and fail the check.
warn: 6144
error: 12288

# File extensions scanned by the check. Per-repo overrides REPLACE this list
# (not additive) — repos that need a different scan set declare their own.
scan:
- .md
- .nix
- .tf

# Always-exempt files (base name, no extension). Per-repo `exempt` lists are
# additive to this org default.
exempt:
- CHANGELOG
158 changes: 158 additions & 0 deletions .github/scripts/file-size-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#!/usr/bin/env bash
# File-size check executed by .github/workflows/file-size.yml.
#
# Reads org defaults from a trusted file (passed as the only argument) and
# per-repo overrides from the consuming repo's .github/file-size.yml or
# legacy .file-size.yml at root. Override values are attacker-controllable
# on PR runs and are validated against strict regexes before use; the
# defaults file is in the org-controlled checkout and therefore trusted.
#
# Emits GitHub Actions ::warning:: and ::error:: annotations. Exits non-zero
# only when at least one file exceeds the error threshold.

set -euo pipefail

if [ $# -ne 1 ]; then
echo "usage: $0 <path-to-defaults-yml>" >&2
exit 2
fi

DEFAULTS_FILE=$1

if [ ! -f "$DEFAULTS_FILE" ]; then
echo "::error::defaults file not found: $DEFAULTS_FILE"
exit 2
fi

# Validators for attacker-controllable override values. yq output is treated
# as untrusted string data; nothing flows into shell eval, but explicit
# format checks block surprise inputs.
is_positive_int() { [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -gt 0 ]; }
is_extension() { [[ "$1" =~ ^\.[a-zA-Z0-9]+$ ]]; }
is_basename() { [[ "$1" =~ ^[a-zA-Z0-9_.-]+$ ]]; }

# Org defaults — trusted source, no validation needed.
WARN=$(yq '.defaults.warn' "$DEFAULTS_FILE")
ERR=$(yq '.defaults.error' "$DEFAULTS_FILE")
DEFAULT_SCAN=$(yq '.scan | .[]' "$DEFAULTS_FILE" | tr '\n' ' ')
EXEMPT=" $(yq '.exempt | .[]' "$DEFAULTS_FILE" | tr '\n' ' ')"

# Per-repo override: prefer .github/file-size.yml, fall back to legacy
# .file-size.yml at root (deprecation warning emitted).
OVERRIDE=""
if [ -f ".github/file-size.yml" ]; then
OVERRIDE=".github/file-size.yml"
elif [ -f ".file-size.yml" ]; then
OVERRIDE=".file-size.yml"
echo "::warning::.file-size.yml at repo root is deprecated; move to .github/file-size.yml"
fi

EXT_LIMIT=0
EXTENDED=" "

if [ -n "$OVERRIDE" ]; then
cand=$(yq ".defaults.warn // $WARN" "$OVERRIDE")
if is_positive_int "$cand"; then
WARN=$cand
else
echo "::warning file=$OVERRIDE::defaults.warn must be a positive integer; ignoring '$cand'"
fi

cand=$(yq ".defaults.error // $ERR" "$OVERRIDE")
if is_positive_int "$cand"; then
ERR=$cand
else
echo "::warning file=$OVERRIDE::defaults.error must be a positive integer; ignoring '$cand'"
fi

cand=$(yq '.extended.limit // 0' "$OVERRIDE")
if [[ "$cand" =~ ^[0-9]+$ ]]; then
EXT_LIMIT=$cand
else
echo "::warning file=$OVERRIDE::extended.limit must be a non-negative integer; ignoring '$cand'"
fi

cfg_scan=""
while IFS= read -r ext; do
[ -z "$ext" ] && continue
if is_extension "$ext"; then
cfg_scan="$cfg_scan$ext "
else
echo "::warning file=$OVERRIDE::scan entry '$ext' must match ^\\.[a-zA-Z0-9]+\$; skipped"
fi
done < <(yq '.scan // [] | .[]' "$OVERRIDE")
[ -n "$cfg_scan" ] && DEFAULT_SCAN="$cfg_scan"

while IFS= read -r base; do
[ -z "$base" ] && continue
if is_basename "$base"; then
EXTENDED="$EXTENDED$base "
else
echo "::warning file=$OVERRIDE::extended.files entry '$base' must match ^[a-zA-Z0-9_.-]+\$; skipped"
fi
done < <(yq '.extended.files // [] | .[]' "$OVERRIDE")

while IFS= read -r base; do
[ -z "$base" ] && continue
if is_basename "$base"; then
EXEMPT="$EXEMPT$base "
else
echo "::warning file=$OVERRIDE::exempt entry '$base' must match ^[a-zA-Z0-9_.-]+\$; skipped"
fi
done < <(yq '.exempt // [] | .[]' "$OVERRIDE")
fi

# Build find name arguments from scan extensions.
name_args=()
first=true
for ext in $DEFAULT_SCAN; do
if $first; then
first=false
else
name_args+=(-o)
fi
name_args+=(-name "*${ext}")
done

errors=0
warnings=0

while IFS= read -r -d '' f; do
base="${f##*/}"
base="${base%.*}"
size=$(stat -c%s "$f")

if [[ "$EXEMPT" == *" $base "* ]]; then
continue
fi

if [[ "$EXT_LIMIT" -gt 0 ]] && [[ "$EXTENDED" == *" $base "* ]]; then
limit=$EXT_LIMIT
warn_threshold=$limit
else
limit=$ERR
warn_threshold=$WARN
fi

if [ "$size" -gt "$limit" ]; then
echo "::error file=$f::$f is $((size / 1024))KB (exceeds $((limit / 1024))KB limit)"
errors=$((errors + 1))
elif [ "$size" -gt "$warn_threshold" ]; then
echo "::warning file=$f::$f is $((size / 1024))KB (exceeds $((warn_threshold / 1024))KB recommended)"
warnings=$((warnings + 1))
fi
done < <(find . -type f \( "${name_args[@]}" \) \
-not -path "./.git/*" \
-not -path "./.org-github/*" \
-not -path "./node_modules/*" \
-not -path "./result/*" \
-not -name "*.lock" \
-not -name "package-lock.json" \
-not -name "pnpm-lock.yaml" \
-print0)

echo "File size check: ${errors} error(s), ${warnings} warning(s)"
if [ "$errors" -gt 0 ]; then
exit 1
fi
exit 0
49 changes: 49 additions & 0 deletions .github/workflows/file-size.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Standalone file-size check for org-wide enforcement via Required Workflows.
#
# Referenced by dryvist/terraform-github's github_organization_ruleset
# (org_file_size_check), which injects this workflow into the default-branch
# PRs of EVERY repo in the org. All logic lives in
# .github/scripts/file-size-check.sh (org-controlled). Defaults — thresholds,
# scan extensions, exempt list — are sourced from
# .github/file-size-defaults.yml.
#
# Per-repo overrides land in the consuming repo at:
# .github/file-size.yml — preferred (consolidated workflow-consumed
# configs live under .github/)
# .file-size.yml — legacy root path; supported with a deprecation
# warning, removed in a future iteration
#
# Override fields (all optional, all additive unless noted):
# defaults: { warn: <bytes>, error: <bytes> } # overrides org defaults
# scan: [<.ext>, …] # REPLACES default scan list
# extended: { limit: <bytes>, files: [<base>] } # additive higher-limit set
# exempt: [<base>] # additive to org exempt list
name: File Size

on:
pull_request:

permissions:
contents: read

concurrency:
group: org-file-size-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- name: Checkout target repo
uses: actions/checkout@v6

- name: Checkout org config (single source of truth)
uses: actions/checkout@v6
with:
repository: dryvist/.github
ref: main
path: .org-github

- name: Check file sizes
run: .org-github/.github/scripts/file-size-check.sh .org-github/.github/file-size-defaults.yml
74 changes: 55 additions & 19 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,46 @@ The canonical `biome.jsonc` and `.markdownlint-cli2.yaml` live in this repo at
the root. Repos copy them at scaffold time; periodic sync is handled by
Renovate's custom manager (or manual update for now — see `renovate.json`).

## Inheritance from `JacobPEvans/.github`

We reuse JacobPEvans's reusable workflows directly. Don't fork or wrap them
unless we need behavior they don't provide.

| Need | Inherited from | Caller pattern |
| --- | --- | --- |
| Release-please (org-wide major-bump block) | `JacobPEvans/.github/.github/workflows/_release-please.yml@main` | `release-please.yml` in any dryvist repo |
| Renovate presets | `github>JacobPEvans/.github:renovate-presets` | `extends` in `renovate.json` |
| Security policy structure | `JacobPEvans/.github/SECURITY.md` | Adapted/scoped to dryvist (this repo) |

**Inheritance chain:** `JacobPEvans/.github` → `dryvist/.github` → individual
dryvist repos. Re-inheritance works through the same mechanisms (workflow
`uses:` + Renovate `extends:`).
## Workflow library ownership (migration in progress)

`dryvist/.github` is becoming the **source of truth** for the shared workflow
library. `JacobPEvans-personal/.github` is being reduced to a consumer —
inheritance flows dryvist → JacobPEvans-personal, never the reverse. Each
workflow migrates atomically: it lands here, all consumers flip their
`uses:` to point at this repo, then the source in
`JacobPEvans-personal/.github` is deleted.

When adding a new shared workflow (one that more than one repo will call),
write it here. Don't add it to `JacobPEvans-personal/.github`. Existing
`uses: JacobPEvans-personal/.github/.github/workflows/_*.yml@main` references
should be flipped to `uses: dryvist/.github/.github/workflows/<name>.yml@main`
the next time they're touched, even if their workflow isn't formally
migrated yet.

Sourced from this repo (`dryvist/.github`) — Required Workflows attached
via `terraform-github`; no per-repo caller needed:

- `file-size` — workflow at `.github/workflows/file-size.yml`, logic in
`.github/scripts/file-size-check.sh`, defaults in
`.github/file-size-defaults.yml`.
- `markdownlint` — workflow at `.github/workflows/markdownlint.yml`,
config in `.markdownlint-cli2.yaml` at the repo root.

Still inherited from `JacobPEvans-personal/.github` (pending migration
into this repo):

- Release-please — `_release-please.yml@main`. Per-repo caller
`release-please.yml` forwards `GH_APP_ID` / `GH_APP_PRIVATE_KEY`
secrets (see Prereq below).
- Renovate presets — extends
`github>JacobPEvans-personal/.github:renovate-presets` in
`renovate.json`.
- Security policy structure — `SECURITY.md` template, scoped and
adapted in this repo.

Older docs and PR templates may still use the redirect-friendly
`JacobPEvans/<repo>` form. Don't mass-rewrite those — see `~/CLAUDE.local.md`
for the redirect rules.

**Prereq for release-please:** the inherited workflow needs a GitHub App
token at runtime. dryvist exposes two generic org-level secrets — caller
Expand All @@ -78,7 +104,15 @@ steps.)
- AI assistant policy (this file)
- Org-wide tooling configs (`biome.jsonc`, `renovate.json`)
- Community health files GitHub auto-applies (`SECURITY.md`, `profile/README.md`)
- Caller workflow templates that wire up inherited reusable workflows
- The shared workflow library (`.github/workflows/*.yml`) — Required
Workflows referenced by org rulesets in `terraform-github`, plus
reusables that any dryvist repo can opt into with `uses:`
- Bash/POSIX implementations of workflow steps (`.github/scripts/*.sh`) —
extracted from workflow YAML per the no-scripts rule; ship as
committed artifacts with `+x` in the index
- Workflow defaults (`.github/<name>-defaults.yml`) — no magic numbers in
workflow YAML or scripts; thresholds and lists live in these dedicated
files, consumed via `yq`

It does **NOT** contain anything vendor- or product-specific. Cribl pack
infrastructure lives in [`dryvist/cc-edge-pack-template`](https://github.com/dryvist/cc-edge-pack-template).
Expand All @@ -98,10 +132,12 @@ For every change in dryvist:

## When in doubt

- Read [`JacobPEvans/.github`](https://github.com/JacobPEvans/.github) for the
upstream patterns we inherit.
- Read [`JacobPEvans-personal/.github`](https://github.com/JacobPEvans-personal/.github)
for patterns still inherited from there (mostly release-please and Renovate
presets, pending migration into this repo).
- Read this repo's `biome.jsonc` for current lint/format rules.
- Read [`dryvist/cc-edge-pack-template`](https://github.com/dryvist/cc-edge-pack-template)
for Cribl-specific test/build scaffolding.
- For release-please specifics, the inherited workflow's docstring at
`JacobPEvans/.github/.github/workflows/_release-please.yml` is authoritative.
- For release-please specifics, the (still-)inherited workflow's docstring at
`JacobPEvans-personal/.github/.github/workflows/_release-please.yml` is
authoritative until that workflow migrates here.
Loading