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

add sbt plugin #1962

Merged
merged 31 commits into from May 8, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7fabc67
generate sbt plugin
laughedelic Apr 22, 2021
3572002
add code from git-tag, implement publish/canary hooks
laughedelic Apr 25, 2021
ca9252a
add publish logs to the canary details
laughedelic Apr 25, 2021
136aa7b
blank lines
laughedelic Apr 25, 2021
28fd6d6
add semver dependency
laughedelic Apr 25, 2021
437b4d3
refactor
laughedelic Apr 25, 2021
1f55712
add SNAPSHOT suffix to the canary version
laughedelic Apr 25, 2021
730b776
use strip-ansi
laughedelic Apr 25, 2021
db009e1
cut off v prefix
laughedelic Apr 25, 2021
5e71703
clean output log
laughedelic Apr 25, 2021
baa64ec
unused import
laughedelic Apr 25, 2021
c820bfd
add manageVersion config option
laughedelic Apr 25, 2021
7c6925e
docs
laughedelic Apr 25, 2021
84ca44d
linting
laughedelic Apr 25, 2021
5f91ad3
note about sbt version
laughedelic Apr 25, 2021
5f65920
remove unused test
laughedelic Apr 25, 2021
6b1ae33
don't aggregate version
laughedelic Apr 26, 2021
20bd60a
trim version output
laughedelic Apr 26, 2021
bef0fe9
downgrade strip-ansi
laughedelic Apr 26, 2021
d600aae
add publishCommand option
laughedelic Apr 27, 2021
8c2dda9
switch sbt-specific logs to verbose logger
laughedelic Apr 27, 2021
f6cfefc
adjust sbtClient interface
laughedelic Apr 27, 2021
6de4587
always set version on release
laughedelic Apr 27, 2021
552297e
rename manageVersion to setCanaryVersion
laughedelic Apr 27, 2021
266fdb6
pull sbt helpers out for testing
laughedelic Apr 27, 2021
ec195d3
first test for cleaning sbt client output
laughedelic Apr 27, 2021
788db29
tests for sbtGetVersion
laughedelic Apr 27, 2021
eba3fab
moar tests!
laughedelic Apr 27, 2021
47627ce
unused import
laughedelic Apr 27, 2021
bd18523
mention new plugin in the docs
laughedelic Apr 29, 2021
7ef81a8
add link to sbt's readme
laughedelic Apr 29, 2021
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
75 changes: 75 additions & 0 deletions plugins/sbt/README.md
@@ -0,0 +1,75 @@
# sbt plugin

Publish Scala projects with sbt

> :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

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

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

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

With this option Auto will override the version in sbt during the 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.
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"
}
}
198 changes: 198 additions & 0 deletions plugins/sbt/src/index.ts
@@ -0,0 +1,198 @@
import {
Auto,
execPromise,
getCurrentBranch,
IPlugin,
validatePluginConfiguration,
} from "@auto-it/core";
import { inc, ReleaseType } from "semver";
import * as t from "io-ts";
import stripAnsi from "strip-ansi";

const pluginOptions = t.partial({
manageVersion: t.boolean,
});

export type ISbtPluginOptions = t.TypeOf<typeof pluginOptions>;

/** Publish Scala projects with sbt */
export default class SbtPlugin implements IPlugin {
/** The name of the plugin */
name = "sbt";

/** The options of the plugin */
readonly options: ISbtPluginOptions;

/** Initialize the plugin with it's options */
constructor(options: ISbtPluginOptions) {
this.options = options;
}

/** Tap into auto plugin points. */
apply(auto: Auto) {
// exact copy-paste from the git-tag plugin
/** Get the latest tag in the repo, if none then the first commit */
async function getTag() {
try {
return await auto.git!.getLatestTagInBranch();
} catch (error) {
return auto.prefixRelease("0.0.0");
}
}

/** Calls sbt in the client and returns cleaned up logs */
async function sbtClient(...args: string[]) {
const output = await execPromise("sbt", ["--client", ...args]);
const cleanOutput = stripAnsi(output).replace(/(.*\n)*^>.*$/m, "").trim();
return cleanOutput;
}

/** Read version from sbt */
async function sbtGetVersion() {
// in multi-module projects, we want to get only ThisBuild/version
await sbtClient("set version/aggregate := false");
const output = await sbtClient("print version");
const version = output.split("\n").shift()?.trim();
if (!version) {
throw new Error(`Failed to read version from sbt: ${output}`);
}

auto.logger.log.info(`Got version from sbt: ${version}`);
return version;
}

/** Set version in sbt to the given value */
async function sbtSetVersion(version: string) {
auto.logger.log.info(`Set version in sbt to "${version}"`);
laughedelic marked this conversation as resolved.
Show resolved Hide resolved
return sbtClient(`set every version := \\"${version}\\"`);
}

/** Run sbt publish */
async function sbtPublish() {
auto.logger.log.info("Run sbt publish");
const publishLog = await sbtClient("publish");
auto.logger.log.info("Output:\n" + publishLog);
return publishLog;
}

/** Construct canary version using Auto-provided suffix */
async function getCanaryVersion(canaryIdentifier: string) {
const lastTag = await getTag();
const lastVersion = lastTag.replace(/^v/, "");
return `${lastVersion}${canaryIdentifier}-SNAPSHOT`;
}

auto.hooks.validateConfig.tapPromise(this.name, async (name, options) => {
// If it's a string thats valid config
if (name === this.name && typeof options !== "string") {
return validatePluginConfiguration(this.name, pluginOptions, options);
}
});

// exact copy-paste from the git-tag plugin
auto.hooks.getPreviousVersion.tapPromise(this.name, async () => {
if (!auto.git) {
throw new Error(
"Can't calculate previous version without Git initialized!",
);
}

return getTag();
});

// exact copy-paste from the git-tag plugin
auto.hooks.version.tapPromise(
this.name,
async ({ bump, dryRun, quiet }) => {
if (!auto.git) {
return;
}

const lastTag = await getTag();
const newTag = inc(lastTag, bump as ReleaseType);

if (dryRun && newTag) {
if (quiet) {
console.log(newTag);
} else {
auto.logger.log.info(`Would have published: ${newTag}`);
}

return;
}

if (!newTag) {
auto.logger.log.info("No release found, doing nothing");
return;
}

const prefixedTag = auto.prefixRelease(newTag);

auto.logger.log.info(`Tagging new tag: ${lastTag} => ${prefixedTag}`);
await execPromise("git", [
"tag",
prefixedTag,
"-m",
`"Update version to ${prefixedTag}"`,
]);

if (this.options.manageVersion) {
await sbtSetVersion(newTag);
hipstersmoothie marked this conversation as resolved.
Show resolved Hide resolved
}
},
);

auto.hooks.publish.tapPromise(this.name, async () => {
await sbtPublish();

auto.logger.log.info("Pushing new tag to GitHub");
await execPromise("git", [
"push",
"--follow-tags",
"--set-upstream",
auto.remote,
getCurrentBranch() || auto.baseBranch,
]);
});

auto.hooks.canary.tapPromise(
this.name,
async ({ canaryIdentifier, dryRun, quiet }) => {
if (!auto.git) {
return;
}

const canaryVersion = this.options.manageVersion
? await getCanaryVersion(canaryIdentifier)
: await sbtGetVersion();
auto.logger.log.info(`Canary version: ${canaryVersion}`);

if (dryRun) {
if (quiet) {
console.log(canaryVersion);
} else {
auto.logger.log.info(`Would have published: ${canaryVersion}`);
}

return;
}

if (this.options.manageVersion) {
await sbtSetVersion(canaryVersion);
}

const publishLogs = await sbtPublish();

auto.logger.verbose.info("Successfully published canary version");
return {
newVersion: canaryVersion,
details: [
"```",
publishLogs,
"```",
].join("\n"),
};
},
);
}
}
16 changes: 16 additions & 0 deletions plugins/sbt/tsconfig.json
@@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*", "../../typings/**/*"],

"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"composite": true
},

"references": [
{
"path": "../../packages/core"
}
]
}
5 changes: 4 additions & 1 deletion tsconfig.dev.json
Expand Up @@ -88,6 +88,9 @@
},
{
"path": "plugins/s3"
},
{
"path": "plugins/sbt"
}
]
}
}
11 changes: 6 additions & 5 deletions yarn.lock
Expand Up @@ -69,10 +69,10 @@
integrity sha512-TYiuOxy5Pf9ORn94X/ujl7PY9opIh+l6NzRAV8EBLpIv3IC9gmEoev4wmmyP7Q33J0/nGjqxAaZcq/n2SZrYaQ==

"@auto-it/bot-list@link:packages/bot-list":
version "10.23.0"
version "10.25.1"

"@auto-it/core@link:packages/core":
version "10.23.0"
version "10.25.1"
dependencies:
"@auto-it/bot-list" "link:packages/bot-list"
"@endemolshinegroup/cosmiconfig-typescript-loader" "^3.0.2"
Expand Down Expand Up @@ -102,6 +102,7 @@
parse-author "^2.0.0"
parse-github-url "1.0.2"
pretty-ms "^7.0.0"
requireg "^0.2.2"
semver "^7.0.0"
signale "^1.4.0"
tapable "^2.0.0-beta.2"
Expand All @@ -114,7 +115,7 @@
url-join "^4.0.0"

"@auto-it/npm@link:plugins/npm":
version "10.23.0"
version "10.25.1"
dependencies:
"@auto-it/core" "link:packages/core"
"@auto-it/package-json-utils" "link:packages/package-json-utils"
Expand All @@ -132,13 +133,13 @@
user-home "^2.0.0"

"@auto-it/package-json-utils@link:packages/package-json-utils":
version "10.23.0"
version "10.25.1"
dependencies:
parse-author "^2.0.0"
parse-github-url "1.0.2"

"@auto-it/released@link:plugins/released":
version "10.23.0"
version "10.25.1"
dependencies:
"@auto-it/bot-list" "link:packages/bot-list"
"@auto-it/core" "link:packages/core"
Expand Down