@wpelevator/wp-release 0.2.0 → 0.3.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 CHANGED
@@ -153,6 +153,7 @@ wp-release version-check --constant MY_PLUGIN_VERSION --git-tag v1.2.0
153
153
  | Flag | Default | Writes |
154
154
  |---|---|---|
155
155
  | `--trunk` | off | Sync the full source directory to `trunk/` |
156
+ | `--readme-only` | off | Sync only the readme to `trunk/`, for updates between releases such as bumping `Tested up to`. Can't be combined with `--svn-tag`. |
156
157
  | `--svn-tag <version>` | off | Copy `trunk/` to `tags/<version>`. Without `--trunk`, the tag is copied from the repository's current `trunk/`. A leading `v` is stripped, so `v1.2.0` creates `tags/1.2.0`. |
157
158
  | `--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
159
  | `--no-assets` | | Skip the `--assets-dir` sync for one run, for example when a config always passes `--assets-dir`. |
@@ -163,6 +164,9 @@ Typical runs:
163
164
  # Deploy 1.2.0 to trunk and the tag, and update the assets
164
165
  wp-release deploy-svn dist --trunk --svn-tag 1.2.0 --assets-dir .wordpress-org
165
166
 
167
+ # Update only the readme between releases
168
+ wp-release deploy-svn dist --readme-only
169
+
166
170
  # Update only the assets
167
171
  wp-release deploy-svn --assets-dir .wordpress-org
168
172
 
@@ -173,7 +177,7 @@ wp-release deploy-svn --from-zip my-plugin.1.2.0.zip --trunk --svn-tag 1.2.0
173
177
  How a deploy works:
174
178
 
175
179
  1. The options are validated, including the slug, version and readme name, before they're used in URLs or paths.
176
- 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. A tag-only run doesn't check the files, since it tags whatever is in `trunk/`. Nothing is checked out when a check fails.
180
+ 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. A tag-only run doesn't check the files, since it tags whatever is in `trunk/`. `--readme-only` needs the readme. Nothing is checked out when a check fails.
177
181
  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.
178
182
  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`, from the working copy when trunk was written and from the repository otherwise.
179
183
  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.
@@ -185,8 +189,9 @@ Credentials are only needed for the commit, not for dry runs, no-op runs or `fil
185
189
 
186
190
  Validation:
187
191
 
188
- - Pre-release versions like `1.0.0-rc.1` are never tagged.
192
+ - `--readme-only` can't be combined with `--svn-tag`, since a tag is a release snapshot that a readme update never changes.
189
193
  - `--version` defaults to `--svn-tag`, then the `Version` header of the deployed files; when `--version` and `--svn-tag` are both set they must match.
194
+ - Pre-release versions like `1.0.0-rc.1` can be tagged, but syncing one to `trunk/` warns, since trunk is for the development version.
190
195
  - `--trunk` without `--svn-tag` works but warns, because trunk then differs from the stable tag that WordPress.org serves downloads from.
191
196
 
192
197
  ### Exit codes
@@ -304,8 +309,8 @@ The `wpelevator-source` export condition points at `src/index.ts`. The tests and
304
309
 
305
310
  | Tool | What it does | How `wp-release` differs |
306
311
  |---|---|---|
307
- | [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 tag-only run 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. |
308
- | [`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. Tagging is independent of syncing trunk, and assets are independent of both. |
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. |
309
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. |
310
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. |
311
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. |
@@ -41,6 +41,9 @@ declare const _default: {
41
41
  trunk: {
42
42
  type: "boolean";
43
43
  };
44
+ 'readme-only': {
45
+ type: "boolean";
46
+ };
44
47
  'svn-tag': {
45
48
  type: "string";
46
49
  };
@@ -19,6 +19,7 @@ export function getDeployInputFromArgs(input) {
19
19
  slug: getString(values, 'slug'),
20
20
  version: getString(values, 'version'),
21
21
  trunk: flag('trunk'),
22
+ readmeOnly: flag('readme-only'),
22
23
  svnTag: getString(values, 'svn-tag'),
23
24
  assets: getBoolean(values, 'no-assets') ? false : undefined,
24
25
  assetsDir: getString(values, 'assets-dir'),
@@ -42,6 +43,7 @@ export default {
42
43
 
43
44
  Writes (none by default, assets only with --assets-dir):
44
45
  --trunk Sync the full source directory to trunk/
46
+ --readme-only Sync only the readme to trunk/ (not with --svn-tag)
45
47
  --svn-tag <version> Copy trunk/ to tags/<version>, with or without
46
48
  --trunk. A leading "v" is stripped.
47
49
  --assets-dir <dir> Sync this directory with banners, icons and
@@ -70,11 +72,14 @@ or rejected.
70
72
  Examples:
71
73
  wp-release deploy-svn dist --trunk --svn-tag 1.2.0 --assets-dir .wordpress-org
72
74
  Deploy 1.2.0 to trunk and the tag, and update the assets
75
+ wp-release deploy-svn dist --readme-only
76
+ Update only the readme
73
77
  wp-release deploy-svn --assets-dir .wordpress-org
74
78
  Update only the assets`,
75
79
  options: {
76
80
  ...sourceDirArgs,
77
81
  trunk: { type: 'boolean' },
82
+ 'readme-only': { type: 'boolean' },
78
83
  'svn-tag': { type: 'string' },
79
84
  'assets-dir': { type: 'string' },
80
85
  'no-assets': { type: 'boolean' },
@@ -13,6 +13,8 @@ export interface DeployInput {
13
13
  version?: string;
14
14
  /** Sync the full source directory to trunk/. */
15
15
  trunk?: boolean;
16
+ /** Sync only the readme to trunk/. */
17
+ readmeOnly?: boolean;
16
18
  /** Copy trunk/ to tags/<svnTag>. */
17
19
  svnTag?: string;
18
20
  /** Sync the assets directory, defaults to on when assetsDir is set. */
@@ -50,6 +52,7 @@ export interface DeployOptions extends FileSelection {
50
52
  slug: string;
51
53
  version: string | null;
52
54
  trunk: boolean;
55
+ readmeOnly: boolean;
53
56
  svnTag: string | null;
54
57
  assets: boolean;
55
58
  assetsDir: string | null;
@@ -117,7 +120,7 @@ export declare function getDeployOptionErrors(options: DeployOptions): string[];
117
120
  */
118
121
  export declare function getDeployOptionWarnings(options: DeployOptions): string[];
119
122
  /**
120
- * Apply defaults to deploy options and validate them. Trunk and tag
123
+ * Apply defaults to deploy options and validate them. Trunk, readme and tag
121
124
  * writes happen only when requested, and assets are synced whenever an
122
125
  * assets directory is set, unless turned off. The version defaults to the
123
126
  * SVN tag; without either, SvnDeploy reads the Version header of the
@@ -62,9 +62,9 @@ function isValidFilename(name) {
62
62
  */
63
63
  export function getDeployOptionErrors(options) {
64
64
  const errors = [];
65
- const { svnTag, version, assets, assetsDir, slug } = options;
66
- if (svnTag && isPrerelease(svnTag)) {
67
- errors.push(`Pre-release version ${svnTag} can't be tagged on WordPress.org.`);
65
+ const { readmeOnly, svnTag, version, assets, assetsDir, slug } = options;
66
+ if (readmeOnly && svnTag) {
67
+ errors.push("--readme-only can't be combined with --svn-tag, since a tag is a release snapshot that a readme update never changes.");
68
68
  }
69
69
  if (svnTag && !isValidVersion(svnTag)) {
70
70
  errors.push(`SVN tag "${svnTag}" isn't a valid version, use only letters, digits, dots, dashes, plus signs and underscores.`);
@@ -109,7 +109,7 @@ export function getDeployOptionWarnings(options) {
109
109
  return warnings;
110
110
  }
111
111
  /**
112
- * Apply defaults to deploy options and validate them. Trunk and tag
112
+ * Apply defaults to deploy options and validate them. Trunk, readme and tag
113
113
  * writes happen only when requested, and assets are synced whenever an
114
114
  * assets directory is set, unless turned off. The version defaults to the
115
115
  * SVN tag; without either, SvnDeploy reads the Version header of the
@@ -130,6 +130,7 @@ export function resolveDeployOptions(input = {}) {
130
130
  slug,
131
131
  version: input.version ? normalizeTag(input.version) : svnTag,
132
132
  trunk: Boolean(input.trunk),
133
+ readmeOnly: Boolean(input.readmeOnly),
133
134
  assets: input.assets ?? Boolean(assetsDir),
134
135
  svnTag,
135
136
  sourceDir,
@@ -73,7 +73,8 @@ export declare class SvnDeploy {
73
73
  /**
74
74
  * Check the plugin files before anything is written. A trunk sync needs
75
75
  * a plugin with a Version header that matches the version, and with an
76
- * SVN tag also a readme Stable tag that matches.
76
+ * SVN tag also a readme Stable tag that matches. Readme-only updates need
77
+ * the readme.
77
78
  *
78
79
  * @throws {ValidationError} When the files or versions don't match.
79
80
  */
@@ -184,7 +185,7 @@ export declare class SvnDeploy {
184
185
  /**
185
186
  * Check if the deploy reads plugin files, which assets-only runs don't.
186
187
  *
187
- * @return Whether trunk is written.
188
+ * @return Whether trunk or the readme is written.
188
189
  */
189
190
  needsFiles(): boolean;
190
191
  /**
@@ -9,7 +9,7 @@ import { CredentialsError, ValidationError } from "./errors.js";
9
9
  import { loadFileRules, selectFiles } from "./file-selection.js";
10
10
  import { readPackageMeta } from "./package-meta.js";
11
11
  import { Svn } from "./svn.js";
12
- import { syncDirectory } from "./sync.js";
12
+ import { copyFileIfChanged, syncDirectory } from "./sync.js";
13
13
  import { extractZip } from "./unzip.js";
14
14
  import { findVersionMismatches } from "./version-check.js";
15
15
  /**
@@ -97,27 +97,35 @@ export class SvnDeploy {
97
97
  /**
98
98
  * Check the plugin files before anything is written. A trunk sync needs
99
99
  * a plugin with a Version header that matches the version, and with an
100
- * SVN tag also a readme Stable tag that matches.
100
+ * SVN tag also a readme Stable tag that matches. Readme-only updates need
101
+ * the readme.
101
102
  *
102
103
  * @throws {ValidationError} When the files or versions don't match.
103
104
  */
104
105
  async checkVersions() {
105
- const { trunk, svnTag, slug } = this.options;
106
+ const { trunk, readmeOnly, svnTag, slug, readme } = this.options;
106
107
  const sourceDir = this.filesDir ?? this.options.sourceDir;
107
- if (trunk) {
108
+ if (trunk || readmeOnly) {
108
109
  const meta = await this.readPluginMeta(sourceDir);
109
- const { version, mismatches } = await findVersionMismatches({
110
- sourceDir,
111
- meta,
112
- slug,
113
- version: this.options.version,
114
- stableTag: Boolean(svnTag),
115
- });
116
- this.version = version;
117
- if (mismatches.length) {
118
- throw new ValidationError(mismatches.map(({ message }) => message).join('\n'));
110
+ if (readmeOnly && !meta.readme) {
111
+ throw new ValidationError(`${readme} doesn't exist in ${sourceDir}.`);
112
+ }
113
+ if (trunk) {
114
+ const { version, mismatches } = await findVersionMismatches({
115
+ sourceDir,
116
+ meta,
117
+ slug,
118
+ version: this.options.version,
119
+ stableTag: Boolean(svnTag),
120
+ });
121
+ this.version = version;
122
+ if (mismatches.length) {
123
+ throw new ValidationError(mismatches
124
+ .map(({ message }) => message)
125
+ .join('\n'));
126
+ }
127
+ this.logger.info(`Deploying version ${version}.`);
119
128
  }
120
- this.logger.info(`Deploying version ${version}.`);
121
129
  }
122
130
  }
123
131
  /**
@@ -187,7 +195,7 @@ export class SvnDeploy {
187
195
  * additions, deletions and asset MIME types, then copy trunk to the tag.
188
196
  */
189
197
  async sync() {
190
- const { trunk, assets } = this.options;
198
+ const { trunk, readmeOnly, assets, readme } = this.options;
191
199
  const workingCopy = this.getWorkingCopy();
192
200
  const sourceDir = this.filesDir ?? this.options.sourceDir;
193
201
  if (trunk) {
@@ -197,6 +205,9 @@ export class SvnDeploy {
197
205
  targetDir: join(workingCopy, 'trunk'),
198
206
  });
199
207
  }
208
+ if (readmeOnly) {
209
+ await copyFileIfChanged(join(sourceDir, readme), join(workingCopy, 'trunk', readme));
210
+ }
200
211
  if (assets) {
201
212
  await this.syncAssets();
202
213
  }
@@ -367,7 +378,7 @@ export class SvnDeploy {
367
378
  * @return Planned changes, in order.
368
379
  */
369
380
  getPlan() {
370
- const { svnUrl, trunk, assets, assetsDir, force } = this.options;
381
+ const { svnUrl, trunk, readmeOnly, assets, assetsDir, readme, force } = this.options;
371
382
  const tagPath = this.getTagPath();
372
383
  const message = this.options.message ?? this.getDefaultMessage();
373
384
  const plan = [
@@ -377,6 +388,7 @@ export class SvnDeploy {
377
388
  tagPath && !force && `Stop if ${tagPath} already exists`,
378
389
  tagPath && force && `Replace ${tagPath} if it exists`,
379
390
  trunk && `Sync ${this.describeSource()} to trunk/`,
391
+ readmeOnly && `Copy ${readme} to trunk/`,
380
392
  tagPath && `Copy trunk/ to ${tagPath}`,
381
393
  assets && `Sync ${assetsDir} to assets/`,
382
394
  this.options.dryRun
@@ -392,9 +404,9 @@ export class SvnDeploy {
392
404
  * @return Relative paths with their SVN checkout depth.
393
405
  */
394
406
  getCheckoutPaths() {
395
- const { trunk, svnTag, assets } = this.options;
407
+ const { trunk, readmeOnly, svnTag, assets } = this.options;
396
408
  const paths = [];
397
- if (trunk) {
409
+ if (trunk || readmeOnly) {
398
410
  paths.push({ path: 'trunk', depth: 'infinity' });
399
411
  }
400
412
  if (svnTag) {
@@ -411,16 +423,16 @@ export class SvnDeploy {
411
423
  * @return Whether to check out and commit.
412
424
  */
413
425
  hasWrites() {
414
- const { trunk, svnTag, assets } = this.options;
415
- return Boolean(trunk || svnTag || assets);
426
+ const { trunk, readmeOnly, svnTag, assets } = this.options;
427
+ return Boolean(trunk || readmeOnly || svnTag || assets);
416
428
  }
417
429
  /**
418
430
  * Check if the deploy reads plugin files, which assets-only runs don't.
419
431
  *
420
- * @return Whether trunk is written.
432
+ * @return Whether trunk or the readme is written.
421
433
  */
422
434
  needsFiles() {
423
- return this.options.trunk;
435
+ return this.options.trunk || this.options.readmeOnly;
424
436
  }
425
437
  /**
426
438
  * Describe the files that are synced, including the ignore rules.
@@ -469,11 +481,15 @@ export class SvnDeploy {
469
481
  return null;
470
482
  }
471
483
  getDefaultMessage() {
472
- const { trunk, svnTag, assets } = this.options;
484
+ const { trunk, readmeOnly, svnTag, assets } = this.options;
473
485
  if (svnTag) {
474
486
  return `Release version ${svnTag}`;
475
487
  }
476
- const parts = [trunk && 'trunk', assets && 'assets'];
488
+ const parts = [
489
+ trunk && 'trunk',
490
+ readmeOnly && 'readme',
491
+ assets && 'assets',
492
+ ];
477
493
  return `Update ${parts.filter(Boolean).join(' and ')}`;
478
494
  }
479
495
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpelevator/wp-release",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Package, checksum, verify and version-check WordPress plugins and themes, and deploy plugins to WordPress.org SVN",
5
5
  "license": "MIT",
6
6
  "repository": {