36c06b55ea
Do not inflate latest.json from dmg filenames; use build version only. Add client loop guard when the same update retries without version bump. Co-authored-by: Cursor <cursoragent@cursor.com>
120 lines
4.1 KiB
JavaScript
120 lines
4.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* dist-desktop/updates → latest.json (Tauri updater v2)
|
|
*
|
|
* Usage:
|
|
* node scripts/generate-desktop-latest-json.mjs \
|
|
* --version 0.1.0 \
|
|
* --dir dist-desktop/updates \
|
|
* --base-url https://exdev.co.kr/desktop/updates \
|
|
* --notes "Release notes"
|
|
*/
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
function arg(name, fallback) {
|
|
const i = process.argv.indexOf(name);
|
|
return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : fallback;
|
|
}
|
|
|
|
const version = arg('--version', '0.1.0');
|
|
const dir = path.resolve(arg('--dir', 'dist-desktop/updates'));
|
|
const rawBaseUrl = arg('--base-url', 'https://exdev.co.kr/desktop/updates');
|
|
const baseUrl = rawBaseUrl.replace(/\/$/, '').replace(/^http:\/\//i, 'https://');
|
|
const notes = arg('--notes', `GoldenChart Desktop ${version}`);
|
|
|
|
if (!fs.existsSync(dir)) {
|
|
console.error('Directory not found:', dir);
|
|
process.exit(1);
|
|
}
|
|
|
|
const files = fs.readdirSync(dir);
|
|
|
|
function versionFromName(name) {
|
|
const m = name.match(/(\d+\.\d+\.\d+)/);
|
|
return m ? m[1] : '0.0.0';
|
|
}
|
|
|
|
function cmpSemver(a, b) {
|
|
const pa = a.split('.').map(Number);
|
|
const pb = b.split('.').map(Number);
|
|
for (let i = 0; i < 3; i++) {
|
|
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
if (d !== 0) return d;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** @type {Record<string, { url: string; signature: string; bundleVersion: string }>} */
|
|
const platforms = {};
|
|
|
|
function addPlatform(platformKey, bundleName) {
|
|
const bundlePath = path.join(dir, bundleName);
|
|
if (!fs.existsSync(bundlePath)) return;
|
|
const sigPath = `${bundlePath}.sig`;
|
|
let signature = '';
|
|
if (fs.existsSync(sigPath)) {
|
|
signature = fs.readFileSync(sigPath, 'utf8').trim();
|
|
} else {
|
|
console.warn(`[warn] missing signature: ${sigPath}`);
|
|
return;
|
|
}
|
|
const bundleVersion = versionFromName(bundleName);
|
|
const effectiveVersion = bundleVersion === '0.0.0' ? version : bundleVersion;
|
|
const existing = platforms[platformKey];
|
|
if (existing && cmpSemver(effectiveVersion, existing.bundleVersion) <= 0) return;
|
|
platforms[platformKey] = {
|
|
url: `${baseUrl}/${encodeURIComponent(bundleName)}`,
|
|
signature,
|
|
bundleVersion: effectiveVersion,
|
|
};
|
|
}
|
|
|
|
for (const f of files) {
|
|
if (f.endsWith('.sig')) continue;
|
|
if (f.endsWith('.dmg')) continue; // 설치용 dmg — updater manifest 대상 아님
|
|
const lower = f.toLowerCase();
|
|
if (lower.includes('aarch64') && (lower.endsWith('.tar.gz') || lower.endsWith('.app.tar.gz'))) {
|
|
addPlatform('darwin-aarch64', f);
|
|
} else if ((lower.includes('x64') || lower.includes('x86_64')) && lower.endsWith('.tar.gz')) {
|
|
addPlatform('darwin-x86_64', f);
|
|
} else if (lower.includes('aarch64') && lower.endsWith('.app.tar.gz')) {
|
|
addPlatform('darwin-aarch64', f);
|
|
} else if (lower.endsWith('.nsis.zip') || (lower.includes('setup') && lower.endsWith('.zip'))) {
|
|
addPlatform('windows-x86_64', f);
|
|
} else if (lower.endsWith('-setup.exe') || (lower.includes('setup') && lower.endsWith('.exe'))) {
|
|
addPlatform('windows-x86_64', f);
|
|
} else if (lower.endsWith('.app.tar.gz')) {
|
|
addPlatform('darwin-aarch64', f);
|
|
}
|
|
}
|
|
|
|
const bundleMaxVersion = Object.values(platforms).reduce(
|
|
(max, p) => (cmpSemver(p.bundleVersion, max) > 0 ? p.bundleVersion : max),
|
|
'0.0.0',
|
|
);
|
|
// manifest 버전 = 빌드 버전(--version). 번들 파일명에 버전 없으면 CLI 버전 사용.
|
|
// dmg 파일명으로 올리지 않음 — tar.gz 내용과 불일치하면 업데이트 무한 루프.
|
|
let manifestVersion = version;
|
|
if (bundleMaxVersion !== '0.0.0' && cmpSemver(bundleMaxVersion, version) < 0) {
|
|
console.warn(`[warn] bundle version ${bundleMaxVersion} < build ${version} — using build version`);
|
|
}
|
|
|
|
const manifest = {
|
|
version: manifestVersion,
|
|
notes,
|
|
pub_date: new Date().toISOString(),
|
|
platforms: Object.fromEntries(
|
|
Object.entries(platforms).map(([k, v]) => [k, { url: v.url, signature: v.signature }]),
|
|
),
|
|
};
|
|
|
|
const outPath = path.join(dir, 'latest.json');
|
|
fs.writeFileSync(outPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
console.log('Wrote', outPath);
|
|
console.log('Platforms:', Object.keys(platforms).join(', ') || '(none — sign updater bundles first)');
|
|
|
|
if (Object.keys(platforms).length === 0) {
|
|
process.exit(2);
|
|
}
|