@wpelevator/wp-release 0.8.0 → 0.9.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 +9 -7
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +2 -0
- package/dist/commands/args.d.ts +1 -0
- package/dist/commands/args.js +2 -0
- package/dist/commands/checksums.js +2 -0
- package/dist/commands/deploy-git.js +1 -0
- package/dist/commands/deploy-svn.js +1 -0
- package/dist/commands/keygen.d.ts +17 -0
- package/dist/commands/keygen.js +23 -0
- package/dist/commands/zip.js +2 -0
- package/dist/deploy-options.d.ts +2 -0
- package/dist/deploy-options.js +1 -0
- package/dist/file-selection.d.ts +6 -0
- package/dist/file-selection.js +101 -5
- package/dist/git-deploy.d.ts +2 -0
- package/dist/git-deploy.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/signing.d.ts +9 -0
- package/dist/signing.js +18 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Option names follow the existing community tools (10up WordPress.org actions, `w
|
|
|
14
14
|
npm install --save-dev @wpelevator/wp-release
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Requires Node.js 22.12 or later. `deploy-svn` also needs the `svn` command line client.
|
|
17
|
+
Requires Node.js 22.12 or later. `deploy-svn` also needs the `svn` command line client; the optional `--exclude-gitattributes` flag needs Git.
|
|
18
18
|
|
|
19
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
20
|
|
|
@@ -29,6 +29,7 @@ npx wp-release --help
|
|
|
29
29
|
| `wp-release zip [<source-dir>] [options]` | Create a ZIP archive with a `{slug}/` root folder, respecting `.distignore`. |
|
|
30
30
|
| `wp-release checksums [<source-dir>] [options]` | Write the MD5 and SHA-256 hashes of every distributed file to a checksums manifest. |
|
|
31
31
|
| `wp-release sign <file>... [options]` | Sign files with Ed25519, writing a detached `<file>.sig` next to each. |
|
|
32
|
+
| `wp-release keygen` | Generate a base64 Ed25519 private seed and public key for signing and verification. |
|
|
32
33
|
| `wp-release verify [<source-dir>] --checksums <file> [options]` | Check a plugin or theme directory, or a ZIP, against a checksums manifest. |
|
|
33
34
|
| `wp-release version-check [<source-dir>] [options]` | Check that the `Version` header, readme `Stable tag`, PHP constants and git tag agree. |
|
|
34
35
|
| `wp-release deploy-svn [<source-dir>] [options]` | Deploy a plugin to the WordPress.org plugin SVN repository. |
|
|
@@ -188,15 +189,16 @@ CLI JSON results use camelCase keys like `zipPath`, and GitHub Action outputs us
|
|
|
188
189
|
|
|
189
190
|
### File selection
|
|
190
191
|
|
|
191
|
-
`zip`, `checksums` and `deploy-
|
|
192
|
+
`zip`, `checksums`, `deploy-svn` and `deploy-git` select files the same way:
|
|
192
193
|
|
|
193
194
|
| Option | Description |
|
|
194
195
|
|---|---|
|
|
195
196
|
| `--distignore <file>` | Path to the ignore file. Defaults to `.distignore` in the source directory when present; an explicitly passed file must exist. |
|
|
196
197
|
| `--no-distignore` | Don't read any ignore file. |
|
|
197
198
|
| `--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. |
|
|
199
|
+
| `--exclude-gitattributes` | Also exclude paths marked `export-ignore` by Git attributes. This is opt-in and requires the source directory to be inside a Git repository. |
|
|
198
200
|
|
|
199
|
-
|
|
201
|
+
`.distignore` and `--exclude` use full gitignore syntax, including `!` negation. `--exclude-gitattributes` asks Git to evaluate `export-ignore` attributes, including nested `.gitattributes` files, then adds those paths to the exclusions. It is off by default, so releases don't unexpectedly depend on Git or change selection when a repository adds attributes. Git must be installed and the source directory must be inside a working tree; the flag is ignored by `deploy-svn --from-zip`, since the ZIP is already filtered. 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 deploys when the source directory is the repository root. The comma-separated `--exclude` list works like `--exclude` in `wp i18n make-pot`. Selected symbolic links are an error, since ZIP archives and WordPress.org SVN don't support them.
|
|
200
202
|
|
|
201
203
|
### Plugin and theme headers
|
|
202
204
|
|
|
@@ -304,7 +306,7 @@ WP_RELEASE_SIGNING_KEY=… wp-release sign my-plugin.1.2.0.zip my-plugin.1.2.0.c
|
|
|
304
306
|
- `--verbose` prints the public key of the signing key, which `--json` also returns as `publicKey`. Pass it to `verify --public-key` and to the plugin that checks the signatures.
|
|
305
307
|
- An existing `.sig` file isn't overwritten without `--force`.
|
|
306
308
|
|
|
307
|
-
|
|
309
|
+
Generate a key pair with `wp-release keygen`. It prints the private seed once as `WP_RELEASE_SIGNING_KEY=…` and the matching public key. Store the private seed securely; never commit it or put it in a workflow log. Don't run `keygen` in CI or with `--github-output`, since those outputs can expose the private seed. The public key can be distributed to verifiers.
|
|
308
310
|
|
|
309
311
|
### Version check
|
|
310
312
|
|
|
@@ -538,7 +540,7 @@ const { valid, changed, missing, extra } = await verifyChecksums( {
|
|
|
538
540
|
} );
|
|
539
541
|
```
|
|
540
542
|
|
|
541
|
-
`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`.
|
|
543
|
+
`loadFileRules()` reads the ignore file and `--exclude` patterns into a gitignore matcher, optionally adds Git `export-ignore` paths, 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`.
|
|
542
544
|
|
|
543
545
|
## Development
|
|
544
546
|
|
|
@@ -560,9 +562,9 @@ The `wpelevator-source` export condition points at `src/index.ts`. The tests and
|
|
|
560
562
|
|
|
561
563
|
| Tool | What it does | How `wp-release` differs |
|
|
562
564
|
|---|---|---|
|
|
563
|
-
| [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. |
|
|
565
|
+
| [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, opt-in Git `export-ignore` support and `--from-zip` to deploy the exact files of a ZIP. |
|
|
564
566
|
| [`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. |
|
|
565
567
|
| [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. |
|
|
566
568
|
| [`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. |
|
|
567
569
|
| [`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. |
|
|
568
|
-
| [`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`
|
|
570
|
+
| [`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` by default and can optionally honor Git `export-ignore` attributes, puts the version from the plugin headers in the filename and streams the archive instead of building it in memory. |
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ChecksumsResult } from './commands/checksums.ts';
|
|
2
2
|
import type { SignResult } from './commands/sign.ts';
|
|
3
|
+
import type { KeygenResult } from './commands/keygen.ts';
|
|
3
4
|
import type { ZipResult } from './commands/zip.ts';
|
|
4
5
|
import type { ChangesetVersionResult } from './commands/changeset-version.ts';
|
|
5
6
|
import { Logger } from './logger.ts';
|
|
@@ -44,6 +45,7 @@ export interface CommandResults {
|
|
|
44
45
|
zip: ZipResult;
|
|
45
46
|
checksums: ChecksumsResult;
|
|
46
47
|
sign: SignResult;
|
|
48
|
+
keygen: KeygenResult;
|
|
47
49
|
'changeset-add': import('./commands/changeset-add.ts').ChangesetAddResult;
|
|
48
50
|
'changeset-status': import('./commands/changeset-status.ts').ChangesetStatusResult;
|
|
49
51
|
'changeset-version': ChangesetVersionResult;
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import { parseArgs } from 'node:util';
|
|
|
3
3
|
import zip from "./commands/zip.js";
|
|
4
4
|
import checksums from "./commands/checksums.js";
|
|
5
5
|
import sign from "./commands/sign.js";
|
|
6
|
+
import keygen from "./commands/keygen.js";
|
|
6
7
|
import verify from "./commands/verify.js";
|
|
7
8
|
import versionCheck from "./commands/version-check.js";
|
|
8
9
|
import deploySvn from "./commands/deploy-svn.js";
|
|
@@ -23,6 +24,7 @@ export const commands = [
|
|
|
23
24
|
zip,
|
|
24
25
|
checksums,
|
|
25
26
|
sign,
|
|
27
|
+
keygen,
|
|
26
28
|
verify,
|
|
27
29
|
versionCheck,
|
|
28
30
|
deploySvn,
|
package/dist/commands/args.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare const fileSelectionArgs: ParseArgsOptionsConfig;
|
|
|
15
15
|
export declare function getFileSelectionInput(values: ParsedValues): {
|
|
16
16
|
distignore: string | false | undefined;
|
|
17
17
|
exclude: string[];
|
|
18
|
+
excludeGitattributes: boolean;
|
|
18
19
|
};
|
|
19
20
|
/**
|
|
20
21
|
* Check that a command got no more positional arguments than it accepts.
|
package/dist/commands/args.js
CHANGED
|
@@ -11,6 +11,7 @@ export const fileSelectionArgs = {
|
|
|
11
11
|
distignore: { type: 'string' },
|
|
12
12
|
'no-distignore': { type: 'boolean' },
|
|
13
13
|
exclude: { type: 'string', multiple: true },
|
|
14
|
+
'exclude-gitattributes': { type: 'boolean' },
|
|
14
15
|
};
|
|
15
16
|
/**
|
|
16
17
|
* Map the parsed file selection flags to resolveFileSelection() input.
|
|
@@ -24,6 +25,7 @@ export function getFileSelectionInput(values) {
|
|
|
24
25
|
? false
|
|
25
26
|
: getString(values, 'distignore'),
|
|
26
27
|
exclude: getStrings(values, 'exclude'),
|
|
28
|
+
excludeGitattributes: getBoolean(values, 'exclude-gitattributes'),
|
|
27
29
|
};
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
@@ -28,6 +28,8 @@ Options:
|
|
|
28
28
|
--no-distignore Don't read any ignore file
|
|
29
29
|
--exclude <patterns> Extra gitignore-style patterns, comma-separated or
|
|
30
30
|
repeated, like build/,assets/*,images/**/*
|
|
31
|
+
--exclude-gitattributes Also exclude paths marked export-ignore in Git
|
|
32
|
+
attributes (requires a Git working tree)
|
|
31
33
|
--force Overwrite an existing --output file
|
|
32
34
|
|
|
33
35
|
The manifest uses the WordPress.org plugin checksums format, with paths
|
|
@@ -46,6 +46,7 @@ Options:
|
|
|
46
46
|
--distignore <file> Ignore file (default: <source-dir>/.distignore when present)
|
|
47
47
|
--no-distignore Don't read any ignore file
|
|
48
48
|
--exclude <patterns> Extra gitignore-style patterns, comma-separated or repeated
|
|
49
|
+
--exclude-gitattributes Also exclude paths marked export-ignore in Git attributes
|
|
49
50
|
--message <text> Release commit message
|
|
50
51
|
--dry-run Run everything except the push
|
|
51
52
|
|
|
@@ -60,6 +60,7 @@ Options:
|
|
|
60
60
|
--distignore <file> Ignore file (default: <source-dir>/.distignore when present)
|
|
61
61
|
--no-distignore Don't read any ignore file
|
|
62
62
|
--exclude <patterns> Extra gitignore-style patterns, comma-separated or repeated
|
|
63
|
+
--exclude-gitattributes Also exclude paths marked export-ignore in Git attributes
|
|
63
64
|
--svn-url <url> Repository URL with {slug}
|
|
64
65
|
(default: https://plugins.svn.wordpress.org/{slug}/)
|
|
65
66
|
--svn-username <name> SVN username (env: SVN_USERNAME, password: SVN_PASSWORD)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CommandOutcome } from './command.ts';
|
|
2
|
+
/** Generated Ed25519 signing keys. */
|
|
3
|
+
export interface KeygenResult {
|
|
4
|
+
/** Base64 Ed25519 seed for WP_RELEASE_SIGNING_KEY. */
|
|
5
|
+
privateKey: string;
|
|
6
|
+
/** Base64 Ed25519 public key for verification. */
|
|
7
|
+
publicKey: string;
|
|
8
|
+
}
|
|
9
|
+
declare const _default: {
|
|
10
|
+
name: string;
|
|
11
|
+
usage: string;
|
|
12
|
+
summary: string;
|
|
13
|
+
help: string;
|
|
14
|
+
options: {};
|
|
15
|
+
run({ values, logger }: import("./command.ts").CommandContext): Promise<CommandOutcome<KeygenResult>>;
|
|
16
|
+
};
|
|
17
|
+
export default _default;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ValidationError } from "../errors.js";
|
|
2
|
+
import { generateSigningKeyPair } from "../signing.js";
|
|
3
|
+
import { getBoolean } from "./command.js";
|
|
4
|
+
export default {
|
|
5
|
+
name: 'keygen',
|
|
6
|
+
usage: 'keygen',
|
|
7
|
+
summary: 'Generate an Ed25519 signing key pair.',
|
|
8
|
+
help: `Generate a signing key pair for "wp-release sign" and "wp-release verify".
|
|
9
|
+
|
|
10
|
+
The private seed is printed once as WP_RELEASE_SIGNING_KEY. Store it securely
|
|
11
|
+
and never commit it or share it. The public key can be distributed to verifiers.`,
|
|
12
|
+
options: {},
|
|
13
|
+
async run({ values, logger }) {
|
|
14
|
+
if (getBoolean(values, 'github-output')) {
|
|
15
|
+
throw new ValidationError('keygen cannot be used with --github-output, which would store the private seed in a workflow output.');
|
|
16
|
+
}
|
|
17
|
+
const result = generateSigningKeyPair();
|
|
18
|
+
logger.info(`WP_RELEASE_SIGNING_KEY=${result.privateKey}`);
|
|
19
|
+
logger.info(`Public key: ${result.publicKey}`);
|
|
20
|
+
logger.info('Store the private seed securely; it cannot be recovered from the public key.');
|
|
21
|
+
return { exitCode: 0, result };
|
|
22
|
+
},
|
|
23
|
+
};
|
package/dist/commands/zip.js
CHANGED
|
@@ -56,6 +56,8 @@ Options:
|
|
|
56
56
|
--no-distignore Don't read any ignore file
|
|
57
57
|
--exclude <patterns> Extra gitignore-style patterns, comma-separated or
|
|
58
58
|
repeated, like build/,assets/*,images/**/*
|
|
59
|
+
--exclude-gitattributes Also exclude paths marked export-ignore in Git
|
|
60
|
+
attributes (requires a Git working tree)
|
|
59
61
|
--checksums Also write the checksums manifest of the archived
|
|
60
62
|
files next to the ZIP, with .checksums.json
|
|
61
63
|
instead of .zip
|
package/dist/deploy-options.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface DeployInput {
|
|
|
27
27
|
distignore?: string | false;
|
|
28
28
|
/** Extra gitignore-style patterns. */
|
|
29
29
|
exclude?: string | string[];
|
|
30
|
+
/** Also exclude paths marked export-ignore by Git attributes. */
|
|
31
|
+
excludeGitattributes?: boolean;
|
|
30
32
|
/** Readme file name. */
|
|
31
33
|
readme?: string;
|
|
32
34
|
/** ZIP archive to deploy instead of sourceDir. */
|
package/dist/deploy-options.js
CHANGED
package/dist/file-selection.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface FileSelectionInput {
|
|
|
21
21
|
distignore?: string | false;
|
|
22
22
|
/** Extra gitignore-style patterns, comma or newline separated. */
|
|
23
23
|
exclude?: string | string[];
|
|
24
|
+
/** Also exclude paths marked export-ignore by Git attributes. */
|
|
25
|
+
excludeGitattributes?: boolean;
|
|
24
26
|
}
|
|
25
27
|
export interface FileSelection {
|
|
26
28
|
/** Ignore file path, or null when disabled. */
|
|
@@ -29,6 +31,10 @@ export interface FileSelection {
|
|
|
29
31
|
distignoreRequired: boolean;
|
|
30
32
|
/** Extra gitignore-style patterns. */
|
|
31
33
|
exclude: string[];
|
|
34
|
+
/** Source directory, needed to resolve Git attributes. */
|
|
35
|
+
sourceDir?: string;
|
|
36
|
+
/** Also exclude paths marked export-ignore by Git attributes. */
|
|
37
|
+
excludeGitattributes?: boolean;
|
|
32
38
|
}
|
|
33
39
|
/**
|
|
34
40
|
* Parse exclude patterns from a comma or newline separated string, an array
|
package/dist/file-selection.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
-
import { join, posix, resolve } from 'node:path';
|
|
1
|
+
import { realpath, readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { isAbsolute, join, posix, relative, resolve } from 'node:path';
|
|
3
3
|
import ignore from 'ignore';
|
|
4
4
|
import { ValidationError } from "./errors.js";
|
|
5
|
+
import { createGitRunner } from "./git.js";
|
|
5
6
|
export const DISTIGNORE_FILENAME = '.distignore';
|
|
6
7
|
/**
|
|
7
8
|
* Version control metadata that is never released, at any depth. Without
|
|
@@ -35,11 +36,13 @@ export function parseExclude(value) {
|
|
|
35
36
|
* @return Ignore file path (or null), whether it's required, and the patterns.
|
|
36
37
|
*/
|
|
37
38
|
export function resolveFileSelection(input) {
|
|
38
|
-
const { cwd, sourceDir, distignore, exclude } = input;
|
|
39
|
+
const { cwd, sourceDir, distignore, exclude, excludeGitattributes = false, } = input;
|
|
39
40
|
const selection = {
|
|
40
41
|
distignore: join(sourceDir, DISTIGNORE_FILENAME),
|
|
41
42
|
distignoreRequired: false,
|
|
42
43
|
exclude: parseExclude(exclude),
|
|
44
|
+
sourceDir,
|
|
45
|
+
excludeGitattributes,
|
|
43
46
|
};
|
|
44
47
|
if (false === distignore) {
|
|
45
48
|
selection.distignore = null;
|
|
@@ -61,8 +64,101 @@ export function resolveFileSelection(input) {
|
|
|
61
64
|
*/
|
|
62
65
|
export async function loadFileRules(selection) {
|
|
63
66
|
const { distignore = null, distignoreRequired = false, exclude = [], } = selection;
|
|
64
|
-
const rules =
|
|
65
|
-
|
|
67
|
+
const rules = ignore()
|
|
68
|
+
.add(await readIgnoreFile(distignore, distignoreRequired))
|
|
69
|
+
.add(exclude);
|
|
70
|
+
if (selection.excludeGitattributes) {
|
|
71
|
+
if (!selection.sourceDir) {
|
|
72
|
+
throw new ValidationError('--exclude-gitattributes requires a source directory.');
|
|
73
|
+
}
|
|
74
|
+
const excluded = await getExportIgnoredPaths(selection.sourceDir, rules);
|
|
75
|
+
rules.add(excluded.map((path) => `/${escapePattern(path)}`));
|
|
76
|
+
}
|
|
77
|
+
return rules.add(ALWAYS_EXCLUDED);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Read Git's export-ignore attribute for the source files selected by the
|
|
81
|
+
* other ignore rules. Git itself evaluates .gitattributes, including nested
|
|
82
|
+
* files and its pattern syntax.
|
|
83
|
+
*
|
|
84
|
+
* @param sourceDir Directory whose files are being selected.
|
|
85
|
+
* @param rules Rules already loaded from .distignore and --exclude.
|
|
86
|
+
* @return Source-relative paths marked export-ignore.
|
|
87
|
+
*/
|
|
88
|
+
async function getExportIgnoredPaths(sourceDir, rules) {
|
|
89
|
+
const runner = createGitRunner();
|
|
90
|
+
let root;
|
|
91
|
+
try {
|
|
92
|
+
({ stdout: root } = await runner({
|
|
93
|
+
args: ['rev-parse', '--show-toplevel'],
|
|
94
|
+
cwd: sourceDir,
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw new ValidationError('--exclude-gitattributes requires the source directory to be inside a Git repository.');
|
|
99
|
+
}
|
|
100
|
+
root = await realpath(root.trim());
|
|
101
|
+
const sourceFromRoot = relative(root, await realpath(sourceDir));
|
|
102
|
+
if (isAbsolute(sourceFromRoot) ||
|
|
103
|
+
sourceFromRoot === '..' ||
|
|
104
|
+
sourceFromRoot.startsWith(`..${posix.sep}`)) {
|
|
105
|
+
throw new ValidationError('--exclude-gitattributes requires the source directory to be inside a Git repository.');
|
|
106
|
+
}
|
|
107
|
+
const candidates = await findAttributeCandidates(sourceDir, '', rules);
|
|
108
|
+
if (!candidates.length) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const pathsFromRoot = candidates.map((path) => posix.join(sourceFromRoot.split('\\').join('/'), path));
|
|
112
|
+
const { stdout } = await runner({
|
|
113
|
+
args: ['check-attr', '-z', '--stdin', 'export-ignore'],
|
|
114
|
+
cwd: root,
|
|
115
|
+
input: pathsFromRoot.map((path) => `${path}\0`).join(''),
|
|
116
|
+
});
|
|
117
|
+
const fields = stdout.split('\0');
|
|
118
|
+
const ignored = [];
|
|
119
|
+
for (let index = 0; index + 2 < fields.length; index += 3) {
|
|
120
|
+
const candidate = candidates[index / 3];
|
|
121
|
+
if (fields[index + 2] === 'set' && candidate) {
|
|
122
|
+
ignored.push(candidate);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return ignored;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* List regular files and symbolic links not already excluded by ignore rules.
|
|
129
|
+
* Symbolic links are included here so Git can exclude them before selectFiles
|
|
130
|
+
* applies its unsupported-link validation.
|
|
131
|
+
*
|
|
132
|
+
* @param sourceDir Source directory.
|
|
133
|
+
* @param dir Relative directory being traversed.
|
|
134
|
+
* @param rules Ignore rules.
|
|
135
|
+
* @return Relative paths to candidate files.
|
|
136
|
+
*/
|
|
137
|
+
async function findAttributeCandidates(sourceDir, dir, rules) {
|
|
138
|
+
const entries = await readdir(join(sourceDir, dir), {
|
|
139
|
+
withFileTypes: true,
|
|
140
|
+
});
|
|
141
|
+
const paths = await Promise.all(entries.map(async (entry) => {
|
|
142
|
+
const path = posix.join(dir, entry.name);
|
|
143
|
+
if (entry.isDirectory() && !rules.ignores(`${path}/`)) {
|
|
144
|
+
return findAttributeCandidates(sourceDir, path, rules);
|
|
145
|
+
}
|
|
146
|
+
if ((entry.isFile() || entry.isSymbolicLink()) &&
|
|
147
|
+
!rules.ignores(path)) {
|
|
148
|
+
return [path];
|
|
149
|
+
}
|
|
150
|
+
return [];
|
|
151
|
+
}));
|
|
152
|
+
return paths.flat();
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Escape a path for an anchored gitignore pattern.
|
|
156
|
+
*
|
|
157
|
+
* @param path Relative path.
|
|
158
|
+
* @return Pattern-safe path.
|
|
159
|
+
*/
|
|
160
|
+
function escapePattern(path) {
|
|
161
|
+
return path.replace(/[\\*?[\\]!#]/g, '\\$&');
|
|
66
162
|
}
|
|
67
163
|
/**
|
|
68
164
|
* Read the ignore file, or nothing when there's no file to read.
|
package/dist/git-deploy.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface GitDeployInput {
|
|
|
24
24
|
distignore?: string | false;
|
|
25
25
|
/** Extra gitignore-style patterns. */
|
|
26
26
|
exclude?: string | string[];
|
|
27
|
+
/** Also exclude paths marked export-ignore by Git attributes. */
|
|
28
|
+
excludeGitattributes?: boolean;
|
|
27
29
|
/** Commit message of the release. */
|
|
28
30
|
message?: string;
|
|
29
31
|
/** Run everything except the push. */
|
package/dist/git-deploy.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export { DISTIGNORE_FILENAME, parseExclude, resolveFileSelection, loadFileRules,
|
|
|
9
9
|
export { FILENAME_FORMAT, formatFilename } from './filename.ts';
|
|
10
10
|
export { CHECKSUMS_EXTENSION, CHECKSUM_ALGORITHMS, hashFile, hashFiles, createChecksums, writeChecksums, readChecksums, parseChecksums, compareChecksums, verifyChecksums, } from './checksums.ts';
|
|
11
11
|
export { HEADER_BYTES, PLUGIN_HEADERS, THEME_HEADERS, README_HEADERS, README_FILENAMES, parseFileHeaders, parseReadmeHeaders, readFileStart, findFileHeaderLine, findReadmeHeaderLine, findMainPluginFile, readPackageMeta, } from './package-meta.ts';
|
|
12
|
-
export { SIGNATURE_EXTENSION, SIGNING_KEY_ENV, parseSigningKey, parsePublicKey, getPublicKey, getFileDigest, signFile, writeSignature, verifyFileSignature, } from './signing.ts';
|
|
12
|
+
export { SIGNATURE_EXTENSION, SIGNING_KEY_ENV, parseSigningKey, parsePublicKey, generateSigningKeyPair, getPublicKey, getFileDigest, signFile, writeSignature, verifyFileSignature, } from './signing.ts';
|
|
13
13
|
export { ZIP_MTIME, ZIP_FILE_MODE, createZip } from './zip.ts';
|
|
14
14
|
export { isUrl, downloadFile } from './download.ts';
|
|
15
15
|
export { readZipEntries, getZipEntryErrors, extractZip } from './unzip.ts';
|
|
@@ -26,6 +26,7 @@ export { getGitDeployInputFromArgs } from './commands/deploy-git.ts';
|
|
|
26
26
|
export type { CliIo, CommandResults } from './cli.ts';
|
|
27
27
|
export type { ChecksumsResult } from './commands/checksums.ts';
|
|
28
28
|
export type { SignResult } from './commands/sign.ts';
|
|
29
|
+
export type { KeygenResult } from './commands/keygen.ts';
|
|
29
30
|
export type { ZipResult } from './commands/zip.ts';
|
|
30
31
|
export type { Command, CommandContext, CommandOutcome, ParsedValues, } from './commands/command.ts';
|
|
31
32
|
export type { DeployArgsInput } from './commands/deploy-svn.ts';
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ export { DISTIGNORE_FILENAME, parseExclude, resolveFileSelection, loadFileRules,
|
|
|
9
9
|
export { FILENAME_FORMAT, formatFilename } from "./filename.js";
|
|
10
10
|
export { CHECKSUMS_EXTENSION, CHECKSUM_ALGORITHMS, hashFile, hashFiles, createChecksums, writeChecksums, readChecksums, parseChecksums, compareChecksums, verifyChecksums, } from "./checksums.js";
|
|
11
11
|
export { HEADER_BYTES, PLUGIN_HEADERS, THEME_HEADERS, README_HEADERS, README_FILENAMES, parseFileHeaders, parseReadmeHeaders, readFileStart, findFileHeaderLine, findReadmeHeaderLine, findMainPluginFile, readPackageMeta, } from "./package-meta.js";
|
|
12
|
-
export { SIGNATURE_EXTENSION, SIGNING_KEY_ENV, parseSigningKey, parsePublicKey, getPublicKey, getFileDigest, signFile, writeSignature, verifyFileSignature, } from "./signing.js";
|
|
12
|
+
export { SIGNATURE_EXTENSION, SIGNING_KEY_ENV, parseSigningKey, parsePublicKey, generateSigningKeyPair, getPublicKey, getFileDigest, signFile, writeSignature, verifyFileSignature, } from "./signing.js";
|
|
13
13
|
export { ZIP_MTIME, ZIP_FILE_MODE, createZip } from "./zip.js";
|
|
14
14
|
export { isUrl, downloadFile } from "./download.js";
|
|
15
15
|
export { readZipEntries, getZipEntryErrors, extractZip } from "./unzip.js";
|
package/dist/signing.d.ts
CHANGED
|
@@ -4,6 +4,15 @@ export declare const SIGNATURE_EXTENSION = ".sig";
|
|
|
4
4
|
* Environment variable with the base64 Ed25519 signing key.
|
|
5
5
|
*/
|
|
6
6
|
export declare const SIGNING_KEY_ENV = "WP_RELEASE_SIGNING_KEY";
|
|
7
|
+
/**
|
|
8
|
+
* Generate an Ed25519 key pair in the base64 formats used by wp-release.
|
|
9
|
+
*
|
|
10
|
+
* @return Private seed for WP_RELEASE_SIGNING_KEY and public verification key.
|
|
11
|
+
*/
|
|
12
|
+
export declare function generateSigningKeyPair(): {
|
|
13
|
+
privateKey: string;
|
|
14
|
+
publicKey: string;
|
|
15
|
+
};
|
|
7
16
|
/**
|
|
8
17
|
* Get an Ed25519 private key from the base64 format that sodium and the
|
|
9
18
|
* update-client signing_key setting use: a 32-byte seed or a 64-byte
|
package/dist/signing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash, createPrivateKey, createPublicKey, sign, verify, } from 'node:crypto';
|
|
1
|
+
import { createHash, createPrivateKey, generateKeyPairSync, createPublicKey, sign, verify, } from 'node:crypto';
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
3
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { ValidationError } from "./errors.js";
|
|
@@ -14,6 +14,23 @@ const PUBLIC_KEY_BYTES = 32;
|
|
|
14
14
|
const PKCS8_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
|
|
15
15
|
// DER prefix of a SPKI Ed25519 public key, followed by the 32-byte key.
|
|
16
16
|
const SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
|
|
17
|
+
/**
|
|
18
|
+
* Generate an Ed25519 key pair in the base64 formats used by wp-release.
|
|
19
|
+
*
|
|
20
|
+
* @return Private seed for WP_RELEASE_SIGNING_KEY and public verification key.
|
|
21
|
+
*/
|
|
22
|
+
export function generateSigningKeyPair() {
|
|
23
|
+
const { privateKey, publicKey } = generateKeyPairSync('ed25519');
|
|
24
|
+
const privateJwk = privateKey.export({ format: 'jwk' });
|
|
25
|
+
const publicJwk = publicKey.export({ format: 'jwk' });
|
|
26
|
+
if (!privateJwk.d || !publicJwk.x) {
|
|
27
|
+
throw new ValidationError('Could not export the generated Ed25519 key pair.');
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
privateKey: Buffer.from(privateJwk.d, 'base64url').toString('base64'),
|
|
31
|
+
publicKey: Buffer.from(publicJwk.x, 'base64url').toString('base64'),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
17
34
|
/**
|
|
18
35
|
* Decode a base64 key, accepting the base64url alphabet and whitespace.
|
|
19
36
|
*
|
package/package.json
CHANGED