Repository: denoland/deno
Pull request: feat(unstable): deploy subcommand


Changed files:
filename: cli/args/flags.rs
patch: @@ -31,6 +31,7 @@ use deno_core::error::AnyError;
 use deno_core::resolve_url_or_path;
 use deno_core::url::Url;
 use deno_graph::GraphKind;
+use deno_lib::args::has_flag_env_var;
 use deno_lib::args::CaData;
 use deno_lib::args::UnstableConfig;
 use deno_lib::version::DENO_VERSION_INFO;
@@ -475,6 +476,7 @@ pub enum DenoSubcommand {
   Compile(CompileFlags),
   Completions(CompletionsFlags),
   Coverage(CoverageFlags),
+  Deploy,
   Doc(DocFlags),
   Eval(EvalFlags),
   Fmt(FmtFlags),
@@ -1408,6 +1410,7 @@ pub fn flags_from_vec(args: Vec<OsString>) -> clap::error::Result<Flags> {
       "compile" => compile_parse(&mut flags, &mut m)?,
       "completions" => completions_parse(&mut flags, &mut m, app),
       "coverage" => coverage_parse(&mut flags, &mut m)?,
+      "deploy" => deploy_parse(&mut flags, &mut m)?,
       "doc" => doc_parse(&mut flags, &mut m)?,
       "eval" => eval_parse(&mut flags, &mut m)?,
       "fmt" => fmt_parse(&mut flags, &mut m)?,
@@ -1684,6 +1687,12 @@ pub fn clap_root() -> Command {
         .subcommand(upgrade_subcommand())
         .subcommand(vendor_subcommand());
 
+      let cmd = if std::env::var("DENO_DEPLOY_SUBCOMMAND").is_ok() {
+        cmd.subcommand(deploy_subcommand())
+      } else {
+        cmd
+      };
+
       let help = help_subcommand(&cmd);
       cmd.subcommand(help)
     })
@@ -2187,6 +2196,16 @@ Generate html reports from lcov:
   })
 }
 
+fn deploy_subcommand() -> Command {
+  Command::new("deploy").arg(
+    Arg::new("args")
+      .num_args(0..)
+      .action(ArgAction::Append)
+      .trailing_var_arg(true)
+      .allow_hyphen_values(true),
+  )
+}
+
 fn doc_subcommand() -> Command {
   command("doc",
       cstr!("Show documentation for a module.
@@ -4824,6 +4843,32 @@ fn coverage_parse(
   Ok(())
 }
 
+fn deploy_parse(
+  flags: &mut Flags,
+  matches: &mut ArgMatches,
+) -> clap::error::Result<()> {
+  flags.permissions.allow_all = true;
+
+  let mut args: Vec<String> = matches
+    .remove_many("args")
+    .map(|args| args.collect())
+    .unwrap_or_default();
+
+  if !has_flag_env_var("DENO_DEPLOY_SUBCOMMAND") {
+    args.insert(
+      0,
+      format!(
+        "--endpoint={}",
+        std::env::var("DENO_DEPLOY_SUBCOMMAND").unwrap()
+      ),
+    );
+  }
+
+  flags.argv = args;
+  flags.subcommand = DenoSubcommand::Deploy;
+  Ok(())
+}
+
 fn doc_parse(
   flags: &mut Flags,
   matches: &mut ArgMatches,

filename: cli/main.rs
patch: @@ -128,6 +128,9 @@ async fn run_subcommand(
       }
     }),
     DenoSubcommand::Bundle => exit_with_message("⚠️ `deno bundle` was removed in Deno 2.\n\nSee the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations", 1),
+    DenoSubcommand::Deploy => {
+      spawn_subcommand(async { tools::deploy::deploy(flags, roots).await })
+    }
     DenoSubcommand::Doc(doc_flags) => {
       spawn_subcommand(async { tools::doc::doc(flags, doc_flags).await })
     }

filename: cli/registry.rs
patch: @@ -53,6 +53,12 @@ pub struct PublishingTask {
   pub error: Option<PublishingTaskError>,
 }
 
+#[derive(serde::Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Package {
+  pub latest_version: Option<String>,
+}
+
 #[derive(serde::Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct ApiError {

filename: cli/tools/deploy.rs
patch: @@ -0,0 +1,50 @@
+// Copyright 2018-2025 the Deno authors. MIT license.
+
+use std::sync::Arc;
+
+use deno_core::error::AnyError;
+use deno_lib::worker::LibWorkerFactoryRoots;
+use deno_runtime::WorkerExecutionMode;
+
+use crate::args::jsr_api_url;
+use crate::args::DenoSubcommand;
+use crate::args::Flags;
+use crate::args::RunFlags;
+use crate::factory::CliFactory;
+use crate::registry;
+use crate::tools;
+
+pub async fn deploy(
+  flags: Arc<Flags>,
+  roots: LibWorkerFactoryRoots,
+) -> Result<i32, AnyError> {
+  let cli_factory = CliFactory::from_flags(flags.clone());
+  let client = cli_factory.http_client_provider().get_or_create()?;
+  let registry_api_url = jsr_api_url();
+
+  let response =
+    registry::get_package(&client, registry_api_url, "deno", "deploy").await?;
+  let res = registry::parse_response::<registry::Package>(response).await?;
+
+  let mut flags = Arc::unwrap_or_clone(flags);
+  flags.subcommand = DenoSubcommand::Run(RunFlags {
+    // https://github.com/denoland/deploy-cli
+    script: format!(
+      "jsr:@deno/deploy@{}",
+      res
+        .latest_version
+        .expect("expected @deno/deploy to be published")
+    ),
+    watch: None,
+    bare: false,
+  });
+
+  tools::run::run_script(
+    WorkerExecutionMode::Run,
+    Arc::new(flags),
+    None,
+    None,
+    roots,
+  )
+  .await
+}

filename: cli/tools/mod.rs
patch: @@ -5,6 +5,7 @@ pub mod check;
 pub mod clean;
 pub mod compile;
 pub mod coverage;
+pub mod deploy;
 pub mod doc;
 pub mod fmt;
 pub mod info;