0607c6c271d5
chore: Add native sync manifest generator (v2.13.15)
- time
- 2026-04-30 14:05 UTC · 6 months ago
- machine
- koh2
- parent
- 7d8739461dec
↓ peach-browser-0607c6c2.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+3 −2scripts/generate-native-sync-manifest.mjs+113 −0src/shared/native-sync-manifest-script.test.ts+54 −0manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "2.13.14",4+ "version": "2.13.15",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "2.13.14",4+ "version": "2.13.15",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",package.json+3 −2
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "2.13.14",3+ "version": "2.13.15",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {@@ -14,7 +14,8 @@1414 "test": "vitest run",1515 "test:watch": "vitest",1616 "test:passkeys": "npm run build:e2e && playwright test tests/passkeys.spec.ts",17- "build:mobile": "pnpm vite build --config vite.config.mobile.ts && node scripts/copy-mobile-index.js"17+ "build:mobile": "pnpm vite build --config vite.config.mobile.ts && node scripts/copy-mobile-index.js",18+ "native:manifest": "node scripts/generate-native-sync-manifest.mjs"1819 },1920 "dependencies": {2021 "@bokuweb/zstd-wasm": "^0.0.27",scripts/generate-native-sync-manifest.mjs+113 −0
@@ -0,0 +1,113 @@1+#!/usr/bin/env node2+import { mkdir, readFile, writeFile } from "node:fs/promises";3+import path from "node:path";4+import process from "node:process";5+import { pathToFileURL } from "node:url";6+7+const HOST_NAME = "com.peachpasswords.sync";8+const DESCRIPTION = "Peach Sync native messaging host";9+const DEFAULT_HOST_PATH = "native/peach-sync-host/target/release/peach-sync-host";10+11+function usage() {12+ return [13+ "Usage:",14+ " node scripts/generate-native-sync-manifest.mjs --browser chrome --extension-id <chrome-id> [--host-path <path>] [--out <path>]",15+ " node scripts/generate-native-sync-manifest.mjs --browser firefox [--extension-id <gecko-id>] [--host-path <path>] [--out <path>]",16+ ].join("\n");17+}18+19+function parseArgs(argv) {20+ const options = {};21+ for (let i = 0; i < argv.length; i += 1) {22+ const arg = argv[i];23+ if (arg === "--help" || arg === "-h") {24+ options.help = true;25+ continue;26+ }27+ if (!arg.startsWith("--")) {28+ throw new Error(`unexpected argument: ${arg}`);29+ }30+ const key = arg.slice(2);31+ const value = argv[i + 1];32+ if (!value || value.startsWith("--")) {33+ throw new Error(`missing value for --${key}`);34+ }35+ options[toCamel(key)] = value;36+ i += 1;37+ }38+ return options;39+}40+41+function toCamel(value) {42+ return value.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());43+}44+45+async function defaultFirefoxExtensionId(projectRoot) {46+ const manifestPath = path.join(projectRoot, "manifest.firefox.json");47+ const manifest = JSON.parse(await readFile(manifestPath, "utf8"));48+ const id = manifest?.browser_specific_settings?.gecko?.id;49+ if (typeof id !== "string" || id.length === 0) {50+ throw new Error("manifest.firefox.json is missing browser_specific_settings.gecko.id");51+ }52+ return id;53+}54+55+async function buildNativeManifest(options, projectRoot = process.cwd()) {56+ const browser = options.browser;57+ if (browser !== "chrome" && browser !== "firefox") {58+ throw new Error("--browser must be chrome or firefox");59+ }60+61+ const hostPath = path.resolve(projectRoot, options.hostPath ?? DEFAULT_HOST_PATH);62+ const manifest = {63+ name: HOST_NAME,64+ description: DESCRIPTION,65+ path: hostPath,66+ type: "stdio",67+ };68+69+ if (browser === "chrome") {70+ const extensionId = options.extensionId ?? process.env.PEACH_CHROME_EXTENSION_ID;71+ if (!extensionId) {72+ throw new Error("--extension-id or PEACH_CHROME_EXTENSION_ID is required for Chrome");73+ }74+ return {75+ ...manifest,76+ allowed_origins: [`chrome-extension://${extensionId}/`],77+ };78+ }79+80+ return {81+ ...manifest,82+ allowed_extensions: [options.extensionId ?? (await defaultFirefoxExtensionId(projectRoot))],83+ };84+}85+86+async function main() {87+ const options = parseArgs(process.argv.slice(2));88+ if (options.help) {89+ console.log(usage());90+ return;91+ }92+93+ const manifest = await buildNativeManifest(options);94+ const json = `${JSON.stringify(manifest, null, 2)}\n`;95+ if (options.out) {96+ const outPath = path.resolve(process.cwd(), options.out);97+ await mkdir(path.dirname(outPath), { recursive: true });98+ await writeFile(outPath, json, { mode: 0o644 });99+ console.log(outPath);100+ return;101+ }102+ process.stdout.write(json);103+}104+105+if (import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {106+ main().catch((err) => {107+ console.error(err instanceof Error ? err.message : String(err));108+ console.error(usage());109+ process.exitCode = 1;110+ });111+}112+113+export { buildNativeManifest, parseArgs };src/shared/native-sync-manifest-script.test.ts+54 −0
@@ -0,0 +1,54 @@1+import { execFile } from "node:child_process";2+import { promisify } from "node:util";3+import { describe, expect, it } from "vitest";4+5+const execFileAsync = promisify(execFile);6+const scriptPath = "scripts/generate-native-sync-manifest.mjs";7+8+describe("native sync manifest generator", () => {9+ it("generates a Chrome native messaging host manifest", async () => {10+ const { stdout } = await execFileAsync(process.execPath, [11+ scriptPath,12+ "--browser",13+ "chrome",14+ "--extension-id",15+ "abcdefghijklmnopabcdefghijklmnop",16+ "--host-path",17+ "/opt/peach/peach-sync-host",18+ ]);19+20+ expect(JSON.parse(stdout)).toEqual({21+ name: "com.peachpasswords.sync",22+ description: "Peach Sync native messaging host",23+ path: "/opt/peach/peach-sync-host",24+ type: "stdio",25+ allowed_origins: ["chrome-extension://abcdefghijklmnopabcdefghijklmnop/"],26+ });27+ });28+29+ it("generates a Firefox native messaging host manifest using the Gecko id", async () => {30+ const { stdout } = await execFileAsync(process.execPath, [31+ scriptPath,32+ "--browser",33+ "firefox",34+ "--host-path",35+ "/opt/peach/peach-sync-host",36+ ]);37+38+ expect(JSON.parse(stdout)).toEqual({39+ name: "com.peachpasswords.sync",40+ description: "Peach Sync native messaging host",41+ path: "/opt/peach/peach-sync-host",42+ type: "stdio",43+ allowed_extensions: ["[email protected]"],44+ });45+ });46+47+ it("requires a Chrome extension id", async () => {48+ await expect(49+ execFileAsync(process.execPath, [scriptPath, "--browser", "chrome"], {50+ env: { ...process.env, PEACH_CHROME_EXTENSION_ID: "" },51+ })52+ ).rejects.toThrow("--extension-id or PEACH_CHROME_EXTENSION_ID is required for Chrome");53+ });54+});