facett-ops/src/main.rs
//! **facett-ops** — the verbs that are NOT builds, invoked as `cargo ops <cmd>`
//! (alias in `.cargo/config.toml` for `cargo run -p facett-ops --`).
//!
//! THE LAW: *xtask is a THIN DOOR onto the build thing* and carries ONE dependency,
//! the recipe. Publishing a wheel, packing holger's index, pip-installing into a
//! clean container, running the Streamlit demo, staging and pushing the Pages site —
//! none of those build a row, and each needs a crate the recipe must not (bollard,
//! znippy, zip …: the recipe compiles to the wasm32 plugin the forge loads). So they
//! live here, in a plain workspace bin nothing loads as a plugin. See
//! `.nornir/xtask-thin-door-audit.md` §7.
//!
//! When a verb here needs a BUILD (`demo-wasm`, `deploy-pages`, `panel-wasm`), it
//! builds the recipe's ROW through `nornir_build_thing::container_road` — the same
//! writer `cargo xtask build` uses, in the pinned image — and names no package,
//! feature or triple.
//!
//! ## Commands
//! - `demo-wasm` / `deploy-pages` — the `facett-demo-wasm` row in the pinned image +
//! the Pages site shell; `deploy-pages` then pushes it to the `pages` branch.
//! - `panel-wasm` — the `facett-wasm-demo-panel` row + the harness's loader page.
//! - `publish-pip` · `publish-holger` · `verify-install [index]` · `verify-demo-install`
//! · `streamlit [--index URL]` · `test-demo [--index URL]` — the wheel road (python
//! only ever in a pinned container): the two PyPI releases, `facett` and `facett-demo`.
//! - `publish-pypi [--repository testpypi|pypi] [--dry-run] [files…]` — upload them
//! (the warehouse legacy API, in Rust; token from the environment, never printed;
//! every file passes the personal-name gate first, `pypi::name_gate`).
use facett_build_thing::nornir_build_thing;
use nornir_xtask_support as xs;
use xs::workspace_root_of;
use facett_ops::pages::{PAGES_BRANCH, PAGES_URL};
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
let cmd = args.first().map(String::as_str).unwrap_or("help");
let result: xs::Result<()> = match cmd {
"demo-wasm" => demo_wasm(),
"deploy-pages" => deploy_pages(),
"publish-pip" => publish_pip(),
"publish-pypi" => publish_pypi(),
"publish-holger" => publish_holger(),
"verify-install" => verify_install(),
"verify-demo-install" => verify_demo_install(),
"streamlit" => streamlit(),
"test-demo" => test_demo(),
"panel-wasm" => panel_wasm(),
"help" | "--help" | "-h" => {
print_help();
Ok(())
}
other => {
eprintln!("ops: unknown command `{other}` (builds are `cargo xtask build|wrap-wasm|buildgate`)\n");
print_help();
std::process::exit(2);
}
};
if let Err(e) = result {
eprintln!("\nops: {cmd} failed: {e}");
std::process::exit(1);
}
}
fn root() -> std::path::PathBuf {
workspace_root_of(env!("CARGO_MANIFEST_DIR").as_ref())
}
fn print_help() {
eprintln!(
"facett-ops — the verbs that are not builds (builds: `cargo xtask build`)\n\n\
USAGE:\n cargo ops <command>\n\n\
COMMANDS:\n\
\x20 demo-wasm Build the {} row IN the pinned image into its dist/ + page\n\
\x20 deploy-pages Build, then publish that dist/ to the `{PAGES_BRANCH}` branch ({PAGES_URL})\n\
\x20 panel-wasm Build the `{}` row (the {} `panel` example) into its dist/\n\
\x20 + the harness's loader page (the WebGPU render guard's fixture)\n\
\x20 publish-pip Build the facett + facett-demo WHEELS (and the demo's sdist)\n\
\x20 inside the pinned `nordisk-pip-toolchain` container (no python\n\
\x20 on the host), copy them to each cell's dist/ and VERIFY them back\n\
\x20 publish-pypi [--repository testpypi|pypi] [--dry-run] [files…]\n\
\x20 Upload to (Test)PyPI — token from /\n\
\x20 , never printed; default index testpypi\n\
\x20 publish-holger Pack both releases' dist/ into holger's PEP 503 pip\n\
\x20 archive (znippy as a library — no CLI, no shell)\n\
\x20 verify-install [ix] pip-install `facett` from an index in a clean container and judge it\n\
\x20 verify-demo-install Both wheels in a clean container (Streamlit from PyPI): facett\n\
\x20 alone without Streamlit, `facett-demo` starts and is healthy\n\
\x20 streamlit [--index URL] Run the INSTALLED facett-demo wheel in a container on :8501\n\
\x20 test-demo [--index URL] Run the demo's python suite in the container, on the wheels\n\
\x20 help Show this help\n",
facett_build_thing::DEMO_WASM_VARIANT,
facett_build_thing::PANEL_WASM_VARIANT,
facett_build_thing::HARNESS_PACKAGE,
);
}
/// `cargo ops demo-wasm` — the `facett-demo-wasm` row in the pinned image, then the
/// site shell ([`facett_ops::pages::demo_bundle`]).
fn demo_wasm() -> xs::Result<()> {
facett_ops::pages::demo_bundle(&root())?;
Ok(())
}
/// `cargo ops deploy-pages` — [`demo_wasm`], then publish the as-built dist to the
/// `pages` branch ([`facett_ops::pages::deploy`]).
fn deploy_pages() -> xs::Result<()> {
let sha = facett_ops::pages::deploy(&root())?;
println!("ops: ✔ pages commit {sha} → {PAGES_URL}");
Ok(())
}
// ── command: panel-wasm ───────────────────────────────────────────────────
//
// The demo HOST had no permanent render guard of its own: every `<repo>-demo` in the
// constellation mounts on `facett-wasm-demo`, so a regression in its web path (the
// backend it picks, a mount that silently refuses) would land in nornir's, holger's
// and dwarves's bundles at once with nothing failing here first. This verb builds the
// smallest bundle that exercises that path, for `tests/verify_webgpu_render.py` to
// drive in a headless browser.
/// The harness's `panel` example, built to wasm and assembled into a complete,
/// servable bundle in `facett-wasm-demo/dist/`: `panel.js`, `panel_bg.wasm` and the
/// `index.html` the harness itself renders.
///
/// The loader page is written by [`facett_wasm_demo::IndexHtml`] — the SAME renderer
/// every consuming repo's `demo-wasm` uses — so the guard drives the real page and a
/// change to the template is caught here rather than in four downstream repos.
///
/// The canvas id and state hook are the ones `examples/panel.rs` actually exports; a
///
/// The bundle is the recipe's `facett-wasm-demo-panel` ROW (a cargo EXAMPLE — edda's
/// `BuildVariant::example`, 2026-09-29), built by `nornir_build_thing::container_road` exactly as
/// `cargo xtask build --variant facett-wasm-demo-panel` builds it: cargo and its
/// bindgen stage in the pinned image, into the row's cell `out_dir`. This file names
/// no package, example or triple for it.
fn panel_wasm() -> xs::Result<()> {
let root = root();
let set = facett_build_thing::declared();
let row = set
.require(facett_build_thing::PANEL_WASM_VARIANT)
.map_err(|e| format!("{e:#}"))?;
set.validate_all_in(&root).map_err(|e| format!("facett-build-thing is refused: {e:#}"))?;
let (_, dist) = nornir_build_thing::container_road::build_wasm_row(&set, &row.name, &root)
.map_err(|e| format!("{e:#}"))?;
// The loader, from the harness's own template — never a second copy of it. The
// stem is the row's (what bindgen named the bundle after).
let stem = nornir_build_thing::wasm::artifact_stem(row);
facett_wasm_demo::IndexHtml {
stem: &stem,
// Must match `WebDemo::new("demo_canvas")` in examples/panel.rs.
canvas_id: "demo_canvas",
title: "facett-wasm-demo — panel harness",
description: "The reusable facett demo HOST, rendering its template panel on WebGPU.",
// Must match the `js_name = __demo_state` export in examples/panel.rs.
state_export: "__demo_state",
// No deploy here, so no git sha to bust with; the guard serves a fresh dir.
cache_bust: "dev",
}
.write(&dist)
.map_err(|e| format!("write index.html: {e}"))?;
let bytes = std::fs::metadata(dist.join(format!("{stem}_bg.wasm"))).map(|m| m.len()).unwrap_or(0);
println!("ops: ✔ panel bundle in {} ({bytes} bytes of wasm)", dist.display());
println!(
"ops: drive it: python {}/tests/verify_webgpu_render.py {}",
root.join(facett_build_thing::HARNESS_PACKAGE).display(),
dist.display()
);
Ok(())
}
// ── command: publish-pip ──────────────────────────────────────────────────
//
// The wheel is built IN A CONTAINER (the owner, 2026-09-05: "use a CONTAINER for the
// horrible python language"), and this function is the thin reporting shell over
// `facett_ops::pip`. Everything with logic lives in the library so `cargo test -p facett-ops`
// can drive the checking half without a container — see `tests/wheel_verify.rs`,
// which builds eleven broken wheels and asserts each one is refused BY NAME.
/// Run the containerised wheel build and print what it actually produced.
///
/// It prints NUMBERS, not "ok": the image ID that was pinned, the wheel's own
/// filename, and the byte length of each of the four shipped assets. A verb that
/// says "success" cannot tell a two-megabyte wasm module from a two-kilobyte one,
/// and the second one is a wheel that installs cleanly and renders nothing.
fn publish_pip() -> xs::Result<()> {
let root = root();
for out in facett_ops::pip::publish_pip(&root)? {
let sha = std::fs::read(&out.wheel).map(|b| facett_ops::pip::sha256_hex(&b)).unwrap_or_default();
let bytes = std::fs::metadata(&out.wheel).map(|m| m.len()).unwrap_or(0);
println!("\nops: ✔ {} wheel built and VERIFIED", out.release.project());
// The ID, when the engine can name it — that is the pin. When it cannot, say so
// in the line rather than printing the tag where an id belongs.
match &out.image_id {
Some(id) => println!(" image {} = {id}", out.image_ref),
None => println!(" image {} (UNPINNED — the engine could not name the id)", out.image_ref),
}
println!(" wheel {} ({bytes} bytes, sha256 {sha})", out.wheel.display());
println!(" dist {} {}", out.report.distribution, out.report.version);
println!(" files {} members, {} hash-checked against RECORD", out.report.files.len(), out.report.recorded);
for (asset, len) in &out.report.assets {
println!(" asset {asset}: {len} bytes");
}
if let Some(s) = &out.sdist {
let sha = std::fs::read(s).map(|b| facett_ops::pip::sha256_hex(&b)).unwrap_or_default();
println!(" sdist {} (sha256 {sha})", s.display());
}
}
Ok(())
}
// ── command: publish-pypi ─────────────────────────────────────────────────
//
// The warehouse legacy upload API, in Rust (`facett_ops::pypi`). Default index:
// TestPyPI — the real one is named, never defaulted to. With no files, both releases'
// wheels (+ the demo's sdist), components first.
/// Check, then upload (or with `--dry-run`, print what would be sent).
fn publish_pypi() -> xs::Result<()> {
let args: Vec<String> = std::env::args().skip(2).collect();
let mut opts = facett_ops::pypi::parse_args(&args)?;
if opts.files.is_empty() {
opts.files = facett_ops::pypi::default_files(&root())?;
}
for line in facett_ops::pypi::publish(&opts)? {
println!("{line}");
}
println!(
"\nops: ✔ {}",
if opts.dry_run { "dry run — every check passed, nothing was sent" } else { "uploaded" }
);
Ok(())
}
// ── command: publish-holger ───────────────────────────────────────────────
//
// The second half of "ready for release": `publish-pip` makes the artifact,
// this puts it where a `pip install --index-url …` can reach it. It replaces
// `pkg-facet-wrapped/publish_to_holger.sh` — a shell script (LAW breach) that
// additionally shelled out to a `znippy-cli` and a `holger-server` binary, NEITHER
// of which exists on this box. See `facett_ops::holger`.
/// Pack both releases (`facett`, `facett-demo`) into holger's pip archive.
fn publish_holger() -> xs::Result<()> {
let root = root();
let out = facett_ops::holger::publish_to_holger(&root)?;
println!("\nops: ✔ packed holger's pip archive");
println!(" archive {} ({} bytes)", out.archive.display(), out.archive_bytes);
for (path, len) in &out.packed {
println!(" served {path} ({len} bytes, byte-identical after extract)");
}
println!("\n serve: holger-server start --config {}", out.config.display());
println!(" install: pip install --index-url {} facett-demo", out.index_url);
Ok(())
}
// ── command: verify-install ───────────────────────────────────────────────
//
// Everything before this verb is our own code checking our own output. This is a
// REAL `pip`, resolving a REAL PEP 503 index, inside an image that has never seen
// this repo and has no source tree mounted — and the verdict is made HERE, in Rust,
// against what we built, not by the container's exit code.
/// pip-install the published wheel in a clean container and judge what came out.
fn verify_install() -> xs::Result<()> {
let root = root();
let index = std::env::args()
.nth(2)
.unwrap_or_else(|| "http://127.0.0.1:8455/pip/simple/".to_string());
// What we BUILT, read off the wheel on disk. The install is compared against
// this rather than against numbers written down here, so the comparison cannot
// drift from the artifact.
let dist = facett_ops::pip::dist_dir(&root);
let wheel = std::fs::read_dir(&dist)
.map_err(|e| format!("{}: {e}", dist.display()))?
.filter_map(|e| e.ok())
.map(|e| e.path())
.find(|p| p.to_string_lossy().ends_with(".whl"))
.ok_or("no wheel in pkg-facet-wrapped/dist — run `cargo ops publish-pip` first")?;
let built = facett_ops::pip::verify_wheel(&wheel)?;
let proof = facett_ops::holger::verify_install(&root, &index)?;
facett_ops::holger::judge_install(&proof, &built.version, &built.assets)?;
println!("\nops: ✔ INSTALLED from {index} and verified");
println!(" image {}{}", proof.image_ref, match &proof.image_id {
Some(id) => format!(" = {id}"),
None => " (UNPINNED)".to_string(),
});
println!(" version {} (as the installed package reports it)", proof.version);
println!(" assets {}", proof.assets_dir);
for (name, len) in &proof.assets {
println!(" installed {name}: {len} bytes (identical to what wrap-wasm built)");
}
println!(" wasm magic {} — a real module after the whole trip", proof.wasm_magic);
println!(" manifest {} components", proof.components);
Ok(())
}
// ── command: streamlit ────────────────────────────────────────────────────
//
// facett-demo, in a Streamlit application, in a container. The Gallery pane mounts
// all 27 wrapped components with real payloads and needs no korp-server — the
// payload table is held against the real registry by
// `tests/gallery_covers_the_manifest.rs`, which proves every shipped component is
// covered AND that every payload BUILDS through the same adapter the wasm calls.
/// Start the Streamlit demo in a container.
fn streamlit() -> xs::Result<()> {
let root = root();
let source = facett_ops::holger::WheelSource::from_args(&std::env::args().skip(2).collect::<Vec<_>>())?;
let id = facett_ops::holger::start_streamlit(&root, &source)?;
println!("\nops: ✔ streamlit demo running");
println!(" container {id}");
println!(" open http://127.0.0.1:{}", facett_ops::holger::STREAMLIT_PORT);
println!(" logs podman logs -f {id}");
println!(" stop podman rm -f {id}");
Ok(())
}
/// Run the demo's python suite inside the container, against the INSTALLED wheel.
fn test_demo() -> xs::Result<()> {
let root = root();
let source = facett_ops::holger::WheelSource::from_args(&std::env::args().skip(2).collect::<Vec<_>>())?;
let lines = facett_ops::holger::test_demo(&root, &source)?;
for l in lines.iter().rev().take(12).collect::<Vec<_>>().into_iter().rev() {
println!(" {}", l.trim_end());
}
println!("\nops: ✔ the demo's python suite is green against the installed wheels");
Ok(())
}
/// `cargo ops verify-demo-install` — both wheels in a clean container, judged in Rust.
fn verify_demo_install() -> xs::Result<()> {
let (c, lines) = facett_ops::holger::verify_demo_install(&root())?;
for l in lines.iter().filter(|l| {
let t = l.trim_start();
t.starts_with("streamlit ") || t.starts_with("facett") || t.starts_with("Successfully installed")
}) {
println!(" {}", l.trim_end());
}
println!("\nops: ✔ CLEAN INSTALL verified (judged in Rust)");
println!(" facett alone {}", c.alone);
println!(" facett-demo {}", c.demo);
Ok(())
}