generate-native-sync-manifest.mjs

3.4 KB · raw

1#!/usr/bin/env node2import { mkdir, readFile, writeFile } from "node:fs/promises";3import path from "node:path";4import process from "node:process";5import { pathToFileURL } from "node:url";67const HOST_NAME = "com.peachpasswords.sync";8const DESCRIPTION = "Peach Sync native messaging host";9const DEFAULT_HOST_PATH = "native/peach-sync-host/target/release/peach-sync-host";1011function 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}1819function 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}4041function toCamel(value) {42  return value.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());43}4445async 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}5455async 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  }6061  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  };6869  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  }7980  return {81    ...manifest,82    allowed_extensions: [options.extensionId ?? (await defaultFirefoxExtensionId(projectRoot))],83  };84}8586async function main() {87  const options = parseArgs(process.argv.slice(2));88  if (options.help) {89    console.log(usage());90    return;91  }9293  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}104105if (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}112113export { buildNativeManifest, parseArgs };