@frontend-monitor/upload-sourcemaps 0.0.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.
package/README.md ADDED
@@ -0,0 +1,307 @@
1
+ # @frontend-monitor/upload-sourcemaps
2
+
3
+ **English** | [中文](README.zh-CN.md)
4
+
5
+ Upload Source Maps from your build output to the monitoring API so the admin can resolve error stacks to original source locations. Use it after build or run the `build` subcommand for a one-step build + upload.
6
+
7
+ ---
8
+
9
+ ## 3 steps to get started
10
+
11
+ ### Step 1: Install
12
+
13
+ ```bash
14
+ npm install @frontend-monitor/upload-sourcemaps
15
+ ```
16
+
17
+ Or run without installing: `npx @frontend-monitor/upload-sourcemaps`
18
+
19
+ ### Step 2: Configure credentials
20
+
21
+ Create `.upload-sourcemaps.json` at your **project root** (do not commit to public repos):
22
+
23
+ ```json
24
+ {
25
+ "projectId": "your-project-id",
26
+ "secretKey": "your-secret-key",
27
+ "endpoint": "https://your-monitor-api/monitorApi",
28
+ "dir": "./dist"
29
+ }
30
+ ```
31
+
32
+ Or use environment variables: `PROJECT_ID`, `SECRET_KEY`, `ENDPOINT`, `DIR` (handy for CI).
33
+
34
+ ### Step 3: Run upload
35
+
36
+ **Option A: Build, then upload**
37
+
38
+ ```bash
39
+ npm run build
40
+ npx upload-sourcemaps
41
+ ```
42
+
43
+ **Option B: One-step build + upload** (enables Source Maps and injects buildId automatically)
44
+
45
+ ```bash
46
+ npx upload-sourcemaps build
47
+ ```
48
+
49
+ **Recommended**: Add `--archive` to pack and encrypt in one go (fewer requests, more secure):
50
+
51
+ ```bash
52
+ npx upload-sourcemaps --archive
53
+ # or
54
+ npx upload-sourcemaps build --archive
55
+ ```
56
+
57
+ ---
58
+
59
+ ## In this doc
60
+
61
+ | Section | Contents |
62
+ |---------|----------|
63
+ | [Quick Start](#quick-start) | Upload after build / build subcommand / env vars |
64
+ | [Config](#config) | Config files, shared monitor.config.js, auth & certificates |
65
+ | [Parameters](#parameters) | CLI args and env vars at a glance |
66
+ | [Advanced](#advanced) | buildId, archive & chunked upload, .map deletion, build subcommand |
67
+ | [CI/CD](#cicd) | GitHub Actions, GitLab CI examples |
68
+ | [Deploy & Nginx](#deploy--nginx) | Direct API vs Nginx config |
69
+ | [With SDK](#with-sdk) | How release / buildId align with the SDK |
70
+ | [FAQ](#faq) | Stack not resolved, no .map, 413, etc. |
71
+
72
+ ---
73
+
74
+ ## Overview
75
+
76
+ - **Purpose**: Upload `.map` files from your build to the monitoring API so the admin can resolve error stacks to **source file, line, and function**.
77
+ - **Does not change deployment output**: Only scans → uploads → optionally deletes local `.map`.
78
+ - **Recommended**: Use `--archive` to pack and encrypt; single-file upload is plain, use only when packing is not practical.
79
+
80
+ ---
81
+
82
+ ## Quick Start
83
+
84
+ ### Upload after build
85
+
86
+ ```bash
87
+ npm run build
88
+ npx upload-sourcemaps \
89
+ --project-id your-project-id \
90
+ --secret-key your-secret-key \
91
+ --endpoint https://monitor.example.com/monitorApi \
92
+ --dir ./dist
93
+ ```
94
+
95
+ Omit `--release` to auto-read `package.json` version; omit `--build-id` to auto-generate.
96
+
97
+ ### One-step build + upload (build subcommand)
98
+
99
+ The tool will: temporarily enable Source Maps → run build → inject buildId → upload .map files.
100
+
101
+ ```bash
102
+ npx upload-sourcemaps build \
103
+ --project-id your-project-id \
104
+ --secret-key your-secret-key \
105
+ --endpoint https://monitor.example.com/monitorApi
106
+ ```
107
+
108
+ Build command defaults to `npm run build`; override with `buildCommand` in config.
109
+
110
+ ### Environment variables (for CI)
111
+
112
+ ```bash
113
+ export PROJECT_ID=your-project-id
114
+ export SECRET_KEY=your-secret-key
115
+ export ENDPOINT=https://monitor.example.com/monitorApi
116
+ export DIR=./dist
117
+ npx upload-sourcemaps
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Config
123
+
124
+ ### Config files
125
+
126
+ - **When `--config` is not set**: Reads **monitor.config.js** first (same format as SDK; can share `projectId`, `secretKey`, `authCert`, `endpoint`), then merges **.upload-sourcemaps.json** (can override `dir`, `chunkSize`, `buildCommand`, etc.).
127
+ - **When `--config` is set**: Only that file is read (e.g. `--config .upload-sourcemaps.json`).
128
+
129
+ Example `.upload-sourcemaps.json`:
130
+
131
+ ```json
132
+ {
133
+ "projectId": "your-project-id",
134
+ "secretKey": "your-secret-key",
135
+ "endpoint": "https://monitor.example.com/monitorApi",
136
+ "dir": "./dist",
137
+ "uploadAsArchive": true,
138
+ "buildCommand": "npm run build",
139
+ "buildOutputDir": "./dist"
140
+ }
141
+ ```
142
+
143
+ ### Auth and certificates
144
+
145
+ - **Option 1**: Project ID + Secret Key (form fields or headers `X-Project-Id`, `X-Secret-Key`).
146
+ - **Option 2**: Certificate auth. Issue a `.mcert` in the admin (Projects → Certificate Management) and set the **full file content** as `authCert` (or env `MONITOR_AUTH_CERT`). Newer certificates support encrypted upload; older ones upload in plain form — re-issue if you need encryption.
147
+ - Certificates are shared with the SDK; see [SDK docs - Certificate authentication](../sdk/README.md#certificate-authentication).
148
+
149
+ ---
150
+
151
+ ## Parameters
152
+
153
+ Priority: **CLI > env > config**.
154
+
155
+ | Argument | Env | Description | Default |
156
+ |----------|-----|-------------|---------|
157
+ | `--project-id` | `PROJECT_ID` | Project ID | — |
158
+ | `--secret-key` | `SECRET_KEY` | Project secret | — |
159
+ | `--endpoint` | `ENDPOINT` | API URL | — |
160
+ | `--dir` | `DIR` | Directory with .map files | Current dir |
161
+ | `--release` | `RELEASE` | Version (must match SDK) | From package.json |
162
+ | `--build-id` | `BUILD_ID` | Build serial number | Auto-generated |
163
+ | `--archive` | — | Pack as tar.gz and upload encrypted (recommended) | — |
164
+ | `--chunk-size` | `UPLOAD_SOURCEMAPS_CHUNK_SIZE` | Chunk size with --archive (e.g. 1M) | 1MB |
165
+ | `--auth-cert` | `MONITOR_AUTH_CERT` | Certificate content (alternative to project-id + secret-key) | — |
166
+ | `-d` / `--delete-after-upload` | — | Delete local .map after upload | Inferred from bundler |
167
+ | `--no-encrypt` | — | With --archive: upload plain tar.gz (older servers) | — |
168
+ | `--config` | `UPLOAD_SOURCEMAPS_CONFIG` | Config file path | — |
169
+
170
+ ---
171
+
172
+ ## Advanced
173
+
174
+ ### Build ID (buildId)
175
+
176
+ Links errors to the right Source Map when the same version is built multiple times. With the **build** subcommand, buildId is generated and injected automatically; the SDK reads it — no manual setup. For manual builds, inject `MONITOR_BUILD_ID` in your bundler and pass the same `--build-id` when uploading.
177
+
178
+ ### Archive upload (--archive)
179
+
180
+ Packs the output as `.tar.gz` and uploads it. The server decrypts and extracts only `.map` files. Default is encrypted. Use when you have many .map files to reduce requests.
181
+
182
+ ### Chunked upload (--chunk-size)
183
+
184
+ With `--archive`, add `--chunk-size 1M` to enable chunked upload, resume, and retries. Useful when Nginx limits body size or for large archives.
185
+
186
+ ### build subcommand: force Source Maps
187
+
188
+ Temporarily patches your bundler config (e.g. Vite `sourcemap: false` → `true`), then restores it after build. Supports Vite, Webpack, Vue CLI, Rollup, Next.js, Nuxt.
189
+
190
+ ### Post-upload .map deletion
191
+
192
+ Auto-inferred from your bundler config: if it outputs standalone .map files, they are kept; otherwise deleted. Use `-d` to force delete.
193
+
194
+ ---
195
+
196
+ ## package.json scripts
197
+
198
+ ```json
199
+ {
200
+ "scripts": {
201
+ "build": "vite build",
202
+ "upload-sourcemaps": "upload-sourcemaps --dir ./dist",
203
+ "build:upload": "upload-sourcemaps build",
204
+ "build:upload:archive": "upload-sourcemaps build --archive"
205
+ }
206
+ }
207
+ ```
208
+
209
+ ```bash
210
+ npm run build && npm run upload-sourcemaps # upload after build
211
+ npm run build:upload # one-step build + upload
212
+ ```
213
+
214
+ ---
215
+
216
+ ## CI/CD
217
+
218
+ ### GitHub Actions (upload after build)
219
+
220
+ ```yaml
221
+ - name: Build
222
+ run: npm run build
223
+ - name: Upload Source Maps
224
+ env:
225
+ PROJECT_ID: ${{ secrets.MONITOR_PROJECT_ID }}
226
+ SECRET_KEY: ${{ secrets.MONITOR_SECRET_KEY }}
227
+ ENDPOINT: https://monitor.example.com/monitorApi
228
+ DIR: ./dist
229
+ run: npx @frontend-monitor/upload-sourcemaps
230
+ ```
231
+
232
+ ### GitHub Actions (one-step build + upload)
233
+
234
+ ```yaml
235
+ - name: Build & Upload Source Maps
236
+ env:
237
+ PROJECT_ID: ${{ secrets.MONITOR_PROJECT_ID }}
238
+ SECRET_KEY: ${{ secrets.MONITOR_SECRET_KEY }}
239
+ ENDPOINT: https://monitor.example.com/monitorApi
240
+ run: npx @frontend-monitor/upload-sourcemaps build
241
+ ```
242
+
243
+ Exit code is 1 on upload failure so CI fails the job.
244
+
245
+ ---
246
+
247
+ ## Deploy & Nginx
248
+
249
+ - **Recommended**: Have uploads hit the API directly (e.g. from CI or same host). Use `ENDPOINT=http://127.0.0.1:3000` or internal IP — no Nginx changes.
250
+ - **If uploads must go through Nginx**: Configure larger `client_max_body_size` and timeouts for `/monitorApi/sourcemaps/upload` and `upload-archive`. See repo [Nginx deployment docs](../../docs/Nginx部署配置说明.md).
251
+
252
+ ---
253
+
254
+ ## Programmatic API (Node.js)
255
+
256
+ ```js
257
+ import { uploadAll, findMapFiles, uploadOne, uploadArchiveThenDelete } from '@frontend-monitor/upload-sourcemaps';
258
+
259
+ const { ok, fail } = await uploadAll({
260
+ endpoint: 'https://monitor.example.com/monitorApi',
261
+ projectId: 'your-project-id',
262
+ secretKey: 'your-secret-key',
263
+ release: '1.0.0',
264
+ dir: './dist',
265
+ deleteAfterUpload: true,
266
+ });
267
+ ```
268
+
269
+ | Method | Description |
270
+ |--------|-------------|
271
+ | `uploadAll(opts, logger?)` | Upload all .map in a directory, returns `{ ok, fail, deleted }` |
272
+ | `uploadOne(opts)` | Upload a single file |
273
+ | `findMapFiles(dir)` | Recursively find .map paths |
274
+ | `uploadArchiveThenDelete(opts, logger?)` | Pack, upload, then delete local .map |
275
+ | `uploadArchiveChunkedThenDelete(opts, logger?)` | Chunked archive upload then delete local .map |
276
+
277
+ ---
278
+
279
+ ## With SDK
280
+
281
+ This tool **uploads Source Maps**; [`@frontend-monitor/sdk`](https://www.npmjs.com/package/@frontend-monitor/sdk) **collects and reports errors**. Together they enable resolved stack traces in the admin.
282
+
283
+ | Data | This tool | SDK | Note |
284
+ |------|-----------|-----|------|
285
+ | `release` | `--release` or package.json | `init({ release })` | **Must match** |
286
+ | `buildId` | Injected by `build` subcommand | Reads `MONITOR_BUILD_ID` | Distinguishes builds of same release |
287
+ | `projectId` / `endpoint` | Upload target | Report target | Same project and API |
288
+
289
+ Recommended: `npx upload-sourcemaps build --archive` and wire up the SDK as in its docs; no manual alignment needed.
290
+
291
+ ---
292
+
293
+ ## FAQ
294
+
295
+ **Stack still shows minified locations?** Ensure SDK `release` matches upload version; stack script names match uploaded .map basenames; correct project is selected in the admin.
296
+
297
+ **No .map after build subcommand?** Vite usually works as-is; other bundlers may need to enable Source Maps when `BUILD_SOURCEMAP=1`.
298
+
299
+ **Where to get Project ID / Secret Key?** Admin console → Project settings.
300
+
301
+ **413 or timeout?** If upload goes through Nginx, increase body size and timeouts, or use direct API (see [Deploy & Nginx](#deploy--nginx)).
302
+
303
+ ---
304
+
305
+ ## License
306
+
307
+ MIT
@@ -0,0 +1,307 @@
1
+ # @frontend-monitor/upload-sourcemaps
2
+
3
+ [English](README.md) | **中文**
4
+
5
+ 将构建产物中的 Source Map 上传到监控 API,便于在管理端将错误堆栈还原为源码位置。支持构建后上传或一键构建+上传。
6
+
7
+ ---
8
+
9
+ ## 三步上手
10
+
11
+ ### 第一步:安装
12
+
13
+ ```bash
14
+ npm install @frontend-monitor/upload-sourcemaps
15
+ ```
16
+
17
+ 或直接使用(不安装):`npx @frontend-monitor/upload-sourcemaps`
18
+
19
+ ### 第二步:配置凭证
20
+
21
+ 在**项目根目录**创建 `.upload-sourcemaps.json`(不要提交到公开仓库):
22
+
23
+ ```json
24
+ {
25
+ "projectId": "你的项目ID",
26
+ "secretKey": "你的密钥",
27
+ "endpoint": "https://你的监控API地址/monitorApi",
28
+ "dir": "./dist"
29
+ }
30
+ ```
31
+
32
+ 也可用环境变量:`PROJECT_ID`、`SECRET_KEY`、`ENDPOINT`、`DIR`(适合 CI)。
33
+
34
+ ### 第三步:执行上传
35
+
36
+ **方式 A:先构建,再上传**
37
+
38
+ ```bash
39
+ npm run build
40
+ npx upload-sourcemaps
41
+ ```
42
+
43
+ **方式 B:一键构建并上传**(自动开启 Source Map、注入 buildId)
44
+
45
+ ```bash
46
+ npx upload-sourcemaps build
47
+ ```
48
+
49
+ **推荐**:加上 `--archive` 打压缩包并加密上传,减少请求次数且更安全:
50
+
51
+ ```bash
52
+ npx upload-sourcemaps --archive
53
+ # 或
54
+ npx upload-sourcemaps build --archive
55
+ ```
56
+
57
+ ---
58
+
59
+ ## 文档导航
60
+
61
+ | 章节 | 内容 |
62
+ |------|------|
63
+ | [快速开始](#快速开始) | 构建后上传 / build 子命令 / 环境变量示例 |
64
+ | [配置](#配置) | 配置文件、与 SDK 共用 monitor.config.js、鉴权与证书 |
65
+ | [参数速查](#参数速查) | 命令行参数与环境变量一览 |
66
+ | [进阶](#进阶) | buildId、压缩包与分片、上传后删除 .map、build 子命令说明 |
67
+ | [CI/CD](#cicd-集成) | GitHub Actions、GitLab CI 示例 |
68
+ | [部署与 Nginx](#部署与-nginx) | 直连 API 与 Nginx 配置片段 |
69
+ | [与 SDK 配合](#与-sdk-配合) | release / buildId 联动说明 |
70
+ | [常见问题](#常见问题) | 堆栈未还原、无 .map、413 等 |
71
+
72
+ ---
73
+
74
+ ## 简介
75
+
76
+ - **作用**:将构建产物中的 `.map` 文件上传到监控 API,管理端据此将错误堆栈还原为**源码文件名、行号、函数名**。
77
+ - **不修改部署产物**:仅扫描 → 上传 → 可选删除本地 `.map`。
78
+ - **推荐用法**:使用 `--archive` 打压缩包并加密上传;单文件上传为明文,仅在不便打压缩包时使用。
79
+
80
+ ---
81
+
82
+ ## 快速开始
83
+
84
+ ### 构建后上传
85
+
86
+ ```bash
87
+ npm run build
88
+ npx upload-sourcemaps \
89
+ --project-id your-project-id \
90
+ --secret-key your-secret-key \
91
+ --endpoint https://monitor.example.com/monitorApi \
92
+ --dir ./dist
93
+ ```
94
+
95
+ 未指定 `--release` 时自动读取 `package.json` 的 `version`;未指定 `--build-id` 时自动生成。
96
+
97
+ ### 一键构建并上传(build 子命令)
98
+
99
+ 工具会:临时开启 Source Map → 执行构建 → 注入 buildId → 上传 .map。
100
+
101
+ ```bash
102
+ npx upload-sourcemaps build \
103
+ --project-id your-project-id \
104
+ --secret-key your-secret-key \
105
+ --endpoint https://monitor.example.com/monitorApi
106
+ ```
107
+
108
+ 构建命令默认为 `npm run build`,可在配置中通过 `buildCommand` 修改。
109
+
110
+ ### 环境变量(适合 CI)
111
+
112
+ ```bash
113
+ export PROJECT_ID=your-project-id
114
+ export SECRET_KEY=your-secret-key
115
+ export ENDPOINT=https://monitor.example.com/monitorApi
116
+ export DIR=./dist
117
+ npx upload-sourcemaps
118
+ ```
119
+
120
+ ---
121
+
122
+ ## 配置
123
+
124
+ ### 配置文件
125
+
126
+ - **未指定 `--config` 时**:先读 **monitor.config.js**(与 SDK 同格式,可共用 `projectId`、`secretKey`、`authCert`、`endpoint`),再合并 **.upload-sourcemaps.json**(可覆盖 `dir`、`chunkSize`、`buildCommand` 等)。
127
+ - **指定 `--config`**:只读该文件(如 `--config .upload-sourcemaps.json`)。
128
+
129
+ `.upload-sourcemaps.json` 示例:
130
+
131
+ ```json
132
+ {
133
+ "projectId": "your-project-id",
134
+ "secretKey": "your-secret-key",
135
+ "endpoint": "https://monitor.example.com/monitorApi",
136
+ "dir": "./dist",
137
+ "uploadAsArchive": true,
138
+ "buildCommand": "npm run build",
139
+ "buildOutputDir": "./dist"
140
+ }
141
+ ```
142
+
143
+ ### 鉴权与证书
144
+
145
+ - **方式一**:Project ID + Secret Key(表单或请求头 `X-Project-Id`、`X-Secret-Key`)。
146
+ - **方式二**:证书认证。在管理端「项目管理 → 证书管理」颁发 `.mcert`,将**文件完整内容**配置为 `authCert`(或环境变量 `MONITOR_AUTH_CERT`)。新证书支持加密上传;旧证书上传为明文,需加密时请重新颁发。
147
+ - 证书与 SDK 共用,详见 [SDK 文档 - 证书认证](../sdk/README.zh-CN.md#证书认证替代-secret-key)。
148
+
149
+ ---
150
+
151
+ ## 参数速查
152
+
153
+ 优先级:**命令行 > 环境变量 > 配置文件**。
154
+
155
+ | 参数 | 环境变量 | 说明 | 默认 |
156
+ |------|----------|------|------|
157
+ | `--project-id` | `PROJECT_ID` | 项目 ID | — |
158
+ | `--secret-key` | `SECRET_KEY` | 项目密钥 | — |
159
+ | `--endpoint` | `ENDPOINT` | API 地址 | — |
160
+ | `--dir` | `DIR` | 包含 .map 的目录 | 当前目录 |
161
+ | `--release` | `RELEASE` | 版本号(需与 SDK 一致) | 读 package.json |
162
+ | `--build-id` | `BUILD_ID` | 构建序列号 | 自动生成 |
163
+ | `--archive` | — | 打 tar.gz 并加密上传(推荐) | — |
164
+ | `--chunk-size` | `UPLOAD_SOURCEMAPS_CHUNK_SIZE` | 分片大小(如 1M),与 --archive 同用 | 1MB |
165
+ | `--auth-cert` | `MONITOR_AUTH_CERT` | 证书内容(与 project-id+secret-key 二选一) | — |
166
+ | `-d` / `--delete-after-upload` | — | 上传后删除本地 .map | 按打包配置推断 |
167
+ | `--no-encrypt` | — | 与 --archive 同用时明文上传(兼容旧服务端) | — |
168
+ | `--config` | `UPLOAD_SOURCEMAPS_CONFIG` | 配置文件路径 | — |
169
+
170
+ ---
171
+
172
+ ## 进阶
173
+
174
+ ### 构建序列号(buildId)
175
+
176
+ 同一版本可能构建多次,`buildId` 用于精确关联错误与 Source Map。使用 **`build` 子命令**时自动生成并注入构建环境,SDK 自动读取,无需手动配置。手动构建时需在打包配置中注入 `MONITOR_BUILD_ID`,上传时传相同 `--build-id`。
177
+
178
+ ### 压缩包上传(--archive)
179
+
180
+ 将产物打成 `.tar.gz` 上传,服务端解压后只保留 `.map`。默认加密,服务端解密后保存。适合 .map 较多时减少请求次数。
181
+
182
+ ### 分片上传(--chunk-size)
183
+
184
+ 与 `--archive` 同用时,可加 `--chunk-size 1M` 启用分片上传、断点续传与重试,适合 Nginx 限制 body 或大压缩包。
185
+
186
+ ### build 子命令:强制生成 Source Map
187
+
188
+ 会临时修改打包配置(如 Vite 的 `sourcemap: false` → `true`),构建后自动恢复。支持 Vite、Webpack、Vue CLI、Rollup、Next.js、Nuxt。
189
+
190
+ ### 上传后是否删除本地 .map
191
+
192
+ 按项目打包配置自动推断:配置为输出独立 .map 则保留,否则删除。用 `-d` 可强制删除。
193
+
194
+ ---
195
+
196
+ ## 集成到 package.json
197
+
198
+ ```json
199
+ {
200
+ "scripts": {
201
+ "build": "vite build",
202
+ "upload-sourcemaps": "upload-sourcemaps --dir ./dist",
203
+ "build:upload": "upload-sourcemaps build",
204
+ "build:upload:archive": "upload-sourcemaps build --archive"
205
+ }
206
+ }
207
+ ```
208
+
209
+ ```bash
210
+ npm run build && npm run upload-sourcemaps # 构建后上传
211
+ npm run build:upload # 一键构建并上传
212
+ ```
213
+
214
+ ---
215
+
216
+ ## CI/CD 集成
217
+
218
+ ### GitHub Actions(构建后上传)
219
+
220
+ ```yaml
221
+ - name: Build
222
+ run: npm run build
223
+ - name: Upload Source Maps
224
+ env:
225
+ PROJECT_ID: ${{ secrets.MONITOR_PROJECT_ID }}
226
+ SECRET_KEY: ${{ secrets.MONITOR_SECRET_KEY }}
227
+ ENDPOINT: https://monitor.example.com/monitorApi
228
+ DIR: ./dist
229
+ run: npx @frontend-monitor/upload-sourcemaps
230
+ ```
231
+
232
+ ### GitHub Actions(一键构建并上传)
233
+
234
+ ```yaml
235
+ - name: Build & Upload Source Maps
236
+ env:
237
+ PROJECT_ID: ${{ secrets.MONITOR_PROJECT_ID }}
238
+ SECRET_KEY: ${{ secrets.MONITOR_SECRET_KEY }}
239
+ ENDPOINT: https://monitor.example.com/monitorApi
240
+ run: npx @frontend-monitor/upload-sourcemaps build
241
+ ```
242
+
243
+ 上传失败时退出码为 1,CI 会报错。
244
+
245
+ ---
246
+
247
+ ## 部署与 Nginx
248
+
249
+ - **推荐**:上传请求直连 API(CI 或内网),不经过 Nginx。例如 `ENDPOINT=http://127.0.0.1:3000` 或内网 IP,无需改 Nginx。
250
+ - **若必须经 Nginx**:对 `/monitorApi/sourcemaps/upload` 和 `upload-archive` 单独配置 `client_max_body_size`、`proxy_read_timeout` 等。详见仓库 [Nginx 部署配置说明](../../docs/Nginx部署配置说明.md)。
251
+
252
+ ---
253
+
254
+ ## 编程接口(Node.js)
255
+
256
+ ```js
257
+ import { uploadAll, findMapFiles, uploadOne, uploadArchiveThenDelete } from '@frontend-monitor/upload-sourcemaps';
258
+
259
+ const { ok, fail } = await uploadAll({
260
+ endpoint: 'https://monitor.example.com/monitorApi',
261
+ projectId: 'your-project-id',
262
+ secretKey: 'your-secret-key',
263
+ release: '1.0.0',
264
+ dir: './dist',
265
+ deleteAfterUpload: true,
266
+ });
267
+ ```
268
+
269
+ | 方法 | 说明 |
270
+ |------|------|
271
+ | `uploadAll(opts, logger?)` | 批量上传目录下 .map,返回 `{ ok, fail, deleted }` |
272
+ | `uploadOne(opts)` | 上传单个文件 |
273
+ | `findMapFiles(dir)` | 递归查找 .map 路径 |
274
+ | `uploadArchiveThenDelete(opts, logger?)` | 打 tar 包上传并删除本地 .map |
275
+ | `uploadArchiveChunkedThenDelete(opts, logger?)` | 分片上传压缩包并删除本地 .map |
276
+
277
+ ---
278
+
279
+ ## 与 SDK 配合
280
+
281
+ 本工具负责**上传 Source Map**,[`@frontend-monitor/sdk`](https://www.npmjs.com/package/@frontend-monitor/sdk) 负责**采集并上报错误**。两者配合才能在管理端看到还原后的堆栈。
282
+
283
+ | 数据 | 本工具 | SDK | 说明 |
284
+ |------|--------|-----|------|
285
+ | `release` | `--release` 或 package.json version | `init({ release })` | **必须一致** |
286
+ | `buildId` | `build` 子命令自动注入 | 自动读 `MONITOR_BUILD_ID` | 精确区分同版本多次构建 |
287
+ | `projectId` / `endpoint` | 上传目标 | 上报目标 | 需同一项目、同一 API |
288
+
289
+ 推荐:`npx upload-sourcemaps build --archive`,SDK 按文档接入即可,无需手动对齐参数。
290
+
291
+ ---
292
+
293
+ ## 常见问题
294
+
295
+ **堆栈仍显示压缩后位置?** 确认 SDK 的 `release` 与上传版本一致;堆栈中的脚本名与上传的 .map 文件名对应;管理端选择了正确项目。
296
+
297
+ **build 子命令后没有 .map?** Vite 一般自动生效;其他打包工具需在配置中根据 `BUILD_SOURCEMAP=1` 开启 Source Map。
298
+
299
+ **Project ID / Secret Key 在哪?** 登录管理端 → 项目设置。
300
+
301
+ **413 或超时?** 上传经 Nginx 时需放宽 body 与超时,或改用直连 API(见 [部署与 Nginx](#部署与-nginx))。
302
+
303
+ ---
304
+
305
+ ## License
306
+
307
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ var He=Object.defineProperty;var K=(t,e)=>()=>(t&&(e=t(t=0)),e);var _=(t,e)=>{for(var o in e)He(t,o,{get:e[o],enumerable:!0})};var z={};_(z,{extractProjectIdFromCert:()=>Ge,extractUploadKeyFromCert:()=>qe});function Ge(t){if(typeof t!="string")return"";let e=t.trim();if(e.startsWith(Ve+"$"))return e.split("$")[1]||"";if(e.includes(de)){let o=e.match(/Project:\s*(\S+)/);return o?o[1]:""}return""}function qe(t){if(typeof t!="string"||!t.includes(de))return null;let e=t.match(/Upload-Key:\s*([0-9a-fA-F]{64})/);if(!e)return null;try{return Buffer.from(e[1],"hex")}catch{return null}}var Ve,de,J=K(()=>{Ve="MC1",de="-----BEGIN MONITOR CERT-----"});var L={};_(L,{decryptArchive:()=>rt,encryptArchive:()=>tt,encryptArchiveWithKey:()=>he});import{createCipheriv as Ye,createDecipheriv as Ze,createHash as Qe,randomBytes as et}from"crypto";function me(t,e){let o=`${t}:${e}`;return Qe("sha256").update(o,"utf8").digest()}function tt(t,e,o){let r=me(e,o);return he(t,r)}function he(t,e){if(!Buffer.isBuffer(e)||e.length!==32)throw new Error("Invalid key: must be 32-byte Buffer");let o=et(X),r=Ye(fe,e,o,{authTagLength:B}),a=Buffer.concat([r.update(t),r.final()]),n=r.getAuthTag();return Buffer.concat([o,a,n])}function rt(t,e,o){if(t.length<X+B)throw new Error("Invalid encrypted archive: too short");let r=me(e,o),a=t.subarray(0,X),n=t.subarray(t.length-B),s=t.subarray(X,t.length-B),c=Ze(fe,r,a,{authTagLength:B});return c.setAuthTag(n),Buffer.concat([c.update(s),c.final()])}var fe,X,B,$=K(()=>{fe="aes-256-gcm",X=12,B=16});var te={};_(te,{DEFAULT_EXCLUDE_EXT:()=>V,collectFiles:()=>ee,createArchive:()=>G,deleteMapFiles:()=>ye,uploadArchive:()=>ft});import{createRequire as ot}from"module";import{readdir as we,unlink as nt,readFile as at,mkdtemp as st,rm as ct}from"fs/promises";import{join as H,relative as it}from"path";import{createWriteStream as ut}from"fs";import{tmpdir as lt}from"os";async function ee(t,e=t,o=V){let r=await we(t,{withFileTypes:!0}),a=[];for(let n of r){let s=H(t,n.name),c=it(e,s).replace(/\\/g,"/");if(n.isDirectory()){let p=await ee(s,e,o);a.push(...p)}else{let p=(n.name.includes(".")?"."+n.name.split(".").pop():"").toLowerCase();if(o.has(p))continue;a.push({relativePath:c,fullPath:s})}}return a}async function G(t,e,o=V,r=!0){let a=await ee(t,t,o),n=a.map(s=>s.relativePath);return n.length===0?0:new Promise((s,c)=>{let p=dt.create({gzip:r,cwd:t},n);p.on("error",c),p.pipe(ut(e)).on("close",()=>s(a.length)).on("error",c)})}async function ft(t,e=console){let{dir:o,endpoint:r,projectId:a,secretKey:n,authCert:s,release:c,buildId:p,excludeExt:f,noEncrypt:i}=t,d=!!s,{extractProjectIdFromCert:u,extractUploadKeyFromCert:m}=await Promise.resolve().then(()=>(J(),z)),l=a||(d?u(s):""),h=d?m(s):null,y=await st(H(lt(),"upload-sourcemaps-")),g=H(y,"archive.tar.gz");try{let k=await G(o,g,f||V,!0);if(k===0)return e.log("No files to pack (directory empty or only excluded). Skip upload."),{ok:!0,uploaded:0};let S=await at(g),x=!i&&(d?h!==null:!!(l&&n));if(x)if(h){let{encryptArchiveWithKey:D}=await Promise.resolve().then(()=>($(),L));S=D(S,h),e.log("Created tar.gz with",k,"file(s), encrypted with cert Upload-Key, uploading...")}else{let{encryptArchive:D}=await Promise.resolve().then(()=>($(),L));S=D(S,l,n),e.log("Created tar.gz with",k,"file(s), encrypted, uploading...")}else e.log("Created tar.gz with",k,"file(s), uploading...");let R="/sourcemaps/upload-archive",T=r.replace(/\/$/,""),C=T.endsWith(R)?T:T+R,I=new FormData;I.append("projectId",l),d||I.append("secretKey",n||""),I.append("release",c),p&&I.append("buildId",p),x&&I.append("encrypted","1"),I.append("file",new Blob([S]),x?"archive.tar.gz.enc":"archive.tar.gz");let M={"X-Project-Id":l};d?M["X-Auth-Cert"]=s:M["X-Secret-Key"]=n||"";let P=await fetch(C,{method:"POST",headers:M,body:I}),Z=await P.text();if(!P.ok)throw new Error(`${P.status} ${Z}`);let A=JSON.parse(Z||"{}");return e.log("Upload archive OK, server extracted and saved",A.uploaded,".map file(s)"),{ok:!0,uploaded:A.uploaded||0}}finally{await ct(y,{recursive:!0,force:!0})}}async function ye(t,e=[]){let o=await we(t,{withFileTypes:!0});for(let r of o){let a=H(t,r.name);r.isDirectory()?await ye(a,e):r.name.endsWith(".map")&&(await nt(a),e.push(a))}return e}var pt,dt,V,q=K(()=>{pt=ot(import.meta.url),dt=pt("tar"),V=new Set([".png",".jpg",".jpeg",".gif",".svg",".webp",".ico",".bmp",".avif",".woff",".woff2",".ttf",".eot",".otf",".mp4",".webm",".ogg",".mp3",".wav",".m4a",".aac",".pdf",".doc",".docx",".xls",".xlsx"])});var ge={};_(ge,{uploadArchiveChunked:()=>xt});import{readFile as mt,mkdtemp as ht,rm as wt}from"fs/promises";import{join as be}from"path";import{tmpdir as yt}from"os";function It(t){return t.replace(/\/$/,"")}function Y(t,e){let o=It(t),r=e.startsWith("/")?e:`/${e}`;return o.endsWith(r)?o:o+r}async function kt(t){return new Promise(e=>setTimeout(e,t))}async function St(t,e,o,r=gt){let a;for(let n=0;n<=r;n++)try{let s=await fetch(t,e),c=await s.text();if(!s.ok)throw new Error(`${s.status} ${c}`);return c?JSON.parse(c):{}}catch(s){if(a=s,n<r){let c=vt*Math.pow(2,n);o.log?.(` Retry ${n+1}/${r} after ${c}ms: ${s.message}`),await kt(c)}}throw a}async function xt(t,e=console){let{dir:o,endpoint:r,projectId:a,secretKey:n,authCert:s,release:c,buildId:p,noEncrypt:f,chunkSizeBytes:i=bt,excludeExt:d}=t,u=!!s,{extractProjectIdFromCert:m,extractUploadKeyFromCert:l}=await Promise.resolve().then(()=>(J(),z)),h=a||(u?m(s):""),y=u?l(s):null,g=await ht(be(yt(),"upload-sourcemaps-chunked-")),k=be(g,"archive.tar.gz");try{let S=await G(o,k,d||void 0,!0);if(S===0)return e.log?.("No files to pack. Skip upload."),{ok:!0,uploaded:0};let x=await mt(k),R=!f&&(u?y!==null:!!(h&&n));if(R){if(y){let{encryptArchiveWithKey:b}=await Promise.resolve().then(()=>($(),L));x=b(x,y)}else{let{encryptArchive:b}=await Promise.resolve().then(()=>($(),L));x=b(x,h,n)}e.log?.(`Created tar.gz with ${S} file(s), encrypted, uploading in chunks...`)}else e.log?.(`Created tar.gz with ${S} file(s), uploading in chunks...`);let T=x.length,C=Math.ceil(T/i),I={"Content-Type":"application/json"};u?I["X-Auth-Cert"]=s:(I["X-Project-Id"]=h,I["X-Secret-Key"]=n||"");let M=await fetch(Y(r,"/sourcemaps/upload-archive-multipart/init"),{method:"POST",headers:{...I,"Content-Type":"application/json"},body:JSON.stringify({projectId:h,secretKey:u?"":n||"",release:c,buildId:p||void 0,totalChunks:C,chunkSizeBytes:i,encrypted:R})}),P=await M.text();if(!M.ok)throw new Error(`${M.status} ${P}`);let A=JSON.parse(P||"{}").uploadId;if(!A)throw new Error("Init did not return uploadId");let D=new Set;for(let b=0;b<C;b++)D.add(b);try{let b=await fetch(Y(r,"/sourcemaps/upload-archive-multipart/status")+`?uploadId=${encodeURIComponent(A)}`,{method:"GET",headers:I}),F=await b.text();if(b.ok&&F){let N=JSON.parse(F).receivedChunks||[];N.forEach(U=>D.delete(U)),N.length>0&&e.log?.(`Resume: ${N.length}/${C} chunks already uploaded`)}}catch{}for(let b=0;b<C;b++){if(!D.has(b))continue;let F=b*i,pe=Math.min(F+i,T),N=x.subarray(F,pe),U=new FormData;U.append("uploadId",A),U.append("index",String(b)),U.append("totalChunks",String(C)),U.append("chunk",new Blob([N]),`chunk-${b}`);let Xe=Y(r,"/sourcemaps/upload-archive-multipart/chunk");await St(Xe,{method:"POST",headers:u?{"X-Auth-Cert":s}:{"X-Project-Id":h,"X-Secret-Key":n||""},body:U},e),e.log?.(` Chunk ${b+1}/${C} OK`)}let Q=await fetch(Y(r,"/sourcemaps/upload-archive-multipart/complete"),{method:"POST",headers:{...I,"Content-Type":"application/json"},body:JSON.stringify({uploadId:A})}),ue=await Q.text();if(!Q.ok)throw new Error(`${Q.status} ${ue}`);let le=JSON.parse(ue||"{}").uploaded??0;return e.log?.("Chunked upload OK, server extracted and saved",le,".map file(s)"),{ok:!0,uploaded:le}}finally{await wt(g,{recursive:!0,force:!0})}}var bt,gt,vt,ve=K(()=>{q();bt=1*1024*1024,gt=3,vt=1e3});var Be={};_(Be,{loadMonitorConfig:()=>Ke,loadUploadSourcemapsJson:()=>_e,resolveUploadConfig:()=>Rt});import{readFile as Pe}from"fs/promises";import{join as Re,resolve as Pt}from"path";import{pathToFileURL as Fe}from"url";function Ne(){try{return process.env.UPLOAD_ENV||process.env.NODE_ENV||"production"}catch{return"production"}}async function Ke(t){let e=["monitor.config.js","monitor.config.cjs","monitor.config.mjs"];for(let o of e){let r=Re(t,o),a;try{a=await import(Fe(r).href)}catch{continue}let n=a.default;if(!n||typeof n!="object")return null;let s=Ne(),c=n.common||{},p=n[s]||{},f={...c,...p},i=(f.endpoint||"").trim(),d=(f.apiPrefix||"").trim(),u=i+(d.startsWith("/")?d:d?"/"+d:"");return{projectId:f.projectId,secretKey:f.secretKey,authCert:f.authCert,endpoint:u||void 0,release:f.release||n.version,version:n.version}}return null}async function _e(t){try{let e=await Pe(Re(t,".upload-sourcemaps.json"),"utf-8");return JSON.parse(e)}catch{return{}}}async function Rt(t,e){let o=null,r=null;if(e){let a=Pt(t,e);if(e.endsWith(".json"))try{r=JSON.parse(await Pe(a,"utf-8"))}catch{r={}}else try{let s=(await import(Fe(a).href)).default;if(s&&typeof s=="object"&&(s.common||s.production||s.development)){let c=Ne(),p=s.common||{},f=s[c]||{},i={...p,...f},d=(i.endpoint||"").trim(),u=(i.apiPrefix||"").trim();o={projectId:i.projectId,secretKey:i.secretKey,authCert:i.authCert,endpoint:d+(u.startsWith("/")?u:u?"/"+u:""),release:i.release||s.version}}else r=s||{}}catch{r={}}}else o=await Ke(t),r=await _e(t);return{projectId:r.projectId??o?.projectId,secretKey:r.secretKey??o?.secretKey,authCert:r.authCert??o?.authCert,endpoint:r.endpoint??o?.endpoint,release:r.release??o?.release??o?.version,dir:r.dir,chunkSize:r.chunkSize,uploadAsArchive:r.uploadAsArchive,noEncrypt:r.noEncrypt,buildCommand:r.buildCommand,buildOutputDir:r.buildOutputDir}}var Le=K(()=>{});import{readFile as Ft}from"fs/promises";import{resolve as Nt,dirname as Kt,join as _t}from"path";import{fileURLToPath as Bt}from"url";import{randomUUID as $e}from"crypto";import{spawn as Lt}from"child_process";import{readdir as jt,readFile as Ct,unlink as Et}from"fs/promises";import{join as Ot,basename as W}from"path";async function ke(t,e=[]){let o;try{o=await jt(t,{withFileTypes:!0})}catch(r){if(r?.code==="ENOENT")return e;throw r}for(let r of o){let a=Ot(t,r.name);r.isDirectory()?await ke(a,e):r.name.endsWith(".map")&&e.push(a)}return e}var Ie="/sourcemaps/upload";function Mt(t){let e=t.replace(/\/$/,"");return e.endsWith(Ie)?e:e+Ie}async function At(t){let{endpoint:e,projectId:o,secretKey:r,authCert:a,release:n,buildId:s,filePath:c}=t,p=!!a,f=o||(p?(await Promise.resolve().then(()=>(J(),z))).extractProjectIdFromCert(a):""),i=W(c,".map"),d=await Ct(c),u=Mt(e),m=new FormData;m.append("projectId",f),p||m.append("secretKey",r||""),m.append("release",n),s&&m.append("buildId",s),m.append("filename",i),m.append("file",new Blob([d]),W(c));let h=await fetch(u,{method:"POST",headers:p?{"X-Project-Id":f,"X-Auth-Cert":a}:{"X-Project-Id":f,"X-Secret-Key":r||""},body:m}),y=await h.text();if(!h.ok)throw new Error(`${h.status} ${y}`);return JSON.parse(y||"{}")}async function re(t,e=console){let{dir:o,endpoint:r,projectId:a,secretKey:n,authCert:s,release:c,buildId:p,deleteAfterUpload:f=!1,deleteEvenOnFailure:i=!1}=t,d=await ke(o);if(d.length===0)return e.log("No .map files found in",o),{ok:0,fail:0,deleted:0};let u=f&&i;e.log("Uploading",d.length,"source map(s) to",r,"release=",c,f?u?"(will delete local .map even on failure)":"(will delete local .map only after all succeed)":"");let m=0,l=0;for(let g of d)try{await At({endpoint:r,projectId:a,secretKey:n,authCert:s,release:c,buildId:p,filePath:g}),e.log(" OK",W(g)),m++}catch(k){e.error(" FAIL",W(g),k.message),l++}let h=f&&(l===0||i),y=0;if(h&&d.length>0){for(let g of d)try{await Et(g),y++,e.log(" Deleted",W(g))}catch(k){e.error(" Delete failed",g,k.message)}l>0&&i&&e.log("Deleted .map files despite upload failures. Fix errors and re-run to re-upload.")}else l>0&&!i&&e.log("Upload had failures. Local .map files were NOT deleted. Fix errors and re-run.");return{ok:m,fail:l,deleted:y}}async function oe(t,e=console){let{uploadArchive:o,deleteMapFiles:r}=await Promise.resolve().then(()=>(q(),te)),{dir:a,endpoint:n,projectId:s,secretKey:c,authCert:p,release:f,buildId:i,deleteEvenOnFailure:d=!1,noEncrypt:u}=t;try{let{uploaded:m}=await o({dir:a,endpoint:n,projectId:s,secretKey:c,authCert:p,release:f,buildId:i,noEncrypt:u},e);if(m===0)return{uploaded:0,deleted:0};let l=await r(a,[]);return e.log("Deleted",l.length,"local .map file(s)."),{uploaded:m,deleted:l.length}}catch(m){if(d)try{let l=await r(a,[]);e.log("Deleted",l.length,"local .map file(s) despite upload failure.")}catch(l){e.error("Delete .map after upload failure failed:",l.message)}else e.error("Upload failed. Local .map files were NOT deleted.",m.message);throw m}}async function ne(t,e=console){let{uploadArchiveChunked:o}=await Promise.resolve().then(()=>(ve(),ge)),{deleteMapFiles:r}=await Promise.resolve().then(()=>(q(),te)),{dir:a,endpoint:n,projectId:s,secretKey:c,authCert:p,release:f,buildId:i,deleteEvenOnFailure:d=!1,noEncrypt:u,chunkSizeBytes:m}=t;try{let{uploaded:l}=await o({dir:a,endpoint:n,projectId:s,secretKey:c,authCert:p,release:f,buildId:i,noEncrypt:u,chunkSizeBytes:m},e);if(l===0)return{uploaded:0,deleted:0};let h=await r(a,[]);return e.log("Deleted",h.length,"local .map file(s)."),{uploaded:l,deleted:h.length}}catch(l){if(d)try{let h=await r(a,[]);e.log("Deleted",h.length,"local .map file(s) despite upload failure.")}catch(h){e.error("Delete .map after upload failure failed:",h.message)}else e.error("Upload failed. Local .map files were NOT deleted.",l.message);throw l}}import{readFile as ae}from"fs/promises";import{join as j,resolve as Se}from"path";async function se(t){let e=[t,Se(t,".."),Se(t,"..",".."),process.cwd()];for(let o of e)try{return await ae(j(o,"package.json"),"utf-8"),o}catch{continue}return null}async function E(t){try{return await ae(t,"utf-8")}catch{return""}}async function xe(t){let e=["vite.config.js","vite.config.mjs","vite.config.cjs","vite.config.ts"];for(let o of e){let r=await E(j(t,o));if(r){if(/\bsourcemap\s*:\s*false\b/.test(r)||/build\s*:\s*\{[^}]*sourcemap\s*:\s*false/.test(r))return{emitsSourceMap:!1,bundler:"vite",keepMapInOutput:!1};if(/\bsourcemap\s*:\s*['"]inline['"]/.test(r)||/build\s*:\s*\{[\s\S]*?sourcemap\s*:\s*['"]inline['"]/.test(r))return{emitsSourceMap:!0,bundler:"vite",keepMapInOutput:!1};if(/\bsourcemap\s*:\s*true\b/.test(r)||/\bsourcemap\s*:\s*['"]hidden['"]/.test(r))return{emitsSourceMap:!0,bundler:"vite",keepMapInOutput:!0};if(/build\s*:\s*\{[\s\S]*?sourcemap\s*:\s*true/.test(r))return{emitsSourceMap:!0,bundler:"vite",keepMapInOutput:!0};if(/build\s*:\s*\{[\s\S]*?sourcemap\s*:\s*['"]hidden['"]/.test(r))return{emitsSourceMap:!0,bundler:"vite",keepMapInOutput:!0};if(/BUILD_SOURCEMAP|SOURCEMAP/.test(r)&&/build\s*:\s*\{[\s\S]*?sourcemap\s*:/.test(r))return{emitsSourceMap:!0,bundler:"vite",keepMapInOutput:!0};if(/build\s*:|\bdefineConfig\s*\(/.test(r))return{emitsSourceMap:!1,bundler:"vite",keepMapInOutput:!1}}}return null}async function je(t){let e=["webpack.config.js","webpack.config.cjs","webpack.config.mjs","webpack.config.ts","webpack.common.js","webpack.prod.js"];for(let o of e){let r=await E(j(t,o));if(r){if(/\bdevtool\s*:\s*false\b/.test(r))return{emitsSourceMap:!1,bundler:"webpack",keepMapInOutput:!1};if(/\bdevtool\s*:\s*['"]source-map['"]/.test(r)||/\bdevtool\s*:\s*['"]hidden-source-map['"]/.test(r))return{emitsSourceMap:!0,bundler:"webpack",keepMapInOutput:!0};if(/\bdevtool\s*:\s*true\b/.test(r))return{emitsSourceMap:!0,bundler:"webpack",keepMapInOutput:!0}}}return null}async function Ce(t){let e=await E(j(t,"vue.config.js"))||await E(j(t,"vue.config.cjs"));return e?/\bproductionSourceMap\s*:\s*false\b/.test(e)?{emitsSourceMap:!1,bundler:"vue-cli",keepMapInOutput:!1}:/\bproductionSourceMap\s*:\s*true\b/.test(e)?{emitsSourceMap:!0,bundler:"vue-cli",keepMapInOutput:!0}:/\.devtool\s*\(\s*['"]source-map['"]\s*\)/.test(e)?{emitsSourceMap:!0,bundler:"vue-cli",keepMapInOutput:!0}:null:null}async function Ee(t){let e=["rollup.config.js","rollup.config.mjs","rollup.config.cjs"];for(let o of e){let r=await E(j(t,o));if(r){if(/\bsourcemap\s*:\s*false\b/.test(r))return{emitsSourceMap:!1,bundler:"rollup",keepMapInOutput:!1};if(/\bsourcemap\s*:\s*['"]inline['"]/.test(r))return{emitsSourceMap:!0,bundler:"rollup",keepMapInOutput:!1};if(/\bsourcemap\s*:\s*true\b/.test(r))return{emitsSourceMap:!0,bundler:"rollup",keepMapInOutput:!0}}}return null}async function Oe(t){let e=["next.config.js","next.config.mjs","next.config.cjs","next.config.ts"];for(let o of e){let r=await E(j(t,o));if(r){if(/\bproductionBrowserSourceMaps\s*:\s*true\b/.test(r))return{emitsSourceMap:!0,bundler:"next",keepMapInOutput:!0};if(/\bproductionBrowserSourceMaps\s*:\s*false\b/.test(r))return{emitsSourceMap:!1,bundler:"next",keepMapInOutput:!1};if(/module\.exports|next\s*\(/.test(r))return{emitsSourceMap:!1,bundler:"next",keepMapInOutput:!1}}}return null}async function Me(t){let e=await E(j(t,"nuxt.config.js"))||await E(j(t,"nuxt.config.ts"));return e?/\bsourcemap\s*:\s*false\b/.test(e)?{emitsSourceMap:!1,bundler:"nuxt",keepMapInOutput:!1}:/\bsourcemap\s*:\s*true\b/.test(e)?{emitsSourceMap:!0,bundler:"nuxt",keepMapInOutput:!0}:null:null}function Dt(t){let e={...t.dependencies,...t.devDependencies};return!e||typeof e!="object"?null:e.vite||e.vite?"vite":e.next?"next":e.nuxt||e.nuxt3?"nuxt":e.vue&&(e["@vue/cli-service"]||e["vue-cli-service"])?"vue-cli":e.webpack||e["react-scripts"]||e["@angular/cli"]?"webpack":e.rollup?"rollup":e.parcel?"parcel":null}async function ce(t){let e=await se(t);if(!e)return{emitsSourceMap:!0,keepMapInOutput:!1};let o={};try{let n=await ae(j(e,"package.json"),"utf-8");o=JSON.parse(n)}catch{return{emitsSourceMap:!0,keepMapInOutput:!1}}let r=Dt(o),a=[()=>r==="vite"?xe(e):null,()=>r==="next"?Oe(e):null,()=>r==="nuxt"?Me(e):null,()=>r==="vue-cli"?Ce(e):null,()=>r==="webpack"?je(e):null,()=>r==="rollup"?Ee(e):null,()=>xe(e),()=>Oe(e),()=>Ce(e),()=>je(e),()=>Ee(e),()=>Me(e)];for(let n of a){let s=await n();if(s)return s}return{emitsSourceMap:!0,keepMapInOutput:!1}}import{readFile as Ue,writeFile as Ae}from"fs/promises";import{join as De}from"path";async function Ut(t){try{return await Ue(t,"utf-8")}catch{return""}}function Tt(t){let e={...t?.dependencies||{},...t?.devDependencies||{}};return e.vite||e.vite?"vite":e.next?"next":e.nuxt||e.nuxt3?"nuxt":e.vue&&(e["@vue/cli-service"]||e["vue-cli-service"])?"vue-cli":e.webpack||e["react-scripts"]||e["@angular/cli"]?"webpack":e.rollup?"rollup":null}var ie=[{name:"vite",configNames:["vite.config.js","vite.config.mjs","vite.config.cjs","vite.config.ts"],needPatch:t=>/\bsourcemap\s*:\s*false\b/.test(t),patch:t=>t.replace(/(sourcemap\s*:\s*)false\b/,"$1true")},{name:"webpack",configNames:["webpack.config.js","webpack.config.cjs","webpack.config.mjs","webpack.config.ts","webpack.common.js","webpack.prod.js"],needPatch:t=>/\bdevtool\s*:\s*false\b/.test(t),patch:t=>t.replace(/(devtool\s*:\s*)false\b/,(e,o)=>`${o}'source-map'`)},{name:"vue-cli",configNames:["vue.config.js","vue.config.cjs"],needPatch:t=>/\bproductionSourceMap\s*:\s*false\b/.test(t),patch:t=>t.replace(/(productionSourceMap\s*:\s*)false\b/,"$1true")},{name:"rollup",configNames:["rollup.config.js","rollup.config.mjs","rollup.config.cjs"],needPatch:t=>/\bsourcemap\s*:\s*false\b/.test(t),patch:t=>t.replace(/(sourcemap\s*:\s*)false\b/,"$1true")},{name:"next",configNames:["next.config.js","next.config.mjs","next.config.cjs","next.config.ts"],needPatch:t=>/\bproductionBrowserSourceMaps\s*:\s*false\b/.test(t),patch:t=>t.replace(/(productionBrowserSourceMaps\s*:\s*)false\b/,"$1true")},{name:"nuxt",configNames:["nuxt.config.js","nuxt.config.ts"],needPatch:t=>/\bsourcemap\s*:\s*false\b/.test(t),patch:t=>t.replace(/(sourcemap\s*:\s*)false\b/,"$1true")}];async function Te(t){let e=await se(t);if(!e)return{restore:async()=>{}};let o={};try{let n=await Ue(De(e,"package.json"),"utf-8");o=JSON.parse(n)}catch{return{restore:async()=>{}}}let r=Tt(o),a=r?[...ie.filter(n=>n.name===r),...ie.filter(n=>n.name!==r)]:ie;for(let n of a)for(let s of n.configNames){let c=De(e,s),p=await Ut(c);if(!p||!n.needPatch(p))continue;let f=n.patch(p);return await Ae(c,f,"utf-8"),{restore:async()=>{try{await Ae(c,p,"utf-8")}catch(i){console.error(`Restore ${n.name} config failed:`,i?.message||i)}}}}return{restore:async()=>{}}}var Er=Kt(Bt(import.meta.url));function v(t,e){let o=process.env[e];if(o)return o;let r=process.argv.indexOf(t);return r!==-1&&process.argv[r+1]?process.argv[r+1]:null}function O(t){return process.argv.includes(t)}function $t(t){if(t==null||t==="")return null;let e=String(t).trim().toUpperCase(),o=parseInt(e.replace(/[^0-9]/g,""),10)||0;return e.endsWith("G")?o*1024*1024*1024:e.endsWith("MB")||e.endsWith("M")?o*1024*1024:e.endsWith("KB")||e.endsWith("K")?o*1024:/^\d+$/.test(String(t).trim())?parseInt(String(t).trim(),10)||null:o>0?o*1024*1024:null}function We(t={}){let e=v("--chunk-size","UPLOAD_SOURCEMAPS_CHUNK_SIZE")||t.chunkSize;return $t(e)}async function Wt(){let{resolveUploadConfig:t}=await Promise.resolve().then(()=>(Le(),Be));return t(process.cwd(),v("--config","UPLOAD_SOURCEMAPS_CONFIG")||void 0)}async function zt(t){let e=[t,Nt(t,".."),process.cwd()];for(let o of e)try{let r=await Ft(_t(o,"package.json"),"utf-8"),a=JSON.parse(r);if(a.version&&typeof a.version=="string")return a.version.trim()}catch{continue}return null}async function ze(t,e){return t&&t!=="default"?t:await zt(e)||"default"}var w=await Wt(),Jt=process.argv[2]==="build";function Je(t,e){return t||e===!0}async function Xt(){let t=v("--project-id","PROJECT_ID")||w.projectId,e=v("--secret-key","SECRET_KEY")||w.secretKey,o=v("--auth-cert","MONITOR_AUTH_CERT")||w.authCert,r=(v("--endpoint","ENDPOINT")||w.endpoint||"").replace(/\/$/,"");if(!r)throw new Error("build subcommand requires endpoint (config file or env/args).");if(!o&&(!t||!e))throw new Error("build subcommand requires projectId+secretKey or authCert (config file or env/args).");let a=w.buildCommand||process.env.UPLOAD_SOURCEMAPS_BUILD_CMD||"npm run build",n=w.buildOutputDir||w.dir||v("--dir","DIR")||process.cwd(),s=O("--archive")||w.uploadAsArchive===!0,c=Je(O("--no-encrypt"),w.noEncrypt),f=!(await ce(n)).keepMapInOutput||O("--delete-after-upload")||O("-d")||w.deleteAfterUpload===!0,i=v("--build-id","BUILD_ID")||w.buildId||$e(),d=await Te(n);try{console.log("Step 1: Running build (forcing source map generation):",a),console.log("Build ID:",i);let[u,...m]=a.split(/\s+/);await new Promise((y,g)=>{Lt(u,m,{stdio:"inherit",shell:!0,env:{...process.env,BUILD_SOURCEMAP:"1",SOURCEMAP:"1",MONITOR_BUILD_ID:i,VITE_MONITOR_BUILD_ID:i}}).on("exit",S=>S===0?y():g(new Error(`Build exited ${S}`)))}),console.log("Step 2: Uploading source maps...");let l=await ze(v("--release","RELEASE")||w.release,n);l!=="default"&&console.log("Release (from package.json or config):",l);let h=We(w);if(s){let y={dir:n,endpoint:r,projectId:t,secretKey:e,authCert:o,release:l,buildId:i,deleteEvenOnFailure:!1,noEncrypt:c};(h?await ne({...y,chunkSizeBytes:h},console):await oe(y,console)).uploaded===0&&console.log("No .map files in output. For Vite we patch config to force source map; check build output dir and README.")}else{let{ok:y,fail:g}=await re({endpoint:r,projectId:t,secretKey:e,authCert:o,release:l,buildId:i,dir:n,deleteAfterUpload:f,deleteEvenOnFailure:!1},console);if(g>0)throw new Error("Upload had failures. Local .map files were NOT deleted. Fix errors and re-run.");y===0&&console.log("No .map files in output. For Vite we patch config to force source map; check build output dir and README.")}console.log("Done. Output dir:",n)}catch(u){throw console.error("Error:",u.message),console.error(f?"If the failure was during upload, local .map files may have been removed. Fix the issue and re-run.":"Local files were not modified. Fix the issue and re-run."),u}finally{await d.restore()}}if(Jt)try{await Xt()}catch{process.exitCode=1}else{let t=v("--project-id","PROJECT_ID")||w.projectId,e=v("--secret-key","SECRET_KEY")||w.secretKey,o=v("--auth-cert","MONITOR_AUTH_CERT")||w.authCert,r=(v("--endpoint","ENDPOINT")||w.endpoint||"").replace(/\/$/,""),a=v("--dir","DIR")||w.dir||process.cwd(),n=await ze(v("--release","RELEASE")||w.release,a),s=v("--build-id","BUILD_ID")||w.buildId||$e(),c=O("--archive")||w.uploadAsArchive===!0,p=Je(O("--no-encrypt"),w.noEncrypt),f=We(w),d=!(await ce(a)).keepMapInOutput||O("--delete-after-upload")||O("-d")||w.deleteAfterUpload===!0;if(!r)console.error("Usage: ENDPOINT is required.");else if(!o&&(!t||!e))console.error("Usage: PROJECT_ID and SECRET_KEY are required, or use AUTH_CERT (certificate auth)."),console.error(""),console.error(" Arguments:"),console.error(" --project-id <id> Project ID"),console.error(" --secret-key <key> Secret Key"),console.error(" --endpoint <url> API base URL (e.g. https://api.example.com/monitorApi)"),console.error(" --auth-cert <cert> Certificate content (or MONITOR_AUTH_CERT); use with monitor.config.js, no secretKey needed"),console.error(" --release <tag> Release version (default: read from package.json)"),console.error(" --build-id <id> Build ID (default: auto-generated UUID)"),console.error(" --dir <path> Directory (default: cwd)"),console.error(" --delete-after-upload, -d Delete local .map after upload (from deployment output)"),console.error(" --archive Pack as .tar.gz (encrypted by default) \u2192 upload \u2192 server decrypts and extracts"),console.error(" --chunk-size <size> With --archive: chunked upload (e.g. 1M, 5M), default 1MB, enables resumable and retry"),console.error(" --no-encrypt With --archive: upload plain tar.gz (certificate auth implies no-encrypt)"),console.error(" --config <path> Config file (default: monitor.config.js + .upload-sourcemaps.json)"),console.error(""),console.error(" Whether to delete .map after upload: inferred from project bundler config (Vite/Webpack etc.). Use -d to force delete."),process.exitCode=1;else{n!=="default"&&console.log("Release:",n),console.log("Build ID:",s);try{if(c){let u={dir:a,endpoint:r,projectId:t,secretKey:e,authCert:o,release:n,buildId:s,deleteEvenOnFailure:!1,noEncrypt:p};f?await ne({...u,chunkSizeBytes:f},console):await oe(u,console)}else{let{ok:u,fail:m,deleted:l}=await re({endpoint:r,projectId:t,secretKey:e,authCert:o,release:n,buildId:s,dir:a,deleteAfterUpload:d,deleteEvenOnFailure:d},console);l>0&&console.log("Deleted",l,"local .map file(s) from output."),m>0&&(console.error(d?"Upload had failures. Local .map files were still deleted (per project config). Fix errors and re-run to re-upload.":"Upload had failures. Local .map files were NOT deleted. Fix errors and re-run."),process.exitCode=1)}}catch(u){console.error("Error:",u.message),console.error(d?"Local .map files were still deleted. Fix the issue and re-run.":"Local files were not modified. Fix the issue and re-run."),process.exitCode=1}}}
package/dist/upload.js ADDED
@@ -0,0 +1 @@
1
+ var ue=Object.defineProperty;var U=(t,e)=>()=>(t&&(e=t(t=0)),e);var L=(t,e)=>{for(var a in e)ue(t,a,{get:e[a],enumerable:!0})};var R={};L(R,{extractProjectIdFromCert:()=>he,extractUploadKeyFromCert:()=>me});function he(t){if(typeof t!="string")return"";let e=t.trim();if(e.startsWith(fe+"$"))return e.split("$")[1]||"";if(e.includes(ee)){let a=e.match(/Project:\s*(\S+)/);return a?a[1]:""}return""}function me(t){if(typeof t!="string"||!t.includes(ee))return null;let e=t.match(/Upload-Key:\s*([0-9a-fA-F]{64})/);if(!e)return null;try{return Buffer.from(e[1],"hex")}catch{return null}}var fe,ee,X=U(()=>{fe="MC1",ee="-----BEGIN MONITOR CERT-----"});var O={};L(O,{decryptArchive:()=>Ie,encryptArchive:()=>Ce,encryptArchiveWithKey:()=>ae});import{createCipheriv as we,createDecipheriv as ye,createHash as ve,randomBytes as xe}from"crypto";function re(t,e){let a=`${t}:${e}`;return ve("sha256").update(a,"utf8").digest()}function Ce(t,e,a){let r=re(e,a);return ae(t,r)}function ae(t,e){if(!Buffer.isBuffer(e)||e.length!==32)throw new Error("Invalid key: must be 32-byte Buffer");let a=xe(B),r=we(te,e,a,{authTagLength:K}),o=Buffer.concat([r.update(t),r.final()]),n=r.getAuthTag();return Buffer.concat([a,o,n])}function Ie(t,e,a){if(t.length<B+K)throw new Error("Invalid encrypted archive: too short");let r=re(e,a),o=t.subarray(0,B),n=t.subarray(t.length-K),i=t.subarray(B,t.length-K),c=ye(te,r,o,{authTagLength:K});return c.setAuthTag(n),Buffer.concat([c.update(i),c.final()])}var te,B,K,P=U(()=>{te="aes-256-gcm",B=12,K=16});var Y={};L(Y,{DEFAULT_EXCLUDE_EXT:()=>z,collectFiles:()=>q,createArchive:()=>M,deleteMapFiles:()=>ne,uploadArchive:()=>je});import{createRequire as ke}from"module";import{readdir as oe,unlink as Ee,readFile as ge,mkdtemp as be,rm as Te}from"fs/promises";import{join as _,relative as Ae}from"path";import{createWriteStream as De}from"fs";import{tmpdir as Fe}from"os";async function q(t,e=t,a=z){let r=await oe(t,{withFileTypes:!0}),o=[];for(let n of r){let i=_(t,n.name),c=Ae(e,i).replace(/\\/g,"/");if(n.isDirectory()){let p=await q(i,e,a);o.push(...p)}else{let p=(n.name.includes(".")?"."+n.name.split(".").pop():"").toLowerCase();if(a.has(p))continue;o.push({relativePath:c,fullPath:i})}}return o}async function M(t,e,a=z,r=!0){let o=await q(t,t,a),n=o.map(i=>i.relativePath);return n.length===0?0:new Promise((i,c)=>{let p=$e.create({gzip:r,cwd:t},n);p.on("error",c),p.pipe(De(e)).on("close",()=>i(o.length)).on("error",c)})}async function je(t,e=console){let{dir:a,endpoint:r,projectId:o,secretKey:n,authCert:i,release:c,buildId:p,excludeExt:w,noEncrypt:h}=t,u=!!i,{extractProjectIdFromCert:m,extractUploadKeyFromCert:l}=await Promise.resolve().then(()=>(X(),R)),s=o||(u?m(i):""),d=u?l(i):null,x=await be(_(Fe(),"upload-sourcemaps-")),v=_(x,"archive.tar.gz");try{let C=await M(a,v,w||z,!0);if(C===0)return e.log("No files to pack (directory empty or only excluded). Skip upload."),{ok:!0,uploaded:0};let k=await ge(v),I=!h&&(u?d!==null:!!(s&&n));if(I)if(d){let{encryptArchiveWithKey:T}=await Promise.resolve().then(()=>(P(),O));k=T(k,d),e.log("Created tar.gz with",C,"file(s), encrypted with cert Upload-Key, uploading...")}else{let{encryptArchive:T}=await Promise.resolve().then(()=>(P(),O));k=T(k,s,n),e.log("Created tar.gz with",C,"file(s), encrypted, uploading...")}else e.log("Created tar.gz with",C,"file(s), uploading...");let S="/sourcemaps/upload-archive",D=r.replace(/\/$/,""),E=D.endsWith(S)?D:D+S,y=new FormData;y.append("projectId",s),u||y.append("secretKey",n||""),y.append("release",c),p&&y.append("buildId",p),I&&y.append("encrypted","1"),y.append("file",new Blob([k]),I?"archive.tar.gz.enc":"archive.tar.gz");let g={"X-Project-Id":s};u?g["X-Auth-Cert"]=i:g["X-Secret-Key"]=n||"";let F=await fetch(E,{method:"POST",headers:g,body:y}),G=await F.text();if(!F.ok)throw new Error(`${F.status} ${G}`);let b=JSON.parse(G||"{}");return e.log("Upload archive OK, server extracted and saved",b.uploaded,".map file(s)"),{ok:!0,uploaded:b.uploaded||0}}finally{await Te(x,{recursive:!0,force:!0})}}async function ne(t,e=[]){let a=await oe(t,{withFileTypes:!0});for(let r of a){let o=_(t,r.name);r.isDirectory()?await ne(o,e):r.name.endsWith(".map")&&(await Ee(o),e.push(o))}return e}var Se,$e,z,W=U(()=>{Se=ke(import.meta.url),$e=Se("tar"),z=new Set([".png",".jpg",".jpeg",".gif",".svg",".webp",".ico",".bmp",".avif",".woff",".woff2",".ttf",".eot",".otf",".mp4",".webm",".ogg",".mp3",".wav",".m4a",".aac",".pdf",".doc",".docx",".xls",".xlsx"])});var ce={};L(ce,{uploadArchiveChunked:()=>ze});import{readFile as Ke,mkdtemp as Oe,rm as Pe}from"fs/promises";import{join as ie}from"path";import{tmpdir as Ne}from"os";function Xe(t){return t.replace(/\/$/,"")}function J(t,e){let a=Xe(t),r=e.startsWith("/")?e:`/${e}`;return a.endsWith(r)?a:a+r}async function Be(t){return new Promise(e=>setTimeout(e,t))}async function _e(t,e,a,r=Le){let o;for(let n=0;n<=r;n++)try{let i=await fetch(t,e),c=await i.text();if(!i.ok)throw new Error(`${i.status} ${c}`);return c?JSON.parse(c):{}}catch(i){if(o=i,n<r){let c=Re*Math.pow(2,n);a.log?.(` Retry ${n+1}/${r} after ${c}ms: ${i.message}`),await Be(c)}}throw o}async function ze(t,e=console){let{dir:a,endpoint:r,projectId:o,secretKey:n,authCert:i,release:c,buildId:p,noEncrypt:w,chunkSizeBytes:h=Ue,excludeExt:u}=t,m=!!i,{extractProjectIdFromCert:l,extractUploadKeyFromCert:s}=await Promise.resolve().then(()=>(X(),R)),d=o||(m?l(i):""),x=m?s(i):null,v=await Oe(ie(Ne(),"upload-sourcemaps-chunked-")),C=ie(v,"archive.tar.gz");try{let k=await M(a,C,u||void 0,!0);if(k===0)return e.log?.("No files to pack. Skip upload."),{ok:!0,uploaded:0};let I=await Ke(C),S=!w&&(m?x!==null:!!(d&&n));if(S){if(x){let{encryptArchiveWithKey:f}=await Promise.resolve().then(()=>(P(),O));I=f(I,x)}else{let{encryptArchive:f}=await Promise.resolve().then(()=>(P(),O));I=f(I,d,n)}e.log?.(`Created tar.gz with ${k} file(s), encrypted, uploading in chunks...`)}else e.log?.(`Created tar.gz with ${k} file(s), uploading in chunks...`);let D=I.length,E=Math.ceil(D/h),y={"Content-Type":"application/json"};m?y["X-Auth-Cert"]=i:(y["X-Project-Id"]=d,y["X-Secret-Key"]=n||"");let g=await fetch(J(r,"/sourcemaps/upload-archive-multipart/init"),{method:"POST",headers:{...y,"Content-Type":"application/json"},body:JSON.stringify({projectId:d,secretKey:m?"":n||"",release:c,buildId:p||void 0,totalChunks:E,chunkSizeBytes:h,encrypted:S})}),F=await g.text();if(!g.ok)throw new Error(`${g.status} ${F}`);let b=JSON.parse(F||"{}").uploadId;if(!b)throw new Error("Init did not return uploadId");let T=new Set;for(let f=0;f<E;f++)T.add(f);try{let f=await fetch(J(r,"/sourcemaps/upload-archive-multipart/status")+`?uploadId=${encodeURIComponent(b)}`,{method:"GET",headers:y}),$=await f.text();if(f.ok&&$){let j=JSON.parse($).receivedChunks||[];j.forEach(A=>T.delete(A)),j.length>0&&e.log?.(`Resume: ${j.length}/${E} chunks already uploaded`)}}catch{}for(let f=0;f<E;f++){if(!T.has(f))continue;let $=f*h,Q=Math.min($+h,D),j=I.subarray($,Q),A=new FormData;A.append("uploadId",b),A.append("index",String(f)),A.append("totalChunks",String(E)),A.append("chunk",new Blob([j]),`chunk-${f}`);let pe=J(r,"/sourcemaps/upload-archive-multipart/chunk");await _e(pe,{method:"POST",headers:m?{"X-Auth-Cert":i}:{"X-Project-Id":d,"X-Secret-Key":n||""},body:A},e),e.log?.(` Chunk ${f+1}/${E} OK`)}let H=await fetch(J(r,"/sourcemaps/upload-archive-multipart/complete"),{method:"POST",headers:{...y,"Content-Type":"application/json"},body:JSON.stringify({uploadId:b})}),V=await H.text();if(!H.ok)throw new Error(`${H.status} ${V}`);let Z=JSON.parse(V||"{}").uploaded??0;return e.log?.("Chunked upload OK, server extracted and saved",Z,".map file(s)"),{ok:!0,uploaded:Z}}finally{await Pe(v,{recursive:!0,force:!0})}}var Ue,Le,Re,se=U(()=>{W();Ue=1*1024*1024,Le=3,Re=1e3});import{readdir as Me,readFile as We,unlink as Je}from"fs/promises";import{join as Ge,basename as N}from"path";async function de(t,e=[]){let a;try{a=await Me(t,{withFileTypes:!0})}catch(r){if(r?.code==="ENOENT")return e;throw r}for(let r of a){let o=Ge(t,r.name);r.isDirectory()?await de(o,e):r.name.endsWith(".map")&&e.push(o)}return e}var le="/sourcemaps/upload";function He(t){let e=t.replace(/\/$/,"");return e.endsWith(le)?e:e+le}async function qe(t){let{endpoint:e,projectId:a,secretKey:r,authCert:o,release:n,buildId:i,filePath:c}=t,p=!!o,w=a||(p?(await Promise.resolve().then(()=>(X(),R))).extractProjectIdFromCert(o):""),h=N(c,".map"),u=await We(c),m=He(e),l=new FormData;l.append("projectId",w),p||l.append("secretKey",r||""),l.append("release",n),i&&l.append("buildId",i),l.append("filename",h),l.append("file",new Blob([u]),N(c));let d=await fetch(m,{method:"POST",headers:p?{"X-Project-Id":w,"X-Auth-Cert":o}:{"X-Project-Id":w,"X-Secret-Key":r||""},body:l}),x=await d.text();if(!d.ok)throw new Error(`${d.status} ${x}`);return JSON.parse(x||"{}")}async function pt(t,e=console){let{dir:a,endpoint:r,projectId:o,secretKey:n,authCert:i,release:c,buildId:p,deleteAfterUpload:w=!1,deleteEvenOnFailure:h=!1}=t,u=await de(a);if(u.length===0)return e.log("No .map files found in",a),{ok:0,fail:0,deleted:0};let m=w&&h;e.log("Uploading",u.length,"source map(s) to",r,"release=",c,w?m?"(will delete local .map even on failure)":"(will delete local .map only after all succeed)":"");let l=0,s=0;for(let v of u)try{await qe({endpoint:r,projectId:o,secretKey:n,authCert:i,release:c,buildId:p,filePath:v}),e.log(" OK",N(v)),l++}catch(C){e.error(" FAIL",N(v),C.message),s++}let d=w&&(s===0||h),x=0;if(d&&u.length>0){for(let v of u)try{await Je(v),x++,e.log(" Deleted",N(v))}catch(C){e.error(" Delete failed",v,C.message)}s>0&&h&&e.log("Deleted .map files despite upload failures. Fix errors and re-run to re-upload.")}else s>0&&!h&&e.log("Upload had failures. Local .map files were NOT deleted. Fix errors and re-run.");return{ok:l,fail:s,deleted:x}}async function ut(t,e=console){let{uploadArchive:a,deleteMapFiles:r}=await Promise.resolve().then(()=>(W(),Y)),{dir:o,endpoint:n,projectId:i,secretKey:c,authCert:p,release:w,buildId:h,deleteEvenOnFailure:u=!1,noEncrypt:m}=t;try{let{uploaded:l}=await a({dir:o,endpoint:n,projectId:i,secretKey:c,authCert:p,release:w,buildId:h,noEncrypt:m},e);if(l===0)return{uploaded:0,deleted:0};let s=await r(o,[]);return e.log("Deleted",s.length,"local .map file(s)."),{uploaded:l,deleted:s.length}}catch(l){if(u)try{let s=await r(o,[]);e.log("Deleted",s.length,"local .map file(s) despite upload failure.")}catch(s){e.error("Delete .map after upload failure failed:",s.message)}else e.error("Upload failed. Local .map files were NOT deleted.",l.message);throw l}}async function ft(t,e=console){let{uploadArchiveChunked:a}=await Promise.resolve().then(()=>(se(),ce)),{deleteMapFiles:r}=await Promise.resolve().then(()=>(W(),Y)),{dir:o,endpoint:n,projectId:i,secretKey:c,authCert:p,release:w,buildId:h,deleteEvenOnFailure:u=!1,noEncrypt:m,chunkSizeBytes:l}=t;try{let{uploaded:s}=await a({dir:o,endpoint:n,projectId:i,secretKey:c,authCert:p,release:w,buildId:h,noEncrypt:m,chunkSizeBytes:l},e);if(s===0)return{uploaded:0,deleted:0};let d=await r(o,[]);return e.log("Deleted",d.length,"local .map file(s)."),{uploaded:s,deleted:d.length}}catch(s){if(u)try{let d=await r(o,[]);e.log("Deleted",d.length,"local .map file(s) despite upload failure.")}catch(d){e.error("Delete .map after upload failure failed:",d.message)}else e.error("Upload failed. Local .map files were NOT deleted.",s.message);throw s}}export{de as findMapFiles,pt as uploadAll,ft as uploadArchiveChunkedThenDelete,ut as uploadArchiveThenDelete,qe as uploadOne};
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@frontend-monitor/upload-sourcemaps",
3
+ "version": "0.0.2",
4
+ "description": "CLI to upload Source Map files to frontend-monitor API (for CI/CD)",
5
+ "type": "module",
6
+ "main": "dist/upload.js",
7
+ "bin": {
8
+ "upload-sourcemaps": "./dist/upload-sourcemaps.js"
9
+ },
10
+ "scripts": {
11
+ "build": "node build.js",
12
+ "prepublishOnly": "npm run build",
13
+ "publish:dry": "npm run build && npm publish --dry-run",
14
+ "publish:public": "npm run build && npm publish --access public --tag release"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "README.zh-CN.md"
20
+ ],
21
+ "devDependencies": {
22
+ "esbuild": "^0.24.2"
23
+ },
24
+ "keywords": [
25
+ "sourcemap",
26
+ "source-map",
27
+ "frontend",
28
+ "monitoring",
29
+ "cli",
30
+ "upload",
31
+ "ci"
32
+ ],
33
+ "author": "mishuxing",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": ""
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "dependencies": {
43
+ "tar": "^7.4.0"
44
+ }
45
+ }