220 lines
7.0 KiB
JavaScript
220 lines
7.0 KiB
JavaScript
#!/usr/bin/env node
|
|
import { execSync } from 'child_process';
|
|
import { readFileSync, writeFileSync, statSync, createReadStream, existsSync } from 'fs';
|
|
import { resolve, basename } from 'path';
|
|
|
|
const ROOT = resolve(import.meta.dirname, '..');
|
|
const PKG_PATH = resolve(ROOT, 'package.json');
|
|
const RELEASE_DIR = resolve(ROOT, 'release');
|
|
const PRODUCT_NAME = 'Multi-Hoster-Upload';
|
|
|
|
// --- CLI args ---
|
|
const args = process.argv.slice(2);
|
|
const dryRun = args.includes('--dry-run');
|
|
const version = args.find(a => /^\d+\.\d+\.\d+$/.test(a));
|
|
const notes = args.filter(a => a !== version && a !== '--dry-run').join(' ') || '';
|
|
|
|
if (!version) {
|
|
console.error('Usage: node scripts/release_gitea.mjs <version> [release notes] [--dry-run]');
|
|
console.error('Example: node scripts/release_gitea.mjs 1.0.1 "Bugfix release"');
|
|
process.exit(1);
|
|
}
|
|
|
|
const tag = `v${version}`;
|
|
|
|
// --- Helpers ---
|
|
function run(cmd, opts = {}) {
|
|
console.log(` $ ${cmd}`);
|
|
if (dryRun && !opts.allowDry) { console.log(' [dry-run] skipped'); return ''; }
|
|
return execSync(cmd, { cwd: ROOT, encoding: 'utf-8', stdio: opts.stdio || 'pipe' }).trim();
|
|
}
|
|
|
|
function resolveGiteaRemote() {
|
|
const remotes = run('git remote -v', { allowDry: true });
|
|
for (const name of ['gitea', 'forgejo', 'origin']) {
|
|
const match = remotes.match(new RegExp(`^${name}\\s+(\\S+)`, 'm'));
|
|
if (match && match[1].includes('git.24-music.de')) return { name, url: match[1] };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function resolveToken() {
|
|
// Env var first
|
|
const envToken = process.env.GITEA_TOKEN || process.env.FORGEJO_TOKEN;
|
|
if (envToken) return envToken;
|
|
|
|
// Try git credential helper
|
|
try {
|
|
const input = 'protocol=https\nhost=git.24-music.de\n\n';
|
|
const output = execSync('git credential fill', { input, encoding: 'utf-8', cwd: ROOT });
|
|
const match = output.match(/password=(.+)/);
|
|
if (match) return match[1].trim();
|
|
} catch {}
|
|
|
|
return null;
|
|
}
|
|
|
|
async function giteaApi(method, urlPath, token, body) {
|
|
const base = process.env.GITEA_BASE_URL || 'https://git.24-music.de';
|
|
const url = `${base}${urlPath}`;
|
|
const headers = { 'Authorization': `token ${token}`, 'Content-Type': 'application/json' };
|
|
|
|
const res = await fetch(url, {
|
|
method,
|
|
headers,
|
|
body: body ? JSON.stringify(body) : undefined
|
|
});
|
|
|
|
const text = await res.text();
|
|
let json;
|
|
try { json = JSON.parse(text); } catch { json = null; }
|
|
|
|
if (!res.ok && res.status !== 409 && res.status !== 422) {
|
|
throw new Error(`Gitea API ${res.status}: ${text.slice(0, 300)}`);
|
|
}
|
|
|
|
return { status: res.status, data: json };
|
|
}
|
|
|
|
async function uploadAsset(releaseId, filePath, token) {
|
|
const base = process.env.GITEA_BASE_URL || 'https://git.24-music.de';
|
|
const name = basename(filePath);
|
|
const size = statSync(filePath).size;
|
|
const url = `${base}/api/v1/repos/Administrator/${PRODUCT_NAME}/releases/${releaseId}/assets?name=${encodeURIComponent(name)}`;
|
|
|
|
console.log(` Uploading ${name} (${(size / 1024 / 1024).toFixed(1)} MB)...`);
|
|
|
|
if (dryRun) { console.log(' [dry-run] skipped'); return; }
|
|
|
|
const stream = createReadStream(filePath);
|
|
const res = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `token ${token}`,
|
|
'Content-Type': 'application/octet-stream',
|
|
'Content-Length': String(size)
|
|
},
|
|
body: stream,
|
|
duplex: 'half'
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(`Asset upload failed (${res.status}): ${text.slice(0, 200)}`);
|
|
}
|
|
|
|
console.log(` Uploaded: ${name}`);
|
|
}
|
|
|
|
// --- Main ---
|
|
async function main() {
|
|
console.log(`\nReleasing ${PRODUCT_NAME} ${tag}${dryRun ? ' [DRY RUN]' : ''}\n`);
|
|
|
|
// 1. Resolve remote
|
|
const remote = resolveGiteaRemote();
|
|
if (!remote) {
|
|
console.error('No Gitea remote found. Add one: git remote add gitea https://git.24-music.de/Administrator/Multi-Hoster-Upload.git');
|
|
process.exit(1);
|
|
}
|
|
console.log(`Remote: ${remote.name} -> ${remote.url}`);
|
|
|
|
// 2. Check clean working tree
|
|
const status = run('git status --porcelain', { allowDry: true });
|
|
const trackedChanges = status.split('\n').filter(l => l.trim() && !l.startsWith('??')).join('\n');
|
|
if (trackedChanges) {
|
|
console.error('Working tree has uncommitted tracked changes. Commit or stash first.');
|
|
process.exit(1);
|
|
}
|
|
|
|
// 3. Check if tag already exists (recovery mode)
|
|
let recoveryMode = false;
|
|
try {
|
|
run(`git rev-parse ${tag}`, { allowDry: true });
|
|
console.log(`Tag ${tag} already exists - recovery mode (skip version bump/git)`);
|
|
recoveryMode = true;
|
|
} catch {}
|
|
|
|
if (!recoveryMode) {
|
|
// 4. Update package.json version
|
|
const pkg = JSON.parse(readFileSync(PKG_PATH, 'utf-8'));
|
|
pkg.version = version;
|
|
if (!dryRun) writeFileSync(PKG_PATH, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
console.log(`Updated package.json -> ${version}`);
|
|
|
|
// 5. Build
|
|
console.log('\nBuilding...');
|
|
run('npm run release:win', { stdio: 'inherit' });
|
|
|
|
// 6. Git commit + tag + push
|
|
run('git add package.json');
|
|
run(`git commit -m "release: ${tag}"`);
|
|
run(`git tag ${tag}`);
|
|
run(`git push ${remote.name} HEAD`);
|
|
run(`git push ${remote.name} ${tag}`);
|
|
}
|
|
|
|
// 7. Verify artifacts
|
|
const expectedArtifacts = [
|
|
`${PRODUCT_NAME} Setup ${version}.exe`,
|
|
`${PRODUCT_NAME} ${version}.exe`,
|
|
'latest.yml'
|
|
];
|
|
|
|
for (const name of expectedArtifacts) {
|
|
const p = resolve(RELEASE_DIR, name);
|
|
if (!existsSync(p)) {
|
|
console.error(`Missing artifact: ${p}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Also check for blockmap
|
|
const blockmapName = `${PRODUCT_NAME} Setup ${version}.exe.blockmap`;
|
|
const hasBlockmap = existsSync(resolve(RELEASE_DIR, blockmapName));
|
|
|
|
console.log('\nArtifacts verified.');
|
|
|
|
// 8. Get token
|
|
const token = resolveToken();
|
|
if (!token) {
|
|
console.error('No Gitea token. Set GITEA_TOKEN env var or configure git credential helper.');
|
|
process.exit(1);
|
|
}
|
|
|
|
// 9. Create release
|
|
const releaseBody = notes || `${PRODUCT_NAME} ${tag}`;
|
|
let releaseId;
|
|
|
|
const { status: createStatus, data: createData } = await giteaApi(
|
|
'POST',
|
|
`/api/v1/repos/Administrator/${PRODUCT_NAME}/releases`,
|
|
token,
|
|
{ tag_name: tag, name: `${PRODUCT_NAME} ${tag}`, body: releaseBody }
|
|
);
|
|
|
|
if (createStatus === 409 || createStatus === 422) {
|
|
// Release already exists, find it
|
|
const { data: releases } = await giteaApi('GET', `/api/v1/repos/Administrator/${PRODUCT_NAME}/releases/tags/${tag}`, token);
|
|
releaseId = releases.id;
|
|
console.log(`Release already exists (id: ${releaseId})`);
|
|
} else {
|
|
releaseId = createData.id;
|
|
console.log(`Created release (id: ${releaseId})`);
|
|
}
|
|
|
|
// 10. Upload assets
|
|
for (const name of expectedArtifacts) {
|
|
await uploadAsset(releaseId, resolve(RELEASE_DIR, name), token);
|
|
}
|
|
if (hasBlockmap) {
|
|
await uploadAsset(releaseId, resolve(RELEASE_DIR, blockmapName), token);
|
|
}
|
|
|
|
console.log(`\nDone! Release: ${process.env.GITEA_BASE_URL || 'https://git.24-music.de'}/Administrator/${PRODUCT_NAME}/releases/tag/${tag}\n`);
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('\nRelease failed:', err.message);
|
|
process.exit(1);
|
|
});
|