refactor(ci): extract large inline scripts to scripts/ci/ (#587)
The CI workflow contained a ~90-line bash script for change-detection (lines 38-128) and a ~80-line JavaScript block for lint feedback (lines 292-370) directly inline in the YAML. Large inline scripts are harder to test, lint, and maintain than standalone files. Extract: - Change-detection logic → scripts/ci/detect_change_scope.sh - Lint feedback logic → scripts/ci/lint_feedback.js The workflow now calls these external scripts. GitHub expression values that were previously interpolated inline are passed as environment variables instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
64f91a00d8
commit
0964eebb10
3 changed files with 193 additions and 169 deletions
177
.github/workflows/ci.yml
vendored
177
.github/workflows/ci.yml
vendored
|
|
@ -35,97 +35,10 @@ jobs:
|
|||
- name: Detect docs-only changes
|
||||
id: scope
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
write_empty_docs_files() {
|
||||
{
|
||||
echo "docs_files<<EOF"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
}
|
||||
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
BASE="${{ github.event.pull_request.base.sha }}"
|
||||
else
|
||||
BASE="${{ github.event.before }}"
|
||||
fi
|
||||
|
||||
if [ -z "$BASE" ] || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then
|
||||
{
|
||||
echo "docs_only=false"
|
||||
echo "docs_changed=false"
|
||||
echo "rust_changed=true"
|
||||
echo "workflow_changed=false"
|
||||
echo "base_sha="
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
write_empty_docs_files
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CHANGED="$(git diff --name-only "$BASE" HEAD || true)"
|
||||
if [ -z "$CHANGED" ]; then
|
||||
{
|
||||
echo "docs_only=false"
|
||||
echo "docs_changed=false"
|
||||
echo "rust_changed=false"
|
||||
echo "workflow_changed=false"
|
||||
echo "base_sha=$BASE"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
write_empty_docs_files
|
||||
exit 0
|
||||
fi
|
||||
|
||||
docs_only=true
|
||||
docs_changed=false
|
||||
rust_changed=false
|
||||
workflow_changed=false
|
||||
docs_files=()
|
||||
while IFS= read -r file; do
|
||||
[ -z "$file" ] && continue
|
||||
|
||||
if [[ "$file" == .github/workflows/* ]]; then
|
||||
workflow_changed=true
|
||||
fi
|
||||
|
||||
if [[ "$file" == docs/* ]] \
|
||||
|| [[ "$file" == *.md ]] \
|
||||
|| [[ "$file" == *.mdx ]] \
|
||||
|| [[ "$file" == "LICENSE" ]] \
|
||||
|| [[ "$file" == ".markdownlint-cli2.yaml" ]] \
|
||||
|| [[ "$file" == .github/ISSUE_TEMPLATE/* ]] \
|
||||
|| [[ "$file" == .github/pull_request_template.md ]]; then
|
||||
if [[ "$file" == *.md ]] \
|
||||
|| [[ "$file" == *.mdx ]] \
|
||||
|| [[ "$file" == "LICENSE" ]] \
|
||||
|| [[ "$file" == .github/pull_request_template.md ]]; then
|
||||
docs_changed=true
|
||||
docs_files+=("$file")
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
docs_only=false
|
||||
|
||||
if [[ "$file" == src/* ]] \
|
||||
|| [[ "$file" == tests/* ]] \
|
||||
|| [[ "$file" == "Cargo.toml" ]] \
|
||||
|| [[ "$file" == "Cargo.lock" ]] \
|
||||
|| [[ "$file" == "deny.toml" ]]; then
|
||||
rust_changed=true
|
||||
fi
|
||||
done <<< "$CHANGED"
|
||||
|
||||
{
|
||||
echo "docs_only=$docs_only"
|
||||
echo "docs_changed=$docs_changed"
|
||||
echo "rust_changed=$rust_changed"
|
||||
echo "workflow_changed=$workflow_changed"
|
||||
echo "base_sha=$BASE"
|
||||
echo "docs_files<<EOF"
|
||||
printf '%s\n' "${docs_files[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
|
||||
run: ./scripts/ci/detect_change_scope.sh
|
||||
|
||||
lint:
|
||||
name: Lint Gate (Format + Clippy)
|
||||
|
|
@ -279,6 +192,8 @@ jobs:
|
|||
pull-requests: write
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
|
||||
- name: Post actionable lint failure summary
|
||||
if: always()
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
|
|
@ -290,84 +205,8 @@ jobs:
|
|||
DOCS_RESULT: ${{ needs.docs-quality.result }}
|
||||
with:
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const issueNumber = context.payload.pull_request?.number;
|
||||
if (!issueNumber) return;
|
||||
|
||||
const marker = "<!-- ci-lint-feedback -->";
|
||||
const rustChanged = process.env.RUST_CHANGED === "true";
|
||||
const docsChanged = process.env.DOCS_CHANGED === "true";
|
||||
const lintResult = process.env.LINT_RESULT || "skipped";
|
||||
const lintDeltaResult = process.env.LINT_DELTA_RESULT || "skipped";
|
||||
const docsResult = process.env.DOCS_RESULT || "skipped";
|
||||
|
||||
const failures = [];
|
||||
if (rustChanged && !["success", "skipped"].includes(lintResult)) {
|
||||
failures.push("`Lint Gate (Format + Clippy)` failed.");
|
||||
}
|
||||
if (rustChanged && !["success", "skipped"].includes(lintDeltaResult)) {
|
||||
failures.push("`Lint Gate (Strict Delta)` failed.");
|
||||
}
|
||||
if (docsChanged && !["success", "skipped"].includes(docsResult)) {
|
||||
failures.push("`Docs Quality` failed.");
|
||||
}
|
||||
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
const existing = comments.find((comment) => (comment.body || "").includes(marker));
|
||||
|
||||
if (failures.length === 0) {
|
||||
if (existing) {
|
||||
await github.rest.issues.deleteComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: existing.id,
|
||||
});
|
||||
}
|
||||
core.info("No lint/docs gate failures. No feedback comment required.");
|
||||
return;
|
||||
}
|
||||
|
||||
const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`;
|
||||
const body = [
|
||||
marker,
|
||||
"### CI lint feedback",
|
||||
"",
|
||||
"This PR failed one or more fast lint/documentation gates:",
|
||||
"",
|
||||
...failures.map((item) => `- ${item}`),
|
||||
"",
|
||||
"Open the failing logs in this run:",
|
||||
`- ${runUrl}`,
|
||||
"",
|
||||
"Local fix commands:",
|
||||
"- `./scripts/ci/rust_quality_gate.sh`",
|
||||
"- `./scripts/ci/rust_strict_delta_gate.sh`",
|
||||
"- `./scripts/ci/docs_quality_gate.sh`",
|
||||
"",
|
||||
"After fixes, push a new commit and CI will re-run automatically.",
|
||||
].join("\n");
|
||||
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
body,
|
||||
});
|
||||
}
|
||||
const script = require('./scripts/ci/lint_feedback.js');
|
||||
await script({github, context, core});
|
||||
|
||||
workflow-owner-approval:
|
||||
name: Workflow Owner Approval
|
||||
|
|
|
|||
95
scripts/ci/detect_change_scope.sh
Executable file
95
scripts/ci/detect_change_scope.sh
Executable file
|
|
@ -0,0 +1,95 @@
|
|||
#!/usr/bin/env bash
|
||||
# Detect change scope for CI pipeline.
|
||||
# Classifies changed files into docs-only, rust, workflow categories
|
||||
# and writes results to $GITHUB_OUTPUT.
|
||||
#
|
||||
# Required environment variables:
|
||||
# GITHUB_OUTPUT — GitHub Actions output file
|
||||
# EVENT_NAME — github.event_name (push or pull_request)
|
||||
# BASE_SHA — base commit SHA to diff against
|
||||
set -euo pipefail
|
||||
|
||||
write_empty_docs_files() {
|
||||
{
|
||||
echo "docs_files<<EOF"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
}
|
||||
|
||||
BASE="$BASE_SHA"
|
||||
|
||||
if [ -z "$BASE" ] || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then
|
||||
{
|
||||
echo "docs_only=false"
|
||||
echo "docs_changed=false"
|
||||
echo "rust_changed=true"
|
||||
echo "workflow_changed=false"
|
||||
echo "base_sha="
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
write_empty_docs_files
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CHANGED="$(git diff --name-only "$BASE" HEAD || true)"
|
||||
if [ -z "$CHANGED" ]; then
|
||||
{
|
||||
echo "docs_only=false"
|
||||
echo "docs_changed=false"
|
||||
echo "rust_changed=false"
|
||||
echo "workflow_changed=false"
|
||||
echo "base_sha=$BASE"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
write_empty_docs_files
|
||||
exit 0
|
||||
fi
|
||||
|
||||
docs_only=true
|
||||
docs_changed=false
|
||||
rust_changed=false
|
||||
workflow_changed=false
|
||||
docs_files=()
|
||||
while IFS= read -r file; do
|
||||
[ -z "$file" ] && continue
|
||||
|
||||
if [[ "$file" == .github/workflows/* ]]; then
|
||||
workflow_changed=true
|
||||
fi
|
||||
|
||||
if [[ "$file" == docs/* ]] \
|
||||
|| [[ "$file" == *.md ]] \
|
||||
|| [[ "$file" == *.mdx ]] \
|
||||
|| [[ "$file" == "LICENSE" ]] \
|
||||
|| [[ "$file" == ".markdownlint-cli2.yaml" ]] \
|
||||
|| [[ "$file" == .github/ISSUE_TEMPLATE/* ]] \
|
||||
|| [[ "$file" == .github/pull_request_template.md ]]; then
|
||||
if [[ "$file" == *.md ]] \
|
||||
|| [[ "$file" == *.mdx ]] \
|
||||
|| [[ "$file" == "LICENSE" ]] \
|
||||
|| [[ "$file" == .github/pull_request_template.md ]]; then
|
||||
docs_changed=true
|
||||
docs_files+=("$file")
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
docs_only=false
|
||||
|
||||
if [[ "$file" == src/* ]] \
|
||||
|| [[ "$file" == tests/* ]] \
|
||||
|| [[ "$file" == "Cargo.toml" ]] \
|
||||
|| [[ "$file" == "Cargo.lock" ]] \
|
||||
|| [[ "$file" == "deny.toml" ]]; then
|
||||
rust_changed=true
|
||||
fi
|
||||
done <<< "$CHANGED"
|
||||
|
||||
{
|
||||
echo "docs_only=$docs_only"
|
||||
echo "docs_changed=$docs_changed"
|
||||
echo "rust_changed=$rust_changed"
|
||||
echo "workflow_changed=$workflow_changed"
|
||||
echo "base_sha=$BASE"
|
||||
echo "docs_files<<EOF"
|
||||
printf '%s\n' "${docs_files[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
90
scripts/ci/lint_feedback.js
Normal file
90
scripts/ci/lint_feedback.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// Post actionable lint failure summary as a PR comment.
|
||||
// Used by the lint-feedback CI job via actions/github-script.
|
||||
//
|
||||
// Required environment variables:
|
||||
// RUST_CHANGED — "true" if Rust files changed
|
||||
// DOCS_CHANGED — "true" if docs files changed
|
||||
// LINT_RESULT — result of the lint job
|
||||
// LINT_DELTA_RESULT — result of the strict delta lint job
|
||||
// DOCS_RESULT — result of the docs-quality job
|
||||
|
||||
module.exports = async ({ github, context, core }) => {
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const issueNumber = context.payload.pull_request?.number;
|
||||
if (!issueNumber) return;
|
||||
|
||||
const marker = "<!-- ci-lint-feedback -->";
|
||||
const rustChanged = process.env.RUST_CHANGED === "true";
|
||||
const docsChanged = process.env.DOCS_CHANGED === "true";
|
||||
const lintResult = process.env.LINT_RESULT || "skipped";
|
||||
const lintDeltaResult = process.env.LINT_DELTA_RESULT || "skipped";
|
||||
const docsResult = process.env.DOCS_RESULT || "skipped";
|
||||
|
||||
const failures = [];
|
||||
if (rustChanged && !["success", "skipped"].includes(lintResult)) {
|
||||
failures.push("`Lint Gate (Format + Clippy)` failed.");
|
||||
}
|
||||
if (rustChanged && !["success", "skipped"].includes(lintDeltaResult)) {
|
||||
failures.push("`Lint Gate (Strict Delta)` failed.");
|
||||
}
|
||||
if (docsChanged && !["success", "skipped"].includes(docsResult)) {
|
||||
failures.push("`Docs Quality` failed.");
|
||||
}
|
||||
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
const existing = comments.find((comment) => (comment.body || "").includes(marker));
|
||||
|
||||
if (failures.length === 0) {
|
||||
if (existing) {
|
||||
await github.rest.issues.deleteComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: existing.id,
|
||||
});
|
||||
}
|
||||
core.info("No lint/docs gate failures. No feedback comment required.");
|
||||
return;
|
||||
}
|
||||
|
||||
const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`;
|
||||
const body = [
|
||||
marker,
|
||||
"### CI lint feedback",
|
||||
"",
|
||||
"This PR failed one or more fast lint/documentation gates:",
|
||||
"",
|
||||
...failures.map((item) => `- ${item}`),
|
||||
"",
|
||||
"Open the failing logs in this run:",
|
||||
`- ${runUrl}`,
|
||||
"",
|
||||
"Local fix commands:",
|
||||
"- `./scripts/ci/rust_quality_gate.sh`",
|
||||
"- `./scripts/ci/rust_strict_delta_gate.sh`",
|
||||
"- `./scripts/ci/docs_quality_gate.sh`",
|
||||
"",
|
||||
"After fixes, push a new commit and CI will re-run automatically.",
|
||||
].join("\n");
|
||||
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
body,
|
||||
});
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue