From 5e4bd22609615d3fdc3958cfa97fa43e2ea7619f Mon Sep 17 00:00:00 2001 From: Local Dev Date: Mon, 21 Sep 2026 01:06:58 +0200 Subject: [PATCH] refactor(docx-editor): ship it as a community extension, not part of the browser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A .docx editor is a megabyte of vendored library. Bundling it would charge that to everyone who wanted a browser, including the people who will never open a Word document in it. So it leaves the build: out of bundled-addons/, out of extraResources, absent from a fresh profile. It arrives the way anyone else's extension does — Settings › Extensions › Community, from the catalogue the gateway builds, and listed on theseus.x/extensions alongside everything else published there. That also means it is signed by the owner of a BNS name rather than by the operator key, which is the right trust story for something that isn't part of the browser. `npm run pack` produces the tarball the publish page takes; the signature needs the publisher name's wallet, so it isn't something the repo can do. The end-to-end test now installs the extension into a throwaway profile the way the community installer would, and asserts up front that a fresh profile doesn't already have it — the bundling is what was being removed, so it is worth a test that would notice it coming back. --- addon-build/docx-editor/.gitignore | 1 + addon-build/docx-editor/build.mjs | 6 +- addon-build/docx-editor/pack.mjs | 65 +++++++++++++++++++ addon-build/docx-editor/package.json | 5 +- addon-build/docx-editor/test/harness.mjs | 4 +- extensions/docx-editor/README.md | 60 +++++++++++++++++ .../docx-editor/ROUND-TRIP.md | 0 .../docx-editor/addon.json | 2 +- .../docx-editor/editor.css | 0 .../docx-editor/editor.html | 0 .../docx-editor/editor.js | 0 .../docx-editor/index.js | 0 .../docx-editor/lib/pkg.js | 0 .../docx-editor/lib/read.js | 0 .../docx-editor/lib/schema.js | 0 .../docx-editor/lib/write.js | 0 .../docx-editor/panel.html | 0 .../docx-editor/vendor/LICENSES.txt | 0 .../docx-editor/vendor/docx-vendor.js | 0 19 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 addon-build/docx-editor/pack.mjs create mode 100644 extensions/docx-editor/README.md rename {bundled-addons => extensions}/docx-editor/ROUND-TRIP.md (100%) rename {bundled-addons => extensions}/docx-editor/addon.json (82%) rename {bundled-addons => extensions}/docx-editor/editor.css (100%) rename {bundled-addons => extensions}/docx-editor/editor.html (100%) rename {bundled-addons => extensions}/docx-editor/editor.js (100%) rename {bundled-addons => extensions}/docx-editor/index.js (100%) rename {bundled-addons => extensions}/docx-editor/lib/pkg.js (100%) rename {bundled-addons => extensions}/docx-editor/lib/read.js (100%) rename {bundled-addons => extensions}/docx-editor/lib/schema.js (100%) rename {bundled-addons => extensions}/docx-editor/lib/write.js (100%) rename {bundled-addons => extensions}/docx-editor/panel.html (100%) rename {bundled-addons => extensions}/docx-editor/vendor/LICENSES.txt (100%) rename {bundled-addons => extensions}/docx-editor/vendor/docx-vendor.js (100%) diff --git a/addon-build/docx-editor/.gitignore b/addon-build/docx-editor/.gitignore index 6a7c9ff..dae13e2 100644 --- a/addon-build/docx-editor/.gitignore +++ b/addon-build/docx-editor/.gitignore @@ -7,3 +7,4 @@ test/fixture.docx test/roundtrip-out.docx test/roundtrip-a.json test/roundtrip-b.json +out/ diff --git a/addon-build/docx-editor/build.mjs b/addon-build/docx-editor/build.mjs index 8846d5a..d49488b 100644 --- a/addon-build/docx-editor/build.mjs +++ b/addon-build/docx-editor/build.mjs @@ -1,5 +1,5 @@ -// Bundles the add-on's npm dependencies into -// bundled-addons/docx-editor/vendor/docx-vendor.js. +// Bundles the extension's npm dependencies into +// extensions/docx-editor/vendor/docx-vendor.js. // // npm run build (from addon-build/docx-editor/) // @@ -13,7 +13,7 @@ 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 out = path.resolve(here, "../../extensions/docx-editor/vendor"); const patchedRoot = path.join(here, ".patched"); // --- 1. patched mammoth --------------------------------------------------- diff --git a/addon-build/docx-editor/pack.mjs b/addon-build/docx-editor/pack.mjs new file mode 100644 index 0000000..06f9e2d --- /dev/null +++ b/addon-build/docx-editor/pack.mjs @@ -0,0 +1,65 @@ +// Package the extension for the community channel. +// +// node pack.mjs (from addon-build/docx-editor/) +// +// Produces out/docx-editor-.tar.gz and prints its sha256. +// +// This channel is NOT the operator-signed one that bundled add-ons use +// (scripts/sign-addon-update.mjs). A community extension is signed by the +// owner of a BNS name, with that name's wallet, and uploaded to the gateway: +// +// PUT https://silentmode.st/api/ext//docx-editor/ +// x-bns-sig: BNS-EXT1\n\n\n\n\n +// x-bns-entry-sig: silentmode.extension-v1|||| +// body: this tarball +// +// Both signatures are 65-byte BCH message signatures over the raw sha256 +// digest, made by the key that owns the publisher name's NFT. Nothing here +// can produce them — the wallet is the user's. The easy route is the publish +// page at theseus.x/extensions/publish, which unlocks a wallet in the +// browser, checks ownership, signs and PUTs the tarball you drop on it. +// +// Once the gateway accepts it, the extension appears in the catalog, which is +// what Settings › Extensions › Community and theseus.x/extensions both read. +import { execFileSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; +import fs from "node:fs"; +import path from "node:path"; +import crypto from "node:crypto"; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const addonDir = path.resolve(here, "../../extensions/docx-editor"); +const outDir = path.join(here, "out"); + +const manifest = JSON.parse(fs.readFileSync(path.join(addonDir, "addon.json"), "utf8")); +const { id, version } = manifest; +if (!id || !version) throw new Error("addon.json is missing id or version"); + +if (!fs.existsSync(path.join(addonDir, "vendor", "docx-vendor.js"))) { + throw new Error("vendor/docx-vendor.js is missing — run `npm run build` first"); +} + +fs.mkdirSync(outDir, { recursive: true }); +const tarPath = path.join(outDir, `${id}-${version}.tar.gz`); + +// Tar the CONTENTS of the folder so addon.json sits at the archive root, +// which is where both the gateway's validator and the installer look. +// +// On Windows, Git-Bash tar mistakes a drive letter for remote-archive +// host:file syntax and mangles backslashes on the way to argv; +// --force-local fixes the first and forward slashes fix the second. +const posix = (p) => p.replace(/\\/g, "/"); +execFileSync("tar", ["--force-local", "-c", "-z", "-f", posix(tarPath), + "-C", posix(addonDir), "."], { stdio: "inherit" }); + +const bytes = fs.readFileSync(tarPath); +const sha256 = crypto.createHash("sha256").update(bytes).digest("hex"); +const MAX = 8 * 1024 * 1024; + +console.log(`\n${id} ${version}`); +console.log(` tarball : ${tarPath}`); +console.log(` size : ${(bytes.length / 1024).toFixed(0)} KB${bytes.length > MAX ? " ** over the gateway's 8 MB limit **" : ""}`); +console.log(` sha256 : ${sha256}`); +console.log(`\nPublish it from theseus.x/extensions/publish — unlock the publisher name's`); +console.log(`wallet there, drop this tarball in, and it signs and uploads.`); +if (bytes.length > MAX) process.exit(1); diff --git a/addon-build/docx-editor/package.json b/addon-build/docx-editor/package.json index 63b9d8e..72a6efb 100644 --- a/addon-build/docx-editor/package.json +++ b/addon-build/docx-editor/package.json @@ -2,10 +2,11 @@ "name": "docx-editor-vendor-build", "private": true, "version": "0.0.0", - "description": "Build-time only: bundles mammoth + ProseMirror + docx into bundled-addons/docx-editor/vendor/.", + "description": "Build-time only: bundles mammoth + ProseMirror + docx into extensions/docx-editor/vendor/, and packs the extension for the community channel.", "type": "module", "scripts": { - "build": "node build.mjs" + "build": "node build.mjs", + "pack": "node pack.mjs" }, "devDependencies": { "docx": "^9.5.1", diff --git a/addon-build/docx-editor/test/harness.mjs b/addon-build/docx-editor/test/harness.mjs index 35458cf..0eac8e8 100644 --- a/addon-build/docx-editor/test/harness.mjs +++ b/addon-build/docx-editor/test/harness.mjs @@ -1,4 +1,4 @@ -// Loads the add-on's libraries the way the browser does, but under node, so +// Loads the extension's libraries the way the browser does, but under node, so // the round-trip can be tested without driving a browser. // // The only difference from the real thing is where the vendored packages come @@ -14,7 +14,7 @@ import { JSDOM } from "jsdom"; const here = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); -export const ADDON = path.resolve(here, "../../../bundled-addons/docx-editor"); +export const ADDON = path.resolve(here, "../../../extensions/docx-editor"); export function loadAddonLibs() { const dom = new JSDOM(""); diff --git a/extensions/docx-editor/README.md b/extensions/docx-editor/README.md new file mode 100644 index 0000000..31abec1 --- /dev/null +++ b/extensions/docx-editor/README.md @@ -0,0 +1,60 @@ +# Word editor — a Theseus community extension + +Opens, edits and saves Word documents (`.docx`) in a full Theseus tab. + +This is **not** a bundled add-on. It isn't in `bundled-addons/`, it isn't in +the installer's `extraResources`, and a fresh Theseus profile doesn't have it. +Users get it the same way they'd get anyone else's extension: from +**Settings › Extensions › Community**, which lists whatever is in the +catalogue at theseus.x/extensions. + +Living outside the browser build is the point. A .docx editor is a big +dependency — a megabyte of vendored library — and nobody should carry it +because they wanted a browser. + +## Building + +The vendored libraries (mammoth, ProseMirror, docx, JSZip) are bundled by a +build step that lives outside this folder, because mammoth needs local patches +before it can carry everything the editor edits: + + cd ../../addon-build/docx-editor + npm install + npm run build # writes vendor/docx-vendor.js here + npm run pack # writes out/docx-editor-.tar.gz + +`vendor/docx-vendor.js` is committed, so the extension is installable straight +from a checkout; re-run `npm run build` after touching anything under +`addon-build/`. + +## Publishing + +`npm run pack` produces the tarball and its sha256. Uploading it is a separate +step, and this repo can't do it: a community extension is signed by the +**owner of a BNS name**, using that name's wallet. + +The straightforward route is the publish page at +**theseus.x/extensions/publish** — it unlocks a wallet in the browser, checks +that the name is yours, then signs and `PUT`s the tarball to +`/api/ext//docx-editor/`. The gateway verifies both +signatures against the name's current owner, stores the tarball on Sia and +updates `catalog.json`, which is what Settings and the site both read. + +Version numbers must increase, and the id `docx-editor` belongs to whichever +name publishes it first. + +## Testing + +Three levels, all re-runnable, all from `../../addon-build/docx-editor`: + + node test/roundtrip.mjs # the built-in fixture + node test/corpus.mjs # real documents + node ../../../scratchpad/verify-docx-editor/drive.mjs # a real Theseus, over CDP + +The last one installs this folder into a throwaway profile the way the +community channel would, and is the only one that catches browser-only +breakage. + +What survives a round trip, and what doesn't, is written up in +[ROUND-TRIP.md](ROUND-TRIP.md). Read that before promising anyone a Word +feature. diff --git a/bundled-addons/docx-editor/ROUND-TRIP.md b/extensions/docx-editor/ROUND-TRIP.md similarity index 100% rename from bundled-addons/docx-editor/ROUND-TRIP.md rename to extensions/docx-editor/ROUND-TRIP.md diff --git a/bundled-addons/docx-editor/addon.json b/extensions/docx-editor/addon.json similarity index 82% rename from bundled-addons/docx-editor/addon.json rename to extensions/docx-editor/addon.json index 37a041f..b1034a4 100644 --- a/bundled-addons/docx-editor/addon.json +++ b/extensions/docx-editor/addon.json @@ -7,5 +7,5 @@ "icon": "📝", "main": "index.js", "capabilities": ["sidebar-panel", "open-tab"], - "updateURL": "https://navigate.st/bns/theseus.x/extensions/docx-editor/updates.json" + "updateURL": "https://navigate.st/bns/theseus.x/extensions/community/docx-editor/updates.json" } diff --git a/bundled-addons/docx-editor/editor.css b/extensions/docx-editor/editor.css similarity index 100% rename from bundled-addons/docx-editor/editor.css rename to extensions/docx-editor/editor.css diff --git a/bundled-addons/docx-editor/editor.html b/extensions/docx-editor/editor.html similarity index 100% rename from bundled-addons/docx-editor/editor.html rename to extensions/docx-editor/editor.html diff --git a/bundled-addons/docx-editor/editor.js b/extensions/docx-editor/editor.js similarity index 100% rename from bundled-addons/docx-editor/editor.js rename to extensions/docx-editor/editor.js diff --git a/bundled-addons/docx-editor/index.js b/extensions/docx-editor/index.js similarity index 100% rename from bundled-addons/docx-editor/index.js rename to extensions/docx-editor/index.js diff --git a/bundled-addons/docx-editor/lib/pkg.js b/extensions/docx-editor/lib/pkg.js similarity index 100% rename from bundled-addons/docx-editor/lib/pkg.js rename to extensions/docx-editor/lib/pkg.js diff --git a/bundled-addons/docx-editor/lib/read.js b/extensions/docx-editor/lib/read.js similarity index 100% rename from bundled-addons/docx-editor/lib/read.js rename to extensions/docx-editor/lib/read.js diff --git a/bundled-addons/docx-editor/lib/schema.js b/extensions/docx-editor/lib/schema.js similarity index 100% rename from bundled-addons/docx-editor/lib/schema.js rename to extensions/docx-editor/lib/schema.js diff --git a/bundled-addons/docx-editor/lib/write.js b/extensions/docx-editor/lib/write.js similarity index 100% rename from bundled-addons/docx-editor/lib/write.js rename to extensions/docx-editor/lib/write.js diff --git a/bundled-addons/docx-editor/panel.html b/extensions/docx-editor/panel.html similarity index 100% rename from bundled-addons/docx-editor/panel.html rename to extensions/docx-editor/panel.html diff --git a/bundled-addons/docx-editor/vendor/LICENSES.txt b/extensions/docx-editor/vendor/LICENSES.txt similarity index 100% rename from bundled-addons/docx-editor/vendor/LICENSES.txt rename to extensions/docx-editor/vendor/LICENSES.txt diff --git a/bundled-addons/docx-editor/vendor/docx-vendor.js b/extensions/docx-editor/vendor/docx-vendor.js similarity index 100% rename from bundled-addons/docx-editor/vendor/docx-vendor.js rename to extensions/docx-editor/vendor/docx-vendor.js