@wpelevator/wp-release 0.12.0 → 0.14.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 +150 -19
- package/dist/changeset-config.d.ts +91 -5
- package/dist/changeset-config.js +108 -11
- package/dist/changeset-dependents.d.ts +19 -8
- package/dist/changeset-dependents.js +96 -34
- package/dist/changeset-files.d.ts +6 -5
- package/dist/changeset-files.js +7 -5
- package/dist/changeset-packages.d.ts +55 -8
- package/dist/changeset-packages.js +85 -14
- package/dist/changeset-prompt.d.ts +119 -0
- package/dist/changeset-prompt.js +203 -0
- package/dist/changeset-release.d.ts +43 -5
- package/dist/changeset-release.js +99 -16
- package/dist/changesets.d.ts +6 -2
- package/dist/changesets.js +18 -12
- package/dist/cli.d.ts +7 -1
- package/dist/cli.js +51 -14
- package/dist/commands/args.d.ts +0 -21
- package/dist/commands/args.js +1 -35
- package/dist/commands/changeset-add.d.ts +5 -2
- package/dist/commands/changeset-add.js +109 -9
- package/dist/commands/changeset-args.d.ts +24 -5
- package/dist/commands/changeset-args.js +57 -14
- package/dist/commands/changeset-status.d.ts +3 -1
- package/dist/commands/changeset-status.js +2 -1
- package/dist/commands/changeset-tag.d.ts +1 -1
- package/dist/commands/changeset-tag.js +6 -3
- package/dist/commands/changeset-version.d.ts +7 -1
- package/dist/commands/changeset-version.js +17 -4
- package/dist/commands/checksums.js +2 -2
- package/dist/commands/command.d.ts +5 -0
- package/dist/commands/deploy-git.d.ts +24 -4
- package/dist/commands/deploy-git.js +57 -3
- package/dist/commands/deploy-svn.d.ts +24 -4
- package/dist/commands/deploy-svn.js +57 -2
- package/dist/commands/keygen.d.ts +3 -0
- package/dist/commands/keygen.js +6 -1
- package/dist/commands/package-deploys.d.ts +86 -0
- package/dist/commands/package-deploys.js +113 -0
- package/dist/commands/sign.d.ts +19 -1
- package/dist/commands/sign.js +47 -12
- package/dist/commands/verify.d.ts +3 -0
- package/dist/commands/verify.js +43 -24
- package/dist/commands/zip.d.ts +12 -8
- package/dist/commands/zip.js +135 -78
- package/dist/ed25519.d.ts +25 -0
- package/dist/ed25519.js +44 -0
- package/dist/file-selection.d.ts +21 -0
- package/dist/file-selection.js +35 -0
- package/dist/git-deploy.d.ts +32 -0
- package/dist/git-deploy.js +57 -1
- package/dist/git.d.ts +17 -0
- package/dist/git.js +23 -0
- package/dist/index.d.ts +13 -5
- package/dist/index.js +7 -3
- package/dist/minisign.d.ts +41 -0
- package/dist/minisign.js +128 -0
- package/dist/package-deploy.d.ts +133 -0
- package/dist/package-deploy.js +323 -0
- package/dist/package-zip.d.ts +52 -0
- package/dist/package-zip.js +78 -0
- package/dist/release-repository.d.ts +18 -5
- package/dist/release-repository.js +51 -11
- package/dist/release-tags.d.ts +3 -1
- package/dist/release-tags.js +5 -2
- package/dist/signing.d.ts +64 -9
- package/dist/signing.js +113 -40
- package/dist/ssh-signature.d.ts +43 -0
- package/dist/ssh-signature.js +156 -0
- package/dist/summary.d.ts +3 -3
- package/dist/summary.js +2 -2
- package/dist/tag-format.d.ts +23 -14
- package/dist/tag-format.js +26 -40
- package/dist/version-files.d.ts +8 -8
- package/dist/version-files.js +22 -18
- package/dist/versions.d.ts +28 -3
- package/dist/versions.js +84 -9
- package/package.json +2 -2
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { realpath } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { loadChangesetConfig, toTargetList } from "./changeset-config.js";
|
|
4
|
+
import { discoverDeployPackages, discoverConfiguredPackages, findPackage, } from "./changeset-packages.js";
|
|
5
|
+
import { ValidationError } from "./errors.js";
|
|
6
|
+
import { parseRepositoryUrl, redactUrls } from "./git.js";
|
|
7
|
+
import { ReleaseRepository } from "./release-repository.js";
|
|
8
|
+
import { DEFAULT_TAG_FORMAT, formatTag, formatTemplate } from "./tag-format.js";
|
|
9
|
+
import { isPrerelease, isVersion } from "./versions.js";
|
|
10
|
+
/**
|
|
11
|
+
* Get the Git deploys of the configured packages that a ref releases: every
|
|
12
|
+
* Git target of the package of a release tag that releases tags, or of every
|
|
13
|
+
* package with a Git target that releases the branch.
|
|
14
|
+
*
|
|
15
|
+
* @param options Repository, ref and package selection.
|
|
16
|
+
* @return Deploys in the configured package and target order.
|
|
17
|
+
* @throws {ValidationError} Without a config, or for an invalid one or an unknown package.
|
|
18
|
+
*/
|
|
19
|
+
export async function getGitDeploys(options) {
|
|
20
|
+
const { config, releases, root } = await loadDeployContext(options);
|
|
21
|
+
const deploys = await Promise.all(releases.map(async (release) => {
|
|
22
|
+
const targets = toTargetList(release.package.group.deploy?.git);
|
|
23
|
+
const resolved = await Promise.all(targets
|
|
24
|
+
.filter((target) => isGitTargetReleased(target, release))
|
|
25
|
+
.map((target) => getGitDeploy(target, release, config, root, options)));
|
|
26
|
+
return resolved.filter((deploy) => undefined !== deploy);
|
|
27
|
+
}));
|
|
28
|
+
return deploys.flat();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the WordPress.org SVN deploys of the configured package that a release
|
|
32
|
+
* tag releases. A stable release syncs trunk/, the tag and the assets, and a
|
|
33
|
+
* pre-release only creates its tag. Branches aren't deployed to SVN.
|
|
34
|
+
*
|
|
35
|
+
* @param options Repository, ref and package selection.
|
|
36
|
+
* @return Deploys in the configured target order.
|
|
37
|
+
* @throws {ValidationError} Without a config, or for an invalid one or an unknown package.
|
|
38
|
+
*/
|
|
39
|
+
export async function getSvnDeploys(options) {
|
|
40
|
+
const { config, releases } = await loadDeployContext(options);
|
|
41
|
+
const deploys = await Promise.all(releases
|
|
42
|
+
.filter((release) => release.version)
|
|
43
|
+
.map(async (release) => {
|
|
44
|
+
const targets = toTargetList(release.package.group.deploy?.svn);
|
|
45
|
+
const resolved = await Promise.all(targets.map((target) => getSvnDeploy(target, release, config, options)));
|
|
46
|
+
return resolved.filter((deploy) => undefined !== deploy);
|
|
47
|
+
}));
|
|
48
|
+
return deploys.flat();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the packages with deploy targets and the files that their deploys
|
|
52
|
+
* ship, for example to build a ZIP with the same files.
|
|
53
|
+
*
|
|
54
|
+
* @param options Repository root and config file.
|
|
55
|
+
* @param options.cwd Repository root with the wp-release config.
|
|
56
|
+
* @param options.configFile Config file, instead of the conventional one at the root.
|
|
57
|
+
* @return Packages in the configured order.
|
|
58
|
+
* @throws {ValidationError} Without a config, or for an invalid one.
|
|
59
|
+
*/
|
|
60
|
+
export async function getDeployPackages(options) {
|
|
61
|
+
const config = await requireConfig(options);
|
|
62
|
+
const packages = await discoverDeployPackages(new ReleaseRepository({ cwd: options.cwd }), config);
|
|
63
|
+
return packages.map((pkg) => ({
|
|
64
|
+
...toDeployPackage(pkg),
|
|
65
|
+
files: getFileSelection(pkg, config),
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get a configured package with the files that its releases ship, from the
|
|
70
|
+
* files config of its package group, which its deploys use too, for example
|
|
71
|
+
* to build its ZIP with the same files. Any package of the config works, with
|
|
72
|
+
* or without a version or deploy targets.
|
|
73
|
+
*
|
|
74
|
+
* @param options Repository root, config file and package.
|
|
75
|
+
* @param options.cwd Repository root with the wp-release config.
|
|
76
|
+
* @param options.configFile Config file, instead of the conventional one at the root.
|
|
77
|
+
* @param options.package Package identifier or path under the configured root.
|
|
78
|
+
* @return The package and its file selection.
|
|
79
|
+
* @throws {ValidationError} Without a config, for an invalid one or an unknown package.
|
|
80
|
+
*/
|
|
81
|
+
export async function getPackageFiles(options) {
|
|
82
|
+
const config = await requireConfig(options);
|
|
83
|
+
const pkg = findPackage(await discoverConfiguredPackages(new ReleaseRepository({ cwd: options.cwd }), config), options.package);
|
|
84
|
+
if (!pkg) {
|
|
85
|
+
throw new ValidationError(`Unknown package "${options.package}".`);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
...toDeployPackage(pkg),
|
|
89
|
+
files: getFileSelection(pkg, config),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function requireConfig(options) {
|
|
93
|
+
const loaded = await loadChangesetConfig({
|
|
94
|
+
cwd: options.cwd,
|
|
95
|
+
...(options.configFile ? { configFile: options.configFile } : {}),
|
|
96
|
+
...(options.tagFormat ? { tagFormat: options.tagFormat } : {}),
|
|
97
|
+
});
|
|
98
|
+
if (!loaded) {
|
|
99
|
+
throw new ValidationError('No wp-release config found. Add wp-release.config.js or pass --config <path>.');
|
|
100
|
+
}
|
|
101
|
+
return loaded.config;
|
|
102
|
+
}
|
|
103
|
+
async function loadDeployContext(options) {
|
|
104
|
+
const config = await requireConfig(options);
|
|
105
|
+
const repository = new ReleaseRepository({
|
|
106
|
+
cwd: options.cwd,
|
|
107
|
+
runner: options.git,
|
|
108
|
+
});
|
|
109
|
+
const packages = selectPackages(await discoverDeployPackages(repository, config), options.packages ?? []);
|
|
110
|
+
const root = await repository.run(['rev-parse', '--show-toplevel']);
|
|
111
|
+
const revision = await repository.run(['rev-parse', 'HEAD^{commit}']);
|
|
112
|
+
return {
|
|
113
|
+
config,
|
|
114
|
+
releases: getReleases(packages, config, options.ref).map((release) => ({ ...release, revision: revision.trim() })),
|
|
115
|
+
root: root.trim(),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function selectPackages(packages, identifiers) {
|
|
119
|
+
const selected = identifiers.map((identifier) => {
|
|
120
|
+
const pkg = findPackage(packages, identifier);
|
|
121
|
+
if (!pkg) {
|
|
122
|
+
throw new ValidationError(`Unknown package "${identifier}", or it has no deploy targets.`);
|
|
123
|
+
}
|
|
124
|
+
return pkg;
|
|
125
|
+
});
|
|
126
|
+
return identifiers.length
|
|
127
|
+
? packages.filter((pkg) => selected.includes(pkg))
|
|
128
|
+
: packages;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Resolve the releases of a ref: the package of a release tag, or every
|
|
132
|
+
* package for a branch.
|
|
133
|
+
*
|
|
134
|
+
* @param packages Packages with deploy targets.
|
|
135
|
+
* @param config Repository release configuration.
|
|
136
|
+
* @param ref Ref to deploy.
|
|
137
|
+
* @return Releases.
|
|
138
|
+
*/
|
|
139
|
+
function getReleases(packages, config, ref) {
|
|
140
|
+
const tagFormat = config.tagFormat ?? DEFAULT_TAG_FORMAT;
|
|
141
|
+
const tag = ref.startsWith('refs/tags/')
|
|
142
|
+
? ref.slice('refs/tags/'.length)
|
|
143
|
+
: ref;
|
|
144
|
+
const tagged = ref.startsWith('refs/heads/')
|
|
145
|
+
? []
|
|
146
|
+
: packages.flatMap((pkg) => getTaggedVersion(pkg, tagFormat, tag));
|
|
147
|
+
const branch = ref.replace(/^refs\/heads\//, '');
|
|
148
|
+
return ref.startsWith('refs/tags/') || tagged.length
|
|
149
|
+
? tagged
|
|
150
|
+
: packages.map((pkg) => ({ package: pkg, branch }));
|
|
151
|
+
}
|
|
152
|
+
function getTaggedVersion(pkg, tagFormat, tag) {
|
|
153
|
+
const prefix = formatTag(tagFormat, pkg, '');
|
|
154
|
+
const version = tag.startsWith(prefix) ? tag.slice(prefix.length) : '';
|
|
155
|
+
return pkg.group.version && isVersion(version)
|
|
156
|
+
? [{ package: pkg, version }]
|
|
157
|
+
: [];
|
|
158
|
+
}
|
|
159
|
+
function isGitTargetReleased(target, release) {
|
|
160
|
+
const branches = target.branches ?? true;
|
|
161
|
+
const branchReleased = Array.isArray(branches)
|
|
162
|
+
? branches.includes(release.branch ?? '')
|
|
163
|
+
: branches;
|
|
164
|
+
return release.version ? false !== target.tag : branchReleased;
|
|
165
|
+
}
|
|
166
|
+
async function getGitDeploy(target, release, config, root, options) {
|
|
167
|
+
const pkg = release.package;
|
|
168
|
+
const tag = release.version
|
|
169
|
+
? formatTag(target.tag || '{version}', pkg, release.version)
|
|
170
|
+
: undefined;
|
|
171
|
+
const ref = tag ?? release.branch ?? '';
|
|
172
|
+
const repository = await resolveTemplate(target.repository, release, ref);
|
|
173
|
+
const message = target.message
|
|
174
|
+
? await resolveTemplate(target.message, release, ref)
|
|
175
|
+
: '';
|
|
176
|
+
// Git reports the real path of the root, so compare it with the real path
|
|
177
|
+
// of the package, like on macOS where the temporary directory is a link.
|
|
178
|
+
const splitPrefix = false === target.history
|
|
179
|
+
? ''
|
|
180
|
+
: path
|
|
181
|
+
.relative(root, await realpath(pkg.directory))
|
|
182
|
+
.split(path.sep)
|
|
183
|
+
.join('/');
|
|
184
|
+
const location = repository ? parseRepositoryUrl(repository) : undefined;
|
|
185
|
+
return repository
|
|
186
|
+
? {
|
|
187
|
+
package: toDeployPackage(pkg),
|
|
188
|
+
release: toRelease(release),
|
|
189
|
+
target: redactUrls(repository),
|
|
190
|
+
targetRef: ref,
|
|
191
|
+
...(location
|
|
192
|
+
? { targetHost: location.host, targetPath: location.path }
|
|
193
|
+
: {}),
|
|
194
|
+
label: `${pkg.name} to ${redactUrls(repository)} ${ref}`,
|
|
195
|
+
input: {
|
|
196
|
+
cwd: options.cwd,
|
|
197
|
+
sourceDir: pkg.directory,
|
|
198
|
+
gitUrl: repository,
|
|
199
|
+
...(release.branch ? { branch: release.branch } : {}),
|
|
200
|
+
...(tag ? { tag } : {}),
|
|
201
|
+
...(splitPrefix ? { splitPrefix } : {}),
|
|
202
|
+
...(message ? { message } : {}),
|
|
203
|
+
...getFileSelection(pkg, config),
|
|
204
|
+
dryRun: Boolean(options.dryRun),
|
|
205
|
+
},
|
|
206
|
+
}
|
|
207
|
+
: undefined;
|
|
208
|
+
}
|
|
209
|
+
async function getSvnDeploy(target, release, config, options) {
|
|
210
|
+
const pkg = release.package;
|
|
211
|
+
const version = release.version ?? '';
|
|
212
|
+
const stable = !isPrerelease(version);
|
|
213
|
+
// The WordPress.org slug is the name of the package directory by default.
|
|
214
|
+
const slug = target.slug
|
|
215
|
+
? await resolveTemplate(target.slug, release, version)
|
|
216
|
+
: path.basename(pkg.directory);
|
|
217
|
+
const message = target.message
|
|
218
|
+
? await resolveTemplate(target.message, release, version)
|
|
219
|
+
: '';
|
|
220
|
+
return slug
|
|
221
|
+
? {
|
|
222
|
+
package: toDeployPackage(pkg),
|
|
223
|
+
release: toRelease(release),
|
|
224
|
+
target: `WordPress.org ${slug}`,
|
|
225
|
+
targetRef: version,
|
|
226
|
+
label: `${pkg.name} to WordPress.org ${slug} ${version}`,
|
|
227
|
+
input: {
|
|
228
|
+
cwd: options.cwd,
|
|
229
|
+
sourceDir: pkg.directory,
|
|
230
|
+
slug,
|
|
231
|
+
version,
|
|
232
|
+
svnTag: version,
|
|
233
|
+
trunk: stable,
|
|
234
|
+
...(stable && target.assetsDir
|
|
235
|
+
? {
|
|
236
|
+
assetsDir: path.join(pkg.directory, target.assetsDir),
|
|
237
|
+
}
|
|
238
|
+
: {}),
|
|
239
|
+
...(target.readme ? { readme: target.readme } : {}),
|
|
240
|
+
...(target.url ? { svnUrl: target.url } : {}),
|
|
241
|
+
...(message ? { message } : {}),
|
|
242
|
+
...getFileSelection(pkg, config),
|
|
243
|
+
dryRun: Boolean(options.dryRun),
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
: undefined;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Resolve a template with the package and release placeholders, or call the
|
|
250
|
+
* callback with the package and its release.
|
|
251
|
+
*
|
|
252
|
+
* @param template Template or callback.
|
|
253
|
+
* @param release Release of the package.
|
|
254
|
+
* @param ref Tag or branch that the deploy writes in the target.
|
|
255
|
+
* @return Resolved value, empty for a skipped target or the default message.
|
|
256
|
+
*/
|
|
257
|
+
async function resolveTemplate(template, release, ref) {
|
|
258
|
+
const value = 'function' === typeof template
|
|
259
|
+
? await template({
|
|
260
|
+
package: toDeployPackage(release.package),
|
|
261
|
+
...toRelease(release),
|
|
262
|
+
ref,
|
|
263
|
+
revision: release.revision,
|
|
264
|
+
})
|
|
265
|
+
: formatTemplate(template, release.package, release.version ?? '')
|
|
266
|
+
.replaceAll('{branch}', release.branch ?? '')
|
|
267
|
+
.replaceAll('{ref}', ref)
|
|
268
|
+
.replaceAll('{revision}', release.revision);
|
|
269
|
+
return value?.trim() ?? '';
|
|
270
|
+
}
|
|
271
|
+
function getFileSelection(pkg, config) {
|
|
272
|
+
const shared = config.files ?? {};
|
|
273
|
+
const files = pkg.group.files ?? {};
|
|
274
|
+
const setting = undefined === files.distignore ? shared.distignore : files.distignore;
|
|
275
|
+
const distignore = 'string' === typeof setting
|
|
276
|
+
? path.join(pkg.directory, setting)
|
|
277
|
+
: setting;
|
|
278
|
+
const exclude = [...(shared.exclude ?? []), ...(files.exclude ?? [])];
|
|
279
|
+
return {
|
|
280
|
+
...(undefined === distignore ? {} : { distignore }),
|
|
281
|
+
...(exclude.length ? { exclude } : {}),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function toDeployPackage(pkg) {
|
|
285
|
+
return {
|
|
286
|
+
name: pkg.name,
|
|
287
|
+
directory: pkg.directory,
|
|
288
|
+
path: pkg.path,
|
|
289
|
+
type: pkg.type,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function toRelease(release) {
|
|
293
|
+
return {
|
|
294
|
+
...(release.version ? { version: release.version } : {}),
|
|
295
|
+
...(release.branch ? { branch: release.branch } : {}),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Group deploys by package, for a preview of what a ref deploys or a CI
|
|
300
|
+
* matrix with a job for each package.
|
|
301
|
+
*
|
|
302
|
+
* @param deploys Deploys from getGitDeploys() or getSvnDeploys().
|
|
303
|
+
* @return One item for each package, in the deploy order.
|
|
304
|
+
*/
|
|
305
|
+
export function getDeployPlan(deploys) {
|
|
306
|
+
const names = [
|
|
307
|
+
...new Set(deploys.map((item) => item.package.name)),
|
|
308
|
+
];
|
|
309
|
+
return names.map((name) => {
|
|
310
|
+
const items = deploys.filter((item) => item.package.name === name);
|
|
311
|
+
return {
|
|
312
|
+
package: name,
|
|
313
|
+
...items[0]?.release,
|
|
314
|
+
targets: items.map((item) => ({
|
|
315
|
+
target: item.target,
|
|
316
|
+
ref: item.targetRef,
|
|
317
|
+
...(item.targetHost
|
|
318
|
+
? { host: item.targetHost, path: item.targetPath }
|
|
319
|
+
: {}),
|
|
320
|
+
})),
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { PackageType } from './checksums.ts';
|
|
2
|
+
import type { FileSelection } from './file-selection.ts';
|
|
3
|
+
export interface ZipPackageInput {
|
|
4
|
+
/** Directory to archive. */
|
|
5
|
+
sourceDir: string;
|
|
6
|
+
/** File selection from resolveFileSelection(). */
|
|
7
|
+
selection: FileSelection;
|
|
8
|
+
/** Archive path. */
|
|
9
|
+
zipPath: string;
|
|
10
|
+
/** Folder inside the archive, or null for the root. */
|
|
11
|
+
rootFolder: string | null;
|
|
12
|
+
/** Plugin or theme slug, for the checksums manifest. */
|
|
13
|
+
slug: string;
|
|
14
|
+
/** Version for the checksums manifest. */
|
|
15
|
+
version?: string | null;
|
|
16
|
+
/** Package type for the checksums manifest. */
|
|
17
|
+
type?: PackageType;
|
|
18
|
+
/** Also write the checksums manifest next to the archive. */
|
|
19
|
+
checksums?: boolean;
|
|
20
|
+
/** Overwrite existing output files. */
|
|
21
|
+
force?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Result of zipPackage(). */
|
|
24
|
+
export interface ZipPackageResult {
|
|
25
|
+
/** Absolute path of the ZIP. */
|
|
26
|
+
zipPath: string;
|
|
27
|
+
/** Absolute path of the checksums manifest, set with checksums. */
|
|
28
|
+
checksumsPath?: string;
|
|
29
|
+
/** Number of archived files. */
|
|
30
|
+
files: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get the checksums manifest path next to an archive, with .checksums.json
|
|
34
|
+
* instead of .zip.
|
|
35
|
+
*
|
|
36
|
+
* @param zipPath Archive path.
|
|
37
|
+
* @return Manifest path.
|
|
38
|
+
*/
|
|
39
|
+
export declare function getChecksumsPath(zipPath: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Write a ZIP of the selected files of a plugin, theme or package, and
|
|
42
|
+
* optionally the checksums manifest of the same files next to it. The zip
|
|
43
|
+
* command, with a directory or a configured package release, and the
|
|
44
|
+
* snapshot builds of a monorepo all use it, so that every ZIP of a package
|
|
45
|
+
* selects its files the same way. Output files inside the source directory,
|
|
46
|
+
* and their signatures, are never archived.
|
|
47
|
+
*
|
|
48
|
+
* @param input Files, archive path and manifest options.
|
|
49
|
+
* @return Paths of the written files and the number of archived files.
|
|
50
|
+
* @throws {ValidationError} Without selected files, or when an output file exists without force.
|
|
51
|
+
*/
|
|
52
|
+
export declare function zipPackage(input: ZipPackageInput): Promise<ZipPackageResult>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { CHECKSUMS_EXTENSION, createChecksums, writeChecksums, } from "./checksums.js";
|
|
3
|
+
import { ValidationError } from "./errors.js";
|
|
4
|
+
import { ignoreOwnFile, ignoreOwnSignature, loadFileRules, selectFiles, } from "./file-selection.js";
|
|
5
|
+
import { createZip } from "./zip.js";
|
|
6
|
+
const ZIP_EXTENSION = '.zip';
|
|
7
|
+
/**
|
|
8
|
+
* Get the checksums manifest path next to an archive, with .checksums.json
|
|
9
|
+
* instead of .zip.
|
|
10
|
+
*
|
|
11
|
+
* @param zipPath Archive path.
|
|
12
|
+
* @return Manifest path.
|
|
13
|
+
*/
|
|
14
|
+
export function getChecksumsPath(zipPath) {
|
|
15
|
+
const name = zipPath.endsWith(ZIP_EXTENSION)
|
|
16
|
+
? zipPath.slice(0, -ZIP_EXTENSION.length)
|
|
17
|
+
: zipPath;
|
|
18
|
+
return name + CHECKSUMS_EXTENSION;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Fail before writing anything when an output file exists.
|
|
22
|
+
*
|
|
23
|
+
* @param paths Output paths.
|
|
24
|
+
* @param force Whether existing files are overwritten.
|
|
25
|
+
* @throws {ValidationError} If a file exists and force isn't set.
|
|
26
|
+
*/
|
|
27
|
+
function checkOverwrite(paths, force) {
|
|
28
|
+
const existing = paths.filter((path) => !force && existsSync(path));
|
|
29
|
+
if (existing.length) {
|
|
30
|
+
throw new ValidationError(`${existing.join(' and ')} already exists, pass --force to overwrite it.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Write a ZIP of the selected files of a plugin, theme or package, and
|
|
35
|
+
* optionally the checksums manifest of the same files next to it. The zip
|
|
36
|
+
* command, with a directory or a configured package release, and the
|
|
37
|
+
* snapshot builds of a monorepo all use it, so that every ZIP of a package
|
|
38
|
+
* selects its files the same way. Output files inside the source directory,
|
|
39
|
+
* and their signatures, are never archived.
|
|
40
|
+
*
|
|
41
|
+
* @param input Files, archive path and manifest options.
|
|
42
|
+
* @return Paths of the written files and the number of archived files.
|
|
43
|
+
* @throws {ValidationError} Without selected files, or when an output file exists without force.
|
|
44
|
+
*/
|
|
45
|
+
export async function zipPackage(input) {
|
|
46
|
+
const { sourceDir, zipPath, force = false } = input;
|
|
47
|
+
const checksumsPath = input.checksums ? getChecksumsPath(zipPath) : null;
|
|
48
|
+
const outputs = [zipPath, checksumsPath].filter((path) => null !== path);
|
|
49
|
+
const rules = await loadFileRules(input.selection);
|
|
50
|
+
outputs.forEach((path) => ignoreOwnSignature(ignoreOwnFile(rules, sourceDir, path), sourceDir, path));
|
|
51
|
+
const files = await selectFiles(sourceDir, rules);
|
|
52
|
+
if (!files.length) {
|
|
53
|
+
throw new ValidationError(`No files to archive in ${sourceDir}.`);
|
|
54
|
+
}
|
|
55
|
+
checkOverwrite(outputs, force);
|
|
56
|
+
await createZip({
|
|
57
|
+
sourceDir,
|
|
58
|
+
files,
|
|
59
|
+
zipPath,
|
|
60
|
+
rootFolder: input.rootFolder,
|
|
61
|
+
force,
|
|
62
|
+
});
|
|
63
|
+
if (checksumsPath) {
|
|
64
|
+
const manifest = await createChecksums({
|
|
65
|
+
sourceDir,
|
|
66
|
+
rules,
|
|
67
|
+
slug: input.slug,
|
|
68
|
+
version: input.version ?? null,
|
|
69
|
+
type: input.type,
|
|
70
|
+
});
|
|
71
|
+
await writeChecksums(checksumsPath, manifest, force);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
zipPath,
|
|
75
|
+
...(checksumsPath ? { checksumsPath } : {}),
|
|
76
|
+
files: files.length,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -11,6 +11,8 @@ export interface ChangedFilesOptions {
|
|
|
11
11
|
directory?: string;
|
|
12
12
|
/** Value of --diff-filter, like ACMR for files that still exist. */
|
|
13
13
|
diffFilter?: string;
|
|
14
|
+
/** Whether to include the uncommitted and untracked files of the working tree. */
|
|
15
|
+
uncommitted?: boolean;
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* The git queries and writes of a changeset release: tags, the commits of a
|
|
@@ -64,11 +66,12 @@ export declare class ReleaseRepository {
|
|
|
64
66
|
*/
|
|
65
67
|
createTag(tag: string): Promise<void>;
|
|
66
68
|
/**
|
|
67
|
-
* Push a tag to
|
|
69
|
+
* Push a tag to a remote.
|
|
68
70
|
*
|
|
69
|
-
* @param tag
|
|
71
|
+
* @param tag Tag name.
|
|
72
|
+
* @param remote Remote name.
|
|
70
73
|
*/
|
|
71
|
-
pushTag(tag: string): Promise<void>;
|
|
74
|
+
pushTag(tag: string, remote: string): Promise<void>;
|
|
72
75
|
/**
|
|
73
76
|
* Find the commit that added a file.
|
|
74
77
|
*
|
|
@@ -85,13 +88,23 @@ export declare class ReleaseRepository {
|
|
|
85
88
|
*/
|
|
86
89
|
getDirectoryCommits(revision: string, directory: string): Promise<ReleaseCommit[]>;
|
|
87
90
|
/**
|
|
88
|
-
* List the files changed between the merge base of a ref and HEAD
|
|
91
|
+
* List the files changed between the merge base of a ref and HEAD, or the
|
|
92
|
+
* working tree with the uncommitted option, which adds untracked files.
|
|
89
93
|
*
|
|
90
94
|
* @param since Base ref, like origin/main.
|
|
91
|
-
* @param options Directory and
|
|
95
|
+
* @param options Directory, diff filter and uncommitted files.
|
|
92
96
|
* @return Changed files relative to the repository root.
|
|
93
97
|
*/
|
|
94
98
|
getChangedFiles(since: string, options?: ChangedFilesOptions): Promise<string[]>;
|
|
99
|
+
/**
|
|
100
|
+
* Read a file as of the merge base of a ref and HEAD, the side that
|
|
101
|
+
* getChangedFiles() diffs against.
|
|
102
|
+
*
|
|
103
|
+
* @param since Base ref, like origin/main.
|
|
104
|
+
* @param file Absolute or relative file path.
|
|
105
|
+
* @return File contents, or undefined when the file doesn't exist there.
|
|
106
|
+
*/
|
|
107
|
+
readFileAtMergeBase(since: string, file: string): Promise<string | undefined>;
|
|
95
108
|
/**
|
|
96
109
|
* Run git log and parse the commits.
|
|
97
110
|
*
|
|
@@ -78,12 +78,13 @@ export class ReleaseRepository {
|
|
|
78
78
|
await this.run(['tag', tag]);
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
|
-
* Push a tag to
|
|
81
|
+
* Push a tag to a remote.
|
|
82
82
|
*
|
|
83
|
-
* @param tag
|
|
83
|
+
* @param tag Tag name.
|
|
84
|
+
* @param remote Remote name.
|
|
84
85
|
*/
|
|
85
|
-
async pushTag(tag) {
|
|
86
|
-
await this.run(['push',
|
|
86
|
+
async pushTag(tag, remote) {
|
|
87
|
+
await this.run(['push', remote, tag]);
|
|
87
88
|
}
|
|
88
89
|
/**
|
|
89
90
|
* Find the commit that added a file.
|
|
@@ -115,25 +116,64 @@ export class ReleaseRepository {
|
|
|
115
116
|
]);
|
|
116
117
|
}
|
|
117
118
|
/**
|
|
118
|
-
* List the files changed between the merge base of a ref and HEAD
|
|
119
|
+
* List the files changed between the merge base of a ref and HEAD, or the
|
|
120
|
+
* working tree with the uncommitted option, which adds untracked files.
|
|
119
121
|
*
|
|
120
122
|
* @param since Base ref, like origin/main.
|
|
121
|
-
* @param options Directory and
|
|
123
|
+
* @param options Directory, diff filter and uncommitted files.
|
|
122
124
|
* @return Changed files relative to the repository root.
|
|
123
125
|
*/
|
|
124
126
|
async getChangedFiles(since, options = {}) {
|
|
127
|
+
const pathspec = options.directory
|
|
128
|
+
? ['--', this.relativePath(options.directory)]
|
|
129
|
+
: [];
|
|
130
|
+
const range = options.uncommitted
|
|
131
|
+
? (await this.run(['merge-base', since, 'HEAD'])).trim()
|
|
132
|
+
: `${since}...HEAD`;
|
|
125
133
|
const output = await this.run([
|
|
126
134
|
'diff',
|
|
127
135
|
'--name-only',
|
|
128
136
|
...(options.diffFilter
|
|
129
137
|
? [`--diff-filter=${options.diffFilter}`]
|
|
130
138
|
: []),
|
|
131
|
-
|
|
132
|
-
...
|
|
133
|
-
? ['--', this.relativePath(options.directory)]
|
|
134
|
-
: []),
|
|
139
|
+
range,
|
|
140
|
+
...pathspec,
|
|
135
141
|
]);
|
|
136
|
-
|
|
142
|
+
const untracked = options.uncommitted
|
|
143
|
+
? await this.run([
|
|
144
|
+
'ls-files',
|
|
145
|
+
'--others',
|
|
146
|
+
'--exclude-standard',
|
|
147
|
+
'--full-name',
|
|
148
|
+
...pathspec,
|
|
149
|
+
])
|
|
150
|
+
: '';
|
|
151
|
+
const files = `${output}\n${untracked}`
|
|
152
|
+
.split(/\r?\n/)
|
|
153
|
+
.filter(Boolean);
|
|
154
|
+
return [...new Set(files)];
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Read a file as of the merge base of a ref and HEAD, the side that
|
|
158
|
+
* getChangedFiles() diffs against.
|
|
159
|
+
*
|
|
160
|
+
* @param since Base ref, like origin/main.
|
|
161
|
+
* @param file Absolute or relative file path.
|
|
162
|
+
* @return File contents, or undefined when the file doesn't exist there.
|
|
163
|
+
*/
|
|
164
|
+
async readFileAtMergeBase(since, file) {
|
|
165
|
+
const base = (await this.run(['merge-base', since, 'HEAD'])).trim();
|
|
166
|
+
const relative = this.relativePath(file);
|
|
167
|
+
const listed = await this.run([
|
|
168
|
+
'ls-tree',
|
|
169
|
+
'--name-only',
|
|
170
|
+
base,
|
|
171
|
+
'--',
|
|
172
|
+
relative,
|
|
173
|
+
]);
|
|
174
|
+
return '' === listed.trim()
|
|
175
|
+
? undefined
|
|
176
|
+
: this.run(['show', `${base}:${relative}`]);
|
|
137
177
|
}
|
|
138
178
|
/**
|
|
139
179
|
* Run git log and parse the commits.
|
package/dist/release-tags.d.ts
CHANGED
|
@@ -33,7 +33,9 @@ export declare function selectTagPackages(packages: ConfiguredPackage[], names:
|
|
|
33
33
|
*/
|
|
34
34
|
export declare function getTagVersion(pkg: ConfiguredPackage, expected?: string): Promise<string>;
|
|
35
35
|
/**
|
|
36
|
-
* Find the tag of the newest release before a version.
|
|
36
|
+
* Find the tag of the newest release before a version. A stable release
|
|
37
|
+
* skips the pre-releases before it, so that it compares with the previous
|
|
38
|
+
* stable release.
|
|
37
39
|
*
|
|
38
40
|
* @param repository Repository with the tags.
|
|
39
41
|
* @param prefix Tag prefix of the package, like `plugins/alpha/`.
|
package/dist/release-tags.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readChangelogSection, getLatestChangelogVersion, } from "./changelog.js";
|
|
2
2
|
import { assertKnownPackages, toChangesetPackage, } from "./changeset-packages.js";
|
|
3
3
|
import { ValidationError } from "./errors.js";
|
|
4
|
-
import { compareVersions, isVersion } from "./versions.js";
|
|
4
|
+
import { compareVersions, isPrerelease, isVersion } from "./versions.js";
|
|
5
5
|
/**
|
|
6
6
|
* Pick the packages to tag.
|
|
7
7
|
*
|
|
@@ -37,7 +37,9 @@ export async function getTagVersion(pkg, expected) {
|
|
|
37
37
|
(await getLatestChangelogVersion(pkg.directory, pkg.currentVersion)));
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
|
-
* Find the tag of the newest release before a version.
|
|
40
|
+
* Find the tag of the newest release before a version. A stable release
|
|
41
|
+
* skips the pre-releases before it, so that it compares with the previous
|
|
42
|
+
* stable release.
|
|
41
43
|
*
|
|
42
44
|
* @param repository Repository with the tags.
|
|
43
45
|
* @param prefix Tag prefix of the package, like `plugins/alpha/`.
|
|
@@ -50,6 +52,7 @@ export async function getPreviousTag(repository, prefix, version) {
|
|
|
50
52
|
.filter((tag) => tag.startsWith(prefix))
|
|
51
53
|
.map((tag) => ({ tag, version: tag.slice(prefix.length) }))
|
|
52
54
|
.filter((item) => isVersion(item.version) &&
|
|
55
|
+
(isPrerelease(version) || !isPrerelease(item.version)) &&
|
|
53
56
|
compareVersions(item.version, version) < 0)
|
|
54
57
|
.sort((a, b) => compareVersions(b.version, a.version))[0]?.tag;
|
|
55
58
|
}
|