953f200f4fd4

add release.ts, setup-chrome-release.sh, and release/release:chrome npm scripts

time
2026-05-27 23:39 UTC · 5 months ago
machine
koh2
parent
d4460bd9f432
3 files package.json+2 −0scripts/release.ts+195 −0scripts/setup-chrome-release.sh+181 −0
package.json+2 −0
@@ -17,6 +17,8 @@1717     "test:passkeys": "npm run build:e2e && playwright test tests/passkeys.spec.ts",1818     "ci": "bun run scripts/ci.ts",1919     "package:source": "zip -r peach-source.zip src scripts public manifest.json manifest.firefox.json package.json package-lock.json tsconfig.json vite.config.ts vite.config.firefox.ts vite.config.chrome.ts vite.config.mobile.ts --exclude 'node_modules/*' --exclude 'dist-*/*' --exclude '*.zip'",20+    "release": "bun run scripts/release.ts",21+    "release:chrome": "bun run scripts/release.ts --chrome-only",2022     "release:firefox": "bun run scripts/release-firefox.ts",2123     "submit:firefox": "web-ext sign --source-dir dist-firefox --artifacts-dir artifacts --api-key=$AMO_JWT_ISSUER --api-secret=$AMO_JWT_SECRET --channel=listed"2224   },

view file

scripts/release.ts+195 −0
@@ -0,0 +1,195 @@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 via setup-chrome-release.sh and shell profile):8+//   CWS_CLIENT_ID, CWS_CLIENT_SECRET, CWS_REFRESH_TOKEN, CWS_EXTENSION_ID9+//   AMO_JWT_ISSUER, AMO_JWT_SECRET  (not needed for --chrome-only)10+11+import { execSync } from "child_process";12+import { readFileSync, existsSync, mkdirSync, rmSync } from "fs";13+import { join } from "path";14+15+const ROOT = process.cwd();16+const CHROME_ONLY = process.argv.includes("--chrome-only");17+18+const BOLD = "\x1b[1m";19+const DIM = "\x1b[2m";20+const ACCENT = "\x1b[38;5;208m";21+const GREEN = "\x1b[32m";22+const RED = "\x1b[31m";23+const RESET = "\x1b[0m";24+25+function run(cmd: string, label?: string) {26+  console.log(`\n${ACCENT}▶${RESET} ${label ?? cmd}`);27+  execSync(cmd, { stdio: "inherit", cwd: ROOT });28+}29+30+function success(msg: string) {31+  console.log(`${GREEN}✓ ${msg}${RESET}`);32+}33+34+function fail(msg: string): never {35+  console.error(`${RED}✗ ${msg}${RESET}`);36+  process.exit(1);37+}38+39+function check(name: string): string {40+  const val = process.env[name];41+  if (!val) fail(`${name} not set. Run setup-chrome-release.sh and reload your shell.`);42+  return val!;43+}44+45+// ─── Header ──────────────────────────────────────────────────────────────────46+47+console.clear();48+console.log("");49+console.log(`${ACCENT}${BOLD}  ░░  peach release  ░░${RESET}`);50+console.log("");51+52+// ─── Read version ─────────────────────────────────────────────────────────────53+54+const manifest = JSON.parse(readFileSync(join(ROOT, "manifest.json"), "utf8"));55+const version = manifest.version;56+if (!version) fail("Could not read version from manifest.json");57+const targets = CHROME_ONLY ? "Chrome" : "Chrome + Firefox";58+console.log(`${BOLD}Releasing Peach v${version} to ${targets}${RESET}`);59+console.log(`${DIM}Stores update automatically for existing users.${RESET}`);60+61+// ─── Verify environment ───────────────────────────────────────────────────────62+63+console.log(`\n${BOLD}Checking credentials…${RESET}`);64+65+const CWS_CLIENT_ID     = check("CWS_CLIENT_ID");66+const CWS_CLIENT_SECRET = check("CWS_CLIENT_SECRET");67+const CWS_REFRESH_TOKEN = check("CWS_REFRESH_TOKEN");68+const CWS_EXTENSION_ID  = check("CWS_EXTENSION_ID");69+70+let AMO_JWT_ISSUER = "";71+let AMO_JWT_SECRET = "";72+if (!CHROME_ONLY) {73+  AMO_JWT_ISSUER = check("AMO_JWT_ISSUER");74+  AMO_JWT_SECRET = check("AMO_JWT_SECRET");75+}76+77+success("All credentials present");78+79+// ─── CI gate ──────────────────────────────────────────────────────────────────80+81+run("npm run ci", "Running CI gate");82+success("All tests pass");83+84+// ─── Build ────────────────────────────────────────────────────────────────────85+86+if (CHROME_ONLY) {87+  run("npm run build:chrome", "Building Chrome target");88+} else {89+  run("npm run build", "Building all targets (Chrome + Firefox + Mobile)");90+}91+success("Build complete");92+93+// ─── Ensure artifacts dir ─────────────────────────────────────────────────────94+95+if (!existsSync(join(ROOT, "artifacts"))) {96+  mkdirSync(join(ROOT, "artifacts"));97+}98+99+// ─── Chrome release ───────────────────────────────────────────────────────────100+101+console.log(`\n${ACCENT}${BOLD}Chrome Web Store${RESET}`);102+103+const chromeZip = join(ROOT, "artifacts", `peach-chrome-${version}.zip`);104+if (existsSync(chromeZip)) rmSync(chromeZip);105+106+run(107+  `cd dist-chrome && zip -r "${chromeZip}" . --exclude '*.map' --exclude '.DS_Store' --exclude '__MACOSX/*'`,108+  `Packaging Chrome extension → peach-chrome-${version}.zip`,109+);110+111+run(112+  [113+    "npx chrome-webstore-upload-cli upload",114+    `--source "${chromeZip}"`,115+    `--extension-id "${CWS_EXTENSION_ID}"`,116+    `--client-id "${CWS_CLIENT_ID}"`,117+    `--client-secret "${CWS_CLIENT_SECRET}"`,118+    `--refresh-token "${CWS_REFRESH_TOKEN}"`,119+    "--auto-publish",120+  ].join(" "),121+  "Uploading and publishing to Chrome Web Store",122+);123+124+success(`Chrome v${version} submitted — review typically takes minutes to hours`);125+126+// ─── Firefox release ──────────────────────────────────────────────────────────127+128+if (!CHROME_ONLY) {129+  console.log(`\n${ACCENT}${BOLD}Firefox Add-ons (AMO)${RESET}`);130+131+  const sourceZip = `peach-source-${version}.zip`;132+  run(133+    [134+      "zip -r",135+      sourceZip,136+      "src",137+      "scripts",138+      "public",139+      "manifest.json",140+      "manifest.firefox.json",141+      "package.json",142+      "package-lock.json",143+      "tsconfig.json",144+      "vite.config.ts",145+      "vite.config.firefox.ts",146+      "vite.config.chrome.ts",147+      "vite.config.mobile.ts",148+      "--exclude 'node_modules/*'",149+      "--exclude 'dist-*/*'",150+      "--exclude '*.zip'",151+      "--exclude '.DS_Store'",152+      "--exclude '__MACOSX/*'",153+    ].join(" "),154+    "Packaging source archive for AMO",155+  );156+157+  run(158+    [159+      "npx web-ext sign",160+      "--source-dir dist-firefox",161+      "--artifacts-dir artifacts",162+      `--api-key="${AMO_JWT_ISSUER}"`,163+      `--api-secret="${AMO_JWT_SECRET}"`,164+      "--channel=listed",165+    ].join(" "),166+    "Signing and submitting to Firefox AMO",167+  );168+169+  rmSync(join(ROOT, sourceZip), { force: true });170+171+  success(`Firefox v${version} submitted — review typically takes minutes`);172+}173+174+// ─── Done ─────────────────────────────────────────────────────────────────────175+176+console.log("");177+console.log(`${ACCENT}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);178+console.log(`${ACCENT}${BOLD}  Peach v${version} released to ${targets}.${RESET}`);179+console.log(`${ACCENT}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);180+console.log("");181+182+if (!CHROME_ONLY) {183+  console.log(`  Chrome:  under review — existing users update automatically`);184+  console.log(`  Firefox: under review — existing users update automatically`);185+} else {186+  console.log(`  Chrome: under review — existing users update automatically`);187+}188+189+console.log("");190+console.log(`${DIM}Check status:${RESET}`);191+console.log(`  Chrome:  https://chrome.google.com/webstore/devconsole`);192+if (!CHROME_ONLY) {193+  console.log(`  Firefox: https://addons.mozilla.org/developers/`);194+}195+console.log("");

view file

scripts/setup-chrome-release.sh+181 −0
@@ -0,0 +1,181 @@1+#!/usr/bin/env bash2+# ─────────────────────────────────────────────────────────────────────────────3+# Peach — Chrome Web Store Release Setup4+# Run this once to configure Chrome publishing.5+# After setup, use: npm run release6+# ─────────────────────────────────────────────────────────────────────────────7+8+set -e9+10+BOLD="\033[1m"11+DIM="\033[2m"12+ACCENT="\033[38;5;208m"13+GREEN="\033[32m"14+RED="\033[31m"15+RESET="\033[0m"16+17+header() {18+  echo ""19+  echo -e "${ACCENT}${BOLD}$1${RESET}"20+  echo -e "${DIM}$2${RESET}"21+  echo ""22+}23+24+success() { echo -e "${GREEN}✓ $1${RESET}"; }25+fail() { echo -e "${RED}✗ $1${RESET}"; exit 1; }26+27+clear28+echo ""29+echo -e "${ACCENT}${BOLD}  ░░  peach chrome release setup  ░░${RESET}"30+echo ""31+echo -e "${DIM}This sets up one-command Chrome Web Store publishing.${RESET}"32+echo -e "${DIM}Run this once. After that: npm run release${RESET}"33+echo ""34+read -rp "Press Enter to begin…"35+36+# ─── Step 1: Check dependencies ───────────────────────────────────────────────37+38+header "Step 1 / 4 — Checking dependencies" ""39+40+if ! command -v node &>/dev/null; then41+  fail "Node.js not found."42+fi43+success "Node.js $(node --version)"44+45+# Install chrome-webstore-upload-cli46+echo "Installing chrome-webstore-upload-cli…"47+npm install -D chrome-webstore-upload-cli 2>/dev/null || \48+  npm install -g chrome-webstore-upload-cli49+success "chrome-webstore-upload-cli ready"50+51+# ─── Step 2: OAuth setup ──────────────────────────────────────────────────────52+53+header "Step 2 / 4 — Google OAuth setup" \54+  "This is the only annoying part. Follow the steps carefully."55+56+echo -e "${BOLD}You need to:${RESET}"57+echo "  1. Go to: ${ACCENT}https://console.cloud.google.com/apis/credentials${RESET}"58+echo "  2. Create a new project called 'Peach CWS' (top-left dropdown → New Project)"59+echo "  3. Enable the Chrome Web Store API:"60+echo "     ${ACCENT}https://console.cloud.google.com/apis/library/chromewebstore.googleapis.com${RESET}"61+echo "  4. Go to Credentials → Create Credentials → OAuth 2.0 Client ID"62+echo "  5. Application type: Desktop app"63+echo "  6. Name: Peach Release"64+echo "  7. Download the JSON — copy the client_id and client_secret"65+echo ""66+echo -e "${DIM}Once you have those, come back here.${RESET}"67+echo ""68+read -rp "Press Enter when you have your client_id and client_secret…"69+70+echo ""71+echo -e "${BOLD}Paste your client_id:${RESET}"72+read -r CWS_CLIENT_ID73+74+echo -e "${BOLD}Paste your client_secret (hidden):${RESET}"75+read -rs CWS_CLIENT_SECRET76+echo ""77+78+if [ -z "$CWS_CLIENT_ID" ] || [ -z "$CWS_CLIENT_SECRET" ]; then79+  fail "Client ID and secret are required."80+fi81+82+# ─── Step 3: Get refresh token ────────────────────────────────────────────────83+84+header "Step 3 / 4 — Get refresh token" \85+  "Open the URL below in your browser, authorize, then paste the code."86+87+AUTH_URL="https://accounts.google.com/o/oauth2/auth?client_id=${CWS_CLIENT_ID}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=https://www.googleapis.com/auth/chromewebstore"88+89+echo -e "${BOLD}Open this URL in your browser:${RESET}"90+echo ""91+echo -e "  ${ACCENT}${AUTH_URL}${RESET}"92+echo ""93+94+# Try to open browser automatically95+if command -v open &>/dev/null; then96+  open "$AUTH_URL" 2>/dev/null || true97+elif command -v xdg-open &>/dev/null; then98+  xdg-open "$AUTH_URL" 2>/dev/null || true99+fi100+101+echo -e "${DIM}(Browser may have opened automatically)${RESET}"102+echo ""103+echo -e "${BOLD}Paste the authorization code Google gives you:${RESET}"104+read -r AUTH_CODE105+106+if [ -z "$AUTH_CODE" ]; then107+  fail "Authorization code is required."108+fi109+110+# Exchange code for refresh token111+echo ""112+echo "Exchanging code for refresh token…"113+114+TOKEN_RESPONSE=$(curl -s -X POST https://oauth2.googleapis.com/token \115+  -d "client_id=${CWS_CLIENT_ID}" \116+  -d "client_secret=${CWS_CLIENT_SECRET}" \117+  -d "code=${AUTH_CODE}" \118+  -d "grant_type=authorization_code" \119+  -d "redirect_uri=urn:ietf:wg:oauth:2.0:oob")120+121+CWS_REFRESH_TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"refresh_token":"[^"]*"' | cut -d'"' -f4)122+123+if [ -z "$CWS_REFRESH_TOKEN" ]; then124+  echo "Token response: $TOKEN_RESPONSE"125+  fail "Failed to get refresh token. Check your client ID, secret, and auth code."126+fi127+128+success "Got refresh token"129+130+# ─── Step 4: Extension ID ─────────────────────────────────────────────────────131+132+header "Step 4 / 4 — Extension ID" \133+  "Found in the Chrome Web Store Developer Dashboard."134+135+echo -e "${BOLD}Your Chrome extension ID${RESET}"136+echo -e "${DIM}Go to: https://chrome.google.com/webstore/devconsole${RESET}"137+echo -e "${DIM}Click your extension — the ID is in the URL or the item details.${RESET}"138+echo -e "${DIM}Looks like: abcdefghijklmnopqrstuvwxyz123456${RESET}"139+echo ""140+echo -e "${BOLD}Paste your extension ID:${RESET}"141+read -r CWS_EXTENSION_ID142+143+if [ -z "$CWS_EXTENSION_ID" ]; then144+  fail "Extension ID is required."145+fi146+147+# ─── Write to shell profile ───────────────────────────────────────────────────148+149+SHELL_PROFILE="$HOME/.zshrc"150+if [ -f "$HOME/.bashrc" ] && [ ! -f "$HOME/.zshrc" ]; then151+  SHELL_PROFILE="$HOME/.bashrc"152+fi153+154+echo "" >> "$SHELL_PROFILE"155+echo "# Peach Chrome Web Store release credentials" >> "$SHELL_PROFILE"156+echo "export CWS_CLIENT_ID=\"${CWS_CLIENT_ID}\"" >> "$SHELL_PROFILE"157+echo "export CWS_CLIENT_SECRET=\"${CWS_CLIENT_SECRET}\"" >> "$SHELL_PROFILE"158+echo "export CWS_REFRESH_TOKEN=\"${CWS_REFRESH_TOKEN}\"" >> "$SHELL_PROFILE"159+echo "export CWS_EXTENSION_ID=\"${CWS_EXTENSION_ID}\"" >> "$SHELL_PROFILE"160+161+# Clear from memory162+unset CWS_CLIENT_SECRET163+unset CWS_REFRESH_TOKEN164+165+success "Credentials saved to $SHELL_PROFILE"166+167+# ─── Done ─────────────────────────────────────────────────────────────────────168+169+echo ""170+echo -e "${ACCENT}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"171+echo -e "${ACCENT}${BOLD}  Chrome release configured.${RESET}"172+echo -e "${ACCENT}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"173+echo ""174+echo "Reload your shell:"175+echo ""176+echo -e "  ${ACCENT}source ${SHELL_PROFILE}${RESET}"177+echo ""178+echo "Then to release to both stores:"179+echo ""180+echo -e "  ${ACCENT}npm run release${RESET}"181+echo ""

view file