@wpelevator/wp-release 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -0
- package/dist/changeset-config.d.ts +189 -0
- package/dist/changeset-config.js +273 -0
- package/dist/changeset-release.d.ts +74 -0
- package/dist/changeset-release.js +829 -0
- package/dist/changesets.d.ts +72 -0
- package/dist/changesets.js +103 -0
- package/dist/cli.d.ts +12 -5
- package/dist/cli.js +28 -7
- package/dist/commands/changeset-add.d.ts +37 -0
- package/dist/commands/changeset-add.js +35 -0
- package/dist/commands/changeset-args.d.ts +114 -0
- package/dist/commands/changeset-args.js +139 -0
- package/dist/commands/changeset-status.d.ts +43 -0
- package/dist/commands/changeset-status.js +65 -0
- package/dist/commands/changeset-tag.d.ts +25 -0
- package/dist/commands/changeset-tag.js +27 -0
- package/dist/commands/changeset-version.d.ts +38 -0
- package/dist/commands/changeset-version.js +39 -0
- package/dist/commands/command.d.ts +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,6 +32,10 @@ npx wp-release --help
|
|
|
32
32
|
| `wp-release verify [<source-dir>] --checksums <file> [options]` | Check a plugin or theme directory, or a ZIP, against a checksums manifest. |
|
|
33
33
|
| `wp-release version-check [<source-dir>] [options]` | Check that the `Version` header, readme `Stable tag`, PHP constants and git tag agree. |
|
|
34
34
|
| `wp-release deploy-svn [<source-dir>] [options]` | Deploy a plugin to the WordPress.org plugin SVN repository. |
|
|
35
|
+
| `wp-release changeset-add [options]` | Record the release impact of a pull request. |
|
|
36
|
+
| `wp-release changeset-status [options]` | List pending releases and check changed packages for changesets. |
|
|
37
|
+
| `wp-release changeset-version [options]` | Apply version bumps and changelog entries. |
|
|
38
|
+
| `wp-release changeset-tag [options]` | Create missing package release tags. |
|
|
35
39
|
|
|
36
40
|
Run `npx wp-release <command> --help` for the options of each command. Every command reads the plugin or theme directory from the first argument or `--source-dir`, defaulting to the current directory, and prints its result as JSON with `--json`.
|
|
37
41
|
|
|
@@ -64,6 +68,69 @@ Each command also adds a section to the [job summary](https://docs.github.com/en
|
|
|
64
68
|
|
|
65
69
|
A failed command adds its error to the summary, or a table of its problems with their files and lines, listing up to 100 and counting the rest. The Markdown of the summaries isn't part of the library API. File paths are relative to `GITHUB_WORKSPACE`, or the current directory without it. Files outside it, like those of an extracted ZIP, get an annotation without a file. Multi-line messages stay one annotation.
|
|
66
70
|
|
|
71
|
+
Use global `--config <path>` to select a config; relative paths resolve from the current directory. Otherwise, Changesets commands discover `wp-release.config.js`, `.mjs` or `.cjs` there.
|
|
72
|
+
|
|
73
|
+
### Changesets support (experimental)
|
|
74
|
+
|
|
75
|
+
Changesets support is experimental. It reads Changesets-style Markdown and loads JavaScript configs with descriptors for npm and Composer manifests, plugin and theme headers, readme Stable tags, PHP constants, and Git tags. Use ESM (`export default`) or CommonJS (`module.exports`); TypeScript configs are not supported. Frontmatter accepts `package-name: patch|minor|major` entries; advanced YAML and prerelease mode are unsupported.
|
|
76
|
+
|
|
77
|
+
Typical release flow, from a feature pull request to the publish workflows triggered by release tags:
|
|
78
|
+
|
|
79
|
+
```mermaid
|
|
80
|
+
flowchart TD
|
|
81
|
+
A["Contributor changes a plugin or package"] --> B{"Does this change need a release?"}
|
|
82
|
+
B -->|Yes| C["Add a changeset with package, bump, and release note"]
|
|
83
|
+
B -->|No| D["Add an empty changeset"]
|
|
84
|
+
C --> E["Open or update the feature pull request"]
|
|
85
|
+
D --> E
|
|
86
|
+
E --> F["changeset-status checks changed packages and PR changesets"]
|
|
87
|
+
F -->|Missing a changeset| G["Add the missing changeset"]
|
|
88
|
+
G --> E
|
|
89
|
+
F -->|Pass| H["Merge the feature pull request to main"]
|
|
90
|
+
H --> I["changeset-version updates configured version files and changelogs"]
|
|
91
|
+
I --> J{"Are there package releases to review?"}
|
|
92
|
+
J -->|Yes| K["Workflow opens or updates the Version Packages pull request"]
|
|
93
|
+
K --> L["Review and merge the Version Packages pull request"]
|
|
94
|
+
L --> M["changeset-tag creates tags for packages changed by that release"]
|
|
95
|
+
M --> N["GitHub Releases are created from changelog entries"]
|
|
96
|
+
N --> O["Tag-triggered publish and split workflows run"]
|
|
97
|
+
J -->|No| P["No package versions or release tags are created"]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
An empty changeset marks a feature PR as no-release. The version workflow consumes it without changing versions, and a release PR containing only its deletion produces no tags.
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
import { composerJsonVersion, defineConfig, gitTagVersion, packageJsonVersion, wordpressPluginVersion, wordpressStableTag } from '@wpelevator/wp-release';
|
|
104
|
+
|
|
105
|
+
export default defineConfig( {
|
|
106
|
+
baseBranch: 'main',
|
|
107
|
+
changesets: '.changeset',
|
|
108
|
+
readmeChangelog: true,
|
|
109
|
+
changelogInsertAfter: '<!-- changelog -->',
|
|
110
|
+
root: 'packages',
|
|
111
|
+
tagFormat: '{path}/{version}',
|
|
112
|
+
packages: [
|
|
113
|
+
{ glob: 'js/*', idFrom: 'package.json#name', version: { from: packageJsonVersion(), to: [ packageJsonVersion() ] } },
|
|
114
|
+
{ glob: 'plugins/*', idFrom: 'package.json#name', version: { from: wordpressPluginVersion(), to: [ wordpressPluginVersion(), wordpressStableTag( { ifPresent: true } ), composerJsonVersion( { ifPresent: true } ) ] } },
|
|
115
|
+
{ glob: 'php/*', idFrom: 'composer.json#name', version: { from: gitTagVersion( { pattern: '{path}/{version}' } ), to: [] } },
|
|
116
|
+
],
|
|
117
|
+
} );
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`version.from` selects the current version; `version.to` lists release targets. Package globs support `*`, `?` and `**`; `{path}` in tag templates is relative to `root`. Templates also accept `{type}`, `{name}` and `{slug}`, while `tagType` customizes the type label. Use `phpConstantVersion( { file, constant } )` to sync a PHP constant.
|
|
121
|
+
|
|
122
|
+
The default strategy requires stable `x.y.z` versions. Configure `nextVersion( { package, bump, currentVersion, changesets } )` for other formats; changeset bumps remain `patch`, `minor` or `major`, and prerelease syntax is unsupported.
|
|
123
|
+
|
|
124
|
+
Without `--since`, `changeset-status` compares against `origin/<baseBranch>`; fetch that ref or pass `--since` when using a fork or different upstream. `loadChangesetConfig()` discovers a conventional config at the repository root, unless `--config <path>` selects one explicitly.
|
|
125
|
+
|
|
126
|
+
Add release notes in a feature PR with `npx wp-release changeset-add --package @vendor/plugin --bump patch --summary "Fix the checkout validation."`. Repeat package and bump flags for multiple packages, or use `--empty` when no release is needed. In CI, `changeset-status --since origin/main` requires a changeset added by the PR; changesets already on the base do not count, while an empty PR changeset records an explicit no-release decision. Missing-change annotations identify an affected file.
|
|
127
|
+
|
|
128
|
+
On the release branch, preview with `changeset-version --dry-run`, then run `changeset-version` to update version targets and changelogs. By default, new Markdown entries go before the first version heading; `changelogInsertAfter` sets a literal insertion marker for `CHANGELOG.md` and, with `readmeChangelog`, the plugin readme. Put the marker in both files; a missing marker is an error. Without a marker, plugin readmes use the `== Changelog ==` heading. Failed updates are rolled back for safe retries.
|
|
129
|
+
|
|
130
|
+
After merging the release PR, `changeset-tag --since <merge-commit>^1 --push` pushes tags only for packages changed by that release. Add `--github-release` to create missing GitHub Releases; it requires `--push`. For initial adoption, preview all missing tags with `changeset-tag --dry-run` (without `--since`), then push if needed. Tag pushes need Git write credentials; GitHub Releases need authenticated `gh`.
|
|
131
|
+
|
|
132
|
+
For local development, run the TypeScript source with `npm run wp-release -- <command>` (for example, `npm run wp-release -- changeset-status --since origin/main`). GitHub workflows use the same CLI for versioning and tagging. `SUBTREE_DEPLOY_TOKEN` lets them update the Version Packages PR, push tags, and create GitHub Releases.
|
|
133
|
+
|
|
67
134
|
### Naming convention
|
|
68
135
|
|
|
69
136
|
`@wpelevator/wp-release` is the suite of release tools and `wp-release` is its CLI. "Release" is the whole workflow; each command is one operation in it. Commands are flat: a single word or dashed words like `version-check` and `deploy-svn`, without command groups or aliases. Each GitHub Action is named `wp-release-` followed by the command name, so the CLI command and the `uses:` name always match:
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { Changeset, ChangesetBump, ChangesetPackage } from './changesets.ts';
|
|
2
|
+
export type PackageVersionSource = {
|
|
3
|
+
kind: 'package-json';
|
|
4
|
+
file: string;
|
|
5
|
+
field: 'version';
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'composer-json';
|
|
8
|
+
file: string;
|
|
9
|
+
field: 'version';
|
|
10
|
+
} | {
|
|
11
|
+
kind: 'wordpress-plugin-header';
|
|
12
|
+
file?: string;
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'wordpress-theme-header';
|
|
15
|
+
file?: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'wordpress-stable-tag';
|
|
18
|
+
file?: string;
|
|
19
|
+
ifPresent?: boolean;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'php-constant';
|
|
22
|
+
file: string;
|
|
23
|
+
constant: string;
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'git-tag';
|
|
26
|
+
pattern?: string;
|
|
27
|
+
};
|
|
28
|
+
export type PackageVersionTarget = {
|
|
29
|
+
kind: 'package-json';
|
|
30
|
+
file: string;
|
|
31
|
+
field: 'version';
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'composer-json';
|
|
34
|
+
file: string;
|
|
35
|
+
field: 'version';
|
|
36
|
+
ifPresent: boolean;
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'wordpress-plugin-header';
|
|
39
|
+
file?: string;
|
|
40
|
+
} | {
|
|
41
|
+
kind: 'wordpress-theme-header';
|
|
42
|
+
file?: string;
|
|
43
|
+
} | {
|
|
44
|
+
kind: 'wordpress-stable-tag';
|
|
45
|
+
file?: string;
|
|
46
|
+
ifPresent?: boolean;
|
|
47
|
+
} | {
|
|
48
|
+
kind: 'php-constant';
|
|
49
|
+
file: string;
|
|
50
|
+
constant: string;
|
|
51
|
+
};
|
|
52
|
+
export interface PackageVersionConfig {
|
|
53
|
+
/** File or tag that provides the package's current version. */
|
|
54
|
+
from: PackageVersionSource;
|
|
55
|
+
/** All version locations that should receive the next version. */
|
|
56
|
+
to: PackageVersionTarget[];
|
|
57
|
+
}
|
|
58
|
+
export type PackageIdSource = 'package.json#name' | 'composer.json#name' | 'path';
|
|
59
|
+
/** One directory pattern that describes packages sharing release behavior. */
|
|
60
|
+
export interface ChangesetPackageGroup {
|
|
61
|
+
/** Package directories to discover, relative to the configured package root. */
|
|
62
|
+
glob: string;
|
|
63
|
+
/** Package paths excluded from this group, relative to root. */
|
|
64
|
+
exclude?: string[];
|
|
65
|
+
/** Optional type used by the {type} tag template. */
|
|
66
|
+
tagType?: string;
|
|
67
|
+
/** Manifest field used as the changeset name; defaults to `path`. */
|
|
68
|
+
idFrom?: PackageIdSource;
|
|
69
|
+
/** Version source and files to update for every discovered package. */
|
|
70
|
+
version: PackageVersionConfig;
|
|
71
|
+
}
|
|
72
|
+
/** JavaScript configuration for changeset-aware releases. */
|
|
73
|
+
export interface ChangesetConfig {
|
|
74
|
+
/** Branch receiving ordinary changes. */
|
|
75
|
+
baseBranch?: string;
|
|
76
|
+
/** Directory containing Markdown changesets, relative to the repository root. */
|
|
77
|
+
changesets?: string;
|
|
78
|
+
/** Package groups to discover and release. */
|
|
79
|
+
packages: ChangesetPackageGroup[];
|
|
80
|
+
/** Git tag template supporting {name}, {slug}, {path}, {type} and {version}. */
|
|
81
|
+
tagFormat?: string;
|
|
82
|
+
/** Directory against which package paths in tag formats are calculated. */
|
|
83
|
+
root?: string;
|
|
84
|
+
/** Whether plugin readme changelogs are updated with CHANGELOG.md. */
|
|
85
|
+
readmeChangelog?: boolean;
|
|
86
|
+
/** Insert generated changelog entries after this literal marker. */
|
|
87
|
+
changelogInsertAfter?: string;
|
|
88
|
+
/** Calculate a package's next version instead of using stable SemVer bumps. */
|
|
89
|
+
nextVersion?: (input: NextVersionInput) => string;
|
|
90
|
+
}
|
|
91
|
+
/** Inputs supplied to a custom next-version strategy. */
|
|
92
|
+
export interface NextVersionInput {
|
|
93
|
+
package: ChangesetPackage;
|
|
94
|
+
bump: ChangesetBump;
|
|
95
|
+
currentVersion: string;
|
|
96
|
+
changesets: Changeset[];
|
|
97
|
+
}
|
|
98
|
+
export type WpReleaseConfig = ChangesetConfig;
|
|
99
|
+
export interface PackageJsonVersionOptions {
|
|
100
|
+
file?: string;
|
|
101
|
+
}
|
|
102
|
+
export interface ComposerJsonVersionOptions {
|
|
103
|
+
file?: string;
|
|
104
|
+
/** Skip this target when the `version` property is absent instead of adding it. */
|
|
105
|
+
ifPresent?: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface WordPressVersionFileOptions {
|
|
108
|
+
file?: string;
|
|
109
|
+
/** Skip a missing target file instead of failing. */
|
|
110
|
+
ifPresent?: boolean;
|
|
111
|
+
}
|
|
112
|
+
export interface GitTagVersionOptions {
|
|
113
|
+
pattern?: string;
|
|
114
|
+
}
|
|
115
|
+
export interface PhpConstantVersionOptions {
|
|
116
|
+
file: string;
|
|
117
|
+
constant: string;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Return a package.json version descriptor for use as a source or target.
|
|
121
|
+
*
|
|
122
|
+
* @param options Version manifest path.
|
|
123
|
+
* @return Package version descriptor.
|
|
124
|
+
*/
|
|
125
|
+
export declare function packageJsonVersion(options?: PackageJsonVersionOptions): PackageVersionSource & PackageVersionTarget;
|
|
126
|
+
/**
|
|
127
|
+
* Return a Composer version descriptor for use as a source or target.
|
|
128
|
+
*
|
|
129
|
+
* @param options Composer manifest path and missing-field behavior.
|
|
130
|
+
* @return Package version descriptor.
|
|
131
|
+
*/
|
|
132
|
+
export declare function composerJsonVersion(options?: ComposerJsonVersionOptions): PackageVersionSource & PackageVersionTarget;
|
|
133
|
+
/**
|
|
134
|
+
* Return a WordPress plugin header version descriptor.
|
|
135
|
+
*
|
|
136
|
+
* @param options Optional plugin main file path.
|
|
137
|
+
* @return Package version descriptor.
|
|
138
|
+
*/
|
|
139
|
+
export declare function wordpressPluginVersion(options?: WordPressVersionFileOptions): PackageVersionSource & PackageVersionTarget;
|
|
140
|
+
/**
|
|
141
|
+
* Return a WordPress theme style.css version descriptor.
|
|
142
|
+
*
|
|
143
|
+
* @param options Optional theme stylesheet path.
|
|
144
|
+
* @return Package version descriptor.
|
|
145
|
+
*/
|
|
146
|
+
export declare function wordpressThemeVersion(options?: WordPressVersionFileOptions): PackageVersionSource & PackageVersionTarget;
|
|
147
|
+
/**
|
|
148
|
+
* Return a WordPress readme Stable tag version descriptor.
|
|
149
|
+
*
|
|
150
|
+
* @param options Optional readme path.
|
|
151
|
+
* @return Package version descriptor.
|
|
152
|
+
*/
|
|
153
|
+
export declare function wordpressStableTag(options?: WordPressVersionFileOptions): PackageVersionSource & PackageVersionTarget;
|
|
154
|
+
/**
|
|
155
|
+
* Return a git tag version descriptor for packages without a version file.
|
|
156
|
+
*
|
|
157
|
+
* @param options Optional tag pattern.
|
|
158
|
+
* @return Package version descriptor.
|
|
159
|
+
*/
|
|
160
|
+
export declare function gitTagVersion(options?: GitTagVersionOptions): PackageVersionSource;
|
|
161
|
+
/**
|
|
162
|
+
* Return a PHP constant descriptor for use as a version source or target.
|
|
163
|
+
*
|
|
164
|
+
* @param options PHP file and constant name.
|
|
165
|
+
* @return Package version descriptor.
|
|
166
|
+
*/
|
|
167
|
+
export declare function phpConstantVersion(options: PhpConstantVersionOptions): PackageVersionSource & PackageVersionTarget;
|
|
168
|
+
/**
|
|
169
|
+
* Add type checking to a JavaScript wp-release configuration.
|
|
170
|
+
*
|
|
171
|
+
* @param config Repository release configuration.
|
|
172
|
+
* @return The same configuration object.
|
|
173
|
+
*/
|
|
174
|
+
export declare function defineConfig(config: ChangesetConfig): ChangesetConfig;
|
|
175
|
+
export interface LoadChangesetConfigInput {
|
|
176
|
+
cwd?: string;
|
|
177
|
+
configFile?: string;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Load and validate the repository's JavaScript wp-release configuration.
|
|
181
|
+
*
|
|
182
|
+
* @param input Working directory and optional explicit config path.
|
|
183
|
+
* @return Loaded configuration and its absolute file path, or undefined when absent.
|
|
184
|
+
* @throws {ValidationError} If the module cannot be loaded or its shape is invalid.
|
|
185
|
+
*/
|
|
186
|
+
export declare function loadChangesetConfig(input?: LoadChangesetConfigInput): Promise<{
|
|
187
|
+
config: ChangesetConfig;
|
|
188
|
+
file: string;
|
|
189
|
+
} | undefined>;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { access } from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { pathToFileURL } from 'node:url';
|
|
12
|
+
import { ValidationError } from "./errors.js";
|
|
13
|
+
/**
|
|
14
|
+
* Return a package.json version descriptor for use as a source or target.
|
|
15
|
+
*
|
|
16
|
+
* @param options Version manifest path.
|
|
17
|
+
* @return Package version descriptor.
|
|
18
|
+
*/
|
|
19
|
+
export function packageJsonVersion(options = {}) {
|
|
20
|
+
return {
|
|
21
|
+
kind: 'package-json',
|
|
22
|
+
file: options.file ?? 'package.json',
|
|
23
|
+
field: 'version',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Return a Composer version descriptor for use as a source or target.
|
|
28
|
+
*
|
|
29
|
+
* @param options Composer manifest path and missing-field behavior.
|
|
30
|
+
* @return Package version descriptor.
|
|
31
|
+
*/
|
|
32
|
+
export function composerJsonVersion(options = {}) {
|
|
33
|
+
return {
|
|
34
|
+
kind: 'composer-json',
|
|
35
|
+
file: options.file ?? 'composer.json',
|
|
36
|
+
field: 'version',
|
|
37
|
+
ifPresent: options.ifPresent ?? true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Return a WordPress plugin header version descriptor.
|
|
42
|
+
*
|
|
43
|
+
* @param options Optional plugin main file path.
|
|
44
|
+
* @return Package version descriptor.
|
|
45
|
+
*/
|
|
46
|
+
export function wordpressPluginVersion(options = {}) {
|
|
47
|
+
return {
|
|
48
|
+
kind: 'wordpress-plugin-header',
|
|
49
|
+
...(options.file ? { file: options.file } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Return a WordPress theme style.css version descriptor.
|
|
54
|
+
*
|
|
55
|
+
* @param options Optional theme stylesheet path.
|
|
56
|
+
* @return Package version descriptor.
|
|
57
|
+
*/
|
|
58
|
+
export function wordpressThemeVersion(options = {}) {
|
|
59
|
+
return {
|
|
60
|
+
kind: 'wordpress-theme-header',
|
|
61
|
+
...(options.file ? { file: options.file } : {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Return a WordPress readme Stable tag version descriptor.
|
|
66
|
+
*
|
|
67
|
+
* @param options Optional readme path.
|
|
68
|
+
* @return Package version descriptor.
|
|
69
|
+
*/
|
|
70
|
+
export function wordpressStableTag(options = {}) {
|
|
71
|
+
return {
|
|
72
|
+
kind: 'wordpress-stable-tag',
|
|
73
|
+
...(options.file ? { file: options.file } : {}),
|
|
74
|
+
...(undefined !== options.ifPresent
|
|
75
|
+
? { ifPresent: options.ifPresent }
|
|
76
|
+
: {}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Return a git tag version descriptor for packages without a version file.
|
|
81
|
+
*
|
|
82
|
+
* @param options Optional tag pattern.
|
|
83
|
+
* @return Package version descriptor.
|
|
84
|
+
*/
|
|
85
|
+
export function gitTagVersion(options = {}) {
|
|
86
|
+
return {
|
|
87
|
+
kind: 'git-tag',
|
|
88
|
+
...(options.pattern ? { pattern: options.pattern } : {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Return a PHP constant descriptor for use as a version source or target.
|
|
93
|
+
*
|
|
94
|
+
* @param options PHP file and constant name.
|
|
95
|
+
* @return Package version descriptor.
|
|
96
|
+
*/
|
|
97
|
+
export function phpConstantVersion(options) {
|
|
98
|
+
return { kind: 'php-constant', ...options };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Add type checking to a JavaScript wp-release configuration.
|
|
102
|
+
*
|
|
103
|
+
* @param config Repository release configuration.
|
|
104
|
+
* @return The same configuration object.
|
|
105
|
+
*/
|
|
106
|
+
export function defineConfig(config) {
|
|
107
|
+
return config;
|
|
108
|
+
}
|
|
109
|
+
const CONFIG_FILES = [
|
|
110
|
+
'wp-release.config.js',
|
|
111
|
+
'wp-release.config.mjs',
|
|
112
|
+
'wp-release.config.cjs',
|
|
113
|
+
];
|
|
114
|
+
/**
|
|
115
|
+
* Load and validate the repository's JavaScript wp-release configuration.
|
|
116
|
+
*
|
|
117
|
+
* @param input Working directory and optional explicit config path.
|
|
118
|
+
* @return Loaded configuration and its absolute file path, or undefined when absent.
|
|
119
|
+
* @throws {ValidationError} If the module cannot be loaded or its shape is invalid.
|
|
120
|
+
*/
|
|
121
|
+
export async function loadChangesetConfig(input = {}) {
|
|
122
|
+
const cwd = path.resolve(input.cwd ?? process.cwd());
|
|
123
|
+
const configFile = input.configFile
|
|
124
|
+
? path.resolve(cwd, input.configFile)
|
|
125
|
+
: await findConfigFile(cwd);
|
|
126
|
+
if (!configFile) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
let loaded;
|
|
130
|
+
try {
|
|
131
|
+
loaded = await import(__rewriteRelativeImportExtension(pathToFileURL(configFile).href));
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
135
|
+
throw new ValidationError(`Could not load wp-release config ${configFile}: ${message}`);
|
|
136
|
+
}
|
|
137
|
+
const config = 'object' === typeof loaded && null !== loaded && 'default' in loaded
|
|
138
|
+
? loaded.default
|
|
139
|
+
: undefined;
|
|
140
|
+
validateChangesetConfig(config, configFile);
|
|
141
|
+
return { config, file: configFile };
|
|
142
|
+
}
|
|
143
|
+
async function findConfigFile(cwd) {
|
|
144
|
+
for (const fileName of CONFIG_FILES) {
|
|
145
|
+
const candidate = path.join(cwd, fileName);
|
|
146
|
+
try {
|
|
147
|
+
await access(candidate);
|
|
148
|
+
return candidate;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Try the next conventional filename.
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Validate the supported config shape before commands consume it.
|
|
158
|
+
*
|
|
159
|
+
* @param config Untrusted default export value.
|
|
160
|
+
* @param configFile Config file path for error messages.
|
|
161
|
+
*/
|
|
162
|
+
function validateChangesetConfig(config, configFile) {
|
|
163
|
+
if ('object' !== typeof config ||
|
|
164
|
+
null === config ||
|
|
165
|
+
Array.isArray(config)) {
|
|
166
|
+
throw new ValidationError(`wp-release config ${configFile} must default-export a configuration object.`);
|
|
167
|
+
}
|
|
168
|
+
const candidate = config;
|
|
169
|
+
if (!Array.isArray(candidate.packages)) {
|
|
170
|
+
throw new ValidationError(`wp-release config ${configFile} must define a packages array.`);
|
|
171
|
+
}
|
|
172
|
+
for (const [key, value] of Object.entries({
|
|
173
|
+
baseBranch: candidate.baseBranch,
|
|
174
|
+
changesets: candidate.changesets,
|
|
175
|
+
changelogInsertAfter: candidate.changelogInsertAfter,
|
|
176
|
+
root: candidate.root,
|
|
177
|
+
})) {
|
|
178
|
+
if (undefined !== value && 'string' !== typeof value) {
|
|
179
|
+
throw new ValidationError(`wp-release config ${configFile}.${key} must be a string.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if ('' === candidate.changelogInsertAfter) {
|
|
183
|
+
throw new ValidationError(`wp-release config ${configFile}.changelogInsertAfter must not be empty.`);
|
|
184
|
+
}
|
|
185
|
+
if (undefined !== candidate.tagFormat &&
|
|
186
|
+
'string' !== typeof candidate.tagFormat) {
|
|
187
|
+
throw new ValidationError(`wp-release config ${configFile}.tagFormat must be a string.`);
|
|
188
|
+
}
|
|
189
|
+
if (undefined !== candidate.readmeChangelog &&
|
|
190
|
+
'boolean' !== typeof candidate.readmeChangelog) {
|
|
191
|
+
throw new ValidationError(`wp-release config ${configFile}.readmeChangelog must be a boolean.`);
|
|
192
|
+
}
|
|
193
|
+
if (undefined !== candidate.nextVersion &&
|
|
194
|
+
'function' !== typeof candidate.nextVersion) {
|
|
195
|
+
throw new ValidationError(`wp-release config ${configFile}.nextVersion must be a function.`);
|
|
196
|
+
}
|
|
197
|
+
candidate.packages.forEach((group, index) => {
|
|
198
|
+
if ('object' !== typeof group ||
|
|
199
|
+
null === group ||
|
|
200
|
+
'object' !== typeof group.version ||
|
|
201
|
+
null === group.version ||
|
|
202
|
+
'object' !== typeof group.version.from ||
|
|
203
|
+
!Array.isArray(group.version.to)) {
|
|
204
|
+
throw new ValidationError(`wp-release config ${configFile} has an invalid packages[${index}] entry.`);
|
|
205
|
+
}
|
|
206
|
+
if (undefined !== group.idFrom &&
|
|
207
|
+
!['path', 'package.json#name', 'composer.json#name'].includes(group.idFrom)) {
|
|
208
|
+
throw new ValidationError(`wp-release config ${configFile} packages[${index}].idFrom is not supported.`);
|
|
209
|
+
}
|
|
210
|
+
if ('string' !== typeof group.glob || !group.glob.trim()) {
|
|
211
|
+
throw new ValidationError(`wp-release config ${configFile} packages[${index}].glob must be a non-empty string.`);
|
|
212
|
+
}
|
|
213
|
+
if ('string' !== typeof group.tagType &&
|
|
214
|
+
undefined !== group.tagType) {
|
|
215
|
+
throw new ValidationError(`wp-release config ${configFile} packages[${index}].tagType must be a string.`);
|
|
216
|
+
}
|
|
217
|
+
if (undefined !== group.exclude &&
|
|
218
|
+
(!Array.isArray(group.exclude) ||
|
|
219
|
+
group.exclude.some((item) => 'string' !== typeof item))) {
|
|
220
|
+
throw new ValidationError(`wp-release config ${configFile} packages[${index}].exclude must be an array of strings.`);
|
|
221
|
+
}
|
|
222
|
+
validateVersionDescriptor(group.version.from, `${configFile}.packages[${index}].version.from`, false);
|
|
223
|
+
group.version.to.forEach((target, targetIndex) => validateVersionDescriptor(target, `${configFile}.packages[${index}].version.to[${targetIndex}]`, true));
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function validateVersionDescriptor(descriptor, location, isTarget) {
|
|
227
|
+
if ('object' !== typeof descriptor || null === descriptor) {
|
|
228
|
+
throw new ValidationError(`wp-release config ${location} must be an object.`);
|
|
229
|
+
}
|
|
230
|
+
const candidate = descriptor;
|
|
231
|
+
const sourceKinds = [
|
|
232
|
+
'package-json',
|
|
233
|
+
'composer-json',
|
|
234
|
+
'wordpress-plugin-header',
|
|
235
|
+
'wordpress-theme-header',
|
|
236
|
+
'wordpress-stable-tag',
|
|
237
|
+
'php-constant',
|
|
238
|
+
'git-tag',
|
|
239
|
+
];
|
|
240
|
+
const targetKinds = sourceKinds.filter((kind) => 'git-tag' !== kind);
|
|
241
|
+
const kinds = isTarget ? targetKinds : sourceKinds;
|
|
242
|
+
if ('string' !== typeof candidate.kind ||
|
|
243
|
+
!kinds.includes(candidate.kind)) {
|
|
244
|
+
throw new ValidationError(`wp-release config ${location}.kind is not supported.`);
|
|
245
|
+
}
|
|
246
|
+
const needsFile = [
|
|
247
|
+
'package-json',
|
|
248
|
+
'composer-json',
|
|
249
|
+
'php-constant',
|
|
250
|
+
].includes(candidate.kind);
|
|
251
|
+
if (needsFile &&
|
|
252
|
+
('string' !== typeof candidate.file || !candidate.file.trim())) {
|
|
253
|
+
throw new ValidationError(`wp-release config ${location}.file must be a non-empty string.`);
|
|
254
|
+
}
|
|
255
|
+
if (['package-json', 'composer-json'].includes(candidate.kind) &&
|
|
256
|
+
'version' !== candidate.field) {
|
|
257
|
+
throw new ValidationError(`wp-release config ${location}.field must be "version".`);
|
|
258
|
+
}
|
|
259
|
+
if ('php-constant' === candidate.kind &&
|
|
260
|
+
('string' !== typeof candidate.constant ||
|
|
261
|
+
!candidate.constant.trim())) {
|
|
262
|
+
throw new ValidationError(`wp-release config ${location}.constant must be a non-empty string.`);
|
|
263
|
+
}
|
|
264
|
+
if ('git-tag' === candidate.kind &&
|
|
265
|
+
undefined !== candidate.pattern &&
|
|
266
|
+
'string' !== typeof candidate.pattern) {
|
|
267
|
+
throw new ValidationError(`wp-release config ${location}.pattern must be a string.`);
|
|
268
|
+
}
|
|
269
|
+
if (undefined !== candidate.ifPresent &&
|
|
270
|
+
'boolean' !== typeof candidate.ifPresent) {
|
|
271
|
+
throw new ValidationError(`wp-release config ${location}.ifPresent must be a boolean.`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Changeset, ChangesetReleasePlan, PackageRelease } from './changesets.ts';
|
|
2
|
+
export interface ChangesetRepository {
|
|
3
|
+
cwd: string;
|
|
4
|
+
configFile?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ChangesetVersionInput extends ChangesetRepository {
|
|
7
|
+
dryRun?: boolean;
|
|
8
|
+
date?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ChangesetTagInput extends ChangesetRepository {
|
|
11
|
+
dryRun?: boolean;
|
|
12
|
+
push?: boolean;
|
|
13
|
+
githubRelease?: boolean;
|
|
14
|
+
since?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ChangesetTagResult {
|
|
17
|
+
tags: Array<{
|
|
18
|
+
package: string;
|
|
19
|
+
version: string;
|
|
20
|
+
tag: string;
|
|
21
|
+
created: boolean;
|
|
22
|
+
notes: string;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Discover configured packages, pending changesets, and their proposed versions.
|
|
27
|
+
*
|
|
28
|
+
* @param input Repository root and optional explicit config file.
|
|
29
|
+
* @return A release plan; `undefined` when no config is present.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getChangesetReleasePlan(input: ChangesetRepository): Promise<ChangesetReleasePlan | undefined>;
|
|
32
|
+
/**
|
|
33
|
+
* Create a Changesets-compatible Markdown file.
|
|
34
|
+
*
|
|
35
|
+
* @param input Repository, packages and summary.
|
|
36
|
+
* @return Created file path.
|
|
37
|
+
*/
|
|
38
|
+
export declare function addChangeset(input: ChangesetRepository & {
|
|
39
|
+
packages: Record<string, 'patch' | 'minor' | 'major'>;
|
|
40
|
+
summary: string;
|
|
41
|
+
name?: string;
|
|
42
|
+
}): Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Apply pending package versions and changelogs.
|
|
45
|
+
*
|
|
46
|
+
* @param input Repository and versioning options.
|
|
47
|
+
* @return Planned releases and whether files were changed.
|
|
48
|
+
*/
|
|
49
|
+
export declare function versionChangesets(input: ChangesetVersionInput): Promise<ChangesetReleasePlan & {
|
|
50
|
+
date: string;
|
|
51
|
+
changed: boolean;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Create missing release tags, optionally pushing them to the configured remote.
|
|
55
|
+
*
|
|
56
|
+
* @param input Repository and tag options.
|
|
57
|
+
* @return Per-package tags and release notes.
|
|
58
|
+
*/
|
|
59
|
+
export declare function tagChangesets(input: ChangesetTagInput): Promise<ChangesetTagResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Check whether changed files belong to packages without a changeset.
|
|
62
|
+
*
|
|
63
|
+
* @param input Repository and base git ref.
|
|
64
|
+
* @return Changed packages and missing changeset identifiers.
|
|
65
|
+
*/
|
|
66
|
+
export declare function getChangesetStatus(input: ChangesetRepository & {
|
|
67
|
+
since?: string;
|
|
68
|
+
}): Promise<{
|
|
69
|
+
changesets: Changeset[];
|
|
70
|
+
releases: PackageRelease[];
|
|
71
|
+
changedPackages: string[];
|
|
72
|
+
changedPackageFiles: Record<string, string[]>;
|
|
73
|
+
missingChangesets: string[];
|
|
74
|
+
}>;
|