Skip to content

Commit

Permalink
feat: bump mix project version
Browse files Browse the repository at this point in the history
  • Loading branch information
fahchen committed Apr 21, 2024
1 parent 0e5bb41 commit 5220376
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 0 deletions.
5 changes: 5 additions & 0 deletions denoified-actions/deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,10 @@
"test": "deno test -A --allow-none --shuffle --parallel",
"check:types": "deno check **/*.ts",
"check": "deno fmt --check && deno lint && deno task check:types && deno task test"
},
"imports": {
"@david/dax": "jsr:@david/dax@^0.40.0",
"@std/semver": "jsr:@std/semver@^0.223.0",
"octokit": "npm:octokit@^3.2.0"
}
}
46 changes: 46 additions & 0 deletions denoified-actions/elixir/bump_version/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Bump mix project version

on:
workflow_call:
inputs:
workflow-actor:
required: true
type: string
repository:
required: true
type: string
release-kind:
description: 'Kind of version bump'
type: choice
options:
- patch
- minor
- major
required: true
secrets:
token:
required: true

jobs:
bump-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
repository: 'Byzanteam/jet-actions'
path: '../denoified-actions'
sparse-checkout: |
denoified-actions/
- uses: denoland/setup-deno@v1
with:
deno-version: v1.x

- name: Create PR
env:
GH_TOKEN: ${{ secrets.token }}
GH_WORKFLOW_ACTOR: ${{ inputs.workflow-actor }}
GH_REPOSITORY: ${{ inputs.repository }}
RELEASE_KIND: ${{ inputs.release-kind }}
run: |
../denoified-actions/denoified-actions/elixir/bump_version/main.ts
76 changes: 76 additions & 0 deletions denoified-actions/elixir/bump_version/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env -S deno run --allow-all
// Bumps the version of a mix project and opens a PR
// Usage: deno run --allow-all bump_version.ts --patch
// Environment variables:
// - GH_WORKFLOW_ACTOR: the GitHub username of the actor
// - GH_TOKEN: the GitHub token
// - GH_REPOSITORY: the GitHub repository in the format "owner/repo"

import { default as $, Path } from "@david/dax";

import { MixProject } from "../src/mix_project.ts";
import { Repo } from "../../fundamental/repo.ts";
import {
createOctoKit,
getEnvVarOrThrow,
getGitHubRepository,
} from "../../fundamental/utils.ts";

const actor = getEnvVarOrThrow("GH_WORKFLOW_ACTOR");
const octoKit = createOctoKit();
const rootPath = new Path(Deno.cwd());
const repo = new Repo(rootPath);
const mixProject = new MixProject(repo);

// increment the mix project version
const releaseKind = getEnvVarOrThrow("RELEASE_KIND");
if (
releaseKind !== "patch" && releaseKind !== "minor" && releaseKind !== "major"
) {
throw new Error("Must specify --patch, --minor, or --major");
}
await mixProject.increment(releaseKind);

// setup git config
await repo.setGitConfig(actor);

// update the lock file
const newProjectVersion = (await mixProject.getVersion()).toString();
const originalBranch = await repo.gitCurrentBranch();
const releaseBranch = `release/${newProjectVersion}`;

// Create and push branch
$.logStep(`Creating branch ${releaseBranch}...`);
await repo.gitBranch(releaseBranch);
await repo.gitAdd();
await repo.gitCommit(
`chore: bump ${repo.name} to ${newProjectVersion}`,
);
$.logStep("Pushing branch...");
await repo.gitPush("-u", "origin", "HEAD");

// Open PR
$.logStep("Opening PR...");
const openedPr = await octoKit.request("POST /repos/{owner}/{repo}/pulls", {
...getGitHubRepository(),
base: originalBranch,
head: releaseBranch,
draft: true,
title: newProjectVersion,
body: getPRBody(),
});
$.log(`Opened PR at ${openedPr.data.url}`);

function getPRBody() {
return `Bumped ${repo.name} version\n\n` +
`Please ensure:\n` +
`- [ ] Target branch is correct (\`main\`)\n` +
`- [ ] \`${repo.name}\` version is bumped correctly\n` +
`To make edits to this PR:\n` +
"```shell\n" +
`git fetch origin ${releaseBranch} && git checkout -b ${releaseBranch} origin/${releaseBranch}\n` +
"```\n" +
`\ncc @${actor}`;
}

Deno.exit(0);
45 changes: 45 additions & 0 deletions denoified-actions/elixir/src/mix_project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { increment, parse, ReleaseType, SemVer } from "@std/semver";
import { default as $, Path } from "@david/dax";

import { Repo } from "../../fundamental/repo.ts";

const VERSION_REGEX = /^version = "([0-9]+\.[0-9]+\.[0-9]+)"/gm;

export class MixProject {
constructor(readonly repo: Repo) {}

get mixExsPath(): Path {
const repoRootDir = this.repo.rootDir;
return repoRootDir.join("mix.exs");
}

async getVersion(): Promise<SemVer> {
const text = await this.mixExsPath.readText();

const version = VERSION_REGEX.exec(text)?.[1];

if (version == null) {
throw new Error(`Could not find version in ${this.mixExsPath}`);
}

return parse(version);
}

async increment(part: ReleaseType) {
const currentVersion = await this.getVersion();
const newVersion = increment(currentVersion, part).toString();
return this.setVersion(newVersion);
}

async setVersion(version: string) {
$.logStep(`Setting ${this.repo.name} to ${version}...`);

const text = await this.mixExsPath.readText();
const newText = text.replace(
VERSION_REGEX,
`version = "${version}"`,
);

await this.mixExsPath.writeText(newText);
}
}
52 changes: 52 additions & 0 deletions denoified-actions/fundamental/repo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { CommandBuilder, Path } from "@david/dax";

export class Repo {
constructor(readonly rootDir: Path) {}

get name() {
return this.rootDir.basename;
}

async setGitConfig(actor: string) {
await this.command([
"git",
"config",
"user.email",
`${actor}@users.noreply.github.com`,
]);

await this.command([
"git",
"config",
"user.name",
actor,
]);
}

gitCurrentBranch() {
return this.command("git rev-parse --abbrev-ref HEAD")
.text();
}

async gitBranch(name: string) {
await this.command(["git", "checkout", "-b", name]);
}

async gitAdd() {
await this.command(["git", "add", "."]);
}

async gitCommit(message: string) {
await this.command(["git", "commit", "-m", message]);
}

async gitPush(...additionalArgs: string[]) {
await this.command(["git", "push", ...additionalArgs]);
}

command(command: string | string[]) {
return new CommandBuilder()
.command(command)
.cwd(this.rootDir);
}
}
31 changes: 31 additions & 0 deletions denoified-actions/fundamental/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Octokit } from "octokit";

export function createOctoKit() {
return new Octokit({
auth: getGitHubToken(),
});
}

export function getGitHubRepository() {
const repoEnvVar = getEnvVarOrThrow("GH_REPOSITORY");
const [owner, repo] = repoEnvVar.split("/");
return {
owner,
repo,
};
}

function getGitHubToken() {
return getEnvVarOrThrow("GH_TOKEN");
}

export function getEnvVarOrThrow(name: string) {
const value = Deno.env.get(name);
if (value == null) {
throw new Error(
`Could not find environment variable ${name}. ` +
`Ensure you are running in a GitHub action.`,
);
}
return value;
}
Empty file removed denoified-actions/mod.ts
Empty file.

0 comments on commit 5220376

Please sign in to comment.