@wpelevator/wp-release 0.4.1 → 0.6.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.
Files changed (44) hide show
  1. package/README.md +136 -6
  2. package/dist/changeset-config.d.ts +189 -0
  3. package/dist/changeset-config.js +273 -0
  4. package/dist/changeset-release.d.ts +74 -0
  5. package/dist/changeset-release.js +829 -0
  6. package/dist/changesets.d.ts +72 -0
  7. package/dist/changesets.js +103 -0
  8. package/dist/cli.d.ts +12 -5
  9. package/dist/cli.js +78 -18
  10. package/dist/commands/changeset-add.d.ts +37 -0
  11. package/dist/commands/changeset-add.js +35 -0
  12. package/dist/commands/changeset-args.d.ts +114 -0
  13. package/dist/commands/changeset-args.js +139 -0
  14. package/dist/commands/changeset-status.d.ts +43 -0
  15. package/dist/commands/changeset-status.js +65 -0
  16. package/dist/commands/changeset-tag.d.ts +25 -0
  17. package/dist/commands/changeset-tag.js +27 -0
  18. package/dist/commands/changeset-version.d.ts +38 -0
  19. package/dist/commands/changeset-version.js +39 -0
  20. package/dist/commands/checksums.js +2 -0
  21. package/dist/commands/command.d.ts +2 -0
  22. package/dist/commands/sign.js +2 -0
  23. package/dist/commands/verify.js +61 -12
  24. package/dist/commands/version-check.js +5 -2
  25. package/dist/commands/zip.js +7 -7
  26. package/dist/errors.d.ts +18 -0
  27. package/dist/errors.js +8 -0
  28. package/dist/github-actions-writer.d.ts +52 -0
  29. package/dist/github-actions-writer.js +94 -0
  30. package/dist/index.d.ts +15 -4
  31. package/dist/index.js +8 -3
  32. package/dist/logger.d.ts +88 -16
  33. package/dist/logger.js +73 -25
  34. package/dist/package-meta.d.ts +26 -0
  35. package/dist/package-meta.js +65 -10
  36. package/dist/report.d.ts +22 -0
  37. package/dist/report.js +35 -0
  38. package/dist/summary.d.ts +141 -0
  39. package/dist/summary.js +287 -0
  40. package/dist/svn-deploy.d.ts +2 -1
  41. package/dist/svn-deploy.js +8 -6
  42. package/dist/version-check.d.ts +39 -1
  43. package/dist/version-check.js +59 -5
  44. package/package.json +1 -1
@@ -0,0 +1,139 @@
1
+ import { ValidationError } from "../errors.js";
2
+ import { CHANGESET_BUMPS } from "../changesets.js";
3
+ import { getBoolean, getString, getStrings } from "./command.js";
4
+ /** Parse-args options for `changeset-add`. */
5
+ export const changesetAddArgs = {
6
+ package: { type: 'string', multiple: true },
7
+ bump: { type: 'string', multiple: true },
8
+ summary: { type: 'string' },
9
+ empty: { type: 'boolean' },
10
+ name: { type: 'string' },
11
+ };
12
+ /**
13
+ * Parse and validate the `changeset-add` options.
14
+ *
15
+ * @param values Parsed CLI options.
16
+ * @return Normalized changeset input.
17
+ * @throws {ValidationError} If required options are missing or conflicting.
18
+ */
19
+ export function parseChangesetAddArgs(values) {
20
+ const names = getStrings(values, 'package');
21
+ const bumps = getStrings(values, 'bump');
22
+ const summary = getString(values, 'summary')?.trim();
23
+ const empty = getBoolean(values, 'empty');
24
+ const name = getString(values, 'name');
25
+ if (empty && (names.length || bumps.length || summary)) {
26
+ throw new ValidationError('--empty cannot be combined with --package, --bump or --summary.');
27
+ }
28
+ if (!empty && (!names.length || names.length !== bumps.length)) {
29
+ throw new ValidationError('Pass matching repeated --package <name> and --bump <patch|minor|major> options, or use --empty.');
30
+ }
31
+ if (!empty && !summary) {
32
+ throw new ValidationError('--summary <text> is required unless --empty is set.');
33
+ }
34
+ if (name && !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name)) {
35
+ throw new ValidationError('--name must contain only letters, numbers, dots, underscores or hyphens.');
36
+ }
37
+ const packages = {};
38
+ names.forEach((packageName, index) => {
39
+ const bump = bumps[index];
40
+ if (Object.hasOwn(packages, packageName)) {
41
+ throw new ValidationError(`Package "${packageName}" was passed more than once.`);
42
+ }
43
+ if (!CHANGESET_BUMPS.includes(bump)) {
44
+ throw new ValidationError(`Invalid bump "${bump}" for package "${packageName}"; expected patch, minor or major.`);
45
+ }
46
+ packages[packageName] = bump;
47
+ });
48
+ return {
49
+ packages,
50
+ summary: summary ?? '',
51
+ empty,
52
+ ...(name ? { name } : {}),
53
+ };
54
+ }
55
+ /** Parse-args options for `changeset-status`. */
56
+ export const changesetStatusArgs = {
57
+ since: { type: 'string' },
58
+ };
59
+ /**
60
+ * Parse the `changeset-status` options.
61
+ *
62
+ * @param values Parsed CLI options.
63
+ * @return Normalized status input.
64
+ * @throws {ValidationError} If --since is empty.
65
+ */
66
+ export function parseChangesetStatusArgs(values) {
67
+ const since = getString(values, 'since');
68
+ if ('' === since?.trim()) {
69
+ throw new ValidationError('--since <ref> must not be empty.');
70
+ }
71
+ return since ? { since } : {};
72
+ }
73
+ /** Parse-args options for `changeset-version`. */
74
+ export const changesetVersionArgs = {
75
+ 'dry-run': { type: 'boolean' },
76
+ date: { type: 'string' },
77
+ };
78
+ /**
79
+ * Validate and normalize a release date option.
80
+ *
81
+ * @param value Value from --date.
82
+ * @return A validated ISO date or undefined.
83
+ * @throws {ValidationError} If the date is not a real YYYY-MM-DD date.
84
+ */
85
+ export function parseChangesetDate(value) {
86
+ if (undefined === value) {
87
+ return undefined;
88
+ }
89
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
90
+ const parsed = new Date(`${value}T00:00:00.000Z`);
91
+ if (!match ||
92
+ Number.isNaN(parsed.getTime()) ||
93
+ parsed.toISOString().slice(0, 10) !== value) {
94
+ throw new ValidationError(`Invalid release date "${value}"; expected a real date in YYYY-MM-DD format.`);
95
+ }
96
+ return value;
97
+ }
98
+ /**
99
+ * Parse the `changeset-version` options.
100
+ *
101
+ * @param values Parsed CLI options.
102
+ * @return Normalized versioning input.
103
+ * @throws {ValidationError} If --date is invalid.
104
+ */
105
+ export function parseChangesetVersionArgs(values) {
106
+ const date = parseChangesetDate(getString(values, 'date'));
107
+ return {
108
+ dryRun: getBoolean(values, 'dry-run'),
109
+ ...(date ? { date } : {}),
110
+ };
111
+ }
112
+ /** Parse-args options for `changeset-tag`. */
113
+ export const changesetTagArgs = {
114
+ push: { type: 'boolean' },
115
+ 'dry-run': { type: 'boolean' },
116
+ 'github-release': { type: 'boolean' },
117
+ since: { type: 'string' },
118
+ };
119
+ /**
120
+ * Parse the `changeset-tag` options.
121
+ *
122
+ * @param values Parsed CLI options.
123
+ * @return Normalized tag input.
124
+ */
125
+ export function parseChangesetTagArgs(values) {
126
+ const args = {
127
+ push: getBoolean(values, 'push'),
128
+ dryRun: getBoolean(values, 'dry-run'),
129
+ githubRelease: getBoolean(values, 'github-release'),
130
+ since: getString(values, 'since'),
131
+ };
132
+ if ('' === args.since?.trim()) {
133
+ throw new ValidationError('--since <ref> must not be empty.');
134
+ }
135
+ if (args.githubRelease && !args.push) {
136
+ throw new ValidationError('--github-release requires --push.');
137
+ }
138
+ return args;
139
+ }
@@ -0,0 +1,43 @@
1
+ export interface ChangesetStatusResult {
2
+ changesets: Array<{
3
+ fileName: string;
4
+ packages: Record<string, string>;
5
+ summary: string;
6
+ }>;
7
+ releases: Array<{
8
+ package: string;
9
+ bump: string;
10
+ currentVersion: string;
11
+ nextVersion: string;
12
+ }>;
13
+ changedPackages: string[];
14
+ changedPackageFiles: Record<string, string[]>;
15
+ missingChangesets: string[];
16
+ }
17
+ declare const _default: {
18
+ name: string;
19
+ usage: string;
20
+ summary: string;
21
+ help: string;
22
+ options: {
23
+ readonly since: {
24
+ readonly type: "string";
25
+ };
26
+ };
27
+ run({ values, cwd, configFile, logger }: import("./command.ts").CommandContext): Promise<{
28
+ exitCode: number;
29
+ result: {
30
+ releases: {
31
+ package: string;
32
+ bump: "patch" | "minor" | "major";
33
+ currentVersion: string;
34
+ nextVersion: string;
35
+ }[];
36
+ changesets: import("../changesets.ts").Changeset[];
37
+ changedPackages: string[];
38
+ changedPackageFiles: Record<string, string[]>;
39
+ missingChangesets: string[];
40
+ };
41
+ }>;
42
+ };
43
+ export default _default;
@@ -0,0 +1,65 @@
1
+ import { getChangesetStatus } from "../changeset-release.js";
2
+ import { changesetStatusArgs, parseChangesetStatusArgs, } from "./changeset-args.js";
3
+ export default {
4
+ name: 'changeset-status',
5
+ usage: 'changeset-status [--since <ref>]',
6
+ summary: 'Show pending releases and check changed packages for changesets.',
7
+ help: `Options:
8
+ --since <ref> Compare changed files with this git ref (for CI)
9
+
10
+ Exit status is 2 when a changed package has no changeset added since the comparison base.
11
+ An empty changeset added since the comparison base counts as an explicit no-release decision.`,
12
+ options: changesetStatusArgs,
13
+ async run({ values, cwd, configFile, logger }) {
14
+ const { since } = parseChangesetStatusArgs(values);
15
+ const result = await getChangesetStatus({
16
+ cwd,
17
+ ...(configFile ? { configFile } : {}),
18
+ ...(since ? { since } : {}),
19
+ });
20
+ logger.summary(() => {
21
+ const rows = result.changedPackages.map((name) => {
22
+ let status = 'no release requested';
23
+ if (result.releases.some((release) => release.package.name === name)) {
24
+ status = 'changeset found';
25
+ }
26
+ if (result.missingChangesets.includes(name)) {
27
+ status = 'missing changeset';
28
+ }
29
+ return [name, status];
30
+ });
31
+ return [
32
+ '### Changeset status',
33
+ '',
34
+ '| Package | Status |',
35
+ '| --- | --- |',
36
+ ...rows.map((row) => `| ${row.join(' | ')} |`),
37
+ ].join('\n');
38
+ });
39
+ result.releases.forEach((release) => logger.info(`${release.package.name}: ${release.currentVersion} → ${release.nextVersion} (${release.bump})`));
40
+ if (result.missingChangesets.length) {
41
+ result.missingChangesets.forEach((name) => {
42
+ const file = result.changedPackageFiles[name]?.[0];
43
+ logger.error(`Changed package "${name}" has no changeset.`, {
44
+ file,
45
+ title: 'Missing changeset',
46
+ });
47
+ });
48
+ }
49
+ else if (!result.releases.length) {
50
+ logger.info('No pending package releases.');
51
+ }
52
+ return {
53
+ exitCode: result.missingChangesets.length ? 2 : 0,
54
+ result: {
55
+ ...result,
56
+ releases: result.releases.map((item) => ({
57
+ package: item.package.name,
58
+ bump: item.bump,
59
+ currentVersion: item.currentVersion,
60
+ nextVersion: item.nextVersion,
61
+ })),
62
+ },
63
+ };
64
+ },
65
+ };
@@ -0,0 +1,25 @@
1
+ declare const _default: {
2
+ name: string;
3
+ usage: string;
4
+ summary: string;
5
+ help: string;
6
+ options: {
7
+ readonly push: {
8
+ readonly type: "boolean";
9
+ };
10
+ readonly 'dry-run': {
11
+ readonly type: "boolean";
12
+ };
13
+ readonly 'github-release': {
14
+ readonly type: "boolean";
15
+ };
16
+ readonly since: {
17
+ readonly type: "string";
18
+ };
19
+ };
20
+ run({ values, cwd, configFile, logger }: import("./command.ts").CommandContext): Promise<{
21
+ exitCode: number;
22
+ result: import("../changeset-release.ts").ChangesetTagResult;
23
+ }>;
24
+ };
25
+ export default _default;
@@ -0,0 +1,27 @@
1
+ import { tagChangesets } from "../changeset-release.js";
2
+ import { parseChangesetTagArgs, changesetTagArgs } from "./changeset-args.js";
3
+ export default {
4
+ name: 'changeset-tag',
5
+ usage: 'changeset-tag [--dry-run] [--since <ref>] [--push] [--github-release]',
6
+ summary: 'Create missing release tags for configured package versions.',
7
+ help: `Options:
8
+ --dry-run List missing tags without creating them
9
+ --since <ref> Tag packages changed since this git ref
10
+ --push Push newly created tags to origin
11
+ --github-release Create a GitHub Release for each tag
12
+
13
+ Tags are created only when absent, and GitHub Releases are created only when
14
+ missing, so rerunning after a partial failure is safe. --github-release also
15
+ requires --push.`,
16
+ options: changesetTagArgs,
17
+ async run({ values, cwd, configFile, logger }) {
18
+ const args = parseChangesetTagArgs(values);
19
+ const result = await tagChangesets({
20
+ cwd,
21
+ ...(configFile ? { configFile } : {}),
22
+ ...args,
23
+ });
24
+ result.tags.forEach((item) => logger.info(`${item.created ? 'Created' : 'Exists'} ${item.tag}`));
25
+ return { exitCode: 0, result };
26
+ },
27
+ };
@@ -0,0 +1,38 @@
1
+ export interface ChangesetVersionResult {
2
+ date: string;
3
+ changed: boolean;
4
+ releases: Array<{
5
+ package: string;
6
+ bump: string;
7
+ currentVersion: string;
8
+ nextVersion: string;
9
+ }>;
10
+ }
11
+ declare const _default: {
12
+ name: string;
13
+ usage: string;
14
+ summary: string;
15
+ help: string;
16
+ options: {
17
+ readonly 'dry-run': {
18
+ readonly type: "boolean";
19
+ };
20
+ readonly date: {
21
+ readonly type: "string";
22
+ };
23
+ };
24
+ run({ values, cwd, configFile, logger }: import("./command.ts").CommandContext): Promise<{
25
+ exitCode: number;
26
+ result: {
27
+ date: string;
28
+ changed: boolean;
29
+ releases: {
30
+ package: string;
31
+ bump: "patch" | "minor" | "major";
32
+ currentVersion: string;
33
+ nextVersion: string;
34
+ }[];
35
+ };
36
+ }>;
37
+ };
38
+ export default _default;
@@ -0,0 +1,39 @@
1
+ import { versionChangesets } from "../changeset-release.js";
2
+ import { parseChangesetVersionArgs, changesetVersionArgs, } from "./changeset-args.js";
3
+ export default {
4
+ name: 'changeset-version',
5
+ usage: 'changeset-version [--dry-run] [--date <YYYY-MM-DD>]',
6
+ summary: 'Apply pending version bumps and changelog entries.',
7
+ help: `Options:
8
+ --dry-run Print the release plan without writing files
9
+ --date <YYYY-MM-DD> Changelog date (default: today)
10
+
11
+ Updates configured version files, prepends changelog entries and removes the
12
+ applied changeset files.`,
13
+ options: changesetVersionArgs,
14
+ async run({ values, cwd, configFile, logger }) {
15
+ const args = parseChangesetVersionArgs(values);
16
+ const result = await versionChangesets({
17
+ cwd,
18
+ ...(configFile ? { configFile } : {}),
19
+ ...args,
20
+ });
21
+ result.releases.forEach((release) => logger.info(`${release.package.name}: ${release.currentVersion} → ${release.nextVersion} (${release.bump})`));
22
+ if (!result.releases.length) {
23
+ logger.info('No pending package releases.');
24
+ }
25
+ return {
26
+ exitCode: 0,
27
+ result: {
28
+ date: result.date,
29
+ changed: result.changed,
30
+ releases: result.releases.map((item) => ({
31
+ package: item.package.name,
32
+ bump: item.bump,
33
+ currentVersion: item.currentVersion,
34
+ nextVersion: item.nextVersion,
35
+ })),
36
+ },
37
+ };
38
+ },
39
+ };
@@ -3,6 +3,7 @@ import { CHECKSUMS_EXTENSION, createChecksums, writeChecksums, } from "../checks
3
3
  import { loadFileRules, resolveFileSelection } from "../file-selection.js";
4
4
  import { FILENAME_FORMAT, formatFilename } from "../filename.js";
5
5
  import { readPackageMeta } from "../package-meta.js";
6
+ import { getChecksumsSummary } from "../summary.js";
6
7
  import { checkDirectory, fileSelectionArgs, getFileSelectionInput, getSourceDirArg, ignoreOwnFile, ignoreOwnSignature, resolveOutput, sourceDirArgs, } from "./args.js";
7
8
  import { getBoolean, getString } from "./command.js";
8
9
  export default {
@@ -71,6 +72,7 @@ matches the ZIP contents.`,
71
72
  await writeChecksums(checksumsPath, manifest, getBoolean(values, 'force'));
72
73
  logger.debug(`Hashed ${files} files in ${sourceDir}.`);
73
74
  logger.info(checksumsPath);
75
+ logger.summary((context) => getChecksumsSummary(context, { checksumsPath, files }));
74
76
  return { exitCode: 0, result: { checksumsPath, files } };
75
77
  },
76
78
  };
@@ -11,6 +11,8 @@ export interface CommandContext {
11
11
  positionals: string[];
12
12
  /** Working directory that relative paths resolve against. */
13
13
  cwd: string;
14
+ /** Explicit JavaScript release config path from --config, if supplied. */
15
+ configFile?: string;
14
16
  /** Logger for the command output. */
15
17
  logger: Logger;
16
18
  }
@@ -1,6 +1,7 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { stat } from 'node:fs/promises';
3
3
  import { ValidationError } from "../errors.js";
4
+ import { getSignSummary } from "../summary.js";
4
5
  import { SIGNING_KEY_ENV, getPublicKey, parseSigningKey, writeSignature, } from "../signing.js";
5
6
  import { getBoolean } from "./command.js";
6
7
  export default {
@@ -50,6 +51,7 @@ that belongs to the signing key.`,
50
51
  logger.info(signaturePath);
51
52
  signatures.push(signaturePath);
52
53
  }
54
+ logger.summary((context) => getSignSummary(context, { signatures, publicKey }));
53
55
  return { exitCode: 0, result: { signatures, publicKey } };
54
56
  },
55
57
  };
@@ -7,6 +7,8 @@ import { downloadFile, isUrl } from "../download.js";
7
7
  import { loadFileRules, parseExclude } from "../file-selection.js";
8
8
  import { ValidationError } from "../errors.js";
9
9
  import { readPackageMeta } from "../package-meta.js";
10
+ import { logProblems } from "../report.js";
11
+ import { getVerifySummary } from "../summary.js";
10
12
  import { extractZip, readZipEntries } from "../unzip.js";
11
13
  import { SIGNATURE_EXTENSION, parsePublicKey, verifyFileSignature, } from "../signing.js";
12
14
  import { checkDirectory, getSourceDirArg, ignoreOwnFile, ignoreOwnSignature, sourceDirArgs, } from "./args.js";
@@ -199,6 +201,46 @@ async function checkEmbeddedCopies(input) {
199
201
  : [],
200
202
  };
201
203
  }
204
+ /**
205
+ * Get the problems of a verify run. Changed files and files that aren't in
206
+ * the checksums are reported on the file. Missing files have no file to
207
+ * report on.
208
+ *
209
+ * @param dir Checked directory.
210
+ * @param result Differences between the files and the manifest.
211
+ * @param result.changed Changed files.
212
+ * @param result.missing Missing files.
213
+ * @param result.extra Files that aren't in the checksums.
214
+ * @param result.versionMismatch Manifest and file versions when they differ.
215
+ * @return Problems.
216
+ */
217
+ function getVerifyProblems(dir, result) {
218
+ const { changed, missing, extra, versionMismatch } = result;
219
+ return [
220
+ ...changed.map((file) => ({
221
+ message: `Changed: ${file}`,
222
+ file: join(dir, file),
223
+ title: 'Changed file',
224
+ })),
225
+ ...missing.map((file) => ({
226
+ message: `Missing: ${file}`,
227
+ title: 'Missing file',
228
+ })),
229
+ ...extra.map((file) => ({
230
+ message: `Not in the checksums: ${file}`,
231
+ file: join(dir, file),
232
+ title: 'File not in the checksums',
233
+ })),
234
+ ...(versionMismatch
235
+ ? [
236
+ {
237
+ message: `The checksums are for version ${versionMismatch.manifest}, but the files are version ${versionMismatch.files}.`,
238
+ title: 'Version mismatch',
239
+ },
240
+ ]
241
+ : []),
242
+ ];
243
+ }
202
244
  /**
203
245
  * Compare the version in the manifest with the Version header of the
204
246
  * checked files, so that an older signed release can't pass as a newer one.
@@ -253,25 +295,32 @@ async function verifyTarget(input) {
253
295
  !copies.differing.length &&
254
296
  !copies.missing.length &&
255
297
  !versionMismatch;
256
- changed.forEach((file) => logger.error(`Changed: ${file}`));
257
- missing.forEach((file) => logger.error(`Missing: ${file}`));
258
- extra.forEach((file) => logger.error(`Not in the checksums: ${file}`));
259
- if (versionMismatch) {
260
- logger.error(`The checksums are for version ${versionMismatch.manifest}, but the files are version ${versionMismatch.files}.`);
261
- }
262
- logger.info(`${matched.length} files match, ${changed.length} changed, ${missing.length} missing and ${extra.length} extra.`);
263
- return {
264
- exitCode: valid ? 0 : 2,
265
- result: {
266
- valid,
298
+ const counts = `${matched.length} files match, ${changed.length} changed, ${missing.length} missing and ${extra.length} extra.`;
299
+ if (valid) {
300
+ logger.summary((context) => getVerifySummary(context, {
267
301
  checksumsPath,
268
302
  matched: matched.length,
303
+ }));
304
+ }
305
+ else {
306
+ logProblems(logger, 'wp-release verify', getVerifyProblems(dir, {
269
307
  changed,
270
308
  missing,
271
309
  extra,
272
310
  versionMismatch,
273
- },
311
+ }), counts);
312
+ }
313
+ logger.info(counts);
314
+ const result = {
315
+ valid,
316
+ checksumsPath,
317
+ matched: matched.length,
318
+ changed,
319
+ missing,
320
+ extra,
321
+ versionMismatch,
274
322
  };
323
+ return { exitCode: valid ? 0 : 2, result };
275
324
  }
276
325
  export default {
277
326
  name: 'verify',
@@ -2,7 +2,9 @@ import { basename, resolve } from 'node:path';
2
2
  import { isTagRef } from "../deploy-options.js";
3
3
  import { ValidationError } from "../errors.js";
4
4
  import { readPackageMeta } from "../package-meta.js";
5
- import { GIT_TAG_FORMAT, findVersionMismatches } from "../version-check.js";
5
+ import { GIT_TAG_FORMAT, findVersionMismatches, getVersionMismatchProblems, } from "../version-check.js";
6
+ import { logProblems } from "../report.js";
7
+ import { getVersionCheckSummary } from "../summary.js";
6
8
  import { checkDirectory, getSourceDirArg, sourceDirArgs } from "./args.js";
7
9
  import { getBoolean, getString, getStrings } from "./command.js";
8
10
  /**
@@ -68,9 +70,10 @@ no Stable tag, so it's only checked for plugins.`,
68
70
  constants: getStrings(values, 'constant'),
69
71
  stableTag: !getBoolean(values, 'no-stable-tag'),
70
72
  });
71
- mismatches.forEach((mismatch) => logger.error(mismatch.message));
73
+ logProblems(logger, 'wp-release version-check', getVersionMismatchProblems(sourceDir, mismatches));
72
74
  if (!mismatches.length) {
73
75
  logger.info(`All versions match ${version}.`);
76
+ logger.summary(() => getVersionCheckSummary(version));
74
77
  }
75
78
  return {
76
79
  exitCode: mismatches.length ? 2 : 0,
@@ -5,6 +5,7 @@ import { ValidationError } from "../errors.js";
5
5
  import { loadFileRules, resolveFileSelection, selectFiles, } from "../file-selection.js";
6
6
  import { FILENAME_FORMAT, formatFilename } from "../filename.js";
7
7
  import { readPackageMeta } from "../package-meta.js";
8
+ import { getZipSummary } from "../summary.js";
8
9
  import { createZip } from "../zip.js";
9
10
  import { checkDirectory, fileSelectionArgs, getFileSelectionInput, getSourceDirArg, ignoreOwnFile, ignoreOwnSignature, resolveOutput, sourceDirArgs, } from "./args.js";
10
11
  import { getBoolean, getString } from "./command.js";
@@ -126,13 +127,12 @@ always give the same ZIP.`,
126
127
  await writeChecksums(checksumsPath, manifest, force);
127
128
  logger.info(checksumsPath);
128
129
  }
129
- return {
130
- exitCode: 0,
131
- result: {
132
- zipPath,
133
- ...(checksumsPath ? { checksumsPath } : {}),
134
- files: files.length,
135
- },
130
+ const result = {
131
+ zipPath,
132
+ ...(checksumsPath ? { checksumsPath } : {}),
133
+ files: files.length,
136
134
  };
135
+ logger.summary((context) => getZipSummary(context, result));
136
+ return { exitCode: 0, result };
137
137
  },
138
138
  };
package/dist/errors.d.ts CHANGED
@@ -1,9 +1,27 @@
1
+ /**
2
+ * One failed check, with the file and line it's about when there is one.
3
+ */
4
+ export interface Problem {
5
+ /** Description of the problem. */
6
+ message: string;
7
+ /** File the problem is in, absolute or relative to the working directory. */
8
+ file?: string | null;
9
+ /** Line in the file, starting at 1. */
10
+ line?: number | null;
11
+ /** Short title of the kind of problem, like "Version mismatch". */
12
+ title?: string;
13
+ }
1
14
  /**
2
15
  * Invalid options or failed release checks, reported with exit code 2.
16
+ * Failed checks can list their problems, so that each one is reported on its
17
+ * own file and line.
3
18
  */
4
19
  export declare class ValidationError extends Error {
5
20
  name: string;
6
21
  exitCode: number;
22
+ /** Each failed check, empty when the message is all there is. */
23
+ readonly problems: Problem[];
24
+ constructor(message: string, problems?: Problem[]);
7
25
  }
8
26
  /**
9
27
  * Missing SVN credentials or a failed login or permission check, reported
package/dist/errors.js CHANGED
@@ -1,9 +1,17 @@
1
1
  /**
2
2
  * Invalid options or failed release checks, reported with exit code 2.
3
+ * Failed checks can list their problems, so that each one is reported on its
4
+ * own file and line.
3
5
  */
4
6
  export class ValidationError extends Error {
5
7
  name = 'ValidationError';
6
8
  exitCode = 2;
9
+ /** Each failed check, empty when the message is all there is. */
10
+ problems;
11
+ constructor(message, problems = []) {
12
+ super(message);
13
+ this.problems = problems;
14
+ }
7
15
  }
8
16
  /**
9
17
  * Missing SVN credentials or a failed login or permission check, reported
@@ -0,0 +1,52 @@
1
+ import type { Annotation, LogEntry, LogWriter, OutputStream, SummaryBuilder } from './logger.ts';
2
+ export interface GitHubActionsWriterOptions {
3
+ /** Stream for regular output. */
4
+ stdout?: OutputStream;
5
+ /** Stream for warnings and errors. */
6
+ stderr?: OutputStream;
7
+ /**
8
+ * Directory that annotation and summary paths are relative to, defaults
9
+ * to GITHUB_WORKSPACE or the working directory.
10
+ */
11
+ workspace?: string;
12
+ /**
13
+ * File that summaries are appended to, defaults to GITHUB_STEP_SUMMARY.
14
+ * Null turns summaries off.
15
+ */
16
+ summaryPath?: string | null;
17
+ }
18
+ /**
19
+ * Writer that prints GitHub Actions workflow commands, so that notices,
20
+ * warnings and errors are annotations on the run and on the files they're
21
+ * about, and appends summaries to the job summary.
22
+ */
23
+ export declare class GitHubActionsWriter implements LogWriter {
24
+ readonly stdout: OutputStream;
25
+ readonly stderr: OutputStream;
26
+ readonly workspace: string;
27
+ readonly summaryPath: string | null;
28
+ constructor({ stdout, stderr, workspace, summaryPath, }?: GitHubActionsWriterOptions);
29
+ write({ level, message, annotation }: LogEntry): void;
30
+ startGroup(title: string): void;
31
+ endGroup(): void;
32
+ summary(build: SummaryBuilder): void;
33
+ /**
34
+ * Format a workflow command like
35
+ * "::error file=my-plugin.php,line=3,title=Version mismatch::message".
36
+ *
37
+ * @param name Command name.
38
+ * @param message Message.
39
+ * @param annotation File, line and title of the annotation.
40
+ * @return Workflow command with a line break.
41
+ */
42
+ formatCommand(name: string, message: string, annotation?: Annotation): string;
43
+ /**
44
+ * Get the path of an annotation file relative to the workspace. GitHub
45
+ * only links files of the repository, so files outside the workspace,
46
+ * like an extracted ZIP in a temporary directory, get no file.
47
+ *
48
+ * @param file File path, absolute or relative to the working directory.
49
+ * @return Path with forward slashes, or null when it's outside the workspace.
50
+ */
51
+ getAnnotationFile(file?: string | null): string | null;
52
+ }