Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

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(())
}