mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
* add f-droid metadata support * share version code calculation with website * preserve blockquoted sections in changelogs * refactor(android): simplify stable F-Droid metadata Generate store changelogs only for stable releases and keep native version-code ownership in the existing release module. * test(android): run F-Droid changelog suite in CI --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
328 lines
11 KiB
JavaScript
328 lines
11 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import {
|
|
getFdroidVersionCodes,
|
|
getNativeReleaseVersion,
|
|
} from "../packages/app/native-release-version.js";
|
|
import { formatFdroidChangelog, syncFdroidChangelogs } from "./sync-fdroid-changelogs.mjs";
|
|
|
|
const CHANGELOG_CHARACTER_LIMIT = 500;
|
|
|
|
function withTempRepo(fn, { changelog, version = "0.2.3" }) {
|
|
const tempDir = mkdtempSync(path.join(tmpdir(), "paseo-fdroid-changelogs-"));
|
|
try {
|
|
writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ version }));
|
|
writeFileSync(path.join(tempDir, "CHANGELOG.md"), changelog);
|
|
fn(tempDir);
|
|
} finally {
|
|
rmSync(tempDir, { force: true, recursive: true });
|
|
}
|
|
}
|
|
|
|
function readChangelogFile(dir, versionCode, locale = "en-US") {
|
|
return readFileSync(
|
|
path.join(dir, "fastlane", "metadata", "android", locale, "changelogs", `${versionCode}.txt`),
|
|
"utf8",
|
|
);
|
|
}
|
|
|
|
// The whole point of the shared module: these are the numbers the APKs actually
|
|
// ship with, so the filenames have to track them exactly.
|
|
test("derives one version code per published ABI", () => {
|
|
assert.equal(getNativeReleaseVersion("0.2.2").androidVersionCode, 2002);
|
|
assert.deepEqual(getFdroidVersionCodes("0.2.2"), [
|
|
{ abi: "armeabi-v7a", versionCode: 20021 },
|
|
{ abi: "arm64-v8a", versionCode: 20022 },
|
|
{ abi: "x86", versionCode: 20023 },
|
|
{ abi: "x86_64", versionCode: 20024 },
|
|
]);
|
|
});
|
|
|
|
test("does not write or validate F-Droid changelogs for beta releases", () => {
|
|
withTempRepo(
|
|
(dir) => {
|
|
const result = syncFdroidChangelogs([], { cwd: dir });
|
|
assert.deepEqual(result, {
|
|
contents: null,
|
|
version: "0.2.3-beta.1",
|
|
versionCodes: [],
|
|
written: [],
|
|
});
|
|
assert.equal(existsSync(path.join(dir, "fastlane")), false);
|
|
},
|
|
{ changelog: "", version: "0.2.3-beta.1" },
|
|
);
|
|
});
|
|
|
|
test("rejects versions that would collide or overflow", () => {
|
|
assert.throws(() => getNativeReleaseVersion("0.1000.0"), /collision-free/);
|
|
assert.throws(() => getNativeReleaseVersion("not-a-version"), /unsupported version/);
|
|
assert.throws(() => getNativeReleaseVersion("210.0.0"), /out of range/);
|
|
});
|
|
|
|
test("writes an identical changelog for every ABI version code", () => {
|
|
const changelog = "# Changelog\n\n## 0.2.3 - 2026-07-27\n\n### Fixed\n\n- Something broke.\n";
|
|
withTempRepo(
|
|
(dir) => {
|
|
const result = syncFdroidChangelogs([], { cwd: dir });
|
|
|
|
assert.equal(result.written.length, 4);
|
|
for (const { versionCode } of getFdroidVersionCodes("0.2.3")) {
|
|
assert.equal(readChangelogFile(dir, versionCode), result.contents);
|
|
}
|
|
assert.match(result.contents, /- Something broke\./);
|
|
},
|
|
{ changelog },
|
|
);
|
|
});
|
|
|
|
test("writes the four stable 0.5.0 changelog files", () => {
|
|
const changelog = "## 0.5.0 - 2026-08-21\n\n### Added\n\n- Stable F-Droid metadata.\n";
|
|
withTempRepo(
|
|
(dir) => {
|
|
const result = syncFdroidChangelogs([], { cwd: dir });
|
|
assert.deepEqual(
|
|
result.written.map((entry) => entry.replace(/ \(.+\)$/, "")),
|
|
[
|
|
"fastlane/metadata/android/en-US/changelogs/50001.txt",
|
|
"fastlane/metadata/android/en-US/changelogs/50002.txt",
|
|
"fastlane/metadata/android/en-US/changelogs/50003.txt",
|
|
"fastlane/metadata/android/en-US/changelogs/50004.txt",
|
|
],
|
|
);
|
|
},
|
|
{ changelog, version: "0.5.0" },
|
|
);
|
|
});
|
|
|
|
test("strips PR references and contributor attributions", () => {
|
|
const changelog = [
|
|
"## 0.2.3 - 2026-07-27",
|
|
"",
|
|
"### Added",
|
|
"",
|
|
"- Support for `GitLab` merge requests ([#1913](https://x/1913) by [@nllptrx](https://y))",
|
|
"",
|
|
].join("\n");
|
|
|
|
withTempRepo(
|
|
(dir) => {
|
|
const { contents } = syncFdroidChangelogs([], { cwd: dir });
|
|
assert.equal(contents.includes("#1913"), false);
|
|
assert.equal(contents.includes("nllptrx"), false);
|
|
assert.equal(contents.includes("`"), false);
|
|
assert.match(contents, /- Support for GitLab merge requests\n/);
|
|
},
|
|
{ changelog },
|
|
);
|
|
});
|
|
|
|
test("stays inside the 500 character limit and keeps the full-notes link", () => {
|
|
const bullets = Array.from(
|
|
{ length: 40 },
|
|
(_, index) => `- Shipped improvement number ${index}.`,
|
|
);
|
|
const changelog = ["## 0.2.3 - 2026-07-27", "", "### Added", "", ...bullets, ""].join("\n");
|
|
|
|
withTempRepo(
|
|
(dir) => {
|
|
const { contents } = syncFdroidChangelogs([], { cwd: dir });
|
|
assert.ok(
|
|
contents.length <= CHANGELOG_CHARACTER_LIMIT,
|
|
`expected <= ${CHANGELOG_CHARACTER_LIMIT}, got ${contents.length}`,
|
|
);
|
|
assert.match(contents, /Full notes: https:\/\/paseo\.sh\/changelog\n$/);
|
|
// Truncation happens at a bullet boundary, never mid-sentence.
|
|
assert.equal(contents.includes("Shipped improvement number 0."), true);
|
|
assert.equal(/- Shipped improvement number \d+\.\.\.$/m.test(contents), false);
|
|
},
|
|
{ changelog },
|
|
);
|
|
});
|
|
|
|
test("never leaves an orphan section heading when bullets get cut", () => {
|
|
const filler = Array.from({ length: 20 }, (_, index) => `- Improvement ${index} with some text.`);
|
|
const changelog = [
|
|
"## 0.2.3 - 2026-07-27",
|
|
"",
|
|
"### Improved",
|
|
"",
|
|
...filler,
|
|
"",
|
|
"### Fixed",
|
|
"",
|
|
"- A late fix that will not fit.",
|
|
"",
|
|
].join("\n");
|
|
|
|
withTempRepo(
|
|
(dir) => {
|
|
const { contents } = syncFdroidChangelogs([], { cwd: dir });
|
|
assert.equal(contents.includes("Fixed"), false);
|
|
assert.equal(contents.trimEnd().endsWith("Improved"), false);
|
|
},
|
|
{ changelog },
|
|
);
|
|
});
|
|
|
|
test("salvages a truncated first bullet rather than emitting only a link", () => {
|
|
const contents = formatFdroidChangelog(["### Added", `- ${"x".repeat(600)}`]);
|
|
assert.ok(contents.length <= CHANGELOG_CHARACTER_LIMIT);
|
|
assert.match(contents, /^- x+\.\.\.\n\nFull notes:/);
|
|
});
|
|
|
|
// Releases use blockquotes for action-required notices. Dropping one silently is
|
|
// worse than dropping any bullet, so these pin the behaviour.
|
|
test("preserves a blockquoted notice ahead of the bullets", () => {
|
|
const contents = formatFdroidChangelog([
|
|
"> **Important update notice**",
|
|
">",
|
|
"> If you installed Paseo Desktop 0.1.108, you need to [reinstall manually](https://paseo.sh/download).",
|
|
"",
|
|
"### Fixed",
|
|
"",
|
|
"- Desktop no longer gets stuck connecting.",
|
|
]);
|
|
|
|
assert.match(
|
|
contents,
|
|
/^Important update notice\nIf you installed Paseo Desktop 0\.1\.108, you need to reinstall manually\.\n\nFixed\n- Desktop no longer gets stuck connecting\./,
|
|
);
|
|
assert.ok(contents.length <= CHANGELOG_CHARACTER_LIMIT);
|
|
});
|
|
|
|
test("keeps the notice whole and cuts bullets when the budget runs out", () => {
|
|
const notice = `- ${"y".repeat(10)}`;
|
|
const bullets = Array.from({ length: 30 }, () => `- ${"b".repeat(60)}`);
|
|
const contents = formatFdroidChangelog([
|
|
"> Reinstall Paseo manually before updating, the old updater cannot install this build.",
|
|
"",
|
|
"### Fixed",
|
|
"",
|
|
...bullets,
|
|
notice,
|
|
]);
|
|
|
|
assert.ok(contents.length <= CHANGELOG_CHARACTER_LIMIT);
|
|
assert.match(
|
|
contents,
|
|
/^Reinstall Paseo manually before updating, the old updater cannot install this build\.\n/,
|
|
);
|
|
// The notice survived intact, so some bullets must have been dropped.
|
|
assert.ok(contents.split("\n").filter((line) => line.startsWith("- b")).length < bullets.length);
|
|
});
|
|
|
|
test("truncates an over-long notice instead of dropping it", () => {
|
|
const contents = formatFdroidChangelog([`> ${"n".repeat(900)}`, "", "### Fixed", "", "- A fix."]);
|
|
|
|
assert.ok(contents.length <= CHANGELOG_CHARACTER_LIMIT);
|
|
assert.match(contents, /^n+\.\.\.\n\nFull notes:/);
|
|
assert.equal(contents.includes("A fix."), false);
|
|
});
|
|
|
|
test("captures multiple blockquotes and blockquotes that follow sections", () => {
|
|
const contents = formatFdroidChangelog([
|
|
"> First notice.",
|
|
"",
|
|
"### Fixed",
|
|
"",
|
|
"- A fix.",
|
|
"",
|
|
"> Second notice.",
|
|
]);
|
|
|
|
assert.match(contents, /^First notice\.\n\nSecond notice\.\n\nFixed\n- A fix\./);
|
|
});
|
|
|
|
test("renders the real 0.1.109 notice, which is the entry's whole point", () => {
|
|
const changelog = [
|
|
"## 0.1.109 - 2026-07-16",
|
|
"",
|
|
"> **Important update notice**",
|
|
">",
|
|
"> If you installed Paseo Desktop 0.1.108, you need to [download and reinstall Paseo manually](https://paseo.sh/download) to get this fix. The bug in 0.1.108 prevents its automatic updater from installing 0.1.109. Users on 0.1.107 or earlier can update normally.",
|
|
"",
|
|
"### Fixed",
|
|
"",
|
|
"- Paseo Desktop no longer gets stuck connecting or loses native window controls after updating ([#2111](https://github.com/getpaseo/paseo/pull/2111) by [@cleiter](https://github.com/cleiter))",
|
|
"",
|
|
].join("\n");
|
|
|
|
withTempRepo(
|
|
(dir) => {
|
|
const { contents } = syncFdroidChangelogs([], { cwd: dir });
|
|
assert.ok(contents.length <= CHANGELOG_CHARACTER_LIMIT);
|
|
assert.match(contents, /^Important update notice\n/);
|
|
assert.match(contents, /download and reinstall Paseo manually/);
|
|
assert.match(contents, /Users on 0\.1\.107 or earlier can update normally\./);
|
|
assert.match(contents, /\nFixed\n- Paseo Desktop no longer gets stuck connecting/);
|
|
assert.equal(contents.includes("#2111"), false);
|
|
},
|
|
{ changelog, version: "0.1.109" },
|
|
);
|
|
});
|
|
|
|
test("preserves the standalone 0.1.96 Android notice", () => {
|
|
const changelog = readFileSync(path.resolve("CHANGELOG.md"), "utf8");
|
|
const entry = changelog.match(/## 0\.1\.96 - 2026-06-13\n([\s\S]*?)(?=\n## |$)/)?.[1];
|
|
assert.ok(entry);
|
|
|
|
const contents = formatFdroidChangelog(entry.split("\n"));
|
|
assert.match(contents, /This release only fixes an Android issue/);
|
|
assert.equal(contents.includes("_This release"), false);
|
|
});
|
|
|
|
test("--check fails when the generated files are missing or stale", () => {
|
|
const changelog = "## 0.2.3 - 2026-07-27\n\n### Fixed\n\n- Something broke.\n";
|
|
withTempRepo(
|
|
(dir) => {
|
|
assert.throws(() => syncFdroidChangelogs(["--check"], { cwd: dir }), /out of date/);
|
|
|
|
syncFdroidChangelogs([], { cwd: dir });
|
|
assert.doesNotThrow(() => syncFdroidChangelogs(["--check"], { cwd: dir }));
|
|
|
|
const [{ versionCode }] = getFdroidVersionCodes("0.2.3");
|
|
writeFileSync(
|
|
path.join(
|
|
dir,
|
|
"fastlane",
|
|
"metadata",
|
|
"android",
|
|
"en-US",
|
|
"changelogs",
|
|
`${versionCode}.txt`,
|
|
),
|
|
"drifted\n",
|
|
);
|
|
assert.throws(() => syncFdroidChangelogs(["--check"], { cwd: dir }), /out of date/);
|
|
},
|
|
{ changelog },
|
|
);
|
|
});
|
|
|
|
test("fails loudly when the release has no changelog entry", () => {
|
|
const changelog = "## 0.2.2 - 2026-07-25\n\n### Fixed\n\n- Older release.\n";
|
|
withTempRepo(
|
|
(dir) => {
|
|
assert.throws(() => syncFdroidChangelogs([], { cwd: dir }), /No CHANGELOG\.md entry/);
|
|
},
|
|
{ changelog, version: "0.2.3" },
|
|
);
|
|
});
|
|
|
|
test("honours a non-default locale directory", () => {
|
|
const changelog = "## 0.2.3 - 2026-07-27\n\n### Fixed\n\n- Something broke.\n";
|
|
withTempRepo(
|
|
(dir) => {
|
|
mkdirSync(path.join(dir, "fastlane", "metadata", "android", "ja"), { recursive: true });
|
|
syncFdroidChangelogs(["--locale", "ja"], { cwd: dir });
|
|
const [{ versionCode }] = getFdroidVersionCodes("0.2.3");
|
|
assert.match(readChangelogFile(dir, versionCode, "ja"), /- Something broke\./);
|
|
},
|
|
{ changelog },
|
|
);
|
|
});
|