@monocle.sh/vite-plugin 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -1
- package/dist/vite.d.mts +32 -1
- package/dist/vite.mjs +147 -298
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -4,6 +4,9 @@ Detects your release when Vite builds or serves your app, and hands it to
|
|
|
4
4
|
[`@monocle.sh/browser-sdk`](../browser-sdk). Monocle then sees each new version
|
|
5
5
|
of your front end as a deployment, without setting `serviceVersion` by hand.
|
|
6
6
|
|
|
7
|
+
After each production build, it also gives your bundles debug IDs and uploads
|
|
8
|
+
their source maps, so Monocle shows errors with your original code.
|
|
9
|
+
|
|
7
10
|
## Installation
|
|
8
11
|
|
|
9
12
|
```sh
|
|
@@ -40,7 +43,7 @@ Empty values and the placeholders `0.0.0`, `0.0.1`, `1.0.0`, `unknown`, `dev`,
|
|
|
40
43
|
and `latest` are skipped. When available, the commit SHA, repository URL
|
|
41
44
|
(credentials removed) and platform deployment ID are attached too.
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
Release detection makes no network calls, and only reads the variables above.
|
|
44
47
|
|
|
45
48
|
## Options
|
|
46
49
|
|
|
@@ -50,9 +53,18 @@ monocle({
|
|
|
50
53
|
inject: true, // expose the release to the Browser SDK
|
|
51
54
|
enabled: true, // turn the whole plugin off
|
|
52
55
|
verbose: false, // log the detected release and its source
|
|
56
|
+
sourcemaps: {
|
|
57
|
+
apiKey: process.env.MONOCLE_API_KEY, // default; without a key nothing is uploaded
|
|
58
|
+
apiUrl: 'https://api.monocle.sh',
|
|
59
|
+
release: 'v2.4.0', // shown next to the files, defaults to the detected release
|
|
60
|
+
deleteAfterUpload: true, // default when the plugin enabled source maps itself
|
|
61
|
+
failOnError: false, // log upload errors instead of failing the build
|
|
62
|
+
},
|
|
53
63
|
})
|
|
54
64
|
```
|
|
55
65
|
|
|
66
|
+
Pass `sourcemaps: false` to turn source map handling off.
|
|
67
|
+
|
|
56
68
|
A `serviceVersion` passed to `initMonocle()` wins over the injected release,
|
|
57
69
|
unless it is one of the placeholders above.
|
|
58
70
|
|
|
@@ -66,3 +78,32 @@ not apply to pre-bundled dependencies. It needs no inline `<script>`, so it
|
|
|
66
78
|
works with SSR frameworks that don't use `index.html` and with strict CSPs.
|
|
67
79
|
During SSR, the global is also set on the server, where the Browser SDK stays
|
|
68
80
|
inactive.
|
|
81
|
+
|
|
82
|
+
## Source maps
|
|
83
|
+
|
|
84
|
+
During `vite build`, the plugin:
|
|
85
|
+
|
|
86
|
+
1. Turns on `build.sourcemap: 'hidden'` if you haven't configured
|
|
87
|
+
`build.sourcemap` and an API key is set. Maps are generated without a
|
|
88
|
+
`sourceMappingURL` comment, so browsers never request them. An explicit
|
|
89
|
+
`build.sourcemap: false` disables the steps below. With
|
|
90
|
+
`build.sourcemap: true`, it logs a warning: the bundles reference their maps,
|
|
91
|
+
so browsers can download them.
|
|
92
|
+
2. Once the bundle is written, injects a debug ID into each JavaScript chunk
|
|
93
|
+
and its map with [`@monocle.sh/sourcemaps`](../sourcemaps). A one-line
|
|
94
|
+
snippet registers the ID, and the Browser SDK sends it with every error.
|
|
95
|
+
3. With an API key (`sourcemaps.apiKey` or `MONOCLE_API_KEY`), uploads each
|
|
96
|
+
chunk and its map. Files Monocle already has are skipped. Missing sources
|
|
97
|
+
are only embedded from source files inside the Vite `root`. Without a key, it
|
|
98
|
+
only injects the maps you configured and logs a hint.
|
|
99
|
+
4. Deletes the `.map` files if the plugin turned source maps on itself, even
|
|
100
|
+
when the upload fails, so they are never deployed. Set
|
|
101
|
+
`deleteAfterUpload: false` to keep them, or `true` to delete the maps you
|
|
102
|
+
configured after a successful upload.
|
|
103
|
+
|
|
104
|
+
Upload errors never fail the build unless you set `failOnError: true`: the
|
|
105
|
+
plugin logs them. Maps you configured are kept, so you can upload them later
|
|
106
|
+
with `monocle sourcemaps upload <directory>`. Watch builds are skipped.
|
|
107
|
+
|
|
108
|
+
Matching uses debug IDs, never file names or releases, so the same file
|
|
109
|
+
uploaded twice is stored once and redeploys stay resolvable.
|
package/dist/vite.d.mts
CHANGED
|
@@ -23,6 +23,36 @@ interface MonoclePluginOptions {
|
|
|
23
23
|
* Log the detected release and where it came from.
|
|
24
24
|
*/
|
|
25
25
|
verbose?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Give production bundles debug IDs and upload their source maps to Monocle, so stack traces
|
|
28
|
+
* show your original code. Enabled by default for builds; pass `false` to turn it off.
|
|
29
|
+
*/
|
|
30
|
+
sourcemaps?: false | MonocleSourcemapsOptions;
|
|
31
|
+
}
|
|
32
|
+
interface MonocleSourcemapsOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Monocle API key used to upload. Defaults to `MONOCLE_API_KEY`. Without a key, the plugin
|
|
35
|
+
* does not turn source maps on, and only injects debug IDs into maps you configured.
|
|
36
|
+
*/
|
|
37
|
+
apiKey?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Defaults to `https://api.monocle.sh`.
|
|
40
|
+
*/
|
|
41
|
+
apiUrl?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Release shown next to the uploaded files. Defaults to the detected release.
|
|
44
|
+
*/
|
|
45
|
+
release?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Source maps the plugin turned on itself (`build.sourcemap` not configured) are deleted
|
|
48
|
+
* once processed, even when the upload fails, unless this is `false`. Maps you configured are
|
|
49
|
+
* kept, unless this is `true`: they are then deleted after a successful upload.
|
|
50
|
+
*/
|
|
51
|
+
deleteAfterUpload?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Fail the build when injection or upload fails. Defaults to `false`: errors are logged.
|
|
54
|
+
*/
|
|
55
|
+
failOnError?: boolean;
|
|
26
56
|
}
|
|
27
57
|
//#endregion
|
|
28
58
|
//#region src/vite.d.ts
|
|
@@ -34,7 +64,8 @@ declare class MonocleVitePlugin {
|
|
|
34
64
|
}
|
|
35
65
|
/**
|
|
36
66
|
* Detect the release at build time and expose it to `@monocle.sh/browser-sdk`, in dev and build.
|
|
67
|
+
* After each build, give the bundles debug IDs and upload their source maps.
|
|
37
68
|
*/
|
|
38
69
|
declare function monocle(options?: MonoclePluginOptions): Plugin;
|
|
39
70
|
//#endregion
|
|
40
|
-
export { type MonoclePluginOptions, MonocleVitePlugin, monocle };
|
|
71
|
+
export { type MonoclePluginOptions, type MonocleSourcemapsOptions, MonocleVitePlugin, monocle };
|
package/dist/vite.mjs
CHANGED
|
@@ -1,303 +1,8 @@
|
|
|
1
|
-
import { readFileSync, statSync } from "node:fs";
|
|
2
1
|
import { dirname, join, resolve } from "node:path";
|
|
2
|
+
import { ServiceVersionDetector } from "@monocle.sh/otel-utils/service-version";
|
|
3
3
|
import MagicString from "magic-string";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Ordered platform revision variables, before CI and the local checkout.
|
|
8
|
-
*/
|
|
9
|
-
static #platformSources = [
|
|
10
|
-
["VERCEL_GIT_COMMIT_SHA", "platform:vercel"],
|
|
11
|
-
["RENDER_GIT_COMMIT", "platform:render"],
|
|
12
|
-
["RAILWAY_GIT_COMMIT_SHA", "platform:railway"],
|
|
13
|
-
["HEROKU_BUILD_COMMIT", "platform:heroku"],
|
|
14
|
-
["HEROKU_SLUG_COMMIT", "platform:heroku"],
|
|
15
|
-
["SOURCE_COMMIT", "platform:coolify"],
|
|
16
|
-
["GIT_REV", "platform:dokku"],
|
|
17
|
-
["K_REVISION", "platform:cloud-run"]
|
|
18
|
-
];
|
|
19
|
-
/**
|
|
20
|
-
* CI detection adapted from env-ci 11.2.0 (MIT): https://github.com/semantic-release/env-ci
|
|
21
|
-
* See LICENSE.env-ci. Git subprocess and Java properties adapters are excluded.
|
|
22
|
-
*/
|
|
23
|
-
static #ciSources = {
|
|
24
|
-
github: {
|
|
25
|
-
detect: (env) => Boolean(env.GITHUB_ACTIONS),
|
|
26
|
-
revisions: ["GITHUB_SHA"],
|
|
27
|
-
repository: (env) => this.#repositoryFromSlug({
|
|
28
|
-
host: env.GITHUB_SERVER_URL,
|
|
29
|
-
slug: env.GITHUB_REPOSITORY
|
|
30
|
-
})
|
|
31
|
-
},
|
|
32
|
-
gitlab: {
|
|
33
|
-
detect: (env) => Boolean(env.GITLAB_CI),
|
|
34
|
-
revisions: ["CI_COMMIT_SHA"],
|
|
35
|
-
repository: (env) => this.#normalizeRepositoryUrl(env.CI_PROJECT_URL) ?? this.#repositoryFromSlug({
|
|
36
|
-
host: env.CI_SERVER_URL,
|
|
37
|
-
slug: env.CI_PROJECT_PATH
|
|
38
|
-
})
|
|
39
|
-
},
|
|
40
|
-
woodpecker: {
|
|
41
|
-
detect: (env) => env.CI === "woodpecker",
|
|
42
|
-
revisions: ["CI_COMMIT_SHA"],
|
|
43
|
-
repository: (env) => env.CI_REPO_CLONE_URL
|
|
44
|
-
},
|
|
45
|
-
circleci: {
|
|
46
|
-
detect: (env) => Boolean(env.CIRCLECI),
|
|
47
|
-
revisions: ["CIRCLE_SHA1"],
|
|
48
|
-
repository: (env) => env.CIRCLE_REPOSITORY_URL
|
|
49
|
-
},
|
|
50
|
-
bitbucket: {
|
|
51
|
-
detect: (env) => Boolean(env.BITBUCKET_BUILD_NUMBER),
|
|
52
|
-
revisions: ["BITBUCKET_COMMIT"],
|
|
53
|
-
repository: (env) => this.#repositoryFromSlug({
|
|
54
|
-
host: "https://bitbucket.org",
|
|
55
|
-
slug: env.BITBUCKET_REPO_FULL_NAME
|
|
56
|
-
})
|
|
57
|
-
},
|
|
58
|
-
buildkite: {
|
|
59
|
-
detect: (env) => Boolean(env.BUILDKITE),
|
|
60
|
-
revisions: ["BUILDKITE_COMMIT"],
|
|
61
|
-
repository: (env) => env.BUILDKITE_REPO
|
|
62
|
-
},
|
|
63
|
-
jenkins: {
|
|
64
|
-
detect: (env) => Boolean(env.JENKINS_URL),
|
|
65
|
-
revisions: ["ghprbActualCommit", "GIT_COMMIT"],
|
|
66
|
-
repository: (env) => env.GIT_URL
|
|
67
|
-
},
|
|
68
|
-
"azure-pipelines": {
|
|
69
|
-
detect: (env) => Boolean(env.BUILD_BUILDURI),
|
|
70
|
-
revisions: ["BUILD_SOURCEVERSION"],
|
|
71
|
-
repository: (env) => env.BUILD_REPOSITORY_URI
|
|
72
|
-
},
|
|
73
|
-
travis: {
|
|
74
|
-
detect: (env) => Boolean(env.TRAVIS),
|
|
75
|
-
revisions: ["TRAVIS_COMMIT"]
|
|
76
|
-
},
|
|
77
|
-
netlify: {
|
|
78
|
-
detect: (env) => env.NETLIFY === "true",
|
|
79
|
-
revisions: ["COMMIT_REF"],
|
|
80
|
-
repository: (env) => env.REPOSITORY_URL
|
|
81
|
-
},
|
|
82
|
-
"cloudflare-pages": {
|
|
83
|
-
detect: (env) => env.CF_PAGES === "1",
|
|
84
|
-
revisions: ["CF_PAGES_COMMIT_SHA"]
|
|
85
|
-
},
|
|
86
|
-
drone: {
|
|
87
|
-
detect: (env) => Boolean(env.DRONE),
|
|
88
|
-
revisions: ["DRONE_COMMIT_SHA"],
|
|
89
|
-
repository: (env) => env.DRONE_REMOTE_URL
|
|
90
|
-
},
|
|
91
|
-
bitrise: {
|
|
92
|
-
detect: (env) => Boolean(env.BITRISE_IO),
|
|
93
|
-
revisions: ["BITRISE_GIT_COMMIT"]
|
|
94
|
-
},
|
|
95
|
-
appveyor: {
|
|
96
|
-
detect: (env) => Boolean(env.APPVEYOR),
|
|
97
|
-
revisions: ["APPVEYOR_REPO_COMMIT"]
|
|
98
|
-
},
|
|
99
|
-
semaphore: {
|
|
100
|
-
detect: (env) => Boolean(env.SEMAPHORE),
|
|
101
|
-
revisions: ["SEMAPHORE_GIT_SHA"]
|
|
102
|
-
},
|
|
103
|
-
codefresh: {
|
|
104
|
-
detect: (env) => Boolean(env.CF_BUILD_ID),
|
|
105
|
-
revisions: ["CF_REVISION"]
|
|
106
|
-
},
|
|
107
|
-
buddy: {
|
|
108
|
-
detect: (env) => Boolean(env.BUDDY_WORKSPACE_ID),
|
|
109
|
-
revisions: ["BUDDY_EXECUTION_REVISION"]
|
|
110
|
-
},
|
|
111
|
-
cirrus: {
|
|
112
|
-
detect: (env) => Boolean(env.CIRRUS_CI),
|
|
113
|
-
revisions: ["CIRRUS_CHANGE_IN_REPO"]
|
|
114
|
-
},
|
|
115
|
-
vela: {
|
|
116
|
-
detect: (env) => Boolean(env.VELA),
|
|
117
|
-
revisions: ["VELA_BUILD_COMMIT"]
|
|
118
|
-
},
|
|
119
|
-
screwdriver: {
|
|
120
|
-
detect: (env) => Boolean(env.SCREWDRIVER),
|
|
121
|
-
revisions: ["SD_BUILD_SHA"]
|
|
122
|
-
},
|
|
123
|
-
bamboo: {
|
|
124
|
-
detect: (env) => Boolean(env.bamboo_agentId),
|
|
125
|
-
revisions: ["bamboo_planRepository_1_revision"]
|
|
126
|
-
},
|
|
127
|
-
codeship: {
|
|
128
|
-
detect: (env) => env.CI_NAME === "codeship",
|
|
129
|
-
revisions: ["CI_COMMIT_ID"]
|
|
130
|
-
},
|
|
131
|
-
"jetbrains-space": {
|
|
132
|
-
detect: (env) => Boolean(env.JB_SPACE_EXECUTION_NUMBER),
|
|
133
|
-
revisions: ["JB_SPACE_GIT_REVISION"]
|
|
134
|
-
},
|
|
135
|
-
scrutinizer: {
|
|
136
|
-
detect: (env) => Boolean(env.SCRUTINIZER),
|
|
137
|
-
revisions: ["SCRUTINIZER_SHA1"]
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
static #value(value) {
|
|
141
|
-
const trimmed = value?.trim();
|
|
142
|
-
if (!trimmed || [
|
|
143
|
-
"0.0.0",
|
|
144
|
-
"0.0.1",
|
|
145
|
-
"1.0.0",
|
|
146
|
-
"unknown",
|
|
147
|
-
"dev",
|
|
148
|
-
"latest"
|
|
149
|
-
].includes(trimmed.toLowerCase())) return;
|
|
150
|
-
return trimmed;
|
|
151
|
-
}
|
|
152
|
-
static #sha(value) {
|
|
153
|
-
if (value && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i.test(value)) return value;
|
|
154
|
-
}
|
|
155
|
-
static #read(path) {
|
|
156
|
-
try {
|
|
157
|
-
return readFileSync(path, "utf8").trim();
|
|
158
|
-
} catch {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
static #gitDirectory(cwd) {
|
|
163
|
-
let directory = resolve(cwd);
|
|
164
|
-
while (true) {
|
|
165
|
-
const path = join(directory, ".git");
|
|
166
|
-
try {
|
|
167
|
-
if (statSync(path).isDirectory()) return path;
|
|
168
|
-
const target = this.#read(path)?.match(/^gitdir:\s*(.+)$/)?.[1];
|
|
169
|
-
return target ? resolve(directory, target) : void 0;
|
|
170
|
-
} catch {
|
|
171
|
-
const parent = dirname(directory);
|
|
172
|
-
if (parent === directory) return;
|
|
173
|
-
directory = parent;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
static #gitRevision(cwd) {
|
|
178
|
-
const gitDirectory = this.#gitDirectory(cwd);
|
|
179
|
-
if (!gitDirectory) return;
|
|
180
|
-
const head = this.#read(join(gitDirectory, "HEAD"));
|
|
181
|
-
const detached = this.#sha(head);
|
|
182
|
-
if (detached) return detached;
|
|
183
|
-
const ref = head?.match(/^ref:\s*(refs\/[^\s]+)$/)?.[1];
|
|
184
|
-
if (!ref || ref.split("/").includes("..")) return;
|
|
185
|
-
const common = this.#read(join(gitDirectory, "commondir"));
|
|
186
|
-
const directories = common ? [gitDirectory, resolve(gitDirectory, common)] : [gitDirectory];
|
|
187
|
-
for (const directory of directories) {
|
|
188
|
-
const direct = this.#sha(this.#read(join(directory, ref)));
|
|
189
|
-
if (direct) return direct;
|
|
190
|
-
const packed = this.#read(join(directory, "packed-refs"));
|
|
191
|
-
for (const line of packed?.split("\n") ?? []) {
|
|
192
|
-
const [sha, name] = line.trim().split(/\s+/);
|
|
193
|
-
if (name === ref && this.#sha(sha)) return sha;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
static #githubRevision(env) {
|
|
198
|
-
const fallback = this.#value(env.GITHUB_SHA);
|
|
199
|
-
if (env.GITHUB_EVENT_NAME !== "pull_request") return fallback;
|
|
200
|
-
if (!env.GITHUB_EVENT_PATH) return fallback;
|
|
201
|
-
try {
|
|
202
|
-
const sha = JSON.parse(this.#read(env.GITHUB_EVENT_PATH) ?? "")?.pull_request?.head?.sha;
|
|
203
|
-
return typeof sha === "string" ? this.#sha(sha) ?? fallback : fallback;
|
|
204
|
-
} catch {
|
|
205
|
-
return fallback;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
static #repositoryFromSlug(options) {
|
|
209
|
-
const host = this.#normalizeRepositoryUrl(options.host);
|
|
210
|
-
const slug = this.#value(options.slug);
|
|
211
|
-
if (!host || !slug) return;
|
|
212
|
-
return `${host}/${slug.replace(/^\/+/, "")}`;
|
|
213
|
-
}
|
|
214
|
-
static #vercelRepository(env) {
|
|
215
|
-
const hosts = new Map([
|
|
216
|
-
["github", "https://github.com"],
|
|
217
|
-
["gitlab", "https://gitlab.com"],
|
|
218
|
-
["bitbucket", "https://bitbucket.org"]
|
|
219
|
-
]);
|
|
220
|
-
const owner = this.#value(env.VERCEL_GIT_REPO_OWNER);
|
|
221
|
-
const slug = this.#value(env.VERCEL_GIT_REPO_SLUG);
|
|
222
|
-
if (!owner || !slug) return;
|
|
223
|
-
return this.#repositoryFromSlug({
|
|
224
|
-
host: hosts.get(env.VERCEL_GIT_PROVIDER ?? ""),
|
|
225
|
-
slug: `${owner}/${slug}`
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
static #repositoryUrl(env) {
|
|
229
|
-
return [
|
|
230
|
-
env.VERCEL || env.NOW_GITHUB_DEPLOYMENT ? this.#vercelRepository(env) : void 0,
|
|
231
|
-
env.RENDER ? env.RENDER_GIT_REPO_URL : void 0,
|
|
232
|
-
env.RAILWAY_PROJECT_ID ? env.RAILWAY_GIT_REPO_URL : void 0,
|
|
233
|
-
...Object.values(this.#ciSources).filter((provider) => provider.detect(env)).map((provider) => provider.repository?.(env))
|
|
234
|
-
].map((candidate) => this.#normalizeRepositoryUrl(candidate)).find(Boolean);
|
|
235
|
-
}
|
|
236
|
-
static #normalizeRepositoryUrl(candidate) {
|
|
237
|
-
const value = this.#value(candidate);
|
|
238
|
-
if (!value) return;
|
|
239
|
-
try {
|
|
240
|
-
const url = new URL(value.replace(/^git@([^:/]+):/, "ssh://git@$1/"));
|
|
241
|
-
if (!url.host || ![
|
|
242
|
-
"https:",
|
|
243
|
-
"http:",
|
|
244
|
-
"ssh:"
|
|
245
|
-
].includes(url.protocol)) return;
|
|
246
|
-
const path = url.pathname.replace(/\/+$/, "").replace(/\.git$/, "").replace(/\/+$/, "");
|
|
247
|
-
return `https://${url.protocol === "ssh:" ? url.hostname : url.host}${path}`;
|
|
248
|
-
} catch {
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
static detect(options = {}) {
|
|
253
|
-
const env = options.env ?? process.env;
|
|
254
|
-
const candidates = [
|
|
255
|
-
{
|
|
256
|
-
version: this.#value(options.serviceVersion),
|
|
257
|
-
source: "config"
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
version: this.#value(env.MONOCLE_VERSION),
|
|
261
|
-
source: "env"
|
|
262
|
-
},
|
|
263
|
-
{
|
|
264
|
-
version: this.#value(env.APP_VERSION),
|
|
265
|
-
source: "env"
|
|
266
|
-
},
|
|
267
|
-
...this.#platformSources.map(([key, source]) => ({
|
|
268
|
-
version: this.#value(env[key]),
|
|
269
|
-
source
|
|
270
|
-
})),
|
|
271
|
-
...Object.entries(this.#ciSources).filter(([, provider]) => provider.detect(env)).flatMap(([name, provider]) => provider.revisions.map((key) => ({
|
|
272
|
-
version: name === "github" ? this.#githubRevision(env) : this.#value(env[key]),
|
|
273
|
-
source: `ci:${name}`
|
|
274
|
-
})))
|
|
275
|
-
];
|
|
276
|
-
const selected = candidates.find(({ version }) => version);
|
|
277
|
-
const knownSha = candidates.map(({ version }) => this.#sha(version)).find(Boolean);
|
|
278
|
-
const gitSha = knownSha ? void 0 : this.#gitRevision(options.cwd ?? process.cwd());
|
|
279
|
-
return {
|
|
280
|
-
version: selected?.version ?? gitSha,
|
|
281
|
-
source: selected?.source ?? (gitSha ? "git" : "none"),
|
|
282
|
-
commitSha: knownSha ?? gitSha,
|
|
283
|
-
repositoryUrl: this.#repositoryUrl(env),
|
|
284
|
-
deploymentId: [
|
|
285
|
-
env.RAILWAY_DEPLOYMENT_ID,
|
|
286
|
-
env.VERCEL_DEPLOYMENT_ID,
|
|
287
|
-
env.RENDER_DEPLOY_ID
|
|
288
|
-
].map((value) => this.#value(value)).find(Boolean)
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
static resourceAttributes(result) {
|
|
292
|
-
const attributes = { "monocle.service.version.source": result.source };
|
|
293
|
-
if (result.version) attributes["service.version"] = result.version;
|
|
294
|
-
if (result.commitSha) attributes["vcs.ref.head.revision"] = result.commitSha;
|
|
295
|
-
if (result.repositoryUrl) attributes["vcs.repository.url.full"] = result.repositoryUrl;
|
|
296
|
-
if (result.deploymentId) attributes["deployment.id"] = result.deploymentId;
|
|
297
|
-
return attributes;
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
//#endregion
|
|
4
|
+
import { rm } from "node:fs/promises";
|
|
5
|
+
import { injectDebugIds, uploadSourcemaps } from "@monocle.sh/sourcemaps";
|
|
301
6
|
//#region src/core/build_metadata.ts
|
|
302
7
|
/**
|
|
303
8
|
* Resolve the release with the same priority chain as the server agents, without spawning Git
|
|
@@ -419,6 +124,110 @@ var MonocleBuild = class {
|
|
|
419
124
|
}
|
|
420
125
|
};
|
|
421
126
|
//#endregion
|
|
127
|
+
//#region src/core/sourcemaps_step.ts
|
|
128
|
+
/**
|
|
129
|
+
* Inject debug IDs into the written chunks, then upload their source maps when an API key is
|
|
130
|
+
* available. Bundler-agnostic: adapters call `sourcemapDefault` while configuring and `run` once
|
|
131
|
+
* the output is on disk.
|
|
132
|
+
*/
|
|
133
|
+
var SourcemapsStep = class {
|
|
134
|
+
#options;
|
|
135
|
+
#enabledHiddenMaps = false;
|
|
136
|
+
#missingApiKey = false;
|
|
137
|
+
#processed = /* @__PURE__ */ new Set();
|
|
138
|
+
constructor(options) {
|
|
139
|
+
this.#options = options;
|
|
140
|
+
}
|
|
141
|
+
get #settings() {
|
|
142
|
+
return this.#options.options.sourcemaps || {};
|
|
143
|
+
}
|
|
144
|
+
get #apiKey() {
|
|
145
|
+
return this.#settings.apiKey ?? this.#options.env?.MONOCLE_API_KEY;
|
|
146
|
+
}
|
|
147
|
+
get enabled() {
|
|
148
|
+
return this.#options.options.enabled !== false && this.#options.options.sourcemaps !== false;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Turn on hidden source maps when the user did not configure them and an API key can upload
|
|
152
|
+
* them: the maps are generated without a `sourceMappingURL` comment pointing browsers to
|
|
153
|
+
* them, and deleted once processed.
|
|
154
|
+
*/
|
|
155
|
+
sourcemapDefault(configured) {
|
|
156
|
+
if (!this.enabled || configured !== void 0) return;
|
|
157
|
+
if (!this.#apiKey) {
|
|
158
|
+
this.#missingApiKey = true;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
this.#enabledHiddenMaps = true;
|
|
162
|
+
return "hidden";
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Explain the resolved build configuration: why no source map is generated, or that the
|
|
166
|
+
* configured ones are referenced by the bundles, so browsers download them.
|
|
167
|
+
*/
|
|
168
|
+
checkConfig(options) {
|
|
169
|
+
if (this.#missingApiKey) options.logger.info("[monocle] Set MONOCLE_API_KEY or sourcemaps.apiKey to generate and upload source maps.");
|
|
170
|
+
if (this.enabled && options.sourcemap === true) options.logger.warn("[monocle] build.sourcemap is true: browsers can download your source maps. Use 'hidden' to upload them without serving them.");
|
|
171
|
+
}
|
|
172
|
+
async #deleteSourcemaps(options) {
|
|
173
|
+
const count = (await Promise.all(options.paths.map((path) => rm(path).then(() => true, () => false)))).filter(Boolean).length;
|
|
174
|
+
if (count > 0) options.logger.info(`[monocle] Removed ${count} source map(s) from the output.`);
|
|
175
|
+
}
|
|
176
|
+
async #process(options) {
|
|
177
|
+
const { logger } = options;
|
|
178
|
+
const settings = this.#settings;
|
|
179
|
+
const { files } = await injectDebugIds({
|
|
180
|
+
directory: options.directory,
|
|
181
|
+
files: options.files
|
|
182
|
+
});
|
|
183
|
+
for (const file of files) if (file.status === "invalid_sourcemap") logger.warn(`[monocle] ${file.error}`);
|
|
184
|
+
const ready = files.filter((file) => file.debugId && file.sourcemap);
|
|
185
|
+
if (ready.length === 0) return;
|
|
186
|
+
const apiKey = this.#apiKey;
|
|
187
|
+
if (!apiKey) {
|
|
188
|
+
logger.info(`[monocle] Debug IDs injected into ${ready.length} file(s). Set MONOCLE_API_KEY or sourcemaps.apiKey to upload source maps.`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const result = await uploadSourcemaps({
|
|
192
|
+
directory: options.directory,
|
|
193
|
+
files: ready.map((file) => file.file),
|
|
194
|
+
apiKey,
|
|
195
|
+
apiUrl: settings.apiUrl,
|
|
196
|
+
release: settings.release ?? options.release,
|
|
197
|
+
root: options.root,
|
|
198
|
+
logger: {
|
|
199
|
+
info: () => {},
|
|
200
|
+
warn: (message) => logger.warn(`[monocle] ${message}`)
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
logger.info(`[monocle] Source maps: ${result.uploaded.length} uploaded, ${result.skipped.length} already uploaded.`);
|
|
204
|
+
if (!this.#enabledHiddenMaps && settings.deleteAfterUpload) await this.#deleteSourcemaps({
|
|
205
|
+
paths: ready.map((file) => file.sourcemap),
|
|
206
|
+
logger
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Process one output directory once. Errors are logged unless `failOnError` is set. The maps
|
|
211
|
+
* the plugin enabled are deleted even when the upload fails, so they are never deployed.
|
|
212
|
+
*/
|
|
213
|
+
async run(options) {
|
|
214
|
+
if (!this.enabled || this.#processed.has(options.directory)) return;
|
|
215
|
+
this.#processed.add(options.directory);
|
|
216
|
+
try {
|
|
217
|
+
await this.#process(options);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
const message = `[monocle] Could not process source maps: ${error.message}`;
|
|
220
|
+
if (this.#settings.failOnError) throw new Error(message, { cause: error });
|
|
221
|
+
options.logger.error(message);
|
|
222
|
+
} finally {
|
|
223
|
+
if (this.#enabledHiddenMaps && this.#settings.deleteAfterUpload !== false) await this.#deleteSourcemaps({
|
|
224
|
+
paths: options.files.map((file) => `${file}.map`),
|
|
225
|
+
logger: options.logger
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
//#endregion
|
|
422
231
|
//#region src/vite.ts
|
|
423
232
|
/**
|
|
424
233
|
* Translate Vite hooks to the bundler-agnostic Monocle build.
|
|
@@ -428,6 +237,11 @@ var MonocleVitePlugin = class {
|
|
|
428
237
|
const plugin = { name: "@monocle.sh/vite-plugin" };
|
|
429
238
|
if (options.enabled === false) return plugin;
|
|
430
239
|
let build;
|
|
240
|
+
let resolved;
|
|
241
|
+
const sourcemaps = new SourcemapsStep({
|
|
242
|
+
options,
|
|
243
|
+
env: process.env
|
|
244
|
+
});
|
|
431
245
|
return {
|
|
432
246
|
...plugin,
|
|
433
247
|
/**
|
|
@@ -435,7 +249,17 @@ var MonocleVitePlugin = class {
|
|
|
435
249
|
* JavaScript instead of a raw component, wherever `monocle()` sits in `plugins`.
|
|
436
250
|
*/
|
|
437
251
|
enforce: "post",
|
|
252
|
+
config(config, env) {
|
|
253
|
+
if (env.command !== "build") return;
|
|
254
|
+
const sourcemap = sourcemaps.sourcemapDefault(config.build?.sourcemap);
|
|
255
|
+
if (sourcemap) return { build: { sourcemap } };
|
|
256
|
+
},
|
|
438
257
|
configResolved(config) {
|
|
258
|
+
resolved = config;
|
|
259
|
+
if (config.command === "build") sourcemaps.checkConfig({
|
|
260
|
+
sourcemap: config.build.sourcemap,
|
|
261
|
+
logger: config.logger
|
|
262
|
+
});
|
|
439
263
|
build = new MonocleBuild({
|
|
440
264
|
options,
|
|
441
265
|
cwd: config.root,
|
|
@@ -460,12 +284,37 @@ var MonocleVitePlugin = class {
|
|
|
460
284
|
id
|
|
461
285
|
});
|
|
462
286
|
}
|
|
287
|
+
},
|
|
288
|
+
/**
|
|
289
|
+
* Sequential and last, so compression or copy plugins see the injected files.
|
|
290
|
+
*/
|
|
291
|
+
writeBundle: {
|
|
292
|
+
sequential: true,
|
|
293
|
+
order: "post",
|
|
294
|
+
async handler(output, bundle) {
|
|
295
|
+
if (!resolved || resolved.command !== "build" || resolved.build.watch) return;
|
|
296
|
+
if (!resolved.build.sourcemap) return;
|
|
297
|
+
const directory = resolve(resolved.root, output.dir ?? dirname(output.file ?? "."));
|
|
298
|
+
const files = Object.values(bundle).filter((item) => item.type === "chunk").map((chunk) => join(directory, chunk.fileName));
|
|
299
|
+
try {
|
|
300
|
+
await sourcemaps.run({
|
|
301
|
+
directory,
|
|
302
|
+
files,
|
|
303
|
+
root: resolved.root,
|
|
304
|
+
release: build?.resolve()?.version,
|
|
305
|
+
logger: resolved.logger
|
|
306
|
+
});
|
|
307
|
+
} catch (error) {
|
|
308
|
+
this.error(error);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
463
311
|
}
|
|
464
312
|
};
|
|
465
313
|
}
|
|
466
314
|
};
|
|
467
315
|
/**
|
|
468
316
|
* Detect the release at build time and expose it to `@monocle.sh/browser-sdk`, in dev and build.
|
|
317
|
+
* After each build, give the bundles debug IDs and upload their source maps.
|
|
469
318
|
*/
|
|
470
319
|
function monocle(options = {}) {
|
|
471
320
|
return MonocleVitePlugin.create(options);
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monocle.sh/vite-plugin",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Vite plugin that injects your release into the Monocle Browser SDK at build time",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Vite plugin that injects your release into the Monocle Browser SDK and uploads source maps at build time",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"monocle",
|
|
7
7
|
"observability",
|
|
8
8
|
"opentelemetry",
|
|
9
9
|
"release",
|
|
10
|
+
"sourcemaps",
|
|
10
11
|
"vite",
|
|
11
12
|
"vite-plugin"
|
|
12
13
|
],
|
|
@@ -37,7 +38,8 @@
|
|
|
37
38
|
"tag": "latest"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
|
-
"magic-string": "^0.30.21"
|
|
41
|
+
"magic-string": "^0.30.21",
|
|
42
|
+
"@monocle.sh/sourcemaps": "^0.1.0"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
45
|
"@playwright/test": "^1.63.0",
|
|
@@ -47,8 +49,8 @@
|
|
|
47
49
|
"tsdown": "^0.21.10",
|
|
48
50
|
"vite": "^8.3.4",
|
|
49
51
|
"vue": "^3.5.44",
|
|
50
|
-
"@monocle.sh/browser-sdk": "0.
|
|
51
|
-
"@monocle.sh/otel-utils": "1.
|
|
52
|
+
"@monocle.sh/browser-sdk": "0.12.0",
|
|
53
|
+
"@monocle.sh/otel-utils": "1.9.0"
|
|
52
54
|
},
|
|
53
55
|
"peerDependencies": {
|
|
54
56
|
"vite": ">=5.0.0"
|