From 3f9727138ce1ff915b527f120fdd21b9a39de1ef Mon Sep 17 00:00:00 2001 From: Harris Borawski Date: Wed, 6 Jan 2021 11:36:17 -0800 Subject: [PATCH 01/10] feat: tag canaries for cocoapods plugin --- plugins/cocoapods/__tests__/cocoapods.test.ts | 37 +++ plugins/cocoapods/src/index.ts | 232 +++++++++++------- 2 files changed, 178 insertions(+), 91 deletions(-) diff --git a/plugins/cocoapods/__tests__/cocoapods.test.ts b/plugins/cocoapods/__tests__/cocoapods.test.ts index 094b1e7d7..1fb32f02a 100644 --- a/plugins/cocoapods/__tests__/cocoapods.test.ts +++ b/plugins/cocoapods/__tests__/cocoapods.test.ts @@ -93,6 +93,11 @@ describe("Cocoapods Plugin", () => { expect(getVersion("./Test.podspec")).toBe("0.0.1"); }); + test("should return canary version", () => { + mockPodspec(specWithVersion("0.0.1-canary.1.0.0")); + + expect(getVersion("./Test.podspec")).toBe("0.0.1-canary.1.0.0"); + }); }); describe("updatePodspecVersion", () => { test("should throw error if there is an error writing file", async () => { @@ -235,6 +240,38 @@ describe("Cocoapods Plugin", () => { }); }); + describe("canary hook", () => { + test("should tag with canary version", async () => { + mockPodspec(specWithVersion("0.0.1")); + + const plugin = new CocoapodsPlugin(options); + const hook = makeHooks(); + plugin.apply(({ + hooks: hook, + logger: dummyLog(), + prefixRelease, + git: { + getLatestRelease: async () => "0.0.1", + }, + getCurrentVersion: async () => "0.0.1", + } as unknown) as Auto.Auto); + + const newVersion = await hook.canary.promise({ + bump: "minor" as Auto.SEMVER, + canaryIdentifier: "1.1.1", + }); + + expect(newVersion).toBe("0.1.0-canary.1.1.1"); + expect(exec).toBeCalledTimes(3); + expect(exec).toHaveBeenCalledWith("git", [ + "tag", + "0.1.0-canary.1.1.1", + "-m", + "Update version to 0.1.0-canary.1.1.1", + ]); + }); + }); + describe("publish hook", () => { test("should push to trunk if no specsRepo in options", async () => { mockPodspec(specWithVersion("0.0.1")); diff --git a/plugins/cocoapods/src/index.ts b/plugins/cocoapods/src/index.ts index 5d5109e28..68b390533 100644 --- a/plugins/cocoapods/src/index.ts +++ b/plugins/cocoapods/src/index.ts @@ -2,7 +2,9 @@ import { Auto, IPlugin, execPromise, + getCurrentBranch, validatePluginConfiguration, + ILogger, } from "@auto-it/core"; import { inc, ReleaseType } from "semver"; @@ -14,7 +16,7 @@ import { getPodspecContents, writePodspecContents } from "./utilities"; const logPrefix = "[Cocoapods-Plugin]"; /** Regex used to pull the version line from the spec */ -const versionRegex = /\.version\s*=\s*['|"](?\d+\.\d+\.\d+)['|"]/; +const versionRegex = /\.version\s*=\s*['|"](?\d+\.\d+\.\d+.*?)['|"]/; /** * Wrapper to add logPrefix to messages * @@ -113,6 +115,9 @@ export default class CocoapodsPlugin implements IPlugin { /** The name of the plugin */ name = "cocoapods"; + /** The auto logger */ + logger?: ILogger; + /** The options of the plugin */ readonly options: ICocoapodsPluginOptions; @@ -123,6 +128,7 @@ export default class CocoapodsPlugin implements IPlugin { /** Tap into auto plugin points. */ apply(auto: Auto) { + this.logger = auto.logger; const isQuiet = auto.logger.logLevel === "quiet"; const isVerbose = auto.logger.logLevel === "verbose" || @@ -144,34 +150,73 @@ export default class CocoapodsPlugin implements IPlugin { auto.prefixRelease(getVersion(this.options.podspecPath)) ); - auto.hooks.version.tapPromise(this.name, async ({ bump, dryRun, quiet }) => { - const previousVersion = getVersion(this.options.podspecPath); - const releaseVersion = inc(previousVersion, bump as ReleaseType); + auto.hooks.version.tapPromise( + this.name, + async ({ bump, dryRun, quiet }) => { + const previousVersion = getVersion(this.options.podspecPath); + const releaseVersion = inc(previousVersion, bump as ReleaseType); + + if (dryRun && releaseVersion) { + if (quiet) { + console.log(releaseVersion); + } else { + auto.logger.log.info(`Would have published: ${releaseVersion}`); + } - if (dryRun && releaseVersion) { - if (quiet) { - console.log(releaseVersion); - } else { - auto.logger.log.info(`Would have published: ${releaseVersion}`); + return; } - return; - } + if (!releaseVersion) { + throw new Error( + `Could not increment previous version: ${previousVersion}` + ); + } - if (!releaseVersion) { - throw new Error( - `Could not increment previous version: ${previousVersion}` - ); + await updatePodspecVersion(this.options.podspecPath, releaseVersion); + await execPromise("git", [ + "tag", + releaseVersion, + "-m", + `"Update version to ${releaseVersion}"`, + ]); } + ); - await updatePodspecVersion(this.options.podspecPath, releaseVersion); - await execPromise("git", [ - "tag", - releaseVersion, - "-m", - `"Update version to ${releaseVersion}"`, - ]); - }); + auto.hooks.canary.tapPromise( + this.name, + async ({ bump, canaryIdentifier, dryRun, quiet }) => { + if (!auto.git) { + return; + } + + const lastRelease = await auto.git.getLatestRelease(); + const current = await auto.getCurrentVersion(lastRelease); + const nextVersion = inc(current, bump as ReleaseType); + const canaryVersion = `${nextVersion}-canary.${canaryIdentifier}`; + const branch = getCurrentBranch() || ""; + + if (dryRun) { + if (quiet) { + console.log(canaryVersion); + } else { + auto.logger.log.info(`Would have published: ${canaryVersion}`); + } + + return; + } + + await execPromise("git", [ + "tag", + canaryVersion, + "-m", + `Update version to ${canaryVersion}`, + ]); + await execPromise("git", ["push", auto.remote, branch, "--tags"]); + + await this.publishPodSpec(podLogLevel); + return canaryVersion; + } + ); auto.hooks.beforeShipIt.tapPromise(this.name, async ({ dryRun }) => { if (dryRun) { @@ -190,8 +235,6 @@ export default class CocoapodsPlugin implements IPlugin { }); auto.hooks.publish.tapPromise(this.name, async () => { - const [pod, ...commands] = this.options.podCommand?.split(" ") || ["pod"]; - await execPromise("git", [ "push", "--follow-tags", @@ -199,73 +242,36 @@ export default class CocoapodsPlugin implements IPlugin { auto.remote, auto.baseBranch, ]); + await this.publishPodSpec(podLogLevel); + }); + } - if (!this.options.specsRepo) { - auto.logger.log.info(logMessage(`Pushing to Cocoapods trunk`)); - await execPromise(pod, [ - ...commands, - "trunk", - "push", - ...(this.options.flags || []), - this.options.podspecPath, - ...podLogLevel, - ]); - return; - } - - try { - const existingRepos = await execPromise(pod, [ - ...commands, - "repo", - "list", - ]); - if (existingRepos.indexOf("autoPublishRepo") !== -1) { - auto.logger.log.info("Removing existing autoPublishRepo"); - await execPromise(pod, [ - ...commands, - "repo", - "remove", - "autoPublishRepo", - ...podLogLevel, - ]); - } - } catch (error) { - auto.logger.log.warn( - `Error Checking for existing Specs repositories: ${error}` - ); - } - - try { - await execPromise(pod, [ - ...commands, - "repo", - "add", - "autoPublishRepo", - this.options.specsRepo, - ...podLogLevel, - ]); - - auto.logger.log.info( - logMessage(`Pushing to specs repo: ${this.options.specsRepo}`) - ); + /** + * + */ + async publishPodSpec(podLogLevel: string[]) { + const [pod, ...commands] = this.options.podCommand?.split(" ") || ["pod"]; + if (!this.options.specsRepo) { + this.logger?.log.info(logMessage(`Pushing to Cocoapods trunk`)); + await execPromise(pod, [ + ...commands, + "trunk", + "push", + ...(this.options.flags || []), + this.options.podspecPath, + ...podLogLevel, + ]); + return; + } - await execPromise(pod, [ - ...commands, - "repo", - "push", - ...(this.options.flags || []), - "autoPublishRepo", - this.options.podspecPath, - ...podLogLevel, - ]); - } catch (error) { - auto.logger.log.error( - logMessage( - `Error pushing to specs repo: ${this.options.specsRepo}. Error: ${error}` - ) - ); - process.exit(1); - } finally { + try { + const existingRepos = await execPromise(pod, [ + ...commands, + "repo", + "list", + ]); + if (existingRepos.indexOf("autoPublishRepo") !== -1) { + this.logger?.log.info("Removing existing autoPublishRepo"); await execPromise(pod, [ ...commands, "repo", @@ -274,6 +280,50 @@ export default class CocoapodsPlugin implements IPlugin { ...podLogLevel, ]); } - }); + } catch (error) { + this.logger?.log.warn( + `Error Checking for existing Specs repositories: ${error}` + ); + } + + try { + await execPromise(pod, [ + ...commands, + "repo", + "add", + "autoPublishRepo", + this.options.specsRepo, + ...podLogLevel, + ]); + + this.logger?.log.info( + logMessage(`Pushing to specs repo: ${this.options.specsRepo}`) + ); + + await execPromise(pod, [ + ...commands, + "repo", + "push", + ...(this.options.flags || []), + "autoPublishRepo", + this.options.podspecPath, + ...podLogLevel, + ]); + } catch (error) { + this.logger?.log.error( + logMessage( + `Error pushing to specs repo: ${this.options.specsRepo}. Error: ${error}` + ) + ); + process.exit(1); + } finally { + await execPromise(pod, [ + ...commands, + "repo", + "remove", + "autoPublishRepo", + ...podLogLevel, + ]); + } } } From 34c593914e0cc962bd68eb38379089f1b099f264 Mon Sep 17 00:00:00 2001 From: Harris Borawski Date: Thu, 7 Jan 2021 14:36:02 -0800 Subject: [PATCH 02/10] refactor: dont use git tags, rewrite the source line to point to the commit --- plugins/cocoapods/__tests__/cocoapods.test.ts | 50 +++++++--- plugins/cocoapods/src/index.ts | 99 +++++++++++++++++-- 2 files changed, 129 insertions(+), 20 deletions(-) diff --git a/plugins/cocoapods/__tests__/cocoapods.test.ts b/plugins/cocoapods/__tests__/cocoapods.test.ts index 1fb32f02a..0f5b2739e 100644 --- a/plugins/cocoapods/__tests__/cocoapods.test.ts +++ b/plugins/cocoapods/__tests__/cocoapods.test.ts @@ -9,7 +9,10 @@ import CocoapodsPlugin, { updatePodspecVersion, } from "../src"; -const specWithVersion = (version: string) => ` +const specWithVersion = ( + version: string, + source = "{ :git => 'https://github.com/intuit/auto.git', :tag => s.version.to_s }" +) => ` Pod:: Spec.new do | s | s.name = 'Test' s.version = '${version}' @@ -29,7 +32,7 @@ const specWithVersion = (version: string) => ` # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' s.license = { : type => 'MIT', : file => 'LICENSE' } s.author = { 'hborawski' => 'harris_borawski@intuit.com' } - s.source = { : git => 'https://github.com/intuit/auto.git', : tag => s.version.to_s } + s.source = ${source} s.ios.deployment_target = '11.0' @@ -67,7 +70,12 @@ describe("Cocoapods Plugin", () => { exec.mockClear(); const plugin = new CocoapodsPlugin(options); hooks = makeHooks(); - plugin.apply({ hooks, logger: dummyLog(), prefixRelease } as Auto.Auto); + plugin.apply({ + hooks, + logger: dummyLog(), + prefixRelease, + remote: "https://github.com/intuit/auto.git", + } as Auto.Auto); }); describe("getParsedPodspecContents", () => { @@ -180,8 +188,10 @@ describe("Cocoapods Plugin", () => { const mock = jest .spyOn(utilities, "writePodspecContents") - .mockImplementationOnce(() => { - throw new Error("Filesystem Error"); + .mockImplementation((path, contents) => { + if (contents.includes("1.0.0")) { + throw new Error("Filesystem Error"); + } }); await expect( @@ -242,8 +252,17 @@ describe("Cocoapods Plugin", () => { describe("canary hook", () => { test("should tag with canary version", async () => { - mockPodspec(specWithVersion("0.0.1")); + // mockPodspec(specWithVersion("0.0.1")); + let podSpec = specWithVersion("0.0.1"); + jest + .spyOn(utilities, "getPodspecContents") + .mockImplementation(() => podSpec); + const mock = jest + .spyOn(utilities, "writePodspecContents") + .mockImplementation((path, contents) => { + podSpec = contents; + }); const plugin = new CocoapodsPlugin(options); const hook = makeHooks(); plugin.apply(({ @@ -254,6 +273,7 @@ describe("Cocoapods Plugin", () => { getLatestRelease: async () => "0.0.1", }, getCurrentVersion: async () => "0.0.1", + remote: "https://github.com/intuit/auto.git", } as unknown) as Auto.Auto); const newVersion = await hook.canary.promise({ @@ -262,13 +282,21 @@ describe("Cocoapods Plugin", () => { }); expect(newVersion).toBe("0.1.0-canary.1.1.1"); - expect(exec).toBeCalledTimes(3); + expect(exec).toBeCalledTimes(4); expect(exec).toHaveBeenCalledWith("git", [ - "tag", - "0.1.0-canary.1.1.1", - "-m", - "Update version to 0.1.0-canary.1.1.1", + "commit", + "-am", + `"update version: 0.1.0-canary.1.1.1 [skip ci]"`, + "--no-verify", ]); + + expect(mock).toHaveBeenLastCalledWith( + expect.any(String), + specWithVersion( + "0.1.0-canary.1.1.1", + "{ :git => 'https://github.com/intuit/auto.git', :commit => 'undefined' }" + ) + ); }); }); diff --git a/plugins/cocoapods/src/index.ts b/plugins/cocoapods/src/index.ts index 68b390533..887abda2e 100644 --- a/plugins/cocoapods/src/index.ts +++ b/plugins/cocoapods/src/index.ts @@ -2,7 +2,6 @@ import { Auto, IPlugin, execPromise, - getCurrentBranch, validatePluginConfiguration, ILogger, } from "@auto-it/core"; @@ -17,6 +16,10 @@ const logPrefix = "[Cocoapods-Plugin]"; /** Regex used to pull the version line from the spec */ const versionRegex = /\.version\s*=\s*['|"](?\d+\.\d+\.\d+.*?)['|"]/; + +/** Regex used to pull the source dictionary from the spec */ +const sourceLineRegex = /(?\w+)\.source.*(?\{\s*:\s*git.*\})/; + /** * Wrapper to add logPrefix to messages * @@ -71,6 +74,36 @@ export function getVersion(podspecPath: string): string { throw new Error(`Version could not be found in podspec: ${podspecPath}`); } +/** + * Retrieves the source dictionary currently in the podspec file + * + * @param podspecPath - The relative path to the podspec file + */ +export function getSourceInfo(podspecPath: string): string { + const podspecContents = sourceLineRegex.exec(getPodspecContents(podspecPath)); + if (podspecContents?.groups?.source) { + return podspecContents.groups.source; + } + + throw new Error(`Source could not be found in podspec: ${podspecPath}`); +} + +/** + * Retrieves the source dictionary currently in the podspec file + * + * @param podspecPath - The relative path to the podspec file + */ +export function getSourceVariable(podspecPath: string): string { + const podspecContents = sourceLineRegex.exec(getPodspecContents(podspecPath)); + if (podspecContents?.groups?.specVar) { + return podspecContents.groups.specVar; + } + + throw new Error( + `Spec variable could not be found in podspec: ${podspecPath}` + ); +} + /** * Updates the version in the podspec to the supplied version * @@ -110,6 +143,47 @@ export async function updatePodspecVersion( } } +/** + * Updates the version in the podspec to the supplied version + * + * @param podspecPath - The relative path to the podspec file + * @param remote - The git remote that is being used + * @param canary - Whether to update to the canary location or not + */ +export async function updateSourceLocation( + podspecPath: string, + remote: string, + canary: boolean +) { + const podspecContents = getPodspecContents(podspecPath); + + const source = getSourceInfo(podspecPath); + const specVar = getSourceVariable(podspecPath); + + try { + if (canary) { + const revision = await execPromise("git", ["rev-parse", "HEAD"]); + const newPodspec = podspecContents.replace( + source, + `{ :git => '${remote}', :commit => '${revision}' }` + ); + + writePodspecContents(podspecPath, newPodspec); + } else { + const newPodspec = podspecContents.replace( + source, + `{ :git => '${remote}', :tag => ${specVar}.version.to_s }` + ); + + writePodspecContents(podspecPath, newPodspec); + } + } catch (error) { + throw new Error( + `Error updating source location in podspec: ${podspecPath}` + ); + } +} + /** Use auto to version your cocoapod */ export default class CocoapodsPlugin implements IPlugin { /** The name of the plugin */ @@ -172,7 +246,14 @@ export default class CocoapodsPlugin implements IPlugin { ); } + await updateSourceLocation( + this.options.podspecPath, + auto.remote, + false + ); + await updatePodspecVersion(this.options.podspecPath, releaseVersion); + await execPromise("git", [ "tag", releaseVersion, @@ -193,7 +274,6 @@ export default class CocoapodsPlugin implements IPlugin { const current = await auto.getCurrentVersion(lastRelease); const nextVersion = inc(current, bump as ReleaseType); const canaryVersion = `${nextVersion}-canary.${canaryIdentifier}`; - const branch = getCurrentBranch() || ""; if (dryRun) { if (quiet) { @@ -205,15 +285,16 @@ export default class CocoapodsPlugin implements IPlugin { return; } - await execPromise("git", [ - "tag", - canaryVersion, - "-m", - `Update version to ${canaryVersion}`, - ]); - await execPromise("git", ["push", auto.remote, branch, "--tags"]); + await updateSourceLocation(this.options.podspecPath, auto.remote, true); + await updatePodspecVersion(this.options.podspecPath, canaryVersion); + + // Publish the canary podspec, committing it isn't needed for specs push await this.publishPodSpec(podLogLevel); + + // Reset changes to podspec file since it doesn't need to be committed + await execPromise("git", ["checkout", this.options.podspecPath]); + return canaryVersion; } ); From 477eea86a62829dfcb4aa5f39d737bec351b6028 Mon Sep 17 00:00:00 2001 From: Harris Borawski Date: Thu, 7 Jan 2021 14:39:08 -0800 Subject: [PATCH 03/10] fix: dont rewrite source location in version hook since the canary rewrite isnt committed --- plugins/cocoapods/src/index.ts | 49 ++++++---------------------------- 1 file changed, 8 insertions(+), 41 deletions(-) diff --git a/plugins/cocoapods/src/index.ts b/plugins/cocoapods/src/index.ts index 887abda2e..ace08120b 100644 --- a/plugins/cocoapods/src/index.ts +++ b/plugins/cocoapods/src/index.ts @@ -88,22 +88,6 @@ export function getSourceInfo(podspecPath: string): string { throw new Error(`Source could not be found in podspec: ${podspecPath}`); } -/** - * Retrieves the source dictionary currently in the podspec file - * - * @param podspecPath - The relative path to the podspec file - */ -export function getSourceVariable(podspecPath: string): string { - const podspecContents = sourceLineRegex.exec(getPodspecContents(podspecPath)); - if (podspecContents?.groups?.specVar) { - return podspecContents.groups.specVar; - } - - throw new Error( - `Spec variable could not be found in podspec: ${podspecPath}` - ); -} - /** * Updates the version in the podspec to the supplied version * @@ -152,31 +136,20 @@ export async function updatePodspecVersion( */ export async function updateSourceLocation( podspecPath: string, - remote: string, - canary: boolean + remote: string ) { const podspecContents = getPodspecContents(podspecPath); const source = getSourceInfo(podspecPath); - const specVar = getSourceVariable(podspecPath); try { - if (canary) { - const revision = await execPromise("git", ["rev-parse", "HEAD"]); - const newPodspec = podspecContents.replace( - source, - `{ :git => '${remote}', :commit => '${revision}' }` - ); - - writePodspecContents(podspecPath, newPodspec); - } else { - const newPodspec = podspecContents.replace( - source, - `{ :git => '${remote}', :tag => ${specVar}.version.to_s }` - ); + const revision = await execPromise("git", ["rev-parse", "HEAD"]); + const newPodspec = podspecContents.replace( + source, + `{ :git => '${remote}', :commit => '${revision}' }` + ); - writePodspecContents(podspecPath, newPodspec); - } + writePodspecContents(podspecPath, newPodspec); } catch (error) { throw new Error( `Error updating source location in podspec: ${podspecPath}` @@ -246,12 +219,6 @@ export default class CocoapodsPlugin implements IPlugin { ); } - await updateSourceLocation( - this.options.podspecPath, - auto.remote, - false - ); - await updatePodspecVersion(this.options.podspecPath, releaseVersion); await execPromise("git", [ @@ -285,7 +252,7 @@ export default class CocoapodsPlugin implements IPlugin { return; } - await updateSourceLocation(this.options.podspecPath, auto.remote, true); + await updateSourceLocation(this.options.podspecPath, auto.remote); await updatePodspecVersion(this.options.podspecPath, canaryVersion); From 727f02ccdd4e702aed20dd6da644df95b2b77745 Mon Sep 17 00:00:00 2001 From: Harris Borawski Date: Thu, 7 Jan 2021 15:38:26 -0800 Subject: [PATCH 04/10] fix: only commit in version hook --- plugins/cocoapods/__tests__/cocoapods.test.ts | 17 ++++---------- plugins/cocoapods/src/index.ts | 23 ++++++++----------- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/plugins/cocoapods/__tests__/cocoapods.test.ts b/plugins/cocoapods/__tests__/cocoapods.test.ts index 0f5b2739e..ad286128e 100644 --- a/plugins/cocoapods/__tests__/cocoapods.test.ts +++ b/plugins/cocoapods/__tests__/cocoapods.test.ts @@ -117,11 +117,9 @@ describe("Cocoapods Plugin", () => { throw new Error("Filesystem Error"); }); - await expect( - updatePodspecVersion("./Test.podspec", "0.0.2") - ).rejects.toThrowError( - "Error updating version in podspec: ./Test.podspec" - ); + expect( + updatePodspecVersion.bind(null, "./Test.podspec", "0.0.2") + ).toThrowError("Error updating version in podspec: ./Test.podspec"); }); test("should successfully write new version", async () => { mockPodspec(specWithVersion("0.0.1")); @@ -282,13 +280,8 @@ describe("Cocoapods Plugin", () => { }); expect(newVersion).toBe("0.1.0-canary.1.1.1"); - expect(exec).toBeCalledTimes(4); - expect(exec).toHaveBeenCalledWith("git", [ - "commit", - "-am", - `"update version: 0.1.0-canary.1.1.1 [skip ci]"`, - "--no-verify", - ]); + expect(exec).toBeCalledTimes(3); + expect(exec).toHaveBeenCalledWith("git", ["checkout", "./Test.podspec"]); expect(mock).toHaveBeenLastCalledWith( expect.any(String), diff --git a/plugins/cocoapods/src/index.ts b/plugins/cocoapods/src/index.ts index ace08120b..8f0bc3340 100644 --- a/plugins/cocoapods/src/index.ts +++ b/plugins/cocoapods/src/index.ts @@ -94,10 +94,7 @@ export function getSourceInfo(podspecPath: string): string { * @param podspecPath - The relative path to the podspec file * @param version - The version to update the podspec to */ -export async function updatePodspecVersion( - podspecPath: string, - version: string -) { +export function updatePodspecVersion(podspecPath: string, version: string) { const previousVersion = getVersion(podspecPath); const parsedContents = getParsedPodspecContents(podspecPath); const podspecContents = getPodspecContents(podspecPath); @@ -114,13 +111,6 @@ export async function updatePodspecVersion( ); writePodspecContents(podspecPath, newPodspec); - - await execPromise("git", [ - "commit", - "-am", - `"update version: ${version} [skip ci]"`, - "--no-verify", - ]); } } catch (error) { throw new Error(`Error updating version in podspec: ${podspecPath}`); @@ -219,7 +209,14 @@ export default class CocoapodsPlugin implements IPlugin { ); } - await updatePodspecVersion(this.options.podspecPath, releaseVersion); + updatePodspecVersion(this.options.podspecPath, releaseVersion); + + await execPromise("git", [ + "commit", + "-am", + `"update version: ${releaseVersion} [skip ci]"`, + "--no-verify", + ]); await execPromise("git", [ "tag", @@ -254,7 +251,7 @@ export default class CocoapodsPlugin implements IPlugin { await updateSourceLocation(this.options.podspecPath, auto.remote); - await updatePodspecVersion(this.options.podspecPath, canaryVersion); + updatePodspecVersion(this.options.podspecPath, canaryVersion); // Publish the canary podspec, committing it isn't needed for specs push await this.publishPodSpec(podLogLevel); From 6b24fbf1040df3a0e123b501cf64f998b951a5fd Mon Sep 17 00:00:00 2001 From: Harris Borawski Date: Thu, 7 Jan 2021 15:40:33 -0800 Subject: [PATCH 05/10] fix: remove duplicate canary in version --- plugins/cocoapods/__tests__/cocoapods.test.ts | 2 +- plugins/cocoapods/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/cocoapods/__tests__/cocoapods.test.ts b/plugins/cocoapods/__tests__/cocoapods.test.ts index ad286128e..9ad78ee42 100644 --- a/plugins/cocoapods/__tests__/cocoapods.test.ts +++ b/plugins/cocoapods/__tests__/cocoapods.test.ts @@ -276,7 +276,7 @@ describe("Cocoapods Plugin", () => { const newVersion = await hook.canary.promise({ bump: "minor" as Auto.SEMVER, - canaryIdentifier: "1.1.1", + canaryIdentifier: "canary.1.1.1", }); expect(newVersion).toBe("0.1.0-canary.1.1.1"); diff --git a/plugins/cocoapods/src/index.ts b/plugins/cocoapods/src/index.ts index 8f0bc3340..882db64aa 100644 --- a/plugins/cocoapods/src/index.ts +++ b/plugins/cocoapods/src/index.ts @@ -237,7 +237,7 @@ export default class CocoapodsPlugin implements IPlugin { const lastRelease = await auto.git.getLatestRelease(); const current = await auto.getCurrentVersion(lastRelease); const nextVersion = inc(current, bump as ReleaseType); - const canaryVersion = `${nextVersion}-canary.${canaryIdentifier}`; + const canaryVersion = `${nextVersion}-${canaryIdentifier}`; if (dryRun) { if (quiet) { From 39197c897dbcbd85642e7a04a074de24e7a51c70 Mon Sep 17 00:00:00 2001 From: Harris Borawski Date: Thu, 7 Jan 2021 17:14:33 -0800 Subject: [PATCH 06/10] fix: get remote repo from octokit for the current PR --- packages/core/src/auto.ts | 2 +- plugins/cocoapods/__tests__/cocoapods.test.ts | 13 ++++++++++--- plugins/cocoapods/src/index.ts | 10 ++++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/core/src/auto.ts b/packages/core/src/auto.ts index 4af2cdefa..2060a8f90 100644 --- a/packages/core/src/auto.ts +++ b/packages/core/src/auto.ts @@ -307,7 +307,7 @@ const loadEnv = () => { }; /** Get the pr number from user input or the CI env. */ -function getPrNumberFromEnv(pr?: number) { +export function getPrNumberFromEnv(pr?: number) { const envPr = "pr" in env && Number(env.pr); const prNumber = pr || envPr; diff --git a/plugins/cocoapods/__tests__/cocoapods.test.ts b/plugins/cocoapods/__tests__/cocoapods.test.ts index 9ad78ee42..69a379b51 100644 --- a/plugins/cocoapods/__tests__/cocoapods.test.ts +++ b/plugins/cocoapods/__tests__/cocoapods.test.ts @@ -250,8 +250,7 @@ describe("Cocoapods Plugin", () => { describe("canary hook", () => { test("should tag with canary version", async () => { - // mockPodspec(specWithVersion("0.0.1")); - + jest.spyOn(Auto, "getPrNumberFromEnv").mockReturnValue(1); let podSpec = specWithVersion("0.0.1"); jest .spyOn(utilities, "getPodspecContents") @@ -269,9 +268,17 @@ describe("Cocoapods Plugin", () => { prefixRelease, git: { getLatestRelease: async () => "0.0.1", + getPullRequest: async () => ({ + data: { + head: { + repo: { + clone_url: "https://github.com/intuit/auto.git", + }, + }, + }, + }), }, getCurrentVersion: async () => "0.0.1", - remote: "https://github.com/intuit/auto.git", } as unknown) as Auto.Auto); const newVersion = await hook.canary.promise({ diff --git a/plugins/cocoapods/src/index.ts b/plugins/cocoapods/src/index.ts index 882db64aa..f6b6e8b18 100644 --- a/plugins/cocoapods/src/index.ts +++ b/plugins/cocoapods/src/index.ts @@ -4,6 +4,7 @@ import { execPromise, validatePluginConfiguration, ILogger, + getPrNumberFromEnv, } from "@auto-it/core"; import { inc, ReleaseType } from "semver"; @@ -230,10 +231,15 @@ export default class CocoapodsPlugin implements IPlugin { auto.hooks.canary.tapPromise( this.name, async ({ bump, canaryIdentifier, dryRun, quiet }) => { - if (!auto.git) { + const pr = getPrNumberFromEnv(); + + if (!auto.git || !pr) { return; } + const remoteRepo = await (await auto.git.getPullRequest(pr)).data.head + .repo.clone_url; + const lastRelease = await auto.git.getLatestRelease(); const current = await auto.getCurrentVersion(lastRelease); const nextVersion = inc(current, bump as ReleaseType); @@ -249,7 +255,7 @@ export default class CocoapodsPlugin implements IPlugin { return; } - await updateSourceLocation(this.options.podspecPath, auto.remote); + await updateSourceLocation(this.options.podspecPath, remoteRepo); updatePodspecVersion(this.options.podspecPath, canaryVersion); From 15f7fd476f98355911dd908f9c01b4e87fb0db31 Mon Sep 17 00:00:00 2001 From: Harris Borawski Date: Fri, 8 Jan 2021 09:34:53 -0800 Subject: [PATCH 07/10] remove unused capture group in regex --- plugins/cocoapods/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cocoapods/src/index.ts b/plugins/cocoapods/src/index.ts index f6b6e8b18..66c3bacb1 100644 --- a/plugins/cocoapods/src/index.ts +++ b/plugins/cocoapods/src/index.ts @@ -19,7 +19,7 @@ const logPrefix = "[Cocoapods-Plugin]"; const versionRegex = /\.version\s*=\s*['|"](?\d+\.\d+\.\d+.*?)['|"]/; /** Regex used to pull the source dictionary from the spec */ -const sourceLineRegex = /(?\w+)\.source.*(?\{\s*:\s*git.*\})/; +const sourceLineRegex = /\.source.*(?\{\s*:\s*git.*\})/; /** * Wrapper to add logPrefix to messages From d509006500f17d9b196521fe01ab86ec18b06d0e Mon Sep 17 00:00:00 2001 From: Harris Borawski Date: Fri, 8 Jan 2021 10:15:31 -0800 Subject: [PATCH 08/10] add more tests --- plugins/cocoapods/__tests__/cocoapods.test.ts | 60 ++++++++++++++++++- plugins/cocoapods/src/index.ts | 3 +- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/plugins/cocoapods/__tests__/cocoapods.test.ts b/plugins/cocoapods/__tests__/cocoapods.test.ts index 69a379b51..41a697735 100644 --- a/plugins/cocoapods/__tests__/cocoapods.test.ts +++ b/plugins/cocoapods/__tests__/cocoapods.test.ts @@ -7,6 +7,8 @@ import CocoapodsPlugin, { getParsedPodspecContents, getVersion, updatePodspecVersion, + updateSourceLocation, + getSourceInfo, } from "../src"; const specWithVersion = ( @@ -49,7 +51,7 @@ const mockPodspec = (contents: string) => { return jest.spyOn(utilities, "getPodspecContents").mockReturnValue(contents); }; -let exec = jest.fn().mockResolvedValueOnce(""); +let exec = jest.fn(); // @ts-ignore jest.mock("../../../packages/core/dist/utils/exec-promise", () => (...args) => exec(...args) @@ -74,7 +76,6 @@ describe("Cocoapods Plugin", () => { hooks, logger: dummyLog(), prefixRelease, - remote: "https://github.com/intuit/auto.git", } as Auto.Auto); }); @@ -107,6 +108,20 @@ describe("Cocoapods Plugin", () => { expect(getVersion("./Test.podspec")).toBe("0.0.1-canary.1.0.0"); }); }); + describe("getSourceInfo", () => { + test("should throw error if source line cant be found", () => { + mockPodspec(specWithVersion("0.0.1", "no source")); + + expect(() => getSourceInfo("./Test.podspec")).toThrow(); + }); + test("should retrieve source info", () => { + mockPodspec(specWithVersion("0.0.1")); + + expect(getSourceInfo("./Test.podspec")).toBe( + "{ :git => 'https://github.com/intuit/auto.git', :tag => s.version.to_s }" + ); + }); + }); describe("updatePodspecVersion", () => { test("should throw error if there is an error writing file", async () => { mockPodspec(specWithVersion("0.0.1")); @@ -130,6 +145,47 @@ describe("Cocoapods Plugin", () => { expect(mock).lastCalledWith(expect.any(String), specWithVersion("0.0.2")); }); }); + describe("updateSourceLocation", () => { + test("should throw error if there is an error writing file", async () => { + mockPodspec(specWithVersion("0.0.1")); + + exec.mockReturnValue("commithash"); + + jest + .spyOn(utilities, "writePodspecContents") + .mockImplementationOnce(() => { + throw new Error("Filesystem Error"); + }); + + await expect( + updateSourceLocation( + "./Test.podspec", + "https://github.com/somefork/auto.git" + ) + ).rejects.toThrowError( + "Error updating source location in podspec: ./Test.podspec" + ); + }); + test("should successfully write new source location", async () => { + mockPodspec(specWithVersion("0.0.1")); + + const mock = jest.spyOn(utilities, "writePodspecContents"); + + exec.mockReturnValue(Promise.resolve("commithash")); + + await updateSourceLocation( + "./Test.podspec", + "https://github.com/somefork/auto.git" + ); + expect(mock).lastCalledWith( + expect.any(String), + specWithVersion( + "0.0.1", + "{ :git => 'https://github.com/somefork/auto.git', :commit => 'commithash' }" + ) + ); + }); + }); describe("modifyConfig hook", () => { test("should set noVersionPrefix to true", () => { const config = {}; diff --git a/plugins/cocoapods/src/index.ts b/plugins/cocoapods/src/index.ts index 66c3bacb1..9a4770fd9 100644 --- a/plugins/cocoapods/src/index.ts +++ b/plugins/cocoapods/src/index.ts @@ -119,11 +119,10 @@ export function updatePodspecVersion(podspecPath: string, version: string) { } /** - * Updates the version in the podspec to the supplied version + * Updates the source location to point to the current commit for the given remote * * @param podspecPath - The relative path to the podspec file * @param remote - The git remote that is being used - * @param canary - Whether to update to the canary location or not */ export async function updateSourceLocation( podspecPath: string, From dbccad4d2f128c740bb14353638bebc430e0abba Mon Sep 17 00:00:00 2001 From: Harris Borawski Date: Fri, 8 Jan 2021 10:44:26 -0800 Subject: [PATCH 09/10] test: add tests for dryRun logging --- plugins/cocoapods/__tests__/cocoapods.test.ts | 101 +++++++++++++----- 1 file changed, 77 insertions(+), 24 deletions(-) diff --git a/plugins/cocoapods/__tests__/cocoapods.test.ts b/plugins/cocoapods/__tests__/cocoapods.test.ts index 41a697735..bbf1a124d 100644 --- a/plugins/cocoapods/__tests__/cocoapods.test.ts +++ b/plugins/cocoapods/__tests__/cocoapods.test.ts @@ -56,6 +56,7 @@ let exec = jest.fn(); jest.mock("../../../packages/core/dist/utils/exec-promise", () => (...args) => exec(...args) ); +const logger = dummyLog(); describe("Cocoapods Plugin", () => { let hooks: Auto.IAutoHooks; @@ -72,11 +73,24 @@ describe("Cocoapods Plugin", () => { exec.mockClear(); const plugin = new CocoapodsPlugin(options); hooks = makeHooks(); - plugin.apply({ + plugin.apply(({ hooks, - logger: dummyLog(), + logger: logger, prefixRelease, - } as Auto.Auto); + git: { + getLatestRelease: async () => "0.0.1", + getPullRequest: async () => ({ + data: { + head: { + repo: { + clone_url: "https://github.com/intuit/auto.git", + }, + }, + }, + }), + }, + getCurrentVersion: async () => "0.0.1", + } as unknown) as Auto.Auto); }); describe("getParsedPodspecContents", () => { @@ -210,6 +224,32 @@ describe("Cocoapods Plugin", () => { }); }); describe("version hook", () => { + test("should do nothing on dryRun", async () => { + mockPodspec(specWithVersion("0.0.1")); + + const mockLog = jest.spyOn(logger.log, "info"); + + await hooks.version.promise({ bump: Auto.SEMVER.patch, dryRun: true }); + + expect(exec).toHaveBeenCalledTimes(0); + expect(mockLog).toHaveBeenCalledTimes(1); + }); + test("should not use logger on quiet dryRun", async () => { + mockPodspec(specWithVersion("0.0.1")); + + const mockLog = jest.spyOn(logger.log, "info"); + const mockConsole = jest.spyOn(console, "log"); + + await hooks.version.promise({ + bump: Auto.SEMVER.patch, + dryRun: true, + quiet: true, + }); + + expect(exec).toHaveBeenCalledTimes(0); + expect(mockLog).toHaveBeenCalledTimes(0); + expect(mockConsole).toHaveBeenCalledTimes(1); + }); test("should version release - patch version", async () => { mockPodspec(specWithVersion("0.0.1")); @@ -305,6 +345,39 @@ describe("Cocoapods Plugin", () => { }); describe("canary hook", () => { + test("should do nothing on dryRun", async () => { + mockPodspec(specWithVersion("0.0.1")); + jest.spyOn(Auto, "getPrNumberFromEnv").mockReturnValue(1); + + const mockLog = jest.spyOn(logger.log, "info"); + + await hooks.canary.promise({ + bump: Auto.SEMVER.patch, + canaryIdentifier: "canary.1.0", + dryRun: true, + }); + + expect(exec).toHaveBeenCalledTimes(0); + expect(mockLog).toHaveBeenCalledTimes(1); + }); + test("should not use logger on quiet dryRun", async () => { + mockPodspec(specWithVersion("0.0.1")); + jest.spyOn(Auto, "getPrNumberFromEnv").mockReturnValue(1); + + const mockLog = jest.spyOn(logger.log, "info"); + const mockConsole = jest.spyOn(console, "log"); + + await hooks.canary.promise({ + bump: Auto.SEMVER.patch, + canaryIdentifier: "canary.1.0", + dryRun: true, + quiet: true, + }); + + expect(exec).toHaveBeenCalledTimes(0); + expect(mockLog).toHaveBeenCalledTimes(0); + expect(mockConsole).toHaveBeenCalledTimes(1); + }); test("should tag with canary version", async () => { jest.spyOn(Auto, "getPrNumberFromEnv").mockReturnValue(1); let podSpec = specWithVersion("0.0.1"); @@ -316,28 +389,8 @@ describe("Cocoapods Plugin", () => { .mockImplementation((path, contents) => { podSpec = contents; }); - const plugin = new CocoapodsPlugin(options); - const hook = makeHooks(); - plugin.apply(({ - hooks: hook, - logger: dummyLog(), - prefixRelease, - git: { - getLatestRelease: async () => "0.0.1", - getPullRequest: async () => ({ - data: { - head: { - repo: { - clone_url: "https://github.com/intuit/auto.git", - }, - }, - }, - }), - }, - getCurrentVersion: async () => "0.0.1", - } as unknown) as Auto.Auto); - const newVersion = await hook.canary.promise({ + const newVersion = await hooks.canary.promise({ bump: "minor" as Auto.SEMVER, canaryIdentifier: "canary.1.1.1", }); From ac02033761f50e86ea4e861afa76f0ce3380b1b4 Mon Sep 17 00:00:00 2001 From: Harris Borawski Date: Fri, 8 Jan 2021 13:40:31 -0800 Subject: [PATCH 10/10] fix: if no PR number, default to auto.remote instead of fetching from octokit --- plugins/cocoapods/__tests__/cocoapods.test.ts | 31 ++++++++++++++++++- plugins/cocoapods/src/index.ts | 17 +++++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/plugins/cocoapods/__tests__/cocoapods.test.ts b/plugins/cocoapods/__tests__/cocoapods.test.ts index bbf1a124d..c327ee79b 100644 --- a/plugins/cocoapods/__tests__/cocoapods.test.ts +++ b/plugins/cocoapods/__tests__/cocoapods.test.ts @@ -83,12 +83,13 @@ describe("Cocoapods Plugin", () => { data: { head: { repo: { - clone_url: "https://github.com/intuit/auto.git", + clone_url: "https://github.com/intuit-fork/auto.git", }, }, }, }), }, + remote: "https://github.com/intuit/auto.git", getCurrentVersion: async () => "0.0.1", } as unknown) as Auto.Auto); }); @@ -399,6 +400,34 @@ describe("Cocoapods Plugin", () => { expect(exec).toBeCalledTimes(3); expect(exec).toHaveBeenCalledWith("git", ["checkout", "./Test.podspec"]); + expect(mock).toHaveBeenLastCalledWith( + expect.any(String), + specWithVersion( + "0.1.0-canary.1.1.1", + "{ :git => 'https://github.com/intuit-fork/auto.git', :commit => 'undefined' }" + ) + ); + }); + test("should tag with canary version with no PR number", async () => { + let podSpec = specWithVersion("0.0.1"); + jest + .spyOn(utilities, "getPodspecContents") + .mockImplementation(() => podSpec); + const mock = jest + .spyOn(utilities, "writePodspecContents") + .mockImplementation((path, contents) => { + podSpec = contents; + }); + + const newVersion = await hooks.canary.promise({ + bump: "minor" as Auto.SEMVER, + canaryIdentifier: "canary.1.1.1", + }); + + expect(newVersion).toBe("0.1.0-canary.1.1.1"); + expect(exec).toBeCalledTimes(3); + expect(exec).toHaveBeenCalledWith("git", ["checkout", "./Test.podspec"]); + expect(mock).toHaveBeenLastCalledWith( expect.any(String), specWithVersion( diff --git a/plugins/cocoapods/src/index.ts b/plugins/cocoapods/src/index.ts index 9a4770fd9..da2512cd0 100644 --- a/plugins/cocoapods/src/index.ts +++ b/plugins/cocoapods/src/index.ts @@ -230,14 +230,23 @@ export default class CocoapodsPlugin implements IPlugin { auto.hooks.canary.tapPromise( this.name, async ({ bump, canaryIdentifier, dryRun, quiet }) => { + if (!auto.git) { + return; + } + const pr = getPrNumberFromEnv(); - if (!auto.git || !pr) { - return; + if (!pr) { + this.logger?.log.info( + logMessage( + `No PR number found, using ${auto.remote} as the remote for canary. Commit must be pushed for this to work.` + ) + ); } - const remoteRepo = await (await auto.git.getPullRequest(pr)).data.head - .repo.clone_url; + const remoteRepo = pr + ? await (await auto.git.getPullRequest(pr)).data.head.repo.clone_url + : auto.remote; const lastRelease = await auto.git.getLatestRelease(); const current = await auto.getCurrentVersion(lastRelease);