Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1caca42
Automated Testing: Document and validate expected changelog structure
aduth Sep 16, 2026
fe73650
Validation: Validate added changelog links not under "Unreleased"
aduth Sep 16, 2026
0866b2a
Automated Testing: Drop sed from actions output
aduth Sep 16, 2026
5ad40fa
Automated Testing: Setup Node version in changelog workflows
aduth Sep 16, 2026
bece3bc
Automated Testing: Fix cwd handling for validation
aduth Sep 16, 2026
e458551
Validation: Resolve lint errors
aduth Sep 16, 2026
6355c92
Validation: Use Node base config for TSConfig
aduth Sep 17, 2026
acc73bc
Documentation: Restore 1.0 promotion paragraph
aduth Sep 17, 2026
9999146
Documentation: Fix heading name and link for subsections
aduth Sep 17, 2026
52a17d1
Restore package path filtering with additional coverage for any CHANG…
aduth Sep 17, 2026
9e11b8c
Automated Testing: Remove and update Code Quality, New APIs
aduth Sep 17, 2026
5517db7
Automated Testing: Don't print optional notice for validation issues
aduth Sep 18, 2026
8ec8851
Automated Testing: Ignore delete CHANGELOGs in validation
aduth Sep 18, 2026
5d727a4
Report Flaky Tests: Fix incorrect PR attribution
aduth Sep 18, 2026
09a9743
Automated Testing: Improve tolerance of blank lines
aduth Sep 18, 2026
a304ef5
Automated Testing: Validate expected section order
aduth Sep 18, 2026
db72a73
Automated Testing: Add describe wrapper per tested export
aduth Sep 18, 2026
01f7255
Merge branch 'trunk' into add/changelog-validation
aduth Sep 18, 2026
acfd467
Update CHANGELOG.md
aduth Sep 18, 2026
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
101 changes: 74 additions & 27 deletions .github/workflows/check-package-changelogs.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: OPTIONAL - Verify package CHANGELOG updates
name: Verify package CHANGELOG updates

on:
pull_request:
Expand All @@ -22,17 +22,21 @@ on:
- 'packages/widget-dashboard/**'
- 'packages/widget-primitives/**'
- 'packages/wp-build/**'
- 'packages/*/CHANGELOG.md'
- 'tools/validation/validate-changelogs.ts'
- '.github/workflows/check-package-changelogs.yml'
- '!packages/*/src/**/stories/**'
- '!packages/*/src/**/test/**'
- '!packages/*/CHANGELOG.md'

# Disable permissions for all available scopes by default.
# Any needed permissions should be configured at the job level.
permissions: {}

jobs:
check:
name: Check CHANGELOG diff (${{ matrix.package }})
# Presence: The allowlisted packages listed in the matrix have stricter
# expectations around changes having an accompanying changelog note.
presence:
name: Check CHANGELOG presence (${{ matrix.package }})
runs-on: 'ubuntu-24.04'
permissions:
contents: read
Expand Down Expand Up @@ -87,12 +91,25 @@ jobs:
| grep -Ev "^${PACKAGE_PATH}/(src/(.+/)?(stories|test)/|CHANGELOG\.md$)" || true)

if [ -z "$relevant_changes" ]; then
echo "No relevant changes under ${PACKAGE_PATH}; skipping CHANGELOG check."
echo "No relevant changes under ${PACKAGE_PATH}; skipping CHANGELOG presence check."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
- name: Check CHANGELOG status
# This intentionally doesn't use `.github/setup-node`, because we
# don't currently need to `npm ci` install any dependencies.
- name: Use desired version of Node.js
if: steps.filter.outputs.has_changes == 'true'
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- name: Set up npm
if: steps.filter.outputs.has_changes == 'true'
uses: ./.github/setup-npm
with:
cache: false
- name: Check CHANGELOG cites this PR
if: steps.filter.outputs.has_changes == 'true'
env:
PR_NUMBER: ${{ github.event.number }}
Expand All @@ -104,30 +121,60 @@ jobs:

# Fail if the PR doesn't touch the changelog
if git diff --quiet "$BASE_SHA"...HEAD -- "$changelog_path"; then
echo "Please add a CHANGELOG entry to $changelog_path"
echo
echo "${optional_check_notice}"
exit 1
fi

pr_link_pattern="\[#${PR_NUMBER}\]\(https://github\.com/WordPress/gutenberg/pull/${PR_NUMBER}\)"
pr_link_grep_pattern="\[#${PR_NUMBER}\](https://github\.com/WordPress/gutenberg/pull/${PR_NUMBER})"

unreleased_section=$(sed -n '/^## Unreleased$/,/^## /p' "${changelog_path}")

# Confirm that the CHANGELOG has an entry for the current PR
if ! grep -nq -e "${pr_link_grep_pattern}" "${changelog_path}"; then
echo "Please add a CHANGELOG entry to $changelog_path, and make sure your CHANGELOG entry has a link to the current PR."
echo "Please add a CHANGELOG entry to $changelog_path"
echo
echo "${optional_check_notice}"
exit 1
fi

# Confirm that there is an 'Unreleased' section and that the relevant entry is in that section
if ! grep -nq -e '^## Unreleased' "${changelog_path}" || \
! [[ $unreleased_section = *${pr_link_pattern}* ]]; then
echo "Please make sure your CHANGELOG entry is in the \`## Unreleased\` section"
echo
echo "${optional_check_notice}"
exit 1
# Confirm that the CHANGELOG has a valid entry for the current PR
npm run lint:changelogs -- --require-pr="${PR_NUMBER}" "${changelog_path}"

# Structure: For any revisions to CHANGELOGs (regardless of package),
# validate that the contents follow the expected format.
structure:
name: Validate CHANGELOG structure
runs-on: 'ubuntu-24.04'
permissions:
contents: read
steps:
- name: 'Get PR commit count'
env:
PR_COUNT: ${{ github.event.pull_request.commits }}
run: echo "PR_COMMIT_COUNT=$(( PR_COUNT + 1 ))" >> "${GITHUB_ENV}"
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
fetch-depth: ${{ env.PR_COMMIT_COUNT }}
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
persist-credentials: false
- name: 'Fetch relevant history from base repository'
run: git fetch "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" "$GITHUB_BASE_REF"
# This intentionally doesn't use `.github/setup-node`, because we
# don't currently need to `npm ci` install any dependencies.
- name: Use desired version of Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- name: Set up npm
uses: ./.github/setup-npm
with:
cache: false
- name: Validate touched CHANGELOG files
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_NUMBER: ${{ github.event.number }}
run: |
changelogs=$(git diff --name-only --diff-filter=d "$BASE_SHA"...HEAD -- 'packages/*/CHANGELOG.md')

if [ -z "$changelogs" ]; then
echo "No package CHANGELOG.md files changed; skipping structure check."
exit 0
fi

echo "Validating:"
echo "$changelogs"
echo "$changelogs" | xargs npm run lint:changelogs -- --pr="${PR_NUMBER}"
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ For full architecture details, see `docs/explanations/architecture/`.
- Pages and routes are stable by default. Mark experimental pages with `"experimental": true` in `wpPlugin.pages`; WordPress Core builds exclude those pages and routes used only by experimental pages. Do not introduce separate `stable` or `core` flags for this distinction.
- Never invoke a CLI through `npx`. When the binary is missing locally, `npx` installs whatever the public registry serves under that name: WordPress ships its own `wp-prettier` fork, and a bare `npx wp-scripts` can resolve to an unrelated third-party package rather than `@wordpress/scripts`. Use the npm scripts (`npm run format`, `npm run lint:js`, `npm run lint:css` and so on), which run the binaries from local `node_modules`. Where no script fits, use `npm exec --no -- <binary>` (adding `--workspace <name>` when the workspace that declares the dependency is not the one you are in), which fails instead of downloading. A bare `npm exec` behaves exactly like `npx`, so it needs `--no` too. The exception is a doc example handing the reader a tool they have not installed, such as `npx @wordpress/create-block@latest`: it names an exact package rather than a hijackable bin name.
- PHP function and class names are renamed at build time (`gutenberg_*` prefix, `*_Gutenberg` suffix) to avoid conflicts with WordPress Core — the built names, not the source names, are what runs (and what tests must call). See `docs/contributors/code/build-system-function-prefixing.md`.
- Code changes in a package that have an external impact to consumers should be accompanied by an entry in that package's `CHANGELOG.md`. Entries in the changelog should have a PR reference. In some cases you may not know the PR reference, ask the user. See `docs/contributors/code/managing-packages.md`.
- Code changes in a package that have an external impact to consumers should be accompanied by an entry in that package's `CHANGELOG.md`. Follow the formatting structure described in [Maintaining Changelogs](packages/README.md#maintaining-changelogs).
- Packages with TypeScript dev files split their configs: `tsconfig.build.json` (src only, emits `build-types`) and the default `tsconfig.json` (dev project: tests and stories, `noEmit`, jest types). Packages without dev files keep a single `tsconfig.json` build project, and a few packages deviate (a dev-only project with handwritten declarations, or specialized build projects); see the TypeScript section in `packages/README.md`. Reference a split package by `../<pkg>/tsconfig.build.json` and an unsplit one by `../<pkg>`. `npm run build` never type checks (it emits declarations with `--noCheck`); use `npm run typecheck`, and never add jest types to a build project.
- A rejected `apiFetch` is not always an `Error`: a REST error arrives as a plain object (`{ code, message, data }`), `parse: false` rejects with the `Response` (which carries `status`, not `message`), an aborted request rethrows an `AbortError`, and a handler set via `setFetchHandler` can reject anything. Do not interpolate the rejection into a string (`` `${ error }` `` gives `[object Object]`) or branch on `instanceof Error`. Normalise it to a message before showing the user anything, and supply your own copy when there is none — `ensureError` in `packages/core-data/src/private-actions.js` is the reference implementation, though it is local to that file rather than exported.

Expand Down
1 change: 1 addition & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
"lint:lockfile": "npm run --workspace @wordpress/validation-tools validate-package-lock --",
"lint:package-contents": "npm run --if-present --workspaces lint:package-contents",
"lint:tsconfig": "npm run --workspace @wordpress/validation-tools validate-tsconfig --",
"lint:changelogs": "npm run --workspace @wordpress/validation-tools validate-changelogs --",
"lint:md:docs": "wp-scripts lint-md-docs",
"prelint:php": "npm run other:update-packages:php",
"lint:php": "wp-env run --env-cwd='wp-content/plugins/gutenberg' cli composer run-script lint",
Expand Down
29 changes: 16 additions & 13 deletions packages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,15 @@ See [Testing published packages across WordPress versions](/docs/contributors/co

## Maintaining Changelogs

When maintaining dozens of npm packages, it can be tough to keep track of changes. To simplify the release process, each package includes a `CHANGELOG.md` file which details all published releases and the unreleased ("Unreleased") changes, if any exist.
Each package keeps a `CHANGELOG.md` so the release process can see what changed since the last publish. Put new entries under `## Unreleased` at the top of the file, or create the heading if it is missing.

For each pull request, you should always include relevant changes under an "Unreleased" heading at the top of the file. You should add the heading if it doesn't already exist.
Prefer an entry for anything that affects package consumers. Changelog entries are optional for trivial or for changes that don't impact the user or consumer. Internal changes can be added under an **Internal** heading.

_Example:_
### Changelog Entry Format

Under the "Unreleased" heading, add entries as list items under an appropriate `###` subheading (see ["Changelog Subsections"](#changelog-subsections)). Each top-level bullet should end with a link to the pull request and a period.

Example:

```md
<!-- Learn how to maintain this file at https://github.com/WordPress/gutenberg/tree/HEAD/packages#maintaining-changelogs. -->
Expand All @@ -281,16 +285,16 @@ _Example:_

### Bug Fixes

- Fixed an off-by-one error with the `sum` function.
- Fixed an off-by-one error with the `sum` function ([#12347](https://github.com/WordPress/gutenberg/pull/12347)).
```

You can verify the structure locally by running `npm run lint:changelogs`.

### Promoting a Pre-Release Package to Stable (1.0.0)

The automated package publishing workflow will at most bump the minor version of a pre-release package (those having a version like `0.x.x`), even if it includes breaking changes. This is consistent with semantic versioning, where `0.x` versions are intended for initial development where the API may change frequently.

When a package's API is considered stable and ready for production use, it should be promoted to version 1.0.0. This is done by adding a "Stable Release" section to the `CHANGELOG.md` file:

_Example:_
A `0.x` package is promoted to `1.0.0` by adding a **Stable Release** section.

```md
## Unreleased
Expand All @@ -301,7 +305,7 @@ This package is now considered stable and production-ready. The API will follow

### Breaking Changes

- Final API adjustments before 1.0.0 release.
- Final API adjustments before 1.0.0 release ([#12345](https://github.com/WordPress/gutenberg/pull/12345)).
```

The presence of the "Stable Release" heading will cause the automated release process to bump a pre-1.0 package to 1.0.0. The "Stable Release" heading should only be used for pre-1.0 packages, and from that point forward breaking changes will result in major version bumps as expected.
Expand All @@ -310,17 +314,16 @@ The presence of the "Stable Release" heading will cause the automated release pr

There are a number of common release subsections you can follow. Each is intended to align to a specific meaning in the context of the [Semantic Versioning (`semver`) specification](https://semver.org/) the project adheres to. It is important that you describe your changes accurately, since this is used in the packages release process to help determine the version of the next release.

Use the following standardized section headings and ordering based on the type of change you are making:
Comment thread
manzoorwanijk marked this conversation as resolved.

- "Stable Release" - Marks a pre-1.0 package as stable and production-ready. This should only be used for packages currently published as a 0.x pre-release, to intentionally communicate that a package's API is now stable and ready for production use.
- "Breaking Changes" - A backwards-incompatible change which requires specific attention of the impacted developers to reconcile (requires a major version bump for stable packages).
- "New Features" - The addition of a new backwards-compatible function or feature to the existing public API (requires a minor version bump).
- "Enhancements" - Backwards-compatible improvements to existing functionality (requires a minor version bump).
- "Deprecations" - Deprecation notices. These do not impact the public interface or behavior of the module (requires a minor version bump).
- "Bug Fixes" - Resolutions to existing buggy behavior (requires a patch version bump).
- "Internal" - Changes which do not have an impact on the public interface or behavior of the module (requires a patch version bump).
- "Stable Release" - Marks a pre-1.0 package as stable and production-ready. This should only be used for packages currently published as a 0.x pre-release, to intentionally communicate that a package's API is now stable and ready for production use.

While other section naming can be used when appropriate, it's important that are expressed clearly to avoid confusion for both the packages releaser and third-party consumers.

When in doubt, refer to [Semantic Versioning specification](https://semver.org/).
- "Documentation" - Changes affecting consumer documentation.
Comment thread
aduth marked this conversation as resolved.

If you are publishing new versions of packages, note that there are versioning recommendations outlined in the [Gutenberg Release Process document](https://github.com/WordPress/gutenberg/blob/HEAD/docs/contributors/code/release/README.md) which prescribe _minimum_ version bumps for specific types of releases. The chosen version should be the greater of the two between the semantic versioning and Gutenberg release minimum version bumps.

Expand Down
5 changes: 1 addition & 4 deletions packages/block-editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

## Unreleased

### Internal

- Remove the `crossorigin` MutationObserver. Under `Document-Isolation-Policy: isolate-and-credentialless` cross-origin resources load without the attribute, and adding it broke any resource served without CORS headers ([#82614](https://github.com/WordPress/gutenberg/pull/82614)).

### New Features

- Add a text shadow block support and its control in the typography panel ([#79584](https://github.com/WordPress/gutenberg/pull/79584)).
Expand All @@ -27,6 +23,7 @@

### Internal

- Remove the `crossorigin` MutationObserver. Under `Document-Isolation-Policy: isolate-and-credentialless` cross-origin resources load without the attribute, and adding it broke any resource served without CORS headers ([#82614](https://github.com/WordPress/gutenberg/pull/82614)).
- Block bindings and variation transforms: Use the public `Menu` from `@wordpress/ui` instead of the private Components API. ([#81925](https://github.com/WordPress/gutenberg/pull/81925))
- Layout hooks: Use `normalizeLegacyLayout` in `useLayoutClasses`, `useLayoutStyles`, the block layout styles wrapper and `isAxialBlockGapAllowed`, replacing four inline copies of the legacy `inherit` / size check ([#82710](https://github.com/WordPress/gutenberg/pull/82710)).
- Extract the populated pattern category computation from `usePatternCategories` into a `getPopulatedCategories` util, and expose it and `searchItems` through private APIs for reuse by the start page options modal ([#81396](https://github.com/WordPress/gutenberg/pull/81396)).
Expand Down
8 changes: 4 additions & 4 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@
- `Autocomplete`: Show focus ring with the design system's outline instead of previous box-shadow implementation ([#82766](https://github.com/WordPress/gutenberg/pull/82766)).
- `CustomGradientPicker`: Show the control point's focus ring with the design system's outline and a contrasting backing to separate it from the gradient ([#82834](https://github.com/WordPress/gutenberg/pull/82834)).

### Deprecations

- `Divider`: Deprecate the component, planned for removal in WordPress 7.4 ([#83040](https://github.com/WordPress/gutenberg/pull/83040)).

### Bug Fixes

- `Card`: Keep shadow radii aligned with the Card's actual radius regardless of render order ([#82572](https://github.com/WordPress/gutenberg/pull/82572)).
- `SelectControl`: Stop forcing a fill on the chevron icon, so the stroke-based chevron renders as a line again instead of a filled shape ([#82949](https://github.com/WordPress/gutenberg/pull/82949)).
- `BorderBoxControl`: Restore the split borders layout. The top and bottom controls are centred across the row and the right control sits at the end of its column again, after `BorderControl`'s own `margin: 0` began overriding both ([#82939](https://github.com/WordPress/gutenberg/pull/82939)).
- `PaletteEdit`: Separate adjacent header action buttons so their focus rings do not overlap ([#82768](https://github.com/WordPress/gutenberg/pull/82768)).

### Deprecations

- `Divider`: Deprecate the component, planned for removal in WordPress 7.4 ([#83040](https://github.com/WordPress/gutenberg/pull/83040)).

### Internal

- Remove obsolete Jest test dependencies and types ([#82975](https://github.com/WordPress/gutenberg/pull/82975)).
Expand Down
Loading
Loading