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

fix: match version only in project definition #19

Merged
merged 5 commits into from
Nov 21, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions lib/helpers/regexes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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+"/, /"/),
];

Expand All @@ -20,6 +29,7 @@ export const versionRegexesArray = [
*/
export const versionRegex = new RegExp(
versionRegexesArray.map((r) => r.source).join("|"),
"s",
);

/**
Expand Down Expand Up @@ -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");
}

/**
Expand Down
59 changes: 42 additions & 17 deletions lib/helpers/regexes.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand All @@ -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);
Expand All @@ -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);
}
Expand Down
46 changes: 46 additions & 0 deletions tests/fixtures/mix-attribute-trap.exs
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions tests/fixtures/mix-attribute.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
44 changes: 44 additions & 0 deletions tests/fixtures/mix-regular-trap.exs
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions tests/fixtures/mix-regular.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
15 changes: 8 additions & 7 deletions tests/helpers/create-test-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions tests/helpers/read-project-version.spec.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
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");
expect(simple.patch).toBe("4");
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");
Expand Down
2 changes: 2 additions & 0 deletions tests/helpers/test.constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const DEF_P_1 = "def project do\n[";
export const DEF_P_2 = "]\nend";
37 changes: 33 additions & 4 deletions tests/prepare.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
{},
Expand All @@ -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");
}
Expand Down Expand Up @@ -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(
{},
Expand Down