@greenhat-services/magpie-vite-plugin 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +21 -2
- package/dist/index.cjs +258 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +142 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.js +245 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Magpie contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @greenhat-services/magpie-vite-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Uploads source maps to Magpie, records the release and its commits, and injects the release into your Vite build.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install -D @greenhat-services/magpie-vite-plugin
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
// vite.config.ts
|
|
11
|
+
import { magpie } from '@greenhat-services/magpie-vite-plugin';
|
|
12
|
+
|
|
13
|
+
export default defineConfig({ plugins: [vue(), magpie()] });
|
|
14
|
+
```
|
|
15
|
+
|
|
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
|
+
|
|
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
|
+
|
|
20
|
+
Full documentation, options and what is (and is not) sent: https://github.com/Greenhat-Services/magpie-js#readme
|
|
21
|
+
|
|
22
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var promises = require('fs/promises');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
var child_process = require('child_process');
|
|
8
|
+
|
|
9
|
+
// src/index.ts
|
|
10
|
+
var defaultExec = (file, args) => child_process.execFileSync(file, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
11
|
+
var COMMIT_ENV = [
|
|
12
|
+
"MAGPIE_RELEASE",
|
|
13
|
+
"GITHUB_SHA",
|
|
14
|
+
"CI_COMMIT_SHA",
|
|
15
|
+
"VERCEL_GIT_COMMIT_SHA",
|
|
16
|
+
"CF_PAGES_COMMIT_SHA",
|
|
17
|
+
"COMMIT_REF",
|
|
18
|
+
"RENDER_GIT_COMMIT",
|
|
19
|
+
"SOURCE_VERSION",
|
|
20
|
+
"BITBUCKET_COMMIT",
|
|
21
|
+
"CIRCLE_SHA1"
|
|
22
|
+
];
|
|
23
|
+
function resolveRelease(explicit, env = process.env, exec = defaultExec) {
|
|
24
|
+
if (explicit) return explicit;
|
|
25
|
+
for (const name of COMMIT_ENV) {
|
|
26
|
+
if (env[name]) return env[name];
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
return exec("git", ["rev-parse", "HEAD"]) || void 0;
|
|
30
|
+
} catch {
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function recentCommits(limit = 50, exec = defaultExec) {
|
|
35
|
+
try {
|
|
36
|
+
const out = exec("git", ["log", "-n", String(Math.max(1, Math.floor(limit))), "--pretty=format:%H%x1f%an%x1f%aI%x1f%s"]);
|
|
37
|
+
return out.split("\n").filter(Boolean).map((line) => {
|
|
38
|
+
const [sha, author, timestamp, ...message] = line.split("");
|
|
39
|
+
return { sha, author, timestamp, message: message.join("") };
|
|
40
|
+
});
|
|
41
|
+
} catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
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
|
+
}
|
|
78
|
+
var MAX_FILES = 50;
|
|
79
|
+
var MAX_FILE_BYTES = 20 * 1024 * 1024;
|
|
80
|
+
var MAX_BATCH_BYTES = 6 * 1024 * 1024;
|
|
81
|
+
async function uploadSourceMaps(options) {
|
|
82
|
+
const result = { uploaded: [], failed: [] };
|
|
83
|
+
const sized = [];
|
|
84
|
+
for (const file of options.files) {
|
|
85
|
+
const bytes = (await promises.stat(file.path)).size;
|
|
86
|
+
if (bytes > MAX_FILE_BYTES) result.failed.push({ file, reason: `larger than 20 MB (${(bytes / 1048576).toFixed(1)} MB), which Magpie refuses` });
|
|
87
|
+
else sized.push({ ...file, bytes });
|
|
88
|
+
}
|
|
89
|
+
for (const batch of batches(sized)) {
|
|
90
|
+
const error = await sendBatch(options, batch);
|
|
91
|
+
if (error) for (const file of batch) result.failed.push({ file, reason: error });
|
|
92
|
+
else result.uploaded.push(...batch);
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
function batches(files) {
|
|
97
|
+
const out = [];
|
|
98
|
+
let current = [];
|
|
99
|
+
let size = 0;
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
if (current.length && (current.length >= MAX_FILES || size + file.bytes > MAX_BATCH_BYTES)) {
|
|
102
|
+
out.push(current);
|
|
103
|
+
current = [];
|
|
104
|
+
size = 0;
|
|
105
|
+
}
|
|
106
|
+
current.push(file);
|
|
107
|
+
size += file.bytes;
|
|
108
|
+
}
|
|
109
|
+
if (current.length) out.push(current);
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
async function sendBatch(options, batch) {
|
|
113
|
+
const doFetch = options.fetch ?? fetch;
|
|
114
|
+
const retries = options.retries ?? 2;
|
|
115
|
+
let last = "unknown error";
|
|
116
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
117
|
+
if (attempt > 0) await sleep((options.retryDelayMs ?? 500) * 2 ** (attempt - 1));
|
|
118
|
+
try {
|
|
119
|
+
const form = new FormData();
|
|
120
|
+
form.append("version", options.version);
|
|
121
|
+
for (const file of batch) form.append("maps[]", new Blob([await promises.readFile(file.path)], { type: "application/json" }), file.name);
|
|
122
|
+
const response = await doFetch(`${options.origin}/api/${options.project}/sourcemaps`, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: { Authorization: `Bearer ${options.token}`, Accept: "application/json" },
|
|
125
|
+
body: form
|
|
126
|
+
});
|
|
127
|
+
if (response.ok) return void 0;
|
|
128
|
+
last = await describeFailure(response);
|
|
129
|
+
if (response.status < 500) return last;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
last = `could not reach Magpie (${error.message})`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return last;
|
|
135
|
+
}
|
|
136
|
+
async function recordRelease(record) {
|
|
137
|
+
try {
|
|
138
|
+
const response = await (record.fetch ?? fetch)(`${record.origin}/api/${record.project}/releases`, {
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers: { Authorization: `Bearer ${record.token}`, "Content-Type": "application/json", Accept: "application/json" },
|
|
141
|
+
body: JSON.stringify({
|
|
142
|
+
version: record.version,
|
|
143
|
+
environment: record.environment,
|
|
144
|
+
deployed_by: record.deployedBy,
|
|
145
|
+
...record.commits.length ? { commits: record.commits } : {}
|
|
146
|
+
})
|
|
147
|
+
});
|
|
148
|
+
return response.ok ? void 0 : await describeFailure(response);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
return `could not reach Magpie (${error.message})`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function describeFailure(response) {
|
|
154
|
+
let detail = "";
|
|
155
|
+
try {
|
|
156
|
+
const body = await response.json();
|
|
157
|
+
detail = body.error ?? body.message ?? "";
|
|
158
|
+
} catch {
|
|
159
|
+
}
|
|
160
|
+
return `HTTP ${response.status}${detail ? `: ${detail}` : ""}`;
|
|
161
|
+
}
|
|
162
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
163
|
+
|
|
164
|
+
// src/index.ts
|
|
165
|
+
var PREFIX = "[magpie]";
|
|
166
|
+
function magpie(options = {}) {
|
|
167
|
+
const env = options.env ?? process.env;
|
|
168
|
+
let release;
|
|
169
|
+
let deployed = false;
|
|
170
|
+
let platformReported = false;
|
|
171
|
+
let root = process.cwd();
|
|
172
|
+
const log = (message) => console.log(`${PREFIX} ${message}`);
|
|
173
|
+
const warn = (message) => console.warn(`${PREFIX} ${message}`);
|
|
174
|
+
return {
|
|
175
|
+
name: "magpie",
|
|
176
|
+
apply: "build",
|
|
177
|
+
config(config) {
|
|
178
|
+
release = resolveRelease(options.release, env, options.exec);
|
|
179
|
+
return {
|
|
180
|
+
// Hidden: the map is written but the bundle does not point at it, so visitors cannot fetch your source.
|
|
181
|
+
...config.build?.sourcemap === void 0 && options.sourcemaps?.enabled !== false ? { build: { sourcemap: "hidden" } } : {},
|
|
182
|
+
...release ? { define: { "import.meta.env.VITE_MAGPIE_RELEASE": JSON.stringify(release) } } : {}
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
configResolved(config) {
|
|
186
|
+
root = config.root;
|
|
187
|
+
},
|
|
188
|
+
transformIndexHtml() {
|
|
189
|
+
if (!release) return void 0;
|
|
190
|
+
return [{ tag: "script", children: `window.__MAGPIE_RELEASE__=${JSON.stringify(release)};`, injectTo: "head-prepend" }];
|
|
191
|
+
},
|
|
192
|
+
async writeBundle(outputOptions, bundle) {
|
|
193
|
+
if (options.disable) return;
|
|
194
|
+
release ??= resolveRelease(options.release, env, options.exec);
|
|
195
|
+
const environment = this.environment?.name ?? "client";
|
|
196
|
+
if (environment !== "client" && !options.sourcemaps?.server) return;
|
|
197
|
+
const token = options.authToken ?? env.MAGPIE_AUTH_TOKEN;
|
|
198
|
+
const dsn = parseDsn(options.dsn ?? env.MAGPIE_DSN);
|
|
199
|
+
const fail = (message) => {
|
|
200
|
+
if (options.failOnError) throw new Error(`${PREFIX} ${message}`);
|
|
201
|
+
warn(message);
|
|
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
|
+
}
|
|
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.");
|
|
211
|
+
if (!dsn) return warn("No DSN (the dsn option or MAGPIE_DSN), so source maps are not uploaded.");
|
|
212
|
+
if (!token) return log("No MAGPIE_AUTH_TOKEN, so source maps are not uploaded. This is expected for a local build.");
|
|
213
|
+
const dir = outputOptions.dir ?? ".";
|
|
214
|
+
const files = Object.keys(bundle).filter((name) => name.endsWith(".map")).map((name) => ({ name: path.basename(name), path: path.join(dir, name) }));
|
|
215
|
+
if (options.sourcemaps?.enabled !== false && files.length) {
|
|
216
|
+
const result = await uploadSourceMaps({ origin: dsn.origin, project: dsn.project, token, version: release, files, fetch: options.fetch });
|
|
217
|
+
log(`Uploaded ${result.uploaded.length} of ${files.length} source maps for ${release}.`);
|
|
218
|
+
for (const { file, reason } of result.failed) fail(`${file.name} was not uploaded: ${reason}`);
|
|
219
|
+
if (options.sourcemaps?.deleteAfterUpload !== false) await Promise.all(result.uploaded.map((f) => promises.rm(f.path, { force: true })));
|
|
220
|
+
} else if (options.sourcemaps?.enabled !== false) {
|
|
221
|
+
warn("The build produced no source maps. Check that build.sourcemap is not false.");
|
|
222
|
+
}
|
|
223
|
+
if (options.deploy?.enabled !== false && !deployed) {
|
|
224
|
+
deployed = true;
|
|
225
|
+
const reason = await recordRelease({
|
|
226
|
+
origin: dsn.origin,
|
|
227
|
+
project: dsn.project,
|
|
228
|
+
token,
|
|
229
|
+
version: release,
|
|
230
|
+
environment: options.environment ?? env.MAGPIE_ENVIRONMENT ?? "production",
|
|
231
|
+
deployedBy: options.deploy?.deployedBy ?? env.GITHUB_ACTOR ?? env.GITLAB_USER_LOGIN ?? "build",
|
|
232
|
+
commits: recentCommits(options.deploy?.commits ?? 50, options.exec),
|
|
233
|
+
fetch: options.fetch
|
|
234
|
+
});
|
|
235
|
+
if (reason) fail(`The release was not recorded: ${reason}`);
|
|
236
|
+
else log(`Recorded release ${release} for ${options.environment ?? env.MAGPIE_ENVIRONMENT ?? "production"}.`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function parseDsn(dsn) {
|
|
242
|
+
const m = /^(https?):\/\/[^@/\s:]+@([^/\s]+)\/(\d+)\/?$/i.exec((dsn ?? "").trim());
|
|
243
|
+
return m ? { origin: `${m[1].toLowerCase()}://${m[2]}`, project: m[3] } : null;
|
|
244
|
+
}
|
|
245
|
+
var index_default = magpie;
|
|
246
|
+
|
|
247
|
+
exports.PLATFORM_PACKAGES = PLATFORM_PACKAGES;
|
|
248
|
+
exports.default = index_default;
|
|
249
|
+
exports.installedVersions = installedVersions;
|
|
250
|
+
exports.magpie = magpie;
|
|
251
|
+
exports.parseDsn = parseDsn;
|
|
252
|
+
exports.recentCommits = recentCommits;
|
|
253
|
+
exports.recordRelease = recordRelease;
|
|
254
|
+
exports.reportPlatform = reportPlatform;
|
|
255
|
+
exports.resolveRelease = resolveRelease;
|
|
256
|
+
exports.uploadSourceMaps = uploadSourceMaps;
|
|
257
|
+
//# sourceMappingURL=index.cjs.map
|
|
258
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
/** Runs a program with an argument list (no shell, so nothing is ever interpreted) and returns what it printed. */
|
|
4
|
+
type Exec = (file: string, args: string[]) => string;
|
|
5
|
+
/**
|
|
6
|
+
* The version this build is known as, in the order: what you passed, an environment variable a CI or host sets, then
|
|
7
|
+
* the commit of the checkout. The same string must be used when the source maps are uploaded and when the page runs,
|
|
8
|
+
* or Magpie cannot match a stack frame to its map.
|
|
9
|
+
*/
|
|
10
|
+
declare function resolveRelease(explicit?: string, env?: Record<string, string | undefined>, exec?: Exec): string | undefined;
|
|
11
|
+
interface Commit {
|
|
12
|
+
sha: string;
|
|
13
|
+
message: string;
|
|
14
|
+
author: string;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
}
|
|
17
|
+
/** The newest commits, newest first, as `git log` lists them. Empty when this is not a git checkout. */
|
|
18
|
+
declare function recentCommits(limit?: number, exec?: Exec): Commit[];
|
|
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
|
+
|
|
43
|
+
interface MapFile {
|
|
44
|
+
/** The name Magpie files it under: the map's own file name (`app-3f2a1.js.map`). */
|
|
45
|
+
name: string;
|
|
46
|
+
path: string;
|
|
47
|
+
}
|
|
48
|
+
interface UploadOptions {
|
|
49
|
+
/** `https://host`, no trailing slash. */
|
|
50
|
+
origin: string;
|
|
51
|
+
project: string;
|
|
52
|
+
token: string;
|
|
53
|
+
version: string;
|
|
54
|
+
files: MapFile[];
|
|
55
|
+
fetch?: typeof fetch;
|
|
56
|
+
retries?: number;
|
|
57
|
+
/** Wait between retries, doubled each time. Tests set it to 0. */
|
|
58
|
+
retryDelayMs?: number;
|
|
59
|
+
}
|
|
60
|
+
interface UploadResult {
|
|
61
|
+
uploaded: MapFile[];
|
|
62
|
+
failed: Array<{
|
|
63
|
+
file: MapFile;
|
|
64
|
+
reason: string;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Sends source maps to `POST /api/{project}/sourcemaps` in batches that fit Magpie's limits. A batch that fails does
|
|
69
|
+
* not stop the others, and the result says exactly which files made it, so only those are deleted from the build.
|
|
70
|
+
*/
|
|
71
|
+
declare function uploadSourceMaps(options: UploadOptions): Promise<UploadResult>;
|
|
72
|
+
interface ReleaseRecord {
|
|
73
|
+
origin: string;
|
|
74
|
+
project: string;
|
|
75
|
+
token: string;
|
|
76
|
+
version: string;
|
|
77
|
+
environment: string;
|
|
78
|
+
deployedBy: string;
|
|
79
|
+
commits: Commit[];
|
|
80
|
+
fetch?: typeof fetch;
|
|
81
|
+
}
|
|
82
|
+
/** Tells Magpie a release was deployed to an environment, with its commits (newest first). @returns undefined on success. */
|
|
83
|
+
declare function recordRelease(record: ReleaseRecord): Promise<string | undefined>;
|
|
84
|
+
|
|
85
|
+
interface MagpiePluginOptions {
|
|
86
|
+
/** The app's DSN (`https://<key>@<host>/<project>`), used to find where to upload. Falls back to `MAGPIE_DSN`. */
|
|
87
|
+
dsn?: string;
|
|
88
|
+
/**
|
|
89
|
+
* An organisation token with the `project:releases` scope (Organisation settings > API tokens). Falls back to
|
|
90
|
+
* `MAGPIE_AUTH_TOKEN`. It is read at build time only and is never put in the bundle. Without one, nothing is uploaded.
|
|
91
|
+
*/
|
|
92
|
+
authToken?: string;
|
|
93
|
+
/** The version of this build. Defaults to the commit being built; see `resolveRelease`. */
|
|
94
|
+
release?: string;
|
|
95
|
+
/** Where this build is going, for the deploy record. Defaults to `MAGPIE_ENVIRONMENT`, then "production". */
|
|
96
|
+
environment?: string;
|
|
97
|
+
sourcemaps?: {
|
|
98
|
+
/** Default true. */
|
|
99
|
+
enabled?: boolean;
|
|
100
|
+
/** Remove each map from the output once Magpie has it, so it is never served. Default true. */
|
|
101
|
+
deleteAfterUpload?: boolean;
|
|
102
|
+
/** Also upload the maps of the server build (Nuxt's `ssr` environment). Default false. */
|
|
103
|
+
server?: boolean;
|
|
104
|
+
};
|
|
105
|
+
deploy?: {
|
|
106
|
+
/** Record the release, its environment and its commits with Magpie after the upload. Default true. */
|
|
107
|
+
enabled?: boolean;
|
|
108
|
+
deployedBy?: string;
|
|
109
|
+
/** How many recent commits to send. Default 50. */
|
|
110
|
+
commits?: number;
|
|
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
|
+
};
|
|
119
|
+
/** Fail the build when an upload fails. Default false: a Magpie outage should not stop you deploying. */
|
|
120
|
+
failOnError?: boolean;
|
|
121
|
+
disable?: boolean;
|
|
122
|
+
/** @internal Replaced in tests. */
|
|
123
|
+
fetch?: typeof fetch;
|
|
124
|
+
/** @internal Replaced in tests. */
|
|
125
|
+
exec?: Exec;
|
|
126
|
+
/** @internal Replaced in tests. */
|
|
127
|
+
env?: Record<string, string | undefined>;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Makes production stack traces readable. During `vite build` it:
|
|
131
|
+
* - makes the build write source maps without referencing them (`sourcemap: 'hidden'`), unless you chose otherwise;
|
|
132
|
+
* - puts the release on the page, as `import.meta.env.VITE_MAGPIE_RELEASE` and `window.__MAGPIE_RELEASE__`;
|
|
133
|
+
* - uploads the maps to Magpie under that release, and deletes them from the output once Magpie has them;
|
|
134
|
+
* - records the release, the environment it went to and its recent commits.
|
|
135
|
+
*/
|
|
136
|
+
declare function magpie(options?: MagpiePluginOptions): Plugin;
|
|
137
|
+
declare function parseDsn(dsn: string | undefined): {
|
|
138
|
+
origin: string;
|
|
139
|
+
project: string;
|
|
140
|
+
} | null;
|
|
141
|
+
|
|
142
|
+
export { type MagpiePluginOptions, type MapFile, PLATFORM_PACKAGES, magpie as default, installedVersions, magpie, parseDsn, recentCommits, recordRelease, reportPlatform, resolveRelease, uploadSourceMaps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
/** Runs a program with an argument list (no shell, so nothing is ever interpreted) and returns what it printed. */
|
|
4
|
+
type Exec = (file: string, args: string[]) => string;
|
|
5
|
+
/**
|
|
6
|
+
* The version this build is known as, in the order: what you passed, an environment variable a CI or host sets, then
|
|
7
|
+
* the commit of the checkout. The same string must be used when the source maps are uploaded and when the page runs,
|
|
8
|
+
* or Magpie cannot match a stack frame to its map.
|
|
9
|
+
*/
|
|
10
|
+
declare function resolveRelease(explicit?: string, env?: Record<string, string | undefined>, exec?: Exec): string | undefined;
|
|
11
|
+
interface Commit {
|
|
12
|
+
sha: string;
|
|
13
|
+
message: string;
|
|
14
|
+
author: string;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
}
|
|
17
|
+
/** The newest commits, newest first, as `git log` lists them. Empty when this is not a git checkout. */
|
|
18
|
+
declare function recentCommits(limit?: number, exec?: Exec): Commit[];
|
|
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
|
+
|
|
43
|
+
interface MapFile {
|
|
44
|
+
/** The name Magpie files it under: the map's own file name (`app-3f2a1.js.map`). */
|
|
45
|
+
name: string;
|
|
46
|
+
path: string;
|
|
47
|
+
}
|
|
48
|
+
interface UploadOptions {
|
|
49
|
+
/** `https://host`, no trailing slash. */
|
|
50
|
+
origin: string;
|
|
51
|
+
project: string;
|
|
52
|
+
token: string;
|
|
53
|
+
version: string;
|
|
54
|
+
files: MapFile[];
|
|
55
|
+
fetch?: typeof fetch;
|
|
56
|
+
retries?: number;
|
|
57
|
+
/** Wait between retries, doubled each time. Tests set it to 0. */
|
|
58
|
+
retryDelayMs?: number;
|
|
59
|
+
}
|
|
60
|
+
interface UploadResult {
|
|
61
|
+
uploaded: MapFile[];
|
|
62
|
+
failed: Array<{
|
|
63
|
+
file: MapFile;
|
|
64
|
+
reason: string;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Sends source maps to `POST /api/{project}/sourcemaps` in batches that fit Magpie's limits. A batch that fails does
|
|
69
|
+
* not stop the others, and the result says exactly which files made it, so only those are deleted from the build.
|
|
70
|
+
*/
|
|
71
|
+
declare function uploadSourceMaps(options: UploadOptions): Promise<UploadResult>;
|
|
72
|
+
interface ReleaseRecord {
|
|
73
|
+
origin: string;
|
|
74
|
+
project: string;
|
|
75
|
+
token: string;
|
|
76
|
+
version: string;
|
|
77
|
+
environment: string;
|
|
78
|
+
deployedBy: string;
|
|
79
|
+
commits: Commit[];
|
|
80
|
+
fetch?: typeof fetch;
|
|
81
|
+
}
|
|
82
|
+
/** Tells Magpie a release was deployed to an environment, with its commits (newest first). @returns undefined on success. */
|
|
83
|
+
declare function recordRelease(record: ReleaseRecord): Promise<string | undefined>;
|
|
84
|
+
|
|
85
|
+
interface MagpiePluginOptions {
|
|
86
|
+
/** The app's DSN (`https://<key>@<host>/<project>`), used to find where to upload. Falls back to `MAGPIE_DSN`. */
|
|
87
|
+
dsn?: string;
|
|
88
|
+
/**
|
|
89
|
+
* An organisation token with the `project:releases` scope (Organisation settings > API tokens). Falls back to
|
|
90
|
+
* `MAGPIE_AUTH_TOKEN`. It is read at build time only and is never put in the bundle. Without one, nothing is uploaded.
|
|
91
|
+
*/
|
|
92
|
+
authToken?: string;
|
|
93
|
+
/** The version of this build. Defaults to the commit being built; see `resolveRelease`. */
|
|
94
|
+
release?: string;
|
|
95
|
+
/** Where this build is going, for the deploy record. Defaults to `MAGPIE_ENVIRONMENT`, then "production". */
|
|
96
|
+
environment?: string;
|
|
97
|
+
sourcemaps?: {
|
|
98
|
+
/** Default true. */
|
|
99
|
+
enabled?: boolean;
|
|
100
|
+
/** Remove each map from the output once Magpie has it, so it is never served. Default true. */
|
|
101
|
+
deleteAfterUpload?: boolean;
|
|
102
|
+
/** Also upload the maps of the server build (Nuxt's `ssr` environment). Default false. */
|
|
103
|
+
server?: boolean;
|
|
104
|
+
};
|
|
105
|
+
deploy?: {
|
|
106
|
+
/** Record the release, its environment and its commits with Magpie after the upload. Default true. */
|
|
107
|
+
enabled?: boolean;
|
|
108
|
+
deployedBy?: string;
|
|
109
|
+
/** How many recent commits to send. Default 50. */
|
|
110
|
+
commits?: number;
|
|
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
|
+
};
|
|
119
|
+
/** Fail the build when an upload fails. Default false: a Magpie outage should not stop you deploying. */
|
|
120
|
+
failOnError?: boolean;
|
|
121
|
+
disable?: boolean;
|
|
122
|
+
/** @internal Replaced in tests. */
|
|
123
|
+
fetch?: typeof fetch;
|
|
124
|
+
/** @internal Replaced in tests. */
|
|
125
|
+
exec?: Exec;
|
|
126
|
+
/** @internal Replaced in tests. */
|
|
127
|
+
env?: Record<string, string | undefined>;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Makes production stack traces readable. During `vite build` it:
|
|
131
|
+
* - makes the build write source maps without referencing them (`sourcemap: 'hidden'`), unless you chose otherwise;
|
|
132
|
+
* - puts the release on the page, as `import.meta.env.VITE_MAGPIE_RELEASE` and `window.__MAGPIE_RELEASE__`;
|
|
133
|
+
* - uploads the maps to Magpie under that release, and deletes them from the output once Magpie has them;
|
|
134
|
+
* - records the release, the environment it went to and its recent commits.
|
|
135
|
+
*/
|
|
136
|
+
declare function magpie(options?: MagpiePluginOptions): Plugin;
|
|
137
|
+
declare function parseDsn(dsn: string | undefined): {
|
|
138
|
+
origin: string;
|
|
139
|
+
project: string;
|
|
140
|
+
} | null;
|
|
141
|
+
|
|
142
|
+
export { type MagpiePluginOptions, type MapFile, PLATFORM_PACKAGES, magpie as default, installedVersions, magpie, parseDsn, recentCommits, recordRelease, reportPlatform, resolveRelease, uploadSourceMaps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { readFile, stat, rm } from 'fs/promises';
|
|
2
|
+
import { join, basename } from 'path';
|
|
3
|
+
import { execFileSync } from 'child_process';
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
var defaultExec = (file, args) => execFileSync(file, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
7
|
+
var COMMIT_ENV = [
|
|
8
|
+
"MAGPIE_RELEASE",
|
|
9
|
+
"GITHUB_SHA",
|
|
10
|
+
"CI_COMMIT_SHA",
|
|
11
|
+
"VERCEL_GIT_COMMIT_SHA",
|
|
12
|
+
"CF_PAGES_COMMIT_SHA",
|
|
13
|
+
"COMMIT_REF",
|
|
14
|
+
"RENDER_GIT_COMMIT",
|
|
15
|
+
"SOURCE_VERSION",
|
|
16
|
+
"BITBUCKET_COMMIT",
|
|
17
|
+
"CIRCLE_SHA1"
|
|
18
|
+
];
|
|
19
|
+
function resolveRelease(explicit, env = process.env, exec = defaultExec) {
|
|
20
|
+
if (explicit) return explicit;
|
|
21
|
+
for (const name of COMMIT_ENV) {
|
|
22
|
+
if (env[name]) return env[name];
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return exec("git", ["rev-parse", "HEAD"]) || void 0;
|
|
26
|
+
} catch {
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function recentCommits(limit = 50, exec = defaultExec) {
|
|
31
|
+
try {
|
|
32
|
+
const out = exec("git", ["log", "-n", String(Math.max(1, Math.floor(limit))), "--pretty=format:%H%x1f%an%x1f%aI%x1f%s"]);
|
|
33
|
+
return out.split("\n").filter(Boolean).map((line) => {
|
|
34
|
+
const [sha, author, timestamp, ...message] = line.split("");
|
|
35
|
+
return { sha, author, timestamp, message: message.join("") };
|
|
36
|
+
});
|
|
37
|
+
} catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
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
|
+
}
|
|
74
|
+
var MAX_FILES = 50;
|
|
75
|
+
var MAX_FILE_BYTES = 20 * 1024 * 1024;
|
|
76
|
+
var MAX_BATCH_BYTES = 6 * 1024 * 1024;
|
|
77
|
+
async function uploadSourceMaps(options) {
|
|
78
|
+
const result = { uploaded: [], failed: [] };
|
|
79
|
+
const sized = [];
|
|
80
|
+
for (const file of options.files) {
|
|
81
|
+
const bytes = (await stat(file.path)).size;
|
|
82
|
+
if (bytes > MAX_FILE_BYTES) result.failed.push({ file, reason: `larger than 20 MB (${(bytes / 1048576).toFixed(1)} MB), which Magpie refuses` });
|
|
83
|
+
else sized.push({ ...file, bytes });
|
|
84
|
+
}
|
|
85
|
+
for (const batch of batches(sized)) {
|
|
86
|
+
const error = await sendBatch(options, batch);
|
|
87
|
+
if (error) for (const file of batch) result.failed.push({ file, reason: error });
|
|
88
|
+
else result.uploaded.push(...batch);
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
function batches(files) {
|
|
93
|
+
const out = [];
|
|
94
|
+
let current = [];
|
|
95
|
+
let size = 0;
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
if (current.length && (current.length >= MAX_FILES || size + file.bytes > MAX_BATCH_BYTES)) {
|
|
98
|
+
out.push(current);
|
|
99
|
+
current = [];
|
|
100
|
+
size = 0;
|
|
101
|
+
}
|
|
102
|
+
current.push(file);
|
|
103
|
+
size += file.bytes;
|
|
104
|
+
}
|
|
105
|
+
if (current.length) out.push(current);
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
async function sendBatch(options, batch) {
|
|
109
|
+
const doFetch = options.fetch ?? fetch;
|
|
110
|
+
const retries = options.retries ?? 2;
|
|
111
|
+
let last = "unknown error";
|
|
112
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
113
|
+
if (attempt > 0) await sleep((options.retryDelayMs ?? 500) * 2 ** (attempt - 1));
|
|
114
|
+
try {
|
|
115
|
+
const form = new FormData();
|
|
116
|
+
form.append("version", options.version);
|
|
117
|
+
for (const file of batch) form.append("maps[]", new Blob([await readFile(file.path)], { type: "application/json" }), file.name);
|
|
118
|
+
const response = await doFetch(`${options.origin}/api/${options.project}/sourcemaps`, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: { Authorization: `Bearer ${options.token}`, Accept: "application/json" },
|
|
121
|
+
body: form
|
|
122
|
+
});
|
|
123
|
+
if (response.ok) return void 0;
|
|
124
|
+
last = await describeFailure(response);
|
|
125
|
+
if (response.status < 500) return last;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
last = `could not reach Magpie (${error.message})`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return last;
|
|
131
|
+
}
|
|
132
|
+
async function recordRelease(record) {
|
|
133
|
+
try {
|
|
134
|
+
const response = await (record.fetch ?? fetch)(`${record.origin}/api/${record.project}/releases`, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: { Authorization: `Bearer ${record.token}`, "Content-Type": "application/json", Accept: "application/json" },
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
version: record.version,
|
|
139
|
+
environment: record.environment,
|
|
140
|
+
deployed_by: record.deployedBy,
|
|
141
|
+
...record.commits.length ? { commits: record.commits } : {}
|
|
142
|
+
})
|
|
143
|
+
});
|
|
144
|
+
return response.ok ? void 0 : await describeFailure(response);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return `could not reach Magpie (${error.message})`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function describeFailure(response) {
|
|
150
|
+
let detail = "";
|
|
151
|
+
try {
|
|
152
|
+
const body = await response.json();
|
|
153
|
+
detail = body.error ?? body.message ?? "";
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
return `HTTP ${response.status}${detail ? `: ${detail}` : ""}`;
|
|
157
|
+
}
|
|
158
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
159
|
+
|
|
160
|
+
// src/index.ts
|
|
161
|
+
var PREFIX = "[magpie]";
|
|
162
|
+
function magpie(options = {}) {
|
|
163
|
+
const env = options.env ?? process.env;
|
|
164
|
+
let release;
|
|
165
|
+
let deployed = false;
|
|
166
|
+
let platformReported = false;
|
|
167
|
+
let root = process.cwd();
|
|
168
|
+
const log = (message) => console.log(`${PREFIX} ${message}`);
|
|
169
|
+
const warn = (message) => console.warn(`${PREFIX} ${message}`);
|
|
170
|
+
return {
|
|
171
|
+
name: "magpie",
|
|
172
|
+
apply: "build",
|
|
173
|
+
config(config) {
|
|
174
|
+
release = resolveRelease(options.release, env, options.exec);
|
|
175
|
+
return {
|
|
176
|
+
// Hidden: the map is written but the bundle does not point at it, so visitors cannot fetch your source.
|
|
177
|
+
...config.build?.sourcemap === void 0 && options.sourcemaps?.enabled !== false ? { build: { sourcemap: "hidden" } } : {},
|
|
178
|
+
...release ? { define: { "import.meta.env.VITE_MAGPIE_RELEASE": JSON.stringify(release) } } : {}
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
configResolved(config) {
|
|
182
|
+
root = config.root;
|
|
183
|
+
},
|
|
184
|
+
transformIndexHtml() {
|
|
185
|
+
if (!release) return void 0;
|
|
186
|
+
return [{ tag: "script", children: `window.__MAGPIE_RELEASE__=${JSON.stringify(release)};`, injectTo: "head-prepend" }];
|
|
187
|
+
},
|
|
188
|
+
async writeBundle(outputOptions, bundle) {
|
|
189
|
+
if (options.disable) return;
|
|
190
|
+
release ??= resolveRelease(options.release, env, options.exec);
|
|
191
|
+
const environment = this.environment?.name ?? "client";
|
|
192
|
+
if (environment !== "client" && !options.sourcemaps?.server) return;
|
|
193
|
+
const token = options.authToken ?? env.MAGPIE_AUTH_TOKEN;
|
|
194
|
+
const dsn = parseDsn(options.dsn ?? env.MAGPIE_DSN);
|
|
195
|
+
const fail = (message) => {
|
|
196
|
+
if (options.failOnError) throw new Error(`${PREFIX} ${message}`);
|
|
197
|
+
warn(message);
|
|
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
|
+
}
|
|
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.");
|
|
207
|
+
if (!dsn) return warn("No DSN (the dsn option or MAGPIE_DSN), so source maps are not uploaded.");
|
|
208
|
+
if (!token) return log("No MAGPIE_AUTH_TOKEN, so source maps are not uploaded. This is expected for a local build.");
|
|
209
|
+
const dir = outputOptions.dir ?? ".";
|
|
210
|
+
const files = Object.keys(bundle).filter((name) => name.endsWith(".map")).map((name) => ({ name: basename(name), path: join(dir, name) }));
|
|
211
|
+
if (options.sourcemaps?.enabled !== false && files.length) {
|
|
212
|
+
const result = await uploadSourceMaps({ origin: dsn.origin, project: dsn.project, token, version: release, files, fetch: options.fetch });
|
|
213
|
+
log(`Uploaded ${result.uploaded.length} of ${files.length} source maps for ${release}.`);
|
|
214
|
+
for (const { file, reason } of result.failed) fail(`${file.name} was not uploaded: ${reason}`);
|
|
215
|
+
if (options.sourcemaps?.deleteAfterUpload !== false) await Promise.all(result.uploaded.map((f) => rm(f.path, { force: true })));
|
|
216
|
+
} else if (options.sourcemaps?.enabled !== false) {
|
|
217
|
+
warn("The build produced no source maps. Check that build.sourcemap is not false.");
|
|
218
|
+
}
|
|
219
|
+
if (options.deploy?.enabled !== false && !deployed) {
|
|
220
|
+
deployed = true;
|
|
221
|
+
const reason = await recordRelease({
|
|
222
|
+
origin: dsn.origin,
|
|
223
|
+
project: dsn.project,
|
|
224
|
+
token,
|
|
225
|
+
version: release,
|
|
226
|
+
environment: options.environment ?? env.MAGPIE_ENVIRONMENT ?? "production",
|
|
227
|
+
deployedBy: options.deploy?.deployedBy ?? env.GITHUB_ACTOR ?? env.GITLAB_USER_LOGIN ?? "build",
|
|
228
|
+
commits: recentCommits(options.deploy?.commits ?? 50, options.exec),
|
|
229
|
+
fetch: options.fetch
|
|
230
|
+
});
|
|
231
|
+
if (reason) fail(`The release was not recorded: ${reason}`);
|
|
232
|
+
else log(`Recorded release ${release} for ${options.environment ?? env.MAGPIE_ENVIRONMENT ?? "production"}.`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function parseDsn(dsn) {
|
|
238
|
+
const m = /^(https?):\/\/[^@/\s:]+@([^/\s]+)\/(\d+)\/?$/i.exec((dsn ?? "").trim());
|
|
239
|
+
return m ? { origin: `${m[1].toLowerCase()}://${m[2]}`, project: m[3] } : null;
|
|
240
|
+
}
|
|
241
|
+
var index_default = magpie;
|
|
242
|
+
|
|
243
|
+
export { PLATFORM_PACKAGES, index_default as default, installedVersions, magpie, parseDsn, recentCommits, recordRelease, reportPlatform, resolveRelease, uploadSourceMaps };
|
|
244
|
+
//# sourceMappingURL=index.js.map
|
|
245
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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,49 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@greenhat-services/magpie-vite-plugin",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Uploads source maps to Magpie, records the release and its commits, and injects the release into your Vite build.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"prepack": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"vite": ">=5.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"vite": "^7.0.0"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/Greenhat-Services/magpie-js.git",
|
|
40
|
+
"directory": "packages/vite-plugin"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"magpie",
|
|
44
|
+
"vite",
|
|
45
|
+
"vite-plugin",
|
|
46
|
+
"sourcemaps",
|
|
47
|
+
"release"
|
|
48
|
+
]
|
|
49
|
+
}
|