Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Partial Release using --only to bump version of select packages #80

Merged
merged 13 commits into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# Change versionKind to one of: breaking, feature, fix, internal
changeKind: fix
packages:
- "@chronus/chronus"
---

Partial Release using `--only` to bump version of select packages
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,27 @@ chronus goal is to provide changelog management. It was heavily inspired by [cha
- Design to be able to plug in different monorepo(changesets also does that), source control system as a plugin system(only git added but left room for more).
- Change can be defined in custom categories that allow more meaningful grouping of changes in the changelog. See [Change kinds doc](./docs/change-kinds.md)

## Requirements
## Documentation

- Node 16+
- pnpm 7.5.2+
- [CLI](cli.md)

Chronus functionalities:

- [Configure change kinds](change-kinds.md) _Use a different set of change kinds from `major`, `minor`, `patch`_

## Develop

- [CLI](cli.md)

Chronus functionalities:

- [Configure change kinds](change-kinds.md) _Use a different set of change kinds from `major`, `minor`, `patch`_

### Requirements

- Node LTS
- `pnpm`

[Click here to release current changes](https://github.com/timotheeguerin/chronus/pull/new/publish/auto-release)

This project uses [pnpm workspaces](https://pnpm.io/workspaces) to manage multiple packages.
Expand Down
33 changes: 33 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Chronus CLI

## `chronus add`

Add a new change description

## `chronus verify`

Verify that the packages with changes from the `baseBranch` have all been described.

## `chronus status`

This command will show the current status of the changes in the workspace. This is basically a summary of what `chronus version` will do if run.

This command takes the same options as [`chronus version`](#chronus-version).

## `chronus version`

Apply the change description and bump version of packages. By default this will respect the version policies configured(see [version policies](version-policies.md)).

### Options

#### `--ignore-policies`

This allows to ignore the [version policies](version-policies.md) and bump the packages independently. This can be useful in the `lockStep` policies when doing a hotfix.

#### `--only`

Only bumps the packages specified. This can be useful if wanting to only release certain packages. This command will extract the change descriptions for the specified packages and bump the version of those packages. If a change applied to a package is not specified in the `--only` option it will be ignored. If a change is specified in both it will be applied and the packages included in the `only` array will be removed from the change description file.

```bash
chronus version --only @my-scope/my-package1 --only @my-scope/my-package2
```
7 changes: 7 additions & 0 deletions docs/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Chronus Documentation

- [CLI](cli.md)

Chronus functionalities:

- [Configure change kinds](change-kinds.md) _Use a different set of change kinds from `major`, `minor`, `patch`_
5 changes: 5 additions & 0 deletions docs/version-policies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Chronus Version Policies

By default each package is versioned independentely. This means if you describe a change for package `a` it will only bump the version of `a` and any package depending on `a` that would need a release to include the new version of `a`.

TODO
32 changes: 29 additions & 3 deletions packages/chronus/src/apply-release-plan/apply-release-plan.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { deleteChangeDescription } from "../change/write.js";
import type { ChangeDescription } from "../change/types.js";
import { deleteChangeDescription, writeChangeDescription } from "../change/write.js";
import { updateChangelog } from "../changelog/generate.js";
import type { ReleaseAction, ReleasePlan } from "../release-plan/types.js";
import type { ReleaseAction, ReleasePlan, ReleasePlanChangeApplication } from "../release-plan/types.js";
import type { ChronusHost } from "../utils/host.js";
import type { ChronusWorkspace } from "../workspace/types.js";
import { updatePackageJson } from "./update-package-json.js";
Expand All @@ -20,6 +21,31 @@ export async function applyReleasePlan(
}

for (const change of releasePlan.changes) {
deleteChangeDescription(host, workspace, change);
await cleanChangeApplication(host, workspace, change);
}
}

async function cleanChangeApplication(
host: ChronusHost,
workspace: ChronusWorkspace,
application: ReleasePlanChangeApplication,
) {
switch (application.usage) {
case "used":
return await deleteChangeDescription(host, workspace, application.change);
case "partial":
return await patchChangeDescription(host, workspace, application.change, application.packages);
}
}
async function patchChangeDescription(
host: ChronusHost,
workspace: ChronusWorkspace,
change: ChangeDescription,
exclude: string[],
) {
const newChange = {
...change,
packages: change.packages.filter((x) => !exclude.includes(x)),
};
writeChangeDescription(host, workspace, newChange);
}
36 changes: 24 additions & 12 deletions packages/chronus/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,35 @@ async function main() {
"version",
"Apply change changeset and bump the versions",
(cmd) =>
cmd.option("ignore-policies", {
type: "boolean",
description: "Ignore versioning policies and bump each package independently",
default: false,
}),
(args) => applyChangesets(process.cwd(), { ignorePolicies: args.ignorePolicies }),
cmd
.option("ignore-policies", {
type: "boolean",
description: "Ignore versioning policies and bump each package independently",
default: false,
})
.option("only", {
type: "string",
array: true,
description: "Only bump the specified package(s)",
}),
(args) => applyChangesets(process.cwd(), { ignorePolicies: args.ignorePolicies, only: args.only }),
)
.command(
"status",
"Display the status of changes. What will happen during the next release",
(cmd) =>
cmd.option("ignore-policies", {
type: "boolean",
description: "Ignore versioning policies and bump each package independently",
default: false,
}),
(args) => showStatus(process.cwd(), { ignorePolicies: args.ignorePolicies }),
cmd
.option("ignore-policies", {
type: "boolean",
description: "Ignore versioning policies and bump each package independently",
default: false,
})
.option("only", {
type: "string",
array: true,
description: "Only bump the specified package(s)",
}),
(args) => showStatus(process.cwd(), { ignorePolicies: args.ignorePolicies, only: args.only }),
)
.parse();
}
Expand Down
4 changes: 3 additions & 1 deletion packages/chronus/src/cli/commands/apply-changesets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import { loadChronusWorkspace } from "../../workspace/load.js";
import type { ChronusWorkspace } from "../../workspace/types.js";

export interface ApplyChangesetsOptions {
ignorePolicies?: boolean;
readonly ignorePolicies?: boolean;
readonly only?: string[];
}

export async function applyChangesets(cwd: string, options?: ApplyChangesetsOptions): Promise<void> {
const host = NodeChronusHost;
const workspace = await loadChronusWorkspace(host, cwd);
Expand Down
1 change: 1 addition & 0 deletions packages/chronus/src/cli/commands/show-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { resolveReleasePlan, type ApplyChangesetsOptions } from "./apply-changes

export interface ShowStatusOptions {
readonly ignorePolicies?: boolean;
readonly only?: string[];
}

function log(...args: any[]) {
Expand Down
61 changes: 58 additions & 3 deletions packages/chronus/src/release-plan/assemble-release-plan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ import { assembleReleasePlan } from "./assemble-release-plan.js";
describe("Assemble Release Plan", () => {
let idCounter = 0;
const changeKinds = addNameToChangeKinds(defaultChangeKinds);
function mkChange(pkg: string, type: VersionType): ChangeDescription {
function mkChange(pkg: string | string[], type: VersionType): ChangeDescription {
const id = String(idCounter++);
return { id, content: `Changeset ${id}`, changeKind: changeKinds[type], packages: [pkg] };
return {
id,
content: `Changeset ${id}`,
changeKind: changeKinds[type],
packages: Array.isArray(pkg) ? pkg : [pkg],
};
}

function mkWorkspace(packages: Package[]): Workspace {
Expand All @@ -33,7 +38,7 @@ describe("Assemble Release Plan", () => {
const baseConfig: ChronusResolvedConfig = { workspaceRoot: "proj", baseBranch: "main", changeKinds };

describe("bumps package independently", () => {
it("only packages with changeset ", () => {
it("only packages with changeset", () => {
const plan = assembleReleasePlan([mkChange("pkg-a", "minor")], createChronusWorkspace(workspace, baseConfig));
expect(plan.actions).toHaveLength(1);
expect(plan.actions[0]).toMatchObject({ packageName: "pkg-a", oldVersion: "1.0.0", newVersion: "1.1.0" });
Expand Down Expand Up @@ -162,4 +167,54 @@ describe("Assemble Release Plan", () => {
expect(plan.actions[0]).toMatchObject({ packageName: "pkg-a", oldVersion: "1.0.0", newVersion: "1.1.0" });
});
});

describe("partial release plan using only option", () => {
it("ignore other packages with changes", () => {
const workspace: Workspace = mkWorkspace([mkPkg("pkg-a", {}), mkPkg("pkg-b", {})]);
const plan = assembleReleasePlan(
[mkChange("pkg-a", "minor"), mkChange("pkg-b", "minor")],
createChronusWorkspace(workspace, baseConfig),
{ only: ["pkg-a"] },
);
expect(plan.actions).toHaveLength(1);
expect(plan.actions[0]).toMatchObject({ packageName: "pkg-a", oldVersion: "1.0.0", newVersion: "1.1.0" });
});

it("ignore packages that would need to be bumped as dependent", () => {
const workspace: Workspace = mkWorkspace([
mkPkg("pkg-a", {}),
mkPkg("pkg-b", { dependencies: { "pkg-a": "1.0.0" } }),
]);
const plan = assembleReleasePlan([mkChange("pkg-a", "minor")], createChronusWorkspace(workspace, baseConfig), {
only: ["pkg-a"],
});
expect(plan.actions).toHaveLength(1);
expect(plan.actions[0]).toMatchObject({ packageName: "pkg-a", oldVersion: "1.0.0", newVersion: "1.1.0" });
});

it("report changes as fully used if bumping all packages tagged in it", () => {
const workspace: Workspace = mkWorkspace([mkPkg("pkg-a", {}), mkPkg("pkg-b", {}), mkPkg("pkg-c", {})]);
const plan = assembleReleasePlan(
[mkChange(["pkg-a", "pkg-b"], "minor")],
createChronusWorkspace(workspace, baseConfig),
{
only: ["pkg-a", "pkg-b"],
},
);
expect(plan.changes).toHaveLength(1);
expect(plan.changes[0]).toMatchObject({ usage: "used", packages: ["pkg-a", "pkg-b"] });
});
it("report changes as partially used if only bumping some packages tagged in it", () => {
const workspace: Workspace = mkWorkspace([mkPkg("pkg-a", {}), mkPkg("pkg-b", {})]);
const plan = assembleReleasePlan(
[mkChange(["pkg-a", "pkg-b"], "minor")],
createChronusWorkspace(workspace, baseConfig),
{
only: ["pkg-a"],
},
);
expect(plan.changes).toHaveLength(1);
expect(plan.changes[0]).toMatchObject({ usage: "partial", packages: ["pkg-a"] });
});
});
});
Loading
Loading