@frontend-monitor/upload-sourcemaps 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @frontend-monitor/upload-sourcemaps might be problematic. Click here for more details.
- package/README.md +122 -0
- package/bin/upload-sourcemaps.js +57 -0
- package/lib/upload.js +79 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# @frontend-monitor/upload-sourcemaps
|
|
2
|
+
|
|
3
|
+
将构建产物中的 Source Map 文件批量上传到前端监控平台 API,便于错误堆栈还原。适用于 CI/CD 流水线。
|
|
4
|
+
|
|
5
|
+
## 要求
|
|
6
|
+
|
|
7
|
+
- Node.js >= 18(使用原生 `fetch`、`FormData`)
|
|
8
|
+
- 监控平台 API 已部署,并已创建项目(获得 Project ID 与 Secret Key)
|
|
9
|
+
|
|
10
|
+
## 安装
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# 安装为项目依赖(推荐在 CI 中与 npx 二选一)
|
|
14
|
+
npm install @frontend-monitor/upload-sourcemaps
|
|
15
|
+
|
|
16
|
+
# 全局安装(可选,便于直接使用命令)
|
|
17
|
+
npm install -g @frontend-monitor/upload-sourcemaps
|
|
18
|
+
|
|
19
|
+
# 或仅用 npx 运行,无需安装
|
|
20
|
+
npx @frontend-monitor/upload-sourcemaps --project-id xxx --secret-key yyy --endpoint https://api.example.com
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 用法
|
|
24
|
+
|
|
25
|
+
### 参数(环境变量或命令行)
|
|
26
|
+
|
|
27
|
+
| 参数 | 环境变量 | 说明 | 必填 |
|
|
28
|
+
|------|----------|------|------|
|
|
29
|
+
| `--project-id` | `PROJECT_ID` | 项目 ID | 是 |
|
|
30
|
+
| `--secret-key` | `SECRET_KEY` | 项目密钥 | 是 |
|
|
31
|
+
| `--endpoint` | `ENDPOINT` | API 根地址,如 `https://api.example.com` | 是 |
|
|
32
|
+
| `--release` | `RELEASE` | 版本号,需与前端 SDK `init({ release })` 一致 | 否,默认 `default` |
|
|
33
|
+
| `--dir` | `DIR` | 包含 `.map` 文件的目录 | 否,默认当前目录 |
|
|
34
|
+
|
|
35
|
+
### 示例
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# 使用命令行参数
|
|
39
|
+
npx @frontend-monitor/upload-sourcemaps \
|
|
40
|
+
--project-id p_xxx \
|
|
41
|
+
--secret-key your_secret_key \
|
|
42
|
+
--endpoint https://api.example.com \
|
|
43
|
+
--release 1.0.0 \
|
|
44
|
+
--dir ./dist
|
|
45
|
+
|
|
46
|
+
# 使用环境变量(适合 CI)
|
|
47
|
+
export PROJECT_ID=p_xxx
|
|
48
|
+
export SECRET_KEY=your_secret_key
|
|
49
|
+
export ENDPOINT=https://api.example.com
|
|
50
|
+
export RELEASE=1.0.0
|
|
51
|
+
export DIR=./dist
|
|
52
|
+
npx @frontend-monitor/upload-sourcemaps
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 在 CI 中使用
|
|
56
|
+
|
|
57
|
+
**GitHub Actions 示例:**
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
- name: Upload Source Maps
|
|
61
|
+
env:
|
|
62
|
+
PROJECT_ID: ${{ secrets.MONITOR_PROJECT_ID }}
|
|
63
|
+
SECRET_KEY: ${{ secrets.MONITOR_SECRET_KEY }}
|
|
64
|
+
ENDPOINT: https://api.your-company.com
|
|
65
|
+
RELEASE: ${{ github.ref_name }}
|
|
66
|
+
DIR: ./dist
|
|
67
|
+
run: npx @frontend-monitor/upload-sourcemaps
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**在代码中调用(Node.js):**
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
import { uploadAll } from '@frontend-monitor/upload-sourcemaps';
|
|
74
|
+
|
|
75
|
+
const { ok, fail } = await uploadAll({
|
|
76
|
+
endpoint: 'https://api.example.com',
|
|
77
|
+
projectId: 'p_xxx',
|
|
78
|
+
secretKey: 'your_secret_key',
|
|
79
|
+
release: '1.0.0',
|
|
80
|
+
dir: './dist',
|
|
81
|
+
});
|
|
82
|
+
console.log(`Uploaded ${ok} files, ${fail} failed`);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 行为说明
|
|
86
|
+
|
|
87
|
+
- 递归扫描 `--dir` 下所有 `.map` 文件并逐个上传。
|
|
88
|
+
- 请求地址为 `{ENDPOINT}/sourcemaps/upload`,鉴权方式与监控平台 ingest 一致(Project ID + Secret Key)。
|
|
89
|
+
- 若有文件上传失败,会打印错误并以退出码 `1` 结束,便于 CI 失败感知。
|
|
90
|
+
|
|
91
|
+
## 发布到 npm
|
|
92
|
+
|
|
93
|
+
本包为 **scoped 包**(`@frontend-monitor/upload-sourcemaps`),首次发布需带 `--access public`。
|
|
94
|
+
|
|
95
|
+
### 发布脚本说明
|
|
96
|
+
|
|
97
|
+
| 脚本 | 说明 |
|
|
98
|
+
|------|------|
|
|
99
|
+
| `prepublishOnly` | 发布前自动执行:检查 `bin`、`lib` 是否存在,避免漏文件 |
|
|
100
|
+
| `npm run publish:dry` | 仅做发布预检(不实际上传):先跑 prepublishOnly,再 `npm publish --dry-run` |
|
|
101
|
+
| `npm run publish:public` | 执行发布并设为公开:prepublishOnly + `npm publish --access public` |
|
|
102
|
+
|
|
103
|
+
### 发布步骤
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
cd packages/upload-sourcemaps
|
|
107
|
+
|
|
108
|
+
# 1. 确认将要发布的文件列表(推荐先执行)
|
|
109
|
+
npm run publish:dry
|
|
110
|
+
|
|
111
|
+
# 2. 登录 npm(未登录时)
|
|
112
|
+
npm login
|
|
113
|
+
|
|
114
|
+
# 3. 发布(scoped 包需加 --access public)
|
|
115
|
+
npm run publish:public
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
若已配置 `npm config set access public`,也可直接执行 `npm publish`。
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* frontend-monitor-upload-sourcemaps CLI
|
|
4
|
+
*
|
|
5
|
+
* 环境变量或参数:
|
|
6
|
+
* PROJECT_ID / --project-id 项目 ID(必填)
|
|
7
|
+
* SECRET_KEY / --secret-key 项目密钥(必填)
|
|
8
|
+
* ENDPOINT / --endpoint API 地址(必填)
|
|
9
|
+
* RELEASE / --release release 版本(默认 default)
|
|
10
|
+
* DIR / --dir 要上传的目录(默认当前目录)
|
|
11
|
+
*
|
|
12
|
+
* 示例:
|
|
13
|
+
* npx frontend-monitor-upload-sourcemaps --project-id xxx --secret-key yyy --endpoint https://api.com
|
|
14
|
+
* PROJECT_ID=xxx SECRET_KEY=yyy ENDPOINT=https://api.com RELEASE=1.0.0 DIR=./dist upload-sourcemaps
|
|
15
|
+
*/
|
|
16
|
+
import { uploadAll } from '../lib/upload.js';
|
|
17
|
+
|
|
18
|
+
function getArg(name, envName) {
|
|
19
|
+
const env = process.env[envName];
|
|
20
|
+
if (env) return env;
|
|
21
|
+
const i = process.argv.indexOf(name);
|
|
22
|
+
if (i !== -1 && process.argv[i + 1]) return process.argv[i + 1];
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const projectId = getArg('--project-id', 'PROJECT_ID');
|
|
27
|
+
const secretKey = getArg('--secret-key', 'SECRET_KEY');
|
|
28
|
+
const endpoint = getArg('--endpoint', 'ENDPOINT') || '';
|
|
29
|
+
const release = getArg('--release', 'RELEASE') || 'default';
|
|
30
|
+
const dir = getArg('--dir', 'DIR') || process.cwd();
|
|
31
|
+
|
|
32
|
+
if (!projectId || !secretKey || !endpoint) {
|
|
33
|
+
console.error('Usage: PROJECT_ID, SECRET_KEY and ENDPOINT are required.');
|
|
34
|
+
console.error('');
|
|
35
|
+
console.error(' Environment:');
|
|
36
|
+
console.error(' PROJECT_ID=xxx SECRET_KEY=yyy ENDPOINT=https://api.com [RELEASE=1.0.0] [DIR=./dist]');
|
|
37
|
+
console.error('');
|
|
38
|
+
console.error(' Arguments:');
|
|
39
|
+
console.error(' --project-id <id> Project ID');
|
|
40
|
+
console.error(' --secret-key <key> Secret Key');
|
|
41
|
+
console.error(' --endpoint <url> API base URL');
|
|
42
|
+
console.error(' --release <tag> Release version (default: default)');
|
|
43
|
+
console.error(' --dir <path> Directory containing .map files (default: cwd)');
|
|
44
|
+
console.error('');
|
|
45
|
+
console.error(' Example:');
|
|
46
|
+
console.error(' npx @frontend-monitor/upload-sourcemaps --project-id p_xxx --secret-key sk_yyy --endpoint https://api.example.com --release 1.0.0 --dir ./dist');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { ok, fail } = await uploadAll(
|
|
51
|
+
{ endpoint, projectId, secretKey, release, dir },
|
|
52
|
+
console
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
if (fail > 0) {
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
package/lib/upload.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { readdir, readFile } from 'fs/promises';
|
|
2
|
+
import { join, basename } from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 递归收集目录下所有 .map 文件
|
|
6
|
+
*/
|
|
7
|
+
export async function findMapFiles(dirPath, list = []) {
|
|
8
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
9
|
+
for (const e of entries) {
|
|
10
|
+
const full = join(dirPath, e.name);
|
|
11
|
+
if (e.isDirectory()) {
|
|
12
|
+
await findMapFiles(full, list);
|
|
13
|
+
} else if (e.name.endsWith('.map')) {
|
|
14
|
+
list.push(full);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return list;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 上传单个 .map 文件到监控 API
|
|
22
|
+
* @param {object} opts - { endpoint, projectId, secretKey, release, filePath }
|
|
23
|
+
* @returns {Promise<object>} API 返回结果
|
|
24
|
+
*/
|
|
25
|
+
export async function uploadOne(opts) {
|
|
26
|
+
const { endpoint, projectId, secretKey, release, filePath } = opts;
|
|
27
|
+
const base = basename(filePath, '.map');
|
|
28
|
+
const buf = await readFile(filePath);
|
|
29
|
+
const url = endpoint.replace(/\/$/, '') + '/sourcemaps/upload';
|
|
30
|
+
const form = new FormData();
|
|
31
|
+
form.append('projectId', projectId);
|
|
32
|
+
form.append('secretKey', secretKey);
|
|
33
|
+
form.append('release', release);
|
|
34
|
+
form.append('filename', base);
|
|
35
|
+
form.append('file', new Blob([buf]), basename(filePath));
|
|
36
|
+
|
|
37
|
+
const res = await fetch(url, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
'X-Project-Id': projectId,
|
|
41
|
+
'X-Secret-Key': secretKey,
|
|
42
|
+
},
|
|
43
|
+
body: form,
|
|
44
|
+
});
|
|
45
|
+
const text = await res.text();
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
throw new Error(`${res.status} ${text}`);
|
|
48
|
+
}
|
|
49
|
+
return JSON.parse(text || '{}');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 批量上传目录下所有 .map 文件
|
|
54
|
+
* @param {object} opts - { endpoint, projectId, secretKey, release, dir }
|
|
55
|
+
* @param {object} [logger] - { log, error } 默认 console
|
|
56
|
+
* @returns {Promise<{ ok: number, fail: number }>}
|
|
57
|
+
*/
|
|
58
|
+
export async function uploadAll(opts, logger = console) {
|
|
59
|
+
const { dir, endpoint, projectId, secretKey, release } = opts;
|
|
60
|
+
const files = await findMapFiles(dir);
|
|
61
|
+
if (files.length === 0) {
|
|
62
|
+
logger.log('No .map files found in', dir);
|
|
63
|
+
return { ok: 0, fail: 0 };
|
|
64
|
+
}
|
|
65
|
+
logger.log('Uploading', files.length, 'source map(s) to', endpoint, 'release=', release);
|
|
66
|
+
let ok = 0;
|
|
67
|
+
let fail = 0;
|
|
68
|
+
for (const f of files) {
|
|
69
|
+
try {
|
|
70
|
+
await uploadOne({ endpoint, projectId, secretKey, release, filePath: f });
|
|
71
|
+
logger.log(' OK', basename(f));
|
|
72
|
+
ok++;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
logger.error(' FAIL', f, err.message);
|
|
75
|
+
fail++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { ok, fail };
|
|
79
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@frontend-monitor/upload-sourcemaps",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI to upload Source Map files to frontend-monitor API (for CI/CD)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/upload.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"upload-sourcemaps": "./bin/upload-sourcemaps.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"prepublishOnly": "node -e \"require('fs').accessSync('bin/upload-sourcemaps.js'); require('fs').accessSync('lib/upload.js'); console.log('publish check ok');\"",
|
|
12
|
+
"publish:dry": "npm run prepublishOnly && npm publish --dry-run",
|
|
13
|
+
"publish:public": "npm run prepublishOnly && npm publish --access public"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin",
|
|
17
|
+
"lib",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"sourcemap",
|
|
22
|
+
"source-map",
|
|
23
|
+
"frontend",
|
|
24
|
+
"monitoring",
|
|
25
|
+
"cli",
|
|
26
|
+
"upload",
|
|
27
|
+
"ci"
|
|
28
|
+
],
|
|
29
|
+
"author": "mishuxing",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": ""
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
}
|
|
38
|
+
}
|