@monocle.sh/cli 0.1.0 → 0.2.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/dist/cli.mjs +18 -2
- package/dist/fetch-DZ9BsQle.mjs +189 -0
- package/dist/index.mjs +1 -1
- package/dist/{upload-DJfE9Qcx.mjs → upload-D2WShd9A.mjs} +11 -9
- package/package.json +5 -5
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as uploadSourcemaps, t as UploadError } from "./upload-
|
|
2
|
+
import { i as uploadSourcemaps, t as UploadError } from "./upload-D2WShd9A.mjs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { readFileSync, readdirSync, rmSync } from "node:fs";
|
|
5
5
|
import pc from "picocolors";
|
|
@@ -38,7 +38,11 @@ program.command("sourcemaps").description("Manage source maps").command("upload"
|
|
|
38
38
|
process.exit(1);
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* `monocle studio` command group — local DevTools server and data access.
|
|
43
|
+
*/
|
|
44
|
+
const studio = program.command("studio").description("Local DevTools server for development observability");
|
|
45
|
+
studio.command("dev").description("Start the local Studio server").option("--port <port>", "Port for the Studio 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
46
|
const port = Number.parseInt(options.port, 10);
|
|
43
47
|
if (options.clean) {
|
|
44
48
|
const configDir = join(homedir(), ".config", "monocle");
|
|
@@ -68,6 +72,18 @@ program.command("dev").description("Start local DevTools server for development
|
|
|
68
72
|
process.exit(1);
|
|
69
73
|
}
|
|
70
74
|
});
|
|
75
|
+
studio.command("traces").description("List recent traces from Studio").option("-p, --port <port>", "Studio server port", "4200").option("-f, --format <format>", "Output format: pretty, json, short", "pretty").option("-n, --limit <limit>", "Number of traces to fetch", "25").option("-s, --service <service>", "Filter by service name").option("--trace-id <traceId>", "Filter by trace ID").action(async (options) => {
|
|
76
|
+
const { fetchTraces } = await import("./fetch-DZ9BsQle.mjs");
|
|
77
|
+
await fetchTraces(options);
|
|
78
|
+
});
|
|
79
|
+
studio.command("logs").description("List recent logs from Studio").option("-p, --port <port>", "Studio server port", "4200").option("-f, --format <format>", "Output format: pretty, json, short", "pretty").option("-n, --limit <limit>", "Number of logs to fetch", "50").option("--severity <severity>", "Filter by severity (error, warn, info, debug)").option("--search <search>", "Search in log body and attributes").option("--trace-id <traceId>", "Filter by trace ID").action(async (options) => {
|
|
80
|
+
const { fetchLogs } = await import("./fetch-DZ9BsQle.mjs");
|
|
81
|
+
await fetchLogs(options);
|
|
82
|
+
});
|
|
83
|
+
studio.command("errors").description("List recent errors and exceptions from Studio").option("-p, --port <port>", "Studio server port", "4200").option("-f, --format <format>", "Output format: pretty, json, short", "pretty").option("-n, --limit <limit>", "Number of errors to fetch", "25").option("--trace-id <traceId>", "Filter by trace ID").action(async (options) => {
|
|
84
|
+
const { fetchErrors } = await import("./fetch-DZ9BsQle.mjs");
|
|
85
|
+
await fetchErrors(options);
|
|
86
|
+
});
|
|
71
87
|
program.parse();
|
|
72
88
|
//#endregion
|
|
73
89
|
export {};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import ky from "ky";
|
|
3
|
+
//#region src/studio/formatters.ts
|
|
4
|
+
/**
|
|
5
|
+
* Format a unix nanosecond timestamp to a HH:mm:ss.SSS string.
|
|
6
|
+
*/
|
|
7
|
+
function formatTimestamp(unixNano) {
|
|
8
|
+
return (/* @__PURE__ */ new Date(Number(BigInt(unixNano)) / 1e6)).toISOString().slice(11, 23);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Return a colorizer based on log severity level.
|
|
12
|
+
*/
|
|
13
|
+
function severityColor(severity) {
|
|
14
|
+
const s = (severity ?? "").toUpperCase();
|
|
15
|
+
if (s === "ERROR" || s === "FATAL") return pc.red;
|
|
16
|
+
if (s === "WARN" || s === "WARNING") return pc.yellow;
|
|
17
|
+
if (s === "INFO") return pc.cyan;
|
|
18
|
+
if (s === "DEBUG" || s === "TRACE") return pc.dim;
|
|
19
|
+
return pc.white;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Format a log entry for terminal output.
|
|
23
|
+
* Supports pretty, short, and json formats.
|
|
24
|
+
*/
|
|
25
|
+
function formatLog(log, format) {
|
|
26
|
+
if (format === "json") return JSON.stringify(log);
|
|
27
|
+
const time = formatTimestamp(log.timestampUnix);
|
|
28
|
+
const severity = (log.severityText ?? "INFO").toUpperCase().padEnd(5);
|
|
29
|
+
const color = severityColor(log.severityText);
|
|
30
|
+
const service = log.serviceName ?? "unknown";
|
|
31
|
+
const body = log.body ?? "";
|
|
32
|
+
if (format === "short") return `${pc.dim(time)} ${color(severity)} ${service} ${body}`;
|
|
33
|
+
const parts = [];
|
|
34
|
+
parts.push(`${pc.dim(time)} ${color(pc.bold(severity))} ${pc.dim("·")} ${pc.bold(service)}`);
|
|
35
|
+
parts.push(` ${body}`);
|
|
36
|
+
if (log.traceId) parts.push(` ${pc.dim(`trace=${log.traceId.slice(0, 8)}`)}`);
|
|
37
|
+
return parts.join("\n");
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/studio/studio_api.ts
|
|
41
|
+
/**
|
|
42
|
+
* HTTP client for the Monocle Studio local server.
|
|
43
|
+
* Wraps ky to query traces, logs, and exceptions from the running instance.
|
|
44
|
+
*/
|
|
45
|
+
var StudioApi = class {
|
|
46
|
+
#client;
|
|
47
|
+
constructor(port) {
|
|
48
|
+
this.#client = ky.create({ prefixUrl: `http://localhost:${port}` });
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Fetch traces from the Studio server.
|
|
52
|
+
*/
|
|
53
|
+
async traces(params = {}) {
|
|
54
|
+
return this.#fetch("api/traces", params);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Fetch logs from the Studio server.
|
|
58
|
+
*/
|
|
59
|
+
async logs(params = {}) {
|
|
60
|
+
return this.#fetch("api/logs", params);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Fetch exceptions from the Studio server.
|
|
64
|
+
*/
|
|
65
|
+
async exceptions(params = {}) {
|
|
66
|
+
return this.#fetch("api/exceptions", params);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Generic GET request against the Studio API.
|
|
70
|
+
* Filters out undefined params and exits the process on connection failure.
|
|
71
|
+
*/
|
|
72
|
+
async #fetch(path, params = {}) {
|
|
73
|
+
const searchParams = Object.fromEntries(Object.entries(params).filter((entry) => !!entry[1]));
|
|
74
|
+
try {
|
|
75
|
+
return (await this.#client.get(path, { searchParams }).json()).data;
|
|
76
|
+
} catch {
|
|
77
|
+
console.error(pc.red(`Could not connect to Monocle Studio`));
|
|
78
|
+
console.error(pc.dim(`Make sure the server is running: monocle studio dev`));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/studio/fetch.ts
|
|
85
|
+
/**
|
|
86
|
+
* Print data as formatted JSON to stdout.
|
|
87
|
+
*/
|
|
88
|
+
function printJson(data) {
|
|
89
|
+
console.log(JSON.stringify(data, null, 2));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Format a duration in milliseconds to a human-readable string.
|
|
93
|
+
*/
|
|
94
|
+
function formatDuration(ms) {
|
|
95
|
+
if (ms < 1) return `${(ms * 1e3).toFixed(0)}µs`;
|
|
96
|
+
if (ms < 1e3) return `${ms.toFixed(1)}ms`;
|
|
97
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Return a picocolors colorizer based on HTTP status code range.
|
|
101
|
+
*/
|
|
102
|
+
function httpStatusColor(code) {
|
|
103
|
+
if (code >= 500) return pc.red;
|
|
104
|
+
if (code >= 400) return pc.yellow;
|
|
105
|
+
if (code >= 200) return pc.green;
|
|
106
|
+
return pc.dim;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Print a single trace to stdout in the given format.
|
|
110
|
+
*/
|
|
111
|
+
function printTrace(trace, format) {
|
|
112
|
+
const time = trace.startTime?.slice(11, 23) ?? "";
|
|
113
|
+
const duration = formatDuration(trace.durationMs);
|
|
114
|
+
if (format === "short") {
|
|
115
|
+
const status = trace.httpStatus ? ` ${trace.httpStatus}` : "";
|
|
116
|
+
console.log(`${pc.dim(time)} ${pc.magenta("TRC")} ${trace.serviceName ?? "unknown"} ${trace.method ?? ""} ${trace.path ?? trace.rootSpanName}${status} ${pc.dim(duration)}`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
console.log(`${pc.dim(time)} ${pc.magenta(pc.bold("TRACE"))} ${pc.dim("·")} ${pc.bold(trace.serviceName ?? "unknown")} ${pc.dim(trace.traceId.slice(0, 8))}`);
|
|
120
|
+
if (trace.method) {
|
|
121
|
+
const statusStr = httpStatusColor(trace.httpStatus)(String(trace.httpStatus ?? ""));
|
|
122
|
+
console.log(` ${pc.bold(trace.method)} ${trace.path ?? trace.rootSpanName} ${statusStr} ${pc.dim(duration)} ${pc.dim(`(${trace.spanCount} spans)`)}`);
|
|
123
|
+
} else console.log(` ${trace.rootSpanName} ${pc.dim(duration)}`);
|
|
124
|
+
console.log();
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Print a single error/exception to stdout in the given format.
|
|
128
|
+
*/
|
|
129
|
+
function printError(error, format) {
|
|
130
|
+
const time = error.timestamp?.slice(11, 23) ?? "";
|
|
131
|
+
const service = error.serviceName ?? "unknown";
|
|
132
|
+
if (format === "short") {
|
|
133
|
+
console.log(`${pc.dim(time)} ${pc.red("ERR")} ${service} ${error.exceptionType ?? "Error"}: ${error.exceptionMessage ?? ""}`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
console.log(`${pc.dim(time)} ${pc.red(pc.bold("ERROR"))} ${pc.dim("·")} ${pc.bold(service)}`);
|
|
137
|
+
console.log(` ${pc.red(error.exceptionType ?? "Error")}: ${error.exceptionMessage ?? ""}`);
|
|
138
|
+
if (error.exceptionStacktrace) {
|
|
139
|
+
const lines = error.exceptionStacktrace.split("\n");
|
|
140
|
+
for (const line of lines.slice(0, 5)) console.log(` ${pc.dim(line)}`);
|
|
141
|
+
if (lines.length > 5) console.log(` ${pc.dim("...")}`);
|
|
142
|
+
}
|
|
143
|
+
console.log(` ${pc.dim(`trace=${error.traceId.slice(0, 8)} span=${error.spanName}`)}`);
|
|
144
|
+
console.log();
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Fetch and print traces from the Studio server.
|
|
148
|
+
*/
|
|
149
|
+
async function fetchTraces(options) {
|
|
150
|
+
const data = await new StudioApi(Number.parseInt(options.port, 10)).traces({
|
|
151
|
+
limit: options.limit,
|
|
152
|
+
service: options.service,
|
|
153
|
+
traceId: options.traceId
|
|
154
|
+
});
|
|
155
|
+
if (!data.length) return console.log(pc.dim(" No traces found."));
|
|
156
|
+
if (options.format === "json") return printJson(data);
|
|
157
|
+
for (const trace of data) printTrace(trace, options.format);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Fetch and print logs from the Studio server.
|
|
161
|
+
*/
|
|
162
|
+
async function fetchLogs(options) {
|
|
163
|
+
const data = await new StudioApi(Number.parseInt(options.port, 10)).logs({
|
|
164
|
+
limit: options.limit,
|
|
165
|
+
severity: options.severity,
|
|
166
|
+
search: options.search,
|
|
167
|
+
traceId: options.traceId
|
|
168
|
+
});
|
|
169
|
+
if (!data.length) return console.log(pc.dim(" No logs found."));
|
|
170
|
+
if (options.format === "json") return printJson(data);
|
|
171
|
+
for (const log of data) {
|
|
172
|
+
const output = formatLog(log, options.format);
|
|
173
|
+
if (output) console.log(output);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Fetch and print exceptions from the Studio server.
|
|
178
|
+
*/
|
|
179
|
+
async function fetchErrors(options) {
|
|
180
|
+
const data = await new StudioApi(Number.parseInt(options.port, 10)).exceptions({
|
|
181
|
+
limit: options.limit,
|
|
182
|
+
traceId: options.traceId
|
|
183
|
+
});
|
|
184
|
+
if (!data.length) return console.log(pc.dim(" No errors found."));
|
|
185
|
+
if (options.format === "json") return printJson(data);
|
|
186
|
+
for (const error of data) printError(error, options.format);
|
|
187
|
+
}
|
|
188
|
+
//#endregion
|
|
189
|
+
export { fetchErrors, fetchLogs, fetchTraces };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as validateSourcemap, i as uploadSourcemaps, n as findSourcemapFiles, r as loadAndValidateSourcemap, t as UploadError } from "./upload-
|
|
1
|
+
import { a as validateSourcemap, i as uploadSourcemaps, n as findSourcemapFiles, r as loadAndValidateSourcemap, t as UploadError } from "./upload-D2WShd9A.mjs";
|
|
2
2
|
export { UploadError, findSourcemapFiles, loadAndValidateSourcemap, uploadSourcemaps, validateSourcemap };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import pc from "picocolors";
|
|
4
|
-
import
|
|
4
|
+
import { Spinner } from "picospinner";
|
|
5
5
|
import ky, { HTTPError, TimeoutError } from "ky";
|
|
6
|
-
import
|
|
6
|
+
import { glob } from "tinyglobby";
|
|
7
7
|
//#region src/upload.ts
|
|
8
8
|
const MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
9
9
|
const UPLOAD_TIMEOUT = 12e4;
|
|
@@ -12,7 +12,8 @@ const UPLOAD_TIMEOUT = 12e4;
|
|
|
12
12
|
*/
|
|
13
13
|
async function uploadSourcemaps(options) {
|
|
14
14
|
const { patterns, apiKey, release, apiUrl, dryRun } = options;
|
|
15
|
-
const spinner =
|
|
15
|
+
const spinner = new Spinner("Finding source map files...");
|
|
16
|
+
spinner.start();
|
|
16
17
|
const filePaths = await findSourcemapFiles(patterns);
|
|
17
18
|
if (filePaths.length === 0) {
|
|
18
19
|
spinner.fail("No source map files found");
|
|
@@ -20,7 +21,7 @@ async function uploadSourcemaps(options) {
|
|
|
20
21
|
console.error(pc.dim("Make sure the glob patterns match .map files in your build output"));
|
|
21
22
|
throw new UploadError("No source map files found");
|
|
22
23
|
}
|
|
23
|
-
spinner.
|
|
24
|
+
spinner.setText(`Validating ${filePaths.length} source map file(s)...`);
|
|
24
25
|
const files = [];
|
|
25
26
|
for (const filePath of filePaths) {
|
|
26
27
|
const file = loadAndValidateSourcemap(filePath);
|
|
@@ -38,14 +39,15 @@ async function uploadSourcemaps(options) {
|
|
|
38
39
|
count: 0
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
|
-
spinner.
|
|
42
|
+
spinner.setText(`Uploading to ${sanitizeUrl(apiUrl)}...`);
|
|
43
|
+
spinner.start();
|
|
42
44
|
const result = await uploadFiles({
|
|
43
45
|
files,
|
|
44
46
|
apiKey,
|
|
45
47
|
release,
|
|
46
48
|
apiUrl,
|
|
47
49
|
onProgress: (current, total) => {
|
|
48
|
-
spinner.
|
|
50
|
+
spinner.setText(`Uploading file ${current}/${total} to ${sanitizeUrl(apiUrl)}...`);
|
|
49
51
|
}
|
|
50
52
|
});
|
|
51
53
|
spinner.succeed(`Uploaded ${result.count} source map(s) for release ${pc.cyan(release)}`);
|
|
@@ -55,11 +57,11 @@ async function uploadSourcemaps(options) {
|
|
|
55
57
|
* Find source map files matching the given glob patterns.
|
|
56
58
|
*/
|
|
57
59
|
async function findSourcemapFiles(patterns) {
|
|
58
|
-
return (await
|
|
60
|
+
return (await glob(patterns, {
|
|
59
61
|
onlyFiles: true,
|
|
60
62
|
absolute: true,
|
|
61
|
-
|
|
62
|
-
})).filter((file) => file.endsWith(".map"));
|
|
63
|
+
expandDirectories: false
|
|
64
|
+
})).filter((file) => file.endsWith(".map") && !file.includes("/node_modules/"));
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
67
|
* Load and validate a source map file. Returns the file with its content.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monocle.sh/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Monocle CLI - upload source maps and manage your Monocle projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"commander": "^14.0.3",
|
|
31
|
-
"fast-glob": "^3.3.3",
|
|
32
31
|
"ky": "^1.14.3",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
32
|
+
"picocolors": "^1.1.1",
|
|
33
|
+
"picospinner": "^3.0.0",
|
|
34
|
+
"tinyglobby": "^0.2.14"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@adonisjs/tsconfig": "^2.0.0",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@japa/runner": "^5.3.0",
|
|
41
41
|
"@poppinss/ts-exec": "^1.4.4",
|
|
42
42
|
"release-it": "^19.2.4",
|
|
43
|
-
"@monocle.sh/studio": "0.
|
|
43
|
+
"@monocle.sh/studio": "0.2.0"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
46
|
"node": ">=22.0.0"
|