Skip to content

Add publish workflow for pub.dev#109

Merged
kherembourg merged 3 commits intomainfrom
feat/publish-workflow
Mar 17, 2026
Merged

Add publish workflow for pub.dev#109
kherembourg merged 3 commits intomainfrom
feat/publish-workflow

Conversation

@kherembourg
Copy link
Copy Markdown
Collaborator

Summary

  • Add GitHub Actions workflow to automate publishing to pub.dev
  • Triggered on tag push (v*) or manual dispatch
  • Validates version consistency across all 3 packages before publishing
  • Publishes packages in order: purchasely_flutterpurchasely_googlepurchasely_android_player
  • Uses PUB_CREDENTIALS secret (already configured) for authentication

Usage

Automatic (next release)

Just push a tag and it publishes:

git tag -a v5.8.0 -m "Release 5.8.0"
git push origin v5.8.0

Manual

Go to Actions → "Publish to pub.dev" → Run workflow (uncheck "dry run" to actually publish)

Test plan

  • YAML validated
  • CI passes
  • Test with manual dispatch (dry run mode)

🤖 Generated with Claude Code

Automates package publishing on tag push (v*) or manual dispatch.
Publishes all 3 packages in order with version consistency checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 17, 2026

Greptile Summary

This PR introduces a GitHub Actions workflow to automate publishing the three Purchasely Flutter packages (purchasely_flutter, purchasely_google, purchasely_android_player) to pub.dev. The workflow is triggered by v* tag pushes or manual dispatch, validates version consistency and runs dry-runs before publishing sequentially in dependency order.

The overall structure is sound and follows the existing CI pattern in the repository. However, there are a few issues worth addressing before relying on this in production:

  • Suppressed dry-run validation: Failures from flutter pub publish --dry-run for purchasely_google and purchasely_android_player are silently swallowed with || echo "::warning::". While this is intentional to handle the expected local-path dependency resolution error, it also hides any other real validation failure (missing files, bad pubspec fields, etc.), making the validate job provide a false sense of security.
  • Insufficient indexing wait: The hardcoded sleep 30 between publishing purchasely_flutter and running flutter pub get for purchasely_google (which depends on purchasely_flutter: any) may be too short. pub.dev indexing can take 1–5+ minutes, and no retry/polling mechanism exists.
  • No approval gate: Publishing to pub.dev is irreversible. Any v* tag push immediately triggers a real publish with no human review step. Adding a GitHub Environment with required reviewers would prevent accidental releases.

Confidence Score: 3/5

  • Safe to merge for initial setup, but the suppressed dry-run failures and lack of an approval gate mean production reliability is not yet guaranteed.
  • The workflow is well-structured and correctly sequences the three publishes. The main concerns are operational: suppressed validation failures could hide real publish-time errors, the 30-second sleep is likely too short for pub.dev indexing, and there is no human approval gate to prevent accidental irreversible publishes on any tag push.
  • .github/workflows/publish.yml — specifically the dry-run suppression (lines 74–84) and the missing environment protection on the publish job.

Important Files Changed

Filename Overview
.github/workflows/publish.yml New GitHub Actions workflow that automates pub.dev publishing for three packages sequentially. Key concerns: dry-run failures for dependent packages are fully suppressed masking real validation errors (P1), a hardcoded 30-second sleep may be insufficient for pub.dev indexing between sequential publishes, and no GitHub Environment approval gate protects against accidental irreversible publishes triggered by any tag push.

Sequence Diagram

sequenceDiagram
    participant Dev as Developer
    participant GH as GitHub Actions
    participant PubDev as pub.dev

    Dev->>GH: Push tag v* (or manual dispatch)
    GH->>GH: validate job: check version consistency
    GH->>GH: validate job: flutter pub publish --dry-run (purchasely_flutter)
    GH->>GH: validate job: dry-run purchasely_google (failures suppressed)
    GH->>GH: validate job: dry-run purchasely_android_player (failures suppressed)
    GH->>PubDev: publish job: flutter pub publish --force (purchasely_flutter)
    GH->>GH: sleep 30s (fixed delay)
    GH->>PubDev: publish job: flutter pub publish --force (purchasely_google)
    Note over GH,PubDev: purchasely_flutter: any resolved from pub.dev<br/>(may not be indexed yet after only 30s)
    GH->>PubDev: publish job: flutter pub publish --force (purchasely_android_player)
    GH->>GH: Write summary to GITHUB_STEP_SUMMARY
Loading

Fix All in Claude Code Fix All in Cursor Fix All in Codex

Prompt To Fix All With AI
This is a comment left during a code review.
Path: .github/workflows/publish.yml
Line: 116-117

Comment:
**Hardcoded sleep may be insufficient for pub.dev indexing**

A static `sleep 30` is commonly not enough for pub.dev to fully index a newly published package. pub.dev indexing can take anywhere from 1 to 5+ minutes depending on load. When `flutter pub get` subsequently runs for `purchasely_google`, it resolves `purchasely_flutter: any` from pub.dev. If the new version isn't indexed yet, it silently resolves to the previous version, meaning `purchasely_google` could be published with a stale resolved dependency.

Consider replacing the static sleep with a retry loop that polls the pub.dev API until the new version is confirmed available:

```yaml
      - name: Wait for pub.dev indexing
        run: |
          VERSION=$(grep '^version:' purchasely/pubspec.yaml | awk '{print $2}')
          echo "Waiting for purchasely_flutter $VERSION to be indexed on pub.dev..."
          for i in $(seq 1 20); do
            STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://pub.dev/api/packages/purchasely_flutter/versions/$VERSION")
            if [[ "$STATUS" == "200" ]]; then
              echo "Package indexed successfully."
              break
            fi
            echo "Attempt $i: not indexed yet (HTTP $STATUS), retrying in 30s..."
            sleep 30
          done
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: .github/workflows/publish.yml
Line: 74-84

Comment:
**Suppressed dry-run failures hide real validation errors**

Both the `purchasely_google` and `purchasely_android_player` dry-run steps use `|| echo "::warning::"` to swallow failures. The intent is to tolerate the expected failure caused by the local path override making the package unpublishable. However, this also silently suppresses **any other real issue** — e.g., missing files, invalid `pubspec.yaml` entries, or analysis errors — giving false confidence that validation passed when it may not have.

Since the `purchasely_flutter` path override is already applied via `pubspec_overrides.yaml` before these steps, the dry-run has some value. Consider at least capturing the exit code and emitting a more visible warning, or validating only the fields that don't require the dependency to be live (e.g., `dart pub publish --dry-run 2>&1 | grep -v "path dependency"`). At minimum, document in a comment that *any* failure (not just the dependency one) is being masked:

```yaml
      - name: Dry run (purchasely_google)
        working-directory: purchasely_google
        # NOTE: This dry-run is expected to fail due to the local path override for
        # purchasely_flutter. ALL failures are suppressed — fix real issues before merging.
        run: flutter pub publish --dry-run || echo "::warning::purchasely_google dry-run failed (expected if purchasely_flutter not yet published)"
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: .github/workflows/publish.yml
Line: 89-93

Comment:
**No environment protection gate for irreversible publish**

Publishing to pub.dev is irreversible — a version can only be "retracted" (not deleted), and retraction still leaves the version visible and accessible. The `publish` job triggers automatically on any `v*` tag push with no additional human approval step. An accidental `git push origin v5.8.0` immediately publishes all three packages to pub.dev.

Consider adding a [GitHub Environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment) with required reviewers:

```yaml
  publish:
    name: Publish packages
    runs-on: ubuntu-latest
    needs: [validate]
    environment: pub-dev-production   # requires manual approval
    if: ${{ github.ref_type == 'tag' || (github.event_name == 'workflow_dispatch' && inputs.dry_run == false) }}
```

Then define `pub-dev-production` in GitHub → Settings → Environments with at least one required reviewer. This adds a human gate before any publication occurs.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 3f850fb

Comment on lines +116 to +117
- name: Wait for pub.dev indexing
run: sleep 30
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Hardcoded sleep may be insufficient for pub.dev indexing

A static sleep 30 is commonly not enough for pub.dev to fully index a newly published package. pub.dev indexing can take anywhere from 1 to 5+ minutes depending on load. When flutter pub get subsequently runs for purchasely_google, it resolves purchasely_flutter: any from pub.dev. If the new version isn't indexed yet, it silently resolves to the previous version, meaning purchasely_google could be published with a stale resolved dependency.

Consider replacing the static sleep with a retry loop that polls the pub.dev API until the new version is confirmed available:

      - name: Wait for pub.dev indexing
        run: |
          VERSION=$(grep '^version:' purchasely/pubspec.yaml | awk '{print $2}')
          echo "Waiting for purchasely_flutter $VERSION to be indexed on pub.dev..."
          for i in $(seq 1 20); do
            STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://pub.dev/api/packages/purchasely_flutter/versions/$VERSION")
            if [[ "$STATUS" == "200" ]]; then
              echo "Package indexed successfully."
              break
            fi
            echo "Attempt $i: not indexed yet (HTTP $STATUS), retrying in 30s..."
            sleep 30
          done
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/publish.yml
Line: 116-117

Comment:
**Hardcoded sleep may be insufficient for pub.dev indexing**

A static `sleep 30` is commonly not enough for pub.dev to fully index a newly published package. pub.dev indexing can take anywhere from 1 to 5+ minutes depending on load. When `flutter pub get` subsequently runs for `purchasely_google`, it resolves `purchasely_flutter: any` from pub.dev. If the new version isn't indexed yet, it silently resolves to the previous version, meaning `purchasely_google` could be published with a stale resolved dependency.

Consider replacing the static sleep with a retry loop that polls the pub.dev API until the new version is confirmed available:

```yaml
      - name: Wait for pub.dev indexing
        run: |
          VERSION=$(grep '^version:' purchasely/pubspec.yaml | awk '{print $2}')
          echo "Waiting for purchasely_flutter $VERSION to be indexed on pub.dev..."
          for i in $(seq 1 20); do
            STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://pub.dev/api/packages/purchasely_flutter/versions/$VERSION")
            if [[ "$STATUS" == "200" ]]; then
              echo "Package indexed successfully."
              break
            fi
            echo "Attempt $i: not indexed yet (HTTP $STATUS), retrying in 30s..."
            sleep 30
          done
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Cursor Fix in Codex

Comment on lines +74 to +84
- name: Dry run (purchasely_google)
working-directory: purchasely_google
run: flutter pub publish --dry-run || echo "::warning::purchasely_google dry-run failed (expected if purchasely_flutter not yet published)"

- name: Install dependencies (purchasely_android_player)
working-directory: purchasely_android_player
run: flutter pub get

- name: Dry run (purchasely_android_player)
working-directory: purchasely_android_player
run: flutter pub publish --dry-run || echo "::warning::purchasely_android_player dry-run failed (expected if purchasely_flutter not yet published)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Suppressed dry-run failures hide real validation errors

Both the purchasely_google and purchasely_android_player dry-run steps use || echo "::warning::" to swallow failures. The intent is to tolerate the expected failure caused by the local path override making the package unpublishable. However, this also silently suppresses any other real issue — e.g., missing files, invalid pubspec.yaml entries, or analysis errors — giving false confidence that validation passed when it may not have.

Since the purchasely_flutter path override is already applied via pubspec_overrides.yaml before these steps, the dry-run has some value. Consider at least capturing the exit code and emitting a more visible warning, or validating only the fields that don't require the dependency to be live (e.g., dart pub publish --dry-run 2>&1 | grep -v "path dependency"). At minimum, document in a comment that any failure (not just the dependency one) is being masked:

      - name: Dry run (purchasely_google)
        working-directory: purchasely_google
        # NOTE: This dry-run is expected to fail due to the local path override for
        # purchasely_flutter. ALL failures are suppressed — fix real issues before merging.
        run: flutter pub publish --dry-run || echo "::warning::purchasely_google dry-run failed (expected if purchasely_flutter not yet published)"
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/publish.yml
Line: 74-84

Comment:
**Suppressed dry-run failures hide real validation errors**

Both the `purchasely_google` and `purchasely_android_player` dry-run steps use `|| echo "::warning::"` to swallow failures. The intent is to tolerate the expected failure caused by the local path override making the package unpublishable. However, this also silently suppresses **any other real issue** — e.g., missing files, invalid `pubspec.yaml` entries, or analysis errors — giving false confidence that validation passed when it may not have.

Since the `purchasely_flutter` path override is already applied via `pubspec_overrides.yaml` before these steps, the dry-run has some value. Consider at least capturing the exit code and emitting a more visible warning, or validating only the fields that don't require the dependency to be live (e.g., `dart pub publish --dry-run 2>&1 | grep -v "path dependency"`). At minimum, document in a comment that *any* failure (not just the dependency one) is being masked:

```yaml
      - name: Dry run (purchasely_google)
        working-directory: purchasely_google
        # NOTE: This dry-run is expected to fail due to the local path override for
        # purchasely_flutter. ALL failures are suppressed — fix real issues before merging.
        run: flutter pub publish --dry-run || echo "::warning::purchasely_google dry-run failed (expected if purchasely_flutter not yet published)"
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Cursor Fix in Codex

Comment thread .github/workflows/publish.yml Outdated
Comment on lines +89 to +93
publish:
name: Publish packages
runs-on: ubuntu-latest
needs: [validate]
if: ${{ github.ref_type == 'tag' || (github.event_name == 'workflow_dispatch' && inputs.dry_run == false) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 No environment protection gate for irreversible publish

Publishing to pub.dev is irreversible — a version can only be "retracted" (not deleted), and retraction still leaves the version visible and accessible. The publish job triggers automatically on any v* tag push with no additional human approval step. An accidental git push origin v5.8.0 immediately publishes all three packages to pub.dev.

Consider adding a GitHub Environment with required reviewers:

  publish:
    name: Publish packages
    runs-on: ubuntu-latest
    needs: [validate]
    environment: pub-dev-production   # requires manual approval
    if: ${{ github.ref_type == 'tag' || (github.event_name == 'workflow_dispatch' && inputs.dry_run == false) }}

Then define pub-dev-production in GitHub → Settings → Environments with at least one required reviewer. This adds a human gate before any publication occurs.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/publish.yml
Line: 89-93

Comment:
**No environment protection gate for irreversible publish**

Publishing to pub.dev is irreversible — a version can only be "retracted" (not deleted), and retraction still leaves the version visible and accessible. The `publish` job triggers automatically on any `v*` tag push with no additional human approval step. An accidental `git push origin v5.8.0` immediately publishes all three packages to pub.dev.

Consider adding a [GitHub Environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment) with required reviewers:

```yaml
  publish:
    name: Publish packages
    runs-on: ubuntu-latest
    needs: [validate]
    environment: pub-dev-production   # requires manual approval
    if: ${{ github.ref_type == 'tag' || (github.event_name == 'workflow_dispatch' && inputs.dry_run == false) }}
```

Then define `pub-dev-production` in GitHub → Settings → Environments with at least one required reviewer. This adds a human gate before any publication occurs.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Cursor Fix in Codex

kherembourg and others added 2 commits March 17, 2026 11:28
Replace PUB_CREDENTIALS secret with pub.dev OIDC automated publishing.
Each package publishes in its own job with its own OIDC token.
No secrets to maintain or rotate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Document the automated publish workflow (OIDC via GitHub Actions).
Update release checklist to use tag-based publishing instead of local publish.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@kherembourg kherembourg merged commit 1435923 into main Mar 17, 2026
8 checks passed
@kherembourg kherembourg deleted the feat/publish-workflow branch March 17, 2026 10:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant