use std::path::{Path, PathBuf};
use serde::Deserialize;
#[derive(Deserialize)]
struct Metadata {
packages: Vec<Package>,
}
#[derive(Deserialize)]
struct Package {
name: String,
manifest_path: String,
}
pub struct ProjectedModule {
pub package: &'static str,
pub dts_filename: &'static str,
pub specifier: &'static str,
pub subpath: &'static str,
pub required: bool,
}
impl ProjectedModule {
pub fn marker(&self) -> String {
format!("superui_modules/{}", self.subpath)
}
pub fn index_path(&self) -> String {
format!("./superui_modules/{}/index.d.ts", self.subpath)
}
}
pub fn projected_modules() -> Vec<ProjectedModule> {
vec![
ProjectedModule {
package: "supersolid",
dts_filename: "supersolid.d.ts",
specifier: "supersolid",
subpath: "supersolid",
required: true,
},
ProjectedModule {
package: "superui",
dts_filename: "superui-test.d.ts",
specifier: "superui/test",
subpath: "superui/test",
required: false,
},
]
}
pub fn find_module_dts(metadata_json: &str, package: &str, filename: &str) -> Option<PathBuf> {
let meta: Metadata = serde_json::from_str(metadata_json).ok()?;
let pkg = meta.packages.into_iter().find(|p| p.name == package)?;
let dir = Path::new(&pkg.manifest_path).parent()?;
Some(dir.join(filename))
}
pub fn find_supersolid_dts(metadata_json: &str) -> Option<PathBuf> {
find_module_dts(metadata_json, "supersolid", "supersolid.d.ts")
}
pub const SUPERSOLID_PATH_MARKER: &str = "superui_modules/supersolid";
pub const GITIGNORE_ENTRY: &str = "superui_modules/";
pub const TSCONFIG_TEMPLATE: &str = r#"{
// Editor-only config: it tells TypeScript / your IDE how to resolve and
// type-check the .tsx UI. superui's Rust transpiler is the real consumer of
// the source — nothing here affects the build or runtime.
"compilerOptions": {
"jsx": "preserve", // leave JSX untouched (no React runtime)
"module": "esnext", // use modern ES module syntax
"moduleResolution": "bundler", // resolve bare imports the way a bundler does
"target": "esnext", // don't down-level; we only type-check
"lib": ["ESNext", "DOM"], // std JS + web globals (document, console, fetch, timers)
"noEmit": true, // never emit output — editor concern only
"baseUrl": ".", // anchor the paths below to this folder
"paths": {
// map the bare `supersolid` import to its projected editor types
"supersolid": ["./superui_modules/supersolid/index.d.ts"],
// map the `superui/test` import to the projected test-framework types
"superui/test": ["./superui_modules/superui/test/index.d.ts"]
}
},
// files the language server should type-check: projected types, UI, and specs
"include": ["superui_modules/**/*.d.ts", "assets/**/*.ts", "assets/**/*.tsx", "tests/**/*.ts"]
}
"#;
pub fn tsconfig_has_path(src: &str, marker: &str) -> bool {
src.contains(marker)
}
pub fn tsconfig_has_supersolid_path(src: &str) -> bool {
tsconfig_has_path(src, SUPERSOLID_PATH_MARKER)
}
pub fn gitignore_needs_entry(src: &str) -> bool {
!src.lines().any(|l| l.trim() == GITIGNORE_ENTRY.trim_end_matches('/')
|| l.trim() == GITIGNORE_ENTRY)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn finds_supersolid_dts_from_metadata() {
let json = r#"{
"packages": [
{ "name": "superui", "manifest_path": "/w/crates/superui/Cargo.toml" },
{ "name": "supersolid", "manifest_path": "/w/crates/supersolid/Cargo.toml" }
]
}"#;
let got = find_supersolid_dts(json).unwrap();
assert_eq!(got, PathBuf::from("/w/crates/supersolid/supersolid.d.ts"));
}
#[test]
fn returns_none_when_supersolid_absent() {
let json = r#"{ "packages": [ { "name": "bevy", "manifest_path": "/x/Cargo.toml" } ] }"#;
assert!(find_supersolid_dts(json).is_none());
}
#[test]
fn tsconfig_template_maps_supersolid() {
assert!(tsconfig_has_supersolid_path(TSCONFIG_TEMPLATE));
}
#[test]
fn detects_missing_supersolid_path() {
let existing = r#"{ "compilerOptions": { "jsx": "preserve" } }"#;
assert!(!tsconfig_has_supersolid_path(existing));
}
#[test]
fn detects_present_supersolid_path() {
let existing = r#"{ "compilerOptions": { "paths": {
"supersolid": ["./superui_modules/supersolid/index.d.ts"] } } }"#;
assert!(tsconfig_has_supersolid_path(existing));
}
#[test]
fn gitignore_needs_entry_when_absent() {
assert!(gitignore_needs_entry("/target\n"));
}
#[test]
fn gitignore_ok_when_present() {
assert!(!gitignore_needs_entry("/target\nsuperui_modules/\n"));
}
#[test]
fn gitignore_ok_when_present_without_slash() {
assert!(!gitignore_needs_entry("/target\nsuperui_modules\n"));
}
#[test]
fn tsconfig_template_maps_superui_test() {
let m = projected_modules().into_iter().find(|m| m.specifier == "superui/test").unwrap();
assert!(tsconfig_has_path(TSCONFIG_TEMPLATE, &m.marker()));
}
#[test]
fn projected_modules_marks_supersolid_required_and_test_optional() {
let mods = projected_modules();
assert!(mods.iter().find(|m| m.specifier == "supersolid").unwrap().required);
assert!(!mods.iter().find(|m| m.specifier == "superui/test").unwrap().required);
}
#[test]
fn superui_test_module_paths_are_correct() {
let m = projected_modules().into_iter().find(|m| m.specifier == "superui/test").unwrap();
assert_eq!(m.package, "superui");
assert_eq!(m.dts_filename, "superui-test.d.ts");
assert_eq!(m.marker(), "superui_modules/superui/test");
assert_eq!(m.index_path(), "./superui_modules/superui/test/index.d.ts");
}
#[test]
fn finds_superui_test_dts_from_metadata() {
let json = r#"{
"packages": [
{ "name": "superui", "manifest_path": "/w/crates/superui/Cargo.toml" },
{ "name": "supersolid", "manifest_path": "/w/crates/supersolid/Cargo.toml" }
]
}"#;
let got = find_module_dts(json, "superui", "superui-test.d.ts").unwrap();
assert_eq!(got, PathBuf::from("/w/crates/superui/superui-test.d.ts"));
}
#[test]
fn find_module_dts_none_when_package_absent() {
let json = r#"{ "packages": [ { "name": "bevy", "manifest_path": "/x/Cargo.toml" } ] }"#;
assert!(find_module_dts(json, "superui", "superui-test.d.ts").is_none());
}
#[test]
fn optional_module_absent_is_skip_not_error() {
let json = r#"{ "packages": [
{ "name": "supersolid", "manifest_path": "/w/crates/supersolid/Cargo.toml" }
] }"#;
for m in projected_modules() {
let found = find_module_dts(json, m.package, m.dts_filename);
if m.required {
assert!(found.is_some(), "required module {} must resolve", m.specifier);
} else {
assert!(found.is_none(), "optional module {} absent -> None", m.specifier);
}
}
}
}