standarbuild-detect 0.3.0

Detect project kind (Rust, Node, Bun, Deno, Python, Lua, C/C++) AND workspace kind (Cargo, Npm/Pnpm/Yarn/Bun, Deno, Go, Lerna, Nx, Turborepo, Mira) in polyglot monorepos
Documentation
//! [`WorkspaceKindId`] — twin of [`crate::KindId`] for monorepo organizers.
//!
//! A "workspace" here is a manifest-declared grouping of one or more
//! projects (Cargo workspace, npm `workspaces` field, pnpm-workspace.yaml,
//! Go work, etc.). Multiple workspace kinds CAN coexist at the same root
//! (Tauri = Cargo workspace + npm workspaces). The flat enum keeps each
//! detection mono-variant; pluralité lives in [`crate::DetectionResult`]
//! which holds a `Vec<WorkspaceInfo>`.

use std::fmt;

/// Identifier for a workspace kind. Built-ins cover the most common
/// monorepo organizers; [`WorkspaceKindId::Custom`] lets downstream crates
/// register their own without forking this lib.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "kind", content = "name", rename_all = "lowercase"))]
pub enum WorkspaceKindId {
    /// `Cargo.toml` with `[workspace]` table.
    Cargo,
    /// `package.json` with `"workspaces"` field, no other JS lockfile.
    Npm,
    /// `pnpm-workspace.yaml`.
    Pnpm,
    /// `.yarnrc.yml` or `.yarn/` + `package.json` `workspaces`.
    Yarn,
    /// `bun.lockb` or `bun.lock` + `package.json` `workspaces`.
    Bun,
    /// `deno.json[c]` with `workspace` field.
    Deno,
    /// `go.work`.
    Go,
    /// `lerna.json`.
    Lerna,
    /// `nx.json`.
    Nx,
    /// `turbo.json`.
    Turborepo,
    /// `Mira.sxb` / `*.sxb` at the root — self-hosting build manifest.
    Mira,
    /// Open-ended slot for custom kinds (Bazel, Pants, Buck, custom DSLs).
    Custom(String),
}

impl WorkspaceKindId {
    /// Canonical lowercase slug for storage / diagnostics.
    pub fn as_slug(&self) -> &str {
        match self {
            Self::Cargo => "cargo",
            Self::Npm => "npm",
            Self::Pnpm => "pnpm",
            Self::Yarn => "yarn",
            Self::Bun => "bun",
            Self::Deno => "deno",
            Self::Go => "go",
            Self::Lerna => "lerna",
            Self::Nx => "nx",
            Self::Turborepo => "turborepo",
            Self::Mira => "mira",
            Self::Custom(s) => s.as_str(),
        }
    }

    /// Parse a canonical slug back into a built-in variant. Unknown slugs
    /// fall through to `Custom(slug.to_string())` — callers that want to
    /// reject unknowns should check the result.
    pub fn from_slug(s: &str) -> Self {
        match s {
            "cargo" => Self::Cargo,
            "npm" => Self::Npm,
            "pnpm" => Self::Pnpm,
            "yarn" => Self::Yarn,
            "bun" => Self::Bun,
            "deno" => Self::Deno,
            "go" => Self::Go,
            "lerna" => Self::Lerna,
            "nx" => Self::Nx,
            "turborepo" => Self::Turborepo,
            "mira" => Self::Mira,
            other => Self::Custom(other.to_string()),
        }
    }

    /// True for everything except [`WorkspaceKindId::Custom`].
    pub fn is_builtin(&self) -> bool {
        !matches!(self, Self::Custom(_))
    }
}

impl fmt::Display for WorkspaceKindId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_slug())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn builtin_slug_roundtrip() {
        for kind in [
            WorkspaceKindId::Cargo,
            WorkspaceKindId::Npm,
            WorkspaceKindId::Pnpm,
            WorkspaceKindId::Yarn,
            WorkspaceKindId::Bun,
            WorkspaceKindId::Deno,
            WorkspaceKindId::Go,
            WorkspaceKindId::Lerna,
            WorkspaceKindId::Nx,
            WorkspaceKindId::Turborepo,
            WorkspaceKindId::Mira,
        ] {
            assert_eq!(WorkspaceKindId::from_slug(kind.as_slug()), kind);
            assert!(kind.is_builtin());
        }
    }

    #[test]
    fn unknown_slug_becomes_custom() {
        let k = WorkspaceKindId::from_slug("bazel");
        assert!(!k.is_builtin());
        assert_eq!(k.as_slug(), "bazel");
        assert_eq!(k, WorkspaceKindId::Custom("bazel".into()));
    }

    #[test]
    fn display_matches_slug() {
        assert_eq!(WorkspaceKindId::Cargo.to_string(), "cargo");
        assert_eq!(WorkspaceKindId::Custom("buck2".into()).to_string(), "buck2");
    }
}