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,776 @@
1
+ name: Repository Setup
2
+ description: Install the pinned mise toolchain and optionally project dependencies.
3
+
4
+ inputs:
5
+ install:
6
+ description: Install dependencies for the detected project ecosystems.
7
+ required: false
8
+ default: 'false'
9
+ install-javascript:
10
+ description: Install JavaScript/TypeScript package dependencies when install is enabled.
11
+ required: false
12
+ default: 'true'
13
+ install-python:
14
+ description: Install Python project dependencies when install is enabled.
15
+ required: false
16
+ default: 'true'
17
+ install-rust:
18
+ description: Fetch Rust project dependencies when install is enabled.
19
+ required: false
20
+ default: 'true'
21
+ mise-scope:
22
+ description: Limit mise installation and language package caches to auto, all, javascript, python, or rust.
23
+ required: false
24
+ default: auto
25
+ cache-build:
26
+ description: Restore JavaScript framework, Rust dependency, and incremental compiler caches.
27
+ required: false
28
+ default: 'true'
29
+ cache-environment:
30
+ description: Restore materialized Python virtual environments; disable for parallel test jobs with large environments.
31
+ required: false
32
+ default: 'true'
33
+ cache-installed:
34
+ description: Cache materialized JavaScript dependencies; auto skips large Bun node_modules trees and keeps compact link-based installs.
35
+ required: false
36
+ default: auto
37
+ cache-save:
38
+ description: Save restored caches after the job completes; disable to avoid post-job archive work in parallel jobs.
39
+ required: false
40
+ default: 'true'
41
+ cache-packages:
42
+ description: Restore JavaScript package-manager caches; auto skips large Bun lockfiles, or use true/false to override.
43
+ required: false
44
+ default: auto
45
+ cache-lint:
46
+ description: Restore the JavaScript/TypeScript lint cache.
47
+ required: false
48
+ default: 'false'
49
+ cache-format:
50
+ description: Restore the Prettier format cache.
51
+ required: false
52
+ default: 'false'
53
+ cache-turbo:
54
+ description: Cache local Turborepo artifacts; auto skips duplicate archives when TURBO_TOKEN enables remote caching.
55
+ required: false
56
+ default: auto
57
+ task:
58
+ description: Optional CI task used to scope test dependency installation.
59
+ required: false
60
+ default: ''
61
+ task-javascript:
62
+ description: Whether the selected task has a JavaScript suite; auto preserves default detection.
63
+ required: false
64
+ default: auto
65
+ task-python:
66
+ description: Whether the selected task has a Python suite; auto preserves default detection.
67
+ required: false
68
+ default: auto
69
+ task-rust:
70
+ description: Whether the selected task has a Rust suite; auto preserves default detection.
71
+ required: false
72
+ default: auto
73
+
74
+ runs:
75
+ using: composite
76
+ steps:
77
+ - name: Disable framework telemetry
78
+ shell: bash
79
+ run: |
80
+ echo 'TURBO_TELEMETRY_DISABLED=1' >> "$GITHUB_ENV"
81
+ echo 'NEXT_TELEMETRY_DISABLED=1' >> "$GITHUB_ENV"
82
+ - name: Detect project profile
83
+ id: profile
84
+ shell: bash
85
+ run: |
86
+ tracked_files="$(git ls-files)"
87
+
88
+ has_tracked_regex() {
89
+ printf '%s\n' "$tracked_files" | grep -Eq "$1"
90
+ }
91
+
92
+ has_python_source() {
93
+ awk '!/^\.github\// && /(^|\/)[^\/]+\.py$/ { found=1; exit } END { exit !found }' <<< "$tracked_files"
94
+ }
95
+
96
+ has_package_script() {
97
+ [ -f package.json ] || return 1
98
+ node - "$1" <<'NODE'
99
+ const fs = require('node:fs');
100
+ const script = process.argv[2];
101
+ const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
102
+ process.exit(packageJson.scripts?.[script] ? 0 : 1);
103
+ NODE
104
+ }
105
+
106
+ script_uses_turbo() {
107
+ [ -f package.json ] || return 1
108
+ REPO_FOUNDRY_TURBO_TASK="$REPO_FOUNDRY_TASK" node <<'NODE'
109
+ const fs = require('node:fs');
110
+ const task = process.env.REPO_FOUNDRY_TURBO_TASK || '';
111
+ const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
112
+ const scripts = packageJson.scripts || {};
113
+ const candidates = {
114
+ format: ['format:check', 'format', 'fmt'],
115
+ lint: ['lint'],
116
+ type_check: ['type-check', 'typecheck', 'type:check'],
117
+ build: ['build'],
118
+ unit: ['test:unit', 'test:coverage', 'test'],
119
+ integration: ['test:integration'],
120
+ e2e: ['test:e2e', 'e2e'],
121
+ smoke: ['test:smoke', 'smoke'],
122
+ }[task] || [];
123
+ const selected = candidates.find((name) => scripts[name]);
124
+ const usesTurbo = Boolean(selected && /\bturbo(?:\s|$)/.test(scripts[selected]));
125
+ process.exit(usesTurbo ? 0 : 1);
126
+ NODE
127
+ }
128
+
129
+ uses_prettier() {
130
+ [ -f package.json ] || return 0
131
+ node <<'NODE'
132
+ const fs = require('node:fs');
133
+ const p = JSON.parse(fs.readFileSync('package.json', 'utf8'));
134
+ const scripts = p.scripts || {};
135
+ const dependencies = {
136
+ ...p.dependencies,
137
+ ...p.devDependencies,
138
+ ...p.optionalDependencies,
139
+ ...p.peerDependencies,
140
+ };
141
+ const usesPrettier = Boolean(dependencies.prettier) || Object.values(scripts).some((value) => /\bprettier\b/.test(value));
142
+ const hasFormatScript = Boolean(scripts['format:check'] || scripts.format || scripts.fmt);
143
+ process.exit(usesPrettier || !hasFormatScript ? 0 : 1);
144
+ NODE
145
+ }
146
+
147
+ uses_eslint() {
148
+ [ -f package.json ] || return 1
149
+ node <<'NODE'
150
+ const fs = require('node:fs');
151
+ const p = JSON.parse(fs.readFileSync('package.json', 'utf8'));
152
+ const scripts = p.scripts || {};
153
+ const dependencies = {
154
+ ...p.dependencies,
155
+ ...p.devDependencies,
156
+ ...p.optionalDependencies,
157
+ ...p.peerDependencies,
158
+ };
159
+ const usesEslint = Boolean(dependencies.eslint) || Object.values(scripts).some((value) => /\beslint\b/.test(value));
160
+ process.exit(usesEslint ? 0 : 1);
161
+ NODE
162
+ }
163
+
164
+ javascript=false
165
+ if [ -f package.json ] || [ -f bun.lock ] || [ -f bun.lockb ] ||
166
+ [ -f pnpm-lock.yaml ] || [ -f yarn.lock ] || [ -f package-lock.json ] ||
167
+ has_tracked_regex '(^|/)[^/]+\.(js|jsx|mjs|cjs|ts|tsx|mts|cts)$'; then
168
+ javascript=true
169
+ echo 'javascript=true' >> "$GITHUB_OUTPUT"
170
+ else
171
+ echo 'javascript=false' >> "$GITHUB_OUTPUT"
172
+ fi
173
+
174
+ if [ -f bun.lock ] || [ -f bun.lockb ] || [ -f pnpm-lock.yaml ] ||
175
+ [ -f yarn.lock ] || [ -f package-lock.json ]; then
176
+ javascript_dependencies=true
177
+ echo 'javascript_dependencies=true' >> "$GITHUB_OUTPUT"
178
+ elif [ -f package.json ] && REPO_FOUNDRY_PACKAGE_FILES="$tracked_files" node <<'NODE'
179
+ const fs = require('node:fs');
180
+
181
+ const files = (process.env.REPO_FOUNDRY_PACKAGE_FILES || '')
182
+ .trim()
183
+ .split('\n')
184
+ .filter((file) => /(^|\/)package\.json$/.test(file));
185
+ const groups = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'];
186
+ const hasDependencies = files.some((file) => {
187
+ try {
188
+ const packageJson = JSON.parse(fs.readFileSync(file, 'utf8'));
189
+ return groups.some((group) => packageJson[group] && Object.keys(packageJson[group]).length > 0) ||
190
+ Array.isArray(packageJson.workspaces) ||
191
+ (packageJson.workspaces && typeof packageJson.workspaces === 'object');
192
+ } catch {
193
+ return true;
194
+ }
195
+ });
196
+ process.exit(hasDependencies ? 0 : 1);
197
+ NODE
198
+ then
199
+ javascript_dependencies=true
200
+ echo 'javascript_dependencies=true' >> "$GITHUB_OUTPUT"
201
+ else
202
+ javascript_dependencies=false
203
+ echo 'javascript_dependencies=false' >> "$GITHUB_OUTPUT"
204
+ fi
205
+
206
+ javascript_package_manager=none
207
+ configured_package_manager=""
208
+ if [ -f .github/template.yml ]; then
209
+ configured_package_manager="$(awk -F': ' '/^package_manager:/ {print $2; exit}' .github/template.yml)"
210
+ fi
211
+ case "$configured_package_manager" in
212
+ bun|pnpm|yarn|npm) javascript_package_manager="$configured_package_manager" ;;
213
+ *)
214
+ if [ -f bun.lock ] || [ -f bun.lockb ]; then javascript_package_manager=bun
215
+ elif [ -f pnpm-lock.yaml ]; then javascript_package_manager=pnpm
216
+ elif [ -f yarn.lock ]; then javascript_package_manager=yarn
217
+ elif [ -f package-lock.json ]; then javascript_package_manager=npm
218
+ elif [ "$javascript_dependencies" = true ]; then javascript_package_manager=bun
219
+ fi
220
+ ;;
221
+ esac
222
+ printf 'javascript_package_manager=%s\n' "$javascript_package_manager" >> "$GITHUB_OUTPUT"
223
+
224
+ javascript_packages_cache=true
225
+ case "$REPO_FOUNDRY_CACHE_PACKAGES" in
226
+ false) javascript_packages_cache=false ;;
227
+ true) : ;;
228
+ auto)
229
+ # Large Bun stores can take longer to transfer and extract than a
230
+ # clean registry install. Keep normal projects cached, but avoid
231
+ # the expensive archive for workspace graphs and once the tracked
232
+ # Bun lockfiles exceed 512 KiB in total. Workspace installs often
233
+ # resolve quickly from the registry, while their shared Bun store
234
+ # is expensive to archive. Set REPO_FOUNDRY_CACHE_PACKAGES=true
235
+ # when a repository has measured a cache win.
236
+ if [ "$javascript_package_manager" = bun ]; then
237
+ bun_workspace=false
238
+ if [ -f package.json ] && node -e '
239
+ const p = require("./package.json");
240
+ const workspaces = p.workspaces;
241
+ process.exit(
242
+ Array.isArray(workspaces) ||
243
+ Boolean(workspaces && typeof workspaces === "object") ? 0 : 1,
244
+ );
245
+ ' 2>/dev/null; then
246
+ bun_workspace=true
247
+ fi
248
+ bun_lock_bytes=0
249
+ while IFS= read -r lockfile; do
250
+ [ -f "$lockfile" ] || continue
251
+ lock_bytes="$(wc -c < "$lockfile")"
252
+ bun_lock_bytes=$((bun_lock_bytes + lock_bytes))
253
+ done < <(printf '%s\n' "$tracked_files" | awk '/(^|\/)bun\.lock(b)?$/')
254
+ if [ "$bun_workspace" = true ] || [ "$bun_lock_bytes" -gt 524288 ]; then
255
+ javascript_packages_cache=false
256
+ fi
257
+ fi
258
+ ;;
259
+ *) echo "Unsupported cache-packages value: $REPO_FOUNDRY_CACHE_PACKAGES" >&2; exit 2 ;;
260
+ esac
261
+ printf 'javascript_packages_cache=%s\n' "$javascript_packages_cache" >> "$GITHUB_OUTPUT"
262
+
263
+ python=false
264
+ if has_python_source || [ -f pyproject.toml ] ||
265
+ [ -f requirements.txt ] || [ -f requirements-dev.txt ]; then
266
+ python=true
267
+ echo 'python=true' >> "$GITHUB_OUTPUT"
268
+ else
269
+ echo 'python=false' >> "$GITHUB_OUTPUT"
270
+ fi
271
+
272
+ rust=false
273
+ if [ -f Cargo.toml ] || [ -f Cargo.lock ]; then
274
+ rust=true
275
+ echo 'rust=true' >> "$GITHUB_OUTPUT"
276
+ else
277
+ echo 'rust=false' >> "$GITHUB_OUTPUT"
278
+ fi
279
+
280
+ turbo=false
281
+ if [ -f turbo.json ] || [ -f turbo.jsonc ] || has_tracked_regex '(^|/)turbo\.jsonc?$'; then
282
+ turbo=true
283
+ echo 'turbo=true' >> "$GITHUB_OUTPUT"
284
+ else
285
+ echo 'turbo=false' >> "$GITHUB_OUTPUT"
286
+ fi
287
+
288
+ eslint=false
289
+ if [ -f .eslintrc ] || [ -f .eslintrc.js ] || [ -f .eslintrc.cjs ] ||
290
+ [ -f .eslintrc.json ] || has_tracked_regex '(^|/)(\.eslintrc[^/]*|eslint\.config\.[^/]*)$' ||
291
+ uses_eslint; then
292
+ eslint=true
293
+ echo 'eslint=true' >> "$GITHUB_OUTPUT"
294
+ else
295
+ echo 'eslint=false' >> "$GITHUB_OUTPUT"
296
+ fi
297
+
298
+ prettier=false
299
+ if [ "$javascript" = true ]; then
300
+ # Cache the fallback formatter for projects without a dedicated
301
+ # config, but avoid probing an empty cache when a repository-owned
302
+ # format script does not use Prettier.
303
+ if uses_prettier; then
304
+ prettier=true
305
+ fi
306
+ fi
307
+ printf 'prettier=%s\n' "$prettier" >> "$GITHUB_OUTPUT"
308
+
309
+ # Repository-owned format/lint scripts are authoritative and commonly
310
+ # do not create the generic tool caches. Avoid recursive cache-path
311
+ # scans and post-job archive work unless the fallback runner is used.
312
+ prettier_fallback=false
313
+ if [ "$prettier" = true ] && ! has_package_script format:check; then
314
+ prettier_fallback=true
315
+ fi
316
+ eslint_fallback=false
317
+ if [ "$eslint" = true ] && ! has_package_script lint; then
318
+ eslint_fallback=true
319
+ fi
320
+ printf 'prettier_fallback=%s\n' "$prettier_fallback" >> "$GITHUB_OUTPUT"
321
+ printf 'eslint_fallback=%s\n' "$eslint_fallback" >> "$GITHUB_OUTPUT"
322
+
323
+ turbo_task=false
324
+ if [ -z "$REPO_FOUNDRY_TASK" ] || script_uses_turbo; then
325
+ turbo_task=true
326
+ fi
327
+ printf 'turbo_task=%s\n' "$turbo_task" >> "$GITHUB_OUTPUT"
328
+
329
+ build=false
330
+ if has_tracked_regex '(^|/)(next\.config\.[^/]*|vite\.config\.[^/]*|tsconfig[^/]*\.json)$'; then
331
+ build=true
332
+ echo 'build=true' >> "$GITHUB_OUTPUT"
333
+ else
334
+ echo 'build=false' >> "$GITHUB_OUTPUT"
335
+ fi
336
+
337
+ python_dependencies=false
338
+ if [ -f pyproject.toml ] || [ -f uv.lock ] ||
339
+ has_tracked_regex '(^|/)requirements[^/]*\.txt$' ||
340
+ [ -f Pipfile.lock ] || [ -f poetry.lock ]; then
341
+ python_dependencies=true
342
+ echo 'python_dependencies=true' >> "$GITHUB_OUTPUT"
343
+ else
344
+ echo 'python_dependencies=false' >> "$GITHUB_OUTPUT"
345
+ fi
346
+
347
+ configured_tool() {
348
+ [ -f .mise.toml ] && grep -Eq "^$1[[:space:]]*=" .mise.toml
349
+ }
350
+
351
+ ruff=false
352
+ if [ "$python" = true ] &&
353
+ { [ -f ruff.toml ] || configured_tool ruff ||
354
+ { [ -f pyproject.toml ] && grep -Eiq 'ruff|\[tool\.ruff' pyproject.toml; } ||
355
+ { [ -f requirements.txt ] && grep -Eiq '(^|[-_.])ruff([<>=[:space:]]|$)' requirements.txt; } ||
356
+ { [ -f requirements-dev.txt ] && grep -Eiq '(^|[-_.])ruff([<>=[:space:]]|$)' requirements-dev.txt; }; }; then
357
+ ruff=true
358
+ fi
359
+ printf 'ruff=%s\n' "$ruff" >> "$GITHUB_OUTPUT"
360
+
361
+ mise_tools=()
362
+ add_configured_tool() {
363
+ # Optional tools are expected to be absent in single-language repos.
364
+ # Keep that a successful probe so Bash strict mode does not abort
365
+ # profile detection before the actual setup step runs.
366
+ if configured_tool "$1"; then
367
+ mise_tools+=("$1")
368
+ fi
369
+ return 0
370
+ }
371
+
372
+ case "$REPO_FOUNDRY_MISE_SCOPE" in
373
+ all)
374
+ for tool in node bun python uv ruff rust; do add_configured_tool "$tool"; done
375
+ ;;
376
+ auto)
377
+ task_language_needed() {
378
+ local language="$1" flag
379
+ case "$language" in
380
+ javascript) flag="$REPO_FOUNDRY_TASK_JAVASCRIPT" ;;
381
+ python) flag="$REPO_FOUNDRY_TASK_PYTHON" ;;
382
+ rust) flag="$REPO_FOUNDRY_TASK_RUST" ;;
383
+ *) return 0 ;;
384
+ esac
385
+ [ -z "$REPO_FOUNDRY_TASK" ] || [ "$flag" = auto ] || [ "$flag" = true ]
386
+ }
387
+ if [ "$javascript" = true ] && task_language_needed javascript; then
388
+ add_configured_tool node
389
+ if { [ -f bun.lock ] || [ -f bun.lockb ] ||
390
+ awk -F': ' '/^package_manager:/ {print $2; exit}' .github/template.yml 2>/dev/null | grep -qx bun; }; then
391
+ add_configured_tool bun
392
+ fi
393
+ fi
394
+ if [ "$python" = true ] && task_language_needed python; then
395
+ add_configured_tool python
396
+ add_configured_tool uv
397
+ add_configured_tool ruff
398
+ fi
399
+ if [ "$rust" = true ] && task_language_needed rust; then add_configured_tool rust; fi
400
+ ;;
401
+ javascript)
402
+ configured_tool node && mise_tools+=(node)
403
+ if { [ -f bun.lock ] || [ -f bun.lockb ] ||
404
+ awk -F': ' '/^package_manager:/ {print $2; exit}' .github/template.yml 2>/dev/null | grep -qx bun; } && configured_tool bun; then
405
+ mise_tools+=(bun)
406
+ fi
407
+ ;;
408
+ python)
409
+ configured_tool python && mise_tools+=(python)
410
+ configured_tool uv && mise_tools+=(uv)
411
+ configured_tool ruff && mise_tools+=(ruff)
412
+ ;;
413
+ rust) configured_tool rust && mise_tools+=(rust) ;;
414
+ *) echo "Unsupported mise-scope: $REPO_FOUNDRY_MISE_SCOPE" >&2; exit 2 ;;
415
+ esac
416
+ printf 'mise_tools=%s\n' "${mise_tools[*]}" >> "$GITHUB_OUTPUT"
417
+ if [ "${#mise_tools[@]}" -gt 0 ]; then
418
+ printf 'mise_required=true\n' >> "$GITHUB_OUTPUT"
419
+ else
420
+ printf 'mise_required=false\n' >> "$GITHUB_OUTPUT"
421
+ fi
422
+ env:
423
+ REPO_FOUNDRY_MISE_SCOPE: ${{ inputs.mise-scope }}
424
+ REPO_FOUNDRY_TASK: ${{ inputs.task }}
425
+ REPO_FOUNDRY_TASK_JAVASCRIPT: ${{ inputs.task-javascript }}
426
+ REPO_FOUNDRY_TASK_PYTHON: ${{ inputs.task-python }}
427
+ REPO_FOUNDRY_TASK_RUST: ${{ inputs.task-rust }}
428
+ REPO_FOUNDRY_CACHE_PACKAGES: ${{ inputs.cache-packages }}
429
+
430
+ - name: Setup uv
431
+ if: >-
432
+ steps.profile.outputs.python == 'true' &&
433
+ (inputs.mise-scope == 'python' ||
434
+ (inputs.install == 'true' && inputs.install-python == 'true')) &&
435
+ !contains(steps.profile.outputs.mise_tools, 'uv')
436
+ uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
437
+ with:
438
+ version: 0.11.32
439
+ enable-cache: false
440
+
441
+ - name: Cache installed JavaScript dependencies
442
+ id: javascript-dependencies
443
+ if: >-
444
+ inputs.install == 'true' &&
445
+ inputs.install-javascript == 'true' &&
446
+ inputs.cache-installed != 'false' &&
447
+ steps.profile.outputs.javascript_dependencies == 'true' &&
448
+ steps.profile.outputs.javascript_package_manager != 'npm' &&
449
+ (inputs.cache-installed == 'true' ||
450
+ steps.profile.outputs.javascript_package_manager == 'pnpm' ||
451
+ steps.profile.outputs.javascript_package_manager == 'yarn') &&
452
+ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/.npmrc', '**/.yarnrc*', '**/.pnpmfile.cjs', '.github/template.yml', '.mise.toml', 'mise.lock') != ''
453
+ uses: actions/cache/restore@v5
454
+ with:
455
+ path: |
456
+ **/node_modules
457
+ key: ${{ runner.os }}-javascript-installed-v3-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/.npmrc', '**/.yarnrc*', '**/.pnpmfile.cjs', '.github/template.yml', '.mise.toml', 'mise.lock') }}
458
+ - name: Restore Bun dependencies
459
+ id: javascript-bun-cache-restore
460
+ if: >-
461
+ inputs.install == 'true' &&
462
+ inputs.install-javascript == 'true' &&
463
+ inputs.cache-installed != 'false' &&
464
+ steps.profile.outputs.javascript_dependencies == 'true' &&
465
+ steps.profile.outputs.javascript_package_manager == 'bun' &&
466
+ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/.npmrc', '.github/template.yml', '.mise.toml', 'mise.lock') != ''
467
+ uses: actions/cache/restore@v5
468
+ with:
469
+ path: |
470
+ **/node_modules
471
+ key: ${{ runner.os }}-javascript-installed-v4-bun-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/.npmrc', '.github/template.yml', '.mise.toml', 'mise.lock') }}
472
+ - name: Detect Bun dependencies
473
+ id: javascript-bun-dependencies
474
+ if: >-
475
+ inputs.install == 'true' &&
476
+ inputs.install-javascript == 'true' &&
477
+ inputs.cache-installed != 'false' &&
478
+ steps.profile.outputs.javascript_dependencies == 'true' &&
479
+ steps.profile.outputs.javascript_package_manager == 'bun'
480
+ shell: bash
481
+ run: |
482
+ if find . -path './.git' -prune -o -type d -name node_modules -print -quit | grep -q .; then
483
+ echo 'restored=true' >> "$GITHUB_OUTPUT"
484
+ else
485
+ echo 'restored=false' >> "$GITHUB_OUTPUT"
486
+ fi
487
+ - name: Cache Corepack managers
488
+ if: (inputs.mise-scope == 'javascript' || steps.javascript-dependencies.outputs.cache-hit == 'true') && inputs.install-javascript == 'true' && steps.profile.outputs.javascript == 'true' && hashFiles('**/pnpm-lock.yaml', '**/yarn.lock') != ''
489
+ uses: ./.github/actions/cache
490
+ with:
491
+ save: ${{ inputs.cache-save }}
492
+ path: ~/.cache/node/corepack
493
+ # Corepack shims are independent of the project toolchain; keep this
494
+ # cache warm when Node, Bun, or other mise-managed tools change.
495
+ key: ${{ runner.os }}-corepack-${{ hashFiles('**/package.json', '**/pnpm-lock.yaml', '**/yarn.lock') }}
496
+ restore-keys: |
497
+ ${{ runner.os }}-corepack-
498
+ - name: Cache JavaScript packages
499
+ id: javascript-packages
500
+ if: inputs.install == 'true' && inputs.install-javascript == 'true' && steps.profile.outputs.javascript_packages_cache == 'true' && steps.profile.outputs.javascript == 'true' && hashFiles('**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json') != '' && steps.javascript-dependencies.outputs.cache-hit != 'true' && steps.javascript-bun-dependencies.outputs.restored != 'true'
501
+ uses: actions/cache/restore@v5
502
+ with:
503
+ path: |
504
+ ~/.bun/install/cache
505
+ ~/.cache/node/corepack
506
+ ~/.npm
507
+ ~/.cache/yarn
508
+ ~/.local/share/pnpm/store
509
+ key: ${{ runner.os }}-javascript-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json') }}
510
+ restore-keys: |
511
+ ${{ runner.os }}-javascript-
512
+ - name: Cache JavaScript lint state
513
+ # Fallback ESLint mode is valid without a dedicated config file; use the
514
+ # manifest/lockfile as the cache identity in that case as well.
515
+ if: inputs.install == 'true' && inputs.cache-lint == 'true' && steps.profile.outputs.eslint_fallback == 'true' && hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/.eslintrc*', '**/eslint.config.*') != ''
516
+ uses: ./.github/actions/cache
517
+ with:
518
+ save: ${{ inputs.cache-save }}
519
+ path: |
520
+ **/.eslintcache
521
+ key: ${{ runner.os }}-javascript-lint-${{ github.workflow }}-${{ github.job }}-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/.eslintrc*', '**/eslint.config.*') }}
522
+ restore-keys: |
523
+ ${{ runner.os }}-javascript-lint-${{ github.workflow }}-${{ github.job }}-
524
+ - name: Cache JavaScript format state
525
+ if: inputs.install == 'true' && inputs.cache-format == 'true' && steps.profile.outputs.prettier_fallback == 'true' && hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/.prettierrc*', '**/prettier.config.*') != ''
526
+ uses: ./.github/actions/cache
527
+ with:
528
+ save: ${{ inputs.cache-save }}
529
+ path: |
530
+ **/node_modules/.cache/prettier
531
+ key: ${{ runner.os }}-javascript-format-${{ github.workflow }}-${{ github.job }}-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/.prettierrc*', '**/prettier.config.*') }}
532
+ # Keep primary saves isolated so parallel jobs cannot race. A cache
533
+ # from another workflow job is still a valid warm fallback.
534
+ restore-keys: |
535
+ ${{ runner.os }}-javascript-format-
536
+ - name: Cache JavaScript build state
537
+ if: >-
538
+ inputs.install == 'true' &&
539
+ inputs.cache-build == 'true' &&
540
+ (steps.profile.outputs.build == 'true' || inputs.task == 'build') &&
541
+ (inputs.task != 'type_check' ||
542
+ steps.profile.outputs.turbo != 'true' ||
543
+ steps.profile.outputs.turbo_task != 'true') &&
544
+ (env.TURBO_TOKEN == '' ||
545
+ env.TURBO_TEAM == '' ||
546
+ steps.profile.outputs.turbo != 'true' ||
547
+ steps.profile.outputs.turbo_task != 'true')
548
+ uses: ./.github/actions/cache
549
+ with:
550
+ save: ${{ inputs.cache-save }}
551
+ # Remote Turbo already stores task outputs for Turbo-backed builds.
552
+ # Avoid a second, often-empty framework cache archive in that mode;
553
+ # Turbo Type-Check tasks do not need framework build state either.
554
+ path: |
555
+ **/.next/cache
556
+ **/node_modules/.vite
557
+ **/*.tsbuildinfo
558
+ key: ${{ runner.os }}-javascript-build-${{ github.workflow }}-${{ github.job }}-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/next.config.*', '**/vite.config.*', '**/tsconfig*.json', '.mise.toml', 'mise.lock') }}
559
+ # Keep primary saves isolated so parallel jobs cannot race. A cache
560
+ # from another workflow job is still a valid warm fallback.
561
+ restore-keys: |
562
+ ${{ runner.os }}-javascript-build-
563
+ - name: Cache Python environment
564
+ id: python-environment
565
+ if: inputs.install == 'true' && inputs.install-python == 'true' && inputs.cache-environment == 'true' && steps.profile.outputs.python == 'true' && hashFiles('**/pyproject.toml', '**/uv.lock', '**/requirements*.txt', '**/Pipfile.lock', '**/poetry.lock', '.mise.toml', 'mise.lock') != ''
566
+ uses: ./.github/actions/cache
567
+ with:
568
+ save: ${{ inputs.cache-save }}
569
+ path: |
570
+ .venv
571
+ **/.venv
572
+ key: ${{ runner.os }}-python-environment-${{ hashFiles('**/pyproject.toml', '**/uv.lock', '**/requirements*.txt', '**/Pipfile.lock', '**/poetry.lock', '.mise.toml', 'mise.lock') }}
573
+ - name: Cache Python packages
574
+ if: inputs.install == 'true' && (inputs.mise-scope == 'auto' || inputs.mise-scope == 'all' || inputs.mise-scope == 'python') && inputs.install-python == 'true' && steps.profile.outputs.python_dependencies == 'true' && hashFiles('**/pyproject.toml', '**/uv.lock', '**/requirements*.txt', '**/Pipfile.lock', '**/poetry.lock') != '' && steps.python-environment.outputs.cache-hit != 'true'
575
+ uses: ./.github/actions/cache
576
+ with:
577
+ save: ${{ inputs.cache-save }}
578
+ path: |
579
+ ~/.cache/pip
580
+ ~/.cache/uv
581
+ key: ${{ runner.os }}-python-${{ hashFiles('**/pyproject.toml', '**/uv.lock', '**/requirements*.txt', '**/Pipfile.lock', '**/poetry.lock') }}
582
+ restore-keys: |
583
+ ${{ runner.os }}-python-
584
+ - name: Cache Python audit tools
585
+ id: python-audit-tools
586
+ if: inputs.install == 'false' && (inputs.mise-scope == 'auto' || inputs.mise-scope == 'all' || inputs.mise-scope == 'python') && inputs.install-python == 'true' && steps.profile.outputs.python_dependencies == 'true' && hashFiles('**/pyproject.toml', '**/uv.lock', '**/requirements*.txt', '**/Pipfile.lock', '**/poetry.lock') != ''
587
+ uses: ./.github/actions/cache
588
+ with:
589
+ save: ${{ inputs.cache-save }}
590
+ path: ~/.cache/uv-audit
591
+ # Keep scanner wheels and metadata warm across application and script
592
+ # changes. Bump v2 only when the pinned scanner version changes.
593
+ key: ${{ runner.os }}-python-audit-v2
594
+ restore-keys: |
595
+ ${{ runner.os }}-python-audit-
596
+ - name: Enable offline Python audit
597
+ if: steps.python-audit-tools.outputs.cache-hit == 'true'
598
+ shell: bash
599
+ run: echo 'REPO_FOUNDRY_UV_AUDIT_OFFLINE=true' >> "$GITHUB_ENV"
600
+ - name: Cache Python lint state
601
+ if: inputs.install == 'true' && inputs.cache-lint == 'true' && steps.profile.outputs.ruff == 'true' && hashFiles('**/pyproject.toml', '**/ruff.toml', '.mise.toml', 'mise.lock') != ''
602
+ uses: ./.github/actions/cache
603
+ with:
604
+ save: ${{ inputs.cache-save }}
605
+ path: |
606
+ **/.ruff_cache
607
+ key: ${{ runner.os }}-python-lint-${{ github.workflow }}-${{ github.job }}-${{ hashFiles('**/pyproject.toml', '**/ruff.toml', '.mise.toml', 'mise.lock') }}
608
+ # Primary saves remain job-specific; another job's Ruff cache is a
609
+ # useful warm fallback because Ruff validates entries by file state.
610
+ restore-keys: |
611
+ ${{ runner.os }}-python-lint-${{ github.workflow }}-
612
+ - name: Cache Turborepo
613
+ if: >-
614
+ inputs.install == 'true' &&
615
+ inputs.install-javascript == 'true' &&
616
+ inputs.cache-turbo != 'false' &&
617
+ steps.profile.outputs.turbo == 'true' &&
618
+ steps.profile.outputs.turbo_task == 'true' &&
619
+ hashFiles('**/turbo.json', '**/turbo.jsonc') != '' &&
620
+ (inputs.cache-turbo == 'true' || env.TURBO_TOKEN == '' || env.TURBO_TEAM == '')
621
+ uses: ./.github/actions/cache
622
+ with:
623
+ save: ${{ inputs.cache-save }}
624
+ path: |
625
+ .turbo/cache
626
+ **/.turbo/cache
627
+ key: ${{ runner.os }}-turbo-${{ github.workflow }}-${{ github.job }}-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/turbo.json', '**/turbo.jsonc', '.mise.toml', 'mise.lock') }}
628
+ restore-keys: |
629
+ ${{ runner.os }}-turbo-
630
+ - name: Configure remote Turbo
631
+ if: >-
632
+ inputs.install == 'true' &&
633
+ inputs.cache-turbo != 'true' &&
634
+ env.TURBO_TOKEN != '' &&
635
+ env.TURBO_TEAM != '' &&
636
+ steps.profile.outputs.turbo_task == 'true' &&
637
+ steps.profile.outputs.turbo == 'true'
638
+ shell: bash
639
+ run: echo 'REPO_FOUNDRY_TURBO_REMOTE_ONLY=true' >> "$GITHUB_ENV"
640
+ - name: Cache Rust packages
641
+ id: rust-packages
642
+ if: (inputs.mise-scope == 'auto' || inputs.mise-scope == 'all' || inputs.mise-scope == 'rust') && inputs.install-rust == 'true' && steps.profile.outputs.rust == 'true' && hashFiles('**/Cargo.toml', '**/Cargo.lock') != ''
643
+ uses: ./.github/actions/cache
644
+ with:
645
+ save: ${{ inputs.cache-save }}
646
+ path: |
647
+ ~/.cargo/registry
648
+ ~/.cargo/git
649
+ key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.toml', '**/Cargo.lock') }}
650
+ restore-keys: |
651
+ ${{ runner.os }}-rust-
652
+ - name: Cache Rust advisory database
653
+ if: inputs.install == 'false' && (inputs.mise-scope == 'auto' || inputs.mise-scope == 'all' || inputs.mise-scope == 'rust') && inputs.install-rust == 'true' && steps.profile.outputs.rust == 'true' && hashFiles('**/Cargo.toml', '**/Cargo.lock') != ''
654
+ uses: ./.github/actions/cache
655
+ with:
656
+ save: ${{ inputs.cache-save }}
657
+ path: ~/.cargo/advisory-db
658
+ # The advisory database is independent of project dependencies. Keep
659
+ # it warm for Security without invalidating it on every lockfile edit.
660
+ key: ${{ runner.os }}-rust-advisory-${{ hashFiles('.github/scripts/security.sh') }}
661
+ restore-keys: |
662
+ ${{ runner.os }}-rust-advisory-
663
+ - name: Cache Rust builds
664
+ if: inputs.install == 'true' && inputs.install-rust == 'true' && inputs.cache-build == 'true' && steps.profile.outputs.rust == 'true' && hashFiles('**/Cargo.toml', '**/Cargo.lock') != ''
665
+ uses: ./.github/actions/cache
666
+ with:
667
+ save: ${{ inputs.cache-save }}
668
+ # Keep reusable dependency/build-script artifacts for the Build job;
669
+ # exclude target/incremental and release output, which are large and
670
+ # provide little value on a fresh hosted runner.
671
+ path: |
672
+ target/.rustc_info.json
673
+ target/debug/deps
674
+ target/debug/build
675
+ target/debug/.fingerprint
676
+ target/debug/*.d
677
+ key: ${{ runner.os }}-rust-build-v2-${{ github.workflow }}-${{ github.job }}-${{ hashFiles('**/Cargo.toml', '**/Cargo.lock', '.mise.toml', 'mise.lock') }}
678
+ # Cargo fingerprints validate restored artifacts; keep job-specific
679
+ # primary saves but reuse a prior workflow's dependency artifacts.
680
+ restore-keys: |
681
+ ${{ runner.os }}-rust-build-v2-
682
+ - name: Setup
683
+ if: ${{ steps.profile.outputs.mise_required == 'true' }}
684
+ uses: jdx/mise-action@v4
685
+ with:
686
+ cache: true
687
+ install_args: ${{ steps.profile.outputs.mise_tools }}
688
+ - name: Use offline Cargo cache
689
+ if: inputs.install == 'true' && inputs.install-rust == 'true' && steps.rust-packages.outputs.cache-hit == 'true' && hashFiles('**/Cargo.lock') != ''
690
+ shell: bash
691
+ run: echo 'CARGO_NET_OFFLINE=true' >> "$GITHUB_ENV"
692
+ - name: Install
693
+ if: inputs.install == 'true'
694
+ shell: bash
695
+ env:
696
+ REPO_FOUNDRY_JAVASCRIPT_CACHE_HIT: ${{ steps.javascript-dependencies.outputs.cache-hit == 'true' || steps.javascript-bun-dependencies.outputs.restored == 'true' }}
697
+ REPO_FOUNDRY_PYTHON_CACHE_HIT: ${{ steps.python-environment.outputs.cache-hit }}
698
+ REPO_FOUNDRY_RUST_CACHE_HIT: ${{ steps.rust-packages.outputs.cache-hit }}
699
+ REPO_FOUNDRY_INSTALL_JAVASCRIPT: ${{ inputs.install-javascript }}
700
+ REPO_FOUNDRY_INSTALL_PYTHON: ${{ inputs.install-python }}
701
+ REPO_FOUNDRY_INSTALL_RUST: ${{ inputs.install-rust }}
702
+ REPO_FOUNDRY_TASK: ${{ inputs.task }}
703
+ run: bash .github/scripts/ci.sh install
704
+ - name: Measure Bun dependencies
705
+ id: javascript-bun-cache
706
+ if: >-
707
+ inputs.cache-save == 'true' &&
708
+ inputs.install == 'true' &&
709
+ inputs.install-javascript == 'true' &&
710
+ inputs.cache-installed != 'false' &&
711
+ steps.profile.outputs.javascript_package_manager == 'bun' &&
712
+ steps.profile.outputs.javascript_dependencies == 'true' &&
713
+ steps.javascript-bun-dependencies.outputs.restored != 'true' &&
714
+ !cancelled()
715
+ shell: bash
716
+ run: |
717
+ total_kib="$(find . -path './.git' -prune -o -type d -name node_modules -print0 |
718
+ xargs -0 -r du -sk |
719
+ awk '{ total += $1 } END { print total + 0 }')"
720
+ # Small Bun installs benefit from reuse; large monorepos are faster
721
+ # with the package cache and a clean linker pass.
722
+ if [ "$total_kib" -le 262144 ]; then
723
+ echo 'cacheable=true' >> "$GITHUB_OUTPUT"
724
+ else
725
+ echo "Skipping Bun dependency cache (${total_kib} KiB > 256 MiB)"
726
+ echo 'cacheable=false' >> "$GITHUB_OUTPUT"
727
+ fi
728
+ - name: Save Bun dependencies
729
+ if: >-
730
+ inputs.cache-save == 'true' &&
731
+ steps.javascript-bun-cache.outputs.cacheable == 'true' &&
732
+ !cancelled()
733
+ continue-on-error: true
734
+ uses: actions/cache/save@v5
735
+ with:
736
+ path: |
737
+ **/node_modules
738
+ key: ${{ runner.os }}-javascript-installed-v4-bun-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/.npmrc', '.github/template.yml', '.mise.toml', 'mise.lock') }}
739
+ - name: Save installed JavaScript dependencies
740
+ if: >-
741
+ inputs.cache-save == 'true' &&
742
+ inputs.install == 'true' &&
743
+ inputs.install-javascript == 'true' &&
744
+ steps.javascript-dependencies.outputs.cache-hit != 'true' &&
745
+ steps.profile.outputs.javascript_dependencies == 'true' &&
746
+ (steps.profile.outputs.javascript_package_manager == 'pnpm' ||
747
+ steps.profile.outputs.javascript_package_manager == 'yarn') &&
748
+ !cancelled()
749
+ continue-on-error: true
750
+ uses: actions/cache/save@v5
751
+ with:
752
+ path: |
753
+ **/node_modules
754
+ key: ${{ runner.os }}-javascript-installed-v3-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/.npmrc', '**/.yarnrc*', '**/.pnpmfile.cjs', '.github/template.yml', '.mise.toml', 'mise.lock') }}
755
+ - name: Save JavaScript packages
756
+ if: >-
757
+ inputs.cache-save == 'true' &&
758
+ inputs.install == 'true' &&
759
+ inputs.install-javascript == 'true' &&
760
+ steps.javascript-packages.outputs.cache-hit != 'true' &&
761
+ steps.profile.outputs.javascript_packages_cache == 'true' &&
762
+ steps.profile.outputs.javascript == 'true' &&
763
+ hashFiles('**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json') != '' &&
764
+ steps.javascript-dependencies.outputs.cache-hit != 'true' &&
765
+ steps.javascript-bun-dependencies.outputs.restored != 'true' &&
766
+ !cancelled()
767
+ continue-on-error: true
768
+ uses: actions/cache/save@v5
769
+ with:
770
+ path: |
771
+ ~/.bun/install/cache
772
+ ~/.cache/node/corepack
773
+ ~/.npm
774
+ ~/.cache/yarn
775
+ ~/.local/share/pnpm/store
776
+ key: ${{ runner.os }}-javascript-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json') }}