posthog_cli/sourcemaps/hermes/
clone.rs

1use std::path::PathBuf;
2
3use anyhow::{anyhow, bail, Result};
4use tracing::{info, warn};
5
6use crate::{
7    invocation_context::context,
8    sourcemaps::{
9        content::SourceMapFile,
10        hermes::{get_composed_map, inject::is_metro_bundle},
11        inject::get_release_for_pairs,
12        source_pairs::read_pairs,
13    },
14};
15
16#[derive(clap::Args)]
17pub struct CloneArgs {
18    /// The directory containing the bundled chunks
19    #[arg(short, long)]
20    pub directory: PathBuf,
21
22    /// One or more directory glob patterns to ignore
23    #[arg(short, long)]
24    pub ignore: Vec<String>,
25
26    /// The project name associated with the uploaded chunks. Required to have the uploaded chunks associated with
27    /// a specific release. We will try to auto-derive this from git information if not provided. Strongly recommended
28    /// to be set explicitly during release CD workflows. Only necessary if no project was provided during injection.
29    #[arg(long)]
30    pub project: Option<String>,
31
32    /// The version of the project - this can be a version number, semantic version, or a git commit hash. Required
33    /// to have the uploaded chunks associated with a specific release.
34    #[arg(long)]
35    pub version: Option<String>,
36}
37
38pub fn clone(args: &CloneArgs) -> Result<()> {
39    context().capture_command_invoked("hermes_clone");
40
41    let CloneArgs {
42        directory,
43        ignore,
44        project,
45        version,
46    } = args;
47
48    let directory = directory.canonicalize().map_err(|e| {
49        anyhow!(
50            "Directory '{}' not found or inaccessible: {}",
51            directory.display(),
52            e
53        )
54    })?;
55
56    info!("Processing directory: {}", directory.display());
57    let pairs = read_pairs(&directory, ignore, is_metro_bundle, &None)?;
58
59    if pairs.is_empty() {
60        bail!("No source files found");
61    }
62
63    info!("Found {} pairs", pairs.len());
64
65    let release_id =
66        get_release_for_pairs(&directory, project, version, &pairs)?.map(|r| r.id.to_string());
67
68    // The flow here differs from plain sourcemap injection a bit - here, we don't ever
69    // overwrite the chunk ID, because at this point in the build process, we no longer
70    // control what chunk ID is inside the compiled hermes byte code bundle. So, instead,
71    // we permit e.g. uploading the same chunk ID's to two different posthog envs with two
72    // different release ID's, or arbitrarily re-running the upload command, but if someone
73    // tries to run `clone` twice, changing release but not posthog env, we'll error out. The
74    // correct way to upload the same set of artefacts to the same posthog env as part of
75    // two different releases is, 1, not to, but failing that, 2, to re-run the bundling process
76    let mut pairs = pairs;
77    for pair in &mut pairs {
78        if !pair.has_release_id() || pair.get_release_id() != release_id {
79            pair.set_release_id(release_id.clone());
80            pair.save()?;
81        }
82    }
83    let pairs = pairs;
84
85    let maps: Result<Vec<(&SourceMapFile, Option<SourceMapFile>)>> = pairs
86        .iter()
87        .map(|p| get_composed_map(p).map(|c| (&p.sourcemap, c)))
88        .collect();
89
90    let maps = maps?;
91
92    for (minified, composed) in maps {
93        let Some(mut composed) = composed else {
94            warn!(
95                "Could not find composed map for minified sourcemap {}",
96                minified.inner.path.display()
97            );
98            continue;
99        };
100
101        // Copy metadata from source map to composed map
102        if let Some(chunk_id) = minified.get_chunk_id() {
103            composed.set_chunk_id(Some(chunk_id));
104        }
105
106        if let Some(release_id) = minified.get_release_id() {
107            composed.set_release_id(Some(release_id));
108        }
109
110        composed.save()?;
111        info!(
112            "Successfully cloned metadata to {}",
113            composed.inner.path.display()
114        );
115    }
116
117    info!("Finished cloning metadata");
118    Ok(())
119}