Skip to content

Commit

Permalink
Merge pull request #1962 from laughedelic/feat/plugin/sbt
Browse files Browse the repository at this point in the history
add sbt plugin
  • Loading branch information
hipstersmoothie committed May 8, 2021
2 parents a7cfda2 + 7ef81a8 commit df0218c
Show file tree
Hide file tree
Showing 9 changed files with 579 additions and 6 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -54,6 +54,7 @@ Auto has an extensive plugin system and wide variety of official plugins. Make a
- [gradle](./plugins/gradle) - Publish code with gradle
- [maven](./plugins/maven) - Publish code with maven
- [npm](./plugins/npm) - Publish code to npm (`default` when installed through `npm`)
- [sbt](./plugins/sbt) - Publish Scala projects with [sbt](https://www.scala-sbt.org)
- [vscode](./plugins/vscode) - Publish code to the VSCode extension marketplace

**Extra Functionality:**
Expand Down
1 change: 1 addition & 0 deletions docs/pages/docs/_sidebar.mdx
Expand Up @@ -55,6 +55,7 @@ Package Manager Plugins
- [Gradle](./generated/gradle)
- [Maven](./generated/maven)
- [NPM](./generated/npm)
- [sbt](./generated/sbt)
- [VSCode](./generated/vscode)

Functionality Plugins
Expand Down
92 changes: 92 additions & 0 deletions plugins/sbt/README.md
@@ -0,0 +1,92 @@
# sbt plugin

Publish Scala projects with [sbt](https://www.scala-sbt.org)

> :warning: only sbt 1.4+ is supported at the moment because this plugin uses `sbt --client` functionality
## Installation

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

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

## Usage

```json
{
"plugins": [
"sbt"
]
}
```

It is strongly recommended to use an sbt plugin to manage the version. There are a few options, but the most reliable and well maintained is [sbt-dynver](https://github.com/dwijnand/sbt-dynver). To enable it in your project add this line to `project/plugins.sbt`:

```scala
addSbtPlugin("com.dwijnand" % "sbt-dynver" % "x.y.z")
```

and then, depending on the publishing repository (e.g. if you are publishing to Sonatype Nexus), you might want to add

```scala
ThisBuild / dynverSeparator := "-"
ThisBuild / dynverSonatypeSnapshots := true
```

to your `build.sbt`.

With this setup canary versions will look like this: `{last_tag}-{number_of_commits}-{commit_sha}-SNAPSHOT`, for example:

```
0.1.2-5-fcdf268c-SNAPSHOT
```

## Options

### `setCanaryVersion: boolean` (default: `false`)

If you don't want to use an sbt plugin for version management, you can let Auto manage the canary version:

```json
{
"plugins": [
[
"sbt",
{
"setCanaryVersion": true
}
]
]
}
```

With this option Auto will override the version in sbt during canary release process.

Canary versions will look like this: `{last_tag}-canary.{pr_number}.{build_number}-SNAPSHOT`, for example:

```
0.1.2-canary.47.5fa1736-SNAPSHOT
```

Here build number is the git commit SHA.

### `publishCommand: string` (default: `publish`)

If you need to run some custom publishing command, you can change this option. For example, to cross-publish a library:

```json
{
"plugins": [
[
"sbt",
{
"publishCommand": "+publish"
}
]
]
}
```
213 changes: 213 additions & 0 deletions plugins/sbt/__tests__/sbt.test.ts
@@ -0,0 +1,213 @@
import * as Auto from "@auto-it/core";
import { dummyLog } from "@auto-it/core/dist/utils/logger";
import { makeHooks } from "@auto-it/core/dist/utils/make-hooks";
import SbtPlugin, { ISbtPluginOptions, sbtClient, sbtGetVersion } from "../src";

const exec = jest.fn();

jest.mock(
"../../../packages/core/dist/utils/exec-promise",
() => (...args: any[]) => exec(...args),
);

const rawOutput =
`[info] entering *experimental* thin client - BEEP WHIRR
[info] terminate the server with \`shutdown\`
> print version
1.2.3
[success] Total time: 2 s, completed Apr 27, 2021 3:39:23 AM
`;

const cleanedOutput = `1.2.3
[success] Total time: 2 s, completed Apr 27, 2021 3:39:23 AM`;

const rawAggregationOutput =
`[info] entering *experimental* thin client - BEEP WHIRR
[info] terminate the server with \`shutdown\`
> set version/aggregate := false
[info] Defining version / aggregate
[info] The new value will be used by no settings or tasks.
[info] Reapplying settings...
[info] set current project to auto-release-test-scala (in build file:/Users/user/project/)
[success] Total time: 2 s, completed Apr 27, 2021 3:52:04 AM
v`;

describe("sbt plugin", () => {
let hooks: Auto.IAutoHooks;
const prefixRelease: (a: string) => string = jest.fn(
(version) => `v${version}`,
);
const options: ISbtPluginOptions = {};
const logger = dummyLog();

const setup = (options: ISbtPluginOptions) => {
const plugin = new SbtPlugin(options);
hooks = makeHooks();
plugin.apply(
({
hooks,
logger,
remote: "stubRemote",
prefixRelease,
git: {
getLastTagNotInBaseBranch: async () => undefined,
getLatestRelease: async () => "0.0.1",
},
getCurrentVersion: async () => "0.0.1",
} as unknown) as Auto.Auto,
);
};

beforeEach(() => {
exec.mockClear();
setup(options);
});

describe("sbt client", () => {
test("should clean output", async () => {
exec.mockReturnValueOnce(rawOutput);
const output = await sbtClient("");
expect(output).toBe(cleanedOutput);
});

test("should parse version value", async () => {
exec
.mockReturnValueOnce(rawAggregationOutput)
.mockReturnValueOnce(rawOutput);
const output = await sbtGetVersion();
expect(output).toBe("1.2.3");
});

test("should error if it can't parse version value", async () => {
exec
.mockReturnValueOnce(rawAggregationOutput)
.mockReturnValueOnce("");
await expect(sbtGetVersion()).rejects.toThrowError(
`Failed to read version from sbt: `,
);
});
});

describe("version hook", () => {
test("should set version in sbt", async () => {
exec.mockReturnValue("");

await hooks.version.promise({
bump: Auto.SEMVER.minor,
});
expect(exec).toHaveBeenCalledTimes(2);
expect(exec).lastCalledWith("sbt", [
"--client",
'set every version := \\"0.1.0\\"',
]);
});
});

describe("publish hook", () => {
test("should call sbt publish", async () => {
exec.mockReturnValue("");

await hooks.publish.promise({
bump: Auto.SEMVER.minor,
});

expect(exec).toHaveBeenCalledWith("sbt", [
"--client",
"publish",
]);
});

test("should call sbt publish with custom command", async () => {
setup({
publishCommand: "+publishLocal",
});
exec.mockReturnValue("");

await hooks.publish.promise({
bump: Auto.SEMVER.minor,
});

expect(exec).toHaveBeenCalledWith("sbt", [
"--client",
"+publishLocal",
]);
});
});

describe("canary hook", () => {
test("should only read version from sbt on dry run", async () => {
exec
.mockReturnValueOnce(rawAggregationOutput)
.mockReturnValueOnce(rawOutput);

await hooks.canary.promise({
bump: Auto.SEMVER.minor,
canaryIdentifier: "-canary.42.1",
dryRun: true,
});

expect(exec).toHaveBeenCalledTimes(2); // 2 calls in sbtGetVersion
});

test("should return version from sbt as canary", async () => {
exec.mockReturnValue(rawOutput);

const result = await hooks.canary.promise({
bump: Auto.SEMVER.minor,
canaryIdentifier: "-canary.42.1",
});

expect(exec).not.toHaveBeenCalledWith("sbt", [
"--client",
'set every version := \\"0.1.0\\"',
]);

expect(result).toMatchObject({
newVersion: "1.2.3",
details: [
"```",
cleanedOutput,
"```",
].join("\n"),
});
});

test("should construct canary version when configured", async () => {
setup({
setCanaryVersion: true,
});
exec.mockReturnValue(rawOutput);

const result = await hooks.canary.promise({
bump: Auto.SEMVER.minor,
canaryIdentifier: "-canary.42.1",
});

const newVersion = "0.0.0-canary.42.1-SNAPSHOT";

expect(exec).toHaveBeenCalledWith("sbt", [
"--client",
`set every version := \\"${newVersion}\\"`,
]);

expect(result).toMatchObject({ newVersion });
});

test("should call sbt publish with custom command", async () => {
setup({
publishCommand: "+publishLocal",
});
exec.mockReturnValue(rawOutput);

await hooks.canary.promise({
bump: Auto.SEMVER.minor,
canaryIdentifier: "-canary.42.1",
});

expect(exec).toHaveBeenCalledWith("sbt", [
"--client",
"+publishLocal",
]);
});
});
});
48 changes: 48 additions & 0 deletions plugins/sbt/package.json
@@ -0,0 +1,48 @@
{
"name": "@auto-it/sbt",
"version": "10.25.1",
"main": "dist/index.js",
"description": "Publish Scala projects with sbt",
"license": "MIT",
"author": {
"name": "Alexey Alekhin",
"email": "laughedelic@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",
"scala",
"sbt"
],
"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",
"semver": "^7.0.0",
"strip-ansi": "^6.0.0",
"tslib": "1.10.0"
}
}

0 comments on commit df0218c

Please sign in to comment.