@wpelevator/wp-release 0.12.0 → 0.13.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 (49) hide show
  1. package/README.md +90 -6
  2. package/dist/changeset-config.d.ts +86 -2
  3. package/dist/changeset-config.js +101 -6
  4. package/dist/changeset-dependents.d.ts +19 -8
  5. package/dist/changeset-dependents.js +96 -34
  6. package/dist/changeset-files.d.ts +6 -5
  7. package/dist/changeset-files.js +7 -5
  8. package/dist/changeset-packages.d.ts +45 -8
  9. package/dist/changeset-packages.js +73 -14
  10. package/dist/changeset-prompt.d.ts +119 -0
  11. package/dist/changeset-prompt.js +203 -0
  12. package/dist/changeset-release.d.ts +38 -3
  13. package/dist/changeset-release.js +88 -11
  14. package/dist/changesets.d.ts +6 -2
  15. package/dist/changesets.js +18 -12
  16. package/dist/cli.d.ts +7 -1
  17. package/dist/cli.js +18 -5
  18. package/dist/commands/changeset-add.d.ts +5 -2
  19. package/dist/commands/changeset-add.js +108 -9
  20. package/dist/commands/changeset-args.d.ts +24 -5
  21. package/dist/commands/changeset-args.js +57 -14
  22. package/dist/commands/changeset-status.d.ts +2 -0
  23. package/dist/commands/changeset-tag.js +2 -1
  24. package/dist/commands/changeset-version.d.ts +6 -0
  25. package/dist/commands/changeset-version.js +15 -3
  26. package/dist/commands/command.d.ts +3 -0
  27. package/dist/commands/deploy-git.d.ts +24 -4
  28. package/dist/commands/deploy-git.js +56 -3
  29. package/dist/commands/deploy-svn.d.ts +24 -4
  30. package/dist/commands/deploy-svn.js +56 -2
  31. package/dist/commands/package-deploys.d.ts +78 -0
  32. package/dist/commands/package-deploys.js +108 -0
  33. package/dist/git-deploy.d.ts +32 -0
  34. package/dist/git-deploy.js +57 -1
  35. package/dist/index.d.ts +5 -2
  36. package/dist/index.js +2 -1
  37. package/dist/package-deploy.d.ts +105 -0
  38. package/dist/package-deploy.js +288 -0
  39. package/dist/release-repository.d.ts +14 -2
  40. package/dist/release-repository.js +46 -7
  41. package/dist/release-tags.d.ts +3 -1
  42. package/dist/release-tags.js +5 -2
  43. package/dist/tag-format.d.ts +3 -3
  44. package/dist/tag-format.js +1 -1
  45. package/dist/version-files.d.ts +2 -1
  46. package/dist/version-files.js +12 -7
  47. package/dist/versions.d.ts +28 -3
  48. package/dist/versions.js +84 -9
  49. package/package.json +1 -1
@@ -3,7 +3,7 @@ import { getVersionedReleases, prependChangelog } from "./changelog.js";
3
3
  import { loadChangesetConfig } from "./changeset-config.js";
4
4
  import { applyDependents, assertDependentsSourcesReleased, collectPinDriftWarnings, expandDependentsPlan, findBundledPinChanges, hasChangeset, selectReleasePlan, stripDependentsPlan, updateDependencyDeclaration, } from "./changeset-dependents.js";
5
5
  import { consumeChangeset, readChangesets, readChangesetsSince, writeChangeset, } from "./changeset-files.js";
6
- import { assertKnownPackages, discoverPackages, getChangedPackages, toChangesetPackage, } from "./changeset-packages.js";
6
+ import { discoverPackages, getChangedPackages, resolvePackageNames, toChangesetPackage, } from "./changeset-packages.js";
7
7
  import { getChangesetBump, getNextVersion } from "./changesets.js";
8
8
  import { ValidationError } from "./errors.js";
9
9
  import { FileJournal } from "./file-journal.js";
@@ -11,7 +11,7 @@ import { getPreviousTag, getReleaseNotes, getTagVersion, selectTagPackages, } fr
11
11
  import { ReleaseRepository } from "./release-repository.js";
12
12
  import { formatTag } from "./tag-format.js";
13
13
  import { verifyVersionTargets, writeVersion } from "./version-files.js";
14
- import { isPrerelease } from "./versions.js";
14
+ import { getPrereleaseVersion, getSnapshotVersion, isPrerelease, } from "./versions.js";
15
15
  const MISSING_CONFIG = 'No wp-release config found. Add wp-release.config.js or pass --config <path>.';
16
16
  /**
17
17
  * Changeset releases of a repository: recording changesets, versioning the
@@ -67,8 +67,17 @@ export class ChangesetRelease {
67
67
  async add(options) {
68
68
  const config = await this.requireConfig();
69
69
  const packages = await discoverPackages(this.repository, config);
70
- assertKnownPackages(packages, Object.keys(options.packages));
71
- return writeChangeset(this.getChangesetDirectory(config), options.packages, options.summary, options.name);
70
+ const entries = Object.entries(options.packages);
71
+ const names = resolvePackageNames(packages, entries.map(([name]) => name));
72
+ const resolved = {};
73
+ entries.forEach(([, bump], index) => {
74
+ const name = names[index] ?? '';
75
+ if (Object.hasOwn(resolved, name)) {
76
+ throw new ValidationError(`Package "${name}" was passed more than once.`);
77
+ }
78
+ resolved[name] = bump;
79
+ });
80
+ return writeChangeset(this.getChangesetDirectory(config), resolved, options.summary, options.name);
72
81
  }
73
82
  /**
74
83
  * Apply pending package versions and changelogs. A failed run restores
@@ -82,9 +91,12 @@ export class ChangesetRelease {
82
91
  const context = await this.requireContext();
83
92
  const { config, packages } = context;
84
93
  const withDependents = options.dependents ?? true;
94
+ const selected = options.packages
95
+ ? resolvePackageNames(packages, options.packages)
96
+ : undefined;
85
97
  let releasePlan = context.plan;
86
- if (options.packages) {
87
- releasePlan = expandDependentsPlan(await selectReleasePlan(context.plan, packages, options.packages, config, withDependents));
98
+ if (selected) {
99
+ releasePlan = expandDependentsPlan(await selectReleasePlan(context.plan, packages, selected, config, withDependents));
88
100
  }
89
101
  else if (!withDependents) {
90
102
  releasePlan = stripDependentsPlan(context.plan);
@@ -96,10 +108,31 @@ export class ChangesetRelease {
96
108
  if (withDependents && config.dependents) {
97
109
  await assertDependentsSourcesReleased(releasePlan, context.plan, packages, config);
98
110
  }
111
+ if (undefined !== options.prerelease &&
112
+ undefined !== options.snapshot) {
113
+ throw new ValidationError('A release cannot be both a pre-release and a snapshot.');
114
+ }
115
+ if (undefined !== options.snapshot) {
116
+ const id = options.snapshot;
117
+ const time = options.snapshotTime ?? new Date();
118
+ releasePlan = {
119
+ ...releasePlan,
120
+ releases: releasePlan.releases.map((release) => ({
121
+ ...release,
122
+ nextVersion: getSnapshotVersion(release.nextVersion, id, time),
123
+ })),
124
+ };
125
+ }
126
+ if (undefined !== options.prerelease) {
127
+ releasePlan = {
128
+ ...releasePlan,
129
+ releases: await this.toPrereleases(context, releasePlan.releases, options.prerelease),
130
+ };
131
+ }
99
132
  const date = options.date ?? new Date().toISOString().slice(0, 10);
100
133
  const releases = await getVersionedReleases(this.repository, config, releasePlan.releases, this.getChangesetDirectory(config));
101
134
  if (!options.dryRun) {
102
- await this.writeReleases(context, releasePlan, releases, date, options.packages);
135
+ await this.writeReleases(context, releasePlan, releases, date, selected);
103
136
  }
104
137
  return {
105
138
  changesets: releasePlan.changesets,
@@ -124,7 +157,7 @@ export class ChangesetRelease {
124
157
  throw new ValidationError('Listing untagged releases cannot be combined with pushing tags.');
125
158
  }
126
159
  const context = await this.requireContext();
127
- const selected = selectTagPackages(context.packages, options.packages ?? [], options.version);
160
+ const selected = selectTagPackages(context.packages, resolvePackageNames(context.packages, options.packages ?? []), options.version);
128
161
  const packages = options.since
129
162
  ? (await getChangedPackages(this.repository, selected, options.since)).packages
130
163
  : selected;
@@ -185,14 +218,23 @@ export class ChangesetRelease {
185
218
  const since = options.since ??
186
219
  (config.baseBranch ? `origin/${config.baseBranch}` : undefined);
187
220
  const changed = since
188
- ? await getChangedPackages(this.repository, packages, since)
221
+ ? await getChangedPackages(this.repository, packages, since, options.uncommitted)
189
222
  : { packages: [], filesByPackage: {} };
190
223
  const changedPackages = changed.packages.map((pkg) => pkg.name);
191
224
  const changesetsInDiff = since
192
- ? await readChangesetsSince(this.repository, this.getChangesetDirectory(config), changesets, since)
225
+ ? await readChangesetsSince(this.repository, this.getChangesetDirectory(config), changesets, since, options.uncommitted)
193
226
  : [];
194
227
  const hasEmptyChangeset = changesetsInDiff.some((changeset) => 0 === Object.keys(changeset.packages).length);
195
- const bundled = await findBundledPinChanges(changed, changesetsInDiff, packages, config);
228
+ const bundled = since
229
+ ? await findBundledPinChanges({
230
+ changed,
231
+ changesetsInDiff,
232
+ packages,
233
+ config,
234
+ repository: this.repository,
235
+ since,
236
+ })
237
+ : new Set();
196
238
  const missingChangesets = hasEmptyChangeset
197
239
  ? []
198
240
  : changedPackages.filter((name) => !hasChangeset(changesetsInDiff, name) &&
@@ -203,6 +245,7 @@ export class ChangesetRelease {
203
245
  ? await collectPinDriftWarnings(config, packages)
204
246
  : [];
205
247
  return {
248
+ ...(since ? { since } : {}),
206
249
  changesets,
207
250
  releases,
208
251
  changedPackages,
@@ -211,6 +254,40 @@ export class ChangesetRelease {
211
254
  warnings,
212
255
  };
213
256
  }
257
+ /**
258
+ * Turn the next versions of releases into pre-releases, like 1.3.0-rc.1
259
+ * for 1.3.0, numbered from the existing tags of each package. Dependents
260
+ * become pre-releases too, so they bundle the pre-releases of their sources.
261
+ *
262
+ * @param context Release context.
263
+ * @param releases Releases with their stable next versions.
264
+ * @param prerelease Pre-release identifier with an optional number, like rc or rc.1.
265
+ * @return Releases with their pre-release next versions.
266
+ * @throws {ValidationError} For an invalid pre-release or an existing version.
267
+ */
268
+ async toPrereleases(context, releases, prerelease) {
269
+ const tagFormat = context.config.tagFormat ?? '{path}/{version}';
270
+ return Promise.all(releases.map(async (release) => {
271
+ const configured = context.packages.find((pkg) => pkg.name === release.package.name);
272
+ const versions = configured
273
+ ? await this.listTaggedVersions(formatTag(tagFormat, configured, ''))
274
+ : [];
275
+ return {
276
+ ...release,
277
+ nextVersion: getPrereleaseVersion(release.nextVersion, prerelease, versions),
278
+ };
279
+ }));
280
+ }
281
+ /**
282
+ * List the versions of the tags with a prefix.
283
+ *
284
+ * @param prefix Tag prefix of a package, like `plugins/alpha/`.
285
+ * @return Tagged versions.
286
+ */
287
+ async listTaggedVersions(prefix) {
288
+ const tags = await this.repository.listTags(prefix);
289
+ return tags.map((tag) => tag.slice(prefix.length));
290
+ }
214
291
  /**
215
292
  * Write the versions, dependency specs and changelogs of the releases and
216
293
  * consume the applied changesets, restoring every changed file when a
@@ -83,11 +83,15 @@ export declare function formatChangelogList(entries: string[]): string;
83
83
  */
84
84
  export declare function getChangesetBump(changesets: Changeset[], packageName: string): ChangesetBump | undefined;
85
85
  /**
86
- * Calculate the next stable semantic version using literal SemVer bump rules.
86
+ * Calculate the next stable version using literal SemVer bump rules. Besides
87
+ * x.y.z, it accepts the x.y and x.y.z.w versions common in WordPress plugins:
88
+ * the bumped part is raised, the parts after it reset to 0, and the version
89
+ * keeps its number of parts, so a minor bump of 2.1 is 2.2 and of 1.2.3.4 is
90
+ * 1.3.0.0. A patch bump of x.y adds the third part, so 2.1 becomes 2.1.1.
87
91
  *
88
92
  * @param currentVersion Current package version.
89
93
  * @param bump Requested bump.
90
94
  * @return Next version.
91
- * @throws {ValidationError} If currentVersion is not a stable x.y.z version.
95
+ * @throws {ValidationError} If currentVersion is not a stable x.y, x.y.z or x.y.z.w version.
92
96
  */
93
97
  export declare function getNextVersion(currentVersion: string, bump: ChangesetBump): string;
@@ -99,26 +99,32 @@ export function getChangesetBump(changesets, packageName) {
99
99
  return bumps.sort((a, b) => BUMP_RANK[b] - BUMP_RANK[a])[0];
100
100
  }
101
101
  /**
102
- * Calculate the next stable semantic version using literal SemVer bump rules.
102
+ * Calculate the next stable version using literal SemVer bump rules. Besides
103
+ * x.y.z, it accepts the x.y and x.y.z.w versions common in WordPress plugins:
104
+ * the bumped part is raised, the parts after it reset to 0, and the version
105
+ * keeps its number of parts, so a minor bump of 2.1 is 2.2 and of 1.2.3.4 is
106
+ * 1.3.0.0. A patch bump of x.y adds the third part, so 2.1 becomes 2.1.1.
103
107
  *
104
108
  * @param currentVersion Current package version.
105
109
  * @param bump Requested bump.
106
110
  * @return Next version.
107
- * @throws {ValidationError} If currentVersion is not a stable x.y.z version.
111
+ * @throws {ValidationError} If currentVersion is not a stable x.y, x.y.z or x.y.z.w version.
108
112
  */
109
113
  export function getNextVersion(currentVersion, bump) {
110
114
  if (!CHANGESET_BUMPS.includes(bump)) {
111
115
  throw new ValidationError(`Invalid bump "${bump}"; expected patch, minor or major.`);
112
116
  }
113
- const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(currentVersion);
114
- if (!match) {
115
- throw new ValidationError(`Cannot bump "${currentVersion}": expected a stable semantic version in x.y.z format.`);
117
+ if (!/^(0|[1-9]\d*)(\.(0|[1-9]\d*)){1,3}$/.test(currentVersion)) {
118
+ throw new ValidationError(`Cannot bump "${currentVersion}": expected a stable version in x.y, x.y.z or x.y.z.w format.`);
116
119
  }
117
- if ('major' === bump) {
118
- return `${Number(match[1]) + 1}.0.0`;
119
- }
120
- if ('minor' === bump) {
121
- return `${match[1]}.${Number(match[2]) + 1}.0`;
122
- }
123
- return `${match[1]}.${match[2]}.${Number(match[3]) + 1}`;
120
+ const parts = currentVersion.split('.').map(Number);
121
+ const index = { major: 0, minor: 1, patch: 2 }[bump];
122
+ const length = Math.max(parts.length, index + 1);
123
+ return Array.from({ length }, (_, position) => {
124
+ const part = parts[position] ?? 0;
125
+ if (position === index) {
126
+ return part + 1;
127
+ }
128
+ return position < index ? part : 0;
129
+ }).join('.');
124
130
  }
package/dist/cli.d.ts CHANGED
@@ -6,7 +6,13 @@ import type { ChangesetVersionResult } from './commands/changeset-version.ts';
6
6
  import { Logger } from './logger.ts';
7
7
  import type { OutputStream } from './logger.ts';
8
8
  import type { Command, CommandOutcome } from './commands/command.ts';
9
+ /** Input stream that interactive prompts read from in a terminal. */
10
+ export type InputStream = NodeJS.ReadableStream & {
11
+ isTTY?: boolean;
12
+ };
9
13
  export interface CliIo {
14
+ /** Input for interactive prompts, used when it and stdout are a terminal. */
15
+ stdin?: InputStream;
10
16
  /** Stream for regular output. */
11
17
  stdout?: OutputStream;
12
18
  /** Stream for errors. */
@@ -74,7 +80,7 @@ export declare function execute<Name extends keyof CommandResults>(name: Name, a
74
80
  * Run the CLI.
75
81
  *
76
82
  * @param args CLI arguments without the node and script paths.
77
- * @param io Output streams and working directory.
83
+ * @param io Input and output streams and working directory.
78
84
  * @return Exit code.
79
85
  */
80
86
  export declare function run(args: string[], io?: CliIo): Promise<number>;
package/dist/cli.js CHANGED
@@ -176,11 +176,11 @@ function getGitHubOutputFile(values) {
176
176
  *
177
177
  * @param command Command definition with options and run().
178
178
  * @param args Arguments after the command name.
179
- * @param io Output streams and working directory.
179
+ * @param io Input and output streams and working directory.
180
180
  * @return Exit code.
181
181
  */
182
182
  async function runCommand(command, args, io) {
183
- const { stdout, stderr, cwd } = io;
183
+ const { stdin, stdout, stderr, cwd } = io;
184
184
  let logger = new Logger({ writer: new TextWriter({ stdout, stderr }) });
185
185
  try {
186
186
  const { values, positionals } = parseArgs({
@@ -198,12 +198,20 @@ async function runCommand(command, args, io) {
198
198
  quiet: getBoolean(values, 'quiet') || getBoolean(values, 'json'),
199
199
  verbose: getBoolean(values, 'verbose'),
200
200
  });
201
+ // Prompts need a person at a terminal, and would corrupt JSON output.
202
+ const interactive = true === stdin.isTTY &&
203
+ true === stdout.isTTY &&
204
+ !getBoolean(values, 'json') &&
205
+ !githubOutputFile;
201
206
  const { exitCode, result } = await command.run({
202
207
  values,
203
208
  positionals,
204
209
  cwd,
205
210
  ...(configFile ? { configFile } : {}),
206
211
  logger,
212
+ ...(interactive
213
+ ? { terminal: { input: stdin, output: stdout } }
214
+ : {}),
207
215
  });
208
216
  if (getBoolean(values, 'json')) {
209
217
  stdout.write(JSON.stringify(result, null, '\t') + '\n');
@@ -259,11 +267,11 @@ export async function execute(name, args, context) {
259
267
  * Run the CLI.
260
268
  *
261
269
  * @param args CLI arguments without the node and script paths.
262
- * @param io Output streams and working directory.
270
+ * @param io Input and output streams and working directory.
263
271
  * @return Exit code.
264
272
  */
265
273
  export async function run(args, io = {}) {
266
- const { stdout = process.stdout, stderr = process.stderr, cwd = process.cwd(), } = io;
274
+ const { stdin = process.stdin, stdout = process.stdout, stderr = process.stderr, cwd = process.cwd(), } = io;
267
275
  const wantsHelp = args.includes('--help') || args.includes('-h');
268
276
  const command = findCommand(args);
269
277
  if (command && wantsHelp) {
@@ -271,7 +279,12 @@ export async function run(args, io = {}) {
271
279
  return EXIT_SUCCESS;
272
280
  }
273
281
  if (command) {
274
- return runCommand(command, args.slice(1), { stdout, stderr, cwd });
282
+ return runCommand(command, args.slice(1), {
283
+ stdin,
284
+ stdout,
285
+ stderr,
286
+ cwd,
287
+ });
275
288
  }
276
289
  if (0 === args.length || wantsHelp || 'help' === args[0]) {
277
290
  stdout.write(getHelp() + '\n');
@@ -22,15 +22,18 @@ declare const _default: {
22
22
  readonly empty: {
23
23
  readonly type: "boolean";
24
24
  };
25
+ readonly since: {
26
+ readonly type: "string";
27
+ };
25
28
  readonly name: {
26
29
  readonly type: "string";
27
30
  };
28
31
  };
29
- run({ values, cwd, configFile, logger }: import("./command.ts").CommandContext): Promise<{
32
+ run({ values, cwd, configFile, logger, terminal }: import("./command.ts").CommandContext): Promise<{
30
33
  exitCode: number;
31
34
  result: {
32
35
  file: string;
33
- packages: import("../changesets.ts").ChangesetPackages;
36
+ packages: any;
34
37
  };
35
38
  }>;
36
39
  };
@@ -1,34 +1,133 @@
1
- import { addChangeset } from "../changeset-release.js";
1
+ import { resolvePackageNames } from "../changeset-packages.js";
2
+ import { ChangesetPrompt } from "../changeset-prompt.js";
3
+ import { ChangesetRelease } from "../changeset-release.js";
4
+ import { ValidationError } from "../errors.js";
2
5
  import { parseChangesetAddArgs, changesetAddArgs } from "./changeset-args.js";
6
+ /**
7
+ * Get the bumps of the changed packages that have no changeset yet.
8
+ *
9
+ * @param release Changeset release of the repository.
10
+ * @param input Parsed options with the base ref.
11
+ * @param bump Bump of the changed packages.
12
+ * @return Bump of each changed package.
13
+ * @throws {ValidationError} If no changed package needs a changeset.
14
+ */
15
+ async function getChangedPackageBumps(release, input, bump) {
16
+ const status = await release.getStatus({
17
+ ...(input.since ? { since: input.since } : {}),
18
+ uncommitted: true,
19
+ });
20
+ if (!status.missingChangesets.length) {
21
+ const since = status.since ? ` since ${status.since}` : '';
22
+ throw new ValidationError(`No package changed${since} without a changeset. Pass --package <name> to choose the packages.`);
23
+ }
24
+ return Object.fromEntries(status.missingChangesets.map((name) => [name, bump]));
25
+ }
26
+ /**
27
+ * Ask for the changeset, preselecting the changed packages without one.
28
+ *
29
+ * @param release Changeset release of the repository.
30
+ * @param input Parsed options with the base ref and release note.
31
+ * @param terminal Terminal to ask in.
32
+ * @return Package bumps and release note.
33
+ */
34
+ async function askChangeset(release, input, terminal) {
35
+ const packages = await release.getPackages();
36
+ const status = await release.getStatus({
37
+ ...(input.since ? { since: input.since } : {}),
38
+ uncommitted: true,
39
+ });
40
+ const prompt = new ChangesetPrompt(terminal);
41
+ try {
42
+ return await prompt.ask({
43
+ packages,
44
+ changed: status.missingChangesets,
45
+ ...(status.since ? { since: status.since } : {}),
46
+ ...(input.summary ? { summary: input.summary } : {}),
47
+ });
48
+ }
49
+ finally {
50
+ prompt.close();
51
+ }
52
+ }
53
+ /**
54
+ * Get the package bumps and release note from the options, the changed
55
+ * packages or the prompts.
56
+ *
57
+ * @param release Changeset release of the repository.
58
+ * @param input Parsed options.
59
+ * @param terminal Terminal to ask in, when the CLI runs in one.
60
+ * @return Package bumps, empty for no release, and release note.
61
+ * @throws {ValidationError} Without packages outside a terminal.
62
+ */
63
+ async function getChangeset(release, input, terminal) {
64
+ const summary = input.summary ?? '';
65
+ let changeset;
66
+ if (input.empty) {
67
+ changeset = { packages: {}, summary };
68
+ }
69
+ else if (Object.keys(input.packages).length) {
70
+ changeset = { packages: input.packages, summary };
71
+ }
72
+ else if (input.bump) {
73
+ changeset = {
74
+ packages: await getChangedPackageBumps(release, input, input.bump),
75
+ summary,
76
+ };
77
+ }
78
+ else if (terminal) {
79
+ changeset = await askChangeset(release, input, terminal);
80
+ }
81
+ else {
82
+ throw new ValidationError('Pass matching repeated --package <name> and --bump <patch|minor|major> options, a single --bump for the changed packages, or --empty.');
83
+ }
84
+ return changeset;
85
+ }
3
86
  export default {
4
87
  name: 'changeset-add',
5
- usage: 'changeset-add (--empty | --package <name> --bump <bump> ...) [options]',
88
+ usage: 'changeset-add [--empty | --package <name> --bump <bump> ... | --bump <bump>] [options]',
6
89
  summary: 'Add a package release note for the next release PR.',
7
90
  help: `Options:
8
- --package <name> Package identifier (repeatable)
91
+ --package <name> Package identifier, or its path under the configured
92
+ root (repeatable)
9
93
  --bump <bump> patch, minor or major (repeatable, in package order)
10
94
  --summary <text> Customer-facing release note
11
95
  --empty Record that this change does not need a release
96
+ --since <ref> Compare changed files with this git ref (default:
97
+ origin/<baseBranch>)
12
98
  --name <name> Changeset filename (default: random identifier)
13
99
 
14
100
  Use --package and --bump pairs for package releases, or --empty for changes
15
- that should pass the status check without producing a release.`,
101
+ that should pass the status check without producing a release. A single
102
+ --bump without --package releases the packages that changed since the base
103
+ ref, including uncommitted files, and have no changeset yet. Without
104
+ --package and --bump in a terminal, the command asks for the packages, with
105
+ the changed ones preselected, their bumps and the release note.`,
16
106
  options: changesetAddArgs,
17
- async run({ values, cwd, configFile, logger }) {
107
+ async run({ values, cwd, configFile, logger, terminal }) {
18
108
  const input = parseChangesetAddArgs(values);
19
- const file = await addChangeset({
109
+ const release = new ChangesetRelease({
20
110
  cwd,
21
111
  ...(configFile ? { configFile } : {}),
22
- packages: input.packages,
23
- summary: input.summary,
112
+ });
113
+ const changeset = await getChangeset(release, input, terminal);
114
+ // Adding resolves the package paths and rejects a package passed twice.
115
+ const file = await release.add({
116
+ packages: changeset.packages,
117
+ summary: changeset.summary,
24
118
  ...(input.name ? { name: input.name } : {}),
25
119
  });
120
+ const names = resolvePackageNames(await release.getPackages(), Object.keys(changeset.packages));
121
+ const packages = Object.fromEntries(Object.values(changeset.packages).map((bump, index) => [
122
+ names[index],
123
+ bump,
124
+ ]));
26
125
  logger.info(`Created ${file}.`);
27
126
  return {
28
127
  exitCode: 0,
29
128
  result: {
30
129
  file,
31
- packages: input.packages,
130
+ packages,
32
131
  },
33
132
  };
34
133
  },
@@ -1,10 +1,14 @@
1
- import type { ChangesetPackages } from '../changesets.ts';
1
+ import type { ChangesetBump, ChangesetPackages } from '../changesets.ts';
2
2
  import type { ParsedValues } from './command.ts';
3
3
  /** Parsed input for `changeset-add`. */
4
4
  export interface ChangesetAddArgs {
5
+ /** Bump of each package passed with --package, by identifier or path. */
5
6
  packages: ChangesetPackages;
6
- summary: string;
7
+ /** Bump for the changed packages without a changeset, passed without --package. */
8
+ bump?: ChangesetBump;
9
+ summary?: string;
7
10
  empty: boolean;
11
+ since?: string;
8
12
  name?: string;
9
13
  }
10
14
  /** Parse-args options for `changeset-add`. */
@@ -23,16 +27,21 @@ export declare const changesetAddArgs: {
23
27
  readonly empty: {
24
28
  readonly type: "boolean";
25
29
  };
30
+ readonly since: {
31
+ readonly type: "string";
32
+ };
26
33
  readonly name: {
27
34
  readonly type: "string";
28
35
  };
29
36
  };
30
37
  /**
31
- * Parse and validate the `changeset-add` options.
38
+ * Parse and validate the `changeset-add` options. Without --package, a
39
+ * single --bump applies to the changed packages without a changeset, and
40
+ * without --bump either the command asks for the changeset in a terminal.
32
41
  *
33
42
  * @param values Parsed CLI options.
34
43
  * @return Normalized changeset input.
35
- * @throws {ValidationError} If required options are missing or conflicting.
44
+ * @throws {ValidationError} If options are conflicting or incomplete.
36
45
  */
37
46
  export declare function parseChangesetAddArgs(values: ParsedValues): ChangesetAddArgs;
38
47
  /** Parse-args options for `changeset-status`. */
@@ -68,6 +77,12 @@ export declare const changesetVersionArgs: {
68
77
  readonly 'no-dependents': {
69
78
  readonly type: "boolean";
70
79
  };
80
+ readonly prerelease: {
81
+ readonly type: "string";
82
+ };
83
+ readonly snapshot: {
84
+ readonly type: "string";
85
+ };
71
86
  };
72
87
  /** Parsed input for `changeset-version`. */
73
88
  export interface ChangesetVersionArgs {
@@ -76,6 +91,10 @@ export interface ChangesetVersionArgs {
76
91
  packages?: string[];
77
92
  /** Whether the selected sources release their configured dependents; defaults to true. */
78
93
  dependents: boolean;
94
+ /** Pre-release identifier with an optional number, like rc or rc.1. */
95
+ prerelease?: string;
96
+ /** Snapshot identifier, like pr-251. */
97
+ snapshot?: string;
79
98
  }
80
99
  /**
81
100
  * Validate and normalize a release date option.
@@ -90,7 +109,7 @@ export declare function parseChangesetDate(value: string | undefined): string |
90
109
  *
91
110
  * @param values Parsed CLI options.
92
111
  * @return Normalized versioning input.
93
- * @throws {ValidationError} If --date is invalid.
112
+ * @throws {ValidationError} If --date is invalid, or an option is empty or conflicting.
94
113
  */
95
114
  export declare function parseChangesetVersionArgs(values: ParsedValues): ChangesetVersionArgs;
96
115
  /** Parse-args options for `changeset-tag`. */