@monocle.sh/cli 0.1.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.
- package/LICENSE +15 -0
- package/README.md +224 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +73 -0
- package/dist/index.d.mts +49 -0
- package/dist/index.mjs +2 -0
- package/dist/upload-DJfE9Qcx.mjs +167 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present, Julien Ripouteau
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# @monocle.sh/cli
|
|
2
|
+
|
|
3
|
+
CLI for Monocle - upload source maps to enable readable stack traces in your error monitoring.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @monocle.sh/cli
|
|
9
|
+
# or use npx
|
|
10
|
+
npx @monocle.sh/cli sourcemaps upload ...
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Upload Source Maps
|
|
16
|
+
|
|
17
|
+
Upload source maps after your build process:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
monocle-cli sourcemaps upload \
|
|
21
|
+
--api-key=mk_live_xxx \
|
|
22
|
+
--release=v1.2.3 \
|
|
23
|
+
./dist/**/*.map
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
With environment variable:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
MONOCLE_API_KEY=mk_live_xxx monocle-cli sourcemaps upload \
|
|
30
|
+
--release=$(git rev-parse HEAD) \
|
|
31
|
+
./dist/**/*.map
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Options
|
|
35
|
+
|
|
36
|
+
| Option | Description | Default |
|
|
37
|
+
| --------------------- | -------------------------------------------------- | ------------------------ |
|
|
38
|
+
| `--api-key <key>` | Monocle API key (or set `MONOCLE_API_KEY` env var) | - |
|
|
39
|
+
| `--release <version>` | Release version (e.g., `v1.2.3` or git SHA) | - |
|
|
40
|
+
| `--url <url>` | Monocle API URL | `https://api.monocle.sh` |
|
|
41
|
+
| `--dry-run` | Show what would be uploaded without uploading | `false` |
|
|
42
|
+
|
|
43
|
+
### Release Identifier
|
|
44
|
+
|
|
45
|
+
The release identifier links exceptions to their source maps. It must match the `serviceVersion` configured in your Monocle agent:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// config/monocle.ts
|
|
49
|
+
export default defineConfig({
|
|
50
|
+
serviceVersion: env.get('APP_VERSION'), // e.g., "v1.2.3" or git SHA
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Best practices:
|
|
55
|
+
|
|
56
|
+
- Use git commit SHA for precise matching: `$(git rev-parse HEAD)`
|
|
57
|
+
- Use semver for human-readable releases: `v1.2.3`
|
|
58
|
+
- Must match exactly between upload and runtime config
|
|
59
|
+
|
|
60
|
+
## CI/CD Integration
|
|
61
|
+
|
|
62
|
+
### GitHub Actions
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
name: Deploy
|
|
66
|
+
|
|
67
|
+
on:
|
|
68
|
+
push:
|
|
69
|
+
branches: [main]
|
|
70
|
+
|
|
71
|
+
jobs:
|
|
72
|
+
deploy:
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
steps:
|
|
75
|
+
- uses: actions/checkout@v4
|
|
76
|
+
|
|
77
|
+
- name: Setup Node.js
|
|
78
|
+
uses: actions/setup-node@v4
|
|
79
|
+
with:
|
|
80
|
+
node-version: '20'
|
|
81
|
+
|
|
82
|
+
- name: Install dependencies
|
|
83
|
+
run: npm ci
|
|
84
|
+
|
|
85
|
+
- name: Build
|
|
86
|
+
run: npm run build
|
|
87
|
+
|
|
88
|
+
- name: Upload Source Maps
|
|
89
|
+
run: |
|
|
90
|
+
npx @monocle.sh/cli sourcemaps upload \
|
|
91
|
+
--api-key=${{ secrets.MONOCLE_API_KEY }} \
|
|
92
|
+
--release=${{ github.sha }} \
|
|
93
|
+
./dist/**/*.map
|
|
94
|
+
|
|
95
|
+
- name: Deploy
|
|
96
|
+
run: # your deploy command
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### GitLab CI
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
deploy:
|
|
103
|
+
stage: deploy
|
|
104
|
+
script:
|
|
105
|
+
- npm ci
|
|
106
|
+
- npm run build
|
|
107
|
+
- npx @monocle.sh/cli sourcemaps upload
|
|
108
|
+
--api-key=$MONOCLE_API_KEY
|
|
109
|
+
--release=$CI_COMMIT_SHA
|
|
110
|
+
./dist/**/*.map
|
|
111
|
+
- # your deploy command
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### CircleCI
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
version: 2.1
|
|
118
|
+
|
|
119
|
+
jobs:
|
|
120
|
+
deploy:
|
|
121
|
+
docker:
|
|
122
|
+
- image: cimg/node:20.0
|
|
123
|
+
steps:
|
|
124
|
+
- checkout
|
|
125
|
+
- run: npm ci
|
|
126
|
+
- run: npm run build
|
|
127
|
+
- run:
|
|
128
|
+
name: Upload Source Maps
|
|
129
|
+
command: |
|
|
130
|
+
npx @monocle.sh/cli sourcemaps upload \
|
|
131
|
+
--api-key=$MONOCLE_API_KEY \
|
|
132
|
+
--release=$CIRCLE_SHA1 \
|
|
133
|
+
./dist/**/*.map
|
|
134
|
+
- run: # your deploy command
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## AdonisJS Configuration
|
|
138
|
+
|
|
139
|
+
For AdonisJS projects using `tsc` and Node.js (no bundler):
|
|
140
|
+
|
|
141
|
+
### 1. Enable source maps in tsconfig.json
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"compilerOptions": {
|
|
146
|
+
"sourceMap": true,
|
|
147
|
+
"inlineSources": true,
|
|
148
|
+
"sourceRoot": "/"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 2. Configure Monocle agent with release version
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// config/monocle.ts
|
|
157
|
+
import env from '#start/env'
|
|
158
|
+
|
|
159
|
+
export default defineConfig({
|
|
160
|
+
serviceVersion: env.get('APP_VERSION'),
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 3. Set APP_VERSION in your deployment
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# In your CI/CD or .env
|
|
168
|
+
APP_VERSION=$(git rev-parse HEAD)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 4. Upload source maps after build
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
# GitHub Actions example
|
|
175
|
+
- name: Build
|
|
176
|
+
run: node ace build
|
|
177
|
+
|
|
178
|
+
- name: Upload Source Maps
|
|
179
|
+
run: |
|
|
180
|
+
npx @monocle.sh/cli sourcemaps upload \
|
|
181
|
+
--api-key=${{ secrets.MONOCLE_API_KEY }} \
|
|
182
|
+
--release=${{ github.sha }} \
|
|
183
|
+
./build/**/*.map
|
|
184
|
+
|
|
185
|
+
- name: Deploy
|
|
186
|
+
run: # rsync, docker push, etc.
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The `--release` value must match `APP_VERSION` used at runtime.
|
|
190
|
+
|
|
191
|
+
### 5. (Optional) Delete source maps before deploy
|
|
192
|
+
|
|
193
|
+
Source maps can expose your source code. Delete them after upload:
|
|
194
|
+
|
|
195
|
+
```yaml
|
|
196
|
+
- name: Upload Source Maps
|
|
197
|
+
run: npx @monocle.sh/cli sourcemaps upload ...
|
|
198
|
+
|
|
199
|
+
- name: Remove source maps from build
|
|
200
|
+
run: find ./build -name "*.map" -delete
|
|
201
|
+
|
|
202
|
+
- name: Deploy
|
|
203
|
+
run: # deploy without .map files
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Troubleshooting
|
|
207
|
+
|
|
208
|
+
### No source map files found
|
|
209
|
+
|
|
210
|
+
- Check that your build generates `.map` files
|
|
211
|
+
- Verify your glob patterns match the output location
|
|
212
|
+
- Try using `--dry-run` to see what would be matched
|
|
213
|
+
|
|
214
|
+
### Authentication failed
|
|
215
|
+
|
|
216
|
+
- Verify your API key is correct
|
|
217
|
+
- Ensure the API key has write permissions for source maps
|
|
218
|
+
- Check that `MONOCLE_API_KEY` environment variable is set correctly
|
|
219
|
+
|
|
220
|
+
### Stack traces not resolving
|
|
221
|
+
|
|
222
|
+
- Ensure the `--release` value matches `serviceVersion` in your Monocle agent config
|
|
223
|
+
- Verify source maps were uploaded successfully
|
|
224
|
+
- Check that the source map filenames match the ones referenced in your minified code
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { i as uploadSourcemaps, t as UploadError } from "./upload-DJfE9Qcx.mjs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { readFileSync, readdirSync, rmSync } from "node:fs";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
import { program } from "commander";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { exec } from "node:child_process";
|
|
9
|
+
//#region src/cli.ts
|
|
10
|
+
const pkg = JSON.parse(readFileSync(join(import.meta.dirname, "..", "package.json"), "utf-8"));
|
|
11
|
+
console.log();
|
|
12
|
+
console.log(pc.magenta(pc.bold(" monocle.sh")), pc.dim(`v${pkg.version}`));
|
|
13
|
+
console.log();
|
|
14
|
+
program.name("monocle").description("Monocle CLI - upload source maps and manage your Monocle projects").version(pkg.version);
|
|
15
|
+
program.command("sourcemaps").description("Manage source maps").command("upload").description("Upload source maps to Monocle").argument("<patterns...>", "Glob patterns for source map files (e.g., ./dist/**/*.map)").option("--api-key <key>", "Monocle API key (or set MONOCLE_API_KEY env var)").option("--release <version>", "Release version (e.g., v1.2.3 or git SHA)").option("--url <url>", "Monocle API URL", "https://api.monocle.sh").option("--dry-run", "Show what would be uploaded without uploading").action(async (patterns, options) => {
|
|
16
|
+
const apiKey = options.apiKey || process.env.MONOCLE_API_KEY;
|
|
17
|
+
if (!apiKey) {
|
|
18
|
+
console.error(pc.red("Error: Missing API key"));
|
|
19
|
+
console.error("Provide --api-key option or set MONOCLE_API_KEY environment variable");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
if (!options.release) {
|
|
23
|
+
console.error(pc.red("Error: Missing --release option"));
|
|
24
|
+
console.error("Provide a release version (e.g., --release=v1.2.3 or --release=$(git rev-parse HEAD))");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
await uploadSourcemaps({
|
|
29
|
+
patterns,
|
|
30
|
+
apiKey,
|
|
31
|
+
release: options.release,
|
|
32
|
+
apiUrl: options.url,
|
|
33
|
+
dryRun: options.dryRun
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (error instanceof UploadError) console.error(pc.red(`Error: ${error.message}`));
|
|
37
|
+
else console.error(pc.red("Error: An unexpected error occurred"));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
program.command("dev").description("Start local DevTools server for development observability").option("--port <port>", "Port for the DevTools server", "4200").option("--host <host>", "Host to bind to", "0.0.0.0").option("--open", "Open browser automatically").option("--clean", "Reset all stored data before starting").option("--db-path <path>", "Custom path for DuckDB database").action(async (options) => {
|
|
42
|
+
const port = Number.parseInt(options.port, 10);
|
|
43
|
+
if (options.clean) {
|
|
44
|
+
const configDir = join(homedir(), ".config", "monocle");
|
|
45
|
+
try {
|
|
46
|
+
for (const file of readdirSync(configDir)) if (file.endsWith(".db") || file.endsWith(".db-wal") || file.endsWith(".db-shm")) rmSync(join(configDir, file));
|
|
47
|
+
console.log(pc.green("Data cleared."));
|
|
48
|
+
} catch {}
|
|
49
|
+
}
|
|
50
|
+
let devtools;
|
|
51
|
+
try {
|
|
52
|
+
devtools = await import("@monocle.sh/studio");
|
|
53
|
+
} catch {
|
|
54
|
+
console.error(pc.red("Error: @monocle.sh/studio is not installed"));
|
|
55
|
+
console.error("Install it with: pnpm add -D @monocle.sh/studio");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
await devtools.startServer({
|
|
60
|
+
port,
|
|
61
|
+
host: options.host,
|
|
62
|
+
dbPath: options.dbPath
|
|
63
|
+
});
|
|
64
|
+
if (options.open) exec(`open http://localhost:${port}`);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(pc.red("Failed to start DevTools server"));
|
|
67
|
+
if (error instanceof Error) console.error(error.message);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
program.parse();
|
|
72
|
+
//#endregion
|
|
73
|
+
export {};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/upload.d.ts
|
|
2
|
+
interface UploadSourcemapsOptions {
|
|
3
|
+
patterns: string[];
|
|
4
|
+
apiKey: string;
|
|
5
|
+
release: string;
|
|
6
|
+
apiUrl: string;
|
|
7
|
+
dryRun?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface UploadResult {
|
|
10
|
+
uploaded: Array<{
|
|
11
|
+
filename: string;
|
|
12
|
+
release: string;
|
|
13
|
+
sizeBytes: number;
|
|
14
|
+
}>;
|
|
15
|
+
count: number;
|
|
16
|
+
}
|
|
17
|
+
interface SourcemapFile {
|
|
18
|
+
path: string;
|
|
19
|
+
filename: string;
|
|
20
|
+
content: Buffer;
|
|
21
|
+
size: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Upload source maps to Monocle API.
|
|
25
|
+
*/
|
|
26
|
+
declare function uploadSourcemaps(options: UploadSourcemapsOptions): Promise<UploadResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Find source map files matching the given glob patterns.
|
|
29
|
+
*/
|
|
30
|
+
declare function findSourcemapFiles(patterns: string[]): Promise<string[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Load and validate a source map file. Returns the file with its content.
|
|
33
|
+
*/
|
|
34
|
+
declare function loadAndValidateSourcemap(filePath: string): SourcemapFile;
|
|
35
|
+
/**
|
|
36
|
+
* Validate that a file is a valid source map. (Legacy function for tests)
|
|
37
|
+
*/
|
|
38
|
+
declare function validateSourcemap(filePath: string): {
|
|
39
|
+
valid: boolean;
|
|
40
|
+
error?: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Custom error class for upload errors. Does not expose sensitive data.
|
|
44
|
+
*/
|
|
45
|
+
declare class UploadError extends Error {
|
|
46
|
+
constructor(message: string);
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { type SourcemapFile, UploadError, type UploadResult, type UploadSourcemapsOptions, findSourcemapFiles, loadAndValidateSourcemap, uploadSourcemaps, validateSourcemap };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
import ky, { HTTPError, TimeoutError } from "ky";
|
|
6
|
+
import fg from "fast-glob";
|
|
7
|
+
//#region src/upload.ts
|
|
8
|
+
const MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
9
|
+
const UPLOAD_TIMEOUT = 12e4;
|
|
10
|
+
/**
|
|
11
|
+
* Upload source maps to Monocle API.
|
|
12
|
+
*/
|
|
13
|
+
async function uploadSourcemaps(options) {
|
|
14
|
+
const { patterns, apiKey, release, apiUrl, dryRun } = options;
|
|
15
|
+
const spinner = ora("Finding source map files...").start();
|
|
16
|
+
const filePaths = await findSourcemapFiles(patterns);
|
|
17
|
+
if (filePaths.length === 0) {
|
|
18
|
+
spinner.fail("No source map files found");
|
|
19
|
+
console.error(pc.yellow(`Searched patterns: ${patterns.join(", ")}`));
|
|
20
|
+
console.error(pc.dim("Make sure the glob patterns match .map files in your build output"));
|
|
21
|
+
throw new UploadError("No source map files found");
|
|
22
|
+
}
|
|
23
|
+
spinner.text = `Validating ${filePaths.length} source map file(s)...`;
|
|
24
|
+
const files = [];
|
|
25
|
+
for (const filePath of filePaths) {
|
|
26
|
+
const file = loadAndValidateSourcemap(filePath);
|
|
27
|
+
files.push(file);
|
|
28
|
+
}
|
|
29
|
+
spinner.succeed(`Found ${files.length} valid source map file(s)`);
|
|
30
|
+
for (const file of files) {
|
|
31
|
+
const sizeKb = (file.size / 1024).toFixed(1);
|
|
32
|
+
console.log(pc.dim(` ${file.path} (${sizeKb} KB)`));
|
|
33
|
+
}
|
|
34
|
+
if (dryRun) {
|
|
35
|
+
console.log(pc.yellow("\nDry run mode - no files uploaded"));
|
|
36
|
+
return {
|
|
37
|
+
uploaded: [],
|
|
38
|
+
count: 0
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
spinner.start(`Uploading to ${sanitizeUrl(apiUrl)}...`);
|
|
42
|
+
const result = await uploadFiles({
|
|
43
|
+
files,
|
|
44
|
+
apiKey,
|
|
45
|
+
release,
|
|
46
|
+
apiUrl,
|
|
47
|
+
onProgress: (current, total) => {
|
|
48
|
+
spinner.text = `Uploading file ${current}/${total} to ${sanitizeUrl(apiUrl)}...`;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
spinner.succeed(`Uploaded ${result.count} source map(s) for release ${pc.cyan(release)}`);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Find source map files matching the given glob patterns.
|
|
56
|
+
*/
|
|
57
|
+
async function findSourcemapFiles(patterns) {
|
|
58
|
+
return (await fg(patterns, {
|
|
59
|
+
onlyFiles: true,
|
|
60
|
+
absolute: true,
|
|
61
|
+
ignore: ["**/node_modules/**"]
|
|
62
|
+
})).filter((file) => file.endsWith(".map"));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Load and validate a source map file. Returns the file with its content.
|
|
66
|
+
*/
|
|
67
|
+
function loadAndValidateSourcemap(filePath) {
|
|
68
|
+
const stats = fs.statSync(filePath);
|
|
69
|
+
if (stats.size > MAX_FILE_SIZE) throw new UploadError(`File ${filePath} exceeds 50MB limit (${(stats.size / 1024 / 1024).toFixed(1)}MB)`);
|
|
70
|
+
const content = fs.readFileSync(filePath);
|
|
71
|
+
const filename = path.basename(filePath);
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(content.toString("utf-8"));
|
|
74
|
+
if (typeof parsed.version !== "number") throw new UploadError(`Invalid source map ${filePath}: missing version field`);
|
|
75
|
+
if (!Array.isArray(parsed.sources)) throw new UploadError(`Invalid source map ${filePath}: missing sources array`);
|
|
76
|
+
if (typeof parsed.mappings !== "string") throw new UploadError(`Invalid source map ${filePath}: missing mappings field`);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error instanceof SyntaxError) throw new UploadError(`Invalid source map ${filePath}: invalid JSON`);
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
path: filePath,
|
|
83
|
+
filename,
|
|
84
|
+
content,
|
|
85
|
+
size: stats.size
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Validate that a file is a valid source map. (Legacy function for tests)
|
|
90
|
+
*/
|
|
91
|
+
function validateSourcemap(filePath) {
|
|
92
|
+
try {
|
|
93
|
+
loadAndValidateSourcemap(filePath);
|
|
94
|
+
return { valid: true };
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return {
|
|
97
|
+
valid: false,
|
|
98
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Upload files to the Monocle API.
|
|
104
|
+
*/
|
|
105
|
+
async function uploadFiles(options) {
|
|
106
|
+
const { files, apiKey, release, apiUrl, onProgress } = options;
|
|
107
|
+
const formData = new FormData();
|
|
108
|
+
formData.append("release", release);
|
|
109
|
+
let current = 0;
|
|
110
|
+
for (const file of files) {
|
|
111
|
+
current++;
|
|
112
|
+
onProgress?.(current, files.length);
|
|
113
|
+
const blob = new Blob([new Uint8Array(file.content)], { type: "application/json" });
|
|
114
|
+
formData.append("files", blob, file.filename);
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
return (await (await ky.post(`${apiUrl}/sourcemaps`, {
|
|
118
|
+
headers: { "x-api-key": apiKey },
|
|
119
|
+
body: formData,
|
|
120
|
+
timeout: UPLOAD_TIMEOUT
|
|
121
|
+
})).json()).data;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
throw handleUploadError(error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Custom error class for upload errors. Does not expose sensitive data.
|
|
128
|
+
*/
|
|
129
|
+
var UploadError = class extends Error {
|
|
130
|
+
constructor(message) {
|
|
131
|
+
super(message);
|
|
132
|
+
this.name = "UploadError";
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Handle upload errors and return a sanitized error.
|
|
137
|
+
*/
|
|
138
|
+
function handleUploadError(error) {
|
|
139
|
+
if (error instanceof TimeoutError) return new UploadError("Upload timed out. Check your network connection and try again.");
|
|
140
|
+
if (error instanceof HTTPError) {
|
|
141
|
+
const status = error.response.status;
|
|
142
|
+
if (status === 401) return new UploadError("Authentication failed. Check your API key.");
|
|
143
|
+
if (status === 413) return new UploadError("Upload too large. Maximum total size is 50MB per file.");
|
|
144
|
+
if (status === 429) return new UploadError("Rate limited. Please wait and try again.");
|
|
145
|
+
if (status >= 500) return new UploadError(`Server error (${status}). Please try again later.`);
|
|
146
|
+
return new UploadError(`Upload failed with status ${status}`);
|
|
147
|
+
}
|
|
148
|
+
if (error instanceof Error) {
|
|
149
|
+
if (error.message.includes("ECONNREFUSED")) return new UploadError("Connection refused. Check the API URL and your network.");
|
|
150
|
+
if (error.message.includes("ENOTFOUND")) return new UploadError("DNS resolution failed. Check the API URL.");
|
|
151
|
+
if (error.message.includes("ETIMEDOUT")) return new UploadError("Connection timed out. Check your network connection.");
|
|
152
|
+
}
|
|
153
|
+
return new UploadError("Upload failed. Check your network connection and try again.");
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Sanitize URL to remove sensitive parts for display.
|
|
157
|
+
*/
|
|
158
|
+
function sanitizeUrl(url) {
|
|
159
|
+
try {
|
|
160
|
+
const parsed = new URL(url);
|
|
161
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
162
|
+
} catch {
|
|
163
|
+
return url;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
export { validateSourcemap as a, uploadSourcemaps as i, findSourcemapFiles as n, loadAndValidateSourcemap as r, UploadError as t };
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@monocle.sh/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Monocle CLI - upload source maps and manage your Monocle projects",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"error-tracking",
|
|
8
|
+
"monocle",
|
|
9
|
+
"observability",
|
|
10
|
+
"sourcemaps"
|
|
11
|
+
],
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"author": "Julien Ripouteau <julien@ripouteau.com>",
|
|
14
|
+
"bin": {
|
|
15
|
+
"monocle": "./dist/cli.mjs"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./dist/index.mjs",
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public",
|
|
27
|
+
"tag": "beta"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"commander": "^14.0.3",
|
|
31
|
+
"fast-glob": "^3.3.3",
|
|
32
|
+
"ky": "^1.14.3",
|
|
33
|
+
"ora": "^9.3.0",
|
|
34
|
+
"picocolors": "^1.1.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@adonisjs/tsconfig": "^2.0.0",
|
|
38
|
+
"@japa/assert": "^4.2.0",
|
|
39
|
+
"@japa/file-system": "^3.0.0",
|
|
40
|
+
"@japa/runner": "^5.3.0",
|
|
41
|
+
"@poppinss/ts-exec": "^1.4.4",
|
|
42
|
+
"release-it": "^19.2.4",
|
|
43
|
+
"@monocle.sh/studio": "0.1.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=22.0.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsdown",
|
|
50
|
+
"dev": "tsdown --watch",
|
|
51
|
+
"typecheck": "tsgo --noEmit",
|
|
52
|
+
"test": "node --import @poppinss/ts-exec bin/test.ts",
|
|
53
|
+
"release": "release-it --preRelease=beta"
|
|
54
|
+
}
|
|
55
|
+
}
|