From e39664fe179de6f5a9d55d07c966604c61731cb0 Mon Sep 17 00:00:00 2001 From: Daniel Szoke Date: Fri, 14 Nov 2025 11:14:45 +0100 Subject: [PATCH] chore: Remove deprecated `files` commands (#2956) ### Description Remove all `sentry-cli files ...` subcommands. Also, remove the `sentry-cli releases files ...` commands, which are aliases for the `files` subcommands. ### Issues - Resolves #2513 - Resolves [CLI-85](https://linear.app/getsentry/issue/CLI-85/remove-all-files-subcommands) --- CHANGELOG.md | 1 + src/api/mod.rs | 50 +--- src/commands/files/delete.rs | 58 ---- src/commands/files/list.rs | 52 ---- src/commands/files/mod.rs | 67 ----- src/commands/files/upload.rs | 268 ------------------ src/commands/mod.rs | 2 - src/commands/releases/mod.rs | 11 - src/utils/progress.rs | 6 - .../releases-files-upload-sourcemaps.trycmd | 10 - tests/integration/sourcemaps/upload.rs | 17 +- 11 files changed, 4 insertions(+), 538 deletions(-) delete mode 100644 src/commands/files/delete.rs delete mode 100644 src/commands/files/list.rs delete mode 100644 src/commands/files/mod.rs delete mode 100644 src/commands/files/upload.rs delete mode 100644 tests/integration/_cases/releases/releases-files-upload-sourcemaps.trycmd diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d0c054619..a54828372e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ we should rename this section to "Unreleased" --> ### Breaking Changes +- Removed all `sentry-cli files ...` and `sentry-cli releases files ...` subcommands ([#2956](https://github.com/getsentry/sentry-cli/pull/2956)). These commands provided functionality for managing release files, a feature that has been deprecated in Sentry. Users still using `sentry-cli files upload` to upload source maps should migrate to `sentry-cli sourcemaps upload`. - Removed the `sentry-cli sourcemaps explain` command ([#2947](https://github.com/getsentry/sentry-cli/pull/2947)). The command had been deprecated for some time, since Sentry now has a better in-product debugging flow for source map problems via the "Unminify Code" button, which is displayed on any JavaScript issues which could not be unminified. - Removed support for the legacy API key authentication method ([#2935](https://github.com/getsentry/sentry-cli/pull/2935)). Sentry CLI now only supports authenticating with Auth Tokens. If you are using API key authentication via any of the following methods, you need to generate and use an [Auth Token](https://docs.sentry.io/account/auth-tokens/), instead: - `--api-key` CLI flag diff --git a/src/api/mod.rs b/src/api/mod.rs index 146636c673..c5a35f961e 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -52,7 +52,6 @@ use crate::utils::http::{self, is_absolute_url}; use crate::utils::non_empty::NonEmptySlice; use crate::utils::progress::{ProgressBar, ProgressBarMode}; use crate::utils::retry::{get_default_backoff, DurationAsMilliseconds as _}; -use crate::utils::sourcemaps::get_sourcemap_reference_from_headers; use crate::utils::ui::{capitalize_string, make_byte_progress_bar}; use self::pagination::Pagination; @@ -587,32 +586,6 @@ impl<'a> AuthenticatedApi<'a> { } } - /// Deletes all release files. Returns `true` if files were - /// deleted or `false` otherwise. - pub fn delete_release_files( - &self, - org: &str, - project: Option<&str>, - version: &str, - ) -> ApiResult<()> { - let path = if let Some(project) = project { - format!( - "/projects/{}/{}/files/source-maps/?name={}", - PathArg(org), - PathArg(project), - PathArg(version) - ) - } else { - format!( - "/organizations/{}/files/source-maps/?name={}", - PathArg(org), - PathArg(version) - ) - }; - - self.delete(&path)?.into_result().map(|_| ()) - } - /// Creates a new release. pub fn new_release(&self, org: &str, release: &NewRelease) -> ApiResult { // for single project releases use the legacy endpoint that is project bound. @@ -1440,19 +1413,9 @@ fn handle_req( } else if progress_bar_mode.active() { let pb_progress = pb.clone(); #[expect(clippy::unwrap_used, reason = "legacy code")] - handle.progress_function(move |a, b, c, d| { - let (down_len, down_pos, up_len, up_pos) = (a as u64, b as u64, c as u64, d as u64); + handle.progress_function(move |a, b, _, _| { + let (down_len, down_pos) = (a as u64, b as u64); let mut pb = pb_progress.borrow_mut(); - if up_len > 0 && progress_bar_mode.request() { - if up_pos < up_len { - if pb.is_none() { - *pb = Some(make_byte_progress_bar(up_len)); - } - pb.as_ref().unwrap().set_position(up_pos); - } else if pb.is_some() { - pb.take().unwrap().finish_and_clear(); - } - } if down_len > 0 && progress_bar_mode.response() { if down_pos < down_len { if pb.is_none() { @@ -1899,17 +1862,8 @@ pub struct AuthInfo { #[derive(Clone, Deserialize, Debug)] pub struct Artifact { pub id: String, - pub sha1: String, pub name: String, - pub size: u64, pub dist: Option, - pub headers: HashMap, -} - -impl Artifact { - pub fn get_sourcemap_reference(&self) -> Option<&str> { - get_sourcemap_reference_from_headers(self.headers.iter()) - } } /// Information for new releases diff --git a/src/commands/files/delete.rs b/src/commands/files/delete.rs deleted file mode 100644 index 39a393fe9f..0000000000 --- a/src/commands/files/delete.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::collections::HashSet; - -use anyhow::Result; -use clap::{Arg, ArgAction, ArgMatches, Command}; - -use crate::api::Api; -use crate::config::Config; - -pub fn make_command(command: Command) -> Command { - command - .about("[DEPRECATED] Delete a release file.") - .hide(true) - // Backward compatibility with `releases files ` commands. - .arg(Arg::new("version").long("version").hide(true)) - .arg( - Arg::new("names") - .value_name("NAMES") - .num_args(1..) - .action(ArgAction::Append) - .help("Filenames to delete."), - ) - .arg( - Arg::new("all") - .short('A') - .long("all") - .action(ArgAction::SetTrue) - .help("Delete all files."), - ) -} - -pub fn execute(matches: &ArgMatches) -> Result<()> { - let config = Config::current(); - let release = config.get_release_with_legacy_fallback(matches)?; - let org = config.get_org(matches)?; - let project = config.get_project(matches).ok(); - let api = Api::current(); - let authenticated_api = api.authenticated()?; - - if matches.get_flag("all") { - authenticated_api.delete_release_files(&org, project.as_deref(), &release)?; - println!("All files deleted."); - return Ok(()); - } - - let files: HashSet = match matches.get_many::("names") { - Some(paths) => paths.map(|x| x.into()).collect(), - None => HashSet::new(), - }; - for file in authenticated_api.list_release_files(&org, project.as_deref(), &release)? { - if !files.contains(&file.name) { - continue; - } - if authenticated_api.delete_release_file(&org, project.as_deref(), &release, &file.id)? { - println!("D {}", file.name); - } - } - Ok(()) -} diff --git a/src/commands/files/list.rs b/src/commands/files/list.rs deleted file mode 100644 index 72e98ab1b5..0000000000 --- a/src/commands/files/list.rs +++ /dev/null @@ -1,52 +0,0 @@ -use anyhow::Result; -use clap::{Arg, ArgMatches, Command}; -use indicatif::HumanBytes; - -use crate::{api::Api, config::Config, utils::formatting::Table}; - -pub fn make_command(command: Command) -> Command { - command - .about("[DEPRECATED] List all release files.") - .hide(true) - // Backward compatibility with `releases files ` commands. - .arg(Arg::new("version").long("version").hide(true)) -} - -pub fn execute(matches: &ArgMatches) -> Result<()> { - let config = Config::current(); - let release = config.get_release_with_legacy_fallback(matches)?; - let org = config.get_org(matches)?; - let project = config.get_project(matches).ok(); - let api = Api::current(); - - let mut table = Table::new(); - table - .title_row() - .add("Name") - .add("Distribution") - .add("Source Map") - .add("Size"); - - for artifact in api - .authenticated()? - .list_release_files(&org, project.as_deref(), &release)? - { - let row = table.add_row(); - row.add(&artifact.name); - if let Some(ref dist) = artifact.dist { - row.add(dist); - } else { - row.add(""); - } - if let Some(sm_ref) = artifact.get_sourcemap_reference() { - row.add(sm_ref); - } else { - row.add(""); - } - row.add(HumanBytes(artifact.size)); - } - - table.print(); - - Ok(()) -} diff --git a/src/commands/files/mod.rs b/src/commands/files/mod.rs deleted file mode 100644 index 74b7edbc36..0000000000 --- a/src/commands/files/mod.rs +++ /dev/null @@ -1,67 +0,0 @@ -use anyhow::Result; -use clap::{ArgMatches, Command}; -use console::style; - -use crate::utils::args::ArgExt as _; - -pub mod delete; -pub mod list; -pub mod upload; - -macro_rules! each_subcommand { - ($mac:ident) => { - $mac!(delete); - $mac!(list); - $mac!(upload); - }; -} - -pub fn make_command(mut command: Command) -> Command { - macro_rules! add_subcommand { - ($name:ident) => {{ - command = command.subcommand(crate::commands::files::$name::make_command( - Command::new(stringify!($name).replace('_', "-")), - )); - }}; - } - - command = command - .about("[DEPRECATED] Manage release artifacts.") - .hide(true) - .subcommand_required(true) - .arg_required_else_help(true) - .org_arg() - .project_arg(true) - .release_arg() - // Backward compatibility with `releases files upload-sourcemaps` commands. - .subcommand( - crate::commands::sourcemaps::upload::make_command(Command::new("upload-sourcemaps")) - .hide(true), - ); - - each_subcommand!(add_subcommand); - command -} - -pub fn execute(matches: &ArgMatches) -> Result<()> { - eprintln!("{}", style("⚠ DEPRECATION NOTICE: This functionality will be removed in a future version of `sentry-cli`. \ - Use the `sourcemaps` command instead.").yellow()); - - macro_rules! execute_subcommand { - ($name:ident) => {{ - if let Some(sub_matches) = - matches.subcommand_matches(&stringify!($name).replace('_', "-")) - { - return crate::commands::files::$name::execute(&sub_matches); - } - }}; - } - each_subcommand!(execute_subcommand); - - // To preserve backward compatibility - if let Some(sub_matches) = matches.subcommand_matches("upload-sourcemaps") { - return crate::commands::sourcemaps::upload::execute(sub_matches); - } - - unreachable!(); -} diff --git a/src/commands/files/upload.rs b/src/commands/files/upload.rs deleted file mode 100644 index 160de7e1ac..0000000000 --- a/src/commands/files/upload.rs +++ /dev/null @@ -1,268 +0,0 @@ -#![expect(clippy::unwrap_used, reason = "contains legacy code which uses unwrap")] - -use std::collections::BTreeMap; -use std::ffi::OsStr; -use std::fs; -use std::io::Read as _; -use std::path::Path; -use std::sync::Arc; -use std::time::Duration; - -use anyhow::{bail, format_err, Result}; -use clap::{Arg, ArgAction, ArgMatches, Command}; -use log::warn; -use symbolic::debuginfo::sourcebundle::SourceFileType; - -use crate::api::Api; -use crate::config::Config; -use crate::constants::DEFAULT_MAX_WAIT; -use crate::utils::args::validate_distribution; -use crate::utils::file_search::ReleaseFileSearch; -use crate::utils::file_upload::{ - initialize_legacy_release_upload, FileUpload, SourceFile, UploadContext, -}; -use crate::utils::fs::{decompress_gzip_content, is_gzip_compressed, path_as_url}; -use crate::utils::progress::ProgressBarMode; - -pub fn make_command(command: Command) -> Command { - command - .about("[DEPRECATED] Upload files for a release.") - .hide(true) - // Backward compatibility with `releases files ` commands. - .arg(Arg::new("version").long("version").hide(true)) - .arg( - Arg::new("path") - .value_name("PATH") - .required(true) - .help("The path to the file or directory to upload."), - ) - .arg( - Arg::new("name") - .value_name("NAME") - .help("The name of the file on the server."), - ) - .arg( - Arg::new("dist") - .long("dist") - .short('d') - .value_name("DISTRIBUTION") - .value_parser(validate_distribution) - .help("Optional distribution identifier for this file."), - ) - .arg( - Arg::new("decompress") - .long("decompress") - .action(ArgAction::SetTrue) - .help("Enable files gzip decompression prior to upload."), - ) - .arg( - Arg::new("wait") - .long("wait") - .action(ArgAction::SetTrue) - .conflicts_with("wait_for") - .help("Wait for the server to fully process uploaded files."), - ) - .arg( - Arg::new("wait_for") - .long("wait-for") - .value_name("SECS") - .value_parser(clap::value_parser!(u64)) - .conflicts_with("wait") - .help( - "Wait for the server to fully process uploaded files, \ - but at most for the given number of seconds.", - ), - ) - .arg( - Arg::new("file-headers") - .long("file-header") - .short('H') - .value_name("KEY VALUE") - .action(ArgAction::Append) - .help("Store a header with this file."), - ) - .arg( - Arg::new("url_prefix") - .short('u') - .long("url-prefix") - .value_name("PREFIX") - .help("The URL prefix to prepend to all filenames."), - ) - .arg( - Arg::new("url_suffix") - .long("url-suffix") - .value_name("SUFFIX") - .help("The URL suffix to append to all filenames."), - ) - .arg( - Arg::new("ignore") - .long("ignore") - .short('i') - .value_name("IGNORE") - .action(ArgAction::Append) - .help("Ignores all files and folders matching the given glob"), - ) - .arg( - Arg::new("ignore_file") - .long("ignore-file") - .short('I') - .value_name("IGNORE_FILE") - .help( - "Ignore all files and folders specified in the given \ - ignore file, e.g. .gitignore.", - ), - ) - .arg( - Arg::new("extensions") - .long("ext") - .short('x') - .value_name("EXT") - .action(ArgAction::Append) - .help( - "Set the file extensions that are considered for upload. \ - This overrides the default extensions. To add an extension, all default \ - extensions must be repeated. Specify once per extension.", - ), - ) -} - -pub fn execute(matches: &ArgMatches) -> Result<()> { - let config = Config::current(); - let release = config.get_release_with_legacy_fallback(matches)?; - let org = config.get_org(matches)?; - let project = config.get_project(matches).ok(); - let api = Api::current(); - let authenticated_api = api.authenticated()?; - let chunk_upload_options = authenticated_api.get_chunk_upload_options(&org)?; - - let dist = matches.get_one::("dist").map(String::as_str); - let mut headers = BTreeMap::new(); - if let Some(header_list) = matches.get_many::("file-headers") { - for header in header_list { - if !header.contains(':') { - bail!("Invalid header. Needs to be in key:value format"); - } - let (key, value) = header.split_once(':').unwrap(); - headers.insert(key.trim().to_owned(), value.trim().to_owned()); - } - }; - - let wait_for_secs = matches.get_one::("wait_for").copied(); - let wait = matches.get_flag("wait") || wait_for_secs.is_some(); - let max_wait = wait_for_secs.map_or(DEFAULT_MAX_WAIT, Duration::from_secs); - - let context = &UploadContext { - org: &org, - projects: project.as_slice().try_into().ok(), - release: Some(&release), - dist, - note: None, - wait, - max_wait, - chunk_upload_options: &chunk_upload_options, - }; - - let path = Path::new(matches.get_one::("path").unwrap()); - // Batch files upload - if path.is_dir() { - let ignore_file = matches - .get_one::("ignore_file") - .map(String::as_str) - .unwrap_or_default(); - let ignores: Vec<_> = matches - .get_many::("ignore") - .map(|ignores| ignores.map(|i| format!("!{i}")).collect()) - .unwrap_or_default(); - let extensions: Vec<_> = matches - .get_many::("extensions") - .map(|extensions| extensions.map(|ext| ext.trim_start_matches('.')).collect()) - .unwrap_or_default(); - - let sources = ReleaseFileSearch::new(path.to_path_buf()) - .ignore_file(ignore_file) - .ignores(ignores) - .extensions(extensions) - .decompress(matches.get_flag("decompress")) - .collect_files()?; - - let url_suffix = matches - .get_one::("url_suffix") - .map(String::as_str) - .unwrap_or_default(); - let mut url_prefix = matches - .get_one::("url_prefix") - .map(String::as_str) - .unwrap_or("~"); - // remove a single slash from the end. so ~/ becomes ~ and app:/// becomes app:// - if url_prefix.ends_with('/') { - url_prefix = &url_prefix[..url_prefix.len() - 1]; - } - let files = sources - .iter() - .map(|source| { - let local_path = source.path.strip_prefix(&source.base_path).unwrap(); - let url = format!("{url_prefix}/{}{url_suffix}", path_as_url(local_path)); - - ( - url.clone(), - SourceFile { - url, - path: source.path.clone(), - contents: Arc::new(source.contents.clone()), - ty: SourceFileType::Source, - headers: headers.clone(), - messages: vec![], - already_uploaded: false, - }, - ) - }) - .collect(); - - FileUpload::new(context).files(&files).upload() - } - // Single file upload - else { - initialize_legacy_release_upload(context)?; - - let name = match matches.get_one::("name") { - Some(name) => name, - None => Path::new(path) - .file_name() - .and_then(OsStr::to_str) - .ok_or_else(|| format_err!("No filename provided."))?, - }; - - let mut f = fs::File::open(path)?; - let mut contents = Vec::new(); - f.read_to_end(&mut contents)?; - - if matches.get_flag("decompress") && is_gzip_compressed(&contents) { - contents = decompress_gzip_content(&contents).unwrap_or_else(|_| { - warn!("Could not decompress: {name}"); - contents - }); - } - - if let Some(artifact) = authenticated_api - .region_specific(context.org) - .upload_release_file( - &context.try_into()?, - &contents, - name, - Some( - headers - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect::>() - .as_slice(), - ), - ProgressBarMode::Request, - )? - { - println!("A {} ({} bytes)", artifact.sha1, artifact.size); - } else { - bail!("File already present!"); - } - Ok(()) - } -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 6b829a915a..330ea7383f 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -26,7 +26,6 @@ mod debug_files; mod deploys; mod derive_parser; mod events; -mod files; mod info; mod issues; mod login; @@ -56,7 +55,6 @@ macro_rules! each_subcommand { $mac!(debug_files); $mac!(deploys); $mac!(events); - $mac!(files); $mac!(info); $mac!(issues); $mac!(login); diff --git a/src/commands/releases/mod.rs b/src/commands/releases/mod.rs index 43a5fd844b..b4dd75fc34 100644 --- a/src/commands/releases/mod.rs +++ b/src/commands/releases/mod.rs @@ -42,13 +42,6 @@ pub fn make_command(mut command: Command) -> Command { .arg_required_else_help(true) .org_arg() .project_arg(true) - // Backward compatibility with `releases files ` commands. - .subcommand( - crate::commands::files::make_command(Command::new("files")) - .allow_hyphen_values(true) - .version_arg(true) - .hide(true), - ) // Backward compatibility with `releases deploys ` commands. .subcommand( crate::commands::deploys::make_command(Command::new("deploys")) @@ -73,10 +66,6 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { } each_subcommand!(execute_subcommand); - // To preserve backward compatibility - if let Some(sub_matches) = matches.subcommand_matches("files") { - return crate::commands::files::execute(sub_matches); - } // To preserve backward compatibility if let Some(sub_matches) = matches.subcommand_matches("deploys") { return crate::commands::deploys::execute(sub_matches); diff --git a/src/utils/progress.rs b/src/utils/progress.rs index 0293c7694d..3efd79b3f9 100644 --- a/src/utils/progress.rs +++ b/src/utils/progress.rs @@ -128,7 +128,6 @@ impl ProgressBar { #[derive(Clone)] pub enum ProgressBarMode { Disabled, - Request, #[cfg(not(feature = "managed"))] Response, Shared((Arc, u64, usize, Arc>>)), @@ -140,11 +139,6 @@ impl ProgressBarMode { !matches!(*self, ProgressBarMode::Disabled) } - /// Returns whether a progress bar should be displayed during upload. - pub fn request(&self) -> bool { - matches!(*self, ProgressBarMode::Request) - } - /// Returns whether a progress bar should be displayed during download. pub fn response(&self) -> bool { #[cfg(not(feature = "managed"))] diff --git a/tests/integration/_cases/releases/releases-files-upload-sourcemaps.trycmd b/tests/integration/_cases/releases/releases-files-upload-sourcemaps.trycmd deleted file mode 100644 index 74ec0c8372..0000000000 --- a/tests/integration/_cases/releases/releases-files-upload-sourcemaps.trycmd +++ /dev/null @@ -1,10 +0,0 @@ -``` -$ sentry-cli releases files wat-release upload-sourcemaps --url-prefix '~/assets' build/assets --rewrite -? success -⚠ DEPRECATION NOTICE: This functionality will be removed in a future version of `sentry-cli`. Use the `sourcemaps` command instead. -> Rewriting sources -> Rewriting completed in [..] -> Adding source map references -> Nothing to upload - -``` diff --git a/tests/integration/sourcemaps/upload.rs b/tests/integration/sourcemaps/upload.rs index f8abce16ac..f21aeaabbb 100644 --- a/tests/integration/sourcemaps/upload.rs +++ b/tests/integration/sourcemaps/upload.rs @@ -1,4 +1,4 @@ -use crate::integration::{ChunkOptions, MockEndpointBuilder, ServerBehavior, TestManager}; +use crate::integration::{ChunkOptions, ServerBehavior, TestManager}; #[test] fn command_sourcemaps_upload_help() { @@ -62,21 +62,6 @@ fn command_sourcemaps_upload_modern_v2() { .assert_mock_endpoints(); } -#[test] -fn command_sourcemaps_upload_empty() { - TestManager::new() - .mock_common_upload_endpoints(ServerBehavior::Legacy, Default::default()) - .mock_endpoint( - MockEndpointBuilder::new( - "GET", - "/api/0/projects/wat-org/wat-project/releases/wat-release/files/?cursor=", - ) - .with_response_body("[]"), - ) - .register_trycmd_test("releases/releases-files-upload-sourcemaps.trycmd") - .with_default_token(); -} - #[test] fn command_sourcemaps_upload_some_debugids() { TestManager::new()