diff --git a/.github/workflows/CheckCodeQuality.yml b/.github/workflows/CheckCodeQuality.yml index 0a3ab484..3ec10a84 100644 --- a/.github/workflows/CheckCodeQuality.yml +++ b/.github/workflows/CheckCodeQuality.yml @@ -86,7 +86,6 @@ jobs: - name: 👮 Bandit id: bandit - if: inputs.artifact != '' run: | set +e diff --git a/.github/workflows/CleanupArtifacts.yml b/.github/workflows/CleanupArtifacts.yml index 988a781e..84fcc872 100644 --- a/.github/workflows/CleanupArtifacts.yml +++ b/.github/workflows/CleanupArtifacts.yml @@ -96,7 +96,7 @@ jobs: case [prefix, key, postfix] if key in artifactNames: artifacts.append(f"{prefix}{artifactNames[key]}{postfix}") case _: - printf(f"Name '{name}' not found in JSON dictionary.") + print(f"Name '{name}' not found in JSON dictionary.") print("Artifact to delete:") for name in artifacts: @@ -143,7 +143,7 @@ jobs: case [prefix, key, postfix] if key in artifactNames: artifacts.append(f"{prefix}{artifactNames[key]}{postfix}") case _: - printf(f"Name '{name}' not found in JSON dictionary.") + print(f"Name '{name}' not found in JSON dictionary.") print("Artifact to delete:") for name in artifacts: diff --git a/.github/workflows/CompletePipeline.yml b/.github/workflows/CompletePipeline.yml index 5ec22429..5e896a2a 100644 --- a/.github/workflows/CompletePipeline.yml +++ b/.github/workflows/CompletePipeline.yml @@ -425,7 +425,11 @@ jobs: - UnitTestingParams - PublishCoverageResults - PublishTestResults - if: ${{ !cancelled() && inputs.cleanup == 'true' }} # not 'success() || failure()', because that's false if a dependency was skipped + # not 'success() || failure()', because that's false if a dependency was skipped + if: >- + ${{ !cancelled() + && inputs.cleanup == 'true' + }} with: json: ${{ needs.UnitTestingParams.outputs.artifact_names }} artifact-json-ids: >- @@ -437,7 +441,11 @@ jobs: needs: - UnitTestingParams - Documentation - if: contains(inputs.documentation_steps, 'latex') && contains(inputs.documentation_steps, 'pdf') + if: >- + ${{ !failure() && !cancelled() + && contains(inputs.documentation_steps, 'latex') + && contains(inputs.documentation_steps, 'pdf') + }} with: document: ${{ needs.UnitTestingParams.outputs.package_fullname }} latex_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_latex }} @@ -454,7 +462,10 @@ jobs: # - PDFDocumentation - PublishCoverageResults - StaticTypeCheck - if: contains(inputs.documentation_steps, 'pages') + if: >- + ${{ !failure() && !cancelled() + && contains(inputs.documentation_steps, 'pages') + }} with: doc: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_html }} coverage: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_html }} @@ -471,7 +482,11 @@ jobs: - Package - Install - PublishToGitHubPages - if: ${{ !failure() && !cancelled() && needs.Prepare.outputs.is_release_commit == 'true' && github.event_name != 'schedule' }} # !failure(): tolerate skipped dependencies (e.g. PublishToGitHubPages), but not failed ones + if: >- + ${{ !failure() && !cancelled() + && needs.Prepare.outputs.is_release_commit == 'true' + && github.event_name != 'schedule' + }} permissions: contents: write # required for create tag actions: write # required for trigger workflow @@ -490,7 +505,10 @@ jobs: - Package - Install - PublishToGitHubPages - if: ${{ !failure() && !cancelled() && needs.Prepare.outputs.is_release_tag == 'true' }} # !failure(): tolerate skipped dependencies (e.g. PublishToGitHubPages), but not failed ones + if: >- + ${{ !failure() && !cancelled() + && needs.Prepare.outputs.is_release_tag == 'true' + }} permissions: contents: write actions: write @@ -505,7 +523,10 @@ jobs: - UnitTestingParams - Package - ReleasePage - if: ${{ !failure() && !cancelled() && needs.Prepare.outputs.is_release_tag == 'true' }} # !failure(): tolerate skipped dependencies, but not failed ones + if: >- + ${{ !failure() && !cancelled() + && needs.Prepare.outputs.is_release_tag == 'true' + }} with: python_version: ${{ needs.UnitTestingParams.outputs.python_version }} requirements: '-r dist/requirements.txt' @@ -529,7 +550,11 @@ jobs: # - PublishOnPyPI - Install - IntermediateCleanUp - if: ${{ !cancelled() && inputs.cleanup == 'true' }} # !cancelled(), because PDFDocumentation is skipped without 'latex' and 'pdf' in documentation_steps + # !cancelled(), because PDFDocumentation is skipped without 'latex' and 'pdf' in documentation_steps + if: >- + ${{ !cancelled() + && inputs.cleanup == 'true' + }} with: json: ${{ needs.UnitTestingParams.outputs.artifact_names }} artifact-json-ids: >- diff --git a/.github/workflows/IntermediateCleanUp.yml b/.github/workflows/IntermediateCleanUp.yml index 1e647c4a..ae289874 100644 --- a/.github/workflows/IntermediateCleanUp.yml +++ b/.github/workflows/IntermediateCleanUp.yml @@ -45,6 +45,33 @@ jobs: name: 🗑️ Intermediate Artifact Cleanup runs-on: "ubuntu-${{ inputs.ubuntu_image_version }}" steps: + - name: ⚠️ Deprecation warning + shell: bash + run: | + printf "::warning title=Deprecated workflow::%s\n" \ + "'IntermediateCleanUp.yml' is deprecated and will be removed in r8. Use 'CleanupArtifacts.yml' instead." + printf "\n" + printf "%s\n" "'IntermediateCleanUp.yml' is deprecated and will be removed in r8." + printf "%s\n" "Use 'CleanupArtifacts.yml' instead, which resolves artifact names from the" + printf "%s\n" "'artifact_names' JSON dictionary produced by 'Parameters.yml', so the caller" + printf "%s\n" "no longer assembles artifact names itself." + printf "\n" + printf "%s\n" "Migration:" + printf "%s\n" " * Pass the whole 'artifact_names' dictionary of 'Parameters.yml' as 'json'," + printf "%s\n" " instead of assembling a prefix per artifact kind." + printf "%s\n" " * Both prefix parameters become entries of 'artifact-json-ids' using the" + printf "%s\n" " postfix form, which is what matches the per-matrix-job variants:" + printf "\n" + printf "%s\n" " artifact-json-ids: >-" + printf "%s\n" " codecoverage_sqlite:-*" + printf "%s\n" " unittesting_xml:-*" + printf "\n" + printf "%s\n" " * Give the job an 'if' containing a status check function, e.g. '!cancelled()'," + printf "%s\n" " so a skipped upstream job doesn't skip the cleanup." + printf "\n" + printf "%s\n" "See 'CompletePipeline.yml' for a worked example - it instantiates" + printf "%s\n" "'CleanupArtifacts.yml' for the intermediate as well as the final cleanup." + - name: 🗑️ Delete SQLite coverage artifacts from matrix jobs uses: geekyeggo/delete-artifact@v6 if: inputs.sqlite_coverage_artifacts_prefix != '' diff --git a/.github/workflows/PrepareJob.yml b/.github/workflows/PrepareJob.yml index 6cd1fd07..6ea6eecc 100644 --- a/.github/workflows/PrepareJob.yml +++ b/.github/workflows/PrepareJob.yml @@ -366,9 +366,9 @@ jobs: fi # Submodules - if [[ -f .gitsubmodules ]]; then + git_modules_file=.gitmodules # $(git rev-parse --show-toplevel)/.gitmodules + if [[ -f "${git_modules_file}" ]]; then has_submodules="true" - git_modules_file=.gitmodules # $(git rev-parse --show-toplevel)/.gitmodules git_submodule_count="$(grep -Po '(?<=\[submodule \")(.*)(?=\"\])' "${git_modules_file}" | wc -l)" git_submodule_names="$(grep -Po '(?<=\[submodule \")(.*)(?=\"\])' "${git_modules_file}" | paste -sd ':' -)" git_submodule_paths="$(git config --file "${git_modules_file}" --null --name-only --get-regexp '\.path$' | xargs -0 -n1 git config --file "${git_modules_file}" --get | paste -sd ':' -)" diff --git a/.github/workflows/_Checking_AvailableRunners.yml b/.github/workflows/_Checking_AvailableRunners.yml index c5b6770f..be415c2f 100644 --- a/.github/workflows/_Checking_AvailableRunners.yml +++ b/.github/workflows/_Checking_AvailableRunners.yml @@ -1,4 +1,5 @@ name: Testing available GitHub Action Images +run-name: ${{ github.ref_type == 'tag' && github.ref_name || '' }} on: push: diff --git a/.github/workflows/_Checking_CleanupArtifacts.yml b/.github/workflows/_Checking_CleanupArtifacts.yml index 1a755a73..5fedf489 100644 --- a/.github/workflows/_Checking_CleanupArtifacts.yml +++ b/.github/workflows/_Checking_CleanupArtifacts.yml @@ -1,4 +1,5 @@ name: Verification Pipeline for ArtifactCleanup +run-name: ${{ github.ref_type == 'tag' && github.ref_name || '' }} on: # push: @@ -57,8 +58,10 @@ jobs: - Package with: json: ${{ needs.Params.outputs.artifact_names }} + # Deliberate added 'unknown_key' for exception testing. artifact-json-ids: >- unittesting_xml:-* + unknown_key # The package artifact is kept on tagged runs, so 'PublishOnPyPI.yml' can still consume it. json2: ${{ needs.Params.outputs.artifact_names }} condition2: ${{ ! startsWith(github.ref, 'refs/tags') }} diff --git a/.github/workflows/_Checking_JobTemplates.yml b/.github/workflows/_Checking_JobTemplates.yml index 29af6ffa..cea08c35 100644 --- a/.github/workflows/_Checking_JobTemplates.yml +++ b/.github/workflows/_Checking_JobTemplates.yml @@ -1,4 +1,5 @@ name: Verification of Job Templates +run-name: ${{ github.ref_type == 'tag' && github.ref_name || '' }} on: push: diff --git a/.github/workflows/_Checking_NamespacePackage_Pipeline.yml b/.github/workflows/_Checking_NamespacePackage_Pipeline.yml index 7b3c4ad3..f2e27d24 100644 --- a/.github/workflows/_Checking_NamespacePackage_Pipeline.yml +++ b/.github/workflows/_Checking_NamespacePackage_Pipeline.yml @@ -1,4 +1,5 @@ name: Verification of Pipeline Templates (Namespace Package) +run-name: ${{ github.ref_type == 'tag' && github.ref_name || '' }} on: push: @@ -21,7 +22,7 @@ jobs: codecov: 'true' codacy: 'true' dorny: 'true' - documentation_steps: 'html' + documentation_steps: 'html latex pdf' auto_tag: 'false' secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/_Checking_Nightly.yml b/.github/workflows/_Checking_Nightly.yml index c318cc39..6d895956 100644 --- a/.github/workflows/_Checking_Nightly.yml +++ b/.github/workflows/_Checking_Nightly.yml @@ -1,4 +1,5 @@ name: Verification of Nightly Releases +run-name: ${{ github.ref_type == 'tag' && github.ref_name || '' }} on: # push: diff --git a/.github/workflows/_Checking_Parameters.yml b/.github/workflows/_Checking_Parameters.yml index 8a3e4665..03419517 100644 --- a/.github/workflows/_Checking_Parameters.yml +++ b/.github/workflows/_Checking_Parameters.yml @@ -1,4 +1,5 @@ name: Verification Pipeline for Parameters +run-name: ${{ github.ref_type == 'tag' && github.ref_name || '' }} on: push: diff --git a/.github/workflows/_Checking_SimplePackage_Pipeline.yml b/.github/workflows/_Checking_SimplePackage_Pipeline.yml index b91fb6f4..222aa407 100644 --- a/.github/workflows/_Checking_SimplePackage_Pipeline.yml +++ b/.github/workflows/_Checking_SimplePackage_Pipeline.yml @@ -1,4 +1,5 @@ name: Verification of Pipeline Templates (Simple Package) +run-name: ${{ github.ref_type == 'tag' && github.ref_name || '' }} on: push: diff --git a/doc/Dependency.rst b/doc/Dependency.rst index aeb90ca5..28293b01 100644 --- a/doc/Dependency.rst +++ b/doc/Dependency.rst @@ -6,14 +6,15 @@ This is a summary of dependencies used by the provided job templates. For more d * Actions provided by GitHub * :gh:`actions/checkout` - * :gh:`actions/upload-artifact` - * :gh:`actions/download-artifact` - * :gh:`actions/create-release` (unmaintained) * :gh:`actions/setup-python` + * :gh:`actions/github-script` + * :gh:`actions/upload-pages-artifact` + * :gh:`actions/deploy-pages` -* BuildTheDocs +* Actions provided by pyTooling - * :gh:`buildthedocs/btd` + * :term:`upload-artifact` (:gh:`pyTooling/upload-artifact`) + * :term:`download-artifact` (:gh:`pyTooling/download-artifact`) * Code Quality Services @@ -28,3 +29,42 @@ This is a summary of dependencies used by the provided job templates. For more d * :gh:`msys2/setup-msys2` * :gh:`geekyeggo/delete-artifact` + * :term:`gh` - preinstalled on GitHub runners. + * :term:`pyTooling/MiKTeX` (:dockerhub:`Docker image `) + +* System packages installed through ``apt`` + + * `graphviz `__ + * `zstd `__ + +* System packages installed through the caller's parameters + + These are **not** installed by the job templates. The templates only pass the package lists on, so the caller + decides which system packages a test needs: + + * ``apt`` - Debian/Ubuntu packages. + * ``brew`` - Homebrew packages on macOS. + * ``pacboy`` - MSYS2 packages, given in ``pacboy`` syntax. + +* Python packages installed through :term:`pip` + + * :term:`bandit` (:pypi:`PyPI package `) + * :term:`build` (:pypi:`PyPI package `) + * :term:`Coverage.py` (:pypi:`PyPI package `) + * :term:`docstr_coverage` (:pypi:`PyPI package `) + * :term:`interrogate` (:pypi:`PyPI package `) + * :term:`pyEDAA.Reports` (:pypi:`PyPI package `) + * :term:`pylint` (:pypi:`PyPI package `) + * :term:`radon` (:pypi:`PyPI package `) + * :term:`twine` (:pypi:`PyPI package `) + * :term:`wheel` (:pypi:`PyPI package `) + +* Python packages installed through the caller's requirements file + + These are **not** installed by the job templates. The templates only run the tools, so the caller decides the + version: + + * :term:`mypy` (:pypi:`PyPI package `) + * :term:`pytest` (:pypi:`PyPI package `) + * :term:`Coverage.py` (:pypi:`PyPI package `) + * :term:`Sphinx` (:pypi:`PyPI package `), its theme and its extensions diff --git a/doc/Deveopment.rst b/doc/Deveopment.rst index c7c2734b..e85c33fc 100644 --- a/doc/Deveopment.rst +++ b/doc/Deveopment.rst @@ -2,3 +2,128 @@ Development ########### .. todo:: Development - Explain how to write new job templates. + + +.. _DEV/ConditionalJobs: + +Conditional Jobs +**************** + +Almost every job template offers switches to disable parts of a pipeline: ``apptest``, ``documentation_steps``, +``cleanup``, ... A disabled job is not removed from the pipeline, it is *skipped*, and a skipped job influences all +jobs depending on it. Handling that influence correctly is the single most error-prone part of writing a job template, +therefore the rules are collected here. + + +.. _DEV/ConditionalJobs/StatusCheckFunctions: + +Status Check Functions +====================== + +GitHub Actions offers four *status check functions*, which can be used in a job's ``if`` expression: + ++---------------------+------------------------------------------------------------------------------------------+ +| Function | Result | ++=====================+==========================================================================================+ +| ``success()`` | ``true``, if no job the current job depends on failed or was skipped. | ++---------------------+------------------------------------------------------------------------------------------+ +| ``failure()`` | ``true``, if any job the current job depends on failed. | ++---------------------+------------------------------------------------------------------------------------------+ +| ``cancelled()`` | ``true``, if the workflow was cancelled. | ++---------------------+------------------------------------------------------------------------------------------+ +| ``always()`` | ``true``, always - even if the workflow was cancelled. | ++---------------------+------------------------------------------------------------------------------------------+ + +If a job has an ``if`` expression, but that expression contains **no** status check function at all, GitHub adds an +implicit ``success()``. This is the behavior that surprises most template authors, because such a condition reads like +a pure feature switch, while it also demands that every dependency succeeded. + +.. code-block:: yaml + + # This condition is evaluated as: success() && contains(inputs.documentation_steps, 'pages') + if: ${{ contains(inputs.documentation_steps, 'pages') }} + + +.. _DEV/ConditionalJobs/Propagation: + +Propagation of Skipped Jobs +=========================== + +The status check functions are not evaluated on the jobs listed in ``needs``, but on the **transitive dependency +closure** of the job: all jobs reachable from the current job by following ``needs`` edges. Therefore a skipped job +propagates its state to jobs it isn't directly connected to. + +An intermediate job does **not** stop that propagation. A job surviving on ``!cancelled()`` runs itself, but the +skipped ancestor remains part of the closure of all its successors: + +.. code-block:: + + AppTestingParams -> AppTesting -> PublishTestResults -> Documentation -> PDFDocumentation + (skipped by (skipped) (if: !cancelled() (if: !cancelled() (if: contains(...)) + apptest: false) - runs) - runs) => skipped! + +With ``apptest: false``, ``PDFDocumentation`` was skipped although its own condition was ``true`` and neither of its +two ``needs`` jobs was skipped. Symmetrically, a job outside the closure cannot suppress a job guarded by +``!failure()``. + +.. attention:: + + A skipped job is not an error, so nothing in the pipeline turns red. The affected jobs are simply missing from the + run, which makes this class of defect easy to overlook - the pipeline is green, but it did less than it claims. + + +.. _DEV/ConditionalJobs/Guidelines: + +Guidelines +========== + +Every job whose ``if`` expression contains a feature switch needs an explicit status check function, otherwise the +implicit ``success()`` links the switch to unrelated jobs: + +.. code-block:: yaml + + if: >- + ${{ !failure() && !cancelled() + && contains(inputs.documentation_steps, 'pages') + }} + +Which function to choose: + +* ``!failure() && !cancelled()`` - the job's own condition decides, a skipped dependency is tolerated, but a failed + dependency suppresses the job. This is the default for optional jobs (:ref:`JOBTMPL/PublishToGitHubPages`, + :ref:`JOBTMPL/LaTeXDocumentation`) as well as for release jobs, which must not run if any check failed. +* ``!cancelled()`` - the job runs regardless of the outcome of its dependencies. Use it for jobs collecting or + cleaning up results (:ref:`JOBTMPL/PublishTestResults`, :ref:`JOBTMPL/CleanupArtifacts`), because artifacts of a + failed run still need to be published or deleted. +* ``always()`` - avoid it. It also runs the job when the workflow was cancelled, which delays the cancellation. + +.. hint:: + + Do not use ``success() || failure()`` as a substitute for ``!cancelled()``. It is ``false`` if a dependency was + *skipped*, because a skipped dependency is neither a success nor a failure. + +Conditions combining a status check function with further terms are written as a folded block scalar, one term per +line, so a condition can be read - and reviewed - without horizontal scrolling: + +.. code-block:: yaml + + if: >- + ${{ !failure() && !cancelled() + && needs.Prepare.outputs.is_release_commit == 'true' + && github.event_name != 'schedule' + }} + + +.. _DEV/ConditionalJobs/Verification: + +Verification +============ + +A skipped job cannot be detected by looking at a green pipeline, so each combination needs a verification pipeline that +actually exercises it. The templates in :file:`.github/workflows/_Checking_*.yml` cover the relevant combinations: +:file:`_Checking_SimplePackage_Pipeline.yml` runs with application testing enabled, while +:file:`_Checking_NamespacePackage_Pipeline.yml` disables it and requests ``html latex pdf``, so it combines a skipped +job with jobs conditioned on ``documentation_steps``. + +When a new switch is added to a job template, add a combination disabling it to one of these pipelines and check the +list of executed jobs of the resulting run, not only its conclusion. diff --git a/doc/Glossary.rst b/doc/Glossary.rst index 1ae21e3d..b45fa53c 100644 --- a/doc/Glossary.rst +++ b/doc/Glossary.rst @@ -41,6 +41,14 @@ Glossary :Marketplace: `github.com/marketplace/actions/delete-artifact/ `__ :README: `github.com/GeekyEggo/delete-artifact → README.md `__ + download-artifact + GitHub Action downloading artifacts within a workflow run. :gh:`pyTooling/download-artifact` wraps + :gh:`actions/download-artifact` and unpacks the tarball created by :term:`upload-artifact`, so file modes and + symbolic links are restored. + + :Source Code: `github.com/pyTooling/download-artifact/ `__ + :Marketplace: `github.com/marketplace/actions/pytooling-download-artifact/ `__ + docstr_coverage Docstring coverage analysis and rating for Python. @@ -73,6 +81,14 @@ Glossary :Source Code: `github.com/MiKTeX/miktex/ `__ :Documentation: `miktex.org/ `__ + pyTooling/MiKTeX + Docker images shipping a :term:`MikTeX` installation with ``latexmk`` and a set of preinstalled LaTeX packages, + so a LaTeX document can be translated to PDF without downloading packages at build time. The ``sphinx`` tag adds + the packages :term:`Sphinx` emits into its LaTeX output. + + :Source Code: `github.com/pyTooling/MiKTeX/ `__ + :Docker Image: :dockerhub:`pytooling/miktex ` + mypy Optional static typing for Python. @@ -94,6 +110,15 @@ Glossary :Package: `pypi.org/project/pip/ `__ :Documentation: `pip.pypa.io/ `__ + pylint + Static code analyzer for Python. It checks a code base without running it and reports errors, violations of + coding conventions, code smells and refactoring suggestions. Each finding has a category and an identifier + (e.g. ``C0116``), so individual checks can be enabled, disabled or configured per project. + + :Source Code: `github.com/pylint-dev/pylint/ `__ + :Package: `pypi.org/project/pylint/ `__ + :Documentation: `pylint.readthedocs.io/ `__ + PyPI Find, install and publish Python packages with the Python Package Index. @@ -106,6 +131,20 @@ Glossary :Package: `pypi.org/project/pytest/ `__ :Documentation: `pytest.org/ `__ + radon + Computes code metrics for Python code: + + * **Raw metrics** - lines of code, logical lines, comment lines, blank lines. + * **Cyclomatic complexity** - the number of linearly independent paths through a function, graded from *A* to + *F*. + * **Halstead metrics** - vocabulary, length, volume, difficulty, effort and the estimated number of bugs, + derived from the operators and operands in the code. + * **Maintainability index** - a single score combining cyclomatic complexity, lines of code and comment ratio. + + :Source Code: `github.com/rubik/radon/ `__ + :Package: `pypi.org/project/radon/ `__ + :Documentation: `radon.readthedocs.io/ `__ + Sphinx The Sphinx documentation generator. @@ -120,9 +159,25 @@ Glossary :Marketplace: `github.com/marketplace/actions/test-reporter/ `__ :README: `github.com/dorny/test-reporter → README.md `__ + upload-artifact + GitHub Action uploading artifacts within a workflow run. :gh:`pyTooling/upload-artifact` wraps + :gh:`actions/upload-artifact` and packs the uploaded files into a tarball first, because the original action + does not preserve file modes and symbolic links. + + :Source Code: `github.com/pyTooling/upload-artifact/ `__ + :Marketplace: `github.com/marketplace/actions/pytooling-upload-artifact/ `__ + twine Utilities for interacting with PyPI. :Source Code: `github.com/pypa/twine/ `__ :Package: `pypi.org/project/twine/ `__ :Documentation: `twine.readthedocs.io/ `__ + + wheel + Reference implementation of the Python wheel packaging standard. It is installed alongside :term:`pip` by most + job templates, because a source distribution that has to be built during installation needs it. + + :Source Code: `github.com/pypa/wheel/ `__ + :Package: `pypi.org/project/wheel/ `__ + :Documentation: `wheel.readthedocs.io/ `__ diff --git a/doc/Instantiation.rst b/doc/Instantiation.rst index 66773b3c..979889c3 100644 --- a/doc/Instantiation.rst +++ b/doc/Instantiation.rst @@ -42,7 +42,7 @@ to handover input parameters to the template. jobs: : - uses: //.github/workflows/