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,636 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ source .github/scripts/changed-files.sh
5
+
6
+ if [ -d .venv/bin ]; then export PATH="$PWD/.venv/bin:$PATH"; fi
7
+
8
+ # Build one tracked-file inventory for the detectors below. These functions are
9
+ # called repeatedly by parallel workflow jobs, so avoid starting Git for each
10
+ # language check.
11
+ tracked_files="$(git ls-files)"
12
+
13
+ has_script() {
14
+ [ -f package.json ] && node -e 'const p=require("./package.json"); process.exit(p.scripts?.[process.argv[1]] ? 0 : 1)' "$1"
15
+ }
16
+
17
+ has_turbo_script() {
18
+ [ -f package.json ] && node -e 'const p=require("./package.json"); const script=p.scripts?.[process.argv[1]] || ""; process.exit(/(^|[\s;&|])turbo(\s|$)/.test(script) ? 0 : 1)' "$1"
19
+ }
20
+
21
+ has_bun_native_coverage() {
22
+ [ -f package.json ] && node -e 'const p=require("./package.json"); process.exit(p.scripts?.["test:coverage"]?.includes("bun test") ? 0 : 1)'
23
+ }
24
+
25
+ has_javascript() {
26
+ printf '%s\n' "$tracked_files" | grep -Eq '(^|/)[^/]+\.(js|jsx|mjs|cjs|ts|tsx|mts|cts)$'
27
+ }
28
+
29
+ has_python() {
30
+ [ -f pyproject.toml ] || [ -f requirements.txt ] || [ -f requirements-dev.txt ] || \
31
+ printf '%s\n' "$tracked_files" | awk '!/^\.github\// && /(^|\/)[^\/]+\.py$/ { found=1; exit } END { exit !found }'
32
+ }
33
+
34
+ has_python_dependencies() {
35
+ [ -f pyproject.toml ] || [ -f requirements.txt ] || [ -f requirements-dev.txt ] ||
36
+ [ -f Pipfile.lock ] || [ -f poetry.lock ] || [ -f uv.lock ]
37
+ }
38
+
39
+ needs_python_environment() {
40
+ has_python_dependencies || ! command -v ruff >/dev/null 2>&1
41
+ }
42
+
43
+ has_javascript_dependencies() {
44
+ [ -f bun.lock ] || [ -f bun.lockb ] || [ -f pnpm-lock.yaml ] ||
45
+ [ -f yarn.lock ] || [ -f package-lock.json ] || {
46
+ [ -f package.json ] || return 1
47
+ REPO_FOUNDRY_TRACKED_FILES="$tracked_files" node <<'NODE'
48
+ const files = (process.env.REPO_FOUNDRY_TRACKED_FILES || '')
49
+ .split('\n')
50
+ .filter((file) => /(^|\/)package\.json$/.test(file));
51
+ const groups = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'];
52
+ const hasDependencies = files.some((file) => {
53
+ try {
54
+ const packageJson = JSON.parse(require('node:fs').readFileSync(file, 'utf8'));
55
+ return groups.some((group) => packageJson[group] && Object.keys(packageJson[group]).length > 0) ||
56
+ Array.isArray(packageJson.workspaces) ||
57
+ (packageJson.workspaces && typeof packageJson.workspaces === 'object');
58
+ } catch {
59
+ return true;
60
+ }
61
+ });
62
+ process.exit(hasDependencies ? 0 : 1);
63
+ NODE
64
+ }
65
+ }
66
+
67
+ has_graph_project() {
68
+ [ -f package.json ] && node -e 'const p=require("./package.json"); process.exit(p.devDependencies?.["@graphprotocol/graph-cli"] ? 0 : 1)'
69
+ }
70
+
71
+ has_browser_dependencies() {
72
+ if [ -f package.json ] && node <<'NODE'
73
+ const fs = require('node:fs');
74
+ const p = JSON.parse(fs.readFileSync('package.json', 'utf8'));
75
+ const groups = [p.dependencies, p.devDependencies, p.optionalDependencies, p.peerDependencies];
76
+ const names = groups.flatMap((group) => Object.keys(group || {}));
77
+ const scripts = Object.values(p.scripts || {});
78
+ process.exit([...names, ...scripts].some((value) => /playwright|cypress|puppeteer/i.test(value)) ? 0 : 1);
79
+ NODE
80
+ then
81
+ return 0
82
+ fi
83
+
84
+ for manifest in pyproject.toml requirements.txt requirements-dev.txt; do
85
+ if [ -f "$manifest" ] && grep -Eiq 'playwright|cypress|puppeteer' "$manifest"; then
86
+ return 0
87
+ fi
88
+ done
89
+ return 1
90
+ }
91
+
92
+ package_manager() {
93
+ local configured=""
94
+ if [ -f .github/template.yml ]; then
95
+ configured="$(awk -F': ' '/^package_manager:/ {print $2; exit}' .github/template.yml)"
96
+ case "$configured" in
97
+ bun|pnpm|yarn|npm) echo "$configured"; return ;;
98
+ esac
99
+ fi
100
+ if [ -f bun.lock ] || [ -f bun.lockb ]; then echo bun
101
+ elif [ -f pnpm-lock.yaml ]; then echo pnpm
102
+ elif [ -f yarn.lock ]; then echo yarn
103
+ elif [ -f package-lock.json ]; then echo npm
104
+ else echo bun
105
+ fi
106
+ }
107
+
108
+ cargo_run() {
109
+ local command="$1"
110
+ shift
111
+ local -a cargo_args=("$@")
112
+ if [ -f Cargo.lock ]; then
113
+ cargo_args+=(--locked)
114
+ fi
115
+ if [ "${CARGO_NET_OFFLINE:-false}" = true ]; then
116
+ cargo_args+=(--offline)
117
+ cargo "$command" "${cargo_args[@]}"
118
+ else
119
+ cargo "$command" "${cargo_args[@]}"
120
+ fi
121
+ }
122
+
123
+ run_script() {
124
+ if ! has_script "$1"; then echo "Skipping $1 (script not defined)"; return; fi
125
+ if [ "${REPO_FOUNDRY_TURBO_REMOTE_ONLY:-false}" = true ] && has_turbo_script "$1"; then
126
+ case "$(package_manager)" in
127
+ bun) bun run "$1" -- --cache=remote:rw ;;
128
+ pnpm) corepack pnpm run "$1" -- --cache=remote:rw ;;
129
+ yarn) corepack yarn run "$1" -- --cache=remote:rw ;;
130
+ npm) npm run "$1" -- --cache=remote:rw ;;
131
+ esac
132
+ return
133
+ fi
134
+ case "$(package_manager)" in
135
+ bun) bun run "$1" ;;
136
+ pnpm) corepack pnpm run "$1" ;;
137
+ yarn) corepack yarn run "$1" ;;
138
+ npm) npm run "$1" ;;
139
+ esac
140
+ }
141
+
142
+ run_package_tool() {
143
+ case "$(package_manager)" in
144
+ bun) bunx --no-install "$@" ;;
145
+ pnpm) corepack pnpm exec "$@" ;;
146
+ yarn) corepack yarn exec "$@" ;;
147
+ npm) npx --no-install "$@" ;;
148
+ esac
149
+ }
150
+
151
+ run_bun_coverage() {
152
+ local log_file coverage_line functions lines minimum
153
+ log_file="$(mktemp)"
154
+ if ! run_script test:coverage 2>&1 | tee "$log_file"; then
155
+ rm -f "$log_file"
156
+ return 1
157
+ fi
158
+ coverage_line="$(grep -E '^All files[[:space:]]*\|' "$log_file" | tail -n 1 || true)"
159
+ if [ -z "$coverage_line" ]; then
160
+ echo "Coverage summary not found in test:coverage output" >&2
161
+ rm -f "$log_file"
162
+ return 1
163
+ fi
164
+ read -r functions lines < <(printf '%s\n' "$coverage_line" | awk -F'|' '{for (i = 1; i <= NF; i++) gsub(/[[:space:]]/, "", $i); if (NF >= 5) print $4, $5; else if (NF >= 3) print $2, $3}')
165
+ minimum="${BUN_COVERAGE_MIN:-80}"
166
+ if ! awk -v functions="$functions" -v lines="$lines" -v minimum="$minimum" \
167
+ 'BEGIN { exit !(functions + 0 >= minimum && lines + 0 >= minimum) }'; then
168
+ echo "Coverage below ${minimum}%: functions=${functions}% lines=${lines}%" >&2
169
+ rm -f "$log_file"
170
+ return 1
171
+ fi
172
+ echo "Coverage threshold passed: functions=${functions}% lines=${lines}% (minimum ${minimum}%)"
173
+ rm -f "$log_file"
174
+ }
175
+
176
+ python_coverage_args() {
177
+ if [ -f pyproject.toml ]; then
178
+ python - <<'PY'
179
+ import tomllib
180
+ from pathlib import Path
181
+
182
+ config = tomllib.loads(Path("pyproject.toml").read_text())
183
+ for source in config.get("tool", {}).get("coverage", {}).get("run", {}).get("source", []):
184
+ print(f"--cov={source}")
185
+ PY
186
+ fi
187
+ }
188
+
189
+ install_javascript() {
190
+ if [ -n "${REPO_FOUNDRY_TASK:-}" ] && ! task_uses_javascript "$REPO_FOUNDRY_TASK"; then
191
+ echo "Skipping JavaScript dependency install (no $REPO_FOUNDRY_TASK JavaScript suite)"
192
+ return
193
+ fi
194
+ if [ "${REPO_FOUNDRY_INSTALL_JAVASCRIPT:-true}" = true ] && [ -f package.json ] && has_javascript_dependencies &&
195
+ { [ "${REPO_FOUNDRY_JAVASCRIPT_CACHE_HIT:-false}" != true ] || [ ! -d node_modules ]; }; then
196
+ case "$(package_manager)" in
197
+ bun) bun install --frozen-lockfile ;;
198
+ pnpm) corepack pnpm install --frozen-lockfile --prefer-offline ;;
199
+ yarn) corepack yarn install --immutable ;;
200
+ npm) npm ci --prefer-offline --no-audit --fund=false ;;
201
+ esac
202
+ elif [ -f package.json ] && ! has_javascript_dependencies; then
203
+ echo "Skipping JavaScript dependency install (no dependencies found)"
204
+ elif [ -f package.json ]; then
205
+ echo "Using cached JavaScript dependencies"
206
+ fi
207
+ }
208
+
209
+ install_rust() {
210
+ if [ -n "${REPO_FOUNDRY_TASK:-}" ] && ! task_uses_rust "$REPO_FOUNDRY_TASK"; then
211
+ echo "Skipping Rust dependency install (no $REPO_FOUNDRY_TASK Rust suite)"
212
+ return
213
+ fi
214
+ if [ "${REPO_FOUNDRY_INSTALL_RUST:-true}" = true ] && [ -f Cargo.toml ] && [ "${REPO_FOUNDRY_RUST_CACHE_HIT:-false}" != true ]; then
215
+ cargo_run fetch
216
+ elif [ "${REPO_FOUNDRY_INSTALL_RUST:-true}" = true ] && [ -f Cargo.toml ]; then
217
+ echo "Using cached Rust packages"
218
+ fi
219
+ }
220
+
221
+ install_python() {
222
+ if [ -n "${REPO_FOUNDRY_TASK:-}" ] && ! task_uses_python "$REPO_FOUNDRY_TASK"; then
223
+ echo "Skipping Python dependency install (no $REPO_FOUNDRY_TASK Python suite)"
224
+ return
225
+ fi
226
+ if [ "${REPO_FOUNDRY_INSTALL_PYTHON:-true}" = true ] && has_python &&
227
+ needs_python_environment &&
228
+ { [ "${REPO_FOUNDRY_PYTHON_CACHE_HIT:-false}" != true ] || [ ! -x .venv/bin/python ]; }; then
229
+ install_python_with_uv() {
230
+ if [ -f pyproject.toml ] && [ -f uv.lock ]; then
231
+ uv sync --frozen --python "$(command -v python)"
232
+ return
233
+ fi
234
+ uv venv --python "$(command -v python)" .venv
235
+ local -a python_packages=()
236
+ command -v ruff >/dev/null 2>&1 || python_packages+=(ruff)
237
+ if [ -f requirements.txt ]; then python_packages+=(-r requirements.txt); fi
238
+ if [ -f requirements-dev.txt ]; then python_packages+=(-r requirements-dev.txt); fi
239
+ if [ "${#python_packages[@]}" -gt 0 ]; then
240
+ uv pip install --python .venv/bin/python "${python_packages[@]}"
241
+ fi
242
+ }
243
+ if command -v uv >/dev/null 2>&1 && install_python_with_uv; then
244
+ echo "Installed Python dependencies with uv"
245
+ else
246
+ python -m venv .venv
247
+ local -a python_packages=()
248
+ command -v ruff >/dev/null 2>&1 || python_packages+=(ruff)
249
+ if [ -f requirements.txt ]; then python_packages+=(-r requirements.txt); fi
250
+ if [ -f requirements-dev.txt ]; then python_packages+=(-r requirements-dev.txt); fi
251
+ if [ "${#python_packages[@]}" -gt 0 ]; then
252
+ .venv/bin/python -m pip install --disable-pip-version-check "${python_packages[@]}"
253
+ fi
254
+ fi
255
+ elif [ "${REPO_FOUNDRY_INSTALL_PYTHON:-true}" = true ] && has_python && needs_python_environment; then
256
+ echo "Using cached Python environment"
257
+ elif [ "${REPO_FOUNDRY_INSTALL_PYTHON:-true}" = true ] && has_python; then
258
+ echo "Using shared Python tools; project environment not required"
259
+ fi
260
+ }
261
+
262
+ task_uses_javascript() {
263
+ local task="$1"
264
+ case "$task" in
265
+ unit) has_script test:unit || has_script test:coverage || { has_script test && ! has_script test:integration; } ;;
266
+ integration) has_script test:integration ;;
267
+ e2e) has_script test:e2e || has_script e2e ;;
268
+ smoke) has_script test:smoke || has_script smoke ;;
269
+ *) return 0 ;;
270
+ esac
271
+ }
272
+
273
+ task_uses_rust() {
274
+ local task="$1"
275
+ [ -f Cargo.toml ] || return 1
276
+ case "$task" in
277
+ unit) has_rust_target lib || has_rust_target bin ;;
278
+ integration) [ -d tests ] ;;
279
+ e2e|smoke) return 1 ;;
280
+ *) return 0 ;;
281
+ esac
282
+ }
283
+
284
+ task_uses_python() {
285
+ local task="$1"
286
+ has_python_tests() {
287
+ local directory="$1"
288
+ [ -d "$directory" ] && find "$directory" -type f -name '*.py' -print -quit | grep -q .
289
+ }
290
+ has_python || return 1
291
+ case "$task" in
292
+ unit) has_python_tests tests/unit || { [ ! -d tests/unit ] && [ ! -d tests/integration ] && has_python_tests tests; } ;;
293
+ integration) has_python_tests tests/integration ;;
294
+ e2e) has_python_tests tests/e2e ;;
295
+ smoke) has_python_tests tests/smoke ;;
296
+ *) return 0 ;;
297
+ esac
298
+ }
299
+
300
+ task_profile() {
301
+ local task="${1:?missing test task}"
302
+ local output applicable
303
+ case "$task" in
304
+ unit|integration|e2e|smoke) ;;
305
+ *) echo "unknown test task: $task" >&2; return 2 ;;
306
+ esac
307
+ output="$(should_run "$task")"
308
+ applicable="$(printf '%s\n' "$output" | awk -F= '$1 == "applicable" {print $2; exit}')"
309
+ printf 'applicable=%s\n' "${applicable:-false}"
310
+ if [ "$task" = e2e ]; then
311
+ printf 'browser=%s\n' "$(printf '%s\n' "$output" | awk -F= '$1 == "browser" {print $2; exit}' | awk 'NF {print; found=1} END {if (!found) print "false"}')"
312
+ fi
313
+ if [ "${applicable:-false}" = true ]; then
314
+ task_uses_javascript "$task" && printf 'javascript=true\n' || printf 'javascript=false\n'
315
+ task_uses_python "$task" && printf 'python=true\n' || printf 'python=false\n'
316
+ task_uses_rust "$task" && printf 'rust=true\n' || printf 'rust=false\n'
317
+ else
318
+ printf '%s\n' 'javascript=false' 'python=false' 'rust=false'
319
+ fi
320
+ }
321
+
322
+ install() {
323
+ run_parallel install_javascript install_rust install_python
324
+ }
325
+
326
+ rust_component() {
327
+ local component="$1"
328
+ if command -v rustup >/dev/null 2>&1; then
329
+ local toolchain="${RUSTUP_TOOLCHAIN:-$(rustup show active-toolchain | awk '{print $1}')}"
330
+ if rustup component list --toolchain "$toolchain" 2>/dev/null |
331
+ grep -q "^${component}.*(installed)"; then
332
+ return
333
+ fi
334
+ rustup component add --toolchain "$toolchain" "$component" >/dev/null
335
+ fi
336
+ }
337
+
338
+ has_rust_target() {
339
+ local kind="$1"
340
+ cargo metadata --no-deps --format-version 1 |
341
+ jq -e --arg kind "$kind" 'any(.packages[].targets[]; (.kind | index($kind)) != null)' >/dev/null
342
+ }
343
+
344
+ run_parallel() {
345
+ local status=0 pid task
346
+ local -a pids=()
347
+ for task in "$@"; do
348
+ "$task" &
349
+ pids+=("$!")
350
+ done
351
+ for pid in "${pids[@]}"; do
352
+ if ! wait "$pid"; then status=1; fi
353
+ done
354
+ return "$status"
355
+ }
356
+
357
+ format_javascript() {
358
+ if has_script format:check; then run_script format:check
359
+ elif has_javascript; then run_package_tool prettier --check . --cache --cache-strategy content
360
+ else echo "Skipping JavaScript/TypeScript formatting (no formatter script or source found)"; fi
361
+ }
362
+
363
+ format_rust() {
364
+ if [ -f Cargo.toml ]; then rust_component rustfmt; cargo fmt --check; fi
365
+ }
366
+
367
+ format_python() {
368
+ if has_python && command -v ruff >/dev/null 2>&1; then ruff format --check .; fi
369
+ }
370
+
371
+ format() {
372
+ run_parallel format_javascript format_rust format_python
373
+ }
374
+
375
+ lint_javascript() {
376
+ if has_script lint; then run_script lint
377
+ elif has_javascript; then run_package_tool eslint --cache --cache-strategy content .
378
+ else echo "Skipping JavaScript/TypeScript lint (no lint script or source found)"; fi
379
+ }
380
+
381
+ lint_rust() {
382
+ if [ -f Cargo.toml ]; then
383
+ rust_component clippy
384
+ clippy_args=(--all-targets)
385
+ if [ -f Cargo.lock ]; then clippy_args+=(--locked); fi
386
+ if [ "${CARGO_NET_OFFLINE:-false}" = true ]; then clippy_args+=(--offline); fi
387
+ cargo clippy "${clippy_args[@]}" -- -D warnings
388
+ fi
389
+ }
390
+
391
+ lint_python() {
392
+ if has_python && command -v ruff >/dev/null 2>&1; then ruff check .; fi
393
+ }
394
+
395
+ lint() {
396
+ run_parallel lint_javascript lint_rust lint_python
397
+ }
398
+
399
+ typecheck_javascript() {
400
+ if has_graph_project; then
401
+ echo "Skipping TypeScript type-check (Graph AssemblyScript project uses graph build/codegen)"
402
+ elif has_script type-check; then run_script type-check
403
+ elif has_script typecheck; then run_script typecheck
404
+ else echo "Skipping type-check (script not defined)"; fi
405
+ }
406
+
407
+ typecheck_rust() {
408
+ if [ -f Cargo.toml ]; then cargo_run check; fi
409
+ }
410
+
411
+ typecheck_python() {
412
+ if has_python && command -v python >/dev/null 2>&1; then
413
+ py_dirs=()
414
+ for dir in tests src scripts; do [ -d "$dir" ] && py_dirs+=("$dir"); done
415
+ if [ "${#py_dirs[@]}" -gt 0 ]; then python -m compileall -q "${py_dirs[@]}"; fi
416
+ fi
417
+ }
418
+
419
+ type_check() {
420
+ run_parallel typecheck_javascript typecheck_rust typecheck_python
421
+ }
422
+
423
+ build_javascript() {
424
+ run_script build
425
+ }
426
+
427
+ build_rust() {
428
+ if [ -f Cargo.toml ]; then cargo_run build --all-targets --all-features; fi
429
+ }
430
+
431
+ build() {
432
+ # Some frameworks validate session secrets while statically collecting pages.
433
+ # Keep CI builds deterministic without weakening runtime/deployment validation.
434
+ if [ "${CI:-}" = true ] && [ -z "${NEXTAUTH_SECRET:-}" ]; then
435
+ export NEXTAUTH_SECRET="ci-only-build-secret-not-for-runtime-0123456789"
436
+ fi
437
+ run_parallel build_javascript build_rust
438
+ }
439
+
440
+ unit_javascript() {
441
+ # Bun repositories should expose test scripts backed by Bun's native runner.
442
+ # Specialized repositories may keep their native runner (for example Matchstick or Hardhat).
443
+ if has_script test:unit; then
444
+ run_script test:unit
445
+ elif has_script test:coverage; then
446
+ if [ "$(package_manager)" = bun ] && has_bun_native_coverage; then
447
+ run_bun_coverage
448
+ else
449
+ run_script test:coverage
450
+ fi
451
+ elif has_script test && ! has_script test:integration; then
452
+ run_script test
453
+ else
454
+ echo "Skipping JavaScript/TypeScript unit tests (script not defined)"
455
+ fi
456
+ }
457
+
458
+ unit_rust() {
459
+ if [ -f Cargo.toml ]; then
460
+ if has_rust_target lib; then cargo_run test --lib --all-features
461
+ elif has_rust_target bin; then cargo_run test --bins --all-features
462
+ else echo "Skipping Rust unit tests (no library or binary target)"; fi
463
+ fi
464
+ }
465
+
466
+ unit_python() {
467
+ if [ -d tests/unit ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
468
+ coverage_args=()
469
+ while IFS= read -r arg; do [ -n "$arg" ] && coverage_args+=("$arg"); done < <(python_coverage_args)
470
+ [ "${#coverage_args[@]}" -gt 0 ] || coverage_args=(--cov)
471
+ env -u MISE_GITHUB_TOKEN -u MISE_TRUSTED_CONFIG_PATHS -u MISE_YES -u MISE_LOG_LEVEL -u PYTHONHOME PYTHONPATH="$PWD/.github/scripts" python -m pytest -q tests/unit "${coverage_args[@]}" --cov-report=term-missing --cov-fail-under="${PYTHON_COVERAGE_MIN:-80}"
472
+ elif [ -d tests ] && [ ! -d tests/integration ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
473
+ coverage_args=()
474
+ while IFS= read -r arg; do [ -n "$arg" ] && coverage_args+=("$arg"); done < <(python_coverage_args)
475
+ [ "${#coverage_args[@]}" -gt 0 ] || coverage_args=(--cov)
476
+ env -u MISE_GITHUB_TOKEN -u MISE_TRUSTED_CONFIG_PATHS -u MISE_YES -u MISE_LOG_LEVEL -u PYTHONHOME PYTHONPATH="$PWD/.github/scripts" python -m pytest -q tests "${coverage_args[@]}" --cov-report=term-missing --cov-fail-under="${PYTHON_COVERAGE_MIN:-80}"
477
+ else
478
+ echo "Skipping Python unit tests (no unit suite detected)"
479
+ fi
480
+ }
481
+
482
+ unit() {
483
+ run_parallel unit_javascript unit_rust unit_python
484
+ }
485
+
486
+ integration_javascript() {
487
+ if has_script test:integration; then
488
+ run_script test:integration
489
+ else
490
+ echo "Skipping JavaScript/TypeScript integration tests (script not defined)"
491
+ fi
492
+ }
493
+
494
+ integration_rust() {
495
+ if [ -f Cargo.toml ] && [ -d tests ]; then cargo_run test --tests --all-features; fi
496
+ }
497
+
498
+ integration_python() {
499
+ if [ -d tests/integration ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
500
+ python -m pytest -q tests/integration
501
+ else
502
+ echo "Skipping Python integration tests (tests/integration not found)"
503
+ fi
504
+ }
505
+
506
+ integration() {
507
+ run_parallel integration_javascript integration_rust integration_python
508
+ }
509
+
510
+ e2e_javascript() {
511
+ if has_script test:e2e; then
512
+ run_script test:e2e
513
+ elif has_script e2e; then
514
+ run_script e2e
515
+ else
516
+ echo "Skipping JavaScript/TypeScript E2E tests (script not defined)"
517
+ fi
518
+ }
519
+
520
+ e2e_python() {
521
+ if [ -d tests/e2e ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
522
+ python -m pytest -q tests/e2e
523
+ else
524
+ echo "Skipping Python E2E tests (tests/e2e not found)"
525
+ fi
526
+ }
527
+
528
+ e2e() {
529
+ run_parallel e2e_javascript e2e_python
530
+ }
531
+
532
+ smoke_javascript() {
533
+ if has_script test:smoke; then
534
+ run_script test:smoke
535
+ elif has_script smoke; then
536
+ run_script smoke
537
+ else
538
+ echo "Skipping JavaScript/TypeScript smoke tests (script not defined)"
539
+ fi
540
+ }
541
+
542
+ smoke_python() {
543
+ if [ -d tests/smoke ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
544
+ python -m pytest -q tests/smoke
545
+ else
546
+ echo "Skipping Python smoke tests (tests/smoke not found)"
547
+ fi
548
+ }
549
+
550
+ smoke() {
551
+ run_parallel smoke_javascript smoke_python
552
+ }
553
+
554
+ should_run() {
555
+ local task="$1"
556
+ if repo_foundry_governance_only; then
557
+ printf '%s\n' 'applicable=false'
558
+ [ "$task" = e2e ] && printf '%s\n' 'browser=false'
559
+ return 0
560
+ fi
561
+ case "$task" in
562
+ format)
563
+ if has_script format:check || has_javascript || [ -f Cargo.toml ] || has_python; then
564
+ printf '%s\n' 'applicable=true'
565
+ else
566
+ printf '%s\n' 'applicable=false'
567
+ fi
568
+ ;;
569
+ lint)
570
+ if has_script lint || has_javascript || [ -f Cargo.toml ] || has_python; then
571
+ printf '%s\n' 'applicable=true'
572
+ else
573
+ printf '%s\n' 'applicable=false'
574
+ fi
575
+ ;;
576
+ type_check)
577
+ if has_script type-check || has_script typecheck || [ -f Cargo.toml ] || has_python; then
578
+ printf '%s\n' 'applicable=true'
579
+ else
580
+ printf '%s\n' 'applicable=false'
581
+ fi
582
+ ;;
583
+ build)
584
+ if has_script build || [ -f Cargo.toml ]; then
585
+ printf '%s\n' 'applicable=true'
586
+ else
587
+ printf '%s\n' 'applicable=false'
588
+ fi
589
+ ;;
590
+ unit)
591
+ if has_script test:unit || has_script test:coverage || has_script test || [ -f Cargo.toml ] ||
592
+ [ -d tests ] || [ -d test ]; then
593
+ printf '%s\n' 'applicable=true'
594
+ else
595
+ printf '%s\n' 'applicable=false'
596
+ fi
597
+ ;;
598
+ integration)
599
+ if has_script test:integration || [ -f Cargo.toml ] && [ -d tests ] || [ -d tests/integration ]; then
600
+ printf '%s\n' 'applicable=true'
601
+ else
602
+ printf '%s\n' 'applicable=false'
603
+ fi
604
+ ;;
605
+ e2e)
606
+ if has_script test:e2e || has_script e2e || [ -d tests/e2e ]; then
607
+ printf '%s\n' 'applicable=true'
608
+ if has_browser_dependencies; then
609
+ printf '%s\n' 'browser=true'
610
+ else
611
+ printf '%s\n' 'browser=false'
612
+ fi
613
+ else
614
+ printf '%s\n' 'applicable=false'
615
+ printf '%s\n' 'browser=false'
616
+ fi
617
+ ;;
618
+ smoke)
619
+ if has_script test:smoke || has_script smoke || [ -d tests/smoke ]; then
620
+ printf '%s\n' 'applicable=true'
621
+ else
622
+ printf '%s\n' 'applicable=false'
623
+ fi
624
+ ;;
625
+ *)
626
+ echo "unknown CI task: $task" >&2
627
+ return 2
628
+ ;;
629
+ esac
630
+ }
631
+
632
+ case "${1:-}" in
633
+ install|format|lint|type_check|build|unit|integration|e2e|smoke|should_run) "$1" "${2:-}" ;;
634
+ task_profile) task_profile "${2:-}" ;;
635
+ *) echo "usage: $0 {install|format|lint|type_check|build|unit|integration|e2e|smoke|should_run|task_profile}" >&2; exit 2 ;;
636
+ esac