@monocle.sh/vite-plugin 0.2.0 → 0.3.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
@@ -70,14 +70,28 @@ unless it is one of the placeholders above.
70
70
 
71
71
  ## How it works
72
72
 
73
- The plugin prepends `globalThis.__MONOCLE_BUILD__ = {…}` to each of your modules
74
- that imports `@monocle.sh/browser-sdk`. The SDK reads it in `initMonocle()`.
75
-
76
- This targets application code rather than relying on `define`, which Vite does
77
- not apply to pre-bundled dependencies. It needs no inline `<script>`, so it
78
- works with SSR frameworks that don't use `index.html` and with strict CSPs.
79
- During SSR, the global is also set on the server, where the Browser SDK stays
80
- inactive.
73
+ When every entry of `vite build` is an HTML page (the default `index.html`
74
+ setup), the plugin writes the release into the page as
75
+ `<meta name="monocle-build" content="{…}">`, which the SDK reads in
76
+ `initMonocle()`. Your JavaScript chunks never contain the release, so two builds
77
+ of the same code produce identical chunks, file names and debug IDs: a deploy
78
+ only uploads the source maps of the chunks that changed. A `<meta>` tag is not
79
+ affected by Content Security Policies. This needs `@monocle.sh/browser-sdk`
80
+ 0.13.0 or later; with an older SDK the plugin warns and falls back to the
81
+ bundle.
82
+
83
+ Otherwise (dev server, SSR, library builds, frameworks rendering the HTML
84
+ themselves), the plugin prepends `globalThis.__MONOCLE_BUILD__ = {…}` to each of
85
+ your modules that imports `@monocle.sh/browser-sdk`. This targets application
86
+ code rather than relying on `define`, which Vite does not apply to pre-bundled
87
+ dependencies. During SSR, the global is also set on the server, where the
88
+ Browser SDK stays inactive.
89
+
90
+ Vite and Rolldown also put the hashed file name of every imported chunk into
91
+ the importing chunk, so changing one chunk renames the chunks that import it,
92
+ up to the entry. To keep those unchanged too, enable Vite's
93
+ [`build.chunkImportMap`](https://vite.dev/config/build-options) (Vite 8,
94
+ experimental).
81
95
 
82
96
  ## Source maps
83
97
 
package/dist/vite.d.mts CHANGED
@@ -60,6 +60,7 @@ interface MonocleSourcemapsOptions {
60
60
  * Translate Vite hooks to the bundler-agnostic Monocle build.
61
61
  */
62
62
  declare class MonocleVitePlugin {
63
+ #private;
63
64
  static create(options?: MonoclePluginOptions): Plugin;
64
65
  }
65
66
  /**
package/dist/vite.mjs CHANGED
@@ -1,8 +1,348 @@
1
- import { dirname, join, resolve } from "node:path";
2
- import { ServiceVersionDetector } from "@monocle.sh/otel-utils/service-version";
1
+ import { createRequire } from "node:module";
2
+ import { dirname, join, resolve, sep } from "node:path";
3
+ import { readFileSync, realpathSync, statSync } from "node:fs";
3
4
  import MagicString from "magic-string";
4
5
  import { rm } from "node:fs/promises";
5
6
  import { injectDebugIds, uploadSourcemaps } from "@monocle.sh/sourcemaps";
7
+ //#region src/core/browser_sdk.ts
8
+ /**
9
+ * What the Browser SDK the application resolves can read.
10
+ */
11
+ var BrowserSdk = class {
12
+ /**
13
+ * First `@monocle.sh/browser-sdk` reading `<meta name="monocle-build">`.
14
+ */
15
+ static pageReleaseSince = "0.13.0";
16
+ static #atLeast(version, minimum) {
17
+ const parse = (value) => value.split(/[-+]/)[0].split(".").map(Number);
18
+ const [actual, expected] = [parse(version), parse(minimum)];
19
+ for (let index = 0; index < 3; index++) {
20
+ const difference = (actual[index] ?? 0) - (expected[index] ?? 0);
21
+ if (Number.isNaN(difference)) return false;
22
+ if (difference !== 0) return difference > 0;
23
+ }
24
+ return true;
25
+ }
26
+ /**
27
+ * Where the installed SDK reads the release: `page` from the HTML, `bundle` only from the
28
+ * global, `missing` when the application does not resolve it, so nothing reads a page tag. A
29
+ * linked local copy (workspace) is assumed current; one that cannot be inspected (Yarn PnP
30
+ * archives…) reads the bundle.
31
+ */
32
+ static releaseSource(root) {
33
+ let path;
34
+ try {
35
+ path = createRequire(join(root, "package.json")).resolve("@monocle.sh/browser-sdk/package.json");
36
+ } catch {
37
+ return "missing";
38
+ }
39
+ try {
40
+ if (!realpathSync(path).includes(`${sep}node_modules${sep}`)) return "page";
41
+ const { version } = JSON.parse(readFileSync(path, "utf8"));
42
+ return typeof version === "string" && this.#atLeast(version, this.pageReleaseSince) ? "page" : "bundle";
43
+ } catch {
44
+ return "bundle";
45
+ }
46
+ }
47
+ };
48
+ //#endregion
49
+ //#region ../otel-utils/dist/service-version.mjs
50
+ var ServiceVersionDetector = class {
51
+ /**
52
+ * Ordered platform revision variables, before CI and the local checkout.
53
+ */
54
+ static #platformSources = [
55
+ ["VERCEL_GIT_COMMIT_SHA", "platform:vercel"],
56
+ ["RENDER_GIT_COMMIT", "platform:render"],
57
+ ["RAILWAY_GIT_COMMIT_SHA", "platform:railway"],
58
+ ["HEROKU_BUILD_COMMIT", "platform:heroku"],
59
+ ["HEROKU_SLUG_COMMIT", "platform:heroku"],
60
+ ["SOURCE_COMMIT", "platform:coolify"],
61
+ ["GIT_REV", "platform:dokku"],
62
+ ["K_REVISION", "platform:cloud-run"]
63
+ ];
64
+ /**
65
+ * CI detection adapted from env-ci 11.2.0 (MIT): https://github.com/semantic-release/env-ci
66
+ * See LICENSE.env-ci. Git subprocess and Java properties adapters are excluded.
67
+ */
68
+ static #ciSources = {
69
+ github: {
70
+ detect: (env) => Boolean(env.GITHUB_ACTIONS),
71
+ revisions: ["GITHUB_SHA"],
72
+ repository: (env) => this.#repositoryFromSlug({
73
+ host: env.GITHUB_SERVER_URL,
74
+ slug: env.GITHUB_REPOSITORY
75
+ })
76
+ },
77
+ gitlab: {
78
+ detect: (env) => Boolean(env.GITLAB_CI),
79
+ revisions: ["CI_COMMIT_SHA"],
80
+ repository: (env) => this.#normalizeRepositoryUrl(env.CI_PROJECT_URL) ?? this.#repositoryFromSlug({
81
+ host: env.CI_SERVER_URL,
82
+ slug: env.CI_PROJECT_PATH
83
+ })
84
+ },
85
+ woodpecker: {
86
+ detect: (env) => env.CI === "woodpecker",
87
+ revisions: ["CI_COMMIT_SHA"],
88
+ repository: (env) => env.CI_REPO_CLONE_URL
89
+ },
90
+ circleci: {
91
+ detect: (env) => Boolean(env.CIRCLECI),
92
+ revisions: ["CIRCLE_SHA1"],
93
+ repository: (env) => env.CIRCLE_REPOSITORY_URL
94
+ },
95
+ bitbucket: {
96
+ detect: (env) => Boolean(env.BITBUCKET_BUILD_NUMBER),
97
+ revisions: ["BITBUCKET_COMMIT"],
98
+ repository: (env) => this.#repositoryFromSlug({
99
+ host: "https://bitbucket.org",
100
+ slug: env.BITBUCKET_REPO_FULL_NAME
101
+ })
102
+ },
103
+ buildkite: {
104
+ detect: (env) => Boolean(env.BUILDKITE),
105
+ revisions: ["BUILDKITE_COMMIT"],
106
+ repository: (env) => env.BUILDKITE_REPO
107
+ },
108
+ jenkins: {
109
+ detect: (env) => Boolean(env.JENKINS_URL),
110
+ revisions: ["ghprbActualCommit", "GIT_COMMIT"],
111
+ repository: (env) => env.GIT_URL
112
+ },
113
+ "azure-pipelines": {
114
+ detect: (env) => Boolean(env.BUILD_BUILDURI),
115
+ revisions: ["BUILD_SOURCEVERSION"],
116
+ repository: (env) => env.BUILD_REPOSITORY_URI
117
+ },
118
+ travis: {
119
+ detect: (env) => Boolean(env.TRAVIS),
120
+ revisions: ["TRAVIS_COMMIT"]
121
+ },
122
+ netlify: {
123
+ detect: (env) => env.NETLIFY === "true",
124
+ revisions: ["COMMIT_REF"],
125
+ repository: (env) => env.REPOSITORY_URL
126
+ },
127
+ "cloudflare-pages": {
128
+ detect: (env) => env.CF_PAGES === "1",
129
+ revisions: ["CF_PAGES_COMMIT_SHA"]
130
+ },
131
+ drone: {
132
+ detect: (env) => Boolean(env.DRONE),
133
+ revisions: ["DRONE_COMMIT_SHA"],
134
+ repository: (env) => env.DRONE_REMOTE_URL
135
+ },
136
+ bitrise: {
137
+ detect: (env) => Boolean(env.BITRISE_IO),
138
+ revisions: ["BITRISE_GIT_COMMIT"]
139
+ },
140
+ appveyor: {
141
+ detect: (env) => Boolean(env.APPVEYOR),
142
+ revisions: ["APPVEYOR_REPO_COMMIT"]
143
+ },
144
+ semaphore: {
145
+ detect: (env) => Boolean(env.SEMAPHORE),
146
+ revisions: ["SEMAPHORE_GIT_SHA"]
147
+ },
148
+ codefresh: {
149
+ detect: (env) => Boolean(env.CF_BUILD_ID),
150
+ revisions: ["CF_REVISION"]
151
+ },
152
+ buddy: {
153
+ detect: (env) => Boolean(env.BUDDY_WORKSPACE_ID),
154
+ revisions: ["BUDDY_EXECUTION_REVISION"]
155
+ },
156
+ cirrus: {
157
+ detect: (env) => Boolean(env.CIRRUS_CI),
158
+ revisions: ["CIRRUS_CHANGE_IN_REPO"]
159
+ },
160
+ vela: {
161
+ detect: (env) => Boolean(env.VELA),
162
+ revisions: ["VELA_BUILD_COMMIT"]
163
+ },
164
+ screwdriver: {
165
+ detect: (env) => Boolean(env.SCREWDRIVER),
166
+ revisions: ["SD_BUILD_SHA"]
167
+ },
168
+ bamboo: {
169
+ detect: (env) => Boolean(env.bamboo_agentId),
170
+ revisions: ["bamboo_planRepository_1_revision"]
171
+ },
172
+ codeship: {
173
+ detect: (env) => env.CI_NAME === "codeship",
174
+ revisions: ["CI_COMMIT_ID"]
175
+ },
176
+ "jetbrains-space": {
177
+ detect: (env) => Boolean(env.JB_SPACE_EXECUTION_NUMBER),
178
+ revisions: ["JB_SPACE_GIT_REVISION"]
179
+ },
180
+ scrutinizer: {
181
+ detect: (env) => Boolean(env.SCRUTINIZER),
182
+ revisions: ["SCRUTINIZER_SHA1"]
183
+ }
184
+ };
185
+ static #value(value) {
186
+ const trimmed = value?.trim();
187
+ if (!trimmed || [
188
+ "0.0.0",
189
+ "0.0.1",
190
+ "1.0.0",
191
+ "unknown",
192
+ "dev",
193
+ "latest"
194
+ ].includes(trimmed.toLowerCase())) return;
195
+ return trimmed;
196
+ }
197
+ static #sha(value) {
198
+ if (value && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i.test(value)) return value;
199
+ }
200
+ static #read(path) {
201
+ try {
202
+ return readFileSync(path, "utf8").trim();
203
+ } catch {
204
+ return;
205
+ }
206
+ }
207
+ static #gitDirectory(cwd) {
208
+ let directory = resolve(cwd);
209
+ while (true) {
210
+ const path = join(directory, ".git");
211
+ try {
212
+ if (statSync(path).isDirectory()) return path;
213
+ const target = this.#read(path)?.match(/^gitdir:\s*(.+)$/)?.[1];
214
+ return target ? resolve(directory, target) : void 0;
215
+ } catch {
216
+ const parent = dirname(directory);
217
+ if (parent === directory) return;
218
+ directory = parent;
219
+ }
220
+ }
221
+ }
222
+ static #gitRevision(cwd) {
223
+ const gitDirectory = this.#gitDirectory(cwd);
224
+ if (!gitDirectory) return;
225
+ const head = this.#read(join(gitDirectory, "HEAD"));
226
+ const detached = this.#sha(head);
227
+ if (detached) return detached;
228
+ const ref = head?.match(/^ref:\s*(refs\/[^\s]+)$/)?.[1];
229
+ if (!ref || ref.split("/").includes("..")) return;
230
+ const common = this.#read(join(gitDirectory, "commondir"));
231
+ const directories = common ? [gitDirectory, resolve(gitDirectory, common)] : [gitDirectory];
232
+ for (const directory of directories) {
233
+ const direct = this.#sha(this.#read(join(directory, ref)));
234
+ if (direct) return direct;
235
+ const packed = this.#read(join(directory, "packed-refs"));
236
+ for (const line of packed?.split("\n") ?? []) {
237
+ const [sha, name] = line.trim().split(/\s+/);
238
+ if (name === ref && this.#sha(sha)) return sha;
239
+ }
240
+ }
241
+ }
242
+ static #githubRevision(env) {
243
+ const fallback = this.#value(env.GITHUB_SHA);
244
+ if (env.GITHUB_EVENT_NAME !== "pull_request") return fallback;
245
+ if (!env.GITHUB_EVENT_PATH) return fallback;
246
+ try {
247
+ const sha = JSON.parse(this.#read(env.GITHUB_EVENT_PATH) ?? "")?.pull_request?.head?.sha;
248
+ return typeof sha === "string" ? this.#sha(sha) ?? fallback : fallback;
249
+ } catch {
250
+ return fallback;
251
+ }
252
+ }
253
+ static #repositoryFromSlug(options) {
254
+ const host = this.#normalizeRepositoryUrl(options.host);
255
+ const slug = this.#value(options.slug);
256
+ if (!host || !slug) return;
257
+ return `${host}/${slug.replace(/^\/+/, "")}`;
258
+ }
259
+ static #vercelRepository(env) {
260
+ const hosts = new Map([
261
+ ["github", "https://github.com"],
262
+ ["gitlab", "https://gitlab.com"],
263
+ ["bitbucket", "https://bitbucket.org"]
264
+ ]);
265
+ const owner = this.#value(env.VERCEL_GIT_REPO_OWNER);
266
+ const slug = this.#value(env.VERCEL_GIT_REPO_SLUG);
267
+ if (!owner || !slug) return;
268
+ return this.#repositoryFromSlug({
269
+ host: hosts.get(env.VERCEL_GIT_PROVIDER ?? ""),
270
+ slug: `${owner}/${slug}`
271
+ });
272
+ }
273
+ static #repositoryUrl(env) {
274
+ return [
275
+ env.VERCEL || env.NOW_GITHUB_DEPLOYMENT ? this.#vercelRepository(env) : void 0,
276
+ env.RENDER ? env.RENDER_GIT_REPO_URL : void 0,
277
+ env.RAILWAY_PROJECT_ID ? env.RAILWAY_GIT_REPO_URL : void 0,
278
+ ...Object.values(this.#ciSources).filter((provider) => provider.detect(env)).map((provider) => provider.repository?.(env))
279
+ ].map((candidate) => this.#normalizeRepositoryUrl(candidate)).find(Boolean);
280
+ }
281
+ static #normalizeRepositoryUrl(candidate) {
282
+ const value = this.#value(candidate);
283
+ if (!value) return;
284
+ try {
285
+ const url = new URL(value.replace(/^git@([^:/]+):/, "ssh://git@$1/"));
286
+ if (!url.host || ![
287
+ "https:",
288
+ "http:",
289
+ "ssh:"
290
+ ].includes(url.protocol)) return;
291
+ const path = url.pathname.replace(/\/+$/, "").replace(/\.git$/, "").replace(/\/+$/, "");
292
+ return `https://${url.protocol === "ssh:" ? url.hostname : url.host}${path}`;
293
+ } catch {
294
+ return;
295
+ }
296
+ }
297
+ static detect(options = {}) {
298
+ const env = options.env ?? process.env;
299
+ const candidates = [
300
+ {
301
+ version: this.#value(options.serviceVersion),
302
+ source: "config"
303
+ },
304
+ {
305
+ version: this.#value(env.MONOCLE_VERSION),
306
+ source: "env"
307
+ },
308
+ {
309
+ version: this.#value(env.APP_VERSION),
310
+ source: "env"
311
+ },
312
+ ...this.#platformSources.map(([key, source]) => ({
313
+ version: this.#value(env[key]),
314
+ source
315
+ })),
316
+ ...Object.entries(this.#ciSources).filter(([, provider]) => provider.detect(env)).flatMap(([name, provider]) => provider.revisions.map((key) => ({
317
+ version: name === "github" ? this.#githubRevision(env) : this.#value(env[key]),
318
+ source: `ci:${name}`
319
+ })))
320
+ ];
321
+ const selected = candidates.find(({ version }) => version);
322
+ const knownSha = candidates.map(({ version }) => this.#sha(version)).find(Boolean);
323
+ const gitSha = knownSha ? void 0 : this.#gitRevision(options.cwd ?? process.cwd());
324
+ return {
325
+ version: selected?.version ?? gitSha,
326
+ source: selected?.source ?? (gitSha ? "git" : "none"),
327
+ commitSha: knownSha ?? gitSha,
328
+ repositoryUrl: this.#repositoryUrl(env),
329
+ deploymentId: [
330
+ env.RAILWAY_DEPLOYMENT_ID,
331
+ env.VERCEL_DEPLOYMENT_ID,
332
+ env.RENDER_DEPLOY_ID
333
+ ].map((value) => this.#value(value)).find(Boolean)
334
+ };
335
+ }
336
+ static resourceAttributes(result) {
337
+ const attributes = { "monocle.service.version.source": result.source };
338
+ if (result.version) attributes["service.version"] = result.version;
339
+ if (result.commitSha) attributes["vcs.ref.head.revision"] = result.commitSha;
340
+ if (result.repositoryUrl) attributes["vcs.repository.url.full"] = result.repositoryUrl;
341
+ if (result.deploymentId) attributes["deployment.id"] = result.deploymentId;
342
+ return attributes;
343
+ }
344
+ };
345
+ //#endregion
6
346
  //#region src/core/build_metadata.ts
7
347
  /**
8
348
  * Resolve the release with the same priority chain as the server agents, without spawning Git
@@ -30,7 +370,10 @@ var BuildMetadataResolver = class {
30
370
  //#endregion
31
371
  //#region src/core/release_injection.ts
32
372
  /**
33
- * Prepend a global assignment to every module importing the Browser SDK.
373
+ * Expose the release to the Browser SDK. Builds with an HTML page write it into a
374
+ * `<meta name="monocle-build">` tag: JavaScript chunks then stay byte-identical between builds of
375
+ * the same code, so their hashes and debug IDs do too. Other builds (SSR, library, dev server)
376
+ * prepend a global assignment to every module importing the Browser SDK.
34
377
  *
35
378
  * Import declarations are hoisted, so the SDK module is evaluated first, but it only reads the
36
379
  * global when `initMonocle()` runs, which always happens after the importing module starts
@@ -42,6 +385,10 @@ var ReleaseInjection = class {
42
385
  * Read by `@monocle.sh/browser-sdk`. Keep both sides in sync.
43
386
  */
44
387
  static globalName = "__MONOCLE_BUILD__";
388
+ /**
389
+ * Name of the `<meta>` tag the Browser SDK reads when the global is not set.
390
+ */
391
+ static metaName = "monocle-build";
45
392
  static sdkImport = /["']@monocle\.sh\/browser-sdk(?:\/[^"'\s]*)?["']/;
46
393
  /**
47
394
  * Stylesheets, markup and data can mention the package without being JavaScript. Markup is
@@ -56,6 +403,15 @@ var ReleaseInjection = class {
56
403
  static snippet(metadata) {
57
404
  return `globalThis.${this.globalName}=${JSON.stringify(metadata)};`;
58
405
  }
406
+ /**
407
+ * URI-encoded JSON: without quotes, it survives Vite 5, which does not escape attributes.
408
+ */
409
+ static meta(metadata) {
410
+ return {
411
+ name: this.metaName,
412
+ content: encodeURIComponent(JSON.stringify(metadata))
413
+ };
414
+ }
59
415
  static shouldInject(options) {
60
416
  return !this.nonScriptIds.test(options.id) && this.sdkImport.test(options.code);
61
417
  }
@@ -112,6 +468,13 @@ var MonocleBuild = class {
112
468
  this.#log(value);
113
469
  return value;
114
470
  }
471
+ /**
472
+ * The release for the `<meta>` tag of an HTML page, when injection is on.
473
+ */
474
+ pageMetadata() {
475
+ if (!this.enabled || this.#options.options.inject === false) return;
476
+ return this.resolve();
477
+ }
115
478
  transform(options) {
116
479
  if (!this.enabled || this.#options.options.inject === false) return;
117
480
  if (!ReleaseInjection.shouldInject(options)) return;
@@ -232,12 +595,47 @@ var SourcemapsStep = class {
232
595
  /**
233
596
  * Translate Vite hooks to the bundler-agnostic Monocle build.
234
597
  */
235
- var MonocleVitePlugin = class {
598
+ var MonocleVitePlugin = class MonocleVitePlugin {
599
+ /**
600
+ * Client builds whose every entry is an HTML page, the only ones where the release can live
601
+ * in the page instead of the chunks. Vite builds `index.html` without an explicit input.
602
+ * Frameworks rendering the HTML themselves set another input, per environment since Vite 6.
603
+ */
604
+ static #htmlBuild(config) {
605
+ const { build } = config;
606
+ if (config.consumer === "server" || build.ssr || build.lib) return false;
607
+ const input = (build.rolldownOptions ?? build.rollupOptions)?.input;
608
+ if (input === void 0) return true;
609
+ const entries = typeof input === "string" ? [input] : Object.values(input);
610
+ return entries.length > 0 && entries.every((entry) => /\.html?$/.test(entry));
611
+ }
236
612
  static create(options = {}) {
237
613
  const plugin = { name: "@monocle.sh/vite-plugin" };
238
614
  if (options.enabled === false) return plugin;
239
615
  let build;
240
616
  let resolved;
617
+ let sdkReadsPage;
618
+ /**
619
+ * Whether the release can go into the HTML pages of this build: a build with injection on,
620
+ * for an installed Browser SDK reading the page. Detected once, with a warning for older
621
+ * SDKs. Without the SDK, nothing reads a page tag: none is written.
622
+ */
623
+ function pageSupported() {
624
+ if (!resolved || resolved.command !== "build" || options.inject === false) return false;
625
+ if (sdkReadsPage === void 0) {
626
+ const source = BrowserSdk.releaseSource(resolved.root);
627
+ sdkReadsPage = source === "page";
628
+ if (source === "bundle") resolved.logger.warn(`[monocle] @monocle.sh/browser-sdk < ${BrowserSdk.pageReleaseSince} reads the release from the bundle only, so every build changes all chunk hashes. Upgrade it to keep unchanged chunks identical.`);
629
+ }
630
+ return sdkReadsPage;
631
+ }
632
+ /**
633
+ * Whether modules of this environment leave the release to its HTML page. Vite 5 has no
634
+ * environments: the top-level configuration applies.
635
+ */
636
+ function releaseInPage(environment) {
637
+ return !!resolved && MonocleVitePlugin.#htmlBuild(environment?.config ?? resolved) && pageSupported();
638
+ }
241
639
  const sourcemaps = new SourcemapsStep({
242
640
  options,
243
641
  env: process.env
@@ -279,6 +677,7 @@ var MonocleVitePlugin = class {
279
677
  code: ReleaseInjection.sdkImport
280
678
  },
281
679
  handler(code, id) {
680
+ if (releaseInPage(this.environment)) return;
282
681
  return build?.transform({
283
682
  code,
284
683
  id
@@ -286,6 +685,20 @@ var MonocleVitePlugin = class {
286
685
  }
287
686
  },
288
687
  /**
688
+ * Builds only call it for HTML entries. When some modules still got the global (mixed
689
+ * HTML and script entries), the SDK prefers the global, so the tag is harmless.
690
+ */
691
+ transformIndexHtml() {
692
+ if (!pageSupported()) return;
693
+ const metadata = build?.pageMetadata();
694
+ if (!metadata) return;
695
+ return [{
696
+ tag: "meta",
697
+ attrs: ReleaseInjection.meta(metadata),
698
+ injectTo: "head-prepend"
699
+ }];
700
+ },
701
+ /**
289
702
  * Sequential and last, so compression or copy plugins see the injected files.
290
703
  */
291
704
  writeBundle: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monocle.sh/vite-plugin",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
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",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "magic-string": "^0.30.21",
42
- "@monocle.sh/sourcemaps": "^0.1.0"
42
+ "@monocle.sh/sourcemaps": "^0.2.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@playwright/test": "^1.63.0",
@@ -49,7 +49,7 @@
49
49
  "tsdown": "^0.21.10",
50
50
  "vite": "^8.3.4",
51
51
  "vue": "^3.5.44",
52
- "@monocle.sh/browser-sdk": "0.12.0",
52
+ "@monocle.sh/browser-sdk": "0.13.0",
53
53
  "@monocle.sh/otel-utils": "1.9.0"
54
54
  },
55
55
  "peerDependencies": {