@monocle.sh/vite-plugin 0.1.0 → 0.2.1

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,6 +1,8 @@
1
- import { readFileSync, statSync } from "node:fs";
2
1
  import { dirname, join, resolve } from "node:path";
2
+ import { readFileSync, statSync } from "node:fs";
3
3
  import MagicString from "magic-string";
4
+ import { rm } from "node:fs/promises";
5
+ import { injectDebugIds, uploadSourcemaps } from "@monocle.sh/sourcemaps";
4
6
  //#region ../otel-utils/dist/service-version.mjs
5
7
  var ServiceVersionDetector = class {
6
8
  /**
@@ -419,6 +421,110 @@ var MonocleBuild = class {
419
421
  }
420
422
  };
421
423
  //#endregion
424
+ //#region src/core/sourcemaps_step.ts
425
+ /**
426
+ * Inject debug IDs into the written chunks, then upload their source maps when an API key is
427
+ * available. Bundler-agnostic: adapters call `sourcemapDefault` while configuring and `run` once
428
+ * the output is on disk.
429
+ */
430
+ var SourcemapsStep = class {
431
+ #options;
432
+ #enabledHiddenMaps = false;
433
+ #missingApiKey = false;
434
+ #processed = /* @__PURE__ */ new Set();
435
+ constructor(options) {
436
+ this.#options = options;
437
+ }
438
+ get #settings() {
439
+ return this.#options.options.sourcemaps || {};
440
+ }
441
+ get #apiKey() {
442
+ return this.#settings.apiKey ?? this.#options.env?.MONOCLE_API_KEY;
443
+ }
444
+ get enabled() {
445
+ return this.#options.options.enabled !== false && this.#options.options.sourcemaps !== false;
446
+ }
447
+ /**
448
+ * Turn on hidden source maps when the user did not configure them and an API key can upload
449
+ * them: the maps are generated without a `sourceMappingURL` comment pointing browsers to
450
+ * them, and deleted once processed.
451
+ */
452
+ sourcemapDefault(configured) {
453
+ if (!this.enabled || configured !== void 0) return;
454
+ if (!this.#apiKey) {
455
+ this.#missingApiKey = true;
456
+ return;
457
+ }
458
+ this.#enabledHiddenMaps = true;
459
+ return "hidden";
460
+ }
461
+ /**
462
+ * Explain the resolved build configuration: why no source map is generated, or that the
463
+ * configured ones are referenced by the bundles, so browsers download them.
464
+ */
465
+ checkConfig(options) {
466
+ if (this.#missingApiKey) options.logger.info("[monocle] Set MONOCLE_API_KEY or sourcemaps.apiKey to generate and upload source maps.");
467
+ 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.");
468
+ }
469
+ async #deleteSourcemaps(options) {
470
+ const count = (await Promise.all(options.paths.map((path) => rm(path).then(() => true, () => false)))).filter(Boolean).length;
471
+ if (count > 0) options.logger.info(`[monocle] Removed ${count} source map(s) from the output.`);
472
+ }
473
+ async #process(options) {
474
+ const { logger } = options;
475
+ const settings = this.#settings;
476
+ const { files } = await injectDebugIds({
477
+ directory: options.directory,
478
+ files: options.files
479
+ });
480
+ for (const file of files) if (file.status === "invalid_sourcemap") logger.warn(`[monocle] ${file.error}`);
481
+ const ready = files.filter((file) => file.debugId && file.sourcemap);
482
+ if (ready.length === 0) return;
483
+ const apiKey = this.#apiKey;
484
+ if (!apiKey) {
485
+ logger.info(`[monocle] Debug IDs injected into ${ready.length} file(s). Set MONOCLE_API_KEY or sourcemaps.apiKey to upload source maps.`);
486
+ return;
487
+ }
488
+ const result = await uploadSourcemaps({
489
+ directory: options.directory,
490
+ files: ready.map((file) => file.file),
491
+ apiKey,
492
+ apiUrl: settings.apiUrl,
493
+ release: settings.release ?? options.release,
494
+ root: options.root,
495
+ logger: {
496
+ info: () => {},
497
+ warn: (message) => logger.warn(`[monocle] ${message}`)
498
+ }
499
+ });
500
+ logger.info(`[monocle] Source maps: ${result.uploaded.length} uploaded, ${result.skipped.length} already uploaded.`);
501
+ if (!this.#enabledHiddenMaps && settings.deleteAfterUpload) await this.#deleteSourcemaps({
502
+ paths: ready.map((file) => file.sourcemap),
503
+ logger
504
+ });
505
+ }
506
+ /**
507
+ * Process one output directory once. Errors are logged unless `failOnError` is set. The maps
508
+ * the plugin enabled are deleted even when the upload fails, so they are never deployed.
509
+ */
510
+ async run(options) {
511
+ if (!this.enabled || this.#processed.has(options.directory)) return;
512
+ this.#processed.add(options.directory);
513
+ try {
514
+ await this.#process(options);
515
+ } catch (error) {
516
+ const message = `[monocle] Could not process source maps: ${error.message}`;
517
+ if (this.#settings.failOnError) throw new Error(message, { cause: error });
518
+ options.logger.error(message);
519
+ } finally {
520
+ if (this.#enabledHiddenMaps && this.#settings.deleteAfterUpload !== false) await this.#deleteSourcemaps({
521
+ paths: options.files.map((file) => `${file}.map`),
522
+ logger: options.logger
523
+ });
524
+ }
525
+ }
526
+ };
527
+ //#endregion
422
528
  //#region src/vite.ts
423
529
  /**
424
530
  * Translate Vite hooks to the bundler-agnostic Monocle build.
@@ -428,6 +534,11 @@ var MonocleVitePlugin = class {
428
534
  const plugin = { name: "@monocle.sh/vite-plugin" };
429
535
  if (options.enabled === false) return plugin;
430
536
  let build;
537
+ let resolved;
538
+ const sourcemaps = new SourcemapsStep({
539
+ options,
540
+ env: process.env
541
+ });
431
542
  return {
432
543
  ...plugin,
433
544
  /**
@@ -435,7 +546,17 @@ var MonocleVitePlugin = class {
435
546
  * JavaScript instead of a raw component, wherever `monocle()` sits in `plugins`.
436
547
  */
437
548
  enforce: "post",
549
+ config(config, env) {
550
+ if (env.command !== "build") return;
551
+ const sourcemap = sourcemaps.sourcemapDefault(config.build?.sourcemap);
552
+ if (sourcemap) return { build: { sourcemap } };
553
+ },
438
554
  configResolved(config) {
555
+ resolved = config;
556
+ if (config.command === "build") sourcemaps.checkConfig({
557
+ sourcemap: config.build.sourcemap,
558
+ logger: config.logger
559
+ });
439
560
  build = new MonocleBuild({
440
561
  options,
441
562
  cwd: config.root,
@@ -460,12 +581,37 @@ var MonocleVitePlugin = class {
460
581
  id
461
582
  });
462
583
  }
584
+ },
585
+ /**
586
+ * Sequential and last, so compression or copy plugins see the injected files.
587
+ */
588
+ writeBundle: {
589
+ sequential: true,
590
+ order: "post",
591
+ async handler(output, bundle) {
592
+ if (!resolved || resolved.command !== "build" || resolved.build.watch) return;
593
+ if (!resolved.build.sourcemap) return;
594
+ const directory = resolve(resolved.root, output.dir ?? dirname(output.file ?? "."));
595
+ const files = Object.values(bundle).filter((item) => item.type === "chunk").map((chunk) => join(directory, chunk.fileName));
596
+ try {
597
+ await sourcemaps.run({
598
+ directory,
599
+ files,
600
+ root: resolved.root,
601
+ release: build?.resolve()?.version,
602
+ logger: resolved.logger
603
+ });
604
+ } catch (error) {
605
+ this.error(error);
606
+ }
607
+ }
463
608
  }
464
609
  };
465
610
  }
466
611
  };
467
612
  /**
468
613
  * Detect the release at build time and expose it to `@monocle.sh/browser-sdk`, in dev and build.
614
+ * After each build, give the bundles debug IDs and upload their source maps.
469
615
  */
470
616
  function monocle(options = {}) {
471
617
  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.1",
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"