@wpelevator/wp-release 0.4.1 → 0.5.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 +69 -6
- package/dist/cli.js +51 -12
- package/dist/commands/checksums.js +2 -0
- package/dist/commands/sign.js +2 -0
- package/dist/commands/verify.js +61 -12
- package/dist/commands/version-check.js +5 -2
- package/dist/commands/zip.js +7 -7
- package/dist/errors.d.ts +18 -0
- package/dist/errors.js +8 -0
- package/dist/github-actions-writer.d.ts +52 -0
- package/dist/github-actions-writer.js +94 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -3
- package/dist/logger.d.ts +88 -16
- package/dist/logger.js +73 -25
- package/dist/package-meta.d.ts +26 -0
- package/dist/package-meta.js +65 -10
- package/dist/report.d.ts +22 -0
- package/dist/report.js +35 -0
- package/dist/summary.d.ts +141 -0
- package/dist/summary.js +287 -0
- package/dist/svn-deploy.d.ts +2 -1
- package/dist/svn-deploy.js +8 -6
- package/dist/version-check.d.ts +39 -1
- package/dist/version-check.js +59 -5
- package/package.json +1 -1
package/dist/summary.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
2
|
+
import { normalizeTag } from "./deploy-options.js";
|
|
3
|
+
/**
|
|
4
|
+
* SVN host of the WordPress.org plugin repositories.
|
|
5
|
+
*/
|
|
6
|
+
export const WPORG_SVN_HOST = 'https://plugins.svn.wordpress.org/';
|
|
7
|
+
/**
|
|
8
|
+
* Number of files or SVN changes listed in a summary before the rest are
|
|
9
|
+
* counted.
|
|
10
|
+
*/
|
|
11
|
+
export const SUMMARY_LIST_LIMIT = 100;
|
|
12
|
+
/**
|
|
13
|
+
* Get the path of a file relative to the workspace.
|
|
14
|
+
*
|
|
15
|
+
* @param workspace Workspace directory.
|
|
16
|
+
* @param file File path, absolute or relative to the working directory.
|
|
17
|
+
* @return Path with forward slashes, or null when it's outside the workspace.
|
|
18
|
+
*/
|
|
19
|
+
export function getWorkspacePath(workspace, file) {
|
|
20
|
+
const path = relative(workspace, resolve(file));
|
|
21
|
+
const inWorkspace = '' !== path && !path.startsWith('..') && !isAbsolute(path);
|
|
22
|
+
return inWorkspace ? path.split(sep).join('/') : null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Format text as inline code, with a fence that is longer than any backticks
|
|
26
|
+
* in the text.
|
|
27
|
+
*
|
|
28
|
+
* @param text Text.
|
|
29
|
+
* @return Markdown code span.
|
|
30
|
+
*/
|
|
31
|
+
export function formatCode(text) {
|
|
32
|
+
const longest = Math.max(0, ...[...text.matchAll(/`+/g)].map(([ticks]) => ticks.length));
|
|
33
|
+
const fence = '`'.repeat(longest + 1);
|
|
34
|
+
const padding = longest ? ' ' : '';
|
|
35
|
+
return `${fence}${padding}${text}${padding}${fence}`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Format a Markdown table.
|
|
39
|
+
*
|
|
40
|
+
* @param header Column titles.
|
|
41
|
+
* @param rows Cells of each row, already formatted as Markdown.
|
|
42
|
+
* @return Markdown table.
|
|
43
|
+
*/
|
|
44
|
+
export function formatTable(header, rows) {
|
|
45
|
+
const formatRow = (cells) => `| ${cells
|
|
46
|
+
.map((cell) => cell.replaceAll('|', '\\|').replace(/\r?\n/g, ' '))
|
|
47
|
+
.join(' | ')} |`;
|
|
48
|
+
return [
|
|
49
|
+
formatRow(header),
|
|
50
|
+
formatRow(header.map(() => '---')),
|
|
51
|
+
...rows.map(formatRow),
|
|
52
|
+
].join('\n');
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Format a file path as code, relative to the workspace when it's inside it.
|
|
56
|
+
* Files aren't linked, since build directories and release files usually
|
|
57
|
+
* aren't in the repository.
|
|
58
|
+
*
|
|
59
|
+
* @param context Workspace directory.
|
|
60
|
+
* @param file File path.
|
|
61
|
+
* @param line Line in the file, if known.
|
|
62
|
+
* @return Markdown code.
|
|
63
|
+
*/
|
|
64
|
+
export function formatFile(context, file, line) {
|
|
65
|
+
const path = getWorkspacePath(context.workspace, file) ?? file;
|
|
66
|
+
return formatCode(`${path}${line ? `:${line}` : ''}`);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Format a list, counting the items after the limit instead of listing them.
|
|
70
|
+
*
|
|
71
|
+
* @param items Items, already formatted as Markdown.
|
|
72
|
+
* @return Markdown list.
|
|
73
|
+
*/
|
|
74
|
+
function formatList(items) {
|
|
75
|
+
const listed = items.slice(0, SUMMARY_LIST_LIMIT);
|
|
76
|
+
const more = items.length - listed.length;
|
|
77
|
+
return [
|
|
78
|
+
...listed.map((item) => `- ${item}`),
|
|
79
|
+
...(more ? [`- … and ${more} more`] : []),
|
|
80
|
+
].join('\n');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Wrap content in a collapsed section.
|
|
84
|
+
*
|
|
85
|
+
* @param title Section title.
|
|
86
|
+
* @param content Markdown content.
|
|
87
|
+
* @return Markdown with a details element.
|
|
88
|
+
*/
|
|
89
|
+
function formatDetails(title, content) {
|
|
90
|
+
return `<details><summary>${title}</summary>\n\n${content}\n\n</details>`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Join summary blocks into one summary.
|
|
94
|
+
*
|
|
95
|
+
* @param blocks Markdown blocks, empty ones are left out.
|
|
96
|
+
* @return Markdown with a line break at the end.
|
|
97
|
+
*/
|
|
98
|
+
function joinBlocks(blocks) {
|
|
99
|
+
return `${blocks.filter(Boolean).join('\n\n')}\n`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get the summary of a ZIP.
|
|
103
|
+
*
|
|
104
|
+
* @param context Workspace directory.
|
|
105
|
+
* @param result ZIP and manifest paths and the number of files.
|
|
106
|
+
* @return Markdown summary.
|
|
107
|
+
*/
|
|
108
|
+
export function getZipSummary(context, result) {
|
|
109
|
+
return joinBlocks([
|
|
110
|
+
'### ZIP',
|
|
111
|
+
formatList([
|
|
112
|
+
`**ZIP:** ${formatFile(context, result.zipPath)}, ${result.files} files`,
|
|
113
|
+
...(result.checksumsPath
|
|
114
|
+
? [
|
|
115
|
+
`**Checksums:** ${formatFile(context, result.checksumsPath)}`,
|
|
116
|
+
]
|
|
117
|
+
: []),
|
|
118
|
+
]),
|
|
119
|
+
]);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get the summary of a checksums manifest.
|
|
123
|
+
*
|
|
124
|
+
* @param context Workspace directory.
|
|
125
|
+
* @param result Manifest path and the number of hashed files.
|
|
126
|
+
* @return Markdown summary.
|
|
127
|
+
*/
|
|
128
|
+
export function getChecksumsSummary(context, result) {
|
|
129
|
+
return joinBlocks([
|
|
130
|
+
'### Checksums',
|
|
131
|
+
formatList([
|
|
132
|
+
`**Manifest:** ${formatFile(context, result.checksumsPath)}, ${result.files} files`,
|
|
133
|
+
]),
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get the summary of signed files.
|
|
138
|
+
*
|
|
139
|
+
* @param context Workspace directory.
|
|
140
|
+
* @param result Signature paths and the public key.
|
|
141
|
+
* @return Markdown summary.
|
|
142
|
+
*/
|
|
143
|
+
export function getSignSummary(context, result) {
|
|
144
|
+
return joinBlocks([
|
|
145
|
+
'### Signatures',
|
|
146
|
+
formatList(result.signatures.map((path) => formatFile(context, path))),
|
|
147
|
+
`Public key: ${formatCode(result.publicKey)}`,
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get the summary of a verify run where every file matches. Failed runs are
|
|
152
|
+
* summarized with their problems instead.
|
|
153
|
+
*
|
|
154
|
+
* @param context Workspace directory.
|
|
155
|
+
* @param result Verify result.
|
|
156
|
+
* @param result.checksumsPath Checksums manifest path.
|
|
157
|
+
* @param result.matched Number of matching files.
|
|
158
|
+
* @return Markdown summary.
|
|
159
|
+
*/
|
|
160
|
+
export function getVerifySummary(context, result) {
|
|
161
|
+
return joinBlocks([
|
|
162
|
+
'### Verify',
|
|
163
|
+
`All ${result.matched} files match ${formatFile(context, result.checksumsPath)}.`,
|
|
164
|
+
]);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get the summary of a version check without mismatches.
|
|
168
|
+
*
|
|
169
|
+
* @param version Version that all sources match.
|
|
170
|
+
* @return Markdown summary.
|
|
171
|
+
*/
|
|
172
|
+
export function getVersionCheckSummary(version) {
|
|
173
|
+
return joinBlocks([
|
|
174
|
+
'### Version check',
|
|
175
|
+
`All versions match ${formatCode(version ?? '')}.`,
|
|
176
|
+
]);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get the summary of failed checks, with the file and line of each problem.
|
|
180
|
+
* Problems after the list limit are counted instead of listed.
|
|
181
|
+
*
|
|
182
|
+
* @param context Workspace directory.
|
|
183
|
+
* @param title What failed, like the command name.
|
|
184
|
+
* @param problems Problems.
|
|
185
|
+
* @param description Text above the problems, if any.
|
|
186
|
+
* @return Markdown summary.
|
|
187
|
+
*/
|
|
188
|
+
export function getProblemsSummary(context, title, problems, description = '') {
|
|
189
|
+
const more = problems.length - SUMMARY_LIST_LIMIT;
|
|
190
|
+
return joinBlocks([
|
|
191
|
+
`### ${title} failed`,
|
|
192
|
+
description,
|
|
193
|
+
formatTable(['Problem', 'Location'], problems
|
|
194
|
+
.slice(0, SUMMARY_LIST_LIMIT)
|
|
195
|
+
.map(({ message, file, line }) => [
|
|
196
|
+
message,
|
|
197
|
+
file ? formatFile(context, file, line) : '',
|
|
198
|
+
])),
|
|
199
|
+
more > 0 && `… and ${more} more`,
|
|
200
|
+
]);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get the summary of a failed run.
|
|
204
|
+
*
|
|
205
|
+
* @param title What failed, like the command name.
|
|
206
|
+
* @param message Error message.
|
|
207
|
+
* @return Markdown summary.
|
|
208
|
+
*/
|
|
209
|
+
export function getErrorSummary(title, message) {
|
|
210
|
+
return joinBlocks([
|
|
211
|
+
`### ${title} failed`,
|
|
212
|
+
message
|
|
213
|
+
.split(/\r?\n/)
|
|
214
|
+
.map((line) => `> ${line}`)
|
|
215
|
+
.join('\n'),
|
|
216
|
+
]);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get the paths that a deploy writes to.
|
|
220
|
+
*
|
|
221
|
+
* @param options Resolved deploy options.
|
|
222
|
+
* @return SVN paths.
|
|
223
|
+
*/
|
|
224
|
+
function getDeployWrites(options) {
|
|
225
|
+
const { trunk, readmeOnly, svnTag, assets, assetsDir, readme } = options;
|
|
226
|
+
return [
|
|
227
|
+
trunk && 'trunk/',
|
|
228
|
+
readmeOnly && `trunk/${readme}`,
|
|
229
|
+
svnTag && `tags/${normalizeTag(svnTag)}/`,
|
|
230
|
+
assets && assetsDir && 'assets/',
|
|
231
|
+
].filter((path) => Boolean(path));
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Describe the result of a deploy commit.
|
|
235
|
+
*
|
|
236
|
+
* @param input Deploy options and result.
|
|
237
|
+
* @return Markdown text.
|
|
238
|
+
*/
|
|
239
|
+
function describeDeployResult(input) {
|
|
240
|
+
const { options, committed, revision, changes } = input;
|
|
241
|
+
const isWporg = options.svnUrl.startsWith(WPORG_SVN_HOST);
|
|
242
|
+
const revisionLabel = `r${revision}`;
|
|
243
|
+
const revisionLink = isWporg
|
|
244
|
+
? `[${revisionLabel}](https://plugins.trac.wordpress.org/changeset/${revision})`
|
|
245
|
+
: revisionLabel;
|
|
246
|
+
const uncommitted = changes.length
|
|
247
|
+
? `Dry run, ${changes.length} changes were not committed.`
|
|
248
|
+
: 'No changes to commit.';
|
|
249
|
+
return committed
|
|
250
|
+
? `Committed ${revisionLink} with ${changes.length} changes.`
|
|
251
|
+
: uncommitted;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get the summary of a deploy, with the written paths, the commit and the
|
|
255
|
+
* SVN changes.
|
|
256
|
+
*
|
|
257
|
+
* @param input Deploy options and result.
|
|
258
|
+
* @return Markdown summary.
|
|
259
|
+
*/
|
|
260
|
+
export function getDeploySummary(input) {
|
|
261
|
+
const { options, version, changes } = input;
|
|
262
|
+
const isWporg = options.svnUrl.startsWith(WPORG_SVN_HOST);
|
|
263
|
+
const plugin = isWporg
|
|
264
|
+
? `[${options.slug}](https://wordpress.org/plugins/${options.slug}/)`
|
|
265
|
+
: formatCode(options.slug);
|
|
266
|
+
return joinBlocks([
|
|
267
|
+
isWporg ? '### Deploy to WordPress.org' : '### Deploy to SVN',
|
|
268
|
+
formatList([
|
|
269
|
+
`**Plugin:** ${plugin}`,
|
|
270
|
+
...(version ? [`**Version:** ${formatCode(version)}`] : []),
|
|
271
|
+
`**Writes:** ${getDeployWrites(options)
|
|
272
|
+
.map(formatCode)
|
|
273
|
+
.join(', ')}`,
|
|
274
|
+
`**Result:** ${describeDeployResult(input)}`,
|
|
275
|
+
]),
|
|
276
|
+
changes.length > 0 &&
|
|
277
|
+
formatDetails(`SVN changes (${changes.length})`, formatTable(['Status', 'Path'], changes
|
|
278
|
+
.slice(0, SUMMARY_LIST_LIMIT)
|
|
279
|
+
.map(({ status, path }) => [
|
|
280
|
+
status,
|
|
281
|
+
formatCode(path),
|
|
282
|
+
])) +
|
|
283
|
+
(changes.length > SUMMARY_LIST_LIMIT
|
|
284
|
+
? `\n\n… and ${changes.length - SUMMARY_LIST_LIMIT} more`
|
|
285
|
+
: '')),
|
|
286
|
+
]);
|
|
287
|
+
}
|
package/dist/svn-deploy.d.ts
CHANGED
|
@@ -78,7 +78,8 @@ export declare class SvnDeploy {
|
|
|
78
78
|
* pre-release tag doesn't change the stable tag. Readme-only updates need
|
|
79
79
|
* the readme.
|
|
80
80
|
*
|
|
81
|
-
* @throws {ValidationError} When the files
|
|
81
|
+
* @throws {ValidationError} When the files don't match.
|
|
82
|
+
* @throws {VersionMismatchError} When the versions don't match.
|
|
82
83
|
*/
|
|
83
84
|
checkVersions(): Promise<void>;
|
|
84
85
|
/**
|
package/dist/svn-deploy.js
CHANGED
|
@@ -10,8 +10,9 @@ import { loadFileRules, selectFiles } from "./file-selection.js";
|
|
|
10
10
|
import { readPackageMeta } from "./package-meta.js";
|
|
11
11
|
import { Svn } from "./svn.js";
|
|
12
12
|
import { copyFileIfChanged, syncDirectory } from "./sync.js";
|
|
13
|
+
import { getDeploySummary } from "./summary.js";
|
|
13
14
|
import { extractZip } from "./unzip.js";
|
|
14
|
-
import { findVersionMismatches } from "./version-check.js";
|
|
15
|
+
import { VersionMismatchError, findVersionMismatches, } from "./version-check.js";
|
|
15
16
|
/**
|
|
16
17
|
* MIME types of the plugin directory asset formats. svn guesses binary
|
|
17
18
|
* types, but WordPress.org serves assets with the svn:mime-type property,
|
|
@@ -102,7 +103,8 @@ export class SvnDeploy {
|
|
|
102
103
|
* pre-release tag doesn't change the stable tag. Readme-only updates need
|
|
103
104
|
* the readme.
|
|
104
105
|
*
|
|
105
|
-
* @throws {ValidationError} When the files
|
|
106
|
+
* @throws {ValidationError} When the files don't match.
|
|
107
|
+
* @throws {VersionMismatchError} When the versions don't match.
|
|
106
108
|
*/
|
|
107
109
|
async checkVersions() {
|
|
108
110
|
const { trunk, readmeOnly, svnTag, slug, readme } = this.options;
|
|
@@ -122,9 +124,7 @@ export class SvnDeploy {
|
|
|
122
124
|
});
|
|
123
125
|
this.version = version;
|
|
124
126
|
if (mismatches.length) {
|
|
125
|
-
throw new
|
|
126
|
-
.map(({ message }) => message)
|
|
127
|
-
.join('\n'));
|
|
127
|
+
throw new VersionMismatchError(sourceDir, mismatches);
|
|
128
128
|
}
|
|
129
129
|
this.logger.info(`Deploying version ${version}.`);
|
|
130
130
|
}
|
|
@@ -379,7 +379,9 @@ export class SvnDeploy {
|
|
|
379
379
|
await this.checkVersions();
|
|
380
380
|
await this.checkout();
|
|
381
381
|
await this.sync();
|
|
382
|
-
|
|
382
|
+
const result = await this.commit();
|
|
383
|
+
this.logger.summary(() => getDeploySummary({ options: this.options, ...result }));
|
|
384
|
+
return result;
|
|
383
385
|
}
|
|
384
386
|
finally {
|
|
385
387
|
await this.cleanup();
|
package/dist/version-check.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ValidationError } from './errors.ts';
|
|
2
|
+
import type { Problem } from './errors.ts';
|
|
1
3
|
import type { PackageMeta } from './package-meta.ts';
|
|
2
4
|
export declare const GIT_TAG_FORMAT = "{version}";
|
|
3
5
|
/**
|
|
@@ -6,8 +8,10 @@ export declare const GIT_TAG_FORMAT = "{version}";
|
|
|
6
8
|
export interface VersionMismatch {
|
|
7
9
|
/** Where the version comes from, like "Stable tag" or "Git tag". */
|
|
8
10
|
source: string;
|
|
9
|
-
/** File that holds the version, if any. */
|
|
11
|
+
/** File that holds the version, relative to the source directory, if any. */
|
|
10
12
|
file: string | null;
|
|
13
|
+
/** Line of the version in the file, starting at 1, if known. */
|
|
14
|
+
line: number | null;
|
|
11
15
|
/** Version found, or null when it's missing. */
|
|
12
16
|
actual: string | null;
|
|
13
17
|
/** Human-readable description. */
|
|
@@ -45,6 +49,28 @@ export interface VersionCheckResult {
|
|
|
45
49
|
/** Every mismatch, empty when all versions agree. */
|
|
46
50
|
mismatches: VersionMismatch[];
|
|
47
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Get the problems of version mismatches, with their files relative to the
|
|
54
|
+
* working directory instead of the plugin directory.
|
|
55
|
+
*
|
|
56
|
+
* @param sourceDir Directory that the mismatch files are relative to.
|
|
57
|
+
* @param mismatches Mismatches from findVersionMismatches().
|
|
58
|
+
* @return Problems.
|
|
59
|
+
*/
|
|
60
|
+
export declare function getVersionMismatchProblems(sourceDir: string, mismatches: VersionMismatch[]): Problem[];
|
|
61
|
+
/**
|
|
62
|
+
* Versions that don't agree, thrown by a deploy before anything is written.
|
|
63
|
+
* The message lists every mismatch, and the problems have their files and
|
|
64
|
+
* lines.
|
|
65
|
+
*/
|
|
66
|
+
export declare class VersionMismatchError extends ValidationError {
|
|
67
|
+
name: string;
|
|
68
|
+
/** Directory that the mismatch files are relative to. */
|
|
69
|
+
readonly sourceDir: string;
|
|
70
|
+
/** Every mismatch. */
|
|
71
|
+
readonly mismatches: VersionMismatch[];
|
|
72
|
+
constructor(sourceDir: string, mismatches: VersionMismatch[]);
|
|
73
|
+
}
|
|
48
74
|
/**
|
|
49
75
|
* Get the version from a git tag with a format like v{version} or
|
|
50
76
|
* {slug}-{version}. A leading "v" is stripped from the version, like for
|
|
@@ -64,6 +90,18 @@ export declare function getGitTagVersion(input: GitTagInput): string | null;
|
|
|
64
90
|
* @return Values in the order they appear.
|
|
65
91
|
*/
|
|
66
92
|
export declare function findConstantValues(contents: string, name: string): string[];
|
|
93
|
+
/**
|
|
94
|
+
* Find the definitions of a PHP constant set with define( 'NAME', '1.2.0' )
|
|
95
|
+
* or const NAME = '1.2.0'.
|
|
96
|
+
*
|
|
97
|
+
* @param contents PHP code.
|
|
98
|
+
* @param name Constant name.
|
|
99
|
+
* @return Values and their line numbers, in the order they appear.
|
|
100
|
+
*/
|
|
101
|
+
export declare function findConstantDefinitions(contents: string, name: string): Array<{
|
|
102
|
+
value: string;
|
|
103
|
+
line: number;
|
|
104
|
+
}>;
|
|
67
105
|
/**
|
|
68
106
|
* Check that the Version header, readme Stable tag, PHP constants and git tag
|
|
69
107
|
* of a plugin or theme agree, and report every one that doesn't. Themes have
|
package/dist/version-check.js
CHANGED
|
@@ -4,6 +4,7 @@ import ignore from 'ignore';
|
|
|
4
4
|
import { normalizeTag } from "./deploy-options.js";
|
|
5
5
|
import { ValidationError } from "./errors.js";
|
|
6
6
|
import { selectFiles } from "./file-selection.js";
|
|
7
|
+
import { findFileHeaderLine, findReadmeHeaderLine, getLineNumber, readFileStart, } from "./package-meta.js";
|
|
7
8
|
export const GIT_TAG_FORMAT = '{version}';
|
|
8
9
|
const TAG_REF_PREFIX = 'refs/tags/';
|
|
9
10
|
/**
|
|
@@ -11,6 +12,39 @@ const TAG_REF_PREFIX = 'refs/tags/';
|
|
|
11
12
|
* dependencies with their own versions.
|
|
12
13
|
*/
|
|
13
14
|
const CONSTANT_SEARCH_IGNORE = ['node_modules/', 'vendor/', '.*'];
|
|
15
|
+
/**
|
|
16
|
+
* Get the problems of version mismatches, with their files relative to the
|
|
17
|
+
* working directory instead of the plugin directory.
|
|
18
|
+
*
|
|
19
|
+
* @param sourceDir Directory that the mismatch files are relative to.
|
|
20
|
+
* @param mismatches Mismatches from findVersionMismatches().
|
|
21
|
+
* @return Problems.
|
|
22
|
+
*/
|
|
23
|
+
export function getVersionMismatchProblems(sourceDir, mismatches) {
|
|
24
|
+
return mismatches.map(({ message, file, line }) => ({
|
|
25
|
+
message,
|
|
26
|
+
file: file ? join(sourceDir, file) : null,
|
|
27
|
+
line,
|
|
28
|
+
title: 'Version mismatch',
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Versions that don't agree, thrown by a deploy before anything is written.
|
|
33
|
+
* The message lists every mismatch, and the problems have their files and
|
|
34
|
+
* lines.
|
|
35
|
+
*/
|
|
36
|
+
export class VersionMismatchError extends ValidationError {
|
|
37
|
+
name = 'VersionMismatchError';
|
|
38
|
+
/** Directory that the mismatch files are relative to. */
|
|
39
|
+
sourceDir;
|
|
40
|
+
/** Every mismatch. */
|
|
41
|
+
mismatches;
|
|
42
|
+
constructor(sourceDir, mismatches) {
|
|
43
|
+
super(mismatches.map(({ message }) => message).join('\n'), getVersionMismatchProblems(sourceDir, mismatches));
|
|
44
|
+
this.sourceDir = sourceDir;
|
|
45
|
+
this.mismatches = mismatches;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
14
48
|
/**
|
|
15
49
|
* Get the version from a git tag with a format like v{version} or
|
|
16
50
|
* {slug}-{version}. A leading "v" is stripped from the version, like for
|
|
@@ -44,6 +78,17 @@ function escapeRegExp(text) {
|
|
|
44
78
|
* @return Values in the order they appear.
|
|
45
79
|
*/
|
|
46
80
|
export function findConstantValues(contents, name) {
|
|
81
|
+
return findConstantDefinitions(contents, name).map(({ value }) => value);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Find the definitions of a PHP constant set with define( 'NAME', '1.2.0' )
|
|
85
|
+
* or const NAME = '1.2.0'.
|
|
86
|
+
*
|
|
87
|
+
* @param contents PHP code.
|
|
88
|
+
* @param name Constant name.
|
|
89
|
+
* @return Values and their line numbers, in the order they appear.
|
|
90
|
+
*/
|
|
91
|
+
export function findConstantDefinitions(contents, name) {
|
|
47
92
|
const quotedName = `(['"])${escapeRegExp(name)}\\1`;
|
|
48
93
|
const patterns = [
|
|
49
94
|
new RegExp(`\\bdefine\\s*\\(\\s*${quotedName}\\s*,\\s*(['"])(.*?)\\2\\s*[,)]`, 'g'),
|
|
@@ -52,7 +97,10 @@ export function findConstantValues(contents, name) {
|
|
|
52
97
|
return patterns
|
|
53
98
|
.flatMap((pattern) => [...contents.matchAll(pattern)])
|
|
54
99
|
.sort((a, b) => (a.index ?? 0) - (b.index ?? 0))
|
|
55
|
-
.map((match) =>
|
|
100
|
+
.map((match) => ({
|
|
101
|
+
value: match[3] ?? '',
|
|
102
|
+
line: getLineNumber(contents, match.index ?? 0),
|
|
103
|
+
}));
|
|
56
104
|
}
|
|
57
105
|
/**
|
|
58
106
|
* Compare a version with the expected one.
|
|
@@ -100,16 +148,17 @@ async function compareConstants(sourceDir, constants, expected) {
|
|
|
100
148
|
})));
|
|
101
149
|
return constants.flatMap((name) => {
|
|
102
150
|
const source = `Constant ${name}`;
|
|
103
|
-
const definitions = sources.flatMap(({ file, contents }) =>
|
|
151
|
+
const definitions = sources.flatMap(({ file, contents }) => findConstantDefinitions(contents, name).map(({ value, line }) => ({
|
|
104
152
|
source,
|
|
105
153
|
file,
|
|
106
|
-
|
|
154
|
+
line,
|
|
155
|
+
actual: value,
|
|
107
156
|
})));
|
|
108
157
|
if (definitions.length) {
|
|
109
158
|
return definitions.map((definition) => compareVersion(definition, expected));
|
|
110
159
|
}
|
|
111
160
|
return [
|
|
112
|
-
compareVersion({ source, file: null, actual: null }, expected),
|
|
161
|
+
compareVersion({ source, file: null, line: null, actual: null }, expected),
|
|
113
162
|
];
|
|
114
163
|
});
|
|
115
164
|
}
|
|
@@ -127,11 +176,12 @@ function compareGitTag(input, expected) {
|
|
|
127
176
|
return {
|
|
128
177
|
source: 'Git tag',
|
|
129
178
|
file: null,
|
|
179
|
+
line: null,
|
|
130
180
|
actual: tag,
|
|
131
181
|
message: `Git tag ${tag} doesn't match the format ${input.gitTagFormat}.`,
|
|
132
182
|
};
|
|
133
183
|
}
|
|
134
|
-
return compareVersion({ source: `Git tag ${tag}`, file: null, actual }, expected);
|
|
184
|
+
return compareVersion({ source: `Git tag ${tag}`, file: null, line: null, actual }, expected);
|
|
135
185
|
}
|
|
136
186
|
/**
|
|
137
187
|
* Check that the Version header, readme Stable tag, PHP constants and git tag
|
|
@@ -152,6 +202,7 @@ export async function findVersionMismatches(input) {
|
|
|
152
202
|
{
|
|
153
203
|
source: 'Version header',
|
|
154
204
|
file: meta.mainFile,
|
|
205
|
+
line: null,
|
|
155
206
|
actual: null,
|
|
156
207
|
message: `Version header is missing in ${meta.mainFile}.`,
|
|
157
208
|
},
|
|
@@ -164,12 +215,14 @@ export async function findVersionMismatches(input) {
|
|
|
164
215
|
compareVersion({
|
|
165
216
|
source: 'Version header',
|
|
166
217
|
file: meta.mainFile,
|
|
218
|
+
line: findFileHeaderLine((await readFileStart(join(sourceDir, meta.mainFile))) ?? '', 'Version'),
|
|
167
219
|
actual: meta.version,
|
|
168
220
|
}, expected),
|
|
169
221
|
checkStableTag && !readme
|
|
170
222
|
? {
|
|
171
223
|
source: 'Stable tag',
|
|
172
224
|
file: null,
|
|
225
|
+
line: null,
|
|
173
226
|
actual: null,
|
|
174
227
|
message: `Readme is missing, expected Stable tag ${expected}.`,
|
|
175
228
|
}
|
|
@@ -178,6 +231,7 @@ export async function findVersionMismatches(input) {
|
|
|
178
231
|
? compareVersion({
|
|
179
232
|
source: 'Stable tag',
|
|
180
233
|
file: readme.file,
|
|
234
|
+
line: findReadmeHeaderLine(await readFile(join(sourceDir, readme.file), 'utf8'), 'Stable tag'),
|
|
181
235
|
actual: readme.headers['Stable tag'] ?? null,
|
|
182
236
|
}, expected)
|
|
183
237
|
: null,
|
package/package.json
CHANGED