Publish on PyPI #11
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish on PyPI | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| dry_run_fixture: | |
| description: Exercise API URL approval with the checked-in fixture. | |
| required: false | |
| default: false | |
| type: boolean | |
| permissions: | |
| contents: read | |
| jobs: | |
| plan: | |
| name: Plan publish | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| api_url_approval_required: ${{ steps.api-url-check.outputs.approval_required }} | |
| api_url_change_count: ${{ steps.api-url-check.outputs.change_count }} | |
| api_url_diff_summary: ${{ steps.api-url-check.outputs.summary }} | |
| changed: ${{ steps.release-plan.outputs.changed }} | |
| reason: ${{ steps.release-plan.outputs.reason }} | |
| run_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| tag: ${{ steps.release-plan.outputs.tag }} | |
| upstream_json_digest: ${{ steps.api-url-input.outputs.digest }} | |
| version: ${{ steps.release-plan.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Prepare API URL check input | |
| id: api-url-input | |
| env: | |
| DRY_RUN_FIXTURE: ${{ inputs.dry_run_fixture }} | |
| run: | | |
| if [ "$DRY_RUN_FIXTURE" = "true" ]; then | |
| python .github/scripts/create_api_url_dry_run_baseline.py \ | |
| .tmp/api-url-dry-run-baseline.sqlite | |
| DIGEST=$(python - <<'PY' | |
| import hashlib | |
| from pathlib import Path | |
| payload = Path(".github/fixtures/api-url-compromise.json").read_bytes() | |
| print(hashlib.sha256(payload).hexdigest()) | |
| PY | |
| ) | |
| printf 'source_path=%s\n' \ | |
| ".github/fixtures/api-url-compromise.json" >> "$GITHUB_OUTPUT" | |
| printf 'baseline_db=%s\n' \ | |
| ".tmp/api-url-dry-run-baseline.sqlite" >> "$GITHUB_OUTPUT" | |
| printf 'baseline_version=%s\n' \ | |
| "dry-run-baseline" >> "$GITHUB_OUTPUT" | |
| else | |
| DIGEST=$(python .github/scripts/fetch_upstream_json.py .tmp/models.dev-api.json) | |
| printf 'source_path=%s\n' ".tmp/models.dev-api.json" >> "$GITHUB_OUTPUT" | |
| printf 'baseline_db=%s\n' "" >> "$GITHUB_OUTPUT" | |
| printf 'baseline_version=%s\n' "" >> "$GITHUB_OUTPUT" | |
| fi | |
| printf 'digest=%s\n' "$DIGEST" >> "$GITHUB_OUTPUT" | |
| - name: Check upstream API URL changes | |
| id: api-url-check | |
| env: | |
| BASELINE_DB: ${{ steps.api-url-input.outputs.baseline_db }} | |
| BASELINE_VERSION: ${{ steps.api-url-input.outputs.baseline_version }} | |
| SOURCE_PATH: ${{ steps.api-url-input.outputs.source_path }} | |
| UPSTREAM_JSON_DIGEST: ${{ steps.api-url-input.outputs.digest }} | |
| run: | | |
| BASELINE_ARGS=() | |
| if [ -n "$BASELINE_DB" ]; then | |
| BASELINE_ARGS=( | |
| --baseline-db "$BASELINE_DB" | |
| --baseline-version "$BASELINE_VERSION" | |
| ) | |
| fi | |
| python .github/scripts/check_upstream_api_urls.py \ | |
| "$SOURCE_PATH" \ | |
| "${BASELINE_ARGS[@]}" \ | |
| --digest "$UPSTREAM_JSON_DIGEST" \ | |
| --json-output .tmp/api-url-diff.json \ | |
| --markdown-output .tmp/api-url-diff.md | |
| - name: Print upstream API URL check | |
| run: | | |
| python - <<'PY' | |
| import os | |
| from pathlib import Path | |
| with Path(os.environ["GITHUB_STEP_SUMMARY"]).open("a") as summary: | |
| summary.write(Path(".tmp/api-url-diff.md").read_text()) | |
| summary.write("\n") | |
| PY | |
| - name: Plan release | |
| id: release-plan | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| UPSTREAM_JSON_DIGEST: ${{ steps.api-url-input.outputs.digest }} | |
| with: | |
| script: | | |
| await require("./.github/scripts/plan_release")({github, context, core}); | |
| - name: Print release plan | |
| env: | |
| REASON: ${{ steps.release-plan.outputs.reason }} | |
| VERSION: ${{ steps.release-plan.outputs.version }} | |
| run: printf '%s\n' "$REASON" | |
| api_url_change_approval: | |
| name: Approve upstream API URL changes | |
| needs: plan | |
| if: ${{ needs.plan.outputs.changed == 'true' && needs.plan.outputs.api_url_approval_required == 'true' }} | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| # The protected environment is the manual supply-chain gate. Its URL points | |
| # reviewers to the plan job summary, where the API URL diff is rendered. | |
| environment: | |
| name: pypi-api-url-change | |
| url: ${{ needs.plan.outputs.run_url }} | |
| steps: | |
| - name: Approval recorded | |
| env: | |
| VERSION: ${{ needs.plan.outputs.version }} | |
| run: printf '%s\n' "Approved upstream API URL changes for $VERSION" | |
| publish: | |
| name: Publish | |
| needs: | |
| - plan | |
| - api_url_change_approval | |
| if: ${{ always() && !cancelled() && inputs.dry_run_fixture != true && needs.plan.outputs.changed == 'true' && (needs.plan.outputs.api_url_approval_required != 'true' || needs.api_url_change_approval.result == 'success') }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # required for creating tags and releases | |
| id-token: write # required for Trusted Publishing (OIDC) | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/p/modelsdotdev | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.14" | |
| - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 | |
| with: | |
| enable-cache: false | |
| - run: uv venv --python 3.14 | |
| - name: Fetch upstream JSON | |
| id: upstream-json | |
| env: | |
| EXPECTED_DIGEST: ${{ needs.plan.outputs.upstream_json_digest }} | |
| run: | | |
| DIGEST=$(uv run python .github/scripts/fetch_upstream_json.py .tmp/models.dev-api.json) | |
| if [ "$DIGEST" != "$EXPECTED_DIGEST" ]; then | |
| printf '%s\n' "upstream JSON digest changed after approval planning" | |
| printf '%s\n' "planned: $EXPECTED_DIGEST" | |
| printf '%s\n' "current: $DIGEST" | |
| exit 1 | |
| fi | |
| echo "digest=$DIGEST" >> "$GITHUB_OUTPUT" | |
| - name: Build | |
| env: | |
| MODELDOTDEV_BUILD_SOURCE: .tmp/models.dev-api.json | |
| UV_DYNAMIC_VERSIONING_BYPASS: ${{ needs.plan.outputs.version }} | |
| run: uv build | |
| - name: Test installed wheel | |
| run: sh .github/scripts/test_installed_wheel.sh | |
| - name: Publish to PyPI | |
| run: uv publish --trusted-publishing always | |
| - name: Create GitHub release | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| RELEASE_TAG: ${{ needs.plan.outputs.tag }} | |
| RELEASE_VERSION: ${{ needs.plan.outputs.version }} | |
| RELEASE_COMMIT_SHA: ${{ github.sha }} | |
| UPSTREAM_JSON_DIGEST: ${{ steps.upstream-json.outputs.digest }} | |
| with: | |
| script: | | |
| await require("./.github/scripts/create_release")({github, context, core}); | |
| dry_run_publish: | |
| name: Dry-run publish continuation | |
| needs: | |
| - plan | |
| - api_url_change_approval | |
| if: ${{ always() && !cancelled() && inputs.dry_run_fixture == true && (needs.plan.outputs.api_url_approval_required != 'true' || needs.api_url_change_approval.result == 'success') }} | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| steps: | |
| - name: Confirm dry run completed | |
| run: | | |
| printf '%s\n' "Dry run complete. No build, upload, tag, or release was created." |