From d3e321b85f51b8d137767e2cf735d79eadb65939 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 8 Sep 2025 12:57:16 +0000 Subject: [PATCH 01/13] copier --- .copier-answers.yml | 2 +- .devcontainer/devcontainer.json | 10 +-- .devcontainer/install-ci-tooling.py | 69 ++++++++++------- .devcontainer/manual-setup-deps.sh | 52 ------------- .github/actions/install_deps/action.yml | 13 +++- .../update-devcontainer-hash/action.yml | 2 +- .../build-docker-image.yaml | 14 +++- .github/workflows/ci.yaml | 4 +- .github/workflows/get-values.yaml | 2 +- .github/workflows/pre-commit.yaml | 7 +- .github/workflows/tag-on-merge.yaml | 2 +- .pre-commit-config.yaml | 6 +- extensions/context.py | 77 +++++++++++-------- pyproject.toml | 10 +-- .../.devcontainer/devcontainer.json.jinja | 12 +-- .../.devcontainer/install-ci-tooling.py.jinja | 61 +++++++++------ .../.github/actions/install_deps/action.yml | 13 +++- .../update-devcontainer-hash/action.yml | 2 +- template/.github/workflows/get-values.yaml | 2 +- template/.github/workflows/pre-commit.yaml | 7 +- template/.pre-commit-config.yaml | 6 +- uv.lock | 35 +++++---- 22 files changed, 209 insertions(+), 199 deletions(-) delete mode 100644 .devcontainer/manual-setup-deps.sh diff --git a/.copier-answers.yml b/.copier-answers.yml index 9f14f496..a478b72a 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: v0.0.64 +_commit: v0.0.69 _src_path: gh:LabAutomationAndScreening/copier-base-template.git description: Copier template for creating Python libraries and executables python_ci_versions: diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 930c4f9c..88991bc0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -23,12 +23,12 @@ "eamodio.gitlens@15.5.1", "ms-vscode.live-server@0.5.2025051301", "MS-vsliveshare.vsliveshare@1.0.5905", - "github.copilot@1.320.1564", - "github.copilot-chat@0.28.2025051402", + "github.copilot@1.366.1775", + "github.copilot-chat@0.31.2025090401", // Python - "ms-python.python@2025.7.2025051401", - "ms-python.vscode-pylance@2025.4.104", + "ms-python.python@2025.13.2025090201", + "ms-python.vscode-pylance@2025.7.102", "ms-vscode-remote.remote-containers@0.414.0", "charliermarsh.ruff@2025.24.0", @@ -61,5 +61,5 @@ "initializeCommand": "sh .devcontainer/initialize-command.sh", "onCreateCommand": "sh .devcontainer/on-create-command.sh", "postStartCommand": "sh .devcontainer/post-start-command.sh" - // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): d72f90f7 # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): bec12e35 # spellchecker:disable-line } diff --git a/.devcontainer/install-ci-tooling.py b/.devcontainer/install-ci-tooling.py index a87f08ba..4a7c06a3 100644 --- a/.devcontainer/install-ci-tooling.py +++ b/.devcontainer/install-ci-tooling.py @@ -7,10 +7,10 @@ import tempfile from pathlib import Path -UV_VERSION = "0.8.4" -PNPM_VERSION = "10.14.0" -COPIER_VERSION = "9.9.1" -COPIER_TEMPLATE_EXTENSIONS_VERSION = "0.3.2" +UV_VERSION = "0.8.15" +PNPM_VERSION = "10.15.1" +COPIER_VERSION = "9.10.1" +COPIER_TEMPLATE_EXTENSIONS_VERSION = "0.3.3" PRE_COMMIT_VERSION = "4.3.0" GITHUB_WINDOWS_RUNNER_BIN_PATH = r"C:\Users\runneradmin\.local\bin" INSTALL_SSM_PLUGIN_BY_DEFAULT = False @@ -31,10 +31,10 @@ "--no-node", action="store_true", default=False, help="Do not process any environments using node package managers" ) _ = parser.add_argument( - "--install-ssm-plugin", + "--skip-installing-ssm-plugin", action="store_true", - default=INSTALL_SSM_PLUGIN_BY_DEFAULT, - help="Install the SSM plugin for AWS CLI", + default=False, + help="Skip installing the SSM plugin for AWS CLI", ) @@ -117,26 +117,43 @@ def main(): else [cmd] ) _ = subprocess.run(cmd, shell=True, check=True) - if args.install_ssm_plugin: - if is_windows: - raise NotImplementedError("SSM plugin installation is not implemented for Windows") + if INSTALL_SSM_PLUGIN_BY_DEFAULT and not args.skip_installing_ssm_plugin: with tempfile.TemporaryDirectory() as tmp_dir: - local_package_path = Path(tmp_dir) / "session-manager-plugin.deb" - # Based on https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-debian-and-ubuntu.html - # no specific reason for that version, just pinning it for best practice - _ = subprocess.run( - [ - "curl", - "https://s3.amazonaws.com/session-manager-downloads/plugin/1.2.707.0/ubuntu_64bit/session-manager-plugin.deb", - "-o", - f"{local_package_path}", - ], - check=True, - ) - _ = subprocess.run( - ["sudo", "dpkg", "-i", str(local_package_path)], - check=True, - ) + if is_windows: + local_package_path = Path(tmp_dir) / "SessionManagerPluginSetup.exe" + # Based on https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-windows.html + # no specific reason for that version, just pinning it for best practice + _ = subprocess.run( + [ + "curl", + "https://s3.amazonaws.com/session-manager-downloads/plugin/1.2.707.0/windows/SessionManagerPluginSetup.exe", + "-o", + f"{local_package_path}", + ], + check=True, + ) + _ = subprocess.run( + [str(local_package_path), "/quiet"], + check=True, + ) + else: + local_package_path = Path(tmp_dir) / "session-manager-plugin.deb" + # Based on https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-debian-and-ubuntu.html + # no specific reason for that version, just pinning it for best practice + _ = subprocess.run( + [ + "curl", + "https://s3.amazonaws.com/session-manager-downloads/plugin/1.2.707.0/ubuntu_64bit/session-manager-plugin.deb", + "-o", + f"{local_package_path}", + ], + check=True, + ) + _ = subprocess.run( + ["sudo", "dpkg", "-i", str(local_package_path)], + check=True, + ) + print("SSM Plugin Manager Version: ") _ = subprocess.run( ["session-manager-plugin", "--version"], check=True, diff --git a/.devcontainer/manual-setup-deps.sh b/.devcontainer/manual-setup-deps.sh deleted file mode 100644 index 70e9c2db..00000000 --- a/.devcontainer/manual-setup-deps.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env sh -# can pass in the full major.minor.patch version of python as an optional argument -# can set `--skip-lock` as optional argument to just install dependencies without verifying lock file -# can set `--optionally-lock` to check for a uv.lock file in the project directory and only respect the lock if it already exists (useful for initially instantiating the repository) (mutually exclusive with --skip-lock) - -set -ex - -# Ensure that uv won't use the default system Python -python_version="3.12.7" - -# Parse arguments -skip_lock=false -optionally_lock=false -while [ "$#" -gt 0 ]; do - case $1 in - --skip-lock) skip_lock=true ;; - --optionally-lock) optionally_lock=true ;; - *) python_version="${1:-$python_version}" ;; # Take the first non-flag argument as the input - esac - shift -done - -# Ensure that --skip-lock and --optionally-lock are mutually exclusive -if [ "$skip_lock" = "true" ] && [ "$optionally_lock" = "true" ]; then - echo "Error: --skip-lock and --optionally-lock cannot be used together." >&2 - exit 1 -fi - -export UV_PYTHON="$python_version" -export UV_PYTHON_PREFERENCE=only-system - -SCRIPT_DIR="$(dirname "$0")" -PROJECT_ROOT_DIR="$(realpath "$SCRIPT_DIR/..")" - -# If optionally_lock is set, decide whether to skip locking based on the presence of uv.lock -if [ "$optionally_lock" = "true" ]; then - if [ ! -f "$PROJECT_ROOT_DIR/uv.lock" ]; then - skip_lock=true - else - skip_lock=false - fi -fi - - - -# Ensure that the lock file is in a good state -if [ "$skip_lock" = "false" ]; then - uv lock --check --directory "$PROJECT_ROOT_DIR" -fi - -uv sync $( [ "$skip_lock" = "false" ] && echo "--frozen" ) --directory "$PROJECT_ROOT_DIR" -uv pip list --directory "$PROJECT_ROOT_DIR" diff --git a/.github/actions/install_deps/action.yml b/.github/actions/install_deps/action.yml index 3fafd45a..ddfe8b45 100644 --- a/.github/actions/install_deps/action.yml +++ b/.github/actions/install_deps/action.yml @@ -16,6 +16,11 @@ inputs: default: true type: boolean description: Whether to run the setup-deps script, or just to setup basic CI tooling + skip-installing-ssm-plugin-manager: + required: false + default: false + type: boolean + description: Whether to explicitly skip installing the SSM Plugin manager when setting up basic CI tooling project-dir: type: string description: What's the relative path to the project? @@ -48,24 +53,24 @@ runs: - name: Setup python if: ${{ inputs.python-version != 'notUsing' }} - uses: actions/setup-python@v5.6.0 + uses: actions/setup-python@v6.0.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: Setup node if: ${{ inputs.node-version != 'notUsing' }} - uses: actions/setup-node@v4.4.0 + uses: actions/setup-node@v5.0.0 with: node-version: ${{ inputs.node-version }} - name: Install tooling # the funky syntax is github action ternary - run: python .devcontainer/install-ci-tooling.py ${{ inputs.python-version == 'notUsing' && '--no-python' || '' }} ${{ inputs.node-version == 'notUsing' && '--no-node' || '' }} + run: python .devcontainer/install-ci-tooling.py ${{ inputs.python-version == 'notUsing' && '--no-python' || '' }} ${{ inputs.node-version == 'notUsing' && '--no-node' || '' }} ${{ inputs.skip-installing-ssm-plugin-manager && '--skip-installing-ssm-plugin' || '' }} shell: pwsh - name: OIDC Auth for CodeArtifact if: ${{ inputs.code-artifact-auth-role-name != 'no-code-artifact' }} - uses: aws-actions/configure-aws-credentials@v4.2.0 + uses: aws-actions/configure-aws-credentials@v5.0.0 with: role-to-assume: arn:aws:iam::${{ inputs.code-artifact-auth-role-account-id }}:role/${{ inputs.code-artifact-auth-role-name }} aws-region: ${{ inputs.code-artifact-auth-region }} diff --git a/.github/actions/update-devcontainer-hash/action.yml b/.github/actions/update-devcontainer-hash/action.yml index d5b7d214..fb64cc81 100644 --- a/.github/actions/update-devcontainer-hash/action.yml +++ b/.github/actions/update-devcontainer-hash/action.yml @@ -27,7 +27,7 @@ runs: shell: bash - name: Checkout code - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v5.0.0 with: persist-credentials: true fetch-depth: 1 diff --git a/.github/reusable_workflows/build-docker-image.yaml b/.github/reusable_workflows/build-docker-image.yaml index dc5df38f..ae6d3c8c 100644 --- a/.github/reusable_workflows/build-docker-image.yaml +++ b/.github/reusable_workflows/build-docker-image.yaml @@ -31,6 +31,10 @@ on: description: 'Should the image be saved as an artifact?' required: false default: false + outputs: + artifact-name: + description: 'The name of the uploaded artifact of the image tarball' + value: ${{ jobs.build-image.outputs.artifact-name }} permissions: id-token: write @@ -40,6 +44,8 @@ jobs: build-image: name: Build Docker Image runs-on: ubuntu-24.04 + outputs: + artifact-name: ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }} steps: - name: Parse ECR URL if: ${{ inputs.push-role-name != 'no-push' }} @@ -59,11 +65,11 @@ jobs: shell: bash - name: Checkout code - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v5.0.0 - name: OIDC Auth for ECR if: ${{ inputs.push-role-name != 'no-push' }} - uses: aws-actions/configure-aws-credentials@v4.2.0 + uses: aws-actions/configure-aws-credentials@v5.0.0 with: role-to-assume: arn:aws:iam::${{ steps.parse_ecr_url.outputs.aws_account_id }}:role/${{ inputs.push-role-name }} aws-region: ${{ steps.parse_ecr_url.outputs.aws_region }} @@ -114,9 +120,9 @@ jobs: - name: Set up Docker Buildx if: ${{ (inputs.save-as-artifact && inputs.push-role-name == 'no-push') || steps.check-if-exists.outputs.status == 'notfound' }} - uses: docker/setup-buildx-action@v3.10.0 + uses: docker/setup-buildx-action@v3.11.1 with: - version: v0.22.0 + version: v0.27.0 - name: Build Docker Image if: ${{ (inputs.save-as-artifact && inputs.push-role-name == 'no-push') || steps.check-if-exists.outputs.status == 'notfound' }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 07169ccf..3a1d0f2d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -53,7 +53,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v5.0.0 - name: Move python script that replaces private package registry information to temp folder so it doesn't get deleted run: | @@ -108,7 +108,7 @@ jobs: timeout-minutes: 30 # this is the amount of time this action will wait to attempt to acquire the mutex lock before failing, e.g. if other jobs are queued up in front of it - name: Cache Pre-commit hooks - uses: actions/cache@v4.2.3 + uses: actions/cache@v4.2.4 env: cache-name: cache-pre-commit-hooks with: diff --git a/.github/workflows/get-values.yaml b/.github/workflows/get-values.yaml index a9f5c333..47dba7cd 100644 --- a/.github/workflows/get-values.yaml +++ b/.github/workflows/get-values.yaml @@ -25,7 +25,7 @@ jobs: pr-short-num: ${{ steps.find-pr-num.outputs.number }} steps: - name: Checkout code - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v5.0.0 - name: Update Devcontainer Hash if: ${{ github.actor == 'dependabot[bot]' && github.event_name == 'push' }} diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 24534af2..68addddd 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -32,19 +32,20 @@ jobs: steps: - name: Checkout code during push if: ${{ github.event_name == 'push' }} - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v5.0.0 with: ref: ${{ github.ref_name }} # explicitly get the head of the branch, which will include any new commits pushed if this is a dependabot branch - name: Checkout code not during push if: ${{ github.event_name != 'push' }} - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v5.0.0 - name: Install latest versions of packages uses: ./.github/actions/install_deps with: python-version: ${{ inputs.python-version }} node-version: ${{ inputs.node-version }} + skip-installing-ssm-plugin-manager: true - name: Set up mutex # Github concurrency management is horrible, things get arbitrarily cancelled if queued up. So using mutex until github fixes itself. When multiple jobs are modifying cache at once, weird things can happen. possible issue is https://github.com/actions/toolkit/issues/658 if: ${{ runner.os != 'Windows' }} # we're just gonna have to YOLO on Windows, because this action doesn't support it yet https://github.com/ben-z/gh-action-mutex/issues/14 @@ -54,7 +55,7 @@ jobs: timeout-minutes: 30 # this is the amount of time this action will wait to attempt to acquire the mutex lock before failing, e.g. if other jobs are queued up in front of it - name: Cache Pre-commit hooks - uses: actions/cache@v4.2.3 + uses: actions/cache@v4.2.4 env: cache-name: cache-pre-commit-hooks with: diff --git a/.github/workflows/tag-on-merge.yaml b/.github/workflows/tag-on-merge.yaml index a11a90d5..00bb0cab 100644 --- a/.github/workflows/tag-on-merge.yaml +++ b/.github/workflows/tag-on-merge.yaml @@ -13,7 +13,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v5.0.0 with: ref: ${{ github.event.pull_request.merge_commit_sha }} fetch-depth: '0' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 23ece589..1103e03a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: # Reformatting (should generally come before any file format or other checks, because reformatting can change things) - repo: https://github.com/crate-ci/typos - rev: 7fb6e0951ad91e4772a2470012fc1ae621016b80 # frozen: v1 + rev: 65a25783d8705c6a72d9fead19c44d87b4ff03c3 # frozen: v1 hooks: - id: typos exclude: @@ -218,7 +218,7 @@ repos: exclude: docs/.*\.rst$ - repo: https://github.com/hadolint/hadolint - rev: c3dc18df7a501f02a560a2cc7ba3c69a85ca01d3 # frozen: v2.13.1-beta + rev: 87de847754330ad47ae16bdfe2d1a757ccb4b4d4 # frozen: v2.13.1 hooks: - id: hadolint-docker name: Lint Dockerfiles @@ -226,7 +226,7 @@ repos: description: Runs hadolint to lint Dockerfiles - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 54a455f7ce629598b7535ff828fd5fb796f4b83f # frozen: v0.12.9 + rev: db90487f48a9dd992d243ef63c156eaffddeaf28 # frozen: v0.12.11 hooks: - id: ruff name: ruff-src diff --git a/extensions/context.py b/extensions/context.py index 81604dab..f5c86edf 100644 --- a/extensions/context.py +++ b/extensions/context.py @@ -10,62 +10,71 @@ class ContextUpdater(ContextHook): @override def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: - context["uv_version"] = "0.8.4" - context["pnpm_version"] = "10.14.0" + context["uv_version"] = "0.8.15" + context["pnpm_version"] = "10.15.1" context["pre_commit_version"] = "4.3.0" - context["pyright_version"] = "1.1.403" - context["pytest_version"] = "8.4.1" + context["pyright_version"] = "1.1.405" + context["pytest_version"] = "8.4.2" context["pytest_randomly_version"] = "3.16.0" - context["pytest_cov_version"] = "6.2.1" - context["copier_version"] = "9.9.1" - context["copier_template_extensions_version"] = "0.3.2" + context["pytest_cov_version"] = "6.3.0" + context["copier_version"] = "9.10.1" + context["copier_template_extensions_version"] = "0.3.3" context["sphinx_version"] = "8.1.3" - context["pulumi_version"] = "3.190.0" - context["pulumi_aws_version"] = "7.4.0" - context["pulumi_aws_native_version"] = "1.32.0" + context["pulumi_version"] = "3.193.0" + context["pulumi_aws_version"] = "7.7.0" + context["pulumi_aws_native_version"] = "1.33.0" context["pulumi_command_version"] = "1.1.0" context["pulumi_github_version"] = "6.7.3" - context["pulumi_okta_version"] = "4.20.0" - context["boto3_version"] = "1.40.9" - context["ephemeral_pulumi_deploy_version"] = "0.0.4" + context["pulumi_okta_version"] = "5.2.0" + context["boto3_version"] = "1.40.25" + context["ephemeral_pulumi_deploy_version"] = "0.0.5" context["pydantic_version"] = "2.11.7" context["pyinstaller_version"] = "6.13.0" context["setuptools_version"] = "80.7.1" - context["strawberry_graphql_version"] = "0.270.4" - context["fastapi_version"] = "0.115.14" + context["strawberry_graphql_version"] = "0.282.0" + context["fastapi_version"] = "0.116.1" + context["fastapi_offline_version"] = "1.7.4" context["uvicorn_version"] = "0.35.0" - context["lab_auto_pulumi_version"] = "0.1.15" + context["lab_auto_pulumi_version"] = "0.1.16" - context["nuxt_ui_version"] = "^3.3.0" - context["nuxt_version"] = "^4.0.3" + context["node_version"] = "24.7.0" + context["nuxt_ui_version"] = "^3.3.2" + context["nuxt_version"] = "^4.1.0" context["nuxt_icon_version"] = "^2.0.0" context["typescript_version"] = "^5.8.2" context["dot_env_cli_version"] = "^9.0.0" - context["playwright_version"] = "^1.52.0" - context["vue_version"] = "^3.5.18" + context["playwright_version"] = "^1.55.0" + context["vue_version"] = "^3.5.21" + context["vue_tsc_version"] = "^3.0.6" + context["vue_devtools_api_version"] = "^8.0.0" context["vue_router_version"] = "^4.5.1" - context["faker_version"] = "^9.9.0" - context["eslint_version"] = "^9.33.0" - context["zod_version"] = "^4.0.17" + context["dotenv_cli_version"] = "^9.0.0" + context["faker_version"] = "^10.0.0" + context["vitest_version"] = "^3.2.4" + context["eslint_version"] = "^9.34.0" + context["nuxt_eslint_version"] = "^1.9.0" + context["zod_version"] = "^4.1.5" + context["zod_from_json_schema_version"] = "^0.5.0" + context["types_node_version"] = "^24.3.1" context["nuxt_apollo_version"] = "5.0.0-alpha.15" context["graphql_codegen_cli_version"] = "^5.0.5" context["graphql_codegen_typescript_version"] = "^4.1.6" - context["gha_checkout"] = "v4.2.2" - context["gha_setup_python"] = "v5.6.0" - context["gha_cache"] = "v4.2.3" + context["gha_checkout"] = "v5.0.0" + context["gha_setup_python"] = "v6.0.0" + context["gha_cache"] = "v4.2.4" context["gha_upload_artifact"] = "v4.6.2" - context["gha_download_artifact"] = "v4.3.0" + context["gha_download_artifact"] = "v5.0.0" context["gha_github_script"] = "v7.0.1" - context["gha_setup_buildx"] = "v3.10.0" - context["buildx_version"] = "v0.22.0" + context["gha_setup_buildx"] = "v3.11.1" + context["buildx_version"] = "v0.27.0" context["gha_docker_build_push"] = "v6.16.0" - context["gha_configure_aws_credentials"] = "v4.2.0" + context["gha_configure_aws_credentials"] = "v5.0.0" context["gha_amazon_ecr_login"] = "v2.0.1" - context["gha_setup_node"] = "v4.4.0" + context["gha_setup_node"] = "v5.0.0" context["gha_action_gh_release"] = "v2.2.1" context["gha_mutex"] = "1ebad517141198e08d47cf72f3c0975316620a65 # v1.0.0-alpha.10" - context["gha_pypi_publish"] = "v1.12.4" + context["gha_pypi_publish"] = "v1.13.0" context["gha_sleep"] = "v2.0.3" context["gha_linux_runner"] = "ubuntu-24.04" context["gha_windows_runner"] = "windows-2025" @@ -75,8 +84,8 @@ def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: context["py313_version"] = "3.13.2" context["debian_release_name"] = "bookworm" - context["alpine_image_version"] = "3.21" - context["nginx_image_version"] = "1.28.0" + context["alpine_image_version"] = "3.22" + context["nginx_image_version"] = "1.29.1" # Kludge to be able to help symlinked jinja files in the child and grandchild templates context["template_uses_vuejs"] = False diff --git a/pyproject.toml b/pyproject.toml index 3912ed1c..9a250ed2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,12 +6,12 @@ readme = "README.md" requires-python = ">=3.12.7" dependencies = [ # Managed by upstream template - "pytest>=8.4.1", - "pytest-cov>=6.2.1", + "pytest>=8.4.2", + "pytest-cov>=6.3.0", "pytest-randomly>=3.16.0", - "pyright[nodejs]>=1.1.403", - "copier>=9.9.1", - "copier-template-extensions>=0.3.2" + "pyright[nodejs]>=1.1.405", + "copier>=9.10.1", + "copier-template-extensions>=0.3.3" # Specific to this template diff --git a/template/.devcontainer/devcontainer.json.jinja b/template/.devcontainer/devcontainer.json.jinja index 16bd1318..b69eddd9 100644 --- a/template/.devcontainer/devcontainer.json.jinja +++ b/template/.devcontainer/devcontainer.json.jinja @@ -29,21 +29,21 @@ "eamodio.gitlens@15.5.1", "ms-vscode.live-server@0.5.2025051301", "MS-vsliveshare.vsliveshare@1.0.5905", - "github.copilot@1.320.1564", - "github.copilot-chat@0.28.2025051402", + "github.copilot@1.366.1775", + "github.copilot-chat@0.31.2025090401", // Python - "ms-python.python@2025.7.2025051401", - "ms-python.vscode-pylance@2025.4.104", + "ms-python.python@2025.13.2025090201", + "ms-python.vscode-pylance@2025.7.102", "ms-vscode-remote.remote-containers@0.414.0", "charliermarsh.ruff@2025.24.0", {% endraw %}{% if is_child_of_copier_base_template is not defined and template_uses_vuejs is defined and template_uses_vuejs is sameas(true) %}{% raw %} // VueJS - "vue.volar@2.2.8", + "vue.volar@3.0.6", "vitest.explorer@1.16.1", {% endraw %}{% endif %}{% raw %}{% endraw %}{% if is_child_of_copier_base_template is not defined and template_uses_javascript is defined and template_uses_javascript is sameas(true) %}{% raw %} // All javascript - "dbaeumer.vscode-eslint@3.0.13", + "dbaeumer.vscode-eslint@3.0.19", {% endraw %}{% endif %}{% raw %} // Misc file formats "bierner.markdown-mermaid@1.28.0", diff --git a/template/.devcontainer/install-ci-tooling.py.jinja b/template/.devcontainer/install-ci-tooling.py.jinja index 7a5e4f3c..4709e86e 100644 --- a/template/.devcontainer/install-ci-tooling.py.jinja +++ b/template/.devcontainer/install-ci-tooling.py.jinja @@ -31,10 +31,10 @@ _ = parser.add_argument( "--no-node", action="store_true", default=False, help="Do not process any environments using node package managers" ) _ = parser.add_argument( - "--install-ssm-plugin", + "--skip-installing-ssm-plugin", action="store_true", - default=INSTALL_SSM_PLUGIN_BY_DEFAULT, - help="Install the SSM plugin for AWS CLI", + default=False, + help="Skip installing the SSM plugin for AWS CLI", ) @@ -117,26 +117,43 @@ def main(): else [cmd] ) _ = subprocess.run(cmd, shell=True, check=True) - if args.install_ssm_plugin: - if is_windows: - raise NotImplementedError("SSM plugin installation is not implemented for Windows") + if INSTALL_SSM_PLUGIN_BY_DEFAULT and not args.skip_installing_ssm_plugin: with tempfile.TemporaryDirectory() as tmp_dir: - local_package_path = Path(tmp_dir) / "session-manager-plugin.deb" - # Based on https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-debian-and-ubuntu.html - # no specific reason for that version, just pinning it for best practice - _ = subprocess.run( - [ - "curl", - "https://s3.amazonaws.com/session-manager-downloads/plugin/1.2.707.0/ubuntu_64bit/session-manager-plugin.deb", - "-o", - f"{local_package_path}", - ], - check=True, - ) - _ = subprocess.run( - ["sudo", "dpkg", "-i", str(local_package_path)], - check=True, - ) + if is_windows: + local_package_path = Path(tmp_dir) / "SessionManagerPluginSetup.exe" + # Based on https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-windows.html + # no specific reason for that version, just pinning it for best practice + _ = subprocess.run( + [ + "curl", + "https://s3.amazonaws.com/session-manager-downloads/plugin/1.2.707.0/windows/SessionManagerPluginSetup.exe", + "-o", + f"{local_package_path}", + ], + check=True, + ) + _ = subprocess.run( + [str(local_package_path), "/quiet"], + check=True, + ) + else: + local_package_path = Path(tmp_dir) / "session-manager-plugin.deb" + # Based on https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-debian-and-ubuntu.html + # no specific reason for that version, just pinning it for best practice + _ = subprocess.run( + [ + "curl", + "https://s3.amazonaws.com/session-manager-downloads/plugin/1.2.707.0/ubuntu_64bit/session-manager-plugin.deb", + "-o", + f"{local_package_path}", + ], + check=True, + ) + _ = subprocess.run( + ["sudo", "dpkg", "-i", str(local_package_path)], + check=True, + ) + print("SSM Plugin Manager Version: ") _ = subprocess.run( ["session-manager-plugin", "--version"], check=True, diff --git a/template/.github/actions/install_deps/action.yml b/template/.github/actions/install_deps/action.yml index 3fafd45a..ddfe8b45 100644 --- a/template/.github/actions/install_deps/action.yml +++ b/template/.github/actions/install_deps/action.yml @@ -16,6 +16,11 @@ inputs: default: true type: boolean description: Whether to run the setup-deps script, or just to setup basic CI tooling + skip-installing-ssm-plugin-manager: + required: false + default: false + type: boolean + description: Whether to explicitly skip installing the SSM Plugin manager when setting up basic CI tooling project-dir: type: string description: What's the relative path to the project? @@ -48,24 +53,24 @@ runs: - name: Setup python if: ${{ inputs.python-version != 'notUsing' }} - uses: actions/setup-python@v5.6.0 + uses: actions/setup-python@v6.0.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: Setup node if: ${{ inputs.node-version != 'notUsing' }} - uses: actions/setup-node@v4.4.0 + uses: actions/setup-node@v5.0.0 with: node-version: ${{ inputs.node-version }} - name: Install tooling # the funky syntax is github action ternary - run: python .devcontainer/install-ci-tooling.py ${{ inputs.python-version == 'notUsing' && '--no-python' || '' }} ${{ inputs.node-version == 'notUsing' && '--no-node' || '' }} + run: python .devcontainer/install-ci-tooling.py ${{ inputs.python-version == 'notUsing' && '--no-python' || '' }} ${{ inputs.node-version == 'notUsing' && '--no-node' || '' }} ${{ inputs.skip-installing-ssm-plugin-manager && '--skip-installing-ssm-plugin' || '' }} shell: pwsh - name: OIDC Auth for CodeArtifact if: ${{ inputs.code-artifact-auth-role-name != 'no-code-artifact' }} - uses: aws-actions/configure-aws-credentials@v4.2.0 + uses: aws-actions/configure-aws-credentials@v5.0.0 with: role-to-assume: arn:aws:iam::${{ inputs.code-artifact-auth-role-account-id }}:role/${{ inputs.code-artifact-auth-role-name }} aws-region: ${{ inputs.code-artifact-auth-region }} diff --git a/template/.github/actions/update-devcontainer-hash/action.yml b/template/.github/actions/update-devcontainer-hash/action.yml index d5b7d214..fb64cc81 100644 --- a/template/.github/actions/update-devcontainer-hash/action.yml +++ b/template/.github/actions/update-devcontainer-hash/action.yml @@ -27,7 +27,7 @@ runs: shell: bash - name: Checkout code - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v5.0.0 with: persist-credentials: true fetch-depth: 1 diff --git a/template/.github/workflows/get-values.yaml b/template/.github/workflows/get-values.yaml index a9f5c333..47dba7cd 100644 --- a/template/.github/workflows/get-values.yaml +++ b/template/.github/workflows/get-values.yaml @@ -25,7 +25,7 @@ jobs: pr-short-num: ${{ steps.find-pr-num.outputs.number }} steps: - name: Checkout code - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v5.0.0 - name: Update Devcontainer Hash if: ${{ github.actor == 'dependabot[bot]' && github.event_name == 'push' }} diff --git a/template/.github/workflows/pre-commit.yaml b/template/.github/workflows/pre-commit.yaml index 24534af2..68addddd 100644 --- a/template/.github/workflows/pre-commit.yaml +++ b/template/.github/workflows/pre-commit.yaml @@ -32,19 +32,20 @@ jobs: steps: - name: Checkout code during push if: ${{ github.event_name == 'push' }} - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v5.0.0 with: ref: ${{ github.ref_name }} # explicitly get the head of the branch, which will include any new commits pushed if this is a dependabot branch - name: Checkout code not during push if: ${{ github.event_name != 'push' }} - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v5.0.0 - name: Install latest versions of packages uses: ./.github/actions/install_deps with: python-version: ${{ inputs.python-version }} node-version: ${{ inputs.node-version }} + skip-installing-ssm-plugin-manager: true - name: Set up mutex # Github concurrency management is horrible, things get arbitrarily cancelled if queued up. So using mutex until github fixes itself. When multiple jobs are modifying cache at once, weird things can happen. possible issue is https://github.com/actions/toolkit/issues/658 if: ${{ runner.os != 'Windows' }} # we're just gonna have to YOLO on Windows, because this action doesn't support it yet https://github.com/ben-z/gh-action-mutex/issues/14 @@ -54,7 +55,7 @@ jobs: timeout-minutes: 30 # this is the amount of time this action will wait to attempt to acquire the mutex lock before failing, e.g. if other jobs are queued up in front of it - name: Cache Pre-commit hooks - uses: actions/cache@v4.2.3 + uses: actions/cache@v4.2.4 env: cache-name: cache-pre-commit-hooks with: diff --git a/template/.pre-commit-config.yaml b/template/.pre-commit-config.yaml index 23ece589..1103e03a 100644 --- a/template/.pre-commit-config.yaml +++ b/template/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: # Reformatting (should generally come before any file format or other checks, because reformatting can change things) - repo: https://github.com/crate-ci/typos - rev: 7fb6e0951ad91e4772a2470012fc1ae621016b80 # frozen: v1 + rev: 65a25783d8705c6a72d9fead19c44d87b4ff03c3 # frozen: v1 hooks: - id: typos exclude: @@ -218,7 +218,7 @@ repos: exclude: docs/.*\.rst$ - repo: https://github.com/hadolint/hadolint - rev: c3dc18df7a501f02a560a2cc7ba3c69a85ca01d3 # frozen: v2.13.1-beta + rev: 87de847754330ad47ae16bdfe2d1a757ccb4b4d4 # frozen: v2.13.1 hooks: - id: hadolint-docker name: Lint Dockerfiles @@ -226,7 +226,7 @@ repos: description: Runs hadolint to lint Dockerfiles - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 54a455f7ce629598b7535ff828fd5fb796f4b83f # frozen: v0.12.9 + rev: db90487f48a9dd992d243ef63c156eaffddeaf28 # frozen: v0.12.11 hooks: - id: ruff name: ruff-src diff --git a/uv.lock b/uv.lock index d4604309..c916ab01 100644 --- a/uv.lock +++ b/uv.lock @@ -22,7 +22,7 @@ wheels = [ [[package]] name = "copier" -version = "9.9.1" +version = "9.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, @@ -34,14 +34,15 @@ dependencies = [ { name = "pathspec" }, { name = "platformdirs" }, { name = "plumbum" }, + { name = "prompt-toolkit" }, { name = "pydantic" }, { name = "pygments" }, { name = "pyyaml" }, { name = "questionary" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/68/e938aec15c204e3c55db570598409fe6bbd81fb81e26616b4b23d96ea92c/copier-9.9.1.tar.gz", hash = "sha256:244bdf3ec5eb460dbe45ef22b825e9897b0bcc9d94ca96dad3ced3d786cfeab7", size = 586353, upload-time = "2025-08-18T12:14:17.316Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/a5/0cf693f3fa51cba1d36765939e0d9956c0487426ad581868a2507c208bad/copier-9.10.1.tar.gz", hash = "sha256:ba2d729465508da04a62bc9b76eed13d952aa7634a74a69519252fcf8a54d94e", size = 586680, upload-time = "2025-08-28T13:04:54.307Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/d9/f7abe1fe19f58c3ed2f1625dcd46ad76612c0f6b3ac320db77c267223f39/copier-9.9.1-py3-none-any.whl", hash = "sha256:9439280baca00194933b04885f1376b17e3287c2324053666fbe9a7a0aceaa44", size = 56118, upload-time = "2025-08-18T12:14:15.009Z" }, + { url = "https://files.pythonhosted.org/packages/9b/4b/4a12d04124b0158b77958fb12349828bb4023f6530aaa1713ceb784c77a3/copier-9.10.1-py3-none-any.whl", hash = "sha256:8b1b406367c67e5ee389778246cea18cddd55e71bfc6503d5fa13fe73304407a", size = 56017, upload-time = "2025-08-28T13:04:52.444Z" }, ] [[package]] @@ -59,11 +60,11 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "copier", specifier = ">=9.9.1" }, - { name = "copier-template-extensions", specifier = ">=0.3.2" }, - { name = "pyright", extras = ["nodejs"], specifier = ">=1.1.403" }, - { name = "pytest", specifier = ">=8.4.1" }, - { name = "pytest-cov", specifier = ">=6.2.1" }, + { name = "copier", specifier = ">=9.10.1" }, + { name = "copier-template-extensions", specifier = ">=0.3.3" }, + { name = "pyright", extras = ["nodejs"], specifier = ">=1.1.405" }, + { name = "pytest", specifier = ">=8.4.2" }, + { name = "pytest-cov", specifier = ">=6.3.0" }, { name = "pytest-randomly", specifier = ">=3.16.0" }, ] @@ -358,15 +359,15 @@ wheels = [ [[package]] name = "pyright" -version = "1.1.403" +version = "1.1.405" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/f6/35f885264ff08c960b23d1542038d8da86971c5d8c955cfab195a4f672d7/pyright-1.1.403.tar.gz", hash = "sha256:3ab69b9f41c67fb5bbb4d7a36243256f0d549ed3608678d381d5f51863921104", size = 3913526, upload-time = "2025-07-09T07:15:52.882Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/6c/ba4bbee22e76af700ea593a1d8701e3225080956753bee9750dcc25e2649/pyright-1.1.405.tar.gz", hash = "sha256:5c2a30e1037af27eb463a1cc0b9f6d65fec48478ccf092c1ac28385a15c55763", size = 4068319, upload-time = "2025-09-04T03:37:06.776Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/b6/b04e5c2f41a5ccad74a1a4759da41adb20b4bc9d59a5e08d29ba60084d07/pyright-1.1.403-py3-none-any.whl", hash = "sha256:c0eeca5aa76cbef3fcc271259bbd785753c7ad7bcac99a9162b4c4c7daed23b3", size = 5684504, upload-time = "2025-07-09T07:15:50.958Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1a/524f832e1ff1962a22a1accc775ca7b143ba2e9f5924bb6749dce566784a/pyright-1.1.405-py3-none-any.whl", hash = "sha256:a2cb13700b5508ce8e5d4546034cb7ea4aedb60215c6c33f56cec7f53996035a", size = 5905038, upload-time = "2025-09-04T03:37:04.913Z" }, ] [package.optional-dependencies] @@ -376,7 +377,7 @@ nodejs = [ [[package]] name = "pytest" -version = "8.4.1" +version = "8.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -385,23 +386,23 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] [[package]] name = "pytest-cov" -version = "6.2.1" +version = "6.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage" }, { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4c/f883ab8f0daad69f47efdf95f55a66b51a8b939c430dadce0611508d9e99/pytest_cov-6.3.0.tar.gz", hash = "sha256:35c580e7800f87ce892e687461166e1ac2bcb8fb9e13aea79032518d6e503ff2", size = 70398, upload-time = "2025-09-06T15:40:14.361Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, + { url = "https://files.pythonhosted.org/packages/80/b4/bb7263e12aade3842b938bc5c6958cae79c5ee18992f9b9349019579da0f/pytest_cov-6.3.0-py3-none-any.whl", hash = "sha256:440db28156d2468cafc0415b4f8e50856a0d11faefa38f30906048fe490f1749", size = 25115, upload-time = "2025-09-06T15:40:12.44Z" }, ] [[package]] From 95bdd51f67773aec2cf384fe41f30bd1bd472be1 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 8 Sep 2025 13:03:25 +0000 Subject: [PATCH 02/13] new ipmort --- template/.github/workflows/publish.yaml.jinja | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/template/.github/workflows/publish.yaml.jinja b/template/.github/workflows/publish.yaml.jinja index 153b8f04..a506e4bb 100644 --- a/template/.github/workflows/publish.yaml.jinja +++ b/template/.github/workflows/publish.yaml.jinja @@ -186,13 +186,23 @@ jobs: - name: Sleep to allow PyPI Index to update before proceeding to the next step uses: juliangruber/sleep-action@{% endraw %}{{ gha_sleep }}{% raw %} with: - time: 90s{% endraw %}{% endif %}{% raw %} + time: 120s{% endraw %}{% endif %}{% raw %} - name: Install from staging registry run: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://www.pypi.org/simple {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} - name: Display dependencies run: pip list - name: Confirm library can be imported successfully - run: python -c "import sys; print(f'Python version {sys.version}'); import {% endraw %}{{ package_name | replace('-', '_') }}{% raw %}" + env: + PYTHONPATH: "" # avoid picking up local sources + run: | + python - <<'PY' + import sys, importlib, pathlib + print(f"Python version {sys.version}") + m = importlib.import_module("{% endraw %}{{ package_name | replace('-', '_') }}{% raw %}") + p = pathlib.Path(getattr(m, "__file__", "")) + print(f"Imported from: {p}") + assert "site-packages" in str(p), f"Expected site-packages, got {p}" + PY create-tag: name: Create the git tag @@ -281,10 +291,20 @@ jobs: - name: Sleep to allow PyPI Index to update before proceeding to the next step uses: juliangruber/sleep-action@{% endraw %}{{ gha_sleep }}{% raw %} with: - time: 90s{% endraw %}{% endif %}{% raw %} + time: 120s{% endraw %}{% endif %}{% raw %} - name: Install from primary registry run: pip install {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} - name: Display dependencies run: pip list - name: Confirm library can be imported successfully - run: python -c "import sys; print(f'Python version {sys.version}'); import {% endraw %}{{ package_name | replace('-', '_') }}{% raw %}"{% endraw %} + env: + PYTHONPATH: "" # avoid picking up local sources + run: | + python - <<'PY' + import sys, importlib, pathlib + print(f"Python version {sys.version}") + m = importlib.import_module("{% endraw %}{{ package_name | replace('-', '_') }}{% raw %}") + p = pathlib.Path(getattr(m, "__file__", "")) + print(f"Imported from: {p}") + assert "site-packages" in str(p), f"Expected site-packages, got {p}" + PY{% endraw %} From 12fca10ef06fbfc0aac09c74685a176ca3a954f9 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 8 Sep 2025 13:15:40 +0000 Subject: [PATCH 03/13] is true --- template/.github/workflows/publish.yaml.jinja | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/template/.github/workflows/publish.yaml.jinja b/template/.github/workflows/publish.yaml.jinja index a506e4bb..ea9bc66f 100644 --- a/template/.github/workflows/publish.yaml.jinja +++ b/template/.github/workflows/publish.yaml.jinja @@ -206,7 +206,7 @@ jobs: create-tag: name: Create the git tag - if: ${{ github.event.inputs.publish_to_primary }} + if: ${{ github.event.inputs.publish_to_primary == 'true' }} needs: [ install-from-staging ] permissions: contents: write # needed to push the tag @@ -225,7 +225,7 @@ jobs: publish-to-primary: name: Publish Python distribution to Primary Package Registry - if: ${{ github.event.inputs.publish_to_primary }} + if: ${{ github.event.inputs.publish_to_primary == 'true' }} needs: [ create-tag ] runs-on: {% endraw %}{{ gha_linux_runner }}{% raw %} environment: @@ -260,7 +260,7 @@ jobs: install-from-primary: name: Install package from primary registry - if: ${{ github.event.inputs.publish_to_primary }} + if: ${{ github.event.inputs.publish_to_primary == 'true' }} needs: [ publish-to-primary, get-values ] strategy: matrix: From cffe1a40be05a0cf43f82d6ad696e4b9a5b7a9f5 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 8 Sep 2025 13:24:01 +0000 Subject: [PATCH 04/13] longer sleep --- template/.github/workflows/publish.yaml.jinja | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/template/.github/workflows/publish.yaml.jinja b/template/.github/workflows/publish.yaml.jinja index ea9bc66f..dd1c0e34 100644 --- a/template/.github/workflows/publish.yaml.jinja +++ b/template/.github/workflows/publish.yaml.jinja @@ -186,7 +186,7 @@ jobs: - name: Sleep to allow PyPI Index to update before proceeding to the next step uses: juliangruber/sleep-action@{% endraw %}{{ gha_sleep }}{% raw %} with: - time: 120s{% endraw %}{% endif %}{% raw %} + time: 150s{% endraw %}{% endif %}{% raw %} - name: Install from staging registry run: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://www.pypi.org/simple {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} - name: Display dependencies @@ -291,7 +291,7 @@ jobs: - name: Sleep to allow PyPI Index to update before proceeding to the next step uses: juliangruber/sleep-action@{% endraw %}{{ gha_sleep }}{% raw %} with: - time: 120s{% endraw %}{% endif %}{% raw %} + time: 150s{% endraw %}{% endif %}{% raw %} - name: Install from primary registry run: pip install {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} - name: Display dependencies From 3f018ca16cca1643e151ab3902d486131eb237ed Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 8 Sep 2025 13:25:22 +0000 Subject: [PATCH 05/13] no cache --- template/.github/workflows/publish.yaml.jinja | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/template/.github/workflows/publish.yaml.jinja b/template/.github/workflows/publish.yaml.jinja index dd1c0e34..6ec64be3 100644 --- a/template/.github/workflows/publish.yaml.jinja +++ b/template/.github/workflows/publish.yaml.jinja @@ -186,9 +186,9 @@ jobs: - name: Sleep to allow PyPI Index to update before proceeding to the next step uses: juliangruber/sleep-action@{% endraw %}{{ gha_sleep }}{% raw %} with: - time: 150s{% endraw %}{% endif %}{% raw %} + time: 60s{% endraw %}{% endif %}{% raw %} - name: Install from staging registry - run: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://www.pypi.org/simple {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} + run: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://www.pypi.org/simple {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} --no-cache - name: Display dependencies run: pip list - name: Confirm library can be imported successfully @@ -291,9 +291,9 @@ jobs: - name: Sleep to allow PyPI Index to update before proceeding to the next step uses: juliangruber/sleep-action@{% endraw %}{{ gha_sleep }}{% raw %} with: - time: 150s{% endraw %}{% endif %}{% raw %} + time: 60s{% endraw %}{% endif %}{% raw %} - name: Install from primary registry - run: pip install {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} + run: pip install {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} --no-cache - name: Display dependencies run: pip list - name: Confirm library can be imported successfully From 45ec396878b5f0c023f64759eb962a86fc653fbc Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 8 Sep 2025 13:31:11 +0000 Subject: [PATCH 06/13] fromJSON --- template/.github/workflows/publish.yaml.jinja | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/template/.github/workflows/publish.yaml.jinja b/template/.github/workflows/publish.yaml.jinja index 6ec64be3..aa579479 100644 --- a/template/.github/workflows/publish.yaml.jinja +++ b/template/.github/workflows/publish.yaml.jinja @@ -206,7 +206,7 @@ jobs: create-tag: name: Create the git tag - if: ${{ github.event.inputs.publish_to_primary == 'true' }} + if: ${{ fromJSON(github.event.inputs.publish_to_primary) }} needs: [ install-from-staging ] permissions: contents: write # needed to push the tag @@ -225,7 +225,7 @@ jobs: publish-to-primary: name: Publish Python distribution to Primary Package Registry - if: ${{ github.event.inputs.publish_to_primary == 'true' }} + if: ${{ fromJSON(github.event.inputs.publish_to_primary) }} needs: [ create-tag ] runs-on: {% endraw %}{{ gha_linux_runner }}{% raw %} environment: @@ -260,7 +260,7 @@ jobs: install-from-primary: name: Install package from primary registry - if: ${{ github.event.inputs.publish_to_primary == 'true' }} + if: ${{ fromJSON(github.event.inputs.publish_to_primary) }} needs: [ publish-to-primary, get-values ] strategy: matrix: From 5422e9cc8be146a7a420b4fa9fa1f8ef0b0c3ee8 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 8 Sep 2025 13:33:49 +0000 Subject: [PATCH 07/13] more publish checking --- template/.github/workflows/publish.yaml.jinja | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/template/.github/workflows/publish.yaml.jinja b/template/.github/workflows/publish.yaml.jinja index aa579479..4bdf8924 100644 --- a/template/.github/workflows/publish.yaml.jinja +++ b/template/.github/workflows/publish.yaml.jinja @@ -196,12 +196,20 @@ jobs: PYTHONPATH: "" # avoid picking up local sources run: | python - <<'PY' - import sys, importlib, pathlib + import sys, importlib, importlib.util, importlib.metadata as md, pathlib print(f"Python version {sys.version}") - m = importlib.import_module("{% endraw %}{{ package_name | replace('-', '_') }}{% raw %}") - p = pathlib.Path(getattr(m, "__file__", "")) + mod_name = "{% endraw %}{{ package_name | replace('-', '_') }}{% raw %}" + dist_name = mod_name + m = importlib.import_module(mod_name) + spec = importlib.util.find_spec(mod_name) + origin = (getattr(spec, "origin", None) or getattr(m, "__file__", "")) or "" + p = pathlib.Path(origin) print(f"Imported from: {p}") - assert "site-packages" in str(p), f"Expected site-packages, got {p}" + assert any(s in str(p) for s in ("site-packages", "dist-packages")), f"Expected site/dist-packages, got {p}" + expected = "${{ needs.get-values.outputs.package-version }}" + installed = md.version(dist_name) + print(f"Installed distribution version: {installed} (expected {expected})") + assert installed == expected, f"Version mismatch: expected {expected}, got {installed}" PY create-tag: @@ -301,10 +309,18 @@ jobs: PYTHONPATH: "" # avoid picking up local sources run: | python - <<'PY' - import sys, importlib, pathlib + import sys, importlib, importlib.util, importlib.metadata as md, pathlib print(f"Python version {sys.version}") - m = importlib.import_module("{% endraw %}{{ package_name | replace('-', '_') }}{% raw %}") - p = pathlib.Path(getattr(m, "__file__", "")) + mod_name = "{% endraw %}{{ package_name | replace('-', '_') }}{% raw %}" + dist_name = mod_name + m = importlib.import_module(mod_name) + spec = importlib.util.find_spec(mod_name) + origin = (getattr(spec, "origin", None) or getattr(m, "__file__", "")) or "" + p = pathlib.Path(origin) print(f"Imported from: {p}") - assert "site-packages" in str(p), f"Expected site-packages, got {p}" + assert any(s in str(p) for s in ("site-packages", "dist-packages")), f"Expected site/dist-packages, got {p}" + expected = "${{ needs.get-values.outputs.package-version }}" + installed = md.version(dist_name) + print(f"Installed distribution version: {installed} (expected {expected})") + assert installed == expected, f"Version mismatch: expected {expected}, got {installed}" PY{% endraw %} From d84f8d0439cfbfbcdbdf0eb9bfe641c4f2a485ef Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 8 Sep 2025 13:37:15 +0000 Subject: [PATCH 08/13] poll --- template/.github/workflows/publish.yaml.jinja | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/template/.github/workflows/publish.yaml.jinja b/template/.github/workflows/publish.yaml.jinja index 4bdf8924..527094d2 100644 --- a/template/.github/workflows/publish.yaml.jinja +++ b/template/.github/workflows/publish.yaml.jinja @@ -184,9 +184,27 @@ jobs: python-version: ${{ matrix.python-version }} {% endraw %}{% if python_package_registry == "PyPI" %}{% raw %} - name: Sleep to allow PyPI Index to update before proceeding to the next step + if: ${{ runner.os == 'Windows' }} uses: juliangruber/sleep-action@{% endraw %}{{ gha_sleep }}{% raw %} with: - time: 60s{% endraw %}{% endif %}{% raw %} + time: 60s + - name: Wait for Test PyPI to serve the new version + if: ${{ runner.os != 'Windows' }} + run: | + set -euo pipefail + PKG="{% endraw %}{{ package_name | replace('_', '-') }}{% raw %}" + VER="${{ needs.get-values.outputs.package-version }}" + for i in $(seq 1 60); do + code="$(curl -fsS -o /dev/null -w '%{http_code}' "https://test.pypi.org/pypi/${PKG}/${VER}/json" || true)" + if [ "$code" = "200" ]; then + echo "Found ${PKG}==${VER} on Test PyPI." + exit 0 + fi + echo "Not yet available; sleeping 5s..." + sleep 5 + done + echo "Timeout waiting for ${PKG}==${VER} on Test PyPI." + exit 1{% endraw %}{% endif %}{% raw %} - name: Install from staging registry run: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://www.pypi.org/simple {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} --no-cache - name: Display dependencies @@ -297,9 +315,27 @@ jobs: python-version: ${{ matrix.python-version }} {% endraw %}{% if python_package_registry == "PyPI" %}{% raw %} - name: Sleep to allow PyPI Index to update before proceeding to the next step + if: ${{ runner.os == 'Windows' }} uses: juliangruber/sleep-action@{% endraw %}{{ gha_sleep }}{% raw %} with: - time: 60s{% endraw %}{% endif %}{% raw %} + time: 60s + - name: Wait for PyPI to serve the new version + if: ${{ runner.os != 'Windows' }} + run: | + set -euo pipefail + PKG="{% endraw %}{{ package_name | replace('_', '-') }}{% raw %}" + VER="${{ needs.get-values.outputs.package-version }}" + for i in $(seq 1 60); do + code="$(curl -fsS -o /dev/null -w '%{http_code}' "https://pypi.org/pypi/${PKG}/${VER}/json" || true)" + if [ "$code" = "200" ]; then + echo "Found ${PKG}==${VER} on PyPI." + exit 0 + fi + echo "Not yet available; sleeping 5s..." + sleep 5 + done + echo "Timeout waiting for ${PKG}==${VER} on PyPI." + exit 1{% endraw %}{% endif %}{% raw %} - name: Install from primary registry run: pip install {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} --no-cache - name: Display dependencies From 18e831c97f96739979f04a63d222b3852dab5f12 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 8 Sep 2025 13:52:52 +0000 Subject: [PATCH 09/13] More polling --- template/.github/workflows/publish.yaml.jinja | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/template/.github/workflows/publish.yaml.jinja b/template/.github/workflows/publish.yaml.jinja index 527094d2..c561006d 100644 --- a/template/.github/workflows/publish.yaml.jinja +++ b/template/.github/workflows/publish.yaml.jinja @@ -203,10 +203,27 @@ jobs: echo "Not yet available; sleeping 5s..." sleep 5 done - echo "Timeout waiting for ${PKG}==${VER} on Test PyPI." + + if [ "$code" != "200" ]; then + echo "Timeout waiting for ${PKG}==${VER} on Test PyPI API." + exit 1 + fi + + # Then try to install with retries to ensure it's actually available in the index + for i in $(seq 1 12); do + if pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://www.pypi.org/simple "${PKG}==${VER}" --no-cache-dir --quiet; then + echo "Successfully installed ${PKG}==${VER}!" + exit 0 + fi + echo "Package not yet installable; sleeping 10s... (attempt $i/12)" + sleep 10 + done + + echo "Timeout waiting for ${PKG}==${VER} to be installable." exit 1{% endraw %}{% endif %}{% raw %} - name: Install from staging registry - run: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://www.pypi.org/simple {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} --no-cache + if: ${{ runner.os == 'Windows' }} + run: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://www.pypi.org/simple {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} --no-cache-dir - name: Display dependencies run: pip list - name: Confirm library can be imported successfully @@ -334,10 +351,27 @@ jobs: echo "Not yet available; sleeping 5s..." sleep 5 done - echo "Timeout waiting for ${PKG}==${VER} on PyPI." + + if [ "$code" != "200" ]; then + echo "Timeout waiting for ${PKG}==${VER} on PyPI API." + exit 1 + fi + + # Then try to install with retries to ensure it's actually available in the index + for i in $(seq 1 12); do + if pip install --index-url https://pypi.org/simple/ "${PKG}==${VER}" --no-cache-dir --quiet; then + echo "Successfully installed ${PKG}==${VER}!" + exit 0 + fi + echo "Package not yet installable; sleeping 10s... (attempt $i/12)" + sleep 10 + done + + echo "Timeout waiting for ${PKG}==${VER} to be installable." exit 1{% endraw %}{% endif %}{% raw %} - name: Install from primary registry - run: pip install {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} --no-cache + if: ${{ runner.os == 'Windows' }} + run: pip install {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} --no-cache-dir - name: Display dependencies run: pip list - name: Confirm library can be imported successfully From 3c79d54b9579df078468f8d7879ac803a4b0934c Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 8 Sep 2025 13:58:32 +0000 Subject: [PATCH 10/13] break --- template/.github/workflows/publish.yaml.jinja | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/template/.github/workflows/publish.yaml.jinja b/template/.github/workflows/publish.yaml.jinja index c561006d..d9b63a90 100644 --- a/template/.github/workflows/publish.yaml.jinja +++ b/template/.github/workflows/publish.yaml.jinja @@ -198,7 +198,7 @@ jobs: code="$(curl -fsS -o /dev/null -w '%{http_code}' "https://test.pypi.org/pypi/${PKG}/${VER}/json" || true)" if [ "$code" = "200" ]; then echo "Found ${PKG}==${VER} on Test PyPI." - exit 0 + break fi echo "Not yet available; sleeping 5s..." sleep 5 @@ -346,7 +346,7 @@ jobs: code="$(curl -fsS -o /dev/null -w '%{http_code}' "https://pypi.org/pypi/${PKG}/${VER}/json" || true)" if [ "$code" = "200" ]; then echo "Found ${PKG}==${VER} on PyPI." - exit 0 + break fi echo "Not yet available; sleeping 5s..." sleep 5 From 2864715a17c6fdf57de1660609a17278f6f6deb0 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 8 Sep 2025 14:06:29 +0000 Subject: [PATCH 11/13] bash --- template/.github/workflows/publish.yaml.jinja | 2 ++ 1 file changed, 2 insertions(+) diff --git a/template/.github/workflows/publish.yaml.jinja b/template/.github/workflows/publish.yaml.jinja index d9b63a90..33919996 100644 --- a/template/.github/workflows/publish.yaml.jinja +++ b/template/.github/workflows/publish.yaml.jinja @@ -229,6 +229,7 @@ jobs: - name: Confirm library can be imported successfully env: PYTHONPATH: "" # avoid picking up local sources + shell: bash run: | python - <<'PY' import sys, importlib, importlib.util, importlib.metadata as md, pathlib @@ -377,6 +378,7 @@ jobs: - name: Confirm library can be imported successfully env: PYTHONPATH: "" # avoid picking up local sources + shell: bash run: | python - <<'PY' import sys, importlib, importlib.util, importlib.metadata as md, pathlib From 492b266efa30fd5b9a0875c9dfa81550e77afe1a Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 8 Sep 2025 14:11:18 +0000 Subject: [PATCH 12/13] windows --- template/.github/workflows/publish.yaml.jinja | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/template/.github/workflows/publish.yaml.jinja b/template/.github/workflows/publish.yaml.jinja index 33919996..395d4551 100644 --- a/template/.github/workflows/publish.yaml.jinja +++ b/template/.github/workflows/publish.yaml.jinja @@ -183,13 +183,8 @@ jobs: with: python-version: ${{ matrix.python-version }} {% endraw %}{% if python_package_registry == "PyPI" %}{% raw %} - - name: Sleep to allow PyPI Index to update before proceeding to the next step - if: ${{ runner.os == 'Windows' }} - uses: juliangruber/sleep-action@{% endraw %}{{ gha_sleep }}{% raw %} - with: - time: 60s - name: Wait for Test PyPI to serve the new version - if: ${{ runner.os != 'Windows' }} + shell: bash run: | set -euo pipefail PKG="{% endraw %}{{ package_name | replace('_', '-') }}{% raw %}" @@ -221,9 +216,6 @@ jobs: echo "Timeout waiting for ${PKG}==${VER} to be installable." exit 1{% endraw %}{% endif %}{% raw %} - - name: Install from staging registry - if: ${{ runner.os == 'Windows' }} - run: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://www.pypi.org/simple {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} --no-cache-dir - name: Display dependencies run: pip list - name: Confirm library can be imported successfully @@ -332,13 +324,8 @@ jobs: with: python-version: ${{ matrix.python-version }} {% endraw %}{% if python_package_registry == "PyPI" %}{% raw %} - - name: Sleep to allow PyPI Index to update before proceeding to the next step - if: ${{ runner.os == 'Windows' }} - uses: juliangruber/sleep-action@{% endraw %}{{ gha_sleep }}{% raw %} - with: - time: 60s - name: Wait for PyPI to serve the new version - if: ${{ runner.os != 'Windows' }} + shell: bash run: | set -euo pipefail PKG="{% endraw %}{{ package_name | replace('_', '-') }}{% raw %}" @@ -370,9 +357,6 @@ jobs: echo "Timeout waiting for ${PKG}==${VER} to be installable." exit 1{% endraw %}{% endif %}{% raw %} - - name: Install from primary registry - if: ${{ runner.os == 'Windows' }} - run: pip install {% endraw %}{{ package_name | replace('_', '-') }}{% raw %}==${{ needs.get-values.outputs.package-version }} --no-cache-dir - name: Display dependencies run: pip list - name: Confirm library can be imported successfully From 84defe65387cee303f8aecb967d9ad4bbf1b0140 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 8 Sep 2025 14:32:54 +0000 Subject: [PATCH 13/13] phrasing --- template/.github/workflows/publish.yaml.jinja | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/template/.github/workflows/publish.yaml.jinja b/template/.github/workflows/publish.yaml.jinja index 395d4551..099c526d 100644 --- a/template/.github/workflows/publish.yaml.jinja +++ b/template/.github/workflows/publish.yaml.jinja @@ -207,7 +207,7 @@ jobs: # Then try to install with retries to ensure it's actually available in the index for i in $(seq 1 12); do if pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://www.pypi.org/simple "${PKG}==${VER}" --no-cache-dir --quiet; then - echo "Successfully installed ${PKG}==${VER}!" + echo "Successfully installed ${PKG}==${VER}" exit 0 fi echo "Package not yet installable; sleeping 10s... (attempt $i/12)" @@ -348,7 +348,7 @@ jobs: # Then try to install with retries to ensure it's actually available in the index for i in $(seq 1 12); do if pip install --index-url https://pypi.org/simple/ "${PKG}==${VER}" --no-cache-dir --quiet; then - echo "Successfully installed ${PKG}==${VER}!" + echo "Successfully installed ${PKG}==${VER}" exit 0 fi echo "Package not yet installable; sleeping 10s... (attempt $i/12)"