// Covers the version a repo's header shows without a build: what the project declares, by Koh's // own rule (Koh 3's src/build/version.v), and a build's version over it. import { describe, expect, test } from "bun:test"; import { writeFile } from "node:fs/promises"; import { join } from "node:path"; import { openBucket } from "../src/bucket/reader.ts"; import { syncAll } from "../src/bucket/sync.ts"; import { declaredVersion } from "../src/describe/version.ts"; import { instance } from "./browser.ts"; import { makeRepos, writeRelease } from "./fixture.ts"; const from = (files: Record) => declaredVersion((path) => files[path] ?? null); describe("the declared version", () => { test("reads CR, LF and CRLF lines as Koh does", () => { for (const ending of ["\r", "\n", "\r\n"]) { expect(from({ "v.mod": `Module {${ending}version: '1.2.3'${ending}}` })).toBe("1.2.3"); expect( from({ "app/build.gradle.kts": `android {${ending}versionName = "1.2.3"${ending}}` }), ).toBe("1.2.3"); } }); test("trims only Koh's ASCII whitespace and refuses an invalid first answer", () => { expect(from({ VERSION: " \n\t\v\f\r1.2.3 \n\t\v\f\r" })).toBe("1.2.3"); for (const space of ["\u00a0", "\ufeff"]) { expect( from({ VERSION: `${space}1.2.3${space}`, "Cargo.toml": 'version = "9.0.0"' }), ).toBeNull(); expect( from({ "Info.plist": `CFBundleShortVersionString${space}1.2.3${space}`, "app/build.gradle.kts": 'versionName = "9.0.0"', }), ).toBeNull(); } }); test("is the first source that answers, VERSION first", () => { expect(from({ VERSION: " 2.0.1\n", "package.json": '{ "version": "1.0.0" }' })).toBe("2.0.1"); expect(from({ "package.json": '{\n "name": "x",\n "version": "3.0.199",\n}' })).toBe( "3.0.199", ); expect( from({ "build.zig.zon": '.{\n .version = "0.14.1",\n}', "Cargo.toml": 'version = "9"' }), ).toBe("0.14.1"); expect(from({ "v.mod": "Module {\n\tname: 'koh'\n\tversion: '3.2.27'\n}" })).toBe("3.2.27"); expect(from({ "Cargo.toml": '[package]\nversion = "0.5.0"' })).toBe("0.5.0"); expect( from({ "Info.plist": "CFBundleShortVersionString\n\t 1.4 ", }), ).toBe("1.4"); }); test("reads Gradle's versionName literally, never a suffix, comment or variable", () => { const gradle = (text: string) => from({ "app/build.gradle.kts": text }); expect(gradle('versionNameSuffix = "-dev"\nversionName = "2.17.43"')).toBe("2.17.43"); expect(gradle('// versionName = "1.0"\nversionName "5.2.62"')).toBe("5.2.62"); expect(gradle("versionName = libs.versions.app.get()")).toBeNull(); }); test("skips a source with no literal, and is none when the answer is not a version", () => { expect( from({ "package.json": '"version": pkg.version', "Cargo.toml": 'version = "1.1"' }), ).toBe("1.1"); expect(from({ VERSION: "not a version!", "package.json": '"version": "1.0.0"' })).toBeNull(); expect(from({})).toBeNull(); }); }); test("a repo's header shows its declared version, and a build's over it", async () => { const repos = await makeRepos(["app"]); await writeFile( join(repos.bucketDir, "..", "work", "app", "package.json"), '{\n "name": "app",\n "version": "1.2.3"\n}\n', ); repos.koh("app", "save", "declare a version"); repos.koh("app", "offer"); const inst = await instance(repos.bucketDir); inst.kepr.run("UPDATE repos SET public = 1"); const meta = async () => /

([^<]*)<\/p>/.exec(await (await inst.app.request("/app")).text())?.[1]; expect(await meta()).toStartWith("v1.2.3 · "); const head = JSON.parse(repos.koh("app", "view", "--json")).head_id as string; await writeRelease(repos.bucketDir, "app", { version: "2.0.0", save: head, files: [{ platform: "linux-x86_64", name: "app", bytes: new Uint8Array([1]) }], }); await syncAll({ bucket: openBucket({ kind: "folder", path: repos.bucketDir }), kepr: inst.kepr, mirrorDir: join(inst.dataDir, "mirror"), }); expect(await meta()).toStartWith("v2.0.0 · "); }); test("a BOM in a saved VERSION is preserved and refuses a lower-priority version", async () => { const repos = await makeRepos(["app"]); const work = join(repos.bucketDir, "..", "work", "app"); await writeFile(join(work, "VERSION"), "\ufeff1.2.3"); await writeFile(join(work, "Cargo.toml"), 'version = "9.0.0"'); repos.koh("app", "save", "declare a version with a BOM"); repos.koh("app", "offer"); const inst = await instance(repos.bucketDir); inst.kepr.run("UPDATE repos SET public = 1"); const page = await (await inst.app.request("/app")).text(); const meta = /

([^<]*)<\/p>/.exec(page)?.[1]; expect(meta).toBeDefined(); expect(meta).not.toStartWith("v"); });