seam-cli 0.5.38

CLI for the SeamJS compile-time rendering framework
/* src/cli/core/src/build/route/types.rs */

use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

use seam_skeleton::Axis;

// -- Node script output types --

#[derive(Deserialize)]
pub(crate) struct SkeletonLayout {
	pub(crate) id: String,
	// i18n OFF: single html
	#[serde(default)]
	pub(crate) html: Option<String>,
	// i18n ON: per-locale html
	#[serde(rename = "localeHtml", default)]
	pub(crate) locale_html: Option<BTreeMap<String, String>>,
	#[serde(default)]
	pub(crate) loaders: serde_json::Value,
	#[serde(rename = "i18nKeys", default)]
	pub(crate) i18n_keys: Option<Vec<String>>,
	pub(crate) parent: Option<String>,
}

#[derive(Deserialize)]
pub(crate) struct CacheStats {
	pub(crate) hits: u32,
	pub(crate) misses: u32,
}

#[derive(Deserialize)]
pub(crate) struct SkeletonOutput {
	#[serde(default)]
	pub(crate) layouts: Vec<SkeletonLayout>,
	pub(crate) routes: Vec<SkeletonRoute>,
	#[serde(rename = "sourceFileMap", default)]
	pub(crate) source_file_map: Option<BTreeMap<String, String>>,
	#[serde(default)]
	pub(crate) warnings: Vec<String>,
	#[serde(rename = "cacheStats", default)]
	pub(crate) cache: Option<CacheStats>,
}

#[derive(Deserialize)]
pub(crate) struct SkeletonRoute {
	pub(super) path: String,
	pub(super) loaders: serde_json::Value,
	#[serde(default)]
	pub(super) derives: Option<BTreeMap<String, DeriveManifestEntry>>,
	// i18n OFF: flat fields (backward compatible)
	#[serde(default)]
	pub(super) axes: Option<Vec<Axis>>,
	#[serde(default)]
	pub(super) variants: Option<Vec<RenderedVariant>>,
	#[serde(rename = "mockHtml", default)]
	pub(super) mock_html: Option<String>,
	// i18n ON: per-locale data
	#[serde(rename = "localeVariants", default)]
	pub(super) locale_variants: Option<BTreeMap<String, LocaleRouteData>>,
	pub(super) mock: serde_json::Value,
	#[serde(rename = "pageSchema")]
	pub(super) page_schema: Option<serde_json::Value>,
	#[serde(default)]
	pub(super) layout: Option<String>,
	#[serde(rename = "headMeta", default)]
	pub(super) head_meta: Option<String>,
	#[serde(rename = "i18nKeys", default)]
	pub(super) i18n_keys: Option<Vec<String>>,
	#[serde(default)]
	pub(crate) prerender: Option<bool>,
}

#[derive(Deserialize)]
pub(super) struct LocaleRouteData {
	pub(super) axes: Vec<Axis>,
	pub(super) variants: Vec<RenderedVariant>,
	#[serde(rename = "mockHtml")]
	pub(super) mock_html: String,
}

#[derive(Deserialize)]
pub(super) struct RenderedVariant {
	#[serde(rename = "variant")]
	pub(super) _variant: serde_json::Value,
	pub(super) html: String,
}

/// Serialized derive entry: client-side data derivation from loader results.
#[derive(Clone, Serialize, Deserialize)]
pub(crate) struct DeriveManifestEntry {
	pub(crate) sources: Vec<String>,
	#[serde(rename = "fn")]
	pub(crate) fn_source: String,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(crate) output: Option<serde_json::Value>,
}

// -- Manifest metadata for freshness checks --

#[derive(Serialize, Deserialize)]
pub(crate) struct ManifestMeta {
	pub(crate) seam_version: String,
	pub(crate) config_hash: String,
}

// -- Route manifest output --

#[derive(Serialize, Deserialize, Clone)]
pub(super) struct LayoutManifestEntry {
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) template: Option<String>,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) templates: Option<BTreeMap<String, String>>,
	#[serde(skip_serializing_if = "serde_json::Value::is_null")]
	pub(super) loaders: serde_json::Value,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) parent: Option<String>,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) i18n_keys: Option<Vec<String>>,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) projections: Option<BTreeMap<String, Vec<String>>>,
}

#[derive(Serialize, Deserialize)]
pub(crate) struct RouteManifest {
	#[serde(skip_serializing_if = "Option::is_none", default)]
	pub(crate) _meta: Option<ManifestMeta>,
	#[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
	pub(super) layouts: BTreeMap<String, LayoutManifestEntry>,
	#[serde(default)]
	pub(super) routes: BTreeMap<String, RouteManifestEntry>,
	#[serde(skip_serializing_if = "Option::is_none", default)]
	pub(super) data_id: Option<String>,
	#[serde(skip_serializing_if = "Option::is_none", default)]
	pub(super) i18n: Option<I18nManifest>,
}

#[derive(Serialize, Deserialize)]
pub(super) struct I18nManifest {
	pub(super) locales: Vec<String>,
	pub(super) default: String,
	pub(super) mode: String,
	#[serde(skip_serializing_if = "std::ops::Not::not")]
	pub(super) cache: bool,
	#[serde(skip_serializing_if = "BTreeMap::is_empty")]
	pub(super) route_hashes: BTreeMap<String, String>,
	#[serde(skip_serializing_if = "BTreeMap::is_empty")]
	pub(super) content_hashes: BTreeMap<String, BTreeMap<String, String>>,
}

/// Per-route asset references for page-level resource splitting.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct RouteAssets {
	/// Page-specific CSS files
	pub(crate) styles: Vec<String>,
	/// Page-specific JS entry file
	pub(crate) scripts: Vec<String>,
	/// Shared chunks to modulepreload
	pub(crate) preload: Vec<String>,
	/// Other pages' unique assets to idle-prefetch
	pub(crate) prefetch: Vec<String>,
}

#[derive(Serialize, Deserialize)]
pub(super) struct RouteManifestEntry {
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) template: Option<String>,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) templates: Option<BTreeMap<String, String>>,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) layout: Option<String>,
	pub(super) loaders: serde_json::Value,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) derives: Option<BTreeMap<String, DeriveManifestEntry>>,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) head_meta: Option<String>,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) i18n_keys: Option<Vec<String>>,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) assets: Option<RouteAssets>,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) procedures: Option<Vec<String>>,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) projections: Option<BTreeMap<String, Vec<String>>>,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub(super) prerender: Option<bool>,
}

pub(crate) fn build_manifest_meta(config: &super::super::config::BuildConfig) -> ManifestMeta {
	ManifestMeta {
		seam_version: env!("CARGO_PKG_VERSION").to_string(),
		config_hash: config.config_hash(),
	}
}