Files
posthog/cli/src/sourcemaps/inject.rs
2025-10-23 10:01:05 +00:00

124 lines
3.9 KiB
Rust

use anyhow::{anyhow, bail, Ok, Result};
use std::path::PathBuf;
use tracing::info;
use uuid;
use crate::{
api::releases::ReleaseBuilder,
invocation_context::context,
sourcemaps::source_pair::{read_pairs, SourcePair},
utils::git::get_git_info,
};
#[derive(clap::Args)]
pub struct InjectArgs {
/// The directory containing the bundled chunks
#[arg(short, long)]
pub directory: PathBuf,
/// If your bundler adds a public path prefix to sourcemap URLs,
/// we need to ignore it while searching for them
/// For use alongside e.g. esbuilds "publicPath" config setting.
#[arg(short, long)]
pub public_path_prefix: Option<String>,
/// One or more directory glob patterns to ignore
#[arg(short, long)]
pub ignore: Vec<String>,
/// The project name associated with the uploaded chunks. Required to have the uploaded chunks associated with
/// a specific release. We will try to auto-derive this from git information if not provided. Strongly recommended
/// to be set explicitly during release CD workflows
#[arg(long)]
pub project: Option<String>,
/// The version of the project - this can be a version number, semantic version, or a git commit hash. Required
/// to have the uploaded chunks associated with a specific release. Overrides release information set during
/// injection. Strongly prefer setting release information during injection.
#[arg(long)]
pub version: Option<String>,
}
pub fn inject(args: &InjectArgs) -> Result<()> {
let InjectArgs {
directory,
public_path_prefix,
ignore,
project,
version,
} = args;
context().capture_command_invoked("sourcemap_inject");
let directory = directory.canonicalize().map_err(|e| {
anyhow!(
"Directory '{}' not found or inaccessible: {}",
directory.display(),
e
)
})?;
info!("Processing directory: {}", directory.display());
let mut pairs = read_pairs(&directory, ignore, public_path_prefix)?;
if pairs.is_empty() {
bail!("No source files found");
}
info!("Found {} pairs", pairs.len());
// We need to fetch or create a release if: the user specified one, any pair is missing one, or the user
// forced release overriding
let needs_release =
project.is_some() || version.is_some() || pairs.iter().any(|p| !p.has_release_id());
let mut created_release = None;
if needs_release {
let mut builder = get_git_info(Some(directory))?
.map(ReleaseBuilder::init_from_git)
.unwrap_or_default();
if let Some(project) = project {
builder.with_project(project);
}
if let Some(version) = version {
builder.with_version(version);
}
if builder.can_create() {
created_release = Some(builder.fetch_or_create()?);
}
}
let created_release_id = created_release.as_ref().map(|r| r.id.to_string());
pairs = inject_pairs(pairs, created_release_id)?;
// Write the source and sourcemaps back to disk
for pair in &pairs {
pair.save()?;
}
info!("Finished processing directory");
Ok(())
}
pub fn inject_pairs(
mut pairs: Vec<SourcePair>,
created_release_id: Option<String>,
) -> Result<Vec<SourcePair>> {
for pair in &mut pairs {
let current_release_id = pair.get_release_id();
// We only update release ids and chunk ids when the release id changed or is not present
if current_release_id != created_release_id || pair.get_chunk_id().is_none() {
pair.set_release_id(created_release_id.clone());
let chunk_id = uuid::Uuid::now_v7().to_string();
if let Some(previous_chunk_id) = pair.get_chunk_id() {
pair.update_chunk_id(previous_chunk_id, chunk_id)?;
} else {
pair.add_chunk_id(chunk_id)?;
}
}
}
Ok(pairs)
}