@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
package/dist/changeset-files.js
CHANGED
|
@@ -28,17 +28,19 @@ export async function readChangesets(directory) {
|
|
|
28
28
|
/**
|
|
29
29
|
* Pick the changesets that were added or changed since a ref.
|
|
30
30
|
*
|
|
31
|
-
* @param repository
|
|
32
|
-
* @param directory
|
|
33
|
-
* @param changesets
|
|
34
|
-
* @param since
|
|
31
|
+
* @param repository Repository of the changesets.
|
|
32
|
+
* @param directory Changeset directory.
|
|
33
|
+
* @param changesets Pending changesets.
|
|
34
|
+
* @param since Base ref, like origin/main.
|
|
35
|
+
* @param uncommitted Whether to include uncommitted and untracked changesets.
|
|
35
36
|
* @return Changesets of the diff.
|
|
36
37
|
*/
|
|
37
|
-
export async function readChangesetsSince(repository, directory, changesets, since) {
|
|
38
|
+
export async function readChangesetsSince(repository, directory, changesets, since, uncommitted = false) {
|
|
38
39
|
const relativeDirectory = repository.relativePath(directory);
|
|
39
40
|
const changedFiles = new Set(await repository.getChangedFiles(since, {
|
|
40
41
|
directory,
|
|
41
42
|
diffFilter: 'ACMR',
|
|
43
|
+
uncommitted,
|
|
42
44
|
}));
|
|
43
45
|
return changesets.filter((changeset) => changedFiles.has(path.posix.join(relativeDirectory, changeset.fileName)));
|
|
44
46
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChangesetConfig, ChangesetPackageGroup } from './changeset-config.ts';
|
|
1
|
+
import type { ChangesetConfig, ChangesetPackageGroup, DeployPackage, PackageVersionConfig } from './changeset-config.ts';
|
|
2
2
|
import type { ChangesetPackage } from './changesets.ts';
|
|
3
3
|
import type { ReleaseRepository } from './release-repository.ts';
|
|
4
4
|
/**
|
|
@@ -6,7 +6,14 @@ import type { ReleaseRepository } from './release-repository.ts';
|
|
|
6
6
|
*/
|
|
7
7
|
export interface ConfiguredPackage extends ChangesetPackage {
|
|
8
8
|
group: ChangesetPackageGroup;
|
|
9
|
-
versionConfig:
|
|
9
|
+
versionConfig: PackageVersionConfig;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A discovered package of a group with deploy targets, which may have no
|
|
13
|
+
* version configuration.
|
|
14
|
+
*/
|
|
15
|
+
export interface ConfiguredDeployPackage extends DeployPackage {
|
|
16
|
+
group: ChangesetPackageGroup;
|
|
10
17
|
}
|
|
11
18
|
export interface ChangedPackages {
|
|
12
19
|
/** Packages with changed files, in the configured order. */
|
|
@@ -16,7 +23,8 @@ export interface ChangedPackages {
|
|
|
16
23
|
}
|
|
17
24
|
/**
|
|
18
25
|
* Find the packages of the configured package groups, with their current
|
|
19
|
-
* versions.
|
|
26
|
+
* versions. Groups without a version configuration aren't released with
|
|
27
|
+
* changesets, so they're left out.
|
|
20
28
|
*
|
|
21
29
|
* @param repository Repository the packages are in.
|
|
22
30
|
* @param config Repository release configuration.
|
|
@@ -24,6 +32,16 @@ export interface ChangedPackages {
|
|
|
24
32
|
* @throws {ValidationError} For a glob without matches or a duplicate package name.
|
|
25
33
|
*/
|
|
26
34
|
export declare function discoverPackages(repository: ReleaseRepository, config: ChangesetConfig): Promise<ConfiguredPackage[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Find the packages of the package groups with deploy targets, including the
|
|
37
|
+
* groups without a version configuration, without reading their versions.
|
|
38
|
+
*
|
|
39
|
+
* @param repository Repository the packages are in.
|
|
40
|
+
* @param config Repository release configuration.
|
|
41
|
+
* @return Packages in the configured order.
|
|
42
|
+
* @throws {ValidationError} For a glob without matches or a duplicate package name.
|
|
43
|
+
*/
|
|
44
|
+
export declare function discoverDeployPackages(repository: ReleaseRepository, config: ChangesetConfig): Promise<ConfiguredDeployPackage[]>;
|
|
27
45
|
/**
|
|
28
46
|
* Fail for package names that aren't configured.
|
|
29
47
|
*
|
|
@@ -33,15 +51,34 @@ export declare function discoverPackages(repository: ReleaseRepository, config:
|
|
|
33
51
|
*/
|
|
34
52
|
export declare function assertKnownPackages(packages: ChangesetPackage[], names: string[]): void;
|
|
35
53
|
/**
|
|
36
|
-
* Find
|
|
37
|
-
*
|
|
54
|
+
* Find a package by its identifier, or by its path under the configured root,
|
|
55
|
+
* so that contributors can name a package by the directory they changed.
|
|
38
56
|
*
|
|
39
|
-
* @param repository Repository the packages are in.
|
|
40
57
|
* @param packages Discovered packages.
|
|
41
|
-
* @param
|
|
58
|
+
* @param identifier Package identifier or path, like plugins/alpha or plugins/alpha/.
|
|
59
|
+
* @return The package, or undefined when none matches.
|
|
60
|
+
*/
|
|
61
|
+
export declare function findPackage<Package extends Pick<ChangesetPackage, 'name' | 'path'>>(packages: Package[], identifier: string): Package | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Resolve package identifiers or paths to the package identifiers.
|
|
64
|
+
*
|
|
65
|
+
* @param packages Discovered packages.
|
|
66
|
+
* @param identifiers Package identifiers or paths under the configured root.
|
|
67
|
+
* @return Package identifiers, in the given order.
|
|
68
|
+
* @throws {ValidationError} For an unknown package.
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolvePackageNames(packages: ChangesetPackage[], identifiers: string[]): string[];
|
|
71
|
+
/**
|
|
72
|
+
* Find the packages with files changed between the merge base of a ref and
|
|
73
|
+
* HEAD, or the working tree.
|
|
74
|
+
*
|
|
75
|
+
* @param repository Repository the packages are in.
|
|
76
|
+
* @param packages Discovered packages.
|
|
77
|
+
* @param since Base ref, like origin/main.
|
|
78
|
+
* @param uncommitted Whether to include uncommitted and untracked files.
|
|
42
79
|
* @return Changed packages and their changed files.
|
|
43
80
|
*/
|
|
44
|
-
export declare function getChangedPackages(repository: ReleaseRepository, packages: ConfiguredPackage[], since: string): Promise<ChangedPackages>;
|
|
81
|
+
export declare function getChangedPackages(repository: ReleaseRepository, packages: ConfiguredPackage[], since: string, uncommitted?: boolean): Promise<ChangedPackages>;
|
|
45
82
|
/**
|
|
46
83
|
* Remove the release configuration from a package passed to config callbacks.
|
|
47
84
|
*
|
|
@@ -7,7 +7,8 @@ import { readVersion } from "./version-files.js";
|
|
|
7
7
|
const SKIPPED_DIRECTORIES = ['.git', 'node_modules', 'vendor', 'dist'];
|
|
8
8
|
/**
|
|
9
9
|
* Find the packages of the configured package groups, with their current
|
|
10
|
-
* versions.
|
|
10
|
+
* versions. Groups without a version configuration aren't released with
|
|
11
|
+
* changesets, so they're left out.
|
|
11
12
|
*
|
|
12
13
|
* @param repository Repository the packages are in.
|
|
13
14
|
* @param config Repository release configuration.
|
|
@@ -15,13 +16,38 @@ const SKIPPED_DIRECTORIES = ['.git', 'node_modules', 'vendor', 'dist'];
|
|
|
15
16
|
* @throws {ValidationError} For a glob without matches or a duplicate package name.
|
|
16
17
|
*/
|
|
17
18
|
export async function discoverPackages(repository, config) {
|
|
19
|
+
const found = await findGroupPackages(repository, config, config.packages.filter((group) => group.version));
|
|
20
|
+
const packages = [];
|
|
21
|
+
for (const pkg of found) {
|
|
22
|
+
const versionConfig = pkg.group.version;
|
|
23
|
+
packages.push({
|
|
24
|
+
...pkg,
|
|
25
|
+
currentVersion: await readVersion(repository, pkg.directory, versionConfig.from, pkg.path, pkg.group.tagType),
|
|
26
|
+
versionConfig,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return packages;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Find the packages of the package groups with deploy targets, including the
|
|
33
|
+
* groups without a version configuration, without reading their versions.
|
|
34
|
+
*
|
|
35
|
+
* @param repository Repository the packages are in.
|
|
36
|
+
* @param config Repository release configuration.
|
|
37
|
+
* @return Packages in the configured order.
|
|
38
|
+
* @throws {ValidationError} For a glob without matches or a duplicate package name.
|
|
39
|
+
*/
|
|
40
|
+
export async function discoverDeployPackages(repository, config) {
|
|
41
|
+
return findGroupPackages(repository, config, config.packages.filter((group) => group.deploy));
|
|
42
|
+
}
|
|
43
|
+
async function findGroupPackages(repository, config, groups) {
|
|
18
44
|
const root = path.resolve(repository.cwd, config.root ?? '.');
|
|
19
45
|
const packages = [];
|
|
20
|
-
const hasRecursiveGlob =
|
|
46
|
+
const hasRecursiveGlob = groups.some((item) => item.glob.split('/').includes('**'));
|
|
21
47
|
const directories = await findDirectories(root, '', hasRecursiveGlob
|
|
22
48
|
? Infinity
|
|
23
|
-
: Math.max(1, ...
|
|
24
|
-
for (const group of
|
|
49
|
+
: Math.max(1, ...groups.map((item) => item.glob.split('/').length + 2)));
|
|
50
|
+
for (const group of groups) {
|
|
25
51
|
const matcher = globRegExp(group.glob);
|
|
26
52
|
const candidates = matcher.test('.')
|
|
27
53
|
? ['.', ...directories]
|
|
@@ -36,7 +62,6 @@ export async function discoverPackages(repository, config) {
|
|
|
36
62
|
for (const relativePath of matches) {
|
|
37
63
|
const directory = path.join(root, relativePath);
|
|
38
64
|
const name = await packageName(directory, relativePath, group.idFrom ?? 'path');
|
|
39
|
-
const currentVersion = await readVersion(repository, directory, group.version.from, relativePath, group.tagType);
|
|
40
65
|
if (packages.some((pkg) => pkg.name === name)) {
|
|
41
66
|
throw new ValidationError(`Package identifier "${name}" is configured more than once.`);
|
|
42
67
|
}
|
|
@@ -45,9 +70,7 @@ export async function discoverPackages(repository, config) {
|
|
|
45
70
|
directory,
|
|
46
71
|
path: relativePath,
|
|
47
72
|
type: packageType(group),
|
|
48
|
-
currentVersion,
|
|
49
73
|
group,
|
|
50
|
-
versionConfig: group.version,
|
|
51
74
|
});
|
|
52
75
|
}
|
|
53
76
|
}
|
|
@@ -68,16 +91,52 @@ export function assertKnownPackages(packages, names) {
|
|
|
68
91
|
});
|
|
69
92
|
}
|
|
70
93
|
/**
|
|
71
|
-
* Find
|
|
72
|
-
*
|
|
94
|
+
* Find a package by its identifier, or by its path under the configured root,
|
|
95
|
+
* so that contributors can name a package by the directory they changed.
|
|
73
96
|
*
|
|
74
|
-
* @param repository Repository the packages are in.
|
|
75
97
|
* @param packages Discovered packages.
|
|
76
|
-
* @param
|
|
98
|
+
* @param identifier Package identifier or path, like plugins/alpha or plugins/alpha/.
|
|
99
|
+
* @return The package, or undefined when none matches.
|
|
100
|
+
*/
|
|
101
|
+
export function findPackage(packages, identifier) {
|
|
102
|
+
const packagePath = identifier
|
|
103
|
+
.replaceAll('\\', '/')
|
|
104
|
+
.replace(/^\.\//, '')
|
|
105
|
+
.replace(/\/+$/, '');
|
|
106
|
+
return (packages.find((pkg) => pkg.name === identifier) ??
|
|
107
|
+
packages.find((pkg) => pkg.path === packagePath));
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Resolve package identifiers or paths to the package identifiers.
|
|
111
|
+
*
|
|
112
|
+
* @param packages Discovered packages.
|
|
113
|
+
* @param identifiers Package identifiers or paths under the configured root.
|
|
114
|
+
* @return Package identifiers, in the given order.
|
|
115
|
+
* @throws {ValidationError} For an unknown package.
|
|
116
|
+
*/
|
|
117
|
+
export function resolvePackageNames(packages, identifiers) {
|
|
118
|
+
return identifiers.map((identifier) => {
|
|
119
|
+
const pkg = findPackage(packages, identifier);
|
|
120
|
+
if (!pkg) {
|
|
121
|
+
throw new ValidationError(`Unknown package "${identifier}".`);
|
|
122
|
+
}
|
|
123
|
+
return pkg.name;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Find the packages with files changed between the merge base of a ref and
|
|
128
|
+
* HEAD, or the working tree.
|
|
129
|
+
*
|
|
130
|
+
* @param repository Repository the packages are in.
|
|
131
|
+
* @param packages Discovered packages.
|
|
132
|
+
* @param since Base ref, like origin/main.
|
|
133
|
+
* @param uncommitted Whether to include uncommitted and untracked files.
|
|
77
134
|
* @return Changed packages and their changed files.
|
|
78
135
|
*/
|
|
79
|
-
export async function getChangedPackages(repository, packages, since) {
|
|
80
|
-
const changedFiles = await repository.getChangedFiles(since
|
|
136
|
+
export async function getChangedPackages(repository, packages, since, uncommitted = false) {
|
|
137
|
+
const changedFiles = await repository.getChangedFiles(since, {
|
|
138
|
+
uncommitted,
|
|
139
|
+
});
|
|
81
140
|
const filesByPackage = {};
|
|
82
141
|
const changed = packages.filter((pkg) => {
|
|
83
142
|
const packageFromRepo = repository.relativePath(pkg.directory);
|
|
@@ -172,7 +231,7 @@ async function packageName(directory, relativePath, idFrom) {
|
|
|
172
231
|
return name;
|
|
173
232
|
}
|
|
174
233
|
function packageType(group) {
|
|
175
|
-
const kind = group.version
|
|
234
|
+
const kind = group.version?.from.kind;
|
|
176
235
|
if ('wordpress-plugin-header' === kind) {
|
|
177
236
|
return 'plugin';
|
|
178
237
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { ChangesetPackage, ChangesetPackages } from './changesets.ts';
|
|
2
|
+
import type { OutputStream } from './logger.ts';
|
|
3
|
+
/** Terminal that the prompts read answers from and write questions to. */
|
|
4
|
+
export interface PromptTerminal {
|
|
5
|
+
input: NodeJS.ReadableStream;
|
|
6
|
+
output: OutputStream;
|
|
7
|
+
}
|
|
8
|
+
/** Packages to choose from, and the changed ones that are preselected. */
|
|
9
|
+
export interface ChangesetPromptInput {
|
|
10
|
+
/** Configured packages. */
|
|
11
|
+
packages: ChangesetPackage[];
|
|
12
|
+
/** Identifiers of the changed packages without a changeset. */
|
|
13
|
+
changed: string[];
|
|
14
|
+
/** Base ref that the changed packages were compared with. */
|
|
15
|
+
since?: string;
|
|
16
|
+
/** Release note to use instead of asking for one. */
|
|
17
|
+
summary?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Answers of the prompts: no packages for an empty changeset. */
|
|
20
|
+
export interface ChangesetPromptAnswers {
|
|
21
|
+
packages: ChangesetPackages;
|
|
22
|
+
summary: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Ask for the packages, bumps and release note of a changeset, line by line,
|
|
26
|
+
* so that it works in any terminal and with piped input.
|
|
27
|
+
*/
|
|
28
|
+
export declare class ChangesetPrompt {
|
|
29
|
+
readonly terminal: PromptTerminal;
|
|
30
|
+
private lines;
|
|
31
|
+
private readline;
|
|
32
|
+
/**
|
|
33
|
+
* @param terminal Input to read answers from and output for the questions.
|
|
34
|
+
*/
|
|
35
|
+
constructor(terminal: PromptTerminal);
|
|
36
|
+
/**
|
|
37
|
+
* Ask for the changeset, preselecting the changed packages.
|
|
38
|
+
*
|
|
39
|
+
* @param input Packages, changed packages and an optional release note.
|
|
40
|
+
* @return Package bumps, empty for no release, and the release note.
|
|
41
|
+
* @throws {ValidationError} If the input ends before the changeset is complete.
|
|
42
|
+
*/
|
|
43
|
+
ask(input: ChangesetPromptInput): Promise<ChangesetPromptAnswers>;
|
|
44
|
+
/**
|
|
45
|
+
* Stop reading the input, so that the process can exit.
|
|
46
|
+
*/
|
|
47
|
+
close(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Print the numbered packages, marking the changed ones.
|
|
50
|
+
*
|
|
51
|
+
* @param input Packages and changed packages.
|
|
52
|
+
*/
|
|
53
|
+
private listPackages;
|
|
54
|
+
/**
|
|
55
|
+
* Get the package question, with the changed packages as the default.
|
|
56
|
+
*
|
|
57
|
+
* @param input Packages and changed packages.
|
|
58
|
+
* @return Question text.
|
|
59
|
+
*/
|
|
60
|
+
private getPackagesQuestion;
|
|
61
|
+
/**
|
|
62
|
+
* Parse the chosen packages.
|
|
63
|
+
*
|
|
64
|
+
* @param answer Comma or space separated numbers, identifiers or paths.
|
|
65
|
+
* @param input Packages and changed packages.
|
|
66
|
+
* @return Package identifiers, empty for no release.
|
|
67
|
+
* @throws {ValidationError} For unknown, repeated or missing packages.
|
|
68
|
+
*/
|
|
69
|
+
private parsePackages;
|
|
70
|
+
/**
|
|
71
|
+
* Parse one chosen package.
|
|
72
|
+
*
|
|
73
|
+
* @param token Package number, identifier or path.
|
|
74
|
+
* @param packages Configured packages.
|
|
75
|
+
* @return Package identifier.
|
|
76
|
+
* @throws {ValidationError} For an unknown package.
|
|
77
|
+
*/
|
|
78
|
+
private parsePackage;
|
|
79
|
+
/**
|
|
80
|
+
* Parse a bump, patch by default.
|
|
81
|
+
*
|
|
82
|
+
* @param answer Bump name.
|
|
83
|
+
* @return Bump.
|
|
84
|
+
* @throws {ValidationError} For an unknown bump.
|
|
85
|
+
*/
|
|
86
|
+
private parseBump;
|
|
87
|
+
/**
|
|
88
|
+
* Parse a release note.
|
|
89
|
+
*
|
|
90
|
+
* @param answer Release note.
|
|
91
|
+
* @return Trimmed release note.
|
|
92
|
+
* @throws {ValidationError} For an empty release note.
|
|
93
|
+
*/
|
|
94
|
+
private parseSummary;
|
|
95
|
+
/**
|
|
96
|
+
* Ask a question until the answer parses, printing why it didn't.
|
|
97
|
+
*
|
|
98
|
+
* @param question Question text.
|
|
99
|
+
* @param parse Parser that throws a ValidationError for invalid answers.
|
|
100
|
+
* @return Parsed answer.
|
|
101
|
+
*/
|
|
102
|
+
private askUntilValid;
|
|
103
|
+
/**
|
|
104
|
+
* Write a question and read the next line of the input.
|
|
105
|
+
*
|
|
106
|
+
* @param question Question text.
|
|
107
|
+
* @return Answer line.
|
|
108
|
+
* @throws {ValidationError} If the input ended.
|
|
109
|
+
*/
|
|
110
|
+
private readAnswer;
|
|
111
|
+
/**
|
|
112
|
+
* Get the lines of the input, reading it from the first question on. The
|
|
113
|
+
* interface stays in line mode, so the terminal echoes and edits the
|
|
114
|
+
* answers and Ctrl+C ends the process.
|
|
115
|
+
*
|
|
116
|
+
* @return Line iterator.
|
|
117
|
+
*/
|
|
118
|
+
private getLines;
|
|
119
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { findPackage } from "./changeset-packages.js";
|
|
3
|
+
import { CHANGESET_BUMPS } from "./changesets.js";
|
|
4
|
+
import { ValidationError } from "./errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* Ask for the packages, bumps and release note of a changeset, line by line,
|
|
7
|
+
* so that it works in any terminal and with piped input.
|
|
8
|
+
*/
|
|
9
|
+
export class ChangesetPrompt {
|
|
10
|
+
terminal;
|
|
11
|
+
lines;
|
|
12
|
+
readline;
|
|
13
|
+
/**
|
|
14
|
+
* @param terminal Input to read answers from and output for the questions.
|
|
15
|
+
*/
|
|
16
|
+
constructor(terminal) {
|
|
17
|
+
this.terminal = terminal;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Ask for the changeset, preselecting the changed packages.
|
|
21
|
+
*
|
|
22
|
+
* @param input Packages, changed packages and an optional release note.
|
|
23
|
+
* @return Package bumps, empty for no release, and the release note.
|
|
24
|
+
* @throws {ValidationError} If the input ends before the changeset is complete.
|
|
25
|
+
*/
|
|
26
|
+
async ask(input) {
|
|
27
|
+
this.listPackages(input);
|
|
28
|
+
const names = await this.askUntilValid(this.getPackagesQuestion(input), (answer) => this.parsePackages(answer, input));
|
|
29
|
+
const packages = {};
|
|
30
|
+
for (const name of names) {
|
|
31
|
+
packages[name] = await this.askUntilValid(`Bump for ${name} (patch, minor or major) [patch]: `, (answer) => this.parseBump(answer));
|
|
32
|
+
}
|
|
33
|
+
const summary = input.summary ??
|
|
34
|
+
(names.length
|
|
35
|
+
? await this.askUntilValid('Release note: ', (answer) => this.parseSummary(answer))
|
|
36
|
+
: '');
|
|
37
|
+
return { packages, summary };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Stop reading the input, so that the process can exit.
|
|
41
|
+
*/
|
|
42
|
+
close() {
|
|
43
|
+
this.readline?.close();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Print the numbered packages, marking the changed ones.
|
|
47
|
+
*
|
|
48
|
+
* @param input Packages and changed packages.
|
|
49
|
+
*/
|
|
50
|
+
listPackages(input) {
|
|
51
|
+
const width = String(input.packages.length).length;
|
|
52
|
+
const rows = input.packages.map((pkg, index) => {
|
|
53
|
+
const marker = input.changed.includes(pkg.name) ? '*' : ' ';
|
|
54
|
+
const label = pkg.name === pkg.path
|
|
55
|
+
? pkg.name
|
|
56
|
+
: `${pkg.name} (${pkg.path})`;
|
|
57
|
+
return ` ${String(index + 1).padStart(width)} ${marker} ${label}`;
|
|
58
|
+
});
|
|
59
|
+
const legend = input.changed.length
|
|
60
|
+
? `* changed${input.since ? ` since ${input.since}` : ''} without a changeset`
|
|
61
|
+
: 'No changed packages without a changeset.';
|
|
62
|
+
this.terminal.output.write(`Packages:\n${rows.join('\n')}\n${legend}\n`);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get the package question, with the changed packages as the default.
|
|
66
|
+
*
|
|
67
|
+
* @param input Packages and changed packages.
|
|
68
|
+
* @return Question text.
|
|
69
|
+
*/
|
|
70
|
+
getPackagesQuestion(input) {
|
|
71
|
+
const defaults = input.changed.length
|
|
72
|
+
? ` [${input.changed.join(', ')}]`
|
|
73
|
+
: '';
|
|
74
|
+
return `Packages to release, by number, identifier or path, comma-separated, or "none" for no release${defaults}: `;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parse the chosen packages.
|
|
78
|
+
*
|
|
79
|
+
* @param answer Comma or space separated numbers, identifiers or paths.
|
|
80
|
+
* @param input Packages and changed packages.
|
|
81
|
+
* @return Package identifiers, empty for no release.
|
|
82
|
+
* @throws {ValidationError} For unknown, repeated or missing packages.
|
|
83
|
+
*/
|
|
84
|
+
parsePackages(answer, input) {
|
|
85
|
+
const tokens = answer.split(/[\s,]+/).filter(Boolean);
|
|
86
|
+
let names = input.changed;
|
|
87
|
+
if (1 === tokens.length && 'none' === tokens[0]) {
|
|
88
|
+
names = [];
|
|
89
|
+
}
|
|
90
|
+
else if (tokens.length) {
|
|
91
|
+
names = tokens.map((token) => this.parsePackage(token, input.packages));
|
|
92
|
+
}
|
|
93
|
+
else if (!names.length) {
|
|
94
|
+
throw new ValidationError('Choose at least one package, or "none" for no release.');
|
|
95
|
+
}
|
|
96
|
+
const repeated = names.find((name, index) => names.indexOf(name) !== index);
|
|
97
|
+
if (repeated) {
|
|
98
|
+
throw new ValidationError(`Package "${repeated}" was chosen more than once.`);
|
|
99
|
+
}
|
|
100
|
+
return names;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Parse one chosen package.
|
|
104
|
+
*
|
|
105
|
+
* @param token Package number, identifier or path.
|
|
106
|
+
* @param packages Configured packages.
|
|
107
|
+
* @return Package identifier.
|
|
108
|
+
* @throws {ValidationError} For an unknown package.
|
|
109
|
+
*/
|
|
110
|
+
parsePackage(token, packages) {
|
|
111
|
+
const pkg = /^\d+$/.test(token)
|
|
112
|
+
? packages[Number(token) - 1]
|
|
113
|
+
: findPackage(packages, token);
|
|
114
|
+
if (!pkg) {
|
|
115
|
+
throw new ValidationError(`Unknown package "${token}".`);
|
|
116
|
+
}
|
|
117
|
+
return pkg.name;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Parse a bump, patch by default.
|
|
121
|
+
*
|
|
122
|
+
* @param answer Bump name.
|
|
123
|
+
* @return Bump.
|
|
124
|
+
* @throws {ValidationError} For an unknown bump.
|
|
125
|
+
*/
|
|
126
|
+
parseBump(answer) {
|
|
127
|
+
const bump = answer.trim().toLowerCase() || 'patch';
|
|
128
|
+
if (!CHANGESET_BUMPS.includes(bump)) {
|
|
129
|
+
throw new ValidationError(`Invalid bump "${bump}"; expected patch, minor or major.`);
|
|
130
|
+
}
|
|
131
|
+
return bump;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Parse a release note.
|
|
135
|
+
*
|
|
136
|
+
* @param answer Release note.
|
|
137
|
+
* @return Trimmed release note.
|
|
138
|
+
* @throws {ValidationError} For an empty release note.
|
|
139
|
+
*/
|
|
140
|
+
parseSummary(answer) {
|
|
141
|
+
const summary = answer.trim();
|
|
142
|
+
if (!summary) {
|
|
143
|
+
throw new ValidationError('The release note must not be empty.');
|
|
144
|
+
}
|
|
145
|
+
return summary;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Ask a question until the answer parses, printing why it didn't.
|
|
149
|
+
*
|
|
150
|
+
* @param question Question text.
|
|
151
|
+
* @param parse Parser that throws a ValidationError for invalid answers.
|
|
152
|
+
* @return Parsed answer.
|
|
153
|
+
*/
|
|
154
|
+
async askUntilValid(question, parse) {
|
|
155
|
+
let parsed;
|
|
156
|
+
while (undefined === parsed) {
|
|
157
|
+
const answer = await this.readAnswer(question);
|
|
158
|
+
try {
|
|
159
|
+
parsed = { answer: parse(answer) };
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (!(error instanceof ValidationError)) {
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
this.terminal.output.write(`${error.message}\n`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return parsed.answer;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Write a question and read the next line of the input.
|
|
172
|
+
*
|
|
173
|
+
* @param question Question text.
|
|
174
|
+
* @return Answer line.
|
|
175
|
+
* @throws {ValidationError} If the input ended.
|
|
176
|
+
*/
|
|
177
|
+
async readAnswer(question) {
|
|
178
|
+
this.terminal.output.write(question);
|
|
179
|
+
const line = await this.getLines().next();
|
|
180
|
+
if (line.done) {
|
|
181
|
+
throw new ValidationError('The input ended before the changeset was complete.');
|
|
182
|
+
}
|
|
183
|
+
return line.value;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get the lines of the input, reading it from the first question on. The
|
|
187
|
+
* interface stays in line mode, so the terminal echoes and edits the
|
|
188
|
+
* answers and Ctrl+C ends the process.
|
|
189
|
+
*
|
|
190
|
+
* @return Line iterator.
|
|
191
|
+
*/
|
|
192
|
+
getLines() {
|
|
193
|
+
if (!this.lines) {
|
|
194
|
+
this.readline = createInterface({
|
|
195
|
+
input: this.terminal.input,
|
|
196
|
+
terminal: false,
|
|
197
|
+
crlfDelay: Infinity,
|
|
198
|
+
});
|
|
199
|
+
this.lines = this.readline[Symbol.asyncIterator]();
|
|
200
|
+
}
|
|
201
|
+
return this.lines;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -10,7 +10,7 @@ export interface ChangesetReleaseOptions extends ChangesetRepository {
|
|
|
10
10
|
git?: GitRunner;
|
|
11
11
|
}
|
|
12
12
|
export interface ChangesetAddOptions {
|
|
13
|
-
/** Bump of each package. */
|
|
13
|
+
/** Bump of each package, by package identifier or path under the configured root. */
|
|
14
14
|
packages: Record<string, ChangesetBump>;
|
|
15
15
|
/** Changelog summary. */
|
|
16
16
|
summary: string;
|
|
@@ -20,10 +20,22 @@ export interface ChangesetAddOptions {
|
|
|
20
20
|
export interface ChangesetVersionOptions {
|
|
21
21
|
dryRun?: boolean;
|
|
22
22
|
date?: string;
|
|
23
|
-
/** Version only these packages, leaving the other changesets pending. */
|
|
23
|
+
/** Version only these packages, by identifier or path under the configured root, leaving the other changesets pending. */
|
|
24
24
|
packages?: string[];
|
|
25
25
|
/** Whether the selected sources release their configured dependents; defaults to true. */
|
|
26
26
|
dependents?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Release pre-releases of the next versions, with this identifier and an
|
|
29
|
+
* optional number, like rc or rc.1, numbered from the package tags without one.
|
|
30
|
+
*/
|
|
31
|
+
prerelease?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Release snapshots of the next versions, like 1.3.0-pr-251.20261012183000,
|
|
34
|
+
* with this identifier and the UTC time, for test builds that aren't committed.
|
|
35
|
+
*/
|
|
36
|
+
snapshot?: string;
|
|
37
|
+
/** Time of a snapshot, now by default. */
|
|
38
|
+
snapshotTime?: Date;
|
|
27
39
|
}
|
|
28
40
|
export type ChangesetVersionInput = ChangesetRepository & ChangesetVersionOptions;
|
|
29
41
|
/** Versioned releases, the applied changesets and whether files were changed. */
|
|
@@ -38,7 +50,7 @@ export interface ChangesetTagOptions {
|
|
|
38
50
|
untagged?: boolean;
|
|
39
51
|
push?: boolean;
|
|
40
52
|
since?: string;
|
|
41
|
-
/** Tag only these packages. */
|
|
53
|
+
/** Tag only these packages, by identifier or path under the configured root. */
|
|
42
54
|
packages?: string[];
|
|
43
55
|
/** Expected version of the single package in `packages`. */
|
|
44
56
|
version?: string;
|
|
@@ -61,8 +73,12 @@ export interface ChangesetTagResult {
|
|
|
61
73
|
export interface ChangesetStatusOptions {
|
|
62
74
|
/** Base ref to diff against, origin/<baseBranch> by default. */
|
|
63
75
|
since?: string;
|
|
76
|
+
/** Whether to include uncommitted and untracked files, like before adding a changeset. */
|
|
77
|
+
uncommitted?: boolean;
|
|
64
78
|
}
|
|
65
79
|
export interface ChangesetStatusResult {
|
|
80
|
+
/** Base ref that the changed files were compared with, if any. */
|
|
81
|
+
since?: string;
|
|
66
82
|
changesets: Changeset[];
|
|
67
83
|
releases: PackageRelease[];
|
|
68
84
|
changedPackages: string[];
|
|
@@ -135,6 +151,25 @@ export declare class ChangesetRelease {
|
|
|
135
151
|
* @throws {ValidationError} Without a config.
|
|
136
152
|
*/
|
|
137
153
|
getStatus(options?: ChangesetStatusOptions): Promise<ChangesetStatusResult>;
|
|
154
|
+
/**
|
|
155
|
+
* Turn the next versions of releases into pre-releases, like 1.3.0-rc.1
|
|
156
|
+
* for 1.3.0, numbered from the existing tags of each package. Dependents
|
|
157
|
+
* become pre-releases too, so they bundle the pre-releases of their sources.
|
|
158
|
+
*
|
|
159
|
+
* @param context Release context.
|
|
160
|
+
* @param releases Releases with their stable next versions.
|
|
161
|
+
* @param prerelease Pre-release identifier with an optional number, like rc or rc.1.
|
|
162
|
+
* @return Releases with their pre-release next versions.
|
|
163
|
+
* @throws {ValidationError} For an invalid pre-release or an existing version.
|
|
164
|
+
*/
|
|
165
|
+
private toPrereleases;
|
|
166
|
+
/**
|
|
167
|
+
* List the versions of the tags with a prefix.
|
|
168
|
+
*
|
|
169
|
+
* @param prefix Tag prefix of a package, like `plugins/alpha/`.
|
|
170
|
+
* @return Tagged versions.
|
|
171
|
+
*/
|
|
172
|
+
private listTaggedVersions;
|
|
138
173
|
/**
|
|
139
174
|
* Write the versions, dependency specs and changelogs of the releases and
|
|
140
175
|
* consume the applied changesets, restoring every changed file when a
|