@spotlightjs/spotlight 4.2.0 → 4.4.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/README.md +2 -2
- package/bin/instrument.js +70 -2
- package/bin/run.js +25 -116
- package/dist/overlay/assets/main.js +67 -67
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# `@spotlightjs/spotlight`
|
|
2
2
|
|
|
3
|
-
This package combines the
|
|
3
|
+
This package combines the UI from `@spotlightjs/overlay` and the sidecar from `@spotlightjs/sidecar`.
|
|
4
4
|
|
|
5
|
-
This
|
|
5
|
+
This is the package to use for Spotlight.
|
package/bin/instrument.js
CHANGED
|
@@ -1,12 +1,72 @@
|
|
|
1
|
-
import { init } from "@sentry/node";
|
|
1
|
+
import { consoleLoggingIntegration, getClient, init } from "@sentry/node";
|
|
2
|
+
import { parseCLIArgs } from "@spotlightjs/sidecar/cli";
|
|
3
|
+
import { DEFAULT_PORT } from "@spotlightjs/sidecar/constants";
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
// Parse CLI arguments to determine the port this Spotlight instance will use
|
|
6
|
+
const { port: instancePort } = parseCLIArgs();
|
|
7
|
+
|
|
8
|
+
// Check if SENTRY_SPOTLIGHT points to this instance
|
|
9
|
+
const spotlightEnv = process.env.SENTRY_SPOTLIGHT;
|
|
10
|
+
let disableSpotlight = false;
|
|
11
|
+
|
|
12
|
+
// Note: If port is set to 0 (dynamic port assignment), we cannot detect the actual port
|
|
13
|
+
// before starting the server, so we cannot prevent the feedback loop in that case.
|
|
14
|
+
// The server will be assigned a port by the OS after it starts, and if SENTRY_SPOTLIGHT
|
|
15
|
+
// happens to point to that same port, a feedback loop may occur.
|
|
16
|
+
if (spotlightEnv && instancePort !== 0) {
|
|
17
|
+
let targetPort;
|
|
18
|
+
let targetHost;
|
|
19
|
+
|
|
20
|
+
// SENTRY_SPOTLIGHT can be:
|
|
21
|
+
// 1. A full URL like "http://localhost:8969"
|
|
22
|
+
// 2. A truthy value (true, t, y, yes, on, 1) which means use the default URL
|
|
23
|
+
const TRUTHY_ENV_VALUES = new Set(["true", "t", "y", "yes", "on", "1"]);
|
|
24
|
+
const isTruthy = TRUTHY_ENV_VALUES.has(spotlightEnv.toLowerCase());
|
|
25
|
+
|
|
26
|
+
if (isTruthy) {
|
|
27
|
+
// Use default Spotlight URL
|
|
28
|
+
targetHost = "localhost";
|
|
29
|
+
targetPort = DEFAULT_PORT;
|
|
30
|
+
} else {
|
|
31
|
+
// Try to parse as URL
|
|
32
|
+
try {
|
|
33
|
+
const spotlightUrl = new URL(spotlightEnv);
|
|
34
|
+
targetHost = spotlightUrl.hostname;
|
|
35
|
+
targetPort = spotlightUrl.port ? Number(spotlightUrl.port) : spotlightUrl.protocol === "https:" ? 443 : 80;
|
|
36
|
+
} catch (_err) {
|
|
37
|
+
// If we can't parse it, we can't determine if it's a feedback loop, so do nothing
|
|
38
|
+
targetPort = null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Only disable if we successfully determined the target and it matches our instance
|
|
43
|
+
if (targetPort !== null && targetPort !== undefined) {
|
|
44
|
+
const isLocalhost = targetHost === "localhost" || targetHost === "127.0.0.1" || targetHost === "::1";
|
|
45
|
+
|
|
46
|
+
if (isLocalhost && targetPort === instancePort) {
|
|
47
|
+
disableSpotlight = true;
|
|
48
|
+
console.warn(
|
|
49
|
+
`⚠️ [Spotlight] SENTRY_SPOTLIGHT is set to ${spotlightEnv} which points to this Spotlight instance (port ${instancePort}). Disabling Spotlight integration to prevent feedback loop.`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const sentry = init({
|
|
4
56
|
dsn: "https://51bcd92dba1128934afd1c5726c84442@o1.ingest.us.sentry.io/4508404727283713",
|
|
5
57
|
environment: process.env.NODE_ENV || "development",
|
|
6
58
|
release: `spotlight@${process.env.npm_package_version}`,
|
|
7
59
|
debug: Boolean(process.env.SENTRY_DEBUG),
|
|
8
60
|
|
|
9
61
|
tracesSampleRate: 1,
|
|
62
|
+
enableLogs: true,
|
|
63
|
+
...(disableSpotlight && { spotlight: false }),
|
|
64
|
+
|
|
65
|
+
integrations: [
|
|
66
|
+
consoleLoggingIntegration({
|
|
67
|
+
levels: ["log", "info", "warn", "error", "debug"],
|
|
68
|
+
}),
|
|
69
|
+
],
|
|
10
70
|
|
|
11
71
|
beforeSendTransaction: event => {
|
|
12
72
|
event.server_name = undefined; // Server name might contain PII
|
|
@@ -37,3 +97,11 @@ init({
|
|
|
37
97
|
return event;
|
|
38
98
|
},
|
|
39
99
|
});
|
|
100
|
+
|
|
101
|
+
function shutdown() {
|
|
102
|
+
sentry.close();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
process.on("SIGINT", shutdown);
|
|
106
|
+
process.on("SIGTERM", shutdown);
|
|
107
|
+
// process.on("beforeExit", shutdown);
|
package/bin/run.js
CHANGED
|
@@ -3,9 +3,8 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import Module from "node:module";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { inflateRawSync } from "node:zlib";
|
|
7
6
|
import { setContext, startSpan } from "@sentry/node";
|
|
8
|
-
import {
|
|
7
|
+
import { main } from "@spotlightjs/sidecar/cli";
|
|
9
8
|
import "./instrument.js";
|
|
10
9
|
const require = Module.createRequire(import.meta.url);
|
|
11
10
|
let sea = null;
|
|
@@ -21,134 +20,44 @@ const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
|
21
20
|
setContext("CLI", {
|
|
22
21
|
sea: sea.isSea(),
|
|
23
22
|
// TODO: Be less naive with path obscuring
|
|
24
|
-
argv: process.argv.map(
|
|
23
|
+
argv: process.argv.map(arg => arg.replace(homeDir, "~")),
|
|
25
24
|
});
|
|
26
25
|
|
|
27
26
|
const withTracing =
|
|
28
27
|
(fn, spanArgs = {}) =>
|
|
29
28
|
(...args) =>
|
|
30
|
-
startSpan({ name: fn.name, attributes: { args }, ...spanArgs }, () =>
|
|
31
|
-
fn(...args)
|
|
32
|
-
);
|
|
29
|
+
startSpan({ name: fn.name, attributes: { args }, ...spanArgs }, () => fn(...args));
|
|
33
30
|
|
|
34
31
|
const readAsset = withTracing(
|
|
35
32
|
sea.isSea()
|
|
36
|
-
?
|
|
33
|
+
? name => Buffer.from(sea.getRawAsset(name))
|
|
37
34
|
: (() => {
|
|
38
|
-
const ASSET_DIR = join(
|
|
39
|
-
fileURLToPath(import.meta.url),
|
|
40
|
-
"../../dist/overlay/"
|
|
41
|
-
);
|
|
35
|
+
const ASSET_DIR = join(fileURLToPath(import.meta.url), "../../dist/overlay/");
|
|
42
36
|
|
|
43
|
-
return
|
|
37
|
+
return name => readFileSync(join(ASSET_DIR, name));
|
|
44
38
|
})(),
|
|
45
|
-
{ name: "readAsset", op: "cli.asset.read" }
|
|
39
|
+
{ name: "readAsset", op: "cli.asset.read" },
|
|
46
40
|
);
|
|
47
41
|
|
|
48
42
|
startSpan({ name: "Spotlight CLI", op: "cli" }, async () => {
|
|
49
|
-
startSpan({ name: "
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"base64"
|
|
62
|
-
)
|
|
63
|
-
)
|
|
64
|
-
)
|
|
65
|
-
);
|
|
66
|
-
const E = "\x1b[";
|
|
67
|
-
const C = `${E}38;5;`;
|
|
68
|
-
const M_COL = `${C}96m`;
|
|
69
|
-
const F_COL = `${C}61m`;
|
|
70
|
-
const BOLD = `${E}1m`;
|
|
71
|
-
const RESET = `${E}0m`;
|
|
72
|
-
const NL = `${RESET}\n`;
|
|
73
|
-
let factor = 0.22;
|
|
74
|
-
let c = 0;
|
|
75
|
-
let col = 0;
|
|
76
|
-
let line = 0;
|
|
77
|
-
let lim = 26;
|
|
78
|
-
let r = 0;
|
|
79
|
-
for (let p of data) {
|
|
80
|
-
if (p === 255) {
|
|
81
|
-
stderrBuffer += NL;
|
|
82
|
-
c = col = 0;
|
|
83
|
-
if (line++ === 5) {
|
|
84
|
-
factor = factor / -3;
|
|
85
|
-
}
|
|
86
|
-
lim = Math.round(lim * (1 + factor));
|
|
87
|
-
r = Math.round(Math.random() * 18);
|
|
88
|
-
} else {
|
|
89
|
-
while (p-- >= 0 && col < MAX_COLS) {
|
|
90
|
-
if (col < lim - 1) {
|
|
91
|
-
stderrBuffer += M_COL;
|
|
92
|
-
} else if (col === lim - 1) {
|
|
93
|
-
stderrBuffer += F_COL;
|
|
94
|
-
} else if (col === lim + r) {
|
|
95
|
-
stderrBuffer += `${RESET}${BOLD}`;
|
|
96
|
-
}
|
|
97
|
-
stderrBuffer += c ? (col >= 35 ? "#" : "s") : " ";
|
|
98
|
-
col++;
|
|
99
|
-
}
|
|
100
|
-
c = !c;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
stderrBuffer += NL;
|
|
104
|
-
process.stderr.write(stderrBuffer);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
await startSpan(
|
|
108
|
-
{ name: "Setup Sidecar", op: "cli.setup.sidecar" },
|
|
109
|
-
async () => {
|
|
110
|
-
const args = parseCLIArgs();
|
|
111
|
-
if (args.help) {
|
|
112
|
-
console.log(`
|
|
113
|
-
Usage: spotlight [options]
|
|
114
|
-
|
|
115
|
-
Options:
|
|
116
|
-
-p, --port <port> Port to listen on (default: 8969)
|
|
117
|
-
--stdio-mcp Enable MCP stdio transport
|
|
118
|
-
-d, --debug Enable debug logging
|
|
119
|
-
-h, --help Show this help message
|
|
120
|
-
|
|
121
|
-
Examples:
|
|
122
|
-
spotlight # Start on default port 8969
|
|
123
|
-
spotlight --port 3000 # Start on port 3000
|
|
124
|
-
spotlight -p 3000 -d # Start on port 3000 with debug logging
|
|
125
|
-
`);
|
|
126
|
-
process.exit(0);
|
|
43
|
+
await startSpan({ name: "Setup Sidecar", op: "cli.setup.sidecar" }, async () => {
|
|
44
|
+
const MANIFEST_NAME = "manifest.json";
|
|
45
|
+
const ENTRY_POINT_NAME = "src/index.html";
|
|
46
|
+
const filesToServe = Object.create(null);
|
|
47
|
+
|
|
48
|
+
startSpan({ name: "Setup Server Assets", op: "cli.setup.sidecar.assets" }, () => {
|
|
49
|
+
// Following the guide here: https://vite.dev/guide/backend-integration.html
|
|
50
|
+
const manifest = JSON.parse(readAsset(MANIFEST_NAME));
|
|
51
|
+
filesToServe[ENTRY_POINT_NAME] = readAsset(ENTRY_POINT_NAME);
|
|
52
|
+
const entries = Object.values(manifest);
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
filesToServe[entry.file] = readAsset(entry.file);
|
|
127
55
|
}
|
|
128
|
-
|
|
129
|
-
const ENTRY_POINT_NAME = "src/index.html";
|
|
130
|
-
const basePath = process.cwd();
|
|
131
|
-
const filesToServe = Object.create(null);
|
|
56
|
+
});
|
|
132
57
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
filesToServe[ENTRY_POINT_NAME] = readAsset(ENTRY_POINT_NAME);
|
|
139
|
-
const entries = Object.values(manifest);
|
|
140
|
-
for (const entry of entries) {
|
|
141
|
-
filesToServe[entry.file] = readAsset(entry.file);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
await setupSidecar({
|
|
147
|
-
...args,
|
|
148
|
-
basePath,
|
|
149
|
-
filesToServe,
|
|
150
|
-
isStandalone: true,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
);
|
|
58
|
+
await main({
|
|
59
|
+
basePath: process.cwd(),
|
|
60
|
+
filesToServe,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
154
63
|
});
|