@greenhat-services/magpie-vite-plugin 0.1.0 → 0.1.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
@@ -15,6 +15,8 @@ export default defineConfig({ plugins: [vue(), magpie()] });
15
15
 
16
16
  Build with `MAGPIE_DSN` and `MAGPIE_AUTH_TOKEN` (an organisation token with the `project:releases` scope) set. Without a token the plugin does nothing, so local builds are untouched.
17
17
 
18
+ The build also tells Magpie which versions of Vue, Nuxt, Vite and a few other well-known packages the app is built with, so every project's platform is listed in one place. Versions only, read from `node_modules`; nothing from your configuration or environment is sent. Turn it off with `platform: { enabled: false }`.
19
+
18
20
  Full documentation, options and what is (and is not) sent: https://github.com/Greenhat-Services/magpie-js#readme
19
21
 
20
22
  MIT
package/dist/index.cjs CHANGED
@@ -42,6 +42,39 @@ function recentCommits(limit = 50, exec = defaultExec) {
42
42
  return [];
43
43
  }
44
44
  }
45
+ var PLATFORM_PACKAGES = ["nuxt", "vue", "vue-router", "pinia", "react", "next", "svelte", "@sveltejs/kit", "vite", "tailwindcss", "typescript"];
46
+ async function installedVersions(root, packages = PLATFORM_PACKAGES) {
47
+ const found = {};
48
+ await Promise.all(
49
+ packages.map(async (name) => {
50
+ try {
51
+ const { version } = JSON.parse(await promises.readFile(path.join(root, "node_modules", name, "package.json"), "utf8"));
52
+ if (typeof version === "string" && version) found[name === "@sveltejs/kit" ? "sveltekit" : name] = version;
53
+ } catch {
54
+ }
55
+ })
56
+ );
57
+ return Object.fromEntries(Object.entries(found).sort(([a], [b]) => a.localeCompare(b)));
58
+ }
59
+ async function reportPlatform(report) {
60
+ try {
61
+ const response = await (report.fetch ?? fetch)(`${report.origin}/api/${report.project}/platform`, {
62
+ method: "POST",
63
+ headers: { Authorization: `Bearer ${report.token}`, "Content-Type": "application/json", Accept: "application/json" },
64
+ body: JSON.stringify({ environment: report.environment, components: report.components, ...report.sdk ? { sdk: report.sdk } : {} })
65
+ });
66
+ if (response.ok) return void 0;
67
+ let detail = "";
68
+ try {
69
+ const body = await response.json();
70
+ detail = body.error ?? body.message ?? "";
71
+ } catch {
72
+ }
73
+ return `HTTP ${response.status}${detail ? `: ${detail}` : ""}`;
74
+ } catch (error) {
75
+ return `could not reach Magpie (${error.message})`;
76
+ }
77
+ }
45
78
  var MAX_FILES = 50;
46
79
  var MAX_FILE_BYTES = 20 * 1024 * 1024;
47
80
  var MAX_BATCH_BYTES = 6 * 1024 * 1024;
@@ -134,6 +167,8 @@ function magpie(options = {}) {
134
167
  const env = options.env ?? process.env;
135
168
  let release;
136
169
  let deployed = false;
170
+ let platformReported = false;
171
+ let root = process.cwd();
137
172
  const log = (message) => console.log(`${PREFIX} ${message}`);
138
173
  const warn = (message) => console.warn(`${PREFIX} ${message}`);
139
174
  return {
@@ -147,6 +182,9 @@ function magpie(options = {}) {
147
182
  ...release ? { define: { "import.meta.env.VITE_MAGPIE_RELEASE": JSON.stringify(release) } } : {}
148
183
  };
149
184
  },
185
+ configResolved(config) {
186
+ root = config.root;
187
+ },
150
188
  transformIndexHtml() {
151
189
  if (!release) return void 0;
152
190
  return [{ tag: "script", children: `window.__MAGPIE_RELEASE__=${JSON.stringify(release)};`, injectTo: "head-prepend" }];
@@ -162,6 +200,13 @@ function magpie(options = {}) {
162
200
  if (options.failOnError) throw new Error(`${PREFIX} ${message}`);
163
201
  warn(message);
164
202
  };
203
+ if (dsn && token && options.platform?.enabled !== false && !platformReported) {
204
+ platformReported = true;
205
+ const components = await installedVersions(root);
206
+ const reason = Object.keys(components).length ? await reportPlatform({ origin: dsn.origin, project: dsn.project, token, environment: options.environment ?? env.MAGPIE_ENVIRONMENT ?? "production", components, sdk: { name: "magpie.javascript.vite-plugin" }, fetch: options.fetch }) : "no known packages are installed under the project root";
207
+ if (reason) warn(`The platform was not reported: ${reason}`);
208
+ else log(`Reported the platform (${Object.entries(components).map(([name, version]) => `${name} ${version}`).join(", ")}).`);
209
+ }
165
210
  if (!release) return warn("No release could be worked out (no MAGPIE_RELEASE, CI commit variable or git checkout), so source maps are not uploaded.");
166
211
  if (!dsn) return warn("No DSN (the dsn option or MAGPIE_DSN), so source maps are not uploaded.");
167
212
  if (!token) return log("No MAGPIE_AUTH_TOKEN, so source maps are not uploaded. This is expected for a local build.");
@@ -199,11 +244,14 @@ function parseDsn(dsn) {
199
244
  }
200
245
  var index_default = magpie;
201
246
 
247
+ exports.PLATFORM_PACKAGES = PLATFORM_PACKAGES;
202
248
  exports.default = index_default;
249
+ exports.installedVersions = installedVersions;
203
250
  exports.magpie = magpie;
204
251
  exports.parseDsn = parseDsn;
205
252
  exports.recentCommits = recentCommits;
206
253
  exports.recordRelease = recordRelease;
254
+ exports.reportPlatform = reportPlatform;
207
255
  exports.resolveRelease = resolveRelease;
208
256
  exports.uploadSourceMaps = uploadSourceMaps;
209
257
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/release.ts","../src/upload.ts","../src/index.ts"],"names":["execFileSync","stat","readFile","basename","join","rm"],"mappings":"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","file":"index.cjs","sourcesContent":["import { execFileSync } from 'node:child_process';\n\n/** Runs a program with an argument list (no shell, so nothing is ever interpreted) and returns what it printed. */\nexport type Exec = (file: string, args: string[]) => string;\n\nconst defaultExec: Exec = (file, args) => execFileSync(file, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();\n\n/** Environment variables that CI and hosts set to the commit being built, in the order they are tried. */\nconst COMMIT_ENV = [\n 'MAGPIE_RELEASE',\n 'GITHUB_SHA',\n 'CI_COMMIT_SHA',\n 'VERCEL_GIT_COMMIT_SHA',\n 'CF_PAGES_COMMIT_SHA',\n 'COMMIT_REF',\n 'RENDER_GIT_COMMIT',\n 'SOURCE_VERSION',\n 'BITBUCKET_COMMIT',\n 'CIRCLE_SHA1',\n];\n\n/**\n * The version this build is known as, in the order: what you passed, an environment variable a CI or host sets, then\n * the commit of the checkout. The same string must be used when the source maps are uploaded and when the page runs,\n * or Magpie cannot match a stack frame to its map.\n */\nexport function resolveRelease(explicit?: string, env: Record<string, string | undefined> = process.env, exec: Exec = defaultExec): string | undefined {\n if (explicit) return explicit;\n\n for (const name of COMMIT_ENV) {\n if (env[name]) return env[name];\n }\n\n try {\n return exec('git', ['rev-parse', 'HEAD']) || undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface Commit {\n sha: string;\n message: string;\n author: string;\n timestamp: string;\n}\n\n/** The newest commits, newest first, as `git log` lists them. Empty when this is not a git checkout. */\nexport function recentCommits(limit = 50, exec: Exec = defaultExec): Commit[] {\n try {\n const out = exec('git', ['log', '-n', String(Math.max(1, Math.floor(limit))), '--pretty=format:%H%x1f%an%x1f%aI%x1f%s']);\n\n return out\n .split('\\n')\n .filter(Boolean)\n .map((line) => {\n const [sha, author, timestamp, ...message] = line.split('\\u001f');\n\n return { sha, author, timestamp, message: message.join('\\u001f') };\n });\n } catch {\n return [];\n }\n}\n","import { readFile, stat } from 'node:fs/promises';\nimport type { Commit } from './release';\n\n/** Magpie accepts up to 50 maps per request, each up to 20 MB. */\nconst MAX_FILES = 50;\nconst MAX_FILE_BYTES = 20 * 1024 * 1024;\n/** PHP's default `post_max_size` is 8 MB, so a request is kept well under it. A map larger than this goes alone. */\nconst MAX_BATCH_BYTES = 6 * 1024 * 1024;\n\nexport interface MapFile {\n /** The name Magpie files it under: the map's own file name (`app-3f2a1.js.map`). */\n name: string;\n path: string;\n}\n\nexport interface UploadOptions {\n /** `https://host`, no trailing slash. */\n origin: string;\n project: string;\n token: string;\n version: string;\n files: MapFile[];\n fetch?: typeof fetch;\n retries?: number;\n /** Wait between retries, doubled each time. Tests set it to 0. */\n retryDelayMs?: number;\n}\n\nexport interface UploadResult {\n uploaded: MapFile[];\n failed: Array<{ file: MapFile; reason: string }>;\n}\n\n/**\n * Sends source maps to `POST /api/{project}/sourcemaps` in batches that fit Magpie's limits. A batch that fails does\n * not stop the others, and the result says exactly which files made it, so only those are deleted from the build.\n */\nexport async function uploadSourceMaps(options: UploadOptions): Promise<UploadResult> {\n const result: UploadResult = { uploaded: [], failed: [] };\n const sized: Array<MapFile & { bytes: number }> = [];\n\n for (const file of options.files) {\n const bytes = (await stat(file.path)).size;\n if (bytes > MAX_FILE_BYTES) result.failed.push({ file, reason: `larger than 20 MB (${(bytes / 1048576).toFixed(1)} MB), which Magpie refuses` });\n else sized.push({ ...file, bytes });\n }\n\n for (const batch of batches(sized)) {\n const error = await sendBatch(options, batch);\n if (error) for (const file of batch) result.failed.push({ file, reason: error });\n else result.uploaded.push(...batch);\n }\n\n return result;\n}\n\nfunction batches<T extends { bytes: number }>(files: T[]): T[][] {\n const out: T[][] = [];\n let current: T[] = [];\n let size = 0;\n\n for (const file of files) {\n if (current.length && (current.length >= MAX_FILES || size + file.bytes > MAX_BATCH_BYTES)) {\n out.push(current);\n current = [];\n size = 0;\n }\n current.push(file);\n size += file.bytes;\n }\n if (current.length) out.push(current);\n\n return out;\n}\n\n/** @returns undefined on success, or the reason it failed. */\nasync function sendBatch(options: UploadOptions, batch: MapFile[]): Promise<string | undefined> {\n const doFetch = options.fetch ?? fetch;\n const retries = options.retries ?? 2;\n let last = 'unknown error';\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n if (attempt > 0) await sleep((options.retryDelayMs ?? 500) * 2 ** (attempt - 1));\n\n try {\n const form = new FormData();\n form.append('version', options.version);\n for (const file of batch) form.append('maps[]', new Blob([await readFile(file.path)], { type: 'application/json' }), file.name);\n\n const response = await doFetch(`${options.origin}/api/${options.project}/sourcemaps`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${options.token}`, Accept: 'application/json' },\n body: form,\n });\n\n if (response.ok) return undefined;\n\n last = await describeFailure(response);\n // A wrong token, an unknown app or a bad map will not get better by asking again.\n if (response.status < 500) return last;\n } catch (error) {\n last = `could not reach Magpie (${(error as Error).message})`;\n }\n }\n\n return last;\n}\n\nexport interface ReleaseRecord {\n origin: string;\n project: string;\n token: string;\n version: string;\n environment: string;\n deployedBy: string;\n commits: Commit[];\n fetch?: typeof fetch;\n}\n\n/** Tells Magpie a release was deployed to an environment, with its commits (newest first). @returns undefined on success. */\nexport async function recordRelease(record: ReleaseRecord): Promise<string | undefined> {\n try {\n const response = await (record.fetch ?? fetch)(`${record.origin}/api/${record.project}/releases`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${record.token}`, 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({\n version: record.version,\n environment: record.environment,\n deployed_by: record.deployedBy,\n ...(record.commits.length ? { commits: record.commits } : {}),\n }),\n });\n\n return response.ok ? undefined : await describeFailure(response);\n } catch (error) {\n return `could not reach Magpie (${(error as Error).message})`;\n }\n}\n\nasync function describeFailure(response: Response): Promise<string> {\n let detail = '';\n try {\n const body = (await response.json()) as { error?: string; message?: string };\n detail = body.error ?? body.message ?? '';\n } catch {\n // Not JSON: the status says enough.\n }\n\n return `HTTP ${response.status}${detail ? `: ${detail}` : ''}`;\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n","import { rm } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport type { Plugin } from 'vite';\nimport { type Exec, recentCommits, resolveRelease } from './release';\nimport { type MapFile, recordRelease, uploadSourceMaps } from './upload';\n\nexport { recentCommits, resolveRelease } from './release';\nexport { recordRelease, uploadSourceMaps, type MapFile } from './upload';\n\nexport interface MagpiePluginOptions {\n /** The app's DSN (`https://<key>@<host>/<project>`), used to find where to upload. Falls back to `MAGPIE_DSN`. */\n dsn?: string;\n /**\n * An organisation token with the `project:releases` scope (Organisation settings > API tokens). Falls back to\n * `MAGPIE_AUTH_TOKEN`. It is read at build time only and is never put in the bundle. Without one, nothing is uploaded.\n */\n authToken?: string;\n /** The version of this build. Defaults to the commit being built; see `resolveRelease`. */\n release?: string;\n /** Where this build is going, for the deploy record. Defaults to `MAGPIE_ENVIRONMENT`, then \"production\". */\n environment?: string;\n sourcemaps?: {\n /** Default true. */\n enabled?: boolean;\n /** Remove each map from the output once Magpie has it, so it is never served. Default true. */\n deleteAfterUpload?: boolean;\n /** Also upload the maps of the server build (Nuxt's `ssr` environment). Default false. */\n server?: boolean;\n };\n deploy?: {\n /** Record the release, its environment and its commits with Magpie after the upload. Default true. */\n enabled?: boolean;\n deployedBy?: string;\n /** How many recent commits to send. Default 50. */\n commits?: number;\n };\n /** Fail the build when an upload fails. Default false: a Magpie outage should not stop you deploying. */\n failOnError?: boolean;\n disable?: boolean;\n /** @internal Replaced in tests. */\n fetch?: typeof fetch;\n /** @internal Replaced in tests. */\n exec?: Exec;\n /** @internal Replaced in tests. */\n env?: Record<string, string | undefined>;\n}\n\nconst PREFIX = '[magpie]';\n\n/**\n * Makes production stack traces readable. During `vite build` it:\n * - makes the build write source maps without referencing them (`sourcemap: 'hidden'`), unless you chose otherwise;\n * - puts the release on the page, as `import.meta.env.VITE_MAGPIE_RELEASE` and `window.__MAGPIE_RELEASE__`;\n * - uploads the maps to Magpie under that release, and deletes them from the output once Magpie has them;\n * - records the release, the environment it went to and its recent commits.\n */\nexport function magpie(options: MagpiePluginOptions = {}): Plugin {\n const env = options.env ?? process.env;\n let release: string | undefined;\n let deployed = false;\n\n const log = (message: string) => console.log(`${PREFIX} ${message}`);\n const warn = (message: string) => console.warn(`${PREFIX} ${message}`);\n\n return {\n name: 'magpie',\n apply: 'build',\n\n config(config) {\n release = resolveRelease(options.release, env, options.exec);\n\n return {\n // Hidden: the map is written but the bundle does not point at it, so visitors cannot fetch your source.\n ...(config.build?.sourcemap === undefined && options.sourcemaps?.enabled !== false ? { build: { sourcemap: 'hidden' as const } } : {}),\n ...(release ? { define: { 'import.meta.env.VITE_MAGPIE_RELEASE': JSON.stringify(release) } } : {}),\n };\n },\n\n transformIndexHtml() {\n if (!release) return undefined;\n\n return [{ tag: 'script', children: `window.__MAGPIE_RELEASE__=${JSON.stringify(release)};`, injectTo: 'head-prepend' as const }];\n },\n\n async writeBundle(outputOptions, bundle) {\n if (options.disable) return;\n release ??= resolveRelease(options.release, env, options.exec);\n\n // Nuxt and other SSR setups build twice. The browser build is the one whose stack traces need maps.\n const environment = (this as { environment?: { name?: string } }).environment?.name ?? 'client';\n if (environment !== 'client' && !options.sourcemaps?.server) return;\n\n const token = options.authToken ?? env.MAGPIE_AUTH_TOKEN;\n const dsn = parseDsn(options.dsn ?? env.MAGPIE_DSN);\n const fail = (message: string) => {\n if (options.failOnError) throw new Error(`${PREFIX} ${message}`);\n warn(message);\n };\n\n if (!release) return warn('No release could be worked out (no MAGPIE_RELEASE, CI commit variable or git checkout), so source maps are not uploaded.');\n if (!dsn) return warn('No DSN (the dsn option or MAGPIE_DSN), so source maps are not uploaded.');\n if (!token) return log('No MAGPIE_AUTH_TOKEN, so source maps are not uploaded. This is expected for a local build.');\n\n const dir = outputOptions.dir ?? '.';\n const files: MapFile[] = Object.keys(bundle)\n .filter((name) => name.endsWith('.map'))\n .map((name) => ({ name: basename(name), path: join(dir, name) }));\n\n if (options.sourcemaps?.enabled !== false && files.length) {\n const result = await uploadSourceMaps({ origin: dsn.origin, project: dsn.project, token, version: release, files, fetch: options.fetch });\n\n log(`Uploaded ${result.uploaded.length} of ${files.length} source maps for ${release}.`);\n for (const { file, reason } of result.failed) fail(`${file.name} was not uploaded: ${reason}`);\n\n // Only what Magpie actually has is removed, so a failed upload never costs you the map.\n if (options.sourcemaps?.deleteAfterUpload !== false) await Promise.all(result.uploaded.map((f) => rm(f.path, { force: true })));\n } else if (options.sourcemaps?.enabled !== false) {\n warn('The build produced no source maps. Check that build.sourcemap is not false.');\n }\n\n if (options.deploy?.enabled !== false && !deployed) {\n deployed = true;\n const reason = await recordRelease({\n origin: dsn.origin,\n project: dsn.project,\n token,\n version: release,\n environment: options.environment ?? env.MAGPIE_ENVIRONMENT ?? 'production',\n deployedBy: options.deploy?.deployedBy ?? env.GITHUB_ACTOR ?? env.GITLAB_USER_LOGIN ?? 'build',\n commits: recentCommits(options.deploy?.commits ?? 50, options.exec),\n fetch: options.fetch,\n });\n\n if (reason) fail(`The release was not recorded: ${reason}`);\n else log(`Recorded release ${release} for ${options.environment ?? env.MAGPIE_ENVIRONMENT ?? 'production'}.`);\n }\n },\n };\n}\n\nexport function parseDsn(dsn: string | undefined): { origin: string; project: string } | null {\n const m = /^(https?):\\/\\/[^@/\\s:]+@([^/\\s]+)\\/(\\d+)\\/?$/i.exec((dsn ?? '').trim());\n\n return m ? { origin: `${m[1].toLowerCase()}://${m[2]}`, project: m[3] } : null;\n}\n\nexport default magpie;\n"]}
1
+ {"version":3,"sources":["../src/release.ts","../src/platform.ts","../src/upload.ts","../src/index.ts"],"names":["execFileSync","readFile","join","stat","basename","rm"],"mappings":"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","file":"index.cjs","sourcesContent":["import { execFileSync } from 'node:child_process';\n\n/** Runs a program with an argument list (no shell, so nothing is ever interpreted) and returns what it printed. */\nexport type Exec = (file: string, args: string[]) => string;\n\nconst defaultExec: Exec = (file, args) => execFileSync(file, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();\n\n/** Environment variables that CI and hosts set to the commit being built, in the order they are tried. */\nconst COMMIT_ENV = [\n 'MAGPIE_RELEASE',\n 'GITHUB_SHA',\n 'CI_COMMIT_SHA',\n 'VERCEL_GIT_COMMIT_SHA',\n 'CF_PAGES_COMMIT_SHA',\n 'COMMIT_REF',\n 'RENDER_GIT_COMMIT',\n 'SOURCE_VERSION',\n 'BITBUCKET_COMMIT',\n 'CIRCLE_SHA1',\n];\n\n/**\n * The version this build is known as, in the order: what you passed, an environment variable a CI or host sets, then\n * the commit of the checkout. The same string must be used when the source maps are uploaded and when the page runs,\n * or Magpie cannot match a stack frame to its map.\n */\nexport function resolveRelease(explicit?: string, env: Record<string, string | undefined> = process.env, exec: Exec = defaultExec): string | undefined {\n if (explicit) return explicit;\n\n for (const name of COMMIT_ENV) {\n if (env[name]) return env[name];\n }\n\n try {\n return exec('git', ['rev-parse', 'HEAD']) || undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface Commit {\n sha: string;\n message: string;\n author: string;\n timestamp: string;\n}\n\n/** The newest commits, newest first, as `git log` lists them. Empty when this is not a git checkout. */\nexport function recentCommits(limit = 50, exec: Exec = defaultExec): Commit[] {\n try {\n const out = exec('git', ['log', '-n', String(Math.max(1, Math.floor(limit))), '--pretty=format:%H%x1f%an%x1f%aI%x1f%s']);\n\n return out\n .split('\\n')\n .filter(Boolean)\n .map((line) => {\n const [sha, author, timestamp, ...message] = line.split('\\u001f');\n\n return { sha, author, timestamp, message: message.join('\\u001f') };\n });\n } catch {\n return [];\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\n/** Packages whose installed version Magpie shows, in the order they are listed. Names and versions only. */\nexport const PLATFORM_PACKAGES = ['nuxt', 'vue', 'vue-router', 'pinia', 'react', 'next', 'svelte', '@sveltejs/kit', 'vite', 'tailwindcss', 'typescript'] as const;\n\n/** The version of each known package installed under `root`, keyed by the name Magpie shows (`@sveltejs/kit` is `sveltekit`). */\nexport async function installedVersions(root: string, packages: readonly string[] = PLATFORM_PACKAGES): Promise<Record<string, string>> {\n const found: Record<string, string> = {};\n\n await Promise.all(\n packages.map(async (name) => {\n try {\n const { version } = JSON.parse(await readFile(join(root, 'node_modules', name, 'package.json'), 'utf8')) as { version?: unknown };\n if (typeof version === 'string' && version) found[name === '@sveltejs/kit' ? 'sveltekit' : name] = version;\n } catch {\n // Not installed here: it is simply not part of this app.\n }\n }),\n );\n\n return Object.fromEntries(Object.entries(found).sort(([a], [b]) => a.localeCompare(b)));\n}\n\nexport interface PlatformReport {\n /** `https://host`, no trailing slash. */\n origin: string;\n project: string;\n token: string;\n environment: string;\n components: Record<string, string>;\n sdk?: { name: string; version?: string };\n fetch?: typeof fetch;\n}\n\n/**\n * Tells Magpie what the app is built with, to `POST /api/{project}/platform`. Returns why it failed, or undefined when\n * Magpie has it. Versions only: nothing from the app's configuration or environment is sent.\n */\nexport async function reportPlatform(report: PlatformReport): Promise<string | undefined> {\n try {\n const response = await (report.fetch ?? fetch)(`${report.origin}/api/${report.project}/platform`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${report.token}`, 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({ environment: report.environment, components: report.components, ...(report.sdk ? { sdk: report.sdk } : {}) }),\n });\n\n if (response.ok) return undefined;\n\n let detail = '';\n try {\n const body = (await response.json()) as { error?: string; message?: string };\n detail = body.error ?? body.message ?? '';\n } catch {\n // Not JSON: the status says enough.\n }\n\n return `HTTP ${response.status}${detail ? `: ${detail}` : ''}`;\n } catch (error) {\n return `could not reach Magpie (${(error as Error).message})`;\n }\n}\n","import { readFile, stat } from 'node:fs/promises';\nimport type { Commit } from './release';\n\n/** Magpie accepts up to 50 maps per request, each up to 20 MB. */\nconst MAX_FILES = 50;\nconst MAX_FILE_BYTES = 20 * 1024 * 1024;\n/** PHP's default `post_max_size` is 8 MB, so a request is kept well under it. A map larger than this goes alone. */\nconst MAX_BATCH_BYTES = 6 * 1024 * 1024;\n\nexport interface MapFile {\n /** The name Magpie files it under: the map's own file name (`app-3f2a1.js.map`). */\n name: string;\n path: string;\n}\n\nexport interface UploadOptions {\n /** `https://host`, no trailing slash. */\n origin: string;\n project: string;\n token: string;\n version: string;\n files: MapFile[];\n fetch?: typeof fetch;\n retries?: number;\n /** Wait between retries, doubled each time. Tests set it to 0. */\n retryDelayMs?: number;\n}\n\nexport interface UploadResult {\n uploaded: MapFile[];\n failed: Array<{ file: MapFile; reason: string }>;\n}\n\n/**\n * Sends source maps to `POST /api/{project}/sourcemaps` in batches that fit Magpie's limits. A batch that fails does\n * not stop the others, and the result says exactly which files made it, so only those are deleted from the build.\n */\nexport async function uploadSourceMaps(options: UploadOptions): Promise<UploadResult> {\n const result: UploadResult = { uploaded: [], failed: [] };\n const sized: Array<MapFile & { bytes: number }> = [];\n\n for (const file of options.files) {\n const bytes = (await stat(file.path)).size;\n if (bytes > MAX_FILE_BYTES) result.failed.push({ file, reason: `larger than 20 MB (${(bytes / 1048576).toFixed(1)} MB), which Magpie refuses` });\n else sized.push({ ...file, bytes });\n }\n\n for (const batch of batches(sized)) {\n const error = await sendBatch(options, batch);\n if (error) for (const file of batch) result.failed.push({ file, reason: error });\n else result.uploaded.push(...batch);\n }\n\n return result;\n}\n\nfunction batches<T extends { bytes: number }>(files: T[]): T[][] {\n const out: T[][] = [];\n let current: T[] = [];\n let size = 0;\n\n for (const file of files) {\n if (current.length && (current.length >= MAX_FILES || size + file.bytes > MAX_BATCH_BYTES)) {\n out.push(current);\n current = [];\n size = 0;\n }\n current.push(file);\n size += file.bytes;\n }\n if (current.length) out.push(current);\n\n return out;\n}\n\n/** @returns undefined on success, or the reason it failed. */\nasync function sendBatch(options: UploadOptions, batch: MapFile[]): Promise<string | undefined> {\n const doFetch = options.fetch ?? fetch;\n const retries = options.retries ?? 2;\n let last = 'unknown error';\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n if (attempt > 0) await sleep((options.retryDelayMs ?? 500) * 2 ** (attempt - 1));\n\n try {\n const form = new FormData();\n form.append('version', options.version);\n for (const file of batch) form.append('maps[]', new Blob([await readFile(file.path)], { type: 'application/json' }), file.name);\n\n const response = await doFetch(`${options.origin}/api/${options.project}/sourcemaps`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${options.token}`, Accept: 'application/json' },\n body: form,\n });\n\n if (response.ok) return undefined;\n\n last = await describeFailure(response);\n // A wrong token, an unknown app or a bad map will not get better by asking again.\n if (response.status < 500) return last;\n } catch (error) {\n last = `could not reach Magpie (${(error as Error).message})`;\n }\n }\n\n return last;\n}\n\nexport interface ReleaseRecord {\n origin: string;\n project: string;\n token: string;\n version: string;\n environment: string;\n deployedBy: string;\n commits: Commit[];\n fetch?: typeof fetch;\n}\n\n/** Tells Magpie a release was deployed to an environment, with its commits (newest first). @returns undefined on success. */\nexport async function recordRelease(record: ReleaseRecord): Promise<string | undefined> {\n try {\n const response = await (record.fetch ?? fetch)(`${record.origin}/api/${record.project}/releases`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${record.token}`, 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({\n version: record.version,\n environment: record.environment,\n deployed_by: record.deployedBy,\n ...(record.commits.length ? { commits: record.commits } : {}),\n }),\n });\n\n return response.ok ? undefined : await describeFailure(response);\n } catch (error) {\n return `could not reach Magpie (${(error as Error).message})`;\n }\n}\n\nasync function describeFailure(response: Response): Promise<string> {\n let detail = '';\n try {\n const body = (await response.json()) as { error?: string; message?: string };\n detail = body.error ?? body.message ?? '';\n } catch {\n // Not JSON: the status says enough.\n }\n\n return `HTTP ${response.status}${detail ? `: ${detail}` : ''}`;\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n","import { rm } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport type { Plugin } from 'vite';\nimport { type Exec, recentCommits, resolveRelease } from './release';\nimport { installedVersions, reportPlatform } from './platform';\nimport { type MapFile, recordRelease, uploadSourceMaps } from './upload';\n\nexport { recentCommits, resolveRelease } from './release';\nexport { installedVersions, PLATFORM_PACKAGES, reportPlatform } from './platform';\nexport { recordRelease, uploadSourceMaps, type MapFile } from './upload';\n\nexport interface MagpiePluginOptions {\n /** The app's DSN (`https://<key>@<host>/<project>`), used to find where to upload. Falls back to `MAGPIE_DSN`. */\n dsn?: string;\n /**\n * An organisation token with the `project:releases` scope (Organisation settings > API tokens). Falls back to\n * `MAGPIE_AUTH_TOKEN`. It is read at build time only and is never put in the bundle. Without one, nothing is uploaded.\n */\n authToken?: string;\n /** The version of this build. Defaults to the commit being built; see `resolveRelease`. */\n release?: string;\n /** Where this build is going, for the deploy record. Defaults to `MAGPIE_ENVIRONMENT`, then \"production\". */\n environment?: string;\n sourcemaps?: {\n /** Default true. */\n enabled?: boolean;\n /** Remove each map from the output once Magpie has it, so it is never served. Default true. */\n deleteAfterUpload?: boolean;\n /** Also upload the maps of the server build (Nuxt's `ssr` environment). Default false. */\n server?: boolean;\n };\n deploy?: {\n /** Record the release, its environment and its commits with Magpie after the upload. Default true. */\n enabled?: boolean;\n deployedBy?: string;\n /** How many recent commits to send. Default 50. */\n commits?: number;\n };\n platform?: {\n /**\n * Tell Magpie which versions of Vue, Nuxt, Vite and the like this app is built with, so every project's platform\n * is listed in one place. Versions only. Needs the auth token. Default true.\n */\n enabled?: boolean;\n };\n /** Fail the build when an upload fails. Default false: a Magpie outage should not stop you deploying. */\n failOnError?: boolean;\n disable?: boolean;\n /** @internal Replaced in tests. */\n fetch?: typeof fetch;\n /** @internal Replaced in tests. */\n exec?: Exec;\n /** @internal Replaced in tests. */\n env?: Record<string, string | undefined>;\n}\n\nconst PREFIX = '[magpie]';\n\n/**\n * Makes production stack traces readable. During `vite build` it:\n * - makes the build write source maps without referencing them (`sourcemap: 'hidden'`), unless you chose otherwise;\n * - puts the release on the page, as `import.meta.env.VITE_MAGPIE_RELEASE` and `window.__MAGPIE_RELEASE__`;\n * - uploads the maps to Magpie under that release, and deletes them from the output once Magpie has them;\n * - records the release, the environment it went to and its recent commits.\n */\nexport function magpie(options: MagpiePluginOptions = {}): Plugin {\n const env = options.env ?? process.env;\n let release: string | undefined;\n let deployed = false;\n let platformReported = false;\n let root = process.cwd();\n\n const log = (message: string) => console.log(`${PREFIX} ${message}`);\n const warn = (message: string) => console.warn(`${PREFIX} ${message}`);\n\n return {\n name: 'magpie',\n apply: 'build',\n\n config(config) {\n release = resolveRelease(options.release, env, options.exec);\n\n return {\n // Hidden: the map is written but the bundle does not point at it, so visitors cannot fetch your source.\n ...(config.build?.sourcemap === undefined && options.sourcemaps?.enabled !== false ? { build: { sourcemap: 'hidden' as const } } : {}),\n ...(release ? { define: { 'import.meta.env.VITE_MAGPIE_RELEASE': JSON.stringify(release) } } : {}),\n };\n },\n\n configResolved(config) {\n root = config.root;\n },\n\n transformIndexHtml() {\n if (!release) return undefined;\n\n return [{ tag: 'script', children: `window.__MAGPIE_RELEASE__=${JSON.stringify(release)};`, injectTo: 'head-prepend' as const }];\n },\n\n async writeBundle(outputOptions, bundle) {\n if (options.disable) return;\n release ??= resolveRelease(options.release, env, options.exec);\n\n // Nuxt and other SSR setups build twice. The browser build is the one whose stack traces need maps.\n const environment = (this as { environment?: { name?: string } }).environment?.name ?? 'client';\n if (environment !== 'client' && !options.sourcemaps?.server) return;\n\n const token = options.authToken ?? env.MAGPIE_AUTH_TOKEN;\n const dsn = parseDsn(options.dsn ?? env.MAGPIE_DSN);\n const fail = (message: string) => {\n if (options.failOnError) throw new Error(`${PREFIX} ${message}`);\n warn(message);\n };\n\n if (dsn && token && options.platform?.enabled !== false && !platformReported) {\n platformReported = true;\n const components = await installedVersions(root);\n const reason = Object.keys(components).length\n ? await reportPlatform({ origin: dsn.origin, project: dsn.project, token, environment: options.environment ?? env.MAGPIE_ENVIRONMENT ?? 'production', components, sdk: { name: 'magpie.javascript.vite-plugin' }, fetch: options.fetch })\n : 'no known packages are installed under the project root';\n\n if (reason) warn(`The platform was not reported: ${reason}`);\n else log(`Reported the platform (${Object.entries(components).map(([name, version]) => `${name} ${version}`).join(', ')}).`);\n }\n\n if (!release) return warn('No release could be worked out (no MAGPIE_RELEASE, CI commit variable or git checkout), so source maps are not uploaded.');\n if (!dsn) return warn('No DSN (the dsn option or MAGPIE_DSN), so source maps are not uploaded.');\n if (!token) return log('No MAGPIE_AUTH_TOKEN, so source maps are not uploaded. This is expected for a local build.');\n\n const dir = outputOptions.dir ?? '.';\n const files: MapFile[] = Object.keys(bundle)\n .filter((name) => name.endsWith('.map'))\n .map((name) => ({ name: basename(name), path: join(dir, name) }));\n\n if (options.sourcemaps?.enabled !== false && files.length) {\n const result = await uploadSourceMaps({ origin: dsn.origin, project: dsn.project, token, version: release, files, fetch: options.fetch });\n\n log(`Uploaded ${result.uploaded.length} of ${files.length} source maps for ${release}.`);\n for (const { file, reason } of result.failed) fail(`${file.name} was not uploaded: ${reason}`);\n\n // Only what Magpie actually has is removed, so a failed upload never costs you the map.\n if (options.sourcemaps?.deleteAfterUpload !== false) await Promise.all(result.uploaded.map((f) => rm(f.path, { force: true })));\n } else if (options.sourcemaps?.enabled !== false) {\n warn('The build produced no source maps. Check that build.sourcemap is not false.');\n }\n\n if (options.deploy?.enabled !== false && !deployed) {\n deployed = true;\n const reason = await recordRelease({\n origin: dsn.origin,\n project: dsn.project,\n token,\n version: release,\n environment: options.environment ?? env.MAGPIE_ENVIRONMENT ?? 'production',\n deployedBy: options.deploy?.deployedBy ?? env.GITHUB_ACTOR ?? env.GITLAB_USER_LOGIN ?? 'build',\n commits: recentCommits(options.deploy?.commits ?? 50, options.exec),\n fetch: options.fetch,\n });\n\n if (reason) fail(`The release was not recorded: ${reason}`);\n else log(`Recorded release ${release} for ${options.environment ?? env.MAGPIE_ENVIRONMENT ?? 'production'}.`);\n }\n },\n };\n}\n\nexport function parseDsn(dsn: string | undefined): { origin: string; project: string } | null {\n const m = /^(https?):\\/\\/[^@/\\s:]+@([^/\\s]+)\\/(\\d+)\\/?$/i.exec((dsn ?? '').trim());\n\n return m ? { origin: `${m[1].toLowerCase()}://${m[2]}`, project: m[3] } : null;\n}\n\nexport default magpie;\n"]}
package/dist/index.d.cts CHANGED
@@ -17,6 +17,29 @@ interface Commit {
17
17
  /** The newest commits, newest first, as `git log` lists them. Empty when this is not a git checkout. */
18
18
  declare function recentCommits(limit?: number, exec?: Exec): Commit[];
19
19
 
20
+ /** Packages whose installed version Magpie shows, in the order they are listed. Names and versions only. */
21
+ declare const PLATFORM_PACKAGES: readonly ["nuxt", "vue", "vue-router", "pinia", "react", "next", "svelte", "@sveltejs/kit", "vite", "tailwindcss", "typescript"];
22
+ /** The version of each known package installed under `root`, keyed by the name Magpie shows (`@sveltejs/kit` is `sveltekit`). */
23
+ declare function installedVersions(root: string, packages?: readonly string[]): Promise<Record<string, string>>;
24
+ interface PlatformReport {
25
+ /** `https://host`, no trailing slash. */
26
+ origin: string;
27
+ project: string;
28
+ token: string;
29
+ environment: string;
30
+ components: Record<string, string>;
31
+ sdk?: {
32
+ name: string;
33
+ version?: string;
34
+ };
35
+ fetch?: typeof fetch;
36
+ }
37
+ /**
38
+ * Tells Magpie what the app is built with, to `POST /api/{project}/platform`. Returns why it failed, or undefined when
39
+ * Magpie has it. Versions only: nothing from the app's configuration or environment is sent.
40
+ */
41
+ declare function reportPlatform(report: PlatformReport): Promise<string | undefined>;
42
+
20
43
  interface MapFile {
21
44
  /** The name Magpie files it under: the map's own file name (`app-3f2a1.js.map`). */
22
45
  name: string;
@@ -86,6 +109,13 @@ interface MagpiePluginOptions {
86
109
  /** How many recent commits to send. Default 50. */
87
110
  commits?: number;
88
111
  };
112
+ platform?: {
113
+ /**
114
+ * Tell Magpie which versions of Vue, Nuxt, Vite and the like this app is built with, so every project's platform
115
+ * is listed in one place. Versions only. Needs the auth token. Default true.
116
+ */
117
+ enabled?: boolean;
118
+ };
89
119
  /** Fail the build when an upload fails. Default false: a Magpie outage should not stop you deploying. */
90
120
  failOnError?: boolean;
91
121
  disable?: boolean;
@@ -109,4 +139,4 @@ declare function parseDsn(dsn: string | undefined): {
109
139
  project: string;
110
140
  } | null;
111
141
 
112
- export { type MagpiePluginOptions, type MapFile, magpie as default, magpie, parseDsn, recentCommits, recordRelease, resolveRelease, uploadSourceMaps };
142
+ export { type MagpiePluginOptions, type MapFile, PLATFORM_PACKAGES, magpie as default, installedVersions, magpie, parseDsn, recentCommits, recordRelease, reportPlatform, resolveRelease, uploadSourceMaps };
package/dist/index.d.ts CHANGED
@@ -17,6 +17,29 @@ interface Commit {
17
17
  /** The newest commits, newest first, as `git log` lists them. Empty when this is not a git checkout. */
18
18
  declare function recentCommits(limit?: number, exec?: Exec): Commit[];
19
19
 
20
+ /** Packages whose installed version Magpie shows, in the order they are listed. Names and versions only. */
21
+ declare const PLATFORM_PACKAGES: readonly ["nuxt", "vue", "vue-router", "pinia", "react", "next", "svelte", "@sveltejs/kit", "vite", "tailwindcss", "typescript"];
22
+ /** The version of each known package installed under `root`, keyed by the name Magpie shows (`@sveltejs/kit` is `sveltekit`). */
23
+ declare function installedVersions(root: string, packages?: readonly string[]): Promise<Record<string, string>>;
24
+ interface PlatformReport {
25
+ /** `https://host`, no trailing slash. */
26
+ origin: string;
27
+ project: string;
28
+ token: string;
29
+ environment: string;
30
+ components: Record<string, string>;
31
+ sdk?: {
32
+ name: string;
33
+ version?: string;
34
+ };
35
+ fetch?: typeof fetch;
36
+ }
37
+ /**
38
+ * Tells Magpie what the app is built with, to `POST /api/{project}/platform`. Returns why it failed, or undefined when
39
+ * Magpie has it. Versions only: nothing from the app's configuration or environment is sent.
40
+ */
41
+ declare function reportPlatform(report: PlatformReport): Promise<string | undefined>;
42
+
20
43
  interface MapFile {
21
44
  /** The name Magpie files it under: the map's own file name (`app-3f2a1.js.map`). */
22
45
  name: string;
@@ -86,6 +109,13 @@ interface MagpiePluginOptions {
86
109
  /** How many recent commits to send. Default 50. */
87
110
  commits?: number;
88
111
  };
112
+ platform?: {
113
+ /**
114
+ * Tell Magpie which versions of Vue, Nuxt, Vite and the like this app is built with, so every project's platform
115
+ * is listed in one place. Versions only. Needs the auth token. Default true.
116
+ */
117
+ enabled?: boolean;
118
+ };
89
119
  /** Fail the build when an upload fails. Default false: a Magpie outage should not stop you deploying. */
90
120
  failOnError?: boolean;
91
121
  disable?: boolean;
@@ -109,4 +139,4 @@ declare function parseDsn(dsn: string | undefined): {
109
139
  project: string;
110
140
  } | null;
111
141
 
112
- export { type MagpiePluginOptions, type MapFile, magpie as default, magpie, parseDsn, recentCommits, recordRelease, resolveRelease, uploadSourceMaps };
142
+ export { type MagpiePluginOptions, type MapFile, PLATFORM_PACKAGES, magpie as default, installedVersions, magpie, parseDsn, recentCommits, recordRelease, reportPlatform, resolveRelease, uploadSourceMaps };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { stat, readFile, rm } from 'fs/promises';
1
+ import { readFile, stat, rm } from 'fs/promises';
2
2
  import { join, basename } from 'path';
3
3
  import { execFileSync } from 'child_process';
4
4
 
@@ -38,6 +38,39 @@ function recentCommits(limit = 50, exec = defaultExec) {
38
38
  return [];
39
39
  }
40
40
  }
41
+ var PLATFORM_PACKAGES = ["nuxt", "vue", "vue-router", "pinia", "react", "next", "svelte", "@sveltejs/kit", "vite", "tailwindcss", "typescript"];
42
+ async function installedVersions(root, packages = PLATFORM_PACKAGES) {
43
+ const found = {};
44
+ await Promise.all(
45
+ packages.map(async (name) => {
46
+ try {
47
+ const { version } = JSON.parse(await readFile(join(root, "node_modules", name, "package.json"), "utf8"));
48
+ if (typeof version === "string" && version) found[name === "@sveltejs/kit" ? "sveltekit" : name] = version;
49
+ } catch {
50
+ }
51
+ })
52
+ );
53
+ return Object.fromEntries(Object.entries(found).sort(([a], [b]) => a.localeCompare(b)));
54
+ }
55
+ async function reportPlatform(report) {
56
+ try {
57
+ const response = await (report.fetch ?? fetch)(`${report.origin}/api/${report.project}/platform`, {
58
+ method: "POST",
59
+ headers: { Authorization: `Bearer ${report.token}`, "Content-Type": "application/json", Accept: "application/json" },
60
+ body: JSON.stringify({ environment: report.environment, components: report.components, ...report.sdk ? { sdk: report.sdk } : {} })
61
+ });
62
+ if (response.ok) return void 0;
63
+ let detail = "";
64
+ try {
65
+ const body = await response.json();
66
+ detail = body.error ?? body.message ?? "";
67
+ } catch {
68
+ }
69
+ return `HTTP ${response.status}${detail ? `: ${detail}` : ""}`;
70
+ } catch (error) {
71
+ return `could not reach Magpie (${error.message})`;
72
+ }
73
+ }
41
74
  var MAX_FILES = 50;
42
75
  var MAX_FILE_BYTES = 20 * 1024 * 1024;
43
76
  var MAX_BATCH_BYTES = 6 * 1024 * 1024;
@@ -130,6 +163,8 @@ function magpie(options = {}) {
130
163
  const env = options.env ?? process.env;
131
164
  let release;
132
165
  let deployed = false;
166
+ let platformReported = false;
167
+ let root = process.cwd();
133
168
  const log = (message) => console.log(`${PREFIX} ${message}`);
134
169
  const warn = (message) => console.warn(`${PREFIX} ${message}`);
135
170
  return {
@@ -143,6 +178,9 @@ function magpie(options = {}) {
143
178
  ...release ? { define: { "import.meta.env.VITE_MAGPIE_RELEASE": JSON.stringify(release) } } : {}
144
179
  };
145
180
  },
181
+ configResolved(config) {
182
+ root = config.root;
183
+ },
146
184
  transformIndexHtml() {
147
185
  if (!release) return void 0;
148
186
  return [{ tag: "script", children: `window.__MAGPIE_RELEASE__=${JSON.stringify(release)};`, injectTo: "head-prepend" }];
@@ -158,6 +196,13 @@ function magpie(options = {}) {
158
196
  if (options.failOnError) throw new Error(`${PREFIX} ${message}`);
159
197
  warn(message);
160
198
  };
199
+ if (dsn && token && options.platform?.enabled !== false && !platformReported) {
200
+ platformReported = true;
201
+ const components = await installedVersions(root);
202
+ const reason = Object.keys(components).length ? await reportPlatform({ origin: dsn.origin, project: dsn.project, token, environment: options.environment ?? env.MAGPIE_ENVIRONMENT ?? "production", components, sdk: { name: "magpie.javascript.vite-plugin" }, fetch: options.fetch }) : "no known packages are installed under the project root";
203
+ if (reason) warn(`The platform was not reported: ${reason}`);
204
+ else log(`Reported the platform (${Object.entries(components).map(([name, version]) => `${name} ${version}`).join(", ")}).`);
205
+ }
161
206
  if (!release) return warn("No release could be worked out (no MAGPIE_RELEASE, CI commit variable or git checkout), so source maps are not uploaded.");
162
207
  if (!dsn) return warn("No DSN (the dsn option or MAGPIE_DSN), so source maps are not uploaded.");
163
208
  if (!token) return log("No MAGPIE_AUTH_TOKEN, so source maps are not uploaded. This is expected for a local build.");
@@ -195,6 +240,6 @@ function parseDsn(dsn) {
195
240
  }
196
241
  var index_default = magpie;
197
242
 
198
- export { index_default as default, magpie, parseDsn, recentCommits, recordRelease, resolveRelease, uploadSourceMaps };
243
+ export { PLATFORM_PACKAGES, index_default as default, installedVersions, magpie, parseDsn, recentCommits, recordRelease, reportPlatform, resolveRelease, uploadSourceMaps };
199
244
  //# sourceMappingURL=index.js.map
200
245
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/release.ts","../src/upload.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { execFileSync } from 'node:child_process';\n\n/** Runs a program with an argument list (no shell, so nothing is ever interpreted) and returns what it printed. */\nexport type Exec = (file: string, args: string[]) => string;\n\nconst defaultExec: Exec = (file, args) => execFileSync(file, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();\n\n/** Environment variables that CI and hosts set to the commit being built, in the order they are tried. */\nconst COMMIT_ENV = [\n 'MAGPIE_RELEASE',\n 'GITHUB_SHA',\n 'CI_COMMIT_SHA',\n 'VERCEL_GIT_COMMIT_SHA',\n 'CF_PAGES_COMMIT_SHA',\n 'COMMIT_REF',\n 'RENDER_GIT_COMMIT',\n 'SOURCE_VERSION',\n 'BITBUCKET_COMMIT',\n 'CIRCLE_SHA1',\n];\n\n/**\n * The version this build is known as, in the order: what you passed, an environment variable a CI or host sets, then\n * the commit of the checkout. The same string must be used when the source maps are uploaded and when the page runs,\n * or Magpie cannot match a stack frame to its map.\n */\nexport function resolveRelease(explicit?: string, env: Record<string, string | undefined> = process.env, exec: Exec = defaultExec): string | undefined {\n if (explicit) return explicit;\n\n for (const name of COMMIT_ENV) {\n if (env[name]) return env[name];\n }\n\n try {\n return exec('git', ['rev-parse', 'HEAD']) || undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface Commit {\n sha: string;\n message: string;\n author: string;\n timestamp: string;\n}\n\n/** The newest commits, newest first, as `git log` lists them. Empty when this is not a git checkout. */\nexport function recentCommits(limit = 50, exec: Exec = defaultExec): Commit[] {\n try {\n const out = exec('git', ['log', '-n', String(Math.max(1, Math.floor(limit))), '--pretty=format:%H%x1f%an%x1f%aI%x1f%s']);\n\n return out\n .split('\\n')\n .filter(Boolean)\n .map((line) => {\n const [sha, author, timestamp, ...message] = line.split('\\u001f');\n\n return { sha, author, timestamp, message: message.join('\\u001f') };\n });\n } catch {\n return [];\n }\n}\n","import { readFile, stat } from 'node:fs/promises';\nimport type { Commit } from './release';\n\n/** Magpie accepts up to 50 maps per request, each up to 20 MB. */\nconst MAX_FILES = 50;\nconst MAX_FILE_BYTES = 20 * 1024 * 1024;\n/** PHP's default `post_max_size` is 8 MB, so a request is kept well under it. A map larger than this goes alone. */\nconst MAX_BATCH_BYTES = 6 * 1024 * 1024;\n\nexport interface MapFile {\n /** The name Magpie files it under: the map's own file name (`app-3f2a1.js.map`). */\n name: string;\n path: string;\n}\n\nexport interface UploadOptions {\n /** `https://host`, no trailing slash. */\n origin: string;\n project: string;\n token: string;\n version: string;\n files: MapFile[];\n fetch?: typeof fetch;\n retries?: number;\n /** Wait between retries, doubled each time. Tests set it to 0. */\n retryDelayMs?: number;\n}\n\nexport interface UploadResult {\n uploaded: MapFile[];\n failed: Array<{ file: MapFile; reason: string }>;\n}\n\n/**\n * Sends source maps to `POST /api/{project}/sourcemaps` in batches that fit Magpie's limits. A batch that fails does\n * not stop the others, and the result says exactly which files made it, so only those are deleted from the build.\n */\nexport async function uploadSourceMaps(options: UploadOptions): Promise<UploadResult> {\n const result: UploadResult = { uploaded: [], failed: [] };\n const sized: Array<MapFile & { bytes: number }> = [];\n\n for (const file of options.files) {\n const bytes = (await stat(file.path)).size;\n if (bytes > MAX_FILE_BYTES) result.failed.push({ file, reason: `larger than 20 MB (${(bytes / 1048576).toFixed(1)} MB), which Magpie refuses` });\n else sized.push({ ...file, bytes });\n }\n\n for (const batch of batches(sized)) {\n const error = await sendBatch(options, batch);\n if (error) for (const file of batch) result.failed.push({ file, reason: error });\n else result.uploaded.push(...batch);\n }\n\n return result;\n}\n\nfunction batches<T extends { bytes: number }>(files: T[]): T[][] {\n const out: T[][] = [];\n let current: T[] = [];\n let size = 0;\n\n for (const file of files) {\n if (current.length && (current.length >= MAX_FILES || size + file.bytes > MAX_BATCH_BYTES)) {\n out.push(current);\n current = [];\n size = 0;\n }\n current.push(file);\n size += file.bytes;\n }\n if (current.length) out.push(current);\n\n return out;\n}\n\n/** @returns undefined on success, or the reason it failed. */\nasync function sendBatch(options: UploadOptions, batch: MapFile[]): Promise<string | undefined> {\n const doFetch = options.fetch ?? fetch;\n const retries = options.retries ?? 2;\n let last = 'unknown error';\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n if (attempt > 0) await sleep((options.retryDelayMs ?? 500) * 2 ** (attempt - 1));\n\n try {\n const form = new FormData();\n form.append('version', options.version);\n for (const file of batch) form.append('maps[]', new Blob([await readFile(file.path)], { type: 'application/json' }), file.name);\n\n const response = await doFetch(`${options.origin}/api/${options.project}/sourcemaps`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${options.token}`, Accept: 'application/json' },\n body: form,\n });\n\n if (response.ok) return undefined;\n\n last = await describeFailure(response);\n // A wrong token, an unknown app or a bad map will not get better by asking again.\n if (response.status < 500) return last;\n } catch (error) {\n last = `could not reach Magpie (${(error as Error).message})`;\n }\n }\n\n return last;\n}\n\nexport interface ReleaseRecord {\n origin: string;\n project: string;\n token: string;\n version: string;\n environment: string;\n deployedBy: string;\n commits: Commit[];\n fetch?: typeof fetch;\n}\n\n/** Tells Magpie a release was deployed to an environment, with its commits (newest first). @returns undefined on success. */\nexport async function recordRelease(record: ReleaseRecord): Promise<string | undefined> {\n try {\n const response = await (record.fetch ?? fetch)(`${record.origin}/api/${record.project}/releases`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${record.token}`, 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({\n version: record.version,\n environment: record.environment,\n deployed_by: record.deployedBy,\n ...(record.commits.length ? { commits: record.commits } : {}),\n }),\n });\n\n return response.ok ? undefined : await describeFailure(response);\n } catch (error) {\n return `could not reach Magpie (${(error as Error).message})`;\n }\n}\n\nasync function describeFailure(response: Response): Promise<string> {\n let detail = '';\n try {\n const body = (await response.json()) as { error?: string; message?: string };\n detail = body.error ?? body.message ?? '';\n } catch {\n // Not JSON: the status says enough.\n }\n\n return `HTTP ${response.status}${detail ? `: ${detail}` : ''}`;\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n","import { rm } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport type { Plugin } from 'vite';\nimport { type Exec, recentCommits, resolveRelease } from './release';\nimport { type MapFile, recordRelease, uploadSourceMaps } from './upload';\n\nexport { recentCommits, resolveRelease } from './release';\nexport { recordRelease, uploadSourceMaps, type MapFile } from './upload';\n\nexport interface MagpiePluginOptions {\n /** The app's DSN (`https://<key>@<host>/<project>`), used to find where to upload. Falls back to `MAGPIE_DSN`. */\n dsn?: string;\n /**\n * An organisation token with the `project:releases` scope (Organisation settings > API tokens). Falls back to\n * `MAGPIE_AUTH_TOKEN`. It is read at build time only and is never put in the bundle. Without one, nothing is uploaded.\n */\n authToken?: string;\n /** The version of this build. Defaults to the commit being built; see `resolveRelease`. */\n release?: string;\n /** Where this build is going, for the deploy record. Defaults to `MAGPIE_ENVIRONMENT`, then \"production\". */\n environment?: string;\n sourcemaps?: {\n /** Default true. */\n enabled?: boolean;\n /** Remove each map from the output once Magpie has it, so it is never served. Default true. */\n deleteAfterUpload?: boolean;\n /** Also upload the maps of the server build (Nuxt's `ssr` environment). Default false. */\n server?: boolean;\n };\n deploy?: {\n /** Record the release, its environment and its commits with Magpie after the upload. Default true. */\n enabled?: boolean;\n deployedBy?: string;\n /** How many recent commits to send. Default 50. */\n commits?: number;\n };\n /** Fail the build when an upload fails. Default false: a Magpie outage should not stop you deploying. */\n failOnError?: boolean;\n disable?: boolean;\n /** @internal Replaced in tests. */\n fetch?: typeof fetch;\n /** @internal Replaced in tests. */\n exec?: Exec;\n /** @internal Replaced in tests. */\n env?: Record<string, string | undefined>;\n}\n\nconst PREFIX = '[magpie]';\n\n/**\n * Makes production stack traces readable. During `vite build` it:\n * - makes the build write source maps without referencing them (`sourcemap: 'hidden'`), unless you chose otherwise;\n * - puts the release on the page, as `import.meta.env.VITE_MAGPIE_RELEASE` and `window.__MAGPIE_RELEASE__`;\n * - uploads the maps to Magpie under that release, and deletes them from the output once Magpie has them;\n * - records the release, the environment it went to and its recent commits.\n */\nexport function magpie(options: MagpiePluginOptions = {}): Plugin {\n const env = options.env ?? process.env;\n let release: string | undefined;\n let deployed = false;\n\n const log = (message: string) => console.log(`${PREFIX} ${message}`);\n const warn = (message: string) => console.warn(`${PREFIX} ${message}`);\n\n return {\n name: 'magpie',\n apply: 'build',\n\n config(config) {\n release = resolveRelease(options.release, env, options.exec);\n\n return {\n // Hidden: the map is written but the bundle does not point at it, so visitors cannot fetch your source.\n ...(config.build?.sourcemap === undefined && options.sourcemaps?.enabled !== false ? { build: { sourcemap: 'hidden' as const } } : {}),\n ...(release ? { define: { 'import.meta.env.VITE_MAGPIE_RELEASE': JSON.stringify(release) } } : {}),\n };\n },\n\n transformIndexHtml() {\n if (!release) return undefined;\n\n return [{ tag: 'script', children: `window.__MAGPIE_RELEASE__=${JSON.stringify(release)};`, injectTo: 'head-prepend' as const }];\n },\n\n async writeBundle(outputOptions, bundle) {\n if (options.disable) return;\n release ??= resolveRelease(options.release, env, options.exec);\n\n // Nuxt and other SSR setups build twice. The browser build is the one whose stack traces need maps.\n const environment = (this as { environment?: { name?: string } }).environment?.name ?? 'client';\n if (environment !== 'client' && !options.sourcemaps?.server) return;\n\n const token = options.authToken ?? env.MAGPIE_AUTH_TOKEN;\n const dsn = parseDsn(options.dsn ?? env.MAGPIE_DSN);\n const fail = (message: string) => {\n if (options.failOnError) throw new Error(`${PREFIX} ${message}`);\n warn(message);\n };\n\n if (!release) return warn('No release could be worked out (no MAGPIE_RELEASE, CI commit variable or git checkout), so source maps are not uploaded.');\n if (!dsn) return warn('No DSN (the dsn option or MAGPIE_DSN), so source maps are not uploaded.');\n if (!token) return log('No MAGPIE_AUTH_TOKEN, so source maps are not uploaded. This is expected for a local build.');\n\n const dir = outputOptions.dir ?? '.';\n const files: MapFile[] = Object.keys(bundle)\n .filter((name) => name.endsWith('.map'))\n .map((name) => ({ name: basename(name), path: join(dir, name) }));\n\n if (options.sourcemaps?.enabled !== false && files.length) {\n const result = await uploadSourceMaps({ origin: dsn.origin, project: dsn.project, token, version: release, files, fetch: options.fetch });\n\n log(`Uploaded ${result.uploaded.length} of ${files.length} source maps for ${release}.`);\n for (const { file, reason } of result.failed) fail(`${file.name} was not uploaded: ${reason}`);\n\n // Only what Magpie actually has is removed, so a failed upload never costs you the map.\n if (options.sourcemaps?.deleteAfterUpload !== false) await Promise.all(result.uploaded.map((f) => rm(f.path, { force: true })));\n } else if (options.sourcemaps?.enabled !== false) {\n warn('The build produced no source maps. Check that build.sourcemap is not false.');\n }\n\n if (options.deploy?.enabled !== false && !deployed) {\n deployed = true;\n const reason = await recordRelease({\n origin: dsn.origin,\n project: dsn.project,\n token,\n version: release,\n environment: options.environment ?? env.MAGPIE_ENVIRONMENT ?? 'production',\n deployedBy: options.deploy?.deployedBy ?? env.GITHUB_ACTOR ?? env.GITLAB_USER_LOGIN ?? 'build',\n commits: recentCommits(options.deploy?.commits ?? 50, options.exec),\n fetch: options.fetch,\n });\n\n if (reason) fail(`The release was not recorded: ${reason}`);\n else log(`Recorded release ${release} for ${options.environment ?? env.MAGPIE_ENVIRONMENT ?? 'production'}.`);\n }\n },\n };\n}\n\nexport function parseDsn(dsn: string | undefined): { origin: string; project: string } | null {\n const m = /^(https?):\\/\\/[^@/\\s:]+@([^/\\s]+)\\/(\\d+)\\/?$/i.exec((dsn ?? '').trim());\n\n return m ? { origin: `${m[1].toLowerCase()}://${m[2]}`, project: m[3] } : null;\n}\n\nexport default magpie;\n"]}
1
+ {"version":3,"sources":["../src/release.ts","../src/platform.ts","../src/upload.ts","../src/index.ts"],"names":["readFile","join"],"mappings":"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","file":"index.js","sourcesContent":["import { execFileSync } from 'node:child_process';\n\n/** Runs a program with an argument list (no shell, so nothing is ever interpreted) and returns what it printed. */\nexport type Exec = (file: string, args: string[]) => string;\n\nconst defaultExec: Exec = (file, args) => execFileSync(file, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();\n\n/** Environment variables that CI and hosts set to the commit being built, in the order they are tried. */\nconst COMMIT_ENV = [\n 'MAGPIE_RELEASE',\n 'GITHUB_SHA',\n 'CI_COMMIT_SHA',\n 'VERCEL_GIT_COMMIT_SHA',\n 'CF_PAGES_COMMIT_SHA',\n 'COMMIT_REF',\n 'RENDER_GIT_COMMIT',\n 'SOURCE_VERSION',\n 'BITBUCKET_COMMIT',\n 'CIRCLE_SHA1',\n];\n\n/**\n * The version this build is known as, in the order: what you passed, an environment variable a CI or host sets, then\n * the commit of the checkout. The same string must be used when the source maps are uploaded and when the page runs,\n * or Magpie cannot match a stack frame to its map.\n */\nexport function resolveRelease(explicit?: string, env: Record<string, string | undefined> = process.env, exec: Exec = defaultExec): string | undefined {\n if (explicit) return explicit;\n\n for (const name of COMMIT_ENV) {\n if (env[name]) return env[name];\n }\n\n try {\n return exec('git', ['rev-parse', 'HEAD']) || undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface Commit {\n sha: string;\n message: string;\n author: string;\n timestamp: string;\n}\n\n/** The newest commits, newest first, as `git log` lists them. Empty when this is not a git checkout. */\nexport function recentCommits(limit = 50, exec: Exec = defaultExec): Commit[] {\n try {\n const out = exec('git', ['log', '-n', String(Math.max(1, Math.floor(limit))), '--pretty=format:%H%x1f%an%x1f%aI%x1f%s']);\n\n return out\n .split('\\n')\n .filter(Boolean)\n .map((line) => {\n const [sha, author, timestamp, ...message] = line.split('\\u001f');\n\n return { sha, author, timestamp, message: message.join('\\u001f') };\n });\n } catch {\n return [];\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\n/** Packages whose installed version Magpie shows, in the order they are listed. Names and versions only. */\nexport const PLATFORM_PACKAGES = ['nuxt', 'vue', 'vue-router', 'pinia', 'react', 'next', 'svelte', '@sveltejs/kit', 'vite', 'tailwindcss', 'typescript'] as const;\n\n/** The version of each known package installed under `root`, keyed by the name Magpie shows (`@sveltejs/kit` is `sveltekit`). */\nexport async function installedVersions(root: string, packages: readonly string[] = PLATFORM_PACKAGES): Promise<Record<string, string>> {\n const found: Record<string, string> = {};\n\n await Promise.all(\n packages.map(async (name) => {\n try {\n const { version } = JSON.parse(await readFile(join(root, 'node_modules', name, 'package.json'), 'utf8')) as { version?: unknown };\n if (typeof version === 'string' && version) found[name === '@sveltejs/kit' ? 'sveltekit' : name] = version;\n } catch {\n // Not installed here: it is simply not part of this app.\n }\n }),\n );\n\n return Object.fromEntries(Object.entries(found).sort(([a], [b]) => a.localeCompare(b)));\n}\n\nexport interface PlatformReport {\n /** `https://host`, no trailing slash. */\n origin: string;\n project: string;\n token: string;\n environment: string;\n components: Record<string, string>;\n sdk?: { name: string; version?: string };\n fetch?: typeof fetch;\n}\n\n/**\n * Tells Magpie what the app is built with, to `POST /api/{project}/platform`. Returns why it failed, or undefined when\n * Magpie has it. Versions only: nothing from the app's configuration or environment is sent.\n */\nexport async function reportPlatform(report: PlatformReport): Promise<string | undefined> {\n try {\n const response = await (report.fetch ?? fetch)(`${report.origin}/api/${report.project}/platform`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${report.token}`, 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({ environment: report.environment, components: report.components, ...(report.sdk ? { sdk: report.sdk } : {}) }),\n });\n\n if (response.ok) return undefined;\n\n let detail = '';\n try {\n const body = (await response.json()) as { error?: string; message?: string };\n detail = body.error ?? body.message ?? '';\n } catch {\n // Not JSON: the status says enough.\n }\n\n return `HTTP ${response.status}${detail ? `: ${detail}` : ''}`;\n } catch (error) {\n return `could not reach Magpie (${(error as Error).message})`;\n }\n}\n","import { readFile, stat } from 'node:fs/promises';\nimport type { Commit } from './release';\n\n/** Magpie accepts up to 50 maps per request, each up to 20 MB. */\nconst MAX_FILES = 50;\nconst MAX_FILE_BYTES = 20 * 1024 * 1024;\n/** PHP's default `post_max_size` is 8 MB, so a request is kept well under it. A map larger than this goes alone. */\nconst MAX_BATCH_BYTES = 6 * 1024 * 1024;\n\nexport interface MapFile {\n /** The name Magpie files it under: the map's own file name (`app-3f2a1.js.map`). */\n name: string;\n path: string;\n}\n\nexport interface UploadOptions {\n /** `https://host`, no trailing slash. */\n origin: string;\n project: string;\n token: string;\n version: string;\n files: MapFile[];\n fetch?: typeof fetch;\n retries?: number;\n /** Wait between retries, doubled each time. Tests set it to 0. */\n retryDelayMs?: number;\n}\n\nexport interface UploadResult {\n uploaded: MapFile[];\n failed: Array<{ file: MapFile; reason: string }>;\n}\n\n/**\n * Sends source maps to `POST /api/{project}/sourcemaps` in batches that fit Magpie's limits. A batch that fails does\n * not stop the others, and the result says exactly which files made it, so only those are deleted from the build.\n */\nexport async function uploadSourceMaps(options: UploadOptions): Promise<UploadResult> {\n const result: UploadResult = { uploaded: [], failed: [] };\n const sized: Array<MapFile & { bytes: number }> = [];\n\n for (const file of options.files) {\n const bytes = (await stat(file.path)).size;\n if (bytes > MAX_FILE_BYTES) result.failed.push({ file, reason: `larger than 20 MB (${(bytes / 1048576).toFixed(1)} MB), which Magpie refuses` });\n else sized.push({ ...file, bytes });\n }\n\n for (const batch of batches(sized)) {\n const error = await sendBatch(options, batch);\n if (error) for (const file of batch) result.failed.push({ file, reason: error });\n else result.uploaded.push(...batch);\n }\n\n return result;\n}\n\nfunction batches<T extends { bytes: number }>(files: T[]): T[][] {\n const out: T[][] = [];\n let current: T[] = [];\n let size = 0;\n\n for (const file of files) {\n if (current.length && (current.length >= MAX_FILES || size + file.bytes > MAX_BATCH_BYTES)) {\n out.push(current);\n current = [];\n size = 0;\n }\n current.push(file);\n size += file.bytes;\n }\n if (current.length) out.push(current);\n\n return out;\n}\n\n/** @returns undefined on success, or the reason it failed. */\nasync function sendBatch(options: UploadOptions, batch: MapFile[]): Promise<string | undefined> {\n const doFetch = options.fetch ?? fetch;\n const retries = options.retries ?? 2;\n let last = 'unknown error';\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n if (attempt > 0) await sleep((options.retryDelayMs ?? 500) * 2 ** (attempt - 1));\n\n try {\n const form = new FormData();\n form.append('version', options.version);\n for (const file of batch) form.append('maps[]', new Blob([await readFile(file.path)], { type: 'application/json' }), file.name);\n\n const response = await doFetch(`${options.origin}/api/${options.project}/sourcemaps`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${options.token}`, Accept: 'application/json' },\n body: form,\n });\n\n if (response.ok) return undefined;\n\n last = await describeFailure(response);\n // A wrong token, an unknown app or a bad map will not get better by asking again.\n if (response.status < 500) return last;\n } catch (error) {\n last = `could not reach Magpie (${(error as Error).message})`;\n }\n }\n\n return last;\n}\n\nexport interface ReleaseRecord {\n origin: string;\n project: string;\n token: string;\n version: string;\n environment: string;\n deployedBy: string;\n commits: Commit[];\n fetch?: typeof fetch;\n}\n\n/** Tells Magpie a release was deployed to an environment, with its commits (newest first). @returns undefined on success. */\nexport async function recordRelease(record: ReleaseRecord): Promise<string | undefined> {\n try {\n const response = await (record.fetch ?? fetch)(`${record.origin}/api/${record.project}/releases`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${record.token}`, 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({\n version: record.version,\n environment: record.environment,\n deployed_by: record.deployedBy,\n ...(record.commits.length ? { commits: record.commits } : {}),\n }),\n });\n\n return response.ok ? undefined : await describeFailure(response);\n } catch (error) {\n return `could not reach Magpie (${(error as Error).message})`;\n }\n}\n\nasync function describeFailure(response: Response): Promise<string> {\n let detail = '';\n try {\n const body = (await response.json()) as { error?: string; message?: string };\n detail = body.error ?? body.message ?? '';\n } catch {\n // Not JSON: the status says enough.\n }\n\n return `HTTP ${response.status}${detail ? `: ${detail}` : ''}`;\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n","import { rm } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport type { Plugin } from 'vite';\nimport { type Exec, recentCommits, resolveRelease } from './release';\nimport { installedVersions, reportPlatform } from './platform';\nimport { type MapFile, recordRelease, uploadSourceMaps } from './upload';\n\nexport { recentCommits, resolveRelease } from './release';\nexport { installedVersions, PLATFORM_PACKAGES, reportPlatform } from './platform';\nexport { recordRelease, uploadSourceMaps, type MapFile } from './upload';\n\nexport interface MagpiePluginOptions {\n /** The app's DSN (`https://<key>@<host>/<project>`), used to find where to upload. Falls back to `MAGPIE_DSN`. */\n dsn?: string;\n /**\n * An organisation token with the `project:releases` scope (Organisation settings > API tokens). Falls back to\n * `MAGPIE_AUTH_TOKEN`. It is read at build time only and is never put in the bundle. Without one, nothing is uploaded.\n */\n authToken?: string;\n /** The version of this build. Defaults to the commit being built; see `resolveRelease`. */\n release?: string;\n /** Where this build is going, for the deploy record. Defaults to `MAGPIE_ENVIRONMENT`, then \"production\". */\n environment?: string;\n sourcemaps?: {\n /** Default true. */\n enabled?: boolean;\n /** Remove each map from the output once Magpie has it, so it is never served. Default true. */\n deleteAfterUpload?: boolean;\n /** Also upload the maps of the server build (Nuxt's `ssr` environment). Default false. */\n server?: boolean;\n };\n deploy?: {\n /** Record the release, its environment and its commits with Magpie after the upload. Default true. */\n enabled?: boolean;\n deployedBy?: string;\n /** How many recent commits to send. Default 50. */\n commits?: number;\n };\n platform?: {\n /**\n * Tell Magpie which versions of Vue, Nuxt, Vite and the like this app is built with, so every project's platform\n * is listed in one place. Versions only. Needs the auth token. Default true.\n */\n enabled?: boolean;\n };\n /** Fail the build when an upload fails. Default false: a Magpie outage should not stop you deploying. */\n failOnError?: boolean;\n disable?: boolean;\n /** @internal Replaced in tests. */\n fetch?: typeof fetch;\n /** @internal Replaced in tests. */\n exec?: Exec;\n /** @internal Replaced in tests. */\n env?: Record<string, string | undefined>;\n}\n\nconst PREFIX = '[magpie]';\n\n/**\n * Makes production stack traces readable. During `vite build` it:\n * - makes the build write source maps without referencing them (`sourcemap: 'hidden'`), unless you chose otherwise;\n * - puts the release on the page, as `import.meta.env.VITE_MAGPIE_RELEASE` and `window.__MAGPIE_RELEASE__`;\n * - uploads the maps to Magpie under that release, and deletes them from the output once Magpie has them;\n * - records the release, the environment it went to and its recent commits.\n */\nexport function magpie(options: MagpiePluginOptions = {}): Plugin {\n const env = options.env ?? process.env;\n let release: string | undefined;\n let deployed = false;\n let platformReported = false;\n let root = process.cwd();\n\n const log = (message: string) => console.log(`${PREFIX} ${message}`);\n const warn = (message: string) => console.warn(`${PREFIX} ${message}`);\n\n return {\n name: 'magpie',\n apply: 'build',\n\n config(config) {\n release = resolveRelease(options.release, env, options.exec);\n\n return {\n // Hidden: the map is written but the bundle does not point at it, so visitors cannot fetch your source.\n ...(config.build?.sourcemap === undefined && options.sourcemaps?.enabled !== false ? { build: { sourcemap: 'hidden' as const } } : {}),\n ...(release ? { define: { 'import.meta.env.VITE_MAGPIE_RELEASE': JSON.stringify(release) } } : {}),\n };\n },\n\n configResolved(config) {\n root = config.root;\n },\n\n transformIndexHtml() {\n if (!release) return undefined;\n\n return [{ tag: 'script', children: `window.__MAGPIE_RELEASE__=${JSON.stringify(release)};`, injectTo: 'head-prepend' as const }];\n },\n\n async writeBundle(outputOptions, bundle) {\n if (options.disable) return;\n release ??= resolveRelease(options.release, env, options.exec);\n\n // Nuxt and other SSR setups build twice. The browser build is the one whose stack traces need maps.\n const environment = (this as { environment?: { name?: string } }).environment?.name ?? 'client';\n if (environment !== 'client' && !options.sourcemaps?.server) return;\n\n const token = options.authToken ?? env.MAGPIE_AUTH_TOKEN;\n const dsn = parseDsn(options.dsn ?? env.MAGPIE_DSN);\n const fail = (message: string) => {\n if (options.failOnError) throw new Error(`${PREFIX} ${message}`);\n warn(message);\n };\n\n if (dsn && token && options.platform?.enabled !== false && !platformReported) {\n platformReported = true;\n const components = await installedVersions(root);\n const reason = Object.keys(components).length\n ? await reportPlatform({ origin: dsn.origin, project: dsn.project, token, environment: options.environment ?? env.MAGPIE_ENVIRONMENT ?? 'production', components, sdk: { name: 'magpie.javascript.vite-plugin' }, fetch: options.fetch })\n : 'no known packages are installed under the project root';\n\n if (reason) warn(`The platform was not reported: ${reason}`);\n else log(`Reported the platform (${Object.entries(components).map(([name, version]) => `${name} ${version}`).join(', ')}).`);\n }\n\n if (!release) return warn('No release could be worked out (no MAGPIE_RELEASE, CI commit variable or git checkout), so source maps are not uploaded.');\n if (!dsn) return warn('No DSN (the dsn option or MAGPIE_DSN), so source maps are not uploaded.');\n if (!token) return log('No MAGPIE_AUTH_TOKEN, so source maps are not uploaded. This is expected for a local build.');\n\n const dir = outputOptions.dir ?? '.';\n const files: MapFile[] = Object.keys(bundle)\n .filter((name) => name.endsWith('.map'))\n .map((name) => ({ name: basename(name), path: join(dir, name) }));\n\n if (options.sourcemaps?.enabled !== false && files.length) {\n const result = await uploadSourceMaps({ origin: dsn.origin, project: dsn.project, token, version: release, files, fetch: options.fetch });\n\n log(`Uploaded ${result.uploaded.length} of ${files.length} source maps for ${release}.`);\n for (const { file, reason } of result.failed) fail(`${file.name} was not uploaded: ${reason}`);\n\n // Only what Magpie actually has is removed, so a failed upload never costs you the map.\n if (options.sourcemaps?.deleteAfterUpload !== false) await Promise.all(result.uploaded.map((f) => rm(f.path, { force: true })));\n } else if (options.sourcemaps?.enabled !== false) {\n warn('The build produced no source maps. Check that build.sourcemap is not false.');\n }\n\n if (options.deploy?.enabled !== false && !deployed) {\n deployed = true;\n const reason = await recordRelease({\n origin: dsn.origin,\n project: dsn.project,\n token,\n version: release,\n environment: options.environment ?? env.MAGPIE_ENVIRONMENT ?? 'production',\n deployedBy: options.deploy?.deployedBy ?? env.GITHUB_ACTOR ?? env.GITLAB_USER_LOGIN ?? 'build',\n commits: recentCommits(options.deploy?.commits ?? 50, options.exec),\n fetch: options.fetch,\n });\n\n if (reason) fail(`The release was not recorded: ${reason}`);\n else log(`Recorded release ${release} for ${options.environment ?? env.MAGPIE_ENVIRONMENT ?? 'production'}.`);\n }\n },\n };\n}\n\nexport function parseDsn(dsn: string | undefined): { origin: string; project: string } | null {\n const m = /^(https?):\\/\\/[^@/\\s:]+@([^/\\s]+)\\/(\\d+)\\/?$/i.exec((dsn ?? '').trim());\n\n return m ? { origin: `${m[1].toLowerCase()}://${m[2]}`, project: m[3] } : null;\n}\n\nexport default magpie;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greenhat-services/magpie-vite-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Uploads source maps to Magpie, records the release and its commits, and injects the release into your Vite build.",
5
5
  "license": "MIT",
6
6
  "type": "module",