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

feat(gem): add canary hook #1916

Merged
merged 2 commits into from Apr 13, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
72 changes: 72 additions & 0 deletions plugins/gem/__tests__/gem.test.ts
Expand Up @@ -2,6 +2,7 @@ import glob from "fast-glob";
import { makeHooks } from "@auto-it/core/dist/utils/make-hooks";
import { dummyLog } from "@auto-it/core/dist/utils/logger";
import { execSync } from "child_process";
import { when } from 'jest-when';

import * as utils from "../src/utils";
import Gem from "../src";
Expand Down Expand Up @@ -265,6 +266,10 @@ describe("Gem Plugin", () => {
});

describe("publish", () => {
beforeEach(() => {
execSpy.mockClear();
});

test("uses bundler + rake as default publishing method", async () => {
globSpy.mockReturnValueOnce(["test.gemspec"]);
readFile.mockReturnValue(endent`
Expand Down Expand Up @@ -303,4 +308,71 @@ describe("Gem Plugin", () => {
});
});
});

describe("canary", () => {
beforeEach(() => {
execSpy.mockClear();
});
test("uses (bundler + rake + gem push) as default publishing method", async () => {
globSpy.mockReturnValue(["test.gemspec"]);
readFile.mockReturnValue(endent`
Gem::Specification.new do |spec|
spec.name = "test"
spec.version = "0.1.0"
end
`);

const canaryIdentifier = '-canary-x'
when(execSpy).calledWith("bundle", ["exec", "rake", "build"])
.mockReturnValue(`test 0.2.0.pre${canaryIdentifier} built to pkg/test-0.2.0.pre${canaryIdentifier.replace('-','.')}.gem.`);

const plugin = new Gem();
const hooks = makeHooks();

plugin.apply({ hooks, logger } as any);

const result = await hooks.canary
.promise({ bump: SEMVER.minor, canaryIdentifier: "-canary-x", dryRun: false, quiet: false });

expect(result.newVersion).toBe(`0.2.0.pre${canaryIdentifier.replace('-','.')}`)
expect(result.details).toBe(endent`
:sparkles: Test out this PR via:

\`\`\`bash
gem test, 0.2.0.pre${canaryIdentifier.replace('-','.')}
or
gem install test -v 0.2.0.pre${canaryIdentifier.replace('-','.')}
\`\`\`
`)

expect(execSpy).toHaveBeenCalledWith("bundle", ["exec", "rake", "build"]);
expect(execSpy).toHaveBeenCalledWith("gem", ["push", "pkg/test-0.2.0.pre.canary-x.gem"]);
});

test("dry-run not release", async () => {
globSpy.mockReturnValue(["test.gemspec"]);
readFile.mockReturnValue(endent`
Gem::Specification.new do |spec|
spec.name = "test"
spec.version = "0.1.0"
end
`);

const canaryIdentifier = '-canary-x'
when(execSpy).calledWith("bundle", ["exec", "rake", "build"])
.mockReturnValue(`test 0.2.0.pre${canaryIdentifier} built to pkg/test-0.2.0.pre${canaryIdentifier.replace('-','.')}.gem.`);

const plugin = new Gem();
const hooks = makeHooks();

plugin.apply({ hooks, logger } as any);

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

expect(execSpy).not.toHaveBeenCalledWith("bundle", ["exec", "rake", "build"]);
expect(execSpy).not.toHaveBeenCalledWith("gem", ["push", "pkg/test-0.2.0.pre.canary-x.gem"]);
});

});
});
3 changes: 3 additions & 0 deletions plugins/gem/package.json
Expand Up @@ -45,5 +45,8 @@
"parse-github-url": "1.0.2",
"semver": "^7.0.0",
"tslib": "2.1.0"
},
"devDependencies": {
"jest-when": "^3.2.1"
}
}
161 changes: 126 additions & 35 deletions plugins/gem/src/index.ts
Expand Up @@ -16,6 +16,9 @@ import envCi from "env-ci";
import { readFile, writeFile, mkdir } from "./utils";

const VERSION_REGEX = /\d+\.\d+\.\d+/;
const GEM_PKG_BUILD_REGEX = /(pkg.*)[^.]/;
const GEM_SPEC_NAME_REGEX = /name\s*=\s*["']([\S ]+)["']/;

const { isCi } = envCi();

const pluginOptions = t.partial({
Expand Down Expand Up @@ -96,8 +99,7 @@ export default class GemPlugin implements IPlugin {
auto.hooks.version.tapPromise(
this.name,
async ({ bump, dryRun, quiet }) => {
const [version, versionFile] = await this.getVersion(auto);
const newTag = inc(version, bump as ReleaseType);
const [version, newTag, versionFile] = await this.getNewVersion(auto, bump as ReleaseType)

if (dryRun && newTag) {
if (quiet) {
Expand All @@ -109,47 +111,62 @@ export default class GemPlugin implements IPlugin {
return;
}

if (!newTag) {
throw new Error(
`The version "${version}" parsed from your version file "${versionFile}" was invalid and could not be incremented. Please fix this!`
);
}

const content = await readFile(versionFile, { encoding: "utf8" });
await writeFile(versionFile, content.replace(version, newTag));
await this.writeNewVersion(version, newTag, versionFile)
}
);

auto.hooks.publish.tapPromise(this.name, async () => {
if (
isCi &&
!fs.existsSync("~/.gem/credentials") &&
process.env.RUBYGEMS_API_KEY
) {
const home = process.env.HOME || "~";
const gemDir = path.join(home, ".gem");

if (!fs.existsSync(gemDir)) {
auto.logger.verbose.info(`Creating ${gemDir} directory`);
await mkdir(gemDir);
auto.hooks.canary.tapPromise(
this.name,
async ({ bump, canaryIdentifier, dryRun, quiet }) => {
await this.writeCredentials(auto)

const [version, newTag, versionFile] = await this.getNewVersion(auto, bump as ReleaseType)

const canaryVersion = `${newTag}.pre${canaryIdentifier.replace('-','.')}`

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

const credentials = path.join(gemDir, "credentials");
return;
}

await writeFile(
credentials,
endent`
---
:rubygems_api_key: ${process.env.RUBYGEMS_API_KEY}
await this.writeNewVersion(version, canaryVersion, versionFile)

`
);
auto.logger.verbose.success(`Wrote ${credentials}`);
/** Commit the new version. we wait because "rake build" changes the lock file */
/** we don't push that version, is just to clean the stage */
const commitVersion = async () =>
execPromise("git", [
"commit",
"-am",
`"update version: ${canaryVersion} [skip ci]"`,
"--no-verify",
]);

execSync(`chmod 0600 ${credentials}`, {
stdio: "inherit",
});
}

auto.logger.verbose.info("Running default release command");
const buildResult = await execPromise("bundle", ["exec", "rake", "build"]);
const gemPath = GEM_PKG_BUILD_REGEX.exec(buildResult)?.[0]
await commitVersion();
// will push the canary gem
await execPromise("gem", ["push", `${gemPath}`]);

auto.logger.verbose.info("Successfully published canary version");

const name = await this.loadGemName();

return {
newVersion: canaryVersion,
details: this.makeInstallDetails(name, canaryVersion),
};

});

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

const [version] = await this.getVersion(auto);

Expand All @@ -176,6 +193,80 @@ export default class GemPlugin implements IPlugin {
});
}

/** create the installation details */
private makeInstallDetails(name: string | undefined, canaryVersion: string) {
return [
":sparkles: Test out this PR via:\n",
"```bash",
`gem ${name}, ${canaryVersion}`,
"or",
`gem install ${name} -v ${canaryVersion}`,
"```",
].join("\n");
}

/** loads the gem name from .gemspec */
private async loadGemName() {
const gemspec = glob.sync("*.gemspec")[0];
const content = await readFile(gemspec, { encoding: "utf8" });

return content.match(GEM_SPEC_NAME_REGEX)?.[1];
}

/** write the credentials file when necessary */
private async writeCredentials(auto: Auto) {
if (
isCi &&
!fs.existsSync("~/.gem/credentials") &&
process.env.RUBYGEMS_API_KEY
) {
const home = process.env.HOME || "~";
const gemDir = path.join(home, ".gem");

if (!fs.existsSync(gemDir)) {
auto.logger.verbose.info(`Creating ${gemDir} directory`);
await mkdir(gemDir);
}

const credentials = path.join(gemDir, "credentials");

await writeFile(
credentials,
endent`
---
:rubygems_api_key: ${process.env.RUBYGEMS_API_KEY}

`
);
auto.logger.verbose.success(`Wrote ${credentials}`);

execSync(`chmod 0600 ${credentials}`, {
stdio: "inherit",
});
}
}

/** resolves the version to a new one */
private async getNewVersion(auto: Auto, bump: ReleaseType) {
const [version, versionFile] = await this.getVersion(auto);
const newTag = inc(version, bump);

if (!newTag) {
throw new Error(
`The version "${version}" parsed from your version file "${versionFile}" was invalid and could not be incremented. Please fix this!`
);
}

return [version, newTag, versionFile];
}

/** write the version in the file */
private async writeNewVersion(version: string, newVersion: string, versionFile: string){
const content = await readFile(versionFile, { encoding: "utf8" });
await writeFile(versionFile, content.replace(version, newVersion));

}

/** Get the current version of the gem and where it was found */
private async getVersion(auto: Auto) {
let content = await readFile(this.gemspec, { encoding: "utf8" });
Expand Down