report: storebound report writes one shareable, self-contained readiness page (.storebound/reports/readiness-<date>.html; --md, --json, --no-open, --offline, --out) from scan, check and validate: a verdict per store (Ready to submit / N blockers / Not checked with the reason), findings per store by severity with guideline, evidence, fix and console button, the owner's compliance questions as a checklist, secrets masked; storebound.dev look, light / dark, print; desktop Checks > Report (same code path, opens through project_reveal) and its Markdown export on the same renderer; read-only MCP tool report

This commit is contained in:
CRTkafa 2026-10-18 01:42:03 +08:00
parent 6d95ea00f5
commit af349b3db1
18 changed files with 792 additions and 12 deletions

View file

@ -174,9 +174,14 @@ pub fn project_relative(rel: &str) -> Option<std::path::PathBuf> {
Some(p.to_path_buf())
}
/// What `project_reveal` may open (not only show in the file manager): a folder, or the gallery page storebound writes.
/// What `project_reveal` may open (not only show in the file manager): a folder, the gallery page storebound writes,
/// or a readiness report page of `storebound report` (.storebound/reports/readiness-*.html, no scripts in it).
pub fn openable(rel: &std::path::Path, is_dir: bool) -> bool {
is_dir || rel.file_name().and_then(|n| n.to_str()) == Some("index.html")
if is_dir || rel.file_name().and_then(|n| n.to_str()) == Some("index.html") {
return true;
}
let parts: Vec<&str> = rel.components().filter_map(|c| c.as_os_str().to_str()).collect();
matches!(parts.as_slice(), [".storebound", "reports", name] if name.starts_with("readiness-") && name.ends_with(".html") && name.len() <= 80)
}
/// Hosts `open_external` may open in the browser: the two store consoles and storebound's own site.

View file

@ -32,6 +32,10 @@ fn app_folder_paths() {
assert!(openable(Path::new("store/renders/index.html"), false));
assert!(openable(Path::new("store/renders"), true));
assert!(!openable(Path::new("store/renders/run.sh"), false));
assert!(openable(Path::new(".storebound/reports/readiness-2026-10-18.html"), false));
assert!(!openable(Path::new(".storebound/reports/readiness-2026-10-18.sh"), false));
assert!(!openable(Path::new(".storebound/credentials.json"), false));
assert!(!openable(Path::new("other/reports/readiness-2026-10-18.html"), false));
assert!(!openable(Path::new("store/prices.csv"), false));
}