1use include_dir::{Dir, include_dir};
10
11pub use crate::payload_roots::PAYLOAD_ROOTS;
12
13pub static METHOD: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/method");
15
16pub static BINDINGS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/bindings");
18
19pub static RUNBOOKS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/runbooks");
21
22pub static FORGES: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/forges");
24
25pub static SETUP: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/setup");
28
29pub static SNIPPETS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/snippets");
32
33pub static SKILLS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/skills");
35
36pub static SKILL_SHARED: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/skill-shared");
38
39pub static VERSIONS: &str = include_str!("../versions.toml");
41
42pub static LICENSE: &str = include_str!("../LICENSE");
44
45pub static LICENSE_MIT: &str = include_str!("../LICENSE-MIT");
47
48pub static LICENSE_CC_BY: &str = include_str!("../LICENSE-CC-BY-4.0");
50
51pub const SENTINEL: &str = "TODO(release-kit)";
54
55pub(crate) fn walk<'a>(dir: &Dir<'a>) -> Vec<(String, &'a [u8])> {
58 let mut out = Vec::new();
59 for file in dir.files() {
60 out.push((file.path().to_string_lossy().into_owned(), file.contents()));
61 }
62 for sub in dir.dirs() {
63 out.extend(walk(sub));
64 }
65 out.sort_by(|a, b| a.0.cmp(&b.0));
66 out
67}
68
69#[must_use]
73pub fn root_files(root: &str) -> Option<Vec<(String, &'static [u8])>> {
74 let dir = match root {
75 "method" => &METHOD,
76 "bindings" => &BINDINGS,
77 "runbooks" => &RUNBOOKS,
78 "forges" => &FORGES,
79 "snippets" => &SNIPPETS,
80 "setup" => &SETUP,
81 "skills" => &SKILLS,
82 "skill-shared" => &SKILL_SHARED,
83 "versions.toml" => return Some(vec![(root.to_owned(), VERSIONS.as_bytes())]),
84 _ => return None,
85 };
86 Some(
87 walk(dir)
88 .into_iter()
89 .map(|(path, bytes)| (format!("{root}/{path}"), bytes))
90 .collect(),
91 )
92}
93
94#[must_use]
100pub fn artifacts() -> Vec<(String, &'static [u8])> {
101 PAYLOAD_ROOTS
102 .iter()
103 .filter_map(|root| root_files(root))
104 .flatten()
105 .collect()
106}
107
108#[cfg(test)]
109mod tests {
110 #![allow(clippy::expect_used)]
111
112 use super::{PAYLOAD_ROOTS, artifacts, root_files};
113
114 #[test]
120 fn the_inventory_and_the_embed_declare_the_same_roots() {
121 let source = include_str!("embedded.rs");
122 let mut embedded: Vec<String> = source
123 .lines()
124 .filter_map(|line| {
125 let (_, rest) = line.split_once("include_dir!(\"$CARGO_MANIFEST_DIR/")?;
126 let (root, _) = rest.split_once('"')?;
127 Some(root.to_owned())
128 })
129 .collect();
130 embedded.extend(source.lines().filter_map(|line| {
131 let (_, rest) = line.split_once("include_str!(\"../")?;
132 let (name, _) = rest.split_once('"')?;
133 (!name.starts_with("LICENSE")).then(|| name.to_owned())
134 }));
135 embedded.sort();
136 let mut declared: Vec<String> = PAYLOAD_ROOTS.iter().map(ToString::to_string).collect();
137 declared.sort();
138 assert_eq!(
139 embedded, declared,
140 "src/embedded.rs and src/payload_roots.rs disagree on the payload roots"
141 );
142 }
143
144 #[test]
145 fn every_declared_root_serves_at_least_one_file() {
146 for root in PAYLOAD_ROOTS {
147 let files = root_files(root).expect("a declared root resolves");
148 assert!(!files.is_empty(), "{root}: the root carries no file");
149 for (path, _) in &files {
150 assert!(
151 path == root || path.starts_with(&format!("{root}/")),
152 "{path}: an artifact path must carry its root"
153 );
154 }
155 }
156 assert!(root_files("no-such-root").is_none());
157 }
158
159 #[test]
160 fn the_artifact_list_is_stable_and_complete() {
161 let listed = artifacts();
162 let total: usize = PAYLOAD_ROOTS
163 .iter()
164 .map(|root| root_files(root).expect("a declared root resolves").len())
165 .sum();
166 assert_eq!(listed.len(), total);
167 assert!(
168 listed.iter().any(|(path, _)| path == "versions.toml"),
169 "the single-file root must appear as itself"
170 );
171 }
172}