feat(release): auto-update releases/updates.json in release:push using manifest.update_url and chosen XPI

This commit is contained in:
Jordan Wages 2025-08-22 23:59:12 -05:00
commit 2083780420

View file

@ -59,6 +59,7 @@ function run(cmd, secrets = []) {
} }
const pkg = require(path.join(root, 'package.json')); const pkg = require(path.join(root, 'package.json'));
const manifest = require(path.join(root, 'manifest.json'));
const version = pkg.version; const version = pkg.version;
if (!version) { if (!version) {
console.error('package.json is missing version'); console.error('package.json is missing version');
@ -72,6 +73,12 @@ if (!fs.existsSync(artifactsDir) || !fs.statSync(artifactsDir).isDirectory()) {
} }
const files = fs.readdirSync(artifactsDir).filter(f => fs.statSync(path.join(artifactsDir, f)).isFile()); const files = fs.readdirSync(artifactsDir).filter(f => fs.statSync(path.join(artifactsDir, f)).isFile());
const xpis = files.filter(f => f.toLowerCase().endsWith('.xpi'));
if (xpis.length === 0) {
console.error(`No .xpi artifacts found in ${artifactsDir}`);
process.exit(1);
}
const chosenXpi = xpis.sort()[0];
if (files.length === 0) { if (files.length === 0) {
console.error(`No files found in ${artifactsDir}`); console.error(`No files found in ${artifactsDir}`);
process.exit(1); process.exit(1);
@ -93,9 +100,63 @@ if (!host || !user || !pass || !remoteDir) {
if (!remoteDir.startsWith('/')) remoteDir = '/' + remoteDir; if (!remoteDir.startsWith('/')) remoteDir = '/' + remoteDir;
if (remoteDir.endsWith('/')) remoteDir = remoteDir.slice(0, -1); if (remoteDir.endsWith('/')) remoteDir = remoteDir.slice(0, -1);
// Construct base URL // Construct base URL for FTP upload target
const baseUrl = `${protocol}://${host}${port ? `:${port}` : ''}${remoteDir}`; const baseUrl = `${protocol}://${host}${port ? `:${port}` : ''}${remoteDir}`;
// Prepare or update self-hosted updates.json before upload
function ensureUpdatesJson() {
try {
const updatesPath = path.join(root, 'releases', 'updates.json');
const examplePath = path.join(root, 'releases', 'updates.example.json');
let data;
if (fs.existsSync(updatesPath)) {
data = JSON.parse(fs.readFileSync(updatesPath, 'utf8'));
} else if (fs.existsSync(examplePath)) {
data = JSON.parse(fs.readFileSync(examplePath, 'utf8'));
} else {
data = { addons: {} };
}
const addonId = manifest?.applications?.gecko?.id;
const updateUrl = manifest?.applications?.gecko?.update_url;
if (!addonId || !updateUrl) {
console.warn('Missing add-on id or update_url in manifest; skipping updates.json generation');
return;
}
// Derive public base directory from update_url (strip trailing filename)
let updatesBaseDir = updateUrl.replace(/\/?[^/]*$/, '');
// Build public link to the uploaded XPI
const publicLink = `${updatesBaseDir}/releases/${version}/${encodeURIComponent(chosenXpi)}`;
if (!data.addons) data.addons = {};
if (!data.addons[addonId]) data.addons[addonId] = { updates: [] };
const entry = { version: String(version), update_link: publicLink };
// Try to preserve existing strict_min_version if present in latest entry
const existingUpdates = data.addons[addonId].updates || [];
let strictMin = '91.0';
for (const u of existingUpdates) {
if (u?.applications?.gecko?.strict_min_version) {
strictMin = u.applications.gecko.strict_min_version;
break;
}
}
entry.applications = { gecko: { strict_min_version: strictMin } };
// Replace or append the entry for this version
const idx = existingUpdates.findIndex(u => String(u.version) === String(version));
if (idx >= 0) existingUpdates[idx] = entry; else existingUpdates.push(entry);
data.addons[addonId].updates = existingUpdates;
fs.writeFileSync(updatesPath, JSON.stringify(data, null, 2) + '\n', 'utf8');
console.log(`Prepared releases/updates.json for version ${version}`);
} catch (e) {
console.warn('Warning: Failed to update releases/updates.json:', e.message);
}
}
ensureUpdatesJson();
console.log(`Uploading ${files.length} file(s) from ${artifactsDir} to ${protocol}://${host}${port ? `:${port}` : ''}${remoteDir}/`); console.log(`Uploading ${files.length} file(s) from ${artifactsDir} to ${protocol}://${host}${port ? `:${port}` : ''}${remoteDir}/`);
for (const file of files) { for (const file of files) {