Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .copier-answers.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Changes here will be overwritten by Copier
_commit: v0.0.120
_commit: v0.0.120-6-gf5455ba
_src_path: gh:LabAutomationAndScreening/copier-base-template.git
description: A web app that is hosted within a local intranet. Nuxt frontend, python
backend, docker-compose
Expand All @@ -15,6 +15,7 @@ ssh_port_number: 15173
template_might_want_to_install_aws_ssm_port_forwarding_plugin: true
template_might_want_to_use_python_asyncio: true
template_might_want_to_use_vcrpy: true
template_publishes_releases: true
template_uses_javascript: true
template_uses_pulumi: false
template_uses_python: true
Expand Down
11 changes: 11 additions & 0 deletions .github/reusable_workflows/build-docker-image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ on:
type: boolean
required: false
default: false
release-tag:
description: 'Optional release tag (e.g. 1.0.6) to push to ECR; applied even if an image with the same build-context-hash already exists'
type: string
required: false
default: ''
context:
description: 'Build context path'
type: string
Expand Down Expand Up @@ -166,6 +171,12 @@ jobs:
aws ecr put-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-tag production--${{ steps.calculate-build-context-hash.outputs.build_context_tag }} --image-manifest file://manifest.json || true
aws ecr put-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-tag production--git-sha-${{ github.sha }} --image-manifest file://manifest.json

- name: Add release tag
if: ${{ inputs.push-role-name != 'no-push' && inputs.release-tag != '' }}
run: |
aws ecr batch-get-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-ids imageTag=${{ steps.calculate-build-context-hash.outputs.build_context_tag }} --query 'images[].imageManifest' --output text > manifest.json
aws ecr put-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-tag ${{ inputs.release-tag }} --image-manifest file://manifest.json

- name: Save Docker Image as tar
if: ${{ inputs.save-as-artifact }}
run: docker save -o ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }}.tar ${{ steps.calculate-build-context-hash.outputs.full_image_tag }}
Expand Down
2 changes: 1 addition & 1 deletion extensions/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def hook( # noqa: PLR0915 # yes, this is a lot of statements, but it's all just
context["gha_configure_aws_credentials"] = "v6.1.0"
context["gha_amazon_ecr_login"] = "v2.1.5"
context["gha_setup_node"] = "v6.3.0"
context["gha_action_gh_release"] = "v2.2.1"
context["gha_action_gh_release"] = "v3.0.0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does softprops/action-gh-release v3.0.0 exist and what are the breaking changes from v2.2.1?

💡 Result:

Yes, softprops/action-gh-release v3.0.0 exists, released on 2026-04-12.[1][2][3] The primary breaking change from v2.2.1 (and the v2 series) to v3.0.0 is the upgrade of the action runtime from Node 20 to Node 24, requiring GitHub-hosted runners or self-hosted fleets that support Node 24. Users still needing Node 20 compatibility should remain on v2.6.2.[1][2][4][5] No other breaking changes to inputs, outputs, or behavior are mentioned in the v3.0.0 release notes; it primarily updates the runtime, Node types, and tag management.[1][4]

Citations:


Confirm Node 24 compatibility before updating to softprops/action-gh-release v3.0.0

The update from v2.2.1 to v3.0.0 includes a breaking change: the runtime upgrades from Node 20 to Node 24. Ensure your GitHub-hosted runners or self-hosted fleet support Node 24; otherwise, remain on v2.6.2 for Node 20 compatibility.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/context.py` at line 101, The context key
context["gha_action_gh_release"] currently set to "v3.0.0" may require Node 24;
confirm runner compatibility and either revert to a Node-20-compatible tag
(e.g., "v2.6.2") or make the value conditional based on an environment/feature
flag; update context["gha_action_gh_release"] accordingly and add a short
comment noting the Node runtime requirement so future changes reference the Node
24 breaking change.

context["gha_mutex"] = "1ebad517141198e08d47cf72f3c0975316620a65 # v1.0.0-alpha.10"
context["gha_pypi_publish"] = "v1.14.0"
context["gha_sleep"] = "v2.0.3"
Expand Down
46 changes: 46 additions & 0 deletions template/.github/workflows/extract_project_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import argparse
import json
import tomllib
from pathlib import Path


def extract_version(file_path: Path | str) -> str:
path = Path(file_path)

if path.name == "package.json":
data = json.loads(path.read_text())
if version := data.get("version"):
return version
raise KeyError(f"No version field found in {path!r}")

if path.name == "pyproject.toml":
with path.open("rb") as f:
data = tomllib.load(f)
project = data.get("project", {})
if version := project.get("version"):
return version
tool = data.get("tool", {})
if version := tool.get("poetry", {}).get("version"):
return version
raise KeyError(f"No version field found in {path!r}")

raise ValueError(f"Unsupported file type {path.name!r}; expected pyproject.toml or package.json")


def main() -> None:
parser = argparse.ArgumentParser(
description="Extract the version from a pyproject.toml or package.json file and print it."
)
_ = parser.add_argument(
"file",
nargs="?",
default="pyproject.toml",
help="Path to pyproject.toml or package.json (default: pyproject.toml)",
)
args = parser.parse_args()

print(extract_version(args.file)) # noqa: T201 # specifically printing this out so CI pipelines can read the value from stdout


if __name__ == "__main__":
main()
58 changes: 58 additions & 0 deletions template/.github/workflows/git_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import argparse
import subprocess


def ensure_tag_not_present(tag: str, remote: str) -> None:
try:
_ = subprocess.run( # noqa: S603 # this is trusted input, it's our own arguments being passed in
["git", "ls-remote", "--exit-code", "--tags", remote, f"refs/tags/{tag}"], # noqa: S607 # if `git` isn't in PATH already, then there are bigger problems to solve
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
raise Exception(f"Error: tag '{tag}' exists on remote '{remote}'") # noqa: TRY002 # not worth a custom exception
except subprocess.CalledProcessError:
# tag not present, continue
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider keyword-only parameters for clarity.

The ensure_tag_not_present function would benefit from keyword-only parameters to make call sites more explicit about which argument is the tag and which is the remote. As per coding guidelines, preferring keyword-only parameters improves maintainability.

♻️ Proposed refactor
-def ensure_tag_not_present(tag: str, remote: str) -> None:
+def ensure_tag_not_present(*, tag: str, remote: str) -> None:

Then update call sites:

-        ensure_tag_not_present(tag, args.remote)
+        ensure_tag_not_present(tag=tag, remote=args.remote)

And:

-        ensure_tag_not_present(tag, args.remote)
+        ensure_tag_not_present(tag=tag, remote=args.remote)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@template/.github/workflows/git_tag.py` around lines 5 - 16, The function
ensure_tag_not_present has positional parameters tag and remote; make them
keyword-only to improve call-site clarity by changing the signature so tag and
remote must be passed as keywords (e.g., add a bare * in the parameter list),
then update all call sites to invoke ensure_tag_not_present(tag=..., remote=...)
so callers are explicit about which value is the tag vs remote.



def main() -> None:
parser = argparse.ArgumentParser(
description=("Confirm that git tag v<version> is not present on a remote, or create and push the tag.")
)
_ = parser.add_argument(
"--version",
required=True,
help="Version string (e.g. 1.0.6); the tag will be v<version>",
)
_ = parser.add_argument(
"--confirm-tag-not-present",
action="store_true",
help="Check that git tag v<version> is NOT present on the remote. If the tag exists, exit with an error.",
)
_ = parser.add_argument(
"--push-tag-to-remote",
action="store_true",
help="Create git tag v<version> locally and push it to the remote. Internally confirms the tag is not already present.",
)
_ = parser.add_argument(
"--remote",
default="origin",
help="Name of git remote to query/push (default: origin)",
)
args = parser.parse_args()

tag = args.version if args.version.startswith("v") else f"v{args.version}"

if args.push_tag_to_remote:
ensure_tag_not_present(tag, args.remote)
_ = subprocess.run(["git", "tag", tag], check=True) # noqa: S603,S607 # this is trusted input, it's our own pyproject.toml file. and if `git` isn't in PATH, then there are larger problems anyway
_ = subprocess.run(["git", "push", args.remote, tag], check=True) # noqa: S603,S607 # this is trusted input, it's our own pyproject.toml file. and if `git` isn't in PATH, then there are larger problems anyway
return

if args.confirm_tag_not_present:
ensure_tag_not_present(tag, args.remote)

Comment on lines +49 to +57

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add success logging for workflow visibility.

The script completes silently on success. In CI logs, explicit confirmation messages improve debugging and auditability.

📊 Proposed enhancement
     if args.push_tag_to_remote:
         ensure_tag_not_present(tag, args.remote)
         _ = subprocess.run(["git", "tag", tag], check=True)  # noqa: S603,S607 # this is trusted input, it's our own pyproject.toml file. and if `git` isn't in PATH, then there are larger problems anyway
         _ = subprocess.run(["git", "push", args.remote, tag], check=True)  # noqa: S603,S607 # this is trusted input, it's our own pyproject.toml file. and if `git` isn't in PATH, then there are larger problems anyway
+        print(f"✓ Tag {tag} created and pushed to {args.remote}")
         return
 
     if args.confirm_tag_not_present:
         ensure_tag_not_present(tag, args.remote)
+        print(f"✓ Tag {tag} confirmed not present on {args.remote}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if args.push_tag_to_remote:
ensure_tag_not_present(tag, args.remote)
_ = subprocess.run(["git", "tag", tag], check=True) # noqa: S603,S607 # this is trusted input, it's our own pyproject.toml file. and if `git` isn't in PATH, then there are larger problems anyway
_ = subprocess.run(["git", "push", args.remote, tag], check=True) # noqa: S603,S607 # this is trusted input, it's our own pyproject.toml file. and if `git` isn't in PATH, then there are larger problems anyway
return
if args.confirm_tag_not_present:
ensure_tag_not_present(tag, args.remote)
if args.push_tag_to_remote:
ensure_tag_not_present(tag, args.remote)
_ = subprocess.run(["git", "tag", tag], check=True) # noqa: S603,S607 # this is trusted input, it's our own pyproject.toml file. and if `git` isn't in PATH, then there are larger problems anyway
_ = subprocess.run(["git", "push", args.remote, tag], check=True) # noqa: S603,S607 # this is trusted input, it's our own pyproject.toml file. and if `git` isn't in PATH, then there are larger problems anyway
print(f"✓ Tag {tag} created and pushed to {args.remote}")
return
if args.confirm_tag_not_present:
ensure_tag_not_present(tag, args.remote)
print(f"✓ Tag {tag} confirmed not present on {args.remote}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@template/.github/workflows/git_tag.py` around lines 49 - 57, Add explicit
success logs after the tag creation/push and after the confirm-only flow: when
handling args.push_tag_to_remote (after subprocess.run(["git", "tag", tag"],
...) and after subprocess.run(["git", "push", args.remote, tag"], ...)) emit an
informational message indicating the tag was created and pushed (include tag and
remote); likewise, when args.confirm_tag_not_present triggers
ensure_tag_not_present, emit an informational message confirming the tag is
absent. Use the module logger (e.g., logging.getLogger(__name__).info) or a
clear print statement so CI logs show success for the functions/branches
involving args.push_tag_to_remote, ensure_tag_not_present, and
args.confirm_tag_not_present.


if __name__ == "__main__":
main()
165 changes: 165 additions & 0 deletions template/.github/workflows/release.yaml.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
{% raw %}name: Release

on:
workflow_dispatch:
inputs:
dry_run:
description: "Dry run (build only, skip release)"
type: boolean
default: false

env:
PYTHONUNBUFFERED: True

concurrency:
group: release
cancel-in-progress: false

jobs:
guard:
runs-on: {% endraw %}{{ gha_linux_runner }}{% raw %}
timeout-minutes: {% endraw %}{{ gha_short_timeout_minutes }}{% raw %}
permissions:
contents: read # needed for checkout and git ls-remote
statuses: read # needed to read required-check commit status
actions: read # needed to query CI workflow runs
outputs:
version: ${{ steps.get-version.outputs.version }}
ci-run-id: ${{ steps.get-ci-run-id.outputs.ci-run-id }}
docker-image-tag: ${{ steps.set-docker-image-tag.outputs.docker-image-tag }} # empty on dry run to skip ECR tag push
steps:
- name: Fail if real release is not on main
if: ${{ !inputs.dry_run && github.ref != 'refs/heads/main' }}
run: |
echo "Real releases must run from main. Current ref: ${{ github.ref }}"
exit 1
- name: Check required-check CI status for this commit
env:
GH_TOKEN: ${{ github.token }}
run: |
STATUS=$(gh api "/repos/${{ github.repository }}/commits/${{ github.sha }}/statuses" --jq '[.[] | select(.context == "required-check")] | first | .state // "not found"')
echo "required-check status for ${{ github.sha }}: $STATUS"
if [ "$STATUS" != "success" ] && [ "${{ inputs.dry_run }}" != "true" ]; then
echo "Wait for CI to pass before releasing."
exit 1
fi
Comment on lines +53 to +65

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# Find the file and examine its content
find . -name "release.yaml.jinja" -type f

Repository: LabAutomationAndScreening/copier-nuxt-python-intranet-app

Length of output: 149


🌐 Web query:

gh api pagination default behavior GitHub CLI documentation

💡 Result:

The default behavior of gh api in GitHub CLI does not automatically paginate results. By default, it fetches only the first page of results from the GitHub API [1]. To fetch all pages, use the --paginate flag, which makes additional HTTP requests following the next link in response headers for REST APIs or using endCursor and pageInfo for GraphQL queries until no more pages exist [1][2]. Each page is output as a separate JSON array or object; combine with --slurp to wrap multiple pages into a single JSON array [1]. Without --paginate, users must manually handle pagination using query parameters like page or per_page [3][4].

Citations:


🏁 Script executed:

cat -n ./template/.github/workflows/release.yaml.jinja | sed -n '40,75p'

Repository: LabAutomationAndScreening/copier-nuxt-python-intranet-app

Length of output: 2566


🏁 Script executed:

# Check the full guard job context to understand the flow
cat -n ./template/.github/workflows/release.yaml.jinja | sed -n '19,95p'

Repository: LabAutomationAndScreening/copier-nuxt-python-intranet-app

Length of output: 4596


gh api does not paginate by default — workflow-summary can be silently missed.

gh api without --paginate returns only the first page of results (default 30 items, max per_page=100). The Jobs API (/actions/runs/{id}/jobs) often returns >30 jobs in CI workflows that use OS/Python matrices, and workflow-summary is typically the last job (it depends on all others), so it can fall on a later page. When that happens, the jq filter yields an empty $CONCLUSION and:

  • on a real release this fails the guard with a misleading "workflow-summary did not succeed" message,
  • on a dry run the bypass on line 62 silently lets the workflow continue without ever verifying workflow-summary.

Fetch all pages (or at least bump per_page to 100) so the lookup is reliable:

🔧 Suggested fix
-          JOBS=$(gh api "/repos/${{ github.repository }}/actions/runs/${{ steps.get-ci-run-id.outputs.ci-run-id }}/jobs")
+          JOBS=$(gh api --paginate "/repos/${{ github.repository }}/actions/runs/${{ steps.get-ci-run-id.outputs.ci-run-id }}/jobs?per_page=100" \
+            | jq -s '{jobs: [.[].jobs[]]}')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@template/.github/workflows/release.yaml.jinja` around lines 53 - 65, The gh
api call that populates JOBS can miss later jobs because it isn't paginated;
update the command that sets JOBS (currently JOBS=$(gh api "/repos/${{
github.repository }}/actions/runs/${{ steps.get-ci-run-id.outputs.ci-run-id
}}/jobs")) to request all pages (e.g. add --paginate) or at minimum include
per_page=100 so the subsequent jq lookup for the workflow-summary job
(CONCLUSION variable and the "Verify workflow-summary passed in CI run" step)
reliably finds the job; keep the rest of the logic (jq filter and dry_run check)
the same.

- name: Checkout code
uses: actions/checkout@{% endraw %}{{ gha_checkout }}{% raw %}
- name: Extract project version
id: get-version
run: |
VERSION=$(python3 .github/workflows/extract_project_version.py {% endraw %}{% if has_backend %}{% raw %}backend/pyproject.toml{% endraw %}{% else %}{% raw %}frontend/package.json{% endraw %}{% endif %}{% raw %})
echo "Project version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Get CI run ID for this commit
id: get-ci-run-id
env:
GH_TOKEN: ${{ github.token }}
run: |
RUN_ID=$(gh api "/repos/${{ github.repository }}/actions/workflows/ci.yaml/runs?head_sha=${{ github.sha }}&status=success" --jq '.workflow_runs[0].id')
if [ "$RUN_ID" = "null" ] || [ -z "$RUN_ID" ]; then
echo "No successful CI run found for ${{ github.sha }}. Wait for CI to complete before releasing."
exit 1
fi
echo "ci-run-id=$RUN_ID" >> $GITHUB_OUTPUT
- name: Check version tag does not already exist
run: |
VERSION="${{ steps.get-version.outputs.version }}"
if python3 .github/workflows/git_tag.py --version "$VERSION" --confirm-tag-not-present; then
echo "Tag v$VERSION does not exist yet."
else
if [ "${{ inputs.dry_run }}" = "true" ]; then
echo "Tag v$VERSION already exists (dry run, continuing)."
else
echo "Tag v$VERSION already exists. Bump the version before releasing."
exit 1
fi
fi
- name: Set Docker image tag
id: set-docker-image-tag
run: |
if [ "${{ inputs.dry_run }}" = "true" ]; then
echo "docker-image-tag=" >> $GITHUB_OUTPUT
else
echo "docker-image-tag=${{ steps.get-version.outputs.version }}" >> $GITHUB_OUTPUT
fi

{% endraw %}{% if not deploy_as_executable and push_to_ecr %}{% raw %}
tag-frontend-image:
name: Tag Frontend Image
needs:
- guard
uses: ./.github/workflows/build-docker-image.yaml
permissions:
contents: write # needed for mutex
id-token: write # needed to assume OIDC roles for ECR push
with:
context: ./frontend
build-contexts: repo-root=.
additional-hash-paths: pnpm-workspace.yaml pnpm-lock.yaml
repository: {% endraw %}{{ frontend_ecr_base_url }}{% raw %}
image_name: {% endraw %}{{ frontend_image_name }}{% raw %}
push-role-name: {% endraw %}{{ frontend_ecr_push_role_name }}{% raw %}
release-tag: ${{ needs.guard.outputs.docker-image-tag }}

Comment on lines +96 to +113
{% endraw %}{% if has_backend %}{% raw %}
tag-backend-image:
name: Tag Backend Image
needs:
- guard
uses: ./.github/workflows/build-docker-image.yaml
permissions:
contents: write # needed for mutex
id-token: write # needed to assume OIDC roles for ECR push
with:
context: ./backend
repository: {% endraw %}{{ backend_ecr_base_url }}{% raw %}
image_name: {% endraw %}{{ backend_image_name }}{% raw %}
push-role-name: {% endraw %}{{ backend_ecr_push_role_name }}{% raw %}
release-tag: ${{ needs.guard.outputs.docker-image-tag }}

{% endraw %}{% endif %}{% endif %}{% raw %}
release:
name: Create GitHub Release
runs-on: {% endraw %}{{ gha_linux_runner }}{% raw %}
timeout-minutes: {% endraw %}{{ gha_short_timeout_minutes }}{% raw %}
needs:
- guard{% endraw %}{% if not deploy_as_executable and push_to_ecr %}{% raw %}
- tag-frontend-image{% endraw %}{% if has_backend %}{% raw %}
- tag-backend-image{% endraw %}{% endif %}{% endif %}{% raw %}
permissions:
contents: write # needed to create GitHub releases
actions: read # needed to download artifacts from CI runs
steps:{% endraw %}{% if deploy_as_executable %}{% raw %}
{% endraw %}{% set os_runners = [gha_linux_runner] + ([gha_windows_runner] if use_windows_in_ci else []) %}{% for runner in os_runners %}{% set ext = "tar" if runner == gha_linux_runner else "zip" %}{% raw %}
- name: Download executable artifact ({% endraw %}{{ runner }}{% raw %})
uses: actions/download-artifact@{% endraw %}{{ gha_download_artifact }}{% raw %}
with:
name: built-{% endraw %}{{ repo_name }}-{{ runner }}{% raw %}
path: artifacts/{% endraw %}{{ runner }}{% raw %}
run-id: ${{ needs.guard.outputs.ci-run-id }}
github-token: ${{ github.token }}
- name: Prepare release asset ({% endraw %}{{ runner }}{% raw %})
run: {% endraw %}{% if runner == gha_linux_runner %}mv artifacts/{{ runner }}/app.tar {{ repo_name }}-{{ runner }}-v{% raw %}${{ needs.guard.outputs.version }}{% endraw %}.{{ ext }}{% else %}(cd artifacts/{{ runner }} && zip -r ../../{{ repo_name }}-{{ runner }}-v{% raw %}${{ needs.guard.outputs.version }}{% endraw %}.{{ ext }} .){% endif %}
{% endfor %}{% endif %}{% if has_circuit_python_backend_template_been_instantiated %}{% raw %}
- name: Download firmware artifact
uses: actions/download-artifact@{% endraw %}{{ gha_download_artifact }}{% raw %}
with:
name: {% endraw %}{{ repo_name.removesuffix('-driver') }}{% raw %}-firmware-v${{ needs.guard.outputs.version }}
path: artifacts/firmware
run-id: ${{ needs.guard.outputs.ci-run-id }}
github-token: ${{ github.token }}
- name: Package firmware for release
run: zip -r {% endraw %}{{ repo_name.removesuffix('-driver') }}{% raw %}-firmware-v${{ needs.guard.outputs.version }}.zip artifacts/firmware/
{% endraw %}{% endif %}{% raw %}
- name: Create GitHub Release
if: ${{ !inputs.dry_run }}
uses: softprops/action-gh-release@{% endraw %}{{ gha_action_gh_release }}{% raw %}
with:
tag_name: v${{ needs.guard.outputs.version }}
name: {% endraw %}{{ repo_name }}{% raw %} v${{ needs.guard.outputs.version }}
generate_release_notes: true{% endraw %}{% if deploy_as_executable or has_circuit_python_backend_template_been_instantiated %}{% raw %}
fail_on_unmatched_files: true
files: |{% endraw %}{% if deploy_as_executable %}{% for runner in ([gha_linux_runner] + ([gha_windows_runner] if use_windows_in_ci else [])) %}{% set ext = "tar" if runner == gha_linux_runner else "zip" %}
{{ repo_name }}-{{ runner }}-v{% raw %}${{ needs.guard.outputs.version }}{% endraw %}.{{ ext }}{% endfor %}{% endif %}{% if has_circuit_python_backend_template_been_instantiated %}
{{ repo_name.removesuffix('-driver') }}-firmware-v{% raw %}${{ needs.guard.outputs.version }}{% endraw %}.zip{% endif %}{% endif %}
Loading