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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Publish

on:
release:
types: [published]
workflow_dispatch:

jobs:
notify-packagist:
name: Notify Packagist
runs-on: ubuntu-latest

steps:
- name: Notify Packagist of new release
env:
PACKAGIST_USERNAME: ${{ secrets.PACKAGIST_USERNAME }}
PACKAGIST_API_TOKEN: ${{ secrets.PACKAGIST_API_TOKEN }}
run: |
HTTP_STATUS=$(curl --silent \
--output /dev/null \
--write-out "%{http_code}" \
--request POST \
--header "Content-Type: application/json" \
--data '{"repository":{"url":"https://github.com/KnpLabs/phpstan-rules"}}' \
"https://packagist.org/api/update-package?username=${PACKAGIST_USERNAME}&apiToken=${PACKAGIST_API_TOKEN}")
if [ "${HTTP_STATUS}" -lt 200 ] || [ "${HTTP_STATUS}" -ge 300 ]; then
echo "Packagist API call failed with HTTP status ${HTTP_STATUS}"
exit 1
fi
echo "Packagist notified successfully (HTTP ${HTTP_STATUS})"
3 changes: 3 additions & 0 deletions .specify/feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"feature_directory": "specs/001-packagist-release-automation"
}
57 changes: 31 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,6 @@ PHPStan rules shared across KnpLabs organization projects.
composer require --dev knplabs/phpstan-rules phpstan/extension-installer
```

The extension is loaded automatically.

### Manual configuration

> [!WARNING]
> The package is not yet available on Packagist. It will be release with the v1.0.0 once internal tests has been done.

During testing phase, install via VCS repository pointing at `dev-main`. Add repository to your `composer.json`:

```json
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/KnpLabs/phpstan-rules"
}
]
}
```

Then require dev-main version:

```bash
composer require --dev knplabs/phpstan-rules:dev-main
```

If you don't use `phpstan/extension-installer`, include the extension in your `phpstan.neon`:

```neon
Expand Down Expand Up @@ -135,6 +109,37 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for the human contributor guide.

If you are working with an AI agent, refer to [AGENTS.md](AGENTS.md) — it contains the AI-facing instructions for this repository.

## Release & Publishing

Releases are published to [Packagist](https://packagist.org/packages/knplabs/phpstan-rules) automatically when a GitHub release is created.

### Prerequisites (one-time setup)

1. **Register the package on Packagist** — submit the repository once at [packagist.org/packages/submit](https://packagist.org/packages/submit).

2. **Add repository secrets** — in GitHub → Settings → Secrets and variables → Actions, create two repository secrets:

| Secret name | Value |
|-------------|-------|
| `PACKAGIST_USERNAME` | Your Packagist account username |
| `PACKAGIST_API_TOKEN` | An API token generated on your [Packagist profile page](https://packagist.org/profile/) |

### Publishing a release

1. Create a new release in GitHub (Releases → Draft a new release).
2. Set the tag (e.g. `v1.2.0`), fill in the title and description, then click **Publish release**.
3. The `Publish` workflow triggers automatically and notifies Packagist via its REST API.
4. The new version appears on Packagist within a few minutes.

> **Draft and pre-releases** — the workflow only fires on published releases. Saving a draft or marking a release as a pre-release does **not** trigger the automation.

### Manual re-trigger

If the workflow fails or you need to re-sync without creating a new release:

1. Go to Actions → **Publish** → **Run workflow**.
2. Click **Run workflow** (no inputs required).

## License

MIT — see [LICENSE](LICENSE).
34 changes: 34 additions & 0 deletions specs/001-packagist-release-automation/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Specification Quality Checklist: Packagist Release Automation

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-07-15
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified (failed update, draft/pre-release exclusion)
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

All items pass. Spec is ready for `/speckit-plan`.
64 changes: 64 additions & 0 deletions specs/001-packagist-release-automation/contracts/packagist-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Contract — Packagist Update API

**Used by:** `.github/workflows/publish.yml`
**External service:** packagist.org

---

## Endpoint

```
POST https://packagist.org/api/update-package
```

## Query Parameters

| Parameter | Source | Description |
|------------|-------------------------------|-----------------------------------|
| `username` | `PACKAGIST_USERNAME` secret | Packagist account username |
| `apiToken` | `PACKAGIST_API_TOKEN` secret | Packagist API token |

## Request Body

```json
{
"repository": {
"url": "https://github.com/KnpLabs/phpstan-rules"
}
}
```

`Content-Type: application/json`

## Response Codes

| Code | Meaning |
|------|------------------------------------------|
| 202 | Accepted — Packagist will re-crawl soon |
| 400 | Bad request — malformed body |
| 401 | Unauthorized — invalid credentials |
| 404 | Package not found on Packagist |

## Success Condition

HTTP response code in the 2xx range. Any other code is treated as a failure and MUST cause the
GitHub Actions step to exit non-zero.

---

## Contract — GitHub Actions Release Trigger

**Event:** `release`
**Activity type:** `published`

Only fires when a release transitions from draft/pre-release to the `published` state.
Does NOT fire on `created`, `edited`, `prereleased`, or `unpublished` activity types.

**Payload fields used:**

| Field | Value | Description |
|-------|-------|-------------|
| `action` | `"published"` | Activity type filter |
| `release.tag_name` | e.g. `"v1.0.0"` | The version tag (informational; not sent to Packagist) |
| `release.prerelease` | `false` | Guaranteed false when `types: [published]` is used |
| `release.draft` | `false` | Guaranteed false when `types: [published]` is used |
111 changes: 111 additions & 0 deletions specs/001-packagist-release-automation/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Implementation Plan — Packagist Release Automation

**Constitution version:** 1.0.0
**Spec reference:** `specs/001-packagist-release-automation/spec.md`
**Research reference:** `specs/001-packagist-release-automation/research.md`
**Author:** KnpLabs
**Date:** 2026-07-15

---

## Constitution Check

| Principle | Check | Notes |
|-----------|-------|-------|
| 1 — Broad Compatibility | ✅ N/A | CI automation; no PHP/PHPStan constraint changes |
| 2 — Test Coverage per Rule | ✅ N/A | No new PHPStan rule added |
| 3 — Documentation per Rule | ✅ Required | `README.md` MUST document the setup prerequisites |
| 4 — Open-Source Quality Standards | ✅ Required | Workflow file must follow GitHub Actions best practices |
| 5 — Single-Responsibility Rule Design | ✅ Satisfied | Standalone `publish.yml`, separate from `ci.yml` |

---

## Approach

Create a dedicated GitHub Actions workflow that fires on `release: published` (and optionally
`workflow_dispatch` for manual re-runs). The workflow calls the Packagist REST API via `curl`
to notify Packagist that a new version is available. Credentials are sourced from encrypted
repository secrets.

No source code changes to the PHP extension itself are required.

### Alternatives considered

| Alternative | Rejected because |
|-------------|-----------------|
| GitHub webhook approach | No failure signal in Actions; harder to audit |
| Third-party publish Action | Supply-chain risk; curl is sufficient |

---

## File Inventory

| File | Action | Notes |
|------|--------|-------|
| `.github/workflows/publish.yml` | **Create** | Release trigger + Packagist API call |
| `README.md` | **Edit** | Add "Release & Publishing" section with setup steps |

---

## Workflow Design — `.github/workflows/publish.yml`

**Triggers:**
- `release: types: [published]` — automatic on GitHub release publication
- `workflow_dispatch:` — manual re-run from the Actions UI

**Jobs:**

```
notify-packagist
runs-on: ubuntu-latest
steps:
1. Call Packagist update API via curl
- URL: https://packagist.org/api/update-package
- Method: POST
- Query params: username, apiToken (from secrets)
- Body: {"repository":{"url":"https://github.com/KnpLabs/phpstan-rules"}}
- Assert HTTP response is 2xx; non-2xx fails the step
```

**Secrets required (set by maintainer once):**
- `PACKAGIST_USERNAME` — Packagist account username
- `PACKAGIST_API_TOKEN` — Packagist API token (generated on packagist.org profile page)

**Secret exposure mitigation:**
- Secrets injected as environment variables, never echoed or logged
- Passed as query parameters (HTTPS), not in the request body or log output

---

## README Changes

Add a new section **"Release & Publishing"** covering:
1. Prerequisite: register the package on Packagist once (link to Packagist docs).
2. Set `PACKAGIST_USERNAME` and `PACKAGIST_API_TOKEN` in GitHub repository secrets.
3. When a GitHub release is published, the workflow runs automatically.
4. How to manually re-trigger from the Actions UI.

---

## Edge Cases & Risks

| Case | Handling |
|------|----------|
| Packagist API outage | `curl` returns non-2xx; workflow step fails; maintainer sees error in Actions |
| Invalid credentials | Packagist returns 4xx; workflow step fails with HTTP status in logs |
| Draft or pre-release published | `types: [published]` excludes these; no trigger |
| Accidental secret exposure | Secrets masked by GitHub Actions runner; HTTPS transport |
| Workflow re-run needed | `workflow_dispatch` trigger allows manual re-run |

---

## Validation Steps

1. Create a test release on GitHub and verify:
- The `publish` workflow appears in the Actions tab.
- The workflow completes with a green status.
- The new version appears on `packagist.org/packages/knplabs/phpstan-rules`.
2. Verify secret values are masked (`***`) in the workflow log output.
3. Verify that a draft release does NOT trigger the workflow.
4. Manually trigger the workflow via the Actions UI ("Run workflow" button) and confirm it runs.
5. Verify the existing `ci.yml` matrix jobs are unaffected.
Loading