From 5e6b07c3a0df3630a99744038ec1c73937db31aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20V=C3=A9tillard?= <84911237+clementvtrd@users.noreply.github.com> Date: Wed, 15 Jul 2026 13:54:29 +0200 Subject: [PATCH] feat: implement Packagist release automation workflow and update documentation --- .github/workflows/publish.yml | 30 ++++ .specify/feature.json | 3 + README.md | 57 ++++---- .../checklists/requirements.md | 34 +++++ .../contracts/packagist-api.md | 64 +++++++++ .../001-packagist-release-automation/plan.md | 111 +++++++++++++++ .../quickstart.md | 102 ++++++++++++++ .../research.md | 107 ++++++++++++++ .../001-packagist-release-automation/spec.md | 133 ++++++++++++++++++ .../001-packagist-release-automation/tasks.md | 128 +++++++++++++++++ 10 files changed, 743 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/publish.yml create mode 100644 .specify/feature.json create mode 100644 specs/001-packagist-release-automation/checklists/requirements.md create mode 100644 specs/001-packagist-release-automation/contracts/packagist-api.md create mode 100644 specs/001-packagist-release-automation/plan.md create mode 100644 specs/001-packagist-release-automation/quickstart.md create mode 100644 specs/001-packagist-release-automation/research.md create mode 100644 specs/001-packagist-release-automation/spec.md create mode 100644 specs/001-packagist-release-automation/tasks.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..fa1a8c4 --- /dev/null +++ b/.github/workflows/publish.yml @@ -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})" diff --git a/.specify/feature.json b/.specify/feature.json new file mode 100644 index 0000000..48bae1d --- /dev/null +++ b/.specify/feature.json @@ -0,0 +1,3 @@ +{ + "feature_directory": "specs/001-packagist-release-automation" +} diff --git a/README.md b/README.md index f70eb76..d95a234 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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). diff --git a/specs/001-packagist-release-automation/checklists/requirements.md b/specs/001-packagist-release-automation/checklists/requirements.md new file mode 100644 index 0000000..59fd5e2 --- /dev/null +++ b/specs/001-packagist-release-automation/checklists/requirements.md @@ -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`. diff --git a/specs/001-packagist-release-automation/contracts/packagist-api.md b/specs/001-packagist-release-automation/contracts/packagist-api.md new file mode 100644 index 0000000..66dd8d4 --- /dev/null +++ b/specs/001-packagist-release-automation/contracts/packagist-api.md @@ -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 | diff --git a/specs/001-packagist-release-automation/plan.md b/specs/001-packagist-release-automation/plan.md new file mode 100644 index 0000000..6a6dc1d --- /dev/null +++ b/specs/001-packagist-release-automation/plan.md @@ -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. diff --git a/specs/001-packagist-release-automation/quickstart.md b/specs/001-packagist-release-automation/quickstart.md new file mode 100644 index 0000000..4cc9afd --- /dev/null +++ b/specs/001-packagist-release-automation/quickstart.md @@ -0,0 +1,102 @@ +# Quickstart Validation Guide — Packagist Release Automation + +**Feature:** `specs/001-packagist-release-automation` +**Plan:** `specs/001-packagist-release-automation/plan.md` + +--- + +## Prerequisites + +Before validating this feature, a maintainer must complete these one-time steps: + +1. **Register the package on Packagist** + - Visit [packagist.org/packages/submit](https://packagist.org/packages/submit) + - Submit `https://github.com/KnpLabs/phpstan-rules` + - Verify the package page appears at `packagist.org/packages/knplabs/phpstan-rules` + +2. **Generate a Packagist API token** + - Log in to packagist.org → Profile → API tokens → "Create token" + - Copy the token value immediately (it is shown only once) + +3. **Store secrets in GitHub** + - Navigate to: `github.com/KnpLabs/phpstan-rules` → Settings → Secrets and variables → Actions + - Add `PACKAGIST_USERNAME` = your Packagist username + - Add `PACKAGIST_API_TOKEN` = the token from step 2 + +4. **Ensure the workflow file exists** at `.github/workflows/publish.yml` (created by this feature) + +--- + +## Validation Scenarios + +### Scenario A — Automated trigger on release publication + +**Steps:** +1. On GitHub, create a new release: Releases → "Draft a new release" +2. Create a new tag (e.g., `v1.0.0`), set title, description +3. Click **"Publish release"** (not "Save draft") + +**Expected outcome:** +- Within seconds, a new workflow run named `publish` appears in the Actions tab +- The run completes green within ~1 minute +- On `packagist.org/packages/knplabs/phpstan-rules`, the new version appears within 5 minutes + +**Failure signal:** +- If the step fails, the Actions log shows the HTTP status code and Packagist error message + +--- + +### Scenario B — Secret masking + +**Steps:** +1. Inspect the Actions log of a successful `publish` run + +**Expected outcome:** +- The `PACKAGIST_USERNAME` value appears as `***` in the log +- The `PACKAGIST_API_TOKEN` value appears as `***` in the log +- No plaintext credentials visible anywhere in the log + +--- + +### Scenario C — Draft release does NOT trigger + +**Steps:** +1. On GitHub, create a release and click **"Save draft"** (do NOT publish) + +**Expected outcome:** +- No new `publish` workflow run appears in the Actions tab + +--- + +### Scenario D — Manual re-trigger + +**Steps:** +1. Navigate to Actions → `publish` workflow +2. Click **"Run workflow"** → select branch `main` → "Run workflow" + +**Expected outcome:** +- A new workflow run starts immediately +- The run calls Packagist and completes green (if secrets are valid) + +--- + +### Scenario E — Existing CI unaffected + +**Steps:** +1. Open any passing run of the `CI` workflow + +**Expected outcome:** +- All `CI` matrix jobs (PHP 8.2–8.5 × test/phpstan/cs-check) still pass +- `CI` and `publish` workflows do not reference or depend on each other + +--- + +## Interpreting Failures + +| Symptom | Likely cause | Next step | +|---------|-------------|-----------| +| HTTP 401 in log | Invalid API token | Regenerate token and update secret | +| HTTP 404 in log | Package not registered on Packagist | Complete prerequisite step 1 | +| HTTP 400 in log | Malformed request body | Check workflow file body JSON | +| Workflow not triggered | Release was draft or pre-release | Publish a full release | +| Workflow not appearing | `publish.yml` not on default branch | Check file was merged to `main` | diff --git a/specs/001-packagist-release-automation/research.md b/specs/001-packagist-release-automation/research.md new file mode 100644 index 0000000..7ba3b5e --- /dev/null +++ b/specs/001-packagist-release-automation/research.md @@ -0,0 +1,107 @@ +# Research — Packagist Release Automation + +**Feature:** `specs/001-packagist-release-automation` +**Date:** 2026-07-15 + +--- + +## Decision 1 — Packagist update mechanism + +**Decision:** Use the Packagist REST API (`POST /api/update-package`) via `curl` in the workflow. + +**Rationale:** +Packagist exposes two update mechanisms: +- **GitHub webhook**: Packagist registers itself as a GitHub webhook; works automatically for + public repos but requires GitHub–Packagist integration setup and doesn't give a failure signal + in GitHub Actions. +- **Packagist API call** (`POST https://packagist.org/api/update-package?username=…&apiToken=…` + with body `{"repository":{"url":"…"}}`): Called explicitly from a GitHub Actions step. + Returns `HTTP 202` on success. A non-2xx response causes the workflow step to fail with a + visible error, satisfying FR-4. + +The API-call approach wins because it provides a verifiable success/failure signal inside GitHub +Actions and requires no external webhook configuration. Using `curl` keeps the workflow +dependency-free (no third-party Actions needed). + +**Alternatives considered:** + +| Alternative | Rejected because | +|-------------|-----------------| +| GitHub → Packagist webhook | Silent on failure; no signal in Actions run | +| `hughcube/packagist-update` Action | Third-party dependency; adds supply-chain risk | +| VCS polling by Packagist | No guarantee of immediate update; no failure signal | + +--- + +## Decision 2 — GitHub Actions release trigger + +**Decision:** `on: release: types: [published]` + +**Rationale:** +The `release` event in GitHub Actions fires for multiple activity types: +- `published` — the release is publicly visible (satisfies FR-1) +- `created` — fires for draft creation (must be excluded per scope) +- `prereleased` — fires for pre-releases (must be excluded per scope) + +Using `types: [published]` ensures the workflow fires only when a release transitions to the +published state, not on draft creation or pre-release tagging. + +**Alternatives considered:** + +| Alternative | Rejected because | +|-------------|-----------------| +| `on: push: tags:` | Fires on tag push, not release publication; no draft exclusion | +| `on: create:` | Fires on branch and tag creation, too broad | + +--- + +## Decision 3 — Manual re-trigger (FR-6) + +**Decision:** Add `workflow_dispatch:` trigger alongside `release: types: [published]`. + +**Rationale:** +`workflow_dispatch` enables a maintainer to manually trigger the workflow from the GitHub Actions +UI without creating a new release. This is the standard GitHub-native approach for ad-hoc +re-runs and requires no additional tooling. + +--- + +## Decision 4 — Secret names + +**Decision:** Use `PACKAGIST_USERNAME` and `PACKAGIST_API_TOKEN` as repository secret names. + +**Rationale:** +These names are the de-facto community standard (used in the Packagist documentation and +referenced in most open-source release workflows). Using the standard names makes the setup +instructions immediately recognizable to maintainers familiar with Packagist. + +--- + +## Decision 5 — Workflow file placement + +**Decision:** Create `.github/workflows/publish.yml` as a standalone file, separate from +`ci.yml`. + +**Rationale:** +Keeping release automation in its own file satisfies FR-5 (no interference with CI) and follows +the GitHub Actions convention of one-workflow-one-concern. It also makes it easy to audit or +disable publishing independently of CI. + +--- + +## Decision 6 — README documentation + +**Decision:** Add a "Release & Publishing" section to `README.md` documenting the one-time setup +steps a maintainer must perform. + +**Rationale:** +The automation depends on two prerequisites (Packagist registration and GitHub secret setup) +that are outside the scope of automated work. Documenting them in `README.md` ensures future +maintainers know what to do when they onboard or rotate credentials. This aligns with +constitution Principle 3 (Documentation). + +--- + +## Resolved unknowns + +All technical unknowns from the spec are now resolved. No NEEDS CLARIFICATION items remain. diff --git a/specs/001-packagist-release-automation/spec.md b/specs/001-packagist-release-automation/spec.md new file mode 100644 index 0000000..935bf19 --- /dev/null +++ b/specs/001-packagist-release-automation/spec.md @@ -0,0 +1,133 @@ +# Feature Specification — Packagist Release Automation + +**Constitution version:** 1.0.0 +**Status:** draft +**Author:** KnpLabs +**Date:** 2026-07-15 + +--- + +## Overview + +When a maintainer publishes a GitHub release for `knplabs/phpstan-rules`, the package should +automatically become available on Packagist so that PHP developers can install it via Composer +without any manual steps from the maintainer. This eliminates the risk of forgetting to update +Packagist and ensures every release is immediately discoverable by the community. + +--- + +## Scope + +### In scope + +- Triggering a Packagist update automatically whenever a GitHub release is published. +- Ensuring the release is available on Packagist within a short window after the GitHub release. +- Providing clear feedback to maintainers when the update succeeds or fails. +- Storing required credentials (Packagist API token) securely in the repository secrets. + +### Out of scope + +- The one-time manual registration of the package on Packagist (prerequisite, done once by a + maintainer). +- Version number validation or changelog generation (separate concerns). +- Automated pre-release or draft release publishing. +- Publishing to other package registries (e.g., npm, PyPI). + +--- + +## User Scenarios & Testing + +### Scenario 1 — Successful release publication + +1. A maintainer creates a new GitHub release (e.g., `v1.0.0`) on the repository. +2. Packagist is automatically notified within 5 minutes of the release being published. +3. The new version appears on the Packagist package page and is installable via + `composer require knplabs/phpstan-rules`. +4. The maintainer receives confirmation (via the GitHub Actions log) that the update succeeded. + +### Scenario 2 — Failed Packagist update + +1. A maintainer creates a GitHub release. +2. The Packagist notification fails (e.g., invalid credentials, Packagist API outage). +3. The failure is visible in the GitHub Actions tab with a clear error message. +4. The maintainer can re-trigger the workflow manually to retry. + +### Scenario 3 — Draft or pre-release (excluded) + +1. A maintainer publishes a draft or pre-release on GitHub. +2. The automation does NOT trigger; Packagist is not updated. + +--- + +## Functional Requirements + +### FR-1 — Automatic trigger on release publication + +The automation MUST activate when a GitHub release is published (not on draft or pre-release +events). + +### FR-2 — Packagist notification + +Upon activation, the automation MUST notify Packagist that a new version is available, using +the official Packagist update mechanism. + +### FR-3 — Credential management + +The Packagist API credentials (username and token) MUST be stored as encrypted repository +secrets. They MUST NOT appear in any log, artifact, or source file. + +### FR-4 — Success and failure visibility + +The outcome of the Packagist update MUST be surfaced in the GitHub Actions run: success returns +a zero exit code; failure returns a non-zero exit code and surfaces the error message. + +### FR-5 — No interference with existing CI + +The release automation MUST run independently from the existing CI workflow (`ci.yml`). It MUST +NOT block or be blocked by test, PHPStan, or code-style jobs. + +### FR-6 — Manual re-trigger + +Maintainers MUST be able to manually re-run the failed workflow from the GitHub Actions UI +without creating a new release. + +--- + +## Success Criteria + +- A new GitHub release results in the corresponding version appearing on Packagist within + 5 minutes, without any manual action from the maintainer. +- Failed notifications are surfaced immediately in the GitHub Actions tab, with a descriptive + error message that allows the maintainer to diagnose and retry. +- Zero secrets are exposed in workflow logs or repository artifacts. +- Existing CI workflows continue to pass unaffected after the automation is added. + +--- + +## Key Entities + +| Entity | Description | +|--------|-------------| +| GitHub Release | A tagged, published release event on the repository | +| Packagist | The PHP package registry where the package is publicly listed | +| Packagist API Token | Credential authorizing the automation to call Packagist on behalf of the package owner | +| GitHub Actions Workflow | The automation job triggered by the release event | +| Repository Secret | Encrypted key-value pair stored in GitHub, injected at runtime | + +--- + +## Dependencies & Assumptions + +- **Assumption:** The package `knplabs/phpstan-rules` is already registered on Packagist before + this automation runs. Registration is a one-time manual step outside this feature's scope. +- **Assumption:** The repository maintainer has a valid Packagist API token and can store it as + a GitHub repository secret. +- **Assumption:** GitHub Actions is enabled for this repository (already confirmed by existing + `ci.yml`). +- **Dependency:** Packagist public API availability (external service). + +--- + +## Open Questions + +_(None — all significant decisions resolved via assumptions above.)_ diff --git a/specs/001-packagist-release-automation/tasks.md b/specs/001-packagist-release-automation/tasks.md new file mode 100644 index 0000000..12cd32a --- /dev/null +++ b/specs/001-packagist-release-automation/tasks.md @@ -0,0 +1,128 @@ +# Tasks — Packagist Release Automation + +**Constitution version:** 1.0.0 +**Plan reference:** `specs/001-packagist-release-automation/plan.md` +**Spec reference:** `specs/001-packagist-release-automation/spec.md` +**Author:** KnpLabs +**Date:** 2026-07-15 + +--- + +## User Stories + +| ID | Story | Priority | +|-----|-------|----------| +| US1 | As a maintainer, when I publish a GitHub release, Packagist is automatically notified so the new version becomes installable via Composer without any manual step | P1 | + +--- + +## Implementation Strategy + +MVP = US1. All tasks are part of this single story. Deliver in one PR: +1. Phase 1 (Setup) unblocks Phase 2. +2. Phase 2 (Workflow) and Phase 3 (Docs) can be implemented in parallel once Phase 1 is done. +3. Phase 4 (Polish) validates the output of Phases 2–3. + +--- + +## Phase 1 — Setup + +> Establish the implementation baseline. No code shipped here. + +- [x] T001 Read `.github/workflows/ci.yml` to note the actions version pinning (`actions/checkout@v7`, `actions/cache@v6`), `runs-on` value, and job naming conventions before writing the new workflow + +**Phase 1 done when:** Implementation conventions are confirmed and T002 can start. + +--- + +## Phase 2 — Workflow Implementation [US1: Automated Packagist Notification] + +> Delivers the core automation: a GitHub Actions workflow that fires on release publication +> and notifies Packagist via its REST API. + +**Independent test criterion (US1):** +Create a published GitHub release → the `publish` workflow appears in the Actions tab, completes +green, and the package version appears on `packagist.org/packages/knplabs/phpstan-rules` +within 5 minutes. + +- [x] T002 [US1] Create `.github/workflows/publish.yml` with: + - `on.release.types: [published]` trigger (FR-1: fires only on published releases, not drafts or pre-releases) + - `on.workflow_dispatch:` trigger (FR-6: enables manual re-run from the Actions UI without a new release) + - Single job `notify-packagist` running on `ubuntu-latest` + - One step that calls `POST https://packagist.org/api/update-package` via `curl`: + - Query params `username` and `apiToken` sourced from `${{ secrets.PACKAGIST_USERNAME }}` and `${{ secrets.PACKAGIST_API_TOKEN }}` + - Request body `{"repository":{"url":"https://github.com/KnpLabs/phpstan-rules"}}` + - Header `Content-Type: application/json` + - HTTP response code captured with `--write-out "%{http_code}"` and `--output /dev/null`; step fails on any non-2xx response (FR-4) + - Secrets passed only through environment variables; they MUST NOT be echoed or interpolated into shell strings (FR-3) + +**Phase 2 done when:** T002 is complete. Workflow file is syntactically valid YAML and follows the +same `actions/checkout` version and `runs-on` value as `ci.yml`. + +--- + +## Phase 3 — Documentation [US1] + +> Satisfies constitution Principle 3: every publicly visible feature must be documented. +> Can run in parallel with Phase 2 (different file). + +- [x] T003 [P] [US1] Add a "Release & Publishing" section to `README.md` covering: + - Prerequisite: register the package on Packagist once via `packagist.org/packages/submit` (FR-2 prerequisite) + - Step-by-step instructions to create the two required repository secrets (`PACKAGIST_USERNAME`, `PACKAGIST_API_TOKEN`) in GitHub Settings → Secrets and variables → Actions (FR-3) + - Confirmation that publishing is automatic on release publication (FR-1) + - Instructions for manually re-triggering via Actions → `publish` → "Run workflow" (FR-6) + +**Phase 3 done when:** T003 is complete. README section references both secret names correctly +and is consistent with the workflow file created in T002. + +--- + +## Phase 4 — Polish & Validation + +> Cross-cutting audit before the PR is opened. + +- [x] T004 Audit `.github/workflows/publish.yml` against the following checklist: + - [x] No `echo`, `run: echo`, or `::set-output` statements that could print secret values + - [x] HTTP status code check is present and causes a non-zero exit on non-2xx response + - [x] `workflow_dispatch` trigger is present alongside the release trigger + - [x] Job and step names are human-readable (appear clearly in the Actions UI) + - [x] `actions/checkout` is NOT included (not needed — the workflow only calls an API, not the code) + - [x] The workflow does not reference, import, or call `ci.yml` (FR-5: no interference) + +- [x] T005 Verify `README.md` "Release & Publishing" section: + - [x] Lists both `PACKAGIST_USERNAME` and `PACKAGIST_API_TOKEN` with correct casing + - [x] Explains what happens on draft releases (nothing — automation does not trigger) + - [x] Section is placed logically after the existing "Contributing" section + +**Phase 4 done when:** T004 and T005 pass. PR is ready for review. + +--- + +## Dependencies + +``` +T001 + └── T002 (Phase 2) + └── T003 (Phase 3, parallel to T002) + └── T004 (Phase 4, depends on T002) + └── T005 (Phase 4, depends on T003) +``` + +--- + +## Parallel Execution Opportunities + +| Parallel group | Tasks | Condition | +|----------------|-------|-----------| +| Group A | T002 + T003 | After T001 is complete | +| Group B | T004 + T005 | After T002 and T003 are both complete | + +--- + +## Definition of Done + +All tasks T001–T005 checked AND: +- `.github/workflows/publish.yml` exists, is valid YAML, and follows project workflow conventions +- `README.md` "Release & Publishing" section is complete and accurate +- No secrets appear in any log output +- Existing CI workflow (`ci.yml`) is untouched and unaffected