code-foundry 0.1.3 → 0.2.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 (37) hide show
  1. package/.githooks/pre-commit +20 -5
  2. package/.github/CODEOWNERS +4 -2
  3. package/.github/CONTRIBUTING.md +5 -1
  4. package/.github/actions/cache/action.yml +43 -0
  5. package/.github/actions/codeql/action.yml +29 -0
  6. package/.github/actions/setup/action.yml +756 -0
  7. package/.github/scripts/bootstrap.sh +7 -0
  8. package/.github/scripts/changed-files.sh +96 -0
  9. package/.github/scripts/ci.sh +421 -28
  10. package/.github/scripts/codeql-languages.sh +71 -5
  11. package/.github/scripts/doctor.sh +1 -1
  12. package/.github/scripts/format-fast-path.sh +80 -0
  13. package/.github/scripts/init-repo.sh +39 -11
  14. package/.github/scripts/profile.sh +179 -0
  15. package/.github/scripts/security.sh +180 -20
  16. package/.github/scripts/sync-codeowners.sh +76 -0
  17. package/.github/scripts/sync-protection.sh +15 -2
  18. package/.github/scripts/sync-template.sh +222 -15
  19. package/.github/scripts/turbo-cache-probe.sh +102 -0
  20. package/.github/template.yml +14 -0
  21. package/.github/template.yml.example +10 -0
  22. package/.github/workflows/ci.yml +108 -7
  23. package/.github/workflows/codeql.yml +126 -17
  24. package/.github/workflows/draft-pr.yml +4 -7
  25. package/.github/workflows/release-pr.yml +20 -7
  26. package/.github/workflows/release.yml +99 -7
  27. package/.github/workflows/security.yml +201 -6
  28. package/.github/workflows/test.yml +100 -3
  29. package/.gitignore +4 -0
  30. package/.mise.toml +0 -2
  31. package/AGENTS.md +1 -0
  32. package/CHANGELOG.md +12 -0
  33. package/README.md +92 -10
  34. package/package.json +11 -1
  35. package/release-please-config.json +16 -0
  36. package/src/cli.mjs +20 -8
  37. package/.github/workflows/publish.yml +0 -25
@@ -15,42 +15,151 @@ permissions:
15
15
  packages: read
16
16
  security-events: write
17
17
 
18
+ env:
19
+ REPO_FOUNDRY_PROFILE: ${{ vars.REPO_FOUNDRY_PROFILE }}
20
+ REPO_FOUNDRY_LANGUAGES: ${{ vars.REPO_FOUNDRY_LANGUAGES }}
21
+ REPO_FOUNDRY_FEATURES: ${{ vars.REPO_FOUNDRY_FEATURES }}
22
+
18
23
  concurrency:
19
- group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
24
+ group: ${{ github.workflow }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}
20
25
  cancel-in-progress: true
21
26
 
22
27
  jobs:
23
28
  detect:
24
29
  name: Detect
25
- runs-on: ubuntu-latest
30
+ runs-on: ubuntu-slim
31
+ timeout-minutes: 10
26
32
  outputs:
27
33
  languages: ${{ steps.languages.outputs.languages }}
34
+ code_security: ${{ steps.security.outputs.status }}
35
+ actions_available: ${{ steps.languages.outputs.actions_available }}
36
+ actions_changed: ${{ steps.languages.outputs.actions_changed }}
37
+ actions_build_mode: ${{ steps.languages.outputs.actions_build_mode }}
38
+ javascript_available: ${{ steps.languages.outputs.javascript_available }}
39
+ javascript_changed: ${{ steps.languages.outputs.javascript_changed }}
40
+ javascript_build_mode: ${{ steps.languages.outputs.javascript_build_mode }}
41
+ python_available: ${{ steps.languages.outputs.python_available }}
42
+ python_changed: ${{ steps.languages.outputs.python_changed }}
43
+ python_build_mode: ${{ steps.languages.outputs.python_build_mode }}
44
+ rust_available: ${{ steps.languages.outputs.rust_available }}
45
+ rust_changed: ${{ steps.languages.outputs.rust_changed }}
46
+ rust_build_mode: ${{ steps.languages.outputs.rust_build_mode }}
28
47
  steps:
48
+ - name: Detect Code Security
49
+ id: security
50
+ env:
51
+ GH_TOKEN: ${{ github.token }}
52
+ run: |
53
+ status="disabled"
54
+ if [ "${{ github.event.repository.private }}" != "true" ]; then
55
+ status="enabled"
56
+ else
57
+ status="$(gh api "repos/${GITHUB_REPOSITORY}" --jq '.security_and_analysis.code_security.status // "disabled"' 2>/dev/null || printf 'disabled')"
58
+ fi
59
+ printf 'status=%s\n' "$status" >> "$GITHUB_OUTPUT"
29
60
  - name: Checkout
61
+ if: ${{ !github.event.repository.private || steps.security.outputs.status == 'enabled' }}
30
62
  uses: actions/checkout@v7
63
+ with:
64
+ fetch-depth: 2
65
+ # Detection only reads Git trees, paths, and small profile files;
66
+ # defer source blob transfer until the analyzer jobs.
67
+ filter: blob:none
68
+ sparse-checkout: |
69
+ .github
70
+ .mise.toml
71
+ mise.lock
72
+ package.json
73
+ bun.lock
74
+ bun.lockb
75
+ pnpm-lock.yaml
76
+ yarn.lock
77
+ package-lock.json
31
78
  - id: languages
32
79
  name: Detect languages
33
- run: bash .github/scripts/codeql-languages.sh
80
+ env:
81
+ CODEQL_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
82
+ REPO_FOUNDRY_CODE_SECURITY: ${{ steps.security.outputs.status }}
83
+ REPO_FOUNDRY_PRIVATE: ${{ github.event.repository.private }}
84
+ run: |
85
+ if [ "$REPO_FOUNDRY_PRIVATE" = "true" ] && [ "$REPO_FOUNDRY_CODE_SECURITY" != "enabled" ]; then
86
+ echo 'languages=[]' >> "$GITHUB_OUTPUT"
87
+ else
88
+ bash .github/scripts/codeql-languages.sh
89
+ fi
90
+
91
+ analyze-actions:
92
+ name: Analyze (Actions)
93
+ needs: detect
94
+ if: >-
95
+ ( !github.event.repository.private || needs.detect.outputs.code_security == 'enabled' ) &&
96
+ needs.detect.outputs.actions_available == 'true' &&
97
+ needs.detect.outputs.actions_changed == 'true'
98
+ runs-on: ubuntu-latest
99
+ timeout-minutes: 30
100
+ steps:
101
+ - name: Checkout
102
+ uses: actions/checkout@v7
103
+ - name: Analyze
104
+ uses: ./.github/actions/codeql
105
+ with:
106
+ language: actions
107
+ build-mode: ${{ needs.detect.outputs.actions_build_mode }}
108
+ category: /language:actions
109
+
110
+ analyze-typescript:
111
+ name: Analyze (TypeScript)
112
+ needs: detect
113
+ if: >-
114
+ ( !github.event.repository.private || needs.detect.outputs.code_security == 'enabled' ) &&
115
+ needs.detect.outputs.javascript_available == 'true' &&
116
+ needs.detect.outputs.javascript_changed == 'true'
117
+ runs-on: ubuntu-latest
118
+ timeout-minutes: 30
119
+ steps:
120
+ - name: Checkout
121
+ uses: actions/checkout@v7
122
+ - name: Analyze
123
+ uses: ./.github/actions/codeql
124
+ with:
125
+ language: javascript-typescript
126
+ build-mode: ${{ needs.detect.outputs.javascript_build_mode }}
127
+ category: /language:javascript-typescript
34
128
 
35
- analyze:
36
- name: Analyze (${{ matrix.name }})
129
+ analyze-python:
130
+ name: Analyze (Python)
37
131
  needs: detect
38
- if: ${{ !github.event.repository.private }}
132
+ if: >-
133
+ ( !github.event.repository.private || needs.detect.outputs.code_security == 'enabled' ) &&
134
+ needs.detect.outputs.python_available == 'true' &&
135
+ needs.detect.outputs.python_changed == 'true'
39
136
  runs-on: ubuntu-latest
40
- strategy:
41
- fail-fast: false
42
- max-parallel: 4
43
- matrix:
44
- include: ${{ fromJSON(needs.detect.outputs.languages) }}
137
+ timeout-minutes: 30
45
138
  steps:
46
139
  - name: Checkout
47
140
  uses: actions/checkout@v7
48
- - name: Initialize
49
- uses: github/codeql-action/init@v4
141
+ - name: Analyze
142
+ uses: ./.github/actions/codeql
50
143
  with:
51
- languages: ${{ matrix.language }}
52
- build-mode: ${{ matrix.build-mode }}
144
+ language: python
145
+ build-mode: ${{ needs.detect.outputs.python_build_mode }}
146
+ category: /language:python
147
+
148
+ analyze-rust:
149
+ name: Analyze (Rust)
150
+ needs: detect
151
+ if: >-
152
+ ( !github.event.repository.private || needs.detect.outputs.code_security == 'enabled' ) &&
153
+ needs.detect.outputs.rust_available == 'true' &&
154
+ needs.detect.outputs.rust_changed == 'true'
155
+ runs-on: ubuntu-latest
156
+ timeout-minutes: 30
157
+ steps:
158
+ - name: Checkout
159
+ uses: actions/checkout@v7
53
160
  - name: Analyze
54
- uses: github/codeql-action/analyze@v4
161
+ uses: ./.github/actions/codeql
55
162
  with:
56
- category: /language:${{ matrix.language }}
163
+ language: rust
164
+ build-mode: ${{ needs.detect.outputs.rust_build_mode }}
165
+ category: /language:rust
@@ -21,20 +21,17 @@ env:
21
21
  jobs:
22
22
  create-draft-pr:
23
23
  name: Create
24
- runs-on: ubuntu-latest
24
+ runs-on: ubuntu-slim
25
+ timeout-minutes: 10
25
26
  concurrency:
26
27
  group: ${{ github.workflow }}-${{ github.ref }}
27
28
  cancel-in-progress: true
28
29
  steps:
29
- - uses: actions/checkout@v7
30
- with:
31
- fetch-depth: 0
32
-
33
30
  - name: Check
34
31
  id: check-pr
35
32
  run: |
36
33
  BRANCH="${{ github.ref_name }}"
37
- EXISTING=$(gh pr list --base staging --head "$BRANCH" --state open --json number,title 2>/dev/null | jq 'length')
34
+ EXISTING=$(gh pr list --limit 1 --base staging --head "$BRANCH" --state open --json number,title 2>/dev/null | jq 'length')
38
35
  echo "existing=$EXISTING" >> "$GITHUB_OUTPUT"
39
36
 
40
37
  - name: Create
@@ -56,7 +53,7 @@ jobs:
56
53
  if: steps.check-pr.outputs.existing == '0'
57
54
  run: |
58
55
  BRANCH="${{ github.ref_name }}"
59
- PR_NUMBER=$(gh pr list --base staging --head "$BRANCH" --state open --json number 2>/dev/null | jq '.[0].number')
56
+ PR_NUMBER=$(gh pr list --limit 1 --base staging --head "$BRANCH" --state open --json number 2>/dev/null | jq '.[0].number')
60
57
  if [ -n "$PR_NUMBER" ]; then
61
58
  gh pr ready "$PR_NUMBER"
62
59
  fi
@@ -14,19 +14,23 @@ env:
14
14
  jobs:
15
15
  create-release-pr:
16
16
  name: Create
17
- runs-on: ubuntu-latest
17
+ runs-on: ubuntu-slim
18
+ timeout-minutes: 10
18
19
  concurrency:
19
20
  group: ${{ github.workflow }}-${{ github.ref }}
20
21
  cancel-in-progress: true
21
22
  steps:
22
- - uses: actions/checkout@v7
23
- with:
24
- fetch-depth: 0
25
-
26
23
  - name: Check
27
24
  id: check-pr
28
25
  run: |
29
- EXISTING=$(gh pr list --base main --head staging --state open --json number 2>/dev/null | jq 'length')
26
+ set -euo pipefail
27
+ EXISTING=$(gh pr list \
28
+ --repo "$GITHUB_REPOSITORY" \
29
+ --limit 1 \
30
+ --base main \
31
+ --head staging \
32
+ --state open \
33
+ --json number | jq 'length')
30
34
  echo "existing=$EXISTING" >> "$GITHUB_OUTPUT"
31
35
 
32
36
  - name: Create
@@ -34,7 +38,15 @@ jobs:
34
38
  run: |
35
39
  DATE=$(date +%Y-%m-%d)
36
40
  BODY_FILE="$RUNNER_TEMP/release-pr.md"
37
- COMMITS=$(git log --no-merges --pretty=format:'- %s (%h)' origin/main..HEAD)
41
+ COMPARE_URL="repos/${GITHUB_REPOSITORY}/compare/main...staging"
42
+ COMPARE_FILE="$RUNNER_TEMP/release-compare.json"
43
+ gh api "$COMPARE_URL?per_page=100" > "$COMPARE_FILE"
44
+ TOTAL_COMMITS=$(jq -r '.total_commits // 0' "$COMPARE_FILE")
45
+ if [ "$TOTAL_COMMITS" -gt 100 ]; then
46
+ LAST_PAGE=$(( (TOTAL_COMMITS + 99) / 100 ))
47
+ gh api "$COMPARE_URL?per_page=100&page=$LAST_PAGE" > "$COMPARE_FILE"
48
+ fi
49
+ COMMITS=$(jq -r '.commits[] | "- \(.commit.message | split("\\n")[0]) (\(.sha[0:7]))"' "$COMPARE_FILE")
38
50
  if [ -z "$COMMITS" ]; then
39
51
  COMMITS='- No commits found since main.'
40
52
  fi
@@ -55,6 +67,7 @@ jobs:
55
67
  EOF
56
68
 
57
69
  gh pr create \
70
+ --repo "$GITHUB_REPOSITORY" \
58
71
  --base main \
59
72
  --head staging \
60
73
  --title "Release: staging -> main ($DATE)" \
@@ -2,22 +2,114 @@ name: Release
2
2
 
3
3
  on:
4
4
  push:
5
- tags: ['v*.*.*']
5
+ branches: [main]
6
6
  workflow_dispatch:
7
7
 
8
8
  permissions:
9
9
  contents: write
10
+ issues: write
11
+ pull-requests: write
12
+
13
+ env:
14
+ REPO_FOUNDRY_PROFILE: ${{ vars.REPO_FOUNDRY_PROFILE }}
15
+ REPO_FOUNDRY_LANGUAGES: ${{ vars.REPO_FOUNDRY_LANGUAGES }}
16
+ REPO_FOUNDRY_FEATURES: ${{ vars.REPO_FOUNDRY_FEATURES }}
17
+ REPO_FOUNDRY_PACKAGE_MANAGER: ${{ vars.REPO_FOUNDRY_PACKAGE_MANAGER }}
18
+ REPO_FOUNDRY_RELEASE_TYPE: ${{ vars.REPO_FOUNDRY_RELEASE_TYPE }}
19
+ REPO_FOUNDRY_NPM_PUBLISH: ${{ vars.REPO_FOUNDRY_NPM_PUBLISH }}
10
20
 
11
21
  jobs:
12
22
  release:
13
- name: Publish
14
- runs-on: ubuntu-latest
23
+ name: Release / Version
24
+ runs-on: ubuntu-slim
25
+ timeout-minutes: 20
15
26
  concurrency:
16
27
  group: ${{ github.workflow }}-${{ github.ref }}
17
28
  cancel-in-progress: false
29
+ outputs:
30
+ release_created: ${{ steps.release.outputs.release_created || 'false' }}
31
+ tag_name: ${{ steps.release.outputs.tag_name }}
32
+ npm_publish: ${{ steps.profile.outputs.npm_publish }}
33
+ steps:
34
+ - name: Checkout
35
+ uses: actions/checkout@v7
36
+ with:
37
+ # Release Please reads release metadata and manifests; defer source
38
+ # blob transfer until a package file is actually needed.
39
+ filter: blob:none
40
+ - name: Detect release profile
41
+ id: profile
42
+ shell: bash
43
+ run: |
44
+ if [ -x .github/scripts/profile.sh ]; then
45
+ release_type="$(bash .github/scripts/profile.sh get release_type)"
46
+ npm_publish="$(bash .github/scripts/profile.sh get npm_publish)"
47
+ else
48
+ release_type="$(awk -F': ' '/^release_type:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
49
+ npm_publish="$(awk -F': ' '/^npm_publish:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
50
+ fi
51
+ [ -n "$release_type" ] || release_type=auto
52
+ [ -n "$npm_publish" ] || npm_publish=false
53
+
54
+ if [ "$release_type" = auto ]; then
55
+ if [ -f package.json ]; then release_type=node
56
+ elif [ -f pyproject.toml ]; then release_type=python
57
+ elif [ -f Cargo.toml ]; then release_type=rust
58
+ elif [ -f version.txt ]; then release_type=simple
59
+ else release_type=none
60
+ fi
61
+ fi
62
+
63
+ case "$release_type" in
64
+ node|python|rust|simple) ;;
65
+ none) npm_publish=false ;;
66
+ *) echo "Unsupported release_type: $release_type" >&2; exit 2 ;;
67
+ esac
68
+ if [ ! -f package.json ]; then npm_publish=false; fi
69
+ printf 'release_type=%s\n' "$release_type" >> "$GITHUB_OUTPUT"
70
+ printf 'npm_publish=%s\n' "$npm_publish" >> "$GITHUB_OUTPUT"
71
+ - name: Release Please
72
+ id: release
73
+ if: steps.profile.outputs.release_type != 'none'
74
+ uses: googleapis/release-please-action@v5
75
+ with:
76
+ # Use a PAT or GitHub App token when GITHUB_TOKEN cannot create PRs.
77
+ token: ${{ secrets.RELEASE_PLEASE_TOKEN || github.token }}
78
+ config-file: release-please-config.json
79
+ release-type: ${{ steps.profile.outputs.release_type }}
80
+
81
+ npm:
82
+ name: Release / Publish npm
83
+ needs: release
84
+ if: needs.release.outputs.release_created == 'true' && needs.release.outputs.npm_publish == 'true'
85
+ runs-on: ubuntu-slim
86
+ timeout-minutes: 15
87
+ permissions:
88
+ contents: read
89
+ id-token: write
90
+ env:
91
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
18
92
  steps:
19
- - uses: actions/checkout@v7
20
- - name: Create GitHub release
93
+ - name: Checkout release
94
+ uses: actions/checkout@v7
95
+ with:
96
+ ref: ${{ needs.release.outputs.tag_name }}
97
+ - name: Setup Node (trusted)
98
+ if: env.NPM_TOKEN == ''
99
+ uses: actions/setup-node@v5
100
+ with:
101
+ node-version: 24
102
+ - name: Setup Node (token)
103
+ if: env.NPM_TOKEN != ''
104
+ uses: actions/setup-node@v5
105
+ with:
106
+ node-version: 24
107
+ registry-url: https://registry.npmjs.org
108
+ - name: Publish npm with token
109
+ if: env.NPM_TOKEN != ''
21
110
  env:
22
- GH_TOKEN: ${{ github.token }}
23
- run: gh release create "${GITHUB_REF_NAME}" --generate-notes --verify-tag
111
+ NODE_AUTH_TOKEN: ${{ env.NPM_TOKEN }}
112
+ run: npm publish --provenance --access public
113
+ - name: Publish npm with trusted publishing
114
+ if: env.NPM_TOKEN == ''
115
+ run: npm publish --provenance --access public
@@ -10,26 +10,221 @@ on:
10
10
  permissions:
11
11
  contents: read
12
12
 
13
+ env:
14
+ REPO_FOUNDRY_PROFILE: ${{ vars.REPO_FOUNDRY_PROFILE }}
15
+ REPO_FOUNDRY_LANGUAGES: ${{ vars.REPO_FOUNDRY_LANGUAGES }}
16
+ REPO_FOUNDRY_FEATURES: ${{ vars.REPO_FOUNDRY_FEATURES }}
17
+ REPO_FOUNDRY_PACKAGE_MANAGER: ${{ vars.REPO_FOUNDRY_PACKAGE_MANAGER }}
18
+ REPO_FOUNDRY_CACHE_PACKAGES: ${{ vars.REPO_FOUNDRY_CACHE_PACKAGES || 'auto' }}
19
+ REPO_FOUNDRY_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before || '' }}
20
+
13
21
  concurrency:
14
- group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
22
+ group: ${{ github.workflow }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}
15
23
  cancel-in-progress: true
16
24
 
17
25
  jobs:
18
- dependency-audit:
19
- name: Dependency Audit
20
- runs-on: ubuntu-latest
26
+ profile:
27
+ name: Profile
28
+ runs-on: ubuntu-slim
29
+ timeout-minutes: 10
30
+ outputs:
31
+ javascript: ${{ steps.profile.outputs.javascript }}
32
+ rust: ${{ steps.profile.outputs.rust }}
33
+ python: ${{ steps.profile.outputs.python }}
34
+ python_requirements: ${{ steps.profile.outputs.python_requirements }}
35
+ steps:
36
+ - name: Checkout
37
+ uses: actions/checkout@v7
38
+ with:
39
+ fetch-depth: 2
40
+ filter: blob:none
41
+ sparse-checkout: |
42
+ .github
43
+ .mise.toml
44
+ mise.lock
45
+ .npmrc
46
+ .pnpmfile.cjs
47
+ .yarnrc*
48
+ **/package.json
49
+ **/bun.lock
50
+ **/bun.lockb
51
+ **/pnpm-lock.yaml
52
+ **/yarn.lock
53
+ **/package-lock.json
54
+ **/Cargo.toml
55
+ **/Cargo.lock
56
+ **/.cargo/**
57
+ **/pyproject.toml
58
+ **/requirements*.txt
59
+ **/setup.py
60
+ **/setup.cfg
61
+ **/Pipfile
62
+ **/Pipfile.lock
63
+ **/poetry.lock
64
+ **/uv.lock
65
+ sparse-checkout-cone-mode: false
66
+ - name: Detect
67
+ id: profile
68
+ run: bash .github/scripts/security.sh profile >> "$GITHUB_OUTPUT"
69
+
70
+ dependency-audit-javascript:
71
+ name: Dependency Audit (JavaScript)
72
+ runs-on: ubuntu-slim
73
+ timeout-minutes: 20
21
74
  steps:
22
75
  - name: Checkout
23
76
  uses: actions/checkout@v7
77
+ with:
78
+ # JavaScript audits only need manifests, lockfiles, and audit config.
79
+ filter: blob:none
80
+ sparse-checkout: |
81
+ .github
82
+ .mise.toml
83
+ mise.lock
84
+ .npmrc
85
+ .pnpmfile.cjs
86
+ .yarnrc*
87
+ **/package.json
88
+ **/bun.lock
89
+ **/bun.lockb
90
+ **/pnpm-lock.yaml
91
+ **/yarn.lock
92
+ **/package-lock.json
93
+ sparse-checkout-cone-mode: false
94
+ - name: Detect
95
+ id: applicability
96
+ run: bash .github/scripts/security.sh should_run javascript >> "$GITHUB_OUTPUT"
24
97
  - name: Setup
98
+ if: steps.applicability.outputs.applicable == 'true'
25
99
  uses: ./.github/actions/setup
100
+ with:
101
+ mise-scope: javascript
102
+ cache-save: ${{ github.event_name != 'pull_request' }}
26
103
  - name: Audit dependencies
27
- run: bash .github/scripts/security.sh
104
+ if: steps.applicability.outputs.applicable == 'true'
105
+ run: bash .github/scripts/security.sh audit javascript
106
+
107
+ dependency-audit-rust:
108
+ name: Dependency Audit (Rust)
109
+ # ubuntu-latest ships with Cargo/Rust; use it for the audit-only job and
110
+ # fall back to the repository toolchain when the image lacks Rust.
111
+ runs-on: ${{ vars.REPO_FOUNDRY_RUST_AUDIT_RUNNER || 'ubuntu-latest' }}
112
+ timeout-minutes: 20
113
+ steps:
114
+ - name: Checkout
115
+ uses: actions/checkout@v7
116
+ with:
117
+ # Rust audits only need manifests, Cargo configuration, and audit config.
118
+ filter: blob:none
119
+ sparse-checkout: |
120
+ .github
121
+ .mise.toml
122
+ mise.lock
123
+ **/Cargo.toml
124
+ **/Cargo.lock
125
+ **/.cargo/**
126
+ sparse-checkout-cone-mode: false
127
+ - name: Detect
128
+ id: applicability
129
+ run: bash .github/scripts/security.sh should_run rust >> "$GITHUB_OUTPUT"
130
+ - name: Check Rust
131
+ id: system-rust
132
+ if: steps.applicability.outputs.applicable == 'true'
133
+ shell: bash
134
+ run: |
135
+ if command -v cargo >/dev/null 2>&1 && command -v rustc >/dev/null 2>&1; then
136
+ cargo --version
137
+ rustc --version
138
+ echo 'available=true' >> "$GITHUB_OUTPUT"
139
+ else
140
+ echo 'available=false' >> "$GITHUB_OUTPUT"
141
+ fi
142
+ - name: Setup
143
+ if: >-
144
+ steps.applicability.outputs.applicable == 'true' &&
145
+ steps.system-rust.outputs.available != 'true'
146
+ uses: ./.github/actions/setup
147
+ with:
148
+ mise-scope: rust
149
+ cache-save: ${{ github.event_name != 'pull_request' }}
150
+ - name: Install cargo-audit
151
+ if: steps.applicability.outputs.applicable == 'true'
152
+ uses: taiki-e/install-action@v2
153
+ with:
154
+ tool: cargo-audit
155
+ - name: Audit dependencies
156
+ if: steps.applicability.outputs.applicable == 'true'
157
+ run: bash .github/scripts/security.sh audit rust
158
+
159
+ dependency-audit-python:
160
+ name: Dependency Audit (Python) / ${{ matrix.requirement }}
161
+ needs: profile
162
+ if: >-
163
+ ${{ needs.profile.result == 'success' &&
164
+ needs.profile.outputs.python == 'true' &&
165
+ needs.profile.outputs.python_requirements != '["none"]' }}
166
+ strategy:
167
+ fail-fast: false
168
+ max-parallel: 8
169
+ matrix:
170
+ requirement: ${{ fromJSON(needs.profile.outputs.python_requirements) }}
171
+ runs-on: ubuntu-slim
172
+ timeout-minutes: 20
173
+ env:
174
+ REPO_FOUNDRY_PYTHON_REQUIREMENT: ${{ matrix.requirement }}
175
+ steps:
176
+ - name: Checkout
177
+ uses: actions/checkout@v7
178
+ with:
179
+ # Python audits only need manifests, lockfiles, and audit config.
180
+ filter: blob:none
181
+ sparse-checkout: |
182
+ .github
183
+ .mise.toml
184
+ mise.lock
185
+ **/pyproject.toml
186
+ **/requirements*.txt
187
+ **/setup.py
188
+ **/setup.cfg
189
+ **/Pipfile
190
+ **/Pipfile.lock
191
+ **/poetry.lock
192
+ **/uv.lock
193
+ sparse-checkout-cone-mode: false
194
+ - name: Setup
195
+ uses: ./.github/actions/setup
196
+ with:
197
+ mise-scope: python
198
+ cache-save: ${{ github.event_name != 'pull_request' }}
199
+ - name: Audit dependencies
200
+ run: bash .github/scripts/security.sh audit python
201
+
202
+ dependency-audit-python-gate:
203
+ name: Dependency Audit (Python)
204
+ needs: [profile, dependency-audit-python]
205
+ if: always()
206
+ runs-on: ubuntu-slim
207
+ timeout-minutes: 5
208
+ steps:
209
+ - name: Gate
210
+ env:
211
+ PROFILE_RESULT: ${{ needs.profile.result }}
212
+ AUDIT_RESULT: ${{ needs.dependency-audit-python.result }}
213
+ run: |
214
+ if [ "$PROFILE_RESULT" != success ]; then
215
+ echo "Python audit profile failed: $PROFILE_RESULT" >&2
216
+ exit 1
217
+ fi
218
+ case "$AUDIT_RESULT" in
219
+ success|skipped) exit 0 ;;
220
+ *) echo "Python dependency audit failed: $AUDIT_RESULT" >&2; exit 1 ;;
221
+ esac
28
222
 
29
223
  dependency-review:
30
224
  name: Dependency Review
31
225
  if: ${{ github.event_name == 'pull_request' && !github.event.repository.private }}
32
- runs-on: ubuntu-latest
226
+ runs-on: ubuntu-slim
227
+ timeout-minutes: 10
33
228
  steps:
34
229
  - name: Review
35
230
  uses: actions/dependency-review-action@v5