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 5 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
24 changes: 24 additions & 0 deletions plugins/bazel/README.md
@@ -0,0 +1,24 @@
# Bazel Plugin
Copy link
Collaborator

Choose a reason for hiding this comment

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

Looking at the code, I'm wondering if this just makes sense to be a version-file-plugin instead of a Bazel one, since there isn't anything Bazel specific in here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fair point. Is there an easy way to rename a plugin and its references?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agreed, a Bazel plugin will be interesting though. If that is the purpose of this PR, maybe we should consider how Auto could stamp package.json that are output to a bazel-bin folder? At the end of the day, Bazel shouldn't look at a version file and have that impact the build, Auto should take the outputs of Bazel and release them appropriately, thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

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

With auto would we even need the VERSION file?

Could we just do something like

#!/usr/bin/env bash
echo STABLE_GIT_COMMIT $(git rev-parse HEAD)
echo STABLE_AUTO_VERSION $(auto version)

as the workspace-status-script? Then all of the build-in mechanisms for stamping a release would carry over and not be tooling dependent.

We'd still need a source of truth (and maybe that's where the VERSION file comes into play), but could easily also be done using the existing git tag plugin.

auto shipit would be interesting though since it'd have to know that bazel command to call

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah that could work, I think we'll just need to make sure auto creates a GitHub release with that still so that the next SemVer calculation will have appropriate context.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Bazel shouldn't look at a version file and have that impact the build

Just to note, the version file approach was heavily influenced by how the publishing Bazel libraries that I've used require the version to be stored:

https://github.com/vaticle/bazel-distribution

So this idea is somewhat interesting. The version file would be needed by the publish targets (if using those rules, which we most likely will be since I already patched those rules to work for our Maven releases), but that wouldn't cause a complete rebuild. Additionally, the idea with this is that whatever publish script you include here would stamp the Bazel build while actually publishing if the version is needed in the actual source.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's a fair callout, assuming that distribution via version file is the expectation within Bazel (admittedly my understanding of non JS/Go publishes is limited by comparison). I have seen recommendations from some rule maintainers to use scripts to correctly query for releasable assets and then substitute the version in via workspace status as @adierkens mentioned. In that case, each rule would just need to inject STABLE_AUTO_VERSION appropriately for the version.

Copy link
Collaborator

Choose a reason for hiding this comment

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

assuming that distribution via version file is the expectation within Bazel

Not really the expectation within Bazel, just the distribution rules that I linked above, which include rules for NPM distribution as well. Even if not using these rules, it might make sense to be able to standardize the repo such that it can be easy to version without Auto, but still work with rules of this style. In addition, it is very easy to stamp with a VERSION file:

#!/usr/bin/env bash
echo STABLE_VERSION $(cat VERSION)
echo STABLE_GIT_COMMIT $(git rev-parse HEAD)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This does touch on an issue I did come across while writing this PR. If in a bazel repo you have a script that does the build/stamp/release you don't know what version you need to stamp the built packages with until after auto figures it out as part of shipit. So when auto runs the release script as part of whatever kind of release it is (regular, canary, next) the release script needs to restamp the packages then run release. By having a version file and having auto change it, bazel automatically knows it just needs to rerun the stamping step before running its release targets. This flow would prevent bazel from having to rebuild everything from scratch.




## Installation

This plugin is not included with the `auto` CLI installed via NPM. To install:
Copy link
Collaborator

Choose a reason for hiding this comment

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

It might be worth bundling this into the binary version so that projects that don't have any NPM deps can still consume it easily.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Gotcha! I think that was included/set by default when the plugin was scaffolded. I will look into changing that.


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

## Usage

```json
{
"plugins": [
"bazel"
// other plugins
]
}
```
217 changes: 217 additions & 0 deletions plugins/bazel/__tests__/bazel.test.ts
@@ -0,0 +1,217 @@
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 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.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", 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 called
expect(execPromise).toHaveBeenNthCalledWith(1, "./tools/release.sh", ["snapshot"]);

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

test("Next Release", 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 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")
});
});
45 changes: 45 additions & 0 deletions plugins/bazel/package.json
@@ -0,0 +1,45 @@
{
"name": "@auto-it/bazel",
"version": "10.32.2",
"main": "dist/index.js",
"description": "",
"license": "MIT",
"author": {
"name": "Andrew Lisowski",
"email": "lisowski54@gmail.com"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/intuit/auto"
},
"files": [
"dist"
],
"keywords": [
"automation",
"semantic",
"release",
"github",
"labels",
"automated",
"continuos integration",
"changelog"
],
"scripts": {
"build": "tsc -b",
"start": "npm run build -- -w",
"lint": "eslint src --ext .ts",
"test": "jest --maxWorkers=2 --config ../../package.json"
},
"dependencies": {
"@auto-it/core": "link:../../packages/core",
"fp-ts": "^2.5.3",
"io-ts": "^2.1.2",
"tslib": "1.10.0",
"semver":"^7.0.0"
}
}