Skip to content

Commit

Permalink
Add --all option for change command (#891)
Browse files Browse the repository at this point in the history
  • Loading branch information
ecraig12345 authored Jul 13, 2023
1 parent 8dd65dd commit 73d3558
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 50 deletions.
7 changes: 7 additions & 0 deletions change/beachball-9bdf704a-61ed-4b77-8dfa-26bc583976fa.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Add --all option for `change` command",
"packageName": "beachball",
"email": "elcraig@microsoft.com",
"dependentChangeType": "patch"
}
74 changes: 57 additions & 17 deletions docs/cli/change.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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`.

> 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.
```
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, 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`.
Next, it asks for a **description** of the change. You can type any text or choose from a list of recent commit messages.

```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.
> 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.
```
Please describe the changes for: some-pkg
? Describe changes (type or choose one) ›
adding a new file
```
17 changes: 6 additions & 11 deletions docs/cli/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 9 additions & 0 deletions src/__e2e__/getChangedPackages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
});
});
59 changes: 43 additions & 16 deletions src/changefile/getChangedPackages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)`);

Expand All @@ -66,26 +102,17 @@ 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;
}
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

if (omitReason) {
logIgnored(moddedFile, omitReason);
const { isIncluded, reason } = isPackageIncluded(packageInfo, scopedPackages);

if (!isIncluded) {
logIgnored(moddedFile, reason);
} else {
includedPackages.add(packageInfo!.name);
fileCount++;
Expand All @@ -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;
Expand Down
3 changes: 1 addition & 2 deletions src/commands/change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 6 additions & 3 deletions src/packageManager/listPackageVersions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
})
)
);
Expand Down
4 changes: 3 additions & 1 deletion src/validation/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 73d3558

Please sign in to comment.