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.
49 lines
2.9 KiB
JavaScript
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}`);
|