@spotlightjs/spotlight 4.8.0 → 4.9.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/_virtual/_sentry-release-injection-file.js +2 -2
- package/dist/sentry-config.js +2 -2
- package/dist/server/cli/help.d.ts +2 -1
- package/dist/server/cli/help.js +41 -18
- package/dist/server/cli/help.js.map +1 -1
- package/dist/server/cli/mcp.d.ts +8 -2
- package/dist/server/cli/mcp.js +27 -3
- package/dist/server/cli/mcp.js.map +1 -1
- package/dist/server/cli/run.d.ts +8 -2
- package/dist/server/cli/run.js +34 -6
- package/dist/server/cli/run.js.map +1 -1
- package/dist/server/cli/server.d.ts +8 -2
- package/dist/server/cli/server.js +27 -3
- package/dist/server/cli/server.js.map +1 -1
- package/dist/server/cli/tail.d.ts +8 -2
- package/dist/server/cli/tail.js +28 -3
- package/dist/server/cli/tail.js.map +1 -1
- package/dist/server/cli.d.ts +3 -2
- package/dist/server/cli.js +18 -16
- package/dist/server/cli.js.map +1 -1
- package/dist/server/formatters/schema.d.ts +382 -382
- package/dist/server/mcp/mcp.js +2 -2
- package/dist/server/parser/types.d.ts +47 -1
- package/dist/server/types/cli.d.ts +11 -0
- package/dist/ui/assets/index.js +58 -58
- package/dist/ui/assets/index.js.map +1 -1
- package/dist/ui/assets/instrumentation.js +2 -2
- package/dist/ui/assets/main.css +1 -1
- package/dist/ui/assets/main.js +1 -1
- package/package.json +1 -1
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
!function() {
|
|
3
3
|
try {
|
|
4
4
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {}, n = new e.Error().stack;
|
|
5
|
-
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "
|
|
5
|
+
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "fb85be00-a205-4b9b-9630-85057f3ff808", e._sentryDebugIdIdentifier = "sentry-dbid-fb85be00-a205-4b9b-9630-85057f3ff808");
|
|
6
6
|
} catch (e2) {
|
|
7
7
|
}
|
|
8
8
|
}();
|
|
9
9
|
var _global = typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : {};
|
|
10
|
-
_global.SENTRY_RELEASE = { id: "4.
|
|
10
|
+
_global.SENTRY_RELEASE = { id: "4.9.0" };
|
|
11
11
|
//# sourceMappingURL=_sentry-release-injection-file.js.map
|
package/dist/sentry-config.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
!function() {
|
|
3
3
|
try {
|
|
4
4
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {}, n = new e.Error().stack;
|
|
5
|
-
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "
|
|
5
|
+
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "e3629ce1-8dd0-44cf-b2aa-738f940e100e", e._sentryDebugIdIdentifier = "sentry-dbid-e3629ce1-8dd0-44cf-b2aa-738f940e100e");
|
|
6
6
|
} catch (e2) {
|
|
7
7
|
}
|
|
8
8
|
}();
|
|
@@ -11,7 +11,7 @@ const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
|
|
11
11
|
const sentryBaseConfig = {
|
|
12
12
|
enabled: Boolean("production") && true,
|
|
13
13
|
environment: isCI ? "github-ci" : "production",
|
|
14
|
-
release: `spotlight@${"4.
|
|
14
|
+
release: `spotlight@${"4.9.0"}`,
|
|
15
15
|
tracesSampleRate: 1,
|
|
16
16
|
enableLogs: true
|
|
17
17
|
};
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { CLIHandlerOptions } from '../types/cli.ts';
|
|
2
|
+
export default function showHelp({ cmdArgs }: CLIHandlerOptions): void;
|
package/dist/server/cli/help.js
CHANGED
|
@@ -2,24 +2,43 @@
|
|
|
2
2
|
!function() {
|
|
3
3
|
try {
|
|
4
4
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {}, n = new e.Error().stack;
|
|
5
|
-
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "
|
|
5
|
+
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "d8378fd8-7267-4573-9c94-cf79105656bb", e._sentryDebugIdIdentifier = "sentry-dbid-d8378fd8-7267-4573-9c94-cf79105656bb");
|
|
6
6
|
} catch (e2) {
|
|
7
7
|
}
|
|
8
8
|
}();
|
|
9
|
-
import {
|
|
9
|
+
import { COMMANDS } from "../cli.js";
|
|
10
10
|
import { AVAILABLE_FORMATTERS } from "../formatters/types.js";
|
|
11
11
|
import "../../_virtual/_sentry-release-injection-file.js";
|
|
12
|
-
function
|
|
12
|
+
function showCommandHelp(commandName) {
|
|
13
|
+
const command = COMMANDS.find((c) => c.meta.name === commandName);
|
|
14
|
+
if (!command) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const { meta } = command;
|
|
18
|
+
console.log(`
|
|
19
|
+
${meta.name} - ${meta.short}
|
|
20
|
+
|
|
21
|
+
Usage: ${meta.usage || `spotlight ${meta.name} [options]`}
|
|
22
|
+
${meta.long ? `
|
|
23
|
+
${meta.long}` : ""}
|
|
24
|
+
${meta.examples && meta.examples.length > 0 ? `
|
|
25
|
+
Examples:
|
|
26
|
+
${meta.examples.map((ex) => ` ${ex}`).join("\n")}` : ""}
|
|
27
|
+
`);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
function showMainHelp() {
|
|
31
|
+
const commandsHelp = COMMANDS.map(({ meta }) => {
|
|
32
|
+
const name = meta.name.padEnd(20);
|
|
33
|
+
return ` ${name} ${meta.short}`;
|
|
34
|
+
}).join("\n");
|
|
13
35
|
console.log(`
|
|
14
36
|
Spotlight Sidecar - Development proxy server for Spotlight
|
|
15
37
|
|
|
16
38
|
Usage: spotlight [command] [options]
|
|
17
39
|
|
|
18
40
|
Commands:
|
|
19
|
-
|
|
20
|
-
Available types: ${[...Object.keys(NAME_TO_TYPE_MAPPING)].join(", ")}
|
|
21
|
-
Magic words: ${[...EVERYTHING_MAGIC_WORDS].join(", ")}
|
|
22
|
-
mcp Start in MCP (Model Context Protocol) mode
|
|
41
|
+
${commandsHelp}
|
|
23
42
|
help Show this help message
|
|
24
43
|
|
|
25
44
|
Options:
|
|
@@ -37,18 +56,22 @@ Options:
|
|
|
37
56
|
-h, --help Show this help message
|
|
38
57
|
|
|
39
58
|
Examples:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
spotlight tail errors # Tail only errors
|
|
44
|
-
spotlight tail errors logs # Tail errors and logs
|
|
45
|
-
spotlight tail --format json # Explicitly use json format
|
|
46
|
-
spotlight mcp # Start in MCP mode
|
|
47
|
-
spotlight --port 3000 # Start on port 3000
|
|
48
|
-
spotlight -p 3000 -d # Start on port 3000 with debug logging
|
|
49
|
-
spotlight -A myapp.local # Allow requests from myapp.local
|
|
50
|
-
spotlight -A https://tunnel.ngrok.io -A dev.local # Multiple origins
|
|
59
|
+
${COMMANDS.flatMap(({ meta }) => meta.examples?.slice(0, 2) || []).map((ex) => ` ${ex}`).join("\n")}
|
|
60
|
+
|
|
61
|
+
Run 'spotlight help <command>' for more information on a specific command.
|
|
51
62
|
`);
|
|
63
|
+
}
|
|
64
|
+
function showHelp({ cmdArgs }) {
|
|
65
|
+
const targetCmd = cmdArgs?.[0];
|
|
66
|
+
if (targetCmd) {
|
|
67
|
+
if (!showCommandHelp(targetCmd)) {
|
|
68
|
+
console.error(`Unknown command: ${targetCmd}`);
|
|
69
|
+
console.error(`Run 'spotlight help' to see available commands.`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
showMainHelp();
|
|
74
|
+
}
|
|
52
75
|
process.exit(0);
|
|
53
76
|
}
|
|
54
77
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.js","sources":["../../../src/server/cli/help.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"help.js","sources":["../../../src/server/cli/help.ts"],"sourcesContent":["import { COMMANDS } from \"../cli.ts\";\nimport { AVAILABLE_FORMATTERS } from \"../formatters/types.ts\";\nimport type { CLIHandlerOptions } from \"../types/cli.ts\";\n\nfunction showCommandHelp(commandName: string): boolean {\n const command = COMMANDS.find(c => c.meta.name === commandName);\n if (!command) {\n return false;\n }\n\n const { meta } = command;\n console.log(`\n${meta.name} - ${meta.short}\n\nUsage: ${meta.usage || `spotlight ${meta.name} [options]`}\n${meta.long ? `\\n${meta.long}` : \"\"}\n${\n meta.examples && meta.examples.length > 0\n ? `\nExamples:\n${meta.examples.map(ex => ` ${ex}`).join(\"\\n\")}`\n : \"\"\n}\n`);\n return true;\n}\n\nfunction showMainHelp() {\n // Build commands section dynamically from COMMANDS\n const commandsHelp = COMMANDS.map(({ meta }) => {\n const name = meta.name.padEnd(20);\n return ` ${name} ${meta.short}`;\n }).join(\"\\n\");\n\n console.log(`\nSpotlight Sidecar - Development proxy server for Spotlight\n\nUsage: spotlight [command] [options]\n\nCommands:\n${commandsHelp}\n help Show this help message\n\nOptions:\n -p, --port <port> Port to listen on (default: 8969, or 0 for random)\n -o, --open Open the Spotlight dashboard in your default browser\n -d, --debug Enable debug logging\n -f, --format <format> Output format for tail command (default: human)\n Available formats: ${[...AVAILABLE_FORMATTERS].join(\", \")}\n -A, --allowed-origin <origin>\n Additional origins to allow for CORS requests.\n Can be specified multiple times or comma-separated.\n Accepts full origins (https://example.com:443) for\n strict matching or plain domains (myapp.local) to\n allow any protocol/port.\n -h, --help Show this help message\n\nExamples:\n${COMMANDS.flatMap(({ meta }) => meta.examples?.slice(0, 2) || [])\n .map(ex => ` ${ex}`)\n .join(\"\\n\")}\n\nRun 'spotlight help <command>' for more information on a specific command.\n`);\n}\n\nexport default function showHelp({ cmdArgs }: CLIHandlerOptions) {\n const targetCmd = cmdArgs?.[0];\n\n if (targetCmd) {\n // Show detailed help for specific command\n if (!showCommandHelp(targetCmd)) {\n console.error(`Unknown command: ${targetCmd}`);\n console.error(`Run 'spotlight help' to see available commands.`);\n process.exit(1);\n }\n } else {\n // Show main help\n showMainHelp();\n }\n\n process.exit(0);\n}\n"],"names":[],"mappings":";;;;;;;;;;;AAIA,SAAS,gBAAgB,aAA8B;AACrD,QAAM,UAAU,SAAS,KAAK,OAAK,EAAE,KAAK,SAAS,WAAW;AAC9D,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,SAAS;AACjB,UAAQ,IAAI;AAAA,EACZ,KAAK,IAAI,MAAM,KAAK,KAAK;AAAA;AAAA,SAElB,KAAK,SAAS,aAAa,KAAK,IAAI,YAAY;AAAA,EACvD,KAAK,OAAO;AAAA,EAAK,KAAK,IAAI,KAAK,EAAE;AAAA,EAEjC,KAAK,YAAY,KAAK,SAAS,SAAS,IACpC;AAAA;AAAA,EAEJ,KAAK,SAAS,IAAI,CAAA,OAAM,KAAK,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC,KACzC,EACN;AAAA,CACC;AACC,SAAO;AACT;AAEA,SAAS,eAAe;AAEtB,QAAM,eAAe,SAAS,IAAI,CAAC,EAAE,WAAW;AAC9C,UAAM,OAAO,KAAK,KAAK,OAAO,EAAE;AAChC,WAAO,KAAK,IAAI,IAAI,KAAK,KAAK;AAAA,EAChC,CAAC,EAAE,KAAK,IAAI;AAEZ,UAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMZ,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8CAQgC,CAAC,GAAG,oBAAoB,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUhF,SAAS,QAAQ,CAAC,EAAE,WAAW,KAAK,UAAU,MAAM,GAAG,CAAC,KAAK,CAAA,CAAE,EAC9D,IAAI,CAAA,OAAM,KAAK,EAAE,EAAE,EACnB,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA,CAGZ;AACD;AAEA,SAAwB,SAAS,EAAE,WAA8B;AAC/D,QAAM,YAAY,UAAU,CAAC;AAE7B,MAAI,WAAW;AAEb,QAAI,CAAC,gBAAgB,SAAS,GAAG;AAC/B,cAAQ,MAAM,oBAAoB,SAAS,EAAE;AAC7C,cAAQ,MAAM,iDAAiD;AAC/D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,OAAO;AAEL,iBAAA;AAAA,EACF;AAEA,UAAQ,KAAK,CAAC;AAChB;"}
|
package/dist/server/cli/mcp.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
import { CLIHandlerOptions } from '../types/cli.ts';
|
|
2
|
-
export
|
|
1
|
+
import { CLIHandlerOptions, CommandMeta } from '../types/cli.ts';
|
|
2
|
+
export declare const meta: CommandMeta;
|
|
3
|
+
export declare function handler({ port, basePath, filesToServe, allowedOrigins }: CLIHandlerOptions): Promise<import('http').Server<typeof import('http').IncomingMessage, typeof import('http').ServerResponse> | undefined>;
|
|
4
|
+
declare const _default: {
|
|
5
|
+
meta: CommandMeta;
|
|
6
|
+
handler: typeof handler;
|
|
7
|
+
};
|
|
8
|
+
export default _default;
|
package/dist/server/cli/mcp.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
!function() {
|
|
3
3
|
try {
|
|
4
4
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {}, n = new e.Error().stack;
|
|
5
|
-
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "
|
|
5
|
+
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "f8d2431f-3fab-49ff-a5e2-8a9059a5187e", e._sentryDebugIdIdentifier = "sentry-dbid-f8d2431f-3fab-49ff-a5e2-8a9059a5187e");
|
|
6
6
|
} catch (e2) {
|
|
7
7
|
}
|
|
8
8
|
}();
|
|
@@ -16,6 +16,27 @@ import { createMCPInstance } from "../mcp/mcp.js";
|
|
|
16
16
|
import { normalizeAllowedOrigins } from "../utils/cors.js";
|
|
17
17
|
import { isSidecarRunning } from "../utils/extras.js";
|
|
18
18
|
import "../../_virtual/_sentry-release-injection-file.js";
|
|
19
|
+
const meta = {
|
|
20
|
+
name: "mcp",
|
|
21
|
+
short: "Start in MCP (Model Context Protocol) mode",
|
|
22
|
+
usage: "spotlight mcp [options]",
|
|
23
|
+
long: `Start Spotlight in MCP (Model Context Protocol) stdio server mode for
|
|
24
|
+
integration with AI coding assistants like Cursor, Claude, etc.
|
|
25
|
+
|
|
26
|
+
Connects to existing sidecar if running, otherwise starts a new one.
|
|
27
|
+
Provides MCP tools for AI assistants to query errors, logs, and traces.
|
|
28
|
+
|
|
29
|
+
Available MCP Tools:
|
|
30
|
+
- search_errors: Search for errors
|
|
31
|
+
- search_logs: Search for logs
|
|
32
|
+
- search_traces: List traces
|
|
33
|
+
- get_traces: Get trace details`,
|
|
34
|
+
examples: [
|
|
35
|
+
"spotlight mcp # Start MCP server on default port",
|
|
36
|
+
"spotlight mcp -p 3000 # Start on custom port",
|
|
37
|
+
"spotlight mcp -d # Start with debug logging"
|
|
38
|
+
]
|
|
39
|
+
};
|
|
19
40
|
async function startServerWithStdioMCP(port, basePath, filesToServe, normalizedAllowedOrigins) {
|
|
20
41
|
const serverInstance = await startServer({
|
|
21
42
|
port,
|
|
@@ -101,7 +122,7 @@ async function startMCPStdioHTTPProxy(port, basePath, filesToServe, normalizedAl
|
|
|
101
122
|
}
|
|
102
123
|
};
|
|
103
124
|
}
|
|
104
|
-
async function
|
|
125
|
+
async function handler({ port, basePath, filesToServe, allowedOrigins }) {
|
|
105
126
|
const normalizedAllowedOrigins = allowedOrigins ? normalizeAllowedOrigins(allowedOrigins) : void 0;
|
|
106
127
|
if (port > 0 && await isSidecarRunning(port)) {
|
|
107
128
|
logger.info("Connecting to existing MCP instance with stdio proxy...");
|
|
@@ -111,7 +132,10 @@ async function mcp({ port, basePath, filesToServe, allowedOrigins }) {
|
|
|
111
132
|
return await startServerWithStdioMCP(port, basePath, filesToServe, normalizedAllowedOrigins);
|
|
112
133
|
}
|
|
113
134
|
}
|
|
135
|
+
const mcp = { meta, handler };
|
|
114
136
|
export {
|
|
115
|
-
mcp as default
|
|
137
|
+
mcp as default,
|
|
138
|
+
handler,
|
|
139
|
+
meta
|
|
116
140
|
};
|
|
117
141
|
//# sourceMappingURL=mcp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.js","sources":["../../../src/server/cli/mcp.ts"],"sourcesContent":["import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { captureException } from \"@sentry/node\";\nimport { ServerType as ProxyServerType, startStdioServer } from \"mcp-proxy\";\nimport { logger } from \"../logger.ts\";\nimport { setShutdownHandlers, startServer } from \"../main.ts\";\nimport { createMCPInstance } from \"../mcp/mcp.ts\";\nimport type { CLIHandlerOptions } from \"../types/cli.ts\";\nimport type { NormalizedAllowedOrigins } from \"../utils/cors.ts\";\nimport { normalizeAllowedOrigins } from \"../utils/cors.ts\";\nimport { isSidecarRunning } from \"../utils/extras.ts\";\n\nasync function startServerWithStdioMCP(\n port: CLIHandlerOptions[\"port\"],\n basePath: CLIHandlerOptions[\"basePath\"],\n filesToServe: CLIHandlerOptions[\"filesToServe\"],\n normalizedAllowedOrigins: NormalizedAllowedOrigins | undefined,\n) {\n const serverInstance = await startServer({\n port,\n basePath,\n filesToServe,\n normalizedAllowedOrigins,\n });\n setShutdownHandlers(serverInstance);\n\n logger.info(\"Starting MCP over stdio too...\");\n const mcpInstance = createMCPInstance();\n await mcpInstance.connect(new StdioServerTransport());\n\n const shutdownMcp = () => {\n mcpInstance.close();\n };\n\n process.on(\"SIGINT\", shutdownMcp);\n process.on(\"SIGTERM\", shutdownMcp);\n\n return serverInstance;\n}\n\nasync function startMCPStdioHTTPProxy(\n port: CLIHandlerOptions[\"port\"],\n basePath: CLIHandlerOptions[\"basePath\"],\n filesToServe: CLIHandlerOptions[\"filesToServe\"],\n normalizedAllowedOrigins: NormalizedAllowedOrigins | undefined,\n) {\n let intentionalShutdown = false;\n let client: Client | null = null;\n let server: Awaited<ReturnType<typeof startStdioServer>> | null = null;\n\n const shutdown = async () => {\n if (intentionalShutdown) {\n // If we get the signal again, exit immediately\n logger.info(\"Bye.\");\n process.exit(0);\n }\n\n intentionalShutdown = true;\n logger.info(\"Shutting down MCP stdio proxy...\");\n\n if (client) {\n await client.close();\n }\n if (server) {\n await server.close();\n }\n };\n\n // Set up signal handlers for graceful shutdown\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n server = await startStdioServer({\n // We need to hook into `initStreamClient` as the returned object from startStdioServer\n // is not a meta proxy object giving access to both the server and the client. It just\n // returns the StdioServerTransport instance without a way to access the client or its errors.\n // TODO: We should probably upstream a fix for this to close the server or bubble the errors\n initStreamClient: () => {\n client = new Client({\n name: \"Spotlight Sidecar (stdio proxy)\",\n version: \"1.0.0\",\n });\n client.onerror = async (err: Error) => {\n if (\n err.message.startsWith(\"Maximum reconnection attempts\") ||\n /disconnected|fetch failed|connection closed/i.test(err.message)\n ) {\n if (client) await client.close();\n if (server) await server.close();\n } else if (!/conflict/i.test(err.message)) {\n captureException(err);\n logger.error(`MCP stdio proxy error: ${err.name}: ${err.message}`);\n }\n };\n return Promise.resolve(client);\n },\n serverType: ProxyServerType.HTTPStream,\n url: `http://localhost:${port}/mcp`,\n });\n server.onclose = async () => {\n // If this is an intentional shutdown, don't restart\n if (intentionalShutdown) {\n logger.info(\"MCP stdio proxy server closed.\");\n return;\n }\n\n logger.info(\"MCP stdio proxy server closed unexpectedly. Attempting to restart...\");\n process.off(\"SIGINT\", shutdown);\n process.off(\"SIGTERM\", shutdown);\n // We need to manually resume stdin as `StdioServerTransport` pauses it on\n // close but does not `resume` it when a new instance is created. Probably\n // a bug in https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/stdio.ts\n process.stdin.resume();\n\n try {\n await startMCPStdioHTTPProxy(port, basePath, filesToServe, normalizedAllowedOrigins);\n logger.info(\"Connection restored\");\n } catch (_err) {\n try {\n return await startServerWithStdioMCP(port, basePath, filesToServe, normalizedAllowedOrigins);\n } catch (_err2) {\n logger.error(\"Failed to restart sidecar server after MCP stdio proxy closed.\");\n captureException(_err2);\n await startMCPStdioHTTPProxy(port, basePath, filesToServe, normalizedAllowedOrigins);\n }\n }\n };\n}\n\nexport
|
|
1
|
+
{"version":3,"file":"mcp.js","sources":["../../../src/server/cli/mcp.ts"],"sourcesContent":["import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { captureException } from \"@sentry/node\";\nimport { ServerType as ProxyServerType, startStdioServer } from \"mcp-proxy\";\nimport { logger } from \"../logger.ts\";\nimport { setShutdownHandlers, startServer } from \"../main.ts\";\nimport { createMCPInstance } from \"../mcp/mcp.ts\";\nimport type { CLIHandlerOptions, Command, CommandMeta } from \"../types/cli.ts\";\nimport type { NormalizedAllowedOrigins } from \"../utils/cors.ts\";\nimport { normalizeAllowedOrigins } from \"../utils/cors.ts\";\nimport { isSidecarRunning } from \"../utils/extras.ts\";\n\nexport const meta: CommandMeta = {\n name: \"mcp\",\n short: \"Start in MCP (Model Context Protocol) mode\",\n usage: \"spotlight mcp [options]\",\n long: `Start Spotlight in MCP (Model Context Protocol) stdio server mode for\nintegration with AI coding assistants like Cursor, Claude, etc.\n\nConnects to existing sidecar if running, otherwise starts a new one.\nProvides MCP tools for AI assistants to query errors, logs, and traces.\n\nAvailable MCP Tools:\n - search_errors: Search for errors\n - search_logs: Search for logs\n - search_traces: List traces\n - get_traces: Get trace details`,\n examples: [\n \"spotlight mcp # Start MCP server on default port\",\n \"spotlight mcp -p 3000 # Start on custom port\",\n \"spotlight mcp -d # Start with debug logging\",\n ],\n};\n\nasync function startServerWithStdioMCP(\n port: CLIHandlerOptions[\"port\"],\n basePath: CLIHandlerOptions[\"basePath\"],\n filesToServe: CLIHandlerOptions[\"filesToServe\"],\n normalizedAllowedOrigins: NormalizedAllowedOrigins | undefined,\n) {\n const serverInstance = await startServer({\n port,\n basePath,\n filesToServe,\n normalizedAllowedOrigins,\n });\n setShutdownHandlers(serverInstance);\n\n logger.info(\"Starting MCP over stdio too...\");\n const mcpInstance = createMCPInstance();\n await mcpInstance.connect(new StdioServerTransport());\n\n const shutdownMcp = () => {\n mcpInstance.close();\n };\n\n process.on(\"SIGINT\", shutdownMcp);\n process.on(\"SIGTERM\", shutdownMcp);\n\n return serverInstance;\n}\n\nasync function startMCPStdioHTTPProxy(\n port: CLIHandlerOptions[\"port\"],\n basePath: CLIHandlerOptions[\"basePath\"],\n filesToServe: CLIHandlerOptions[\"filesToServe\"],\n normalizedAllowedOrigins: NormalizedAllowedOrigins | undefined,\n) {\n let intentionalShutdown = false;\n let client: Client | null = null;\n let server: Awaited<ReturnType<typeof startStdioServer>> | null = null;\n\n const shutdown = async () => {\n if (intentionalShutdown) {\n // If we get the signal again, exit immediately\n logger.info(\"Bye.\");\n process.exit(0);\n }\n\n intentionalShutdown = true;\n logger.info(\"Shutting down MCP stdio proxy...\");\n\n if (client) {\n await client.close();\n }\n if (server) {\n await server.close();\n }\n };\n\n // Set up signal handlers for graceful shutdown\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n server = await startStdioServer({\n // We need to hook into `initStreamClient` as the returned object from startStdioServer\n // is not a meta proxy object giving access to both the server and the client. It just\n // returns the StdioServerTransport instance without a way to access the client or its errors.\n // TODO: We should probably upstream a fix for this to close the server or bubble the errors\n initStreamClient: () => {\n client = new Client({\n name: \"Spotlight Sidecar (stdio proxy)\",\n version: \"1.0.0\",\n });\n client.onerror = async (err: Error) => {\n if (\n err.message.startsWith(\"Maximum reconnection attempts\") ||\n /disconnected|fetch failed|connection closed/i.test(err.message)\n ) {\n if (client) await client.close();\n if (server) await server.close();\n } else if (!/conflict/i.test(err.message)) {\n captureException(err);\n logger.error(`MCP stdio proxy error: ${err.name}: ${err.message}`);\n }\n };\n return Promise.resolve(client);\n },\n serverType: ProxyServerType.HTTPStream,\n url: `http://localhost:${port}/mcp`,\n });\n server.onclose = async () => {\n // If this is an intentional shutdown, don't restart\n if (intentionalShutdown) {\n logger.info(\"MCP stdio proxy server closed.\");\n return;\n }\n\n logger.info(\"MCP stdio proxy server closed unexpectedly. Attempting to restart...\");\n process.off(\"SIGINT\", shutdown);\n process.off(\"SIGTERM\", shutdown);\n // We need to manually resume stdin as `StdioServerTransport` pauses it on\n // close but does not `resume` it when a new instance is created. Probably\n // a bug in https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/stdio.ts\n process.stdin.resume();\n\n try {\n await startMCPStdioHTTPProxy(port, basePath, filesToServe, normalizedAllowedOrigins);\n logger.info(\"Connection restored\");\n } catch (_err) {\n try {\n return await startServerWithStdioMCP(port, basePath, filesToServe, normalizedAllowedOrigins);\n } catch (_err2) {\n logger.error(\"Failed to restart sidecar server after MCP stdio proxy closed.\");\n captureException(_err2);\n await startMCPStdioHTTPProxy(port, basePath, filesToServe, normalizedAllowedOrigins);\n }\n }\n };\n}\n\nexport async function handler({ port, basePath, filesToServe, allowedOrigins }: CLIHandlerOptions) {\n // Normalize allowed origins once at startup\n const normalizedAllowedOrigins = allowedOrigins ? normalizeAllowedOrigins(allowedOrigins) : undefined;\n\n if (port > 0 && (await isSidecarRunning(port))) {\n logger.info(\"Connecting to existing MCP instance with stdio proxy...\");\n await startMCPStdioHTTPProxy(port, basePath, filesToServe, normalizedAllowedOrigins);\n logger.info(`Connected to existing MCP instance on port ${port}`);\n } else {\n return await startServerWithStdioMCP(port, basePath, filesToServe, normalizedAllowedOrigins);\n }\n}\n\nexport default { meta, handler } satisfies Command;\n"],"names":["ProxyServerType"],"mappings":"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"}
|
package/dist/server/cli/run.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
import { CLIHandlerOptions } from '../types/cli.ts';
|
|
2
|
-
export
|
|
1
|
+
import { CLIHandlerOptions, CommandMeta } from '../types/cli.ts';
|
|
2
|
+
export declare const meta: CommandMeta;
|
|
3
|
+
export declare function handler({ port, cmdArgs, basePath, filesToServe, format, allowedOrigins, open, }: CLIHandlerOptions): Promise<void>;
|
|
4
|
+
declare const _default: {
|
|
5
|
+
meta: CommandMeta;
|
|
6
|
+
handler: typeof handler;
|
|
7
|
+
};
|
|
8
|
+
export default _default;
|
package/dist/server/cli/run.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
!function() {
|
|
3
3
|
try {
|
|
4
4
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {}, n = new e.Error().stack;
|
|
5
|
-
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "
|
|
5
|
+
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "e59232d7-48d8-4dfe-907e-19a2d2b3cd58", e._sentryDebugIdIdentifier = "sentry-dbid-e59232d7-48d8-4dfe-907e-19a2d2b3cd58");
|
|
6
6
|
} catch (e2) {
|
|
7
7
|
}
|
|
8
8
|
}();
|
|
@@ -23,8 +23,33 @@ import { EventContainer } from "../utils/eventContainer.js";
|
|
|
23
23
|
import "node:dns/promises";
|
|
24
24
|
import "node:net";
|
|
25
25
|
import "node:os";
|
|
26
|
-
import
|
|
27
|
-
const
|
|
26
|
+
import { handler as handler$1 } from "./tail.js";
|
|
27
|
+
const meta = {
|
|
28
|
+
name: "run",
|
|
29
|
+
short: "Run your app with automatic Spotlight instrumentation",
|
|
30
|
+
usage: "spotlight run [options] [command...]",
|
|
31
|
+
long: `Run your application with Spotlight, automatically setting up the necessary
|
|
32
|
+
environment variables and capturing telemetry data.
|
|
33
|
+
|
|
34
|
+
If no command is provided, Spotlight auto-detects:
|
|
35
|
+
- Docker Compose projects (docker-compose.yml, compose.yml, etc.)
|
|
36
|
+
- package.json scripts (dev, develop, serve, start)
|
|
37
|
+
|
|
38
|
+
Environment variables set automatically:
|
|
39
|
+
- SENTRY_SPOTLIGHT=http://localhost:<port>/stream
|
|
40
|
+
- NEXT_PUBLIC_SENTRY_SPOTLIGHT=http://localhost:<port>/stream
|
|
41
|
+
- SENTRY_TRACES_SAMPLE_RATE=1
|
|
42
|
+
|
|
43
|
+
stdout/stderr are captured and sent to Spotlight as log events.`,
|
|
44
|
+
examples: [
|
|
45
|
+
"spotlight run # Auto-detect and run (package.json or docker-compose)",
|
|
46
|
+
"spotlight run node server.js # Run a specific command with Spotlight",
|
|
47
|
+
"spotlight run -p 3000 npm start # Run with custom port",
|
|
48
|
+
"spotlight run python manage.py runserver # Run Python app",
|
|
49
|
+
"spotlight run docker compose up # Run Docker Compose with Spotlight injection"
|
|
50
|
+
]
|
|
51
|
+
};
|
|
52
|
+
const SPOTLIGHT_VERSION = "4.9.0";
|
|
28
53
|
const LOCALHOST_HOST = "localhost";
|
|
29
54
|
function detectPackageJson() {
|
|
30
55
|
try {
|
|
@@ -97,7 +122,7 @@ function createLogEnvelope(level, body, timestamp) {
|
|
|
97
122
|
return Buffer.from(`${parts.join("\n")}
|
|
98
123
|
`, "utf-8");
|
|
99
124
|
}
|
|
100
|
-
async function
|
|
125
|
+
async function handler({
|
|
101
126
|
port,
|
|
102
127
|
cmdArgs,
|
|
103
128
|
basePath,
|
|
@@ -135,7 +160,7 @@ async function run({
|
|
|
135
160
|
}
|
|
136
161
|
return true;
|
|
137
162
|
};
|
|
138
|
-
const serverInstance = await
|
|
163
|
+
const serverInstance = await handler$1({ port, cmdArgs: [], basePath, filesToServe, format, allowedOrigins }, logChecker);
|
|
139
164
|
if (!serverInstance) {
|
|
140
165
|
logger.error("Failed to start Spotlight sidecar server.");
|
|
141
166
|
logger.error(`The port ${port} might already be in use — most likely by another Spotlight instance.`);
|
|
@@ -299,7 +324,10 @@ async function run({
|
|
|
299
324
|
process.on("beforeExit", killRunCmd);
|
|
300
325
|
});
|
|
301
326
|
}
|
|
327
|
+
const run = { meta, handler };
|
|
302
328
|
export {
|
|
303
|
-
run as default
|
|
329
|
+
run as default,
|
|
330
|
+
handler,
|
|
331
|
+
meta
|
|
304
332
|
};
|
|
305
333
|
//# sourceMappingURL=run.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sources":["../../../src/server/cli/run.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport type { AddressInfo } from \"node:net\";\nimport * as path from \"node:path\";\nimport * as readline from \"node:readline\";\nimport type { SerializedLog } from \"@sentry/core\";\nimport { metrics } from \"@sentry/node\";\nimport { Searcher } from \"fast-fuzzy\";\nimport { uuidv7 } from \"uuidv7\";\nimport { SENTRY_CONTENT_TYPE } from \"../constants.ts\";\nimport { logger } from \"../logger.ts\";\nimport type { SentryLogEvent } from \"../parser/types.ts\";\nimport type { CLIHandlerOptions } from \"../types/cli.ts\";\nimport { detectDockerCompose, parseExplicitDockerCompose, prepareDockerComposeRun } from \"../utils/docker-compose.ts\";\nimport { getSpotlightURL, openInBrowser } from \"../utils/extras.ts\";\nimport { EventContainer, getBuffer } from \"../utils/index.ts\";\nimport tail, { type OnItemCallback } from \"./tail.ts\";\n\nconst SPOTLIGHT_VERSION = process.env.npm_package_version || \"unknown\";\n\n// Environment variable host configurations\nconst LOCALHOST_HOST = \"localhost\";\n\n/**\n * Detect if there's a package.json with runnable scripts\n */\nfunction detectPackageJson(): { scriptName: string; scriptCommand: string } | null {\n try {\n const scripts = JSON.parse(readFileSync(\"./package.json\", \"utf-8\")).scripts;\n const scriptName = [\"dev\", \"develop\", \"serve\", \"start\"].find(name => scripts[name]);\n if (!scriptName || !scripts[scriptName]) {\n return null;\n }\n return { scriptName, scriptCommand: scripts[scriptName] };\n } catch {\n // pass\n }\n return null;\n}\n\n/**\n * Prompt the user to choose between Docker Compose and package.json\n */\nasync function promptUserChoice(): Promise<\"docker\" | \"package\"> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise(resolve => {\n console.log(\"\\n⚠️ Both Docker Compose and package.json detected!\");\n console.log(\"\\nWhich would you like to use?\");\n console.log(\" 1) docker compose\");\n console.log(\" 2) package.json\");\n\n rl.question(\"Enter your choice (1 or 2): \", answer => {\n rl.close();\n const choice = answer.trim();\n const selected = choice === \"2\" ? \"package\" : \"docker\";\n logger.info(`Selected: ${selected === \"docker\" ? \"Docker compose\" : \"package.json\"}`);\n resolve(selected);\n });\n });\n}\n\nfunction createLogEnvelope(level: \"info\" | \"error\", body: string, timestamp: number): Buffer {\n const envelopeHeader = {\n sdk: {\n name: \"sentry.spotlight.stdio\",\n version: SPOTLIGHT_VERSION,\n },\n };\n\n const logItem: SerializedLog = {\n timestamp,\n level,\n body,\n attributes: {},\n };\n\n const logPayload: SentryLogEvent = {\n type: \"log\",\n event_id: uuidv7(),\n timestamp,\n items: [\n {\n ...logItem,\n id: uuidv7(),\n severity_number: level === \"error\" ? 17 : 9, // ERROR=17, INFO=9 per OpenTelemetry spec\n sdk: undefined,\n },\n ],\n };\n\n const payloadJson = JSON.stringify(logPayload);\n const payloadBuffer = Buffer.from(payloadJson, \"utf-8\");\n\n const itemHeader = {\n type: \"log\",\n length: payloadBuffer.length, // Use byte length, not string length\n item_count: 1,\n content_type: \"application/json\",\n };\n\n const parts = [JSON.stringify(envelopeHeader), JSON.stringify(itemHeader), payloadJson];\n\n return Buffer.from(`${parts.join(\"\\n\")}\\n`, \"utf-8\");\n}\n\nexport default async function run({\n port,\n cmdArgs,\n basePath,\n filesToServe,\n format,\n allowedOrigins,\n open,\n}: CLIHandlerOptions) {\n let relayStdioAsLogs = true;\n\n const fuzzySearcher = new Searcher([] as string[], {\n threshold: 0.8,\n ignoreCase: false,\n });\n\n const logChecker: OnItemCallback = (type, item, envelopeHeader) => {\n if (type !== \"log\") return true;\n\n const [, payload] = item;\n const logEvent = payload as SentryLogEvent;\n\n if (logEvent.items && logEvent.items.length > 0) {\n for (const logItem of logEvent.items) {\n // Check if this is a stdio log from Spotlight itself\n if (envelopeHeader.sdk?.name === \"sentry.spotlight.stdio\") {\n continue;\n }\n\n if (!relayStdioAsLogs) continue;\n\n const logBody = typeof logItem.body === \"string\" ? logItem.body : String(logItem.body);\n const trimmedBody = logBody.trim();\n\n if (trimmedBody) {\n const matches = fuzzySearcher.search(trimmedBody);\n if (matches.length > 0) {\n logger.debug(\"Detected Sentry logging in the process, disabling stdio relay\");\n relayStdioAsLogs = false;\n return false;\n }\n }\n }\n }\n\n return true;\n };\n\n const serverInstance = await tail({ port, cmdArgs: [], basePath, filesToServe, format, allowedOrigins }, logChecker);\n if (!serverInstance) {\n logger.error(\"Failed to start Spotlight sidecar server.\");\n logger.error(`The port ${port} might already be in use — most likely by another Spotlight instance.`);\n process.exit(1);\n }\n\n // We *MUST* have an instance address and a port here\n // as not having that indicates either the server did not start\n // or started in a weird manner (like over a unix socket)\n const actualServerPort = (serverInstance.address() as AddressInfo).port;\n const spotlightUrl = getSpotlightURL(actualServerPort, LOCALHOST_HOST);\n\n if (open) {\n openInBrowser(actualServerPort);\n }\n let shell = false;\n let stdin: string | undefined = undefined;\n const env = {\n ...process.env,\n SENTRY_SPOTLIGHT: spotlightUrl,\n // We need the one below for Next.js projects -- they only allow NEXT_PUBLIC_ prefixed env vars\n // on frontend code\n NEXT_PUBLIC_SENTRY_SPOTLIGHT: spotlightUrl,\n // This is not supported in all SDKs but worth adding\n // for the ones that support it\n SENTRY_TRACES_SAMPLE_RATE: \"1\",\n } as {\n PATH: string;\n SENTRY_SPOTLIGHT: string;\n NEXT_PUBLIC_SENTRY_SPOTLIGHT: string;\n SENTRY_TRACES_SAMPLE_RATE: string;\n [key: string]: string;\n };\n if (cmdArgs.length === 0) {\n const dockerCompose = detectDockerCompose();\n const packageJson = detectPackageJson();\n\n // If both Docker Compose and package.json are detected, ask the user which one to use\n if (dockerCompose && packageJson) {\n if (!process.stdin.isTTY) {\n logger.error(\"Both Docker Compose and package.json detected, but cannot prompt in non-interactive mode.\");\n logger.error(\"Please specify a command explicitly or run in an interactive terminal.\");\n process.exit(1);\n }\n const choice = await promptUserChoice();\n metrics.count(\"cli.run.prompt_choice\", 1, { attributes: { choice } });\n\n if (choice === \"docker\") {\n logger.info(\n `Detected Docker Compose project with ${dockerCompose.serviceNames.length} service(s): ${dockerCompose.serviceNames.join(\", \")}`,\n );\n metrics.count(\"cli.run.autodetect\", 1, { attributes: { type: \"docker-compose\" } });\n ({ cmdArgs, stdin } = prepareDockerComposeRun(dockerCompose, actualServerPort, env));\n } else {\n logger.info(`Using package.json script: ${packageJson.scriptName}`);\n metrics.count(\"cli.run.autodetect\", 1, { attributes: { type: \"package-json\" } });\n cmdArgs = [packageJson.scriptCommand];\n shell = true;\n env.PATH = path.resolve(\"./node_modules/.bin\") + path.delimiter + env.PATH;\n }\n } else if (dockerCompose) {\n logger.info(\n `Detected Docker Compose project with ${dockerCompose.serviceNames.length} service(s): ${dockerCompose.serviceNames.join(\", \")}`,\n );\n metrics.count(\"cli.run.autodetect\", 1, { attributes: { type: \"docker-compose\" } });\n ({ cmdArgs, stdin } = prepareDockerComposeRun(dockerCompose, actualServerPort, env));\n } else if (packageJson) {\n logger.info(`Using package.json script: ${packageJson.scriptName}`);\n metrics.count(\"cli.run.autodetect\", 1, { attributes: { type: \"package-json\" } });\n cmdArgs = [packageJson.scriptCommand];\n shell = true;\n env.PATH = path.resolve(\"./node_modules/.bin\") + path.delimiter + env.PATH;\n }\n } else {\n // Handle explicit docker compose commands (e.g., \"spotlight run docker compose up\")\n const explicitDockerConfig = parseExplicitDockerCompose(cmdArgs);\n if (explicitDockerConfig) {\n logger.info(\n `Detected explicit Docker Compose command with ${explicitDockerConfig.serviceNames.length} service(s): ${explicitDockerConfig.serviceNames.join(\", \")}`,\n );\n metrics.count(\"cli.run.docker_compose.explicit\", 1, {\n attributes: {\n services: explicitDockerConfig.serviceNames.length,\n subcommand: explicitDockerConfig.subcommandArgs.join(\" \"),\n },\n });\n ({ cmdArgs, stdin } = prepareDockerComposeRun(explicitDockerConfig, actualServerPort, env));\n }\n }\n if (cmdArgs.length === 0) {\n logger.error(\"Error: No command specified to run and could not infer the command automatically.\");\n process.exit(1);\n }\n const cmdStr = cmdArgs.join(\" \");\n logger.info(`Starting command: ${cmdStr}`);\n // When we have Docker Compose override YAML (for -f -), we need to pipe stdin\n // Otherwise, inherit stdin to relay user input to the downstream process\n const stdinMode: \"pipe\" | \"inherit\" = stdin ? \"pipe\" : \"inherit\";\n const runCmd = spawn(cmdArgs[0], cmdArgs.slice(1), {\n cwd: process.cwd(),\n env,\n shell,\n windowsVerbatimArguments: true,\n windowsHide: true,\n stdio: [stdinMode, \"pipe\", \"pipe\"],\n });\n\n const { stdout, stderr } = runCmd;\n if (!stdout || !stderr) {\n logger.error(\"Failed to create process streams\");\n process.exit(1);\n }\n\n // If our command has a stdin input _and_ we can send to stdin, write and close\n if (stdin) {\n if (!runCmd.stdin) {\n logger.error(\"Failed to pipe Docker Compose override: stdin is not available\");\n process.exit(1);\n }\n runCmd.stdin.write(stdin);\n runCmd.stdin.end();\n }\n\n const processLogLine = (line: string, level: \"info\" | \"error\") => {\n if (!relayStdioAsLogs) return;\n const trimmedLine = line.trim();\n if (!trimmedLine) return;\n\n fuzzySearcher.add(trimmedLine);\n\n const timestamp = Date.now() / 1000;\n const envelopeBuffer = createLogEnvelope(level, trimmedLine, timestamp);\n\n const container = new EventContainer(SENTRY_CONTENT_TYPE, envelopeBuffer);\n // Add to buffer - this will automatically trigger all subscribers\n // including the onEnvelope callback registered in tail()\n getBuffer().put(container);\n };\n\n let stdoutBuffer = \"\";\n let stderrBuffer = \"\";\n\n // Stream stdout as info-level logs\n stdout.on(\"data\", (data: Buffer) => {\n stdoutBuffer += data.toString();\n const lines = stdoutBuffer.split(\"\\n\");\n // Keep the last partial line in the buffer\n stdoutBuffer = lines.pop() || \"\";\n\n for (const line of lines) {\n processLogLine(line, \"info\");\n }\n });\n\n // Stream stderr as error-level logs\n stderr.on(\"data\", (data: Buffer) => {\n stderrBuffer += data.toString();\n const lines = stderrBuffer.split(\"\\n\");\n // Keep the last partial line in the buffer\n stderrBuffer = lines.pop() || \"\";\n\n for (const line of lines) {\n processLogLine(line, \"error\");\n }\n });\n\n runCmd.on(\"error\", err => {\n logger.error(`Failed to run ${cmdStr}:`);\n logger.error(err);\n process.exit(1);\n });\n\n runCmd.on(\"close\", code => {\n // Handle any remaining buffered lines\n if (stdoutBuffer.trim()) {\n processLogLine(stdoutBuffer, \"info\");\n }\n if (stderrBuffer.trim()) {\n processLogLine(stderrBuffer, \"error\");\n }\n\n if (!code) {\n logger.error(`${cmdStr} exited, terminating.`);\n } else {\n logger.error(`${cmdStr} exited with code ${code}.`);\n }\n process.exit(code);\n });\n const killRunCmd = () => {\n if (runCmd.killed) return;\n stdout.destroy();\n stderr.destroy();\n runCmd.kill();\n };\n runCmd.on(\"spawn\", () => {\n runCmd.removeAllListeners(\"spawn\");\n runCmd.removeAllListeners(\"error\");\n process.on(\"SIGINT\", killRunCmd);\n process.on(\"SIGTERM\", killRunCmd);\n process.on(\"beforeExit\", killRunCmd);\n });\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"run.js","sources":["../../../src/server/cli/run.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport type { AddressInfo } from \"node:net\";\nimport * as path from \"node:path\";\nimport * as readline from \"node:readline\";\nimport type { SerializedLog } from \"@sentry/core\";\nimport { metrics } from \"@sentry/node\";\nimport { Searcher } from \"fast-fuzzy\";\nimport { uuidv7 } from \"uuidv7\";\nimport { SENTRY_CONTENT_TYPE } from \"../constants.ts\";\nimport { logger } from \"../logger.ts\";\nimport type { SentryLogEvent } from \"../parser/types.ts\";\nimport type { CLIHandlerOptions, Command, CommandMeta } from \"../types/cli.ts\";\nimport { detectDockerCompose, parseExplicitDockerCompose, prepareDockerComposeRun } from \"../utils/docker-compose.ts\";\nimport { getSpotlightURL, openInBrowser } from \"../utils/extras.ts\";\nimport { EventContainer, getBuffer } from \"../utils/index.ts\";\nimport { type OnItemCallback, handler as tail } from \"./tail.ts\";\n\nexport const meta: CommandMeta = {\n name: \"run\",\n short: \"Run your app with automatic Spotlight instrumentation\",\n usage: \"spotlight run [options] [command...]\",\n long: `Run your application with Spotlight, automatically setting up the necessary\nenvironment variables and capturing telemetry data.\n\nIf no command is provided, Spotlight auto-detects:\n - Docker Compose projects (docker-compose.yml, compose.yml, etc.)\n - package.json scripts (dev, develop, serve, start)\n\nEnvironment variables set automatically:\n - SENTRY_SPOTLIGHT=http://localhost:<port>/stream\n - NEXT_PUBLIC_SENTRY_SPOTLIGHT=http://localhost:<port>/stream\n - SENTRY_TRACES_SAMPLE_RATE=1\n\nstdout/stderr are captured and sent to Spotlight as log events.`,\n examples: [\n \"spotlight run # Auto-detect and run (package.json or docker-compose)\",\n \"spotlight run node server.js # Run a specific command with Spotlight\",\n \"spotlight run -p 3000 npm start # Run with custom port\",\n \"spotlight run python manage.py runserver # Run Python app\",\n \"spotlight run docker compose up # Run Docker Compose with Spotlight injection\",\n ],\n};\n\nconst SPOTLIGHT_VERSION = process.env.npm_package_version || \"unknown\";\n\n// Environment variable host configurations\nconst LOCALHOST_HOST = \"localhost\";\n\n/**\n * Detect if there's a package.json with runnable scripts\n */\nfunction detectPackageJson(): { scriptName: string; scriptCommand: string } | null {\n try {\n const scripts = JSON.parse(readFileSync(\"./package.json\", \"utf-8\")).scripts;\n const scriptName = [\"dev\", \"develop\", \"serve\", \"start\"].find(name => scripts[name]);\n if (!scriptName || !scripts[scriptName]) {\n return null;\n }\n return { scriptName, scriptCommand: scripts[scriptName] };\n } catch {\n // pass\n }\n return null;\n}\n\n/**\n * Prompt the user to choose between Docker Compose and package.json\n */\nasync function promptUserChoice(): Promise<\"docker\" | \"package\"> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise(resolve => {\n console.log(\"\\n⚠️ Both Docker Compose and package.json detected!\");\n console.log(\"\\nWhich would you like to use?\");\n console.log(\" 1) docker compose\");\n console.log(\" 2) package.json\");\n\n rl.question(\"Enter your choice (1 or 2): \", answer => {\n rl.close();\n const choice = answer.trim();\n const selected = choice === \"2\" ? \"package\" : \"docker\";\n logger.info(`Selected: ${selected === \"docker\" ? \"Docker compose\" : \"package.json\"}`);\n resolve(selected);\n });\n });\n}\n\nfunction createLogEnvelope(level: \"info\" | \"error\", body: string, timestamp: number): Buffer {\n const envelopeHeader = {\n sdk: {\n name: \"sentry.spotlight.stdio\",\n version: SPOTLIGHT_VERSION,\n },\n };\n\n const logItem: SerializedLog = {\n timestamp,\n level,\n body,\n attributes: {},\n };\n\n const logPayload: SentryLogEvent = {\n type: \"log\",\n event_id: uuidv7(),\n timestamp,\n items: [\n {\n ...logItem,\n id: uuidv7(),\n severity_number: level === \"error\" ? 17 : 9, // ERROR=17, INFO=9 per OpenTelemetry spec\n sdk: undefined,\n },\n ],\n };\n\n const payloadJson = JSON.stringify(logPayload);\n const payloadBuffer = Buffer.from(payloadJson, \"utf-8\");\n\n const itemHeader = {\n type: \"log\",\n length: payloadBuffer.length, // Use byte length, not string length\n item_count: 1,\n content_type: \"application/json\",\n };\n\n const parts = [JSON.stringify(envelopeHeader), JSON.stringify(itemHeader), payloadJson];\n\n return Buffer.from(`${parts.join(\"\\n\")}\\n`, \"utf-8\");\n}\n\nexport async function handler({\n port,\n cmdArgs,\n basePath,\n filesToServe,\n format,\n allowedOrigins,\n open,\n}: CLIHandlerOptions) {\n let relayStdioAsLogs = true;\n\n const fuzzySearcher = new Searcher([] as string[], {\n threshold: 0.8,\n ignoreCase: false,\n });\n\n const logChecker: OnItemCallback = (type, item, envelopeHeader) => {\n if (type !== \"log\") return true;\n\n const [, payload] = item;\n const logEvent = payload as SentryLogEvent;\n\n if (logEvent.items && logEvent.items.length > 0) {\n for (const logItem of logEvent.items) {\n // Check if this is a stdio log from Spotlight itself\n if (envelopeHeader.sdk?.name === \"sentry.spotlight.stdio\") {\n continue;\n }\n\n if (!relayStdioAsLogs) continue;\n\n const logBody = typeof logItem.body === \"string\" ? logItem.body : String(logItem.body);\n const trimmedBody = logBody.trim();\n\n if (trimmedBody) {\n const matches = fuzzySearcher.search(trimmedBody);\n if (matches.length > 0) {\n logger.debug(\"Detected Sentry logging in the process, disabling stdio relay\");\n relayStdioAsLogs = false;\n return false;\n }\n }\n }\n }\n\n return true;\n };\n\n const serverInstance = await tail({ port, cmdArgs: [], basePath, filesToServe, format, allowedOrigins }, logChecker);\n if (!serverInstance) {\n logger.error(\"Failed to start Spotlight sidecar server.\");\n logger.error(`The port ${port} might already be in use — most likely by another Spotlight instance.`);\n process.exit(1);\n }\n\n // We *MUST* have an instance address and a port here\n // as not having that indicates either the server did not start\n // or started in a weird manner (like over a unix socket)\n const actualServerPort = (serverInstance.address() as AddressInfo).port;\n const spotlightUrl = getSpotlightURL(actualServerPort, LOCALHOST_HOST);\n\n if (open) {\n openInBrowser(actualServerPort);\n }\n let shell = false;\n let stdin: string | undefined = undefined;\n const env = {\n ...process.env,\n SENTRY_SPOTLIGHT: spotlightUrl,\n // We need the one below for Next.js projects -- they only allow NEXT_PUBLIC_ prefixed env vars\n // on frontend code\n NEXT_PUBLIC_SENTRY_SPOTLIGHT: spotlightUrl,\n // This is not supported in all SDKs but worth adding\n // for the ones that support it\n SENTRY_TRACES_SAMPLE_RATE: \"1\",\n } as {\n PATH: string;\n SENTRY_SPOTLIGHT: string;\n NEXT_PUBLIC_SENTRY_SPOTLIGHT: string;\n SENTRY_TRACES_SAMPLE_RATE: string;\n [key: string]: string;\n };\n if (cmdArgs.length === 0) {\n const dockerCompose = detectDockerCompose();\n const packageJson = detectPackageJson();\n\n // If both Docker Compose and package.json are detected, ask the user which one to use\n if (dockerCompose && packageJson) {\n if (!process.stdin.isTTY) {\n logger.error(\"Both Docker Compose and package.json detected, but cannot prompt in non-interactive mode.\");\n logger.error(\"Please specify a command explicitly or run in an interactive terminal.\");\n process.exit(1);\n }\n const choice = await promptUserChoice();\n metrics.count(\"cli.run.prompt_choice\", 1, { attributes: { choice } });\n\n if (choice === \"docker\") {\n logger.info(\n `Detected Docker Compose project with ${dockerCompose.serviceNames.length} service(s): ${dockerCompose.serviceNames.join(\", \")}`,\n );\n metrics.count(\"cli.run.autodetect\", 1, { attributes: { type: \"docker-compose\" } });\n ({ cmdArgs, stdin } = prepareDockerComposeRun(dockerCompose, actualServerPort, env));\n } else {\n logger.info(`Using package.json script: ${packageJson.scriptName}`);\n metrics.count(\"cli.run.autodetect\", 1, { attributes: { type: \"package-json\" } });\n cmdArgs = [packageJson.scriptCommand];\n shell = true;\n env.PATH = path.resolve(\"./node_modules/.bin\") + path.delimiter + env.PATH;\n }\n } else if (dockerCompose) {\n logger.info(\n `Detected Docker Compose project with ${dockerCompose.serviceNames.length} service(s): ${dockerCompose.serviceNames.join(\", \")}`,\n );\n metrics.count(\"cli.run.autodetect\", 1, { attributes: { type: \"docker-compose\" } });\n ({ cmdArgs, stdin } = prepareDockerComposeRun(dockerCompose, actualServerPort, env));\n } else if (packageJson) {\n logger.info(`Using package.json script: ${packageJson.scriptName}`);\n metrics.count(\"cli.run.autodetect\", 1, { attributes: { type: \"package-json\" } });\n cmdArgs = [packageJson.scriptCommand];\n shell = true;\n env.PATH = path.resolve(\"./node_modules/.bin\") + path.delimiter + env.PATH;\n }\n } else {\n // Handle explicit docker compose commands (e.g., \"spotlight run docker compose up\")\n const explicitDockerConfig = parseExplicitDockerCompose(cmdArgs);\n if (explicitDockerConfig) {\n logger.info(\n `Detected explicit Docker Compose command with ${explicitDockerConfig.serviceNames.length} service(s): ${explicitDockerConfig.serviceNames.join(\", \")}`,\n );\n metrics.count(\"cli.run.docker_compose.explicit\", 1, {\n attributes: {\n services: explicitDockerConfig.serviceNames.length,\n subcommand: explicitDockerConfig.subcommandArgs.join(\" \"),\n },\n });\n ({ cmdArgs, stdin } = prepareDockerComposeRun(explicitDockerConfig, actualServerPort, env));\n }\n }\n if (cmdArgs.length === 0) {\n logger.error(\"Error: No command specified to run and could not infer the command automatically.\");\n process.exit(1);\n }\n const cmdStr = cmdArgs.join(\" \");\n logger.info(`Starting command: ${cmdStr}`);\n // When we have Docker Compose override YAML (for -f -), we need to pipe stdin\n // Otherwise, inherit stdin to relay user input to the downstream process\n const stdinMode: \"pipe\" | \"inherit\" = stdin ? \"pipe\" : \"inherit\";\n const runCmd = spawn(cmdArgs[0], cmdArgs.slice(1), {\n cwd: process.cwd(),\n env,\n shell,\n windowsVerbatimArguments: true,\n windowsHide: true,\n stdio: [stdinMode, \"pipe\", \"pipe\"],\n });\n\n const { stdout, stderr } = runCmd;\n if (!stdout || !stderr) {\n logger.error(\"Failed to create process streams\");\n process.exit(1);\n }\n\n // If our command has a stdin input _and_ we can send to stdin, write and close\n if (stdin) {\n if (!runCmd.stdin) {\n logger.error(\"Failed to pipe Docker Compose override: stdin is not available\");\n process.exit(1);\n }\n runCmd.stdin.write(stdin);\n runCmd.stdin.end();\n }\n\n const processLogLine = (line: string, level: \"info\" | \"error\") => {\n if (!relayStdioAsLogs) return;\n const trimmedLine = line.trim();\n if (!trimmedLine) return;\n\n fuzzySearcher.add(trimmedLine);\n\n const timestamp = Date.now() / 1000;\n const envelopeBuffer = createLogEnvelope(level, trimmedLine, timestamp);\n\n const container = new EventContainer(SENTRY_CONTENT_TYPE, envelopeBuffer);\n // Add to buffer - this will automatically trigger all subscribers\n // including the onEnvelope callback registered in tail()\n getBuffer().put(container);\n };\n\n let stdoutBuffer = \"\";\n let stderrBuffer = \"\";\n\n // Stream stdout as info-level logs\n stdout.on(\"data\", (data: Buffer) => {\n stdoutBuffer += data.toString();\n const lines = stdoutBuffer.split(\"\\n\");\n // Keep the last partial line in the buffer\n stdoutBuffer = lines.pop() || \"\";\n\n for (const line of lines) {\n processLogLine(line, \"info\");\n }\n });\n\n // Stream stderr as error-level logs\n stderr.on(\"data\", (data: Buffer) => {\n stderrBuffer += data.toString();\n const lines = stderrBuffer.split(\"\\n\");\n // Keep the last partial line in the buffer\n stderrBuffer = lines.pop() || \"\";\n\n for (const line of lines) {\n processLogLine(line, \"error\");\n }\n });\n\n runCmd.on(\"error\", err => {\n logger.error(`Failed to run ${cmdStr}:`);\n logger.error(err);\n process.exit(1);\n });\n\n runCmd.on(\"close\", code => {\n // Handle any remaining buffered lines\n if (stdoutBuffer.trim()) {\n processLogLine(stdoutBuffer, \"info\");\n }\n if (stderrBuffer.trim()) {\n processLogLine(stderrBuffer, \"error\");\n }\n\n if (!code) {\n logger.error(`${cmdStr} exited, terminating.`);\n } else {\n logger.error(`${cmdStr} exited with code ${code}.`);\n }\n process.exit(code);\n });\n const killRunCmd = () => {\n if (runCmd.killed) return;\n stdout.destroy();\n stderr.destroy();\n runCmd.kill();\n };\n runCmd.on(\"spawn\", () => {\n runCmd.removeAllListeners(\"spawn\");\n runCmd.removeAllListeners(\"error\");\n process.on(\"SIGINT\", killRunCmd);\n process.on(\"SIGTERM\", killRunCmd);\n process.on(\"beforeExit\", killRunCmd);\n });\n}\n\nexport default { meta, handler } satisfies Command;\n"],"names":["tail"],"mappings":"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"}
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
import { CLIHandlerOptions } from '../types/cli.ts';
|
|
2
|
-
export
|
|
1
|
+
import { CLIHandlerOptions, CommandMeta } from '../types/cli.ts';
|
|
2
|
+
export declare const meta: CommandMeta;
|
|
3
|
+
export declare function handler({ port, basePath, filesToServe, allowedOrigins, open }: CLIHandlerOptions): Promise<import('http').Server<typeof import('http').IncomingMessage, typeof import('http').ServerResponse> | undefined>;
|
|
4
|
+
declare const _default: {
|
|
5
|
+
meta: CommandMeta;
|
|
6
|
+
handler: typeof handler;
|
|
7
|
+
};
|
|
8
|
+
export default _default;
|
|
@@ -2,14 +2,35 @@
|
|
|
2
2
|
!function() {
|
|
3
3
|
try {
|
|
4
4
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {}, n = new e.Error().stack;
|
|
5
|
-
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "
|
|
5
|
+
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "ce0912cd-6bc9-403e-885e-b9e4cee0c3a4", e._sentryDebugIdIdentifier = "sentry-dbid-ce0912cd-6bc9-403e-885e-b9e4cee0c3a4");
|
|
6
6
|
} catch (e2) {
|
|
7
7
|
}
|
|
8
8
|
}();
|
|
9
9
|
import { setupSpotlight } from "../main.js";
|
|
10
10
|
import { openInBrowser } from "../utils/extras.js";
|
|
11
11
|
import "../../_virtual/_sentry-release-injection-file.js";
|
|
12
|
-
|
|
12
|
+
const meta = {
|
|
13
|
+
name: "server",
|
|
14
|
+
short: "Start the Spotlight sidecar server (default command)",
|
|
15
|
+
usage: "spotlight [server] [options]",
|
|
16
|
+
long: `Start the Spotlight sidecar HTTP server.
|
|
17
|
+
|
|
18
|
+
This is the default command when running 'spotlight' without arguments.
|
|
19
|
+
The server listens for events from Sentry SDKs and serves the Spotlight UI.
|
|
20
|
+
|
|
21
|
+
The server provides:
|
|
22
|
+
- HTTP endpoint for receiving Sentry envelopes
|
|
23
|
+
- Server-Sent Events (SSE) stream for real-time updates
|
|
24
|
+
- Web UI at http://localhost:PORT`,
|
|
25
|
+
examples: [
|
|
26
|
+
"spotlight # Start on default port 8969",
|
|
27
|
+
"spotlight server # Explicit server command",
|
|
28
|
+
"spotlight --open # Start and open dashboard in browser",
|
|
29
|
+
"spotlight --port 3000 # Start on port 3000",
|
|
30
|
+
"spotlight -p 3000 -d # Start on port 3000 with debug logging"
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
async function handler({ port, basePath, filesToServe, allowedOrigins, open }) {
|
|
13
34
|
const serverInstance = await setupSpotlight({
|
|
14
35
|
port,
|
|
15
36
|
basePath,
|
|
@@ -23,7 +44,10 @@ async function server({ port, basePath, filesToServe, allowedOrigins, open }) {
|
|
|
23
44
|
}
|
|
24
45
|
return serverInstance;
|
|
25
46
|
}
|
|
47
|
+
const server = { meta, handler };
|
|
26
48
|
export {
|
|
27
|
-
server as default
|
|
49
|
+
server as default,
|
|
50
|
+
handler,
|
|
51
|
+
meta
|
|
28
52
|
};
|
|
29
53
|
//# sourceMappingURL=server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sources":["../../../src/server/cli/server.ts"],"sourcesContent":["import type { AddressInfo } from \"node:net\";\nimport { setupSpotlight } from \"../main.ts\";\nimport type { CLIHandlerOptions } from \"../types/cli.ts\";\nimport { openInBrowser } from \"../utils/extras.ts\";\n\nexport default async function
|
|
1
|
+
{"version":3,"file":"server.js","sources":["../../../src/server/cli/server.ts"],"sourcesContent":["import type { AddressInfo } from \"node:net\";\nimport { setupSpotlight } from \"../main.ts\";\nimport type { CLIHandlerOptions, Command, CommandMeta } from \"../types/cli.ts\";\nimport { openInBrowser } from \"../utils/extras.ts\";\n\nexport const meta: CommandMeta = {\n name: \"server\",\n short: \"Start the Spotlight sidecar server (default command)\",\n usage: \"spotlight [server] [options]\",\n long: `Start the Spotlight sidecar HTTP server.\n\nThis is the default command when running 'spotlight' without arguments.\nThe server listens for events from Sentry SDKs and serves the Spotlight UI.\n\nThe server provides:\n - HTTP endpoint for receiving Sentry envelopes\n - Server-Sent Events (SSE) stream for real-time updates\n - Web UI at http://localhost:PORT`,\n examples: [\n \"spotlight # Start on default port 8969\",\n \"spotlight server # Explicit server command\",\n \"spotlight --open # Start and open dashboard in browser\",\n \"spotlight --port 3000 # Start on port 3000\",\n \"spotlight -p 3000 -d # Start on port 3000 with debug logging\",\n ],\n};\n\nexport async function handler({ port, basePath, filesToServe, allowedOrigins, open }: CLIHandlerOptions) {\n const serverInstance = await setupSpotlight({\n port,\n basePath,\n filesToServe,\n isStandalone: true,\n allowedOrigins,\n });\n\n if (open) {\n // Use actual port from server instance, or fall back to requested port\n // (when serverInstance is undefined, a server is already running on the requested port)\n const actualPort = serverInstance ? (serverInstance.address() as AddressInfo).port : port;\n openInBrowser(actualPort);\n }\n\n return serverInstance;\n}\n\nexport default { meta, handler } satisfies Command;\n"],"names":[],"mappings":";;;;;;;;;;;AAKO,MAAM,OAAoB;AAAA,EAC/B,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASN,UAAU;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AAEA,eAAsB,QAAQ,EAAE,MAAM,UAAU,cAAc,gBAAgB,QAA2B;AACvG,QAAM,iBAAiB,MAAM,eAAe;AAAA,IAC1C;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,EAAA,CACD;AAED,MAAI,MAAM;AAGR,UAAM,aAAa,iBAAkB,eAAe,QAAA,EAA0B,OAAO;AACrF,kBAAc,UAAU;AAAA,EAC1B;AAEA,SAAO;AACT;AAEA,MAAA,SAAe,EAAE,MAAM,QAAA;"}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { ServerType } from '@hono/node-server';
|
|
2
2
|
import { ParsedEnvelope } from '../parser/index.ts';
|
|
3
|
-
import { CLIHandlerOptions } from '../types/cli.ts';
|
|
3
|
+
import { CLIHandlerOptions, CommandMeta } from '../types/cli.ts';
|
|
4
4
|
export type OnItemCallback = (type: string, item: ParsedEnvelope["envelope"][1][number], envelopeHeader: ParsedEnvelope["envelope"][0]) => boolean;
|
|
5
5
|
export declare const NAME_TO_TYPE_MAPPING: Record<string, string[]>;
|
|
6
6
|
export declare const EVERYTHING_MAGIC_WORDS: Set<string>;
|
|
7
7
|
export declare const SUPPORTED_TAIL_ARGS: Set<string>;
|
|
8
|
-
export
|
|
8
|
+
export declare const meta: CommandMeta;
|
|
9
|
+
export declare function handler({ port, cmdArgs, basePath, filesToServe, format, allowedOrigins }: CLIHandlerOptions, onItem?: OnItemCallback): Promise<ServerType | undefined>;
|
|
10
|
+
declare const _default: {
|
|
11
|
+
meta: CommandMeta;
|
|
12
|
+
handler: typeof handler;
|
|
13
|
+
};
|
|
14
|
+
export default _default;
|
package/dist/server/cli/tail.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
!function() {
|
|
3
3
|
try {
|
|
4
4
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {}, n = new e.Error().stack;
|
|
5
|
-
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "
|
|
5
|
+
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "22ba8cd6-fd16-4260-968d-aa7a00ac6ae6", e._sentryDebugIdIdentifier = "sentry-dbid-22ba8cd6-fd16-4260-968d-aa7a00ac6ae6");
|
|
6
6
|
} catch (e2) {
|
|
7
7
|
}
|
|
8
8
|
}();
|
|
@@ -38,6 +38,28 @@ const NAME_TO_TYPE_MAPPING = Object.freeze({
|
|
|
38
38
|
const SUPPORTED_ENVELOPE_TYPES = new Set(Object.values(NAME_TO_TYPE_MAPPING).flat());
|
|
39
39
|
const EVERYTHING_MAGIC_WORDS = /* @__PURE__ */ new Set(["everything", "all", "*"]);
|
|
40
40
|
const SUPPORTED_TAIL_ARGS = /* @__PURE__ */ new Set([...Object.keys(NAME_TO_TYPE_MAPPING), ...EVERYTHING_MAGIC_WORDS]);
|
|
41
|
+
const meta = {
|
|
42
|
+
name: "tail",
|
|
43
|
+
short: `Tail Sentry events (types: ${Object.keys(NAME_TO_TYPE_MAPPING).join(", ")})`,
|
|
44
|
+
usage: "spotlight tail [types...] [options]",
|
|
45
|
+
long: `Stream Sentry events to your terminal in real-time.
|
|
46
|
+
|
|
47
|
+
Available event types:
|
|
48
|
+
${Object.keys(NAME_TO_TYPE_MAPPING).join(", ")}
|
|
49
|
+
|
|
50
|
+
Magic words (to tail everything):
|
|
51
|
+
${[...EVERYTHING_MAGIC_WORDS].join(", ")}
|
|
52
|
+
|
|
53
|
+
Use -f/--format to change output format (human, logfmt, json, md).
|
|
54
|
+
Connects to existing sidecar if running, otherwise starts a new one.`,
|
|
55
|
+
examples: [
|
|
56
|
+
"spotlight tail # Tail all event types (human format)",
|
|
57
|
+
"spotlight tail errors # Tail only errors",
|
|
58
|
+
"spotlight tail errors logs # Tail errors and logs",
|
|
59
|
+
"spotlight tail --format json # Use JSON output format",
|
|
60
|
+
"spotlight tail traces -f logfmt # Tail traces with logfmt format"
|
|
61
|
+
]
|
|
62
|
+
};
|
|
41
63
|
const FORMATTERS = {
|
|
42
64
|
md: formatters$3,
|
|
43
65
|
logfmt: formatters$2,
|
|
@@ -49,7 +71,7 @@ const connectUpstream = async (port) => new Promise((resolve, reject) => {
|
|
|
49
71
|
client.onerror = reject;
|
|
50
72
|
client.onopen = () => resolve(client);
|
|
51
73
|
});
|
|
52
|
-
async function
|
|
74
|
+
async function handler({ port, cmdArgs, basePath, filesToServe, format = "logfmt", allowedOrigins }, onItem) {
|
|
53
75
|
const eventTypes = cmdArgs.length > 0 ? cmdArgs.map((arg) => arg.toLowerCase()) : ["everything"];
|
|
54
76
|
for (const eventType of eventTypes) {
|
|
55
77
|
if (!SUPPORTED_TAIL_ARGS.has(eventType)) {
|
|
@@ -104,10 +126,13 @@ async function tail({ port, cmdArgs, basePath, filesToServe, format = "logfmt",
|
|
|
104
126
|
});
|
|
105
127
|
return serverInstance;
|
|
106
128
|
}
|
|
129
|
+
const tail = { meta, handler };
|
|
107
130
|
export {
|
|
108
131
|
EVERYTHING_MAGIC_WORDS,
|
|
109
132
|
NAME_TO_TYPE_MAPPING,
|
|
110
133
|
SUPPORTED_TAIL_ARGS,
|
|
111
|
-
tail as default
|
|
134
|
+
tail as default,
|
|
135
|
+
handler,
|
|
136
|
+
meta
|
|
112
137
|
};
|
|
113
138
|
//# sourceMappingURL=tail.js.map
|