Repository: axodotdev/cargo-dist
Pull request #43 (2023-01-18 16:17:59)

clean up cargo-dist-schema to make it more robust


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index e0232e85..302c5232 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -300,16 +300,15 @@ dependencies = [
 
 [[package]]
 name = "console"
-version = "0.15.2"
+version = "0.15.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c"
+checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60"
 dependencies = [
  "encode_unicode",
  "lazy_static",
  "libc",
- "terminal_size",
  "unicode-width",
- "winapi",
+ "windows-sys",
 ]
 
 [[package]]
@@ -528,13 +527,14 @@ dependencies = [
 
 [[package]]
 name = "insta"
-version = "1.21.1"
+version = "1.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba1e75aa1530e7385af7b2685478dece08dafb9db3b4225c753286decea83bef"
+checksum = "f6f0f08b46e4379744de2ab67aa8f7de3ffd1da3e275adc41fcc82053ede46ff"
 dependencies = [
  "console",
  "lazy_static",
  "linked-hash-map",
+ "regex",
  "similar",
  "yaml-rust",
 ]
diff --git a/cargo-dist-schema/Cargo.toml b/cargo-dist-schema/Cargo.toml
index 019d222d..e9222d63 100644
--- a/cargo-dist-schema/Cargo.toml
+++ b/cargo-dist-schema/Cargo.toml
@@ -20,4 +20,4 @@ serde = { version = "1.0.147", features = ["derive"] }
 serde_json = "1.0.87"
 
 [dev-dependencies]
-insta = "1.19.1"
+insta = "1.26.0"
diff --git a/cargo-dist-schema/cargo-dist-json-schema.json b/cargo-dist-schema/cargo-dist-json-schema.json
new file mode 100644
index 00000000..bb83363b
--- /dev/null
+++ b/cargo-dist-schema/cargo-dist-json-schema.json
@@ -0,0 +1,253 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "DistManifest",
+  "description": "A report of the releases and artifacts that cargo-dist generated",
+  "type": "object",
+  "properties": {
+    "dist_version": {
+      "description": "The version of cargo-dist that generated this",
+      "type": [
+        "string",
+        "null"
+      ]
+    },
+    "releases": {
+      "description": "App releases we're distributing",
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/Release"
+      }
+    }
+  },
+  "definitions": {
+    "Artifact": {
+      "description": "A distributable bundle that's part of a Release\n\ni.e. a zip or installer",
+      "type": "object",
+      "oneOf": [
+        {
+          "description": "A zip or a tarball",
+          "type": "object",
+          "required": [
+            "kind"
+          ],
+          "properties": {
+            "kind": {
+              "type": "string",
+              "enum": [
+                "executable-zip"
+              ]
+            }
+          }
+        },
+        {
+          "description": "Standalone Symbols/Debuginfo for a build",
+          "type": "object",
+          "required": [
+            "kind"
+          ],
+          "properties": {
+            "kind": {
+              "type": "string",
+              "enum": [
+                "symbols"
+              ]
+            }
+          }
+        },
+        {
+          "description": "Machine-readable metadata",
+          "type": "object",
+          "required": [
+            "kind"
+          ],
+          "properties": {
+            "kind": {
+              "type": "string",
+              "enum": [
+                "dist-metadata"
+              ]
+            }
+          }
+        },
+        {
+          "description": "Installer",
+          "type": "object",
+          "required": [
+            "kind"
+          ],
+          "properties": {
+            "kind": {
+              "type": "string",
+              "enum": [
+                "installer"
+              ]
+            }
+          }
+        },
+        {
+          "type": "object",
+          "required": [
+            "kind"
+          ],
+          "properties": {
+            "kind": {
+              "type": "string",
+              "enum": [
+                "unknown"
+              ]
+            }
+          }
+        }
+      ],
+      "required": [
+        "name"
+      ],
+      "properties": {
+        "assets": {
+          "description": "Assets included in the bundle (like executables and READMEs)",
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/Asset"
+          }
+        },
+        "install_hint": {
+          "description": "A string describing how to install this",
+          "type": [
+            "string",
+            "null"
+          ]
+        },
+        "name": {
+          "description": "The unique name of the artifact (e.g. `myapp-v1.0.0-x86_64-pc-windows-msvc.zip`)",
+          "type": "string"
+        },
+        "path": {
+          "description": "The location of the artifact on the local system",
+          "type": [
+            "string",
+            "null"
+          ]
+        },
+        "target_triples": {
+          "description": "The target triple of the bundle",
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        }
+      }
+    },
+    "Asset": {
+      "description": "An asset contained in an artifact (executable, license, etc.)",
+      "type": "object",
+      "oneOf": [
+        {
+          "description": "An executable artifact",
+          "type": "object",
+          "required": [
+            "kind"
+          ],
+          "properties": {
+            "kind": {
+              "type": "string",
+              "enum": [
+                "executable"
+              ]
+            },
+            "symbols_artifact": {
+              "description": "The name of the Artifact containing symbols for this executable",
+              "type": [
+                "string",
+                "null"
+              ]
+            }
+          }
+        },
+        {
+          "description": "A README file",
+          "type": "object",
+          "required": [
+            "kind"
+          ],
+          "properties": {
+            "kind": {
+              "type": "string",
+              "enum": [
+                "readme"
+              ]
+            }
+          }
+        },
+        {
+          "description": "A LICENSE file",
+          "type": "object",
+          "required": [
+            "kind"
+          ],
+          "properties": {
+            "kind": {
+              "type": "string",
+              "enum": [
+                "license"
+              ]
+            }
+          }
+        },
+        {
+          "type": "object",
+          "required": [
+            "kind"
+          ],
+          "properties": {
+            "kind": {
+              "type": "string",
+              "enum": [
+                "unknown"
+              ]
+            }
+          }
+        }
+      ],
+      "properties": {
+        "name": {
+          "description": "The high-level name of the asset",
+          "type": [
+            "string",
+            "null"
+          ]
+        },
+        "path": {
+          "description": "The path of the asset relative to the root of the artifact",
+          "type": [
+            "string",
+            "null"
+          ]
+        }
+      }
+    },
+    "Release": {
+      "type": "object",
+      "required": [
+        "app_name",
+        "app_version"
+      ],
+      "properties": {
+        "app_name": {
+          "description": "The name of the app",
+          "type": "string"
+        },
+        "app_version": {
+          "description": "The version of the app",
+          "type": "string"
+        },
+        "artifacts": {
+          "description": "The artifacts for this release (zips, debuginfo, metadata...)",
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/Artifact"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/cargo-dist-schema/src/lib.rs b/cargo-dist-schema/src/lib.rs
index 40d17363..ad43a84e 100644
--- a/cargo-dist-schema/src/lib.rs
+++ b/cargo-dist-schema/src/lib.rs
@@ -3,10 +3,16 @@ use serde::{Deserialize, Serialize};
 // FIXME: ideally these would be UTF8PathBufs but JsonSchema doesn't support (yet?)
 use std::path::PathBuf;
 
-/// The final report of cargo-dist
+/// A report of the releases and artifacts that cargo-dist generated
 #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
 pub struct DistManifest {
+    /// The version of cargo-dist that generated this
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub dist_version: Option<String>,
     /// App releases we're distributing
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
     pub releases: Vec<Release>,
 }
 
@@ -18,6 +24,8 @@ pub struct Release {
     // FIXME: should be a Version but JsonSchema doesn't support (yet?)
     pub app_version: String,
     /// The artifacts for this release (zips, debuginfo, metadata...)
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
     pub artifacts: Vec<Artifact>,
 }
 
@@ -33,15 +41,19 @@ pub struct Artifact {
     pub kind: ArtifactKind,
     /// The target triple of the bundle
     #[serde(skip_serializing_if = "Vec::is_empty")]
+    #[serde(default)]
     pub target_triples: Vec<String>,
     /// The location of the artifact on the local system
     #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default)]
     pub path: Option<PathBuf>,
-    /// Assets included in the bundle (like executables)
+    /// Assets included in the bundle (like executables and READMEs)
     #[serde(skip_serializing_if = "Vec::is_empty")]
+    #[serde(default)]
     pub assets: Vec<Asset>,
     /// A string describing how to install this
     #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default)]
     pub install_hint: Option<String>,
 }
 
@@ -49,9 +61,13 @@ pub struct Artifact {
 #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
 pub struct Asset {
     /// The high-level name of the asset
-    pub name: String,
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub name: Option<String>,
     /// The path of the asset relative to the root of the artifact
-    pub path: PathBuf,
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub path: Option<PathBuf>,
     /// The kind of asset this is
     #[serde(flatten)]
     pub kind: AssetKind,
@@ -71,10 +87,14 @@ pub enum AssetKind {
     /// A LICENSE file
     #[serde(rename = "license")]
     License,
+    #[serde(other)]
+    #[serde(rename = "unknown")]
+    Unknown,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
 #[serde(tag = "kind")]
+#[non_exhaustive]
 pub enum ArtifactKind {
     /// A zip or a tarball
     #[serde(rename = "executable-zip")]
@@ -88,6 +108,9 @@ pub enum ArtifactKind {
     /// Installer
     #[serde(rename = "installer")]
     Installer,
+    #[serde(other)]
+    #[serde(rename = "unknown")]
+    Unknown,
 }
 
 /// An executable artifact (exe/binary)
@@ -95,17 +118,43 @@ pub enum ArtifactKind {
 pub struct ExecutableAsset {
     /// The name of the Artifact containing symbols for this executable
     #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default)]
     pub symbols_artifact: Option<String>,
 }
 
 impl DistManifest {
-    pub fn new(releases: Vec<Release>) -> Self {
-        Self { releases }
+    /// Create a new DistManifest
+    pub fn new(dist_version: String, releases: Vec<Release>) -> Self {
+        Self {
+            dist_version: Some(dist_version),
+            releases,
+        }
+    }
+
+    /// Get the JSON Schema for a DistManifest
+    pub fn json_schema() -> schemars::schema::RootSchema {
+        schemars::schema_for!(DistManifest)
     }
 }
 
 #[test]
 fn emit() {
-    let schema = schemars::schema_for!(DistManifest);
-    insta::assert_snapshot!(serde_json::to_string_pretty(&schema).unwrap());
+    use std::fs::File;
+    use std::io::BufWriter;
+    use std::io::Write;
+
+    let schema = DistManifest::json_schema();
+    let json_schema = serde_json::to_string_pretty(&schema).unwrap();
+    insta::assert_snapshot!(json_schema);
+
+    // FIXME: (?) we should use something like xtask to update the schema, but this works ok.
+    let root = std::env!("CARGO_MANIFEST_DIR");
+    let schema = PathBuf::from(root).join("cargo-dist-json-schema.json");
+    let file = File::options()
+        .create(true)
+        .write(true)
+        .open(schema)
+        .unwrap();
+    let mut file = BufWriter::new(file);
+    writeln!(&mut file, "{}", json_schema).unwrap();
 }
diff --git a/cargo-dist-schema/src/snapshots/cargo_dist_schema__emit.snap b/cargo-dist-schema/src/snapshots/cargo_dist_schema__emit.snap
index d9d68bec..dd22a566 100644
--- a/cargo-dist-schema/src/snapshots/cargo_dist_schema__emit.snap
+++ b/cargo-dist-schema/src/snapshots/cargo_dist_schema__emit.snap
@@ -1,16 +1,20 @@
 ---
 source: cargo-dist-schema/src/lib.rs
-expression: "serde_json::to_string_pretty(&schema).unwrap()"
+expression: json_schema
 ---
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "DistManifest",
-  "description": "The final report of cargo-dist",
+  "description": "A report of the releases and artifacts that cargo-dist generated",
   "type": "object",
-  "required": [
-    "releases"
-  ],
   "properties": {
+    "dist_version": {
+      "description": "The version of cargo-dist that generated this",
+      "type": [
+        "string",
+        "null"
+      ]
+    },
     "releases": {
       "description": "App releases we're distributing",
       "type": "array",
@@ -83,16 +87,28 @@ expression: "serde_json::to_string_pretty(&schema).unwrap()"
               ]
             }
           }
+        },
+        {
+          "type": "object",
+          "required": [
+            "kind"
+          ],
+          "properties": {
+            "kind": {
+              "type": "string",
+              "enum": [
+                "unknown"
+              ]
+            }
+          }
         }
       ],
       "required": [
-        "assets",
-        "name",
-        "target_triples"
+        "name"
       ],
       "properties": {
         "assets": {
-          "description": "Assets included in the bundle (like executables)",
+          "description": "Assets included in the bundle (like executables and READMEs)",
           "type": "array",
           "items": {
             "$ref": "#/definitions/Asset"
@@ -180,20 +196,36 @@ expression: "serde_json::to_string_pretty(&schema).unwrap()"
               ]
             }
           }
+        },
+        {
+          "type": "object",
+          "required": [
+            "kind"
+          ],
+          "properties": {
+            "kind": {
+              "type": "string",
+              "enum": [
+                "unknown"
+              ]
+            }
+          }
         }
       ],
-      "required": [
-        "name",
-        "path"
-      ],
       "properties": {
         "name": {
           "description": "The high-level name of the asset",
-          "type": "string"
+          "type": [
+            "string",
+            "null"
+          ]
         },
         "path": {
           "description": "The path of the asset relative to the root of the artifact",
-          "type": "string"
+          "type": [
+            "string",
+            "null"
+          ]
         }
       }
     },
@@ -201,8 +233,7 @@ expression: "serde_json::to_string_pretty(&schema).unwrap()"
       "type": "object",
       "required": [
         "app_name",
-        "app_version",
-        "artifacts"
+        "app_version"
       ],
       "properties": {
         "app_name": {
diff --git a/cargo-dist/Cargo.toml b/cargo-dist/Cargo.toml
index 95176ac8..0522be4c 100644
--- a/cargo-dist/Cargo.toml
+++ b/cargo-dist/Cargo.toml
@@ -49,5 +49,5 @@ semver = "1.0.14"
 toml_edit = "0.15.0"
 
 [dev-dependencies]
-insta = "1.19.1"
+insta = { version = "1.26.0", features = ["filters"] }
 
diff --git a/cargo-dist/src/lib.rs b/cargo-dist/src/lib.rs
index df9a5a43..a7877c1e 100644
--- a/cargo-dist/src/lib.rs
+++ b/cargo-dist/src/lib.rs
@@ -715,8 +715,8 @@ fn build_manifest(cfg: &Config, dist: &DistGraph) -> DistManifest {
                                                 .to_owned()
                                         });
                                         Some(Asset {
-                                            name: exe.exe_name.clone(),
-                                            path: asset_path.clone().into_std_path_buf(),
+                                            name: Some(exe.exe_name.clone()),
+                                            path: Some(asset_path.clone().into_std_path_buf()),
                                             kind: AssetKind::Executable(ExecutableAsset {
                                                 symbols_artifact,
                                             }),
@@ -735,7 +735,8 @@ fn build_manifest(cfg: &Config, dist: &DistGraph) -> DistManifest {
                 .collect(),
         })
     }
-    DistManifest::new(releases)
+    let dist_version = env!("CARGO_PKG_VERSION").to_owned();
+    DistManifest::new(dist_version, releases)
 }
 
 /// Get all the artifacts built by this list of cargo packages


Test patch:
diff --git a/cargo-dist/tests/cli-tests.rs b/cargo-dist/tests/cli-tests.rs
index bb8d2d78..1a4db58a 100644
--- a/cargo-dist/tests/cli-tests.rs
+++ b/cargo-dist/tests/cli-tests.rs
@@ -84,6 +84,12 @@ fn test_manifest() {
         .output()
         .unwrap();
 
-    insta::assert_snapshot!(format_outputs(&output));
+    // We don't want this to churn every time we do a version bump
+    insta::with_settings!({filters => vec![
+        (r"\d+\.\d+\.\d+(\-prerelease\d+)?", "1.0.0-FAKEVERSION")
+    ]}, {
+        insta::assert_snapshot!(format_outputs(&output));
+    });
+
     assert!(output.status.success(), "{}", output.status);
 }
diff --git a/cargo-dist/tests/snapshots/cli_tests__manifest.snap b/cargo-dist/tests/snapshots/cli_tests__manifest.snap
index 8c7a0861..216b1fcd 100644
--- a/cargo-dist/tests/snapshots/cli_tests__manifest.snap
+++ b/cargo-dist/tests/snapshots/cli_tests__manifest.snap
@@ -4,20 +4,21 @@ expression: format_outputs(&output)
 ---
 stdout:
 {
+  "dist_version": "1.0.0-FAKEVERSION",
   "releases": [
     {
       "app_name": "cargo-dist",
-      "app_version": "0.2.2-prerelease6",
+      "app_version": "1.0.0-FAKEVERSION",
       "artifacts": [
         {
-          "name": "cargo-dist-v0.2.2-prerelease6-x86_64-pc-windows-msvc.pdb",
+          "name": "cargo-dist-v1.0.0-FAKEVERSION-x86_64-pc-windows-msvc.pdb",
           "kind": "symbols",
           "target_triples": [
             "x86_64-pc-windows-msvc"
           ]
         },
         {
-          "name": "cargo-dist-v0.2.2-prerelease6-x86_64-pc-windows-msvc.zip",
+          "name": "cargo-dist-v1.0.0-FAKEVERSION-x86_64-pc-windows-msvc.zip",
           "kind": "executable-zip",
           "target_triples": [
             "x86_64-pc-windows-msvc"
@@ -27,12 +28,12 @@ stdout:
               "name": "cargo-dist",
               "path": "cargo-dist.exe",
               "kind": "executable",
-              "symbols_artifact": "cargo-dist-v0.2.2-prerelease6-x86_64-pc-windows-msvc.pdb"
+              "symbols_artifact": "cargo-dist-v1.0.0-FAKEVERSION-x86_64-pc-windows-msvc.pdb"
             }
           ]
         },
         {
-          "name": "cargo-dist-v0.2.2-prerelease6-x86_64-apple-darwin.tar.xz",
+          "name": "cargo-dist-v1.0.0-FAKEVERSION-x86_64-apple-darwin.tar.xz",
           "kind": "executable-zip",
           "target_triples": [
             "x86_64-apple-darwin"
@@ -46,7 +47,7 @@ stdout:
           ]
         },
         {
-          "name": "cargo-dist-v0.2.2-prerelease6-x86_64-unknown-linux-gnu.tar.xz",
+          "name": "cargo-dist-v1.0.0-FAKEVERSION-x86_64-unknown-linux-gnu.tar.xz",
           "kind": "executable-zip",
           "target_triples": [
             "x86_64-unknown-linux-gnu"
@@ -66,7 +67,7 @@ stdout:
             "x86_64-apple-darwin",
             "x86_64-unknown-linux-gnu"
           ],
-          "install_hint": "curl --proto '=https' --tlsv1.2 -L -sSf https://github.com/axodotdev/cargo-dist/releases/download/v0.2.2-prerelease6/installer.sh | sh"
+          "install_hint": "curl --proto '=https' --tlsv1.2 -L -sSf https://github.com/axodotdev/cargo-dist/releases/download/v1.0.0-FAKEVERSION/installer.sh | sh"
         },
         {
           "name": "installer.ps1",
@@ -74,7 +75,7 @@ stdout:
           "target_triples": [
             "x86_64-pc-windows-msvc"
           ],
-          "install_hint": "irm 'https://github.com/axodotdev/cargo-dist/releases/download/v0.2.2-prerelease6/installer.ps1' | iex"
+          "install_hint": "irm 'https://github.com/axodotdev/cargo-dist/releases/download/v1.0.0-FAKEVERSION/installer.ps1' | iex"
         }
       ]
     }