@wpelevator/wp-release 0.14.0 → 0.16.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 (58) hide show
  1. package/README.md +256 -36
  2. package/dist/changeset-config.d.ts +3 -0
  3. package/dist/changeset-release.d.ts +9 -3
  4. package/dist/changeset-release.js +23 -14
  5. package/dist/cli.d.ts +17 -7
  6. package/dist/cli.js +27 -2
  7. package/dist/commands/args.d.ts +11 -0
  8. package/dist/commands/args.js +23 -0
  9. package/dist/commands/artifact-signing.d.ts +20 -0
  10. package/dist/commands/artifact-signing.js +42 -0
  11. package/dist/commands/build.d.ts +32 -0
  12. package/dist/commands/build.js +91 -0
  13. package/dist/commands/changeset-add.d.ts +55 -6
  14. package/dist/commands/changeset-add.js +12 -3
  15. package/dist/commands/changeset-args.js +2 -2
  16. package/dist/commands/changeset-project-args.d.ts +63 -0
  17. package/dist/commands/changeset-project-args.js +114 -0
  18. package/dist/commands/changeset-status.d.ts +50 -1
  19. package/dist/commands/changeset-status.js +4 -2
  20. package/dist/commands/changeset-tag.d.ts +55 -6
  21. package/dist/commands/changeset-tag.js +7 -4
  22. package/dist/commands/changeset-version.d.ts +55 -6
  23. package/dist/commands/changeset-version.js +4 -2
  24. package/dist/commands/checksums.d.ts +4 -1
  25. package/dist/commands/checksums.js +27 -6
  26. package/dist/commands/command.d.ts +3 -0
  27. package/dist/commands/deploy-svn.d.ts +11 -15
  28. package/dist/commands/deploy-svn.js +25 -5
  29. package/dist/commands/package-source.d.ts +43 -0
  30. package/dist/commands/package-source.js +101 -0
  31. package/dist/commands/sign.d.ts +0 -9
  32. package/dist/commands/sign.js +10 -47
  33. package/dist/commands/verify.d.ts +7 -1
  34. package/dist/commands/verify.js +53 -17
  35. package/dist/commands/zip.d.ts +25 -2
  36. package/dist/commands/zip.js +136 -110
  37. package/dist/index.d.ts +9 -2
  38. package/dist/index.js +3 -1
  39. package/dist/integrity.d.ts +33 -0
  40. package/dist/integrity.js +115 -0
  41. package/dist/output-files.d.ts +7 -0
  42. package/dist/output-files.js +25 -0
  43. package/dist/package-build.d.ts +33 -0
  44. package/dist/package-build.js +112 -0
  45. package/dist/package-files.d.ts +25 -0
  46. package/dist/package-files.js +24 -0
  47. package/dist/package-zip.d.ts +14 -2
  48. package/dist/package-zip.js +129 -38
  49. package/dist/release-tags.js +6 -0
  50. package/dist/signing.d.ts +36 -0
  51. package/dist/signing.js +52 -1
  52. package/dist/single-project.d.ts +32 -0
  53. package/dist/single-project.js +144 -0
  54. package/dist/summary.d.ts +9 -0
  55. package/dist/summary.js +25 -0
  56. package/dist/zip.d.ts +3 -0
  57. package/dist/zip.js +16 -6
  58. package/package.json +6 -1
@@ -1,3 +1,4 @@
1
+ import { resolveSingleProjectConfig } from "./single-project.js";
1
2
  import path from 'node:path';
2
3
  import { getVersionedReleases, prependChangelog } from "./changelog.js";
3
4
  import { loadChangesetConfig } from "./changeset-config.js";
@@ -12,7 +13,7 @@ import { ReleaseRepository } from "./release-repository.js";
12
13
  import { DEFAULT_TAG_FORMAT, formatTag } from "./tag-format.js";
13
14
  import { verifyVersionTargets, writeVersion } from "./version-files.js";
14
15
  import { getPrereleaseVersion, getSnapshotVersion, isPrerelease, } from "./versions.js";
15
- const MISSING_CONFIG = 'No wp-release config found. Add wp-release.config.js or pass --config <path>.';
16
+ const MISSING_CONFIG = 'No wp-release config or root project found. Pass --version-* descriptors, add wp-release.config.js or pass --config <path>.';
16
17
  /**
17
18
  * Changeset releases of a repository: recording changesets, versioning the
18
19
  * packages they name and tagging the releases.
@@ -24,6 +25,7 @@ const MISSING_CONFIG = 'No wp-release config found. Add wp-release.config.js or
24
25
  export class ChangesetRelease {
25
26
  cwd;
26
27
  configFile;
28
+ singleProject;
27
29
  tagFormat;
28
30
  repository;
29
31
  /**
@@ -32,6 +34,7 @@ export class ChangesetRelease {
32
34
  constructor(options) {
33
35
  this.cwd = options.cwd;
34
36
  this.configFile = options.configFile;
37
+ this.singleProject = options.singleProject;
35
38
  this.tagFormat = options.tagFormat;
36
39
  this.repository = new ReleaseRepository({
37
40
  cwd: options.cwd,
@@ -42,7 +45,7 @@ export class ChangesetRelease {
42
45
  * Discover configured packages, pending changesets, and their proposed
43
46
  * versions.
44
47
  *
45
- * @return A release plan; `undefined` when no config is present.
48
+ * @return A release plan; `undefined` without a config or a recognized root project.
46
49
  */
47
50
  async getPlan() {
48
51
  const context = await this.loadContext();
@@ -361,19 +364,14 @@ export class ChangesetRelease {
361
364
  * Load the configuration, discover the packages and plan the releases of
362
365
  * the pending changesets, including the releases of their dependents.
363
366
  *
364
- * @return Release context, undefined without a config.
367
+ * @return Release context, undefined without a config or a recognized root project.
365
368
  * @throws {ValidationError} For an invalid config, plan or changeset.
366
369
  */
367
370
  async loadContext() {
368
- const loaded = await loadChangesetConfig({
369
- cwd: this.cwd,
370
- ...(this.configFile ? { configFile: this.configFile } : {}),
371
- ...(this.tagFormat ? { tagFormat: this.tagFormat } : {}),
372
- });
373
- if (!loaded) {
371
+ const config = await this.loadConfig();
372
+ if (!config) {
374
373
  return undefined;
375
374
  }
376
- const config = loaded.config;
377
375
  const packages = await discoverPackages(this.repository, config);
378
376
  const changesets = await readChangesets(this.getChangesetDirectory(config));
379
377
  const releases = [];
@@ -431,15 +429,26 @@ export class ChangesetRelease {
431
429
  * @throws {ValidationError} Without a config.
432
430
  */
433
431
  async requireConfig() {
432
+ const config = await this.loadConfig();
433
+ if (!config) {
434
+ throw new ValidationError(MISSING_CONFIG);
435
+ }
436
+ return config;
437
+ }
438
+ /** Load an explicit/discovered config, otherwise resolve one root project. */
439
+ async loadConfig() {
434
440
  const loaded = await loadChangesetConfig({
435
441
  cwd: this.cwd,
436
442
  ...(this.configFile ? { configFile: this.configFile } : {}),
437
443
  ...(this.tagFormat ? { tagFormat: this.tagFormat } : {}),
438
444
  });
439
- if (!loaded) {
440
- throw new ValidationError(MISSING_CONFIG);
445
+ if (loaded) {
446
+ if (this.singleProject) {
447
+ throw new ValidationError('Single-project options cannot be combined with a wp-release config. Set these options in the config instead.');
448
+ }
449
+ return loaded.config;
441
450
  }
442
- return loaded.config;
451
+ return resolveSingleProjectConfig(this.cwd, this.singleProject, this.tagFormat);
443
452
  }
444
453
  /**
445
454
  * Get the directory of the changeset files.
@@ -455,7 +464,7 @@ export class ChangesetRelease {
455
464
  * Discover configured packages, pending changesets, and their proposed versions.
456
465
  *
457
466
  * @param input Repository root and optional explicit config file.
458
- * @return A release plan; `undefined` when no config is present.
467
+ * @return A release plan; `undefined` without a config or a recognized root project.
459
468
  */
460
469
  export async function getChangesetReleasePlan(input) {
461
470
  return new ChangesetRelease(input).getPlan();
package/dist/cli.d.ts CHANGED
@@ -3,9 +3,8 @@ import type { SignResult } from './commands/sign.ts';
3
3
  import type { KeygenResult } from './commands/keygen.ts';
4
4
  import type { ZipResult } from './commands/zip.ts';
5
5
  import type { ChangesetVersionResult } from './commands/changeset-version.ts';
6
- import { Logger } from './logger.ts';
7
6
  import type { OutputStream } from './logger.ts';
8
- import type { Command, CommandOutcome } from './commands/command.ts';
7
+ import type { CommandContext, Command, CommandOutcome } from './commands/command.ts';
9
8
  /** Input stream that interactive prompts read from in a terminal. */
10
9
  export type InputStream = NodeJS.ReadableStream & {
11
10
  isTTY?: boolean;
@@ -44,10 +43,24 @@ export declare function getCommandHelp(command: Command): string;
44
43
  * @return Command definition.
45
44
  */
46
45
  export declare function findCommand(args: string[]): Command | undefined;
46
+ /**
47
+ * Format the result of a command as GitHub Actions step outputs: the whole
48
+ * result as JSON in "result", and each top-level string, number or boolean
49
+ * as its own output, like "zipPath", so that a workflow reads it without
50
+ * fromJSON(). Lists and objects are only in "result". Every value but the
51
+ * compact JSON, which has no line breaks, uses a random delimiter, so that a
52
+ * value with line breaks can't set other outputs.
53
+ *
54
+ * @param result Command result.
55
+ * @return Lines for the GITHUB_OUTPUT file.
56
+ */
57
+ export declare function formatGitHubOutputs(result: unknown): string;
47
58
  /**
48
59
  * Results of the commands that can be run with execute(), by command name.
49
60
  */
50
61
  export interface CommandResults {
62
+ build: import('./commands/build.ts').BuildResult;
63
+ 'deploy-svn': import('./commands/deploy-svn.ts').DeploySvnResult;
51
64
  zip: ZipResult;
52
65
  checksums: ChecksumsResult;
53
66
  sign: SignResult;
@@ -68,14 +81,11 @@ export interface CommandResults {
68
81
  * @param context.cwd Working directory that relative paths resolve against.
69
82
  * @param context.configFile Explicit config file path, when supplied by a wrapper.
70
83
  * @param context.logger Logger for the command output.
84
+ * @param context.svnRunner Optional SVN command runner supplied by a wrapper.
71
85
  * @return Exit code and result of the command.
72
86
  * @throws {Error} If the command is unknown, the arguments are invalid or the command fails.
73
87
  */
74
- export declare function execute<Name extends keyof CommandResults>(name: Name, args: string[], context: {
75
- cwd: string;
76
- logger: Logger;
77
- configFile?: string;
78
- }): Promise<CommandOutcome<CommandResults[Name]>>;
88
+ export declare function execute<Name extends keyof CommandResults>(name: Name, args: string[], context: Pick<CommandContext, 'cwd' | 'logger' | 'configFile' | 'svnRunner'>): Promise<CommandOutcome<CommandResults[Name]>>;
79
89
  /**
80
90
  * Run the CLI.
81
91
  *
package/dist/cli.js CHANGED
@@ -1,6 +1,8 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { appendFileSync } from 'node:fs';
2
3
  import { parseArgs } from 'node:util';
3
4
  import zip from "./commands/zip.js";
5
+ import build from "./commands/build.js";
4
6
  import checksums from "./commands/checksums.js";
5
7
  import sign from "./commands/sign.js";
6
8
  import keygen from "./commands/keygen.js";
@@ -21,6 +23,7 @@ export const EXIT_SUCCESS = 0;
21
23
  export const EXIT_FAILURE = 1;
22
24
  export const EXIT_INVALID = 2;
23
25
  export const commands = [
26
+ build,
24
27
  zip,
25
28
  checksums,
26
29
  sign,
@@ -177,6 +180,28 @@ function createLogWriter(values, streams) {
177
180
  }
178
181
  return create(streams);
179
182
  }
183
+ /**
184
+ * Format the result of a command as GitHub Actions step outputs: the whole
185
+ * result as JSON in "result", and each top-level string, number or boolean
186
+ * as its own output, like "zipPath", so that a workflow reads it without
187
+ * fromJSON(). Lists and objects are only in "result". Every value but the
188
+ * compact JSON, which has no line breaks, uses a random delimiter, so that a
189
+ * value with line breaks can't set other outputs.
190
+ *
191
+ * @param result Command result.
192
+ * @return Lines for the GITHUB_OUTPUT file.
193
+ */
194
+ export function formatGitHubOutputs(result) {
195
+ const values = 'object' === typeof result && null !== result
196
+ ? Object.entries(result).filter(([name, value]) => 'result' !== name &&
197
+ ['string', 'number', 'boolean'].includes(typeof value))
198
+ : [];
199
+ const delimiter = `ghadelimiter_${randomUUID()}`;
200
+ return [
201
+ `result=${JSON.stringify(result)}\n`,
202
+ ...values.map(([name, value]) => `${name}<<${delimiter}\n${String(value)}\n${delimiter}\n`),
203
+ ].join('');
204
+ }
180
205
  /**
181
206
  * Get the GitHub Actions step output file for --github-output, checked before
182
207
  * the command runs.
@@ -241,8 +266,7 @@ async function runCommand(command, args, io) {
241
266
  stdout.write(JSON.stringify(result, null, '\t') + '\n');
242
267
  }
243
268
  if (githubOutputFile) {
244
- // Compact JSON has no line breaks, so it fits a single output line.
245
- appendFileSync(githubOutputFile, `result=${JSON.stringify(result)}\n`);
269
+ appendFileSync(githubOutputFile, formatGitHubOutputs(result));
246
270
  }
247
271
  return exitCode;
248
272
  }
@@ -262,6 +286,7 @@ async function runCommand(command, args, io) {
262
286
  * @param context.cwd Working directory that relative paths resolve against.
263
287
  * @param context.configFile Explicit config file path, when supplied by a wrapper.
264
288
  * @param context.logger Logger for the command output.
289
+ * @param context.svnRunner Optional SVN command runner supplied by a wrapper.
265
290
  * @return Exit code and result of the command.
266
291
  * @throws {Error} If the command is unknown, the arguments are invalid or the command fails.
267
292
  */
@@ -1,4 +1,5 @@
1
1
  import type { ParseArgsOptionsConfig } from 'node:util';
2
+ import type { SignatureFormat } from '../signing.ts';
2
3
  import type { ParsedValues } from './command.ts';
3
4
  /**
4
5
  * Options shared by the commands that select files with .distignore and
@@ -69,3 +70,13 @@ export interface ResolveOutputInput {
69
70
  * @return Absolute file path.
70
71
  */
71
72
  export declare function resolveOutput(input: ResolveOutputInput): string;
73
+ /**
74
+ * Get the signature formats from the --format values of sign or the
75
+ * --sign-format values of zip, which can be repeated
76
+ * or comma-separated.
77
+ *
78
+ * @param values Option values.
79
+ * @return Unique formats, the default when none are given.
80
+ * @throws {ValidationError} If a format is unknown.
81
+ */
82
+ export declare function parseSignatureFormats(values: string[]): SignatureFormat[];
@@ -1,6 +1,7 @@
1
1
  import { stat } from 'node:fs/promises';
2
2
  import { resolve } from 'node:path';
3
3
  import { ValidationError } from "../errors.js";
4
+ import { DEFAULT_SIGNATURE_FORMAT, SIGNATURE_FORMATS, isSignatureFormat, } from "../signing.js";
4
5
  import { getBoolean, getString, getStrings } from "./command.js";
5
6
  /**
6
7
  * Options shared by the commands that select files with .distignore and
@@ -95,3 +96,25 @@ export function getSourceDirArg(values, positionals) {
95
96
  export function resolveOutput(input) {
96
97
  return resolve(input.cwd, input.output ?? input.filename);
97
98
  }
99
+ /**
100
+ * Get the signature formats from the --format values of sign or the
101
+ * --sign-format values of zip, which can be repeated
102
+ * or comma-separated.
103
+ *
104
+ * @param values Option values.
105
+ * @return Unique formats, the default when none are given.
106
+ * @throws {ValidationError} If a format is unknown.
107
+ */
108
+ export function parseSignatureFormats(values) {
109
+ const names = values
110
+ .flatMap((value) => value.split(','))
111
+ .map((name) => name.trim())
112
+ .filter(Boolean);
113
+ const unknown = names.filter((name) => !isSignatureFormat(name));
114
+ if (unknown.length) {
115
+ throw new ValidationError(`Unknown signature format ${unknown.join(', ')}, use ${Object.keys(SIGNATURE_FORMATS).join(', ')}.`);
116
+ }
117
+ return names.length
118
+ ? [...new Set(names.filter(isSignatureFormat))]
119
+ : [DEFAULT_SIGNATURE_FORMAT];
120
+ }
@@ -0,0 +1,20 @@
1
+ import type { ParseArgsOptionsConfig } from 'node:util';
2
+ import type { SignFilesInput, SignatureFormat } from '../signing.ts';
3
+ import type { ParsedValues } from './command.ts';
4
+ export declare const checksumSigningArgs: ParseArgsOptionsConfig;
5
+ interface SigningPaths {
6
+ artifact?: string;
7
+ checksums: string;
8
+ }
9
+ /**
10
+ * Resolve explicit signing targets and validate them before creating artifacts.
11
+ *
12
+ * @param values Parsed CLI flags.
13
+ * @param paths Artifact and manifest paths that can be signed.
14
+ * @return Signing input, or undefined when no signing is requested.
15
+ * @throws {ValidationError} For formats without a signing target or invalid signing input.
16
+ */
17
+ export declare function getArtifactSigning(values: ParsedValues, paths: SigningPaths): (SignFilesInput & {
18
+ formats: SignatureFormat[];
19
+ }) | undefined;
20
+ export {};
@@ -0,0 +1,42 @@
1
+ import { ValidationError } from "../errors.js";
2
+ import { checkSigningInput } from "../signing.js";
3
+ import { parseSignatureFormats } from "./args.js";
4
+ import { getBoolean, getStrings } from "./command.js";
5
+ export const checksumSigningArgs = {
6
+ checksums: { type: 'boolean' },
7
+ 'sign-checksums': { type: 'boolean' },
8
+ 'sign-format': { type: 'string', multiple: true },
9
+ };
10
+ /**
11
+ * Resolve explicit signing targets and validate them before creating artifacts.
12
+ *
13
+ * @param values Parsed CLI flags.
14
+ * @param paths Artifact and manifest paths that can be signed.
15
+ * @return Signing input, or undefined when no signing is requested.
16
+ * @throws {ValidationError} For formats without a signing target or invalid signing input.
17
+ */
18
+ export function getArtifactSigning(values, paths) {
19
+ const names = getStrings(values, 'sign-format');
20
+ const targets = [
21
+ ...(getBoolean(values, 'sign') && paths.artifact
22
+ ? [paths.artifact]
23
+ : []),
24
+ ...(getBoolean(values, 'sign-checksums')
25
+ ? [paths.checksums]
26
+ : []),
27
+ ];
28
+ if (names.length && !targets.length) {
29
+ throw new ValidationError('--sign-format requires --sign or --sign-checksums; it does not enable signing.');
30
+ }
31
+ const signing = targets.length
32
+ ? {
33
+ paths: targets,
34
+ formats: parseSignatureFormats(names),
35
+ force: getBoolean(values, 'force'),
36
+ }
37
+ : undefined;
38
+ if (signing) {
39
+ checkSigningInput(signing);
40
+ }
41
+ return signing;
42
+ }
@@ -0,0 +1,32 @@
1
+ import type { BuildPackageResult } from '../package-build.ts';
2
+ import type { CommandOutcome } from './command.ts';
3
+ export interface BuildResult extends BuildPackageResult {
4
+ package?: string;
5
+ signatures?: string[];
6
+ publicKey?: string;
7
+ }
8
+ declare const _default: {
9
+ name: string;
10
+ usage: string;
11
+ summary: string;
12
+ help: string;
13
+ options: {
14
+ package: {
15
+ type: "string";
16
+ };
17
+ force: {
18
+ type: "boolean";
19
+ };
20
+ output: {
21
+ type: "string";
22
+ };
23
+ slug: {
24
+ type: "string";
25
+ };
26
+ version: {
27
+ type: "string";
28
+ };
29
+ };
30
+ run({ values, positionals, cwd, configFile, logger, }: import("./command.ts").CommandContext): Promise<CommandOutcome<BuildResult>>;
31
+ };
32
+ export default _default;
@@ -0,0 +1,91 @@
1
+ import { checksumSigningArgs, getArtifactSigning } from "./artifact-signing.js";
2
+ import { signFiles } from "../signing.js";
3
+ import { resolve } from 'node:path';
4
+ import { getBuildSummary, getSignSummary } from "../summary.js";
5
+ import { ValidationError } from "../errors.js";
6
+ import { buildPackage, getBuildChecksumsPath } from "../package-build.js";
7
+ import { fileSelectionArgs, sourceDirArgs } from "./args.js";
8
+ import { getBoolean, getString } from "./command.js";
9
+ import { getDirectorySource, getPackageArg, getPackageSource, } from "./package-source.js";
10
+ export default {
11
+ name: 'build',
12
+ usage: 'build [<source-dir>] --output <dir> [options] | build --package <name> --output <dir>',
13
+ summary: 'Copy the selected release files into an output directory.',
14
+ help: `Options:
15
+ --output <dir> Required destination; files go directly inside it
16
+ --source-dir <dir> Source directory (default: .), or a positional argument
17
+ --package <name> Use the configured package directory and file rules
18
+ --distignore <file> Ignore file (default: <source-dir>/.distignore)
19
+ --no-distignore Don't read an ignore file
20
+ --exclude <patterns> Extra patterns, comma-separated or repeated
21
+ --exclude-gitattributes Exclude Git export-ignore paths
22
+ --slug <slug> Slug in the manifest (default: project directory name)
23
+ --version <version> Manifest version (default: Version header)
24
+ --checksums Write <output-dir>.checksums.json beside the directory
25
+ --sign-checksums Generate and sign the manifest; implies --checksums
26
+ --sign-format <names> Formats: wordpress,minisign,ssh; requires --sign-checksums
27
+ --force Replace an existing output directory, removing stale files
28
+
29
+ The output directory is excluded from the source selection. It cannot be the
30
+ source directory or an ancestor. --sign is unsupported for directories.
31
+
32
+ Examples:
33
+ wp-release build --output dist/my-plugin
34
+ wp-release build --package plugins/alpha --output dist/alpha`,
35
+ options: {
36
+ output: { type: 'string' },
37
+ slug: { type: 'string' },
38
+ version: { type: 'string' },
39
+ ...checksumSigningArgs,
40
+ ...sourceDirArgs,
41
+ ...fileSelectionArgs,
42
+ package: { type: 'string' },
43
+ force: { type: 'boolean' },
44
+ },
45
+ async run({ values, positionals, cwd, configFile, logger, }) {
46
+ const output = getString(values, 'output');
47
+ if (!output?.trim()) {
48
+ throw new ValidationError('build requires --output <dir>.');
49
+ }
50
+ const name = getPackageArg(values, positionals);
51
+ const source = name
52
+ ? await getPackageSource(values, cwd, name, configFile)
53
+ : await getDirectorySource(values, positionals, cwd);
54
+ const outputDir = resolve(cwd, output);
55
+ const signing = getArtifactSigning(values, {
56
+ checksums: getBuildChecksumsPath(outputDir),
57
+ });
58
+ const built = await buildPackage({
59
+ sourceDir: source.sourceDir,
60
+ selection: source.selection,
61
+ outputDir,
62
+ checksums: getBoolean(values, 'checksums') ||
63
+ getBoolean(values, 'sign-checksums'),
64
+ slug: source.slug,
65
+ version: source.version,
66
+ ...(source.type ? { type: source.type } : {}),
67
+ force: getBoolean(values, 'force'),
68
+ });
69
+ const signed = signing
70
+ ? { ...(await signFiles(signing)), formats: signing.formats }
71
+ : undefined;
72
+ const result = {
73
+ ...built,
74
+ ...(signed
75
+ ? { signatures: signed.signatures, publicKey: signed.publicKey }
76
+ : {}),
77
+ ...(source.package ? { package: source.package } : {}),
78
+ };
79
+ logger.debug(`Built ${built.files} files from ${source.sourceDir}.`);
80
+ logger.info(built.outputDir);
81
+ if (built.checksumsPath) {
82
+ logger.info(built.checksumsPath);
83
+ }
84
+ signed?.signatures.forEach((path) => logger.info(path));
85
+ logger.summary((context) => getBuildSummary(context, result));
86
+ if (signed) {
87
+ logger.summary((context) => getSignSummary(context, signed));
88
+ }
89
+ return { exitCode: 0, result };
90
+ },
91
+ };
@@ -8,24 +8,73 @@ declare const _default: {
8
8
  summary: string;
9
9
  help: string;
10
10
  options: {
11
- readonly package: {
11
+ 'version-package-json': {
12
12
  readonly type: "string";
13
13
  readonly multiple: true;
14
14
  };
15
- readonly bump: {
15
+ 'version-composer-json': {
16
16
  readonly type: "string";
17
17
  readonly multiple: true;
18
18
  };
19
- readonly summary: {
19
+ 'version-wordpress-plugin': {
20
+ readonly type: "string";
21
+ readonly multiple: true;
22
+ };
23
+ 'version-wordpress-theme': {
24
+ readonly type: "string";
25
+ readonly multiple: true;
26
+ };
27
+ 'version-wordpress-stable-tag': {
28
+ readonly type: "string";
29
+ readonly multiple: true;
30
+ };
31
+ 'version-php-constant': {
32
+ readonly type: "string";
33
+ readonly multiple: true;
34
+ };
35
+ 'version-git-tag': {
36
+ readonly type: "string";
37
+ readonly multiple: true;
38
+ };
39
+ 'version-from': {
40
+ readonly type: "string";
41
+ };
42
+ 'base-branch': {
43
+ readonly type: "string";
44
+ };
45
+ remote: {
46
+ readonly type: "string";
47
+ };
48
+ 'changesets-dir': {
49
+ readonly type: "string";
50
+ };
51
+ 'readme-changelog': {
52
+ readonly type: "boolean";
53
+ };
54
+ 'no-readme-changelog': {
55
+ readonly type: "boolean";
56
+ };
57
+ 'changelog-insert-after': {
58
+ readonly type: "string";
59
+ };
60
+ package: {
61
+ readonly type: "string";
62
+ readonly multiple: true;
63
+ };
64
+ bump: {
65
+ readonly type: "string";
66
+ readonly multiple: true;
67
+ };
68
+ summary: {
20
69
  readonly type: "string";
21
70
  };
22
- readonly empty: {
71
+ empty: {
23
72
  readonly type: "boolean";
24
73
  };
25
- readonly since: {
74
+ since: {
26
75
  readonly type: "string";
27
76
  };
28
- readonly name: {
77
+ name: {
29
78
  readonly type: "string";
30
79
  };
31
80
  };
@@ -1,3 +1,4 @@
1
+ import { changesetProjectArgs, changesetProjectHelp, parseChangesetProjectArgs, } from "./changeset-project-args.js";
1
2
  import { resolvePackageNames } from "../changeset-packages.js";
2
3
  import { ChangesetPrompt } from "../changeset-prompt.js";
3
4
  import { ChangesetRelease } from "../changeset-release.js";
@@ -13,6 +14,12 @@ import { parseChangesetAddArgs, changesetAddArgs } from "./changeset-args.js";
13
14
  * @throws {ValidationError} If no changed package needs a changeset.
14
15
  */
15
16
  async function getChangedPackageBumps(release, input, bump) {
17
+ const packages = await release.getPackages();
18
+ if (!input.since &&
19
+ 1 === packages.length &&
20
+ '.' === packages[0]?.path) {
21
+ return { [packages[0].name]: bump };
22
+ }
16
23
  const status = await release.getStatus({
17
24
  ...(input.since ? { since: input.since } : {}),
18
25
  uncommitted: true,
@@ -99,15 +106,17 @@ export default {
99
106
 
100
107
  Use --package and --bump pairs for package releases, or --empty for changes
101
108
  that should pass the status check without producing a release. A single
102
- --bump without --package releases the packages that changed since the base
109
+ --bump without --package selects the sole root package, or releases packages
110
+ that changed since the base
103
111
  ref, including uncommitted files, and have no changeset yet. Without
104
112
  --package and --bump in a terminal, the command asks for the packages, with
105
- the changed ones preselected, their bumps and the release note.`,
106
- options: changesetAddArgs,
113
+ the changed ones preselected, their bumps and the release note.${changesetProjectHelp}`,
114
+ options: { ...changesetAddArgs, ...changesetProjectArgs },
107
115
  async run({ values, cwd, configFile, tagFormat, logger, terminal }) {
108
116
  const input = parseChangesetAddArgs(values);
109
117
  const release = new ChangesetRelease({
110
118
  cwd,
119
+ ...parseChangesetProjectArgs(values),
111
120
  ...(configFile ? { configFile } : {}),
112
121
  ...(tagFormat ? { tagFormat } : {}),
113
122
  });
@@ -193,8 +193,8 @@ export function parseChangesetTagArgs(values) {
193
193
  if ('' === args.version) {
194
194
  throw new ValidationError('--version <version> must not be empty.');
195
195
  }
196
- if (undefined !== args.version && 1 !== args.packages.length) {
197
- throw new ValidationError('--version <version> requires exactly one --package <name>.');
196
+ if (undefined !== args.version && args.packages.length > 1) {
197
+ throw new ValidationError('--version <version> accepts at most one --package <name>.');
198
198
  }
199
199
  if (args.untagged && args.push) {
200
200
  throw new ValidationError('--untagged cannot be combined with --push.');
@@ -0,0 +1,63 @@
1
+ import type { SingleProjectOptions } from '../single-project.ts';
2
+ import type { ParsedValues } from './command.ts';
3
+ export declare const changesetProjectArgs: {
4
+ readonly 'version-package-json': {
5
+ readonly type: "string";
6
+ readonly multiple: true;
7
+ };
8
+ readonly 'version-composer-json': {
9
+ readonly type: "string";
10
+ readonly multiple: true;
11
+ };
12
+ readonly 'version-wordpress-plugin': {
13
+ readonly type: "string";
14
+ readonly multiple: true;
15
+ };
16
+ readonly 'version-wordpress-theme': {
17
+ readonly type: "string";
18
+ readonly multiple: true;
19
+ };
20
+ readonly 'version-wordpress-stable-tag': {
21
+ readonly type: "string";
22
+ readonly multiple: true;
23
+ };
24
+ readonly 'version-php-constant': {
25
+ readonly type: "string";
26
+ readonly multiple: true;
27
+ };
28
+ readonly 'version-git-tag': {
29
+ readonly type: "string";
30
+ readonly multiple: true;
31
+ };
32
+ readonly 'version-from': {
33
+ readonly type: "string";
34
+ };
35
+ readonly 'base-branch': {
36
+ readonly type: "string";
37
+ };
38
+ readonly remote: {
39
+ readonly type: "string";
40
+ };
41
+ readonly 'changesets-dir': {
42
+ readonly type: "string";
43
+ };
44
+ readonly 'readme-changelog': {
45
+ readonly type: "boolean";
46
+ };
47
+ readonly 'no-readme-changelog': {
48
+ readonly type: "boolean";
49
+ };
50
+ readonly 'changelog-insert-after': {
51
+ readonly type: "string";
52
+ };
53
+ };
54
+ export declare const changesetProjectHelp = "\nSingle-project options (without a release config file):\n --version-package-json <file> npm version source/target (repeatable)\n --version-composer-json <file> Composer source/target (repeatable)\n --version-wordpress-plugin <file> Plugin header source/target (repeatable)\n --version-wordpress-theme <file> Theme header source/target (repeatable)\n --version-wordpress-stable-tag <file> Readme Stable tag source/target (repeatable)\n --version-php-constant <file>#<constant> PHP constant source/target (repeatable)\n --version-git-tag <pattern> Git tag source only; use {version}\n --version-from <kind>[:<file>] Choose the source; constants use file#constant\n --base-branch <branch> Default comparison branch on the remote\n --remote <name> Git remote (default: origin)\n --changesets-dir <dir> Changeset directory (default: .changeset)\n --readme-changelog / --no-readme-changelog\n Enable/disable readme changelog updates\n --changelog-insert-after <marker> Insert changelog entries after this marker\n\nExplicit descriptors define all version targets. Without them, detect one root\nproject. A unique non-Stable-tag descriptor is the inferred source; otherwise\npass --version-from, qualifying it with :file when its kind is repeated.\nUse the same project options for every changeset command.";
55
+ /**
56
+ * Parse shared single-project settings; operation flags stay command-specific.
57
+ *
58
+ * @param values Parsed command arguments.
59
+ * @return Fallback settings when explicitly supplied.
60
+ */
61
+ export declare function parseChangesetProjectArgs(values: ParsedValues): {
62
+ singleProject?: SingleProjectOptions;
63
+ };