fix(err): don't assume 1:1 mapping between chunk and sourcemap (#40065)

This commit is contained in:
Oliver Browne
2025-10-22 13:47:02 +03:00
committed by GitHub
parent 5992ae8091
commit 5541efc4e4
7 changed files with 72 additions and 62 deletions

View File

@@ -1,5 +1,10 @@
# posthog-cli
# 0.5.2
- Fixes a bug where chunks which shared a sourcemap were mishandled, leading to an error during upload in recent versions, and a silent
failure in older versions. If you're using next, and saw an error message about "duplicate chunk IDs", this fix addresses that issue.
# 0.5.1
- Attempts to reduce impact of previous breaking changes - re-adds `--project` and `--version` arguments to sourcemap upload command, marking them as no longer used

71
cli/Cargo.lock generated
View File

@@ -169,9 +169,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.4"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "bitvec"
@@ -275,9 +275,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.49"
version = "4.5.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f"
checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623"
dependencies = [
"clap_builder",
"clap_derive",
@@ -285,9 +285,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.49"
version = "4.5.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730"
checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0"
dependencies = [
"anstream",
"anstyle",
@@ -405,7 +405,7 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
dependencies = [
"bitflags 2.9.4",
"bitflags 2.10.0",
"crossterm_winapi",
"mio 1.0.4",
"parking_lot",
@@ -951,7 +951,7 @@ dependencies = [
"http 1.3.1",
"hyper 1.7.0",
"hyper-util",
"rustls 0.23.33",
"rustls 0.23.34",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.4",
@@ -1128,9 +1128,9 @@ checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb"
[[package]]
name = "indexmap"
version = "2.11.4"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
dependencies = [
"equivalent",
"hashbrown 0.16.0",
@@ -1138,9 +1138,12 @@ dependencies = [
[[package]]
name = "indoc"
version = "2.0.6"
version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
dependencies = [
"rustversion",
]
[[package]]
name = "inquire"
@@ -1148,7 +1151,7 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a"
dependencies = [
"bitflags 2.9.4",
"bitflags 2.10.0",
"crossterm 0.25.0",
"dyn-clone",
"fuzzy-matcher",
@@ -1196,9 +1199,9 @@ checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itertools"
@@ -1243,7 +1246,7 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [
"bitflags 2.9.4",
"bitflags 2.10.0",
"libc",
]
@@ -1446,9 +1449,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "option-ext"
@@ -1517,7 +1520,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "posthog-cli"
version = "0.5.1"
version = "0.5.2"
dependencies = [
"anyhow",
"chrono",
@@ -1611,7 +1614,7 @@ dependencies = [
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls 0.23.33",
"rustls 0.23.34",
"socket2 0.6.1",
"thiserror 2.0.17",
"tokio",
@@ -1631,7 +1634,7 @@ dependencies = [
"rand",
"ring",
"rustc-hash",
"rustls 0.23.33",
"rustls 0.23.34",
"rustls-pki-types",
"slab",
"thiserror 2.0.17",
@@ -1710,7 +1713,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
dependencies = [
"bitflags 2.9.4",
"bitflags 2.10.0",
"cassowary",
"compact_str",
"crossterm 0.28.1",
@@ -1751,7 +1754,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.9.4",
"bitflags 2.10.0",
]
[[package]]
@@ -1858,7 +1861,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls 0.23.33",
"rustls 0.23.34",
"rustls-pki-types",
"serde",
"serde_json",
@@ -1908,7 +1911,7 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags 2.9.4",
"bitflags 2.10.0",
"errno",
"libc",
"linux-raw-sys 0.4.15",
@@ -1921,7 +1924,7 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [
"bitflags 2.9.4",
"bitflags 2.10.0",
"errno",
"libc",
"linux-raw-sys 0.11.0",
@@ -1942,9 +1945,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.33"
version = "0.23.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c"
checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7"
dependencies = [
"once_cell",
"ring",
@@ -2268,9 +2271,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
[[package]]
name = "syn"
version = "2.0.106"
version = "2.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b"
dependencies = [
"proc-macro2",
"quote",
@@ -2476,7 +2479,7 @@ version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls 0.23.33",
"rustls 0.23.34",
"tokio",
]
@@ -2514,7 +2517,7 @@ version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [
"bitflags 2.9.4",
"bitflags 2.10.0",
"bytes",
"futures-util",
"http 1.3.1",
@@ -2636,9 +2639,9 @@ checksum = "81b79ad29b5e19de4260020f8919b443b2ef0277d242ce532ec7b7a2cc8b6007"
[[package]]
name = "unicode-ident"
version = "1.0.19"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
[[package]]
name = "unicode-linebreak"

View File

@@ -1,6 +1,6 @@
[package]
name = "posthog-cli"
version = "0.5.1"
version = "0.5.2"
authors = [
"David <david@posthog.com>",
"Olly <oliver@posthog.com>",

View File

@@ -189,7 +189,7 @@ impl ReleaseBuilder {
Ok(response)
} else {
let e = response.text()?;
Err(anyhow::anyhow!("Failed to create release: {}", e))
Err(anyhow::anyhow!("Failed to create release: {e}"))
}
}
}

View File

@@ -66,7 +66,7 @@ pub fn upload(input_sets: &[SymbolSetUpload], batch_size: usize) -> Result<()> {
let start_response = start_upload(batch)?;
let id_map: HashMap<_, _> = batch
.into_iter()
.iter()
.map(|u| (u.chunk_id.as_str(), u))
.collect();
@@ -151,7 +151,7 @@ fn upload_to_s3(presigned_url: PresignedUrl, data: &[u8]) -> Result<()> {
}
}
Result::Err(e) => {
last_err = Some(anyhow!("Failed to upload chunk: {}", e));
last_err = Some(anyhow!("Failed to upload chunk: {e}"));
}
}
if attempt < 3 {

View File

@@ -64,7 +64,7 @@ pub fn inject(args: &InjectArgs) -> Result<()> {
let mut created_release = None;
if needs_release {
let mut builder = get_git_info(Some(directory))?
.map(|g| ReleaseBuilder::init_from_git(g))
.map(ReleaseBuilder::init_from_git)
.unwrap_or_default();
if let Some(project) = project {

View File

@@ -17,7 +17,9 @@ use walkdir::WalkDir;
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct SourceMapContent {
#[serde(skip_serializing_if = "Option::is_none")]
pub release_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chunk_id: Option<String>,
#[serde(flatten)]
pub fields: BTreeMap<String, Value>,
@@ -38,18 +40,14 @@ pub struct SourcePair {
}
impl SourcePair {
pub fn new(source: MinifiedSourceFile, sourcemap: SourceMapFile) -> Result<Self> {
if sourcemap.get_chunk_id() != source.get_chunk_id() {
anyhow::bail!(
"Source chunk ID and sourcemap chunk ID disagree. Try re-running injection"
)
}
Ok(Self { source, sourcemap })
pub fn has_chunk_id(&self) -> bool {
// Minified chunks are the source of truth for their ID's, not sourcemaps,
// because sometimes sourcemaps are shared across multiple chunks.
self.source.get_chunk_id().is_some()
}
pub fn has_chunk_id(&self) -> bool {
self.sourcemap.get_chunk_id().is_some()
pub fn get_chunk_id(&self) -> Option<String> {
self.source.get_chunk_id()
}
pub fn has_release_id(&self) -> bool {
@@ -62,8 +60,13 @@ impl SourcePair {
}
let adjustment = self.source.set_chunk_id(&chunk_id)?;
self.sourcemap.apply_adjustment(adjustment)?;
self.sourcemap.set_chunk_id(chunk_id);
// In cases where sourcemaps are shared across multiple chunks,
// we should only apply the adjustment if the sourcemap doesn't
// have a chunk ID set (since otherwise, it's already been adjusted)
if self.sourcemap.get_chunk_id().is_none() {
self.sourcemap.apply_adjustment(adjustment)?;
self.sourcemap.set_chunk_id(chunk_id);
}
Ok(())
}
@@ -131,7 +134,6 @@ impl TryInto<SymbolSetUpload> for SourcePair {
fn try_into(self) -> Result<SymbolSetUpload> {
let chunk_id = self
.sourcemap
.get_chunk_id()
.ok_or_else(|| anyhow!("Chunk ID not found"))?;
let source_content = self.source.inner.content;
@@ -174,12 +176,12 @@ impl SourceMapFile {
let new_content = {
let content = serde_json::to_string(&self.inner.content)?.into_bytes();
let mut original_sourcemap = match sourcemap::decode_slice(content.as_slice())
.map_err(|err| anyhow!("Failed to parse sourcemap: {}", err))?
.map_err(|err| anyhow!("Failed to parse sourcemap: {err}"))?
{
sourcemap::DecodedMap::Regular(map) => map,
sourcemap::DecodedMap::Index(index_map) => index_map
.flatten()
.map_err(|err| anyhow!("Failed to parse sourcemap: {}", err))?,
.map_err(|err| anyhow!("Failed to parse sourcemap: {err}"))?,
sourcemap::DecodedMap::Hermes(_) => {
// TODO(olly) - YES THEY ARE!!!!! WOOOOOOO!!!!! YIPEEEEEEEE!!!
anyhow::bail!("Hermes source maps are not supported")
@@ -224,7 +226,7 @@ impl MinifiedSourceFile {
}
pub fn get_chunk_id(&self) -> Option<String> {
let patterns = ["//#chunk_id"];
let patterns = ["//# chunkId="];
self.get_comment_value(&patterns)
}
@@ -233,27 +235,27 @@ impl MinifiedSourceFile {
// Update source content with chunk ID
let source_content = &self.inner.content;
let mut magic_source = MagicString::new(source_content);
let code_snippet = CODE_SNIPPET_TEMPLATE.replace(CHUNKID_PLACEHOLDER, &chunk_id);
let code_snippet = CODE_SNIPPET_TEMPLATE.replace(CHUNKID_PLACEHOLDER, chunk_id);
magic_source
.prepend(&code_snippet)
.map_err(|err| anyhow!("Failed to prepend code snippet: {}", err))?;
let chunk_comment = CHUNKID_COMMENT_PREFIX.replace(CHUNKID_PLACEHOLDER, &chunk_id);
.map_err(|err| anyhow!("Failed to prepend code snippet: {err}"))?;
let chunk_comment = CHUNKID_COMMENT_PREFIX.replace(CHUNKID_PLACEHOLDER, chunk_id);
magic_source
.append(&chunk_comment)
.map_err(|err| anyhow!("Failed to append chunk comment: {}", err))?;
.map_err(|err| anyhow!("Failed to append chunk comment: {err}"))?;
let adjustment = magic_source
.generate_map(GenerateDecodedMapOptions {
include_content: true,
..Default::default()
})
.map_err(|err| anyhow!("Failed to generate source map: {}", err))?;
.map_err(|err| anyhow!("Failed to generate source map: {err}"))?;
let adjustment_sourcemap = SourceMap::from_slice(
adjustment
.to_string()
.map_err(|err| anyhow!("Failed to serialize source map: {}", err))?
.map_err(|err| anyhow!("Failed to serialize source map: {err}"))?
.as_bytes(),
)
.map_err(|err| anyhow!("Failed to parse adjustment sourcemap: {}", err))?;
.map_err(|err| anyhow!("Failed to parse adjustment sourcemap: {err}"))?;
(magic_source.to_string(), adjustment_sourcemap)
};