code-foundry 0.26.0 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.githooks/pre-commit +3 -4
- package/.github/CONTRIBUTING.md +10 -13
- package/.github/actions/setup/action.yml +68 -3
- package/.github/code-foundry.yml +6 -1
- package/.github/workflows/ci.yml +29 -24
- package/.github/workflows/ci_self-ci.yml +1 -1
- package/.github/workflows/codeql.yml +23 -29
- package/.github/workflows/codeql_self-ci.yml +1 -1
- package/.github/workflows/draft-pr.yml +2 -2
- package/.github/workflows/release-pr.yml +4 -4
- package/.github/workflows/release.yml +17 -29
- package/.github/workflows/security.yml +24 -19
- package/.github/workflows/security_self-ci.yml +1 -1
- package/.github/workflows/test.yml +20 -20
- package/.github/workflows/test_self-ci.yml +1 -1
- package/AGENTS.md +13 -10
- package/CHANGELOG.md +29 -0
- package/README.md +16 -6
- package/docs/CONFIGURATION.md +10 -1
- package/docs/INITIALIZATION.md +6 -5
- package/docs/RELEASES.md +5 -0
- package/docs/WORKFLOWS.md +12 -5
- package/package.json +7 -3
- package/src/cli.mjs +29 -25
- package/src/commands/doctor.mjs +70 -0
- package/src/commands/sync.mjs +224 -0
- package/src/lib/config.mjs +38 -0
- package/src/lib/profile.mjs +114 -0
- package/src/runtime.mjs +321 -0
- package/.github/scripts/bootstrap.sh +0 -21
- package/.github/scripts/changed-files.sh +0 -96
- package/.github/scripts/ci.sh +0 -641
- package/.github/scripts/codeql-languages.sh +0 -91
- package/.github/scripts/doctor.sh +0 -98
- package/.github/scripts/format-fast-path.sh +0 -80
- package/.github/scripts/init-repo.sh +0 -268
- package/.github/scripts/pre-commit.sh +0 -67
- package/.github/scripts/profile.sh +0 -186
- package/.github/scripts/security.sh +0 -214
- package/.github/scripts/sitecustomize.py +0 -17
- package/.github/scripts/sync-codeowners.sh +0 -76
- package/.github/scripts/sync-protection.sh +0 -138
- package/.github/scripts/sync-template.sh +0 -748
- package/.github/scripts/turbo-cache-probe.sh +0 -102
package/.github/scripts/ci.sh
DELETED
|
@@ -1,641 +0,0 @@
|
|
|
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 [ -x .github/scripts/profile.sh ]; then
|
|
95
|
-
configured="$(bash .github/scripts/profile.sh get package_manager 2>/dev/null || true)"
|
|
96
|
-
else
|
|
97
|
-
config_file=.github/code-foundry.yml
|
|
98
|
-
if [ -f "$config_file" ]; then
|
|
99
|
-
configured="$(awk -F': ' '/^package_manager:/ {print $2; exit}' "$config_file")"
|
|
100
|
-
fi
|
|
101
|
-
fi
|
|
102
|
-
case "$configured" in
|
|
103
|
-
bun|pnpm|yarn|npm) echo "$configured"; return ;;
|
|
104
|
-
esac
|
|
105
|
-
if [ -f bun.lock ] || [ -f bun.lockb ]; then echo bun
|
|
106
|
-
elif [ -f pnpm-lock.yaml ]; then echo pnpm
|
|
107
|
-
elif [ -f yarn.lock ]; then echo yarn
|
|
108
|
-
elif [ -f package-lock.json ]; then echo npm
|
|
109
|
-
else echo bun
|
|
110
|
-
fi
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
cargo_run() {
|
|
114
|
-
local command="$1"
|
|
115
|
-
shift
|
|
116
|
-
local -a cargo_args=("$@")
|
|
117
|
-
if [ -f Cargo.lock ]; then
|
|
118
|
-
cargo_args+=(--locked)
|
|
119
|
-
fi
|
|
120
|
-
if [ "${CARGO_NET_OFFLINE:-false}" = true ]; then
|
|
121
|
-
cargo_args+=(--offline)
|
|
122
|
-
cargo "$command" "${cargo_args[@]}"
|
|
123
|
-
else
|
|
124
|
-
cargo "$command" "${cargo_args[@]}"
|
|
125
|
-
fi
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
run_script() {
|
|
129
|
-
if ! has_script "$1"; then echo "Skipping $1 (script not defined)"; return; fi
|
|
130
|
-
if [ "${REPO_FOUNDRY_TURBO_REMOTE_ONLY:-false}" = true ] && has_turbo_script "$1"; then
|
|
131
|
-
case "$(package_manager)" in
|
|
132
|
-
bun) bun run "$1" -- --cache=remote:rw ;;
|
|
133
|
-
pnpm) corepack pnpm run "$1" -- --cache=remote:rw ;;
|
|
134
|
-
yarn) corepack yarn run "$1" -- --cache=remote:rw ;;
|
|
135
|
-
npm) npm run "$1" -- --cache=remote:rw ;;
|
|
136
|
-
esac
|
|
137
|
-
return
|
|
138
|
-
fi
|
|
139
|
-
case "$(package_manager)" in
|
|
140
|
-
bun) bun run "$1" ;;
|
|
141
|
-
pnpm) corepack pnpm run "$1" ;;
|
|
142
|
-
yarn) corepack yarn run "$1" ;;
|
|
143
|
-
npm) npm run "$1" ;;
|
|
144
|
-
esac
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
run_package_tool() {
|
|
148
|
-
case "$(package_manager)" in
|
|
149
|
-
bun) bunx --no-install "$@" ;;
|
|
150
|
-
pnpm) corepack pnpm exec "$@" ;;
|
|
151
|
-
yarn) corepack yarn exec "$@" ;;
|
|
152
|
-
npm) npx --no-install "$@" ;;
|
|
153
|
-
esac
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
run_bun_coverage() {
|
|
157
|
-
local log_file coverage_line functions lines minimum
|
|
158
|
-
log_file="$(mktemp)"
|
|
159
|
-
if ! run_script test:coverage 2>&1 | tee "$log_file"; then
|
|
160
|
-
rm -f "$log_file"
|
|
161
|
-
return 1
|
|
162
|
-
fi
|
|
163
|
-
coverage_line="$(grep -E '^All files[[:space:]]*\|' "$log_file" | tail -n 1 || true)"
|
|
164
|
-
if [ -z "$coverage_line" ]; then
|
|
165
|
-
echo "Coverage summary not found in test:coverage output" >&2
|
|
166
|
-
rm -f "$log_file"
|
|
167
|
-
return 1
|
|
168
|
-
fi
|
|
169
|
-
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}')
|
|
170
|
-
minimum="${BUN_COVERAGE_MIN:-80}"
|
|
171
|
-
if ! awk -v functions="$functions" -v lines="$lines" -v minimum="$minimum" \
|
|
172
|
-
'BEGIN { exit !(functions + 0 >= minimum && lines + 0 >= minimum) }'; then
|
|
173
|
-
echo "Coverage below ${minimum}%: functions=${functions}% lines=${lines}%" >&2
|
|
174
|
-
rm -f "$log_file"
|
|
175
|
-
return 1
|
|
176
|
-
fi
|
|
177
|
-
echo "Coverage threshold passed: functions=${functions}% lines=${lines}% (minimum ${minimum}%)"
|
|
178
|
-
rm -f "$log_file"
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
python_coverage_args() {
|
|
182
|
-
if [ -f pyproject.toml ]; then
|
|
183
|
-
python - <<'PY'
|
|
184
|
-
import tomllib
|
|
185
|
-
from pathlib import Path
|
|
186
|
-
|
|
187
|
-
config = tomllib.loads(Path("pyproject.toml").read_text())
|
|
188
|
-
for source in config.get("tool", {}).get("coverage", {}).get("run", {}).get("source", []):
|
|
189
|
-
print(f"--cov={source}")
|
|
190
|
-
PY
|
|
191
|
-
fi
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
install_javascript() {
|
|
195
|
-
if [ -n "${REPO_FOUNDRY_TASK:-}" ] && ! task_uses_javascript "$REPO_FOUNDRY_TASK"; then
|
|
196
|
-
echo "Skipping JavaScript dependency install (no $REPO_FOUNDRY_TASK JavaScript suite)"
|
|
197
|
-
return
|
|
198
|
-
fi
|
|
199
|
-
if [ "${REPO_FOUNDRY_INSTALL_JAVASCRIPT:-true}" = true ] && [ -f package.json ] && has_javascript_dependencies &&
|
|
200
|
-
{ [ "${REPO_FOUNDRY_JAVASCRIPT_CACHE_HIT:-false}" != true ] || [ ! -d node_modules ]; }; then
|
|
201
|
-
case "$(package_manager)" in
|
|
202
|
-
bun) bun install --frozen-lockfile ;;
|
|
203
|
-
pnpm) corepack pnpm install --frozen-lockfile --prefer-offline ;;
|
|
204
|
-
yarn) corepack yarn install --immutable ;;
|
|
205
|
-
npm) npm ci --prefer-offline --no-audit --fund=false ;;
|
|
206
|
-
esac
|
|
207
|
-
elif [ -f package.json ] && ! has_javascript_dependencies; then
|
|
208
|
-
echo "Skipping JavaScript dependency install (no dependencies found)"
|
|
209
|
-
elif [ -f package.json ]; then
|
|
210
|
-
echo "Using cached JavaScript dependencies"
|
|
211
|
-
fi
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
install_rust() {
|
|
215
|
-
if [ -n "${REPO_FOUNDRY_TASK:-}" ] && ! task_uses_rust "$REPO_FOUNDRY_TASK"; then
|
|
216
|
-
echo "Skipping Rust dependency install (no $REPO_FOUNDRY_TASK Rust suite)"
|
|
217
|
-
return
|
|
218
|
-
fi
|
|
219
|
-
if [ "${REPO_FOUNDRY_INSTALL_RUST:-true}" = true ] && [ -f Cargo.toml ] && [ "${REPO_FOUNDRY_RUST_CACHE_HIT:-false}" != true ]; then
|
|
220
|
-
cargo_run fetch
|
|
221
|
-
elif [ "${REPO_FOUNDRY_INSTALL_RUST:-true}" = true ] && [ -f Cargo.toml ]; then
|
|
222
|
-
echo "Using cached Rust packages"
|
|
223
|
-
fi
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
install_python() {
|
|
227
|
-
if [ -n "${REPO_FOUNDRY_TASK:-}" ] && ! task_uses_python "$REPO_FOUNDRY_TASK"; then
|
|
228
|
-
echo "Skipping Python dependency install (no $REPO_FOUNDRY_TASK Python suite)"
|
|
229
|
-
return
|
|
230
|
-
fi
|
|
231
|
-
if [ "${REPO_FOUNDRY_INSTALL_PYTHON:-true}" = true ] && has_python &&
|
|
232
|
-
needs_python_environment &&
|
|
233
|
-
{ [ "${REPO_FOUNDRY_PYTHON_CACHE_HIT:-false}" != true ] || [ ! -x .venv/bin/python ]; }; then
|
|
234
|
-
install_python_with_uv() {
|
|
235
|
-
if [ -f pyproject.toml ] && [ -f uv.lock ]; then
|
|
236
|
-
uv sync --frozen --python "$(command -v python)"
|
|
237
|
-
return
|
|
238
|
-
fi
|
|
239
|
-
uv venv --python "$(command -v python)" .venv
|
|
240
|
-
local -a python_packages=()
|
|
241
|
-
command -v ruff >/dev/null 2>&1 || python_packages+=(ruff)
|
|
242
|
-
if [ -f requirements.txt ]; then python_packages+=(-r requirements.txt); fi
|
|
243
|
-
if [ -f requirements-dev.txt ]; then python_packages+=(-r requirements-dev.txt); fi
|
|
244
|
-
if [ "${#python_packages[@]}" -gt 0 ]; then
|
|
245
|
-
uv pip install --python .venv/bin/python "${python_packages[@]}"
|
|
246
|
-
fi
|
|
247
|
-
}
|
|
248
|
-
if command -v uv >/dev/null 2>&1 && install_python_with_uv; then
|
|
249
|
-
echo "Installed Python dependencies with uv"
|
|
250
|
-
else
|
|
251
|
-
python -m venv .venv
|
|
252
|
-
local -a python_packages=()
|
|
253
|
-
command -v ruff >/dev/null 2>&1 || python_packages+=(ruff)
|
|
254
|
-
if [ -f requirements.txt ]; then python_packages+=(-r requirements.txt); fi
|
|
255
|
-
if [ -f requirements-dev.txt ]; then python_packages+=(-r requirements-dev.txt); fi
|
|
256
|
-
if [ "${#python_packages[@]}" -gt 0 ]; then
|
|
257
|
-
.venv/bin/python -m pip install --disable-pip-version-check "${python_packages[@]}"
|
|
258
|
-
fi
|
|
259
|
-
fi
|
|
260
|
-
elif [ "${REPO_FOUNDRY_INSTALL_PYTHON:-true}" = true ] && has_python && needs_python_environment; then
|
|
261
|
-
echo "Using cached Python environment"
|
|
262
|
-
elif [ "${REPO_FOUNDRY_INSTALL_PYTHON:-true}" = true ] && has_python; then
|
|
263
|
-
echo "Using shared Python tools; project environment not required"
|
|
264
|
-
fi
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
task_uses_javascript() {
|
|
268
|
-
local task="$1"
|
|
269
|
-
case "$task" in
|
|
270
|
-
unit) has_script test:unit || has_script test:coverage || { has_script test && ! has_script test:integration; } ;;
|
|
271
|
-
integration) has_script test:integration ;;
|
|
272
|
-
e2e) has_script test:e2e || has_script e2e ;;
|
|
273
|
-
smoke) has_script test:smoke || has_script smoke ;;
|
|
274
|
-
*) return 0 ;;
|
|
275
|
-
esac
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
task_uses_rust() {
|
|
279
|
-
local task="$1"
|
|
280
|
-
[ -f Cargo.toml ] || return 1
|
|
281
|
-
case "$task" in
|
|
282
|
-
unit) has_rust_target lib || has_rust_target bin ;;
|
|
283
|
-
integration) [ -d tests ] ;;
|
|
284
|
-
e2e|smoke) return 1 ;;
|
|
285
|
-
*) return 0 ;;
|
|
286
|
-
esac
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
task_uses_python() {
|
|
290
|
-
local task="$1"
|
|
291
|
-
has_python_tests() {
|
|
292
|
-
local directory="$1"
|
|
293
|
-
[ -d "$directory" ] && find "$directory" -type f -name '*.py' -print -quit | grep -q .
|
|
294
|
-
}
|
|
295
|
-
has_python || return 1
|
|
296
|
-
case "$task" in
|
|
297
|
-
unit) has_python_tests tests/unit || { [ ! -d tests/unit ] && [ ! -d tests/integration ] && has_python_tests tests; } ;;
|
|
298
|
-
integration) has_python_tests tests/integration ;;
|
|
299
|
-
e2e) has_python_tests tests/e2e ;;
|
|
300
|
-
smoke) has_python_tests tests/smoke ;;
|
|
301
|
-
*) return 0 ;;
|
|
302
|
-
esac
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
task_profile() {
|
|
306
|
-
local task="${1:?missing test task}"
|
|
307
|
-
local output applicable
|
|
308
|
-
case "$task" in
|
|
309
|
-
unit|integration|e2e|smoke) ;;
|
|
310
|
-
*) echo "unknown test task: $task" >&2; return 2 ;;
|
|
311
|
-
esac
|
|
312
|
-
output="$(should_run "$task")"
|
|
313
|
-
applicable="$(printf '%s\n' "$output" | awk -F= '$1 == "applicable" {print $2; exit}')"
|
|
314
|
-
printf 'applicable=%s\n' "${applicable:-false}"
|
|
315
|
-
if [ "$task" = e2e ]; then
|
|
316
|
-
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"}')"
|
|
317
|
-
fi
|
|
318
|
-
if [ "${applicable:-false}" = true ]; then
|
|
319
|
-
task_uses_javascript "$task" && printf 'javascript=true\n' || printf 'javascript=false\n'
|
|
320
|
-
task_uses_python "$task" && printf 'python=true\n' || printf 'python=false\n'
|
|
321
|
-
task_uses_rust "$task" && printf 'rust=true\n' || printf 'rust=false\n'
|
|
322
|
-
else
|
|
323
|
-
printf '%s\n' 'javascript=false' 'python=false' 'rust=false'
|
|
324
|
-
fi
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
install() {
|
|
328
|
-
run_parallel install_javascript install_rust install_python
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
rust_component() {
|
|
332
|
-
local component="$1"
|
|
333
|
-
if command -v rustup >/dev/null 2>&1; then
|
|
334
|
-
local toolchain="${RUSTUP_TOOLCHAIN:-$(rustup show active-toolchain | awk '{print $1}')}"
|
|
335
|
-
if rustup component list --toolchain "$toolchain" 2>/dev/null |
|
|
336
|
-
grep -q "^${component}.*(installed)"; then
|
|
337
|
-
return
|
|
338
|
-
fi
|
|
339
|
-
rustup component add --toolchain "$toolchain" "$component" >/dev/null
|
|
340
|
-
fi
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
has_rust_target() {
|
|
344
|
-
local kind="$1"
|
|
345
|
-
cargo metadata --no-deps --format-version 1 |
|
|
346
|
-
jq -e --arg kind "$kind" 'any(.packages[].targets[]; (.kind | index($kind)) != null)' >/dev/null
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
run_parallel() {
|
|
350
|
-
local status=0 pid task
|
|
351
|
-
local -a pids=()
|
|
352
|
-
for task in "$@"; do
|
|
353
|
-
"$task" &
|
|
354
|
-
pids+=("$!")
|
|
355
|
-
done
|
|
356
|
-
for pid in "${pids[@]}"; do
|
|
357
|
-
if ! wait "$pid"; then status=1; fi
|
|
358
|
-
done
|
|
359
|
-
return "$status"
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
format_javascript() {
|
|
363
|
-
if has_script format:check; then run_script format:check
|
|
364
|
-
elif has_javascript; then run_package_tool prettier --check . --cache --cache-strategy content
|
|
365
|
-
else echo "Skipping JavaScript/TypeScript formatting (no formatter script or source found)"; fi
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
format_rust() {
|
|
369
|
-
if [ -f Cargo.toml ]; then rust_component rustfmt; cargo fmt --check; fi
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
format_python() {
|
|
373
|
-
if has_python && command -v ruff >/dev/null 2>&1; then ruff format --check .; fi
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
format() {
|
|
377
|
-
run_parallel format_javascript format_rust format_python
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
lint_javascript() {
|
|
381
|
-
if has_script lint; then run_script lint
|
|
382
|
-
elif has_javascript; then run_package_tool eslint --cache --cache-strategy content .
|
|
383
|
-
else echo "Skipping JavaScript/TypeScript lint (no lint script or source found)"; fi
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
lint_rust() {
|
|
387
|
-
if [ -f Cargo.toml ]; then
|
|
388
|
-
rust_component clippy
|
|
389
|
-
clippy_args=(--all-targets)
|
|
390
|
-
if [ -f Cargo.lock ]; then clippy_args+=(--locked); fi
|
|
391
|
-
if [ "${CARGO_NET_OFFLINE:-false}" = true ]; then clippy_args+=(--offline); fi
|
|
392
|
-
cargo clippy "${clippy_args[@]}" -- -D warnings
|
|
393
|
-
fi
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
lint_python() {
|
|
397
|
-
if has_python && command -v ruff >/dev/null 2>&1; then ruff check .; fi
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
lint() {
|
|
401
|
-
run_parallel lint_javascript lint_rust lint_python
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
typecheck_javascript() {
|
|
405
|
-
if has_graph_project; then
|
|
406
|
-
echo "Skipping TypeScript type-check (Graph AssemblyScript project uses graph build/codegen)"
|
|
407
|
-
elif has_script type-check; then run_script type-check
|
|
408
|
-
elif has_script typecheck; then run_script typecheck
|
|
409
|
-
else echo "Skipping type-check (script not defined)"; fi
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
typecheck_rust() {
|
|
413
|
-
if [ -f Cargo.toml ]; then cargo_run check; fi
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
typecheck_python() {
|
|
417
|
-
if has_python && command -v python >/dev/null 2>&1; then
|
|
418
|
-
py_dirs=()
|
|
419
|
-
for dir in tests src scripts; do [ -d "$dir" ] && py_dirs+=("$dir"); done
|
|
420
|
-
if [ "${#py_dirs[@]}" -gt 0 ]; then python -m compileall -q "${py_dirs[@]}"; fi
|
|
421
|
-
fi
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
type_check() {
|
|
425
|
-
run_parallel typecheck_javascript typecheck_rust typecheck_python
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
build_javascript() {
|
|
429
|
-
run_script build
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
build_rust() {
|
|
433
|
-
if [ -f Cargo.toml ]; then cargo_run build --all-targets --all-features; fi
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
build() {
|
|
437
|
-
# Some frameworks validate session secrets while statically collecting pages.
|
|
438
|
-
# Keep CI builds deterministic without weakening runtime/deployment validation.
|
|
439
|
-
if [ "${CI:-}" = true ] && [ -z "${NEXTAUTH_SECRET:-}" ]; then
|
|
440
|
-
export NEXTAUTH_SECRET="ci-only-build-secret-not-for-runtime-0123456789"
|
|
441
|
-
fi
|
|
442
|
-
run_parallel build_javascript build_rust
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
unit_javascript() {
|
|
446
|
-
# Bun repositories should expose test scripts backed by Bun's native runner.
|
|
447
|
-
# Specialized repositories may keep their native runner (for example Matchstick or Hardhat).
|
|
448
|
-
if has_script test:unit; then
|
|
449
|
-
run_script test:unit
|
|
450
|
-
elif has_script test:coverage; then
|
|
451
|
-
if [ "$(package_manager)" = bun ] && has_bun_native_coverage; then
|
|
452
|
-
run_bun_coverage
|
|
453
|
-
else
|
|
454
|
-
run_script test:coverage
|
|
455
|
-
fi
|
|
456
|
-
elif has_script test && ! has_script test:integration; then
|
|
457
|
-
run_script test
|
|
458
|
-
else
|
|
459
|
-
echo "Skipping JavaScript/TypeScript unit tests (script not defined)"
|
|
460
|
-
fi
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
unit_rust() {
|
|
464
|
-
if [ -f Cargo.toml ]; then
|
|
465
|
-
if has_rust_target lib; then cargo_run test --lib --all-features
|
|
466
|
-
elif has_rust_target bin; then cargo_run test --bins --all-features
|
|
467
|
-
else echo "Skipping Rust unit tests (no library or binary target)"; fi
|
|
468
|
-
fi
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
unit_python() {
|
|
472
|
-
if [ -d tests/unit ] && 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/unit "${coverage_args[@]}" --cov-report=term-missing --cov-fail-under="${PYTHON_COVERAGE_MIN:-80}"
|
|
477
|
-
elif [ -d tests ] && [ ! -d tests/integration ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
|
|
478
|
-
coverage_args=()
|
|
479
|
-
while IFS= read -r arg; do [ -n "$arg" ] && coverage_args+=("$arg"); done < <(python_coverage_args)
|
|
480
|
-
[ "${#coverage_args[@]}" -gt 0 ] || coverage_args=(--cov)
|
|
481
|
-
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}"
|
|
482
|
-
else
|
|
483
|
-
echo "Skipping Python unit tests (no unit suite detected)"
|
|
484
|
-
fi
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
unit() {
|
|
488
|
-
run_parallel unit_javascript unit_rust unit_python
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
integration_javascript() {
|
|
492
|
-
if has_script test:integration; then
|
|
493
|
-
run_script test:integration
|
|
494
|
-
else
|
|
495
|
-
echo "Skipping JavaScript/TypeScript integration tests (script not defined)"
|
|
496
|
-
fi
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
integration_rust() {
|
|
500
|
-
if [ -f Cargo.toml ] && [ -d tests ]; then cargo_run test --tests --all-features; fi
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
integration_python() {
|
|
504
|
-
if [ -d tests/integration ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
|
|
505
|
-
python -m pytest -q tests/integration
|
|
506
|
-
else
|
|
507
|
-
echo "Skipping Python integration tests (tests/integration not found)"
|
|
508
|
-
fi
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
integration() {
|
|
512
|
-
run_parallel integration_javascript integration_rust integration_python
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
e2e_javascript() {
|
|
516
|
-
if has_script test:e2e; then
|
|
517
|
-
run_script test:e2e
|
|
518
|
-
elif has_script e2e; then
|
|
519
|
-
run_script e2e
|
|
520
|
-
else
|
|
521
|
-
echo "Skipping JavaScript/TypeScript E2E tests (script not defined)"
|
|
522
|
-
fi
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
e2e_python() {
|
|
526
|
-
if [ -d tests/e2e ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
|
|
527
|
-
python -m pytest -q tests/e2e
|
|
528
|
-
else
|
|
529
|
-
echo "Skipping Python E2E tests (tests/e2e not found)"
|
|
530
|
-
fi
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
e2e() {
|
|
534
|
-
run_parallel e2e_javascript e2e_python
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
smoke_javascript() {
|
|
538
|
-
if has_script test:smoke; then
|
|
539
|
-
run_script test:smoke
|
|
540
|
-
elif has_script smoke; then
|
|
541
|
-
run_script smoke
|
|
542
|
-
else
|
|
543
|
-
echo "Skipping JavaScript/TypeScript smoke tests (script not defined)"
|
|
544
|
-
fi
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
smoke_python() {
|
|
548
|
-
if [ -d tests/smoke ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
|
|
549
|
-
python -m pytest -q tests/smoke
|
|
550
|
-
else
|
|
551
|
-
echo "Skipping Python smoke tests (tests/smoke not found)"
|
|
552
|
-
fi
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
smoke() {
|
|
556
|
-
run_parallel smoke_javascript smoke_python
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
should_run() {
|
|
560
|
-
local task="$1"
|
|
561
|
-
if repo_foundry_governance_only; then
|
|
562
|
-
printf '%s\n' 'applicable=false'
|
|
563
|
-
[ "$task" = e2e ] && printf '%s\n' 'browser=false'
|
|
564
|
-
return 0
|
|
565
|
-
fi
|
|
566
|
-
case "$task" in
|
|
567
|
-
format)
|
|
568
|
-
if has_script format:check || has_javascript || [ -f Cargo.toml ] || has_python; then
|
|
569
|
-
printf '%s\n' 'applicable=true'
|
|
570
|
-
else
|
|
571
|
-
printf '%s\n' 'applicable=false'
|
|
572
|
-
fi
|
|
573
|
-
;;
|
|
574
|
-
lint)
|
|
575
|
-
if has_script lint || has_javascript || [ -f Cargo.toml ] || has_python; then
|
|
576
|
-
printf '%s\n' 'applicable=true'
|
|
577
|
-
else
|
|
578
|
-
printf '%s\n' 'applicable=false'
|
|
579
|
-
fi
|
|
580
|
-
;;
|
|
581
|
-
type_check)
|
|
582
|
-
if has_script type-check || has_script typecheck || [ -f Cargo.toml ] || has_python; then
|
|
583
|
-
printf '%s\n' 'applicable=true'
|
|
584
|
-
else
|
|
585
|
-
printf '%s\n' 'applicable=false'
|
|
586
|
-
fi
|
|
587
|
-
;;
|
|
588
|
-
build)
|
|
589
|
-
if has_script build || [ -f Cargo.toml ]; then
|
|
590
|
-
printf '%s\n' 'applicable=true'
|
|
591
|
-
else
|
|
592
|
-
printf '%s\n' 'applicable=false'
|
|
593
|
-
fi
|
|
594
|
-
;;
|
|
595
|
-
unit)
|
|
596
|
-
if has_script test:unit || has_script test:coverage || has_script test || [ -f Cargo.toml ] ||
|
|
597
|
-
[ -d tests ] || [ -d test ]; then
|
|
598
|
-
printf '%s\n' 'applicable=true'
|
|
599
|
-
else
|
|
600
|
-
printf '%s\n' 'applicable=false'
|
|
601
|
-
fi
|
|
602
|
-
;;
|
|
603
|
-
integration)
|
|
604
|
-
if has_script test:integration || [ -f Cargo.toml ] && [ -d tests ] || [ -d tests/integration ]; then
|
|
605
|
-
printf '%s\n' 'applicable=true'
|
|
606
|
-
else
|
|
607
|
-
printf '%s\n' 'applicable=false'
|
|
608
|
-
fi
|
|
609
|
-
;;
|
|
610
|
-
e2e)
|
|
611
|
-
if has_script test:e2e || has_script e2e || [ -d tests/e2e ]; then
|
|
612
|
-
printf '%s\n' 'applicable=true'
|
|
613
|
-
if has_browser_dependencies; then
|
|
614
|
-
printf '%s\n' 'browser=true'
|
|
615
|
-
else
|
|
616
|
-
printf '%s\n' 'browser=false'
|
|
617
|
-
fi
|
|
618
|
-
else
|
|
619
|
-
printf '%s\n' 'applicable=false'
|
|
620
|
-
printf '%s\n' 'browser=false'
|
|
621
|
-
fi
|
|
622
|
-
;;
|
|
623
|
-
smoke)
|
|
624
|
-
if has_script test:smoke || has_script smoke || [ -d tests/smoke ]; then
|
|
625
|
-
printf '%s\n' 'applicable=true'
|
|
626
|
-
else
|
|
627
|
-
printf '%s\n' 'applicable=false'
|
|
628
|
-
fi
|
|
629
|
-
;;
|
|
630
|
-
*)
|
|
631
|
-
echo "unknown CI task: $task" >&2
|
|
632
|
-
return 2
|
|
633
|
-
;;
|
|
634
|
-
esac
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
case "${1:-}" in
|
|
638
|
-
install|format|lint|type_check|build|unit|integration|e2e|smoke|should_run) "$1" "${2:-}" ;;
|
|
639
|
-
task_profile) task_profile "${2:-}" ;;
|
|
640
|
-
*) echo "usage: $0 {install|format|lint|type_check|build|unit|integration|e2e|smoke|should_run|task_profile}" >&2; exit 2 ;;
|
|
641
|
-
esac
|