diff --git a/lib/helpers/regexes.js b/lib/helpers/regexes.js index 1cb83d7..1814f7a 100644 --- a/lib/helpers/regexes.js +++ b/lib/helpers/regexes.js @@ -11,7 +11,16 @@ const semVerRegexGroup = new RegExp("(" + semVerRegex.source + ")"); * see {@link https://github.com/Talent-Ideal/semantic-release-hex/#supported-version-formats Supported version formats} */ export const versionRegexesArray = [ - composeSemVerRegex(/\bversion:\s*"/, /"/), + /** + * matches `version: "x.x.x"` only inside "def project" + */ + composeSemVerRegex( + /(?<=\bdef project\b)(?:(?!\bend\b).)+?\bversion:\s*"/, + /".+?(?=end)/, + ), + /** + * matches `@version "x.x.x"` + */ composeSemVerRegex(/@version\s+"/, /"/), ]; @@ -20,6 +29,7 @@ export const versionRegexesArray = [ */ export const versionRegex = new RegExp( versionRegexesArray.map((r) => r.source).join("|"), + "s", ); /** @@ -58,7 +68,7 @@ export function replaceVersionInContent(content, version) { * @returns {RegExp} */ function composeSemVerRegex(head, tail) { - return new RegExp(head.source + semVerRegexGroup.source + tail.source); + return new RegExp(head.source + semVerRegexGroup.source + tail.source, "s"); } /** diff --git a/lib/helpers/regexes.spec.js b/lib/helpers/regexes.spec.js index 0ef2ce7..346d61e 100644 --- a/lib/helpers/regexes.spec.js +++ b/lib/helpers/regexes.spec.js @@ -2,25 +2,40 @@ import { invalidSemVers, validSemVers, } from "../../tests/fixtures/regexes.fixture.js"; +import { DEF_P_1, DEF_P_2 } from "../../tests/helpers/test.constants.js"; import { replaceVersionInContent, versionRegex } from "./regexes.js"; describe("replaceVersionInContent", () => { it("should replace the version part of a match", () => { - expect(replaceVersionInContent(`version: "0.0.0-dev"`, "1.0.0")).toBe( - `version: "1.0.0"`, - ); - expect(replaceVersionInContent(`@version "0.0.0-dev"`, "1.0.0")).toBe( - `@version "1.0.0"`, + expect( + replaceVersionInContent( + `${DEF_P_1}version: "0.0.0-dev"${DEF_P_2}`, + "1.0.0", + ), + ).toBe(`${DEF_P_1}version: "1.0.0"${DEF_P_2}`); + + expect(replaceVersionInContent('@version "0.0.0-dev"', "1.0.0")).toBe( + '@version "1.0.0"', ); }); + it("should not replace the version outside of the project definition", () => { + expect(() => + replaceVersionInContent('version: "0.0.0-dev"', "1.0.0"), + ).toThrow(); + }); + it("should preserve indentation and newline", () => { expect( - replaceVersionInContent(`\n version: "0.0.0-dev" \n`, "1.0.0"), - ).toBe(`\n version: "1.0.0" \n`); + replaceVersionInContent( + `\n ${DEF_P_1}\n version: "0.0.0-dev"\n ${DEF_P_2} \n`, + "1.0.0", + ), + ).toBe(`\n ${DEF_P_1}\n version: "1.0.0"\n ${DEF_P_2} \n`); + expect( - replaceVersionInContent(`\n @version "0.0.0-dev" \n`, "1.0.0"), - ).toBe(`\n @version "1.0.0" \n`); + replaceVersionInContent('\n @version "0.0.0-dev" \n', "1.0.0"), + ).toBe('\n @version "1.0.0" \n'); }); it("should throw if no match is found", () => { @@ -34,9 +49,13 @@ describe("versionRegex", () => { expect.assertions(validSemVers.length * 6); for (let semVer of validSemVers) { - expect(`version:"${semVer}"`).toMatch(versionRegex); - expect(` version: "${semVer}" ,`).toMatch(versionRegex); - expect(` version: "${semVer}" ,`).toMatch(versionRegex); + expect(`${DEF_P_1}version:"${semVer}"${DEF_P_2}`).toMatch(versionRegex); + expect(`${DEF_P_1} version: "${semVer}" ,${DEF_P_2}`).toMatch( + versionRegex, + ); + expect(`${DEF_P_1} version: "${semVer}" ,${DEF_P_2}`).toMatch( + versionRegex, + ); expect(`@version "${semVer}"`).toMatch(versionRegex); expect(` @version "${semVer}"`).toMatch(versionRegex); @@ -46,24 +65,30 @@ describe("versionRegex", () => { it("should not match invalid values", () => { // eslint-disable-next-line jest/prefer-expect-assertions - expect.assertions(validSemVers.length * 10 + invalidSemVers.length * 2); + expect.assertions(validSemVers.length * 12 + invalidSemVers.length * 2); for (let semVer of validSemVers) { + expect(`version:"${semVer}"`).not.toMatch(versionRegex); + expect(` version: "${semVer}" ,`).not.toMatch(versionRegex); + expect(` version: "${semVer}" ,`).not.toMatch(versionRegex); + expect(`version: ${semVer}`).not.toMatch(versionRegex); expect(`version "${semVer}"`).not.toMatch(versionRegex); expect(`versin: "${semVer}",`).not.toMatch(versionRegex); - expect(`"~> ${semVer}"`).not.toMatch(versionRegex); - expect(`tag: "${semVer}",`).not.toMatch(versionRegex); expect(`@version ${semVer}`).not.toMatch(versionRegex); expect(`@version"${semVer}"`).not.toMatch(versionRegex); expect(`@versin "${semVer}"`).not.toMatch(versionRegex); - expect(`"~> ${semVer}"`).not.toMatch(versionRegex); + expect(`@tag "${semVer}"`).not.toMatch(versionRegex); + expect(`tag: "${semVer}",`).not.toMatch(versionRegex); + expect(`"~> ${semVer}"`).not.toMatch(versionRegex); } for (let semVer of invalidSemVers) { - expect(`version: "${semVer}"`).not.toMatch(versionRegex); + expect(`${DEF_P_1}version: "${semVer}"${DEF_P_2}`).not.toMatch( + versionRegex, + ); expect(`@version "${semVer}"`).not.toMatch(versionRegex); } diff --git a/tests/fixtures/mix-attribute-trap.exs b/tests/fixtures/mix-attribute-trap.exs new file mode 100644 index 0000000..6ac653c --- /dev/null +++ b/tests/fixtures/mix-attribute-trap.exs @@ -0,0 +1,46 @@ +defmodule HelloWorld.MixProject do + use Mix.Project + + @version "{{VERSION}}" + + def some_config do + [ + app: :hello_world, + version: "1.2.3", + some_config_key: "some string" + ] + end + + def project do + [ + app: :hello_world, + version: @version, + elixir: "~> 1.15", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + def some_other_config do + [ + app: :hello_world, + version: "4.5.6", + some_other_config_key: "some other string" + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:dep_from_hexpm, "~> 0.3.0"}, + {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/tests/fixtures/mix-attribute.exs b/tests/fixtures/mix-attribute.exs index 29a44da..cd3331d 100644 --- a/tests/fixtures/mix-attribute.exs +++ b/tests/fixtures/mix-attribute.exs @@ -23,8 +23,8 @@ defmodule HelloWorld.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - # {:dep_from_hexpm, "~> 0.3.0"}, - # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + {:dep_from_hexpm, "~> 0.3.0"}, + {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} ] end end diff --git a/tests/fixtures/mix-regular-trap.exs b/tests/fixtures/mix-regular-trap.exs new file mode 100644 index 0000000..a56efd5 --- /dev/null +++ b/tests/fixtures/mix-regular-trap.exs @@ -0,0 +1,44 @@ +defmodule HelloWorld.MixProject do + use Mix.Project + + def some_config do + [ + app: :hello_world, + version: "1.2.3", + some_config_key: "some string" + ] + end + + def project do + [ + app: :hello_world, + version: "{{VERSION}}", + elixir: "~> 1.15", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + def some_other_config do + [ + app: :hello_world, + version: "4.5.6", + some_other_config_key: "some other string" + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:dep_from_hexpm, "~> 0.3.0"}, + {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/tests/fixtures/mix-regular.exs b/tests/fixtures/mix-regular.exs index 094b2e7..099924c 100644 --- a/tests/fixtures/mix-regular.exs +++ b/tests/fixtures/mix-regular.exs @@ -21,8 +21,8 @@ defmodule HelloWorld.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - # {:dep_from_hexpm, "~> 0.3.0"}, - # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + {:dep_from_hexpm, "~> 0.3.0"}, + {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} ] end end diff --git a/tests/helpers/create-test-project.js b/tests/helpers/create-test-project.js index f896e5d..b838df2 100644 --- a/tests/helpers/create-test-project.js +++ b/tests/helpers/create-test-project.js @@ -14,18 +14,19 @@ import { temporaryDirectory } from "tempy"; * * @param {string | null} [version] initial version to set in mix.exs (empty if not provided) * @param {boolean | null} [asAttribute] whether to set the version as a module attribute + * @param {"trap" | null} [suffix] optional mix fixture file suffix * @returns {Project} */ -export function createTestProject(version, asAttribute) { +export function createTestProject(version, asAttribute, suffix) { + const versionType = "-" + (asAttribute ? "attribute" : "regular"); + const fixtureSuffix = suffix ? `-${suffix}` : ""; + const cwd = temporaryDirectory(); const projectPath = path.resolve(cwd, "mix.exs"); const projectContent = fs - .readFileSync( - `./tests/fixtures/mix-${asAttribute ? "attribute" : "regular"}.exs`, - { - encoding: "utf-8", - }, - ) + .readFileSync(`./tests/fixtures/mix${versionType}${fixtureSuffix}.exs`, { + encoding: "utf-8", + }) .replace("{{VERSION}}", version ?? ""); fs.writeFileSync(projectPath, projectContent); diff --git a/tests/helpers/read-project-version.spec.js b/tests/helpers/read-project-version.spec.js index bcf403a..7e86d59 100644 --- a/tests/helpers/read-project-version.spec.js +++ b/tests/helpers/read-project-version.spec.js @@ -1,8 +1,9 @@ import { readProjectVersion } from "./read-project-version.js"; +import { DEF_P_1, DEF_P_2 } from "./test.constants.js"; describe("readProjectVersion", () => { it("should return version and subparts when match", () => { - const simple = readProjectVersion('version: "0.0.4"'); + const simple = readProjectVersion(`${DEF_P_1}version: "0.0.4"${DEF_P_2}`); expect(simple.version).toBe("0.0.4"); expect(simple.major).toBe("0"); expect(simple.minor).toBe("0"); @@ -10,7 +11,9 @@ describe("readProjectVersion", () => { expect(simple.prerelease).toBeUndefined(); expect(simple.metadata).toBeUndefined(); - const prerelease = readProjectVersion('version: "1.0.0-alpha"'); + const prerelease = readProjectVersion( + `${DEF_P_1}version: "1.0.0-alpha"${DEF_P_2}`, + ); expect(prerelease.version).toBe("1.0.0-alpha"); expect(prerelease.major).toBe("1"); expect(prerelease.minor).toBe("0"); diff --git a/tests/helpers/test.constants.js b/tests/helpers/test.constants.js new file mode 100644 index 0000000..dfc2142 --- /dev/null +++ b/tests/helpers/test.constants.js @@ -0,0 +1,2 @@ +export const DEF_P_1 = "def project do\n["; +export const DEF_P_2 = "]\nend"; diff --git a/tests/prepare.test.js b/tests/prepare.test.js index de49b66..8de8e7a 100644 --- a/tests/prepare.test.js +++ b/tests/prepare.test.js @@ -32,11 +32,11 @@ describe("prepare", () => { } }); - it("should update version in mix.exs", async () => { - expect.assertions(4); + it("should update project version in mix.exs", async () => { + expect.assertions(6); for (let asAttribute of [false, true]) { - const { cwd, path } = createTestProject("0.0.1-dev", asAttribute); + const { cwd, path } = createTestProject("0.0.0-dev", asAttribute); await prepare( {}, @@ -50,6 +50,35 @@ describe("prepare", () => { const packageContent = fs.readFileSync(path, { encoding: "utf-8" }); expect(packageContent).toMatch(versionRegex); + expect(packageContent).not.toMatch(/0\.0\.0-dev/); + const { version } = readProjectVersion(packageContent); + expect(version).toBe("1.0.0"); + } + }); + + it("should not update the version outside of the project definition in mix.exs", async () => { + expect.assertions(10); + + for (let asAttribute of [false, true]) { + const { cwd, path } = createTestProject("0.0.0-dev", asAttribute, "trap"); + + await prepare( + {}, + { + ...context, + cwd, + nextRelease: { version: "1.0.0" }, + }, + ); + + const packageContent = fs.readFileSync(path, { encoding: "utf-8" }); + + // should still contain the versions in some_config and some_other_config + expect(packageContent).toMatch(/1\.2\.3/); + expect(packageContent).toMatch(/4\.5\.6/); + + expect(packageContent).toMatch(versionRegex); + expect(packageContent).not.toMatch(/0\.0\.0-dev/); const { version } = readProjectVersion(packageContent); expect(version).toBe("1.0.0"); } @@ -84,7 +113,7 @@ describe("prepare", () => { expect.assertions(4); for (let asAttribute of [false, true]) { - const { cwd } = createTestProject("0.0.1-dev", asAttribute); + const { cwd } = createTestProject("0.0.0-dev", asAttribute); await prepare( {},