248 lines
8.9 KiB
Rust
248 lines
8.9 KiB
Rust
use std::{
|
|
env, fs,
|
|
path::{Path, PathBuf},
|
|
};
|
|
|
|
use nana_domain::{
|
|
AcquisitionMode, ActionSuggestion, AppInfo, BeatKind, BranchList, BranchSummary, CharacterCard,
|
|
CharacterJudgmentRule, CharacterStyle, CheckDifficulty, CheckRecord, CheckResult, ClockState,
|
|
DemoPackSummary, ForkBranchRequest, ForkBranchResult, HistoryNodeView, ItemAcquisition,
|
|
ItemInstance, ItemMechanics, ItemPlacement, ItemSpec, KnowledgeCertainty, KnowledgeRecord,
|
|
LappConnectionTestResult, LappMode, LappModelOption, LappSettings, Persona, PlayerItemView,
|
|
PlayerKnowledgeView, PlayerPromiseView, PlayerView, PlotEvent, PlotModule, PlotOutcome,
|
|
PlotPressure, PresentationBeat, PresentationCharacter, PresentationScene, PresentationSnapshot,
|
|
Promise, PromiseStatus, PromiseWeight, RelationshipAdjustment, RelationshipAxes,
|
|
RelationshipBand, RelationshipDimension, RelationshipState, RelationshipView,
|
|
RenameBranchRequest, ResourceBundle, ResourceHeader, ResourceId, ResourceKind, ResourceRef,
|
|
RuntimeState, SkillValue, StateDelta, StateOp, Story, StoryBinding, StoryNode,
|
|
SwitchBranchRequest, SwitchBranchResult, TurnFailure, TurnFailureCode, TurnIntent, TurnRequest,
|
|
TurnResult, UpdateLappSettingsRequest, ValidationCode, ValidationIssue, ValidationReport,
|
|
VisualDirective, WorldBook, WorldBookEntry,
|
|
};
|
|
use schemars::{JsonSchema, schema_for};
|
|
use serde::Serialize;
|
|
use sha2::{Digest, Sha256};
|
|
use ts_rs::TS;
|
|
|
|
type GeneratedOutput = (PathBuf, Vec<u8>);
|
|
type GeneratedOutputs = Vec<GeneratedOutput>;
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
let check = env::args().any(|argument| argument == "--check");
|
|
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
|
|
let outputs = generated_outputs(&root)?;
|
|
|
|
if check {
|
|
let mut stale = Vec::new();
|
|
for (path, expected) in outputs {
|
|
let matches = fs::read(&path).ok().is_some_and(|actual| {
|
|
normalize_line_endings(&actual) == normalize_line_endings(&expected)
|
|
});
|
|
if !matches {
|
|
stale.push(path);
|
|
}
|
|
}
|
|
if stale.is_empty() {
|
|
return Ok(());
|
|
}
|
|
for path in stale {
|
|
eprintln!("stale generated contract: {}", path.display());
|
|
}
|
|
return Err("generated contracts are stale".into());
|
|
}
|
|
|
|
for (path, bytes) in outputs {
|
|
if let Some(parent) = path.parent() {
|
|
fs::create_dir_all(parent)?;
|
|
}
|
|
fs::write(path, bytes)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn normalize_line_endings(bytes: &[u8]) -> Vec<u8> {
|
|
let mut normalized = Vec::with_capacity(bytes.len());
|
|
let mut index = 0;
|
|
|
|
while index < bytes.len() {
|
|
if bytes[index] == b'\r' && bytes.get(index + 1) == Some(&b'\n') {
|
|
normalized.push(b'\n');
|
|
index += 2;
|
|
} else {
|
|
normalized.push(bytes[index]);
|
|
index += 1;
|
|
}
|
|
}
|
|
|
|
normalized
|
|
}
|
|
|
|
fn generated_outputs(root: &Path) -> Result<GeneratedOutputs, Box<dyn std::error::Error>> {
|
|
let mut outputs = Vec::new();
|
|
let schema_dir = root.join("contracts/schema");
|
|
|
|
add_schema::<ResourceHeader>(&mut outputs, &schema_dir, "resource-header")?;
|
|
add_schema::<CharacterCard>(&mut outputs, &schema_dir, "character-card")?;
|
|
add_schema::<WorldBook>(&mut outputs, &schema_dir, "world-book")?;
|
|
add_schema::<Persona>(&mut outputs, &schema_dir, "persona")?;
|
|
add_schema::<PlotModule>(&mut outputs, &schema_dir, "plot-module")?;
|
|
add_schema::<ItemSpec>(&mut outputs, &schema_dir, "item-spec")?;
|
|
add_schema::<ResourceBundle>(&mut outputs, &schema_dir, "resource-bundle")?;
|
|
add_schema::<PresentationSnapshot>(&mut outputs, &schema_dir, "presentation-snapshot")?;
|
|
add_schema::<StoryNode>(&mut outputs, &schema_dir, "story-node")?;
|
|
add_schema::<RuntimeState>(&mut outputs, &schema_dir, "runtime-state")?;
|
|
add_schema::<PlayerView>(&mut outputs, &schema_dir, "player-view")?;
|
|
add_schema::<TurnRequest>(&mut outputs, &schema_dir, "turn-request")?;
|
|
add_schema::<TurnResult>(&mut outputs, &schema_dir, "turn-result")?;
|
|
add_schema::<ForkBranchRequest>(&mut outputs, &schema_dir, "fork-branch-request")?;
|
|
add_schema::<ForkBranchResult>(&mut outputs, &schema_dir, "fork-branch-result")?;
|
|
add_schema::<BranchList>(&mut outputs, &schema_dir, "branch-list")?;
|
|
add_schema::<SwitchBranchRequest>(&mut outputs, &schema_dir, "switch-branch-request")?;
|
|
add_schema::<SwitchBranchResult>(&mut outputs, &schema_dir, "switch-branch-result")?;
|
|
add_schema::<RenameBranchRequest>(&mut outputs, &schema_dir, "rename-branch-request")?;
|
|
add_schema::<LappSettings>(&mut outputs, &schema_dir, "lapp-settings")?;
|
|
add_schema::<UpdateLappSettingsRequest>(
|
|
&mut outputs,
|
|
&schema_dir,
|
|
"update-lapp-settings-request",
|
|
)?;
|
|
add_schema::<LappConnectionTestResult>(
|
|
&mut outputs,
|
|
&schema_dir,
|
|
"lapp-connection-test-result",
|
|
)?;
|
|
add_schema::<TurnFailure>(&mut outputs, &schema_dir, "turn-failure")?;
|
|
add_schema::<AppInfo>(&mut outputs, &schema_dir, "app-info")?;
|
|
add_schema::<DemoPackSummary>(&mut outputs, &schema_dir, "demo-pack-summary")?;
|
|
|
|
let declarations = generated_declarations();
|
|
let ts = format!("// @generated by crates/nana-contracts; do not edit.\n\n{declarations}\n");
|
|
outputs.push((root.join("contracts/ts/index.ts"), ts.into_bytes()));
|
|
|
|
let domain_source = fs::read(root.join("crates/nana-domain/src/lib.rs"))?;
|
|
let source_hash = format!(
|
|
"{:x}\n",
|
|
Sha256::digest(normalize_line_endings(&domain_source))
|
|
);
|
|
outputs.push((
|
|
root.join("contracts/.source.sha256"),
|
|
source_hash.into_bytes(),
|
|
));
|
|
|
|
Ok(outputs)
|
|
}
|
|
|
|
fn generated_declarations() -> String {
|
|
[
|
|
ResourceId::decl(),
|
|
ResourceKind::decl(),
|
|
ResourceRef::decl(),
|
|
ResourceHeader::decl(),
|
|
CharacterStyle::decl(),
|
|
CharacterJudgmentRule::decl(),
|
|
SkillValue::decl(),
|
|
CharacterCard::decl(),
|
|
WorldBookEntry::decl(),
|
|
WorldBook::decl(),
|
|
Persona::decl(),
|
|
PlotPressure::decl(),
|
|
PlotOutcome::decl(),
|
|
PlotEvent::decl(),
|
|
PlotModule::decl(),
|
|
ItemMechanics::decl(),
|
|
ItemSpec::decl(),
|
|
ResourceBundle::decl(),
|
|
RelationshipAxes::decl(),
|
|
RelationshipAdjustment::decl(),
|
|
RelationshipDimension::decl(),
|
|
RelationshipBand::decl(),
|
|
RelationshipView::decl(),
|
|
PromiseStatus::decl(),
|
|
PromiseWeight::decl(),
|
|
Promise::decl(),
|
|
RelationshipState::decl(),
|
|
KnowledgeCertainty::decl(),
|
|
KnowledgeRecord::decl(),
|
|
ItemPlacement::decl(),
|
|
AcquisitionMode::decl(),
|
|
ItemAcquisition::decl(),
|
|
ItemInstance::decl(),
|
|
CheckDifficulty::decl(),
|
|
CheckResult::decl(),
|
|
CheckRecord::decl(),
|
|
ClockState::decl(),
|
|
StoryBinding::decl(),
|
|
Story::decl(),
|
|
StateDelta::decl(),
|
|
StateOp::decl(),
|
|
StoryNode::decl(),
|
|
RuntimeState::decl(),
|
|
BeatKind::decl(),
|
|
VisualDirective::decl(),
|
|
PresentationBeat::decl(),
|
|
ActionSuggestion::decl(),
|
|
PresentationScene::decl(),
|
|
PresentationCharacter::decl(),
|
|
PresentationSnapshot::decl(),
|
|
PlayerItemView::decl(),
|
|
PlayerKnowledgeView::decl(),
|
|
PlayerPromiseView::decl(),
|
|
HistoryNodeView::decl(),
|
|
PlayerView::decl(),
|
|
TurnIntent::decl(),
|
|
TurnRequest::decl(),
|
|
TurnResult::decl(),
|
|
ForkBranchRequest::decl(),
|
|
ForkBranchResult::decl(),
|
|
BranchSummary::decl(),
|
|
BranchList::decl(),
|
|
SwitchBranchRequest::decl(),
|
|
SwitchBranchResult::decl(),
|
|
RenameBranchRequest::decl(),
|
|
LappModelOption::decl(),
|
|
LappMode::decl(),
|
|
LappSettings::decl(),
|
|
UpdateLappSettingsRequest::decl(),
|
|
LappConnectionTestResult::decl(),
|
|
TurnFailureCode::decl(),
|
|
TurnFailure::decl(),
|
|
AppInfo::decl(),
|
|
DemoPackSummary::decl(),
|
|
ValidationCode::decl(),
|
|
ValidationIssue::decl(),
|
|
ValidationReport::decl(),
|
|
]
|
|
.into_iter()
|
|
.map(|declaration| format!("export {declaration}"))
|
|
.collect::<Vec<_>>()
|
|
.join("\n\n")
|
|
}
|
|
|
|
fn add_schema<T: JsonSchema + Serialize>(
|
|
outputs: &mut GeneratedOutputs,
|
|
schema_dir: &Path,
|
|
name: &str,
|
|
) -> Result<(), serde_json::Error> {
|
|
let mut bytes = serde_json::to_vec_pretty(&schema_for!(T))?;
|
|
bytes.push(b'\n');
|
|
outputs.push((schema_dir.join(format!("{name}.schema.json")), bytes));
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::normalize_line_endings;
|
|
|
|
#[test]
|
|
fn source_hash_input_is_independent_of_checkout_line_endings() {
|
|
assert_eq!(
|
|
normalize_line_endings(b"first\r\nsecond\nthird\r"),
|
|
b"first\nsecond\nthird\r"
|
|
);
|
|
assert_eq!(
|
|
normalize_line_endings(b"first\nsecond\nthird\r"),
|
|
b"first\nsecond\nthird\r"
|
|
);
|
|
}
|
|
}
|