deviant/scripts/bundle-surfaces.mjs
Local Dev debd6a6fbb chore: sweep in-flight work across all domains
Snapshot commit rolling up ~255 files of coherent-but-multi-domain in-flight
work that had accumulated on this branch. Split by area:

Contracts + covenants
- New: AsmFxSwap, DeviantOpenMintNBA, DeviantTieredMintOracleFT,
  TermCouponVault, UniformReserve, UniformReserveAggregate.
- Verify-artifact refresh (src/verify/artifacts/*.json + canonical.ts).

Wallet + shell (shells/app + src/wallet)
- App.tsx, MainScreen, ArtemisionScreen, AssetDetail, AgoraScreen,
  SettingsScreen, Unlock, TokenImportModal, UniformFiat/DepositPanel,
  KatalogosPanel (renamed from PinaxPanel), shell.css.
- src/wallet: wallet.ts, runtime.ts, index.ts. Core registry/chipnetFile.
- Deleted stale hook: useAllDevBalances.ts.

Cross-chain + correspondent (services/correspondent + scripts)
- Correspondent server/bch/config updates for PYUSD Solana, EURC Sepolia,
  aggregate reserve flows.
- New scripts: PYUSD roundtrip, EURC onboarding, GMX game loop /
  genesis / mint sanity, tiered mint fee, aggregate reserve, product2
  shares dividends, product3 t-bond, uniform fiat cell tests.

Telegram order-book (P2PExchange bot)
- db.ts, index.ts, en/ru locales — updates upstream from the extracted
  standalone repo at D:/Dev/P2PExchange.

Docs — Parameters (specs), wiki, snapshots, design mockups
- Parameters: FIATm/MBA_Shares/Potidaea/Anthemus refinements +
  ASM_CrossChain/DebitCredit + UniformFiat + Anthemus DEX MVP prompt +
  Artemision + Athenae Politeia + Philosophy + SSHkeyServer runbook +
  new dApp/{Artemision,Katalogos,Pinax,Pari_escrow,SURFACES}.
- wiki: cross-chain, games-tote, glossary, governance/voting-systems,
  infra, wallet, wallet-ui-design, decisions (ADR log).
- snapshots: catch-up entries and INDEX regeneration.
- design: mockups refreshed (agora, asset-detail, assets-tab, potidaea)
  + Potidaea assets + tote deploy notes.

Housekeeping
- .gitignore: exclude the new root test/session state files
  (.sol-vault-keys.json, .football-tote-state.json,
  .tote-platform-state.json, .redeem-roundtrip-state.json,
  .eurc-onboard-state.json — some contain oracle privkeys / vault
  secretKeyBase58; NEVER commit) + the .usdm-historical-chart.html
  chart output + MS Office `~$*` lock files + scripts/bundle-surfaces
  `build/` output. Also add scripts/bundle-surfaces.mjs.
- CLAUDE.md, WALLET-COORDINATION.md, snapshots/INDEX.md, design/README.md
  refreshed.
- package.json / package-lock.json bumps.

Verified: sensitive files at repo root confirmed ignored (git check-ignore
each). No `.env`, no raw WIF/xprv/mnemonic/secretKey/oraclePriv strings in
the staged diff. MS Word autosave file (`Parameters/Philosophy/~$*.docx`)
excluded and gitignored.

The WizardConnect responder work landed separately in the previous commit
(6987591); this is everything else that had accumulated alongside it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-29 01:14:33 +02:00

148 lines
6 KiB
JavaScript

#!/usr/bin/env node
/**
* bundle-surfaces.mjs — make the Artemision design surfaces deployable, one
* subdomain per page.
*
* The pages are authored to run from `file://` inside the repo, so they carry
* two repo-shaped dependencies that DO NOT survive being moved to a subdomain
* root:
*
* 1. `../../../design/cosmo-genesis.css` — three levels up. At a subdomain
* root there is no "three levels up", so the page loads unstyled.
* 2. `../Katalogos/index.html` and friends — sibling-folder links. On separate
* subdomains these are dead.
*
* This copies each page into its own output folder, inlines the design system
* next to it, and rewrites the cross-links to absolute subdomain URLs.
*
* node scripts/bundle-surfaces.mjs --domain example.com
* node scripts/bundle-surfaces.mjs --domain example.com --out build/surfaces
*
* Output: build/surfaces/<sub>/index.html + cosmo-genesis.css, one per surface.
* Everything is static — no server code, no build toolchain, no secrets.
*/
import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
const ROOT = join(import.meta.dirname, '..');
const args = process.argv.slice(2);
const arg = (name, fallback) => {
const i = args.indexOf(`--${name}`);
return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
};
const DOMAIN = arg('domain');
const OUT = join(ROOT, arg('out', 'build/surfaces'));
if (!DOMAIN) {
console.error('usage: node scripts/bundle-surfaces.mjs --domain <example.com> [--out <dir>]');
process.exit(1);
}
/** subdomain → source page. The subdomain is the key. */
const SURFACES = {
artemision: 'Parameters/dApp/Artemision/website.html', // the public site
create: 'Parameters/dApp/Artemision/index.html', // the interactive desk
pinax: 'Parameters/dApp/Pinax/index.html',
katalogos: 'Parameters/dApp/Katalogos/index.html',
};
/** Repo-relative link → absolute subdomain URL. Longest patterns first. */
const LINK_MAP = [
['../Artemision/website.html', `https://artemision.${DOMAIN}/`],
['../Artemision/index.html', `https://create.${DOMAIN}/`],
['../Katalogos/index.html', `https://katalogos.${DOMAIN}/`],
['../Pinax/index.html', `https://pinax.${DOMAIN}/`],
['../Explorer/index.html', `https://katalogos.${DOMAIN}/`], // retired prototype → its successor
['../SURFACES.md', `https://artemision.${DOMAIN}/`],
// Same-folder links (the website ↔ desk pair live in one source folder).
['href="index.html"', `href="https://create.${DOMAIN}/"`],
['\'index.html\'', `'https://create.${DOMAIN}/'`],
];
const CSS_SRC = join(ROOT, 'design/cosmo-genesis.css');
if (!existsSync(CSS_SRC)) {
console.error(`missing design system at ${CSS_SRC}`);
process.exit(1);
}
const css = readFileSync(CSS_SRC, 'utf8');
rmSync(OUT, { recursive: true, force: true });
let built = 0;
for (const [sub, rel] of Object.entries(SURFACES)) {
const src = join(ROOT, rel);
if (!existsSync(src)) { console.error(` ! missing ${rel} — skipped`); continue; }
let html = readFileSync(src, 'utf8');
// 1. the design system now sits beside the page
const before = html;
html = html.replaceAll('../../../design/cosmo-genesis.css', 'cosmo-genesis.css');
if (html === before) console.warn(` ~ ${sub}: no cosmo-genesis.css link found — check the page`);
// 2. cross-links become absolute subdomain URLs
for (const [from, to] of LINK_MAP) html = html.replaceAll(from, to);
// 3. anything still pointing up a directory would 404 at a subdomain root
const stragglers = [...html.matchAll(/(?:href|src)="(\.\.\/[^"]*)"/g)].map((m) => m[1]);
if (stragglers.length) {
console.warn(` ~ ${sub}: unresolved relative links → ${[...new Set(stragglers)].join(', ')}`);
}
const dir = join(OUT, sub);
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, 'index.html'), html);
writeFileSync(join(dir, 'cosmo-genesis.css'), css);
console.log(` ✓ ${sub}.${DOMAIN} ← ${rel}`);
built++;
}
// nginx: one TLS server block per surface, all static, no backend.
const nginx = Object.keys(SURFACES).map((sub) => `
server {
listen 80;
listen [::]:80;
server_name ${sub}.${DOMAIN};
# ACME first, then force TLS — WebCrypto and the clipboard API both require
# a secure context, and the Katalogos opens a WSS connection.
location /.well-known/acme-challenge/ { root /var/www/certbot; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ${sub}.${DOMAIN};
ssl_certificate /etc/letsencrypt/live/${sub}.${DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${sub}.${DOMAIN}/privkey.pem;
root /var/www/surfaces/${sub};
index index.html;
# Static pages that talk to a public Electrum over WSS. No backend, no
# cookies, no keys — so the policy can be strict.
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "no-referrer" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location = /index.html { add_header Cache-Control "no-cache"; }
location ~* \\.(css|js|svg|woff2?)$ { expires 7d; add_header Cache-Control "public"; }
location / { try_files $uri $uri/ =404; }
}`).join('\n');
mkdirSync(OUT, { recursive: true });
writeFileSync(join(OUT, 'surfaces.nginx.conf'), nginx.trimStart() + '\n');
const subs = Object.keys(SURFACES);
writeFileSync(join(OUT, 'DNS.txt'),
`A records to create (point each at the VPS that will serve these):\n\n` +
subs.map((s) => ` ${s}.${DOMAIN}\tA\t<VPS_IP>`).join('\n') +
`\n\nThen, on the VPS:\n\n certbot certonly --webroot -w /var/www/certbot ` +
subs.map((s) => `-d ${s}.${DOMAIN}`).join(' ') + `\n`);
console.log(`\n${built} surface(s) → ${OUT}`);
console.log(` surfaces.nginx.conf — ${subs.length} server blocks`);
console.log(` DNS.txt — the A records + the certbot line`);