Add --all option for change command (#891)

This commit is contained in:
Elizabeth Craig 2023-07-12 18:06:18 -07:00 коммит произвёл GitHub
Родитель 8dd65ddd8f
Коммит 73d3558a8e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 131 добавлений и 49 удалений

Просмотреть файл

@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Add --all option for `change` command",
"packageName": "beachball",
"email": "elcraig@microsoft.com",
"dependentChangeType": "patch"
}

Просмотреть файл

@ -16,9 +16,43 @@ $ beachball change
### Options
See the [options page](./options).
Some [general options](./options) including `--branch` and `--scope` also apply for this command.
### Walkthrough
| Option | Alias | Default | Description |
| ------------- | ----- | -------------------- | --------------------------------------------------------------------------------- |
| `--all` | | false | Generate change files for all packages |
| `--message` | `-m` | (interactive prompt) | Description for all change files |
| `--no-commit` | | false | Stage the change files rather than committing |
| `--package` | | (changed packages) | Generate change files for these packages (option can be specified multiple times) |
| `--type` | | (interactive prompt) | Type for all the change files (must be valid for each package) |
### Examples
Basic interactive prompt (see [walkthrough](#prompt-walkthrough) for details):
```
beachball change
```
Skip the interactive prompt by specifying a message and type for all changed packages:
```
beachball change --type patch --message 'some message'
```
Generate change file for specific package(s), regardless of changes, and even if a change file already exists for the package in this branch. Each package must be specified with a separate `--package` option. (You can also use the `--message` and `--type` options here.)
```
beachball change --package foo --package bar
```
Generate change files for all packages, regardless of changes. This would most often be used for build config updates which only touch a shared config file, but actually impact the output of all packages.
```
beachball change --all --type patch --message 'update build output settings'
```
### Prompt walkthrough
If you have changes that are not committed yet (i.e. `git status` reports changes), then `beachball change` will warn you about these:
@ -31,28 +65,34 @@ There are uncommitted changes in your repository. Please commit these files firs
Make sure to commit _all_ changes before proceeding with the `change` command.
Now we'll commit the changes we made and run `beachball change` again:
After committing, run `beachball change`:
```
$ beachball change
Defaults to "origin/master"
Checking for changes against "origin/master"
Please describe the changes for: single
? Describe changes (type or choose one)
adding a new file
Validating options and change files...
Checking for changes against "origin/main"
Found changes in the following packages:
some-pkg
```
First, it will ask for a **description** of the change. You can enter any text, but `beachball` will also provide a list of recent commit messages to choose from.
For each package, the prompt will start by asking for a change **type**. This should be chosen based on [semantic versioning rules](https://semver.org/) because it determines how to update the package version. If the change doesn't affect the published package at all (e.g. you just updated some comments), choose `none`.
```
Please describe the changes for: some-pkg
? Change type - Use arrow-keys. Return to submit.
Patch - bug fixes; no backwards incompatible changes.
Minor - small feature; backwards compatible changes.
None - this change does not affect the published package in any way.
Major - major feature; breaking changes.
```
Next, it asks for a **description** of the change. You can type any text or choose from a list of recent commit messages.
> Tip: These descriptions will be collated into a changelog when the change is published by `beachball publish`, so think about how to describe your change in a way that's helpful and relevant for consumers of the package.
Next, the form will ask for a change **type**. This should be chosen based on [semantic versioning rules](https://semver.org/) because it determines how to update the package version. If the change doesn't affect the published package at all (e.g. you just updated some comments), choose `none`.
```bash
? Change type - Use arrow-keys. Return to submit.
Patch - bug fixes; no backwards incompatible changes.
Minor - small feature; backwards compatible changes.
None - this change does not affect the published package in any way.
Major - major feature; breaking changes.
```
Please describe the changes for: some-pkg
? Describe changes (type or choose one)
adding a new file
```

Просмотреть файл

@ -13,19 +13,14 @@ For the latest full list of supported options, see `CliOptions` [in this file](h
These apply to most CLI commands.
| Option | Alias | Default | Description |
| ---------- | ---------- | ----------------- | ------------------------- |
| `--branch` | `-b` | `'origin/master'` | target branch from origin |
| `--help` | `-?`, `-h` | | show help message |
| Option | Alias | Default | Description |
| ------------ | ----- | ---------------------------------------------------------------------------- | ------------------------- |
| `--branch` | `-b` | Detected default branch in default remote, falling back to `'origin/master'` | target branch from origin |
| `--no-fetch` | | | Disable fetching |
## Change options
## `change` options
These options are applicable to the `change` command.
| Option | Alias | Default | Description |
| ----------- | ----- | -------------------- | -------------------------------------------------------------- |
| `--message` | `-m` | (interactive prompt) | Description for all change files |
| `--type` | | (interactive prompt) | Type for all the change files (must be valid for each package) |
See the [`change` page](./change).
## Bumping and publishing options

Просмотреть файл

@ -192,4 +192,13 @@ describe('getChangedPackages', () => {
expect(changedPackagesB).toStrictEqual([]);
expect(changedPackagesRoot).toStrictEqual(['@workspace-a/foo']);
});
it('returns all packages with --all option', () => {
repositoryFactory = new RepositoryFactory('monorepo');
const repo = repositoryFactory.cloneRepository();
const options = { all: true, fetch: false, path: repo.rootPath, branch: defaultBranchName } as BeachballOptions;
const packageInfos = getPackageInfos(repo.rootPath);
expect(getChangedPackages(options, packageInfos).sort()).toStrictEqual(['a', 'b', 'bar', 'baz', 'foo']);
});
});

Просмотреть файл

@ -33,15 +33,51 @@ function getMatchingPackageInfo(
}
/**
* Gets all the changed package names, regardless of the change files
* Determines whether the package is included in the list of potentially-changed published packages,
* based on private flags and scopedPackages.
*/
function isPackageIncluded(
packageInfo: PackageInfo | undefined,
scopedPackages: string[]
): { isIncluded: boolean; reason: string } {
const reason = !packageInfo
? 'no corresponding package found'
: packageInfo.private
? `${packageInfo.name} is private`
: packageInfo.combinedOptions.shouldPublish === false
? `${packageInfo.name} has beachball.shouldPublish=false`
: !scopedPackages.includes(packageInfo.name)
? `${packageInfo.name} is out of scope`
: ''; // not ignored
return { isIncluded: !reason, reason };
}
/**
* Gets all the changed package names, regardless of the change files.
* If `options.all` is set, returns all the packages in scope, regardless of whether they've changed.
*/
function getAllChangedPackages(options: BeachballOptions, packageInfos: PackageInfos): string[] {
const { branch, path: cwd, verbose } = options;
const { branch, path: cwd, verbose, all } = options;
const verboseLog = (msg: string) => verbose && console.log(msg);
const logIgnored = (file: string, reason: string) => verboseLog(` - ~~${file}~~ (${reason})`);
const logIncluded = (file: string) => verboseLog(` - ${file}`);
const scopedPackages = getScopedPackages(options, packageInfos);
// If --all is set, return all the packages in scope rather than looking at which files changed
if (all) {
verboseLog('--all option was provided, so including all packages that are in scope (regardless of changes)');
return Object.values(packageInfos)
.filter(pkg => {
const { isIncluded, reason } = isPackageIncluded(pkg, scopedPackages);
verboseLog(isIncluded ? ` - ${pkg.name}` : ` - ~~${pkg.name}~~ (${reason.replace(`${pkg.name} `, '')})`);
return isIncluded;
})
.map(pkg => pkg.name);
}
const changes = [...(getChanges(branch, cwd) || []), ...(getStagedChanges(cwd) || [])];
verboseLog(`Found ${count(changes.length, 'changed file')} in branch "${branch}" (before filtering)`);
@ -66,7 +102,6 @@ function getAllChangedPackages(options: BeachballOptions, packageInfos: PackageI
// and whether that package is in scope and not private
const includedPackages = new Set<string>();
let fileCount = 0;
const scopedPackages = getScopedPackages(options, packageInfos);
const packageInfosByPath: { [packageAbsNormalizedPath: string]: PackageInfo } = {};
for (const info of Object.values(packageInfos)) {
packageInfosByPath[path.normalize(path.dirname(info.packageJsonPath))] = info;
@ -74,18 +109,10 @@ function getAllChangedPackages(options: BeachballOptions, packageInfos: PackageI
for (const moddedFile of nonIgnoredChanges) {
const packageInfo = getMatchingPackageInfo(moddedFile, cwd, packageInfosByPath);
const omitReason = !packageInfo
? 'no corresponding package found'
: packageInfo.private
? `${packageInfo.name} is private`
: packageInfo.combinedOptions.shouldPublish === false
? `${packageInfo.name} has beachball.shouldPublish=false`
: !scopedPackages.includes(packageInfo.name)
? `${packageInfo.name} is out of scope`
: ''; // not ignored
const { isIncluded, reason } = isPackageIncluded(packageInfo, scopedPackages);
if (omitReason) {
logIgnored(moddedFile, omitReason);
if (!isIncluded) {
logIgnored(moddedFile, reason);
} else {
includedPackages.add(packageInfo!.name);
fileCount++;
@ -101,7 +128,7 @@ function getAllChangedPackages(options: BeachballOptions, packageInfos: PackageI
}
/**
* Gets all the changed packages, accounting for change files
* Gets all the changed packages which do not already have a change file
*/
export function getChangedPackages(options: BeachballOptions, packageInfos: PackageInfos): string[] {
const { path: cwd, branch } = options;

Просмотреть файл

@ -7,8 +7,7 @@ import { getChangedPackages } from '../changefile/getChangedPackages';
import { getPackageGroups } from '../monorepo/getPackageGroups';
export async function change(options: BeachballOptions): Promise<void> {
const { branch, path: cwd } = options;
const { package: specificPackage } = options;
const { branch, path: cwd, package: specificPackage } = options;
const packageInfos = getPackageInfos(cwd);
const packageGroups = getPackageGroups(packageInfos, cwd, options.groups);

Просмотреть файл

@ -42,16 +42,19 @@ export async function listPackageVersionsByTag(
packageInfos: PackageInfo[],
tag: string | undefined,
options: NpmOptions
): Promise<{ [pkg: string]: string | undefined }> {
): Promise<{ [pkg: string]: string }> {
const limit = pLimit(NPM_CONCURRENCY);
const versions: { [pkg: string]: string | undefined } = {};
const versions: { [pkg: string]: string } = {};
await Promise.all(
packageInfos.map(pkg =>
limit(async () => {
const info = await getNpmPackageInfo(pkg.name, options);
const npmTag = tag || pkg.combinedOptions.tag || pkg.combinedOptions.defaultNpmTag;
versions[pkg.name] = (npmTag && info['dist-tags']?.[npmTag]) || undefined;
const version = npmTag && info['dist-tags']?.[npmTag];
if (version) {
versions[pkg.name] = version;
}
})
)
);

Просмотреть файл

@ -60,7 +60,9 @@ export function validate(
const packageInfos = getPackageInfos(options.path);
if (typeof options.package === 'string' && !packageInfos[options.package]) {
if (options.all && options.package) {
logValidationError('Cannot specify both "all" and "package" options');
} else if (typeof options.package === 'string' && !packageInfos[options.package]) {
logValidationError(`package "${options.package}" was not found`);
} else {
const invalidPackages = Array.isArray(options.package)