From 670f0ac469043bb166c84290c0dbf62b4908e3cc Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Sun, 11 Oct 2026 11:14:03 +0800 Subject: [PATCH] Run the quality checks from a script file of this repository Task: MIL-003#3 --- .gitea/workflows/quality-checks.yml | 111 +++------------- CHANGELOG.md | 11 ++ docs/adr/adr-0005-quality-check-contract.md | 13 +- docs/artifact-registry.md | 2 +- docs/sqa/reviews/rc-051-adr-0005-tools-ref.md | 50 +++++++ docs/sqa/traceability-matrix.md | 11 +- guides/quality-checks.md | 6 + guides/upgrading.md | 4 + tests/test_quality_workflow.py | 40 ++++-- tools/quality-checks.sh | 122 ++++++++++++++++++ wiki/QualityChecksPythonShell.da.md | 5 +- wiki/QualityChecksPythonShell.md | 6 + wiki/QualityChecksPythonShell.ms.md | 5 +- wiki/Upgrading.da.md | 4 +- wiki/Upgrading.md | 4 + wiki/Upgrading.ms.md | 4 +- 16 files changed, 279 insertions(+), 119 deletions(-) create mode 100644 docs/sqa/reviews/rc-051-adr-0005-tools-ref.md create mode 100644 tools/quality-checks.sh diff --git a/.gitea/workflows/quality-checks.yml b/.gitea/workflows/quality-checks.yml index c53331a..7fd90d5 100644 --- a/.gitea/workflows/quality-checks.yml +++ b/.gitea/workflows/quality-checks.yml @@ -35,6 +35,10 @@ on: description: "Version of the shfmt-py package (shfmt 3.11.0)" type: string default: "3.11.0.2" + tools-ref: + description: "Tag or branch of TirSystem/github-action whose tools/quality-checks.sh is run; pin it to the exact tag together with the workflow" + type: string + default: "v1" jobs: quality-checks: @@ -50,104 +54,21 @@ jobs: SHFMT_PY_VERSION: ${{ inputs.shfmt-py-version }} QC_VENV: /tmp/quality-tools steps: - - name: Validate the inputs - run: | - set -u - IFS=',' read -ra langs <<< "${LANGUAGES// /}" - if [ "${#langs[@]}" -eq 0 ]; then - echo "::error::[configuration] languages is empty; use python, shell or both, comma separated." - exit 1 - fi - for lang in "${langs[@]}"; do - case "$lang" in - python | shell) ;; - *) - echo "::error::[configuration] Unknown language '$lang'; use python and shell, comma separated." - exit 1 - ;; - esac - done - case "$WORKING_DIRECTORY" in - "" | /* | .. | ../* | */.. | */../*) - echo "::error::[configuration] working-directory must be a relative path inside the repository." - exit 1 - ;; - esac - echo "Checking ${LANGUAGES} in ${WORKING_DIRECTORY}" - - name: Check out the repository uses: actions/checkout@v4 + - name: Fetch the shared script + uses: actions/checkout@v4 + with: + repository: TirSystem/github-action + ref: ${{ inputs.tools-ref }} + path: shared-scripts + + - name: Validate the inputs + run: bash shared-scripts/tools/quality-checks.sh validate + - name: Install the tools - run: | - set -eu - IFS=',' read -ra langs <<< "${LANGUAGES// /}" - packages=() - for lang in "${langs[@]}"; do - case "$lang" in - python) packages+=("ruff==${RUFF_VERSION}" "mypy==${MYPY_VERSION}") ;; - shell) packages+=("shellcheck-py==${SHELLCHECK_PY_VERSION}" "shfmt-py==${SHFMT_PY_VERSION}") ;; - esac - done - python3 -m venv "$QC_VENV" - "$QC_VENV/bin/pip" install --quiet "${packages[@]}" + run: bash shared-scripts/tools/quality-checks.sh install - name: Run the checks - run: | - set -u - QC_BIN="${QC_BIN:-$QC_VENV/bin}" - if [ ! -d "$WORKING_DIRECTORY" ]; then - echo "::error::[configuration] working-directory '$WORKING_DIRECTORY' does not exist." - exit 1 - fi - cd "$WORKING_DIRECTORY" - - failed=() - run_tool() { - local name="$1" - shift - echo "::group::$name" - if "$@"; then - echo "::endgroup::" - echo "PASS $name" - else - echo "::endgroup::" - echo "FAIL $name" - failed+=("$name") - fi - } - wanted() { - local lang - IFS=',' read -ra langs <<< "${LANGUAGES// /}" - for lang in "${langs[@]}"; do - [ "$lang" = "$1" ] && return 0 - done - return 1 - } - - if wanted python; then - mapfile -t files < <(git ls-files -- '*.py') - if [ "${#files[@]}" -eq 0 ]; then - echo "::notice::No python files in '$WORKING_DIRECTORY'; nothing was checked." - else - run_tool "ruff check" "$QC_BIN/ruff" check "${files[@]}" - run_tool "ruff format --check" "$QC_BIN/ruff" format --check "${files[@]}" - run_tool "mypy --strict" "$QC_BIN/mypy" --strict "${files[@]}" - fi - fi - - if wanted shell; then - mapfile -t files < <(git ls-files -- '*.sh') - if [ "${#files[@]}" -eq 0 ]; then - echo "::notice::No shell files in '$WORKING_DIRECTORY'; nothing was checked." - else - run_tool "shellcheck" "$QC_BIN/shellcheck" "${files[@]}" - run_tool "shfmt -d" "$QC_BIN/shfmt" -d "${files[@]}" - fi - fi - - if [ "${#failed[@]}" -gt 0 ]; then - echo "::error::Quality checks failed: ${failed[*]}" - exit 1 - fi - echo "All selected checks passed." + run: bash shared-scripts/tools/quality-checks.sh run diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e108b7..3b4f67c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,17 @@ Each release lists its changes in these groups, in this order: ## [Unreleased] +### Added + +- Input `tools-ref` of `quality-checks.yml` (default `v1`): the tag or branch from + which the workflow fetches its script `tools/quality-checks.sh`. + +### Changed + +- `quality-checks.yml` runs the script file `tools/quality-checks.sh` instead of + embedding about 100 lines of shell; the checks, messages and results are the + same. + ## [1.0.1] - 2026-10-11 ### Changed diff --git a/docs/adr/adr-0005-quality-check-contract.md b/docs/adr/adr-0005-quality-check-contract.md index 02944b7..c4fcb84 100644 --- a/docs/adr/adr-0005-quality-check-contract.md +++ b/docs/adr/adr-0005-quality-check-contract.md @@ -9,8 +9,8 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [385d2fb] | -| 2026-10-10 | Accepted | Jens Tirsvad Nielsen | S01 | Reviewed (RC-038): the security impact of installing the tools from PyPI named | [385d2fb] | +| 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Reviewed (RC-038): the security impact of installing the tools from PyPI named | [385d2fb] | +| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Added the input tools-ref: the shell steps moved to a script file fetched at that ref (RC-051) | pending | Allowed Status values: `Proposed`, `Accepted`, `Rejected`, `Deprecated`, `Superseded by ADR-NNNN`. @@ -55,9 +55,12 @@ Options for the shape of the workflow: The shared workflow `quality-checks.yml` is one reusable workflow with the inputs `languages` (a comma list of `python` and `shell`, default both), -`working-directory` (default `.`) and one version input per tool whose -defaults are the pinned versions above, and it installs the tools from PyPI -into a virtual environment. A run passes when every selected tool exits 0 and +`working-directory` (default `.`), one version input per tool whose +defaults are the pinned versions above, and `tools-ref` (default `v1`, the +tag or branch from which the workflow fetches its script +`tools/quality-checks.sh`), and it installs the tools from PyPI into a virtual +environment. A call at an exact tag is pinned completely only when `tools-ref` +names the same tag; with the default the script follows the major tag. A run passes when every selected tool exits 0 and fails when any exits non-zero, and a language with no files is skipped with a notice and counts as a pass. diff --git a/docs/artifact-registry.md b/docs/artifact-registry.md index 1dc028e..d7b9ad5 100644 --- a/docs/artifact-registry.md +++ b/docs/artifact-registry.md @@ -11,7 +11,7 @@ each document lives in *this* project and the next version to use. | SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 | | PP | Project Plan | docs/project-plan.md | 002 | | MIL | Milestone / Gateway | docs/milestones/*.md | 007 | -| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 051 | +| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 052 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | | ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0008 | | UCD | Use Case Diagram | docs/use-case-diagram.md | 002 | diff --git a/docs/sqa/reviews/rc-051-adr-0005-tools-ref.md b/docs/sqa/reviews/rc-051-adr-0005-tools-ref.md new file mode 100644 index 0000000..4309e96 --- /dev/null +++ b/docs/sqa/reviews/rc-051-adr-0005-tools-ref.md @@ -0,0 +1,50 @@ +# Review Record: ADR-0005 (tools-ref) + +## Metadata +| Key | Value | +| --- | --- | +| ID | RC-051 | +| CrossReference | [ADR-0005], [QC-ADR-001] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | pending | + +--- + +## Artifact Under Review + +- Instance reviewed: [ADR-0005] after the addition of the input `tools-ref` +- Checklist used: [QC-ADR-001] + +## Checklist Results + +| # | Criterion | Status | Evidence/Notes | +| --- | --- | --- | --- | +| 1 | The ID (`ADR-NNNN`, 4-digit) matches the filename number and is unique and sequential | Pass | Unchanged. | +| 2 | Context states the problem, the forces (cost, risk, constraints) and the options evaluated | Pass | Unchanged; the move of the shell steps to a script file is an implementation of the decided contract, not a new option. | +| 3 | At least two options were considered, or the absence of alternatives is justified | Pass | Unchanged. | +| 4 | Decision is stated in one or two unhedged sentences and matches one of the evaluated options | Pass | The decision sentence names the new optional input. | +| 5 | Consequences list both Positive and Negative outcomes | Pass | Finding noted for the next revision, not a blocker: a reusable workflow cannot learn the ref it was called at (the context fields `workflow_ref`, `workflow_sha`, `job_workflow_sha` and `action_ref` are empty on this instance, verified on 2026-10-11), so the script ref is an input and a call at an exact tag is pinned completely only with `tools-ref` set to the same tag. | +| 6 | Quality-attribute impacts (performance, security, portability, etc.) are named where the decision affects them | Pass | Maintainability gains (a script of this repository, linted and tested as a file); the pin limitation above is the reliability cost. | +| 7 | Affected Artifacts are listed as links, and each is also in CrossReference | Pass | Unchanged. | +| 8 | Status changes are recorded as new Version History rows with a Change summary and Commit link; retained rows are never rewritten | Pass | The earlier row is kept with status Deprecated and a new row added. | +| 9 | A superseded or deprecated ADR names its successor, and the successor links back | N-A | Nothing is superseded. | +| 10 | Written in business or technical language of the domain, with no unexplained abbreviations | Pass | Unchanged. | +| 11 | Status is one of `Proposed`, `Accepted`, `Rejected`, `Deprecated` or `Superseded by ADR-NNNN`, and every Version History row uses only these | Pass | Deprecated and Accepted. | + +## Overall Verdict + +Go - all applicable criteria pass. Adding an optional input is a minor change under ADR-0006. S01 gave the Go in chat on 2026-10-11, standing in for the other reviewers as ADR-0002 records; there is no independent reader. + +## Action Items + +| Action | Owner | Due | +| --- | --- | --- | +| Run a pinned consumer (`@v1.0.1` with `tools-ref: v1.0.1`) after the next release to confirm the complete pin | S05 | next release | + +--- + +[ADR-0005]: ../../adr/adr-0005-quality-check-contract.md +[QC-ADR-001]: ../../../framework/qc/qc-adr.md diff --git a/docs/sqa/traceability-matrix.md b/docs/sqa/traceability-matrix.md index 0725e5a..ccdf7b5 100644 --- a/docs/sqa/traceability-matrix.md +++ b/docs/sqa/traceability-matrix.md @@ -4,13 +4,13 @@ | Key | Value | | --- | --- | | ID | TM-001 | -| CrossReference | [BC-001], [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [RC-001], [RC-002], [RC-003], [RC-004], [RC-005], [RC-006], [RC-007], [RC-008], [RC-009], [RC-010], [RC-011], [RC-012], [RC-013], [RC-014], [RC-015], [RC-016], [RC-017], [RC-018], [RC-019], [RC-020], [RC-021], [RC-022], [RC-023], [RC-024], [RC-025], [RC-026], [RC-027], [RC-028], [RC-029], [RC-030], [RC-031], [RC-032], [RC-033], [RC-034], [RC-035], [RC-036], [RC-037], [RC-038], [RC-039], [RC-040], [RC-041], [RC-042], [RC-043], [RC-044], [RC-045], [RC-046], [RC-047], [RC-048], [RC-049], [RC-050], [ADR-0007], [UC-007], [UC-006], [ADR-0006], [UC-005], [ADR-0005], [UCD-001], [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [ADR-0001], [ADR-0002], [ADR-0003], [ADR-0004] | +| CrossReference | [BC-001], [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [RC-001], [RC-002], [RC-003], [RC-004], [RC-005], [RC-006], [RC-007], [RC-008], [RC-009], [RC-010], [RC-011], [RC-012], [RC-013], [RC-014], [RC-015], [RC-016], [RC-017], [RC-018], [RC-019], [RC-020], [RC-021], [RC-022], [RC-023], [RC-024], [RC-025], [RC-026], [RC-027], [RC-028], [RC-029], [RC-030], [RC-031], [RC-032], [RC-033], [RC-034], [RC-035], [RC-036], [RC-037], [RC-038], [RC-039], [RC-040], [RC-041], [RC-042], [RC-043], [RC-044], [RC-045], [RC-046], [RC-047], [RC-048], [RC-049], [RC-050], [RC-051], [ADR-0007], [UC-007], [UC-006], [ADR-0006], [UC-005], [ADR-0005], [UCD-001], [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [ADR-0001], [ADR-0002], [ADR-0003], [ADR-0004] | ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-11 | Deprecated | Jens Tirsvad Nielsen | S01 | Added RC-049 (MIL-006 pilot results) | [d575f27] | -| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Added RC-050 (MIL-005 translations accepted) | [ead3d98] | +| 2026-10-11 | Deprecated | Jens Tirsvad Nielsen | S01 | Added RC-050 (MIL-005 translations accepted) | [ead3d98] | +| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Added RC-051 (ADR-0005 tools-ref) | pending | --- @@ -62,7 +62,7 @@ updated whenever an artifact instance is created or reviewed. | [UC-003] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-023] | | [UC-004] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-024] | | [UC-005] | UC | [UCD-001], [US-001], [SA-001] | [ADR-0005] | [RC-037] | -| [ADR-0005] | ADR | [MIL-003], [UC-005], [US-001], [ADR-0004] | - | [RC-038] | +| [ADR-0005] | ADR | [MIL-003], [UC-005], [US-001], [ADR-0004] | - | [RC-038], [RC-051] | | [UC-006] | UC | [UCD-001], [US-001], [SA-001] | [ADR-0006] | [RC-042] | | [ADR-0006] | ADR | [MIL-004], [UC-006], [US-001], [ADR-0004], [ADR-0005] | - | [RC-043] | | [ADR-0007] | ADR | [MIL-004], [UC-006], [ADR-0006] | - | [RC-047] | @@ -99,6 +99,7 @@ updated whenever an artifact instance is created or reviewed. | [RC-048] | RC | [MIL-004] | - | - | | [RC-049] | RC | [MIL-006] | - | - | | [RC-050] | RC | [MIL-005] | - | - | +| [RC-051] | RC | [ADR-0005] | - | - | ## Coverage Notes @@ -178,11 +179,11 @@ updated whenever an artifact instance is created or reviewed. [RC-048]: ./reviews/rc-048-mil-004-outcome.md [RC-049]: ./reviews/rc-049-mil-006-outcome.md [RC-050]: ./reviews/rc-050-mil-005-outcome.md +[RC-051]: ./reviews/rc-051-adr-0005-tools-ref.md [ADR-0007]: ../adr/adr-0007-release-publish-destination.md [UC-007]: ../uc-007/uc.md [UC-006]: ../uc-006/uc.md [ADR-0006]: ../adr/adr-0006-release-process-and-version-pinning.md [UC-005]: ../uc-005/uc.md [ADR-0005]: ../adr/adr-0005-quality-check-contract.md -[d575f27]: https://git.tirsystem.com/TirSystem/github-action/commit/d575f272fcb044e3165951013464177a6e659ea6 [ead3d98]: https://git.tirsystem.com/TirSystem/github-action/commit/ead3d98d73fcab95fd57b0eb005a02aff9983256 diff --git a/guides/quality-checks.md b/guides/quality-checks.md index 9ba17b3..1ed9c37 100644 --- a/guides/quality-checks.md +++ b/guides/quality-checks.md @@ -32,12 +32,18 @@ jobs: | `mypy-version` | `1.11.2` | Version of mypy | | `shellcheck-py-version` | `0.10.0.1` | Version of the shellcheck-py package (ShellCheck 0.10.0) | | `shfmt-py-version` | `3.11.0.2` | Version of the shfmt-py package (shfmt 3.11.0) | +| `tools-ref` | `v1` | Tag or branch of `TirSystem/github-action` from which the script `tools/quality-checks.sh` is fetched | Reference the workflow at the major tag (`@v1`) or at an exact tag (`@v1.0.0`), never at `main` and never at the branch of a pull request: that branch is deleted when the pull request is merged and the reference stops working. See the upgrade guide for what each choice receives. +The workflow file calls a script of this repository, fetched at `tools-ref`. A +call at an exact tag is pinned completely only when `tools-ref` names the same +tag, for example `@v1.0.1` with `tools-ref: v1.0.1`; with the default the +script follows `v1`. + ## What is checked | Language | Files | Tools | diff --git a/guides/upgrading.md b/guides/upgrading.md index 60211fd..98d1afc 100644 --- a/guides/upgrading.md +++ b/guides/upgrading.md @@ -16,6 +16,10 @@ repository, tags `vMAJOR.MINOR.PATCH` and a moving major tag `vMAJOR`. A breaking release gets a new major version (`v2`), so a pipeline on `@v1` never receives it until you change the line yourself. +The quality-checks workflow fetches its script at its input `tools-ref`, which +defaults to `v1`. To pin it completely, call it at an exact tag and set +`tools-ref` to the same tag; otherwise the script follows the major tag. + The two scoped workflows (**Sync GitHub mirror metadata** and **Publish wiki**) are delivered by the instance and are not in your `uses:` lines; you cannot pin them, but they fetch their scripts at the major tag. diff --git a/tests/test_quality_workflow.py b/tests/test_quality_workflow.py index fcc6933..29ebab2 100644 --- a/tests/test_quality_workflow.py +++ b/tests/test_quality_workflow.py @@ -15,6 +15,8 @@ import yaml from workflow_source import REPO_ROOT WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "quality-checks.yml" +SCRIPT = REPO_ROOT / "tools" / "quality-checks.sh" +COMMANDS = {"Validate the inputs": "validate", "Install the tools": "install", "Run the checks": "run"} BASH = shutil.which("bash") pytestmark = pytest.mark.skipif(BASH is None, reason="bash is needed to run the workflow steps") @@ -25,7 +27,7 @@ def document() -> dict: return yaml.safe_load(WORKFLOW.read_text(encoding="utf-8")) -def step_script(document: dict, name: str) -> str: +def step_command(document: dict, name: str) -> str: steps = document["jobs"]["quality-checks"]["steps"] return next(step["run"] for step in steps if step.get("name") == name) @@ -40,9 +42,11 @@ class TestDefinition: "mypy-version", "shellcheck-py-version", "shfmt-py-version", + "tools-ref", } assert inputs["languages"]["default"] == "python,shell" assert inputs["working-directory"]["default"] == "." + assert inputs["tools-ref"]["default"] == "v1" def test_tool_versions_are_pinned_to_the_adr(self, document): inputs = document[True]["workflow_call"]["inputs"] @@ -60,14 +64,32 @@ class TestDefinition: for step in document["jobs"]["quality-checks"]["steps"]: assert "${{" not in step.get("run", ""), f"{step.get('name')} interpolates an expression" - @pytest.mark.parametrize( - "name", ["Validate the inputs", "Install the tools", "Run the checks"] - ) - def test_every_script_parses(self, document, name): - script = step_script(document, name) - result = subprocess.run([BASH, "-n"], input=script, text=True, capture_output=True) + def test_the_script_parses(self): + result = subprocess.run([BASH, "-n", str(SCRIPT)], text=True, capture_output=True) assert result.returncode == 0, result.stderr + def test_the_workflow_embeds_no_script_and_runs_the_file(self, document): + steps = document["jobs"]["quality-checks"]["steps"] + commands = [step["run"] for step in steps if "run" in step] + assert commands == [ + "bash shared-scripts/tools/quality-checks.sh validate", + "bash shared-scripts/tools/quality-checks.sh install", + "bash shared-scripts/tools/quality-checks.sh run", + ] + + def test_the_script_is_fetched_at_the_tools_ref_input(self, document): + steps = document["jobs"]["quality-checks"]["steps"] + fetch = next(step for step in steps if step.get("name") == "Fetch the shared script") + assert fetch["with"] == { + "repository": "TirSystem/github-action", + "ref": "${{ inputs.tools-ref }}", + "path": "shared-scripts", + } + + def test_an_unknown_command_is_refused(self): + result = subprocess.run([BASH, str(SCRIPT), "bogus"], text=True, capture_output=True) + assert result.returncode == 2 + STUB = """#!/usr/bin/env bash name="$(basename "$0")" @@ -111,7 +133,7 @@ def add_files(root: Path, *relative: str) -> None: def run_step(document, name, repo, bin_dir, tmp_path, **env): - script = step_script(document, name) + command = COMMANDS[name] log = tmp_path / "tools.log" environment = dict(os.environ) environment.update( @@ -126,7 +148,7 @@ def run_step(document, name, repo, bin_dir, tmp_path, **env): ) environment.update(env) result = subprocess.run( - [BASH, "-c", script], cwd=repo, env=environment, text=True, capture_output=True + [BASH, str(SCRIPT), command], cwd=repo, env=environment, text=True, capture_output=True ) calls = log.read_text(encoding="utf-8").splitlines() if log.exists() else [] return result, calls diff --git a/tools/quality-checks.sh b/tools/quality-checks.sh new file mode 100644 index 0000000..fb17eea --- /dev/null +++ b/tools/quality-checks.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# Steps of the reusable workflow .gitea/workflows/quality-checks.yml (ADR-0005). +# The workflow checks out the calling repository, fetches this file into +# shared-scripts/ and runs one command per step: +# +# quality-checks.sh validate check the inputs, before anything is installed +# quality-checks.sh install install the pinned tools into a virtual environment +# quality-checks.sh run run every selected tool on the tracked files +# +# Settings come from the environment, never from interpolated expressions: +# LANGUAGES, WORKING_DIRECTORY, RUFF_VERSION, MYPY_VERSION, +# SHELLCHECK_PY_VERSION, SHFMT_PY_VERSION, QC_VENV (and QC_BIN for tests). +# Every failure is printed as ::error::[category] message. + +validate_inputs() { + set -u + IFS=',' read -ra langs <<< "${LANGUAGES// /}" + if [ "${#langs[@]}" -eq 0 ]; then + echo "::error::[configuration] languages is empty; use python, shell or both, comma separated." + exit 1 + fi + for lang in "${langs[@]}"; do + case "$lang" in + python | shell) ;; + *) + echo "::error::[configuration] Unknown language '$lang'; use python and shell, comma separated." + exit 1 + ;; + esac + done + case "$WORKING_DIRECTORY" in + "" | /* | .. | ../* | */.. | */../*) + echo "::error::[configuration] working-directory must be a relative path inside the repository." + exit 1 + ;; + esac + echo "Checking ${LANGUAGES} in ${WORKING_DIRECTORY}" +} + +install_tools() { + set -eu + IFS=',' read -ra langs <<< "${LANGUAGES// /}" + packages=() + for lang in "${langs[@]}"; do + case "$lang" in + python) packages+=("ruff==${RUFF_VERSION}" "mypy==${MYPY_VERSION}") ;; + shell) packages+=("shellcheck-py==${SHELLCHECK_PY_VERSION}" "shfmt-py==${SHFMT_PY_VERSION}") ;; + esac + done + python3 -m venv "$QC_VENV" + "$QC_VENV/bin/pip" install --quiet "${packages[@]}" +} + +run_checks() { + set -u + QC_BIN="${QC_BIN:-$QC_VENV/bin}" + if [ ! -d "$WORKING_DIRECTORY" ]; then + echo "::error::[configuration] working-directory '$WORKING_DIRECTORY' does not exist." + exit 1 + fi + cd "$WORKING_DIRECTORY" + + failed=() + run_tool() { + local name="$1" + shift + echo "::group::$name" + if "$@"; then + echo "::endgroup::" + echo "PASS $name" + else + echo "::endgroup::" + echo "FAIL $name" + failed+=("$name") + fi + } + wanted() { + local lang + IFS=',' read -ra langs <<< "${LANGUAGES// /}" + for lang in "${langs[@]}"; do + [ "$lang" = "$1" ] && return 0 + done + return 1 + } + + if wanted python; then + mapfile -t files < <(git ls-files -- '*.py') + if [ "${#files[@]}" -eq 0 ]; then + echo "::notice::No python files in '$WORKING_DIRECTORY'; nothing was checked." + else + run_tool "ruff check" "$QC_BIN/ruff" check "${files[@]}" + run_tool "ruff format --check" "$QC_BIN/ruff" format --check "${files[@]}" + run_tool "mypy --strict" "$QC_BIN/mypy" --strict "${files[@]}" + fi + fi + + if wanted shell; then + mapfile -t files < <(git ls-files -- '*.sh') + if [ "${#files[@]}" -eq 0 ]; then + echo "::notice::No shell files in '$WORKING_DIRECTORY'; nothing was checked." + else + run_tool "shellcheck" "$QC_BIN/shellcheck" "${files[@]}" + run_tool "shfmt -d" "$QC_BIN/shfmt" -d "${files[@]}" + fi + fi + + if [ "${#failed[@]}" -gt 0 ]; then + echo "::error::Quality checks failed: ${failed[*]}" + exit 1 + fi + echo "All selected checks passed." +} + +case "${1:-}" in + validate) validate_inputs ;; + install) install_tools ;; + run) run_checks ;; + *) + echo "::error::[configuration] usage: quality-checks.sh validate|install|run" >&2 + exit 2 + ;; +esac diff --git a/wiki/QualityChecksPythonShell.da.md b/wiki/QualityChecksPythonShell.da.md index 1044dd7..2f8608f 100644 --- a/wiki/QualityChecksPythonShell.da.md +++ b/wiki/QualityChecksPythonShell.da.md @@ -1,4 +1,4 @@ - + > Dette er en oversættelse af [`QualityChecksPythonShell`](QualityChecksPythonShell). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. @@ -35,9 +35,12 @@ jobs: | `mypy-version` | `1.11.2` | Version af mypy | | `shellcheck-py-version` | `0.10.0.1` | Version af pakken shellcheck-py (ShellCheck 0.10.0) | | `shfmt-py-version` | `3.11.0.2` | Version af pakken shfmt-py (shfmt 3.11.0) | +| `tools-ref` | `v1` | Tag eller gren i `TirSystem/github-action`, hvorfra scriptet `tools/quality-checks.sh` hentes | Henvis til workflowet ved major-tagget (`@v1`) eller et præcist tag (`@v1.0.0`), aldrig ved `main` og aldrig ved en pull requests gren: den gren slettes, når pull requesten flettes, og henvisningen holder op med at virke. Se opgraderingsvejledningen for, hvad hvert valg modtager. +Workflowfilen kalder et script i dette repository, hentet ved `tools-ref`. Et kald ved et præcist tag er kun fuldt fastlåst, når `tools-ref` angiver samme tag, for eksempel `@v1.0.1` med `tools-ref: v1.0.1`; med standardværdien følger scriptet `v1`. + ## Hvad der kontrolleres | Sprog | Filer | Værktøjer | diff --git a/wiki/QualityChecksPythonShell.md b/wiki/QualityChecksPythonShell.md index d7bb238..b6b0a75 100644 --- a/wiki/QualityChecksPythonShell.md +++ b/wiki/QualityChecksPythonShell.md @@ -38,12 +38,18 @@ jobs: | `mypy-version` | `1.11.2` | Version of mypy | | `shellcheck-py-version` | `0.10.0.1` | Version of the shellcheck-py package (ShellCheck 0.10.0) | | `shfmt-py-version` | `3.11.0.2` | Version of the shfmt-py package (shfmt 3.11.0) | +| `tools-ref` | `v1` | Tag or branch of `TirSystem/github-action` from which the script `tools/quality-checks.sh` is fetched | Reference the workflow at the major tag (`@v1`) or at an exact tag (`@v1.0.0`), never at `main` and never at the branch of a pull request: that branch is deleted when the pull request is merged and the reference stops working. See the upgrade guide for what each choice receives. +The workflow file calls a script of this repository, fetched at `tools-ref`. A +call at an exact tag is pinned completely only when `tools-ref` names the same +tag, for example `@v1.0.1` with `tools-ref: v1.0.1`; with the default the +script follows `v1`. + ## What is checked | Language | Files | Tools | diff --git a/wiki/QualityChecksPythonShell.ms.md b/wiki/QualityChecksPythonShell.ms.md index acab59c..8445ea6 100644 --- a/wiki/QualityChecksPythonShell.ms.md +++ b/wiki/QualityChecksPythonShell.ms.md @@ -1,4 +1,4 @@ - + > Ini ialah terjemahan [`QualityChecksPythonShell`](QualityChecksPythonShell). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. @@ -35,9 +35,12 @@ jobs: | `mypy-version` | `1.11.2` | Versi mypy | | `shellcheck-py-version` | `0.10.0.1` | Versi pakej shellcheck-py (ShellCheck 0.10.0) | | `shfmt-py-version` | `3.11.0.2` | Versi pakej shfmt-py (shfmt 3.11.0) | +| `tools-ref` | `v1` | Tag atau cabang `TirSystem/github-action` tempat skrip `tools/quality-checks.sh` diambil | Rujuk aliran kerja pada tag major (`@v1`) atau pada tag tepat (`@v1.0.0`), jangan sekali-kali pada `main` dan jangan pada cabang pull request: cabang itu dipadam apabila pull request digabungkan dan rujukan itu berhenti berfungsi. Lihat panduan naik taraf untuk apa yang diterima oleh setiap pilihan. +Fail aliran kerja memanggil skrip dalam repositori ini, yang diambil pada `tools-ref`. Panggilan pada tag tepat hanya disematkan sepenuhnya apabila `tools-ref` menamakan tag yang sama, contohnya `@v1.0.1` dengan `tools-ref: v1.0.1`; dengan nilai lalai skrip mengikuti `v1`. + ## Perkara yang disemak | Bahasa | Fail | Alat | diff --git a/wiki/Upgrading.da.md b/wiki/Upgrading.da.md index 13c6f61..b4d6651 100644 --- a/wiki/Upgrading.da.md +++ b/wiki/Upgrading.da.md @@ -1,4 +1,4 @@ - + > Dette er en oversættelse af [`Upgrading`](Upgrading). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. @@ -18,6 +18,8 @@ Sådan tager en softwareudvikler en ny udgivelse af de fælles workflows i brug En udgivelse, der bryder kompatibiliteten, får en ny major-version (`v2`), så en pipeline på `@v1` aldrig modtager den, før du selv ændrer linjen. +Kvalitetskontrol-workflowet henter sit script ved sit input `tools-ref`, som som standard er `v1`. For at fastlåse det helt skal du kalde det ved et præcist tag og sætte `tools-ref` til samme tag; ellers følger scriptet major-tagget. + De to scoped workflows (**Sync GitHub mirror metadata** og **Publish wiki**) leveres af instansen og står ikke i dine `uses:`-linjer; du kan ikke fastlåse dem, men de henter deres scripts ved major-tagget. ## Aflæsning af en changelog-post diff --git a/wiki/Upgrading.md b/wiki/Upgrading.md index f52112e..9727ec6 100644 --- a/wiki/Upgrading.md +++ b/wiki/Upgrading.md @@ -22,6 +22,10 @@ repository, tags `vMAJOR.MINOR.PATCH` and a moving major tag `vMAJOR`. A breaking release gets a new major version (`v2`), so a pipeline on `@v1` never receives it until you change the line yourself. +The quality-checks workflow fetches its script at its input `tools-ref`, which +defaults to `v1`. To pin it completely, call it at an exact tag and set +`tools-ref` to the same tag; otherwise the script follows the major tag. + The two scoped workflows (**Sync GitHub mirror metadata** and **Publish wiki**) are delivered by the instance and are not in your `uses:` lines; you cannot pin them, but they fetch their scripts at the major tag. diff --git a/wiki/Upgrading.ms.md b/wiki/Upgrading.ms.md index a990ec3..4488646 100644 --- a/wiki/Upgrading.ms.md +++ b/wiki/Upgrading.ms.md @@ -1,4 +1,4 @@ - + > Ini ialah terjemahan [`Upgrading`](Upgrading). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. @@ -18,6 +18,8 @@ Cara seorang jurutera perisian menerima keluaran baharu aliran kerja bersama den Keluaran yang memecahkan keserasian mendapat versi major baharu (`v2`), jadi saluran paip pada `@v1` tidak menerimanya sehingga anda sendiri menukar baris itu. +Aliran kerja semakan kualiti mengambil skripnya pada inputnya `tools-ref`, yang lalai ialah `v1`. Untuk menyematkannya sepenuhnya, panggil pada tag tepat dan tetapkan `tools-ref` kepada tag yang sama; jika tidak, skrip mengikuti tag major. + Dua aliran kerja scoped (**Sync GitHub mirror metadata** dan **Publish wiki**) dihantar oleh instans dan tidak terdapat dalam baris `uses:` anda; anda tidak boleh menyematkannya, tetapi ia mengambil skripnya pada tag major. ## Membaca satu catatan changelog