libwally/
manifest.rs

1use std::collections::BTreeMap;
2use std::path::Path;
3
4use anyhow::Context;
5use semver::Version;
6use serde::{Deserialize, Serialize};
7
8use crate::package_id::PackageId;
9use crate::package_name::PackageName;
10use crate::package_req::PackageReq;
11
12pub const MANIFEST_FILE_NAME: &str = "wally.toml";
13
14/// The contents of a `wally.toml` file, which defines a package.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(rename_all = "kebab-case")]
17pub struct Manifest {
18    pub package: Package,
19
20    #[serde(default)]
21    pub place: PlaceInfo,
22
23    #[serde(default)]
24    pub dependencies: BTreeMap<String, PackageReq>,
25
26    #[serde(default)]
27    pub server_dependencies: BTreeMap<String, PackageReq>,
28
29    #[serde(default)]
30    pub dev_dependencies: BTreeMap<String, PackageReq>,
31}
32
33impl Manifest {
34    /// Load a manifest from a project directory containing a `wally.toml` file.
35    pub fn load(dir: &Path) -> anyhow::Result<Self> {
36        let file_path = dir.join(MANIFEST_FILE_NAME);
37
38        let content = fs_err::read_to_string(&file_path)?;
39        let manifest: Manifest = toml::from_str(&content)
40            .with_context(|| format!("failed to parse manifest at path {}", file_path.display()))?;
41
42        Ok(manifest)
43    }
44
45    pub fn from_slice(slice: &[u8]) -> anyhow::Result<Self> {
46        let manifest: Manifest =
47            toml::from_slice(slice).with_context(|| format!("failed to parse manifest"))?;
48
49        Ok(manifest)
50    }
51
52    pub fn package_id(&self) -> PackageId {
53        PackageId::new(self.package.name.clone(), self.package.version.clone())
54    }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct Package {
59    /// The scope and name of the package.
60    ///
61    /// Example: `lpghatguy/asink`.
62    pub name: PackageName,
63
64    /// The current version of the package.
65    ///
66    /// Example: `1.0.0`
67    pub version: Version,
68
69    /// The registry that this package should pull its dependencies from.
70    ///
71    /// Example: `https://github.com/UpliftGames/wally-test-index`
72    pub registry: String,
73
74    /// The realms (`shared`, `server`, etc) that this package can be used in.
75    ///
76    /// Packages in the `shared` realm can only depend on other `shared`
77    /// packages. Packages in the `server` realm can depend on any other
78    /// package.
79    ///
80    /// Example: `shared`, `server`
81    pub realm: Realm,
82
83    /// A short description of the package.
84    ///
85    /// Example: `A game about adopting things.`
86    pub description: Option<String>,
87
88    /// An SPDX license specifier for the package.
89    ///
90    /// Example: `MIT OR Apache-2.0`
91    pub license: Option<String>,
92
93    /// A list of the package's authors.
94    ///
95    /// Example: ["Biff Lumfer <biff@playadopt.me>"]
96    #[serde(default)]
97    pub authors: Vec<String>,
98
99    /// A list of paths to include in the package. Glob patterns are supported.
100    ///
101    /// By default all directories and files are included except files generated
102    /// by wally and hidden files/directories. If include is specified then only
103    /// files matching patterns in the include list will be included.
104    ///
105    /// If include is unspecified and a .gitignore file exists then those patterns
106    /// will be respected and wally will also ignore those files.
107    ///
108    /// Example: ["/src", "*.lua"]
109    #[serde(default)]
110    pub include: Vec<String>,
111
112    /// A list of paths to exclude from the package. Glob patterns are supported.
113    ///
114    /// By default files generated by wally and hidden files/directories will be
115    /// excluded. If a .gitignore file exists and include is unspecified then
116    /// those patterns will be respected and wally will also ignore those files.
117    /// Patterns in exclude will be excluded in addition to those patterns in the
118    /// .gitignore.
119    ///
120    /// Example: ["/Packages", "/node_modules"]
121    #[serde(default)]
122    pub exclude: Vec<String>,
123
124    /// Indicates whether the package can be published or not.
125    ///
126    /// Example: true
127    #[serde(default)]
128    pub private: bool,
129}
130
131// Metadata we require when this manifest will be used to generate package folders
132// This information can be present in any package but is only used in the root package
133#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(rename_all = "kebab-case")]
135pub struct PlaceInfo {
136    /// Where the shared packages folder is located in the Roblox Datamodel
137    ///
138    /// Example: `game.ReplicatedStorage.Packages`
139    #[serde(default)]
140    pub shared_packages: Option<String>,
141
142    /// Where the server packages folder is located in the Roblox Datamodel
143    ///
144    /// Example: `game.ServerScriptStorage.Packages`
145    #[serde(default)]
146    pub server_packages: Option<String>,
147}
148
149impl Default for PlaceInfo {
150    fn default() -> Self {
151        Self {
152            shared_packages: None,
153            server_packages: None,
154        }
155    }
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
159#[serde(rename_all = "lowercase")]
160pub enum Realm {
161    Server,
162    Shared,
163    Dev,
164}
165
166impl Realm {
167    pub fn is_dependency_valid(dep_type: Self, dep_realm: Self) -> bool {
168        use Realm::*;
169
170        matches!(
171            (dep_type, dep_realm),
172            (Server, _) | (Shared, Shared) | (Dev, _)
173        )
174    }
175}