mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-21 12:51:16 +08:00
ci: Stand up multi-package mutation health — nightly passes + mutant-* skills (no-changelog) (#31356)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1b8235ef76
commit
ecfc39b3e5
@@ -2,38 +2,76 @@ name: 'Mutation Health (nightly)'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 03:30 UTC daily — outside CI rush, before EU morning.
|
||||
# 03:30 UTC daily — outside CI rush, before EU morning. Runs both passes.
|
||||
- cron: '30 3 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package:
|
||||
description: 'Workspace package to mutate'
|
||||
mode:
|
||||
description: 'Which pass to run'
|
||||
type: choice
|
||||
options:
|
||||
- n8n-workflow
|
||||
default: n8n-workflow
|
||||
- both
|
||||
- baseline # score files with no result yet (the `new` bucket)
|
||||
- coverage # revisit the weakest scored files (`red`/`stale`, lowest first)
|
||||
default: both
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Prevent overlapping scheduled + manual runs from racing on the same
|
||||
# ledger row. The writer MERGE is idempotent, but two concurrent runs
|
||||
# would emit duplicate event rows for the same picked file.
|
||||
concurrency:
|
||||
group: mutation-health-${{ github.event.inputs.package || 'n8n-workflow' }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
PACKAGE_NAME: ${{ github.event.inputs.package || 'n8n-workflow' }}
|
||||
PKG_DIR: packages/workflow # 1-to-1 mapping today; generalise when more packages are wired up
|
||||
REPORTS_DIR: packages/workflow/reports/mutation
|
||||
READER_URL: https://internal.users.n8n.cloud/webhook/mutation-health-ledger
|
||||
|
||||
jobs:
|
||||
run:
|
||||
name: Mutate one file, ledger writeback
|
||||
# Build the (package × pass) matrix once. The package list lives here only —
|
||||
# onboarding a vitest package is a one-line addition below. The requested
|
||||
# `mode` filters which passes run (scheduled runs default to both).
|
||||
# baseline → score files with no result yet (picker `new` bucket)
|
||||
# coverage → revisit the weakest scored files (picker `red`/`stale`, lowest first)
|
||||
setup:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.build.outputs.matrix }}
|
||||
steps:
|
||||
- name: Build matrix
|
||||
id: build
|
||||
env:
|
||||
REQUESTED_MODE: ${{ github.event.inputs.mode || 'both' }}
|
||||
# vitest packages only. A package with its own stryker.config.mjs overrides
|
||||
# the shared default (scripts/mutation-health/stryker.default.mjs) — n8n-workflow
|
||||
# does this to run the legacy expression engine and dodge the isolated-vm
|
||||
# dry-run crash (see its config + DEVP-257). Jest packages and
|
||||
# @n8n/expression-runtime are intentionally absent.
|
||||
run: |
|
||||
node -e '
|
||||
const packages = [
|
||||
{ name: "n8n-workflow", dir: "packages/workflow", slug: "workflow" },
|
||||
{ name: "@n8n/crdt", dir: "packages/@n8n/crdt", slug: "crdt" },
|
||||
{ name: "@n8n/decorators", dir: "packages/@n8n/decorators", slug: "decorators" },
|
||||
];
|
||||
const req = process.env.REQUESTED_MODE;
|
||||
const modes = req === "both" ? ["baseline", "coverage"] : [req];
|
||||
const include = packages.flatMap((p) => modes.map((mode) => ({ ...p, mode })));
|
||||
console.log("matrix=" + JSON.stringify({ include }));
|
||||
' >> "$GITHUB_OUTPUT"
|
||||
|
||||
mutate:
|
||||
needs: setup
|
||||
name: ${{ matrix.mode }} · ${{ matrix.name }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false # one leg's failure must not skip the others
|
||||
matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
|
||||
# Per package + pass: a scheduled run and a manual run won't double-write the
|
||||
# same ledger row, but different packages/passes still run in parallel.
|
||||
concurrency:
|
||||
group: mutation-health-${{ matrix.mode }}-${{ matrix.slug }}
|
||||
cancel-in-progress: false
|
||||
env:
|
||||
PACKAGE_NAME: ${{ matrix.name }}
|
||||
PKG_DIR: ${{ matrix.dir }}
|
||||
MODE: ${{ matrix.mode }}
|
||||
REPORTS_DIR: ${{ matrix.dir }}/reports/mutation
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -46,18 +84,20 @@ jobs:
|
||||
- name: Fetch live ledger from BigQuery
|
||||
run: |
|
||||
mkdir -p "$REPORTS_DIR"
|
||||
curl --fail -sS "$READER_URL?package=$PACKAGE_NAME" -o "$REPORTS_DIR/live-ledger.json"
|
||||
curl --fail -sS --get --data-urlencode "package=$PACKAGE_NAME" \
|
||||
"$READER_URL" -o "$REPORTS_DIR/live-ledger.json"
|
||||
|
||||
- name: Pick next source file
|
||||
id: pick
|
||||
run: |
|
||||
picked_json=$(node scripts/mutation-health/pick-next.mjs \
|
||||
--package-dir "$PKG_DIR" \
|
||||
--ledger-file "$REPORTS_DIR/live-ledger.json")
|
||||
--ledger-file "$REPORTS_DIR/live-ledger.json" \
|
||||
--mode "$MODE")
|
||||
echo "$picked_json"
|
||||
src_repo=$(echo "$picked_json" | jq -r '.picked.source_file_path // ""')
|
||||
if [ -z "$src_repo" ]; then
|
||||
echo "::notice::Picker returned no work (all-green / empty ledger). Exiting cleanly."
|
||||
echo "::notice::Picker returned no work for mode=$MODE (bucket empty). Exiting cleanly."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
@@ -76,7 +116,7 @@ jobs:
|
||||
# "Stryker crashed." continue-on-error: true would collapse them.
|
||||
run: |
|
||||
set +e
|
||||
pnpm --filter "$PACKAGE_NAME" mutate "${{ steps.pick.outputs.source-rel }}"
|
||||
node scripts/mutation-health/mutate.mjs "${{ steps.pick.outputs.source-rel }}" --package-dir "$PKG_DIR"
|
||||
rc=$?
|
||||
set -e
|
||||
if [ "$rc" -gt 1 ]; then
|
||||
@@ -107,7 +147,7 @@ jobs:
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: mutation-health-${{ env.PACKAGE_NAME }}-${{ github.run_id }}
|
||||
name: mutation-health-${{ matrix.mode }}-${{ matrix.slug }}-${{ github.run_id }}
|
||||
path: |
|
||||
${{ env.REPORTS_DIR }}/raw.json
|
||||
${{ env.REPORTS_DIR }}/raw.html
|
||||
|
||||
Reference in New Issue
Block a user