code-foundry 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.githooks/pre-commit +20 -5
- package/.github/CODEOWNERS +4 -2
- package/.github/CONTRIBUTING.md +5 -1
- package/.github/actions/cache/action.yml +43 -0
- package/.github/actions/codeql/action.yml +29 -0
- package/.github/actions/setup/action.yml +756 -0
- package/.github/scripts/bootstrap.sh +7 -0
- package/.github/scripts/changed-files.sh +96 -0
- package/.github/scripts/ci.sh +421 -28
- package/.github/scripts/codeql-languages.sh +71 -5
- package/.github/scripts/doctor.sh +1 -1
- package/.github/scripts/format-fast-path.sh +80 -0
- package/.github/scripts/init-repo.sh +39 -11
- package/.github/scripts/profile.sh +179 -0
- package/.github/scripts/security.sh +180 -20
- package/.github/scripts/sync-codeowners.sh +76 -0
- package/.github/scripts/sync-protection.sh +15 -2
- package/.github/scripts/sync-template.sh +222 -15
- package/.github/scripts/turbo-cache-probe.sh +102 -0
- package/.github/template.yml +14 -0
- package/.github/template.yml.example +10 -0
- package/.github/workflows/ci.yml +108 -7
- package/.github/workflows/codeql.yml +126 -17
- package/.github/workflows/draft-pr.yml +4 -7
- package/.github/workflows/release-pr.yml +20 -7
- package/.github/workflows/release.yml +99 -7
- package/.github/workflows/security.yml +201 -6
- package/.github/workflows/test.yml +100 -3
- package/.gitignore +4 -0
- package/.mise.toml +0 -2
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +12 -0
- package/README.md +92 -10
- package/package.json +11 -1
- package/release-please-config.json +16 -0
- package/src/cli.mjs +20 -8
- package/.github/workflows/publish.yml +0 -25
package/.github/scripts/ci.sh
CHANGED
|
@@ -1,30 +1,104 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
+
source .github/scripts/changed-files.sh
|
|
5
|
+
|
|
4
6
|
if [ -d .venv/bin ]; then export PATH="$PWD/.venv/bin:$PATH"; fi
|
|
5
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
|
+
|
|
6
13
|
has_script() {
|
|
7
14
|
[ -f package.json ] && node -e 'const p=require("./package.json"); process.exit(p.scripts?.[process.argv[1]] ? 0 : 1)' "$1"
|
|
8
15
|
}
|
|
9
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
|
+
|
|
10
21
|
has_bun_native_coverage() {
|
|
11
22
|
[ -f package.json ] && node -e 'const p=require("./package.json"); process.exit(p.scripts?.["test:coverage"]?.includes("bun test") ? 0 : 1)'
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
has_javascript() {
|
|
15
|
-
|
|
26
|
+
printf '%s\n' "$tracked_files" | grep -Eq '(^|/)[^/]+\.(js|jsx|mjs|cjs|ts|tsx|mts|cts)$'
|
|
16
27
|
}
|
|
17
28
|
|
|
18
29
|
has_python() {
|
|
19
30
|
[ -f pyproject.toml ] || [ -f requirements.txt ] || [ -f requirements-dev.txt ] || \
|
|
20
|
-
|
|
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
|
+
}
|
|
21
65
|
}
|
|
22
66
|
|
|
23
67
|
has_graph_project() {
|
|
24
68
|
[ -f package.json ] && node -e 'const p=require("./package.json"); process.exit(p.devDependencies?.["@graphprotocol/graph-cli"] ? 0 : 1)'
|
|
25
69
|
}
|
|
26
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
|
+
|
|
27
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
|
+
elif [ -f .github/template.yml ]; then
|
|
97
|
+
configured="$(awk -F': ' '/^package_manager:/ {print $2; exit}' .github/template.yml)"
|
|
98
|
+
fi
|
|
99
|
+
case "$configured" in
|
|
100
|
+
bun|pnpm|yarn|npm) echo "$configured"; return ;;
|
|
101
|
+
esac
|
|
28
102
|
if [ -f bun.lock ] || [ -f bun.lockb ]; then echo bun
|
|
29
103
|
elif [ -f pnpm-lock.yaml ]; then echo pnpm
|
|
30
104
|
elif [ -f yarn.lock ]; then echo yarn
|
|
@@ -33,8 +107,32 @@ package_manager() {
|
|
|
33
107
|
fi
|
|
34
108
|
}
|
|
35
109
|
|
|
110
|
+
cargo_run() {
|
|
111
|
+
local command="$1"
|
|
112
|
+
shift
|
|
113
|
+
local -a cargo_args=("$@")
|
|
114
|
+
if [ -f Cargo.lock ]; then
|
|
115
|
+
cargo_args+=(--locked)
|
|
116
|
+
fi
|
|
117
|
+
if [ "${CARGO_NET_OFFLINE:-false}" = true ]; then
|
|
118
|
+
cargo_args+=(--offline)
|
|
119
|
+
cargo "$command" "${cargo_args[@]}"
|
|
120
|
+
else
|
|
121
|
+
cargo "$command" "${cargo_args[@]}"
|
|
122
|
+
fi
|
|
123
|
+
}
|
|
124
|
+
|
|
36
125
|
run_script() {
|
|
37
126
|
if ! has_script "$1"; then echo "Skipping $1 (script not defined)"; return; fi
|
|
127
|
+
if [ "${REPO_FOUNDRY_TURBO_REMOTE_ONLY:-false}" = true ] && has_turbo_script "$1"; then
|
|
128
|
+
case "$(package_manager)" in
|
|
129
|
+
bun) bun run "$1" -- --cache=remote:rw ;;
|
|
130
|
+
pnpm) corepack pnpm run "$1" -- --cache=remote:rw ;;
|
|
131
|
+
yarn) corepack yarn run "$1" -- --cache=remote:rw ;;
|
|
132
|
+
npm) npm run "$1" -- --cache=remote:rw ;;
|
|
133
|
+
esac
|
|
134
|
+
return
|
|
135
|
+
fi
|
|
38
136
|
case "$(package_manager)" in
|
|
39
137
|
bun) bun run "$1" ;;
|
|
40
138
|
pnpm) corepack pnpm run "$1" ;;
|
|
@@ -90,25 +188,151 @@ PY
|
|
|
90
188
|
fi
|
|
91
189
|
}
|
|
92
190
|
|
|
93
|
-
|
|
94
|
-
if [ -
|
|
191
|
+
install_javascript() {
|
|
192
|
+
if [ -n "${REPO_FOUNDRY_TASK:-}" ] && ! task_uses_javascript "$REPO_FOUNDRY_TASK"; then
|
|
193
|
+
echo "Skipping JavaScript dependency install (no $REPO_FOUNDRY_TASK JavaScript suite)"
|
|
194
|
+
return
|
|
195
|
+
fi
|
|
196
|
+
if [ "${REPO_FOUNDRY_INSTALL_JAVASCRIPT:-true}" = true ] && [ -f package.json ] && has_javascript_dependencies &&
|
|
197
|
+
{ [ "${REPO_FOUNDRY_JAVASCRIPT_CACHE_HIT:-false}" != true ] || [ ! -d node_modules ]; }; then
|
|
95
198
|
case "$(package_manager)" in
|
|
96
199
|
bun) bun install --frozen-lockfile ;;
|
|
97
|
-
pnpm) corepack pnpm install --frozen-lockfile ;;
|
|
200
|
+
pnpm) corepack pnpm install --frozen-lockfile --prefer-offline ;;
|
|
98
201
|
yarn) corepack yarn install --immutable ;;
|
|
99
|
-
npm) npm ci ;;
|
|
202
|
+
npm) npm ci --prefer-offline --no-audit --fund=false ;;
|
|
100
203
|
esac
|
|
204
|
+
elif [ -f package.json ] && ! has_javascript_dependencies; then
|
|
205
|
+
echo "Skipping JavaScript dependency install (no dependencies found)"
|
|
206
|
+
elif [ -f package.json ]; then
|
|
207
|
+
echo "Using cached JavaScript dependencies"
|
|
208
|
+
fi
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
install_rust() {
|
|
212
|
+
if [ -n "${REPO_FOUNDRY_TASK:-}" ] && ! task_uses_rust "$REPO_FOUNDRY_TASK"; then
|
|
213
|
+
echo "Skipping Rust dependency install (no $REPO_FOUNDRY_TASK Rust suite)"
|
|
214
|
+
return
|
|
215
|
+
fi
|
|
216
|
+
if [ "${REPO_FOUNDRY_INSTALL_RUST:-true}" = true ] && [ -f Cargo.toml ] && [ "${REPO_FOUNDRY_RUST_CACHE_HIT:-false}" != true ]; then
|
|
217
|
+
cargo_run fetch
|
|
218
|
+
elif [ "${REPO_FOUNDRY_INSTALL_RUST:-true}" = true ] && [ -f Cargo.toml ]; then
|
|
219
|
+
echo "Using cached Rust packages"
|
|
101
220
|
fi
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if [ -
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
install_python() {
|
|
224
|
+
if [ -n "${REPO_FOUNDRY_TASK:-}" ] && ! task_uses_python "$REPO_FOUNDRY_TASK"; then
|
|
225
|
+
echo "Skipping Python dependency install (no $REPO_FOUNDRY_TASK Python suite)"
|
|
226
|
+
return
|
|
227
|
+
fi
|
|
228
|
+
if [ "${REPO_FOUNDRY_INSTALL_PYTHON:-true}" = true ] && has_python &&
|
|
229
|
+
needs_python_environment &&
|
|
230
|
+
{ [ "${REPO_FOUNDRY_PYTHON_CACHE_HIT:-false}" != true ] || [ ! -x .venv/bin/python ]; }; then
|
|
231
|
+
install_python_with_uv() {
|
|
232
|
+
if [ -f pyproject.toml ] && [ -f uv.lock ]; then
|
|
233
|
+
uv sync --frozen --python "$(command -v python)"
|
|
234
|
+
return
|
|
235
|
+
fi
|
|
236
|
+
uv venv --python "$(command -v python)" .venv
|
|
237
|
+
local -a python_packages=()
|
|
238
|
+
command -v ruff >/dev/null 2>&1 || python_packages+=(ruff)
|
|
239
|
+
if [ -f requirements.txt ]; then python_packages+=(-r requirements.txt); fi
|
|
240
|
+
if [ -f requirements-dev.txt ]; then python_packages+=(-r requirements-dev.txt); fi
|
|
241
|
+
if [ "${#python_packages[@]}" -gt 0 ]; then
|
|
242
|
+
uv pip install --python .venv/bin/python "${python_packages[@]}"
|
|
243
|
+
fi
|
|
244
|
+
}
|
|
245
|
+
if command -v uv >/dev/null 2>&1 && install_python_with_uv; then
|
|
246
|
+
echo "Installed Python dependencies with uv"
|
|
247
|
+
else
|
|
248
|
+
python -m venv .venv
|
|
249
|
+
local -a python_packages=()
|
|
250
|
+
command -v ruff >/dev/null 2>&1 || python_packages+=(ruff)
|
|
251
|
+
if [ -f requirements.txt ]; then python_packages+=(-r requirements.txt); fi
|
|
252
|
+
if [ -f requirements-dev.txt ]; then python_packages+=(-r requirements-dev.txt); fi
|
|
253
|
+
if [ "${#python_packages[@]}" -gt 0 ]; then
|
|
254
|
+
.venv/bin/python -m pip install --disable-pip-version-check "${python_packages[@]}"
|
|
255
|
+
fi
|
|
256
|
+
fi
|
|
257
|
+
elif [ "${REPO_FOUNDRY_INSTALL_PYTHON:-true}" = true ] && has_python && needs_python_environment; then
|
|
258
|
+
echo "Using cached Python environment"
|
|
259
|
+
elif [ "${REPO_FOUNDRY_INSTALL_PYTHON:-true}" = true ] && has_python; then
|
|
260
|
+
echo "Using shared Python tools; project environment not required"
|
|
261
|
+
fi
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
task_uses_javascript() {
|
|
265
|
+
local task="$1"
|
|
266
|
+
case "$task" in
|
|
267
|
+
unit) has_script test:unit || has_script test:coverage || { has_script test && ! has_script test:integration; } ;;
|
|
268
|
+
integration) has_script test:integration ;;
|
|
269
|
+
e2e) has_script test:e2e || has_script e2e ;;
|
|
270
|
+
smoke) has_script test:smoke || has_script smoke ;;
|
|
271
|
+
*) return 0 ;;
|
|
272
|
+
esac
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
task_uses_rust() {
|
|
276
|
+
local task="$1"
|
|
277
|
+
[ -f Cargo.toml ] || return 1
|
|
278
|
+
case "$task" in
|
|
279
|
+
unit) has_rust_target lib || has_rust_target bin ;;
|
|
280
|
+
integration) [ -d tests ] ;;
|
|
281
|
+
e2e|smoke) return 1 ;;
|
|
282
|
+
*) return 0 ;;
|
|
283
|
+
esac
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
task_uses_python() {
|
|
287
|
+
local task="$1"
|
|
288
|
+
has_python_tests() {
|
|
289
|
+
local directory="$1"
|
|
290
|
+
[ -d "$directory" ] && find "$directory" -type f -name '*.py' -print -quit | grep -q .
|
|
291
|
+
}
|
|
292
|
+
has_python || return 1
|
|
293
|
+
case "$task" in
|
|
294
|
+
unit) has_python_tests tests/unit || { [ ! -d tests/unit ] && [ ! -d tests/integration ] && has_python_tests tests; } ;;
|
|
295
|
+
integration) has_python_tests tests/integration ;;
|
|
296
|
+
e2e) has_python_tests tests/e2e ;;
|
|
297
|
+
smoke) has_python_tests tests/smoke ;;
|
|
298
|
+
*) return 0 ;;
|
|
299
|
+
esac
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
task_profile() {
|
|
303
|
+
local task="${1:?missing test task}"
|
|
304
|
+
local output applicable
|
|
305
|
+
case "$task" in
|
|
306
|
+
unit|integration|e2e|smoke) ;;
|
|
307
|
+
*) echo "unknown test task: $task" >&2; return 2 ;;
|
|
308
|
+
esac
|
|
309
|
+
output="$(should_run "$task")"
|
|
310
|
+
applicable="$(printf '%s\n' "$output" | awk -F= '$1 == "applicable" {print $2; exit}')"
|
|
311
|
+
printf 'applicable=%s\n' "${applicable:-false}"
|
|
312
|
+
if [ "$task" = e2e ]; then
|
|
313
|
+
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"}')"
|
|
314
|
+
fi
|
|
315
|
+
if [ "${applicable:-false}" = true ]; then
|
|
316
|
+
task_uses_javascript "$task" && printf 'javascript=true\n' || printf 'javascript=false\n'
|
|
317
|
+
task_uses_python "$task" && printf 'python=true\n' || printf 'python=false\n'
|
|
318
|
+
task_uses_rust "$task" && printf 'rust=true\n' || printf 'rust=false\n'
|
|
319
|
+
else
|
|
320
|
+
printf '%s\n' 'javascript=false' 'python=false' 'rust=false'
|
|
321
|
+
fi
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
install() {
|
|
325
|
+
run_parallel install_javascript install_rust install_python
|
|
106
326
|
}
|
|
107
327
|
|
|
108
328
|
rust_component() {
|
|
109
329
|
local component="$1"
|
|
110
330
|
if command -v rustup >/dev/null 2>&1; then
|
|
111
331
|
local toolchain="${RUSTUP_TOOLCHAIN:-$(rustup show active-toolchain | awk '{print $1}')}"
|
|
332
|
+
if rustup component list --toolchain "$toolchain" 2>/dev/null |
|
|
333
|
+
grep -q "^${component}.*(installed)"; then
|
|
334
|
+
return
|
|
335
|
+
fi
|
|
112
336
|
rustup component add --toolchain "$toolchain" "$component" >/dev/null
|
|
113
337
|
fi
|
|
114
338
|
}
|
|
@@ -119,29 +343,74 @@ has_rust_target() {
|
|
|
119
343
|
jq -e --arg kind "$kind" 'any(.packages[].targets[]; (.kind | index($kind)) != null)' >/dev/null
|
|
120
344
|
}
|
|
121
345
|
|
|
122
|
-
|
|
346
|
+
run_parallel() {
|
|
347
|
+
local status=0 pid task
|
|
348
|
+
local -a pids=()
|
|
349
|
+
for task in "$@"; do
|
|
350
|
+
"$task" &
|
|
351
|
+
pids+=("$!")
|
|
352
|
+
done
|
|
353
|
+
for pid in "${pids[@]}"; do
|
|
354
|
+
if ! wait "$pid"; then status=1; fi
|
|
355
|
+
done
|
|
356
|
+
return "$status"
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
format_javascript() {
|
|
123
360
|
if has_script format:check; then run_script format:check
|
|
124
|
-
elif has_javascript; then run_package_tool prettier --check .
|
|
361
|
+
elif has_javascript; then run_package_tool prettier --check . --cache --cache-strategy content
|
|
125
362
|
else echo "Skipping JavaScript/TypeScript formatting (no formatter script or source found)"; fi
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
format_rust() {
|
|
126
366
|
if [ -f Cargo.toml ]; then rust_component rustfmt; cargo fmt --check; fi
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
format_python() {
|
|
127
370
|
if has_python && command -v ruff >/dev/null 2>&1; then ruff format --check .; fi
|
|
128
371
|
}
|
|
129
372
|
|
|
130
|
-
|
|
373
|
+
format() {
|
|
374
|
+
run_parallel format_javascript format_rust format_python
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
lint_javascript() {
|
|
131
378
|
if has_script lint; then run_script lint
|
|
132
|
-
elif has_javascript; then run_package_tool eslint .
|
|
379
|
+
elif has_javascript; then run_package_tool eslint --cache --cache-strategy content .
|
|
133
380
|
else echo "Skipping JavaScript/TypeScript lint (no lint script or source found)"; fi
|
|
134
|
-
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
lint_rust() {
|
|
384
|
+
if [ -f Cargo.toml ]; then
|
|
385
|
+
rust_component clippy
|
|
386
|
+
clippy_args=(--all-targets)
|
|
387
|
+
if [ -f Cargo.lock ]; then clippy_args+=(--locked); fi
|
|
388
|
+
if [ "${CARGO_NET_OFFLINE:-false}" = true ]; then clippy_args+=(--offline); fi
|
|
389
|
+
cargo clippy "${clippy_args[@]}" -- -D warnings
|
|
390
|
+
fi
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
lint_python() {
|
|
135
394
|
if has_python && command -v ruff >/dev/null 2>&1; then ruff check .; fi
|
|
136
395
|
}
|
|
137
396
|
|
|
138
|
-
|
|
397
|
+
lint() {
|
|
398
|
+
run_parallel lint_javascript lint_rust lint_python
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
typecheck_javascript() {
|
|
139
402
|
if has_graph_project; then
|
|
140
403
|
echo "Skipping TypeScript type-check (Graph AssemblyScript project uses graph build/codegen)"
|
|
141
404
|
elif has_script type-check; then run_script type-check
|
|
142
405
|
elif has_script typecheck; then run_script typecheck
|
|
143
406
|
else echo "Skipping type-check (script not defined)"; fi
|
|
144
|
-
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
typecheck_rust() {
|
|
410
|
+
if [ -f Cargo.toml ]; then cargo_run check; fi
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
typecheck_python() {
|
|
145
414
|
if has_python && command -v python >/dev/null 2>&1; then
|
|
146
415
|
py_dirs=()
|
|
147
416
|
for dir in tests src scripts; do [ -d "$dir" ] && py_dirs+=("$dir"); done
|
|
@@ -149,17 +418,28 @@ type_check() {
|
|
|
149
418
|
fi
|
|
150
419
|
}
|
|
151
420
|
|
|
421
|
+
type_check() {
|
|
422
|
+
run_parallel typecheck_javascript typecheck_rust typecheck_python
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
build_javascript() {
|
|
426
|
+
run_script build
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
build_rust() {
|
|
430
|
+
if [ -f Cargo.toml ]; then cargo_run build --all-targets --all-features; fi
|
|
431
|
+
}
|
|
432
|
+
|
|
152
433
|
build() {
|
|
153
434
|
# Some frameworks validate session secrets while statically collecting pages.
|
|
154
435
|
# Keep CI builds deterministic without weakening runtime/deployment validation.
|
|
155
436
|
if [ "${CI:-}" = true ] && [ -z "${NEXTAUTH_SECRET:-}" ]; then
|
|
156
437
|
export NEXTAUTH_SECRET="ci-only-build-secret-not-for-runtime-0123456789"
|
|
157
438
|
fi
|
|
158
|
-
|
|
159
|
-
if [ -f Cargo.toml ]; then cargo build --all-targets --all-features; fi
|
|
439
|
+
run_parallel build_javascript build_rust
|
|
160
440
|
}
|
|
161
441
|
|
|
162
|
-
|
|
442
|
+
unit_javascript() {
|
|
163
443
|
# Bun repositories should expose test scripts backed by Bun's native runner.
|
|
164
444
|
# Specialized repositories may keep their native runner (for example Matchstick or Hardhat).
|
|
165
445
|
if has_script test:unit; then
|
|
@@ -175,11 +455,17 @@ unit() {
|
|
|
175
455
|
else
|
|
176
456
|
echo "Skipping JavaScript/TypeScript unit tests (script not defined)"
|
|
177
457
|
fi
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
unit_rust() {
|
|
178
461
|
if [ -f Cargo.toml ]; then
|
|
179
|
-
if has_rust_target lib; then
|
|
180
|
-
elif has_rust_target bin; then
|
|
462
|
+
if has_rust_target lib; then cargo_run test --lib --all-features
|
|
463
|
+
elif has_rust_target bin; then cargo_run test --bins --all-features
|
|
181
464
|
else echo "Skipping Rust unit tests (no library or binary target)"; fi
|
|
182
465
|
fi
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
unit_python() {
|
|
183
469
|
if [ -d tests/unit ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
|
|
184
470
|
coverage_args=()
|
|
185
471
|
while IFS= read -r arg; do [ -n "$arg" ] && coverage_args+=("$arg"); done < <(python_coverage_args)
|
|
@@ -195,13 +481,23 @@ unit() {
|
|
|
195
481
|
fi
|
|
196
482
|
}
|
|
197
483
|
|
|
198
|
-
|
|
484
|
+
unit() {
|
|
485
|
+
run_parallel unit_javascript unit_rust unit_python
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
integration_javascript() {
|
|
199
489
|
if has_script test:integration; then
|
|
200
490
|
run_script test:integration
|
|
201
491
|
else
|
|
202
492
|
echo "Skipping JavaScript/TypeScript integration tests (script not defined)"
|
|
203
493
|
fi
|
|
204
|
-
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
integration_rust() {
|
|
497
|
+
if [ -f Cargo.toml ] && [ -d tests ]; then cargo_run test --tests --all-features; fi
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
integration_python() {
|
|
205
501
|
if [ -d tests/integration ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
|
|
206
502
|
python -m pytest -q tests/integration
|
|
207
503
|
else
|
|
@@ -209,7 +505,11 @@ integration() {
|
|
|
209
505
|
fi
|
|
210
506
|
}
|
|
211
507
|
|
|
212
|
-
|
|
508
|
+
integration() {
|
|
509
|
+
run_parallel integration_javascript integration_rust integration_python
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
e2e_javascript() {
|
|
213
513
|
if has_script test:e2e; then
|
|
214
514
|
run_script test:e2e
|
|
215
515
|
elif has_script e2e; then
|
|
@@ -217,6 +517,9 @@ e2e() {
|
|
|
217
517
|
else
|
|
218
518
|
echo "Skipping JavaScript/TypeScript E2E tests (script not defined)"
|
|
219
519
|
fi
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
e2e_python() {
|
|
220
523
|
if [ -d tests/e2e ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
|
|
221
524
|
python -m pytest -q tests/e2e
|
|
222
525
|
else
|
|
@@ -224,7 +527,11 @@ e2e() {
|
|
|
224
527
|
fi
|
|
225
528
|
}
|
|
226
529
|
|
|
227
|
-
|
|
530
|
+
e2e() {
|
|
531
|
+
run_parallel e2e_javascript e2e_python
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
smoke_javascript() {
|
|
228
535
|
if has_script test:smoke; then
|
|
229
536
|
run_script test:smoke
|
|
230
537
|
elif has_script smoke; then
|
|
@@ -232,6 +539,9 @@ smoke() {
|
|
|
232
539
|
else
|
|
233
540
|
echo "Skipping JavaScript/TypeScript smoke tests (script not defined)"
|
|
234
541
|
fi
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
smoke_python() {
|
|
235
545
|
if [ -d tests/smoke ] && python -c 'import importlib.util; raise SystemExit(importlib.util.find_spec("pytest") is None)' 2>/dev/null; then
|
|
236
546
|
python -m pytest -q tests/smoke
|
|
237
547
|
else
|
|
@@ -239,7 +549,90 @@ smoke() {
|
|
|
239
549
|
fi
|
|
240
550
|
}
|
|
241
551
|
|
|
552
|
+
smoke() {
|
|
553
|
+
run_parallel smoke_javascript smoke_python
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
should_run() {
|
|
557
|
+
local task="$1"
|
|
558
|
+
if repo_foundry_governance_only; then
|
|
559
|
+
printf '%s\n' 'applicable=false'
|
|
560
|
+
[ "$task" = e2e ] && printf '%s\n' 'browser=false'
|
|
561
|
+
return 0
|
|
562
|
+
fi
|
|
563
|
+
case "$task" in
|
|
564
|
+
format)
|
|
565
|
+
if has_script format:check || has_javascript || [ -f Cargo.toml ] || has_python; then
|
|
566
|
+
printf '%s\n' 'applicable=true'
|
|
567
|
+
else
|
|
568
|
+
printf '%s\n' 'applicable=false'
|
|
569
|
+
fi
|
|
570
|
+
;;
|
|
571
|
+
lint)
|
|
572
|
+
if has_script lint || has_javascript || [ -f Cargo.toml ] || has_python; then
|
|
573
|
+
printf '%s\n' 'applicable=true'
|
|
574
|
+
else
|
|
575
|
+
printf '%s\n' 'applicable=false'
|
|
576
|
+
fi
|
|
577
|
+
;;
|
|
578
|
+
type_check)
|
|
579
|
+
if has_script type-check || has_script typecheck || [ -f Cargo.toml ] || has_python; then
|
|
580
|
+
printf '%s\n' 'applicable=true'
|
|
581
|
+
else
|
|
582
|
+
printf '%s\n' 'applicable=false'
|
|
583
|
+
fi
|
|
584
|
+
;;
|
|
585
|
+
build)
|
|
586
|
+
if has_script build || [ -f Cargo.toml ]; then
|
|
587
|
+
printf '%s\n' 'applicable=true'
|
|
588
|
+
else
|
|
589
|
+
printf '%s\n' 'applicable=false'
|
|
590
|
+
fi
|
|
591
|
+
;;
|
|
592
|
+
unit)
|
|
593
|
+
if has_script test:unit || has_script test:coverage || has_script test || [ -f Cargo.toml ] ||
|
|
594
|
+
[ -d tests ] || [ -d test ]; then
|
|
595
|
+
printf '%s\n' 'applicable=true'
|
|
596
|
+
else
|
|
597
|
+
printf '%s\n' 'applicable=false'
|
|
598
|
+
fi
|
|
599
|
+
;;
|
|
600
|
+
integration)
|
|
601
|
+
if has_script test:integration || [ -f Cargo.toml ] && [ -d tests ] || [ -d tests/integration ]; then
|
|
602
|
+
printf '%s\n' 'applicable=true'
|
|
603
|
+
else
|
|
604
|
+
printf '%s\n' 'applicable=false'
|
|
605
|
+
fi
|
|
606
|
+
;;
|
|
607
|
+
e2e)
|
|
608
|
+
if has_script test:e2e || has_script e2e || [ -d tests/e2e ]; then
|
|
609
|
+
printf '%s\n' 'applicable=true'
|
|
610
|
+
if has_browser_dependencies; then
|
|
611
|
+
printf '%s\n' 'browser=true'
|
|
612
|
+
else
|
|
613
|
+
printf '%s\n' 'browser=false'
|
|
614
|
+
fi
|
|
615
|
+
else
|
|
616
|
+
printf '%s\n' 'applicable=false'
|
|
617
|
+
printf '%s\n' 'browser=false'
|
|
618
|
+
fi
|
|
619
|
+
;;
|
|
620
|
+
smoke)
|
|
621
|
+
if has_script test:smoke || has_script smoke || [ -d tests/smoke ]; then
|
|
622
|
+
printf '%s\n' 'applicable=true'
|
|
623
|
+
else
|
|
624
|
+
printf '%s\n' 'applicable=false'
|
|
625
|
+
fi
|
|
626
|
+
;;
|
|
627
|
+
*)
|
|
628
|
+
echo "unknown CI task: $task" >&2
|
|
629
|
+
return 2
|
|
630
|
+
;;
|
|
631
|
+
esac
|
|
632
|
+
}
|
|
633
|
+
|
|
242
634
|
case "${1:-}" in
|
|
243
|
-
install|format|lint|type_check|build|unit|integration|e2e|smoke) "$1" ;;
|
|
244
|
-
|
|
635
|
+
install|format|lint|type_check|build|unit|integration|e2e|smoke|should_run) "$1" "${2:-}" ;;
|
|
636
|
+
task_profile) task_profile "${2:-}" ;;
|
|
637
|
+
*) echo "usage: $0 {install|format|lint|type_check|build|unit|integration|e2e|smoke|should_run|task_profile}" >&2; exit 2 ;;
|
|
245
638
|
esac
|
|
@@ -2,21 +2,87 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
languages=()
|
|
5
|
+
changed_files=""
|
|
6
|
+
|
|
7
|
+
# Scheduled and manually dispatched scans are always full scans. For pushes
|
|
8
|
+
# and pull requests, a shallow commit diff lets the matrix skip analyzers for
|
|
9
|
+
# languages untouched by the change while keeping every matrix check visible.
|
|
10
|
+
case "${GITHUB_EVENT_NAME:-}" in
|
|
11
|
+
schedule|workflow_dispatch) ;;
|
|
12
|
+
*)
|
|
13
|
+
base_sha="${CODEQL_BASE_SHA:-}"
|
|
14
|
+
if [ -n "$base_sha" ] && [ "$base_sha" != "0000000000000000000000000000000000000000" ]; then
|
|
15
|
+
if ! git cat-file -e "$base_sha^{commit}" 2>/dev/null; then
|
|
16
|
+
git fetch --no-tags --filter=blob:none --depth=1 origin "$base_sha" >/dev/null 2>&1 || true
|
|
17
|
+
fi
|
|
18
|
+
changed_files="$(git diff --name-only "$base_sha" "${GITHUB_SHA:-HEAD}" 2>/dev/null || true)"
|
|
19
|
+
fi
|
|
20
|
+
[ -n "$changed_files" ] || changed_files="$(git diff --name-only HEAD^ HEAD 2>/dev/null || true)"
|
|
21
|
+
;;
|
|
22
|
+
esac
|
|
23
|
+
|
|
5
24
|
if find .github/workflows -type f \( -name '*.yml' -o -name '*.yaml' \) -print -quit | grep -q .; then languages+=(actions); fi
|
|
6
25
|
configured=""
|
|
7
|
-
if [ -
|
|
26
|
+
if [ -x .github/scripts/profile.sh ]; then
|
|
27
|
+
configured="$(bash .github/scripts/profile.sh get languages 2>/dev/null || true)"
|
|
28
|
+
elif [ -f .github/template.yml ]; then
|
|
8
29
|
configured="$(awk -F': ' '/^languages:/ {print $2; exit}' .github/template.yml)"
|
|
9
30
|
fi
|
|
10
31
|
|
|
11
32
|
if [ "$configured" = auto ] || [ "$configured" = all ] || [ -z "$configured" ]; then
|
|
12
|
-
|
|
13
|
-
if
|
|
14
|
-
|
|
33
|
+
tracked_files="$(git ls-files)"
|
|
34
|
+
if printf '%s\n' "$tracked_files" | grep -Eq '(^|/)([^/]+\.(ts|tsx|js|jsx)|package\.json|tsconfig[^/]*\.json)$'; then
|
|
35
|
+
languages+=(javascript-typescript)
|
|
36
|
+
fi
|
|
37
|
+
if printf '%s\n' "$tracked_files" | awk '!/^\.github\// && /(^|\/)([^\/]+\.py|pyproject\.toml|requirements[^\/]*\.txt|setup\.py)$/ { found=1; exit } END { exit !found }'; then
|
|
38
|
+
languages+=(python)
|
|
39
|
+
fi
|
|
40
|
+
if printf '%s\n' "$tracked_files" | grep -Eq '(^|/)([^/]+\.rs|Cargo\.toml|Cargo\.lock)$'; then
|
|
41
|
+
languages+=(rust)
|
|
42
|
+
fi
|
|
15
43
|
else
|
|
16
44
|
case ",$configured," in *,typescript,*) languages+=(javascript-typescript) ;; esac
|
|
17
45
|
case ",$configured," in *,python,*) languages+=(python) ;; esac
|
|
18
46
|
case ",$configured," in *,rust,*) languages+=(rust) ;; esac
|
|
19
47
|
fi
|
|
20
48
|
|
|
21
|
-
|
|
49
|
+
changed_json="$(printf '%s\n' "$changed_files" | jq -Rsc 'split("\n") | map(select(length > 0))')"
|
|
50
|
+
json=$(printf '%s\n' "${languages[@]}" | jq -Rsc --argjson changed "$changed_json" '
|
|
51
|
+
split("\n")
|
|
52
|
+
| map(select(length > 0) | {
|
|
53
|
+
language: .,
|
|
54
|
+
name: (if . == "javascript-typescript" then "TypeScript" elif . == "actions" then "Actions" else (. | ascii_upcase[0:1] + .[1:]) end),
|
|
55
|
+
"build-mode": "none",
|
|
56
|
+
changed: (
|
|
57
|
+
if ($changed | length) == 0 then true
|
|
58
|
+
elif . == "actions" then any($changed[]; test("(^|/)(\\.github/workflows/|action\\.yml$)"))
|
|
59
|
+
elif . == "javascript-typescript" then any($changed[]; test("(^|/)(.*\\.(js|jsx|mjs|cjs|ts|tsx|mts|cts)|package\\.json|tsconfig[^/]*\\.json|((bun|pnpm|yarn|package-lock)\\.lock))$"))
|
|
60
|
+
elif . == "python" then any($changed[]; test("(^|/)(.*\\.py|pyproject\\.toml|requirements[^/]*\\.txt|setup\\.py|Pipfile\\.lock|poetry\\.lock)$"))
|
|
61
|
+
elif . == "rust" then any($changed[]; test("(^|/)(.*\\.rs|Cargo\\.toml|Cargo\\.lock)$"))
|
|
62
|
+
else true
|
|
63
|
+
end
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
')
|
|
22
67
|
printf 'languages=%s\n' "$json" >> "$GITHUB_OUTPUT"
|
|
68
|
+
|
|
69
|
+
# Expose stable, shell-friendly outputs for explicit analyzer jobs. Keeping
|
|
70
|
+
# these separate from the JSON matrix lets workflow-level conditions avoid
|
|
71
|
+
# allocating a runner for languages that are absent or unchanged.
|
|
72
|
+
for codeql_language in actions javascript-typescript python rust; do
|
|
73
|
+
output_prefix="$codeql_language"
|
|
74
|
+
case "$codeql_language" in
|
|
75
|
+
javascript-typescript) output_prefix="javascript" ;;
|
|
76
|
+
esac
|
|
77
|
+
|
|
78
|
+
entry="$(jq -c --arg language "$codeql_language" '.[] | select(.language == $language)' <<< "$json")"
|
|
79
|
+
if [ -n "$entry" ]; then
|
|
80
|
+
printf '%s_available=true\n' "$output_prefix" >> "$GITHUB_OUTPUT"
|
|
81
|
+
printf '%s_changed=%s\n' "$output_prefix" "$(jq -r '.changed' <<< "$entry")" >> "$GITHUB_OUTPUT"
|
|
82
|
+
printf '%s_build_mode=%s\n' "$output_prefix" "$(jq -r '."build-mode"' <<< "$entry")" >> "$GITHUB_OUTPUT"
|
|
83
|
+
else
|
|
84
|
+
printf '%s_available=false\n' "$output_prefix" >> "$GITHUB_OUTPUT"
|
|
85
|
+
printf '%s_changed=false\n' "$output_prefix" >> "$GITHUB_OUTPUT"
|
|
86
|
+
printf '%s_build_mode=none\n' "$output_prefix" >> "$GITHUB_OUTPUT"
|
|
87
|
+
fi
|
|
88
|
+
done
|