/**
* Publishes the overlay module (JS + CSS) to the server so installed
* extensions hot-update their UI without reinstalling.
*
* Usage:
* LTT_EMAIL=... LTT_PASSWORD=... node scripts/publish.mjs
* LTT_TOKEN=... LTT_SERVER=https://bugtrail.example node scripts/publish.mjs
*
* LTT_SERVER defaults to http://localhost:8001. Requires a prior
* `node scripts/build.mjs chrome` (and firefox for the sha check).
*/
import { createHash } from "node:crypto";
import { createRequire } from "node:module";
import { existsSync, readFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const require = createRequire(import.meta.url);
const root = dirname(fileURLToPath(import.meta.url));
const extDir = join(root, "..");
const version = require(join(extDir, "..", "..", "package.json")).version;
const server = (process.env.LTT_SERVER ?? "http://localhost:8001").replace(/\/+$/, "");
function readAsset(name) {
const path = join(extDir, "dist", "chrome", "assets", name);
if (!existsSync(path)) {
console.error(`publish: missing ${path} — run \`node scripts/build.mjs chrome\` first`);
process.exit(1);
}
const content = readFileSync(path);
// chrome and firefox builds share the overlay module; a mismatch means the
// targets were built from different sources — warn but keep going
const firefoxPath = join(extDir, "dist", "firefox", "assets", name);
if (existsSync(firefoxPath)) {
const firefoxSha = createHash("sha256").update(readFileSync(firefoxPath)).digest("hex");
const chromeSha = createHash("sha256").update(content).digest("hex");
if (firefoxSha !== chromeSha) {
console.warn(`publish: warning — ${name} differs between chrome and firefox builds`);
}
}
return content;
}
const overlayJs = readAsset("overlay.js");
const overlayCss = readAsset("overlay.css");
async function main() {
let token = process.env.LTT_TOKEN;
if (!token) {
const email = process.env.LTT_EMAIL;
const password = process.env.LTT_PASSWORD;
if (!email || !password) {
console.error("publish: set LTT_TOKEN or LTT_EMAIL+LTT_PASSWORD (and optionally LTT_SERVER)");
process.exit(1);
}
const loginResponse = await fetch(`${server}/api/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json", "X-Client": "extension" },
body: JSON.stringify({ email, password }),
});
if (!loginResponse.ok) {
console.error(`publish: login failed (${loginResponse.status})`);
process.exit(1);
}
token = (await loginResponse.json()).token;
}
const response = await fetch(`${server}/api/ext/publish`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
body: JSON.stringify({
version,
files: {
"overlay.js": overlayJs.toString("base64"),
"overlay.css": overlayCss.toString("base64"),
},
}),
});
if (!response.ok) {
const detail = await response.json().catch(() => null);
console.error(`publish failed (${response.status}):`, JSON.stringify(detail));
process.exit(1);
}
const manifest = await response.json();
console.log(`published overlay ${manifest.version} -> ${server}/api/ext/manifest`);
console.log(JSON.stringify(manifest.assets, null, 2));
}
main().catch((error) => {
console.error("publish failed:", error.message);
process.exit(1);
});