version.ts

3.1 KB · raw

1// The version a project declares, by Koh's own rule (`src/build/version.v` in Koh 3): the first2// source that answers wins, so a `VERSION` file overrides everything, and nothing is evaluated: a3// value built from a variable or a call is skipped and the next source tried. A source that answers4// with something that is not a version means no version, as Koh then refuses to publish.56const GRADLE_FILES = [7	"app/build.gradle.kts",8	"app/build.gradle",9	"build.gradle.kts",10	"build.gradle",11];12const VERSION = /^[A-Za-z0-9.-]{1,32}$/;1314/** The declared version, reading files at one save through `read` (null for a missing file). */15export function declaredVersion(read: (path: string) => string | null): string | null {16	const sources: (() => string | null)[] = [17		() => read("VERSION")?.trim() || null,18		() => lineVersion(read("build.zig.zon"), ".version"),19		() => lineVersion(read("v.mod"), "version"),20		() => lineVersion(read("package.json"), '"version"'),21		() => lineVersion(read("Cargo.toml"), "version"),22		() => plistVersion(read("Info.plist")),23		...GRADLE_FILES.map((path) => () => gradleVersion(read(path))),24	];25	for (const source of sources) {26		const value = source();27		if (value !== null) return VERSION.test(value) ? value : null;28	}29	return null;30}3132// The value of the first line the key starts that assigns a quoted literal.33function lineVersion(text: string | null, key: string): string | null {34	for (const line of text?.split(/\r?\n/) ?? []) {35		const trimmed = line.replace(/^[ \t]+/, "");36		if (trimmed === "" || trimmed.startsWith("//") || trimmed.startsWith("#")) continue;37		if (!trimmed.startsWith(key)) continue;38		const value = literalAfter(trimmed, key.length);39		if (value !== null) return value;40	}41	return null;42}4344// The quoted literal right after the key, an optional `=` or `:` and any spaces.45function literalAfter(line: string, from: number): string | null {46	let at = from;47	while (at < line.length && " \t=:".includes(line[at] as string)) at++;48	const quote = line[at];49	if (quote !== '"' && quote !== "'") return null;50	const end = line.indexOf(quote, at + 1);51	return end < 0 ? null : line.slice(at + 1, end);52}5354// CFBundleShortVersionString: its value is in the <string> after the key, not on the key's line.55function plistVersion(text: string | null): string | null {56	const at = text?.indexOf("<key>CFBundleShortVersionString</key>") ?? -1;57	if (!text || at < 0) return null;58	const rest = text.slice(at);59	const open = rest.indexOf("<string>");60	const close = rest.indexOf("</string>");61	if (open < 0 || close <= open + "<string>".length) return null;62	return rest.slice(open + "<string>".length, close).trim() || null;63}6465// `versionName` assigned a quoted literal, Kotlin (`= "1.2"`) or Groovy (`"1.2"`); comments and66// `versionNameSuffix` are not it.67function gradleVersion(text: string | null): string | null {68	for (const line of text?.split(/\r?\n/) ?? []) {69		const trimmed = line.replace(/^[ \t]+/, "");70		if (trimmed === "" || /^(\/\/|\/\*|\*)/.test(trimmed)) continue;71		if (!/^versionName[ =]/.test(trimmed)) continue;72		const value = literalAfter(trimmed, "versionName".length);73		if (value !== null) return value;74	}75	return null;76}