#!/usr/bin/env node /* * Signs an unlisted release via AMO using web-ext and outputs artifacts to releases// * Requires env: AMO_JWT_ISSUER, AMO_JWT_SECRET */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const root = path.join(__dirname, '..'); // Auto-load .env if present (no dependency on dotenv) (() => { try { const envPath = path.join(root, '.env'); if (fs.existsSync(envPath)) { const content = fs.readFileSync(envPath, 'utf8'); for (const rawLine of content.split(/\r?\n/)) { const line = rawLine.trim(); if (!line || line.startsWith('#')) continue; const cleaned = line.startsWith('export ') ? line.slice('export '.length).trim() : line; const eq = cleaned.indexOf('='); if (eq === -1) continue; const key = cleaned.slice(0, eq).trim(); let val = cleaned.slice(eq + 1).trim(); if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) { val = val.slice(1, -1); } if (!(key in process.env)) process.env[key] = val; } // Do not log secrets; just note that .env was processed console.log('Loaded environment from .env'); } } catch (e) { console.warn('Warning: Failed to load .env:', e.message); } })(); const pkg = require(path.join(root, 'package.json')); const manifest = require(path.join(root, 'manifest.json')); const issuer = process.env.AMO_JWT_ISSUER; const secret = process.env.AMO_JWT_SECRET; if (!issuer || !secret) { console.error('Missing AMO credentials. Set AMO_JWT_ISSUER and AMO_JWT_SECRET.'); process.exit(1); } const addonId = manifest?.applications?.gecko?.id; if (!addonId || addonId.includes('example')) { console.error('Invalid add-on id. Set applications.gecko.id in manifest.json to your stable ID.'); process.exit(1); } const version = pkg.version; if (!version) { console.error('package.json is missing version'); process.exit(1); } const outDir = path.join(root, 'releases', version); fs.mkdirSync(outDir, { recursive: true }); function run(cmd) { console.log(`> ${cmd}`); execSync(cmd, { stdio: 'inherit' }); } try { // Lint before signing (allow self-hosted update_url) run('npx --yes web-ext lint --source-dir . --self-hosted'); // Sign (unlisted) and place artifacts in releases/ const signCmd = [ 'npx --yes web-ext sign', '--channel=unlisted', `--api-key="${issuer}"`, `--api-secret="${secret}"`, `--id="${addonId}"`, `--artifacts-dir "${outDir}"`, ].join(' '); run(signCmd); console.log('\nSigned artifacts written to:', outDir); console.log('Next: host .xpi and update updates.json (if self-hosting updates).'); } catch (err) { console.error('Release signing failed:', err.message); process.exit(1); }