code-foundry 0.1.4

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 (50) hide show
  1. package/.editorconfig +18 -0
  2. package/.env.example +3 -0
  3. package/.gitattributes +9 -0
  4. package/.githooks/pre-commit +67 -0
  5. package/.github/CODEOWNERS +4 -0
  6. package/.github/CODE_OF_CONDUCT.md +41 -0
  7. package/.github/CONTRIBUTING.md +198 -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/cache/action.yml +43 -0
  14. package/.github/actions/codeql/action.yml +29 -0
  15. package/.github/actions/setup/action.yml +776 -0
  16. package/.github/dependabot.yml +42 -0
  17. package/.github/scripts/bootstrap.sh +20 -0
  18. package/.github/scripts/changed-files.sh +96 -0
  19. package/.github/scripts/ci.sh +636 -0
  20. package/.github/scripts/codeql-languages.sh +86 -0
  21. package/.github/scripts/doctor.sh +103 -0
  22. package/.github/scripts/format-fast-path.sh +80 -0
  23. package/.github/scripts/init-repo.sh +134 -0
  24. package/.github/scripts/security.sh +209 -0
  25. package/.github/scripts/sitecustomize.py +17 -0
  26. package/.github/scripts/sync-codeowners.sh +76 -0
  27. package/.github/scripts/sync-protection.sh +137 -0
  28. package/.github/scripts/sync-template.sh +432 -0
  29. package/.github/scripts/turbo-cache-probe.sh +102 -0
  30. package/.github/template.yml +7 -0
  31. package/.github/template.yml.example +8 -0
  32. package/.github/workflows/ci.yml +166 -0
  33. package/.github/workflows/codeql.yml +160 -0
  34. package/.github/workflows/draft-pr.yml +59 -0
  35. package/.github/workflows/release-pr.yml +66 -0
  36. package/.github/workflows/release.yml +103 -0
  37. package/.github/workflows/security.yml +228 -0
  38. package/.github/workflows/test.yml +175 -0
  39. package/.gitignore +77 -0
  40. package/.mise.toml +6 -0
  41. package/.prettierrc +6 -0
  42. package/AGENTS.md +155 -0
  43. package/CHANGELOG.md +3 -0
  44. package/LICENSE +616 -0
  45. package/NOTICE +7 -0
  46. package/README.md +172 -0
  47. package/package.json +44 -0
  48. package/release-please-config.json +16 -0
  49. package/ruff.toml +6 -0
  50. package/src/cli.mjs +137 -0
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ languages=()
5
+ changed_files=""
6
+
7
+ # Scheduled and manually dispatched scans are always full scans. For pushes
8
+ # and pull requests, a shallow commit diff lets the matrix skip analyzers for
9
+ # languages untouched by the change while keeping every matrix check visible.
10
+ case "${GITHUB_EVENT_NAME:-}" in
11
+ schedule|workflow_dispatch) ;;
12
+ *)
13
+ base_sha="${CODEQL_BASE_SHA:-}"
14
+ if [ -n "$base_sha" ] && [ "$base_sha" != "0000000000000000000000000000000000000000" ]; then
15
+ if ! git cat-file -e "$base_sha^{commit}" 2>/dev/null; then
16
+ git fetch --no-tags --filter=blob:none --depth=1 origin "$base_sha" >/dev/null 2>&1 || true
17
+ fi
18
+ changed_files="$(git diff --name-only "$base_sha" "${GITHUB_SHA:-HEAD}" 2>/dev/null || true)"
19
+ fi
20
+ [ -n "$changed_files" ] || changed_files="$(git diff --name-only HEAD^ HEAD 2>/dev/null || true)"
21
+ ;;
22
+ esac
23
+
24
+ if find .github/workflows -type f \( -name '*.yml' -o -name '*.yaml' \) -print -quit | grep -q .; then languages+=(actions); fi
25
+ configured=""
26
+ if [ -f .github/template.yml ]; then
27
+ configured="$(awk -F': ' '/^languages:/ {print $2; exit}' .github/template.yml)"
28
+ fi
29
+
30
+ if [ "$configured" = auto ] || [ "$configured" = all ] || [ -z "$configured" ]; then
31
+ tracked_files="$(git ls-files)"
32
+ if printf '%s\n' "$tracked_files" | grep -Eq '(^|/)([^/]+\.(ts|tsx|js|jsx)|package\.json|tsconfig[^/]*\.json)$'; then
33
+ languages+=(javascript-typescript)
34
+ fi
35
+ if printf '%s\n' "$tracked_files" | awk '!/^\.github\// && /(^|\/)([^\/]+\.py|pyproject\.toml|requirements[^\/]*\.txt|setup\.py)$/ { found=1; exit } END { exit !found }'; then
36
+ languages+=(python)
37
+ fi
38
+ if printf '%s\n' "$tracked_files" | grep -Eq '(^|/)([^/]+\.rs|Cargo\.toml|Cargo\.lock)$'; then
39
+ languages+=(rust)
40
+ fi
41
+ else
42
+ case ",$configured," in *,typescript,*) languages+=(javascript-typescript) ;; esac
43
+ case ",$configured," in *,python,*) languages+=(python) ;; esac
44
+ case ",$configured," in *,rust,*) languages+=(rust) ;; esac
45
+ fi
46
+
47
+ changed_json="$(printf '%s\n' "$changed_files" | jq -Rsc 'split("\n") | map(select(length > 0))')"
48
+ json=$(printf '%s\n' "${languages[@]}" | jq -Rsc --argjson changed "$changed_json" '
49
+ split("\n")
50
+ | map(select(length > 0) | {
51
+ language: .,
52
+ name: (if . == "javascript-typescript" then "TypeScript" elif . == "actions" then "Actions" else (. | ascii_upcase[0:1] + .[1:]) end),
53
+ "build-mode": "none",
54
+ changed: (
55
+ if ($changed | length) == 0 then true
56
+ elif . == "actions" then any($changed[]; test("(^|/)(\\.github/workflows/|action\\.yml$)"))
57
+ elif . == "javascript-typescript" then any($changed[]; test("(^|/)(.*\\.(js|jsx|mjs|cjs|ts|tsx|mts|cts)|package\\.json|tsconfig[^/]*\\.json|((bun|pnpm|yarn|package-lock)\\.lock))$"))
58
+ elif . == "python" then any($changed[]; test("(^|/)(.*\\.py|pyproject\\.toml|requirements[^/]*\\.txt|setup\\.py|Pipfile\\.lock|poetry\\.lock)$"))
59
+ elif . == "rust" then any($changed[]; test("(^|/)(.*\\.rs|Cargo\\.toml|Cargo\\.lock)$"))
60
+ else true
61
+ end
62
+ )
63
+ })
64
+ ')
65
+ printf 'languages=%s\n' "$json" >> "$GITHUB_OUTPUT"
66
+
67
+ # Expose stable, shell-friendly outputs for explicit analyzer jobs. Keeping
68
+ # these separate from the JSON matrix lets workflow-level conditions avoid
69
+ # allocating a runner for languages that are absent or unchanged.
70
+ for codeql_language in actions javascript-typescript python rust; do
71
+ output_prefix="$codeql_language"
72
+ case "$codeql_language" in
73
+ javascript-typescript) output_prefix="javascript" ;;
74
+ esac
75
+
76
+ entry="$(jq -c --arg language "$codeql_language" '.[] | select(.language == $language)' <<< "$json")"
77
+ if [ -n "$entry" ]; then
78
+ printf '%s_available=true\n' "$output_prefix" >> "$GITHUB_OUTPUT"
79
+ printf '%s_changed=%s\n' "$output_prefix" "$(jq -r '.changed' <<< "$entry")" >> "$GITHUB_OUTPUT"
80
+ printf '%s_build_mode=%s\n' "$output_prefix" "$(jq -r '."build-mode"' <<< "$entry")" >> "$GITHUB_OUTPUT"
81
+ else
82
+ printf '%s_available=false\n' "$output_prefix" >> "$GITHUB_OUTPUT"
83
+ printf '%s_changed=false\n' "$output_prefix" >> "$GITHUB_OUTPUT"
84
+ printf '%s_build_mode=none\n' "$output_prefix" >> "$GITHUB_OUTPUT"
85
+ fi
86
+ done
@@ -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 sync-codeowners.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,80 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Bootstrap only the formatter for Bun projects whose repository-owned format
5
+ # script directly invokes Prettier. The workflow keeps a full-install fallback
6
+ # because formatter configs may import arbitrary repository dependencies.
7
+
8
+ set_output() {
9
+ printf '%s=%s\n' "$1" "$2" >> "${GITHUB_OUTPUT:-/dev/null}"
10
+ }
11
+
12
+ set_output hit false
13
+
14
+ [ -f package.json ] || exit 0
15
+ [ -f bun.lock ] || [ -f bun.lockb ] || exit 0
16
+
17
+ project_profile="$(node <<'NODE'
18
+ const fs = require('node:fs');
19
+ const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
20
+ process.stdout.write(Array.isArray(packageJson.workspaces) ||
21
+ (packageJson.workspaces && typeof packageJson.workspaces === 'object') ? 'workspace' : 'standalone');
22
+ NODE
23
+ )"
24
+ lock_file='bun.lock'
25
+ [ -f "$lock_file" ] || lock_file='bun.lockb'
26
+ lock_bytes="$(wc -c < "$lock_file")"
27
+ # A temporary npm formatter install has fixed overhead. For small standalone
28
+ # projects, the normal Bun install is usually faster; workspace graphs and
29
+ # larger lockfiles still benefit materially from avoiding their full tree.
30
+ if [ "$project_profile" = standalone ] && [ "$lock_bytes" -le 524288 ]; then
31
+ exit 0
32
+ fi
33
+
34
+ format_script="$(node <<'NODE'
35
+ const fs = require('node:fs');
36
+ const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
37
+ console.log(packageJson.scripts?.['format:check'] || packageJson.scripts?.format || '');
38
+ NODE
39
+ )"
40
+ if ! grep -Eiq '(^|[[:space:];&|])prettier([[:space:]]|$)' <<<"$format_script" ||
41
+ grep -Eiq '(^|[[:space:];&|])turbo([[:space:]]|$)' <<<"$format_script"; then
42
+ exit 0
43
+ fi
44
+
45
+ prettier_version="$(node <<'NODE'
46
+ const fs = require('node:fs');
47
+ const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
48
+ for (const group of [packageJson.devDependencies, packageJson.dependencies, packageJson.optionalDependencies]) {
49
+ const value = group?.prettier;
50
+ const match = typeof value === 'string' && value.match(/(\d+\.\d+\.\d+)/);
51
+ if (match) {
52
+ console.log(match[1]);
53
+ break;
54
+ }
55
+ }
56
+ NODE
57
+ )"
58
+ [ -n "$prettier_version" ] || exit 0
59
+
60
+ tool_home="${RUNNER_TEMP:-/tmp}/repo-foundry-format"
61
+ install_log="$(mktemp)"
62
+ cleanup() { rm -f "$install_log"; }
63
+ trap cleanup EXIT
64
+
65
+ if ! npm install --prefix "$tool_home" --no-save --ignore-scripts --no-audit --no-fund \
66
+ "prettier@${prettier_version}" >"$install_log" 2>&1; then
67
+ echo 'Format Probe: formatter bootstrap unavailable; using full install'
68
+ exit 0
69
+ fi
70
+
71
+ tool_bin="$tool_home/node_modules/.bin"
72
+ if [ ! -x "$tool_bin/prettier" ]; then
73
+ echo 'Format Probe: formatter binary unavailable; using full install'
74
+ exit 0
75
+ fi
76
+
77
+ echo "$tool_bin" >> "${GITHUB_PATH:-/dev/null}"
78
+ printf 'REPO_FOUNDRY_FORMAT_FAST=true\n' >> "${GITHUB_ENV:-/dev/null}"
79
+ echo "Format Probe: using Prettier ${prettier_version} without project install"
80
+ set_output hit true
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ source="${REPO_FOUNDRY_SOURCE:-https://github.com/${GITHUB_REPOSITORY_OWNER:-OWNER}/code-foundry.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
+ bootstrap=true
13
+ release_type="auto"
14
+ npm_publish="false"
15
+ tool_dir=""
16
+
17
+ cleanup() {
18
+ if [ -n "$tool_dir" ]; then rm -rf "$tool_dir"; fi
19
+ }
20
+ trap cleanup EXIT
21
+
22
+ usage() {
23
+ cat <<'EOF'
24
+ Usage: init-repo.sh [options]
25
+
26
+ Initialize or synchronize a repository from the shared baseline.
27
+
28
+ Options:
29
+ --source PATH_OR_URL Template source (default: REPO_FOUNDRY_SOURCE or GitHub owner)
30
+ --ref REF Template branch or tag (default: main)
31
+ --languages LIST auto or comma-separated: typescript,rust,python,solidity
32
+ --features LIST all or comma-separated optional features:
33
+ ci,codeql,security,test,draft-pr,release-pr,release,dependabot
34
+ --package-manager NAME auto, bun, pnpm, yarn, or npm
35
+ --release-type NAME auto, node, python, rust, or simple
36
+ --npm-publish Enable npm publication in the release workflow
37
+ --dry-run Preview changes without writing files
38
+ --prune Remove disabled standard workflows (never custom workflows)
39
+ --protection Synchronize main branch required checks
40
+ --no-bootstrap Skip mise/hooks/doctor bootstrap after init
41
+ -h, --help Show this help
42
+
43
+ Examples:
44
+ bash .github/scripts/init-repo.sh --languages typescript,python
45
+ bash .github/scripts/init-repo.sh --languages rust --features ci,codeql,security,test
46
+ bash .github/scripts/init-repo.sh --features all --dry-run
47
+ EOF
48
+ }
49
+
50
+ while [ "$#" -gt 0 ]; do
51
+ case "$1" in
52
+ --source) source="${2:?missing source path or URL}"; shift 2 ;;
53
+ --ref) ref="${2:?missing ref}"; shift 2 ;;
54
+ --languages) languages="${2:?missing language list}"; shift 2 ;;
55
+ --features) features="${2:?missing feature list}"; shift 2 ;;
56
+ --package-manager) package_manager="${2:?missing package manager}"; shift 2 ;;
57
+ --release-type) release_type="${2:?missing release type}"; shift 2 ;;
58
+ --npm-publish) npm_publish=true; shift ;;
59
+ --dry-run) dry_run=true; shift ;;
60
+ --prune) prune=true; shift ;;
61
+ --protection) protection=true; shift ;;
62
+ --no-bootstrap) bootstrap=false; shift ;;
63
+ -h|--help) usage; exit 0 ;;
64
+ *)
65
+ printf 'Unknown option: %s\n' "$1" >&2
66
+ exit 2
67
+ ;;
68
+ esac
69
+ done
70
+
71
+ case "$package_manager" in
72
+ auto|bun|pnpm|yarn|npm) ;;
73
+ *) printf 'Unsupported package manager: %s\n' "$package_manager" >&2; exit 2 ;;
74
+ esac
75
+ case "$release_type" in
76
+ auto|node|python|rust|simple|none) ;;
77
+ *) printf 'Unsupported release type: %s\n' "$release_type" >&2; exit 2 ;;
78
+ esac
79
+
80
+ script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" 2>/dev/null && pwd || true)"
81
+ if [ -f "$script_dir/sync-template.sh" ]; then
82
+ sync_script="$script_dir/sync-template.sh"
83
+ elif [ -d "$source" ] && [ -f "$source/.github/scripts/sync-template.sh" ]; then
84
+ sync_script="$source/.github/scripts/sync-template.sh"
85
+ else
86
+ command -v git >/dev/null 2>&1 || { echo "git is required" >&2; exit 1; }
87
+ tool_dir="$(mktemp -d)"
88
+ git clone --quiet --depth 1 --branch "$ref" "$source" "$tool_dir/template-repo"
89
+ sync_script="$tool_dir/template-repo/.github/scripts/sync-template.sh"
90
+ fi
91
+
92
+ sync_args=(
93
+ --source "$source"
94
+ --ref "$ref"
95
+ --languages "$languages"
96
+ --features "$features"
97
+ --package-manager "$package_manager"
98
+ )
99
+ if [ "$dry_run" = true ]; then sync_args+=(--check); else sync_args+=(--apply); fi
100
+ if [ "$prune" = true ]; then sync_args+=(--prune); fi
101
+ bash "$sync_script" "${sync_args[@]}"
102
+
103
+ if [ "$dry_run" = true ]; then
104
+ printf '%s\n' 'Dry run complete; no files were changed.'
105
+ exit 0
106
+ fi
107
+
108
+ mkdir -p .github
109
+ template_ref="$(awk -F': ' '/^template:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
110
+ {
111
+ printf 'version: 1\n'
112
+ if [ -n "$template_ref" ]; then
113
+ printf 'template: %s\n' "$template_ref"
114
+ fi
115
+ printf 'languages: %s\n' "$languages"
116
+ printf 'features: %s\n' "$features"
117
+ printf 'package_manager: %s\n' "$package_manager"
118
+ printf 'release_type: %s\n' "$release_type"
119
+ printf 'npm_publish: %s\n' "$npm_publish"
120
+ } > .github/template.yml
121
+
122
+ if [ "$bootstrap" = false ]; then
123
+ printf '%s\n' 'Bootstrap skipped.'
124
+ exit 0
125
+ fi
126
+
127
+ bash .github/scripts/bootstrap.sh
128
+
129
+ if [ "$protection" = true ]; then
130
+ remote="$(git remote get-url origin 2>/dev/null || true)"
131
+ repo="$(printf '%s\n' "$remote" | sed -E 's#.*github.com[:/]([^/]+/[^/.]+)(\.git)?$#\1#')"
132
+ [ -n "$repo" ] || { echo 'Could not determine GitHub repository from origin' >&2; exit 1; }
133
+ bash .github/scripts/sync-protection.sh --repo "$repo" --apply
134
+ fi
@@ -0,0 +1,209 @@
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 [ -f .github/template.yml ]; then
107
+ configured="$(awk -F': ' '/^package_manager:/ {print $2; exit}' .github/template.yml)"
108
+ case "$configured" in
109
+ bun|pnpm|yarn|npm) echo "$configured"; return ;;
110
+ esac
111
+ fi
112
+ if [ -f bun.lock ] || [ -f bun.lockb ]; then echo bun
113
+ elif [ -f pnpm-lock.yaml ]; then echo pnpm
114
+ elif [ -f yarn.lock ]; then echo yarn
115
+ elif [ -f package-lock.json ]; then echo npm
116
+ else echo none
117
+ fi
118
+ }
119
+
120
+ audit_javascript() {
121
+ if ! has_javascript_dependencies; then
122
+ echo "Skipping JavaScript/TypeScript audit (dependency inputs not found)"
123
+ return
124
+ fi
125
+ audit_args=(--audit-level=high)
126
+ if [ -f .github/security-audit-allowlist.txt ]; then
127
+ while IFS= read -r advisory; do
128
+ [[ -z "$advisory" || "$advisory" == \#* ]] && continue
129
+ audit_args+=(--ignore "$advisory")
130
+ done < .github/security-audit-allowlist.txt
131
+ fi
132
+ case "$(package_manager)" in
133
+ bun) audits=$((audits + 1)); bun audit "${audit_args[@]}" ;;
134
+ pnpm) audits=$((audits + 1)); corepack pnpm audit --audit-level high ;;
135
+ yarn) audits=$((audits + 1)); corepack yarn npm audit --all --recursive ;;
136
+ npm) audits=$((audits + 1)); npm audit --audit-level=high ;;
137
+ esac
138
+ }
139
+
140
+ audit_rust() {
141
+ if [ -f Cargo.toml ]; then
142
+ audits=$((audits + 1))
143
+ if ! command -v cargo-audit >/dev/null 2>&1; then cargo install cargo-audit --locked --quiet; fi
144
+ cargo audit
145
+ else
146
+ echo "Skipping Rust audit (Cargo.toml not found)"
147
+ fi
148
+ }
149
+
150
+ audit_python() {
151
+ local pip_audit_version='2.10.1'
152
+ requirement_files=()
153
+ if [ -n "${REPO_FOUNDRY_PYTHON_REQUIREMENT:-}" ] && [ "$REPO_FOUNDRY_PYTHON_REQUIREMENT" != project ] && [ "$REPO_FOUNDRY_PYTHON_REQUIREMENT" != none ]; then
154
+ if ! git ls-files --error-unmatch -- "${REPO_FOUNDRY_PYTHON_REQUIREMENT}" >/dev/null 2>&1; then
155
+ echo "Python requirement file is not tracked: ${REPO_FOUNDRY_PYTHON_REQUIREMENT}" >&2
156
+ return 1
157
+ fi
158
+ requirement_files+=( "$REPO_FOUNDRY_PYTHON_REQUIREMENT" )
159
+ else
160
+ while IFS= read -r requirement_file; do
161
+ [ -n "$requirement_file" ] && requirement_files+=( "$requirement_file" )
162
+ done < <(git ls-files '*requirements*.txt')
163
+ fi
164
+ if [ "${#requirement_files[@]}" -gt 0 ] || [ -f pyproject.toml ]; then
165
+ audits=$((audits + 1))
166
+ if command -v uv >/dev/null 2>&1; then
167
+ local uv_audit_cache="${REPO_FOUNDRY_UV_AUDIT_CACHE:-$HOME/.cache/uv-audit}"
168
+ pip_audit() {
169
+ if [ "${REPO_FOUNDRY_UV_AUDIT_OFFLINE:-false}" = true ]; then
170
+ if UV_CACHE_DIR="$uv_audit_cache" uv tool run --offline --from "pip-audit==${pip_audit_version}" pip-audit "$@"; then
171
+ return 0
172
+ fi
173
+ echo 'Offline pip-audit cache was incomplete; retrying online.' >&2
174
+ fi
175
+ REPO_FOUNDRY_UV_AUDIT_OFFLINE=false UV_CACHE_DIR="$uv_audit_cache" uv tool run --from "pip-audit==${pip_audit_version}" pip-audit "$@"
176
+ }
177
+ else
178
+ python -m pip install --disable-pip-version-check --quiet "pip-audit==${pip_audit_version}"
179
+ pip_audit() { python -m pip_audit "$@"; }
180
+ fi
181
+ if [ "${#requirement_files[@]}" -gt 0 ]; then
182
+ pids=()
183
+ for requirement_file in "${requirement_files[@]}"; do
184
+ pip_audit -r "$requirement_file" &
185
+ pids+=( "$!" )
186
+ done
187
+ wait_for_parallel "${pids[@]}"
188
+ else
189
+ pip_audit
190
+ fi
191
+ else
192
+ echo "Skipping Python audit (Python dependency manifest not found)"
193
+ fi
194
+ }
195
+
196
+ case "$mode" in
197
+ audit|all)
198
+ run_parallel audit_javascript audit_rust audit_python
199
+ ;;
200
+ javascript) audit_javascript ;;
201
+ rust) audit_rust ;;
202
+ python) audit_python ;;
203
+ esac
204
+
205
+ if [ "$audits" -eq 0 ] && ! has_dependency_manifest; then
206
+ echo "No supported dependency manifests found; nothing to audit"
207
+ fi
208
+
209
+ exit 0
@@ -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