Repository: denoland/deno
Pull request: fix(upgrade): more informative information on invalid version

Before:
```
$ deno upgrade v1.xx
error: Invalid version passed
```

After:
```
$ deno upgrade v1.xx
error: Invalid version passed (v1.xx)

Example usage:
  deno upgrade | deno upgrade 1.46 | deno upgrade canary
```

![Screenshot 2024-08-31 at 01 21 30](https://github.com/user-attachments/assets/b7439ac9-593e-4454-9c82-395bd85b013c)

Also updates help text to use "shorthand version" without flags, but a positional arg.

Changed files:
filename: cli/args/flags.rs
patch: @@ -1173,7 +1173,7 @@ static DENO_HELP: &str = cstr!(
                   <p(245)>deno test  |  deno test test.ts</>
     <g>publish</>      Publish the current working directory's package or workspace
     <g>upgrade</>      Upgrade deno executable to given version
-                  <p(245)>deno upgrade  |  deno upgrade --version=1.45.0  |  deno upgrade --canary</>
+                  <p(245)>deno upgrade  |  deno upgrade 1.45.0  |  deno upgrade canary</>
 {after-help}
 
 <y>Docs:</> https://docs.deno.com

filename: cli/tools/upgrade.rs
patch: @@ -15,6 +15,7 @@ use crate::util::progress_bar::ProgressBarStyle;
 use crate::version;
 
 use async_trait::async_trait;
+use color_print::cstr;
 use deno_core::anyhow::bail;
 use deno_core::anyhow::Context;
 use deno_core::error::AnyError;
@@ -37,6 +38,8 @@ const RELEASE_URL: &str = "https://github.com/denoland/deno/releases";
 const CANARY_URL: &str = "https://dl.deno.land/canary";
 const RC_URL: &str = "https://dl.deno.land/release";
 
+static EXAMPLE_USAGE: &str = cstr!("Example usage:\n  <p(245)>deno upgrade | deno upgrade 1.46 | deno upgrade canary</>");
+
 pub static ARCHIVE_NAME: Lazy<String> =
   Lazy::new(|| format!("deno-{}.zip", env!("TARGET")));
 
@@ -372,14 +375,14 @@ pub fn check_for_upgrades(
         log::info!(
           "{} {}",
           colors::green("A new canary release of Deno is available."),
-          colors::italic_gray("Run `deno upgrade --canary` to install it.")
+          colors::italic_gray("Run `deno upgrade canary` to install it.")
         );
       }
       ReleaseChannel::Rc => {
         log::info!(
           "{} {}",
           colors::green("A new release candidate of Deno is available."),
-          colors::italic_gray("Run `deno upgrade --rc` to install it.")
+          colors::italic_gray("Run `deno upgrade rc` to install it.")
         );
       }
       // TODO(bartlomieju)
@@ -647,12 +650,20 @@ impl RequestedVersion {
 
     let (channel, passed_version) = if is_canary {
       if !re_hash.is_match(&passed_version) {
-        bail!("Invalid commit hash passed");
+        bail!(
+          "Invalid commit hash passed ({})\n\n{}",
+          colors::gray(passed_version),
+          EXAMPLE_USAGE
+        );
       }
       (ReleaseChannel::Canary, passed_version)
     } else {
       let Ok(semver) = Version::parse_standard(&passed_version) else {
-        bail!("Invalid version passed");
+        bail!(
+          "Invalid version passed ({})\n\n{}",
+          colors::gray(passed_version),
+          EXAMPLE_USAGE
+        );
       };
 
       if semver.pre.contains(&"rc".to_string()) {

filename: tests/integration/upgrade_tests.rs
patch: @@ -4,6 +4,7 @@ use std::process::Command;
 use std::process::Stdio;
 use std::time::Instant;
 use test_util as util;
+use test_util::assert_starts_with;
 use test_util::TempDir;
 use test_util::TestContext;
 use util::TestContextBuilder;
@@ -163,9 +164,10 @@ fn upgrade_invalid_stable_version() {
     .wait_with_output()
     .unwrap();
   assert!(!output.status.success());
-  assert_eq!(
-    "error: Invalid version passed\n",
-    util::strip_ansi_codes(&String::from_utf8(output.stderr).unwrap())
+  assert_starts_with!(
+    &util::strip_ansi_codes(&String::from_utf8(output.stderr.clone()).unwrap())
+      .to_string(),
+    "error: Invalid version passed (foobar)"
   );
 }
 
@@ -188,9 +190,10 @@ fn upgrade_invalid_canary_version() {
     .wait_with_output()
     .unwrap();
   assert!(!output.status.success());
-  assert_eq!(
-    "error: Invalid commit hash passed\n",
-    util::strip_ansi_codes(&String::from_utf8(output.stderr).unwrap())
+  assert_starts_with!(
+    &util::strip_ansi_codes(&String::from_utf8(output.stderr.clone()).unwrap())
+      .to_string(),
+    "error: Invalid commit hash passed (foobar)"
   );
 }
 
@@ -221,9 +224,10 @@ fn upgrade_invalid_lockfile() {
     .unwrap();
   assert!(!output.status.success());
   // should make it here instead of erroring on an invalid lockfile
-  assert_eq!(
-    "error: Invalid version passed\n",
-    util::strip_ansi_codes(&String::from_utf8(output.stderr).unwrap())
+  assert_starts_with!(
+    &util::strip_ansi_codes(&String::from_utf8(output.stderr.clone()).unwrap())
+      .to_string(),
+    "error: Invalid version passed (foobar)"
   );
 }
 
@@ -251,7 +255,7 @@ fn upgrade_prompt() {
     pty.expect_any(&[
       " 99999.99.99 Run `deno upgrade` to install it.",
       // it builds canary releases on main, so check for this in that case
-      "Run `deno upgrade --canary` to install it.",
+      "Run `deno upgrade canary` to install it.",
     ]);
   });
 }