WP Elevator Docs

Library API

Exit codes

CodeMeaning
0Success, dry run, no changes, or help
1Unexpected failure, such as a failed svn or git command
2Invalid options, failed release checks, an existing SVN tag, a Git tag that exists with another release, or files that don't match the checksums
3Missing SVN credentials, or credentials that SVN rejected

Library

import { Logger, SvnDeploy, resolveDeployOptions } from '@wpelevator/wp-release';

const options = resolveDeployOptions( {
	slug: 'my-plugin',
	sourceDir: 'dist',
	trunk: true,
	svnTag: '1.2.0',
	dryRun: true,
} );

const { committed, revision, version, changes } = await new SvnDeploy(
	options,
	new Logger()
).run();

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.

Git deploys:

import { GitDeploy, resolveGitDeployOptions } from '@wpelevator/wp-release';

const { revision, created, pushed } = await new GitDeploy(
	resolveGitDeployOptions( {
		sourceDir: 'packages/my-plugin',
		gitUrl: 'git@github.com:me/my-plugin.git',
		branch: 'main',
		tag: '1.2.0',
		splitPrefix: 'packages/my-plugin',
	} )
).run();

resolveGitDeployOptions() throws a ValidationError without a repository URL or with an invalid branch or tag. GitDeploy exposes createTree(), fetchPrevious(), createRelease(), isOutdated(), checkTag(), getRefsToPush() and push() separately, like SvnDeploy, and takes a git command runner from createGitRunner() as a dependency. The result has outdated when the branch wasn't moved for older source history, the refs to write in planned and the refs that were pushed in pushed, which is empty in a dry run. redactUrls() hides the credentials in URLs.

Changeset releases:

import { ChangesetRelease } from '@wpelevator/wp-release';

const release = new ChangesetRelease( { cwd: process.cwd() } );

const { missingChangesets } = await release.getStatus( { since: 'origin/main' } );
const { releases } = await release.version( { dryRun: true } );
const { tags } = await release.tag( { dryRun: true } );

ChangesetRelease runs the changeset-* commands: add(), getStatus(), version() and tag(), plus getPlan(), getPackages() and getConfiguredPackages(). Each call reads the configuration, packages and pending changesets again, so one instance can version and then tag. getConfiguredPackages() returns package directories with their resolved release group metadata; formatTag() and formatTemplate() apply the same placeholders as the release workflows. It takes a git runner from createGitRunner() as git, so that tests can replace it. Without a config file, it detects a single root project. Its optional singleProject settings accept versions (the existing version descriptors), versionFrom (the same selector as --version-from), baseBranch, remote, changesets, readmeChangelog and changelogInsertAfter; tagFormat remains a repository option. The functions addChangeset(), getChangesetStatus(), versionChangesets(), tagChangesets(), getChangesetReleasePlan(), getChangesetPackages() and getConfiguredPackages() do the same with the default runners, in one call.

Signing

The Ed25519 key handling and the WordPress, minisign and OpenSSH signature formats live in the @wpelevator/signatures package, which @wpelevator/wp-release depends on. It still re-exports the signing functions that it has always exported; the bytes-first functions and error guards are only in @wpelevator/signatures. The wp-release functions read the key from WP_RELEASE_SIGNING_KEY and report invalid input as a ValidationError with the original error as its cause; the @wpelevator/signatures functions use SIGNING_KEY by default and throw a SigningError.

ZIP archives

Reproducible archives and safe extraction live in the @wpelevator/zip package, which @wpelevator/wp-release depends on. It still exports createZip(), readZipEntries(), getZipEntryErrors(), extractZip(), ZIP_MTIME and ZIP_FILE_MODE with their sourceDir, files and zipPath inputs, and reports invalid input as a ValidationError with the original error as its cause. The zip package takes a list of { source, name } entries, where the source is a file path or bytes, and can create and read archives in memory. Entries have the fixed ZIP_MTIME, or the SOURCE_DATE_EPOCH time when that environment variable is set.

File selection

Gitignore-style file selection and directory syncing live in the @wpelevator/fileset package, which @wpelevator/wp-release depends on. It still exports resolveFileSelection(), loadFileRules(), selectFiles(), parseExclude(), syncDirectory() and copyFileIfChanged() with the same inputs, and reports invalid input as a ValidationError with the original error as its cause. The fileset package has no Git dependency: it takes a function that lists more files to exclude, listExcluded, and @wpelevator/wp-release passes one that reads the export-ignore attribute with Git when excludeGitattributes is set. The ignore file name (.distignore by default) and the directories that a sync leaves alone (.svn by default) are options of the fileset package.

Checksums

Hashing, the checksums manifest formats and their comparison live in the @wpelevator/checksums package, which @wpelevator/wp-release depends on. It still exports createChecksums(), writeChecksums(), readChecksums(), parseChecksums(), verifyChecksums(), compareChecksums(), hashFile(), hashFiles() and the wordpressCodec and sha256sumCodec codecs with the same inputs and output, and reports invalid input as a ValidationError with the original error as its cause. The checksums package takes a plain list of file paths instead of file rules, hashes bytes and streams with hashBytes() and hashStream(), and keeps the plugin or theme slug and version as generic metadata next to the files, which @wpelevator/wp-release fills in. Manifests are written byte for byte as before.

Translations

Extracting translatable strings from PHP, JavaScript and block.json sources, building catalogs, writing POT files, compiling PO files to MO, script JSON and .l10n.php, and auditing live in the @wpelevator/wp-gettext package, which @wpelevator/wp-release depends on. It still exports the same functions, like extractCatalog(), generatePackagePot(), serializePot(), parseTranslatedPo() and compileTranslations(), with the same inputs and output, and reports invalid input as a ValidationError with the original error as its cause. The wp-gettext package doesn't read plugin and theme headers: it takes the header details as a meta input, which @wpelevator/wp-release reads from the main file with its own package metadata reader. resolveI18nPackages(), which selects the configured packages, stays in @wpelevator/wp-release. Importing the main entry of @wpelevator/wp-gettext doesn't load the PHP and JavaScript parsers, which are in the /php and /javascript sub-paths.

Logging

Logger filters messages by level and passes them to a writer, like the transports of other Node.js loggers. The writer decides how messages look:

  • TextWriter prints plain text to stdout and stderr. It's the default.
  • GitHubActionsWriter prints workflow commands, so that notices, warnings and errors with a file are annotations, and appends summaries to GITHUB_STEP_SUMMARY. It's the CLI's github log format, and the GitHub Actions pass it to their logger. Its workspace and summaryPath options default to GITHUB_WORKSPACE and GITHUB_STEP_SUMMARY.
import { GitHubActionsWriter, Logger, logError } from '@wpelevator/wp-release';

const logger = new Logger( { writer: new GitHubActionsWriter(), verbose: true } );

logger.error( 'Version header is 1.2.0, expected 1.3.0.', {
	file: 'my-plugin.php',
	line: 4,
	title: 'Version mismatch',
} );
logger.summary( ( { workspace } ) => `### Checked ${ workspace }` );

A writer implements LogWriter with write(), startGroup(), endGroup() and summary(), so other CI systems can have their own format. summary() takes a function that builds the Markdown, which only writers with a summary call.

A ValidationError can list problems, each with a message and an optional file, line and title. logError( logger, error, title ) logs each problem as an error on its file and line and lists them in the summary under the title, or logs the message when there are none. VersionMismatchError and version-check report the version mismatches this way.

ZIP files and metadata:

import {
	createZip,
	findVersionMismatches,
	loadFileRules,
	readPackageMeta,
	resolveFileSelection,
	selectFiles,
} from '@wpelevator/wp-release';

const rules = await loadFileRules(
	resolveFileSelection( { cwd: process.cwd(), sourceDir: 'dist' } )
);
const meta = await readPackageMeta( { dir: 'dist', slug: 'my-plugin' } );

await createZip( {
	sourceDir: 'dist',
	files: await selectFiles( 'dist', rules ),
	zipPath: `my-plugin.${ meta.version }.zip`,
	rootFolder: 'my-plugin',
} );

const { mismatches } = await findVersionMismatches( {
	sourceDir: 'dist',
	meta,
	slug: 'my-plugin',
	constants: [ 'MY_PLUGIN_VERSION' ],
} );

Checksums:

import {
	createChecksums,
	loadFileRules,
	readChecksums,
	resolveFileSelection,
	verifyChecksums,
	writeChecksums,
} from '@wpelevator/wp-release';

const rules = await loadFileRules(
	resolveFileSelection( { cwd: process.cwd(), sourceDir: 'dist' } )
);
const manifest = await createChecksums( {
	sourceDir: 'dist',
	rules,
	slug: 'my-plugin',
	version: '1.2.0',
} );

await writeChecksums( 'my-plugin.1.2.0.checksums.json', manifest );

const { valid, changed, missing, extra } = await verifyChecksums( {
	sourceDir: 'wp-content/plugins/my-plugin',
	manifest: await readChecksums( 'my-plugin.1.2.0.checksums.json' ),
	rules: await loadFileRules( {} ),
} );

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.

Translations:

import { extractCatalog, auditCatalog, serializePot, generatePackagePot } from '@wpelevator/wp-release';

const { catalog, diagnostics, files } = await extractCatalog( {
	sourceDir: '/project/plugin',
	options: { domain: 'my-plugin' },
} );
const findings = auditCatalog( catalog );
const pot = serializePot( catalog );
const result = await generatePackagePot( { sourceDir: '/project/plugin', check: true } );

The parsers, catalog and compiler functions are provided by @wpelevator/wp-gettext. Parsers return messages and structured diagnostics with stable codes, severity and source-relative locations. Catalog messages retain IDs, contexts, plurals, translations, translator comments, flags and structured references. aggregateCatalog() selects/merges a domain, unions references/comments and diagnoses plural conflicts. resolveI18nPackages() is the separate config adapter; extraction and serialization primitives know nothing about the repository. Library functions do not log, parse process arguments or set exit codes. Extraction results report unique files consulted, including the main plugin/theme file used for headers and header strings even when source globs exclude it. parseTranslatedPo(), compileMo(), compileScriptTranslations(), compilePhpTranslations() and compileTranslations() expose the same typed operations to library callers.

On this page