Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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');
Expand All @@ -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';
Expand All @@ -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.
Expand All @@ -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
Expand Down
61 changes: 58 additions & 3 deletions src/api/data_types/snapshots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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<String, Value>,
}

impl ImageMetadata {
pub fn new(
image_file_name: String,
width: u32,
height: u32,
mut extra: HashMap<String, Value>,
) -> 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);
}
}
40 changes: 34 additions & 6 deletions src/commands/build/snapshots.rs
Original file line number Diff line number Diff line change
@@ -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 _;

Expand All @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<HashMap<String, Value>> {
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<ImageInfo>,
org: &str,
Expand Down Expand Up @@ -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),
);
}

Expand Down