-
-
Notifications
You must be signed in to change notification settings - Fork 222
/
inject.rs
156 lines (129 loc) · 5.25 KB
/
inject.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use std::fs::{self, File};
use std::io::{Seek, Write};
use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use clap::{Arg, ArgMatches, Command};
use glob::glob;
use log::{debug, warn};
use serde_json::Value;
use symbolic::debuginfo::js;
use uuid::Uuid;
const CODE_SNIPPET_TEMPLATE: &str = r#"!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="__SENTRY_DEBUG_ID__")}catch(e){}}()"#;
const DEBUGID_PLACEHOLDER: &str = "__SENTRY_DEBUG_ID__";
const SOURCEMAP_DEBUGID_KEY: &str = "debug_id";
const DEBUGID_COMMENT_PREFIX: &str = "//# debugId";
pub fn make_command(command: Command) -> Command {
command
.about("Fixes up JavaScript source files and sourcemaps with debug ids.")
// TODO: What are these {n}{n}s? They show up verbatim in the help output.
.long_about(
"Fixes up JavaScript source files and sourcemaps with debug ids.{n}{n}\
For every JS source file that references a sourcemap, a debug id is generated and \
inserted into both files. If the referenced sourcemap already contains a debug id, \
that id is used instead.",
)
.arg(
Arg::new("path")
.value_name("PATH")
.required(true)
.help("The path or glob to the javascript files."),
)
}
pub fn execute(matches: &ArgMatches) -> Result<()> {
let path = matches.value_of("path").unwrap();
let collected_paths: Vec<PathBuf> = glob(path)
.unwrap()
.flatten()
.filter(|path| path.extension().map_or(false, |ext| ext == "js"))
.collect();
if collected_paths.is_empty() {
warn!("Did not match any JavaScript files for pattern: {}", path);
return Ok(());
}
fixup_files(&collected_paths)
}
fn fixup_files(paths: &[PathBuf]) -> Result<()> {
'paths: for path in paths {
let js_path = path.as_path();
debug!("Processing js file {}", js_path.display());
let file =
fs::read_to_string(js_path).context(format!("Failed to open {}", js_path.display()))?;
if js::discover_debug_id(&file).is_some() {
debug!("File {} was previously processed", js_path.display());
continue 'paths;
}
let Some(sourcemap_url) = js::discover_sourcemaps_location(&file) else {
debug!("File {} does not contain a sourcemap url", js_path.display());
continue;
};
let sourcemap_path = js_path.with_file_name(sourcemap_url);
if !sourcemap_path.exists() {
warn!("Sourcemap file {} not found", sourcemap_path.display());
continue;
}
let debug_id = fixup_sourcemap(&sourcemap_path)
.context(format!("Failed to process {}", sourcemap_path.display()))?;
fixup_js_file(js_path, debug_id)
.context(format!("Failed to process {}", js_path.display()))?;
}
Ok(())
}
/// Appends the following text to a file:
/// ```
///
/// <CODE_SNIPPET>[<debug_id>]
/// //# sentryDebugId=<debug_id>
///```
/// where `<CODE_SNIPPET>[<debug_id>]`
/// is `CODE_SNIPPET_TEMPLATE` with `debug_id` substituted for the `__SENTRY_DEBUG_ID__`
/// placeholder.
fn fixup_js_file(js_path: &Path, debug_id: Uuid) -> Result<()> {
make_backup_copy(js_path)?;
let mut js_file = File::options().append(true).open(js_path)?;
let to_inject =
CODE_SNIPPET_TEMPLATE.replace(DEBUGID_PLACEHOLDER, &debug_id.hyphenated().to_string());
writeln!(js_file)?;
writeln!(js_file, "{to_inject}")?;
write!(js_file, "{DEBUGID_COMMENT_PREFIX}={debug_id}")?;
Ok(())
}
/// Fixes up a sourcemap file with a debug id.
///
/// If the file already contains a debug id under the `debugID` key, it is left unmodified.
/// Otherwise, a fresh debug id is inserted under that key.
///
/// In either case, the value of the `debugID` key is returned.
fn fixup_sourcemap(sourcemap_path: &Path) -> Result<Uuid> {
let mut sourcemap_file = File::options()
.read(true)
.write(true)
.open(sourcemap_path)?;
let mut sourcemap: Value = serde_json::from_reader(&sourcemap_file)?;
sourcemap_file.rewind()?;
let Some(map) = sourcemap.as_object_mut() else {
bail!("Invalid sourcemap");
};
match map.get(SOURCEMAP_DEBUGID_KEY) {
Some(id) => {
let debug_id = serde_json::from_value(id.clone())?;
debug!("Sourcemap already has a debug id");
Ok(debug_id)
}
None => {
make_backup_copy(sourcemap_path)?;
let debug_id = Uuid::new_v4();
let id = serde_json::to_value(debug_id)?;
map.insert(SOURCEMAP_DEBUGID_KEY.to_string(), id);
serde_json::to_writer(&mut sourcemap_file, &sourcemap)?;
Ok(debug_id)
}
}
}
// Makes a backup copy of a file that has `.bak` appended to the path.
fn make_backup_copy(path: &Path) -> Result<()> {
let mut file_name = path.file_name().unwrap_or_default().to_os_string();
file_name.push(".bak");
let backup_path = path.with_file_name(file_name);
std::fs::copy(path, backup_path).context("Failed to back up {path}")?;
Ok(())
}