node-app-build 5.20.3

Mini app developer CLI: scaffold, validate, package node-app-* Debian packages
//! `node-app validate`
//!
//! Runs the full set of v2-aware validations on a project's `manifest.json`:
//!   - JSON Schema (manifest-v2.json) when available
//!   - Path safety on entrypoint / ui_path
//!   - Capability requirement parsing
//!   - ABI compatibility
//!   - Tier policy: native cdylib at the apt path is rejected
//!
//! Returns the parsed `AppManifest` so callers (e.g. `package`) can reuse it.

use crate::manifest::{parse_manifest, validate_manifest, AppManifest, AppType};
use anyhow::{bail, Context, Result};
use std::path::{Path, PathBuf};

pub fn run(project_path: &Path) -> Result<AppManifest> {
    let project_path = project_path.canonicalize().with_context(|| {
        format!("could not canonicalize project path '{}'", project_path.display())
    })?;
    let manifest_path = project_path.join("manifest.json");
    if !manifest_path.exists() {
        bail!(
            "no manifest.json at {} — did you run `node-app new` first?",
            manifest_path.display()
        );
    }

    let manifest = parse_manifest(&manifest_path)?;

    // For non-bundled (apt-distributed) packages we always treat the install
    // path as the apt path. The CLI is intended for community / first-party
    // additions; bundled-app authoring uses Cargo + manifest discovery
    // directly without going through `node-app`.
    let is_apt_target = true;
    validate_manifest(&manifest, is_apt_target)?;

    // Verify the declared entrypoint actually exists on disk.
    let ep = manifest
        .entrypoint
        .clone()
        .unwrap_or_else(|| match manifest.app_type {
            AppType::Native => "app.so".into(),
            AppType::Bun => "dist/index.js".into(),
            AppType::Standalone => "app".into(),
        });
    let entry_full = project_path.join(&ep);
    if !entry_full.exists() {
        // For Bun the dist/ directory is built, so the missing entrypoint is
        // a soft warning — `package` will rebuild before staging.
        match manifest.app_type {
            AppType::Bun => {
                eprintln!(
                    "ℹ entrypoint '{}' not found yet (will be built during `package`)",
                    ep
                );
            }
            AppType::Native => {
                eprintln!(
                    "ℹ entrypoint '{}' not found yet (cdylib build artifact missing)",
                    ep
                );
            }
            AppType::Standalone => {
                eprintln!(
                    "ℹ entrypoint '{}' not found yet (binary build artifact missing; run `cargo build --release`)",
                    ep
                );
            }
        }
    }

    // UI bundle directory check (informational).
    if manifest.has_ui {
        if let Some(ui) = &manifest.ui_path {
            let ui_full: PathBuf = project_path.join(ui);
            if !ui_full.is_dir() {
                eprintln!(
                    "ℹ has_ui=true but {} is not yet a directory (build the UI before packaging)",
                    ui_full.display()
                );
            }
        }
    }

    println!("✓ schema v{} valid", manifest.manifest_version);
    println!("✓ all capabilities recognised");
    if let Some(abi) = &manifest.abi {
        println!("✓ ABI {} supported", abi);
    } else {
        println!("✓ ABI default (v1) supported");
    }
    println!("✓ tier policy ok ({} app)", match manifest.app_type {
        AppType::Native => "native",
        AppType::Bun => "bun",
        AppType::Standalone => "standalone",
    });

    Ok(manifest)
}