@nanyujun/rum-vite-plugin-sourcemap 0.1.1 → 0.1.2
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.
Potentially problematic release.
This version of @nanyujun/rum-vite-plugin-sourcemap might be problematic. Click here for more details.
- package/README.md +89 -0
- package/dist/index.cjs +20 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +21 -132
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -27,11 +27,24 @@ export default defineConfig({
|
|
|
27
27
|
// distDir: 'dist', // 可选,默认 build.outDir
|
|
28
28
|
// concurrency: 4, // 可选,上传并发
|
|
29
29
|
// disabled: false, // 可选,强制开/关
|
|
30
|
+
// cleanupAfterUpload: false, // 可选,默认 false;true 时上传成功后删除本地 *.map 避免产物残留
|
|
30
31
|
}),
|
|
31
32
|
],
|
|
32
33
|
})
|
|
33
34
|
```
|
|
34
35
|
|
|
36
|
+
上传成功后如需自动删除构建目录中的 `*.map`(减少产物体积、避免误部署),开启 `cleanupAfterUpload`:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
rumSourcemap({
|
|
40
|
+
app: 'your-app-id',
|
|
41
|
+
release: '1.0.0',
|
|
42
|
+
endpoint: 'https://rum.example.com',
|
|
43
|
+
token: process.env.RUM_ADMIN_TOKEN,
|
|
44
|
+
cleanupAfterUpload: true,
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
35
48
|
## 参数
|
|
36
49
|
|
|
37
50
|
| 参数 | 必填 | 说明 |
|
|
@@ -43,6 +56,7 @@ export default defineConfig({
|
|
|
43
56
|
| distDir | 否 | 默认 `config.build.outDir` |
|
|
44
57
|
| concurrency | 否 | 上传并发数 |
|
|
45
58
|
| disabled | 否 | 覆盖默认的「仅 production 启用」策略 |
|
|
59
|
+
| cleanupAfterUpload | 否 | 默认 `false`;上传成功后删除本地 `*.map` 文件,避免产物残留 |
|
|
46
60
|
|
|
47
61
|
## 注意事项
|
|
48
62
|
|
|
@@ -50,6 +64,81 @@ export default defineConfig({
|
|
|
50
64
|
- `build.sourcemap` 必须设为 `'hidden'`,否则 `.map` 会随产物部署到 CDN,造成源码泄漏
|
|
51
65
|
- `release` 必须与前端 SDK 上报错误时携带的 `release` 一致,否则后台无法匹配对应 map
|
|
52
66
|
|
|
67
|
+
## Webpack 用法
|
|
68
|
+
|
|
69
|
+
Webpack 项目推荐直接使用专用插件 [`@nanyujun/rum-webpack-plugin-sourcemap`](https://www.npmjs.com/package/@nanyujun/rum-webpack-plugin-sourcemap),行为与 Vite 插件一致(`afterEmit` 阶段上传、支持 `cleanupAfterUpload`):
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
// webpack.config.js
|
|
73
|
+
const { RumSourcemapPlugin } = require('@nanyujun/rum-webpack-plugin-sourcemap')
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
mode: 'production',
|
|
77
|
+
devtool: 'hidden-source-map', // 生成但不注入 sourceMappingURL,避免源码泄漏
|
|
78
|
+
plugins: [
|
|
79
|
+
new RumSourcemapPlugin({
|
|
80
|
+
app: 'your-app-id',
|
|
81
|
+
release: '1.0.0',
|
|
82
|
+
endpoint: 'https://rum.example.com',
|
|
83
|
+
token: process.env.RUM_ADMIN_TOKEN,
|
|
84
|
+
cleanupAfterUpload: true,
|
|
85
|
+
}),
|
|
86
|
+
],
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
如果你暂时不想额外安装插件,也可以直接用底层 CLI 的 `uploadDir` Node API 挂到 `compiler.hooks.afterEmit`(等价实现):
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
// webpack.config.js
|
|
94
|
+
const { uploadDir } = require('@nanyujun/rum-sourcemap-cli')
|
|
95
|
+
const path = require('node:path')
|
|
96
|
+
|
|
97
|
+
// 上传完成后删除本地 *.map(对应 cleanupAfterUpload: true)
|
|
98
|
+
async function removeMaps(distDir) {
|
|
99
|
+
const { readdir } = require('node:fs/promises')
|
|
100
|
+
const walk = async (dir) => {
|
|
101
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
102
|
+
const p = path.join(dir, entry.name)
|
|
103
|
+
if (entry.isDirectory()) await walk(p)
|
|
104
|
+
else if (entry.name.endsWith('.map')) require('node:fs').unlinkSync(p)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
await walk(distDir)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
class RumSourcemapPlugin {
|
|
111
|
+
constructor(options) { this.options = options }
|
|
112
|
+
apply(compiler) {
|
|
113
|
+
compiler.hooks.afterEmit.tapPromise('RumSourcemapPlugin', async () => {
|
|
114
|
+
const distDir = this.options.distDir ?? compiler.options.output.path
|
|
115
|
+
await uploadDir({
|
|
116
|
+
appId: this.options.app,
|
|
117
|
+
release: this.options.release,
|
|
118
|
+
endpoint: this.options.endpoint,
|
|
119
|
+
token: this.options.token,
|
|
120
|
+
distDir,
|
|
121
|
+
...(this.options.concurrency !== undefined && { concurrency: this.options.concurrency }),
|
|
122
|
+
})
|
|
123
|
+
if (this.options.cleanupAfterUpload) await removeMaps(distDir)
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
devtool: 'hidden-source-map',
|
|
130
|
+
plugins: [
|
|
131
|
+
new RumSourcemapPlugin({
|
|
132
|
+
app: 'your-app-id',
|
|
133
|
+
release: '1.0.0',
|
|
134
|
+
endpoint: 'https://rum.example.com',
|
|
135
|
+
token: process.env.RUM_ADMIN_TOKEN,
|
|
136
|
+
cleanupAfterUpload: true,
|
|
137
|
+
}),
|
|
138
|
+
],
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
53
142
|
## 相关
|
|
54
143
|
|
|
55
144
|
- `@nanyujun/rum-sourcemap-cli`:底层上传 CLI / Node API
|
package/dist/index.cjs
CHANGED
|
@@ -24,137 +24,8 @@ __export(index_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
var import_node_path = require("path");
|
|
27
|
-
|
|
28
|
-
// ../sourcemap-cli/dist/index.js
|
|
29
27
|
var import_promises = require("fs/promises");
|
|
30
|
-
var
|
|
31
|
-
var SourceMapUploadError = class extends Error {
|
|
32
|
-
constructor(message, status) {
|
|
33
|
-
super(message);
|
|
34
|
-
this.name = "SourceMapUploadError";
|
|
35
|
-
this.status = status;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
var DEFAULT_RETRIES = 3;
|
|
39
|
-
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
40
|
-
var DEFAULT_CONCURRENCY = 4;
|
|
41
|
-
function sourceMapUrl(endpoint) {
|
|
42
|
-
return `${endpoint.replace(/\/+$/, "")}/admin/sourcemaps`;
|
|
43
|
-
}
|
|
44
|
-
function sleep(ms) {
|
|
45
|
-
if (ms <= 0) return Promise.resolve();
|
|
46
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
47
|
-
}
|
|
48
|
-
function retryDelay(baseMs, retryIndex) {
|
|
49
|
-
return baseMs * 2 ** retryIndex;
|
|
50
|
-
}
|
|
51
|
-
async function responseMessage(response) {
|
|
52
|
-
try {
|
|
53
|
-
const body = await response.json();
|
|
54
|
-
if (typeof body.error === "string" && body.error.length > 0) return body.error;
|
|
55
|
-
} catch {
|
|
56
|
-
}
|
|
57
|
-
return response.statusText || `HTTP ${response.status}`;
|
|
58
|
-
}
|
|
59
|
-
function isRetryable(status) {
|
|
60
|
-
return status >= 500 || status === 408 || status === 429;
|
|
61
|
-
}
|
|
62
|
-
async function readUploadResult(response) {
|
|
63
|
-
const body = await response.json();
|
|
64
|
-
return {
|
|
65
|
-
id: Number(body.id),
|
|
66
|
-
sha256: String(body.sha256),
|
|
67
|
-
sizeBytes: Number(body.sizeBytes)
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
async function uploadFile(options) {
|
|
71
|
-
const fetcher = options.fetchImpl ?? fetch;
|
|
72
|
-
const retries = options.retries ?? DEFAULT_RETRIES;
|
|
73
|
-
const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
74
|
-
let lastError;
|
|
75
|
-
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
76
|
-
try {
|
|
77
|
-
const content = await (0, import_promises.readFile)(options.mapPath);
|
|
78
|
-
const form = new FormData();
|
|
79
|
-
form.set("appId", options.appId);
|
|
80
|
-
form.set("release", options.release);
|
|
81
|
-
form.set("file", options.file);
|
|
82
|
-
form.set("map", new Blob([new Uint8Array(content)]), (0, import_path.basename)(options.mapPath));
|
|
83
|
-
const response = await fetcher(sourceMapUrl(options.endpoint), {
|
|
84
|
-
method: "POST",
|
|
85
|
-
headers: { Authorization: `Bearer ${options.token}` },
|
|
86
|
-
body: form
|
|
87
|
-
});
|
|
88
|
-
if (response.ok) return readUploadResult(response);
|
|
89
|
-
const message = await responseMessage(response);
|
|
90
|
-
if (!isRetryable(response.status) || attempt >= retries) {
|
|
91
|
-
throw new SourceMapUploadError(message, response.status);
|
|
92
|
-
}
|
|
93
|
-
lastError = new SourceMapUploadError(message, response.status);
|
|
94
|
-
} catch (err) {
|
|
95
|
-
if (err instanceof SourceMapUploadError && !isRetryable(err.status ?? 0)) throw err;
|
|
96
|
-
lastError = err;
|
|
97
|
-
if (attempt >= retries) break;
|
|
98
|
-
}
|
|
99
|
-
await sleep(retryDelay(retryDelayMs, attempt));
|
|
100
|
-
}
|
|
101
|
-
if (lastError instanceof Error) throw lastError;
|
|
102
|
-
throw new SourceMapUploadError("source map upload failed");
|
|
103
|
-
}
|
|
104
|
-
async function collectMapFiles(dir) {
|
|
105
|
-
const files = [];
|
|
106
|
-
const entries = await (0, import_promises.opendir)(dir);
|
|
107
|
-
for await (const entry of entries) {
|
|
108
|
-
const path = (0, import_path.join)(dir, entry.name);
|
|
109
|
-
if (entry.isDirectory()) {
|
|
110
|
-
files.push(...await collectMapFiles(path));
|
|
111
|
-
} else if (entry.isFile() && entry.name.endsWith(".map")) {
|
|
112
|
-
files.push(path);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return files.sort();
|
|
116
|
-
}
|
|
117
|
-
function toSourceFile(distDir, mapPath) {
|
|
118
|
-
return (0, import_path.relative)(distDir, mapPath).split("\\").join("/").replace(/\.map$/, "");
|
|
119
|
-
}
|
|
120
|
-
async function runPool(items, concurrency, worker) {
|
|
121
|
-
const results = [];
|
|
122
|
-
let cursor = 0;
|
|
123
|
-
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
124
|
-
await Promise.all(
|
|
125
|
-
Array.from({ length: workerCount }, async () => {
|
|
126
|
-
for (; ; ) {
|
|
127
|
-
const index = cursor++;
|
|
128
|
-
const item = items[index];
|
|
129
|
-
if (item === void 0) break;
|
|
130
|
-
results[index] = await worker(item);
|
|
131
|
-
}
|
|
132
|
-
})
|
|
133
|
-
);
|
|
134
|
-
return results;
|
|
135
|
-
}
|
|
136
|
-
async function uploadDir(options) {
|
|
137
|
-
const mapPaths = await collectMapFiles(options.distDir);
|
|
138
|
-
const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
139
|
-
const uploaded = await runPool(mapPaths, concurrency, async (mapPath) => {
|
|
140
|
-
const file = toSourceFile(options.distDir, mapPath);
|
|
141
|
-
const result = await uploadFile({
|
|
142
|
-
appId: options.appId,
|
|
143
|
-
release: options.release,
|
|
144
|
-
endpoint: options.endpoint,
|
|
145
|
-
token: options.token,
|
|
146
|
-
file,
|
|
147
|
-
mapPath,
|
|
148
|
-
retries: options.retries,
|
|
149
|
-
retryDelayMs: options.retryDelayMs,
|
|
150
|
-
fetchImpl: options.fetchImpl
|
|
151
|
-
});
|
|
152
|
-
return { ...result, file, mapPath };
|
|
153
|
-
});
|
|
154
|
-
return { uploaded };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// src/index.ts
|
|
28
|
+
var import_rum_sourcemap_cli = require("@nanyujun/rum-sourcemap-cli");
|
|
158
29
|
function isEnabled(options) {
|
|
159
30
|
if (options.disabled !== void 0) return !options.disabled;
|
|
160
31
|
return process.env.NODE_ENV === "production";
|
|
@@ -180,7 +51,8 @@ function rumSourcemap(options) {
|
|
|
180
51
|
config.logger.warn("[rum-sourcemap] token is missing; skip source map upload.");
|
|
181
52
|
return;
|
|
182
53
|
}
|
|
183
|
-
|
|
54
|
+
const logger = config.logger;
|
|
55
|
+
const result = await (0, import_rum_sourcemap_cli.uploadDir)({
|
|
184
56
|
appId: options.app,
|
|
185
57
|
release: options.release,
|
|
186
58
|
endpoint: options.endpoint,
|
|
@@ -188,6 +60,23 @@ function rumSourcemap(options) {
|
|
|
188
60
|
distDir: resolveDistDir(config, options.distDir),
|
|
189
61
|
...options.concurrency !== void 0 && { concurrency: options.concurrency }
|
|
190
62
|
});
|
|
63
|
+
if (!options.cleanupAfterUpload) return;
|
|
64
|
+
let removed = 0;
|
|
65
|
+
let failed = 0;
|
|
66
|
+
await Promise.allSettled(
|
|
67
|
+
result.uploaded.map(async (item) => {
|
|
68
|
+
try {
|
|
69
|
+
await (0, import_promises.unlink)(item.mapPath);
|
|
70
|
+
removed += 1;
|
|
71
|
+
} catch (err) {
|
|
72
|
+
failed += 1;
|
|
73
|
+
logger.warn(`[rum-sourcemap] failed to remove ${item.mapPath}: ${String(err)}`);
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
logger.info(
|
|
78
|
+
`[rum-sourcemap] cleanupAfterUpload: removed ${removed} .map file(s)` + (failed > 0 ? `, ${failed} failed` : "")
|
|
79
|
+
);
|
|
191
80
|
}
|
|
192
81
|
};
|
|
193
82
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../../sourcemap-cli/src/index.ts"],"sourcesContent":["import { isAbsolute, resolve } from 'node:path';\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport { uploadDir } from '@rum/sourcemap-cli';\n\nexport interface RumSourcemapPluginOptions {\n app: string;\n release: string;\n endpoint: string;\n token?: string;\n distDir?: string;\n concurrency?: number;\n disabled?: boolean;\n}\n\nfunction isEnabled(options: RumSourcemapPluginOptions): boolean {\n if (options.disabled !== undefined) return !options.disabled;\n return process.env.NODE_ENV === 'production';\n}\n\nfunction resolveDistDir(config: ResolvedConfig, distDir: string | undefined): string {\n const dir = distDir ?? config.build.outDir;\n return isAbsolute(dir) ? dir : resolve(config.root, dir);\n}\n\nexport function rumSourcemap(options: RumSourcemapPluginOptions): Plugin {\n let config: ResolvedConfig | undefined;\n\n return {\n name: 'rum-sourcemap',\n apply: 'build',\n\n configResolved(resolved) {\n config = resolved;\n if (isEnabled(options) && resolved.build.sourcemap !== 'hidden') {\n resolved.logger.warn(\"[rum-sourcemap] build.sourcemap should be 'hidden' to avoid publishing .map files.\");\n }\n },\n\n async closeBundle() {\n if (!config || config.command !== 'build' || !isEnabled(options)) return;\n if (!options.token) {\n config.logger.warn('[rum-sourcemap] token is missing; skip source map upload.');\n return;\n }\n\n await uploadDir({\n appId: options.app,\n release: options.release,\n endpoint: options.endpoint,\n token: options.token,\n distDir: resolveDistDir(config, options.distDir),\n ...(options.concurrency !== undefined && { concurrency: options.concurrency }),\n });\n },\n };\n}\n","import { opendir, readFile } from 'node:fs/promises';\nimport { basename, join, relative } from 'node:path';\n\nexport interface UploadFileResult {\n id: number;\n sha256: string;\n sizeBytes: number;\n}\n\nexport interface UploadFileOptions {\n appId: string;\n release: string;\n endpoint: string;\n token: string;\n file: string;\n mapPath: string;\n retries?: number;\n retryDelayMs?: number;\n fetchImpl?: typeof fetch;\n}\n\nexport interface UploadDirOptions {\n appId: string;\n release: string;\n endpoint: string;\n token: string;\n distDir: string;\n concurrency?: number;\n retries?: number;\n retryDelayMs?: number;\n fetchImpl?: typeof fetch;\n}\n\nexport interface UploadedSourceMap extends UploadFileResult {\n file: string;\n mapPath: string;\n}\n\nexport interface UploadDirResult {\n uploaded: UploadedSourceMap[];\n}\n\nexport interface ParsedUploadCommand {\n command: 'upload';\n appId: string;\n release: string;\n distDir: string;\n endpoint: string;\n token: string;\n concurrency?: number;\n}\n\nexport class SourceMapUploadError extends Error {\n status?: number;\n\n constructor(message: string, status?: number) {\n super(message);\n this.name = 'SourceMapUploadError';\n this.status = status;\n }\n}\n\nexport class SourceMapCliUsageError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SourceMapCliUsageError';\n }\n}\n\nconst DEFAULT_RETRIES = 3;\nconst DEFAULT_RETRY_DELAY_MS = 1000;\nconst DEFAULT_CONCURRENCY = 4;\n\nfunction sourceMapUrl(endpoint: string): string {\n return `${endpoint.replace(/\\/+$/, '')}/admin/sourcemaps`;\n}\n\nfunction sleep(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction retryDelay(baseMs: number, retryIndex: number): number {\n return baseMs * 2 ** retryIndex;\n}\n\nasync function responseMessage(response: Response): Promise<string> {\n try {\n const body = (await response.json()) as { error?: unknown };\n if (typeof body.error === 'string' && body.error.length > 0) return body.error;\n } catch {\n // Ignore malformed error bodies; status text is enough for CLI output.\n }\n return response.statusText || `HTTP ${response.status}`;\n}\n\nfunction isRetryable(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\nasync function readUploadResult(response: Response): Promise<UploadFileResult> {\n const body = (await response.json()) as Partial<UploadFileResult>;\n return {\n id: Number(body.id),\n sha256: String(body.sha256),\n sizeBytes: Number(body.sizeBytes),\n };\n}\n\nexport async function uploadFile(options: UploadFileOptions): Promise<UploadFileResult> {\n const fetcher = options.fetchImpl ?? fetch;\n const retries = options.retries ?? DEFAULT_RETRIES;\n const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n const content = await readFile(options.mapPath);\n const form = new FormData();\n form.set('appId', options.appId);\n form.set('release', options.release);\n form.set('file', options.file);\n form.set('map', new Blob([new Uint8Array(content)]), basename(options.mapPath));\n\n const response = await fetcher(sourceMapUrl(options.endpoint), {\n method: 'POST',\n headers: { Authorization: `Bearer ${options.token}` },\n body: form,\n });\n\n if (response.ok) return readUploadResult(response);\n const message = await responseMessage(response);\n if (!isRetryable(response.status) || attempt >= retries) {\n throw new SourceMapUploadError(message, response.status);\n }\n lastError = new SourceMapUploadError(message, response.status);\n } catch (err) {\n if (err instanceof SourceMapUploadError && !isRetryable(err.status ?? 0)) throw err;\n lastError = err;\n if (attempt >= retries) break;\n }\n\n await sleep(retryDelay(retryDelayMs, attempt));\n }\n\n if (lastError instanceof Error) throw lastError;\n throw new SourceMapUploadError('source map upload failed');\n}\n\nasync function collectMapFiles(dir: string): Promise<string[]> {\n const files: string[] = [];\n const entries = await opendir(dir);\n for await (const entry of entries) {\n const path = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await collectMapFiles(path)));\n } else if (entry.isFile() && entry.name.endsWith('.map')) {\n files.push(path);\n }\n }\n return files.sort();\n}\n\nfunction toSourceFile(distDir: string, mapPath: string): string {\n return relative(distDir, mapPath).split('\\\\').join('/').replace(/\\.map$/, '');\n}\n\nasync function runPool<T, R>(\n items: T[],\n concurrency: number,\n worker: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = [];\n let cursor = 0;\n const workerCount = Math.max(1, Math.min(concurrency, items.length));\n\n await Promise.all(\n Array.from({ length: workerCount }, async () => {\n for (;;) {\n const index = cursor++;\n const item = items[index];\n if (item === undefined) break;\n results[index] = await worker(item);\n }\n }),\n );\n\n return results;\n}\n\nexport async function uploadDir(options: UploadDirOptions): Promise<UploadDirResult> {\n const mapPaths = await collectMapFiles(options.distDir);\n const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;\n const uploaded = await runPool(mapPaths, concurrency, async (mapPath) => {\n const file = toSourceFile(options.distDir, mapPath);\n const result = await uploadFile({\n appId: options.appId,\n release: options.release,\n endpoint: options.endpoint,\n token: options.token,\n file,\n mapPath,\n retries: options.retries,\n retryDelayMs: options.retryDelayMs,\n fetchImpl: options.fetchImpl,\n });\n return { ...result, file, mapPath };\n });\n return { uploaded };\n}\n\nfunction readFlag(argv: string[], flag: string): string | undefined {\n const index = argv.indexOf(flag);\n if (index === -1) return undefined;\n return argv[index + 1];\n}\n\nfunction required(value: string | undefined, flag: string): string {\n if (!value) throw new SourceMapCliUsageError(`missing ${flag}`);\n return value;\n}\n\nexport function parseCliArgs(argv: string[]): ParsedUploadCommand {\n const command = argv[0];\n if (command !== 'upload') {\n throw new SourceMapCliUsageError('usage: rum-sourcemap upload --app <appId> --release <release> --dist <dir> --endpoint <url> --token <token>');\n }\n\n const concurrencyRaw = readFlag(argv, '--concurrency');\n return {\n command: 'upload',\n appId: required(readFlag(argv, '--app') ?? readFlag(argv, '--app-id'), '--app'),\n release: required(readFlag(argv, '--release'), '--release'),\n distDir: required(readFlag(argv, '--dist'), '--dist'),\n endpoint: required(readFlag(argv, '--endpoint'), '--endpoint'),\n token: required(readFlag(argv, '--token'), '--token'),\n ...(concurrencyRaw !== undefined && { concurrency: Number(concurrencyRaw) }),\n };\n}\n\nexport async function runCli(argv: string[], log: (message: string) => void = console.warn) {\n const command = parseCliArgs(argv);\n const result = await uploadDir(command);\n log(`uploaded ${result.uploaded.length} source maps`);\n return result;\n}\n"],"mappings":"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","names":["resolve"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { isAbsolute, resolve } from 'node:path';\nimport { unlink } from 'node:fs/promises';\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport { uploadDir, type UploadedSourceMap } from '@nanyujun/rum-sourcemap-cli';\n\nexport interface RumSourcemapPluginOptions {\n app: string;\n release: string;\n endpoint: string;\n token?: string;\n distDir?: string;\n concurrency?: number;\n disabled?: boolean;\n /** 上传成功后删除本地 .map 文件,避免构建产物残留。默认 false。 */\n cleanupAfterUpload?: boolean;\n}\n\nfunction isEnabled(options: RumSourcemapPluginOptions): boolean {\n if (options.disabled !== undefined) return !options.disabled;\n return process.env.NODE_ENV === 'production';\n}\n\nfunction resolveDistDir(config: ResolvedConfig, distDir: string | undefined): string {\n const dir = distDir ?? config.build.outDir;\n return isAbsolute(dir) ? dir : resolve(config.root, dir);\n}\n\nexport function rumSourcemap(options: RumSourcemapPluginOptions): Plugin {\n let config: ResolvedConfig | undefined;\n\n return {\n name: 'rum-sourcemap',\n apply: 'build',\n\n configResolved(resolved) {\n config = resolved;\n if (isEnabled(options) && resolved.build.sourcemap !== 'hidden') {\n resolved.logger.warn(\"[rum-sourcemap] build.sourcemap should be 'hidden' to avoid publishing .map files.\");\n }\n },\n\n async closeBundle() {\n if (!config || config.command !== 'build' || !isEnabled(options)) return;\n if (!options.token) {\n config.logger.warn('[rum-sourcemap] token is missing; skip source map upload.');\n return;\n }\n const logger = config.logger;\n\n const result = await uploadDir({\n appId: options.app,\n release: options.release,\n endpoint: options.endpoint,\n token: options.token,\n distDir: resolveDistDir(config, options.distDir),\n ...(options.concurrency !== undefined && { concurrency: options.concurrency }),\n });\n\n if (!options.cleanupAfterUpload) return;\n\n let removed = 0;\n let failed = 0;\n await Promise.allSettled(\n result.uploaded.map(async (item: UploadedSourceMap) => {\n try {\n await unlink(item.mapPath);\n removed += 1;\n } catch (err) {\n failed += 1;\n logger.warn(`[rum-sourcemap] failed to remove ${item.mapPath}: ${String(err)}`);\n }\n }),\n );\n logger.info(\n `[rum-sourcemap] cleanupAfterUpload: removed ${removed} .map file(s)` +\n (failed > 0 ? `, ${failed} failed` : ''),\n );\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAoC;AACpC,sBAAuB;AAEvB,+BAAkD;AAclD,SAAS,UAAU,SAA6C;AAC9D,MAAI,QAAQ,aAAa,OAAW,QAAO,CAAC,QAAQ;AACpD,SAAO,QAAQ,IAAI,aAAa;AAClC;AAEA,SAAS,eAAe,QAAwB,SAAqC;AACnF,QAAM,MAAM,WAAW,OAAO,MAAM;AACpC,aAAO,6BAAW,GAAG,IAAI,UAAM,0BAAQ,OAAO,MAAM,GAAG;AACzD;AAEO,SAAS,aAAa,SAA4C;AACvE,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IAEP,eAAe,UAAU;AACvB,eAAS;AACT,UAAI,UAAU,OAAO,KAAK,SAAS,MAAM,cAAc,UAAU;AAC/D,iBAAS,OAAO,KAAK,oFAAoF;AAAA,MAC3G;AAAA,IACF;AAAA,IAEA,MAAM,cAAc;AAClB,UAAI,CAAC,UAAU,OAAO,YAAY,WAAW,CAAC,UAAU,OAAO,EAAG;AAClE,UAAI,CAAC,QAAQ,OAAO;AAClB,eAAO,OAAO,KAAK,2DAA2D;AAC9E;AAAA,MACF;AACA,YAAM,SAAS,OAAO;AAEtB,YAAM,SAAS,UAAM,oCAAU;AAAA,QAC7B,OAAO,QAAQ;AAAA,QACf,SAAS,QAAQ;AAAA,QACjB,UAAU,QAAQ;AAAA,QAClB,OAAO,QAAQ;AAAA,QACf,SAAS,eAAe,QAAQ,QAAQ,OAAO;AAAA,QAC/C,GAAI,QAAQ,gBAAgB,UAAa,EAAE,aAAa,QAAQ,YAAY;AAAA,MAC9E,CAAC;AAED,UAAI,CAAC,QAAQ,mBAAoB;AAEjC,UAAI,UAAU;AACd,UAAI,SAAS;AACb,YAAM,QAAQ;AAAA,QACZ,OAAO,SAAS,IAAI,OAAO,SAA4B;AACrD,cAAI;AACF,sBAAM,wBAAO,KAAK,OAAO;AACzB,uBAAW;AAAA,UACb,SAAS,KAAK;AACZ,sBAAU;AACV,mBAAO,KAAK,oCAAoC,KAAK,OAAO,KAAK,OAAO,GAAG,CAAC,EAAE;AAAA,UAChF;AAAA,QACF,CAAC;AAAA,MACH;AACA,aAAO;AAAA,QACL,+CAA+C,OAAO,mBACnD,SAAS,IAAI,KAAK,MAAM,YAAY;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -8,6 +8,8 @@ interface RumSourcemapPluginOptions {
|
|
|
8
8
|
distDir?: string;
|
|
9
9
|
concurrency?: number;
|
|
10
10
|
disabled?: boolean;
|
|
11
|
+
/** 上传成功后删除本地 .map 文件,避免构建产物残留。默认 false。 */
|
|
12
|
+
cleanupAfterUpload?: boolean;
|
|
11
13
|
}
|
|
12
14
|
declare function rumSourcemap(options: RumSourcemapPluginOptions): Plugin;
|
|
13
15
|
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ interface RumSourcemapPluginOptions {
|
|
|
8
8
|
distDir?: string;
|
|
9
9
|
concurrency?: number;
|
|
10
10
|
disabled?: boolean;
|
|
11
|
+
/** 上传成功后删除本地 .map 文件,避免构建产物残留。默认 false。 */
|
|
12
|
+
cleanupAfterUpload?: boolean;
|
|
11
13
|
}
|
|
12
14
|
declare function rumSourcemap(options: RumSourcemapPluginOptions): Plugin;
|
|
13
15
|
|
package/dist/index.js
CHANGED
|
@@ -1,136 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { isAbsolute, resolve } from "path";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { opendir, readFile } from "fs/promises";
|
|
6
|
-
import { basename, join, relative } from "path";
|
|
7
|
-
var SourceMapUploadError = class extends Error {
|
|
8
|
-
constructor(message, status) {
|
|
9
|
-
super(message);
|
|
10
|
-
this.name = "SourceMapUploadError";
|
|
11
|
-
this.status = status;
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
var DEFAULT_RETRIES = 3;
|
|
15
|
-
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
16
|
-
var DEFAULT_CONCURRENCY = 4;
|
|
17
|
-
function sourceMapUrl(endpoint) {
|
|
18
|
-
return `${endpoint.replace(/\/+$/, "")}/admin/sourcemaps`;
|
|
19
|
-
}
|
|
20
|
-
function sleep(ms) {
|
|
21
|
-
if (ms <= 0) return Promise.resolve();
|
|
22
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
23
|
-
}
|
|
24
|
-
function retryDelay(baseMs, retryIndex) {
|
|
25
|
-
return baseMs * 2 ** retryIndex;
|
|
26
|
-
}
|
|
27
|
-
async function responseMessage(response) {
|
|
28
|
-
try {
|
|
29
|
-
const body = await response.json();
|
|
30
|
-
if (typeof body.error === "string" && body.error.length > 0) return body.error;
|
|
31
|
-
} catch {
|
|
32
|
-
}
|
|
33
|
-
return response.statusText || `HTTP ${response.status}`;
|
|
34
|
-
}
|
|
35
|
-
function isRetryable(status) {
|
|
36
|
-
return status >= 500 || status === 408 || status === 429;
|
|
37
|
-
}
|
|
38
|
-
async function readUploadResult(response) {
|
|
39
|
-
const body = await response.json();
|
|
40
|
-
return {
|
|
41
|
-
id: Number(body.id),
|
|
42
|
-
sha256: String(body.sha256),
|
|
43
|
-
sizeBytes: Number(body.sizeBytes)
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
async function uploadFile(options) {
|
|
47
|
-
const fetcher = options.fetchImpl ?? fetch;
|
|
48
|
-
const retries = options.retries ?? DEFAULT_RETRIES;
|
|
49
|
-
const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
50
|
-
let lastError;
|
|
51
|
-
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
52
|
-
try {
|
|
53
|
-
const content = await readFile(options.mapPath);
|
|
54
|
-
const form = new FormData();
|
|
55
|
-
form.set("appId", options.appId);
|
|
56
|
-
form.set("release", options.release);
|
|
57
|
-
form.set("file", options.file);
|
|
58
|
-
form.set("map", new Blob([new Uint8Array(content)]), basename(options.mapPath));
|
|
59
|
-
const response = await fetcher(sourceMapUrl(options.endpoint), {
|
|
60
|
-
method: "POST",
|
|
61
|
-
headers: { Authorization: `Bearer ${options.token}` },
|
|
62
|
-
body: form
|
|
63
|
-
});
|
|
64
|
-
if (response.ok) return readUploadResult(response);
|
|
65
|
-
const message = await responseMessage(response);
|
|
66
|
-
if (!isRetryable(response.status) || attempt >= retries) {
|
|
67
|
-
throw new SourceMapUploadError(message, response.status);
|
|
68
|
-
}
|
|
69
|
-
lastError = new SourceMapUploadError(message, response.status);
|
|
70
|
-
} catch (err) {
|
|
71
|
-
if (err instanceof SourceMapUploadError && !isRetryable(err.status ?? 0)) throw err;
|
|
72
|
-
lastError = err;
|
|
73
|
-
if (attempt >= retries) break;
|
|
74
|
-
}
|
|
75
|
-
await sleep(retryDelay(retryDelayMs, attempt));
|
|
76
|
-
}
|
|
77
|
-
if (lastError instanceof Error) throw lastError;
|
|
78
|
-
throw new SourceMapUploadError("source map upload failed");
|
|
79
|
-
}
|
|
80
|
-
async function collectMapFiles(dir) {
|
|
81
|
-
const files = [];
|
|
82
|
-
const entries = await opendir(dir);
|
|
83
|
-
for await (const entry of entries) {
|
|
84
|
-
const path = join(dir, entry.name);
|
|
85
|
-
if (entry.isDirectory()) {
|
|
86
|
-
files.push(...await collectMapFiles(path));
|
|
87
|
-
} else if (entry.isFile() && entry.name.endsWith(".map")) {
|
|
88
|
-
files.push(path);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return files.sort();
|
|
92
|
-
}
|
|
93
|
-
function toSourceFile(distDir, mapPath) {
|
|
94
|
-
return relative(distDir, mapPath).split("\\").join("/").replace(/\.map$/, "");
|
|
95
|
-
}
|
|
96
|
-
async function runPool(items, concurrency, worker) {
|
|
97
|
-
const results = [];
|
|
98
|
-
let cursor = 0;
|
|
99
|
-
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
100
|
-
await Promise.all(
|
|
101
|
-
Array.from({ length: workerCount }, async () => {
|
|
102
|
-
for (; ; ) {
|
|
103
|
-
const index = cursor++;
|
|
104
|
-
const item = items[index];
|
|
105
|
-
if (item === void 0) break;
|
|
106
|
-
results[index] = await worker(item);
|
|
107
|
-
}
|
|
108
|
-
})
|
|
109
|
-
);
|
|
110
|
-
return results;
|
|
111
|
-
}
|
|
112
|
-
async function uploadDir(options) {
|
|
113
|
-
const mapPaths = await collectMapFiles(options.distDir);
|
|
114
|
-
const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
115
|
-
const uploaded = await runPool(mapPaths, concurrency, async (mapPath) => {
|
|
116
|
-
const file = toSourceFile(options.distDir, mapPath);
|
|
117
|
-
const result = await uploadFile({
|
|
118
|
-
appId: options.appId,
|
|
119
|
-
release: options.release,
|
|
120
|
-
endpoint: options.endpoint,
|
|
121
|
-
token: options.token,
|
|
122
|
-
file,
|
|
123
|
-
mapPath,
|
|
124
|
-
retries: options.retries,
|
|
125
|
-
retryDelayMs: options.retryDelayMs,
|
|
126
|
-
fetchImpl: options.fetchImpl
|
|
127
|
-
});
|
|
128
|
-
return { ...result, file, mapPath };
|
|
129
|
-
});
|
|
130
|
-
return { uploaded };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// src/index.ts
|
|
3
|
+
import { unlink } from "fs/promises";
|
|
4
|
+
import { uploadDir } from "@nanyujun/rum-sourcemap-cli";
|
|
134
5
|
function isEnabled(options) {
|
|
135
6
|
if (options.disabled !== void 0) return !options.disabled;
|
|
136
7
|
return process.env.NODE_ENV === "production";
|
|
@@ -156,7 +27,8 @@ function rumSourcemap(options) {
|
|
|
156
27
|
config.logger.warn("[rum-sourcemap] token is missing; skip source map upload.");
|
|
157
28
|
return;
|
|
158
29
|
}
|
|
159
|
-
|
|
30
|
+
const logger = config.logger;
|
|
31
|
+
const result = await uploadDir({
|
|
160
32
|
appId: options.app,
|
|
161
33
|
release: options.release,
|
|
162
34
|
endpoint: options.endpoint,
|
|
@@ -164,6 +36,23 @@ function rumSourcemap(options) {
|
|
|
164
36
|
distDir: resolveDistDir(config, options.distDir),
|
|
165
37
|
...options.concurrency !== void 0 && { concurrency: options.concurrency }
|
|
166
38
|
});
|
|
39
|
+
if (!options.cleanupAfterUpload) return;
|
|
40
|
+
let removed = 0;
|
|
41
|
+
let failed = 0;
|
|
42
|
+
await Promise.allSettled(
|
|
43
|
+
result.uploaded.map(async (item) => {
|
|
44
|
+
try {
|
|
45
|
+
await unlink(item.mapPath);
|
|
46
|
+
removed += 1;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
failed += 1;
|
|
49
|
+
logger.warn(`[rum-sourcemap] failed to remove ${item.mapPath}: ${String(err)}`);
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
logger.info(
|
|
54
|
+
`[rum-sourcemap] cleanupAfterUpload: removed ${removed} .map file(s)` + (failed > 0 ? `, ${failed} failed` : "")
|
|
55
|
+
);
|
|
167
56
|
}
|
|
168
57
|
};
|
|
169
58
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../../sourcemap-cli/src/index.ts"],"sourcesContent":["import { isAbsolute, resolve } from 'node:path';\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport { uploadDir } from '@rum/sourcemap-cli';\n\nexport interface RumSourcemapPluginOptions {\n app: string;\n release: string;\n endpoint: string;\n token?: string;\n distDir?: string;\n concurrency?: number;\n disabled?: boolean;\n}\n\nfunction isEnabled(options: RumSourcemapPluginOptions): boolean {\n if (options.disabled !== undefined) return !options.disabled;\n return process.env.NODE_ENV === 'production';\n}\n\nfunction resolveDistDir(config: ResolvedConfig, distDir: string | undefined): string {\n const dir = distDir ?? config.build.outDir;\n return isAbsolute(dir) ? dir : resolve(config.root, dir);\n}\n\nexport function rumSourcemap(options: RumSourcemapPluginOptions): Plugin {\n let config: ResolvedConfig | undefined;\n\n return {\n name: 'rum-sourcemap',\n apply: 'build',\n\n configResolved(resolved) {\n config = resolved;\n if (isEnabled(options) && resolved.build.sourcemap !== 'hidden') {\n resolved.logger.warn(\"[rum-sourcemap] build.sourcemap should be 'hidden' to avoid publishing .map files.\");\n }\n },\n\n async closeBundle() {\n if (!config || config.command !== 'build' || !isEnabled(options)) return;\n if (!options.token) {\n config.logger.warn('[rum-sourcemap] token is missing; skip source map upload.');\n return;\n }\n\n await uploadDir({\n appId: options.app,\n release: options.release,\n endpoint: options.endpoint,\n token: options.token,\n distDir: resolveDistDir(config, options.distDir),\n ...(options.concurrency !== undefined && { concurrency: options.concurrency }),\n });\n },\n };\n}\n","import { opendir, readFile } from 'node:fs/promises';\nimport { basename, join, relative } from 'node:path';\n\nexport interface UploadFileResult {\n id: number;\n sha256: string;\n sizeBytes: number;\n}\n\nexport interface UploadFileOptions {\n appId: string;\n release: string;\n endpoint: string;\n token: string;\n file: string;\n mapPath: string;\n retries?: number;\n retryDelayMs?: number;\n fetchImpl?: typeof fetch;\n}\n\nexport interface UploadDirOptions {\n appId: string;\n release: string;\n endpoint: string;\n token: string;\n distDir: string;\n concurrency?: number;\n retries?: number;\n retryDelayMs?: number;\n fetchImpl?: typeof fetch;\n}\n\nexport interface UploadedSourceMap extends UploadFileResult {\n file: string;\n mapPath: string;\n}\n\nexport interface UploadDirResult {\n uploaded: UploadedSourceMap[];\n}\n\nexport interface ParsedUploadCommand {\n command: 'upload';\n appId: string;\n release: string;\n distDir: string;\n endpoint: string;\n token: string;\n concurrency?: number;\n}\n\nexport class SourceMapUploadError extends Error {\n status?: number;\n\n constructor(message: string, status?: number) {\n super(message);\n this.name = 'SourceMapUploadError';\n this.status = status;\n }\n}\n\nexport class SourceMapCliUsageError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SourceMapCliUsageError';\n }\n}\n\nconst DEFAULT_RETRIES = 3;\nconst DEFAULT_RETRY_DELAY_MS = 1000;\nconst DEFAULT_CONCURRENCY = 4;\n\nfunction sourceMapUrl(endpoint: string): string {\n return `${endpoint.replace(/\\/+$/, '')}/admin/sourcemaps`;\n}\n\nfunction sleep(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction retryDelay(baseMs: number, retryIndex: number): number {\n return baseMs * 2 ** retryIndex;\n}\n\nasync function responseMessage(response: Response): Promise<string> {\n try {\n const body = (await response.json()) as { error?: unknown };\n if (typeof body.error === 'string' && body.error.length > 0) return body.error;\n } catch {\n // Ignore malformed error bodies; status text is enough for CLI output.\n }\n return response.statusText || `HTTP ${response.status}`;\n}\n\nfunction isRetryable(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\nasync function readUploadResult(response: Response): Promise<UploadFileResult> {\n const body = (await response.json()) as Partial<UploadFileResult>;\n return {\n id: Number(body.id),\n sha256: String(body.sha256),\n sizeBytes: Number(body.sizeBytes),\n };\n}\n\nexport async function uploadFile(options: UploadFileOptions): Promise<UploadFileResult> {\n const fetcher = options.fetchImpl ?? fetch;\n const retries = options.retries ?? DEFAULT_RETRIES;\n const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n const content = await readFile(options.mapPath);\n const form = new FormData();\n form.set('appId', options.appId);\n form.set('release', options.release);\n form.set('file', options.file);\n form.set('map', new Blob([new Uint8Array(content)]), basename(options.mapPath));\n\n const response = await fetcher(sourceMapUrl(options.endpoint), {\n method: 'POST',\n headers: { Authorization: `Bearer ${options.token}` },\n body: form,\n });\n\n if (response.ok) return readUploadResult(response);\n const message = await responseMessage(response);\n if (!isRetryable(response.status) || attempt >= retries) {\n throw new SourceMapUploadError(message, response.status);\n }\n lastError = new SourceMapUploadError(message, response.status);\n } catch (err) {\n if (err instanceof SourceMapUploadError && !isRetryable(err.status ?? 0)) throw err;\n lastError = err;\n if (attempt >= retries) break;\n }\n\n await sleep(retryDelay(retryDelayMs, attempt));\n }\n\n if (lastError instanceof Error) throw lastError;\n throw new SourceMapUploadError('source map upload failed');\n}\n\nasync function collectMapFiles(dir: string): Promise<string[]> {\n const files: string[] = [];\n const entries = await opendir(dir);\n for await (const entry of entries) {\n const path = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await collectMapFiles(path)));\n } else if (entry.isFile() && entry.name.endsWith('.map')) {\n files.push(path);\n }\n }\n return files.sort();\n}\n\nfunction toSourceFile(distDir: string, mapPath: string): string {\n return relative(distDir, mapPath).split('\\\\').join('/').replace(/\\.map$/, '');\n}\n\nasync function runPool<T, R>(\n items: T[],\n concurrency: number,\n worker: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = [];\n let cursor = 0;\n const workerCount = Math.max(1, Math.min(concurrency, items.length));\n\n await Promise.all(\n Array.from({ length: workerCount }, async () => {\n for (;;) {\n const index = cursor++;\n const item = items[index];\n if (item === undefined) break;\n results[index] = await worker(item);\n }\n }),\n );\n\n return results;\n}\n\nexport async function uploadDir(options: UploadDirOptions): Promise<UploadDirResult> {\n const mapPaths = await collectMapFiles(options.distDir);\n const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;\n const uploaded = await runPool(mapPaths, concurrency, async (mapPath) => {\n const file = toSourceFile(options.distDir, mapPath);\n const result = await uploadFile({\n appId: options.appId,\n release: options.release,\n endpoint: options.endpoint,\n token: options.token,\n file,\n mapPath,\n retries: options.retries,\n retryDelayMs: options.retryDelayMs,\n fetchImpl: options.fetchImpl,\n });\n return { ...result, file, mapPath };\n });\n return { uploaded };\n}\n\nfunction readFlag(argv: string[], flag: string): string | undefined {\n const index = argv.indexOf(flag);\n if (index === -1) return undefined;\n return argv[index + 1];\n}\n\nfunction required(value: string | undefined, flag: string): string {\n if (!value) throw new SourceMapCliUsageError(`missing ${flag}`);\n return value;\n}\n\nexport function parseCliArgs(argv: string[]): ParsedUploadCommand {\n const command = argv[0];\n if (command !== 'upload') {\n throw new SourceMapCliUsageError('usage: rum-sourcemap upload --app <appId> --release <release> --dist <dir> --endpoint <url> --token <token>');\n }\n\n const concurrencyRaw = readFlag(argv, '--concurrency');\n return {\n command: 'upload',\n appId: required(readFlag(argv, '--app') ?? readFlag(argv, '--app-id'), '--app'),\n release: required(readFlag(argv, '--release'), '--release'),\n distDir: required(readFlag(argv, '--dist'), '--dist'),\n endpoint: required(readFlag(argv, '--endpoint'), '--endpoint'),\n token: required(readFlag(argv, '--token'), '--token'),\n ...(concurrencyRaw !== undefined && { concurrency: Number(concurrencyRaw) }),\n };\n}\n\nexport async function runCli(argv: string[], log: (message: string) => void = console.warn) {\n const command = parseCliArgs(argv);\n const result = await uploadDir(command);\n log(`uploaded ${result.uploaded.length} source maps`);\n return result;\n}\n"],"mappings":"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","names":["resolve"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { isAbsolute, resolve } from 'node:path';\nimport { unlink } from 'node:fs/promises';\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport { uploadDir, type UploadedSourceMap } from '@nanyujun/rum-sourcemap-cli';\n\nexport interface RumSourcemapPluginOptions {\n app: string;\n release: string;\n endpoint: string;\n token?: string;\n distDir?: string;\n concurrency?: number;\n disabled?: boolean;\n /** 上传成功后删除本地 .map 文件,避免构建产物残留。默认 false。 */\n cleanupAfterUpload?: boolean;\n}\n\nfunction isEnabled(options: RumSourcemapPluginOptions): boolean {\n if (options.disabled !== undefined) return !options.disabled;\n return process.env.NODE_ENV === 'production';\n}\n\nfunction resolveDistDir(config: ResolvedConfig, distDir: string | undefined): string {\n const dir = distDir ?? config.build.outDir;\n return isAbsolute(dir) ? dir : resolve(config.root, dir);\n}\n\nexport function rumSourcemap(options: RumSourcemapPluginOptions): Plugin {\n let config: ResolvedConfig | undefined;\n\n return {\n name: 'rum-sourcemap',\n apply: 'build',\n\n configResolved(resolved) {\n config = resolved;\n if (isEnabled(options) && resolved.build.sourcemap !== 'hidden') {\n resolved.logger.warn(\"[rum-sourcemap] build.sourcemap should be 'hidden' to avoid publishing .map files.\");\n }\n },\n\n async closeBundle() {\n if (!config || config.command !== 'build' || !isEnabled(options)) return;\n if (!options.token) {\n config.logger.warn('[rum-sourcemap] token is missing; skip source map upload.');\n return;\n }\n const logger = config.logger;\n\n const result = await uploadDir({\n appId: options.app,\n release: options.release,\n endpoint: options.endpoint,\n token: options.token,\n distDir: resolveDistDir(config, options.distDir),\n ...(options.concurrency !== undefined && { concurrency: options.concurrency }),\n });\n\n if (!options.cleanupAfterUpload) return;\n\n let removed = 0;\n let failed = 0;\n await Promise.allSettled(\n result.uploaded.map(async (item: UploadedSourceMap) => {\n try {\n await unlink(item.mapPath);\n removed += 1;\n } catch (err) {\n failed += 1;\n logger.warn(`[rum-sourcemap] failed to remove ${item.mapPath}: ${String(err)}`);\n }\n }),\n );\n logger.info(\n `[rum-sourcemap] cleanupAfterUpload: removed ${removed} .map file(s)` +\n (failed > 0 ? `, ${failed} failed` : ''),\n );\n },\n };\n}\n"],"mappings":";AAAA,SAAS,YAAY,eAAe;AACpC,SAAS,cAAc;AAEvB,SAAS,iBAAyC;AAclD,SAAS,UAAU,SAA6C;AAC9D,MAAI,QAAQ,aAAa,OAAW,QAAO,CAAC,QAAQ;AACpD,SAAO,QAAQ,IAAI,aAAa;AAClC;AAEA,SAAS,eAAe,QAAwB,SAAqC;AACnF,QAAM,MAAM,WAAW,OAAO,MAAM;AACpC,SAAO,WAAW,GAAG,IAAI,MAAM,QAAQ,OAAO,MAAM,GAAG;AACzD;AAEO,SAAS,aAAa,SAA4C;AACvE,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IAEP,eAAe,UAAU;AACvB,eAAS;AACT,UAAI,UAAU,OAAO,KAAK,SAAS,MAAM,cAAc,UAAU;AAC/D,iBAAS,OAAO,KAAK,oFAAoF;AAAA,MAC3G;AAAA,IACF;AAAA,IAEA,MAAM,cAAc;AAClB,UAAI,CAAC,UAAU,OAAO,YAAY,WAAW,CAAC,UAAU,OAAO,EAAG;AAClE,UAAI,CAAC,QAAQ,OAAO;AAClB,eAAO,OAAO,KAAK,2DAA2D;AAC9E;AAAA,MACF;AACA,YAAM,SAAS,OAAO;AAEtB,YAAM,SAAS,MAAM,UAAU;AAAA,QAC7B,OAAO,QAAQ;AAAA,QACf,SAAS,QAAQ;AAAA,QACjB,UAAU,QAAQ;AAAA,QAClB,OAAO,QAAQ;AAAA,QACf,SAAS,eAAe,QAAQ,QAAQ,OAAO;AAAA,QAC/C,GAAI,QAAQ,gBAAgB,UAAa,EAAE,aAAa,QAAQ,YAAY;AAAA,MAC9E,CAAC;AAED,UAAI,CAAC,QAAQ,mBAAoB;AAEjC,UAAI,UAAU;AACd,UAAI,SAAS;AACb,YAAM,QAAQ;AAAA,QACZ,OAAO,SAAS,IAAI,OAAO,SAA4B;AACrD,cAAI;AACF,kBAAM,OAAO,KAAK,OAAO;AACzB,uBAAW;AAAA,UACb,SAAS,KAAK;AACZ,sBAAU;AACV,mBAAO,KAAK,oCAAoC,KAAK,OAAO,KAAK,OAAO,GAAG,CAAC,EAAE;AAAA,UAChF;AAAA,QACF,CAAC;AAAA,MACH;AACA,aAAO;AAAA,QACL,+CAA+C,OAAO,mBACnD,SAAS,IAAI,KAAK,MAAM,YAAY;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanyujun/rum-vite-plugin-sourcemap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "RUM 前端性能监控平台的 Vite 插件:vite build 完成后自动把产物 *.map 上传到 collector,无需手动跑 CLI。",
|
|
5
5
|
"keywords": ["rum", "sourcemap", "source-map", "vite", "vite-plugin", "upload", "monitoring", "error-tracking"],
|
|
6
6
|
"type": "module",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"vite": "^5.0.0 || ^6.0.0"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@nanyujun/rum-sourcemap-cli": "^0.1.
|
|
35
|
+
"@nanyujun/rum-sourcemap-cli": "^0.1.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"vite": "^5.4.0"
|