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
10 changes: 10 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Keep development-only paths out of the Composer dist tarball.
/tests export-ignore
/website export-ignore
/docs export-ignore
/.github export-ignore
/phpunit.xml export-ignore
/Makefile export-ignore
/renovate.json export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
19 changes: 19 additions & 0 deletions .github/workflows/lint-pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Lint PR title

on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
pull-requests: read

jobs:
title:
runs-on: ubuntu-latest
steps:
# Enforce Conventional Commits on the PR title. Because PRs are
# squash-merged, the title becomes the commit subject on main, which
# release-please parses to decide the version bump and changelog entry.
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24 changes: 24 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Release

on:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
release-please:
runs-on: ubuntu-latest
steps:
# Maintains a rolling release PR: parses Conventional Commit messages from
# squash-merged PRs, updates CHANGELOG.md and the plugin.php version header,
# and on merge creates the git tag + GitHub Release. Packagist picks up the
# new tag via the existing GitHub webhook.
- uses: googleapis/release-please-action@v4
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.0.2"
}
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@
"tangible/framework": "dev-main"
},
"require": {
"php": ">=8.1",
"symfony/yaml": "^6.4 || ^7.0"
},
"suggest": {
"tangible/framework": "Optional. Provides settings-page styling and framework integration; normally already loaded by other Tangible plugins in the WordPress environment."
},
"autoload": {
"psr-4": {
"Tangible\\": "./src"
Expand Down
8 changes: 5 additions & 3 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
/**
* Plugin Name: Tangible Object
* Description: A WordPress tool suite for building data-driven interfaces with a clean extensible architecture.
* x-release-please-start-version
* Version: 0.0.2
* x-release-please-end-version
* GitHub URI: TangibleInc/object
*/

Expand Down
14 changes: 14 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"packages": {
".": {
"release-type": "simple",
"package-name": "tangible/object",
"changelog-path": "CHANGELOG.md",
"include-component-in-tag": false,
"extra-files": [
"plugin.php"
]
}
}
}
132 changes: 132 additions & 0 deletions website/docs/development/releases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
sidebar_position: 1
title: Releasing
description: How versions are cut and published to Packagist
---

# Releasing

Releases are automated. Merging pull requests into `main` is all a maintainer
normally does — versioning, the changelog, the git tag, the GitHub Release, and
the Packagist update all happen from there.

## How it works

We use [release-please](https://github.com/googleapis/release-please) with
[Conventional Commits](https://www.conventionalcommits.org/). The pipeline is:

1. PRs are **squash-merged** into `main`. The PR title becomes the commit
subject on `main`.
2. release-please reads those commit subjects, works out the next version, and
maintains a rolling **release pull request** that updates `CHANGELOG.md` and
the `Version:` header in `plugin.php`.
3. Merging that release PR creates the git tag and the GitHub Release.
4. Packagist picks up the new tag automatically via the GitHub webhook and
publishes the version.

Because the version is derived from the git tag, `composer.json` intentionally
has **no `version` field** — do not add one.

## PR titles must follow Conventional Commits

This is the one rule contributors need to follow. Since PRs are squash-merged,
the **PR title** is what release-please parses, and a non-conforming title
contributes nothing to the changelog or the version bump. PR titles are linted
in CI, so a bad title blocks the merge.

The format is:

```
<type>[optional scope]: <description>
```

Common types and their effect on the version (once the package is `>= 1.0.0`):

| Type | Example | Version bump |
|---|---|---|
| `fix` | `fix: guard against missing field type` | patch (`1.0.0` → `1.0.1`) |
| `feat` | `feat: add repeater field type` | minor (`1.0.0` → `1.1.0`) |
| `feat!` / `BREAKING CHANGE:` footer | `feat!: rename DataView storage API` | major (`1.0.0` → `2.0.0`) |
| `docs`, `chore`, `refactor`, `test`, `ci`, `build`, `perf`, `style` | `docs: expand field-type examples` | no release on its own |

:::note Pre-1.0 behaviour
While the package is still on `0.x`, release-please never bumps to `1.0.0` on
its own — a breaking change bumps the **minor** instead (`0.1.0` → `0.2.0`). The
`1.0.0` release is cut deliberately.
:::

### Breaking changes

Signal a breaking change either by appending `!` after the type
(`feat!: ...`) or by adding a footer to the commit body:

```
feat: rename DataView storage API

BREAKING CHANGE: `storage` now expects an adapter instance instead of a string.
```

With squash merges the PR body becomes the commit body, so a stray
`BREAKING CHANGE:` line in a PR description will also trigger a major bump —
keep descriptions clean and put the intent in the title.

## Cutting a specific version (e.g. the first `1.0.0`)

Normally you don't pick the version — release-please computes it from the
commits. To force a specific version, add a `Release-As:` footer to the commit
that lands on `main`.

Two things to know about our setup:

- `Release-As` is read from the commit **body**, not the subject. Appending it
to the PR title does **not** work (and would break the Conventional Commit
title and fail the lint).
- We squash-merge with **PR title only**, so the PR *description* does not become
the commit body either. You add the footer at merge time instead.

The steps:

1. Open a trivial PR — for example titled `chore: release 1.0.0`.
2. When squash-merging, **edit the commit message in GitHub's merge dialog** and
add the footer to the body (the "PR title only" default only prefills the
message — the description box is still editable):

```
chore: release 1.0.0

Release-As: 1.0.0
```

3. release-please opens or updates its release PR pinned to `1.0.0`. Merge that
to tag `v1.0.0`.

A maintainer who can push directly to `main` can do the same with an empty
commit:

```bash
git commit --allow-empty -m "chore: release 1.0.0" -m "Release-As: 1.0.0"
git push origin main
```

:::warning
`Release-As` is one-shot — it only affects the release triggered by that commit,
so don't leave it in later merges. And do **not** hand-edit
`.release-please-manifest.json` to force a version: it records the *last
released* version, so setting it to `1.0.0` makes release-please think `1.0.0`
already shipped and it won't cut the release.
:::

## Version constraints for consumers

Once published, downstream projects install a released version rather than
`dev-main`:

```bash
composer require tangible/object:^1.0
```

- `^1.0` — receives all backwards-compatible minors and patches below `2.0.0`
(the conventional default).
- `~1.4.0` — patches only (`1.4.x`), no minor upgrades.
- Consumers are never upgraded silently: `composer.lock` pins the exact
installed version until they run `composer update`.
7 changes: 7 additions & 0 deletions website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ const sidebars = {
'examples/invoice-manager',
],
},
{
type: 'category',
label: 'Development',
items: [
'development/releases',
],
},
'api-reference',
],
};
Expand Down
Loading