@wpelevator/wp-release 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +316 -2
- package/dist/bin/wp-release.d.ts +2 -0
- package/dist/bin/wp-release.js +3 -0
- package/dist/checksums.d.ts +138 -0
- package/dist/checksums.js +216 -0
- package/dist/cli.d.ts +42 -0
- package/dist/cli.js +163 -0
- package/dist/commands/args.d.ts +81 -0
- package/dist/commands/args.js +116 -0
- package/dist/commands/checksums.d.ts +34 -0
- package/dist/commands/checksums.js +76 -0
- package/dist/commands/command.d.ts +63 -0
- package/dist/commands/command.js +33 -0
- package/dist/commands/deploy-svn.d.ts +74 -0
- package/dist/commands/deploy-svn.js +103 -0
- package/dist/commands/verify.d.ts +27 -0
- package/dist/commands/verify.js +60 -0
- package/dist/commands/version-check.d.ts +38 -0
- package/dist/commands/version-check.js +80 -0
- package/dist/commands/zip.d.ts +41 -0
- package/dist/commands/zip.js +138 -0
- package/dist/deploy-options.d.ts +136 -0
- package/dist/deploy-options.js +168 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +16 -0
- package/dist/file-selection.d.ts +78 -0
- package/dist/file-selection.js +133 -0
- package/dist/filename.d.ts +17 -0
- package/dist/filename.js +23 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +15 -0
- package/dist/logger.d.ts +42 -0
- package/dist/logger.js +56 -0
- package/dist/package-meta.d.ts +112 -0
- package/dist/package-meta.js +222 -0
- package/dist/svn-deploy.d.ts +208 -0
- package/dist/svn-deploy.js +485 -0
- package/dist/svn.d.ts +209 -0
- package/dist/svn.js +298 -0
- package/dist/sync.d.ts +32 -0
- package/dist/sync.js +90 -0
- package/dist/unzip.d.ts +45 -0
- package/dist/unzip.js +121 -0
- package/dist/version-check.d.ts +75 -0
- package/dist/version-check.js +193 -0
- package/dist/zip.d.ts +32 -0
- package/dist/zip.js +53 -0
- package/package.json +61 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kaspars Dambis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,317 @@
|
|
|
1
|
-
#
|
|
1
|
+
# WP Release
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Package, checksum, verify and version-check WordPress plugins and themes, and deploy plugins to the WordPress.org plugin SVN repository. Usable as a CLI, as a library, and through the [`wp-release-deploy-svn`](../../actions/wp-release-deploy-svn) GitHub Action.
|
|
4
|
+
|
|
5
|
+
Themes are released by uploading a ZIP on WordPress.org, so `deploy-svn` is for plugins only; use `zip` to build the theme upload.
|
|
6
|
+
|
|
7
|
+
**Status:** first release (0.x). All commands are implemented and tested against local SVN repositories, but `deploy-svn` hasn't been used for a WordPress.org deploy yet, so try it with `--dry-run` first. Signing checksums manifests is planned.
|
|
8
|
+
|
|
9
|
+
Option names follow the existing community tools (10up WordPress.org actions, `wp dist-archive`, `wp-scripts plugin-zip`, `grunt-wp-deploy`) so that migrating needs few or no renames. See [Comparison with existing tools](#comparison-with-existing-tools) for the differences.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install --save-dev @wpelevator/wp-release
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires Node.js 22.12 or later. `deploy-svn` also needs the `svn` command line client.
|
|
18
|
+
|
|
19
|
+
The package is an ES module with TypeScript type definitions. CommonJS projects can load it with `require( '@wpelevator/wp-release' )`, which Node.js supports for ES modules without top-level `await` since 22.12.
|
|
20
|
+
|
|
21
|
+
## CLI
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx wp-release --help
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Command | Description |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `wp-release zip [<source-dir>] [options]` | Create a ZIP archive with a `{slug}/` root folder, respecting `.distignore`. |
|
|
30
|
+
| `wp-release checksums [<source-dir>] [options]` | Write the MD5 and SHA-256 hashes of every distributed file to a checksums manifest. |
|
|
31
|
+
| `wp-release verify [<source-dir>] --checksums <file> [options]` | Check a plugin or theme directory against a checksums manifest. |
|
|
32
|
+
| `wp-release version-check [<source-dir>] [options]` | Check that the `Version` header, readme `Stable tag`, PHP constants and git tag agree. |
|
|
33
|
+
| `wp-release deploy-svn [<source-dir>] [options]` | Deploy a plugin to the WordPress.org plugin SVN repository. |
|
|
34
|
+
|
|
35
|
+
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`.
|
|
36
|
+
|
|
37
|
+
### Naming convention
|
|
38
|
+
|
|
39
|
+
`@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:
|
|
40
|
+
|
|
41
|
+
| Capability | CLI command | GitHub Action |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| Build a ZIP | `wp-release zip` | |
|
|
44
|
+
| Create a checksums manifest | `wp-release checksums` | |
|
|
45
|
+
| Verify files against a manifest | `wp-release verify` | |
|
|
46
|
+
| Check that the versions agree | `wp-release version-check` | |
|
|
47
|
+
| Deploy to WordPress.org SVN | `wp-release deploy-svn` | [`wpelevator/wp-release-deploy-svn`](../../actions/wp-release-deploy-svn) |
|
|
48
|
+
|
|
49
|
+
Each concept has one name, written in the syntax of each interface:
|
|
50
|
+
|
|
51
|
+
| Concept | CLI | GitHub Action | Library | Meaning |
|
|
52
|
+
|---|---|---|---|---|
|
|
53
|
+
| Plugin or theme directory | `<source-dir>` argument or `--source-dir` | `source-dir` | `sourceDir` | Directory that a command reads, whether it's the repository or a build directory. |
|
|
54
|
+
| Generated file | `--output <file>` | | `outputPath` | File written by `zip` or `checksums`. |
|
|
55
|
+
| ZIP to deploy | `--from-zip <file>` | `from-zip` | `fromZip` | Existing ZIP read as the deploy source. |
|
|
56
|
+
| Git tag to compare | `--git-tag <tag>` | | `gitTag` | Git tag checked by `version-check`. |
|
|
57
|
+
| SVN tag to create | `--svn-tag <version>` | `svn-tag` | `svnTag` | Tag written by `deploy-svn`, distinct from a git tag. |
|
|
58
|
+
| SVN repository | `--svn-url <url>` | `svn-url` | `svnUrl` | Deploy destination. |
|
|
59
|
+
| Assets sync | `--no-assets` to skip | `assets: false` to skip | `assets` | Booleans are positive in the library and the action; `--no-assets` is only the CLI negation, never `noAssets` or a `no-assets` input. |
|
|
60
|
+
|
|
61
|
+
CLI JSON results use camelCase keys like `zipPath`, and GitHub Action outputs use kebab-case like `zip-path`. Library classes are named after the capability (`SvnDeploy`, `Svn`) without repeating "release".
|
|
62
|
+
|
|
63
|
+
### File selection
|
|
64
|
+
|
|
65
|
+
`zip`, `checksums` and `deploy-svn` select files the same way:
|
|
66
|
+
|
|
67
|
+
| Option | Description |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `--distignore <file>` | Path to the ignore file. Defaults to `.distignore` in the source directory when present; an explicitly passed file must exist. |
|
|
70
|
+
| `--no-distignore` | Don't read any ignore file. |
|
|
71
|
+
| `--exclude <patterns>` | Extra gitignore-style patterns, comma-separated or repeated, like `--exclude build/,assets/*,images/**/*`. They're applied after the ignore file, so a `!` pattern can also re-include a file. |
|
|
72
|
+
|
|
73
|
+
Both use full gitignore syntax, including `!` negation. Version control metadata (`.git`, `.svn` and `.hg`, at any depth, including the `.git` files of worktrees and submodules) is always left out, also without an ignore file and when deploying from a ZIP, and no `!` pattern can re-include it. This keeps a repository's history out of ZIPs and SVN deploys when the source directory is the repository root. The comma-separated `--exclude` list works like `--exclude` in `wp i18n make-pot`. `deploy-svn --from-zip` ignores both options, since the ZIP is already filtered. Symbolic links that aren't excluded are an error, since ZIP archives and WordPress.org SVN don't support them.
|
|
74
|
+
|
|
75
|
+
### Plugin and theme headers
|
|
76
|
+
|
|
77
|
+
The commands read the same headers as WordPress. The main plugin file is the top-level `.php` file with a `Plugin Name:` header in its first 8 KB, preferring `{slug}.php` when several files have one. A directory without a plugin file and with `Theme Name:` in `style.css` is a theme. The readme headers (`Stable tag`, `Tested up to`, …) come from `readme.txt`, or `readme.md` when there's no `readme.txt`.
|
|
78
|
+
|
|
79
|
+
`--version` defaults to the `Version` header in `zip`, `checksums` and `version-check`, and `checksums --type` defaults to the detected package type.
|
|
80
|
+
|
|
81
|
+
### ZIP
|
|
82
|
+
|
|
83
|
+
`zip` writes `{slug}.{version}.zip` to the current directory, or to `--output <file>`, with the selected files in a `{slug}/` root folder:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
wp-release zip dist --slug my-plugin --checksums
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- `--root-folder <name>` changes the root folder and `--no-root-folder` puts the files at the root of the ZIP.
|
|
90
|
+
- `--filename-format` changes the name, like `--filename-format {slug}` for `my-plugin.zip`.
|
|
91
|
+
- Entries are sorted and use a fixed timestamp and file mode, so the same files always give the same ZIP.
|
|
92
|
+
- `--checksums` also writes the [checksums manifest](#checksums) of the archived files next to the ZIP, with `.checksums.json` instead of `.zip`.
|
|
93
|
+
- An existing ZIP or manifest isn't overwritten without `--force`. A ZIP or manifest written inside the source directory is never archived.
|
|
94
|
+
|
|
95
|
+
### Checksums
|
|
96
|
+
|
|
97
|
+
`checksums` writes a manifest with the MD5 and SHA-256 hash of every selected file, in the format of the WordPress.org plugin checksums API (`https://downloads.wordpress.org/plugin-checksums/{slug}/{version}.json`). Paths are relative to the plugin or theme folder, sorted, and use forward slashes:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"plugin": "my-plugin",
|
|
102
|
+
"version": "1.2.0",
|
|
103
|
+
"files": {
|
|
104
|
+
"my-plugin.php": {
|
|
105
|
+
"md5": "…",
|
|
106
|
+
"sha256": "…"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Themes use a `theme` key instead of `plugin`, and the version is left out when it's unknown. The manifest is named `{slug}.{version}.checksums.json` after `--filename-format`, the same name as the ZIP with `.checksums.json` instead of `.zip`, so the two are published side by side; `--output <file>` writes it elsewhere. A manifest written inside `<source-dir>` is never listed in itself.
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Hash the build that is zipped and deployed
|
|
116
|
+
wp-release checksums dist --version 1.2.0
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Because the manifest lists the files and not the ZIP, it stays valid for any copy of the same files: the GitHub release ZIP, the ZIP that WordPress.org builds from SVN, or an installed plugin on a site.
|
|
120
|
+
|
|
121
|
+
### Verify
|
|
122
|
+
|
|
123
|
+
`verify` checks a directory against a manifest from `checksums` or from the WordPress.org checksums API, and reports every changed, missing and extra file. It exits with `2` unless all files match:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
wp-release verify wp-content/plugins/my-plugin --checksums my-plugin.1.2.0.checksums.json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- A file matches when its SHA-256 equals the manifest. Manifests without SHA-256 fall back to MD5, and a hash can also be a list of accepted values.
|
|
130
|
+
- `verify` doesn't read `.distignore`, so that an ignore file inside the checked directory can't hide added files. `--exclude <patterns>` skips paths in both the directory and the manifest, for files that aren't part of the release, such as caches.
|
|
131
|
+
- Manifest paths outside the package folder (absolute or with `..`) are rejected.
|
|
132
|
+
|
|
133
|
+
A checksums manifest proves only that the files match the manifest. Signing the manifest, so that the manifest itself can be trusted, is planned.
|
|
134
|
+
|
|
135
|
+
### Version check
|
|
136
|
+
|
|
137
|
+
`version-check` compares the versions that have to agree before a release, and reports every mismatch with exit code `2`:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
wp-release version-check --constant MY_PLUGIN_VERSION --git-tag v1.2.0
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
- The expected version is `--version`, or the `Version` header.
|
|
144
|
+
- The readme `Stable tag` must match for plugins; `--no-stable-tag` skips it, and themes have no Stable tag.
|
|
145
|
+
- `--constant NAME` (repeatable) checks `define( 'NAME', '1.2.0' )` or `const NAME = '1.2.0'` in every PHP file outside `vendor/`, `node_modules/` and dot directories. A constant that isn't defined is a mismatch.
|
|
146
|
+
- `--git-tag` defaults to `GITHUB_REF` when it's a tag. `--git-tag-format` (default `{version}`) can have a prefix or the slug, like `v{version}` or `{slug}-{version}`; a leading `v` in the version is ignored, like for SVN tags.
|
|
147
|
+
|
|
148
|
+
### Deploy to SVN
|
|
149
|
+
|
|
150
|
+
`deploy-svn` syncs a plugin to the WordPress.org plugin SVN repository. Each SVN write is a separate flag and nothing is written unless requested. All writes go into one commit.
|
|
151
|
+
|
|
152
|
+
| Flag | Default | Writes |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `--trunk` | off | Sync the full source directory to `trunk/` |
|
|
155
|
+
| `--readme-only` | off | Sync only the readme to `trunk/`, for updates between releases such as bumping `Tested up to` |
|
|
156
|
+
| `--svn-tag <version>` | off | Copy `trunk/` to `tags/<version>`; requires `--trunk`. A leading `v` is stripped, so `v1.2.0` creates `tags/1.2.0`. |
|
|
157
|
+
| `--assets-dir <dir>` | not set | Sync this directory with banners, icons and screenshots to the SVN `assets/` directory. There's no default directory, so assets are only deployed when this is set, and it must exist. |
|
|
158
|
+
| `--no-assets` | | Skip the `--assets-dir` sync for one run, for example when a config always passes `--assets-dir`. |
|
|
159
|
+
|
|
160
|
+
Typical runs:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Deploy 1.2.0 to trunk and the tag, and update the assets
|
|
164
|
+
wp-release deploy-svn dist --trunk --svn-tag 1.2.0 --assets-dir .wordpress-org
|
|
165
|
+
|
|
166
|
+
# Update only the readme between releases
|
|
167
|
+
wp-release deploy-svn dist --readme-only
|
|
168
|
+
|
|
169
|
+
# Update only the assets
|
|
170
|
+
wp-release deploy-svn --assets-dir .wordpress-org
|
|
171
|
+
|
|
172
|
+
# Deploy exactly the files of a ZIP built earlier
|
|
173
|
+
wp-release deploy-svn --from-zip my-plugin.1.2.0.zip --trunk --svn-tag 1.2.0
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
How a deploy works:
|
|
177
|
+
|
|
178
|
+
1. The options are validated, including the slug, version and readme name, before they're used in URLs or paths.
|
|
179
|
+
2. With `--trunk`, the files must be a plugin whose `Version` header matches the version, and with `--svn-tag` the readme `Stable tag` must match too. `--readme-only` needs the readme. Nothing is checked out when a check fails.
|
|
180
|
+
3. The repository is checked out sparsely into a new temporary directory, with only the directories that the flags write to. A missing `assets/` (or `trunk/`, `tags/`) directory is created. The deploy stops when `tags/<version>` exists, unless `--force` replaces it.
|
|
181
|
+
4. The files are synced in Node.js, without `rsync`: new and changed files are copied, files that are no longer selected are deleted, and `svn add` and `svn delete` stage them, including files that svn ignores by default. `trunk/` is then copied to the tag with `svn copy`.
|
|
182
|
+
5. Asset images get `svn:mime-type` set for PNG, JPEG, GIF and SVG, the formats that WordPress.org documents for plugin assets. Other formats keep the type that svn assigns.
|
|
183
|
+
6. When `svn status` is empty, the deploy ends without a commit. A dry run logs the status and stops before the commit. Otherwise the changes are committed, and the temporary directory is always removed.
|
|
184
|
+
|
|
185
|
+
`--from-zip` reads the files from a ZIP instead of `<source-dir>`. The ZIP is checked before it's extracted: entries with absolute paths, `..` segments or symbolic links, and entries outside a single `{slug}/` root folder, are rejected.
|
|
186
|
+
|
|
187
|
+
Credentials are only needed for the commit, not for dry runs, no-op runs or `file://` repositories. Pass the username with `--svn-username` or `SVN_USERNAME` and the password in `SVN_PASSWORD`. svn always runs non-interactively without caching credentials, and the password is passed on the standard input with `--password-from-stdin`, never on the command line or in the logs.
|
|
188
|
+
|
|
189
|
+
Validation:
|
|
190
|
+
|
|
191
|
+
- `--trunk` and `--readme-only` can't be combined.
|
|
192
|
+
- `--readme-only` is rejected when `GITHUB_REF` is a tag, since a readme-only update from a release tag usually means the build was forgotten.
|
|
193
|
+
- Pre-release versions like `1.0.0-rc.1` are never tagged.
|
|
194
|
+
- `--version` defaults to `--svn-tag`, then the `Version` header of the deployed files; when `--version` and `--svn-tag` are both set they must match.
|
|
195
|
+
- `--trunk` without `--svn-tag` works but warns, because trunk then differs from the stable tag that WordPress.org serves downloads from.
|
|
196
|
+
|
|
197
|
+
### Exit codes
|
|
198
|
+
|
|
199
|
+
| Code | Meaning |
|
|
200
|
+
|---|---|
|
|
201
|
+
| `0` | Success, dry run, no changes, or help |
|
|
202
|
+
| `1` | Unexpected failure, such as a failed svn command |
|
|
203
|
+
| `2` | Invalid options, failed release checks, an existing SVN tag, or files that don't match the checksums |
|
|
204
|
+
| `3` | Missing SVN credentials, or credentials that SVN rejected |
|
|
205
|
+
|
|
206
|
+
## Library
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
import { Logger, SvnDeploy, resolveDeployOptions } from '@wpelevator/wp-release';
|
|
210
|
+
|
|
211
|
+
const options = resolveDeployOptions( {
|
|
212
|
+
slug: 'my-plugin',
|
|
213
|
+
sourceDir: 'dist',
|
|
214
|
+
trunk: true,
|
|
215
|
+
svnTag: '1.2.0',
|
|
216
|
+
dryRun: true,
|
|
217
|
+
} );
|
|
218
|
+
|
|
219
|
+
const { committed, revision, version, changes } = await new SvnDeploy(
|
|
220
|
+
options,
|
|
221
|
+
new Logger()
|
|
222
|
+
).run();
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`resolveDeployOptions()` applies the defaults and throws a `ValidationError` (with `exitCode` `2`) for invalid combinations. `SvnDeploy` exposes each step (`prepare()`, `checkVersions()`, `checkout()`, `sync()`, `commit()` and `cleanup()`) separately so CI wrappers can run and report them one by one; `run()` runs all of them, or nothing when there's nothing to write. `getPlan()` lists the planned SVN changes. The `Svn` class wraps the svn command line, and takes the command runner as a dependency so that tests can replace it.
|
|
226
|
+
|
|
227
|
+
ZIP files and metadata:
|
|
228
|
+
|
|
229
|
+
```js
|
|
230
|
+
import {
|
|
231
|
+
createZip,
|
|
232
|
+
findVersionMismatches,
|
|
233
|
+
loadFileRules,
|
|
234
|
+
readPackageMeta,
|
|
235
|
+
resolveFileSelection,
|
|
236
|
+
selectFiles,
|
|
237
|
+
} from '@wpelevator/wp-release';
|
|
238
|
+
|
|
239
|
+
const rules = await loadFileRules(
|
|
240
|
+
resolveFileSelection( { cwd: process.cwd(), sourceDir: 'dist' } )
|
|
241
|
+
);
|
|
242
|
+
const meta = await readPackageMeta( { dir: 'dist', slug: 'my-plugin' } );
|
|
243
|
+
|
|
244
|
+
await createZip( {
|
|
245
|
+
sourceDir: 'dist',
|
|
246
|
+
files: await selectFiles( 'dist', rules ),
|
|
247
|
+
zipPath: `my-plugin.${ meta.version }.zip`,
|
|
248
|
+
rootFolder: 'my-plugin',
|
|
249
|
+
} );
|
|
250
|
+
|
|
251
|
+
const { mismatches } = await findVersionMismatches( {
|
|
252
|
+
sourceDir: 'dist',
|
|
253
|
+
meta,
|
|
254
|
+
slug: 'my-plugin',
|
|
255
|
+
constants: [ 'MY_PLUGIN_VERSION' ],
|
|
256
|
+
} );
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Checksums:
|
|
260
|
+
|
|
261
|
+
```js
|
|
262
|
+
import {
|
|
263
|
+
createChecksums,
|
|
264
|
+
loadFileRules,
|
|
265
|
+
readChecksums,
|
|
266
|
+
resolveFileSelection,
|
|
267
|
+
verifyChecksums,
|
|
268
|
+
writeChecksums,
|
|
269
|
+
} from '@wpelevator/wp-release';
|
|
270
|
+
|
|
271
|
+
const rules = await loadFileRules(
|
|
272
|
+
resolveFileSelection( { cwd: process.cwd(), sourceDir: 'dist' } )
|
|
273
|
+
);
|
|
274
|
+
const manifest = await createChecksums( {
|
|
275
|
+
sourceDir: 'dist',
|
|
276
|
+
rules,
|
|
277
|
+
slug: 'my-plugin',
|
|
278
|
+
version: '1.2.0',
|
|
279
|
+
} );
|
|
280
|
+
|
|
281
|
+
await writeChecksums( 'my-plugin.1.2.0.checksums.json', manifest );
|
|
282
|
+
|
|
283
|
+
const { valid, changed, missing, extra } = await verifyChecksums( {
|
|
284
|
+
sourceDir: 'wp-content/plugins/my-plugin',
|
|
285
|
+
manifest: await readChecksums( 'my-plugin.1.2.0.checksums.json' ),
|
|
286
|
+
rules: await loadFileRules( {} ),
|
|
287
|
+
} );
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
`loadFileRules()` reads the ignore file and `--exclude` patterns into a gitignore matcher and `selectFiles()` lists the files it doesn't ignore. `compareChecksums()` compares two hash maps without reading any files, and `parseChecksums()` validates manifest JSON. `extractZip()` extracts a ZIP after the same checks as `deploy-svn --from-zip`.
|
|
291
|
+
|
|
292
|
+
## Development
|
|
293
|
+
|
|
294
|
+
The source is TypeScript in `src/`, with tests next to the files they test (`*.test.ts`). The published package contains only the compiled `dist/` with JavaScript and `.d.ts` files.
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
npm run build --workspace @wpelevator/wp-release
|
|
298
|
+
npm test --workspace @wpelevator/wp-release
|
|
299
|
+
npm run lint --workspace @wpelevator/wp-release
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
- `build` compiles `src/` to `dist/` with `tsc`, leaving out the tests.
|
|
303
|
+
- `test` runs the `.ts` tests directly with `node --test`, using Node.js type stripping, so it needs Node.js 22.18 or later but no build. The SVN integration tests in `svn-deploy.integration.test.ts` create repositories with `svnadmin` and deploy to their `file://` URLs; they're skipped when `svn` or `svnadmin` isn't installed.
|
|
304
|
+
- `lint` runs ESLint and a `tsc` type check.
|
|
305
|
+
|
|
306
|
+
The `wpelevator-source` export condition points at `src/index.ts`. The tests and the `wp-release-deploy-svn` action build use it to work with the source instead of a `dist/` build. It's not set by other tools, so the published package always resolves to `dist/`.
|
|
307
|
+
|
|
308
|
+
## Comparison with existing tools
|
|
309
|
+
|
|
310
|
+
| Tool | What it does | How `wp-release` differs |
|
|
311
|
+
|---|---|---|
|
|
312
|
+
| [10up WordPress.org plugin deploy](https://github.com/10up/action-wordpress-plugin-deploy) and [readme/asset update](https://github.com/10up/action-wordpress-plugin-asset-update) actions | Two separate GitHub Actions configured through env vars. A deploy always syncs the build to `trunk/` and copies it to the tag; readme and asset updates need the second action. Assets come from `.wordpress-org` by default, and files are selected with `.distignore` or `.gitattributes`. Files are synced with `rsync`. | One `deploy-svn` command with independent write flags, so a release, a readme-only update and an assets-only update are the same command with different flags. Runs locally and in any CI, not only GitHub Actions, and syncs in Node.js without `rsync`. Checks that the `Version` header, readme `Stable tag` and SVN tag agree before writing, which the 10up actions don't. Assets are deployed only when `--assets-dir` is passed, instead of always syncing `.wordpress-org` when it exists, so a plugin's own `assets/` folder is never uploaded by accident. Adds `--exclude` for patterns without an ignore file and `--from-zip` to deploy the exact files of a ZIP. |
|
|
313
|
+
| [`grunt-wp-deploy`](https://github.com/stephenharris/grunt-wp-deploy) | Grunt task with `deploy_trunk` and `deploy_tag` booleans, interactive password prompts and a `Stable tag` check. | No Grunt dependency and never interactive. `--readme-only` is a separate flag, where `grunt-wp-deploy` can only update trunk with the full build. Assets are independent of trunk writes. |
|
|
314
|
+
| [codeinwp theme deploy](https://github.com/codeinwp/action-wordpress-theme-deploy) | Fork of the 10up plugin action that commits themes to the themes SVN repository. | Doesn't deploy themes. Theme releases are ZIP uploads on WordPress.org; `zip` builds the upload with the theme folder as the root. |
|
|
315
|
+
| [`wp dist-archive`](https://github.com/wp-cli/dist-archive-command) | WP-CLI command that zips a project using `.distignore`, with `--plugin-dirname` and `--filename-format`. | Same `.distignore` file and `{name}.{version}` style filename format, but needs only Node.js instead of PHP and WP-CLI. `.distignore` is matched with full gitignore syntax, including `!` negation, and `--distignore` / `--exclude` let you point at another ignore file or add patterns on the command line. Builds are reproducible, and `--checksums` writes a manifest next to the ZIP. |
|
|
316
|
+
| [`wp plugin verify-checksums`](https://developer.wordpress.org/cli/commands/plugin/verify-checksums/) | WP-CLI command that checks installed plugins against the WordPress.org checksums API. | Reads the same manifest format, but from a file, so it also works for plugins and themes that aren't hosted on WordPress.org and for build directories in CI. `checksums` writes the manifest that WordPress.org generates for its own plugins. |
|
|
317
|
+
| [`wp-scripts plugin-zip`](https://github.com/WordPress/gutenberg/tree/trunk/packages/scripts#plugin-zip) | Zips files from `package.json` `files` or a fixed allowlist, named after the npm package, with `--root-folder` / `--no-root-folder`. | Reuses the root folder flags, selects files with `.distignore` instead of an allowlist, puts the version from the plugin headers in the filename and streams the archive instead of building it in memory. |
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { FileRules } from './file-selection.ts';
|
|
2
|
+
export declare const CHECKSUMS_EXTENSION = ".checksums.json";
|
|
3
|
+
/**
|
|
4
|
+
* Hash algorithms in each file entry. MD5 is kept for compatibility with the
|
|
5
|
+
* WordPress.org checksums format; verification prefers SHA-256.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CHECKSUM_ALGORITHMS: readonly ["md5", "sha256"];
|
|
8
|
+
export type ChecksumAlgorithm = (typeof CHECKSUM_ALGORITHMS)[number];
|
|
9
|
+
export type PackageType = 'plugin' | 'theme';
|
|
10
|
+
/**
|
|
11
|
+
* Hex digests of a file by algorithm.
|
|
12
|
+
*/
|
|
13
|
+
export type FileHashes = Record<ChecksumAlgorithm, string>;
|
|
14
|
+
/**
|
|
15
|
+
* Expected hashes of a file in a manifest. The WordPress.org checksums API
|
|
16
|
+
* can list several accepted values for a hash.
|
|
17
|
+
*/
|
|
18
|
+
export type ExpectedHashes = Partial<Record<ChecksumAlgorithm, string | string[]>>;
|
|
19
|
+
/**
|
|
20
|
+
* Checksums manifest in the WordPress.org plugin checksums format.
|
|
21
|
+
*/
|
|
22
|
+
export interface ChecksumsManifest {
|
|
23
|
+
plugin?: string;
|
|
24
|
+
theme?: string;
|
|
25
|
+
version?: string;
|
|
26
|
+
files: Record<string, ExpectedHashes>;
|
|
27
|
+
}
|
|
28
|
+
export interface CreateChecksumsInput {
|
|
29
|
+
/** Directory with the files. */
|
|
30
|
+
sourceDir: string;
|
|
31
|
+
/** Matcher from loadFileRules(). */
|
|
32
|
+
rules: FileRules;
|
|
33
|
+
/** Plugin or theme slug. */
|
|
34
|
+
slug: string;
|
|
35
|
+
/** Version, if known. */
|
|
36
|
+
version?: string | null;
|
|
37
|
+
/** Package type, plugin or theme. */
|
|
38
|
+
type?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface ChecksumsComparison {
|
|
41
|
+
/** Whether every file matches and none are missing or extra. */
|
|
42
|
+
valid: boolean;
|
|
43
|
+
/** Paths with matching hashes. */
|
|
44
|
+
matched: string[];
|
|
45
|
+
/** Paths with different hashes. */
|
|
46
|
+
changed: string[];
|
|
47
|
+
/** Paths in the manifest that are missing from the directory. */
|
|
48
|
+
missing: string[];
|
|
49
|
+
/** Paths in the directory that aren't in the manifest. */
|
|
50
|
+
extra: string[];
|
|
51
|
+
}
|
|
52
|
+
export interface VerifyChecksumsInput {
|
|
53
|
+
/** Plugin or theme directory. */
|
|
54
|
+
sourceDir: string;
|
|
55
|
+
/** Manifest from readChecksums() or createChecksums(). */
|
|
56
|
+
manifest: ChecksumsManifest;
|
|
57
|
+
/** Matcher from loadFileRules(). */
|
|
58
|
+
rules: FileRules;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Hash a file with every checksum algorithm in a single read.
|
|
62
|
+
*
|
|
63
|
+
* @param path File path.
|
|
64
|
+
* @return Hex digests keyed by algorithm.
|
|
65
|
+
*/
|
|
66
|
+
export declare function hashFile(path: string): Promise<FileHashes>;
|
|
67
|
+
/**
|
|
68
|
+
* Hash files one at a time, so that large plugins don't run out of file
|
|
69
|
+
* descriptors.
|
|
70
|
+
*
|
|
71
|
+
* @param sourceDir Directory the paths are relative to.
|
|
72
|
+
* @param files Relative file paths.
|
|
73
|
+
* @return Digests keyed by relative path, in the order of `files`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function hashFiles(sourceDir: string, files: string[]): Promise<Record<string, FileHashes>>;
|
|
76
|
+
/**
|
|
77
|
+
* Create a checksums manifest for the selected files of a directory.
|
|
78
|
+
*
|
|
79
|
+
* The manifest uses the format of the WordPress.org plugin checksums API
|
|
80
|
+
* (downloads.wordpress.org/plugin-checksums/{slug}/{version}.json), with
|
|
81
|
+
* paths relative to the plugin or theme folder:
|
|
82
|
+
*
|
|
83
|
+
* { "plugin": "my-plugin", "version": "1.2.0", "files": { "my-plugin.php": { "md5": "…", "sha256": "…" } } }
|
|
84
|
+
*
|
|
85
|
+
* Themes use a "theme" key instead of "plugin". The version is left out when
|
|
86
|
+
* it's unknown.
|
|
87
|
+
*
|
|
88
|
+
* @param input Directory, file rules and package details.
|
|
89
|
+
* @return Checksums manifest.
|
|
90
|
+
*/
|
|
91
|
+
export declare function createChecksums(input: CreateChecksumsInput): Promise<ChecksumsManifest>;
|
|
92
|
+
/**
|
|
93
|
+
* Write a checksums manifest as JSON.
|
|
94
|
+
*
|
|
95
|
+
* @param path Target file path.
|
|
96
|
+
* @param manifest Manifest from createChecksums().
|
|
97
|
+
* @param force Overwrite an existing file.
|
|
98
|
+
* @throws {ValidationError} If the file exists and force isn't set.
|
|
99
|
+
*/
|
|
100
|
+
export declare function writeChecksums(path: string, manifest: ChecksumsManifest, force?: boolean): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Read and validate a checksums manifest. Accepts manifests from
|
|
103
|
+
* createChecksums() and the WordPress.org checksums API, where a hash can
|
|
104
|
+
* also be a list of accepted values.
|
|
105
|
+
*
|
|
106
|
+
* @param path Manifest file path.
|
|
107
|
+
* @return Checksums manifest.
|
|
108
|
+
* @throws {ValidationError} If the file isn't a valid manifest.
|
|
109
|
+
*/
|
|
110
|
+
export declare function readChecksums(path: string): Promise<ChecksumsManifest>;
|
|
111
|
+
/**
|
|
112
|
+
* Parse and validate checksums manifest JSON.
|
|
113
|
+
*
|
|
114
|
+
* @param contents Manifest JSON.
|
|
115
|
+
* @param source Name of the manifest for error messages.
|
|
116
|
+
* @return Checksums manifest.
|
|
117
|
+
* @throws {ValidationError} If the JSON isn't a valid manifest.
|
|
118
|
+
*/
|
|
119
|
+
export declare function parseChecksums(contents: string, source?: string): ChecksumsManifest;
|
|
120
|
+
/**
|
|
121
|
+
* Compare expected file hashes with actual ones. A file matches when its
|
|
122
|
+
* SHA-256 is one of the expected values, or its MD5 for manifests without
|
|
123
|
+
* SHA-256.
|
|
124
|
+
*
|
|
125
|
+
* @param expected Expected hashes keyed by relative path.
|
|
126
|
+
* @param actual Actual hashes keyed by relative path.
|
|
127
|
+
* @return Sorted lists of matched, changed, missing and extra paths, and whether all match.
|
|
128
|
+
*/
|
|
129
|
+
export declare function compareChecksums(expected: Record<string, ExpectedHashes>, actual: Record<string, Partial<FileHashes>>): ChecksumsComparison;
|
|
130
|
+
/**
|
|
131
|
+
* Verify the files of a directory against a checksums manifest. Paths that
|
|
132
|
+
* the rules ignore are skipped on both sides, so files created after the
|
|
133
|
+
* install (such as caches) can be excluded.
|
|
134
|
+
*
|
|
135
|
+
* @param input Directory, manifest and file rules.
|
|
136
|
+
* @return Result from compareChecksums().
|
|
137
|
+
*/
|
|
138
|
+
export declare function verifyChecksums(input: VerifyChecksumsInput): Promise<ChecksumsComparison>;
|