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

Version File Plugin #2107

Merged
merged 16 commits into from May 20, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli/package.json
Expand Up @@ -52,6 +52,7 @@
"@auto-it/core": "link:../core",
"@auto-it/npm": "link:../../plugins/npm",
"@auto-it/released": "link:../../plugins/released",
"@auto-it/version-file": "link:../../plugins/version-file",
"await-to-js": "^3.0.0",
"chalk": "^4.0.0",
"command-line-application": "^0.10.1",
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/tsconfig.json
Expand Up @@ -17,6 +17,9 @@
},
{
"path": "../../plugins/released"
},
{
"path": "../../plugins/version-file"
}
]
}
39 changes: 39 additions & 0 deletions plugins/version-file/README.md
@@ -0,0 +1,39 @@
# Version File Plugin

For managing versions in a repository that maintains the version primarily in a flat file.
Agnostic to the primary language of the repository.
Optional input for a release script to call during the publish/canary/next hooks.

## Installation

This plugin is included with the `auto` CLI so you do not have to install it. To install if you are using the `auto` API directly:

```bash
npm i --save-dev @auto-it/version-file
# or
yarn add -D @auto-it/version-file
```

## Options

- versionFile (optional, default="VERSION"): Path to where the version is stored in the repository. It should be a file containing just the semver.
- releaseScript: (optional, default=None): Path to script that runs the publish actions in your repository. If not supplied nothing will be called. If supplied will be called during the `publish`,`canary` and `next` hooks. For the `publish` hook the first parameter passed to the script will be `release` to indicate that a regular release is being called. For `canary` and `next` hooks the first parameter will be `snapshot` to indicate a prerelease version.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that you've called it out, should probably keep naming consistent here. If this is only called in the publish hook, then it'd be more appropriately named publishScript or something like that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will make it more explicit. Changes are pushed and rebuilding.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KetanReddy looks like you did update the naming in the actual plugin. Can you update the docs to reflect those changes?


## Usage

### With default options
```json
{
"plugins": [
"version-file"
// other plugins
]
}
```
### With optional arguments
```json
{
"plugins": [
"version-file", {"versionFile": "./tools/Version.txt", "releaseScript":"./tools/publish.sh"}
]
}
305 changes: 305 additions & 0 deletions plugins/version-file/__tests__/version-file.test.ts
@@ -0,0 +1,305 @@
import Auto, { SEMVER } from '@auto-it/core';
import mockFs from "mock-fs";
import fs from "fs";
import BazelPlugin from '../src';
import { makeHooks } from '@auto-it/core/dist/utils/make-hooks';
import { dummyLog } from "@auto-it/core/dist/utils/logger";

// Mocks
const execPromise = jest.fn();
jest.mock(
"../../../packages/core/dist/utils/exec-promise",
() => (...args: any[]) => execPromise(...args)
);
jest.mock("../../../packages/core/dist/utils/get-current-branch", () => ({
getCurrentBranch: () => "main",
}));

beforeEach(() => {
execPromise.mockClear();
});
afterEach(() => {
mockFs.restore();
})

describe('Version File Read Operations', () => {
test("It should return the value in the default file", async () => {
mockFs({
"VERSION": `1.0.0`,
});
const plugin = new BazelPlugin({});
const hooks = makeHooks();

plugin.apply({
hooks,
remote: "origin",
baseBranch: "main",
logger: dummyLog(),
} as Auto);

expect(await hooks.getPreviousVersion.promise()).toBe("1.0.0");
});

test("It should return the value in the specified file", async () => {
mockFs({
"VERSIONFILE": `1.0.0`,
});
const plugin = new BazelPlugin({versionFile: "VERSIONFILE"});
const hooks = makeHooks();

plugin.apply({
hooks,
remote: "origin",
baseBranch: "main",
logger: dummyLog(),
} as Auto);

expect(await hooks.getPreviousVersion.promise()).toBe("1.0.0");
});
});

describe("Version File Write Operations", () => {
test("It should version the file properly for major releases", async () => {
mockFs({
"VERSION": `1.0.0`,
});
const plugin = new BazelPlugin({});
const hooks = makeHooks();

plugin.apply({
hooks,
remote: "origin",
baseBranch: "main",
logger: dummyLog(),
} as Auto);

await hooks.version.promise({bump: SEMVER.major})
expect(fs.readFileSync("VERSION", "utf-8")).toStrictEqual("2.0.0")
// check that the proper git operations were performed
expect(execPromise).toHaveBeenNthCalledWith(1, "git", ["commit", "-am", "'\"Bump version to: %s [skip ci]\"'"]);
expect(execPromise).toHaveBeenNthCalledWith(2, "git", ["tag", "2.0.0"]);
});

test("It should version the file properly for minor releases", async () => {
mockFs({
"VERSION": `1.0.0`,
});
const plugin = new BazelPlugin({});
const hooks = makeHooks();

plugin.apply({
hooks,
remote: "origin",
baseBranch: "main",
logger: dummyLog(),
} as Auto);

await hooks.version.promise({bump: SEMVER.minor})
expect(fs.readFileSync("VERSION", "utf-8")).toStrictEqual("1.1.0");
// check that the proper git operations were performed
expect(execPromise).toHaveBeenNthCalledWith(1, "git", ["commit", "-am", "'\"Bump version to: %s [skip ci]\"'"]);
expect(execPromise).toHaveBeenNthCalledWith(2, "git", ["tag", "1.1.0"]);
});

test("It should version the file properly for patch releases", async () => {
mockFs({
"VERSION": `1.0.0`,
});
const plugin = new BazelPlugin({});
const hooks = makeHooks();

plugin.apply({
hooks,
remote: "origin",
baseBranch: "main",
logger: dummyLog(),
} as Auto);

await hooks.version.promise({bump: SEMVER.patch})
expect(fs.readFileSync("VERSION", "utf-8")).toStrictEqual("1.0.1");
// check that the proper git operations were performed
expect(execPromise).toHaveBeenNthCalledWith(1, "git", ["commit", "-am", "'\"Bump version to: %s [skip ci]\"'"]);
expect(execPromise).toHaveBeenNthCalledWith(2, "git", ["tag", "1.0.1"]);
});
})

describe("Test Release Types", () => {
test("Full release with no release script", async () => {
mockFs({
"VERSION": `1.0.0`,
});
const plugin = new BazelPlugin({});
const hooks = makeHooks();

plugin.apply({
hooks,
remote: "origin",
baseBranch: "main",
logger: dummyLog(),
} as Auto);

await hooks.publish.promise({bump: SEMVER.major})

// check release script was not called but check changes would be pushed
expect(execPromise).toHaveBeenNthCalledWith(1, "git", ["push", "origin", "main", "--tags"]);
});

test("Full release with release script", async () => {
mockFs({
"VERSION": `1.0.0`,
});
const plugin = new BazelPlugin({publishScript:"./tools/release.sh"});
const hooks = makeHooks();

plugin.apply({
hooks,
remote: "origin",
baseBranch: "main",
logger: dummyLog(),
} as Auto);

await hooks.publish.promise({bump: SEMVER.major})

// check release script was called
expect(execPromise).toHaveBeenNthCalledWith(1, "./tools/release.sh", ["release"]);

// check changes would be pushed
expect(execPromise).toHaveBeenNthCalledWith(2, "git", ["push", "origin", "main", "--tags"]);
});

test("Canary release with no release script", async () => {
mockFs({
"VERSION": `1.0.0`,
});
const plugin = new BazelPlugin({});
const hooks = makeHooks();

plugin.apply(({
hooks,
remote: "origin",
baseBranch: "main",
logger: dummyLog(),
getCurrentVersion: () => "1.0.0",
git: {
getLatestRelease: () => "1.0.0",
getLatestTagInBranch: () => Promise.resolve("1.0.0"),
},
} as unknown) as Auto);

await hooks.canary.promise({bump: SEMVER.minor, canaryIdentifier: "canary.368.1"})

// check release script was not called and local changes were reverted
expect(execPromise).toHaveBeenNthCalledWith(1, "git", ["reset", "--hard", "HEAD"]);

// Check the right version was written
expect(fs.readFileSync("VERSION", "utf-8")).toStrictEqual("1.1.0-canary.368.1")
});

test("Canary release with release script", async () => {
mockFs({
"VERSION": `1.0.0`,
});
const plugin = new BazelPlugin({publishScript:"./tools/release.sh"});
const hooks = makeHooks();

plugin.apply(({
hooks,
remote: "origin",
baseBranch: "main",
logger: dummyLog(),
getCurrentVersion: () => "1.0.0",
git: {
getLatestRelease: () => "1.0.0",
getLatestTagInBranch: () => Promise.resolve("1.0.0"),
},
} as unknown) as Auto);

await hooks.canary.promise({bump: SEMVER.minor, canaryIdentifier: "canary.368.1"})

// check release script was called
expect(execPromise).toHaveBeenNthCalledWith(1, "./tools/release.sh", ["snapshot"]);

// check local changes were reverted
expect(execPromise).toHaveBeenNthCalledWith(2, "git", ["reset", "--hard", "HEAD"]);

// Check the right version was written
expect(fs.readFileSync("VERSION", "utf-8")).toStrictEqual("1.1.0-canary.368.1")
});

test("Next release with no release script", async () => {

const prefixRelease: (a: string) => string = (version: string) => {
return `v${version}`;
};

mockFs({
"VERSION": `1.0.0`,
});
const plugin = new BazelPlugin({});
const hooks = makeHooks();

plugin.apply(({
hooks,
config: { prereleaseBranches: ["next"] },
remote: "origin",
baseBranch: "main",
logger: dummyLog(),
prefixRelease,
getCurrentVersion: () => "1.0.0",
git: {
getLastTagNotInBaseBranch: async () => undefined,
getLatestRelease: () => "1.0.0",
getLatestTagInBranch: () => Promise.resolve("1.0.0"),
},
} as unknown) as Auto);

await hooks.next.promise(["1.0.0"], {bump: SEMVER.major, fullReleaseNotes:"", releaseNotes:"", commits:[]})

// check release script was not called but git ops were performed
expect(execPromise).toHaveBeenNthCalledWith(1, "git", ["tag", "v2.0.0-next.0"]);
expect(execPromise).toHaveBeenNthCalledWith(2, "git", ["push", "origin", "main", "--tags"]);

// Check the right version was written
expect(fs.readFileSync("VERSION", "utf-8")).toStrictEqual("v2.0.0-next.0")
});

test("Next release with release script", async () => {

const prefixRelease: (a: string) => string = (version: string) => {
return `v${version}`;
};

mockFs({
"VERSION": `1.0.0`,
});
const plugin = new BazelPlugin({publishScript:"./tools/release.sh"});
const hooks = makeHooks();

plugin.apply(({
hooks,
config: { prereleaseBranches: ["next"] },
remote: "origin",
baseBranch: "main",
logger: dummyLog(),
prefixRelease,
getCurrentVersion: () => "1.0.0",
git: {
getLastTagNotInBaseBranch: async () => undefined,
getLatestRelease: () => "1.0.0",
getLatestTagInBranch: () => Promise.resolve("1.0.0"),
},
} as unknown) as Auto);

await hooks.next.promise(["1.0.0"], {bump: SEMVER.major, fullReleaseNotes:"", releaseNotes:"", commits:[]})

// check release script was called
expect(execPromise).toHaveBeenNthCalledWith(1, "./tools/release.sh", ["snapshot"]);

// Check git ops
expect(execPromise).toHaveBeenNthCalledWith(2, "git", ["tag", "v2.0.0-next.0"]);
expect(execPromise).toHaveBeenNthCalledWith(3, "git", ["push", "origin", "main", "--tags"]);

// Check the right version was written
expect(fs.readFileSync("VERSION", "utf-8")).toStrictEqual("v2.0.0-next.0")
});
});