posthog_cli/sourcemaps/
inject.rs

1use anyhow::{anyhow, bail, Ok, Result};
2use std::path::PathBuf;
3use tracing::info;
4use uuid;
5
6use crate::{
7    api::releases::ReleaseBuilder, invocation_context::context,
8    sourcemaps::source_pair::read_pairs, utils::git::get_git_info,
9};
10
11#[derive(clap::Args)]
12pub struct InjectArgs {
13    /// The directory containing the bundled chunks
14    #[arg(short, long)]
15    pub directory: PathBuf,
16
17    /// One or more directory glob patterns to ignore
18    #[arg(short, long)]
19    pub ignore: Vec<String>,
20
21    /// The project name associated with the uploaded chunks. Required to have the uploaded chunks associated with
22    /// a specific release. We will try to auto-derive this from git information if not provided. Strongly recommended
23    /// to be set explicitly during release CD workflows
24    #[arg(long)]
25    pub project: Option<String>,
26
27    /// The version of the project - this can be a version number, semantic version, or a git commit hash. Required
28    /// to have the uploaded chunks associated with a specific release. Overrides release information set during
29    /// injection. Strongly prefer setting release information during injection.
30    #[arg(long)]
31    pub version: Option<String>,
32}
33
34pub fn inject(args: &InjectArgs) -> Result<()> {
35    let InjectArgs {
36        directory,
37        ignore,
38        project,
39        version,
40    } = args;
41
42    context().capture_command_invoked("sourcemap_inject");
43
44    let directory = directory.canonicalize().map_err(|e| {
45        anyhow!(
46            "Directory '{}' not found or inaccessible: {}",
47            directory.display(),
48            e
49        )
50    })?;
51
52    info!("Processing directory: {}", directory.display());
53    let mut pairs = read_pairs(&directory, ignore)?;
54    if pairs.is_empty() {
55        bail!("No source files found");
56    }
57    info!("Found {} pairs", pairs.len());
58
59    // We need to fetch or create a release if: the user specified one, any pair is missing one, or the user
60    // forced release overriding
61    let needs_release =
62        project.is_some() || version.is_some() || pairs.iter().any(|p| !p.has_release_id());
63
64    let mut created_release = None;
65    if needs_release {
66        let mut builder = get_git_info(Some(directory))?
67            .map(|g| ReleaseBuilder::init_from_git(g))
68            .unwrap_or_default();
69
70        if let Some(project) = project {
71            builder.with_project(project);
72        }
73        if let Some(version) = version {
74            builder.with_version(version);
75        }
76
77        if builder.can_create() {
78            created_release = Some(builder.fetch_or_create()?);
79        }
80    }
81
82    let mut skipped_pairs = 0;
83    for pair in &mut pairs {
84        if pair.has_chunk_id() {
85            skipped_pairs += 1;
86            continue;
87        }
88        let chunk_id = uuid::Uuid::now_v7().to_string();
89        pair.set_chunk_id(chunk_id)?;
90
91        // If we've got a release, and the user asked us to, or a set is missing one,
92        // put the release ID on the pair
93        if created_release.is_some() && !pair.has_release_id() {
94            pair.set_release_id(created_release.as_ref().unwrap().id.to_string());
95        }
96    }
97    if skipped_pairs > 0 {
98        info!(
99            "Skipped {} pairs because chunk IDs already exist",
100            skipped_pairs
101        );
102    }
103
104    // Write the source and sourcemaps back to disk
105    for pair in &pairs {
106        pair.save()?;
107    }
108    info!("Finished processing directory");
109
110    Ok(())
111}