Unit Tests / test (pull_request) Successful in 14s
Closes #121. Supersedes the separate Deploy Android workflow in #58 by attaching hesychia-<version>-<code>.aab to an android-v* Gitea release after the prod web deploy.
143 lines
4.7 KiB
JavaScript
143 lines
4.7 KiB
JavaScript
import { appendFileSync, readFileSync, realpathSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
|
const ANDROID_TAG = /^android-v(\d+\.\d+\.\d+)-(\d+)$/;
|
|
|
|
export function parseAndroidTag(tag) {
|
|
const match = ANDROID_TAG.exec(tag || '');
|
|
if (!match) return null;
|
|
return { versionName: match[1], versionCode: Number(match[2]) };
|
|
}
|
|
|
|
export function bumpPatch(versionName) {
|
|
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(versionName);
|
|
if (!match) {
|
|
throw new Error(`versionName must be major.minor.patch, got ${versionName}`);
|
|
}
|
|
return `${match[1]}.${match[2]}.${Number(match[3]) + 1}`;
|
|
}
|
|
|
|
export function readBaseline(gradle) {
|
|
const versionCode = Number(gradle.match(/hesychiaVersionCodeFloor\s*=\s*(\d+)/)?.[1]);
|
|
const versionName = gradle.match(/hesychiaVersionNameFloor\s*=\s*"([^"]+)"/)?.[1];
|
|
if (!Number.isInteger(versionCode) || !versionName) {
|
|
throw new Error(
|
|
'Could not read hesychiaVersionCodeFloor/hesychiaVersionNameFloor from build.gradle',
|
|
);
|
|
}
|
|
return { versionCode, versionName };
|
|
}
|
|
|
|
export function nextAndroidVersion({ baseline, releases, sha }) {
|
|
const existing = releases.find((release) => release.sha === sha);
|
|
if (existing) {
|
|
return {
|
|
versionName: existing.versionName,
|
|
versionCode: existing.versionCode,
|
|
reused: true,
|
|
};
|
|
}
|
|
|
|
const highest = releases.reduce((best, release) => {
|
|
if (!best || release.versionCode > best.versionCode) return release;
|
|
return best;
|
|
}, null);
|
|
|
|
const codeBase = Math.max(baseline.versionCode, highest?.versionCode ?? baseline.versionCode);
|
|
const nameBase =
|
|
highest && highest.versionCode >= baseline.versionCode
|
|
? highest.versionName
|
|
: baseline.versionName;
|
|
|
|
return {
|
|
versionName: bumpPatch(nameBase),
|
|
versionCode: codeBase + 1,
|
|
reused: false,
|
|
};
|
|
}
|
|
|
|
export async function listAndroidReleases({ apiUrl, token, owner, repo, fetchImpl = fetch }) {
|
|
const releases = [];
|
|
for (let page = 1; page <= 20; page += 1) {
|
|
const response = await fetchImpl(
|
|
`${apiUrl}/repos/${owner}/${repo}/releases?limit=50&page=${page}`,
|
|
{
|
|
headers: {
|
|
Authorization: `token ${token}`,
|
|
Accept: 'application/json',
|
|
},
|
|
},
|
|
);
|
|
if (!response.ok) {
|
|
throw new Error(`List releases failed (${response.status})`);
|
|
}
|
|
const payload = await response.json();
|
|
const batch = Array.isArray(payload) ? payload : [];
|
|
for (const release of batch) {
|
|
const parsed = parseAndroidTag(release.tag_name);
|
|
if (!parsed) continue;
|
|
releases.push({ ...parsed, sha: release.target_commitish });
|
|
}
|
|
if (batch.length < 50) break;
|
|
}
|
|
return releases;
|
|
}
|
|
|
|
function releaseEnv() {
|
|
const token = process.env.GITEA_TOKEN || process.env.GITHUB_TOKEN;
|
|
const repository = process.env.GITHUB_REPOSITORY || process.env.GITEA_REPOSITORY;
|
|
const sha = process.env.GITEA_SHA || process.env.GITHUB_SHA;
|
|
const apiUrl = (
|
|
process.env.GITHUB_API_URL ||
|
|
process.env.GITEA_API_URL ||
|
|
(process.env.GITHUB_SERVER_URL
|
|
? `${process.env.GITHUB_SERVER_URL.replace(/\/$/, '')}/api/v1`
|
|
: '')
|
|
).replace(/\/$/, '');
|
|
|
|
if (!token || !repository || !sha || !apiUrl) {
|
|
throw new Error('Missing Gitea token, repository, sha, or API URL for the Play version lookup');
|
|
}
|
|
const [owner, repo] = repository.split('/');
|
|
if (!owner || !repo) {
|
|
throw new Error(`Repository must be owner/repo, got ${repository}`);
|
|
}
|
|
return { token, owner, repo, sha, apiUrl };
|
|
}
|
|
|
|
export async function resolveAndroidVersion() {
|
|
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const gradle = readFileSync(join(root, 'android', 'app', 'build.gradle'), 'utf8');
|
|
const baseline = readBaseline(gradle);
|
|
const { token, owner, repo, sha, apiUrl } = releaseEnv();
|
|
const releases = await listAndroidReleases({ apiUrl, token, owner, repo });
|
|
return nextAndroidVersion({ baseline, releases, sha });
|
|
}
|
|
|
|
function isMainModule() {
|
|
const entry = process.argv[1];
|
|
if (!entry) return false;
|
|
return import.meta.url === pathToFileURL(realpathSync(entry)).href;
|
|
}
|
|
|
|
if (isMainModule()) {
|
|
resolveAndroidVersion()
|
|
.then((next) => {
|
|
const lines = [
|
|
`HESYCHIA_VERSION_NAME=${next.versionName}`,
|
|
`HESYCHIA_VERSION_CODE=${next.versionCode}`,
|
|
];
|
|
if (process.env.GITHUB_ENV) {
|
|
appendFileSync(process.env.GITHUB_ENV, `${lines.join('\n')}\n`);
|
|
}
|
|
console.log(lines.join('\n'));
|
|
const action = next.reused ? 'Reusing' : 'Next Play version is';
|
|
console.error(`${action} ${next.versionName} (${next.versionCode})`);
|
|
})
|
|
.catch((error) => {
|
|
console.error(error instanceof Error ? error.message : error);
|
|
process.exit(1);
|
|
});
|
|
}
|