@monocle.sh/vite-plugin 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,53 @@
1
+ ## Elastic License 2.0 (ELv2)
2
+
3
+ ### Acceptance
4
+
5
+ By using the software, you agree to all of the terms and conditions below.
6
+
7
+ ### Copyright License
8
+
9
+ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations and conditions below.
10
+
11
+ ### Limitations
12
+
13
+ You may not provide the software to third parties as a hosted or managed service, where the service provides users with access to any substantial set of the features or functionality of the software.
14
+
15
+ You may not move, change, disable, or circumvent the license key functionality in the software, and you may not remove or obscure any functionality in the software that is protected by the license key.
16
+
17
+ You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensor's trademarks is subject to applicable law.
18
+
19
+ ### Patents
20
+
21
+ The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
22
+
23
+ ### Notices
24
+
25
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms.
26
+
27
+ If you modify the software, you must include in any modified copies of the software prominent notices stating that you have modified the software.
28
+
29
+ ### No Other Rights
30
+
31
+ These terms do not imply any licenses other than those expressly granted in these terms.
32
+
33
+ ### Termination
34
+
35
+ If you use the software in violation of these terms, such use is not licensed, and your licenses will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your licenses will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your licenses to terminate automatically and permanently.
36
+
37
+ ### No Liability
38
+
39
+ _As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim._
40
+
41
+ ### Definitions
42
+
43
+ The **licensor** is the entity offering these terms, and the **software** is the software the licensor makes available under these terms, including any portion of it.
44
+
45
+ **You** refers to the individual or entity agreeing to these terms.
46
+
47
+ **Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
48
+
49
+ **Your licenses** are all the licenses granted to you for the software under these terms.
50
+
51
+ **Use** means anything you do with the software requiring one of your licenses.
52
+
53
+ **Trademark** means trademarks, service marks, and similar rights.
package/LICENSE.env-ci ADDED
@@ -0,0 +1,24 @@
1
+ env-ci 11.2.0
2
+ https://github.com/semantic-release/env-ci
3
+
4
+ MIT License
5
+
6
+ Copyright (c) 2017 Pierre Vanduynslager
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all
16
+ copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,68 @@
1
- # Temporary Holding Version
1
+ # @monocle.sh/vite-plugin
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Detects your release when Vite builds or serves your app, and hands it to
4
+ [`@monocle.sh/browser-sdk`](../browser-sdk). Monocle then sees each new version
5
+ of your front end as a deployment, without setting `serviceVersion` by hand.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ pnpm add -D @monocle.sh/vite-plugin
11
+ ```
12
+
13
+ ```ts
14
+ // vite.config.ts
15
+ import { defineConfig } from 'vite'
16
+ import { monocle } from '@monocle.sh/vite-plugin'
17
+
18
+ export default defineConfig({
19
+ plugins: [monocle()],
20
+ })
21
+ ```
22
+
23
+ Keep calling `initMonocle()` as usual. Works with `vite dev` and `vite build`,
24
+ including when Vite pre-bundles the Browser SDK.
25
+
26
+ The position of `monocle()` in `plugins` doesn't matter: it always runs after
27
+ framework plugins such as Vue, Svelte or Astro.
28
+
29
+ ## What it detects
30
+
31
+ The first match wins, in the same order as Monocle's server agents:
32
+
33
+ 1. The `release` option
34
+ 2. `MONOCLE_VERSION`
35
+ 3. Platform variables: Vercel, Render, Railway, Heroku, Coolify, Dokku, Cloud Run
36
+ 4. CI variables: GitHub Actions, GitLab, CircleCI, Bitbucket, Buildkite
37
+ 5. The commit of the local `.git` checkout, read without running `git`
38
+
39
+ Empty values and the placeholders `0.0.0`, `0.0.1`, `1.0.0`, `unknown`, `dev`,
40
+ and `latest` are skipped. When available, the commit SHA, repository URL
41
+ (credentials removed) and platform deployment ID are attached too.
42
+
43
+ The plugin makes no network calls, and only reads the variables above.
44
+
45
+ ## Options
46
+
47
+ ```ts
48
+ monocle({
49
+ release: 'v2.4.0', // force a version, or `false` to skip detection
50
+ inject: true, // expose the release to the Browser SDK
51
+ enabled: true, // turn the whole plugin off
52
+ verbose: false, // log the detected release and its source
53
+ })
54
+ ```
55
+
56
+ A `serviceVersion` passed to `initMonocle()` wins over the injected release,
57
+ unless it is one of the placeholders above.
58
+
59
+ ## How it works
60
+
61
+ The plugin prepends `globalThis.__MONOCLE_BUILD__ = {…}` to each of your modules
62
+ that imports `@monocle.sh/browser-sdk`. The SDK reads it in `initMonocle()`.
63
+
64
+ This targets application code rather than relying on `define`, which Vite does
65
+ not apply to pre-bundled dependencies. It needs no inline `<script>`, so it
66
+ works with SSR frameworks that don't use `index.html` and with strict CSPs.
67
+ During SSR, the global is also set on the server, where the Browser SDK stays
68
+ inactive.
@@ -0,0 +1,40 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/core/types.d.ts
4
+ /**
5
+ * Options shared by every Monocle bundler plugin. New features get their own key so
6
+ * existing configurations keep working unchanged.
7
+ */
8
+ interface MonoclePluginOptions {
9
+ /**
10
+ * Turn every build-time feature off at once. Defaults to `true`.
11
+ */
12
+ enabled?: boolean;
13
+ /**
14
+ * Force the release, or pass `false` to skip release detection and injection.
15
+ * Placeholder values such as `0.0.0` or `dev` are ignored, like everywhere in Monocle.
16
+ */
17
+ release?: string | false;
18
+ /**
19
+ * Expose the detected release to the Browser SDK through the bundle. Defaults to `true`.
20
+ */
21
+ inject?: boolean;
22
+ /**
23
+ * Log the detected release and where it came from.
24
+ */
25
+ verbose?: boolean;
26
+ }
27
+ //#endregion
28
+ //#region src/vite.d.ts
29
+ /**
30
+ * Translate Vite hooks to the bundler-agnostic Monocle build.
31
+ */
32
+ declare class MonocleVitePlugin {
33
+ static create(options?: MonoclePluginOptions): Plugin;
34
+ }
35
+ /**
36
+ * Detect the release at build time and expose it to `@monocle.sh/browser-sdk`, in dev and build.
37
+ */
38
+ declare function monocle(options?: MonoclePluginOptions): Plugin;
39
+ //#endregion
40
+ export { type MonoclePluginOptions, MonocleVitePlugin, monocle };
package/dist/vite.mjs ADDED
@@ -0,0 +1,474 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
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
301
+ //#region src/core/build_metadata.ts
302
+ /**
303
+ * Resolve the release with the same priority chain as the server agents, without spawning Git
304
+ * or reaching the network.
305
+ */
306
+ var BuildMetadataResolver = class {
307
+ static resolve(options) {
308
+ if (options.release === false) return;
309
+ const detected = ServiceVersionDetector.detect({
310
+ serviceVersion: options.release,
311
+ env: options.env,
312
+ cwd: options.cwd
313
+ });
314
+ if (!detected.version) return;
315
+ const metadata = {
316
+ version: detected.version,
317
+ source: detected.source
318
+ };
319
+ if (detected.commitSha) metadata.commitSha = detected.commitSha;
320
+ if (detected.repositoryUrl) metadata.repositoryUrl = detected.repositoryUrl;
321
+ if (detected.deploymentId) metadata.deploymentId = detected.deploymentId;
322
+ return metadata;
323
+ }
324
+ };
325
+ //#endregion
326
+ //#region src/core/release_injection.ts
327
+ /**
328
+ * Prepend a global assignment to every module importing the Browser SDK.
329
+ *
330
+ * Import declarations are hoisted, so the SDK module is evaluated first, but it only reads the
331
+ * global when `initMonocle()` runs, which always happens after the importing module starts
332
+ * evaluating. Unlike `define`, this also works when the SDK is a dependency pre-bundled by the
333
+ * dev server, because the importing module is application code.
334
+ */
335
+ var ReleaseInjection = class {
336
+ /**
337
+ * Read by `@monocle.sh/browser-sdk`. Keep both sides in sync.
338
+ */
339
+ static globalName = "__MONOCLE_BUILD__";
340
+ static sdkImport = /["']@monocle\.sh\/browser-sdk(?:\/[^"'\s]*)?["']/;
341
+ /**
342
+ * Stylesheets, markup and data can mention the package without being JavaScript. Markup is
343
+ * only skipped as a whole file: inline `<script type="module">` blocks are served as
344
+ * `index.html?html-proxy&index=0.js` modules and must be injected.
345
+ */
346
+ static nonScriptIds = /\.(?:css|less|sass|scss|styl|stylus|pcss|postcss|sss|json)(?:$|\?)|\.html?$|[?&]vue&type=style/;
347
+ /**
348
+ * Directives such as `"use client"` only work before any other statement.
349
+ */
350
+ static #prologue = /^(?:#![^\n]*\n)?(?:\s*(?:\/\/[^\n]*|\/\*[\s\S]*?\*\/|(["'])use [\w\s-]+\1\s*;?))*/;
351
+ static snippet(metadata) {
352
+ return `globalThis.${this.globalName}=${JSON.stringify(metadata)};`;
353
+ }
354
+ static shouldInject(options) {
355
+ return !this.nonScriptIds.test(options.id) && this.sdkImport.test(options.code);
356
+ }
357
+ static inject(options) {
358
+ if (!this.shouldInject(options)) return;
359
+ const output = new MagicString(options.code);
360
+ const prologue = options.code.match(this.#prologue)?.[0] ?? "";
361
+ const snippet = this.snippet(options.metadata);
362
+ output.appendLeft(prologue.length, prologue ? `\n${snippet}` : `${snippet}\n`);
363
+ return {
364
+ code: output.toString(),
365
+ map: output.generateMap({
366
+ hires: "boundary",
367
+ source: options.id,
368
+ includeContent: true
369
+ })
370
+ };
371
+ }
372
+ };
373
+ //#endregion
374
+ //#region src/core/monocle_build.ts
375
+ /**
376
+ * Bundler-agnostic build state. Adapters create one per build and forward their hooks here.
377
+ */
378
+ var MonocleBuild = class {
379
+ #options;
380
+ #metadata;
381
+ constructor(options) {
382
+ this.#options = options;
383
+ }
384
+ #log(metadata) {
385
+ if (!this.#options.options.verbose) return;
386
+ if (!metadata) {
387
+ this.#options.logger.info("[monocle] No release detected, nothing injected.");
388
+ return;
389
+ }
390
+ this.#options.logger.info(`[monocle] Release ${metadata.version} (source: ${metadata.source})`);
391
+ }
392
+ get enabled() {
393
+ return this.#options.options.enabled !== false;
394
+ }
395
+ /**
396
+ * Detect once per build; the result does not change while the bundler runs.
397
+ */
398
+ resolve() {
399
+ if (this.#metadata) return this.#metadata.value;
400
+ if (!this.enabled) return;
401
+ const value = BuildMetadataResolver.resolve({
402
+ release: this.#options.options.release,
403
+ cwd: this.#options.cwd,
404
+ env: this.#options.env
405
+ });
406
+ this.#metadata = { value };
407
+ this.#log(value);
408
+ return value;
409
+ }
410
+ transform(options) {
411
+ if (!this.enabled || this.#options.options.inject === false) return;
412
+ if (!ReleaseInjection.shouldInject(options)) return;
413
+ const metadata = this.resolve();
414
+ if (!metadata) return;
415
+ return ReleaseInjection.inject({
416
+ ...options,
417
+ metadata
418
+ });
419
+ }
420
+ };
421
+ //#endregion
422
+ //#region src/vite.ts
423
+ /**
424
+ * Translate Vite hooks to the bundler-agnostic Monocle build.
425
+ */
426
+ var MonocleVitePlugin = class {
427
+ static create(options = {}) {
428
+ const plugin = { name: "@monocle.sh/vite-plugin" };
429
+ if (options.enabled === false) return plugin;
430
+ let build;
431
+ return {
432
+ ...plugin,
433
+ /**
434
+ * Run after framework plugins (Vue, Svelte, Astro…) so the handler sees compiled
435
+ * JavaScript instead of a raw component, wherever `monocle()` sits in `plugins`.
436
+ */
437
+ enforce: "post",
438
+ configResolved(config) {
439
+ build = new MonocleBuild({
440
+ options,
441
+ cwd: config.root,
442
+ logger: config.logger
443
+ });
444
+ },
445
+ buildStart() {
446
+ build?.resolve();
447
+ },
448
+ transform: {
449
+ /**
450
+ * Vite >= 6.3 skips non-matching modules natively; older versions call the handler,
451
+ * which applies the same checks.
452
+ */
453
+ filter: {
454
+ id: { exclude: ReleaseInjection.nonScriptIds },
455
+ code: ReleaseInjection.sdkImport
456
+ },
457
+ handler(code, id) {
458
+ return build?.transform({
459
+ code,
460
+ id
461
+ });
462
+ }
463
+ }
464
+ };
465
+ }
466
+ };
467
+ /**
468
+ * Detect the release at build time and expose it to `@monocle.sh/browser-sdk`, in dev and build.
469
+ */
470
+ function monocle(options = {}) {
471
+ return MonocleVitePlugin.create(options);
472
+ }
473
+ //#endregion
474
+ export { MonocleVitePlugin, monocle };
package/package.json CHANGED
@@ -1,6 +1,63 @@
1
1
  {
2
2
  "name": "@monocle.sh/vite-plugin",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Vite plugin that injects your release into the Monocle Browser SDK at build time",
5
+ "keywords": [
6
+ "monocle",
7
+ "observability",
8
+ "opentelemetry",
9
+ "release",
10
+ "vite",
11
+ "vite-plugin"
12
+ ],
13
+ "homepage": "https://github.com/monocle-sh/js-sdk/tree/main/packages/vite-plugin",
14
+ "license": "Elastic-2.0",
15
+ "author": "Julien Ripouteau <julien@ripouteau.com>",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/monocle-sh/js-sdk.git",
19
+ "directory": "packages/vite-plugin"
20
+ },
21
+ "files": [
22
+ "LICENSE.env-ci",
23
+ "dist"
24
+ ],
25
+ "type": "module",
26
+ "sideEffects": false,
27
+ "types": "./dist/vite.d.mts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/vite.d.mts",
31
+ "default": "./dist/vite.mjs"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public",
37
+ "tag": "latest"
38
+ },
39
+ "dependencies": {
40
+ "magic-string": "^0.30.21"
41
+ },
42
+ "devDependencies": {
43
+ "@playwright/test": "^1.63.0",
44
+ "@sveltejs/vite-plugin-svelte": "^7.3.1",
45
+ "@vitejs/plugin-vue": "^6.0.10",
46
+ "svelte": "^5.57.2",
47
+ "tsdown": "^0.21.10",
48
+ "vite": "^8.3.4",
49
+ "vue": "^3.5.44",
50
+ "@monocle.sh/browser-sdk": "0.4.0",
51
+ "@monocle.sh/otel-utils": "1.7.0"
52
+ },
53
+ "peerDependencies": {
54
+ "vite": ">=5.0.0"
55
+ },
56
+ "scripts": {
57
+ "build": "tsdown",
58
+ "dev": "tsdown --watch",
59
+ "typecheck": "tsgo --noEmit",
60
+ "test": "node --conditions=dev --experimental-transform-types --test tests/*.test.ts",
61
+ "test:browser": "NODE_OPTIONS=--conditions=dev playwright test --config tests/browser/playwright.config.ts"
62
+ }
6
63
  }