@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 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
- The plugin makes no network calls, and only reads the variables above.
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
- //#region ../otel-utils/dist/service-version.mjs
5
- var ServiceVersionDetector = class {
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.1.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.4.0",
51
- "@monocle.sh/otel-utils": "1.7.0"
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"