@wpelevator/wp-release 0.12.0 → 0.13.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 +90 -6
- package/dist/changeset-config.d.ts +86 -2
- package/dist/changeset-config.js +101 -6
- package/dist/changeset-dependents.d.ts +19 -8
- package/dist/changeset-dependents.js +96 -34
- package/dist/changeset-files.d.ts +6 -5
- package/dist/changeset-files.js +7 -5
- package/dist/changeset-packages.d.ts +45 -8
- package/dist/changeset-packages.js +73 -14
- package/dist/changeset-prompt.d.ts +119 -0
- package/dist/changeset-prompt.js +203 -0
- package/dist/changeset-release.d.ts +38 -3
- package/dist/changeset-release.js +88 -11
- package/dist/changesets.d.ts +6 -2
- package/dist/changesets.js +18 -12
- package/dist/cli.d.ts +7 -1
- package/dist/cli.js +18 -5
- package/dist/commands/changeset-add.d.ts +5 -2
- package/dist/commands/changeset-add.js +108 -9
- package/dist/commands/changeset-args.d.ts +24 -5
- package/dist/commands/changeset-args.js +57 -14
- package/dist/commands/changeset-status.d.ts +2 -0
- package/dist/commands/changeset-tag.js +2 -1
- package/dist/commands/changeset-version.d.ts +6 -0
- package/dist/commands/changeset-version.js +15 -3
- package/dist/commands/command.d.ts +3 -0
- package/dist/commands/deploy-git.d.ts +24 -4
- package/dist/commands/deploy-git.js +56 -3
- package/dist/commands/deploy-svn.d.ts +24 -4
- package/dist/commands/deploy-svn.js +56 -2
- package/dist/commands/package-deploys.d.ts +78 -0
- package/dist/commands/package-deploys.js +108 -0
- package/dist/git-deploy.d.ts +32 -0
- package/dist/git-deploy.js +57 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +2 -1
- package/dist/package-deploy.d.ts +105 -0
- package/dist/package-deploy.js +288 -0
- package/dist/release-repository.d.ts +14 -2
- package/dist/release-repository.js +46 -7
- package/dist/release-tags.d.ts +3 -1
- package/dist/release-tags.js +5 -2
- package/dist/tag-format.d.ts +3 -3
- package/dist/tag-format.js +1 -1
- package/dist/version-files.d.ts +2 -1
- package/dist/version-files.js +12 -7
- package/dist/versions.d.ts +28 -3
- package/dist/versions.js +84 -9
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ValidationError } from "../errors.js";
|
|
2
|
+
import { getDeployPlan } from "../package-deploy.js";
|
|
3
|
+
import { logError } from "../report.js";
|
|
4
|
+
import { getBoolean, getString, getStrings } from "./command.js";
|
|
5
|
+
/**
|
|
6
|
+
* Options of the deploy commands that deploy the configured packages of a
|
|
7
|
+
* ref, in node:util parseArgs() format.
|
|
8
|
+
*/
|
|
9
|
+
export const packageDeployArgs = {
|
|
10
|
+
'release-ref': { type: 'string' },
|
|
11
|
+
package: { type: 'string', multiple: true },
|
|
12
|
+
list: { type: 'boolean' },
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Get the ref and the packages to deploy from the config, failing for the
|
|
16
|
+
* options that set a single deploy target instead.
|
|
17
|
+
*
|
|
18
|
+
* @param values Parsed CLI values.
|
|
19
|
+
* @param positionals Positional arguments.
|
|
20
|
+
* @param targetFlags Options of a single deploy target.
|
|
21
|
+
* @return The ref and the selected packages.
|
|
22
|
+
* @throws {ValidationError} For --package without --release-ref, or a single target option with it.
|
|
23
|
+
*/
|
|
24
|
+
export function getPackageDeployArgs(values, positionals, targetFlags) {
|
|
25
|
+
const ref = getString(values, 'release-ref')?.trim();
|
|
26
|
+
const packages = getStrings(values, 'package');
|
|
27
|
+
const list = getBoolean(values, 'list');
|
|
28
|
+
const conflicts = targetFlags.filter((flag) => undefined !== getString(values, flag) ||
|
|
29
|
+
getBoolean(values, flag) ||
|
|
30
|
+
getStrings(values, flag).length);
|
|
31
|
+
if ('' === ref) {
|
|
32
|
+
throw new ValidationError('--release-ref <ref> must not be empty.');
|
|
33
|
+
}
|
|
34
|
+
if ((packages.length || list) && !ref) {
|
|
35
|
+
throw new ValidationError(`${list ? '--list' : '--package'} requires --release-ref <ref>.`);
|
|
36
|
+
}
|
|
37
|
+
if (ref && (positionals.length || conflicts.length)) {
|
|
38
|
+
throw new ValidationError(`--release-ref deploys the configured packages and targets, so it can't be combined with ${positionals.length
|
|
39
|
+
? 'a source directory'
|
|
40
|
+
: conflicts.map((flag) => `--${flag}`).join(', ')}.`);
|
|
41
|
+
}
|
|
42
|
+
return { ...(ref ? { ref } : {}), packages, list };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Run the deploys of the configured packages one at a time, in one log group
|
|
46
|
+
* for each package, continuing after a failed deploy so that one package or
|
|
47
|
+
* target doesn't block the others.
|
|
48
|
+
*
|
|
49
|
+
* @param deploys Deploys from getGitDeploys() or getSvnDeploys().
|
|
50
|
+
* @param deploy Runs one deploy.
|
|
51
|
+
* @param logger Logger.
|
|
52
|
+
* @param ref Deployed ref, for the log.
|
|
53
|
+
* @return Exit code 1 when a deploy failed, and the result of each deploy.
|
|
54
|
+
*/
|
|
55
|
+
export async function runPackageDeploys(deploys, deploy, logger, ref) {
|
|
56
|
+
const outcomes = [];
|
|
57
|
+
const names = [
|
|
58
|
+
...new Set(deploys.map((item) => item.package.name)),
|
|
59
|
+
];
|
|
60
|
+
if (!deploys.length) {
|
|
61
|
+
logger.info(`No configured package deploys ${ref}.`);
|
|
62
|
+
}
|
|
63
|
+
for (const name of names) {
|
|
64
|
+
const items = deploys.filter((item) => item.package.name === name);
|
|
65
|
+
const release = items[0]?.release ?? {};
|
|
66
|
+
await logger.group(`${name} ${release.version ?? release.branch ?? ''}`.trim(), async () => {
|
|
67
|
+
for (const item of items) {
|
|
68
|
+
outcomes.push(await runDeploy(item, deploy, logger));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
exitCode: outcomes.some((outcome) => outcome.error) ? 1 : 0,
|
|
74
|
+
result: { deploys: outcomes },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function runDeploy(item, deploy, logger) {
|
|
78
|
+
const outcome = {
|
|
79
|
+
package: item.package.name,
|
|
80
|
+
label: item.label,
|
|
81
|
+
...item.release,
|
|
82
|
+
};
|
|
83
|
+
try {
|
|
84
|
+
outcome.result = await deploy(item.input);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
outcome.error =
|
|
88
|
+
error instanceof Error ? error.message : String(error);
|
|
89
|
+
logError(logger, error, `Deploy of ${item.label}`);
|
|
90
|
+
}
|
|
91
|
+
return outcome;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* List the deploys of the configured packages without running them, one line
|
|
95
|
+
* for each deploy, with the plan as the result for --json and --github-output.
|
|
96
|
+
*
|
|
97
|
+
* @param deploys Deploys from getGitDeploys() or getSvnDeploys().
|
|
98
|
+
* @param logger Logger.
|
|
99
|
+
* @param ref Deployed ref, for the log.
|
|
100
|
+
* @return Exit code 0 and the deploys of each package.
|
|
101
|
+
*/
|
|
102
|
+
export function listPackageDeploys(deploys, logger, ref) {
|
|
103
|
+
if (!deploys.length) {
|
|
104
|
+
logger.info(`No configured package deploys ${ref}.`);
|
|
105
|
+
}
|
|
106
|
+
deploys.forEach((item) => logger.info(item.label));
|
|
107
|
+
return { exitCode: 0, result: { packages: getDeployPlan(deploys) } };
|
|
108
|
+
}
|
package/dist/git-deploy.d.ts
CHANGED
|
@@ -28,6 +28,13 @@ export interface GitDeployInput {
|
|
|
28
28
|
excludeGitattributes?: boolean;
|
|
29
29
|
/** Commit message of the release. */
|
|
30
30
|
message?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Token for an HTTPS repository on the GitHub host, sent as an
|
|
33
|
+
* authorization header instead of in the URL. Defaults to GITHUB_TOKEN for
|
|
34
|
+
* a URL on github.com, or on the host of GITHUB_SERVER_URL, without
|
|
35
|
+
* credentials of its own.
|
|
36
|
+
*/
|
|
37
|
+
token?: string;
|
|
31
38
|
/** Run everything except the push. */
|
|
32
39
|
dryRun?: boolean;
|
|
33
40
|
}
|
|
@@ -43,6 +50,8 @@ export interface GitDeployOptions extends FileSelection {
|
|
|
43
50
|
splitPrefix: string | null;
|
|
44
51
|
sourceRef: string;
|
|
45
52
|
message: string | null;
|
|
53
|
+
/** Token sent as an authorization header to the repository. */
|
|
54
|
+
token: string | null;
|
|
46
55
|
dryRun: boolean;
|
|
47
56
|
}
|
|
48
57
|
export interface GitDeployResult {
|
|
@@ -70,6 +79,29 @@ export interface GitDeployResult {
|
|
|
70
79
|
* @return Whether the name can be used.
|
|
71
80
|
*/
|
|
72
81
|
export declare function isValidRefName(name: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Get GITHUB_TOKEN for an HTTPS repository URL on the GitHub host, like
|
|
84
|
+
* https://github.com/owner/repo.git, unless the URL has credentials.
|
|
85
|
+
*
|
|
86
|
+
* @param gitUrl Repository URL.
|
|
87
|
+
* @param env Environment variables.
|
|
88
|
+
* @return The token, or undefined for another URL or without one.
|
|
89
|
+
*/
|
|
90
|
+
export declare function getDefaultToken(gitUrl: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Get the environment variables that make git send a token as the
|
|
93
|
+
* authorization header to a repository, like actions/checkout, so that the
|
|
94
|
+
* token stays out of the URL, the arguments and the repository config. An
|
|
95
|
+
* empty header first resets the headers that git is configured with, like
|
|
96
|
+
* the one that actions/checkout persists, since a second authorization
|
|
97
|
+
* header would fail.
|
|
98
|
+
*
|
|
99
|
+
* @param gitUrl Repository URL.
|
|
100
|
+
* @param token Token, or null for none.
|
|
101
|
+
* @param env Environment variables, for the existing GIT_CONFIG_COUNT.
|
|
102
|
+
* @return Environment variables for git.
|
|
103
|
+
*/
|
|
104
|
+
export declare function getGitAuthEnv(gitUrl: string, token: string | null, env?: NodeJS.ProcessEnv): Record<string, string>;
|
|
73
105
|
/**
|
|
74
106
|
* Apply the defaults to the Git deploy options and validate them.
|
|
75
107
|
*
|
package/dist/git-deploy.js
CHANGED
|
@@ -18,6 +18,54 @@ export function isValidRefName(name) {
|
|
|
18
18
|
return (/^[^\s~^:?*[\\]+$/.test(name) &&
|
|
19
19
|
!/(^[-/.]|\/\.|\.\.|\/\/|@\{|\.lock$|[/.]$)/.test(name));
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Get GITHUB_TOKEN for an HTTPS repository URL on the GitHub host, like
|
|
23
|
+
* https://github.com/owner/repo.git, unless the URL has credentials.
|
|
24
|
+
*
|
|
25
|
+
* @param gitUrl Repository URL.
|
|
26
|
+
* @param env Environment variables.
|
|
27
|
+
* @return The token, or undefined for another URL or without one.
|
|
28
|
+
*/
|
|
29
|
+
export function getDefaultToken(gitUrl, env = process.env) {
|
|
30
|
+
const url = URL.canParse(gitUrl) ? new URL(gitUrl) : undefined;
|
|
31
|
+
const hosts = [
|
|
32
|
+
'github.com',
|
|
33
|
+
URL.canParse(env.GITHUB_SERVER_URL ?? '')
|
|
34
|
+
? new URL(env.GITHUB_SERVER_URL ?? '').host
|
|
35
|
+
: '',
|
|
36
|
+
];
|
|
37
|
+
const isGitHub = 'https:' === url?.protocol &&
|
|
38
|
+
!url.username &&
|
|
39
|
+
hosts.includes(url.host);
|
|
40
|
+
return isGitHub ? env.GITHUB_TOKEN || undefined : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get the environment variables that make git send a token as the
|
|
44
|
+
* authorization header to a repository, like actions/checkout, so that the
|
|
45
|
+
* token stays out of the URL, the arguments and the repository config. An
|
|
46
|
+
* empty header first resets the headers that git is configured with, like
|
|
47
|
+
* the one that actions/checkout persists, since a second authorization
|
|
48
|
+
* header would fail.
|
|
49
|
+
*
|
|
50
|
+
* @param gitUrl Repository URL.
|
|
51
|
+
* @param token Token, or null for none.
|
|
52
|
+
* @param env Environment variables, for the existing GIT_CONFIG_COUNT.
|
|
53
|
+
* @return Environment variables for git.
|
|
54
|
+
*/
|
|
55
|
+
export function getGitAuthEnv(gitUrl, token, env = process.env) {
|
|
56
|
+
const count = Number(env.GIT_CONFIG_COUNT) || 0;
|
|
57
|
+
const key = token ? `http.${new URL(gitUrl).origin}/.extraheader` : '';
|
|
58
|
+
const credentials = Buffer.from(`x-access-token:${token}`).toString('base64');
|
|
59
|
+
return token
|
|
60
|
+
? {
|
|
61
|
+
GIT_CONFIG_COUNT: String(count + 2),
|
|
62
|
+
[`GIT_CONFIG_KEY_${count}`]: key,
|
|
63
|
+
[`GIT_CONFIG_VALUE_${count}`]: '',
|
|
64
|
+
[`GIT_CONFIG_KEY_${count + 1}`]: key,
|
|
65
|
+
[`GIT_CONFIG_VALUE_${count + 1}`]: `AUTHORIZATION: basic ${credentials}`,
|
|
66
|
+
}
|
|
67
|
+
: {};
|
|
68
|
+
}
|
|
21
69
|
/**
|
|
22
70
|
* Apply the defaults to the Git deploy options and validate them.
|
|
23
71
|
*
|
|
@@ -49,6 +97,7 @@ export function resolveGitDeployOptions(input = {}) {
|
|
|
49
97
|
splitPrefix: input.splitPrefix || null,
|
|
50
98
|
sourceRef: input.sourceRef || 'HEAD',
|
|
51
99
|
message: input.message || null,
|
|
100
|
+
token: input.token || getDefaultToken(input.gitUrl) || null,
|
|
52
101
|
dryRun: Boolean(input.dryRun),
|
|
53
102
|
...resolveFileSelection({
|
|
54
103
|
cwd,
|
|
@@ -415,7 +464,14 @@ export class GitDeploy {
|
|
|
415
464
|
async git(input) {
|
|
416
465
|
const cwd = await this.getRepoRoot();
|
|
417
466
|
this.logger.debug(redactUrls(`git ${input.args.join(' ')}`));
|
|
418
|
-
const { stdout } = await this.runner({
|
|
467
|
+
const { stdout } = await this.runner({
|
|
468
|
+
...input,
|
|
469
|
+
cwd,
|
|
470
|
+
env: {
|
|
471
|
+
...getGitAuthEnv(this.options.gitUrl, this.options.token),
|
|
472
|
+
...input.env,
|
|
473
|
+
},
|
|
474
|
+
});
|
|
419
475
|
return stdout.trim();
|
|
420
476
|
}
|
|
421
477
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,8 +21,11 @@ export { Svn, SvnError, createSvnRunner, parseStatus, toSvnPath, } from './svn.t
|
|
|
21
21
|
export { syncDirectory, copyFileIfChanged } from './sync.ts';
|
|
22
22
|
export { ASSET_MIME_TYPES, SvnDeploy } from './svn-deploy.ts';
|
|
23
23
|
export { GitError, createGitRunner, redactUrls } from './git.ts';
|
|
24
|
-
export { GitDeploy, isValidRefName, resolveGitDeployOptions, } from './git-deploy.ts';
|
|
24
|
+
export { GitDeploy, getDefaultToken, getGitAuthEnv, isValidRefName, resolveGitDeployOptions, } from './git-deploy.ts';
|
|
25
25
|
export { getGitDeployInputFromArgs } from './commands/deploy-git.ts';
|
|
26
|
+
export { getDeployPackages, getGitDeploys, getSvnDeploys, } from './package-deploy.ts';
|
|
27
|
+
export type { PackageDeploy, PackageDeployFiles, PackageDeployOptions, PackageFileSelection, PackageDeployPlanItem, PackageDeployRelease, } from './package-deploy.ts';
|
|
28
|
+
export type { PackageDeployOutcome } from './commands/package-deploys.ts';
|
|
26
29
|
export type { CliIo, CommandResults } from './cli.ts';
|
|
27
30
|
export type { ChecksumsResult } from './commands/checksums.ts';
|
|
28
31
|
export type { SignResult } from './commands/sign.ts';
|
|
@@ -43,7 +46,7 @@ export type { ExtractZipInput, ZipEntryInfo, ZipEntryType } from './unzip.ts';
|
|
|
43
46
|
export type { FindVersionMismatchesInput, GitTagInput, VersionCheckResult, VersionMismatch, } from './version-check.ts';
|
|
44
47
|
export type { Changeset, ChangesetBump, ChangesetPackages, ChangesetConfig, ChangesetPackageType, ChangesetPackage, PackageRelease, VersionedRelease, ChangesetReleasePlan, } from './changesets.ts';
|
|
45
48
|
export type { ChangesetRepository, ChangesetReleaseOptions, ChangesetAddOptions, ChangesetVersionOptions, ChangesetTagOptions, ChangesetStatusOptions, ChangesetStatusResult, ChangesetVersionInput, VersionChangesetsResult, ChangesetTagInput, ChangesetTagResult, } from './changeset-release.ts';
|
|
46
|
-
export type { WpReleaseConfig, ChangesetPackageGroup, PackageVersionConfig, PackageVersionSource, PackageVersionTarget, PackageIdSource, NextVersionInput, ReleaseCommit, ReleaseNotesInput, ChangelogEntryInput, DependentsChangelogInput, DependentsConfig, PackageJsonVersionOptions, ComposerJsonVersionOptions, WordPressVersionFileOptions, GitTagVersionOptions, PhpConstantVersionOptions, LoadChangesetConfigInput, } from './changeset-config.ts';
|
|
49
|
+
export type { WpReleaseConfig, ChangesetPackageGroup, PackageFilesConfig, PackageDeployConfig, GitDeployTarget, SvnDeployTarget, DeployTemplate, DeployTargetInput, DeployPackage, PackageVersionConfig, PackageVersionSource, PackageVersionTarget, PackageIdSource, NextVersionInput, ReleaseCommit, ReleaseNotesInput, ChangelogEntryInput, DependentsChangelogInput, DependentsConfig, PackageJsonVersionOptions, ComposerJsonVersionOptions, WordPressVersionFileOptions, GitTagVersionOptions, PhpConstantVersionOptions, LoadChangesetConfigInput, } from './changeset-config.ts';
|
|
47
50
|
export type { SvnCheckoutInput, SvnCommandInput, SvnCommandOutput, SvnCommitInput, SvnCopyInput, SvnDepth, SvnOptions, SvnPathsInput, SvnPropsetInput, SvnRunner, SvnStatusEntry, SvnStatusType, SvnUpdateInput, } from './svn.ts';
|
|
48
51
|
export type { SyncDirectoryInput, SyncResult } from './sync.ts';
|
|
49
52
|
export type { SummaryContext } from './summary.ts';
|
package/dist/index.js
CHANGED
|
@@ -21,5 +21,6 @@ export { Svn, SvnError, createSvnRunner, parseStatus, toSvnPath, } from "./svn.j
|
|
|
21
21
|
export { syncDirectory, copyFileIfChanged } from "./sync.js";
|
|
22
22
|
export { ASSET_MIME_TYPES, SvnDeploy } from "./svn-deploy.js";
|
|
23
23
|
export { GitError, createGitRunner, redactUrls } from "./git.js";
|
|
24
|
-
export { GitDeploy, isValidRefName, resolveGitDeployOptions, } from "./git-deploy.js";
|
|
24
|
+
export { GitDeploy, getDefaultToken, getGitAuthEnv, isValidRefName, resolveGitDeployOptions, } from "./git-deploy.js";
|
|
25
25
|
export { getGitDeployInputFromArgs } from "./commands/deploy-git.js";
|
|
26
|
+
export { getDeployPackages, getGitDeploys, getSvnDeploys, } from "./package-deploy.js";
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { DeployPackage } from './changeset-config.ts';
|
|
2
|
+
import type { DeployInput } from './deploy-options.ts';
|
|
3
|
+
import type { GitDeployInput } from './git-deploy.ts';
|
|
4
|
+
import type { GitRunner } from './git.ts';
|
|
5
|
+
export interface PackageDeployOptions {
|
|
6
|
+
/** Repository root with the wp-release config. */
|
|
7
|
+
cwd: string;
|
|
8
|
+
/** Config file, instead of the conventional one at the root. */
|
|
9
|
+
configFile?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Ref to deploy: a package release tag, like refs/tags/plugins/alpha/1.2.0,
|
|
12
|
+
* or a branch, like refs/heads/main. A name without refs/ is a release tag
|
|
13
|
+
* when it matches the tag format of a package, otherwise a branch.
|
|
14
|
+
*/
|
|
15
|
+
ref: string;
|
|
16
|
+
/** Deploy only these packages, by identifier or path under the root. */
|
|
17
|
+
packages?: string[];
|
|
18
|
+
/** Run everything except the writes. */
|
|
19
|
+
dryRun?: boolean;
|
|
20
|
+
/** Git runner, spawning git by default. */
|
|
21
|
+
git?: GitRunner;
|
|
22
|
+
}
|
|
23
|
+
/** The release of a package that a ref deploys. */
|
|
24
|
+
export interface PackageDeployRelease {
|
|
25
|
+
/** Released version, for a release tag. */
|
|
26
|
+
version?: string;
|
|
27
|
+
/** Deployed branch, for a branch. */
|
|
28
|
+
branch?: string;
|
|
29
|
+
}
|
|
30
|
+
/** One deploy of a package to one of its targets. */
|
|
31
|
+
export interface PackageDeploy<Input> {
|
|
32
|
+
package: DeployPackage;
|
|
33
|
+
release: PackageDeployRelease;
|
|
34
|
+
/** Repository or WordPress.org plugin of the target, with credentials redacted. */
|
|
35
|
+
target: string;
|
|
36
|
+
/** Tag or branch that the deploy writes in the target. */
|
|
37
|
+
targetRef: string;
|
|
38
|
+
/** Package, target and ref, with credentials redacted, for logs. */
|
|
39
|
+
label: string;
|
|
40
|
+
/** Options of the deploy. */
|
|
41
|
+
input: Input;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get the Git deploys of the configured packages that a ref releases: every
|
|
45
|
+
* Git target of the package of a release tag that releases tags, or of every
|
|
46
|
+
* package with a Git target that releases the branch.
|
|
47
|
+
*
|
|
48
|
+
* @param options Repository, ref and package selection.
|
|
49
|
+
* @return Deploys in the configured package and target order.
|
|
50
|
+
* @throws {ValidationError} Without a config, or for an invalid one or an unknown package.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getGitDeploys(options: PackageDeployOptions): Promise<PackageDeploy<GitDeployInput>[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Get the WordPress.org SVN deploys of the configured package that a release
|
|
55
|
+
* tag releases. A stable release syncs trunk/, the tag and the assets, and a
|
|
56
|
+
* pre-release only creates its tag. Branches aren't deployed to SVN.
|
|
57
|
+
*
|
|
58
|
+
* @param options Repository, ref and package selection.
|
|
59
|
+
* @return Deploys in the configured target order.
|
|
60
|
+
* @throws {ValidationError} Without a config, or for an invalid one or an unknown package.
|
|
61
|
+
*/
|
|
62
|
+
export declare function getSvnDeploys(options: PackageDeployOptions): Promise<PackageDeploy<DeployInput>[]>;
|
|
63
|
+
/** A package with deploy targets and the files that its deploys ship. */
|
|
64
|
+
export interface PackageDeployFiles extends DeployPackage {
|
|
65
|
+
/** File selection of the package, for resolveFileSelection(). */
|
|
66
|
+
files: PackageFileSelection;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get the packages with deploy targets and the files that their deploys
|
|
70
|
+
* ship, for example to build a ZIP with the same files.
|
|
71
|
+
*
|
|
72
|
+
* @param options Repository root and config file.
|
|
73
|
+
* @param options.cwd Repository root with the wp-release config.
|
|
74
|
+
* @param options.configFile Config file, instead of the conventional one at the root.
|
|
75
|
+
* @return Packages in the configured order.
|
|
76
|
+
* @throws {ValidationError} Without a config, or for an invalid one.
|
|
77
|
+
*/
|
|
78
|
+
export declare function getDeployPackages(options: {
|
|
79
|
+
cwd: string;
|
|
80
|
+
configFile?: string;
|
|
81
|
+
}): Promise<PackageDeployFiles[]>;
|
|
82
|
+
/** Ignore file and patterns that select the files of a package. */
|
|
83
|
+
export interface PackageFileSelection {
|
|
84
|
+
/** Absolute ignore file path, or false to not read one. */
|
|
85
|
+
distignore?: string | false;
|
|
86
|
+
/** Gitignore-style patterns relative to the package directory. */
|
|
87
|
+
exclude?: string[];
|
|
88
|
+
}
|
|
89
|
+
/** The deploys of one package in a deploy plan. */
|
|
90
|
+
export interface PackageDeployPlanItem extends PackageDeployRelease {
|
|
91
|
+
package: string;
|
|
92
|
+
/** Targets with the tag or branch that each deploy writes. */
|
|
93
|
+
targets: Array<{
|
|
94
|
+
target: string;
|
|
95
|
+
ref: string;
|
|
96
|
+
}>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Group deploys by package, for a preview of what a ref deploys or a CI
|
|
100
|
+
* matrix with a job for each package.
|
|
101
|
+
*
|
|
102
|
+
* @param deploys Deploys from getGitDeploys() or getSvnDeploys().
|
|
103
|
+
* @return One item for each package, in the deploy order.
|
|
104
|
+
*/
|
|
105
|
+
export declare function getDeployPlan(deploys: PackageDeploy<unknown>[]): PackageDeployPlanItem[];
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { realpath } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { loadChangesetConfig, toTargetList } from "./changeset-config.js";
|
|
4
|
+
import { discoverDeployPackages, findPackage } from "./changeset-packages.js";
|
|
5
|
+
import { ValidationError } from "./errors.js";
|
|
6
|
+
import { redactUrls } from "./git.js";
|
|
7
|
+
import { ReleaseRepository } from "./release-repository.js";
|
|
8
|
+
import { formatTag } from "./tag-format.js";
|
|
9
|
+
import { isPrerelease, isVersion } from "./versions.js";
|
|
10
|
+
/**
|
|
11
|
+
* Get the Git deploys of the configured packages that a ref releases: every
|
|
12
|
+
* Git target of the package of a release tag that releases tags, or of every
|
|
13
|
+
* package with a Git target that releases the branch.
|
|
14
|
+
*
|
|
15
|
+
* @param options Repository, ref and package selection.
|
|
16
|
+
* @return Deploys in the configured package and target order.
|
|
17
|
+
* @throws {ValidationError} Without a config, or for an invalid one or an unknown package.
|
|
18
|
+
*/
|
|
19
|
+
export async function getGitDeploys(options) {
|
|
20
|
+
const { config, releases, root } = await loadDeployContext(options);
|
|
21
|
+
const deploys = await Promise.all(releases.map(async (release) => {
|
|
22
|
+
const targets = toTargetList(release.package.group.deploy?.git);
|
|
23
|
+
const resolved = await Promise.all(targets
|
|
24
|
+
.filter((target) => isGitTargetReleased(target, release))
|
|
25
|
+
.map((target) => getGitDeploy(target, release, config, root, options)));
|
|
26
|
+
return resolved.filter((deploy) => undefined !== deploy);
|
|
27
|
+
}));
|
|
28
|
+
return deploys.flat();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the WordPress.org SVN deploys of the configured package that a release
|
|
32
|
+
* tag releases. A stable release syncs trunk/, the tag and the assets, and a
|
|
33
|
+
* pre-release only creates its tag. Branches aren't deployed to SVN.
|
|
34
|
+
*
|
|
35
|
+
* @param options Repository, ref and package selection.
|
|
36
|
+
* @return Deploys in the configured target order.
|
|
37
|
+
* @throws {ValidationError} Without a config, or for an invalid one or an unknown package.
|
|
38
|
+
*/
|
|
39
|
+
export async function getSvnDeploys(options) {
|
|
40
|
+
const { config, releases } = await loadDeployContext(options);
|
|
41
|
+
const deploys = await Promise.all(releases
|
|
42
|
+
.filter((release) => release.version)
|
|
43
|
+
.map(async (release) => {
|
|
44
|
+
const targets = toTargetList(release.package.group.deploy?.svn);
|
|
45
|
+
const resolved = await Promise.all(targets.map((target) => getSvnDeploy(target, release, config, options)));
|
|
46
|
+
return resolved.filter((deploy) => undefined !== deploy);
|
|
47
|
+
}));
|
|
48
|
+
return deploys.flat();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the packages with deploy targets and the files that their deploys
|
|
52
|
+
* ship, for example to build a ZIP with the same files.
|
|
53
|
+
*
|
|
54
|
+
* @param options Repository root and config file.
|
|
55
|
+
* @param options.cwd Repository root with the wp-release config.
|
|
56
|
+
* @param options.configFile Config file, instead of the conventional one at the root.
|
|
57
|
+
* @return Packages in the configured order.
|
|
58
|
+
* @throws {ValidationError} Without a config, or for an invalid one.
|
|
59
|
+
*/
|
|
60
|
+
export async function getDeployPackages(options) {
|
|
61
|
+
const config = await requireConfig(options);
|
|
62
|
+
const packages = await discoverDeployPackages(new ReleaseRepository({ cwd: options.cwd }), config);
|
|
63
|
+
return packages.map((pkg) => ({
|
|
64
|
+
...toDeployPackage(pkg),
|
|
65
|
+
files: getFileSelection(pkg, config),
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
async function requireConfig(options) {
|
|
69
|
+
const loaded = await loadChangesetConfig({
|
|
70
|
+
cwd: options.cwd,
|
|
71
|
+
...(options.configFile ? { configFile: options.configFile } : {}),
|
|
72
|
+
});
|
|
73
|
+
if (!loaded) {
|
|
74
|
+
throw new ValidationError('No wp-release config found. Add wp-release.config.js or pass --config <path>.');
|
|
75
|
+
}
|
|
76
|
+
return loaded.config;
|
|
77
|
+
}
|
|
78
|
+
async function loadDeployContext(options) {
|
|
79
|
+
const config = await requireConfig(options);
|
|
80
|
+
const repository = new ReleaseRepository({
|
|
81
|
+
cwd: options.cwd,
|
|
82
|
+
runner: options.git,
|
|
83
|
+
});
|
|
84
|
+
const packages = selectPackages(await discoverDeployPackages(repository, config), options.packages ?? []);
|
|
85
|
+
const root = await repository.run(['rev-parse', '--show-toplevel']);
|
|
86
|
+
const revision = await repository.run(['rev-parse', 'HEAD^{commit}']);
|
|
87
|
+
return {
|
|
88
|
+
config,
|
|
89
|
+
releases: getReleases(packages, config, options.ref).map((release) => ({ ...release, revision: revision.trim() })),
|
|
90
|
+
root: root.trim(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function selectPackages(packages, identifiers) {
|
|
94
|
+
const selected = identifiers.map((identifier) => {
|
|
95
|
+
const pkg = findPackage(packages, identifier);
|
|
96
|
+
if (!pkg) {
|
|
97
|
+
throw new ValidationError(`Unknown package "${identifier}", or it has no deploy targets.`);
|
|
98
|
+
}
|
|
99
|
+
return pkg;
|
|
100
|
+
});
|
|
101
|
+
return identifiers.length
|
|
102
|
+
? packages.filter((pkg) => selected.includes(pkg))
|
|
103
|
+
: packages;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Resolve the releases of a ref: the package of a release tag, or every
|
|
107
|
+
* package for a branch.
|
|
108
|
+
*
|
|
109
|
+
* @param packages Packages with deploy targets.
|
|
110
|
+
* @param config Repository release configuration.
|
|
111
|
+
* @param ref Ref to deploy.
|
|
112
|
+
* @return Releases.
|
|
113
|
+
*/
|
|
114
|
+
function getReleases(packages, config, ref) {
|
|
115
|
+
const tagFormat = config.tagFormat ?? '{path}/{version}';
|
|
116
|
+
const tag = ref.startsWith('refs/tags/')
|
|
117
|
+
? ref.slice('refs/tags/'.length)
|
|
118
|
+
: ref;
|
|
119
|
+
const tagged = ref.startsWith('refs/heads/')
|
|
120
|
+
? []
|
|
121
|
+
: packages.flatMap((pkg) => getTaggedVersion(pkg, tagFormat, tag));
|
|
122
|
+
const branch = ref.replace(/^refs\/heads\//, '');
|
|
123
|
+
return ref.startsWith('refs/tags/') || tagged.length
|
|
124
|
+
? tagged
|
|
125
|
+
: packages.map((pkg) => ({ package: pkg, branch }));
|
|
126
|
+
}
|
|
127
|
+
function getTaggedVersion(pkg, tagFormat, tag) {
|
|
128
|
+
const prefix = formatTag(tagFormat, pkg, '');
|
|
129
|
+
const version = tag.startsWith(prefix) ? tag.slice(prefix.length) : '';
|
|
130
|
+
return pkg.group.version && isVersion(version)
|
|
131
|
+
? [{ package: pkg, version }]
|
|
132
|
+
: [];
|
|
133
|
+
}
|
|
134
|
+
function isGitTargetReleased(target, release) {
|
|
135
|
+
const branches = target.branches ?? true;
|
|
136
|
+
const branchReleased = Array.isArray(branches)
|
|
137
|
+
? branches.includes(release.branch ?? '')
|
|
138
|
+
: branches;
|
|
139
|
+
return release.version ? false !== target.tag : branchReleased;
|
|
140
|
+
}
|
|
141
|
+
async function getGitDeploy(target, release, config, root, options) {
|
|
142
|
+
const pkg = release.package;
|
|
143
|
+
const tag = release.version
|
|
144
|
+
? formatTag(target.tag || '{version}', pkg, release.version)
|
|
145
|
+
: undefined;
|
|
146
|
+
const ref = tag ?? release.branch ?? '';
|
|
147
|
+
const repository = await resolveTemplate(target.repository, release, ref);
|
|
148
|
+
const message = target.message
|
|
149
|
+
? await resolveTemplate(target.message, release, ref)
|
|
150
|
+
: '';
|
|
151
|
+
// Git reports the real path of the root, so compare it with the real path
|
|
152
|
+
// of the package, like on macOS where the temporary directory is a link.
|
|
153
|
+
const splitPrefix = false === target.history
|
|
154
|
+
? ''
|
|
155
|
+
: path
|
|
156
|
+
.relative(root, await realpath(pkg.directory))
|
|
157
|
+
.split(path.sep)
|
|
158
|
+
.join('/');
|
|
159
|
+
return repository
|
|
160
|
+
? {
|
|
161
|
+
package: toDeployPackage(pkg),
|
|
162
|
+
release: toRelease(release),
|
|
163
|
+
target: redactUrls(repository),
|
|
164
|
+
targetRef: ref,
|
|
165
|
+
label: `${pkg.name} to ${redactUrls(repository)} ${ref}`,
|
|
166
|
+
input: {
|
|
167
|
+
cwd: options.cwd,
|
|
168
|
+
sourceDir: pkg.directory,
|
|
169
|
+
gitUrl: repository,
|
|
170
|
+
...(release.branch ? { branch: release.branch } : {}),
|
|
171
|
+
...(tag ? { tag } : {}),
|
|
172
|
+
...(splitPrefix ? { splitPrefix } : {}),
|
|
173
|
+
...(message ? { message } : {}),
|
|
174
|
+
...getFileSelection(pkg, config),
|
|
175
|
+
dryRun: Boolean(options.dryRun),
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
: undefined;
|
|
179
|
+
}
|
|
180
|
+
async function getSvnDeploy(target, release, config, options) {
|
|
181
|
+
const pkg = release.package;
|
|
182
|
+
const version = release.version ?? '';
|
|
183
|
+
const stable = !isPrerelease(version);
|
|
184
|
+
const slug = await resolveTemplate(target.slug ?? '{slug}', release, version);
|
|
185
|
+
const message = target.message
|
|
186
|
+
? await resolveTemplate(target.message, release, version)
|
|
187
|
+
: '';
|
|
188
|
+
return slug
|
|
189
|
+
? {
|
|
190
|
+
package: toDeployPackage(pkg),
|
|
191
|
+
release: toRelease(release),
|
|
192
|
+
target: `WordPress.org ${slug}`,
|
|
193
|
+
targetRef: version,
|
|
194
|
+
label: `${pkg.name} to WordPress.org ${slug} ${version}`,
|
|
195
|
+
input: {
|
|
196
|
+
cwd: options.cwd,
|
|
197
|
+
sourceDir: pkg.directory,
|
|
198
|
+
slug,
|
|
199
|
+
version,
|
|
200
|
+
svnTag: version,
|
|
201
|
+
trunk: stable,
|
|
202
|
+
...(stable && target.assetsDir
|
|
203
|
+
? {
|
|
204
|
+
assetsDir: path.join(pkg.directory, target.assetsDir),
|
|
205
|
+
}
|
|
206
|
+
: {}),
|
|
207
|
+
...(target.readme ? { readme: target.readme } : {}),
|
|
208
|
+
...(target.url ? { svnUrl: target.url } : {}),
|
|
209
|
+
...(message ? { message } : {}),
|
|
210
|
+
...getFileSelection(pkg, config),
|
|
211
|
+
dryRun: Boolean(options.dryRun),
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
: undefined;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Resolve a template with the package and release placeholders, or call the
|
|
218
|
+
* callback with the package and its release.
|
|
219
|
+
*
|
|
220
|
+
* @param template Template or callback.
|
|
221
|
+
* @param release Release of the package.
|
|
222
|
+
* @param ref Tag or branch that the deploy writes in the target.
|
|
223
|
+
* @return Resolved value, empty for a skipped target or the default message.
|
|
224
|
+
*/
|
|
225
|
+
async function resolveTemplate(template, release, ref) {
|
|
226
|
+
const value = 'function' === typeof template
|
|
227
|
+
? await template({
|
|
228
|
+
package: toDeployPackage(release.package),
|
|
229
|
+
...toRelease(release),
|
|
230
|
+
ref,
|
|
231
|
+
revision: release.revision,
|
|
232
|
+
})
|
|
233
|
+
: formatTag(template, release.package, release.version ?? '')
|
|
234
|
+
.replaceAll('{branch}', release.branch ?? '')
|
|
235
|
+
.replaceAll('{ref}', ref)
|
|
236
|
+
.replaceAll('{revision}', release.revision);
|
|
237
|
+
return value?.trim() ?? '';
|
|
238
|
+
}
|
|
239
|
+
function getFileSelection(pkg, config) {
|
|
240
|
+
const shared = config.files ?? {};
|
|
241
|
+
const files = pkg.group.files ?? {};
|
|
242
|
+
const setting = undefined === files.distignore ? shared.distignore : files.distignore;
|
|
243
|
+
const distignore = 'string' === typeof setting
|
|
244
|
+
? path.join(pkg.directory, setting)
|
|
245
|
+
: setting;
|
|
246
|
+
const exclude = [...(shared.exclude ?? []), ...(files.exclude ?? [])];
|
|
247
|
+
return {
|
|
248
|
+
...(undefined === distignore ? {} : { distignore }),
|
|
249
|
+
...(exclude.length ? { exclude } : {}),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function toDeployPackage(pkg) {
|
|
253
|
+
return {
|
|
254
|
+
name: pkg.name,
|
|
255
|
+
directory: pkg.directory,
|
|
256
|
+
path: pkg.path,
|
|
257
|
+
type: pkg.type,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function toRelease(release) {
|
|
261
|
+
return {
|
|
262
|
+
...(release.version ? { version: release.version } : {}),
|
|
263
|
+
...(release.branch ? { branch: release.branch } : {}),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Group deploys by package, for a preview of what a ref deploys or a CI
|
|
268
|
+
* matrix with a job for each package.
|
|
269
|
+
*
|
|
270
|
+
* @param deploys Deploys from getGitDeploys() or getSvnDeploys().
|
|
271
|
+
* @return One item for each package, in the deploy order.
|
|
272
|
+
*/
|
|
273
|
+
export function getDeployPlan(deploys) {
|
|
274
|
+
const names = [
|
|
275
|
+
...new Set(deploys.map((item) => item.package.name)),
|
|
276
|
+
];
|
|
277
|
+
return names.map((name) => {
|
|
278
|
+
const items = deploys.filter((item) => item.package.name === name);
|
|
279
|
+
return {
|
|
280
|
+
package: name,
|
|
281
|
+
...items[0]?.release,
|
|
282
|
+
targets: items.map((item) => ({
|
|
283
|
+
target: item.target,
|
|
284
|
+
ref: item.targetRef,
|
|
285
|
+
})),
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
}
|