73 lines
3.7 KiB
TypeScript
73 lines
3.7 KiB
TypeScript
#!/usr/bin/env bun
|
|
// One version for the desktop app. The authority is `desktopVersion` in the root package.json (the npm package's
|
|
// own `version` is the CLI's and moves on its own); it is copied into apps/desktop/package.json,
|
|
// src-tauri/tauri.conf.json, src-tauri/Cargo.toml and the app's entry in src-tauri/Cargo.lock.
|
|
// bun tools/desktop-version.ts check: exit 1 when any copy drifted (CI runs this)
|
|
// bun tools/desktop-version.ts --set 0.1.1 write the new version into the authority and every copy
|
|
// bun tools/desktop-version.ts --print print the version
|
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
|
|
export const ROOT = join(import.meta.dir, '..');
|
|
const TAURI = 'apps/desktop/src-tauri';
|
|
export const SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
|
|
export interface Copy { file: string; read(text: string): string | null; write(text: string, v: string): string }
|
|
|
|
const jsonVersion = (key: string): Pick<Copy, 'read' | 'write'> => ({
|
|
read: t => { const v = (JSON.parse(t) as Record<string, unknown>)[key]; return typeof v === 'string' ? v : null; },
|
|
write: (t, v) => t.replace(new RegExp(`("${key}"\\s*:\\s*)"[^"]*"`), `$1"${v}"`),
|
|
});
|
|
|
|
/** The authority first, then every copy. */
|
|
export const COPIES: Copy[] = [
|
|
{ file: 'package.json', ...jsonVersion('desktopVersion') },
|
|
{ file: 'apps/desktop/package.json', ...jsonVersion('version') },
|
|
{ file: `${TAURI}/tauri.conf.json`, ...jsonVersion('version') },
|
|
{
|
|
file: `${TAURI}/Cargo.toml`,
|
|
// the [package] table's version (the first `version =` line of the file, before any dependency table)
|
|
read: t => /^\[package\][^[]*?^version\s*=\s*"([^"]+)"/ms.exec(t)?.[1] ?? null,
|
|
write: (t, v) => t.replace(/^(\[package\][^[]*?^version\s*=\s*)"[^"]+"/ms, `$1"${v}"`),
|
|
},
|
|
{
|
|
file: `${TAURI}/Cargo.lock`,
|
|
read: t => /^name = "storebound-desktop"\nversion = "([^"]+)"/m.exec(t)?.[1] ?? null,
|
|
write: (t, v) => t.replace(/^(name = "storebound-desktop"\nversion = )"[^"]+"/m, `$1"${v}"`),
|
|
},
|
|
];
|
|
|
|
export interface Drift { file: string; found: string | null }
|
|
|
|
export function versions(root = ROOT): { version: string | null; drift: Drift[] } {
|
|
const [authority, ...copies] = COPIES.map(c => ({ file: c.file, found: c.read(readFileSync(join(root, c.file), 'utf8')) }));
|
|
const version = authority!.found;
|
|
return { version, drift: copies.filter(c => c.found !== version) };
|
|
}
|
|
|
|
export function setVersion(v: string, root = ROOT): void {
|
|
if (!SEMVER.test(v)) throw new Error(`not a version: ${v}`);
|
|
for (const c of COPIES) {
|
|
const p = join(root, c.file);
|
|
const before = readFileSync(p, 'utf8');
|
|
const after = c.write(before, v);
|
|
if (c.read(after) !== v) throw new Error(`could not set the version in ${c.file}`);
|
|
if (after !== before) writeFileSync(p, after);
|
|
}
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
const argv = process.argv.slice(2);
|
|
const i = argv.indexOf('--set');
|
|
if (i >= 0) {
|
|
setVersion(argv[i + 1] ?? '');
|
|
console.log(`desktop version ${argv[i + 1]} written to ${COPIES.map(c => c.file).join(', ')}`);
|
|
process.exit(0);
|
|
}
|
|
const { version, drift } = versions();
|
|
if (argv.includes('--print')) { console.log(version ?? ''); process.exit(version ? 0 : 1); }
|
|
if (!version || !SEMVER.test(version)) { console.error('desktop version: package.json has no valid "desktopVersion"'); process.exit(1); }
|
|
for (const d of drift) console.error(`desktop version drift: ${d.file} has ${d.found ?? 'no version'}, package.json desktopVersion is ${version}`);
|
|
if (drift.length) { console.error('fix: bun tools/desktop-version.ts --set ' + version); process.exit(1); }
|
|
console.log(`desktop version ${version}: ${COPIES.length - 1} copies match`);
|
|
}
|