theseus/addon-build/docx-editor/build.mjs
Local Dev 56b3f2f206 feat(docx-editor): ship it in the build, updated over the first-party channel
Moving it out of the build left it with no way in. Settings can only install
from the community catalogue, so a first-party extension that isn't bundled
has a working update channel and no first copy for anyone to update — the
mechanism was all there and the front door was missing.

So it goes back beside screenshot, aegis and pdf-editor: seeded into every
profile by the build, listed under "Built into Theseus", and kept current
between releases by the operator-signed channel at
theseus.x/extensions/docx-editor/. That is the arrangement docs/ADDON-UPDATES.md
describes, and the one the signing script was written for.

About 400 KB compressed in the installer, most of it the vendored editor
libraries — next to the ~4 MB of pdf.js that pdf-editor already ships, the
weight argument for keeping it out didn't survive contact with the numbers.

The end-to-end driver goes back to checking that a fresh profile seeds it,
which is the property that actually matters now.
2026-09-21 22:47:13 +02:00

77 lines
3.7 KiB
JavaScript

// Bundles the extension's npm dependencies into
// bundled-addons/docx-editor/vendor/docx-vendor.js.
//
// npm run build (from addon-build/docx-editor/)
//
// mammoth is patched on the way through — see patches.mjs for why. The
// patched copy is materialised under .patched/ so the node round-trip tests
// exercise exactly the same reader the browser does.
import * as esbuild from "esbuild";
import { mkdirSync, writeFileSync, readFileSync, statSync, rmSync, cpSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
import { applyPatches, patches } from "./patches.mjs";
const here = path.dirname(fileURLToPath(import.meta.url));
const out = path.resolve(here, "../../bundled-addons/docx-editor/vendor");
const patchedRoot = path.join(here, ".patched");
// --- 1. patched mammoth ---------------------------------------------------
rmSync(patchedRoot, { recursive: true, force: true });
mkdirSync(patchedRoot, { recursive: true });
cpSync(path.join(here, "node_modules", "mammoth"), path.join(patchedRoot, "mammoth"), { recursive: true });
const touched = new Set();
for (const p of patches) {
const f = path.join(patchedRoot, "mammoth", p.file);
if (touched.has(f)) continue;
touched.add(f);
}
for (const f of touched) {
const rel = "mammoth/" + path.relative(path.join(patchedRoot, "mammoth"), f).split(path.sep).join("/");
const { code, hits } = applyPatches(rel, readFileSync(f, "utf8"));
writeFileSync(f, code);
console.log(`patched ${rel} (${hits} hunks)`);
}
// --- 2. bundle ------------------------------------------------------------
mkdirSync(out, { recursive: true });
const res = await esbuild.build({
entryPoints: [path.join(here, "vendor-entry.js")],
bundle: true,
minify: true,
format: "iife",
platform: "browser",
target: ["chrome120"],
legalComments: "none",
outfile: path.join(out, "docx-vendor.js"),
define: { "process.env.NODE_ENV": '"production"' },
alias: { mammoth: path.join(patchedRoot, "mammoth") },
logLevel: "info",
});
if (res.errors.length) process.exit(1);
// --- 3. licences ----------------------------------------------------------
// Vendored code without its licence text is the kind of thing that bites
// later; this file ships next to the bundle.
const pkgs = ["mammoth", "docx", "jszip", "underscore", "orderedmap", "w3c-keyname",
"rope-sequence", "prosemirror-state", "prosemirror-view", "prosemirror-model",
"prosemirror-schema-basic", "prosemirror-schema-list", "prosemirror-tables",
"prosemirror-history", "prosemirror-commands", "prosemirror-keymap",
"prosemirror-inputrules", "prosemirror-dropcursor", "prosemirror-gapcursor",
"prosemirror-transform"];
let notice = "Third-party code bundled into vendor/docx-vendor.js\n" +
"===================================================\n\n" +
"mammoth is shipped with small local patches (colour, paragraph spacing,\n" +
"numbering format); see addon-build/docx-editor/patches.mjs.\n\n";
for (const p of pkgs) {
let j; try { j = JSON.parse(readFileSync(path.join(here, "node_modules", p, "package.json"), "utf8")); }
catch { continue; }
let text = "";
for (const f of ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "license", "LICENSE-MIT"]) {
try { text = readFileSync(path.join(here, "node_modules", p, f), "utf8").trim(); break; } catch {}
}
notice += `--- ${p} ${j.version}${j.license || "see project"} ---\n` +
(text || `(no licence file in the package; see ${j.homepage || j.repository?.url || "the project homepage"})`) + "\n\n";
}
writeFileSync(path.join(out, "LICENSES.txt"), notice);
console.log(`vendor bundle: ${(statSync(path.join(out, "docx-vendor.js")).size / 1024).toFixed(0)} KB`);