Skip to content

Commit

Permalink
Cleanup for use (#24)
Browse files Browse the repository at this point in the history
* Tweak

* Progress

* Create cleanup-for-use-2024-0-29-16-47-4.md

* basic tests
  • Loading branch information
timotheeguerin authored Jan 29, 2024
1 parent 86246ee commit 4f62a90
Show file tree
Hide file tree
Showing 12 changed files with 561 additions and 55 deletions.
5 changes: 5 additions & 0 deletions .changeset/cleanup-for-use-2024-0-29-16-47-4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chronus/chronus": minor
---

Add ability to specify base branch
1 change: 1 addition & 0 deletions .chronus.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
baseBranch: main
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
chronus goal is to provide changelog management. It is currently built depending on [changesets(and forked many of logic)](https://github.com/changesets/changesets) which provide a great way to manage changelogs but chronus does things slightly differently:

- Checking for changes are done by comparing the remote instead of the local base branch. This solve the issue where you might have merged the base branch into your feature branch but not into the local main branch and changesets would report lots of unrelated change.
- Design to be able to plug in different monorepo, source control system as a plugin system.
- Verify checks every packages have changelog not just that we have a single changelog file.
- Different versioning policies than changesets. LockedStep provide a way to say we will only ever bump the version by the single specified step at everyrelease. This is a way to diverge from true semver where you might release major changes in minor version.(Particularly useful in pre-release `0.x` where minor versions can be used to release breaking changes)
- 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).

## Requirements

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
},
"homepage": "https://github.com/timotheeguerin/chronus#readme",
"devDependencies": {
"@changesets/cli": "^2.27.1",
"cspell": "^8.3.2",
"eslint": "^8.56.0",
"eslint-plugin-import": "^2.29.1",
Expand Down
4 changes: 3 additions & 1 deletion packages/chronus/src/change/find.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import readChangeset from "@changesets/parse";
import type { ChronusConfig } from "../config/types.js";
import type { GitRepository } from "../source-control/git.js";
import type { ChronusHost } from "../utils/host.js";
import type { Package, Workspace } from "../workspace-manager/types.js";
Expand Down Expand Up @@ -28,8 +29,9 @@ export async function findChangeStatus(
host: ChronusHost,
sourceControl: GitRepository,
workspace: Workspace,
config: ChronusConfig,
): Promise<ChangeStatus> {
const filesChanged = await sourceControl.listChangedFilesFromBase();
const filesChanged = await sourceControl.listChangedFilesFromBase(config.baseBranch);
const untrackedOrModifiedFiles = await sourceControl.listUntrackedOrModifiedFiles();
const stagedFiles = await sourceControl.listUntrackedOrModifiedFiles();
const publicPackages = workspace.packages.filter((x) => !x.manifest.private);
Expand Down
4 changes: 3 additions & 1 deletion packages/chronus/src/change/get-workspace-status.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { resolveConfig } from "../config/parse.js";
import { createGitSourceControl } from "../source-control/git.js";
import { NodechronusHost } from "../utils/node-host.js";
import { createPnpmWorkspaceManager } from "../workspace-manager/pnpm.js";
import { findChangeStatus } from "./find.js";

export async function getWorkspaceStatus(root: string) {
const host = NodechronusHost;
const config = await resolveConfig(host, root);
const pnpm = createPnpmWorkspaceManager(host);
const workspace = await pnpm.load(root);
const sourceControl = createGitSourceControl(workspace.path);
const status = await findChangeStatus(host, sourceControl, workspace);
const status = await findChangeStatus(host, sourceControl, workspace, config);
return status;
}
4 changes: 3 additions & 1 deletion packages/chronus/src/cli/commands/add-changeset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import pc from "picocolors";
import prompts from "prompts";
import type { ChangeStatus } from "../../change/find.js";
import { findChangeStatus } from "../../change/index.js";
import { resolveConfig } from "../../config/parse.js";
import { createGitSourceControl } from "../../source-control/git.js";
import { NodechronusHost } from "../../utils/node-host.js";
import { createPnpmWorkspaceManager } from "../../workspace-manager/pnpm.js";
Expand All @@ -14,10 +15,11 @@ function log(...args: any[]) {
}
export async function addChangeset(cwd: string): Promise<void> {
const host = NodechronusHost;
const config = await resolveConfig(host, cwd);
const pnpm = createPnpmWorkspaceManager(host);
const workspace = await pnpm.load(cwd);
const sourceControl = createGitSourceControl(workspace.path);
const status = await findChangeStatus(host, sourceControl, workspace);
const status = await findChangeStatus(host, sourceControl, workspace, config);
if (status.committed.packageChanged.length === 0) {
log("No package changed. Exiting.\n");
return;
Expand Down
2 changes: 2 additions & 0 deletions packages/chronus/src/config/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ const versionPolicySchema = z.union([
packages: z.array(z.string()),
}),
]);

const schema = z.object({
baseBranch: z.string(),
versionPolicies: z.array(versionPolicySchema).optional(),
});

Expand Down
1 change: 1 addition & 0 deletions packages/chronus/src/config/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { VersionType } from "@changesets/types";

export interface ChronusConfig {
baseBranch: string;
versionPolicies?: VersionPolicy[];
}

Expand Down
99 changes: 99 additions & 0 deletions packages/chronus/src/release-plan/assemble-release-plan.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import type { NewChangeset, VersionType } from "@changesets/types";
import { describe, expect, it } from "vitest";
import type { ChronusConfig } from "../config/types.js";
import type { Workspace } from "../workspace-manager/types.js";
import { assembleReleasePlan } from "./assemble-release-plan.js";

describe("Assemble Release Plan", () => {
let idCounter = 0;
function makeChangeset(pkg: string, type: VersionType): NewChangeset {
const id = String(idCounter++);
return { id, summary: `Changeset ${id}`, releases: [{ name: pkg, type }] };
}
const workspace: Workspace = {
path: "/",
packages: [
{ name: "pkg-a", manifest: {}, relativePath: "packages/pkg-a", version: "1.0.0" },
{ name: "pkg-b", manifest: {}, relativePath: "packages/pkg-b", version: "1.0.0" },
{ name: "pkg-c", manifest: {}, relativePath: "packages/pkg-c", version: "1.0.0" },
],
};

const baseConfig: ChronusConfig = { baseBranch: "main" };

describe("bumps package independently", () => {
it("only packages with changeset ", () => {
const plan = assembleReleasePlan([makeChangeset("pkg-a", "minor")], workspace, baseConfig);
expect(plan.actions).toHaveLength(1);
expect(plan.actions[0]).toMatchObject({ packageName: "pkg-a", oldVersion: "1.0.0", newVersion: "1.1.0" });
});

describe("picks the heighest version type", () => {
it("major", () => {
const plan = assembleReleasePlan(
[makeChangeset("pkg-a", "patch"), makeChangeset("pkg-a", "minor"), makeChangeset("pkg-a", "major")],
workspace,
baseConfig,
);
expect(plan.actions).toHaveLength(1);
expect(plan.actions[0]).toMatchObject({ packageName: "pkg-a", oldVersion: "1.0.0", newVersion: "2.0.0" });
});
it("minor", () => {
const plan = assembleReleasePlan(
[makeChangeset("pkg-a", "patch"), makeChangeset("pkg-a", "minor")],
workspace,
baseConfig,
);
expect(plan.actions).toHaveLength(1);
expect(plan.actions[0]).toMatchObject({ packageName: "pkg-a", oldVersion: "1.0.0", newVersion: "1.1.0" });
});
});

it("each package with changeset gets bumped accordingly", () => {
const plan = assembleReleasePlan(
[makeChangeset("pkg-a", "minor"), makeChangeset("pkg-b", "patch")],
workspace,
baseConfig,
);
expect(plan.actions).toHaveLength(2);
expect(plan.actions[0]).toMatchObject({ packageName: "pkg-a", oldVersion: "1.0.0", newVersion: "1.1.0" });
expect(plan.actions[1]).toMatchObject({ packageName: "pkg-b", oldVersion: "1.0.0", newVersion: "1.0.1" });
});
});

describe("lockStepVersioning", () => {
const lockStepConfig: ChronusConfig = {
...baseConfig,
versionPolicies: [{ name: "lockStep", type: "lockstep", step: "minor", packages: ["pkg-a", "pkg-b"] }],
};

it("bumps all packages in the lockstep if one changed", () => {
const plan = assembleReleasePlan([makeChangeset("pkg-a", "minor")], workspace, lockStepConfig);
expect(plan.actions).toHaveLength(2);
expect(plan.actions[0]).toMatchObject({ packageName: "pkg-a", oldVersion: "1.0.0", newVersion: "1.1.0" });
expect(plan.actions[1]).toMatchObject({ packageName: "pkg-b", oldVersion: "1.0.0", newVersion: "1.1.0" });
});

it("bumps all packages in the lockstep of the step regardless of what changesets says (higher)", () => {
const plan = assembleReleasePlan(
[makeChangeset("pkg-a", "minor"), makeChangeset("pkg-a", "major")],
workspace,
lockStepConfig,
);
expect(plan.actions).toHaveLength(2);
expect(plan.actions[0]).toMatchObject({ packageName: "pkg-a", oldVersion: "1.0.0", newVersion: "1.1.0" });
expect(plan.actions[1]).toMatchObject({ packageName: "pkg-b", oldVersion: "1.0.0", newVersion: "1.1.0" });
});

it("bumps all packages in the lockstep of the step regardless of what changesets says (lower)", () => {
const plan = assembleReleasePlan(
[makeChangeset("pkg-a", "patch"), makeChangeset("pkg-a", "patch")],
workspace,
lockStepConfig,
);
expect(plan.actions).toHaveLength(2);
expect(plan.actions[0]).toMatchObject({ packageName: "pkg-a", oldVersion: "1.0.0", newVersion: "1.1.0" });
expect(plan.actions[1]).toMatchObject({ packageName: "pkg-b", oldVersion: "1.0.0", newVersion: "1.1.0" });
});
});
});
8 changes: 4 additions & 4 deletions packages/chronus/src/source-control/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export interface GitRepository {
* Returns files changed from the base remote branch.
* @param dir Repository directory
*/
listChangedFilesFromBase(): Promise<string[]>;
listChangedFilesFromBase(baseBranch: string): Promise<string[]>;
}

export class GitError extends Error {
Expand Down Expand Up @@ -157,12 +157,12 @@ export function createGitSourceControl(repositoryPath: string): GitRepository {
return stdout.toString().trim();
}

async function listChangedFilesFromBase() {
async function listChangedFilesFromBase(baseBranch: string) {
let remoteBase: string;
try {
remoteBase = await findRemoteForBranch("main");
remoteBase = await findRemoteForBranch(baseBranch);
} catch {
remoteBase = "refs/remotes/origin/main";
remoteBase = `refs/remotes/origin/${baseBranch}`;
}
return await listChangedFilesSince(remoteBase);
}
Expand Down
Loading

0 comments on commit 4f62a90

Please sign in to comment.