pr 52
This commit is contained in:
parent
7f831d8d37
commit
e7cf5c36d6
@ -1,9 +1,12 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use crate::manifest::Manifest;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct ProjectConfig {
|
pub struct ProjectConfig {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub manifest: Manifest,
|
||||||
pub script_fe: String,
|
pub script_fe: String,
|
||||||
pub entry: PathBuf,
|
pub entry: PathBuf,
|
||||||
}
|
}
|
||||||
@ -11,8 +14,14 @@ pub struct ProjectConfig {
|
|||||||
impl ProjectConfig {
|
impl ProjectConfig {
|
||||||
pub fn load(project_dir: &Path) -> Result<Self> {
|
pub fn load(project_dir: &Path) -> Result<Self> {
|
||||||
let config_path = project_dir.join("prometeu.json");
|
let config_path = project_dir.join("prometeu.json");
|
||||||
let content = std::fs::read_to_string(config_path)?;
|
let content = std::fs::read_to_string(&config_path)?;
|
||||||
let config: ProjectConfig = serde_json::from_str(&content)?;
|
let config: ProjectConfig = serde_json::from_str(&content)
|
||||||
|
.map_err(|e| anyhow::anyhow!("JSON error in {:?}: {}", config_path, e))?;
|
||||||
|
|
||||||
|
// Use manifest validation
|
||||||
|
crate::manifest::load_manifest(project_dir)
|
||||||
|
.map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||||
|
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,6 +39,8 @@ mod tests {
|
|||||||
fs::write(
|
fs::write(
|
||||||
config_path,
|
config_path,
|
||||||
r#"{
|
r#"{
|
||||||
|
"name": "test_project",
|
||||||
|
"version": "0.1.0",
|
||||||
"script_fe": "pbs",
|
"script_fe": "pbs",
|
||||||
"entry": "main.pbs"
|
"entry": "main.pbs"
|
||||||
}"#,
|
}"#,
|
||||||
@ -37,6 +48,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let config = ProjectConfig::load(dir.path()).unwrap();
|
let config = ProjectConfig::load(dir.path()).unwrap();
|
||||||
|
assert_eq!(config.manifest.name, "test_project");
|
||||||
assert_eq!(config.script_fe, "pbs");
|
assert_eq!(config.script_fe, "pbs");
|
||||||
assert_eq!(config.entry, PathBuf::from("main.pbs"));
|
assert_eq!(config.entry, PathBuf::from("main.pbs"));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,6 +117,8 @@ mod tests {
|
|||||||
fs::write(
|
fs::write(
|
||||||
config_path,
|
config_path,
|
||||||
r#"{
|
r#"{
|
||||||
|
"name": "invalid_fe",
|
||||||
|
"version": "0.1.0",
|
||||||
"script_fe": "invalid",
|
"script_fe": "invalid",
|
||||||
"entry": "main.pbs"
|
"entry": "main.pbs"
|
||||||
}"#,
|
}"#,
|
||||||
@ -136,6 +138,8 @@ mod tests {
|
|||||||
fs::write(
|
fs::write(
|
||||||
project_dir.join("prometeu.json"),
|
project_dir.join("prometeu.json"),
|
||||||
r#"{
|
r#"{
|
||||||
|
"name": "hip_test",
|
||||||
|
"version": "0.1.0",
|
||||||
"script_fe": "pbs",
|
"script_fe": "pbs",
|
||||||
"entry": "main.pbs"
|
"entry": "main.pbs"
|
||||||
}"#,
|
}"#,
|
||||||
@ -174,6 +178,8 @@ mod tests {
|
|||||||
fs::write(
|
fs::write(
|
||||||
project_dir.join("prometeu.json"),
|
project_dir.join("prometeu.json"),
|
||||||
r#"{
|
r#"{
|
||||||
|
"name": "golden_test",
|
||||||
|
"version": "0.1.0",
|
||||||
"script_fe": "pbs",
|
"script_fe": "pbs",
|
||||||
"entry": "main.pbs"
|
"entry": "main.pbs"
|
||||||
}"#,
|
}"#,
|
||||||
@ -379,6 +385,8 @@ mod tests {
|
|||||||
fs::write(
|
fs::write(
|
||||||
project_dir.join("prometeu.json"),
|
project_dir.join("prometeu.json"),
|
||||||
r#"{
|
r#"{
|
||||||
|
"name": "resolution_test",
|
||||||
|
"version": "0.1.0",
|
||||||
"script_fe": "pbs",
|
"script_fe": "pbs",
|
||||||
"entry": "src/main.pbs"
|
"entry": "src/main.pbs"
|
||||||
}"#,
|
}"#,
|
||||||
|
|||||||
@ -44,6 +44,7 @@ pub mod lowering;
|
|||||||
pub mod backend;
|
pub mod backend;
|
||||||
pub mod frontends;
|
pub mod frontends;
|
||||||
pub mod compiler;
|
pub mod compiler;
|
||||||
|
pub mod manifest;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
|||||||
404
crates/prometeu-compiler/src/manifest.rs
Normal file
404
crates/prometeu-compiler/src/manifest.rs
Normal file
@ -0,0 +1,404 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum ManifestKind {
|
||||||
|
App,
|
||||||
|
Lib,
|
||||||
|
System,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ManifestKind {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::App
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Alias = String;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum DependencySpec {
|
||||||
|
Path(String),
|
||||||
|
Full(FullDependencySpec),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct FullDependencySpec {
|
||||||
|
pub path: Option<String>,
|
||||||
|
pub git: Option<String>,
|
||||||
|
pub version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct Manifest {
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub kind: ManifestKind,
|
||||||
|
#[serde(default)]
|
||||||
|
pub dependencies: HashMap<Alias, DependencySpec>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ManifestError {
|
||||||
|
Io(std::io::Error),
|
||||||
|
Json {
|
||||||
|
path: PathBuf,
|
||||||
|
error: serde_json::Error,
|
||||||
|
},
|
||||||
|
Validation {
|
||||||
|
path: PathBuf,
|
||||||
|
message: String,
|
||||||
|
pointer: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ManifestError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ManifestError::Io(e) => write!(f, "IO error: {}", e),
|
||||||
|
ManifestError::Json { path, error } => {
|
||||||
|
write!(f, "JSON error in {}: {}", path.display(), error)
|
||||||
|
}
|
||||||
|
ManifestError::Validation { path, message, pointer } => {
|
||||||
|
write!(f, "Validation error in {}: {}", path.display(), message)?;
|
||||||
|
if let Some(p) = pointer {
|
||||||
|
write!(f, " (at {})", p)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ManifestError {}
|
||||||
|
|
||||||
|
pub fn load_manifest(project_root: &Path) -> Result<Manifest, ManifestError> {
|
||||||
|
let manifest_path = project_root.join("prometeu.json");
|
||||||
|
let content = fs::read_to_string(&manifest_path).map_err(ManifestError::Io)?;
|
||||||
|
let manifest: Manifest = serde_json::from_str(&content).map_err(|e| ManifestError::Json {
|
||||||
|
path: manifest_path.clone(),
|
||||||
|
error: e,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
validate_manifest(&manifest, &manifest_path)?;
|
||||||
|
|
||||||
|
Ok(manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_manifest(manifest: &Manifest, path: &Path) -> Result<(), ManifestError> {
|
||||||
|
// Validate name
|
||||||
|
if manifest.name.trim().is_empty() {
|
||||||
|
return Err(ManifestError::Validation {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
message: "Project name cannot be empty".into(),
|
||||||
|
pointer: Some("/name".into()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if manifest.name.chars().any(|c| c.is_whitespace()) {
|
||||||
|
return Err(ManifestError::Validation {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
message: "Project name cannot contain whitespace".into(),
|
||||||
|
pointer: Some("/name".into()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate version (basic check, could be more thorough if we want to enforce semver now)
|
||||||
|
if manifest.version.trim().is_empty() {
|
||||||
|
return Err(ManifestError::Validation {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
message: "Project version cannot be empty".into(),
|
||||||
|
pointer: Some("/version".into()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate dependencies
|
||||||
|
for (alias, spec) in &manifest.dependencies {
|
||||||
|
if alias.trim().is_empty() {
|
||||||
|
return Err(ManifestError::Validation {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
message: "Dependency alias cannot be empty".into(),
|
||||||
|
pointer: Some("/dependencies".into()), // Best effort pointer
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if alias.chars().any(|c| c.is_whitespace()) {
|
||||||
|
return Err(ManifestError::Validation {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
message: format!("Dependency alias '{}' cannot contain whitespace", alias),
|
||||||
|
pointer: Some(format!("/dependencies/{}", alias)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match spec {
|
||||||
|
DependencySpec::Path(p) => {
|
||||||
|
if p.trim().is_empty() {
|
||||||
|
return Err(ManifestError::Validation {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
message: format!("Path for dependency '{}' cannot be empty", alias),
|
||||||
|
pointer: Some(format!("/dependencies/{}", alias)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DependencySpec::Full(full) => {
|
||||||
|
match (full.path.as_ref(), full.git.as_ref()) {
|
||||||
|
(Some(_), Some(_)) => {
|
||||||
|
return Err(ManifestError::Validation {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
message: format!("Dependency '{}' must specify exactly one source (path or git), but both were found", alias),
|
||||||
|
pointer: Some(format!("/dependencies/{}", alias)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
(None, None) => {
|
||||||
|
return Err(ManifestError::Validation {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
message: format!("Dependency '{}' must specify exactly one source (path or git), but none were found", alias),
|
||||||
|
pointer: Some(format!("/dependencies/{}", alias)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
(Some(p), None) => {
|
||||||
|
if p.trim().is_empty() {
|
||||||
|
return Err(ManifestError::Validation {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
message: format!("Path for dependency '{}' cannot be empty", alias),
|
||||||
|
pointer: Some(format!("/dependencies/{}", alias)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(None, Some(g)) => {
|
||||||
|
if g.trim().is_empty() {
|
||||||
|
return Err(ManifestError::Validation {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
message: format!("Git URL for dependency '{}' cannot be empty", alias),
|
||||||
|
pointer: Some(format!("/dependencies/{}", alias)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_minimal_manifest() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let manifest_path = dir.path().join("prometeu.json");
|
||||||
|
fs::write(
|
||||||
|
&manifest_path,
|
||||||
|
r#"{
|
||||||
|
"name": "my_project",
|
||||||
|
"version": "0.1.0"
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let manifest = load_manifest(dir.path()).unwrap();
|
||||||
|
assert_eq!(manifest.name, "my_project");
|
||||||
|
assert_eq!(manifest.version, "0.1.0");
|
||||||
|
assert_eq!(manifest.kind, ManifestKind::App);
|
||||||
|
assert!(manifest.dependencies.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_full_manifest() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let manifest_path = dir.path().join("prometeu.json");
|
||||||
|
fs::write(
|
||||||
|
&manifest_path,
|
||||||
|
r#"{
|
||||||
|
"name": "full_project",
|
||||||
|
"version": "1.2.3",
|
||||||
|
"kind": "lib",
|
||||||
|
"dependencies": {
|
||||||
|
"std": "../std",
|
||||||
|
"core": {
|
||||||
|
"git": "https://github.com/prometeu/core",
|
||||||
|
"version": "v1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let manifest = load_manifest(dir.path()).unwrap();
|
||||||
|
assert_eq!(manifest.name, "full_project");
|
||||||
|
assert_eq!(manifest.version, "1.2.3");
|
||||||
|
assert_eq!(manifest.kind, ManifestKind::Lib);
|
||||||
|
assert_eq!(manifest.dependencies.len(), 2);
|
||||||
|
|
||||||
|
match manifest.dependencies.get("std").unwrap() {
|
||||||
|
DependencySpec::Path(p) => assert_eq!(p, "../std"),
|
||||||
|
_ => panic!("Expected path dependency"),
|
||||||
|
}
|
||||||
|
|
||||||
|
match manifest.dependencies.get("core").unwrap() {
|
||||||
|
DependencySpec::Full(full) => {
|
||||||
|
assert_eq!(full.git.as_ref().unwrap(), "https://github.com/prometeu/core");
|
||||||
|
assert_eq!(full.version.as_ref().unwrap(), "v1.0");
|
||||||
|
assert!(full.path.is_none());
|
||||||
|
}
|
||||||
|
_ => panic!("Expected full dependency"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_name_error() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let manifest_path = dir.path().join("prometeu.json");
|
||||||
|
fs::write(
|
||||||
|
&manifest_path,
|
||||||
|
r#"{
|
||||||
|
"version": "0.1.0"
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let result = load_manifest(dir.path());
|
||||||
|
match result {
|
||||||
|
Err(ManifestError::Json { .. }) => {}
|
||||||
|
_ => panic!("Expected JSON error due to missing name, got {:?}", result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_name_error() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let manifest_path = dir.path().join("prometeu.json");
|
||||||
|
fs::write(
|
||||||
|
&manifest_path,
|
||||||
|
r#"{
|
||||||
|
"name": "my project",
|
||||||
|
"version": "0.1.0"
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let result = load_manifest(dir.path());
|
||||||
|
match result {
|
||||||
|
Err(ManifestError::Validation { message, pointer, .. }) => {
|
||||||
|
assert!(message.contains("whitespace"));
|
||||||
|
assert_eq!(pointer.unwrap(), "/name");
|
||||||
|
}
|
||||||
|
_ => panic!("Expected validation error due to invalid name, got {:?}", result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_dependency_shape_both_sources() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let manifest_path = dir.path().join("prometeu.json");
|
||||||
|
fs::write(
|
||||||
|
&manifest_path,
|
||||||
|
r#"{
|
||||||
|
"name": "test",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"dependencies": {
|
||||||
|
"bad": {
|
||||||
|
"path": "./here",
|
||||||
|
"git": "https://there"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let result = load_manifest(dir.path());
|
||||||
|
match result {
|
||||||
|
Err(ManifestError::Validation { message, pointer, .. }) => {
|
||||||
|
assert!(message.contains("exactly one source"));
|
||||||
|
assert_eq!(pointer.unwrap(), "/dependencies/bad");
|
||||||
|
}
|
||||||
|
_ => panic!("Expected validation error due to both sources, got {:?}", result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_dependency_shape_no_source() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let manifest_path = dir.path().join("prometeu.json");
|
||||||
|
fs::write(
|
||||||
|
&manifest_path,
|
||||||
|
r#"{
|
||||||
|
"name": "test",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"dependencies": {
|
||||||
|
"bad": {
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let result = load_manifest(dir.path());
|
||||||
|
match result {
|
||||||
|
Err(ManifestError::Validation { message, pointer, .. }) => {
|
||||||
|
assert!(message.contains("exactly one source"));
|
||||||
|
assert_eq!(pointer.unwrap(), "/dependencies/bad");
|
||||||
|
}
|
||||||
|
_ => panic!("Expected validation error due to no source, got {:?}", result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_dependency_empty_path() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let manifest_path = dir.path().join("prometeu.json");
|
||||||
|
fs::write(
|
||||||
|
&manifest_path,
|
||||||
|
r#"{
|
||||||
|
"name": "test",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"dependencies": {
|
||||||
|
"empty": ""
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let result = load_manifest(dir.path());
|
||||||
|
match result {
|
||||||
|
Err(ManifestError::Validation { message, .. }) => {
|
||||||
|
assert!(message.contains("cannot be empty"));
|
||||||
|
}
|
||||||
|
_ => panic!("Expected validation error due to empty path, got {:?}", result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_dependency_alias_whitespace() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let manifest_path = dir.path().join("prometeu.json");
|
||||||
|
fs::write(
|
||||||
|
&manifest_path,
|
||||||
|
r#"{
|
||||||
|
"name": "test",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"dependencies": {
|
||||||
|
"bad alias": "../std"
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let result = load_manifest(dir.path());
|
||||||
|
match result {
|
||||||
|
Err(ManifestError::Validation { message, .. }) => {
|
||||||
|
assert!(message.contains("whitespace"));
|
||||||
|
}
|
||||||
|
_ => panic!("Expected validation error due to whitespace in alias, got {:?}", result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,43 +1,3 @@
|
|||||||
## PR-09 — Add `prometeu.json` manifest parser + schema validation
|
|
||||||
|
|
||||||
**Why:** Dependency resolution cannot exist without a stable project manifest.
|
|
||||||
|
|
||||||
### Scope
|
|
||||||
|
|
||||||
* Implement `prometeu_compiler::manifest` module:
|
|
||||||
|
|
||||||
* `Manifest` struct mirroring the spec fields:
|
|
||||||
|
|
||||||
* `name`, `version`, `kind`
|
|
||||||
* `dependencies: HashMap<Alias, DependencySpec>`
|
|
||||||
* Support `DependencySpec` variants:
|
|
||||||
|
|
||||||
* `path`
|
|
||||||
* `git` (+ optional `version`)
|
|
||||||
* Validate:
|
|
||||||
|
|
||||||
* required fields present
|
|
||||||
* dependency entry must specify exactly one source (`path` or `git`)
|
|
||||||
* dependency aliases must be unique
|
|
||||||
* basic name rules (non-empty, no whitespace)
|
|
||||||
|
|
||||||
### Deliverables
|
|
||||||
|
|
||||||
* `load_manifest(project_root) -> Result<Manifest, ManifestError>`
|
|
||||||
* Diagnostic errors with file path + JSON pointer (or best-effort context)
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
* parse minimal manifest
|
|
||||||
* missing name/version errors
|
|
||||||
* invalid dependency shape errors
|
|
||||||
|
|
||||||
### Acceptance
|
|
||||||
|
|
||||||
* Compiler can reliably load + validate `prometeu.json`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## PR-10 — Dependency Resolver v0: build a resolved project graph
|
## PR-10 — Dependency Resolver v0: build a resolved project graph
|
||||||
|
|
||||||
**Why:** We need a deterministic **module graph** from manifest(s) before compiling.
|
**Why:** We need a deterministic **module graph** from manifest(s) before compiling.
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
{
|
{
|
||||||
|
"name": "canonical",
|
||||||
|
"version": "0.1.0",
|
||||||
"script_fe": "pbs",
|
"script_fe": "pbs",
|
||||||
"entry": "src/main.pbs"
|
"entry": "src/main.pbs"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"project": "sdk",
|
"name": "sdk",
|
||||||
|
"version": "0.1.0",
|
||||||
"script_fe": "pbs",
|
"script_fe": "pbs",
|
||||||
"produces": "lib"
|
"kind": "lib"
|
||||||
}
|
}
|
||||||
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"project": "test01",
|
"name": "test01",
|
||||||
|
"version": "0.1.0",
|
||||||
"script_fe": "pbs",
|
"script_fe": "pbs",
|
||||||
"produces": "app",
|
"kind": "app",
|
||||||
"entry": "src/main.pbs",
|
"entry": "src/main.pbs",
|
||||||
"out": "build/program.pbc",
|
"out": "build/program.pbc",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user