version.ts

3.2 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}$/;13const ASCII_SPACE = /^[ \n\t\v\f\r]+|[ \n\t\v\f\r]+$/g;1415/** The declared version, reading files at one save through `read` (null for a missing file). */16export function declaredVersion(read: (path: string) => string | null): string | null {17	const sources: (() => string | null)[] = [18		() => read("VERSION")?.replace(ASCII_SPACE, "") || null,19		() => lineVersion(read("build.zig.zon"), ".version"),20		() => lineVersion(read("v.mod"), "version"),21		() => lineVersion(read("package.json"), '"version"'),22		() => lineVersion(read("Cargo.toml"), "version"),23		() => plistVersion(read("Info.plist")),24		...GRADLE_FILES.map((path) => () => gradleVersion(read(path))),25	];26	for (const source of sources) {27		const value = source();28		if (value !== null) return VERSION.test(value) ? value : null;29	}30	return null;31}3233// The value of the first line the key starts that assigns a quoted literal.34function lineVersion(text: string | null, key: string): string | null {35	for (const line of text?.split(/\r\n|[\r\n]/) ?? []) {36		const trimmed = line.replace(/^[ \t]+/, "");37		if (trimmed === "" || trimmed.startsWith("//") || trimmed.startsWith("#")) continue;38		if (!trimmed.startsWith(key)) continue;39		const value = literalAfter(trimmed, key.length);40		if (value !== null) return value;41	}42	return null;43}4445// The quoted literal right after the key, an optional `=` or `:` and any spaces.46function literalAfter(line: string, from: number): string | null {47	let at = from;48	while (at < line.length && " \t=:".includes(line[at] as string)) at++;49	const quote = line[at];50	if (quote !== '"' && quote !== "'") return null;51	const end = line.indexOf(quote, at + 1);52	return end < 0 ? null : line.slice(at + 1, end);53}5455// CFBundleShortVersionString: its value is in the <string> after the key, not on the key's line.56function plistVersion(text: string | null): string | null {57	const at = text?.indexOf("<key>CFBundleShortVersionString</key>") ?? -1;58	if (!text || at < 0) return null;59	const rest = text.slice(at);60	const open = rest.indexOf("<string>");61	const close = rest.indexOf("</string>");62	if (open < 0 || close <= open + "<string>".length) return null;63	return rest.slice(open + "<string>".length, close).replace(ASCII_SPACE, "") || null;64}6566// `versionName` assigned a quoted literal, Kotlin (`= "1.2"`) or Groovy (`"1.2"`); comments and67// `versionNameSuffix` are not it.68function gradleVersion(text: string | null): string | null {69	for (const line of text?.split(/\r\n|[\r\n]/) ?? []) {70		const trimmed = line.replace(/^[ \t]+/, "");71		if (trimmed === "" || /^(\/\/|\/\*|\*)/.test(trimmed)) continue;72		if (!/^versionName[ =]/.test(trimmed)) continue;73		const value = literalAfter(trimmed, "versionName".length);74		if (value !== null) return value;75	}76	return null;77}