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.
- package/.editorconfig +18 -0
- package/.env.example +3 -0
- package/.gitattributes +9 -0
- package/.githooks/pre-commit +52 -0
- package/.github/CODEOWNERS +2 -0
- package/.github/CODE_OF_CONDUCT.md +41 -0
- package/.github/CONTRIBUTING.md +194 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +56 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +38 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +29 -0
- package/.github/SECURITY.md +23 -0
- package/.github/actions/setup/action.yml +20 -0
- package/.github/dependabot.yml +42 -0
- package/.github/scripts/bootstrap.sh +13 -0
- package/.github/scripts/ci.sh +245 -0
- package/.github/scripts/codeql-languages.sh +22 -0
- package/.github/scripts/doctor.sh +103 -0
- package/.github/scripts/init-repo.sh +109 -0
- package/.github/scripts/security.sh +51 -0
- package/.github/scripts/sitecustomize.py +17 -0
- package/.github/scripts/sync-protection.sh +124 -0
- package/.github/scripts/sync-template.sh +242 -0
- package/.github/template.yml.example +5 -0
- package/.github/workflows/ci.yml +72 -0
- package/.github/workflows/codeql.yml +56 -0
- package/.github/workflows/draft-pr.yml +62 -0
- package/.github/workflows/publish.yml +25 -0
- package/.github/workflows/release-pr.yml +61 -0
- package/.github/workflows/release.yml +23 -0
- package/.github/workflows/security.yml +38 -0
- package/.github/workflows/test.yml +84 -0
- package/.gitignore +73 -0
- package/.mise.toml +8 -0
- package/.prettierrc +6 -0
- package/AGENTS.md +154 -0
- package/LICENSE +616 -0
- package/NOTICE +7 -0
- package/README.md +94 -0
- package/package.json +42 -0
- package/ruff.toml +6 -0
- 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"
|