Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions .github/actions/compliance/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,23 @@ inputs:
default: 'dark'

offline:
description: 'When true, uses system fonts instead of loading Google Fonts. For air-gapped environments.'
description: 'When true, uses system fonts instead of loading Google Fonts. For air-gapped environments. Defaults to true for compliance reports — auditors typically open the bundle disconnected.'
required: false
default: 'false'
default: 'true'

single-page:
description: >
When true, emits a single self-contained `index.html` (typically
1-5 MB) with all sections inline — index / requirements / documents
/ STPA / EU AI Act / coverage / matrix / validation / graph. This
is the audit-bundle shape: one file, no per-page navigation, no
external resources, browser-openable offline.

When false, emits the full multi-page dashboard mirror (hundreds
of pages, ~100s of MB). Useful for browseable hosted docs but
oversized for an audit deliverable.
required: false
default: 'true'

# ── Rivet tool ─────────────────────────────────────────────────────
rivet-version:
Expand Down Expand Up @@ -170,6 +184,7 @@ runs:
REPORT_HOMEPAGE: ${{ inputs.homepage }}
REPORT_VERSIONS: ${{ inputs.other-versions }}
REPORT_OFFLINE: ${{ inputs.offline }}
REPORT_SINGLE_PAGE: ${{ inputs.single-page }}
run: |
ARGS="--format html --output ${REPORT_OUTPUT}"
ARGS="$ARGS --theme ${REPORT_THEME}"
Expand All @@ -187,6 +202,10 @@ runs:
ARGS="$ARGS --offline"
fi

if [ "$REPORT_SINGLE_PAGE" = "true" ]; then
ARGS="$ARGS --single-page"
fi

eval rivet export $ARGS
echo "report-dir=${REPORT_OUTPUT}" >> "$GITHUB_OUTPUT"

Expand Down
51 changes: 48 additions & 3 deletions rivet-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6573,9 +6573,9 @@ fn cmd_export_gherkin(
fn cmd_export_html(
cli: &Cli,
output: Option<&std::path::Path>,
_single_page: bool,
_theme: &str,
_offline: bool,
single_page: bool,
theme: &str,
offline: bool,
_homepage: Option<&str>,
_version_label: Option<&str>,
_versions_json: Option<&str>,
Expand All @@ -6593,6 +6593,51 @@ fn cmd_export_html(
let state = serve::reload_state(&project_path, &schemas_dir, 0)
.context("loading project for export")?;

// ── Single-page mode (audit-bundle shape) ────────────────────────
//
// The multi-page export below mirrors the `rivet serve` dashboard
// (~800 pages, ~800 MB) which is useful for browsing but not for an
// audit bundle. `--single-page` short-circuits to the dedicated
// single-page renderer in `rivet_core::export::render_single_page`:
// one self-contained `index.html` with the index / requirements /
// documents / STPA / EU-AI-Act / coverage / matrix / validation /
// graph sections inline, ~3-5 MB total. This is the path the
// `.github/actions/compliance` action calls; the CLI previously
// ignored the flag (the parameters were `_`-prefixed).
if single_page {
let config = rivet_core::export::ExportConfig {
theme: match theme {
"light" => rivet_core::export::ExportTheme::Light,
_ => rivet_core::export::ExportTheme::Dark,
},
offline,
};
let project_name = state.context.project_name.clone();
let version = env!("CARGO_PKG_VERSION").to_string();
let html = rivet_core::export::render_single_page(
&state.store,
&state.schema,
&state.graph,
&state.cached_diagnostics,
&project_name,
&version,
&config,
&state.doc_store,
);
let out_dir = output.unwrap_or(std::path::Path::new("dist"));
std::fs::create_dir_all(out_dir)
.with_context(|| format!("creating {}", out_dir.display()))?;
let out_file = out_dir.join("index.html");
std::fs::write(&out_file, &html)
.with_context(|| format!("writing {}", out_file.display()))?;
println!(
"Exported single-page HTML ({} bytes) to {}",
html.len(),
out_file.display(),
);
return Ok(true);
}

// Auto-detect baseline snapshot for delta rendering.
let snap_dir = project_path.join("snapshots");
let baseline_snapshot = find_latest_snapshot(&snap_dir)
Expand Down
8 changes: 6 additions & 2 deletions rivet-core/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1871,8 +1871,12 @@ fn render_section_graph(store: &Store, link_graph: &LinkGraph) -> String {
.get(n.as_str())
.map(|a| a.title.clone())
.unwrap_or_default();
let sublabel = if title.len() > 28 {
Some(format!("{}...", &title[..26]))
let sublabel = if title.chars().count() > 28 {
// Truncate by chars, not bytes — `&title[..26]` panics when
// byte 26 falls inside a multi-byte UTF-8 character (e.g.
// the em-dash `—`, 3 bytes). Take 26 chars and append `…`.
let truncated: String = title.chars().take(26).collect();
Some(format!("{truncated}…"))
} else if title.is_empty() {
None
} else {
Expand Down
Loading