refactor(docx-editor): ship it as a community extension, not part of the browser
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.
This commit is contained in:
parent
6b2c4b25c0
commit
5e4bd22609
19 changed files with 135 additions and 8 deletions
1
addon-build/docx-editor/.gitignore
vendored
1
addon-build/docx-editor/.gitignore
vendored
|
|
@ -7,3 +7,4 @@ test/fixture.docx
|
||||||
test/roundtrip-out.docx
|
test/roundtrip-out.docx
|
||||||
test/roundtrip-a.json
|
test/roundtrip-a.json
|
||||||
test/roundtrip-b.json
|
test/roundtrip-b.json
|
||||||
|
out/
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Bundles the add-on's npm dependencies into
|
// Bundles the extension's npm dependencies into
|
||||||
// bundled-addons/docx-editor/vendor/docx-vendor.js.
|
// extensions/docx-editor/vendor/docx-vendor.js.
|
||||||
//
|
//
|
||||||
// npm run build (from addon-build/docx-editor/)
|
// npm run build (from addon-build/docx-editor/)
|
||||||
//
|
//
|
||||||
|
|
@ -13,7 +13,7 @@ import path from "node:path";
|
||||||
import { applyPatches, patches } from "./patches.mjs";
|
import { applyPatches, patches } from "./patches.mjs";
|
||||||
|
|
||||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
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");
|
const patchedRoot = path.join(here, ".patched");
|
||||||
|
|
||||||
// --- 1. patched mammoth ---------------------------------------------------
|
// --- 1. patched mammoth ---------------------------------------------------
|
||||||
|
|
|
||||||
65
addon-build/docx-editor/pack.mjs
Normal file
65
addon-build/docx-editor/pack.mjs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Package the extension for the community channel.
|
||||||
|
//
|
||||||
|
// node pack.mjs (from addon-build/docx-editor/)
|
||||||
|
//
|
||||||
|
// Produces out/docx-editor-<version>.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/<publisher-name>/docx-editor/<version>
|
||||||
|
// x-bns-sig: BNS-EXT1\n<name>\n<id>\n<version>\n<sha256>\n<ts>
|
||||||
|
// x-bns-entry-sig: silentmode.extension-v1|<id>|<version>|<sha256>|<name>
|
||||||
|
// 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);
|
||||||
|
|
@ -2,10 +2,11 @@
|
||||||
"name": "docx-editor-vendor-build",
|
"name": "docx-editor-vendor-build",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"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",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node build.mjs"
|
"build": "node build.mjs",
|
||||||
|
"pack": "node pack.mjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"docx": "^9.5.1",
|
"docx": "^9.5.1",
|
||||||
|
|
|
||||||
|
|
@ -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 round-trip can be tested without driving a browser.
|
||||||
//
|
//
|
||||||
// The only difference from the real thing is where the vendored packages come
|
// 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 here = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const require = createRequire(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() {
|
export function loadAddonLibs() {
|
||||||
const dom = new JSDOM("<!doctype html><html><body></body></html>");
|
const dom = new JSDOM("<!doctype html><html><body></body></html>");
|
||||||
|
|
|
||||||
60
extensions/docx-editor/README.md
Normal file
60
extensions/docx-editor/README.md
Normal file
|
|
@ -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-<version>.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/<publisher-name>/docx-editor/<version>`. 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 <folder of .docx> # 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.
|
||||||
|
|
@ -7,5 +7,5 @@
|
||||||
"icon": "📝",
|
"icon": "📝",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"capabilities": ["sidebar-panel", "open-tab"],
|
"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"
|
||||||
}
|
}
|
||||||
Loading…
Add table
Reference in a new issue