@wpelevator/wp-release 0.2.0 → 0.4.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 +63 -11
- package/dist/checksums.js +3 -2
- package/dist/cli.d.ts +30 -1
- package/dist/cli.js +32 -0
- package/dist/commands/args.d.ts +10 -0
- package/dist/commands/args.js +13 -0
- package/dist/commands/checksums.d.ts +9 -7
- package/dist/commands/checksums.js +3 -3
- package/dist/commands/command.d.ts +2 -2
- package/dist/commands/deploy-svn.d.ts +3 -0
- package/dist/commands/deploy-svn.js +10 -2
- package/dist/commands/sign.d.ts +21 -0
- package/dist/commands/sign.js +55 -0
- package/dist/commands/verify.d.ts +14 -11
- package/dist/commands/verify.js +289 -27
- package/dist/commands/zip.d.ts +11 -8
- package/dist/commands/zip.js +4 -4
- package/dist/deploy-options.d.ts +4 -1
- package/dist/deploy-options.js +5 -4
- package/dist/download.d.ts +15 -0
- package/dist/download.js +27 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +3 -1
- package/dist/signing.d.ts +69 -0
- package/dist/signing.js +140 -0
- package/dist/svn-deploy.d.ts +13 -14
- package/dist/svn-deploy.js +79 -46
- package/package.json +5 -3
package/dist/signing.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { createHash, createPrivateKey, createPublicKey, sign, verify, } from 'node:crypto';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { ValidationError } from "./errors.js";
|
|
5
|
+
export const SIGNATURE_EXTENSION = '.sig';
|
|
6
|
+
/**
|
|
7
|
+
* Environment variable with the base64 Ed25519 signing key.
|
|
8
|
+
*/
|
|
9
|
+
export const SIGNING_KEY_ENV = 'WP_RELEASE_SIGNING_KEY';
|
|
10
|
+
const SEED_BYTES = 32;
|
|
11
|
+
const SECRET_KEY_BYTES = 64;
|
|
12
|
+
const PUBLIC_KEY_BYTES = 32;
|
|
13
|
+
// DER prefix of a PKCS#8 Ed25519 private key, followed by the 32-byte seed.
|
|
14
|
+
const PKCS8_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
|
|
15
|
+
// DER prefix of a SPKI Ed25519 public key, followed by the 32-byte key.
|
|
16
|
+
const SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
|
|
17
|
+
/**
|
|
18
|
+
* Decode a base64 key, accepting the base64url alphabet and whitespace.
|
|
19
|
+
*
|
|
20
|
+
* @param value Encoded key.
|
|
21
|
+
* @return Key bytes.
|
|
22
|
+
*/
|
|
23
|
+
function decodeKey(value) {
|
|
24
|
+
return Buffer.from(value.trim(), 'base64');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get an Ed25519 private key from the base64 format that sodium and the
|
|
28
|
+
* update-client signing_key setting use: a 32-byte seed or a 64-byte
|
|
29
|
+
* libsodium secret key, which is the seed followed by the public key.
|
|
30
|
+
*
|
|
31
|
+
* @param encoded Base64 key.
|
|
32
|
+
* @return Private key.
|
|
33
|
+
* @throws {ValidationError} If the key has an unsupported length.
|
|
34
|
+
*/
|
|
35
|
+
export function parseSigningKey(encoded) {
|
|
36
|
+
const bytes = decodeKey(encoded);
|
|
37
|
+
if (SEED_BYTES !== bytes.length && SECRET_KEY_BYTES !== bytes.length) {
|
|
38
|
+
throw new ValidationError(`The signing key must be a base64 ${SEED_BYTES}-byte Ed25519 seed or ${SECRET_KEY_BYTES}-byte secret key, got ${bytes.length} bytes.`);
|
|
39
|
+
}
|
|
40
|
+
return createPrivateKey({
|
|
41
|
+
key: Buffer.concat([PKCS8_PREFIX, bytes.subarray(0, SEED_BYTES)]),
|
|
42
|
+
format: 'der',
|
|
43
|
+
type: 'pkcs8',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get an Ed25519 public key from its base64 form.
|
|
48
|
+
*
|
|
49
|
+
* @param encoded Base64 32-byte public key.
|
|
50
|
+
* @return Public key.
|
|
51
|
+
* @throws {ValidationError} If the key has an unsupported length.
|
|
52
|
+
*/
|
|
53
|
+
export function parsePublicKey(encoded) {
|
|
54
|
+
const bytes = decodeKey(encoded);
|
|
55
|
+
if (PUBLIC_KEY_BYTES !== bytes.length) {
|
|
56
|
+
throw new ValidationError(`The public key must be a base64 ${PUBLIC_KEY_BYTES}-byte Ed25519 key, got ${bytes.length} bytes.`);
|
|
57
|
+
}
|
|
58
|
+
return createPublicKey({
|
|
59
|
+
key: Buffer.concat([SPKI_PREFIX, bytes]),
|
|
60
|
+
format: 'der',
|
|
61
|
+
type: 'spki',
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get the base64 public key for a private key, in the format that
|
|
66
|
+
* verify_file_signature() callers and update-client take.
|
|
67
|
+
*
|
|
68
|
+
* @param privateKey Private key from parseSigningKey().
|
|
69
|
+
* @return Base64 public key.
|
|
70
|
+
*/
|
|
71
|
+
export function getPublicKey(privateKey) {
|
|
72
|
+
const der = createPublicKey(privateKey).export({
|
|
73
|
+
format: 'der',
|
|
74
|
+
type: 'spki',
|
|
75
|
+
});
|
|
76
|
+
return der.subarray(SPKI_PREFIX.length).toString('base64');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get the raw SHA-384 digest of a file, which is what WordPress core's
|
|
80
|
+
* verify_file_signature() verifies the signature against.
|
|
81
|
+
*
|
|
82
|
+
* @param path File path.
|
|
83
|
+
* @return Digest bytes.
|
|
84
|
+
*/
|
|
85
|
+
export async function getFileDigest(path) {
|
|
86
|
+
const hash = createHash('sha384');
|
|
87
|
+
for await (const chunk of createReadStream(path)) {
|
|
88
|
+
hash.update(chunk);
|
|
89
|
+
}
|
|
90
|
+
return hash.digest();
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Sign a file with a detached Ed25519 signature of its raw SHA-384 digest.
|
|
94
|
+
*
|
|
95
|
+
* @param path File path.
|
|
96
|
+
* @param privateKey Private key from parseSigningKey().
|
|
97
|
+
* @return Base64 signature.
|
|
98
|
+
*/
|
|
99
|
+
export async function signFile(path, privateKey) {
|
|
100
|
+
return sign(null, await getFileDigest(path), privateKey).toString('base64');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Write the signature of a file to a .sig file next to it.
|
|
104
|
+
*
|
|
105
|
+
* @param path File path.
|
|
106
|
+
* @param privateKey Private key from parseSigningKey().
|
|
107
|
+
* @param force Whether to overwrite an existing signature file.
|
|
108
|
+
* @return Signature file path.
|
|
109
|
+
* @throws {ValidationError} If the signature file exists and force isn't set.
|
|
110
|
+
*/
|
|
111
|
+
export async function writeSignature(path, privateKey, force = false) {
|
|
112
|
+
const signaturePath = path + SIGNATURE_EXTENSION;
|
|
113
|
+
const signature = await signFile(path, privateKey);
|
|
114
|
+
await writeFile(signaturePath, signature + '\n', {
|
|
115
|
+
flag: force ? 'w' : 'wx',
|
|
116
|
+
}).catch((error) => {
|
|
117
|
+
throw 'EEXIST' === error.code
|
|
118
|
+
? new ValidationError(`${signaturePath} already exists, pass --force to overwrite it.`)
|
|
119
|
+
: error;
|
|
120
|
+
});
|
|
121
|
+
return signaturePath;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Check a file against a .sig file, which holds one base64 signature per
|
|
125
|
+
* line. The file is valid when any of the signatures match, so that a file
|
|
126
|
+
* can carry the signatures of a key rotation window.
|
|
127
|
+
*
|
|
128
|
+
* @param path File path.
|
|
129
|
+
* @param signaturePath Signature file path.
|
|
130
|
+
* @param publicKey Public key from parsePublicKey().
|
|
131
|
+
* @return Whether a signature matches.
|
|
132
|
+
*/
|
|
133
|
+
export async function verifyFileSignature(path, signaturePath, publicKey) {
|
|
134
|
+
const digest = await getFileDigest(path);
|
|
135
|
+
const signatures = (await readFile(signaturePath, 'utf8'))
|
|
136
|
+
.split('\n')
|
|
137
|
+
.map((line) => line.trim())
|
|
138
|
+
.filter(Boolean);
|
|
139
|
+
return signatures.some((signature) => verify(null, digest, publicKey, Buffer.from(signature, 'base64')));
|
|
140
|
+
}
|
package/dist/svn-deploy.d.ts
CHANGED
|
@@ -71,9 +71,12 @@ export declare class SvnDeploy {
|
|
|
71
71
|
*/
|
|
72
72
|
prepare(): Promise<void>;
|
|
73
73
|
/**
|
|
74
|
-
* Check the plugin files before anything is written. A trunk sync
|
|
75
|
-
* a plugin with a Version header that
|
|
76
|
-
*
|
|
74
|
+
* Check the plugin files before anything is written. A trunk sync or a
|
|
75
|
+
* tag built from the files needs a plugin with a Version header that
|
|
76
|
+
* matches the version. When trunk is also copied to the tag, the readme
|
|
77
|
+
* Stable tag must match too, which a tag-only run skips since a
|
|
78
|
+
* pre-release tag doesn't change the stable tag. Readme-only updates need
|
|
79
|
+
* the readme.
|
|
77
80
|
*
|
|
78
81
|
* @throws {ValidationError} When the files or versions don't match.
|
|
79
82
|
*/
|
|
@@ -88,7 +91,9 @@ export declare class SvnDeploy {
|
|
|
88
91
|
readPluginMeta(sourceDir: string): Promise<PackageMeta>;
|
|
89
92
|
/**
|
|
90
93
|
* Create a sparse checkout with only the directories the flags write to,
|
|
91
|
-
* creating the ones that don't exist in the repository yet.
|
|
94
|
+
* creating the ones that don't exist in the repository yet. An existing
|
|
95
|
+
* tag that's replaced from the files is checked out in full, so that the
|
|
96
|
+
* sync can update and delete its files.
|
|
92
97
|
*
|
|
93
98
|
* @throws {ValidationError} If the SVN tag exists and force isn't set.
|
|
94
99
|
*/
|
|
@@ -109,6 +114,7 @@ export declare class SvnDeploy {
|
|
|
109
114
|
/**
|
|
110
115
|
* Copy the files for each write flag into the working copy and stage
|
|
111
116
|
* additions, deletions and asset MIME types, then copy trunk to the tag.
|
|
117
|
+
* Without a trunk sync, the tag is built from the files instead.
|
|
112
118
|
*/
|
|
113
119
|
sync(): Promise<void>;
|
|
114
120
|
/**
|
|
@@ -133,9 +139,8 @@ export declare class SvnDeploy {
|
|
|
133
139
|
*/
|
|
134
140
|
setAssetMimeTypes(): Promise<void>;
|
|
135
141
|
/**
|
|
136
|
-
* Copy trunk to the SVN tag, replacing an existing tag with
|
|
137
|
-
*
|
|
138
|
-
* repository otherwise, so that tagging doesn't need a trunk checkout.
|
|
142
|
+
* Copy the synced trunk to the SVN tag, replacing an existing tag with
|
|
143
|
+
* force. Without a trunk sync, the tag was already built from the files.
|
|
139
144
|
*/
|
|
140
145
|
copyTag(): Promise<void>;
|
|
141
146
|
/**
|
|
@@ -184,7 +189,7 @@ export declare class SvnDeploy {
|
|
|
184
189
|
/**
|
|
185
190
|
* Check if the deploy reads plugin files, which assets-only runs don't.
|
|
186
191
|
*
|
|
187
|
-
* @return Whether trunk is written.
|
|
192
|
+
* @return Whether trunk, the readme or a tag is written.
|
|
188
193
|
*/
|
|
189
194
|
needsFiles(): boolean;
|
|
190
195
|
/**
|
|
@@ -199,12 +204,6 @@ export declare class SvnDeploy {
|
|
|
199
204
|
* @return Rules description, empty without rules.
|
|
200
205
|
*/
|
|
201
206
|
describeRules(): string;
|
|
202
|
-
/**
|
|
203
|
-
* Get the repository URL of trunk.
|
|
204
|
-
*
|
|
205
|
-
* @return Trunk URL without a double slash.
|
|
206
|
-
*/
|
|
207
|
-
getTrunkUrl(): string;
|
|
208
207
|
/**
|
|
209
208
|
* Get the path of the tag in the working copy.
|
|
210
209
|
*
|
package/dist/svn-deploy.js
CHANGED
|
@@ -9,7 +9,7 @@ import { CredentialsError, ValidationError } from "./errors.js";
|
|
|
9
9
|
import { loadFileRules, selectFiles } from "./file-selection.js";
|
|
10
10
|
import { readPackageMeta } from "./package-meta.js";
|
|
11
11
|
import { Svn } from "./svn.js";
|
|
12
|
-
import { syncDirectory } from "./sync.js";
|
|
12
|
+
import { copyFileIfChanged, syncDirectory } from "./sync.js";
|
|
13
13
|
import { extractZip } from "./unzip.js";
|
|
14
14
|
import { findVersionMismatches } from "./version-check.js";
|
|
15
15
|
/**
|
|
@@ -95,29 +95,39 @@ export class SvnDeploy {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
98
|
-
* Check the plugin files before anything is written. A trunk sync
|
|
99
|
-
* a plugin with a Version header that
|
|
100
|
-
*
|
|
98
|
+
* Check the plugin files before anything is written. A trunk sync or a
|
|
99
|
+
* tag built from the files needs a plugin with a Version header that
|
|
100
|
+
* matches the version. When trunk is also copied to the tag, the readme
|
|
101
|
+
* Stable tag must match too, which a tag-only run skips since a
|
|
102
|
+
* pre-release tag doesn't change the stable tag. Readme-only updates need
|
|
103
|
+
* the readme.
|
|
101
104
|
*
|
|
102
105
|
* @throws {ValidationError} When the files or versions don't match.
|
|
103
106
|
*/
|
|
104
107
|
async checkVersions() {
|
|
105
|
-
const { trunk, svnTag, slug } = this.options;
|
|
108
|
+
const { trunk, readmeOnly, svnTag, slug, readme } = this.options;
|
|
106
109
|
const sourceDir = this.filesDir ?? this.options.sourceDir;
|
|
107
|
-
if (
|
|
110
|
+
if (this.needsFiles()) {
|
|
108
111
|
const meta = await this.readPluginMeta(sourceDir);
|
|
109
|
-
|
|
110
|
-
sourceDir
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
if (readmeOnly && !meta.readme) {
|
|
113
|
+
throw new ValidationError(`${readme} doesn't exist in ${sourceDir}.`);
|
|
114
|
+
}
|
|
115
|
+
if (trunk || svnTag) {
|
|
116
|
+
const { version, mismatches } = await findVersionMismatches({
|
|
117
|
+
sourceDir,
|
|
118
|
+
meta,
|
|
119
|
+
slug,
|
|
120
|
+
version: this.options.version,
|
|
121
|
+
stableTag: Boolean(trunk && svnTag),
|
|
122
|
+
});
|
|
123
|
+
this.version = version;
|
|
124
|
+
if (mismatches.length) {
|
|
125
|
+
throw new ValidationError(mismatches
|
|
126
|
+
.map(({ message }) => message)
|
|
127
|
+
.join('\n'));
|
|
128
|
+
}
|
|
129
|
+
this.logger.info(`Deploying version ${version}.`);
|
|
119
130
|
}
|
|
120
|
-
this.logger.info(`Deploying version ${version}.`);
|
|
121
131
|
}
|
|
122
132
|
}
|
|
123
133
|
/**
|
|
@@ -140,7 +150,9 @@ export class SvnDeploy {
|
|
|
140
150
|
}
|
|
141
151
|
/**
|
|
142
152
|
* Create a sparse checkout with only the directories the flags write to,
|
|
143
|
-
* creating the ones that don't exist in the repository yet.
|
|
153
|
+
* creating the ones that don't exist in the repository yet. An existing
|
|
154
|
+
* tag that's replaced from the files is checked out in full, so that the
|
|
155
|
+
* sync can update and delete its files.
|
|
144
156
|
*
|
|
145
157
|
* @throws {ValidationError} If the SVN tag exists and force isn't set.
|
|
146
158
|
*/
|
|
@@ -159,6 +171,12 @@ export class SvnDeploy {
|
|
|
159
171
|
if (tagPath && !this.options.force && this.hasTag()) {
|
|
160
172
|
throw new ValidationError(`${tagPath} already exists in ${this.options.svnUrl}. Pass --force to replace it.`);
|
|
161
173
|
}
|
|
174
|
+
if (tagPath && !this.options.trunk && this.hasTag()) {
|
|
175
|
+
await this.svn.update({
|
|
176
|
+
path: join(workingCopy, tagPath),
|
|
177
|
+
depth: 'infinity',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
162
180
|
}
|
|
163
181
|
/**
|
|
164
182
|
* Create and add a top-level directory that's missing from the repository,
|
|
@@ -185,9 +203,11 @@ export class SvnDeploy {
|
|
|
185
203
|
/**
|
|
186
204
|
* Copy the files for each write flag into the working copy and stage
|
|
187
205
|
* additions, deletions and asset MIME types, then copy trunk to the tag.
|
|
206
|
+
* Without a trunk sync, the tag is built from the files instead.
|
|
188
207
|
*/
|
|
189
208
|
async sync() {
|
|
190
|
-
const { trunk, assets } = this.options;
|
|
209
|
+
const { trunk, readmeOnly, assets, readme } = this.options;
|
|
210
|
+
const tagPath = this.getTagPath();
|
|
191
211
|
const workingCopy = this.getWorkingCopy();
|
|
192
212
|
const sourceDir = this.filesDir ?? this.options.sourceDir;
|
|
193
213
|
if (trunk) {
|
|
@@ -197,6 +217,16 @@ export class SvnDeploy {
|
|
|
197
217
|
targetDir: join(workingCopy, 'trunk'),
|
|
198
218
|
});
|
|
199
219
|
}
|
|
220
|
+
if (tagPath && !trunk) {
|
|
221
|
+
await syncDirectory({
|
|
222
|
+
sourceDir,
|
|
223
|
+
files: await this.getSourceFiles(sourceDir),
|
|
224
|
+
targetDir: join(workingCopy, tagPath),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (readmeOnly) {
|
|
228
|
+
await copyFileIfChanged(join(sourceDir, readme), join(workingCopy, 'trunk', readme));
|
|
229
|
+
}
|
|
200
230
|
if (assets) {
|
|
201
231
|
await this.syncAssets();
|
|
202
232
|
}
|
|
@@ -266,20 +296,22 @@ export class SvnDeploy {
|
|
|
266
296
|
}
|
|
267
297
|
}
|
|
268
298
|
/**
|
|
269
|
-
* Copy trunk to the SVN tag, replacing an existing tag with
|
|
270
|
-
*
|
|
271
|
-
* repository otherwise, so that tagging doesn't need a trunk checkout.
|
|
299
|
+
* Copy the synced trunk to the SVN tag, replacing an existing tag with
|
|
300
|
+
* force. Without a trunk sync, the tag was already built from the files.
|
|
272
301
|
*/
|
|
273
302
|
async copyTag() {
|
|
274
303
|
const tagPath = this.getTagPath();
|
|
275
304
|
const workingCopy = this.getWorkingCopy();
|
|
276
|
-
if (tagPath && this.
|
|
277
|
-
this.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
305
|
+
if (tagPath && this.options.trunk) {
|
|
306
|
+
if (this.hasTag()) {
|
|
307
|
+
this.logger.warning(`Replacing the existing ${tagPath}.`);
|
|
308
|
+
await this.svn.remove({
|
|
309
|
+
paths: [tagPath],
|
|
310
|
+
cwd: workingCopy,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
281
313
|
await this.svn.copy({
|
|
282
|
-
from:
|
|
314
|
+
from: 'trunk',
|
|
283
315
|
to: tagPath,
|
|
284
316
|
cwd: workingCopy,
|
|
285
317
|
});
|
|
@@ -367,7 +399,7 @@ export class SvnDeploy {
|
|
|
367
399
|
* @return Planned changes, in order.
|
|
368
400
|
*/
|
|
369
401
|
getPlan() {
|
|
370
|
-
const { svnUrl, trunk, assets, assetsDir, force } = this.options;
|
|
402
|
+
const { svnUrl, trunk, readmeOnly, assets, assetsDir, readme, force } = this.options;
|
|
371
403
|
const tagPath = this.getTagPath();
|
|
372
404
|
const message = this.options.message ?? this.getDefaultMessage();
|
|
373
405
|
const plan = [
|
|
@@ -377,7 +409,11 @@ export class SvnDeploy {
|
|
|
377
409
|
tagPath && !force && `Stop if ${tagPath} already exists`,
|
|
378
410
|
tagPath && force && `Replace ${tagPath} if it exists`,
|
|
379
411
|
trunk && `Sync ${this.describeSource()} to trunk/`,
|
|
380
|
-
|
|
412
|
+
readmeOnly && `Copy ${readme} to trunk/`,
|
|
413
|
+
tagPath && trunk && `Copy trunk/ to ${tagPath}`,
|
|
414
|
+
tagPath &&
|
|
415
|
+
!trunk &&
|
|
416
|
+
`Sync ${this.describeSource()} to ${tagPath}`,
|
|
381
417
|
assets && `Sync ${assetsDir} to assets/`,
|
|
382
418
|
this.options.dryRun
|
|
383
419
|
? `Skip the commit "${message}" (dry run)`
|
|
@@ -392,9 +428,9 @@ export class SvnDeploy {
|
|
|
392
428
|
* @return Relative paths with their SVN checkout depth.
|
|
393
429
|
*/
|
|
394
430
|
getCheckoutPaths() {
|
|
395
|
-
const { trunk, svnTag, assets } = this.options;
|
|
431
|
+
const { trunk, readmeOnly, svnTag, assets } = this.options;
|
|
396
432
|
const paths = [];
|
|
397
|
-
if (trunk) {
|
|
433
|
+
if (trunk || readmeOnly) {
|
|
398
434
|
paths.push({ path: 'trunk', depth: 'infinity' });
|
|
399
435
|
}
|
|
400
436
|
if (svnTag) {
|
|
@@ -411,16 +447,17 @@ export class SvnDeploy {
|
|
|
411
447
|
* @return Whether to check out and commit.
|
|
412
448
|
*/
|
|
413
449
|
hasWrites() {
|
|
414
|
-
const { trunk, svnTag, assets } = this.options;
|
|
415
|
-
return Boolean(trunk || svnTag || assets);
|
|
450
|
+
const { trunk, readmeOnly, svnTag, assets } = this.options;
|
|
451
|
+
return Boolean(trunk || readmeOnly || svnTag || assets);
|
|
416
452
|
}
|
|
417
453
|
/**
|
|
418
454
|
* Check if the deploy reads plugin files, which assets-only runs don't.
|
|
419
455
|
*
|
|
420
|
-
* @return Whether trunk is written.
|
|
456
|
+
* @return Whether trunk, the readme or a tag is written.
|
|
421
457
|
*/
|
|
422
458
|
needsFiles() {
|
|
423
|
-
|
|
459
|
+
const { trunk, readmeOnly, svnTag } = this.options;
|
|
460
|
+
return Boolean(trunk || readmeOnly || svnTag);
|
|
424
461
|
}
|
|
425
462
|
/**
|
|
426
463
|
* Describe the files that are synced, including the ignore rules.
|
|
@@ -448,14 +485,6 @@ export class SvnDeploy {
|
|
|
448
485
|
];
|
|
449
486
|
return rules.filter(Boolean).join(', ');
|
|
450
487
|
}
|
|
451
|
-
/**
|
|
452
|
-
* Get the repository URL of trunk.
|
|
453
|
-
*
|
|
454
|
-
* @return Trunk URL without a double slash.
|
|
455
|
-
*/
|
|
456
|
-
getTrunkUrl() {
|
|
457
|
-
return `${this.options.svnUrl.replace(/\/+$/, '')}/trunk`;
|
|
458
|
-
}
|
|
459
488
|
/**
|
|
460
489
|
* Get the path of the tag in the working copy.
|
|
461
490
|
*
|
|
@@ -469,11 +498,15 @@ export class SvnDeploy {
|
|
|
469
498
|
return null;
|
|
470
499
|
}
|
|
471
500
|
getDefaultMessage() {
|
|
472
|
-
const { trunk, svnTag, assets } = this.options;
|
|
501
|
+
const { trunk, readmeOnly, svnTag, assets } = this.options;
|
|
473
502
|
if (svnTag) {
|
|
474
503
|
return `Release version ${svnTag}`;
|
|
475
504
|
}
|
|
476
|
-
const parts = [
|
|
505
|
+
const parts = [
|
|
506
|
+
trunk && 'trunk',
|
|
507
|
+
readmeOnly && 'readme',
|
|
508
|
+
assets && 'assets',
|
|
509
|
+
];
|
|
477
510
|
return `Update ${parts.filter(Boolean).join(' and ')}`;
|
|
478
511
|
}
|
|
479
512
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wpelevator/wp-release",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Package, checksum, verify and version-check WordPress plugins and themes, and deploy plugins to WordPress.org SVN",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Package, checksum, sign, verify and version-check WordPress plugins and themes, and deploy plugins to WordPress.org SVN",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -35,7 +35,9 @@
|
|
|
35
35
|
"release",
|
|
36
36
|
"deploy",
|
|
37
37
|
"svn",
|
|
38
|
-
"zip"
|
|
38
|
+
"zip",
|
|
39
|
+
"checksums",
|
|
40
|
+
"signing"
|
|
39
41
|
],
|
|
40
42
|
"dependencies": {
|
|
41
43
|
"ignore": "^7.0.12",
|