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.
153 lines
4.6 KiB
JavaScript
153 lines
4.6 KiB
JavaScript
import { readFileSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const gradlePath = join(root, 'android', 'app', 'build.gradle');
|
|
const aabPath = join(root, 'android', 'app', 'build', 'outputs', 'bundle', 'release', 'app-release.aab');
|
|
|
|
const gradle = readFileSync(gradlePath, 'utf8');
|
|
const versionCode =
|
|
process.env.HESYCHIA_VERSION_CODE ||
|
|
gradle.match(/hesychiaVersionCodeFloor\s*=\s*(\d+)/)?.[1];
|
|
const versionName =
|
|
process.env.HESYCHIA_VERSION_NAME ||
|
|
gradle.match(/hesychiaVersionNameFloor\s*=\s*"([^"]+)"/)?.[1];
|
|
if (!versionCode || !versionName) {
|
|
console.error(
|
|
`Could not read versionCode/versionName from the environment or ${gradlePath}`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const tag = `android-v${versionName}-${versionCode}`;
|
|
const assetName = `hesychia-${versionName}-${versionCode}.aab`;
|
|
|
|
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 refName = process.env.GITEA_REF_NAME || process.env.GITHUB_REF_NAME || 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) {
|
|
console.error('Missing required env for Gitea Android release:', {
|
|
apiUrl: Boolean(apiUrl),
|
|
token: Boolean(token),
|
|
repository,
|
|
sha,
|
|
});
|
|
process.exit(1);
|
|
}
|
|
|
|
const [owner, repo] = repository.split('/');
|
|
if (!owner || !repo) {
|
|
console.error(`GITHUB_REPOSITORY must be owner/repo, got ${repository}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
async function api(path, { method = 'GET', json, form } = {}) {
|
|
const headers = {
|
|
Authorization: `token ${token}`,
|
|
Accept: 'application/json',
|
|
};
|
|
let body;
|
|
if (json !== undefined) {
|
|
headers['Content-Type'] = 'application/json';
|
|
body = JSON.stringify(json);
|
|
} else if (form) {
|
|
body = form;
|
|
}
|
|
|
|
const response = await fetch(`${apiUrl}/repos/${owner}/${repo}${path}`, {
|
|
method,
|
|
headers,
|
|
body,
|
|
});
|
|
const text = await response.text();
|
|
let payload = null;
|
|
if (text) {
|
|
try {
|
|
payload = JSON.parse(text);
|
|
} catch {
|
|
payload = text;
|
|
}
|
|
}
|
|
return { status: response.status, payload };
|
|
}
|
|
|
|
function releaseUrl(release) {
|
|
return release?.html_url || release?.url || tag;
|
|
}
|
|
|
|
function hasAsset(release) {
|
|
return Array.isArray(release?.assets) && release.assets.some((asset) => asset.name === assetName);
|
|
}
|
|
|
|
async function uploadAsset(releaseId) {
|
|
const bytes = readFileSync(aabPath);
|
|
const form = new FormData();
|
|
form.append('attachment', new Blob([bytes]), assetName);
|
|
const { status, payload } = await api(
|
|
`/releases/${releaseId}/assets?name=${encodeURIComponent(assetName)}`,
|
|
{ method: 'POST', form },
|
|
);
|
|
if (status < 200 || status >= 300) {
|
|
throw new Error(`Asset upload failed (${status}): ${JSON.stringify(payload)}`);
|
|
}
|
|
}
|
|
|
|
const body = [
|
|
`Play store bundle for the prod deploy of \`${refName}\`.`,
|
|
'',
|
|
`- Commit: \`${sha}\``,
|
|
`- versionName: \`${versionName}\``,
|
|
`- versionCode: \`${versionCode}\``,
|
|
'- Application id: `ai.hesychia.chat`',
|
|
'',
|
|
`Upload \`${assetName}\` to Play Console.`,
|
|
].join('\n');
|
|
|
|
const existing = await api(`/releases/tags/${encodeURIComponent(tag)}`);
|
|
let release = existing.payload;
|
|
|
|
if (existing.status === 404) {
|
|
const created = await api('/releases', {
|
|
method: 'POST',
|
|
json: {
|
|
tag_name: tag,
|
|
target_commitish: sha,
|
|
name: `Android ${versionName} (${versionCode})`,
|
|
body,
|
|
draft: false,
|
|
prerelease: false,
|
|
},
|
|
});
|
|
if (created.status < 200 || created.status >= 300) {
|
|
throw new Error(`Create release failed (${created.status}): ${JSON.stringify(created.payload)}`);
|
|
}
|
|
release = created.payload;
|
|
console.log(`Created ${tag} at ${releaseUrl(release)}`);
|
|
} else if (existing.status < 200 || existing.status >= 300) {
|
|
throw new Error(`Lookup ${tag} failed (${existing.status}): ${JSON.stringify(existing.payload)}`);
|
|
} else if (release.target_commitish !== sha) {
|
|
console.error(
|
|
`${tag} already points at ${release.target_commitish}, not ${sha}. Refusing to replace that Play version.`,
|
|
);
|
|
process.exit(1);
|
|
} else {
|
|
console.log(`${tag} already exists for ${sha}`);
|
|
}
|
|
|
|
if (hasAsset(release)) {
|
|
console.log(`${assetName} already attached: ${releaseUrl(release)}`);
|
|
} else {
|
|
await uploadAsset(release.id);
|
|
console.log(`Attached ${assetName} to ${releaseUrl(release)}`);
|
|
}
|