@wpelevator/wp-release 0.10.0 → 0.11.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 +61 -4
- package/dist/changelog.d.ts +45 -0
- package/dist/changelog.js +246 -0
- package/dist/changeset-dependents.d.ts +132 -0
- package/dist/changeset-dependents.js +540 -0
- package/dist/changeset-files.d.ts +41 -0
- package/dist/changeset-files.js +100 -0
- package/dist/changeset-packages.d.ts +59 -0
- package/dist/changeset-packages.js +186 -0
- package/dist/changeset-release.d.ts +153 -19
- package/dist/changeset-release.js +359 -1685
- package/dist/commands/changeset-args.d.ts +0 -4
- package/dist/commands/changeset-args.js +0 -5
- package/dist/commands/changeset-tag.d.ts +0 -3
- package/dist/commands/changeset-tag.js +8 -10
- package/dist/file-journal.d.ts +22 -0
- package/dist/file-journal.js +56 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/json-files.d.ts +15 -0
- package/dist/json-files.js +35 -0
- package/dist/package-meta.js +1 -3
- package/dist/regexp.d.ts +7 -0
- package/dist/regexp.js +9 -0
- package/dist/release-repository.d.ts +102 -0
- package/dist/release-repository.js +159 -0
- package/dist/release-tags.d.ts +56 -0
- package/dist/release-tags.js +89 -0
- package/dist/tag-format.d.ts +20 -0
- package/dist/tag-format.js +51 -0
- package/dist/version-check.js +1 -3
- package/dist/version-files.d.ts +36 -0
- package/dist/version-files.js +195 -0
- package/dist/versions.d.ts +25 -0
- package/dist/versions.js +74 -0
- package/package.json +1 -1
|
@@ -104,9 +104,6 @@ export declare const changesetTagArgs: {
|
|
|
104
104
|
readonly untagged: {
|
|
105
105
|
readonly type: "boolean";
|
|
106
106
|
};
|
|
107
|
-
readonly 'github-release': {
|
|
108
|
-
readonly type: "boolean";
|
|
109
|
-
};
|
|
110
107
|
readonly since: {
|
|
111
108
|
readonly type: "string";
|
|
112
109
|
};
|
|
@@ -123,7 +120,6 @@ export interface ChangesetTagArgs {
|
|
|
123
120
|
push: boolean;
|
|
124
121
|
dryRun: boolean;
|
|
125
122
|
untagged: boolean;
|
|
126
|
-
githubRelease: boolean;
|
|
127
123
|
since?: string;
|
|
128
124
|
packages: string[];
|
|
129
125
|
version?: string;
|
|
@@ -122,7 +122,6 @@ export const changesetTagArgs = {
|
|
|
122
122
|
push: { type: 'boolean' },
|
|
123
123
|
'dry-run': { type: 'boolean' },
|
|
124
124
|
untagged: { type: 'boolean' },
|
|
125
|
-
'github-release': { type: 'boolean' },
|
|
126
125
|
since: { type: 'string' },
|
|
127
126
|
package: { type: 'string', multiple: true },
|
|
128
127
|
version: { type: 'string' },
|
|
@@ -138,7 +137,6 @@ export function parseChangesetTagArgs(values) {
|
|
|
138
137
|
push: getBoolean(values, 'push'),
|
|
139
138
|
dryRun: getBoolean(values, 'dry-run'),
|
|
140
139
|
untagged: getBoolean(values, 'untagged'),
|
|
141
|
-
githubRelease: getBoolean(values, 'github-release'),
|
|
142
140
|
since: getString(values, 'since'),
|
|
143
141
|
packages: getStrings(values, 'package'),
|
|
144
142
|
version: getString(values, 'version')?.trim(),
|
|
@@ -155,9 +153,6 @@ export function parseChangesetTagArgs(values) {
|
|
|
155
153
|
if (undefined !== args.version && 1 !== args.packages.length) {
|
|
156
154
|
throw new ValidationError('--version <version> requires exactly one --package <name>.');
|
|
157
155
|
}
|
|
158
|
-
if (args.githubRelease && !args.push) {
|
|
159
|
-
throw new ValidationError('--github-release requires --push.');
|
|
160
|
-
}
|
|
161
156
|
if (args.untagged && args.push) {
|
|
162
157
|
throw new ValidationError('--untagged cannot be combined with --push.');
|
|
163
158
|
}
|
|
@@ -3,7 +3,7 @@ import { parseChangesetTagArgs, changesetTagArgs } from "./changeset-args.js";
|
|
|
3
3
|
import { getChangesetTagSummary, getTagStatus } from "../summary.js";
|
|
4
4
|
export default {
|
|
5
5
|
name: 'changeset-tag',
|
|
6
|
-
usage: 'changeset-tag [--dry-run] [--untagged] [--since <ref>] [--package <name>] [--version <version>] [--push]
|
|
6
|
+
usage: 'changeset-tag [--dry-run] [--untagged] [--since <ref>] [--package <name>] [--version <version>] [--push]',
|
|
7
7
|
summary: 'Create missing release tags for configured package versions.',
|
|
8
8
|
help: `Options:
|
|
9
9
|
--dry-run List missing tags and their release notes without
|
|
@@ -13,16 +13,14 @@ export default {
|
|
|
13
13
|
--package <name> Tag only this package (repeatable)
|
|
14
14
|
--version <version> Fail unless the single --package is at this version;
|
|
15
15
|
the version to tag for packages versioned by git tags
|
|
16
|
-
--push Push newly created tags to origin
|
|
17
|
-
--github-release Create a GitHub Release for each tag, marked as a
|
|
18
|
-
pre-release for versions like 1.0.0-rc.1
|
|
16
|
+
--push Push newly created tags to origin, one at a time
|
|
19
17
|
|
|
20
|
-
Tags are created only when absent,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
that still need a tag, for
|
|
25
|
-
workflow.`,
|
|
18
|
+
Tags are created only when absent, so rerunning after a partial failure is
|
|
19
|
+
safe. Before a tag is created, every configured version target must match the
|
|
20
|
+
version. With --json or --github-output, each tag comes with the title, notes
|
|
21
|
+
and pre-release flag of its release, for a workflow step that creates the
|
|
22
|
+
GitHub Release. --untagged lists only the releases that still need a tag, for
|
|
23
|
+
example to release each of them with a separate workflow.`,
|
|
26
24
|
options: changesetTagArgs,
|
|
27
25
|
async run({ values, cwd, configFile, logger }) {
|
|
28
26
|
const args = parseChangesetTagArgs(values);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Original contents of the files that a versioning run changes, so that a
|
|
3
|
+
* failed run can put every file back the way it was.
|
|
4
|
+
*/
|
|
5
|
+
export declare class FileJournal {
|
|
6
|
+
/** Original contents by file, undefined for files that didn't exist. */
|
|
7
|
+
private readonly files;
|
|
8
|
+
/**
|
|
9
|
+
* Remember the contents of a file before its first change. Later calls
|
|
10
|
+
* for the same file keep the original contents.
|
|
11
|
+
*
|
|
12
|
+
* @param file File about to change.
|
|
13
|
+
*/
|
|
14
|
+
remember(file: string): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Put the remembered files back in reverse order, removing the files that
|
|
17
|
+
* didn't exist. Every file is attempted, even after a failure.
|
|
18
|
+
*
|
|
19
|
+
* @return Errors of the files that couldn't be restored.
|
|
20
|
+
*/
|
|
21
|
+
restore(): Promise<Error[]>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
/**
|
|
3
|
+
* Original contents of the files that a versioning run changes, so that a
|
|
4
|
+
* failed run can put every file back the way it was.
|
|
5
|
+
*/
|
|
6
|
+
export class FileJournal {
|
|
7
|
+
/** Original contents by file, undefined for files that didn't exist. */
|
|
8
|
+
files = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Remember the contents of a file before its first change. Later calls
|
|
11
|
+
* for the same file keep the original contents.
|
|
12
|
+
*
|
|
13
|
+
* @param file File about to change.
|
|
14
|
+
*/
|
|
15
|
+
async remember(file) {
|
|
16
|
+
if (this.files.has(file)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
this.files.set(file, await readFile(file, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if ('ENOENT' !== error.code) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
this.files.set(file, undefined);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Put the remembered files back in reverse order, removing the files that
|
|
31
|
+
* didn't exist. Every file is attempted, even after a failure.
|
|
32
|
+
*
|
|
33
|
+
* @return Errors of the files that couldn't be restored.
|
|
34
|
+
*/
|
|
35
|
+
async restore() {
|
|
36
|
+
const errors = [];
|
|
37
|
+
for (const [file, contents] of [
|
|
38
|
+
...this.files.entries(),
|
|
39
|
+
].reverse()) {
|
|
40
|
+
try {
|
|
41
|
+
if (undefined === contents) {
|
|
42
|
+
await rm(file, { force: true });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
await writeFile(file, contents);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
errors.push(error instanceof Error
|
|
50
|
+
? error
|
|
51
|
+
: new Error(String(error)));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return errors;
|
|
55
|
+
}
|
|
56
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export { isUrl, downloadFile } from './download.ts';
|
|
|
15
15
|
export { readZipEntries, getZipEntryErrors, extractZip } from './unzip.ts';
|
|
16
16
|
export { GIT_TAG_FORMAT, VersionMismatchError, getGitTagVersion, findConstantValues, findConstantDefinitions, findVersionMismatches, getVersionMismatchProblems, } from './version-check.ts';
|
|
17
17
|
export { CHANGESET_BUMPS, parseChangeset, formatChangeset, formatChangelogEntries, formatChangelogList, getChangesetBump, getNextVersion, } from './changesets.ts';
|
|
18
|
-
export { addChangeset, getChangesetPackages, getChangesetReleasePlan, getChangesetStatus, versionChangesets, tagChangesets, } from './changeset-release.ts';
|
|
18
|
+
export { ChangesetRelease, addChangeset, getChangesetPackages, getChangesetReleasePlan, getChangesetStatus, versionChangesets, tagChangesets, } from './changeset-release.ts';
|
|
19
19
|
export { defineConfig, packageJsonVersion, composerJsonVersion, wordpressPluginVersion, wordpressThemeVersion, wordpressStableTag, gitTagVersion, phpConstantVersion, loadChangesetConfig, } from './changeset-config.ts';
|
|
20
20
|
export { Svn, SvnError, createSvnRunner, parseStatus, toSvnPath, } from './svn.ts';
|
|
21
21
|
export { syncDirectory, copyFileIfChanged } from './sync.ts';
|
|
@@ -42,7 +42,7 @@ export type { CreateZipInput } from './zip.ts';
|
|
|
42
42
|
export type { ExtractZipInput, ZipEntryInfo, ZipEntryType } from './unzip.ts';
|
|
43
43
|
export type { FindVersionMismatchesInput, GitTagInput, VersionCheckResult, VersionMismatch, } from './version-check.ts';
|
|
44
44
|
export type { Changeset, ChangesetBump, ChangesetPackages, ChangesetConfig, ChangesetPackageType, ChangesetPackage, PackageRelease, VersionedRelease, ChangesetReleasePlan, } from './changesets.ts';
|
|
45
|
-
export type { ChangesetRepository, ChangesetVersionInput, VersionChangesetsResult, ChangesetTagInput, ChangesetTagResult, } from './changeset-release.ts';
|
|
45
|
+
export type { ChangesetRepository, ChangesetReleaseOptions, ChangesetAddOptions, ChangesetVersionOptions, ChangesetTagOptions, ChangesetStatusOptions, ChangesetStatusResult, ChangesetVersionInput, VersionChangesetsResult, ChangesetTagInput, ChangesetTagResult, } from './changeset-release.ts';
|
|
46
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';
|
|
47
47
|
export type { SvnCheckoutInput, SvnCommandInput, SvnCommandOutput, SvnCommitInput, SvnCopyInput, SvnDepth, SvnOptions, SvnPathsInput, SvnPropsetInput, SvnRunner, SvnStatusEntry, SvnStatusType, SvnUpdateInput, } from './svn.ts';
|
|
48
48
|
export type { SyncDirectoryInput, SyncResult } from './sync.ts';
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export { isUrl, downloadFile } from "./download.js";
|
|
|
15
15
|
export { readZipEntries, getZipEntryErrors, extractZip } from "./unzip.js";
|
|
16
16
|
export { GIT_TAG_FORMAT, VersionMismatchError, getGitTagVersion, findConstantValues, findConstantDefinitions, findVersionMismatches, getVersionMismatchProblems, } from "./version-check.js";
|
|
17
17
|
export { CHANGESET_BUMPS, parseChangeset, formatChangeset, formatChangelogEntries, formatChangelogList, getChangesetBump, getNextVersion, } from "./changesets.js";
|
|
18
|
-
export { addChangeset, getChangesetPackages, getChangesetReleasePlan, getChangesetStatus, versionChangesets, tagChangesets, } from "./changeset-release.js";
|
|
18
|
+
export { ChangesetRelease, addChangeset, getChangesetPackages, getChangesetReleasePlan, getChangesetStatus, versionChangesets, tagChangesets, } from "./changeset-release.js";
|
|
19
19
|
export { defineConfig, packageJsonVersion, composerJsonVersion, wordpressPluginVersion, wordpressThemeVersion, wordpressStableTag, gitTagVersion, phpConstantVersion, loadChangesetConfig, } from "./changeset-config.js";
|
|
20
20
|
export { Svn, SvnError, createSvnRunner, parseStatus, toSvnPath, } from "./svn.js";
|
|
21
21
|
export { syncDirectory, copyFileIfChanged } from "./sync.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a JSON manifest that has to exist.
|
|
3
|
+
*
|
|
4
|
+
* @param file JSON file.
|
|
5
|
+
* @return Parsed object.
|
|
6
|
+
* @throws {ValidationError} If the file is missing or isn't valid JSON.
|
|
7
|
+
*/
|
|
8
|
+
export declare function readJson(file: string): Promise<Record<string, unknown>>;
|
|
9
|
+
/**
|
|
10
|
+
* Read an optional JSON manifest.
|
|
11
|
+
*
|
|
12
|
+
* @param file JSON file.
|
|
13
|
+
* @return Parsed object, or undefined when the file doesn't exist.
|
|
14
|
+
*/
|
|
15
|
+
export declare function readJsonIfExists(file: string): Promise<Record<string, unknown> | undefined>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { ValidationError } from "./errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Read a JSON manifest that has to exist.
|
|
5
|
+
*
|
|
6
|
+
* @param file JSON file.
|
|
7
|
+
* @return Parsed object.
|
|
8
|
+
* @throws {ValidationError} If the file is missing or isn't valid JSON.
|
|
9
|
+
*/
|
|
10
|
+
export async function readJson(file) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(await readFile(file, 'utf8'));
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16
|
+
throw new ValidationError(`Could not read JSON manifest ${file}: ${message}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Read an optional JSON manifest.
|
|
21
|
+
*
|
|
22
|
+
* @param file JSON file.
|
|
23
|
+
* @return Parsed object, or undefined when the file doesn't exist.
|
|
24
|
+
*/
|
|
25
|
+
export async function readJsonIfExists(file) {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(await readFile(file, 'utf8'));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if ('ENOENT' !== error.code) {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
package/dist/package-meta.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { open, readdir, readFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { isErrorCode } from "./file-selection.js";
|
|
4
|
+
import { escapeRegExp } from "./regexp.js";
|
|
4
5
|
/**
|
|
5
6
|
* Number of bytes that WordPress reads from a file to parse its headers.
|
|
6
7
|
*/
|
|
@@ -112,9 +113,6 @@ export function findFileHeaderLine(contents, header) {
|
|
|
112
113
|
export function getLineNumber(contents, index) {
|
|
113
114
|
return contents.slice(0, index).split('\n').length;
|
|
114
115
|
}
|
|
115
|
-
function escapeRegExp(text) {
|
|
116
|
-
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
117
|
-
}
|
|
118
116
|
/**
|
|
119
117
|
* Parse the headers of a readme.txt or readme.md, the "Key: value" lines
|
|
120
118
|
* between the title and the first section. Markdown emphasis around the
|
package/dist/regexp.d.ts
ADDED
package/dist/regexp.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { ReleaseCommit } from './changeset-config.ts';
|
|
2
|
+
import type { GitRunner } from './git.ts';
|
|
3
|
+
export interface ReleaseRepositoryOptions {
|
|
4
|
+
/** Repository root that relative paths resolve against. */
|
|
5
|
+
cwd: string;
|
|
6
|
+
/** Git runner, spawning git by default. */
|
|
7
|
+
runner?: GitRunner;
|
|
8
|
+
}
|
|
9
|
+
export interface ChangedFilesOptions {
|
|
10
|
+
/** Directory to limit the diff to. */
|
|
11
|
+
directory?: string;
|
|
12
|
+
/** Value of --diff-filter, like ACMR for files that still exist. */
|
|
13
|
+
diffFilter?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The git queries and writes of a changeset release: tags, the commits of a
|
|
17
|
+
* package and the files changed since a ref.
|
|
18
|
+
*
|
|
19
|
+
* Every command runs through the runner, so that tests can replace git, and
|
|
20
|
+
* fails with a ValidationError that names the command.
|
|
21
|
+
*/
|
|
22
|
+
export declare class ReleaseRepository {
|
|
23
|
+
readonly cwd: string;
|
|
24
|
+
readonly runner: GitRunner;
|
|
25
|
+
/**
|
|
26
|
+
* @param options Repository root and git runner.
|
|
27
|
+
*/
|
|
28
|
+
constructor(options: ReleaseRepositoryOptions);
|
|
29
|
+
/**
|
|
30
|
+
* Run a git command in the repository. The repository is marked as a safe
|
|
31
|
+
* directory, because CI containers often check it out as another user.
|
|
32
|
+
*
|
|
33
|
+
* @param args Arguments after "git".
|
|
34
|
+
* @return Standard output.
|
|
35
|
+
* @throws {ValidationError} If the command fails.
|
|
36
|
+
*/
|
|
37
|
+
run(args: string[]): Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Get the path of a file relative to the repository root, with forward
|
|
40
|
+
* slashes as git expects.
|
|
41
|
+
*
|
|
42
|
+
* @param file Absolute or relative file path.
|
|
43
|
+
* @return Relative path, empty for the root itself.
|
|
44
|
+
*/
|
|
45
|
+
relativePath(file: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Check whether a tag exists.
|
|
48
|
+
*
|
|
49
|
+
* @param tag Tag name.
|
|
50
|
+
* @return Whether the tag exists.
|
|
51
|
+
*/
|
|
52
|
+
tagExists(tag: string): Promise<boolean>;
|
|
53
|
+
/**
|
|
54
|
+
* List the tags that start with a prefix.
|
|
55
|
+
*
|
|
56
|
+
* @param prefix Tag prefix, like `plugins/alpha/`.
|
|
57
|
+
* @return Tag names.
|
|
58
|
+
*/
|
|
59
|
+
listTags(prefix: string): Promise<string[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Tag the current commit.
|
|
62
|
+
*
|
|
63
|
+
* @param tag Tag name.
|
|
64
|
+
*/
|
|
65
|
+
createTag(tag: string): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Push a tag to the origin remote.
|
|
68
|
+
*
|
|
69
|
+
* @param tag Tag name.
|
|
70
|
+
*/
|
|
71
|
+
pushTag(tag: string): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Find the commit that added a file.
|
|
74
|
+
*
|
|
75
|
+
* @param file File path.
|
|
76
|
+
* @return Commit, or undefined when the file isn't committed.
|
|
77
|
+
*/
|
|
78
|
+
getAddedCommit(file: string): Promise<ReleaseCommit | undefined>;
|
|
79
|
+
/**
|
|
80
|
+
* List the commits of a revision range that changed a directory.
|
|
81
|
+
*
|
|
82
|
+
* @param revision Revision range, like `plugins/alpha/1.0.0..HEAD`.
|
|
83
|
+
* @param directory Directory the commits changed.
|
|
84
|
+
* @return Commits, newest first.
|
|
85
|
+
*/
|
|
86
|
+
getDirectoryCommits(revision: string, directory: string): Promise<ReleaseCommit[]>;
|
|
87
|
+
/**
|
|
88
|
+
* List the files changed between the merge base of a ref and HEAD.
|
|
89
|
+
*
|
|
90
|
+
* @param since Base ref, like origin/main.
|
|
91
|
+
* @param options Directory and diff filter.
|
|
92
|
+
* @return Changed files relative to the repository root.
|
|
93
|
+
*/
|
|
94
|
+
getChangedFiles(since: string, options?: ChangedFilesOptions): Promise<string[]>;
|
|
95
|
+
/**
|
|
96
|
+
* Run git log and parse the commits.
|
|
97
|
+
*
|
|
98
|
+
* @param args Arguments after the format option.
|
|
99
|
+
* @return Commits, newest first.
|
|
100
|
+
*/
|
|
101
|
+
private getCommits;
|
|
102
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ValidationError } from "./errors.js";
|
|
3
|
+
import { createGitRunner } from "./git.js";
|
|
4
|
+
/**
|
|
5
|
+
* The git queries and writes of a changeset release: tags, the commits of a
|
|
6
|
+
* package and the files changed since a ref.
|
|
7
|
+
*
|
|
8
|
+
* Every command runs through the runner, so that tests can replace git, and
|
|
9
|
+
* fails with a ValidationError that names the command.
|
|
10
|
+
*/
|
|
11
|
+
export class ReleaseRepository {
|
|
12
|
+
cwd;
|
|
13
|
+
runner;
|
|
14
|
+
/**
|
|
15
|
+
* @param options Repository root and git runner.
|
|
16
|
+
*/
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.cwd = path.resolve(options.cwd);
|
|
19
|
+
this.runner = options.runner ?? createGitRunner();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run a git command in the repository. The repository is marked as a safe
|
|
23
|
+
* directory, because CI containers often check it out as another user.
|
|
24
|
+
*
|
|
25
|
+
* @param args Arguments after "git".
|
|
26
|
+
* @return Standard output.
|
|
27
|
+
* @throws {ValidationError} If the command fails.
|
|
28
|
+
*/
|
|
29
|
+
async run(args) {
|
|
30
|
+
try {
|
|
31
|
+
const { stdout } = await this.runner({
|
|
32
|
+
args: ['-c', `safe.directory=${this.cwd}`, ...args],
|
|
33
|
+
cwd: this.cwd,
|
|
34
|
+
});
|
|
35
|
+
return stdout;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
39
|
+
throw new ValidationError(`Git command failed (${args.join(' ')}): ${message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get the path of a file relative to the repository root, with forward
|
|
44
|
+
* slashes as git expects.
|
|
45
|
+
*
|
|
46
|
+
* @param file Absolute or relative file path.
|
|
47
|
+
* @return Relative path, empty for the root itself.
|
|
48
|
+
*/
|
|
49
|
+
relativePath(file) {
|
|
50
|
+
return path.relative(this.cwd, file).split(path.sep).join('/');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check whether a tag exists.
|
|
54
|
+
*
|
|
55
|
+
* @param tag Tag name.
|
|
56
|
+
* @return Whether the tag exists.
|
|
57
|
+
*/
|
|
58
|
+
async tagExists(tag) {
|
|
59
|
+
const output = await this.run(['tag', '--list', tag]);
|
|
60
|
+
return output.trim() === tag;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* List the tags that start with a prefix.
|
|
64
|
+
*
|
|
65
|
+
* @param prefix Tag prefix, like `plugins/alpha/`.
|
|
66
|
+
* @return Tag names.
|
|
67
|
+
*/
|
|
68
|
+
async listTags(prefix) {
|
|
69
|
+
const output = await this.run(['tag', '--list', `${prefix}*`]);
|
|
70
|
+
return output.trim().split(/\r?\n/).filter(Boolean);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Tag the current commit.
|
|
74
|
+
*
|
|
75
|
+
* @param tag Tag name.
|
|
76
|
+
*/
|
|
77
|
+
async createTag(tag) {
|
|
78
|
+
await this.run(['tag', tag]);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Push a tag to the origin remote.
|
|
82
|
+
*
|
|
83
|
+
* @param tag Tag name.
|
|
84
|
+
*/
|
|
85
|
+
async pushTag(tag) {
|
|
86
|
+
await this.run(['push', 'origin', tag]);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Find the commit that added a file.
|
|
90
|
+
*
|
|
91
|
+
* @param file File path.
|
|
92
|
+
* @return Commit, or undefined when the file isn't committed.
|
|
93
|
+
*/
|
|
94
|
+
async getAddedCommit(file) {
|
|
95
|
+
const [commit] = await this.getCommits([
|
|
96
|
+
'--diff-filter=A',
|
|
97
|
+
'-1',
|
|
98
|
+
'--',
|
|
99
|
+
this.relativePath(file),
|
|
100
|
+
]);
|
|
101
|
+
return commit;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* List the commits of a revision range that changed a directory.
|
|
105
|
+
*
|
|
106
|
+
* @param revision Revision range, like `plugins/alpha/1.0.0..HEAD`.
|
|
107
|
+
* @param directory Directory the commits changed.
|
|
108
|
+
* @return Commits, newest first.
|
|
109
|
+
*/
|
|
110
|
+
async getDirectoryCommits(revision, directory) {
|
|
111
|
+
return this.getCommits([
|
|
112
|
+
revision,
|
|
113
|
+
'--',
|
|
114
|
+
this.relativePath(directory) || '.',
|
|
115
|
+
]);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* List the files changed between the merge base of a ref and HEAD.
|
|
119
|
+
*
|
|
120
|
+
* @param since Base ref, like origin/main.
|
|
121
|
+
* @param options Directory and diff filter.
|
|
122
|
+
* @return Changed files relative to the repository root.
|
|
123
|
+
*/
|
|
124
|
+
async getChangedFiles(since, options = {}) {
|
|
125
|
+
const output = await this.run([
|
|
126
|
+
'diff',
|
|
127
|
+
'--name-only',
|
|
128
|
+
...(options.diffFilter
|
|
129
|
+
? [`--diff-filter=${options.diffFilter}`]
|
|
130
|
+
: []),
|
|
131
|
+
`${since}...HEAD`,
|
|
132
|
+
...(options.directory
|
|
133
|
+
? ['--', this.relativePath(options.directory)]
|
|
134
|
+
: []),
|
|
135
|
+
]);
|
|
136
|
+
return output.trim().split(/\r?\n/).filter(Boolean);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Run git log and parse the commits.
|
|
140
|
+
*
|
|
141
|
+
* @param args Arguments after the format option.
|
|
142
|
+
* @return Commits, newest first.
|
|
143
|
+
*/
|
|
144
|
+
async getCommits(args) {
|
|
145
|
+
const output = await this.run([
|
|
146
|
+
'log',
|
|
147
|
+
'--format=%H%x1f%h%x1f%s',
|
|
148
|
+
...args,
|
|
149
|
+
]);
|
|
150
|
+
return output
|
|
151
|
+
.trim()
|
|
152
|
+
.split(/\r?\n/)
|
|
153
|
+
.filter(Boolean)
|
|
154
|
+
.map((line) => {
|
|
155
|
+
const [hash = '', shortHash = '', subject = ''] = line.split('\x1f');
|
|
156
|
+
return { hash, shortHash, subject };
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ChangesetConfig } from './changeset-config.ts';
|
|
2
|
+
import type { ConfiguredPackage } from './changeset-packages.ts';
|
|
3
|
+
import type { ReleaseRepository } from './release-repository.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Version, tag and previous tag of a package release.
|
|
6
|
+
*/
|
|
7
|
+
export interface ReleaseTag {
|
|
8
|
+
/** Released version. */
|
|
9
|
+
version: string;
|
|
10
|
+
/** Release tag. */
|
|
11
|
+
tag: string;
|
|
12
|
+
/** Tag of the previous release, if any. */
|
|
13
|
+
previousTag?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Pick the packages to tag.
|
|
17
|
+
*
|
|
18
|
+
* @param packages Configured packages.
|
|
19
|
+
* @param names Packages passed with --package, empty for all.
|
|
20
|
+
* @param version Expected version passed with --version, if any.
|
|
21
|
+
* @return Selected packages in the configured order.
|
|
22
|
+
* @throws {ValidationError} For unknown packages, or an expected version without a single package.
|
|
23
|
+
*/
|
|
24
|
+
export declare function selectTagPackages(packages: ConfiguredPackage[], names: string[], version?: string): ConfiguredPackage[];
|
|
25
|
+
/**
|
|
26
|
+
* Get the version to tag. Packages versioned by their git tags have no
|
|
27
|
+
* version file, so the expected version is the version to tag, or else the
|
|
28
|
+
* latest CHANGELOG.md version.
|
|
29
|
+
*
|
|
30
|
+
* @param pkg Configured package.
|
|
31
|
+
* @param expected Expected version from --version, if any.
|
|
32
|
+
* @return Version to tag.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getTagVersion(pkg: ConfiguredPackage, expected?: string): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* Find the tag of the newest release before a version.
|
|
37
|
+
*
|
|
38
|
+
* @param repository Repository with the tags.
|
|
39
|
+
* @param prefix Tag prefix of the package, like `plugins/alpha/`.
|
|
40
|
+
* @param version Version being released.
|
|
41
|
+
* @return Previous tag, or undefined for the first release.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getPreviousTag(repository: ReleaseRepository, prefix: string, version: string): Promise<string | undefined>;
|
|
44
|
+
/**
|
|
45
|
+
* Build the release notes of a tag from its CHANGELOG.md section, or from the
|
|
46
|
+
* commits since the previous release without one, with the configured
|
|
47
|
+
* formatter.
|
|
48
|
+
*
|
|
49
|
+
* @param repository Repository with the release commits.
|
|
50
|
+
* @param config Repository release configuration.
|
|
51
|
+
* @param pkg Released package.
|
|
52
|
+
* @param release Version, tag and previous tag of the release.
|
|
53
|
+
* @param end Commit of the release: the tag, or HEAD before tagging.
|
|
54
|
+
* @return Release notes.
|
|
55
|
+
*/
|
|
56
|
+
export declare function getReleaseNotes(repository: ReleaseRepository, config: ChangesetConfig, pkg: ConfiguredPackage, release: ReleaseTag, end: string): Promise<string>;
|