code-foundry 0.26.0 → 0.27.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/.githooks/pre-commit +3 -4
- package/.github/CONTRIBUTING.md +10 -13
- package/.github/actions/setup/action.yml +50 -3
- package/.github/code-foundry.yml +6 -1
- package/.github/workflows/ci.yml +20 -23
- package/.github/workflows/ci_self-ci.yml +1 -1
- package/.github/workflows/codeql.yml +23 -29
- package/.github/workflows/codeql_self-ci.yml +1 -1
- package/.github/workflows/draft-pr.yml +2 -2
- package/.github/workflows/release-pr.yml +4 -4
- package/.github/workflows/release.yml +17 -29
- package/.github/workflows/security.yml +24 -19
- package/.github/workflows/security_self-ci.yml +1 -1
- package/.github/workflows/test.yml +20 -20
- package/.github/workflows/test_self-ci.yml +1 -1
- package/AGENTS.md +13 -10
- package/CHANGELOG.md +21 -0
- package/README.md +16 -6
- package/docs/CONFIGURATION.md +10 -1
- package/docs/INITIALIZATION.md +6 -5
- package/docs/RELEASES.md +5 -0
- package/docs/WORKFLOWS.md +12 -5
- package/package.json +7 -3
- package/src/cli.mjs +29 -25
- package/src/commands/doctor.mjs +70 -0
- package/src/commands/sync.mjs +224 -0
- package/src/lib/config.mjs +38 -0
- package/src/lib/profile.mjs +114 -0
- package/src/runtime.mjs +316 -0
- package/.github/scripts/bootstrap.sh +0 -21
- package/.github/scripts/changed-files.sh +0 -96
- package/.github/scripts/ci.sh +0 -641
- package/.github/scripts/codeql-languages.sh +0 -91
- package/.github/scripts/doctor.sh +0 -98
- package/.github/scripts/format-fast-path.sh +0 -80
- package/.github/scripts/init-repo.sh +0 -268
- package/.github/scripts/pre-commit.sh +0 -67
- package/.github/scripts/profile.sh +0 -186
- package/.github/scripts/security.sh +0 -214
- package/.github/scripts/sitecustomize.py +0 -17
- package/.github/scripts/sync-codeowners.sh +0 -76
- package/.github/scripts/sync-protection.sh +0 -138
- package/.github/scripts/sync-template.sh +0 -748
- package/.github/scripts/turbo-cache-probe.sh +0 -102
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
# Resolve repository settings with this precedence:
|
|
5
|
-
# explicit REPO_FOUNDRY_* values (CLI callers can export them), then
|
|
6
|
-
# .github/code-foundry.yml, then detected defaults.
|
|
7
|
-
|
|
8
|
-
root="${REPO_FOUNDRY_ROOT:-$PWD}"
|
|
9
|
-
command="detect"
|
|
10
|
-
requested_key=""
|
|
11
|
-
|
|
12
|
-
usage() {
|
|
13
|
-
cat <<'EOF'
|
|
14
|
-
Usage: profile.sh [detect|env|get KEY] [--root PATH]
|
|
15
|
-
|
|
16
|
-
Commands:
|
|
17
|
-
detect Print the resolved profile as key=value lines (default)
|
|
18
|
-
env Print REPO_FOUNDRY_* assignments
|
|
19
|
-
get KEY Print one resolved profile value
|
|
20
|
-
EOF
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
while [ "$#" -gt 0 ]; do
|
|
24
|
-
case "$1" in
|
|
25
|
-
detect|env) command="$1"; shift ;;
|
|
26
|
-
get) command=get; requested_key="${2:?missing profile key}"; shift 2 ;;
|
|
27
|
-
--root) root="${2:?missing root path}"; shift 2 ;;
|
|
28
|
-
-h|--help) usage; exit 0 ;;
|
|
29
|
-
*) printf 'Unknown option: %s\n' "$1" >&2; exit 2 ;;
|
|
30
|
-
esac
|
|
31
|
-
done
|
|
32
|
-
|
|
33
|
-
cd "$root"
|
|
34
|
-
template_file=.github/code-foundry.yml
|
|
35
|
-
|
|
36
|
-
config_value() {
|
|
37
|
-
local key="$1"
|
|
38
|
-
[ -f "$template_file" ] || return 0
|
|
39
|
-
awk -F': *' -v key="$key" '$1 == key { value=substr($0, index($0, ":") + 1); sub(/[[:space:]]+#.*/, "", value); print value; exit }' "$template_file" |
|
|
40
|
-
sed -e 's/^ *//' -e 's/ *$//' -e 's/^['"'"'"]//' -e 's/['"'"'"]$//'
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
env_value() {
|
|
44
|
-
local key="$1" env_key
|
|
45
|
-
env_key="REPO_FOUNDRY_$(printf '%s' "$key" | tr '[:lower:]-' '[:upper:]_')"
|
|
46
|
-
if [ -n "${!env_key+x}" ] && [ -n "${!env_key}" ]; then
|
|
47
|
-
printf '%s\n' "${!env_key}"
|
|
48
|
-
fi
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
raw_value() {
|
|
52
|
-
local key="$1" default="${2:-}" value
|
|
53
|
-
value="$(env_value "$key" || true)"
|
|
54
|
-
[ -n "$value" ] || value="$(config_value "$key" || true)"
|
|
55
|
-
[ -n "$value" ] && printf '%s\n' "$value" || printf '%s\n' "$default"
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
has_source() {
|
|
59
|
-
find . -path './.git' -prune -o -type f -print |
|
|
60
|
-
awk -v pattern="(^|/)[^/]+\\.($1)$" '$0 ~ pattern { found=1; exit } END { exit !found }'
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
detect_languages() {
|
|
64
|
-
local detected=()
|
|
65
|
-
if [ -f package.json ] || has_source 'js|jsx|mjs|cjs|ts|tsx|mts|cts' ||
|
|
66
|
-
[ -f bun.lock ] || [ -f bun.lockb ] || [ -f pnpm-lock.yaml ] ||
|
|
67
|
-
[ -f yarn.lock ] || [ -f package-lock.json ]; then
|
|
68
|
-
detected+=(typescript)
|
|
69
|
-
fi
|
|
70
|
-
if [ -f Cargo.toml ] || has_source 'rs'; then detected+=(rust); fi
|
|
71
|
-
if [ -f pyproject.toml ] || [ -f requirements.txt ] || [ -f requirements-dev.txt ] || has_source 'py'; then
|
|
72
|
-
detected+=(python)
|
|
73
|
-
fi
|
|
74
|
-
if find . -path './.git' -prune -o -type f -name '*.sol' -print -quit | grep -q .; then
|
|
75
|
-
detected+=(solidity)
|
|
76
|
-
fi
|
|
77
|
-
(IFS=,; printf '%s\n' "${detected[*]:-none}")
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
detect_package_manager() {
|
|
81
|
-
if [ -f bun.lock ] || [ -f bun.lockb ]; then echo bun
|
|
82
|
-
elif [ -f pnpm-lock.yaml ]; then echo pnpm
|
|
83
|
-
elif [ -f yarn.lock ]; then echo yarn
|
|
84
|
-
elif [ -f package-lock.json ]; then echo npm
|
|
85
|
-
elif [ -f package.json ]; then echo bun
|
|
86
|
-
else echo none
|
|
87
|
-
fi
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
detect_profile() {
|
|
91
|
-
if [ -f turbo.json ] || [ -f pnpm-workspace.yaml ] ||
|
|
92
|
-
( [ -f package.json ] && node -e 'const p=require("./package.json"); process.exit(Array.isArray(p.workspaces) || (p.workspaces && typeof p.workspaces === "object") ? 0 : 1)' 2>/dev/null ); then
|
|
93
|
-
echo monorepo
|
|
94
|
-
elif [ -f package.json ] || [ -f pyproject.toml ] || [ -f Cargo.toml ]; then
|
|
95
|
-
echo application
|
|
96
|
-
else
|
|
97
|
-
echo minimal
|
|
98
|
-
fi
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
resolve_languages() {
|
|
102
|
-
local value
|
|
103
|
-
value="$(raw_value languages auto)"
|
|
104
|
-
[ "$value" = auto ] && value="$(detect_languages)"
|
|
105
|
-
printf '%s\n' "$value"
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
resolve_profile() {
|
|
109
|
-
local value
|
|
110
|
-
value="$(raw_value profile auto)"
|
|
111
|
-
[ "$value" = auto ] && value="$(detect_profile)"
|
|
112
|
-
printf '%s\n' "$value"
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
resolve_package_manager() {
|
|
116
|
-
local value
|
|
117
|
-
value="$(raw_value package_manager auto)"
|
|
118
|
-
[ "$value" = auto ] && value="$(detect_package_manager)"
|
|
119
|
-
printf '%s\n' "$value"
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
resolve_release_type() {
|
|
123
|
-
local value
|
|
124
|
-
value="$(raw_value release_type auto)"
|
|
125
|
-
if [ "$value" = auto ]; then
|
|
126
|
-
if [ -f package.json ]; then value=node
|
|
127
|
-
elif [ -f pyproject.toml ]; then value=python
|
|
128
|
-
elif [ -f Cargo.toml ]; then value=rust
|
|
129
|
-
elif [ -f version.txt ]; then value=simple
|
|
130
|
-
else value=none
|
|
131
|
-
fi
|
|
132
|
-
fi
|
|
133
|
-
printf '%s\n' "$value"
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
resolve_npm_publish() {
|
|
137
|
-
local value release_type
|
|
138
|
-
value="$(raw_value npm_publish false)"
|
|
139
|
-
release_type="$(resolve_release_type)"
|
|
140
|
-
[ "$release_type" = node ] || value=false
|
|
141
|
-
printf '%s\n' "$value"
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
detect() {
|
|
145
|
-
local profile languages package_manager release_type npm_publish features runner unit_runner
|
|
146
|
-
profile="$(resolve_profile)"
|
|
147
|
-
languages="$(resolve_languages)"
|
|
148
|
-
package_manager="$(resolve_package_manager)"
|
|
149
|
-
release_type="$(resolve_release_type)"
|
|
150
|
-
npm_publish="$(resolve_npm_publish)"
|
|
151
|
-
features="$(raw_value features all)"
|
|
152
|
-
runner="$(raw_value runner ubuntu-latest)"
|
|
153
|
-
unit_runner="$(raw_value unit_runner ubuntu-slim)"
|
|
154
|
-
printf 'profile=%s\n' "$profile"
|
|
155
|
-
printf 'languages=%s\n' "$languages"
|
|
156
|
-
printf 'features=%s\n' "$features"
|
|
157
|
-
printf 'package_manager=%s\n' "$package_manager"
|
|
158
|
-
printf 'release_type=%s\n' "$release_type"
|
|
159
|
-
printf 'npm_publish=%s\n' "$npm_publish"
|
|
160
|
-
printf 'runner=%s\n' "$runner"
|
|
161
|
-
printf 'unit_runner=%s\n' "$unit_runner"
|
|
162
|
-
printf 'ci_runner=%s\n' "$(raw_value ci_runner "$runner")"
|
|
163
|
-
printf 'test_runner=%s\n' "$(raw_value test_runner "$runner")"
|
|
164
|
-
printf 'security_runner=%s\n' "$(raw_value security_runner ubuntu-slim)"
|
|
165
|
-
printf 'codeql_runner=%s\n' "$(raw_value codeql_runner "$runner")"
|
|
166
|
-
printf 'pr_runner=%s\n' "$(raw_value pr_runner ubuntu-slim)"
|
|
167
|
-
printf 'release_runner=%s\n' "$(raw_value release_runner ubuntu-slim)"
|
|
168
|
-
printf 'cache_packages=%s\n' "$(raw_value cache_packages auto)"
|
|
169
|
-
printf 'cache_build=%s\n' "$(raw_value cache_build auto)"
|
|
170
|
-
printf 'coverage_minimum=%s\n' "$(raw_value coverage_minimum 80)"
|
|
171
|
-
printf 'turbo_remote=%s\n' "$(raw_value turbo_remote auto)"
|
|
172
|
-
printf 'prune_standard=%s\n' "$(raw_value prune_standard false)"
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
case "$command" in
|
|
176
|
-
detect) detect ;;
|
|
177
|
-
get)
|
|
178
|
-
detect | awk -F= -v key="$requested_key" '$1 == key { print substr($0, index($0, "=") + 1); found=1 } END { exit !found }'
|
|
179
|
-
;;
|
|
180
|
-
env)
|
|
181
|
-
detect | while IFS='=' read -r key value; do
|
|
182
|
-
env_key="REPO_FOUNDRY_$(printf '%s' "$key" | tr '[:lower:]-' '[:upper:]_')"
|
|
183
|
-
printf '%s=%q\n' "$env_key" "$value"
|
|
184
|
-
done
|
|
185
|
-
;;
|
|
186
|
-
esac
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
source .github/scripts/changed-files.sh
|
|
5
|
-
|
|
6
|
-
has_javascript_dependencies() {
|
|
7
|
-
[ -f bun.lock ] || [ -f bun.lockb ] || [ -f pnpm-lock.yaml ] ||
|
|
8
|
-
[ -f yarn.lock ] || [ -f package-lock.json ] ||
|
|
9
|
-
{ [ -f package.json ] && 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; }
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
has_python_manifest() {
|
|
13
|
-
[ -f pyproject.toml ] || [ -f uv.lock ] ||
|
|
14
|
-
git ls-files | grep -Eq '(^|/)requirements[^/]*\.txt$'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
has_dependency_manifest() {
|
|
18
|
-
has_javascript_dependencies || [ -f Cargo.toml ] || has_python_manifest
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
should_run() {
|
|
22
|
-
if repo_foundry_governance_only; then
|
|
23
|
-
printf '%s\n' 'applicable=false'
|
|
24
|
-
return 0
|
|
25
|
-
fi
|
|
26
|
-
if repo_foundry_pr_dependencies_unchanged "${1:-all}"; then
|
|
27
|
-
return 1
|
|
28
|
-
fi
|
|
29
|
-
case "${1:-all}" in
|
|
30
|
-
javascript) has_javascript_dependencies || return 1 ;;
|
|
31
|
-
rust) [ -f Cargo.toml ] || return 1 ;;
|
|
32
|
-
python) has_python_manifest || return 1 ;;
|
|
33
|
-
all) has_dependency_manifest || return 1 ;;
|
|
34
|
-
*) echo "unknown ecosystem: ${1:-}" >&2; return 2 ;;
|
|
35
|
-
esac
|
|
36
|
-
printf '%s\n' 'applicable=true'
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if [ "${1:-audit}" = should_run ]; then
|
|
40
|
-
if should_run "${2:-all}"; then
|
|
41
|
-
exit 0
|
|
42
|
-
else
|
|
43
|
-
status=$?
|
|
44
|
-
[ "$status" -eq 1 ] && printf '%s\n' 'applicable=false' && exit 0
|
|
45
|
-
exit "$status"
|
|
46
|
-
fi
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
if [ "${1:-}" = profile ]; then
|
|
50
|
-
python_requirements='["none"]'
|
|
51
|
-
if has_python_manifest && ! repo_foundry_governance_only && ! repo_foundry_pr_dependencies_unchanged python; then
|
|
52
|
-
python_requirements="$(
|
|
53
|
-
git ls-files -z '*requirements*.txt' |
|
|
54
|
-
node -e 'let data=""; process.stdin.on("data", (chunk) => { data += chunk; }).on("end", () => process.stdout.write(JSON.stringify(data.split("\0").filter(Boolean))));'
|
|
55
|
-
)"
|
|
56
|
-
[ "$python_requirements" = "[]" ] && python_requirements='["project"]'
|
|
57
|
-
fi
|
|
58
|
-
for ecosystem in javascript rust python; do
|
|
59
|
-
if should_run "$ecosystem"; then
|
|
60
|
-
printf '%s=true\n' "$ecosystem"
|
|
61
|
-
else
|
|
62
|
-
status=$?
|
|
63
|
-
[ "$status" -eq 1 ] && printf '%s=false\n' "$ecosystem" && continue
|
|
64
|
-
exit "$status"
|
|
65
|
-
fi
|
|
66
|
-
done
|
|
67
|
-
printf 'python_requirements=%s\n' "$python_requirements"
|
|
68
|
-
exit 0
|
|
69
|
-
fi
|
|
70
|
-
|
|
71
|
-
mode="${1:-audit}"
|
|
72
|
-
if [ "$mode" = audit ] && [ -n "${2:-}" ]; then mode="$2"; fi
|
|
73
|
-
case "$mode" in
|
|
74
|
-
audit|all|javascript|rust|python) ;;
|
|
75
|
-
*)
|
|
76
|
-
echo "usage: $0 [audit|should_run] [javascript|rust|python|all]" >&2
|
|
77
|
-
exit 2
|
|
78
|
-
;;
|
|
79
|
-
esac
|
|
80
|
-
|
|
81
|
-
audits=0
|
|
82
|
-
|
|
83
|
-
wait_for_parallel() {
|
|
84
|
-
local status=0 pid
|
|
85
|
-
for pid in "$@"; do
|
|
86
|
-
if ! wait "$pid"; then status=1; fi
|
|
87
|
-
done
|
|
88
|
-
return "$status"
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
run_parallel() {
|
|
92
|
-
local status=0 pid task
|
|
93
|
-
local -a pids=()
|
|
94
|
-
for task in "$@"; do
|
|
95
|
-
"$task" &
|
|
96
|
-
pids+=("$!")
|
|
97
|
-
done
|
|
98
|
-
for pid in "${pids[@]}"; do
|
|
99
|
-
if ! wait "$pid"; then status=1; fi
|
|
100
|
-
done
|
|
101
|
-
return "$status"
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
package_manager() {
|
|
105
|
-
local configured=""
|
|
106
|
-
if [ -x .github/scripts/profile.sh ]; then
|
|
107
|
-
configured="$(bash .github/scripts/profile.sh get package_manager 2>/dev/null || true)"
|
|
108
|
-
else
|
|
109
|
-
config_file=.github/code-foundry.yml
|
|
110
|
-
if [ -f "$config_file" ]; then
|
|
111
|
-
configured="$(awk -F': ' '/^package_manager:/ {print $2; exit}' "$config_file")"
|
|
112
|
-
fi
|
|
113
|
-
fi
|
|
114
|
-
case "$configured" in
|
|
115
|
-
bun|pnpm|yarn|npm) echo "$configured"; return ;;
|
|
116
|
-
esac
|
|
117
|
-
if [ -f bun.lock ] || [ -f bun.lockb ]; then echo bun
|
|
118
|
-
elif [ -f pnpm-lock.yaml ]; then echo pnpm
|
|
119
|
-
elif [ -f yarn.lock ]; then echo yarn
|
|
120
|
-
elif [ -f package-lock.json ]; then echo npm
|
|
121
|
-
else echo none
|
|
122
|
-
fi
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
audit_javascript() {
|
|
126
|
-
if ! has_javascript_dependencies; then
|
|
127
|
-
echo "Skipping JavaScript/TypeScript audit (dependency inputs not found)"
|
|
128
|
-
return
|
|
129
|
-
fi
|
|
130
|
-
audit_args=(--audit-level=high)
|
|
131
|
-
if [ -f .github/security-audit-allowlist.txt ]; then
|
|
132
|
-
while IFS= read -r advisory; do
|
|
133
|
-
[[ -z "$advisory" || "$advisory" == \#* ]] && continue
|
|
134
|
-
audit_args+=(--ignore "$advisory")
|
|
135
|
-
done < .github/security-audit-allowlist.txt
|
|
136
|
-
fi
|
|
137
|
-
case "$(package_manager)" in
|
|
138
|
-
bun) audits=$((audits + 1)); bun audit "${audit_args[@]}" ;;
|
|
139
|
-
pnpm) audits=$((audits + 1)); corepack pnpm audit --audit-level high ;;
|
|
140
|
-
yarn) audits=$((audits + 1)); corepack yarn npm audit --all --recursive ;;
|
|
141
|
-
npm) audits=$((audits + 1)); npm audit --audit-level=high ;;
|
|
142
|
-
esac
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
audit_rust() {
|
|
146
|
-
if [ -f Cargo.toml ]; then
|
|
147
|
-
audits=$((audits + 1))
|
|
148
|
-
if ! command -v cargo-audit >/dev/null 2>&1; then cargo install cargo-audit --locked --quiet; fi
|
|
149
|
-
cargo audit
|
|
150
|
-
else
|
|
151
|
-
echo "Skipping Rust audit (Cargo.toml not found)"
|
|
152
|
-
fi
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
audit_python() {
|
|
156
|
-
local pip_audit_version='2.10.1'
|
|
157
|
-
requirement_files=()
|
|
158
|
-
if [ -n "${REPO_FOUNDRY_PYTHON_REQUIREMENT:-}" ] && [ "$REPO_FOUNDRY_PYTHON_REQUIREMENT" != project ] && [ "$REPO_FOUNDRY_PYTHON_REQUIREMENT" != none ]; then
|
|
159
|
-
if ! git ls-files --error-unmatch -- "${REPO_FOUNDRY_PYTHON_REQUIREMENT}" >/dev/null 2>&1; then
|
|
160
|
-
echo "Python requirement file is not tracked: ${REPO_FOUNDRY_PYTHON_REQUIREMENT}" >&2
|
|
161
|
-
return 1
|
|
162
|
-
fi
|
|
163
|
-
requirement_files+=( "$REPO_FOUNDRY_PYTHON_REQUIREMENT" )
|
|
164
|
-
else
|
|
165
|
-
while IFS= read -r requirement_file; do
|
|
166
|
-
[ -n "$requirement_file" ] && requirement_files+=( "$requirement_file" )
|
|
167
|
-
done < <(git ls-files '*requirements*.txt')
|
|
168
|
-
fi
|
|
169
|
-
if [ "${#requirement_files[@]}" -gt 0 ] || [ -f pyproject.toml ]; then
|
|
170
|
-
audits=$((audits + 1))
|
|
171
|
-
if command -v uv >/dev/null 2>&1; then
|
|
172
|
-
local uv_audit_cache="${REPO_FOUNDRY_UV_AUDIT_CACHE:-$HOME/.cache/uv-audit}"
|
|
173
|
-
pip_audit() {
|
|
174
|
-
if [ "${REPO_FOUNDRY_UV_AUDIT_OFFLINE:-false}" = true ]; then
|
|
175
|
-
if UV_CACHE_DIR="$uv_audit_cache" uv tool run --offline --from "pip-audit==${pip_audit_version}" pip-audit "$@"; then
|
|
176
|
-
return 0
|
|
177
|
-
fi
|
|
178
|
-
echo 'Offline pip-audit cache was incomplete; retrying online.' >&2
|
|
179
|
-
fi
|
|
180
|
-
REPO_FOUNDRY_UV_AUDIT_OFFLINE=false UV_CACHE_DIR="$uv_audit_cache" uv tool run --from "pip-audit==${pip_audit_version}" pip-audit "$@"
|
|
181
|
-
}
|
|
182
|
-
else
|
|
183
|
-
python -m pip install --disable-pip-version-check --quiet "pip-audit==${pip_audit_version}"
|
|
184
|
-
pip_audit() { python -m pip_audit "$@"; }
|
|
185
|
-
fi
|
|
186
|
-
if [ "${#requirement_files[@]}" -gt 0 ]; then
|
|
187
|
-
pids=()
|
|
188
|
-
for requirement_file in "${requirement_files[@]}"; do
|
|
189
|
-
pip_audit -r "$requirement_file" &
|
|
190
|
-
pids+=( "$!" )
|
|
191
|
-
done
|
|
192
|
-
wait_for_parallel "${pids[@]}"
|
|
193
|
-
else
|
|
194
|
-
pip_audit
|
|
195
|
-
fi
|
|
196
|
-
else
|
|
197
|
-
echo "Skipping Python audit (Python dependency manifest not found)"
|
|
198
|
-
fi
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
case "$mode" in
|
|
202
|
-
audit|all)
|
|
203
|
-
run_parallel audit_javascript audit_rust audit_python
|
|
204
|
-
;;
|
|
205
|
-
javascript) audit_javascript ;;
|
|
206
|
-
rust) audit_rust ;;
|
|
207
|
-
python) audit_python ;;
|
|
208
|
-
esac
|
|
209
|
-
|
|
210
|
-
if [ "$audits" -eq 0 ] && ! has_dependency_manifest; then
|
|
211
|
-
echo "No supported dependency manifests found; nothing to audit"
|
|
212
|
-
fi
|
|
213
|
-
|
|
214
|
-
exit 0
|
|
@@ -1,17 +0,0 @@
|
|
|
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
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
mode=check
|
|
5
|
-
while [ "$#" -gt 0 ]; do
|
|
6
|
-
case "$1" in
|
|
7
|
-
--check) mode=check; shift ;;
|
|
8
|
-
--apply) mode=apply; shift ;;
|
|
9
|
-
-h|--help)
|
|
10
|
-
printf '%s\n' 'Usage: sync-codeowners.sh [--check|--apply]'
|
|
11
|
-
exit 0
|
|
12
|
-
;;
|
|
13
|
-
*) printf 'Unknown option: %s\n' "$1" >&2; exit 2 ;;
|
|
14
|
-
esac
|
|
15
|
-
done
|
|
16
|
-
|
|
17
|
-
codeowners_file=.github/CODEOWNERS
|
|
18
|
-
mkdir -p .github
|
|
19
|
-
remote="$(git remote get-url origin 2>/dev/null || true)"
|
|
20
|
-
repo="$(printf '%s\n' "$remote" | sed -E 's#.*github.com[:/]([^/]+/[^/.]+)(\.git)?$#\1#')"
|
|
21
|
-
owners=()
|
|
22
|
-
|
|
23
|
-
if [ -n "$repo" ] && command -v gh >/dev/null 2>&1; then
|
|
24
|
-
owner_json="$(gh repo view "$repo" --json owner 2>/dev/null || true)"
|
|
25
|
-
owner_login="$(jq -r '.owner.login // empty' <<< "$owner_json" 2>/dev/null || true)"
|
|
26
|
-
owner_type="$(jq -r '.owner.type // empty' <<< "$owner_json" 2>/dev/null || true)"
|
|
27
|
-
if [ "$owner_type" = User ] && [ -n "$owner_login" ]; then
|
|
28
|
-
owners+=("@$owner_login")
|
|
29
|
-
fi
|
|
30
|
-
admins="$(gh api --paginate "repos/$repo/collaborators?per_page=100" \
|
|
31
|
-
--jq '.[] | select(.permissions.admin == true) | .login' 2>/dev/null || true)"
|
|
32
|
-
while IFS= read -r admin; do
|
|
33
|
-
[ -n "$admin" ] && owners+=("@$admin")
|
|
34
|
-
done <<< "$admins"
|
|
35
|
-
fi
|
|
36
|
-
|
|
37
|
-
if [ "${#owners[@]}" -gt 0 ]; then
|
|
38
|
-
owner_line="$(printf '%s\n' "${owners[@]}" | awk '!seen[$0]++' | paste -sd ' ' -)"
|
|
39
|
-
else
|
|
40
|
-
owner_line='@OWNER'
|
|
41
|
-
printf '%s\n' 'GitHub owner/admin discovery unavailable; retaining @OWNER placeholder.' >&2
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
existing_file=""
|
|
45
|
-
if [ -f "$codeowners_file" ]; then
|
|
46
|
-
existing_file="$(mktemp)"
|
|
47
|
-
cp "$codeowners_file" "$existing_file"
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
if [ "$mode" = check ]; then
|
|
51
|
-
if [ -f "$codeowners_file" ] && grep -q "^\* $owner_line$" "$codeowners_file"; then
|
|
52
|
-
printf '%s\n' 'CODEOWNERS is current.'
|
|
53
|
-
else
|
|
54
|
-
printf 'Would update %s with %s\n' "$codeowners_file" "$owner_line"
|
|
55
|
-
fi
|
|
56
|
-
[ -n "$existing_file" ] && unlink "$existing_file"
|
|
57
|
-
exit 0
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
tmp_file="$(mktemp)"
|
|
61
|
-
{
|
|
62
|
-
printf '%s\n' '# Managed by code-foundry. Repository owners/admins are discovered during initialization.'
|
|
63
|
-
printf '* %s\n' "$owner_line"
|
|
64
|
-
if [ -n "$existing_file" ]; then
|
|
65
|
-
awk '
|
|
66
|
-
/^# Managed by code-foundry\./ { managed=1; next }
|
|
67
|
-
managed && /^\* / { managed=0; next }
|
|
68
|
-
managed { next }
|
|
69
|
-
/^\* @(OWNER|[^[:space:]]+)/ { next }
|
|
70
|
-
{ print }
|
|
71
|
-
' "$existing_file"
|
|
72
|
-
fi
|
|
73
|
-
} > "$tmp_file"
|
|
74
|
-
mv "$tmp_file" "$codeowners_file"
|
|
75
|
-
[ -n "$existing_file" ] && unlink "$existing_file"
|
|
76
|
-
printf 'Updated %s with %s\n' "$codeowners_file" "$owner_line"
|
|
@@ -1,138 +0,0 @@
|
|
|
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
|
-
config_file=.github/code-foundry.yml
|
|
11
|
-
if [ -f "$config_file" ]; then
|
|
12
|
-
configured_features="$(awk -F': ' '/^features:/ {print $2; exit}' "$config_file")"
|
|
13
|
-
configured_languages="$(awk -F': ' '/^languages:/ {print $2; exit}' "$config_file")"
|
|
14
|
-
[ -n "$configured_features" ] || configured_features="all"
|
|
15
|
-
[ -n "$configured_languages" ] || configured_languages="auto"
|
|
16
|
-
fi
|
|
17
|
-
|
|
18
|
-
feature_enabled() {
|
|
19
|
-
[ "$configured_features" = all ] && return 0
|
|
20
|
-
case " $(printf '%s' "$configured_features" | tr ',' ' ') " in
|
|
21
|
-
*" $1 "*) return 0 ;;
|
|
22
|
-
*) return 1 ;;
|
|
23
|
-
esac
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
language_enabled() {
|
|
27
|
-
[ "$configured_languages" = auto ] || [ "$configured_languages" = all ] && return 0
|
|
28
|
-
case " $(printf '%s' "$configured_languages" | tr ',' ' ') " in
|
|
29
|
-
*" $1 "*) return 0 ;;
|
|
30
|
-
*) return 1 ;;
|
|
31
|
-
esac
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
usage() {
|
|
35
|
-
printf '%s\n' 'Usage: sync-protection.sh --repo OWNER/REPO [--branch main] [--check] [--apply]'
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
while [ "$#" -gt 0 ]; do
|
|
39
|
-
case "$1" in
|
|
40
|
-
--repo) repo="${2:?missing OWNER/REPO}"; shift 2 ;;
|
|
41
|
-
--branch) branch="${2:?missing branch}"; shift 2 ;;
|
|
42
|
-
--check) mode="check"; shift ;;
|
|
43
|
-
--apply) mode="apply"; shift ;;
|
|
44
|
-
-h|--help) usage; exit 0 ;;
|
|
45
|
-
*) usage >&2; exit 2 ;;
|
|
46
|
-
esac
|
|
47
|
-
done
|
|
48
|
-
|
|
49
|
-
[ -n "$repo" ] || { usage >&2; exit 2; }
|
|
50
|
-
command -v gh >/dev/null 2>&1 || { echo "gh is required" >&2; exit 1; }
|
|
51
|
-
|
|
52
|
-
is_private="$(gh repo view "$repo" --json isPrivate --jq '.isPrivate')"
|
|
53
|
-
code_security_status="disabled"
|
|
54
|
-
if [ "$is_private" = true ]; then
|
|
55
|
-
code_security_status="$(gh api "repos/$repo" --jq '.security_and_analysis.code_security.status // "disabled"' 2>/dev/null || printf 'disabled')"
|
|
56
|
-
fi
|
|
57
|
-
contexts=()
|
|
58
|
-
if feature_enabled ci; then contexts+=(Format Lint Type-Check Build); fi
|
|
59
|
-
if feature_enabled test; then contexts+=(Unit Integration E2E Smoke); fi
|
|
60
|
-
if feature_enabled security; then
|
|
61
|
-
contexts+=(Profile 'Dependency Audit (JavaScript)' 'Dependency Audit (Rust)' 'Dependency Audit (Python)')
|
|
62
|
-
fi
|
|
63
|
-
if feature_enabled codeql; then contexts+=(Detect); fi
|
|
64
|
-
|
|
65
|
-
if { [ "$is_private" != true ] || [ "$code_security_status" = enabled ]; } && feature_enabled codeql; then
|
|
66
|
-
contexts+=("Analyze (Actions)")
|
|
67
|
-
if language_enabled typescript && git ls-files -- '*.ts' '*.tsx' '*.js' '*.jsx' package.json tsconfig\*.json | grep -q .; then contexts+=("Analyze (TypeScript)"); fi
|
|
68
|
-
if language_enabled python && git ls-files -- '*.py' pyproject.toml requirements\*.txt setup.py ':!.github/**' | grep -q .; then contexts+=("Analyze (Python)"); fi
|
|
69
|
-
if language_enabled rust && git ls-files -- '*.rs' Cargo.toml Cargo.lock | grep -q .; then contexts+=("Analyze (Rust)"); fi
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
|
-
protection="$(gh api "repos/$repo/branches/$branch/protection" 2>/dev/null || true)"
|
|
73
|
-
if ! jq -e 'type == "object" and (.required_status_checks | type == "object")' >/dev/null 2>&1 <<< "$protection"; then
|
|
74
|
-
if [ "$mode" = apply ]; then
|
|
75
|
-
echo "Cannot read branch protection for $repo:$branch (repository plan or permissions may not allow it)." >&2
|
|
76
|
-
exit 1
|
|
77
|
-
fi
|
|
78
|
-
existing=""
|
|
79
|
-
else
|
|
80
|
-
existing="$(jq -r '.required_status_checks.contexts[]?' <<< "$protection")"
|
|
81
|
-
fi
|
|
82
|
-
preserved=()
|
|
83
|
-
while IFS= read -r context; do
|
|
84
|
-
[ -n "$context" ] || continue
|
|
85
|
-
case "$context" in
|
|
86
|
-
'Slither / Analyze') continue ;; # removed from the standard workflow set
|
|
87
|
-
# Remove both the current concise job names and older workflow-prefixed
|
|
88
|
-
# names before rebuilding the standard set. This prevents stale required
|
|
89
|
-
# checks from remaining required after a repository changes visibility or
|
|
90
|
-
# template version.
|
|
91
|
-
'Dependency Audit'|'Dependency Audit ('*|'Profile'|'Test Profile'|\
|
|
92
|
-
'Format'|'Lint'|'Type-Check'|'Build'|'Unit'|'Integration'|'E2E'|'Smoke'|\
|
|
93
|
-
'Detect'|'Analyze'|'Analyze ('*|\
|
|
94
|
-
'CI / '*|'Test / '*|'Security / '*|'CodeQL / '*) ;;
|
|
95
|
-
*) preserved+=("$context") ;;
|
|
96
|
-
esac
|
|
97
|
-
done <<< "$existing"
|
|
98
|
-
|
|
99
|
-
payload="$(printf '%s\n' "${preserved[@]-}" "${contexts[@]}" | jq -Rsc 'split("\n") | map(select(length > 0)) | unique | {strict: true, contexts: .}')"
|
|
100
|
-
|
|
101
|
-
if [ "$mode" = check ]; then
|
|
102
|
-
printf '%s\n' "$payload" | jq .
|
|
103
|
-
exit 0
|
|
104
|
-
fi
|
|
105
|
-
|
|
106
|
-
current="$protection"
|
|
107
|
-
full_payload="$(jq --argjson checks "$payload" '
|
|
108
|
-
{
|
|
109
|
-
required_status_checks: $checks,
|
|
110
|
-
enforce_admins: .enforce_admins.enabled,
|
|
111
|
-
required_pull_request_reviews: (
|
|
112
|
-
if .required_pull_request_reviews == null then null else {
|
|
113
|
-
dismiss_stale_reviews: .required_pull_request_reviews.dismiss_stale_reviews,
|
|
114
|
-
require_code_owner_reviews: .required_pull_request_reviews.require_code_owner_reviews,
|
|
115
|
-
required_approving_review_count: .required_pull_request_reviews.required_approving_review_count,
|
|
116
|
-
require_last_push_approval: .required_pull_request_reviews.require_last_push_approval
|
|
117
|
-
} end
|
|
118
|
-
),
|
|
119
|
-
restrictions: (
|
|
120
|
-
if .restrictions == null then null else {
|
|
121
|
-
users: [.restrictions.users[].login],
|
|
122
|
-
teams: [.restrictions.teams[].slug],
|
|
123
|
-
apps: [.restrictions.apps[].slug]
|
|
124
|
-
} end
|
|
125
|
-
),
|
|
126
|
-
required_linear_history: .required_linear_history.enabled,
|
|
127
|
-
allow_force_pushes: .allow_force_pushes.enabled,
|
|
128
|
-
allow_deletions: .allow_deletions.enabled,
|
|
129
|
-
block_creations: .block_creations.enabled,
|
|
130
|
-
required_conversation_resolution: .required_conversation_resolution.enabled,
|
|
131
|
-
lock_branch: .lock_branch.enabled,
|
|
132
|
-
allow_fork_syncing: .allow_fork_syncing.enabled
|
|
133
|
-
}
|
|
134
|
-
' <<< "$current")"
|
|
135
|
-
|
|
136
|
-
gh api --method PUT "repos/$repo/branches/$branch/protection" \
|
|
137
|
-
--input - <<< "$full_payload" >/dev/null
|
|
138
|
-
printf 'Updated required checks for %s:%s\n' "$repo" "$branch"
|