pithos/scripts/build-theseus-addon.mjs
Local Dev 6b8eeaf82b Pithos: a control panel for s3d, the Sia S3 gateway
s3d is driven entirely by a CLI and a two-route admin API, which keeps
Sia storage out of reach for anyone who does not live in a terminal.
Pithos wraps it: install the pinned s3d release (checksum verified),
start/stop and logs, indexer login, users and access keys, a bucket and
object browser with share links and public-read policies, and the
upload pipeline stats.

One control server (core/) serves one UI (ui/), and three hosts run it:
a web console, an Electron desktop window and a Theseus add-on, so a
feature lands everywhere at once. The server binds loopback, rejects
foreign Host headers, and signs views in with a one-time secret that
becomes a SameSite=Strict cookie. s3d login reads the recovery phrase
with term.ReadPassword, so it is driven through a pty, with a console
window fallback where the pty module is not shipped.
2026-10-03 19:02:50 +02:00

49 lines
2.9 KiB
JavaScript

// Assembles the Theseus add-on folder: manifest + entry from theseus/, the
// shared core/ and ui/, and the one pure-JS dependency (yaml, vendored). The pty module
// is left out on purpose (12 MB of native code per platform); without it the
// login flow opens s3d in its own console window instead.
//
// node scripts/build-theseus-addon.mjs [outDir] default: dist/theseus-addon/pithos
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const out = path.resolve(process.argv[2] || path.join(ROOT, 'dist', 'theseus-addon', 'pithos'));
fs.rmSync(out, { recursive: true, force: true });
fs.mkdirSync(out, { recursive: true });
const copy = (from, to) => fs.cpSync(path.join(ROOT, from), path.join(out, to), { recursive: true });
copy('theseus/addon.json', 'addon.json');
copy('theseus/index.js', 'index.js');
copy('core', 'core');
copy('ui', 'ui');
// core/ is ESM; the add-on root stays CommonJS because the host require()s
// index.js. Both are pinned so a parent package.json cannot flip either.
fs.writeFileSync(path.join(out, 'package.json'), JSON.stringify({ type: 'commonjs', private: true }, null, 2) + '\n');
fs.writeFileSync(path.join(out, 'core', 'package.json'), JSON.stringify({ type: 'module' }, null, 2) + '\n');
// Vendored as vendor/yaml/lib, not node_modules/yaml/dist: Theseus git-ignores
// every node_modules/ and dist/ folder, so a clean-worktree release build
// would ship the add-on without it. yaml's dist only requires its own files
// and Node built-ins, so the folder moves as-is.
const yamlPkg = path.join(ROOT, 'node_modules', 'yaml');
fs.cpSync(path.join(yamlPkg, 'dist'), path.join(out, 'vendor', 'yaml', 'lib'), { recursive: true, filter: (f) => !f.endsWith('.d.ts') });
fs.copyFileSync(path.join(yamlPkg, 'LICENSE'), path.join(out, 'vendor', 'yaml', 'LICENSE'));
const yamlVersion = JSON.parse(fs.readFileSync(path.join(yamlPkg, 'package.json'), 'utf8')).version;
fs.writeFileSync(path.join(out, 'vendor', 'yaml', 'package.json'), JSON.stringify({ name: 'yaml', version: yamlVersion, type: 'commonjs', private: true }, null, 2) + '\n');
const configJs = path.join(out, 'core', 'config.js');
const src = fs.readFileSync(configJs, 'utf8');
const rewritten = src.replace("from 'yaml';", "from '../vendor/yaml/lib/index.js';");
if (rewritten === src) throw new Error('core/config.js no longer imports yaml the expected way');
fs.writeFileSync(configJs, rewritten);
fs.copyFileSync(path.join(ROOT, 'LICENSE'), path.join(out, 'LICENSE'));
const manifest = JSON.parse(fs.readFileSync(path.join(out, 'addon.json'), 'utf8'));
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
if (manifest.version !== pkg.version) {
console.warn(`warning: addon.json version ${manifest.version} != package.json ${pkg.version}`);
}
console.log(`built ${manifest.id} ${manifest.version} -> ${out}`);