release.ts

8.1 KB · raw

1#!/usr/bin/env bun2// scripts/release.ts3// One command to release Peach to both Chrome Web Store and Firefox AMO.4// Usage: npm run release5//        npm run release:chrome   (Chrome only — skips AMO)6//7// Required environment variables (set from your secret store, then export8// in the release shell — PB-26-013: never written to a shell profile):9//   CWS_CLIENT_ID, CWS_CLIENT_SECRET, CWS_REFRESH_TOKEN, CWS_EXTENSION_ID10//   AMO_JWT_ISSUER, AMO_JWT_SECRET  (not needed for --chrome-only)1112import { execSync } from "child_process";13import { readFileSync, existsSync, mkdirSync } from "fs";14import { join } from "path";1516const ROOT = process.cwd();17const CHROME_ONLY = process.argv.includes("--chrome-only");1819const BOLD = "\x1b[1m";20const DIM = "\x1b[2m";21const ACCENT = "\x1b[38;5;208m";22const GREEN = "\x1b[32m";23const RED = "\x1b[31m";24const RESET = "\x1b[0m";2526function run(cmd: string, label?: string, env?: Record<string, string>) {27  console.log(`\n${ACCENT}▶${RESET} ${label ?? cmd}`);28  execSync(cmd, { stdio: "inherit", cwd: ROOT, env: { ...process.env, ...env } });29}3031function success(msg: string) {32  console.log(`${GREEN}✓ ${msg}${RESET}`);33}3435function fail(msg: string): never {36  console.error(`${RED}✗ ${msg}${RESET}`);37  process.exit(1);38}3940function check(name: string): string {41  const val = process.env[name];42  if (!val) fail(`${name} not set. Export it from your secret store and reload your shell.`);43  return val!;44}4546// ─── Header ──────────────────────────────────────────────────────────────────4748console.clear();49console.log("");50console.log(`${ACCENT}${BOLD}  ░░  peach release  ░░${RESET}`);51console.log("");5253// ─── Read version ─────────────────────────────────────────────────────────────5455const manifest = JSON.parse(readFileSync(join(ROOT, "manifest.json"), "utf8"));56const version = manifest.version;57if (!version) fail("Could not read version from manifest.json");58const targets = CHROME_ONLY ? "Chrome" : "Chrome + Firefox";59console.log(`${BOLD}Releasing Peach v${version} to ${targets}${RESET}`);60console.log(`${DIM}Stores update automatically for existing users.${RESET}`);6162// ─── Verify environment ───────────────────────────────────────────────────────6364console.log(`\n${BOLD}Checking credentials…${RESET}`);6566const CWS_CLIENT_ID     = check("CWS_CLIENT_ID");67const CWS_CLIENT_SECRET = check("CWS_CLIENT_SECRET");68const CWS_REFRESH_TOKEN = check("CWS_REFRESH_TOKEN");69const CWS_EXTENSION_ID  = check("CWS_EXTENSION_ID");70const CWS_PUBLISHER_ID  = check("PUBLISHER_ID");7172let AMO_JWT_ISSUER = "";73let AMO_JWT_SECRET = "";74if (!CHROME_ONLY) {75  AMO_JWT_ISSUER = check("AMO_JWT_ISSUER");76  AMO_JWT_SECRET = check("AMO_JWT_SECRET");77}7879success("All credentials present");8081// ─── CI gate ──────────────────────────────────────────────────────────────────8283run("npm run ci", "Running CI gate");84success("All tests pass");8586// ─── Build ────────────────────────────────────────────────────────────────────8788if (CHROME_ONLY) {89  run("npm run build:chrome", "Building Chrome target");90} else {91  run("npm run build", "Building all targets (Chrome + Firefox)");92}93success("Build complete");9495// ─── Ensure artifacts dir ─────────────────────────────────────────────────────9697if (!existsSync(join(ROOT, "artifacts"))) {98  mkdirSync(join(ROOT, "artifacts"));99}100101// ─── Chrome release ───────────────────────────────────────────────────────────102103console.log(`\n${ACCENT}${BOLD}Chrome Web Store${RESET}`);104105const chromeZip = join(ROOT, "artifacts", `peach-chrome-${version}.zip`);106if (existsSync(chromeZip)) run(`trash "${chromeZip}"`, "Moving existing Chrome archive to Trash");107108run(109  `cd dist-chrome && zip -r "${chromeZip}" . --exclude '*.map' --exclude '.DS_Store' --exclude '__MACOSX/*'`,110  `Packaging Chrome extension → peach-chrome-${version}.zip`,111);112113run(114  [115    "npx chrome-webstore-upload-cli upload",116    `--source "${chromeZip}"`,117    `--extension-id "${CWS_EXTENSION_ID}"`,118    "--auto-publish",119  ].join(" "),120  "Uploading and publishing to Chrome Web Store",121  // Credentials via child env, not inline command-line assignments, so they122  // are not visible in the process table. The CLI reads these from env.123  {124    CLIENT_ID: CWS_CLIENT_ID,125    CLIENT_SECRET: CWS_CLIENT_SECRET,126    REFRESH_TOKEN: CWS_REFRESH_TOKEN,127    PUBLISHER_ID: CWS_PUBLISHER_ID,128  },129);130131success(`Chrome v${version} submitted — review typically takes minutes to hours`);132133// ─── Firefox release ──────────────────────────────────────────────────────────134135if (!CHROME_ONLY) {136  console.log(`\n${ACCENT}${BOLD}Firefox Add-ons (AMO)${RESET}`);137138  const sourceZip = join(ROOT, "artifacts", `peach-source-${version}.zip`);139  run(140    [141      "zip -r",142      sourceZip,143      "src",144      "scripts",145      "public",146      // PB-26-004: the shipped .wasm is transpiled from crypto-core/src/lib.rs147      // — AMO reviewers must be able to inspect and rebuild it (npm run148      // build:noise-core). Build cache and generated output are excluded.149      "crypto-core",150      "manifest.json",151      "manifest.firefox.json",152      "package.json",153      "bun.lock",154      "tsconfig.json",155      "vite.config.ts",156      "--exclude 'node_modules/*'",157      "--exclude 'dist-*/*'",158      "--exclude 'crypto-core/target/*'",159      "--exclude 'crypto-core/generated/*'",160      "--exclude '*.zip'",161      "--exclude '.DS_Store'",162      "--exclude '__MACOSX/*'",163    ].join(" "),164    "Packaging source archive for AMO",165  );166167  run(168    [169      "npx web-ext sign",170      "--source-dir dist-firefox",171      "--artifacts-dir artifacts",172      "--channel=listed",173      "--upload-source-code",174      sourceZip,175    ].join(" "),176    "Signing and submitting to Firefox AMO",177    // web-ext reads WEB_EXT_API_KEY/WEB_EXT_API_SECRET from env — pass via178    // child env so the JWT credentials are not visible in the process table.179    {180      WEB_EXT_API_KEY: AMO_JWT_ISSUER,181      WEB_EXT_API_SECRET: AMO_JWT_SECRET,182    },183  );184185  success(`Firefox v${version} submitted with source archive retained in artifacts/`);186}187188// ─── Done ─────────────────────────────────────────────────────────────────────189190console.log("");191console.log(`${ACCENT}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);192console.log(`${ACCENT}${BOLD}  Peach v${version} released to ${targets}.${RESET}`);193console.log(`${ACCENT}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);194console.log("");195196if (!CHROME_ONLY) {197  console.log(`  Chrome:  under review — existing users update automatically`);198  console.log(`  Firefox: under review — existing users update automatically`);199} else {200  console.log(`  Chrome: under review — existing users update automatically`);201}202203console.log("");204console.log(`${DIM}Check status:${RESET}`);205console.log(`  Chrome:  https://chrome.google.com/webstore/devconsole`);206if (!CHROME_ONLY) {207  console.log(`  Firefox: https://addons.mozilla.org/developers/`);208}209console.log("");