Repository: axodotdev/cargo-dist
Pull request #154 (2023-02-23 12:19:24)

Update README.md
Various fixes/quoting/intra links etc.


Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 22efe7fe..7d6db7a7 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -45,6 +45,8 @@ jobs:
   # Create the Github Release™ so the packages have something to be uploaded to
   create-release:
     runs-on: ubuntu-latest
+    outputs:
+      has-releases: ${{ steps.create-release.outputs.has-releases }}
     env:
       GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
     steps:
@@ -52,8 +54,8 @@ jobs:
       - name: Install Rust
         run: rustup update 1.67.1 --no-self-update && rustup default 1.67.1
       - name: Install cargo-dist
-        run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.3-prerelease04/cargo-dist-v0.0.3-prerelease04-installer.sh | sh
-      - name: Create Release
+        run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.3-prerelease09/cargo-dist-v0.0.3-prerelease09-installer.sh | sh
+      - id: create-release
         run: |
           cargo dist manifest --tag=${{ github.ref_name }} --artifacts=all --no-local-paths --output-format=json > dist-manifest.json
           echo "dist manifest ran successfully"
@@ -70,25 +72,31 @@ jobs:
           gh release upload ${{ github.ref_name }} dist-manifest.json
           echo "uploaded manifest!"
 
+          # Disable all the upload-artifacts tasks if we have no actual releases
+          HAS_RELEASES=$(cat dist-manifest.json | jq --raw-output ".releases != null")
+          echo "has-releases=$HAS_RELEASES" >> "$GITHUB_OUTPUT"
+
   # Build and packages all the things
   upload-artifacts:
+    # Let the initial task tell us to not run (currently very blunt)
     needs: create-release
+    if: ${{ needs.create-release.outputs.has-releases == 'true' }}
     strategy:
       matrix:
         # For these target platforms
         include:
         - os: ubuntu-20.04
           dist-args: --artifacts=global
-          install-dist: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.3-prerelease04/cargo-dist-v0.0.3-prerelease04-installer.sh | sh
+          install-dist: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.3-prerelease09/cargo-dist-v0.0.3-prerelease09-installer.sh | sh
         - os: macos-11
           dist-args: --artifacts=local --target=x86_64-apple-darwin
-          install-dist: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.3-prerelease04/cargo-dist-v0.0.3-prerelease04-installer.sh | sh
+          install-dist: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.3-prerelease09/cargo-dist-v0.0.3-prerelease09-installer.sh | sh
         - os: ubuntu-20.04
           dist-args: --artifacts=local --target=x86_64-unknown-linux-gnu
-          install-dist: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.3-prerelease04/cargo-dist-v0.0.3-prerelease04-installer.sh | sh
+          install-dist: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.3-prerelease09/cargo-dist-v0.0.3-prerelease09-installer.sh | sh
         - os: windows-2019
           dist-args: --artifacts=local --target=x86_64-pc-windows-msvc
-          install-dist: irm  https://github.com/axodotdev/cargo-dist/releases/download/v0.0.3-prerelease04/cargo-dist-v0.0.3-prerelease04-installer.ps1 | iex
+          install-dist: irm  https://github.com/axodotdev/cargo-dist/releases/download/v0.0.3-prerelease09/cargo-dist-v0.0.3-prerelease09-installer.ps1 | iex
 
     runs-on: ${{ matrix.os }}
     env:
@@ -111,7 +119,7 @@ jobs:
           cat dist-manifest.json
 
           # Parse out what we just built and upload it to the Github Release™
-          cat dist-manifest.json | jq --raw-output ".releases[].artifacts[].path" > uploads.txt
+          cat dist-manifest.json | jq --raw-output ".artifacts[]?.path | select( . != null )" > uploads.txt
           echo "uploading..."
           cat uploads.txt
           gh release upload ${{ github.ref_name }} $(cat uploads.txt)
@@ -119,7 +127,9 @@ jobs:
 
   # Mark the Github Release™ as a non-draft now that everything has succeeded!
   publish-release:
+    # Only run after all the other tasks, but it's ok if upload-artifacts was skipped
     needs: [create-release, upload-artifacts]
+    if: ${{ always() && needs.create-release.result == 'success' && (needs.upload-artifacts.result == 'skipped' || needs.upload-artifacts.result == 'success') }}
     runs-on: ubuntu-latest
     env:
       GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/Cargo.lock b/Cargo.lock
index eb65f0bc..1249b30b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -146,7 +146,7 @@ dependencies = [
 
 [[package]]
 name = "cargo-dist"
-version = "0.0.3-prerelease04"
+version = "0.0.3-prerelease09"
 dependencies = [
  "camino",
  "cargo-dist-schema",
@@ -174,7 +174,7 @@ dependencies = [
 
 [[package]]
 name = "cargo-dist-schema"
-version = "0.0.3-prerelease04"
+version = "0.0.3-prerelease09"
 dependencies = [
  "camino",
  "insta",
diff --git a/Cargo.toml b/Cargo.toml
index fc3c5c24..420c34a2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,7 +11,7 @@ tag-name = "v{{version}}"
 # Config for 'cargo dist'
 [workspace.metadata.dist]
 # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax)
-cargo-dist-version = "0.0.3-prerelease04"
+cargo-dist-version = "0.0.3-prerelease09"
 # The preferred Rust toolchain to use in CI (rustup toolchain syntax)
 rust-toolchain-version = "1.67.1"
 # CI backends to support (see 'cargo dist generate-ci')
@@ -24,3 +24,4 @@ targets = ["x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-pc-windows
 # The profile that 'cargo dist' will build with
 [profile.dist]
 inherits = "release"
+lto = "thin"
diff --git a/README.md b/README.md
index 6631f3b6..b627634b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# cargo-dist
+# `cargo-dist`
 
 [![crates.io](https://img.shields.io/crates/v/cargo-dist.svg)](https://crates.io/crates/cargo-dist) [![docs](https://docs.rs/cargo-dist/badge.svg)](https://docs.rs/cargo-dist)
 ![Rust CI](https://github.com/axodotdev/cargo-dist/workflows/Rust%20CI/badge.svg?branch=main)
@@ -36,11 +36,8 @@ That said, the current version is Very Very Unstable And Experimental and the ex
   * [Updating Snapshots](#updating-snapshots)
   * [Cutting Releases](#cutting-releases)
 
-
-
 # Way-Too-Quick Start
 
-
 ```sh
 # install tools
 cargo install cargo-dist
@@ -81,12 +78,8 @@ cargo release 0.1.0
 
 (I left off the --execute flag from `cargo-release` so you won't actually break anything if you really did just copy paste that 😇)
 
-
-
-
 # Installation
 
-
 ## Install Prebuilt Binaries With cargo-binstall
 
 ```sh
@@ -95,7 +88,6 @@ cargo binstall cargo-dist --no-symlinks
 
 (Without `--no-symlinks` [this may fail on Windows](https://github.com/cargo-bins/cargo-binstall/issues/728))
 
-
 ## Build From Source With Cargo
 
 ```sh
@@ -104,7 +96,6 @@ cargo install cargo-dist --profile=dist
 
 (`--profile=dist` may get you a slightly more optimized binary.)
 
-
 ## Install From The AUR
 
 Arch Linux users can install `cargo-dist` from the [AUR](https://aur.archlinux.org/packages?O=0&SeB=nd&K=cargo-dist&outdated=&SB=p&SO=d&PP=50&submit=Go) using an [AUR helper](https://wiki.archlinux.org/title/AUR_helpers). For example:
@@ -160,11 +151,9 @@ If you want to just (re)generate the ci scripts, you can do:
 cargo dist generate-ci
 ```
 
-(This assumes you have set `ci = ["github"]` in your Cargo.toml, which you should do so that things like installers understand that Github Releases are a place to fetch artifacts from. You *can* pass "github" to generate-ci to test it out, but it won't persist. Maybe that UX should be reworked.)
-
-See the next section ("Usage (CI)") for how the github workflow is triggered and what it does.
-
+> This assumes you have set `ci = ["github"]` in the `[workspace.metadata.dist]` section of your `Cargo.toml`, which you should do so that things like installers understand that Github Releases are a place to fetch artifacts from. You *can* pass `-ci=github` to `cargo dist generate-ci` to test it out, but it won't persist. Maybe that UX should be reworked.
 
+See the [Usage (CI)](#usage-ci) section for how the Github workflow is triggered and what it does.
 
 ## Configuring Installers
 
@@ -177,8 +166,6 @@ cargo dist init --ci=github --installer=shell --installer=powershell
 
 This will result in `installer.sh` and `installer.ps1` being generated which fetch from a Github Release™️ and copy the binaries to `$HOME/.cargo/bin/` on the assumption that this is on your PATH. The scripts are currently brittle and won't properly tell Cargo about the installation (making `cargo uninstall` and some other commands behave incorrectly). As such they're currently only really appropriate for setting up temporary environments like CI without any other binaries. This will be improved in the future.
 
-
-
 ## Configuring Targets
 
 By default, `init` and `generate-ci` will assume you want to build for a "standard desktop suite of targets". This is currently:
@@ -189,18 +176,15 @@ By default, `init` and `generate-ci` will assume you want to build for a "standa
 
 (In The future arm64 counterparts and linux-musl will probably join this, but unfortunately we currently don't support cross-compilation.)
 
-If you would like to manually specify the targets, you can do this with `--target=...` which can be passed any number of times. If this flag is passed then the defaults will be cleared. 
+If you would like to manually specify the targets, you can do this with `--target=…` which can be passed any number of times. If this flag is passed then the defaults will be cleared. 
 
 Other commands like `cargo dist build` (bare `cargo dist`) will always default to only using the current host target, and may need more manual target specification. This is handled automatically if you're using dist's generated CI scripts.
 
 **cargo-dist does not currently support specifying additional targets based on different `--features` or anything else, this will change in the future. See [issue #22](https://github.com/axodotdev/cargo-dist/issues/22) for discussion.**
 
-
-
-
 # Usage (CI)
 
-Once you've completed setup (run `cargo dist init --ci=...`), you're ready to start cutting releases!
+Once you've completed setup (run `cargo dist init --ci=…`), you're ready to start cutting releases!
 
 The github workflow will trigger whenever you push a [git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) to the main branch of your repository that looks like a version number (`v1`, `v1.2.0`, `v0.1.0-prerelease2`, etc.).
 
@@ -224,9 +208,9 @@ cargo release 0.1.0
 
 > NOTE: this will do nothing unless you also pass `--execute`, this is omitted intentionally!
 
-> ALSO NOTE: if your application is part of a larger workspace, you may want to configure cargo-release with things like `shared-version` and `tag-name` to get the desired result. In the future the CI scripts we generate may be smarter and able to detect things like "partial publishes of the workspace". For now we assume you're always publishing the entire workspace!
+> ALSO NOTE: if your application is part of a larger workspace, you may want to configure `cargo release` with things like `shared-version` and `tag-name` to get the desired result. In the future the CI scripts we generate may be smarter and able to detect things like "partial publishes of the workspace". For now we assume you're always publishing the entire workspace!
 
-cargo-release will then automatically:
+`cargo release` will then automatically:
 
 1. Bump all your version numbers
 2. Make a git commit
@@ -244,14 +228,11 @@ When you *do* push a tag (and the commit it points to) the CI will take over and
 
 The reason we do this extra dance with drafts is that we don't want to notify anyone of the release until it's Complete, but also don't want to lose anything if some only some of the build tasks failed.
 
-
-
-
 # Usage (Local Builds)
 
 > When you run bare `cargo dist` this is actually a synonym for `cargo dist build`. For the sake of clarity these docs will prefer this longer form.
 
-The happy path of cargo-dist is to just have its generated CI scripts handle all the details for you, so you never *really* need to run `cargo dist build` if you're happy to leave it to the CI. But there's plenty of reasons to want to do a local build, or to just want to understand what the builds do, so here's the docs for that!
+The happy path of `cargo dist` is to just have its generated CI scripts handle all the details for you, so you never *really* need to run `cargo dist build` if you're happy to leave it to the CI. But there's plenty of reasons to want to do a local build, or to just want to understand what the builds do, so here's the docs for that!
 
 At a high level, `cargo dist build` will:
 
@@ -274,8 +255,6 @@ If you run `cargo dist manifest --output-format=json` it will skip generating ar
 
 For further details, see [Concepts](#concepts) and [Build Flags](#build-flags).
 
-
-
 # Concepts
 
 cargo-dist views the world as follows:
@@ -309,9 +288,6 @@ A current key property of cargo-dist's design is that it can compute all of thes
 
 (Applications only really exist implicitly -- in practice cargo-dist on really ever talks about Releases, since that's just An Application With A Version, and we always have *some* version.)
 
-
-
-
 # Build Flags
 
 cargo-dist changes a bunch of the default build flags you would get with `cargo build --release`, so here's what we change and why!
@@ -342,7 +318,6 @@ In the future we'll probably also turn on these settings:
 
 In a similar vein to the `crt-static` change, we may also one day prefer `linux-musl` over `linux-gnu` to produce more portable binaries. Currently the only mechanism we have to do this is "try to run builds on Github's older linux images so the minimum glibc version isn't too high". This is a place where we lack expertese and welcome recommendations! (This is blocked on supporting cross-compilation.)
 
-
 # Compatibility With Other Tools
 
 cargo-dist can used totally standalone (well, you need Cargo), but is intended to be a cog in various machines. Here's some things that work well with it:
@@ -351,11 +326,6 @@ cargo-dist can used totally standalone (well, you need Cargo), but is intended t
 * If you set `repository` in your Cargo.toml, then [cargo-binstall](https://github.com/cargo-bins/cargo-binstall) should automagically find, download, and install binaries from the Github Releases™️ we produce without any further configuration
 * FUTURE AXODOTDEV TOOL will be able to consume dist-manifest.json and DO COOL THINGS
 
-
-
-
-
-
 # Contributing
 
 ## Updating Snapshots
@@ -380,8 +350,6 @@ If you know you like the changes, just use `cargo insta accept` to auto-apply al
 
 > NOTE: when it succeeds, cargo-dist-schema's `emit` test will actually commit the results back to disk to `cargo-dist-schema/cargo-dist-schema.json` as a side-effect. This is a janky hack to make sure we have that stored and up to date at all times (the test also uses an insta snapshot but insta snapshots include an extra gunk header so it's not something we'd want to link end users). The file isn't even used for anything yet, I just want it to Exist because it seems useful and important. In the future we might properly host it and have our outputs link it via a `$schema` field.
 
-
-
 ## Cutting Releases
 
 cargo-dist is self-hosting, so just follow the usual [usage instructions](#usage-ci) and publish with `cargo release 1.0.0`! (Or whatever cooler version you prefer.)
diff --git a/RELEASES.md b/RELEASES.md
index db08e97f..b59548ab 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,4 +1,4 @@
-# Version 0.0.3 (not yet released!)
+# Version 0.0.3 (under development)
 
 **USE AT YOUR OWN RISK, SUPER ALPHA**
 
@@ -98,6 +98,8 @@ Release notes are now temporarily simplified for reliability:
 
 * If you are publishing `1.2.0-prerelease` and we don't find that in your RELEASES/CHANGELOG file, we will now also look for bare `1.2.0` (stripping the prerelease/build portions), on the assumption that these are the WIP release notes for the version you're prereleasing. This lets you iterate on a version without having to churn headings every time you want to cut a prerelease (we recommend including a parenthetical indicating the version is not yet released).
 
+* If the above explained deferring happens, we will modify the release note's title to include the prerelease suffix. This ensures they are easily identifiable as prereleases on GitHub's releases page.
+
 * We will no longer attempt to include your release notes for Singular Announcements (see the previous section). They will only get auto-generated installers/downloads sections. This is obviously suboptimal, and will be fixed, we just need to do design work on the proper way to handle those cases. (Please tell me in [issue #139](https://github.com/axodotdev/cargo-dist/issues/139)!)
 
 
diff --git a/cargo-dist-schema/Cargo.toml b/cargo-dist-schema/Cargo.toml
index 904dc988..65cdefab 100644
--- a/cargo-dist-schema/Cargo.toml
+++ b/cargo-dist-schema/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "cargo-dist-schema"
 description = "Schema information for cargo-dist's dist-manifest.json"
-version = "0.0.3-prerelease04"
+version = "0.0.3-prerelease09"
 edition = "2021"
 license = "MIT OR Apache-2.0"
 repository = "https://github.com/axodotdev/cargo-dist"
diff --git a/cargo-dist-schema/cargo-dist-json-schema.json b/cargo-dist-schema/cargo-dist-json-schema.json
index 4f3bc115..84fa435a 100644
--- a/cargo-dist-schema/cargo-dist-json-schema.json
+++ b/cargo-dist-schema/cargo-dist-json-schema.json
@@ -37,6 +37,13 @@
         "null"
       ]
     },
+    "artifacts": {
+      "description": "The artifacts included in this Announcement, referenced by releases.",
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/definitions/Artifact"
+      }
+    },
     "dist_version": {
       "description": "The version of cargo-dist that generated this",
       "type": [
@@ -87,21 +94,6 @@
             }
           }
         },
-        {
-          "description": "Machine-readable metadata",
-          "type": "object",
-          "required": [
-            "kind"
-          ],
-          "properties": {
-            "kind": {
-              "type": "string",
-              "enum": [
-                "dist-metadata"
-              ]
-            }
-          }
-        },
         {
           "description": "Installer",
           "type": "object",
@@ -302,22 +294,8 @@
           "description": "The artifacts for this release (zips, debuginfo, metadata...)",
           "type": "array",
           "items": {
-            "$ref": "#/definitions/Artifact"
+            "type": "string"
           }
-        },
-        "changelog_body": {
-          "description": "The body of the changelog for this release",
-          "type": [
-            "string",
-            "null"
-          ]
-        },
-        "changelog_title": {
-          "description": "The title of the changelog for this release",
-          "type": [
-            "string",
-            "null"
-          ]
         }
       }
     }
diff --git a/cargo-dist-schema/src/lib.rs b/cargo-dist-schema/src/lib.rs
index e026beac..0c100b4e 100644
--- a/cargo-dist-schema/src/lib.rs
+++ b/cargo-dist-schema/src/lib.rs
@@ -8,6 +8,8 @@
 //!
 //! The root type of the schema is [`DistManifest`][].
 
+use std::collections::BTreeMap;
+
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 
@@ -21,6 +23,8 @@ pub type LocalPath = String;
 ///
 /// (Should we normalize this one?)
 pub type RelPath = String;
+/// The unique ID of an Artifact
+pub type ArtifactId = String;
 
 /// A report of the releases and artifacts that cargo-dist generated
 #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
@@ -52,6 +56,10 @@ pub struct DistManifest {
     #[serde(default)]
     #[serde(skip_serializing_if = "Vec::is_empty")]
     pub releases: Vec<Release>,
+    /// The artifacts included in this Announcement, referenced by releases.
+    #[serde(default)]
+    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
+    pub artifacts: BTreeMap<ArtifactId, Artifact>,
 }
 
 /// A Release of an Application
@@ -65,15 +73,7 @@ pub struct Release {
     /// The artifacts for this release (zips, debuginfo, metadata...)
     #[serde(default)]
     #[serde(skip_serializing_if = "Vec::is_empty")]
-    pub artifacts: Vec<Artifact>,
-    /// The title of the changelog for this release
-    #[serde(default)]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub changelog_title: Option<String>,
-    /// The body of the changelog for this release
-    #[serde(default)]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub changelog_body: Option<String>,
+    pub artifacts: Vec<ArtifactId>,
 }
 
 /// A distributable artifact that's part of a Release
@@ -166,9 +166,6 @@ pub enum ArtifactKind {
     /// Standalone Symbols/Debuginfo for a build
     #[serde(rename = "symbols")]
     Symbols,
-    /// Machine-readable metadata
-    #[serde(rename = "dist-metadata")]
-    DistMetadata,
     /// Installer
     #[serde(rename = "installer")]
     Installer,
@@ -191,7 +188,7 @@ pub struct ExecutableAsset {
 
 impl DistManifest {
     /// Create a new DistManifest
-    pub fn new(releases: Vec<Release>) -> Self {
+    pub fn new(releases: Vec<Release>, artifacts: BTreeMap<String, Artifact>) -> Self {
         Self {
             dist_version: None,
             announcement_tag: None,
@@ -200,6 +197,7 @@ impl DistManifest {
             announcement_changelog: None,
             announcement_github_body: None,
             releases,
+            artifacts,
         }
     }
 
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 4b9ccf60..f65772af 100644
--- a/cargo-dist-schema/src/snapshots/cargo_dist_schema__emit.snap
+++ b/cargo-dist-schema/src/snapshots/cargo_dist_schema__emit.snap
@@ -41,6 +41,13 @@ expression: json_schema
         "null"
       ]
     },
+    "artifacts": {
+      "description": "The artifacts included in this Announcement, referenced by releases.",
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/definitions/Artifact"
+      }
+    },
     "dist_version": {
       "description": "The version of cargo-dist that generated this",
       "type": [
@@ -91,21 +98,6 @@ expression: json_schema
             }
           }
         },
-        {
-          "description": "Machine-readable metadata",
-          "type": "object",
-          "required": [
-            "kind"
-          ],
-          "properties": {
-            "kind": {
-              "type": "string",
-              "enum": [
-                "dist-metadata"
-              ]
-            }
-          }
-        },
         {
           "description": "Installer",
           "type": "object",
@@ -306,22 +298,8 @@ expression: json_schema
           "description": "The artifacts for this release (zips, debuginfo, metadata...)",
           "type": "array",
           "items": {
-            "$ref": "#/definitions/Artifact"
+            "type": "string"
           }
-        },
-        "changelog_body": {
-          "description": "The body of the changelog for this release",
-          "type": [
-            "string",
-            "null"
-          ]
-        },
-        "changelog_title": {
-          "description": "The title of the changelog for this release",
-          "type": [
-            "string",
-            "null"
-          ]
         }
       }
     }
diff --git a/cargo-dist/Cargo.toml b/cargo-dist/Cargo.toml
index 82623568..6c470796 100644
--- a/cargo-dist/Cargo.toml
+++ b/cargo-dist/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "cargo-dist"
 description = "Shippable application packaging for Rust"
-version = "0.0.3-prerelease04"
+version = "0.0.3-prerelease09"
 edition = "2021"
 license = "MIT OR Apache-2.0"
 repository = "https://github.com/axodotdev/cargo-dist"
@@ -32,7 +32,7 @@ console = { version = "0.15.1", optional = true }
 clap-cargo = { version = "0.10.0", optional = true }
 
 # Features used by the cli and library
-cargo-dist-schema = { version = "=0.0.3-prerelease04", path = "../cargo-dist-schema" }
+cargo-dist-schema = { version = "=0.0.3-prerelease09", path = "../cargo-dist-schema" }
 miette = { version = "5.3.0" }
 thiserror = "1.0.35"
 tracing = { version = "0.1.36", features = ["log"] }
diff --git a/cargo-dist/src/ci.rs b/cargo-dist/src/ci.rs
index 5666722f..3c26c5a8 100644
--- a/cargo-dist/src/ci.rs
+++ b/cargo-dist/src/ci.rs
@@ -25,13 +25,13 @@ pub fn generate_github_ci(dist: &DistGraph) -> Result<(), miette::Report> {
     std::fs::create_dir_all(&ci_dir)
         .into_diagnostic()
         .wrap_err("Failed to create ci dir")?;
-    let mut file = File::create(ci_file)
+    let mut file = File::create(&ci_file)
         .into_diagnostic()
         .wrap_err("Failed to create ci file")?;
     write_github_ci(&mut file, dist)
         .into_diagnostic()
         .wrap_err("Failed to write to CI file")?;
-    info!("successfully generated Github CI");
+    eprintln!("generated Github CI to {}", ci_file);
     Ok(())
 }
 
diff --git a/cargo-dist/src/ci.yml b/cargo-dist/src/ci.yml
index e436aaaf..f409dc55 100644
--- a/cargo-dist/src/ci.yml
+++ b/cargo-dist/src/ci.yml
@@ -45,6 +45,8 @@ jobs:
   # Create the Github Release™ so the packages have something to be uploaded to
   create-release:
     runs-on: ubuntu-latest
+    outputs:
+      has-releases: ${{ steps.create-release.outputs.has-releases }}
     env:
       GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
     steps:
@@ -53,7 +55,7 @@ jobs:
         run: {{{{INSTALL_RUST}}}}
       - name: Install cargo-dist
         run: {{{{INSTALL_DIST_SH}}}}
-      - name: Create Release
+      - id: create-release
         run: |
           cargo dist manifest --tag=${{ github.ref_name }} --artifacts=all --no-local-paths --output-format=json > dist-manifest.json
           echo "dist manifest ran successfully"
@@ -70,9 +72,15 @@ jobs:
           gh release upload ${{ github.ref_name }} dist-manifest.json
           echo "uploaded manifest!"
 
+          # Disable all the upload-artifacts tasks if we have no actual releases
+          HAS_RELEASES=$(cat dist-manifest.json | jq --raw-output ".releases != null")
+          echo "has-releases=$HAS_RELEASES" >> "$GITHUB_OUTPUT"
+
   # Build and packages all the things
   upload-artifacts:
+    # Let the initial task tell us to not run (currently very blunt)
     needs: create-release
+    if: ${{ needs.create-release.outputs.has-releases == 'true' }}
     strategy:
       matrix:
         # For these target platforms
@@ -99,7 +107,7 @@ jobs:
           cat dist-manifest.json
 
           # Parse out what we just built and upload it to the Github Release™
-          cat dist-manifest.json | jq --raw-output ".releases[].artifacts[].path" > uploads.txt
+          cat dist-manifest.json | jq --raw-output ".artifacts[]?.path | select( . != null )" > uploads.txt
           echo "uploading..."
           cat uploads.txt
           gh release upload ${{ github.ref_name }} $(cat uploads.txt)
@@ -107,7 +115,9 @@ jobs:
 
   # Mark the Github Release™ as a non-draft now that everything has succeeded!
   publish-release:
+    # Only run after all the other tasks, but it's ok if upload-artifacts was skipped
     needs: [create-release, upload-artifacts]
+    if: ${{ always() && needs.create-release.result == 'success' && (needs.upload-artifacts.result == 'skipped' || needs.upload-artifacts.result == 'success') }}
     runs-on: ubuntu-latest
     env:
       GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/cargo-dist/src/lib.rs b/cargo-dist/src/lib.rs
index 7e0ef414..a34f479d 100644
--- a/cargo-dist/src/lib.rs
+++ b/cargo-dist/src/lib.rs
@@ -5,11 +5,18 @@
 //!
 //!
 
-use std::{collections::HashMap, fs::File, io::BufReader, ops::Not, process::Command};
+use std::{
+    collections::{BTreeMap, HashMap},
+    fs::File,
+    io::BufReader,
+    ops::Not,
+    process::Command,
+};
 
 use camino::{Utf8Path, Utf8PathBuf};
 use cargo_dist_schema::{Asset, AssetKind, DistManifest, ExecutableAsset, Release};
 use flate2::{write::ZlibEncoder, Compression, GzBuilder};
+use semver::Version;
 use tracing::{info, warn};
 use xz2::write::XzEncoder;
 use zip::ZipWriter;
@@ -79,16 +86,21 @@ pub fn do_manifest(cfg: &Config) -> Result<DistManifest> {
 fn build_manifest(cfg: &Config, dist: &DistGraph) -> DistManifest {
     // Report the releases
     let mut releases = vec![];
+    let mut all_artifacts = BTreeMap::<String, cargo_dist_schema::Artifact>::new();
     for release in &dist.releases {
         // Gather up all the local and global artifacts
         let mut artifacts = vec![];
         for &artifact_idx in &release.global_artifacts {
-            artifacts.push(manifest_artifact(cfg, dist, artifact_idx));
+            let id = &dist.artifact(artifact_idx).id;
+            all_artifacts.insert(id.clone(), manifest_artifact(cfg, dist, artifact_idx));
+            artifacts.push(id.clone());
         }
         for &variant_idx in &release.variants {
             let variant = dist.variant(variant_idx);
             for &artifact_idx in &variant.local_artifacts {
-                artifacts.push(manifest_artifact(cfg, dist, artifact_idx));
+                let id = &dist.artifact(artifact_idx).id;
+                all_artifacts.insert(id.clone(), manifest_artifact(cfg, dist, artifact_idx));
+                artifacts.push(id.clone());
             }
         }
 
@@ -96,13 +108,11 @@ fn build_manifest(cfg: &Config, dist: &DistGraph) -> DistManifest {
         releases.push(Release {
             app_name: release.app_name.clone(),
             app_version: release.version.to_string(),
-            changelog_title: release.changelog_title.clone(),
-            changelog_body: release.changelog_body.clone(),
             artifacts,
         })
     }
 
-    let mut manifest = DistManifest::new(releases);
+    let mut manifest = DistManifest::new(releases, all_artifacts);
     manifest.dist_version = Some(env!("CARGO_PKG_VERSION").to_owned());
     manifest.announcement_tag = dist.announcement_tag.clone();
     manifest.announcement_is_prerelease = dist.announcement_is_prerelease;
@@ -598,120 +608,26 @@ fn init_dist_profile(_cfg: &Config, workspace_toml: &mut toml_edit::Document) ->
     }
     let mut new_profile = toml_edit::table();
     {
+        // For some detailed discussion, see: https://github.com/axodotdev/cargo-dist/issues/118
         let new_profile = new_profile.as_table_mut().unwrap();
         // We're building for release, so this is a good base!
         new_profile.insert("inherits", toml_edit::value("release"));
-        // We want *full* debuginfo for good crashreporting/profiling
-        // This doesn't bloat the final binary because we use split-debuginfo below
-        // new_profile.insert("debug", toml_edit::value(true));
-
-        // Ensure that all debuginfo is pulled out of the binary and tossed
-        // into a separate file from the final binary. This should ideally be
-        // uploaded to something like a symbol server to be fetched on demand.
-        // This is already the default on windows (.pdb) and macos (.dsym) but
-        // is rather bleeding on other platforms (.dwp) -- it requires Rust 1.65,
-        // which as of this writing in the latest stable rust release! If anyone
-        // ever makes a big deal with building final binaries with an older MSRV
-        // we may need to more intelligently select this.
-        // new_profile.insert("split-debuginfo", toml_edit::value("packed"));
-
-        // TODO: set codegen-units=1? (Probably Not!)
+        // We're building for SUPER DUPER release, so lto is a good idea to enable!
         //
-        // Ok so there's an inherent tradeoff in compilers where if the compiler does
-        // everything in a very serial/global way, it can discover more places where
-        // optimizations can be done and theoretically make things faster/smaller
-        // using all the information at its fingertips... at the cost of your builds
-        // taking forever. Compiler optimizations generally take super-linear time,
-        // so if you let the compiler see and think about EVERYTHING your builds
-        // can literally take *days* for codebases on the order of LLVM itself.
+        // There's a decent argument for lto=true (aka "fat") here but the cost-benefit
+        // is a bit complex. Fat LTO can be way more expensive to compute (to the extent
+        // that enormous applications like chromium can become unbuildable), but definitely
+        // eeks out a bit more from your binaries.
         //
-        // To keep compile times tractable, we generally break up the program
-        // into "codegen units" (AKA "translation units") that get compiled
-        // independently and then combined by the linker. This keeps the super-linear
-        // scaling under control, but prevents optimizations like inlining across
-        // units. (This process is why we have things like "object files" and "rlibs",
-        // those are the intermediate artifacts fed to the linker.)
+        // In principle cargo-dist is targetting True Shippable Binaries and so it's
+        // worth it to go nuts getting every last drop out of your binaries... but a lot
+        // of people are going to build binaries that might never even be used, so really
+        // we're just burning a bunch of CI time for nothing.
         //
-        // Compared to C, Rust codegen units are quite monolithic. Where each C
-        // *file* might gets its own codegen unit, Rust prefers scoping them to
-        // an entire *crate*.  As it turns out, neither of these answers is right in
-        // all cases, and being able to tune the unit size is useful.
-        //
-        // Large C++ codebases like Firefox have "unified" builds where they basically
-        // concatenate files together to get bigger units. Rust provides the
-        // opposite: the codegen-units=N option tells rustc that it should try to
-        // break up a crate into at most N different units. This is done with some
-        // heuristics and constraints to try to still get the most out of each unit
-        // (i.e. try to keep functions that call eachother together for inlining).
-        //
-        // In the --release profile, codegen-units is set to 16, which attempts
-        // to strike a balance between The Best Binaries and Ever Finishing Compiles.
-        // In principle, tuning this down to 1 could be profitable, but LTO
-        // (see the next TODO) does most of that work for us. As such we can probably
-        // leave this alone to keep compile times reasonable.
-
-        // TODO: set lto="thin" (or "fat")? (Probably "fat"!)
-        //
-        // LTO, Link Time Optimization, is basically hijacking the step where we
-        // would link together everything and going back to the compiler (LLVM) to
-        // do global optimizations across codegen-units (see the previous TODO).
-        // Better Binaries, Slower Build Times.
-        //
-        // LTO can be "fat" (or "full") or "thin".
-        //
-        // Fat LTO is the "obvious" implementation: once you're done individually
-        // optimizing the LLVM bitcode (IR) for each compilation unit, you concatenate
-        // all the units and optimize it all together. Extremely serial, extremely
-        // slow, but thorough as hell. For *enormous* codebases (millions of lines)
-        // this can become intractably expensive and crash the compiler.
-        //
-        // Thin LTO is newer and more complicated: instead of unconditionally putting
-        // everything together, we want to optimize each unit with other "useful" units
-        // pulled in for inlining and other analysis. This grouping is done with
-        // similar heuristics that rustc uses to break crates into codegen-units.
-        // This is much faster than Fat LTO and can scale to arbitrarily big
-        // codebases, but does produce slightly worse results.
-        //
-        // Release builds currently default to lto=false, which, despite the name,
-        // actually still does LTO (lto="off" *really* turns it off)! Specifically it
-        // does Thin LTO but *only* between the codegen units for a single crate.
-        // This theoretically negates the disadvantages of codegen-units=16 while
-        // still getting most of the advantages! Neat!
-        //
-        // Since most users will have codebases significantly smaller than An Entire
-        // Browser, we can probably go all the way to default lto="fat", and they
-        // can tune that down if it's problematic. If a user has "nightly" and "stable"
-        // builds, it might be the case that they want lto="thin" for the nightlies
-        // to keep them timely.
-        //
-        // > Aside: you may be wondering "why still have codegen units at all if using
-        // > Fat LTO" and the best answer I can give you is "doing things in parallel
-        // > at first lets you throw out a lot of junk and trim down the input before
-        // > starting the really expensive super-linear global analysis, without losing
-        // > too much of the important information". The less charitable answer is that
-        // > compiler infra is built around codegen units and so this is a pragmatic hack.
-        // >
-        // > Thin LTO of course *really* benefits from still having codegen units.
-
-        // TODO: set panic="abort"?
-        //
-        // PROBABLY NOT, but here's the discussion anyway!
-        //
-        // The default is panic="unwind", and things can be relying on unwinding
-        // for correctness. Unwinding support bloats up the binary and can make
-        // code run slower (because each place that *can* unwind is essentially
-        // an early-return the compiler needs to be cautious of).
-        //
-        // panic="abort" immediately crashes the program if you panic,
-        // but does still run the panic handler, so you *can* get things like
-        // backtraces/crashreports out at that point.
-        //
-        // See RUSTFLAGS="-Cforce-unwind-tables" for the semi-orthogonal flag
-        // that adjusts whether unwinding tables are emitted at all.
-        //
-        // Major C++ applications like Firefox already build with this flag,
-        // the Rust ecosystem largely works fine with either.
-
+        // The user has the freedom to crank this up higher (and/or set codegen-units=1)
+        // if they think it's worth it, but we otherwise probably shouldn't set the planet
+        // on fire just because Number Theoretically Go Up.
+        new_profile.insert("lto", toml_edit::value("thin"));
         new_profile
             .decor_mut()
             .set_prefix("\n# The profile that 'cargo dist' will build with\n")
@@ -880,6 +796,12 @@ pub struct GenerateCiArgs {}
 /// Generate CI scripts (impl of `cargo dist generate-ci`)
 pub fn do_generate_ci(cfg: &Config, _args: &GenerateCiArgs) -> Result<()> {
     let dist = gather_work(cfg)?;
+    if let Some(desired_version) = &dist.desired_cargo_dist_version {
+        let current_version: Version = std::env!("CARGO_PKG_VERSION").parse().unwrap();
+        if desired_version != &current_version {
+            return Err(miette!("you're running cargo-dist {}, but 'cargo-dist-version = {}' is set in your Cargo.toml\n\nYou should update cargo-dist-version if you want to update to this version", current_version, desired_version));
+        }
+    }
     if !dist.is_init {
         return Err(miette!(
             "please run 'cargo dist init' before running any other commands!"
diff --git a/cargo-dist/src/main.rs b/cargo-dist/src/main.rs
index f567d020..904a5183 100644
--- a/cargo-dist/src/main.rs
+++ b/cargo-dist/src/main.rs
@@ -32,6 +32,8 @@ lazy_static! {
 
 fn set_report_errors_as_json() {
     *REPORT_ERROR.lock().unwrap() = Some(Box::new(move |error| {
+        // Still write a human-friendly version to stderr
+        writeln!(&mut Term::stderr(), "{error:?}").unwrap();
         // Manually invoke JSONReportHandler to format the error as a report
         // to out_.
         let mut report = String::new();
@@ -154,7 +156,8 @@ fn print_human(out: &mut Term, manifest: &DistManifest) -> Result<(), std::io::E
                 .blue()
                 .apply_to(format!("  {} {}", release.app_name, release.app_version))
         )?;
-        for artifact in &release.artifacts {
+        for artifact_id in &release.artifacts {
+            let artifact = &manifest.artifacts[artifact_id];
             // Print out the name or path of the artifact (path is more useful by noisier)
             if let Some(path) = &artifact.path {
                 // Try to highlight the actual filename for easier scanning
diff --git a/cargo-dist/src/tasks.rs b/cargo-dist/src/tasks.rs
index ed07fe72..25df4aa0 100644
--- a/cargo-dist/src/tasks.rs
+++ b/cargo-dist/src/tasks.rs
@@ -1393,34 +1393,23 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
             }
         };
 
-        // Try to lookup this version in the changelog
-        let version_string = format!("{}", announcing_version);
-        let release_notes = if let Some(release_notes) = changelogs.get(&*version_string) {
-            release_notes
-        } else if !announcing_version.pre.is_empty() {
-            // If we're a prerelease, try looking for this version without the prerelease suffix
-            // on the basis that we shouldn't be backfilling prereleases after the real release,
-            // so those are probably also our own WIP release notes.
-            let normal_version = Version::new(
-                announcing_version.major,
-                announcing_version.minor,
-                announcing_version.patch,
-            );
-
-            let version_string = format!("{}", normal_version);
-            let Some(release_notes) = changelogs.get(&*version_string) else {
-                info!("failed to find {version_string} in changelogs, skipping changelog generation");
+        // Try to extract the changelog entry for the announcing version.
+        // First, try to find the exact version. If that fails, try to find this version without
+        // the prerelease suffix.
+        // Because releasing a prerelease for a version that was already published before does not
+        // make much sense it is fairly safe to assume that this entry is in fact just our WIP state
+        // of the release notes.
+        let Some((title, notes)) =
+            try_extract_changelog_exact(&changelogs, announcing_version)
+                .or_else(|| try_extract_changelog_normalized(&changelogs, announcing_version))
+            else {
+                info!("failed to find {announcing_version} in changelogs, skipping changelog generation");
                 return;
             };
-            release_notes
-        } else {
-            info!("failed to find {version_string} in changelogs, skipping changelog generation");
-            return;
-        };
 
         info!("succesfully parsed changelog!");
-        self.inner.announcement_title = Some(release_notes.title.to_owned());
-        self.inner.announcement_changelog = Some(release_notes.notes.to_owned());
+        self.inner.announcement_title = Some(title);
+        self.inner.announcement_changelog = Some(notes);
     }
 
     /// If we're publishing to Github, generate some Github notes
@@ -1439,6 +1428,7 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
         if let Some(changelog) = self.inner.announcement_changelog.as_ref() {
             gh_body.push_str("## Release Notes\n\n");
             gh_body.push_str(changelog);
+            gh_body.push_str("\n\n");
         }
 
         // Add the contents of each Release to the body
@@ -1758,9 +1748,13 @@ pub fn gather_work(cfg: &Config) -> Result<DistGraph> {
 
     // Don't proceed if this doesn't make sense
     if rust_releases.is_empty() {
-        return Err(miette!(
-            "This workspace doesn't have anything for cargo-dist to Release!"
-        ));
+        if announcing_package.is_some() {
+            warn!("You're trying to explicitly Release a library, only minimal functionality will work");
+        } else {
+            return Err(miette!(
+                "This workspace doesn't have anything for cargo-dist to Release!"
+            ));
+        }
     }
     // If we don't have a tag yet we MUST successfully select one here or fail
     if announcement_tag.is_none() {
@@ -2284,3 +2278,57 @@ fn try_load_changelog(changelog_path: &Utf8Path) -> Result<String> {
         .wrap_err_with(|| format!("failed to read changelog at {changelog_path}"))?;
     Ok(changelog_str)
 }
+
+/// Tries to find a changelog entry with the exact version given and returns its title and notes.
+fn try_extract_changelog_exact(
+    changelogs: &parse_changelog::Changelog,
+    version: &Version,
+) -> Option<(String, String)> {
+    let version_string = format!("{}", version);
+
+    changelogs.get(&*version_string).map(|release_notes| {
+        (
+            release_notes.title.to_string(),
+            release_notes.notes.to_string(),
+        )
+    })
+}
+
+/// Tries to find a changelog entry that matches the given version's normalized form. That is, just
+/// the `major.minor.patch` part. If successful, the entry's title is modified to include the
+/// version's prerelease part before it is returned together with the notes.
+///
+/// Noop if the given version is already normalized.
+fn try_extract_changelog_normalized(
+    changelogs: &parse_changelog::Changelog,
+    version: &Version,
+) -> Option<(String, String)> {
+    if version.pre.is_empty() {
+        return None;
+    }
+
+    let version_normalized = Version::new(version.major, version.minor, version.patch);
+    let version_normalized_string = format!("{}", version_normalized);
+
+    let release_notes = changelogs.get(&*version_normalized_string)?;
+
+    // title looks something like '<prefix><version><freeform>'
+    // prefix could be 'v' or 'Version ' for example
+    let (prefix_and_version, freeform) = release_notes.title.split_at(
+        release_notes
+            .title
+            .find(&*version_normalized_string)
+            .unwrap() // impossible that this version string is not present in the header
+            + version_normalized_string.len(),
+    );
+
+    // insert prerelease suffix into the title
+    let title = format!(
+        "{}-{} {}",
+        prefix_and_version.trim(),
+        version.pre,
+        freeform.trim()
+    );
+
+    Some((title.trim().to_string(), release_notes.notes.to_string()))
+}


Test patch:
diff --git a/cargo-dist/tests/cli-tests.rs b/cargo-dist/tests/cli-tests.rs
index 8e076735..b244cb99 100644
--- a/cargo-dist/tests/cli-tests.rs
+++ b/cargo-dist/tests/cli-tests.rs
@@ -94,3 +94,63 @@ fn test_manifest() {
 
     assert!(output.status.success(), "{}", output.status);
 }
+
+#[test]
+fn test_lib_manifest() {
+    let version = std::env!("CARGO_PKG_VERSION");
+    let output = Command::new(BIN)
+        .arg("dist")
+        .arg("manifest")
+        .arg("--artifacts=all")
+        .arg("--no-local-paths")
+        .arg("--output-format=json")
+        .arg("--tag")
+        .arg(&format!("cargo-dist-schema-v{}", version))
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    // 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"),
+        (r#""announcement_tag": .*"#, r#""announcement_tag": "CENSORED","#),
+        (r#""announcement_title": .*"#, r#""announcement_title": "CENSORED""#),
+        (r#""announcement_changelog": .*"#, r#""announcement_changelog": "CENSORED""#),
+        (r#""announcement_github_body": .*"#, r#""announcement_github_body": "CENSORED""#),
+        (r#""announcement_is_prerelease": .*"#, r#""announcement_is_prerelease": "CENSORED""#),
+    ]}, {
+        insta::assert_snapshot!(format_outputs(&output));
+    });
+
+    assert!(output.status.success(), "{}", output.status);
+}
+
+#[test]
+fn test_error_manifest() {
+    let output = Command::new(BIN)
+        .arg("dist")
+        .arg("manifest")
+        .arg("--artifacts=all")
+        .arg("--no-local-paths")
+        .arg("--output-format=json")
+        .arg("--tag=v0.0.0")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    // 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"),
+        (r#""announcement_tag": .*"#, r#""announcement_tag": "CENSORED","#),
+        (r#""announcement_title": .*"#, r#""announcement_title": "CENSORED""#),
+        (r#""announcement_changelog": .*"#, r#""announcement_changelog": "CENSORED""#),
+        (r#""announcement_github_body": .*"#, r#""announcement_github_body": "CENSORED""#),
+        (r#""announcement_is_prerelease": .*"#, r#""announcement_is_prerelease": "CENSORED""#),
+    ]}, {
+        insta::assert_snapshot!(format_outputs(&output));
+    });
+
+    assert!(!output.status.success(), "{}", output.status);
+}
diff --git a/cargo-dist/tests/snapshots/cli_tests__error_manifest.snap b/cargo-dist/tests/snapshots/cli_tests__error_manifest.snap
new file mode 100644
index 00000000..5e8a9b07
--- /dev/null
+++ b/cargo-dist/tests/snapshots/cli_tests__error_manifest.snap
@@ -0,0 +1,17 @@
+---
+source: cargo-dist/tests/cli-tests.rs
+expression: format_outputs(&output)
+---
+stdout:
+{"error": {"message": "This workspace doesn't have anything for cargo-dist to Release!","severity": "error","causes": [],"labels": [],"related": []}}
+
+stderr:
+analyzing workspace:
+  cargo-dist (didn't match tag v1.0.0-FAKEVERSION)
+    [bin] cargo-dist
+  cargo-dist-schema (no binaries)
+
+
+  × This workspace doesn't have anything for cargo-dist to Release!
+
+
diff --git a/cargo-dist/tests/snapshots/cli_tests__lib_manifest.snap b/cargo-dist/tests/snapshots/cli_tests__lib_manifest.snap
new file mode 100644
index 00000000..766f108c
--- /dev/null
+++ b/cargo-dist/tests/snapshots/cli_tests__lib_manifest.snap
@@ -0,0 +1,21 @@
+---
+source: cargo-dist/tests/cli-tests.rs
+expression: format_outputs(&output)
+---
+stdout:
+{
+  "dist_version": "1.0.0-FAKEVERSION",
+  "announcement_tag": "CENSORED",
+  "announcement_is_prerelease": "CENSORED"
+  "announcement_title": "CENSORED"
+  "announcement_github_body": "CENSORED"
+}
+
+stderr:
+analyzing workspace:
+  cargo-dist (didn't match tag cargo-dist-schema-v1.0.0-FAKEVERSION)
+    [bin] cargo-dist
+  cargo-dist-schema (no binaries)
+
+ WARN You're trying to explicitly Release a library, only minimal functionality will work
+
diff --git a/cargo-dist/tests/snapshots/cli_tests__manifest.snap b/cargo-dist/tests/snapshots/cli_tests__manifest.snap
index f2b44cdc..f159eaf5 100644
--- a/cargo-dist/tests/snapshots/cli_tests__manifest.snap
+++ b/cargo-dist/tests/snapshots/cli_tests__manifest.snap
@@ -15,130 +15,137 @@ stdout:
       "app_name": "cargo-dist",
       "app_version": "1.0.0-FAKEVERSION",
       "artifacts": [
+        "cargo-dist-v1.0.0-FAKEVERSION-installer.sh",
+        "cargo-dist-v1.0.0-FAKEVERSION-installer.ps1",
+        "cargo-dist-v1.0.0-FAKEVERSION-x86_64-apple-darwin.tar.xz",
+        "cargo-dist-v1.0.0-FAKEVERSION-x86_64-pc-windows-msvc.zip",
+        "cargo-dist-v1.0.0-FAKEVERSION-x86_64-unknown-linux-gnu.tar.xz"
+      ]
+    }
+  ],
+  "artifacts": {
+    "cargo-dist-v1.0.0-FAKEVERSION-installer.ps1": {
+      "name": "cargo-dist-v1.0.0-FAKEVERSION-installer.ps1",
+      "kind": "installer",
+      "target_triples": [
+        "x86_64-pc-windows-msvc"
+      ],
+      "install_hint": "# WARNING: this installer is experimental\nirm https://github.com/axodotdev/cargo-dist/releases/download/v1.0.0-FAKEVERSION/cargo-dist-v1.0.0-FAKEVERSION-installer.ps1 | iex",
+      "description": "Install prebuilt binaries via powershell script"
+    },
+    "cargo-dist-v1.0.0-FAKEVERSION-installer.sh": {
+      "name": "cargo-dist-v1.0.0-FAKEVERSION-installer.sh",
+      "kind": "installer",
+      "target_triples": [
+        "x86_64-apple-darwin",
+        "x86_64-unknown-linux-gnu"
+      ],
+      "install_hint": "# WARNING: this installer is experimental\ncurl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v1.0.0-FAKEVERSION/cargo-dist-v1.0.0-FAKEVERSION-installer.sh | sh",
+      "description": "Install prebuilt binaries via shell script"
+    },
+    "cargo-dist-v1.0.0-FAKEVERSION-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"
+      ],
+      "assets": [
+        {
+          "name": "LICENSE-APACHE",
+          "path": "LICENSE-APACHE",
+          "kind": "license"
+        },
+        {
+          "name": "LICENSE-MIT",
+          "path": "LICENSE-MIT",
+          "kind": "license"
+        },
+        {
+          "name": "README.md",
+          "path": "README.md",
+          "kind": "readme"
+        },
+        {
+          "name": "RELEASES.md",
+          "path": "RELEASES.md",
+          "kind": "changelog"
+        },
+        {
+          "name": "cargo-dist",
+          "path": "cargo-dist",
+          "kind": "executable"
+        }
+      ]
+    },
+    "cargo-dist-v1.0.0-FAKEVERSION-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"
+      ],
+      "assets": [
+        {
+          "name": "LICENSE-APACHE",
+          "path": "LICENSE-APACHE",
+          "kind": "license"
+        },
+        {
+          "name": "LICENSE-MIT",
+          "path": "LICENSE-MIT",
+          "kind": "license"
+        },
+        {
+          "name": "README.md",
+          "path": "README.md",
+          "kind": "readme"
+        },
+        {
+          "name": "RELEASES.md",
+          "path": "RELEASES.md",
+          "kind": "changelog"
+        },
+        {
+          "name": "cargo-dist",
+          "path": "cargo-dist.exe",
+          "kind": "executable"
+        }
+      ]
+    },
+    "cargo-dist-v1.0.0-FAKEVERSION-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"
+      ],
+      "assets": [
         {
-          "name": "cargo-dist-v1.0.0-FAKEVERSION-installer.sh",
-          "kind": "installer",
-          "target_triples": [
-            "x86_64-apple-darwin",
-            "x86_64-unknown-linux-gnu"
-          ],
-          "install_hint": "# WARNING: this installer is experimental\ncurl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v1.0.0-FAKEVERSION/cargo-dist-v1.0.0-FAKEVERSION-installer.sh | sh",
-          "description": "Install prebuilt binaries via shell script"
+          "name": "LICENSE-APACHE",
+          "path": "LICENSE-APACHE",
+          "kind": "license"
         },
         {
-          "name": "cargo-dist-v1.0.0-FAKEVERSION-installer.ps1",
-          "kind": "installer",
-          "target_triples": [
-            "x86_64-pc-windows-msvc"
-          ],
-          "install_hint": "# WARNING: this installer is experimental\nirm https://github.com/axodotdev/cargo-dist/releases/download/v1.0.0-FAKEVERSION/cargo-dist-v1.0.0-FAKEVERSION-installer.ps1 | iex",
-          "description": "Install prebuilt binaries via powershell script"
+          "name": "LICENSE-MIT",
+          "path": "LICENSE-MIT",
+          "kind": "license"
         },
         {
-          "name": "cargo-dist-v1.0.0-FAKEVERSION-x86_64-apple-darwin.tar.xz",
-          "kind": "executable-zip",
-          "target_triples": [
-            "x86_64-apple-darwin"
-          ],
-          "assets": [
-            {
-              "name": "LICENSE-APACHE",
-              "path": "LICENSE-APACHE",
-              "kind": "license"
-            },
-            {
-              "name": "LICENSE-MIT",
-              "path": "LICENSE-MIT",
-              "kind": "license"
-            },
-            {
-              "name": "README.md",
-              "path": "README.md",
-              "kind": "readme"
-            },
-            {
-              "name": "RELEASES.md",
-              "path": "RELEASES.md",
-              "kind": "changelog"
-            },
-            {
-              "name": "cargo-dist",
-              "path": "cargo-dist",
-              "kind": "executable"
-            }
-          ]
+          "name": "README.md",
+          "path": "README.md",
+          "kind": "readme"
         },
         {
-          "name": "cargo-dist-v1.0.0-FAKEVERSION-x86_64-pc-windows-msvc.zip",
-          "kind": "executable-zip",
-          "target_triples": [
-            "x86_64-pc-windows-msvc"
-          ],
-          "assets": [
-            {
-              "name": "LICENSE-APACHE",
-              "path": "LICENSE-APACHE",
-              "kind": "license"
-            },
-            {
-              "name": "LICENSE-MIT",
-              "path": "LICENSE-MIT",
-              "kind": "license"
-            },
-            {
-              "name": "README.md",
-              "path": "README.md",
-              "kind": "readme"
-            },
-            {
-              "name": "RELEASES.md",
-              "path": "RELEASES.md",
-              "kind": "changelog"
-            },
-            {
-              "name": "cargo-dist",
-              "path": "cargo-dist.exe",
-              "kind": "executable"
-            }
-          ]
+          "name": "RELEASES.md",
+          "path": "RELEASES.md",
+          "kind": "changelog"
         },
         {
-          "name": "cargo-dist-v1.0.0-FAKEVERSION-x86_64-unknown-linux-gnu.tar.xz",
-          "kind": "executable-zip",
-          "target_triples": [
-            "x86_64-unknown-linux-gnu"
-          ],
-          "assets": [
-            {
-              "name": "LICENSE-APACHE",
-              "path": "LICENSE-APACHE",
-              "kind": "license"
-            },
-            {
-              "name": "LICENSE-MIT",
-              "path": "LICENSE-MIT",
-              "kind": "license"
-            },
-            {
-              "name": "README.md",
-              "path": "README.md",
-              "kind": "readme"
-            },
-            {
-              "name": "RELEASES.md",
-              "path": "RELEASES.md",
-              "kind": "changelog"
-            },
-            {
-              "name": "cargo-dist",
-              "path": "cargo-dist",
-              "kind": "executable"
-            }
-          ]
+          "name": "cargo-dist",
+          "path": "cargo-dist",
+          "kind": "executable"
         }
       ]
     }
-  ]
+  }
 }
 
 stderr: