@wpelevator/wp-release 0.15.0 → 0.17.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 (98) hide show
  1. package/README.md +366 -39
  2. package/dist/changeset-config.d.ts +8 -0
  3. package/dist/changeset-config.js +3 -0
  4. package/dist/changeset-release.d.ts +9 -3
  5. package/dist/changeset-release.js +23 -14
  6. package/dist/cli.d.ts +6 -0
  7. package/dist/cli.js +12 -0
  8. package/dist/commands/artifact-signing.d.ts +20 -0
  9. package/dist/commands/artifact-signing.js +42 -0
  10. package/dist/commands/build.d.ts +32 -0
  11. package/dist/commands/build.js +91 -0
  12. package/dist/commands/changeset-add.d.ts +55 -6
  13. package/dist/commands/changeset-add.js +12 -3
  14. package/dist/commands/changeset-args.js +2 -2
  15. package/dist/commands/changeset-project-args.d.ts +63 -0
  16. package/dist/commands/changeset-project-args.js +114 -0
  17. package/dist/commands/changeset-status.d.ts +50 -1
  18. package/dist/commands/changeset-status.js +4 -2
  19. package/dist/commands/changeset-tag.d.ts +55 -6
  20. package/dist/commands/changeset-tag.js +7 -4
  21. package/dist/commands/changeset-version.d.ts +55 -6
  22. package/dist/commands/changeset-version.js +4 -2
  23. package/dist/commands/checksums.d.ts +4 -1
  24. package/dist/commands/checksums.js +27 -6
  25. package/dist/commands/i18n-audit.d.ts +2 -0
  26. package/dist/commands/i18n-audit.js +2 -0
  27. package/dist/commands/i18n-compile.d.ts +6 -0
  28. package/dist/commands/i18n-compile.js +76 -0
  29. package/dist/commands/i18n-json.d.ts +2 -0
  30. package/dist/commands/i18n-json.js +2 -0
  31. package/dist/commands/i18n-mo.d.ts +2 -0
  32. package/dist/commands/i18n-mo.js +2 -0
  33. package/dist/commands/i18n-php.d.ts +2 -0
  34. package/dist/commands/i18n-php.js +2 -0
  35. package/dist/commands/i18n-pot.d.ts +2 -0
  36. package/dist/commands/i18n-pot.js +2 -0
  37. package/dist/commands/i18n.d.ts +12 -0
  38. package/dist/commands/i18n.js +174 -0
  39. package/dist/commands/package-source.d.ts +43 -0
  40. package/dist/commands/package-source.js +101 -0
  41. package/dist/commands/verify.d.ts +7 -1
  42. package/dist/commands/verify.js +53 -17
  43. package/dist/commands/zip.d.ts +20 -8
  44. package/dist/commands/zip.js +109 -124
  45. package/dist/i18n/atomic-output.d.ts +23 -0
  46. package/dist/i18n/atomic-output.js +90 -0
  47. package/dist/i18n/audit.d.ts +7 -0
  48. package/dist/i18n/audit.js +69 -0
  49. package/dist/i18n/catalog.d.ts +54 -0
  50. package/dist/i18n/catalog.js +86 -0
  51. package/dist/i18n/compile.d.ts +27 -0
  52. package/dist/i18n/compile.js +71 -0
  53. package/dist/i18n/extraction.d.ts +45 -0
  54. package/dist/i18n/extraction.js +105 -0
  55. package/dist/i18n/files.d.ts +7 -0
  56. package/dist/i18n/files.js +45 -0
  57. package/dist/i18n/generate.d.ts +49 -0
  58. package/dist/i18n/generate.js +240 -0
  59. package/dist/i18n/javascript.d.ts +7 -0
  60. package/dist/i18n/javascript.js +143 -0
  61. package/dist/i18n/metadata.d.ts +16 -0
  62. package/dist/i18n/metadata.js +117 -0
  63. package/dist/i18n/options.d.ts +52 -0
  64. package/dist/i18n/options.js +156 -0
  65. package/dist/i18n/packages.d.ts +24 -0
  66. package/dist/i18n/packages.js +86 -0
  67. package/dist/i18n/php.d.ts +9 -0
  68. package/dist/i18n/php.js +238 -0
  69. package/dist/i18n/placeholders.d.ts +9 -0
  70. package/dist/i18n/placeholders.js +13 -0
  71. package/dist/i18n/po.d.ts +24 -0
  72. package/dist/i18n/po.js +180 -0
  73. package/dist/i18n/pot.d.ts +16 -0
  74. package/dist/i18n/pot.js +119 -0
  75. package/dist/i18n/translation-json.d.ts +14 -0
  76. package/dist/i18n/translation-json.js +96 -0
  77. package/dist/i18n/translation-php.d.ts +6 -0
  78. package/dist/i18n/translation-php.js +35 -0
  79. package/dist/index.d.ts +28 -0
  80. package/dist/index.js +16 -0
  81. package/dist/integrity.d.ts +33 -0
  82. package/dist/integrity.js +115 -0
  83. package/dist/output-files.d.ts +7 -0
  84. package/dist/output-files.js +25 -0
  85. package/dist/package-build.d.ts +33 -0
  86. package/dist/package-build.js +112 -0
  87. package/dist/package-files.d.ts +25 -0
  88. package/dist/package-files.js +24 -0
  89. package/dist/package-zip.d.ts +14 -2
  90. package/dist/package-zip.js +129 -38
  91. package/dist/release-tags.js +6 -0
  92. package/dist/single-project.d.ts +32 -0
  93. package/dist/single-project.js +144 -0
  94. package/dist/summary.d.ts +9 -0
  95. package/dist/summary.js +25 -0
  96. package/dist/zip.d.ts +3 -0
  97. package/dist/zip.js +16 -6
  98. package/package.json +20 -3
@@ -1,8 +1,13 @@
1
- import { existsSync } from 'node:fs';
2
- import { CHECKSUMS_EXTENSION, createChecksums, writeChecksums, } from "./checksums.js";
1
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ import { checkChecksumsFile, resolveManifestCodec, encodeIntegrity, } from "./integrity.js";
5
+ import { checkSigningInput, signFiles, getSignaturePath } from "./signing.js";
6
+ import { CHECKSUMS_EXTENSION, createChecksums } from "./checksums.js";
3
7
  import { ValidationError } from "./errors.js";
4
- import { ignoreOwnFile, ignoreOwnSignature, loadFileRules, selectFiles, } from "./file-selection.js";
5
- import { createZip } from "./zip.js";
8
+ import { checkOutputFiles } from "./output-files.js";
9
+ import { getPackageFilesToRelease } from "./package-files.js";
10
+ import { createZip, isValidRootFolder } from "./zip.js";
6
11
  const ZIP_EXTENSION = '.zip';
7
12
  /**
8
13
  * Get the checksums manifest path next to an archive, with .checksums.json
@@ -17,19 +22,6 @@ export function getChecksumsPath(zipPath) {
17
22
  : zipPath;
18
23
  return name + CHECKSUMS_EXTENSION;
19
24
  }
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
25
  /**
34
26
  * Write a ZIP of the selected files of a plugin, theme or package, and
35
27
  * optionally the checksums manifest of the same files next to it. The zip
@@ -44,35 +36,134 @@ function checkOverwrite(paths, force) {
44
36
  */
45
37
  export async function zipPackage(input) {
46
38
  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}.`);
39
+ if (input.rootFolder && !isValidRootFolder(input.rootFolder)) {
40
+ throw new ValidationError(`Invalid root folder "${input.rootFolder}", use a single folder name.`);
41
+ }
42
+ const enabled = input.checksums ||
43
+ input.checksumsFile ||
44
+ input.embedChecksums ||
45
+ input.signChecksums;
46
+ const codec = resolveManifestCodec(input.integrity?.codec);
47
+ const file = checkChecksumsFile(input.checksumsFile ??
48
+ (codec.id === 'wordpress-json'
49
+ ? basename(getChecksumsPath(zipPath))
50
+ : codec.filename));
51
+ const checksumsPath = enabled && !input.embedChecksums
52
+ ? resolve(dirname(zipPath), file)
53
+ : null;
54
+ if (checksumsPath === resolve(zipPath)) {
55
+ throw new ValidationError('The ZIP and checksums file must have different destinations.');
54
56
  }
55
- checkOverwrite(outputs, force);
56
- await createZip({
57
+ if (input.signChecksums) {
58
+ checkSigningInput({
59
+ paths: checksumsPath ? [checksumsPath] : [],
60
+ formats: input.signChecksums,
61
+ force,
62
+ });
63
+ }
64
+ const outputs = [zipPath, checksumsPath].filter((path) => null !== path);
65
+ const { files, rules } = await getPackageFilesToRelease({
57
66
  sourceDir,
58
- files,
59
- zipPath,
60
- rootFolder: input.rootFolder,
61
- force,
67
+ selection: input.selection,
68
+ outputs,
69
+ excludeSignatures: true,
70
+ operation: 'archive',
62
71
  });
63
- if (checksumsPath) {
64
- const manifest = await createChecksums({
72
+ checkOutputFiles(outputs, force);
73
+ const entries = {};
74
+ let checksumsSigning;
75
+ let stage;
76
+ const metadata = [
77
+ file,
78
+ ...['wordpress', 'minisign', 'ssh'].map((format) => getSignaturePath(file, format)),
79
+ ];
80
+ if (enabled &&
81
+ input.embedChecksums &&
82
+ metadata.some((path) => files.some((selected) => selected === path ||
83
+ selected.startsWith(path + '/') ||
84
+ path.startsWith(selected + '/')))) {
85
+ throw new ValidationError('Embedded checksums collide with selected source files. Remove the generated metadata or exclude it from the payload.');
86
+ }
87
+ try {
88
+ if (enabled) {
89
+ const manifest = await createChecksums({
90
+ sourceDir,
91
+ rules,
92
+ slug: input.slug,
93
+ version: input.version ?? null,
94
+ type: input.type,
95
+ });
96
+ const algorithms = input.integrity?.algorithms;
97
+ if (algorithms) {
98
+ if (!algorithms.length ||
99
+ algorithms.some((algorithm) => !['md5', 'sha256'].includes(algorithm))) {
100
+ throw new ValidationError('Checksums algorithms must be md5 and/or sha256.');
101
+ }
102
+ Object.values(manifest.files).forEach((hashes) => {
103
+ if (!algorithms.includes('md5')) {
104
+ delete hashes.md5;
105
+ }
106
+ if (!algorithms.includes('sha256')) {
107
+ delete hashes.sha256;
108
+ }
109
+ });
110
+ }
111
+ const bytes = await encodeIntegrity(codec, manifest);
112
+ stage = await mkdtemp(join(tmpdir(), 'wp-release-integrity-'));
113
+ const stagedFile = join(stage, file);
114
+ await mkdir(dirname(stagedFile), { recursive: true });
115
+ await writeFile(stagedFile, bytes);
116
+ if (input.signChecksums) {
117
+ checksumsSigning = await signFiles({
118
+ paths: [stagedFile],
119
+ formats: input.signChecksums,
120
+ });
121
+ }
122
+ if (input.embedChecksums) {
123
+ entries[file] = bytes;
124
+ for (const format of input.signChecksums ?? []) {
125
+ entries[getSignaturePath(file, format)] =
126
+ await readFile(getSignaturePath(stagedFile, format));
127
+ }
128
+ }
129
+ else if (checksumsPath) {
130
+ await mkdir(dirname(checksumsPath), { recursive: true });
131
+ await writeFile(checksumsPath, bytes, {
132
+ flag: force ? 'w' : 'wx',
133
+ });
134
+ for (const format of input.signChecksums ?? []) {
135
+ await writeFile(getSignaturePath(checksumsPath, format), await readFile(getSignaturePath(stagedFile, format)), { flag: force ? 'w' : 'wx' });
136
+ }
137
+ }
138
+ }
139
+ await mkdir(dirname(zipPath), { recursive: true });
140
+ await createZip({
65
141
  sourceDir,
66
- rules,
67
- slug: input.slug,
68
- version: input.version ?? null,
69
- type: input.type,
142
+ files,
143
+ entries,
144
+ zipPath,
145
+ rootFolder: input.rootFolder,
146
+ force,
70
147
  });
71
- await writeChecksums(checksumsPath, manifest, force);
72
148
  }
149
+ finally {
150
+ if (stage) {
151
+ await rm(stage, { recursive: true, force: true });
152
+ }
153
+ }
154
+ const signatures = (input.signChecksums ?? []).map((format) => getSignaturePath(checksumsPath ?? file, format));
73
155
  return {
74
156
  zipPath,
75
157
  ...(checksumsPath ? { checksumsPath } : {}),
76
- files: files.length,
158
+ ...(enabled && input.embedChecksums
159
+ ? { checksumsEmbedded: file }
160
+ : {}),
161
+ ...(checksumsSigning
162
+ ? {
163
+ checksumsSigning: { ...checksumsSigning, signatures },
164
+ checksumsSignatures: signatures,
165
+ }
166
+ : {}),
167
+ files: files.length + Object.keys(entries).length,
77
168
  };
78
169
  }
@@ -12,6 +12,12 @@ import { compareVersions, isPrerelease, isVersion } from "./versions.js";
12
12
  * @throws {ValidationError} For unknown packages, or an expected version without a single package.
13
13
  */
14
14
  export function selectTagPackages(packages, names, version) {
15
+ if (undefined !== version &&
16
+ !names.length &&
17
+ 1 === packages.length &&
18
+ '.' === packages[0]?.path) {
19
+ names = [packages[0].name];
20
+ }
15
21
  if (undefined !== version && 1 !== names.length) {
16
22
  throw new ValidationError('An expected version requires exactly one package.');
17
23
  }
@@ -0,0 +1,32 @@
1
+ import type { ChangesetConfig, PackageVersionSource } from './changeset-config.ts';
2
+ /** Options used only when changesets have no JavaScript release config. */
3
+ export interface SingleProjectOptions {
4
+ /** Explicit descriptors replace automatic version detection. Git tags are source-only. */
5
+ versions?: PackageVersionSource[];
6
+ /** Descriptor kind, optionally qualified with :file or :file#constant. */
7
+ versionFrom?: string;
8
+ baseBranch?: string;
9
+ remote?: string;
10
+ changesets?: string;
11
+ readmeChangelog?: boolean;
12
+ changelogInsertAfter?: string;
13
+ }
14
+ /** CLI names of version descriptor kinds. */
15
+ export declare const VERSION_KINDS: {
16
+ readonly 'package-json': "package-json";
17
+ readonly 'composer-json': "composer-json";
18
+ readonly 'wordpress-plugin': "wordpress-plugin-header";
19
+ readonly 'wordpress-theme': "wordpress-theme-header";
20
+ readonly 'wordpress-stable-tag': "wordpress-stable-tag";
21
+ readonly 'php-constant': "php-constant";
22
+ readonly 'git-tag': "git-tag";
23
+ };
24
+ /**
25
+ * Build the root package config, without creating a configuration file.
26
+ *
27
+ * @param cwd Project root directory.
28
+ * @param options Explicit descriptors and shared release settings.
29
+ * @param tagFormat Tag format override, otherwise v{version}.
30
+ * @return Root package config, or undefined without a version source.
31
+ */
32
+ export declare function resolveSingleProjectConfig(cwd: string, options?: SingleProjectOptions, tagFormat?: string): Promise<ChangesetConfig | undefined>;
@@ -0,0 +1,144 @@
1
+ import { readdir } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { packageJsonVersion, composerJsonVersion, wordpressPluginVersion, wordpressThemeVersion, wordpressStableTag, } from "./changeset-config.js";
4
+ import { ValidationError } from "./errors.js";
5
+ import { readJsonIfExists } from "./json-files.js";
6
+ import { parseFileHeaders, readFileStart } from "./package-meta.js";
7
+ /** CLI names of version descriptor kinds. */
8
+ export const VERSION_KINDS = {
9
+ 'package-json': 'package-json',
10
+ 'composer-json': 'composer-json',
11
+ 'wordpress-plugin': 'wordpress-plugin-header',
12
+ 'wordpress-theme': 'wordpress-theme-header',
13
+ 'wordpress-stable-tag': 'wordpress-stable-tag',
14
+ 'php-constant': 'php-constant',
15
+ 'git-tag': 'git-tag',
16
+ };
17
+ function descriptorId(descriptor) {
18
+ if ('git-tag' === descriptor.kind) {
19
+ return descriptor.pattern ?? '';
20
+ }
21
+ return 'php-constant' === descriptor.kind
22
+ ? `${descriptor.file}#${descriptor.constant}`
23
+ : descriptor.file ?? '';
24
+ }
25
+ function selectSource(versions, selector) {
26
+ let candidates = versions;
27
+ if (selector) {
28
+ const separator = selector.indexOf(':');
29
+ const kind = separator < 0 ? selector : selector.slice(0, separator);
30
+ const id = separator < 0 ? undefined : selector.slice(separator + 1);
31
+ const descriptorKind = VERSION_KINDS[kind];
32
+ candidates = versions.filter((descriptor) => descriptor.kind === descriptorKind &&
33
+ (undefined === id || descriptorId(descriptor) === id));
34
+ }
35
+ else if (versions.length > 1) {
36
+ // A Stable tag can accompany a unique authoritative version file.
37
+ candidates = versions.filter((descriptor) => 'wordpress-stable-tag' !== descriptor.kind);
38
+ }
39
+ if (candidates.length !== 1) {
40
+ throw new ValidationError('Cannot choose the current version source. Pass --version-from <kind> or --version-from <kind>:<file> (PHP constants use <file>#<constant>), or add a wp-release config.');
41
+ }
42
+ return candidates[0];
43
+ }
44
+ async function detectVersions(cwd) {
45
+ const versions = [];
46
+ const entries = await readdir(cwd, { withFileTypes: true });
47
+ for (const entry of entries
48
+ .filter((item) => item.isFile() &&
49
+ (item.name.endsWith('.php') || 'style.css' === item.name))
50
+ .sort((a, b) => a.name.localeCompare(b.name))) {
51
+ const contents = await readFileStart(path.join(cwd, entry.name));
52
+ const theme = 'style.css' === entry.name;
53
+ const nameHeader = theme ? 'Theme Name' : 'Plugin Name';
54
+ const headers = parseFileHeaders({
55
+ contents: contents ?? '',
56
+ headers: [nameHeader, 'Version'],
57
+ });
58
+ if (headers[nameHeader]) {
59
+ versions.push(theme
60
+ ? wordpressThemeVersion({ file: entry.name })
61
+ : wordpressPluginVersion({ file: entry.name }));
62
+ }
63
+ }
64
+ for (const file of ['package.json', 'composer.json']) {
65
+ const manifest = await readJsonIfExists(path.join(cwd, file)).catch((error) => {
66
+ throw new ValidationError(`Could not read ${file}: ${error instanceof Error ? error.message : String(error)}`);
67
+ });
68
+ if (manifest && 'package.json' === file && manifest.workspaces) {
69
+ throw new ValidationError('Automatic changeset configuration is for a single root project. Configure workspace packages in a wp-release config or pass explicit --version-* descriptors.');
70
+ }
71
+ if (manifest &&
72
+ 'string' === typeof manifest.version &&
73
+ manifest.version.trim()) {
74
+ versions.push('package.json' === file
75
+ ? packageJsonVersion()
76
+ : composerJsonVersion());
77
+ }
78
+ }
79
+ if (versions.length > 1) {
80
+ throw new ValidationError('Cannot choose among multiple root version sources. Pass explicit --version-* descriptors or add a wp-release config.');
81
+ }
82
+ if (1 === versions.length &&
83
+ 'wordpress-plugin-header' === versions[0]?.kind) {
84
+ const readmes = await Promise.all(['readme.txt', 'readme.md'].map(async (file) => {
85
+ const contents = await readFileStart(path.join(cwd, file));
86
+ const headers = parseFileHeaders({
87
+ contents: contents ?? '',
88
+ headers: ['Stable tag'],
89
+ });
90
+ return headers['Stable tag'] ? file : undefined;
91
+ }));
92
+ const readme = readmes.find(Boolean);
93
+ if (readme) {
94
+ versions.push(wordpressStableTag({ file: readme }));
95
+ }
96
+ }
97
+ return versions;
98
+ }
99
+ /**
100
+ * Build the root package config, without creating a configuration file.
101
+ *
102
+ * @param cwd Project root directory.
103
+ * @param options Explicit descriptors and shared release settings.
104
+ * @param tagFormat Tag format override, otherwise v{version}.
105
+ * @return Root package config, or undefined without a version source.
106
+ */
107
+ export async function resolveSingleProjectConfig(cwd, options = {}, tagFormat) {
108
+ const versions = options.versions ?? (await detectVersions(cwd));
109
+ if (!versions.length) {
110
+ return undefined;
111
+ }
112
+ const ids = versions.map((descriptor) => `${descriptor.kind}:${descriptorId(descriptor)}`);
113
+ if (new Set(ids).size !== ids.length) {
114
+ throw new ValidationError('A version descriptor was supplied more than once. Pass each --version-* file or constant only once.');
115
+ }
116
+ const from = selectSource(versions, options.versionFrom);
117
+ const to = versions.filter((descriptor) => 'git-tag' !== descriptor.kind);
118
+ // Composer targets preserve the existing optional-version convention.
119
+ const targets = to.map((descriptor) => 'composer-json' === descriptor.kind
120
+ ? { ...descriptor, ifPresent: descriptor.ifPresent ?? true }
121
+ : descriptor);
122
+ let idFrom = 'path';
123
+ if ('package-json' === from.kind && 'package.json' === from.file) {
124
+ idFrom = 'package.json#name';
125
+ }
126
+ else if ('composer-json' === from.kind &&
127
+ 'composer.json' === from.file) {
128
+ idFrom = 'composer.json#name';
129
+ }
130
+ return {
131
+ ...(options.baseBranch ? { baseBranch: options.baseBranch } : {}),
132
+ ...(options.remote ? { remote: options.remote } : {}),
133
+ ...(undefined !== options.readmeChangelog
134
+ ? { readmeChangelog: options.readmeChangelog }
135
+ : {}),
136
+ ...(options.changelogInsertAfter
137
+ ? { changelogInsertAfter: options.changelogInsertAfter }
138
+ : {}),
139
+ root: '.',
140
+ changesets: options.changesets ?? '.changeset',
141
+ tagFormat: tagFormat ?? 'v{version}',
142
+ packages: [{ glob: '.', idFrom, version: { from, to: targets } }],
143
+ };
144
+ }
package/dist/summary.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ChecksumsResult } from './commands/checksums.ts';
2
2
  import type { SignResult } from './commands/sign.ts';
3
+ import type { BuildPackageResult } from './package-build.ts';
3
4
  import type { ZipPackageResult } from './package-zip.ts';
4
5
  import type { ChangesetTagResult } from './changeset-release.ts';
5
6
  import type { PackageRelease } from './changesets.ts';
@@ -69,6 +70,14 @@ export declare function formatTable(header: string[], rows: string[][]): string;
69
70
  * @return Markdown code.
70
71
  */
71
72
  export declare function formatFile(context: SummaryContext, file: string, line?: number | null): string;
73
+ /**
74
+ * Get the summary of a directory build.
75
+ *
76
+ * @param context Workspace directory.
77
+ * @param result Output directory and number of copied files.
78
+ * @return Markdown summary.
79
+ */
80
+ export declare function getBuildSummary(context: SummaryContext, result: BuildPackageResult): string;
72
81
  /**
73
82
  * Get the summary of a ZIP.
74
83
  *
package/dist/summary.js CHANGED
@@ -99,6 +99,26 @@ function formatDetails(title, content) {
99
99
  function joinBlocks(blocks) {
100
100
  return `${blocks.filter(Boolean).join('\n\n')}\n`;
101
101
  }
102
+ /**
103
+ * Get the summary of a directory build.
104
+ *
105
+ * @param context Workspace directory.
106
+ * @param result Output directory and number of copied files.
107
+ * @return Markdown summary.
108
+ */
109
+ export function getBuildSummary(context, result) {
110
+ return joinBlocks([
111
+ '### Build',
112
+ formatList([
113
+ `**Directory:** ${formatFile(context, result.outputDir)}, ${result.files} files`,
114
+ ...(result.checksumsPath
115
+ ? [
116
+ `**Checksums:** ${formatFile(context, result.checksumsPath)}`,
117
+ ]
118
+ : []),
119
+ ]),
120
+ ]);
121
+ }
102
122
  /**
103
123
  * Get the summary of a ZIP.
104
124
  *
@@ -111,6 +131,11 @@ export function getZipSummary(context, result) {
111
131
  '### ZIP',
112
132
  formatList([
113
133
  `**ZIP:** ${formatFile(context, result.zipPath)}, ${result.files} files`,
134
+ ...(result.checksumsEmbedded
135
+ ? [
136
+ `**Embedded checksums:** ${formatCode(result.checksumsEmbedded)}`,
137
+ ]
138
+ : []),
114
139
  ...(result.checksumsPath
115
140
  ? [
116
141
  `**Checksums:** ${formatFile(context, result.checksumsPath)}`,
package/dist/zip.d.ts CHANGED
@@ -14,6 +14,8 @@ export interface CreateZipInput {
14
14
  sourceDir: string;
15
15
  /** Relative file paths with forward slashes, from selectFiles(). */
16
16
  files: string[];
17
+ /** Generated entries, keyed by relative archive path. */
18
+ entries?: Record<string, Buffer>;
17
19
  /** Archive path. */
18
20
  zipPath: string;
19
21
  /** Folder to put the files in inside the archive, or null for the root. */
@@ -30,3 +32,4 @@ export interface CreateZipInput {
30
32
  * @throws {ValidationError} If the archive exists and force isn't set.
31
33
  */
32
34
  export declare function createZip(input: CreateZipInput): Promise<void>;
35
+ export declare function isValidRootFolder(name: string): boolean;
package/dist/zip.js CHANGED
@@ -31,11 +31,21 @@ export async function createZip(input) {
31
31
  }
32
32
  const zip = new yazl.ZipFile();
33
33
  const output = createWriteStream(zipPath, { flags: force ? 'w' : 'wx' });
34
- [...files].sort().forEach((file) => zip.addFile(join(sourceDir, file), rootFolder ? posix.join(rootFolder, file) : file, {
35
- mtime: ZIP_MTIME,
36
- mode: ZIP_FILE_MODE,
37
- forceDosTimestamp: true,
38
- }));
34
+ const entries = input.entries ?? {};
35
+ [...files, ...Object.keys(entries)].sort().forEach((file) => {
36
+ const path = rootFolder ? posix.join(rootFolder, file) : file;
37
+ const options = {
38
+ mtime: ZIP_MTIME,
39
+ mode: ZIP_FILE_MODE,
40
+ forceDosTimestamp: true,
41
+ };
42
+ if (Object.hasOwn(entries, file)) {
43
+ zip.addBuffer(entries[file] ?? Buffer.alloc(0), path, options);
44
+ }
45
+ else {
46
+ zip.addFile(join(sourceDir, file), path, options);
47
+ }
48
+ });
39
49
  zip.end();
40
50
  try {
41
51
  await pipeline(zip.outputStream, output);
@@ -48,6 +58,6 @@ export async function createZip(input) {
48
58
  throw error;
49
59
  }
50
60
  }
51
- function isValidRootFolder(name) {
61
+ export function isValidRootFolder(name) {
52
62
  return !/[\\/]/.test(name) && '.' !== name && '..' !== name;
53
63
  }
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@wpelevator/wp-release",
3
- "version": "0.15.0",
4
- "description": "Package, checksum, sign, verify and version-check WordPress plugins and themes, and deploy plugins to WordPress.org SVN",
3
+ "version": "0.17.0",
4
+ "description": "Package, checksum, sign, verify and version-check WordPress plugins and themes, extract and compile their translations, and deploy plugins to WordPress.org SVN",
5
5
  "license": "MIT",
6
+ "author": "WP Elevator (https://wpelevator.com)",
7
+ "homepage": "https://github.com/wpelevator/basement/tree/main/packages/js/wp-release#readme",
8
+ "bugs": {
9
+ "url": "https://github.com/wpelevator/basement/issues"
10
+ },
6
11
  "repository": {
7
12
  "type": "git",
8
13
  "url": "git+https://github.com/wpelevator/basement.git",
@@ -37,15 +42,27 @@
37
42
  "svn",
38
43
  "zip",
39
44
  "checksums",
40
- "signing"
45
+ "signing",
46
+ "i18n",
47
+ "gettext",
48
+ "pot"
41
49
  ],
42
50
  "dependencies": {
51
+ "@tannin/postfix": "^1.1.0",
52
+ "@babel/parser": "^7.29.7",
53
+ "@babel/traverse": "^7.29.7",
54
+ "gettext-parser": "^8.0.0",
43
55
  "ignore": "^7.0.12",
56
+ "minimatch": "^10.2.6",
57
+ "php-parser": "^3.7.0",
44
58
  "semver": "^7.7.1",
45
59
  "yauzl": "^3.4.0",
46
60
  "yazl": "^3.3.1"
47
61
  },
48
62
  "devDependencies": {
63
+ "@babel/types": "^7.29.7",
64
+ "@types/babel__traverse": "^7.28.0",
65
+ "@types/gettext-parser": "^9.0.0",
49
66
  "@types/node": "^22.20.5",
50
67
  "@types/semver": "^7.8.0",
51
68
  "@types/yauzl": "^3.4.0",