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
Version File Plugin #2107
Changes from 5 commits
5558326
136ec9d
7923405
7b134e0
1ed6139
3b025e6
da1d63b
6055c44
163c082
c645dc8
5f1618e
367ddcd
475950b
59292fb
ceba7a2
a9cac5d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Bazel Plugin | ||
|
||
|
||
|
||
## Installation | ||
|
||
This plugin is not included with the `auto` CLI installed via NPM. To install: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
] | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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") | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} | ||
} |
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 abazel-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?There was a problem hiding this comment.
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 theVERSION
file?Could we just do something like
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 existinggit tag
plugin.auto shipit
would be interesting though since it'd have to know thatbazel
command to callThere was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
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.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:There was a problem hiding this comment.
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.