diff --git a/.github/actions/setup-docker/action.yml b/.github/actions/setup-docker/action.yml new file mode 100644 index 0000000..846efd4 --- /dev/null +++ b/.github/actions/setup-docker/action.yml @@ -0,0 +1,23 @@ +name: "Set up Docker" +description: >- + Set up QEMU + Docker Buildx and authenticate to Docker Hub for multi-arch + image builds. Centralizes the QEMU/Buildx/login trio used by release, + preview, and stable workflows. + +inputs: + dockerhub-username: + description: "Docker Hub username (pass from secrets)" + required: true + dockerhub-token: + description: "Docker Hub token/password (pass from secrets)" + required: true + +runs: + using: "composite" + steps: + - uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + with: + username: ${{ inputs.dockerhub-username }} + password: ${{ inputs.dockerhub-token }} diff --git a/.github/actions/setup-hatch/action.yml b/.github/actions/setup-hatch/action.yml new file mode 100644 index 0000000..0da5160 --- /dev/null +++ b/.github/actions/setup-hatch/action.yml @@ -0,0 +1,13 @@ +name: "Set up Hatch build tooling" +description: >- + Install the pinned hatch / hatchling / virtualenv toolchain used to build + and publish the package. Assumes Python is already set up by the caller. + +runs: + using: "composite" + steps: + - shell: bash + run: | + python -m pip install --upgrade pip + pip install "virtualenv<20.36" + pip install hatchling==1.27.0 hatch==1.14.0 diff --git a/.github/actions/setup-sfw/action.yml b/.github/actions/setup-sfw/action.yml new file mode 100644 index 0000000..2804701 --- /dev/null +++ b/.github/actions/setup-sfw/action.yml @@ -0,0 +1,39 @@ +name: "Set up Socket Firewall (free)" +description: >- + Set up the requested language toolchain and install Socket Firewall (free + edition) so subsequent steps can run package-manager commands wrapped with + `sfw`. Free/anonymous mode -- no API token, safe on untrusted/Dependabot PRs. + +inputs: + python: + description: "Set up Python 3.12" + default: "false" + node: + description: "Set up Node 20 (needed for npm-wrapped checks)" + default: "false" + uv: + description: "Install uv (implies Python)" + default: "false" + +runs: + using: "composite" + steps: + - if: ${{ inputs.python == 'true' || inputs.uv == 'true' }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + + - if: ${{ inputs.node == 'true' }} + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "20" + + # Official Socket setup action. Wires up sfw routing correctly. + - uses: socketdev/action@ba6de6cc0565af1f42295590380973573297e31f # v1.3.2 + with: + mode: firewall-free + + - if: ${{ inputs.uv == 'true' }} + name: Install uv + shell: bash + run: python -m pip install --upgrade pip uv diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7c05ee5..89e2ed0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -36,9 +36,11 @@ updates: cooldown: default-days: 7 - # GitHub Actions used in workflows + # GitHub Actions used in workflows and local composite actions. - package-ecosystem: "github-actions" - directory: "/" + directories: + - "/" + - "/.github/actions/*" schedule: interval: "weekly" open-pull-requests-limit: 2 diff --git a/.github/workflows/dependabot-review.yml b/.github/workflows/dependabot-review.yml index 486ccb3..2bc8879 100644 --- a/.github/workflows/dependabot-review.yml +++ b/.github/workflows/dependabot-review.yml @@ -34,7 +34,7 @@ jobs: dockerfile_changed: ${{ steps.diff.outputs.dockerfile_changed }} workflow_or_action_changed: ${{ steps.diff.outputs.workflow_or_action_changed }} steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false @@ -68,7 +68,7 @@ jobs: echo "fixture_npm_changed=$(has_file '^tests/e2e/fixtures/simple-npm/')" echo "fixture_pypi_changed=$(has_file '^tests/e2e/fixtures/simple-pypi/')" echo "dockerfile_changed=$(has_file '^Dockerfile$')" - echo "workflow_or_action_changed=$(has_file '^\.github/workflows/|^\.github/dependabot\.yml$')" + echo "workflow_or_action_changed=$(has_file '^\.github/workflows/|^\.github/actions/|^\.github/dependabot\.yml$')" } >> "$GITHUB_OUTPUT" - name: Summarize review expectations @@ -89,27 +89,23 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + - uses: ./.github/actions/setup-sfw with: - python-version: "3.12" - - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af - with: - node-version: "20" - - - name: Install Socket Firewall - run: npm install -g sfw - - - name: Install uv - run: python -m pip install --upgrade pip uv + uv: "true" - name: Sync project through Socket Firewall - run: sfw uv sync --extra test --extra dev + # `sfw uv sync` is the intended way to route uv through Socket Firewall + # (per Socket's own uv wrapper guidance). --locked verifies the exact + # uv.lock set and fails on lockfile drift rather than silently + # re-resolving, so the firewall inspects precisely what would install. + # Note: uv's sfw integration is quieter than npm/pip -- it does not + # print the "N packages fetched" footer, but interception is active. + run: sfw uv sync --locked --extra test --extra dev - name: Import smoke test run: | @@ -130,17 +126,14 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af + - uses: ./.github/actions/setup-sfw with: - node-version: "20" - - - name: Install Socket Firewall - run: npm install -g sfw + node: "true" - name: Install fixture through Socket Firewall working-directory: tests/e2e/fixtures/simple-npm @@ -152,21 +145,14 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 - with: - python-version: "3.12" - - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af + - uses: ./.github/actions/setup-sfw with: - node-version: "20" - - - name: Install Socket Firewall - run: npm install -g sfw + python: "true" - name: Install fixture through Socket Firewall working-directory: tests/e2e/fixtures/simple-pypi @@ -182,7 +168,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false diff --git a/.github/workflows/docker-stable.yml b/.github/workflows/docker-stable.yml index 3639ffc..934e0d9 100644 --- a/.github/workflows/docker-stable.yml +++ b/.github/workflows/docker-stable.yml @@ -13,7 +13,7 @@ jobs: stable: runs-on: ubuntu-latest steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -28,23 +28,19 @@ jobs: fi echo "Version ${INPUT_VERSION} found on PyPI - proceeding with release" - - name: Set up QEMU - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - - name: Login to Docker Hub with Organization Token - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + - name: Set up Docker publishing + uses: ./.github/actions/setup-docker with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build & Push Stable Docker - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: push: true platforms: linux/amd64,linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max tags: socketdev/cli:stable build-args: | CLI_VERSION=${{ inputs.version }} diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 83a6fa4..1ce306f 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -70,16 +70,16 @@ jobs: name: e2e-${{ matrix.name }} steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false - - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 if: matrix.setup-node == 'true' with: node-version: '20' diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index 1d7115a..eb29ef9 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -3,29 +3,37 @@ on: pull_request: types: [opened, synchronize, ready_for_review] +# Cancel an in-flight preview when the PR is pushed again -- previews are slow +# (publish + multi-step Docker build), so superseded runs shouldn't keep going. +concurrency: + group: pr-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: preview: - if: github.event.pull_request.head.repo.full_name == github.repository + # Skip on: + # - PRs from forks (no access to publish secrets) + # - Dependabot PRs: preview-publishing a dependency bump to Test PyPI / + # Docker Hub is pointless and fails (no version bump, secret access). + if: >- + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.user.login != 'dependabot[bot]' runs-on: ubuntu-latest permissions: id-token: write contents: read pull-requests: write steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false - - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.13' - # Install all dependencies from pyproject.toml - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install "virtualenv<20.36" - pip install hatchling==1.27.0 hatch==1.14.0 + - name: Install build tooling + uses: ./.github/actions/setup-hatch - name: Inject full dynamic version run: python .hooks/sync_version.py --dev @@ -57,14 +65,14 @@ jobs: - name: Publish to Test PyPI if: steps.version_check.outputs.exists != 'true' - uses: pypa/gh-action-pypi-publish@ab69e431e9c9f48a3310be0a56527c679f56e04d + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 with: repository-url: https://test.pypi.org/legacy/ verbose: true - name: Comment on PR if: steps.version_check.outputs.exists != 'true' - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: VERSION: ${{ env.VERSION }} with: @@ -133,27 +141,26 @@ jobs: echo "success=false" >> $GITHUB_OUTPUT exit 1 - - name: Set up QEMU - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 - - - name: Login to Docker Hub with Organization Token + - name: Set up Docker publishing if: steps.verify_package.outputs.success == 'true' - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 + uses: ./.github/actions/setup-docker with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build & Push Docker Preview if: steps.verify_package.outputs.success == 'true' - uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 env: VERSION: ${{ env.VERSION }} with: push: true - platforms: linux/amd64,linux/arm64 + # Preview images are for quick testing -- build amd64 only. arm64 via + # QEMU emulation is the slowest part of the job; release builds keep + # multi-arch. GHA layer cache speeds up repeated preview builds. + platforms: linux/amd64 + cache-from: type=gha + cache-to: type=gha,mode=max tags: | socketdev/cli:pr-${{ github.event.pull_request.number }} build-args: | diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 94f4f82..3247275 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -35,12 +35,12 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: 🐍 setup python - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: 🛠️ install deps @@ -71,12 +71,12 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: 🐍 setup python - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.10" - name: 🚫 verify install is rejected on unsupported python diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5549b88..6c41e9c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,21 +10,17 @@ jobs: id-token: write contents: read steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false - - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.13' - # Install all dependencies from pyproject.toml - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install "virtualenv<20.36" - pip install hatchling==1.27.0 hatch==1.14.0 - + - name: Install build tooling + uses: ./.github/actions/setup-hatch + - name: Get Version id: version env: @@ -70,19 +66,13 @@ jobs: - name: Publish to PyPI if: steps.version_check.outputs.pypi_exists != 'true' - uses: pypa/gh-action-pypi-publish@ab69e431e9c9f48a3310be0a56527c679f56e04d - - - name: Set up QEMU - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 - - name: Login to Docker Hub with Organization Token - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 + - name: Set up Docker publishing + uses: ./.github/actions/setup-docker with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} - name: Verify package is installable id: verify_package @@ -106,12 +96,14 @@ jobs: if: | steps.verify_package.outputs.success == 'true' && steps.docker_check.outputs.docker_exists != 'true' - uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 env: VERSION: ${{ env.VERSION }} with: push: true platforms: linux/amd64,linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max tags: | socketdev/cli:latest socketdev/cli:${{ env.VERSION }} diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index 1eefa27..a097208 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -14,9 +14,13 @@ permissions: jobs: check_version: + # Skip on Dependabot PRs: they bump dependencies (touching uv.lock / + # pyproject.toml) without bumping the app version, so the increment check + # would always fail. App-version bumps come from maintainer PRs. + if: github.event.pull_request.user.login != 'dependabot[bot]' runs-on: ubuntu-latest steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # Fetch all history for all branches persist-credentials: false @@ -86,7 +90,7 @@ jobs: fi - name: Manage PR Comment - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 if: always() && github.event.pull_request.head.repo.full_name == github.repository env: MAIN_VERSION: ${{ env.MAIN_VERSION }}