diff --git a/CHANGELOG.md b/CHANGELOG.md index c3780c14b0..1ce3b4f655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Experimental Feature 🧑‍🔬 (internal-only) + +- Pipe snapshot sidecar metadata into upload as part of `sentry-cli build snapshots` command ([#3163](https://github.com/getsentry/sentry-cli/pull/3163)). + ## 3.3.0 ### New Features @@ -120,6 +126,7 @@ The following changes only apply when using `sentry-cli` via the npm package [`@ - Drop support for Node.js <18. The minimum required Node.js version is now 18.0.0 ([#2985](https://github.com/getsentry/sentry-cli/issues/2985)). - The type export `SentryCliReleases` has been removed. - The JavaScript wrapper now uses named exports instead of default exports ([#2989](https://github.com/getsentry/sentry-cli/pull/2989)). You need to update your imports: + ```js // Old (default import) const SentryCli = require('@sentry/cli'); @@ -129,6 +136,7 @@ The following changes only apply when using `sentry-cli` via the npm package [`@ ``` For ESM imports: + ```js // Old import SentryCli from '@sentry/cli'; @@ -137,7 +145,6 @@ The following changes only apply when using `sentry-cli` via the npm package [`@ import { SentryCli } from '@sentry/cli'; ``` - ### Improvements - The `sentry-cli upload-proguard` command now uses chunked uploading by default ([#2918](https://github.com/getsentry/sentry-cli/pull/2918)). Users who previously set the `SENTRY_EXPERIMENTAL_PROGUARD_CHUNK_UPLOAD` environment variable to opt into this behavior no longer need to set the variable. @@ -164,6 +171,7 @@ The following changes only apply when using `sentry-cli` via the npm package [`@ - The `sentry-cli build upload` command now automatically detects the correct branch or tag reference in non-PR GitHub Actions workflows ([#2976](https://github.com/getsentry/sentry-cli/pull/2976)). Previously, `--head-ref` was only auto-detected for pull request workflows. Now it works for push, release, and other workflow types by using the `GITHUB_REF_NAME` environment variable. ### Fixes + - Fixed a bug where the `sentry-cli sourcemaps inject` command could inject JavaScript code into certain incorrectly formatted source map files, corrupting their JSON structure ([#3003](https://github.com/getsentry/sentry-cli/pull/3003)). ## 2.58.2 diff --git a/src/api/data_types/snapshots.rs b/src/api/data_types/snapshots.rs index c4f8f0946c..ffce5f572a 100644 --- a/src/api/data_types/snapshots.rs +++ b/src/api/data_types/snapshots.rs @@ -3,6 +3,11 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; +use serde_json::Value; + +const IMAGE_FILE_NAME_FIELD: &str = "image_file_name"; +const WIDTH_FIELD: &str = "width"; +const HEIGHT_FIELD: &str = "height"; /// Response from the create snapshot endpoint. #[derive(Debug, Deserialize)] @@ -23,9 +28,59 @@ pub struct SnapshotsManifest { // Keep in sync with https://github.com/getsentry/sentry/blob/master/src/sentry/preprod/snapshots/manifest.py /// Metadata for a single image in a snapshot manifest. +/// +/// CLI-managed fields (`image_file_name`, `width`, `height`) override any +/// identically named fields provided by user sidecar metadata. #[derive(Debug, Serialize)] pub struct ImageMetadata { - pub image_file_name: String, - pub width: u32, - pub height: u32, + #[serde(flatten)] + data: HashMap, +} + +impl ImageMetadata { + pub fn new( + image_file_name: String, + width: u32, + height: u32, + mut extra: HashMap, + ) -> Self { + extra.insert( + IMAGE_FILE_NAME_FIELD.to_owned(), + Value::String(image_file_name), + ); + extra.insert(WIDTH_FIELD.to_owned(), Value::from(width)); + extra.insert(HEIGHT_FIELD.to_owned(), Value::from(height)); + + Self { data: extra } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use serde_json::json; + + #[test] + fn cli_managed_fields_override_sidecar_fields() { + let extra = serde_json::from_value(json!({ + (IMAGE_FILE_NAME_FIELD): "from-sidecar.png", + (WIDTH_FIELD): 1, + (HEIGHT_FIELD): 2, + "custom": "keep-me" + })) + .unwrap(); + + let metadata = ImageMetadata::new("from-cli.png".to_owned(), 100, 200, extra); + let serialized = serde_json::to_value(metadata).unwrap(); + + let expected = json!({ + (IMAGE_FILE_NAME_FIELD): "from-cli.png", + (WIDTH_FIELD): 100, + (HEIGHT_FIELD): 200, + "custom": "keep-me" + }); + + assert_eq!(serialized, expected); + } } diff --git a/src/commands/build/snapshots.rs b/src/commands/build/snapshots.rs index 626f2df53d..196ca51a27 100644 --- a/src/commands/build/snapshots.rs +++ b/src/commands/build/snapshots.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; -use std::fs; +use std::fs::{self, File}; +use std::io::BufReader; use std::path::{Path, PathBuf}; use std::str::FromStr as _; @@ -10,6 +11,7 @@ use itertools::Itertools as _; use log::{debug, info, warn}; use objectstore_client::{ClientBuilder, ExpirationPolicy, Usecase}; use secrecy::ExposeSecret as _; +use serde_json::Value; use sha2::{Digest as _, Sha256}; use walkdir::WalkDir; @@ -105,6 +107,7 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { style(images.len()).yellow(), if images.len() == 1 { "file" } else { "files" } ); + let manifest_entries = upload_images(images, &org, &project)?; // Build manifest from discovered images @@ -231,6 +234,29 @@ fn is_image_file(path: &Path) -> bool { .unwrap_or(false) } +/// Reads the companion JSON sidecar for an image, if it exists. +/// +/// For an image at `path/to/button.png`, looks for `path/to/button.json`. +/// Returns a map of all key-value pairs from the JSON file. +fn read_sidecar_metadata(image_path: &Path) -> Result> { + let sidecar_path = image_path.with_extension("json"); + if !sidecar_path.is_file() { + return Ok(HashMap::new()); + } + + debug!("Reading sidecar metadata: {}", sidecar_path.display()); + + let sidecar_file = File::open(&sidecar_path) + .with_context(|| format!("Failed to open sidecar file {}", sidecar_path.display()))?; + + serde_json::from_reader(BufReader::new(sidecar_file)).with_context(|| { + format!( + "Failed to read sidecar file {} as JSON", + sidecar_path.display() + ) + }) +} + fn upload_images( images: Vec, org: &str, @@ -290,13 +316,15 @@ fn upload_images( .unwrap_or_default() .to_string_lossy() .into_owned(); + + let extra = read_sidecar_metadata(&image.path).unwrap_or_else(|err| { + warn!("Error reading sidecar metadata, ignoring it instead: {err:#}"); + HashMap::new() + }); + manifest_entries.insert( hash, - ImageMetadata { - image_file_name, - width: image.width, - height: image.height, - }, + ImageMetadata::new(image_file_name, image.width, image.height, extra), ); }