From 8e8aca6138b86a5b61625ff5f6204380940996f8 Mon Sep 17 00:00:00 2001 From: Aryan Jasala Date: Wed, 4 Nov 2026 04:45:47 +0530 Subject: [PATCH 1/7] ci: prefix Dependabot commits with chore Dependabot copies build(deps) from history. release-please treats every type with a visible changelog section as releasable, so pin chore to keep dependency bumps from cutting a release on their own. --- .github/dependabot.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6a6bb14..3fb0531 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,9 @@ updates: schedule: interval: weekly open-pull-requests-limit: 10 + commit-message: + prefix: chore + include: scope labels: - Dependencies @@ -13,6 +16,9 @@ updates: schedule: interval: weekly open-pull-requests-limit: 10 + commit-message: + prefix: chore + include: scope versioning-strategy: increase labels: - Dependencies @@ -22,6 +28,9 @@ updates: schedule: interval: weekly open-pull-requests-limit: 10 + commit-message: + prefix: chore + include: scope versioning-strategy: increase labels: - Dependencies From b28c9bdecf81e29a68f89f387ae6d15b2ea87f7e Mon Sep 17 00:00:00 2001 From: Aryan Jasala Date: Wed, 4 Nov 2026 04:45:47 +0530 Subject: [PATCH 2/7] ci: cut releases of the skeleton with release-please Pull request titles, already checked by pr-title.yml, become the Conventional Commit subjects on main. release-please reads them, keeps a release pull request open that bumps package.json, package-lock.json and the plugin header Version, and writes CHANGELOG.md; merging it tags vX.Y.Z and publishes the release. - release.yml, with changelog sections mapped to Keep a Changelog - "## Unreleased" stays for projects' npm run release:changelog; release-please inserts below it - init cleanup is switched on, removing only the release workflow and config, so a new plugin does not inherit the skeleton's history - PROJECT_NAME_FEATURES_VERSION is left out of the bump on purpose: init does not rewrite it, so it must stay at the project start version - contributing, initialization and maintainer docs follow the new flow --- .github/workflows/release.yml | 39 +++++++++++++++++++ .release-please-manifest.json | 3 ++ CHANGELOG.md | 2 + CONTRIBUTING.md | 6 +-- bin/scaffold.config.js | 12 +++++- docs/initialization.md | 2 +- docs/internal/maintenance.md | 12 ++++++ project-name-features.php | 2 + release-please-config.json | 73 +++++++++++++++++++++++++++++++++++ 9 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7cdb83e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,39 @@ +name: Release + +# release-please reads the Conventional Commit subjects squash-merged to main and keeps one +# release pull request open that bumps the version and writes CHANGELOG.md. Merging that pull +# request tags vX.Y.Z on main and publishes the GitHub release with the changelog entry as notes. +# +# The release pull request is opened with GITHUB_TOKEN, so GitHub starts no workflow runs on it +# (no tests, no PR title check). That is why neither check is a required status check. Switching +# the token below to a GitHub App token would give the release pull request its CI runs. + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +# Serialise runs so two merges in quick succession can't race on the release pull request. +concurrency: + group: release + cancel-in-progress: false + +jobs: + release-please: + name: Release Please + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + pull-requests: write + steps: + - name: Open or update the release pull request, or publish the release + uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..37fcefa --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.0.0" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index bc4956c..399e46d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + ## Unreleased ## 1.0.0 - 2026-10-01 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 542a459..e755a95 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,16 +32,16 @@ npm run lint:css Then: - If you changed an example, a module, `Main::CLASSES` or `bin/scaffold.config.js`, validate init in a disposable clone (see [release validation](docs/internal/maintenance.md#release-validation)). -- If you changed behaviour a developer sees, update the guide that describes it, and `CHANGELOG.md` under **Unreleased**. +- If you changed behaviour a developer sees, update the guide that describes it. Don't edit `CHANGELOG.md`: release-please writes it from the PR title. - If you changed the `init` or `scaffold` workflow, update both the Claude skill and the Copilot prompt. ## Pull request checklist - [ ] Tests cover new or changed behaviour (written first). - [ ] The checks above pass. -- [ ] Documentation and `CHANGELOG.md` are updated. +- [ ] Documentation is updated. - [ ] Placeholders (`Project Name` / `project-name` / `Project_Name`) are intact. -- [ ] Commits follow [Conventional Commits](https://www.conventionalcommits.org/) (enforced by the commit-message hook). +- [ ] Commits follow [Conventional Commits](https://www.conventionalcommits.org/) (enforced by the commit-message hook), and so does the PR title (checked by `pr-title.yml`): pull requests are squash-merged, and the title becomes the commit on `main` and the changelog entry. - [ ] `graphify-out/graph.json` is not part of the change unless the pull request is a deliberate baseline refresh. Use the pull request template and describe how you verified the change. diff --git a/bin/scaffold.config.js b/bin/scaffold.config.js index 846bb3f..c57fd69 100644 --- a/bin/scaffold.config.js +++ b/bin/scaffold.config.js @@ -102,7 +102,7 @@ module.exports = { { path: 'package.json', kind: 'json', key: 'version' }, ], - steps: { composer: true, cleanup: false, git: true, hooks: true }, + steps: { composer: true, cleanup: true, git: true, hooks: true }, // Dep/build-config-carrying capabilities. Toggling adds/removes their deps. featuresDir: 'bin/features', @@ -228,6 +228,16 @@ module.exports = { ], }, + // Starter-only files init removes from a new project. The release automation + // cuts plugin-elementary's own versions; a project starts at its own version. + cleanup: { + targets: [ + 'release-please-config.json', + '.release-please-manifest.json', + '.github/workflows/release.yml', + ], + }, + docsUrl: 'https://github.com/rtCamp/plugin-elementary/blob/main/docs/index.md', repoUrl: 'https://github.com/rtCamp/plugin-elementary', diff --git a/docs/initialization.md b/docs/initialization.md index c9a29b0..14fd863 100644 --- a/docs/initialization.md +++ b/docs/initialization.md @@ -74,7 +74,7 @@ What each set contains is listed in [Included features](features.md). | Kept example sets | Stay as working references. Their `// wp:example:` markers are removed. | | Optional features | `hmr` edits `.env.local`; `tailwind` adds `src/css/tailwind.css`, `postcss.config.js`, dependencies and flips the Tailwind constant. | | State | `.wp-scaffold.json` records identity and feature choices. Do not edit it by hand. | -| Cleanup | Nothing is removed. Copilot files, workflows and templates under `.github/` stay. | +| Cleanup | Only the skeleton's own release automation is removed: `.github/workflows/release.yml`, `release-please-config.json` and `.release-please-manifest.json`. Copilot files, the other workflows and templates under `.github/` stay. | | AI instructions | `sync-ai` refreshes `.github/instructions/framework-php.instructions.md` from the installed framework. | Init does not generate a translation template. Run `npm run pot` when preparing translations. diff --git a/docs/internal/maintenance.md b/docs/internal/maintenance.md index afa5ae1..6fc3fa6 100644 --- a/docs/internal/maintenance.md +++ b/docs/internal/maintenance.md @@ -81,6 +81,18 @@ Use the explicit commands in [Local development](../local-development.md#check-a Put journey and link-check results in the pull request description or release hand-off: the date, skeleton commit (and any uncommitted changes), dependency revisions, Node/PHP versions, environment and local overrides. Record one row per route, init mode, removal or feature selection, scaffold or manual example, build and check: the command, the expected result, the actual result, and **pass / fail / blocked** with evidence. +## Releasing the skeleton + +Releases of the skeleton itself are cut by [release-please](https://github.com/googleapis/release-please) (`.github/workflows/release.yml`). Projects made from it do not inherit this: init removes the release workflow and config, and projects release with `npm run release:bump` and `npm run release:changelog`. + +1. Pull requests are squash-merged, so each PR title becomes one Conventional Commit on `main`. release-please reads those subjects and keeps a release pull request open that bumps the version in `package.json`, `package-lock.json` and the plugin header in `project-name-features.php` (between the `x-release-please-start-version` and `x-release-please-end` lines), and writes the next `CHANGELOG.md` section. +2. The title's type decides the version: `fix:` is a patch, `feat:` a minor, and `feat!:` (or a `BREAKING CHANGE:` footer) a major. Anything that changes what init generates, renames a script or a file a project relies on, or raises a minimum version is a `feat!:`. `docs:`, `test:`, `ci:`, `chore:` and `refactor:` cut no release. Dependabot pull requests are prefixed `chore` (see `.github/dependabot.yml`), so they cut no release on their own. Keep it that way: release-please cuts a release for any type with a visible changelog section, `build` included. +3. When you want to release, run the [release validation](#release-validation) against the release pull request's branch, reword its entries if a title reads badly, then squash-merge it. release-please tags `vX.Y.Z` and publishes the GitHub release with that section as the notes. + +The release pull request is opened with `GITHUB_TOKEN`, so GitHub starts no workflow runs on it, and the PR title check is not a required status check for that reason. A GitHub App token in the workflow would give the release pull request its own runs. Never push a `v*` tag or edit `.release-please-manifest.json` by hand. + +`PROJECT_NAME_FEATURES_VERSION` stays at `1.0.0` on purpose. Init rewrites the header and `package.json` to a new project's version but not the constant, so a starter release that moved it would give every new project a constant that disagrees with its header. + ## Dependency updates | Dependency | Where it is declared | When bumping | diff --git a/project-name-features.php b/project-name-features.php index 5a605f1..cfb2b6b 100644 --- a/project-name-features.php +++ b/project-name-features.php @@ -10,7 +10,9 @@ * Plugin Name: Project Name Features * Plugin URI: https://rtcamp.com * Description: All backend functionality: post types, taxonomies, blocks, REST endpoints, settings, shortcodes, and custom roles. + * x-release-please-start-version * Version: 1.0.0 + * x-release-please-end * Author: rtCamp * Author URI: https://rtcamp.com * License: GPL-2.0-or-later diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..1eb03c6 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "release-type": "node", + "changelog-path": "CHANGELOG.md", + "include-v-in-tag": true, + "include-component-in-tag": false, + "changelog-sections": [ + { + "type": "feat", + "section": "Added" + }, + { + "type": "fix", + "section": "Fixed" + }, + { + "type": "perf", + "section": "Changed" + }, + { + "type": "revert", + "section": "Changed" + }, + { + "type": "build", + "section": "Changed" + }, + { + "type": "security", + "section": "Security" + }, + { + "type": "docs", + "section": "Documentation", + "hidden": true + }, + { + "type": "style", + "section": "Styles", + "hidden": true + }, + { + "type": "chore", + "section": "Miscellaneous", + "hidden": true + }, + { + "type": "refactor", + "section": "Code Refactoring", + "hidden": true + }, + { + "type": "test", + "section": "Tests", + "hidden": true + }, + { + "type": "ci", + "section": "Continuous Integration", + "hidden": true + } + ], + "extra-files": [ + { + "type": "generic", + "path": "project-name-features.php" + } + ] + } + } +} From ce4c8d480d753211501039f19d24eb3d585810c4 Mon Sep 17 00:00:00 2001 From: Aryan Jasala Date: Wed, 4 Nov 2026 17:59:10 +0530 Subject: [PATCH 3/7] feat(init): give new plugins release-please of their own Init used to remove the release workflow and config, so a new plugin fell back to release:bump and release:changelog. A new "release" feature (on by default, under Developer Tooling) now sets the project up for release-please: - it copies bin/features/release/release.yml to .github/workflows/release.yml and release-please-config.json to the root, pointing extra-files at the renamed main file (the engine never rewrites files under bin/, so the template still names the skeleton's); - it seeds .release-please-manifest.json at 0.0.0 (nothing released yet), sets initial-version to the version init wrote, so the first release is exactly that version, and sets bootstrap-sha to the HEAD init ran on, so skeleton commits stay out of the first changelog; - during first setup it removes the skeleton's CHANGELOG.md, which release-please writes afresh on the first release. Turning the feature off removes the workflow and config and keeps the manifest; turning it on again carries on from the recorded version. The skeleton's own release machinery moves to skeleton-only paths (.github/workflows/release-starter.yml, .github/release-please/), which cleanup removes, so it never ships into a project, including one that opts out. The project workflow documents the GITHUB_TOKEN limits: tags it creates don't start `on: push: tags` workflows, so deploys belong in the release workflow, gated on its release-created output. --- .../release-please/config.json | 0 .../release-please/manifest.json | 0 .../{release.yml => release-starter.yml} | 12 ++- CHANGELOG.md | 2 +- .../release/release-please-config.json | 73 ++++++++++++++ bin/features/release/release.yml | 62 ++++++++++++ bin/scaffold.config.js | 94 ++++++++++++++++++- docs/initialization.md | 27 +++++- docs/internal/maintenance.md | 4 +- 9 files changed, 259 insertions(+), 15 deletions(-) rename release-please-config.json => .github/release-please/config.json (100%) rename .release-please-manifest.json => .github/release-please/manifest.json (100%) rename .github/workflows/{release.yml => release-starter.yml} (68%) create mode 100644 bin/features/release/release-please-config.json create mode 100644 bin/features/release/release.yml diff --git a/release-please-config.json b/.github/release-please/config.json similarity index 100% rename from release-please-config.json rename to .github/release-please/config.json diff --git a/.release-please-manifest.json b/.github/release-please/manifest.json similarity index 100% rename from .release-please-manifest.json rename to .github/release-please/manifest.json diff --git a/.github/workflows/release.yml b/.github/workflows/release-starter.yml similarity index 68% rename from .github/workflows/release.yml rename to .github/workflows/release-starter.yml index 7cdb83e..3190f92 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release-starter.yml @@ -1,4 +1,4 @@ -name: Release +name: Release (skeleton) # release-please reads the Conventional Commit subjects squash-merged to main and keeps one # release pull request open that bumps the version and writes CHANGELOG.md. Merging that pull @@ -7,6 +7,12 @@ name: Release # The release pull request is opened with GITHUB_TOKEN, so GitHub starts no workflow runs on it # (no tests, no PR title check). That is why neither check is a required status check. Switching # the token below to a GitHub App token would give the release pull request its CI runs. +# +# This file releases the skeleton itself, from the skeleton-only config in .github/release-please/. +# Init cleanup removes both. A project made from the skeleton gets its own +# .github/workflows/release.yml from bin/features/release/ when init's "release" feature is on +# (the default), with release-please-config.json and a fresh manifest at the root. Keep this +# workflow and the bin/features/release/ copy in step. on: push: @@ -35,5 +41,5 @@ jobs: uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} - config-file: release-please-config.json - manifest-file: .release-please-manifest.json + config-file: .github/release-please/config.json + manifest-file: .github/release-please/manifest.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 399e46d..e5870d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - + ## Unreleased diff --git a/bin/features/release/release-please-config.json b/bin/features/release/release-please-config.json new file mode 100644 index 0000000..1eb03c6 --- /dev/null +++ b/bin/features/release/release-please-config.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "release-type": "node", + "changelog-path": "CHANGELOG.md", + "include-v-in-tag": true, + "include-component-in-tag": false, + "changelog-sections": [ + { + "type": "feat", + "section": "Added" + }, + { + "type": "fix", + "section": "Fixed" + }, + { + "type": "perf", + "section": "Changed" + }, + { + "type": "revert", + "section": "Changed" + }, + { + "type": "build", + "section": "Changed" + }, + { + "type": "security", + "section": "Security" + }, + { + "type": "docs", + "section": "Documentation", + "hidden": true + }, + { + "type": "style", + "section": "Styles", + "hidden": true + }, + { + "type": "chore", + "section": "Miscellaneous", + "hidden": true + }, + { + "type": "refactor", + "section": "Code Refactoring", + "hidden": true + }, + { + "type": "test", + "section": "Tests", + "hidden": true + }, + { + "type": "ci", + "section": "Continuous Integration", + "hidden": true + } + ], + "extra-files": [ + { + "type": "generic", + "path": "project-name-features.php" + } + ] + } + } +} diff --git a/bin/features/release/release.yml b/bin/features/release/release.yml new file mode 100644 index 0000000..d7100af --- /dev/null +++ b/bin/features/release/release.yml @@ -0,0 +1,62 @@ +name: Release + +# release-please reads the Conventional Commit subjects squash-merged to main and keeps one +# release pull request open that bumps the version (package.json, package-lock.json and the +# plugin header) and writes CHANGELOG.md. Merging that pull request tags vX.Y.Z on main and +# publishes the GitHub release with the changelog entry as notes. +# +# Before the first run: +# - Allow GitHub Actions to create pull requests (repository or organisation settings, Actions, +# General, Workflow permissions). Without it release-please cannot open its pull request. +# - Squash-merge pull requests and use the pull request title as the commit subject, so each title +# becomes one Conventional Commit on main (the PR Title workflow checks the format). +# +# The tag and the release are created with GITHUB_TOKEN, and GitHub starts no workflow runs for +# events GITHUB_TOKEN causes. A workflow that deploys `on: push: tags` never fires for these tags. +# Deploy from this workflow instead, in a job gated on the release output: +# +# deploy: +# needs: release-please +# if: needs.release-please.outputs.release-created == 'true' +# +# or switch the token below to a GitHub App token. The release pull request is opened with +# GITHUB_TOKEN too, so it gets no CI runs; that is why neither check should be a required status +# check unless you switch tokens. +# +# rtCamp/wp-shared-workflows is adding a reusable cd-release-please.yml. Once it is released, +# this file can shrink to a caller of it. + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +# Serialise runs so two merges in quick succession can't race on the release pull request. +concurrency: + group: release + cancel-in-progress: false + +jobs: + release-please: + name: Release Please + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + pull-requests: write + outputs: + release-created: ${{ steps.release.outputs.release_created }} + tag-name: ${{ steps.release.outputs.tag_name }} + version: ${{ steps.release.outputs.version }} + steps: + - name: Open or update the release pull request, or publish the release + id: release + uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json diff --git a/bin/scaffold.config.js b/bin/scaffold.config.js index c57fd69..68a14bd 100644 --- a/bin/scaffold.config.js +++ b/bin/scaffold.config.js @@ -23,6 +23,66 @@ const mainFile = ( api ) => `${ api.identity.package.split( '/' )[ 1 ] }.php`; const tailwindConst = ( api ) => `${ api.identity.constantPrefix }_FEATURES_ENABLE_TAILWIND`; +// Release automation (release-please). The "release" feature copies the workflow +// and config from bin/features/release/; seedRelease then gives a new project a +// release history of its own. The skeleton's own workflow, config and manifest +// live at skeleton-only paths, which cleanup removes. +const RELEASE_MANIFEST = '.release-please-manifest.json'; +const RELEASE_CONFIG = 'release-please-config.json'; + +// HEAD of the repository init runs in, or null without git or commits. +const gitHead = ( root ) => { + try { + return require( 'child_process' ) + .execFileSync( 'git', [ 'rev-parse', '--verify', '-q', 'HEAD' ], { + cwd: root, + encoding: 'utf8', + stdio: [ 'ignore', 'pipe', 'ignore' ], + } ) + .trim(); + } catch { + return null; + } +}; + +const seedRelease = ( api ) => { + // The manifest records the last released version, so a project that turns the + // feature off and on again keeps its history. "0.0.0" tells release-please + // nothing is released yet. + const fresh = ! api.exists( RELEASE_MANIFEST ); + if ( fresh ) { + api.editJson( + RELEASE_MANIFEST, + ( manifest ) => { + manifest[ '.' ] = '0.0.0'; + }, + { create: true, indentFrom: RELEASE_CONFIG } + ); + } + const released = JSON.parse( api.read( RELEASE_MANIFEST ) )[ '.' ]; + api.editJson( RELEASE_CONFIG, ( config ) => { + const pkg = config.packages[ '.' ]; + // The template names the skeleton's main file; the engine never rewrites + // files under bin/, so point release-please at the renamed one. + pkg[ 'extra-files' ] = [ { type: 'generic', path: mainFile( api ) } ]; + // Until the first release, that release is the version init wrote. + if ( '0.0.0' === released ) { + pkg[ 'initial-version' ] = api.identity.version; + } + // Commits up to this one (the skeleton's history, when the clone keeps it) + // stay out of the first changelog entry. + const head = fresh ? gitHead( api.root ) : null; + if ( head ) { + config[ 'bootstrap-sha' ] = head; + } + } ); + // During first setup (no .wp-scaffold.json yet) CHANGELOG.md is still the + // skeleton's. Drop it: release-please writes a fresh one on the first release. + if ( fresh && ! api.exists( '.wp-scaffold.json' ) ) { + api.remove( 'CHANGELOG.md' ); + } +}; + // One keep/remove capability. inc/Main.php is always a strip target (the // Main::CLASSES line); `module` adds the module file + its class dir to the // removal set. `strip`/`remove` add any extra files/globs (e.g. a Core file @@ -159,6 +219,30 @@ module.exports = { ); }, }, + { + key: 'release', + label: 'Release automation (release-please)', + category: 'Developer Tooling', + defaultOn: true, + description: + 'A release pull request kept up to date from Conventional Commit PR titles; merging it bumps the plugin header and package.json, writes CHANGELOG.md, tags vX.Y.Z and publishes the GitHub release. Default on. Needs squash merges with the PR title as the commit subject, and Actions allowed to create pull requests.', + apply: { + files: [ + { + from: 'release/release.yml', + to: '.github/workflows/release.yml', + }, + { + from: 'release/release-please-config.json', + to: RELEASE_CONFIG, + }, + ], + }, + onEnable: seedRelease, + // Disabling removes the workflow and config (apply.files) and keeps the + // manifest, the record of what was released. + onDisable: () => {}, + }, ], // Keep/remove capabilities. Removing deletes the module + classes + its @@ -228,13 +312,13 @@ module.exports = { ], }, - // Starter-only files init removes from a new project. The release automation - // cuts plugin-elementary's own versions; a project starts at its own version. + // Skeleton-only files init removes from a new project: the release workflow, + // config and manifest that cut plugin-elementary's own versions. A project + // gets its own from the "release" feature. cleanup: { targets: [ - 'release-please-config.json', - '.release-please-manifest.json', - '.github/workflows/release.yml', + '.github/release-please', + '.github/workflows/release-starter.yml', ], }, diff --git a/docs/initialization.md b/docs/initialization.md index 14fd863..e569d44 100644 --- a/docs/initialization.md +++ b/docs/initialization.md @@ -69,12 +69,12 @@ What each set contains is listed in [Included features](features.md). | Area | Result | | --- | --- | | Identity | `Project Name` / `project-name` / `Project_Name` / `PROJECT_NAME` tokens are replaced across source, config, tests and docs. The main file is renamed. | -| Version | Written to the main file's header and `package.json`. | +| Version | Written to the main file's header and `package.json`. With the release feature on, it also becomes `initial-version` in `release-please-config.json`, the version of the project's first release. | | Removed example sets | The module file (`inc/Modules/.php`), its folder, its test (`tests/php/Test.php`), its `Main::CLASSES` line and any coupled region (for example the static block list in `inc/Core/Assets.php` for `blocks`, the unschedule call in `inc/Core/PluginSetup.php` for `cron`). | | Kept example sets | Stay as working references. Their `// wp:example:` markers are removed. | -| Optional features | `hmr` edits `.env.local`; `tailwind` adds `src/css/tailwind.css`, `postcss.config.js`, dependencies and flips the Tailwind constant. | +| Optional features | `hmr` edits `.env.local`; `tailwind` adds `src/css/tailwind.css`, `postcss.config.js`, dependencies and flips the Tailwind constant; `release` (on by default) adds `.github/workflows/release.yml`, `release-please-config.json` and `.release-please-manifest.json`, and removes the skeleton's `CHANGELOG.md`. See [Releases](#releases). | | State | `.wp-scaffold.json` records identity and feature choices. Do not edit it by hand. | -| Cleanup | Only the skeleton's own release automation is removed: `.github/workflows/release.yml`, `release-please-config.json` and `.release-please-manifest.json`. Copilot files, the other workflows and templates under `.github/` stay. | +| Cleanup | Only the skeleton's own release automation is removed: `.github/workflows/release-starter.yml` and `.github/release-please/`. Copilot files, the other workflows and templates under `.github/` stay. | | AI instructions | `sync-ai` refreshes `.github/instructions/framework-php.instructions.md` from the installed framework. | Init does not generate a translation template. Run `npm run pot` when preparing translations. @@ -99,12 +99,31 @@ npm run init -- --list # read-only status npm run init -- --list --json # one machine-readable line npm run init -- --enable=tailwind --yes # change one feature npm run init -- --disable=hmr --yes -npm run init -- --features=hmr --yes # set the exact enabled set +npm run init -- --features=hmr,release --yes # set the exact enabled set npm run init -- --help # options for your installed engine ``` `--features` replaces the whole enabled set; `--enable` and `--disable` change one feature at a time. Do not combine them. For JSON without npm's banner, run `node bin/init.js --list --json`. Do not use `--reinit` to restore examples. +### Releases + +The `release` feature is on by default. It sets the project up for [release-please](https://github.com/googleapis/release-please): + +- `.github/workflows/release.yml` keeps one release pull request open on `main`, built from the Conventional Commit pull request titles merged since the last release. Merging it bumps `package.json`, `package-lock.json` and the main file's `Version:` header line (between the `x-release-please-start-version` and `x-release-please-end` lines), writes the `CHANGELOG.md` section, tags `vX.Y.Z` and publishes the GitHub release. The `PR Title` workflow checks the title format. The `_VERSION` constant in the main file is not bumped; keep it in step by hand or read it from the header. +- `release-please-config.json` carries `initial-version`, the version init wrote, so the first release is exactly that version. It also carries `bootstrap-sha` when init ran in a git repository, so commits from before init stay out of the first changelog entry. +- `.release-please-manifest.json` records the last released version. It starts at `0.0.0`, which release-please reads as "nothing released yet". Don't edit it, `CHANGELOG.md` or `v*` tags by hand once releases start. +- The skeleton's `CHANGELOG.md` is removed during setup; release-please writes a fresh one on the first release. + +Before the first release: + +- Allow GitHub Actions to create pull requests (repository or organisation settings, Actions, General, Workflow permissions). Client organisations often switch this off. +- Allow only squash merges, with the pull request title as the commit subject. +- If you deploy on tags: the tag and release are created with `GITHUB_TOKEN`, and GitHub starts no workflow runs for events `GITHUB_TOKEN` causes, so a workflow triggered `on: push: tags` never runs for them. Deploy from `release.yml` in a job with `needs: release-please` and `if: needs.release-please.outputs.release-created == 'true'`, or switch the workflow to a GitHub App token. The same limit means the release pull request gets no CI runs, so don't make checks on it required unless you switch tokens. + +A project that does not release versions (a site deployed from a branch, for example) can turn it off with `npm run init -- --disable=release --yes`; that removes the workflow and config and keeps the manifest. Then release by hand with `npm run release:bump` and `npm run release:changelog`, which need a `CHANGELOG.md` with an `## Unreleased` heading. If an existing project that already tags releases turns the feature on, set `.release-please-manifest.json` to its latest released version before the first run. + +rtCamp/wp-shared-workflows is adding a reusable `cd-release-please.yml`. Once it is released, the project workflow can shrink to a caller of it; the skeleton does not use it yet. + ### Optional dependencies - **HMR:** toggling only flips `ENABLE_HMR` in `.env.local`, a per-developer file. See [Live reload](hmr.md). diff --git a/docs/internal/maintenance.md b/docs/internal/maintenance.md index 6fc3fa6..66be9f5 100644 --- a/docs/internal/maintenance.md +++ b/docs/internal/maintenance.md @@ -83,13 +83,13 @@ Put journey and link-check results in the pull request description or release ha ## Releasing the skeleton -Releases of the skeleton itself are cut by [release-please](https://github.com/googleapis/release-please) (`.github/workflows/release.yml`). Projects made from it do not inherit this: init removes the release workflow and config, and projects release with `npm run release:bump` and `npm run release:changelog`. +Releases of the skeleton itself are cut by [release-please](https://github.com/googleapis/release-please) (`.github/workflows/release-starter.yml`), from the skeleton-only config and manifest in `.github/release-please/`. Cleanup removes all three. Projects get release-please of their own through init's `release` feature: it copies `bin/features/release/release.yml` to `.github/workflows/release.yml` and `bin/features/release/release-please-config.json` to the root, points `extra-files` at the renamed main file, seeds a `0.0.0` manifest, `initial-version` and `bootstrap-sha`, and removes the skeleton's `CHANGELOG.md` (see [initialization.md](../initialization.md#releases)). When you change the skeleton's workflow or config, make the same change in the `bin/features/release/` copies unless it is skeleton-only. 1. Pull requests are squash-merged, so each PR title becomes one Conventional Commit on `main`. release-please reads those subjects and keeps a release pull request open that bumps the version in `package.json`, `package-lock.json` and the plugin header in `project-name-features.php` (between the `x-release-please-start-version` and `x-release-please-end` lines), and writes the next `CHANGELOG.md` section. 2. The title's type decides the version: `fix:` is a patch, `feat:` a minor, and `feat!:` (or a `BREAKING CHANGE:` footer) a major. Anything that changes what init generates, renames a script or a file a project relies on, or raises a minimum version is a `feat!:`. `docs:`, `test:`, `ci:`, `chore:` and `refactor:` cut no release. Dependabot pull requests are prefixed `chore` (see `.github/dependabot.yml`), so they cut no release on their own. Keep it that way: release-please cuts a release for any type with a visible changelog section, `build` included. 3. When you want to release, run the [release validation](#release-validation) against the release pull request's branch, reword its entries if a title reads badly, then squash-merge it. release-please tags `vX.Y.Z` and publishes the GitHub release with that section as the notes. -The release pull request is opened with `GITHUB_TOKEN`, so GitHub starts no workflow runs on it, and the PR title check is not a required status check for that reason. A GitHub App token in the workflow would give the release pull request its own runs. Never push a `v*` tag or edit `.release-please-manifest.json` by hand. +The release pull request is opened with `GITHUB_TOKEN`, so GitHub starts no workflow runs on it, and the PR title check is not a required status check for that reason. A GitHub App token in the workflow would give the release pull request its own runs. Never push a `v*` tag or edit `.github/release-please/manifest.json` by hand. `PROJECT_NAME_FEATURES_VERSION` stays at `1.0.0` on purpose. Init rewrites the header and `package.json` to a new project's version but not the constant, so a starter release that moved it would give every new project a constant that disagrees with its header. From 8f8d06107cc035ebd638b9b9f059b22d3057c427 Mon Sep 17 00:00:00 2001 From: Aryan Jasala Date: Wed, 4 Nov 2026 18:08:16 +0530 Subject: [PATCH 4/7] docs(init): drop the note about a reusable release workflow wp-shared-workflows is not adding cd-release-please.yml after all: the release workflow stays a plain release-please-action job in each project. --- bin/features/release/release.yml | 3 --- docs/initialization.md | 1 - 2 files changed, 4 deletions(-) diff --git a/bin/features/release/release.yml b/bin/features/release/release.yml index d7100af..f64731e 100644 --- a/bin/features/release/release.yml +++ b/bin/features/release/release.yml @@ -22,9 +22,6 @@ name: Release # or switch the token below to a GitHub App token. The release pull request is opened with # GITHUB_TOKEN too, so it gets no CI runs; that is why neither check should be a required status # check unless you switch tokens. -# -# rtCamp/wp-shared-workflows is adding a reusable cd-release-please.yml. Once it is released, -# this file can shrink to a caller of it. on: push: diff --git a/docs/initialization.md b/docs/initialization.md index e569d44..dd36bfc 100644 --- a/docs/initialization.md +++ b/docs/initialization.md @@ -122,7 +122,6 @@ Before the first release: A project that does not release versions (a site deployed from a branch, for example) can turn it off with `npm run init -- --disable=release --yes`; that removes the workflow and config and keeps the manifest. Then release by hand with `npm run release:bump` and `npm run release:changelog`, which need a `CHANGELOG.md` with an `## Unreleased` heading. If an existing project that already tags releases turns the feature on, set `.release-please-manifest.json` to its latest released version before the first run. -rtCamp/wp-shared-workflows is adding a reusable `cd-release-please.yml`. Once it is released, the project workflow can shrink to a caller of it; the skeleton does not use it yet. ### Optional dependencies From 3322a2851996c28a912bd88686f656046ae6992d Mon Sep 17 00:00:00 2001 From: Aryan Jasala Date: Wed, 4 Nov 2026 18:46:00 +0530 Subject: [PATCH 5/7] ci: release only the skeleton itself, drop the init release feature New plugins no longer get release-please from init: the release feature, its bin/features/release/ templates and the seeding of a fresh release history are gone, so there is one release workflow to maintain and Dependabot keeps its pins current. Init cleanup still removes the skeleton's own release workflow, config and manifest, which live at skeleton-only paths, so projects keep releasing by hand as before. Also from review: the PR title check no longer uses wip mode, which posts a commit status a fork's read-only token can't write; the workflows use github.token; and the security changelog section is gone, since the title check doesn't accept a security type. --- .github/release-please/config.json | 4 - .github/workflows/pr-title.yml | 5 +- .github/workflows/release-starter.yml | 9 +- CHANGELOG.md | 2 +- .../release/release-please-config.json | 73 --------------- bin/features/release/release.yml | 59 ------------ bin/scaffold.config.js | 90 +------------------ docs/initialization.md | 24 +---- docs/internal/maintenance.md | 2 +- 9 files changed, 12 insertions(+), 256 deletions(-) delete mode 100644 bin/features/release/release-please-config.json delete mode 100644 bin/features/release/release.yml diff --git a/.github/release-please/config.json b/.github/release-please/config.json index 1eb03c6..ac72ee9 100644 --- a/.github/release-please/config.json +++ b/.github/release-please/config.json @@ -27,10 +27,6 @@ "type": "build", "section": "Changed" }, - { - "type": "security", - "section": "Security" - }, { "type": "docs", "section": "Documentation", diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index a6668fc..8349dfc 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -17,11 +17,8 @@ jobs: runs-on: ubuntu-latest permissions: pull-requests: read - statuses: write steps: - name: Validate pull request title uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - wip: true + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/release-starter.yml b/.github/workflows/release-starter.yml index 3190f92..4a01290 100644 --- a/.github/workflows/release-starter.yml +++ b/.github/workflows/release-starter.yml @@ -8,11 +8,8 @@ name: Release (skeleton) # (no tests, no PR title check). That is why neither check is a required status check. Switching # the token below to a GitHub App token would give the release pull request its CI runs. # -# This file releases the skeleton itself, from the skeleton-only config in .github/release-please/. -# Init cleanup removes both. A project made from the skeleton gets its own -# .github/workflows/release.yml from bin/features/release/ when init's "release" feature is on -# (the default), with release-please-config.json and a fresh manifest at the root. Keep this -# workflow and the bin/features/release/ copy in step. +# This file releases the skeleton itself, from the config and manifest in .github/release-please/. +# Init cleanup removes all of them, so projects made from the skeleton don't inherit them. on: push: @@ -40,6 +37,6 @@ jobs: - name: Open or update the release pull request, or publish the release uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ github.token }} config-file: .github/release-please/config.json manifest-file: .github/release-please/manifest.json diff --git a/CHANGELOG.md b/CHANGELOG.md index e5870d7..f785df6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - + ## Unreleased diff --git a/bin/features/release/release-please-config.json b/bin/features/release/release-please-config.json deleted file mode 100644 index 1eb03c6..0000000 --- a/bin/features/release/release-please-config.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", - "packages": { - ".": { - "release-type": "node", - "changelog-path": "CHANGELOG.md", - "include-v-in-tag": true, - "include-component-in-tag": false, - "changelog-sections": [ - { - "type": "feat", - "section": "Added" - }, - { - "type": "fix", - "section": "Fixed" - }, - { - "type": "perf", - "section": "Changed" - }, - { - "type": "revert", - "section": "Changed" - }, - { - "type": "build", - "section": "Changed" - }, - { - "type": "security", - "section": "Security" - }, - { - "type": "docs", - "section": "Documentation", - "hidden": true - }, - { - "type": "style", - "section": "Styles", - "hidden": true - }, - { - "type": "chore", - "section": "Miscellaneous", - "hidden": true - }, - { - "type": "refactor", - "section": "Code Refactoring", - "hidden": true - }, - { - "type": "test", - "section": "Tests", - "hidden": true - }, - { - "type": "ci", - "section": "Continuous Integration", - "hidden": true - } - ], - "extra-files": [ - { - "type": "generic", - "path": "project-name-features.php" - } - ] - } - } -} diff --git a/bin/features/release/release.yml b/bin/features/release/release.yml deleted file mode 100644 index f64731e..0000000 --- a/bin/features/release/release.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Release - -# release-please reads the Conventional Commit subjects squash-merged to main and keeps one -# release pull request open that bumps the version (package.json, package-lock.json and the -# plugin header) and writes CHANGELOG.md. Merging that pull request tags vX.Y.Z on main and -# publishes the GitHub release with the changelog entry as notes. -# -# Before the first run: -# - Allow GitHub Actions to create pull requests (repository or organisation settings, Actions, -# General, Workflow permissions). Without it release-please cannot open its pull request. -# - Squash-merge pull requests and use the pull request title as the commit subject, so each title -# becomes one Conventional Commit on main (the PR Title workflow checks the format). -# -# The tag and the release are created with GITHUB_TOKEN, and GitHub starts no workflow runs for -# events GITHUB_TOKEN causes. A workflow that deploys `on: push: tags` never fires for these tags. -# Deploy from this workflow instead, in a job gated on the release output: -# -# deploy: -# needs: release-please -# if: needs.release-please.outputs.release-created == 'true' -# -# or switch the token below to a GitHub App token. The release pull request is opened with -# GITHUB_TOKEN too, so it gets no CI runs; that is why neither check should be a required status -# check unless you switch tokens. - -on: - push: - branches: - - main - workflow_dispatch: - -permissions: - contents: read - -# Serialise runs so two merges in quick succession can't race on the release pull request. -concurrency: - group: release - cancel-in-progress: false - -jobs: - release-please: - name: Release Please - runs-on: ubuntu-latest - permissions: - contents: write - issues: write - pull-requests: write - outputs: - release-created: ${{ steps.release.outputs.release_created }} - tag-name: ${{ steps.release.outputs.tag_name }} - version: ${{ steps.release.outputs.version }} - steps: - - name: Open or update the release pull request, or publish the release - id: release - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - config-file: release-please-config.json - manifest-file: .release-please-manifest.json diff --git a/bin/scaffold.config.js b/bin/scaffold.config.js index 68a14bd..f61d2c0 100644 --- a/bin/scaffold.config.js +++ b/bin/scaffold.config.js @@ -23,66 +23,6 @@ const mainFile = ( api ) => `${ api.identity.package.split( '/' )[ 1 ] }.php`; const tailwindConst = ( api ) => `${ api.identity.constantPrefix }_FEATURES_ENABLE_TAILWIND`; -// Release automation (release-please). The "release" feature copies the workflow -// and config from bin/features/release/; seedRelease then gives a new project a -// release history of its own. The skeleton's own workflow, config and manifest -// live at skeleton-only paths, which cleanup removes. -const RELEASE_MANIFEST = '.release-please-manifest.json'; -const RELEASE_CONFIG = 'release-please-config.json'; - -// HEAD of the repository init runs in, or null without git or commits. -const gitHead = ( root ) => { - try { - return require( 'child_process' ) - .execFileSync( 'git', [ 'rev-parse', '--verify', '-q', 'HEAD' ], { - cwd: root, - encoding: 'utf8', - stdio: [ 'ignore', 'pipe', 'ignore' ], - } ) - .trim(); - } catch { - return null; - } -}; - -const seedRelease = ( api ) => { - // The manifest records the last released version, so a project that turns the - // feature off and on again keeps its history. "0.0.0" tells release-please - // nothing is released yet. - const fresh = ! api.exists( RELEASE_MANIFEST ); - if ( fresh ) { - api.editJson( - RELEASE_MANIFEST, - ( manifest ) => { - manifest[ '.' ] = '0.0.0'; - }, - { create: true, indentFrom: RELEASE_CONFIG } - ); - } - const released = JSON.parse( api.read( RELEASE_MANIFEST ) )[ '.' ]; - api.editJson( RELEASE_CONFIG, ( config ) => { - const pkg = config.packages[ '.' ]; - // The template names the skeleton's main file; the engine never rewrites - // files under bin/, so point release-please at the renamed one. - pkg[ 'extra-files' ] = [ { type: 'generic', path: mainFile( api ) } ]; - // Until the first release, that release is the version init wrote. - if ( '0.0.0' === released ) { - pkg[ 'initial-version' ] = api.identity.version; - } - // Commits up to this one (the skeleton's history, when the clone keeps it) - // stay out of the first changelog entry. - const head = fresh ? gitHead( api.root ) : null; - if ( head ) { - config[ 'bootstrap-sha' ] = head; - } - } ); - // During first setup (no .wp-scaffold.json yet) CHANGELOG.md is still the - // skeleton's. Drop it: release-please writes a fresh one on the first release. - if ( fresh && ! api.exists( '.wp-scaffold.json' ) ) { - api.remove( 'CHANGELOG.md' ); - } -}; - // One keep/remove capability. inc/Main.php is always a strip target (the // Main::CLASSES line); `module` adds the module file + its class dir to the // removal set. `strip`/`remove` add any extra files/globs (e.g. a Core file @@ -219,30 +159,6 @@ module.exports = { ); }, }, - { - key: 'release', - label: 'Release automation (release-please)', - category: 'Developer Tooling', - defaultOn: true, - description: - 'A release pull request kept up to date from Conventional Commit PR titles; merging it bumps the plugin header and package.json, writes CHANGELOG.md, tags vX.Y.Z and publishes the GitHub release. Default on. Needs squash merges with the PR title as the commit subject, and Actions allowed to create pull requests.', - apply: { - files: [ - { - from: 'release/release.yml', - to: '.github/workflows/release.yml', - }, - { - from: 'release/release-please-config.json', - to: RELEASE_CONFIG, - }, - ], - }, - onEnable: seedRelease, - // Disabling removes the workflow and config (apply.files) and keeps the - // manifest, the record of what was released. - onDisable: () => {}, - }, ], // Keep/remove capabilities. Removing deletes the module + classes + its @@ -312,9 +228,9 @@ module.exports = { ], }, - // Skeleton-only files init removes from a new project: the release workflow, - // config and manifest that cut plugin-elementary's own versions. A project - // gets its own from the "release" feature. + // The skeleton's own release workflow, config and manifest. Projects don't + // release with them, and the skeleton-only paths keep a re-init from removing + // a project's own release files. cleanup: { targets: [ '.github/release-please', diff --git a/docs/initialization.md b/docs/initialization.md index dd36bfc..64e169c 100644 --- a/docs/initialization.md +++ b/docs/initialization.md @@ -69,10 +69,10 @@ What each set contains is listed in [Included features](features.md). | Area | Result | | --- | --- | | Identity | `Project Name` / `project-name` / `Project_Name` / `PROJECT_NAME` tokens are replaced across source, config, tests and docs. The main file is renamed. | -| Version | Written to the main file's header and `package.json`. With the release feature on, it also becomes `initial-version` in `release-please-config.json`, the version of the project's first release. | +| Version | Written to the main file's header and `package.json`. | | Removed example sets | The module file (`inc/Modules/.php`), its folder, its test (`tests/php/Test.php`), its `Main::CLASSES` line and any coupled region (for example the static block list in `inc/Core/Assets.php` for `blocks`, the unschedule call in `inc/Core/PluginSetup.php` for `cron`). | | Kept example sets | Stay as working references. Their `// wp:example:` markers are removed. | -| Optional features | `hmr` edits `.env.local`; `tailwind` adds `src/css/tailwind.css`, `postcss.config.js`, dependencies and flips the Tailwind constant; `release` (on by default) adds `.github/workflows/release.yml`, `release-please-config.json` and `.release-please-manifest.json`, and removes the skeleton's `CHANGELOG.md`. See [Releases](#releases). | +| Optional features | `hmr` edits `.env.local`; `tailwind` adds `src/css/tailwind.css`, `postcss.config.js`, dependencies and flips the Tailwind constant. | | State | `.wp-scaffold.json` records identity and feature choices. Do not edit it by hand. | | Cleanup | Only the skeleton's own release automation is removed: `.github/workflows/release-starter.yml` and `.github/release-please/`. Copilot files, the other workflows and templates under `.github/` stay. | | AI instructions | `sync-ai` refreshes `.github/instructions/framework-php.instructions.md` from the installed framework. | @@ -99,30 +99,12 @@ npm run init -- --list # read-only status npm run init -- --list --json # one machine-readable line npm run init -- --enable=tailwind --yes # change one feature npm run init -- --disable=hmr --yes -npm run init -- --features=hmr,release --yes # set the exact enabled set +npm run init -- --features=hmr --yes # set the exact enabled set npm run init -- --help # options for your installed engine ``` `--features` replaces the whole enabled set; `--enable` and `--disable` change one feature at a time. Do not combine them. For JSON without npm's banner, run `node bin/init.js --list --json`. Do not use `--reinit` to restore examples. -### Releases - -The `release` feature is on by default. It sets the project up for [release-please](https://github.com/googleapis/release-please): - -- `.github/workflows/release.yml` keeps one release pull request open on `main`, built from the Conventional Commit pull request titles merged since the last release. Merging it bumps `package.json`, `package-lock.json` and the main file's `Version:` header line (between the `x-release-please-start-version` and `x-release-please-end` lines), writes the `CHANGELOG.md` section, tags `vX.Y.Z` and publishes the GitHub release. The `PR Title` workflow checks the title format. The `_VERSION` constant in the main file is not bumped; keep it in step by hand or read it from the header. -- `release-please-config.json` carries `initial-version`, the version init wrote, so the first release is exactly that version. It also carries `bootstrap-sha` when init ran in a git repository, so commits from before init stay out of the first changelog entry. -- `.release-please-manifest.json` records the last released version. It starts at `0.0.0`, which release-please reads as "nothing released yet". Don't edit it, `CHANGELOG.md` or `v*` tags by hand once releases start. -- The skeleton's `CHANGELOG.md` is removed during setup; release-please writes a fresh one on the first release. - -Before the first release: - -- Allow GitHub Actions to create pull requests (repository or organisation settings, Actions, General, Workflow permissions). Client organisations often switch this off. -- Allow only squash merges, with the pull request title as the commit subject. -- If you deploy on tags: the tag and release are created with `GITHUB_TOKEN`, and GitHub starts no workflow runs for events `GITHUB_TOKEN` causes, so a workflow triggered `on: push: tags` never runs for them. Deploy from `release.yml` in a job with `needs: release-please` and `if: needs.release-please.outputs.release-created == 'true'`, or switch the workflow to a GitHub App token. The same limit means the release pull request gets no CI runs, so don't make checks on it required unless you switch tokens. - -A project that does not release versions (a site deployed from a branch, for example) can turn it off with `npm run init -- --disable=release --yes`; that removes the workflow and config and keeps the manifest. Then release by hand with `npm run release:bump` and `npm run release:changelog`, which need a `CHANGELOG.md` with an `## Unreleased` heading. If an existing project that already tags releases turns the feature on, set `.release-please-manifest.json` to its latest released version before the first run. - - ### Optional dependencies - **HMR:** toggling only flips `ENABLE_HMR` in `.env.local`, a per-developer file. See [Live reload](hmr.md). diff --git a/docs/internal/maintenance.md b/docs/internal/maintenance.md index 66be9f5..5a52396 100644 --- a/docs/internal/maintenance.md +++ b/docs/internal/maintenance.md @@ -83,7 +83,7 @@ Put journey and link-check results in the pull request description or release ha ## Releasing the skeleton -Releases of the skeleton itself are cut by [release-please](https://github.com/googleapis/release-please) (`.github/workflows/release-starter.yml`), from the skeleton-only config and manifest in `.github/release-please/`. Cleanup removes all three. Projects get release-please of their own through init's `release` feature: it copies `bin/features/release/release.yml` to `.github/workflows/release.yml` and `bin/features/release/release-please-config.json` to the root, points `extra-files` at the renamed main file, seeds a `0.0.0` manifest, `initial-version` and `bootstrap-sha`, and removes the skeleton's `CHANGELOG.md` (see [initialization.md](../initialization.md#releases)). When you change the skeleton's workflow or config, make the same change in the `bin/features/release/` copies unless it is skeleton-only. +Releases of the skeleton itself are cut by [release-please](https://github.com/googleapis/release-please) (`.github/workflows/release-starter.yml`), from the config and manifest in `.github/release-please/`. Init cleanup removes the workflow and that folder, so projects made from the skeleton don't inherit them; projects release by hand with `npm run release:bump`, `npm run release:changelog` and `npm run release:zip`. 1. Pull requests are squash-merged, so each PR title becomes one Conventional Commit on `main`. release-please reads those subjects and keeps a release pull request open that bumps the version in `package.json`, `package-lock.json` and the plugin header in `project-name-features.php` (between the `x-release-please-start-version` and `x-release-please-end` lines), and writes the next `CHANGELOG.md` section. 2. The title's type decides the version: `fix:` is a patch, `feat:` a minor, and `feat!:` (or a `BREAKING CHANGE:` footer) a major. Anything that changes what init generates, renames a script or a file a project relies on, or raises a minimum version is a `feat!:`. `docs:`, `test:`, `ci:`, `chore:` and `refactor:` cut no release. Dependabot pull requests are prefixed `chore` (see `.github/dependabot.yml`), so they cut no release on their own. Keep it that way: release-please cuts a release for any type with a visible changelog section, `build` included. From 98acec24e911c06db512b16665287df020ff2204 Mon Sep 17 00:00:00 2001 From: Aryan Jasala Date: Thu, 5 Nov 2026 02:52:44 +0530 Subject: [PATCH 6/7] ci: keep the skeleton's release config in .github/release-starter/ Init cleanup also runs on re-init and --clean and deletes its targets recursively, so the generic .github/release-please/ would take a project's own release-please config and manifest with it if the project used that layout. Name the folder for the skeleton, like release-starter.yml, so cleanup only ever removes the skeleton's files. --- .github/{release-please => release-starter}/config.json | 0 .github/{release-please => release-starter}/manifest.json | 0 .github/workflows/release-starter.yml | 6 +++--- bin/scaffold.config.js | 8 ++++---- docs/initialization.md | 2 +- docs/internal/maintenance.md | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) rename .github/{release-please => release-starter}/config.json (100%) rename .github/{release-please => release-starter}/manifest.json (100%) diff --git a/.github/release-please/config.json b/.github/release-starter/config.json similarity index 100% rename from .github/release-please/config.json rename to .github/release-starter/config.json diff --git a/.github/release-please/manifest.json b/.github/release-starter/manifest.json similarity index 100% rename from .github/release-please/manifest.json rename to .github/release-starter/manifest.json diff --git a/.github/workflows/release-starter.yml b/.github/workflows/release-starter.yml index 4a01290..fe65506 100644 --- a/.github/workflows/release-starter.yml +++ b/.github/workflows/release-starter.yml @@ -8,7 +8,7 @@ name: Release (skeleton) # (no tests, no PR title check). That is why neither check is a required status check. Switching # the token below to a GitHub App token would give the release pull request its CI runs. # -# This file releases the skeleton itself, from the config and manifest in .github/release-please/. +# This file releases the skeleton itself, from the config and manifest in .github/release-starter/. # Init cleanup removes all of them, so projects made from the skeleton don't inherit them. on: @@ -38,5 +38,5 @@ jobs: uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 with: token: ${{ github.token }} - config-file: .github/release-please/config.json - manifest-file: .github/release-please/manifest.json + config-file: .github/release-starter/config.json + manifest-file: .github/release-starter/manifest.json diff --git a/bin/scaffold.config.js b/bin/scaffold.config.js index f61d2c0..c82a648 100644 --- a/bin/scaffold.config.js +++ b/bin/scaffold.config.js @@ -228,12 +228,12 @@ module.exports = { ], }, - // The skeleton's own release workflow, config and manifest. Projects don't - // release with them, and the skeleton-only paths keep a re-init from removing - // a project's own release files. + // The skeleton's own release workflow, config and manifest. Cleanup also runs + // on re-init and --clean, so both paths are named for the skeleton: a + // project's own release-please files never live there. cleanup: { targets: [ - '.github/release-please', + '.github/release-starter', '.github/workflows/release-starter.yml', ], }, diff --git a/docs/initialization.md b/docs/initialization.md index 64e169c..79accf5 100644 --- a/docs/initialization.md +++ b/docs/initialization.md @@ -74,7 +74,7 @@ What each set contains is listed in [Included features](features.md). | Kept example sets | Stay as working references. Their `// wp:example:` markers are removed. | | Optional features | `hmr` edits `.env.local`; `tailwind` adds `src/css/tailwind.css`, `postcss.config.js`, dependencies and flips the Tailwind constant. | | State | `.wp-scaffold.json` records identity and feature choices. Do not edit it by hand. | -| Cleanup | Only the skeleton's own release automation is removed: `.github/workflows/release-starter.yml` and `.github/release-please/`. Copilot files, the other workflows and templates under `.github/` stay. | +| Cleanup | Only the skeleton's own release automation is removed: `.github/workflows/release-starter.yml` and `.github/release-starter/`. Copilot files, the other workflows and templates under `.github/` stay. | | AI instructions | `sync-ai` refreshes `.github/instructions/framework-php.instructions.md` from the installed framework. | Init does not generate a translation template. Run `npm run pot` when preparing translations. diff --git a/docs/internal/maintenance.md b/docs/internal/maintenance.md index 5a52396..e1c44e7 100644 --- a/docs/internal/maintenance.md +++ b/docs/internal/maintenance.md @@ -83,13 +83,13 @@ Put journey and link-check results in the pull request description or release ha ## Releasing the skeleton -Releases of the skeleton itself are cut by [release-please](https://github.com/googleapis/release-please) (`.github/workflows/release-starter.yml`), from the config and manifest in `.github/release-please/`. Init cleanup removes the workflow and that folder, so projects made from the skeleton don't inherit them; projects release by hand with `npm run release:bump`, `npm run release:changelog` and `npm run release:zip`. +Releases of the skeleton itself are cut by [release-please](https://github.com/googleapis/release-please) (`.github/workflows/release-starter.yml`), from the config and manifest in `.github/release-starter/`. Init cleanup removes the workflow and that folder, so projects made from the skeleton don't inherit them; projects release by hand with `npm run release:bump`, `npm run release:changelog` and `npm run release:zip`. 1. Pull requests are squash-merged, so each PR title becomes one Conventional Commit on `main`. release-please reads those subjects and keeps a release pull request open that bumps the version in `package.json`, `package-lock.json` and the plugin header in `project-name-features.php` (between the `x-release-please-start-version` and `x-release-please-end` lines), and writes the next `CHANGELOG.md` section. 2. The title's type decides the version: `fix:` is a patch, `feat:` a minor, and `feat!:` (or a `BREAKING CHANGE:` footer) a major. Anything that changes what init generates, renames a script or a file a project relies on, or raises a minimum version is a `feat!:`. `docs:`, `test:`, `ci:`, `chore:` and `refactor:` cut no release. Dependabot pull requests are prefixed `chore` (see `.github/dependabot.yml`), so they cut no release on their own. Keep it that way: release-please cuts a release for any type with a visible changelog section, `build` included. 3. When you want to release, run the [release validation](#release-validation) against the release pull request's branch, reword its entries if a title reads badly, then squash-merge it. release-please tags `vX.Y.Z` and publishes the GitHub release with that section as the notes. -The release pull request is opened with `GITHUB_TOKEN`, so GitHub starts no workflow runs on it, and the PR title check is not a required status check for that reason. A GitHub App token in the workflow would give the release pull request its own runs. Never push a `v*` tag or edit `.github/release-please/manifest.json` by hand. +The release pull request is opened with `GITHUB_TOKEN`, so GitHub starts no workflow runs on it, and the PR title check is not a required status check for that reason. A GitHub App token in the workflow would give the release pull request its own runs. Never push a `v*` tag or edit `.github/release-starter/manifest.json` by hand. `PROJECT_NAME_FEATURES_VERSION` stays at `1.0.0` on purpose. Init rewrites the header and `package.json` to a new project's version but not the constant, so a starter release that moved it would give every new project a constant that disagrees with its header. From 135535632ef6e688280f79e06748a22376b87f81 Mon Sep 17 00:00:00 2001 From: Aryan Jasala Date: Thu, 5 Nov 2026 13:25:48 +0530 Subject: [PATCH 7/7] ci: make Dependabot updates releasable New projects come from composer create-project, which installs the latest tag, so a dependency fix on main reaches nobody until it is released. Prefix Dependabot commits with build, which the changelog lists under Changed and release-please counts as releasable. Each merge only updates the open release pull request; a release still happens only when a maintainer merges that. --- .github/dependabot.yml | 6 +++--- docs/internal/maintenance.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3fb0531..0b57a3b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,7 +6,7 @@ updates: interval: weekly open-pull-requests-limit: 10 commit-message: - prefix: chore + prefix: build include: scope labels: - Dependencies @@ -17,7 +17,7 @@ updates: interval: weekly open-pull-requests-limit: 10 commit-message: - prefix: chore + prefix: build include: scope versioning-strategy: increase labels: @@ -29,7 +29,7 @@ updates: interval: weekly open-pull-requests-limit: 10 commit-message: - prefix: chore + prefix: build include: scope versioning-strategy: increase labels: diff --git a/docs/internal/maintenance.md b/docs/internal/maintenance.md index e1c44e7..bcddb19 100644 --- a/docs/internal/maintenance.md +++ b/docs/internal/maintenance.md @@ -86,7 +86,7 @@ Put journey and link-check results in the pull request description or release ha Releases of the skeleton itself are cut by [release-please](https://github.com/googleapis/release-please) (`.github/workflows/release-starter.yml`), from the config and manifest in `.github/release-starter/`. Init cleanup removes the workflow and that folder, so projects made from the skeleton don't inherit them; projects release by hand with `npm run release:bump`, `npm run release:changelog` and `npm run release:zip`. 1. Pull requests are squash-merged, so each PR title becomes one Conventional Commit on `main`. release-please reads those subjects and keeps a release pull request open that bumps the version in `package.json`, `package-lock.json` and the plugin header in `project-name-features.php` (between the `x-release-please-start-version` and `x-release-please-end` lines), and writes the next `CHANGELOG.md` section. -2. The title's type decides the version: `fix:` is a patch, `feat:` a minor, and `feat!:` (or a `BREAKING CHANGE:` footer) a major. Anything that changes what init generates, renames a script or a file a project relies on, or raises a minimum version is a `feat!:`. `docs:`, `test:`, `ci:`, `chore:` and `refactor:` cut no release. Dependabot pull requests are prefixed `chore` (see `.github/dependabot.yml`), so they cut no release on their own. Keep it that way: release-please cuts a release for any type with a visible changelog section, `build` included. +2. The title's type decides the version: `fix:` is a patch, `feat:` a minor, and `feat!:` (or a `BREAKING CHANGE:` footer) a major. Anything that changes what init generates, renames a script or a file a project relies on, or raises a minimum version is a `feat!:`. `docs:`, `test:`, `ci:`, `chore:` and `refactor:` cut no release. Dependabot pull requests are prefixed `build` (see `.github/dependabot.yml`), which lists them under Changed and makes them releasable: `composer create-project` installs the latest tag, so a dependency fix only reaches new projects once it is released. Each merge only updates the open release pull request, so merge that when you want to release, for example after a batch of security updates. 3. When you want to release, run the [release validation](#release-validation) against the release pull request's branch, reword its entries if a title reads badly, then squash-merge it. release-please tags `vX.Y.Z` and publishes the GitHub release with that section as the notes. The release pull request is opened with `GITHUB_TOKEN`, so GitHub starts no workflow runs on it, and the PR title check is not a required status check for that reason. A GitHub App token in the workflow would give the release pull request its own runs. Never push a `v*` tag or edit `.github/release-starter/manifest.json` by hand.