code-foundry 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.editorconfig +18 -0
  2. package/.env.example +3 -0
  3. package/.gitattributes +9 -0
  4. package/.githooks/pre-commit +52 -0
  5. package/.github/CODEOWNERS +2 -0
  6. package/.github/CODE_OF_CONDUCT.md +41 -0
  7. package/.github/CONTRIBUTING.md +194 -0
  8. package/.github/ISSUE_TEMPLATE/bug_report.yml +56 -0
  9. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  10. package/.github/ISSUE_TEMPLATE/feature_request.yml +38 -0
  11. package/.github/PULL_REQUEST_TEMPLATE.md +29 -0
  12. package/.github/SECURITY.md +23 -0
  13. package/.github/actions/setup/action.yml +20 -0
  14. package/.github/dependabot.yml +42 -0
  15. package/.github/scripts/bootstrap.sh +13 -0
  16. package/.github/scripts/ci.sh +245 -0
  17. package/.github/scripts/codeql-languages.sh +22 -0
  18. package/.github/scripts/doctor.sh +103 -0
  19. package/.github/scripts/init-repo.sh +109 -0
  20. package/.github/scripts/security.sh +51 -0
  21. package/.github/scripts/sitecustomize.py +17 -0
  22. package/.github/scripts/sync-protection.sh +124 -0
  23. package/.github/scripts/sync-template.sh +242 -0
  24. package/.github/template.yml.example +5 -0
  25. package/.github/workflows/ci.yml +72 -0
  26. package/.github/workflows/codeql.yml +56 -0
  27. package/.github/workflows/draft-pr.yml +62 -0
  28. package/.github/workflows/publish.yml +25 -0
  29. package/.github/workflows/release-pr.yml +61 -0
  30. package/.github/workflows/release.yml +23 -0
  31. package/.github/workflows/security.yml +38 -0
  32. package/.github/workflows/test.yml +84 -0
  33. package/.gitignore +73 -0
  34. package/.mise.toml +8 -0
  35. package/.prettierrc +6 -0
  36. package/AGENTS.md +154 -0
  37. package/LICENSE +616 -0
  38. package/NOTICE +7 -0
  39. package/README.md +94 -0
  40. package/package.json +42 -0
  41. package/ruff.toml +6 -0
  42. package/src/cli.mjs +134 -0
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ -d .venv/bin ]; then export PATH="$PWD/.venv/bin:$PATH"; fi
5
+
6
+ has_script() {
7
+ [ -f package.json ] && node -e 'const p=require("./package.json"); process.exit(p.scripts?.[process.argv[1]] ? 0 : 1)' "$1"
8
+ }
9
+
10
+ has_bun_native_coverage() {
11
+ [ -f package.json ] && node -e 'const p=require("./package.json"); process.exit(p.scripts?.["test:coverage"]?.includes("bun test") ? 0 : 1)'
12
+ }
13
+
14
+ has_javascript() {
15
+ git ls-files -- '*.js' '*.jsx' '*.ts' '*.tsx' | grep -q .
16
+ }
17
+
18
+ has_python() {
19
+ [ -f pyproject.toml ] || [ -f requirements.txt ] || [ -f requirements-dev.txt ] || \
20
+ git ls-files -- '*.py' ':!.github/**' | grep -q .
21
+ }
22
+
23
+ has_graph_project() {
24
+ [ -f package.json ] && node -e 'const p=require("./package.json"); process.exit(p.devDependencies?.["@graphprotocol/graph-cli"] ? 0 : 1)'
25
+ }
26
+
27
+ package_manager() {
28
+ if [ -f bun.lock ] || [ -f bun.lockb ]; then echo bun
29
+ elif [ -f pnpm-lock.yaml ]; then echo pnpm
30
+ elif [ -f yarn.lock ]; then echo yarn
31
+ elif [ -f package-lock.json ]; then echo npm
32
+ else echo bun
33
+ fi
34
+ }
35
+
36
+ run_script() {
37
+ if ! has_script "$1"; then echo "Skipping $1 (script not defined)"; return; fi
38
+ case "$(package_manager)" in
39
+ bun) bun run "$1" ;;
40
+ pnpm) corepack pnpm run "$1" ;;
41
+ yarn) corepack yarn run "$1" ;;
42
+ npm) npm run "$1" ;;
43
+ esac
44
+ }
45
+
46
+ run_package_tool() {
47
+ case "$(package_manager)" in
48
+ bun) bunx --no-install "$@" ;;
49
+ pnpm) corepack pnpm exec "$@" ;;
50
+ yarn) corepack yarn exec "$@" ;;
51
+ npm) npx --no-install "$@" ;;
52
+ esac
53
+ }
54
+
55
+ run_bun_coverage() {
56
+ local log_file coverage_line functions lines minimum
57
+ log_file="$(mktemp)"
58
+ if ! run_script test:coverage 2>&1 | tee "$log_file"; then
59
+ rm -f "$log_file"
60
+ return 1
61
+ fi
62
+ coverage_line="$(grep -E '^All files[[:space:]]*\|' "$log_file" | tail -n 1 || true)"
63
+ if [ -z "$coverage_line" ]; then
64
+ echo "Coverage summary not found in test:coverage output" >&2
65
+ rm -f "$log_file"
66
+ return 1
67
+ fi
68
+ read -r functions lines < <(printf '%s\n' "$coverage_line" | awk -F'|' '{for (i = 1; i <= NF; i++) gsub(/[[:space:]]/, "", $i); if (NF >= 5) print $4, $5; else if (NF >= 3) print $2, $3}')
69
+ minimum="${BUN_COVERAGE_MIN:-80}"
70
+ if ! awk -v functions="$functions" -v lines="$lines" -v minimum="$minimum" \
71
+ 'BEGIN { exit !(functions + 0 >= minimum && lines + 0 >= minimum) }'; then
72
+ echo "Coverage below ${minimum}%: functions=${functions}% lines=${lines}%" >&2
73
+ rm -f "$log_file"
74
+ return 1
75
+ fi
76
+ echo "Coverage threshold passed: functions=${functions}% lines=${lines}% (minimum ${minimum}%)"
77
+ rm -f "$log_file"
78
+ }
79
+
80
+ python_coverage_args() {
81
+ if [ -f pyproject.toml ]; then
82
+ python - <<'PY'
83
+ import tomllib
84
+ from pathlib import Path
85
+
86
+ config = tomllib.loads(Path("pyproject.toml").read_text())
87
+ for source in config.get("tool", {}).get("coverage", {}).get("run", {}).get("source", []):
88
+ print(f"--cov={source}")
89
+ PY
90
+ fi
91
+ }
92
+
93
+ install() {
94
+ if [ -f package.json ]; then
95
+ case "$(package_manager)" in
96
+ bun) bun install --frozen-lockfile ;;
97
+ pnpm) corepack pnpm install --frozen-lockfile ;;
98
+ yarn) corepack yarn install --immutable ;;
99
+ npm) npm ci ;;
100
+ esac
101
+ fi
102
+ if [ -f Cargo.toml ]; then cargo fetch --locked; fi
103
+ if has_python; then python -m venv .venv; .venv/bin/python -m pip install --disable-pip-version-check --quiet ruff; fi
104
+ if [ -f requirements.txt ]; then .venv/bin/python -m pip install --disable-pip-version-check -r requirements.txt; fi
105
+ if [ -f requirements-dev.txt ]; then .venv/bin/python -m pip install --disable-pip-version-check -r requirements-dev.txt; fi
106
+ }
107
+
108
+ rust_component() {
109
+ local component="$1"
110
+ if command -v rustup >/dev/null 2>&1; then
111
+ local toolchain="${RUSTUP_TOOLCHAIN:-$(rustup show active-toolchain | awk '{print $1}')}"
112
+ rustup component add --toolchain "$toolchain" "$component" >/dev/null
113
+ fi
114
+ }
115
+
116
+ has_rust_target() {
117
+ local kind="$1"
118
+ cargo metadata --no-deps --format-version 1 |
119
+ jq -e --arg kind "$kind" 'any(.packages[].targets[]; (.kind | index($kind)) != null)' >/dev/null
120
+ }
121
+
122
+ format() {
123
+ if has_script format:check; then run_script format:check
124
+ elif has_javascript; then run_package_tool prettier --check .
125
+ else echo "Skipping JavaScript/TypeScript formatting (no formatter script or source found)"; fi
126
+ if [ -f Cargo.toml ]; then rust_component rustfmt; cargo fmt --check; fi
127
+ if has_python && command -v ruff >/dev/null 2>&1; then ruff format --check .; fi
128
+ }
129
+
130
+ lint() {
131
+ if has_script lint; then run_script lint
132
+ elif has_javascript; then run_package_tool eslint .
133
+ else echo "Skipping JavaScript/TypeScript lint (no lint script or source found)"; fi
134
+ if [ -f Cargo.toml ]; then rust_component clippy; cargo clippy --all-targets -- -D warnings; fi
135
+ if has_python && command -v ruff >/dev/null 2>&1; then ruff check .; fi
136
+ }
137
+
138
+ type_check() {
139
+ if has_graph_project; then
140
+ echo "Skipping TypeScript type-check (Graph AssemblyScript project uses graph build/codegen)"
141
+ elif has_script type-check; then run_script type-check
142
+ elif has_script typecheck; then run_script typecheck
143
+ else echo "Skipping type-check (script not defined)"; fi
144
+ if [ -f Cargo.toml ]; then cargo check; fi
145
+ if has_python && command -v python >/dev/null 2>&1; then
146
+ py_dirs=()
147
+ for dir in tests src scripts; do [ -d "$dir" ] && py_dirs+=("$dir"); done
148
+ if [ "${#py_dirs[@]}" -gt 0 ]; then python -m compileall -q "${py_dirs[@]}"; fi
149
+ fi
150
+ }
151
+
152
+ build() {
153
+ # Some frameworks validate session secrets while statically collecting pages.
154
+ # Keep CI builds deterministic without weakening runtime/deployment validation.
155
+ if [ "${CI:-}" = true ] && [ -z "${NEXTAUTH_SECRET:-}" ]; then
156
+ export NEXTAUTH_SECRET="ci-only-build-secret-not-for-runtime-0123456789"
157
+ fi
158
+ run_script build
159
+ if [ -f Cargo.toml ]; then cargo build --all-targets --all-features; fi
160
+ }
161
+
162
+ unit() {
163
+ # Bun repositories should expose test scripts backed by Bun's native runner.
164
+ # Specialized repositories may keep their native runner (for example Matchstick or Hardhat).
165
+ if has_script test:unit; then
166
+ run_script test:unit
167
+ elif has_script test:coverage; then
168
+ if [ "$(package_manager)" = bun ] && has_bun_native_coverage; then
169
+ run_bun_coverage
170
+ else
171
+ run_script test:coverage
172
+ fi
173
+ elif has_script test && ! has_script test:integration; then
174
+ run_script test
175
+ else
176
+ echo "Skipping JavaScript/TypeScript unit tests (script not defined)"
177
+ fi
178
+ if [ -f Cargo.toml ]; then
179
+ if has_rust_target lib; then cargo test --lib --all-features
180
+ elif has_rust_target bin; then cargo test --bins --all-features
181
+ else echo "Skipping Rust unit tests (no library or binary target)"; fi
182
+ fi
183
+ if [ -d tests/unit ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
184
+ coverage_args=()
185
+ while IFS= read -r arg; do [ -n "$arg" ] && coverage_args+=("$arg"); done < <(python_coverage_args)
186
+ [ "${#coverage_args[@]}" -gt 0 ] || coverage_args=(--cov)
187
+ env -u MISE_GITHUB_TOKEN -u MISE_TRUSTED_CONFIG_PATHS -u MISE_YES -u MISE_LOG_LEVEL -u PYTHONHOME PYTHONPATH="$PWD/.github/scripts" python -m pytest -q tests/unit "${coverage_args[@]}" --cov-report=term-missing --cov-fail-under="${PYTHON_COVERAGE_MIN:-80}"
188
+ elif [ -d tests ] && [ ! -d tests/integration ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
189
+ coverage_args=()
190
+ while IFS= read -r arg; do [ -n "$arg" ] && coverage_args+=("$arg"); done < <(python_coverage_args)
191
+ [ "${#coverage_args[@]}" -gt 0 ] || coverage_args=(--cov)
192
+ env -u MISE_GITHUB_TOKEN -u MISE_TRUSTED_CONFIG_PATHS -u MISE_YES -u MISE_LOG_LEVEL -u PYTHONHOME PYTHONPATH="$PWD/.github/scripts" python -m pytest -q tests "${coverage_args[@]}" --cov-report=term-missing --cov-fail-under="${PYTHON_COVERAGE_MIN:-80}"
193
+ else
194
+ echo "Skipping Python unit tests (no unit suite detected)"
195
+ fi
196
+ }
197
+
198
+ integration() {
199
+ if has_script test:integration; then
200
+ run_script test:integration
201
+ else
202
+ echo "Skipping JavaScript/TypeScript integration tests (script not defined)"
203
+ fi
204
+ if [ -f Cargo.toml ] && [ -d tests ]; then cargo test --tests --all-features; fi
205
+ if [ -d tests/integration ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
206
+ python -m pytest -q tests/integration
207
+ else
208
+ echo "Skipping Python integration tests (tests/integration not found)"
209
+ fi
210
+ }
211
+
212
+ e2e() {
213
+ if has_script test:e2e; then
214
+ run_script test:e2e
215
+ elif has_script e2e; then
216
+ run_script e2e
217
+ else
218
+ echo "Skipping JavaScript/TypeScript E2E tests (script not defined)"
219
+ fi
220
+ if [ -d tests/e2e ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
221
+ python -m pytest -q tests/e2e
222
+ else
223
+ echo "Skipping Python E2E tests (tests/e2e not found)"
224
+ fi
225
+ }
226
+
227
+ smoke() {
228
+ if has_script test:smoke; then
229
+ run_script test:smoke
230
+ elif has_script smoke; then
231
+ run_script smoke
232
+ else
233
+ echo "Skipping JavaScript/TypeScript smoke tests (script not defined)"
234
+ fi
235
+ if [ -d tests/smoke ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
236
+ python -m pytest -q tests/smoke
237
+ else
238
+ echo "Skipping Python smoke tests (tests/smoke not found)"
239
+ fi
240
+ }
241
+
242
+ case "${1:-}" in
243
+ install|format|lint|type_check|build|unit|integration|e2e|smoke) "$1" ;;
244
+ *) echo "usage: $0 {install|format|lint|type_check|build|unit|integration|e2e|smoke}" >&2; exit 2 ;;
245
+ esac
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ languages=()
5
+ if find .github/workflows -type f \( -name '*.yml' -o -name '*.yaml' \) -print -quit | grep -q .; then languages+=(actions); fi
6
+ configured=""
7
+ if [ -f .github/template.yml ]; then
8
+ configured="$(awk -F': ' '/^languages:/ {print $2; exit}' .github/template.yml)"
9
+ fi
10
+
11
+ if [ "$configured" = auto ] || [ "$configured" = all ] || [ -z "$configured" ]; then
12
+ if git ls-files -- '*.ts' '*.tsx' '*.js' '*.jsx' 'package.json' 'tsconfig*.json' | grep -q .; then languages+=(javascript-typescript); fi
13
+ if git ls-files -- '*.py' 'pyproject.toml' 'requirements*.txt' 'setup.py' ':!.github/**' | grep -q .; then languages+=(python); fi
14
+ if git ls-files -- '*.rs' 'Cargo.toml' 'Cargo.lock' | grep -q .; then languages+=(rust); fi
15
+ else
16
+ case ",$configured," in *,typescript,*) languages+=(javascript-typescript) ;; esac
17
+ case ",$configured," in *,python,*) languages+=(python) ;; esac
18
+ case ",$configured," in *,rust,*) languages+=(rust) ;; esac
19
+ fi
20
+
21
+ json=$(printf '%s\n' "${languages[@]}" | jq -Rsc 'split("\n") | map(select(length > 0) | {language: ., name: (if . == "javascript-typescript" then "TypeScript" elif . == "actions" then "Actions" else (. | ascii_upcase[0:1] + .[1:]) end), "build-mode": "none"})')
22
+ printf 'languages=%s\n' "$json" >> "$GITHUB_OUTPUT"
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ errors=0
5
+
6
+ configured_features="all"
7
+ if [ -f .github/template.yml ]; then
8
+ configured_features="$(awk -F': ' '/^features:/ {print $2; exit}' .github/template.yml)"
9
+ [ -n "$configured_features" ] || configured_features="all"
10
+ fi
11
+
12
+ feature_enabled() {
13
+ [ "$configured_features" = all ] && return 0
14
+ case " $(printf '%s' "$configured_features" | tr ',' ' ') " in
15
+ *" $1 "*) return 0 ;;
16
+ *) return 1 ;;
17
+ esac
18
+ }
19
+
20
+ error() {
21
+ printf 'ERROR: %s\n' "$1" >&2
22
+ errors=$((errors + 1))
23
+ }
24
+
25
+ warn() {
26
+ printf 'WARN: %s\n' "$1" >&2
27
+ }
28
+
29
+ if [ ! -f .mise.toml ]; then
30
+ error ".mise.toml is missing"
31
+ fi
32
+
33
+ if [ "$(git config --get core.hooksPath || true)" != ".githooks" ]; then
34
+ warn "Git hooks are not enabled; run bash .github/scripts/bootstrap.sh"
35
+ fi
36
+
37
+ if [ -f package.json ]; then
38
+ node -e 'JSON.parse(require("fs").readFileSync("package.json", "utf8"))'
39
+ lockfiles=0
40
+ for lockfile in bun.lock bun.lockb pnpm-lock.yaml yarn.lock package-lock.json; do
41
+ if [ -f "$lockfile" ]; then lockfiles=$((lockfiles + 1)); fi
42
+ done
43
+ if [ "$lockfiles" -eq 0 ]; then
44
+ if node -e 'const p=require("./package.json"); const groups=[p.dependencies,p.devDependencies,p.optionalDependencies,p.peerDependencies]; process.exit(groups.some((g)=>g && Object.keys(g).length) ? 0 : 1)' 2>/dev/null; then
45
+ error "package.json exists but no supported lockfile was found"
46
+ else
47
+ printf '%s\n' "INFO: package.json has no dependencies; a lockfile is optional"
48
+ fi
49
+ elif [ "$lockfiles" -gt 1 ]; then
50
+ error "multiple JavaScript lockfiles found; keep one package manager"
51
+ fi
52
+ if node -e 'const p=require("./package.json"); process.exit(p.packageManager ? 0 : 1)' 2>/dev/null; then
53
+ declared="$(node -p 'require("./package.json").packageManager.split("@")[0]')"
54
+ actual=""
55
+ [ -f bun.lock ] || [ -f bun.lockb ] && actual="bun"
56
+ [ -f pnpm-lock.yaml ] && actual="pnpm"
57
+ [ -f yarn.lock ] && actual="yarn"
58
+ [ -f package-lock.json ] && actual="npm"
59
+ [ -n "$actual" ] && [ "$declared" != "$actual" ] && error "packageManager ($declared) does not match $actual lockfile"
60
+ fi
61
+ if git ls-files -- '*.ts' '*.tsx' '*.js' '*.jsx' | grep -q .; then
62
+ if ! node -e 'const p=require("./package.json"); const s=p.scripts||{}; process.exit(s.test||s["test:unit"]||s["test:integration"] ? 0 : 1)' 2>/dev/null; then
63
+ warn "JavaScript/TypeScript sources found but no test or test:unit/test:integration script is defined"
64
+ fi
65
+ fi
66
+ if [ -f bunfig.toml ] && node -e 'const p=require("./package.json"); process.exit(p.scripts?.["test:coverage"] ? 0 : 1)' 2>/dev/null; then
67
+ if ! grep -q 'coverageThreshold' bunfig.toml; then
68
+ if [ -x .github/scripts/ci.sh ]; then
69
+ printf '%s\n' "INFO: shared CI enforces the Bun aggregate coverage threshold"
70
+ else
71
+ error "Bun coverage is enabled by test:coverage but no coverage policy is configured"
72
+ fi
73
+ fi
74
+ fi
75
+ fi
76
+
77
+ if [ -f Cargo.toml ]; then
78
+ command -v cargo >/dev/null 2>&1 || error "Cargo is required for this repository"
79
+ cargo metadata --no-deps --format-version 1 >/dev/null
80
+ fi
81
+
82
+ if [ -f pyproject.toml ] || [ -f requirements.txt ] || [ -f requirements-dev.txt ]; then
83
+ if ! command -v python >/dev/null 2>&1 && [ ! -x .venv/bin/python ]; then
84
+ error "Python is required for this repository"
85
+ fi
86
+ fi
87
+
88
+ for workflow in ci codeql security test draft-pr release-pr release; do
89
+ if feature_enabled "$workflow"; then
90
+ [ -f ".github/workflows/$workflow.yml" ] || error "missing enabled workflow: $workflow.yml"
91
+ fi
92
+ done
93
+
94
+ for script in ci.sh codeql-languages.sh security.sh doctor.sh bootstrap.sh sync-template.sh init-repo.sh sync-protection.sh; do
95
+ [ -x ".github/scripts/$script" ] || error "missing executable script: .github/scripts/$script"
96
+ done
97
+
98
+ if [ "$errors" -gt 0 ]; then
99
+ printf '%s\n' "Repository doctor found $errors error(s)." >&2
100
+ exit 1
101
+ fi
102
+
103
+ printf '%s\n' "Repository doctor passed."
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ source="https://github.com/0xPlayerOne/template-repo.git"
5
+ ref="main"
6
+ protection=false
7
+ dry_run=false
8
+ prune=false
9
+ languages="auto"
10
+ features="all"
11
+ package_manager="auto"
12
+ tool_dir=""
13
+
14
+ cleanup() {
15
+ if [ -n "$tool_dir" ]; then rm -rf "$tool_dir"; fi
16
+ }
17
+ trap cleanup EXIT
18
+
19
+ usage() {
20
+ cat <<'EOF'
21
+ Usage: init-repo.sh [options]
22
+
23
+ Initialize or synchronize a repository from the shared baseline.
24
+
25
+ Options:
26
+ --source PATH_OR_URL Template source (default: 0xPlayerOne/template-repo)
27
+ --ref REF Template branch or tag (default: main)
28
+ --languages LIST auto or comma-separated: typescript,rust,python,solidity
29
+ --features LIST all or comma-separated optional features:
30
+ ci,codeql,security,test,draft-pr,release-pr,release,dependabot
31
+ --package-manager NAME auto, bun, pnpm, yarn, or npm
32
+ --dry-run Preview changes without writing files
33
+ --prune Remove disabled standard workflows (never custom workflows)
34
+ --protection Synchronize main branch required checks
35
+ -h, --help Show this help
36
+
37
+ Examples:
38
+ bash .github/scripts/init-repo.sh --languages typescript,python
39
+ bash .github/scripts/init-repo.sh --languages rust --features ci,codeql,security,test
40
+ bash .github/scripts/init-repo.sh --features all --dry-run
41
+ EOF
42
+ }
43
+
44
+ while [ "$#" -gt 0 ]; do
45
+ case "$1" in
46
+ --source) source="${2:?missing source path or URL}"; shift 2 ;;
47
+ --ref) ref="${2:?missing ref}"; shift 2 ;;
48
+ --languages) languages="${2:?missing language list}"; shift 2 ;;
49
+ --features) features="${2:?missing feature list}"; shift 2 ;;
50
+ --package-manager) package_manager="${2:?missing package manager}"; shift 2 ;;
51
+ --dry-run) dry_run=true; shift ;;
52
+ --prune) prune=true; shift ;;
53
+ --protection) protection=true; shift ;;
54
+ -h|--help) usage; exit 0 ;;
55
+ *)
56
+ printf 'Unknown option: %s\n' "$1" >&2
57
+ exit 2
58
+ ;;
59
+ esac
60
+ done
61
+
62
+ case "$package_manager" in
63
+ auto|bun|pnpm|yarn|npm) ;;
64
+ *) printf 'Unsupported package manager: %s\n' "$package_manager" >&2; exit 2 ;;
65
+ esac
66
+
67
+ script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" 2>/dev/null && pwd || true)"
68
+ if [ -f "$script_dir/sync-template.sh" ]; then
69
+ sync_script="$script_dir/sync-template.sh"
70
+ elif [ -d "$source" ] && [ -f "$source/.github/scripts/sync-template.sh" ]; then
71
+ sync_script="$source/.github/scripts/sync-template.sh"
72
+ else
73
+ command -v git >/dev/null 2>&1 || { echo "git is required" >&2; exit 1; }
74
+ tool_dir="$(mktemp -d)"
75
+ git clone --quiet --depth 1 --branch "$ref" "$source" "$tool_dir/template-repo"
76
+ sync_script="$tool_dir/template-repo/.github/scripts/sync-template.sh"
77
+ fi
78
+
79
+ sync_args=(
80
+ --source "$source"
81
+ --ref "$ref"
82
+ --languages "$languages"
83
+ --features "$features"
84
+ )
85
+ if [ "$dry_run" = true ]; then sync_args+=(--check); else sync_args+=(--apply); fi
86
+ if [ "$prune" = true ]; then sync_args+=(--prune); fi
87
+ bash "$sync_script" "${sync_args[@]}"
88
+
89
+ if [ "$dry_run" = true ]; then
90
+ printf '%s\n' 'Dry run complete; no files were changed.'
91
+ exit 0
92
+ fi
93
+
94
+ mkdir -p .github
95
+ cat > .github/template.yml <<EOF
96
+ version: 1
97
+ languages: $languages
98
+ features: $features
99
+ package_manager: $package_manager
100
+ EOF
101
+
102
+ bash .github/scripts/bootstrap.sh
103
+
104
+ if [ "$protection" = true ]; then
105
+ remote="$(git remote get-url origin 2>/dev/null || true)"
106
+ repo="$(printf '%s\n' "$remote" | sed -E 's#.*github.com[:/]([^/]+/[^/.]+)(\.git)?$#\1#')"
107
+ [ -n "$repo" ] || { echo 'Could not determine GitHub repository from origin' >&2; exit 1; }
108
+ bash .github/scripts/sync-protection.sh --repo "$repo" --apply
109
+ fi
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ audits=0
5
+
6
+ package_manager() {
7
+ if [ -f bun.lock ] || [ -f bun.lockb ]; then echo bun
8
+ elif [ -f pnpm-lock.yaml ]; then echo pnpm
9
+ elif [ -f yarn.lock ]; then echo yarn
10
+ elif [ -f package-lock.json ]; then echo npm
11
+ else echo none
12
+ fi
13
+ }
14
+
15
+ if [ -f package.json ]; then
16
+ audit_args=(--audit-level=high)
17
+ if [ -f .github/security-audit-allowlist.txt ]; then
18
+ while IFS= read -r advisory; do
19
+ [[ -z "$advisory" || "$advisory" == \#* ]] && continue
20
+ audit_args+=(--ignore "$advisory")
21
+ done < .github/security-audit-allowlist.txt
22
+ fi
23
+ case "$(package_manager)" in
24
+ bun) audits=$((audits + 1)); bun audit "${audit_args[@]}" ;;
25
+ pnpm) audits=$((audits + 1)); corepack pnpm audit --audit-level high ;;
26
+ yarn) audits=$((audits + 1)); corepack yarn npm audit --all --recursive ;;
27
+ npm) audits=$((audits + 1)); npm audit --audit-level=high ;;
28
+ esac
29
+ else
30
+ echo "Skipping JavaScript/TypeScript audit (package.json not found)"
31
+ fi
32
+
33
+ if [ -f Cargo.toml ]; then
34
+ audits=$((audits + 1))
35
+ if ! command -v cargo-audit >/dev/null 2>&1; then cargo install cargo-audit --locked --quiet; fi
36
+ cargo audit
37
+ else
38
+ echo "Skipping Rust audit (Cargo.toml not found)"
39
+ fi
40
+
41
+ if [ -f requirements.txt ] || [ -f requirements-dev.txt ] || [ -f pyproject.toml ]; then
42
+ audits=$((audits + 1))
43
+ python -m pip install --disable-pip-version-check --quiet pip-audit
44
+ if [ -f requirements.txt ]; then python -m pip_audit -r requirements.txt; fi
45
+ if [ -f requirements-dev.txt ]; then python -m pip_audit -r requirements-dev.txt; fi
46
+ if [ -f pyproject.toml ] && [ ! -f requirements.txt ] && [ ! -f requirements-dev.txt ]; then python -m pip_audit; fi
47
+ else
48
+ echo "Skipping Python audit (Python dependency manifest not found)"
49
+ fi
50
+
51
+ if [ "$audits" -eq 0 ]; then echo "No supported dependency manifests found; nothing to audit"; fi
@@ -0,0 +1,17 @@
1
+ """Keep coverage.py usable with runners that ship malformed sysconfig schemes."""
2
+
3
+ import sysconfig
4
+
5
+ _get_paths = sysconfig.get_paths
6
+
7
+
8
+ def get_paths(scheme=None, vars=None, expand=True):
9
+ try:
10
+ return _get_paths(scheme, vars, expand)
11
+ except ValueError as error:
12
+ if "Single '}' encountered in format string" not in str(error):
13
+ raise
14
+ return {}
15
+
16
+
17
+ sysconfig.get_paths = get_paths
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ branch="main"
5
+ repo=""
6
+ mode="check"
7
+ configured_features="all"
8
+ configured_languages="auto"
9
+
10
+ if [ -f .github/template.yml ]; then
11
+ configured_features="$(awk -F': ' '/^features:/ {print $2; exit}' .github/template.yml)"
12
+ configured_languages="$(awk -F': ' '/^languages:/ {print $2; exit}' .github/template.yml)"
13
+ [ -n "$configured_features" ] || configured_features="all"
14
+ [ -n "$configured_languages" ] || configured_languages="auto"
15
+ fi
16
+
17
+ feature_enabled() {
18
+ [ "$configured_features" = all ] && return 0
19
+ case " $(printf '%s' "$configured_features" | tr ',' ' ') " in
20
+ *" $1 "*) return 0 ;;
21
+ *) return 1 ;;
22
+ esac
23
+ }
24
+
25
+ language_enabled() {
26
+ [ "$configured_languages" = auto ] || [ "$configured_languages" = all ] && return 0
27
+ case " $(printf '%s' "$configured_languages" | tr ',' ' ') " in
28
+ *" $1 "*) return 0 ;;
29
+ *) return 1 ;;
30
+ esac
31
+ }
32
+
33
+ usage() {
34
+ printf '%s\n' 'Usage: sync-protection.sh --repo OWNER/REPO [--branch main] [--check] [--apply]'
35
+ }
36
+
37
+ while [ "$#" -gt 0 ]; do
38
+ case "$1" in
39
+ --repo) repo="${2:?missing OWNER/REPO}"; shift 2 ;;
40
+ --branch) branch="${2:?missing branch}"; shift 2 ;;
41
+ --check) mode="check"; shift ;;
42
+ --apply) mode="apply"; shift ;;
43
+ -h|--help) usage; exit 0 ;;
44
+ *) usage >&2; exit 2 ;;
45
+ esac
46
+ done
47
+
48
+ [ -n "$repo" ] || { usage >&2; exit 2; }
49
+ command -v gh >/dev/null 2>&1 || { echo "gh is required" >&2; exit 1; }
50
+
51
+ is_private="$(gh repo view "$repo" --json isPrivate --jq '.isPrivate')"
52
+ contexts=()
53
+ if feature_enabled ci; then contexts+=(Format Lint Type-Check Build); fi
54
+ if feature_enabled test; then contexts+=(Unit Integration E2E Smoke); fi
55
+ if feature_enabled security; then contexts+=('Dependency Audit'); fi
56
+ if feature_enabled codeql; then contexts+=(Detect); fi
57
+
58
+ if [ "$is_private" != true ] && feature_enabled codeql; then
59
+ contexts+=("Analyze (Actions)")
60
+ if language_enabled typescript && git ls-files -- '*.ts' '*.tsx' '*.js' '*.jsx' package.json tsconfig\*.json | grep -q .; then contexts+=("Analyze (TypeScript)"); fi
61
+ if language_enabled python && git ls-files -- '*.py' pyproject.toml requirements\*.txt setup.py ':!.github/**' | grep -q .; then contexts+=("Analyze (Python)"); fi
62
+ if language_enabled rust && git ls-files -- '*.rs' Cargo.toml Cargo.lock | grep -q .; then contexts+=("Analyze (Rust)"); fi
63
+ fi
64
+
65
+ protection="$(gh api "repos/$repo/branches/$branch/protection" 2>/dev/null || true)"
66
+ if ! jq -e 'type == "object" and (.required_status_checks | type == "object")' >/dev/null 2>&1 <<< "$protection"; then
67
+ if [ "$mode" = apply ]; then
68
+ echo "Cannot read branch protection for $repo:$branch (repository plan or permissions may not allow it)." >&2
69
+ exit 1
70
+ fi
71
+ existing=""
72
+ else
73
+ existing="$(jq -r '.required_status_checks.contexts[]?' <<< "$protection")"
74
+ fi
75
+ preserved=()
76
+ while IFS= read -r context; do
77
+ [ -n "$context" ] || continue
78
+ case "$context" in
79
+ 'Slither / Analyze') continue ;; # removed from the standard workflow set
80
+ 'CI / '*|'Test / '*|'Security / '*|'CodeQL / '*) ;;
81
+ *) preserved+=("$context") ;;
82
+ esac
83
+ done <<< "$existing"
84
+
85
+ payload="$(printf '%s\n' "${preserved[@]-}" "${contexts[@]}" | jq -Rsc 'split("\n") | map(select(length > 0)) | unique | {strict: true, contexts: .}')"
86
+
87
+ if [ "$mode" = check ]; then
88
+ printf '%s\n' "$payload" | jq .
89
+ exit 0
90
+ fi
91
+
92
+ current="$protection"
93
+ full_payload="$(jq --argjson checks "$payload" '
94
+ {
95
+ required_status_checks: $checks,
96
+ enforce_admins: .enforce_admins.enabled,
97
+ required_pull_request_reviews: (
98
+ if .required_pull_request_reviews == null then null else {
99
+ dismiss_stale_reviews: .required_pull_request_reviews.dismiss_stale_reviews,
100
+ require_code_owner_reviews: .required_pull_request_reviews.require_code_owner_reviews,
101
+ required_approving_review_count: .required_pull_request_reviews.required_approving_review_count,
102
+ require_last_push_approval: .required_pull_request_reviews.require_last_push_approval
103
+ } end
104
+ ),
105
+ restrictions: (
106
+ if .restrictions == null then null else {
107
+ users: [.restrictions.users[].login],
108
+ teams: [.restrictions.teams[].slug],
109
+ apps: [.restrictions.apps[].slug]
110
+ } end
111
+ ),
112
+ required_linear_history: .required_linear_history.enabled,
113
+ allow_force_pushes: .allow_force_pushes.enabled,
114
+ allow_deletions: .allow_deletions.enabled,
115
+ block_creations: .block_creations.enabled,
116
+ required_conversation_resolution: .required_conversation_resolution.enabled,
117
+ lock_branch: .lock_branch.enabled,
118
+ allow_fork_syncing: .allow_fork_syncing.enabled
119
+ }
120
+ ' <<< "$current")"
121
+
122
+ gh api --method PUT "repos/$repo/branches/$branch/protection" \
123
+ --input - <<< "$full_payload" >/dev/null
124
+ printf 'Updated required checks for %s:%s\n' "$repo" "$branch"