Files
go/release.js

189 lines
7.0 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const https = require('https');
const { execSync } = require('child_process');
const PROJECT_ID = process.env.CI_PROJECT_ID || 'vyla-entertainment%2Fgo';
const TOKEN = process.env.GITLAB_TOKEN || process.env.VYLA_GITLAB_TOKEN;
if (!TOKEN) {
console.error('Error: GITLAB_TOKEN or VYLA_GITLAB_TOKEN is required.');
process.exit(1);
}
function incrementVersion() {
const pkgPath = path.join(__dirname, 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
let [major, minor, patch] = (pkg.version || '1.0.0').split('.').map(Number);
patch = (patch || 0) + 1;
const newVersion = `${major}.${minor}.${patch}`;
pkg.version = newVersion;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
const tauriPath = path.join(__dirname, 'src-tauri', 'tauri.conf.json');
if (fs.existsSync(tauriPath)) {
const tauriConf = JSON.parse(fs.readFileSync(tauriPath, 'utf8'));
tauriConf.version = newVersion;
fs.writeFileSync(tauriPath, JSON.stringify(tauriConf, null, 2) + '\n', 'utf8');
}
return newVersion;
}
function gitCommitAndTag(version) {
try {
execSync('git add package.json src-tauri/tauri.conf.json', { stdio: 'inherit' });
execSync(`git commit -m "chore: release ${version}"`, { stdio: 'inherit' });
execSync(`git tag -a ${version} -m "Release ${version}"`, { stdio: 'inherit' });
execSync('git push origin HEAD --tags', { stdio: 'inherit' });
} catch (e) {
console.warn('Git operations failed (non-fatal):', e.message);
}
}
function httpsRequest(options, body = null) {
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) resolve({ status: res.statusCode, body: data });
else reject(new Error(`HTTP ${res.statusCode}: ${data}`));
});
});
req.on('error', reject);
if (body) req.write(body);
req.end();
});
}
function uploadFile(filePath, packageName, version, fileName) {
return new Promise((resolve, reject) => {
const stat = fs.statSync(filePath);
const timestamp = Date.now();
const uploadName = `${path.parse(fileName).name}-${timestamp}${path.parse(fileName).ext}`;
const urlPath = `/api/v4/projects/${PROJECT_ID}/packages/generic/${packageName}/${version}/${encodeURIComponent(uploadName)}`;
const req = https.request({
hostname: 'gitlab.com',
path: urlPath,
method: 'PUT',
headers: {
'PRIVATE-TOKEN': TOKEN,
'Content-Type': 'application/octet-stream',
'Content-Length': stat.size
}
}, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve({ uploadName, url: `https://gitlab.com/api/v4/projects/${PROJECT_ID}/packages/generic/${packageName}/${version}/${encodeURIComponent(uploadName)}` });
} else {
reject(new Error(`Upload failed (${res.statusCode}): ${body}`));
}
});
});
req.on('error', reject);
fs.createReadStream(filePath).pipe(req);
});
}
async function createRelease(version, assets) {
const tag = version;
const apk = assets.apk;
const payload = assets.payload;
const manifest = assets.manifest;
const description = [
`## Automated Release ${version}`,
'',
'### Assets',
'',
`* [vyla-go-${version}.apk](${apk.url})`,
'',
'### Payload',
'',
`* [manifest.json](${manifest.url})`,
`* [update-payload.zip](${payload.url})`,
].join('\n');
const body = JSON.stringify({
name: `Vyla Go Release ${version}`,
tag_name: tag,
description,
assets: {
links: [
{ name: `vyla-go-${version}.apk`, url: apk.url, link_type: 'package' },
{ name: 'update-payload.zip', url: payload.url, link_type: 'other' },
{ name: 'manifest.json', url: manifest.url, link_type: 'other' },
]
}
});
await httpsRequest({
hostname: 'gitlab.com',
path: `/api/v4/projects/${PROJECT_ID}/releases`,
method: 'POST',
headers: {
'PRIVATE-TOKEN': TOKEN,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body)
}
}, body);
}
async function main() {
const rawVersion = incrementVersion();
const version = `v${rawVersion}`;
console.log(`Releasing ${version}...`);
console.log('Building payload...');
execSync('npm run build:payload', { stdio: 'inherit' });
console.log('Building APK...');
execSync('npm run build:apk', { stdio: 'inherit' });
const apkSearchDirs = [
path.join(__dirname, 'src-tauri', 'gen', 'android', 'app', 'build', 'outputs', 'apk', 'universal', 'release'),
path.join(__dirname, 'src-tauri', 'gen', 'android', 'app', 'build', 'outputs', 'apk', 'arm64', 'release'),
path.join(__dirname, 'src-tauri', 'gen', 'android', 'app', 'build', 'outputs', 'apk', 'release'),
];
let apkPath = null;
for (const dir of apkSearchDirs) {
if (!fs.existsSync(dir)) continue;
const found = fs.readdirSync(dir).find(f => f.endsWith('.apk') && !f.includes('unsigned'));
if (found) { apkPath = path.join(dir, found); break; }
}
if (!apkPath) throw new Error('Signed release APK not found');
const payloadPath = path.join(__dirname, 'update-payload.zip');
const manifestPath = path.join(__dirname, 'manifest.json');
if (!fs.existsSync(payloadPath)) throw new Error('update-payload.zip not found — run build:payload first');
if (!fs.existsSync(manifestPath)) throw new Error('manifest.json not found — run build:payload first');
gitCommitAndTag(version);
console.log('Uploading APK...');
const apkAsset = await uploadFile(apkPath, 'vyla-go', version, `vyla-go-${version}.apk`);
console.log('Uploading payload...');
const payloadAsset = await uploadFile(payloadPath, 'vyla-go', version, 'update-payload.zip');
console.log('Uploading manifest...');
const manifestAsset = await uploadFile(manifestPath, 'vyla-go', version, 'manifest.json');
console.log('Creating GitLab release...');
await createRelease(version, { apk: apkAsset, payload: payloadAsset, manifest: manifestAsset });
console.log(`\nRelease ${version} published successfully.`);
console.log(`APK: ${apkAsset.url}`);
console.log(`Payload: ${payloadAsset.url}`);
console.log(`Manifest: ${manifestAsset.url}`);
}
main().catch(err => {
console.error(err);
process.exit(1);
});