@neat.is/core 0.4.9 → 0.4.11
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/{chunk-J5CEKCTR.js → chunk-OJHI33LD.js} +167 -20
- package/dist/chunk-OJHI33LD.js.map +1 -0
- package/dist/{chunk-RC3CIDZO.js → chunk-W4RNPPB5.js} +4 -2
- package/dist/{chunk-RC3CIDZO.js.map → chunk-W4RNPPB5.js.map} +1 -1
- package/dist/cli.cjs +415 -38
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +254 -24
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +164 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +164 -15
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +87 -10
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +3 -2
- package/dist/chunk-J5CEKCTR.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -639,6 +639,7 @@ init_cjs_shims();
|
|
|
639
639
|
init_cjs_shims();
|
|
640
640
|
var import_node_fs3 = require("fs");
|
|
641
641
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
642
|
+
var sourceMapJs = __toESM(require("source-map-js"), 1);
|
|
642
643
|
|
|
643
644
|
// src/policy.ts
|
|
644
645
|
init_cjs_shims();
|
|
@@ -1693,6 +1694,14 @@ function warnUnidentifiedSpan(project) {
|
|
|
1693
1694
|
`[neatd] span lacked service.name; routed to 'unidentified' in project ${project}; check your OTel SDK config.`
|
|
1694
1695
|
);
|
|
1695
1696
|
}
|
|
1697
|
+
var noSourceMapWarnedServices = /* @__PURE__ */ new Set();
|
|
1698
|
+
function warnNoSourceMaps(serviceName) {
|
|
1699
|
+
if (noSourceMapWarnedServices.has(serviceName)) return;
|
|
1700
|
+
noSourceMapWarnedServices.add(serviceName);
|
|
1701
|
+
console.warn(
|
|
1702
|
+
`[neat] ${serviceName}: no .map files found under dist/; observed file edges will land on dist paths, not src. Set sourceMap: true in tsconfig to enable file-level reconciliation.`
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1696
1705
|
function pickAttr(span, ...keys) {
|
|
1697
1706
|
for (const k of keys) {
|
|
1698
1707
|
const v = span.attributes[k];
|
|
@@ -1735,8 +1744,13 @@ function languageForExt(relPath) {
|
|
|
1735
1744
|
return void 0;
|
|
1736
1745
|
}
|
|
1737
1746
|
}
|
|
1738
|
-
function relPathForRuntimeFile(filepath, serviceNode) {
|
|
1747
|
+
function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
|
|
1739
1748
|
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1749
|
+
if (scanPath && scanPath.length > 0) {
|
|
1750
|
+
const absRoot = toPosix(import_node_path3.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
|
|
1751
|
+
const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
|
|
1752
|
+
if (p.startsWith(anchor)) return p.slice(anchor.length);
|
|
1753
|
+
}
|
|
1740
1754
|
const root = serviceNode?.repoPath;
|
|
1741
1755
|
if (root && root !== "." && root.length > 0) {
|
|
1742
1756
|
const rootPosix = toPosix(root);
|
|
@@ -1753,16 +1767,65 @@ function relPathForRuntimeFile(filepath, serviceNode) {
|
|
|
1753
1767
|
p = p.replace(/^[A-Za-z]:/, "").replace(/^\/+/, "");
|
|
1754
1768
|
return p.length > 0 ? p : null;
|
|
1755
1769
|
}
|
|
1756
|
-
|
|
1770
|
+
var sourceMapCache = /* @__PURE__ */ new Map();
|
|
1771
|
+
function resolveDistToSrc(absFilepath, line) {
|
|
1772
|
+
if (!absFilepath.endsWith(".js")) return null;
|
|
1773
|
+
let entry2 = sourceMapCache.get(absFilepath);
|
|
1774
|
+
if (entry2 === void 0) {
|
|
1775
|
+
entry2 = null;
|
|
1776
|
+
const mapPath = `${absFilepath}.map`;
|
|
1777
|
+
try {
|
|
1778
|
+
if ((0, import_node_fs3.existsSync)(mapPath)) {
|
|
1779
|
+
const raw = JSON.parse((0, import_node_fs3.readFileSync)(mapPath, "utf8"));
|
|
1780
|
+
const consumer = new sourceMapJs.SourceMapConsumer(raw);
|
|
1781
|
+
entry2 = { consumer, dir: import_node_path3.default.dirname(mapPath) };
|
|
1782
|
+
}
|
|
1783
|
+
} catch {
|
|
1784
|
+
entry2 = null;
|
|
1785
|
+
}
|
|
1786
|
+
sourceMapCache.set(absFilepath, entry2);
|
|
1787
|
+
}
|
|
1788
|
+
if (!entry2) return null;
|
|
1789
|
+
try {
|
|
1790
|
+
const pos = entry2.consumer.originalPositionFor({
|
|
1791
|
+
line: line !== void 0 && Number.isFinite(line) ? line : 1,
|
|
1792
|
+
column: 0
|
|
1793
|
+
});
|
|
1794
|
+
if (!pos || !pos.source) return null;
|
|
1795
|
+
const root = entry2.consumer.sourceRoot ?? "";
|
|
1796
|
+
const resolved = import_node_path3.default.resolve(entry2.dir, root, pos.source);
|
|
1797
|
+
return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
|
|
1798
|
+
} catch {
|
|
1799
|
+
return null;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
1757
1803
|
const filepath = span.attributes[CODE_FILEPATH_ATTR];
|
|
1758
1804
|
if (typeof filepath !== "string" || filepath.length === 0) return null;
|
|
1759
|
-
const relPath = relPathForRuntimeFile(filepath, serviceNode);
|
|
1760
|
-
if (!relPath) return null;
|
|
1761
1805
|
const linenoRaw = span.attributes[CODE_LINENO_ATTR];
|
|
1762
|
-
|
|
1806
|
+
let line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
|
|
1807
|
+
const abs = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1808
|
+
const resolved = resolveDistToSrc(abs, line);
|
|
1809
|
+
let effectivePath = filepath;
|
|
1810
|
+
let originalRelPath;
|
|
1811
|
+
if (resolved) {
|
|
1812
|
+
originalRelPath = relPathForRuntimeFile(filepath, serviceNode, scanPath) ?? void 0;
|
|
1813
|
+
effectivePath = resolved.filepath;
|
|
1814
|
+
if (resolved.line !== void 0) line = resolved.line;
|
|
1815
|
+
}
|
|
1816
|
+
const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
|
|
1817
|
+
if (!relPath) return null;
|
|
1818
|
+
if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
|
|
1819
|
+
warnNoSourceMaps(serviceNode.name);
|
|
1820
|
+
}
|
|
1763
1821
|
const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
|
|
1764
1822
|
const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
|
|
1765
|
-
return {
|
|
1823
|
+
return {
|
|
1824
|
+
relPath,
|
|
1825
|
+
...line !== void 0 ? { line } : {},
|
|
1826
|
+
...fn ? { fn } : {},
|
|
1827
|
+
...originalRelPath && originalRelPath !== relPath ? { originalRelPath } : {}
|
|
1828
|
+
};
|
|
1766
1829
|
}
|
|
1767
1830
|
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
1768
1831
|
const fileNodeId = (0, import_types3.fileId)(serviceName, callSite.relPath);
|
|
@@ -1774,6 +1837,7 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
|
1774
1837
|
service: serviceName,
|
|
1775
1838
|
path: callSite.relPath,
|
|
1776
1839
|
...language ? { language } : {},
|
|
1840
|
+
...callSite.originalRelPath ? { originalPath: callSite.originalRelPath } : {},
|
|
1777
1841
|
discoveredVia: "otel"
|
|
1778
1842
|
};
|
|
1779
1843
|
graph.addNode(fileNodeId, node);
|
|
@@ -1799,6 +1863,12 @@ function makeInferredEdgeId(type, source, target) {
|
|
|
1799
1863
|
}
|
|
1800
1864
|
var INFERRED_CONFIDENCE = 0.6;
|
|
1801
1865
|
var STITCH_MAX_DEPTH = 2;
|
|
1866
|
+
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
1867
|
+
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
1868
|
+
function spanMintsObservedEdge(kind) {
|
|
1869
|
+
if (kind === void 0 || kind === 0) return true;
|
|
1870
|
+
return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
|
|
1871
|
+
}
|
|
1802
1872
|
var PARENT_SPAN_CACHE_SIZE = 1e4;
|
|
1803
1873
|
var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1804
1874
|
var parentSpanCache = /* @__PURE__ */ new Map();
|
|
@@ -2034,12 +2104,13 @@ async function handleSpan(ctx, span) {
|
|
|
2034
2104
|
const isError = span.statusCode === 2;
|
|
2035
2105
|
cacheSpanService(span, nowMs);
|
|
2036
2106
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2037
|
-
const callSite = callSiteFromSpan(span, sourceServiceNode);
|
|
2107
|
+
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
2038
2108
|
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
2039
2109
|
let affectedNode = sourceId;
|
|
2110
|
+
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2040
2111
|
if (span.dbSystem) {
|
|
2041
2112
|
const host = pickAddress(span);
|
|
2042
|
-
if (host) {
|
|
2113
|
+
if (mintsFromCallerSide && host) {
|
|
2043
2114
|
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
2044
2115
|
const targetId = (0, import_types3.databaseId)(host);
|
|
2045
2116
|
const result = upsertObservedEdge(
|
|
@@ -2055,7 +2126,7 @@ async function handleSpan(ctx, span) {
|
|
|
2055
2126
|
} else {
|
|
2056
2127
|
const host = pickAddress(span);
|
|
2057
2128
|
let resolvedViaAddress = false;
|
|
2058
|
-
if (host && host !== span.service) {
|
|
2129
|
+
if (mintsFromCallerSide && host && host !== span.service) {
|
|
2059
2130
|
const targetId = resolveServiceId(ctx.graph, host, env);
|
|
2060
2131
|
if (targetId && targetId !== sourceId) {
|
|
2061
2132
|
upsertObservedEdge(
|
|
@@ -3851,8 +3922,14 @@ function collectStringLiterals(node, out) {
|
|
|
3851
3922
|
if (child) collectStringLiterals(child, out);
|
|
3852
3923
|
}
|
|
3853
3924
|
}
|
|
3925
|
+
var PARSE_CHUNK = 16384;
|
|
3926
|
+
function parseSource(parser, source) {
|
|
3927
|
+
return parser.parse(
|
|
3928
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
|
|
3929
|
+
);
|
|
3930
|
+
}
|
|
3854
3931
|
function callsFromSource(source, parser, knownHosts) {
|
|
3855
|
-
const tree = parser
|
|
3932
|
+
const tree = parseSource(parser, source);
|
|
3856
3933
|
const literals = [];
|
|
3857
3934
|
collectStringLiterals(tree.rootNode, literals);
|
|
3858
3935
|
const out = [];
|
|
@@ -5298,6 +5375,66 @@ function registryPath() {
|
|
|
5298
5375
|
function registryLockPath() {
|
|
5299
5376
|
return import_node_path34.default.join(neatHome(), "projects.json.lock");
|
|
5300
5377
|
}
|
|
5378
|
+
function daemonPidPath() {
|
|
5379
|
+
return import_node_path34.default.join(neatHome(), "neatd.pid");
|
|
5380
|
+
}
|
|
5381
|
+
function isPidAliveDefault(pid) {
|
|
5382
|
+
try {
|
|
5383
|
+
process.kill(pid, 0);
|
|
5384
|
+
return true;
|
|
5385
|
+
} catch (err) {
|
|
5386
|
+
return err.code === "EPERM";
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
5389
|
+
async function readPidFile(file) {
|
|
5390
|
+
try {
|
|
5391
|
+
const raw = await import_node_fs21.promises.readFile(file, "utf8");
|
|
5392
|
+
const pid = Number.parseInt(raw.trim(), 10);
|
|
5393
|
+
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
5394
|
+
} catch {
|
|
5395
|
+
return void 0;
|
|
5396
|
+
}
|
|
5397
|
+
}
|
|
5398
|
+
var defaultLockHolderProbe = {
|
|
5399
|
+
isPidAlive: isPidAliveDefault,
|
|
5400
|
+
daemonPidFromFile: () => readPidFile(daemonPidPath()),
|
|
5401
|
+
async daemonResponds() {
|
|
5402
|
+
const base = process.env.NEAT_API_URL ?? "http://localhost:8080";
|
|
5403
|
+
try {
|
|
5404
|
+
await fetch(`${base}/health`, { signal: AbortSignal.timeout(750) });
|
|
5405
|
+
return true;
|
|
5406
|
+
} catch {
|
|
5407
|
+
return false;
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
};
|
|
5411
|
+
async function readLockPid(lockPath) {
|
|
5412
|
+
return readPidFile(lockPath);
|
|
5413
|
+
}
|
|
5414
|
+
async function classifyLockHolder(lockPath, probe = defaultLockHolderProbe) {
|
|
5415
|
+
const lockPid = await readLockPid(lockPath);
|
|
5416
|
+
const daemonPid = await probe.daemonPidFromFile();
|
|
5417
|
+
if (daemonPid !== void 0 && daemonPid !== process.pid && probe.isPidAlive(daemonPid) && // The lock already names the daemon, or the daemon answers on its port.
|
|
5418
|
+
// Either confirms a live daemon is in the picture (the second guards
|
|
5419
|
+
// against a stale pidfile whose PID got reused).
|
|
5420
|
+
(daemonPid === lockPid || await probe.daemonResponds())) {
|
|
5421
|
+
return { kind: "daemon", pid: daemonPid };
|
|
5422
|
+
}
|
|
5423
|
+
if (lockPid !== void 0 && lockPid !== process.pid && probe.isPidAlive(lockPid)) {
|
|
5424
|
+
return { kind: "command", pid: lockPid };
|
|
5425
|
+
}
|
|
5426
|
+
return { kind: "stale" };
|
|
5427
|
+
}
|
|
5428
|
+
function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
5429
|
+
switch (holder.kind) {
|
|
5430
|
+
case "daemon":
|
|
5431
|
+
return `The neat daemon (pid ${holder.pid}) is holding the registry lock. Register this project through the daemon, or stop neatd and re-run \`neat init\`.`;
|
|
5432
|
+
case "command":
|
|
5433
|
+
return `Another neat command (pid ${holder.pid}) is holding the registry lock. Wait for it to finish, or check \`ps\` if you're not sure what's running.`;
|
|
5434
|
+
case "stale":
|
|
5435
|
+
return `neat registry: timed out after ${timeoutMs}ms waiting for ${lockPath}. Another neat process is holding the lock; if no such process exists, remove the file by hand.`;
|
|
5436
|
+
}
|
|
5437
|
+
}
|
|
5301
5438
|
async function normalizeProjectPath(input) {
|
|
5302
5439
|
const resolved = import_node_path34.default.resolve(input);
|
|
5303
5440
|
try {
|
|
@@ -5318,21 +5455,31 @@ async function writeAtomically(target, contents) {
|
|
|
5318
5455
|
}
|
|
5319
5456
|
await import_node_fs21.promises.rename(tmp, target);
|
|
5320
5457
|
}
|
|
5321
|
-
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
5458
|
+
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
5322
5459
|
const deadline = Date.now() + timeoutMs;
|
|
5323
5460
|
await import_node_fs21.promises.mkdir(import_node_path34.default.dirname(lockPath), { recursive: true });
|
|
5461
|
+
let probedHolder = false;
|
|
5324
5462
|
while (true) {
|
|
5325
5463
|
try {
|
|
5326
5464
|
const fd = await import_node_fs21.promises.open(lockPath, "wx");
|
|
5327
|
-
|
|
5465
|
+
try {
|
|
5466
|
+
await fd.writeFile(`${process.pid}
|
|
5467
|
+
`, "utf8");
|
|
5468
|
+
} finally {
|
|
5469
|
+
await fd.close();
|
|
5470
|
+
}
|
|
5328
5471
|
return;
|
|
5329
5472
|
} catch (err) {
|
|
5330
5473
|
const code = err.code;
|
|
5331
5474
|
if (code !== "EEXIST") throw err;
|
|
5475
|
+
if (!probedHolder) {
|
|
5476
|
+
probedHolder = true;
|
|
5477
|
+
const holder = await classifyLockHolder(lockPath, probe);
|
|
5478
|
+
if (holder.kind === "daemon") throw new Error(lockHolderMessage(holder, lockPath, timeoutMs));
|
|
5479
|
+
}
|
|
5332
5480
|
if (Date.now() >= deadline) {
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
);
|
|
5481
|
+
const holder = await classifyLockHolder(lockPath, probe);
|
|
5482
|
+
throw new Error(lockHolderMessage(holder, lockPath, timeoutMs));
|
|
5336
5483
|
}
|
|
5337
5484
|
await new Promise((r) => setTimeout(r, LOCK_RETRY_MS));
|
|
5338
5485
|
}
|
|
@@ -6525,6 +6672,7 @@ async function startWatch(graph, opts) {
|
|
|
6525
6672
|
const onSpan = makeSpanHandler({
|
|
6526
6673
|
graph,
|
|
6527
6674
|
errorsPath: opts.errorsPath,
|
|
6675
|
+
scanPath: opts.scanPath,
|
|
6528
6676
|
project: projectName,
|
|
6529
6677
|
writeErrorEventInline: false,
|
|
6530
6678
|
onPolicyTrigger
|
|
@@ -6539,6 +6687,7 @@ async function startWatch(graph, opts) {
|
|
|
6539
6687
|
const onSpanGrpc = makeSpanHandler({
|
|
6540
6688
|
graph,
|
|
6541
6689
|
errorsPath: opts.errorsPath,
|
|
6690
|
+
scanPath: opts.scanPath,
|
|
6542
6691
|
project: projectName,
|
|
6543
6692
|
onPolicyTrigger
|
|
6544
6693
|
});
|
|
@@ -6804,13 +6953,20 @@ var import_node_path40 = __toESM(require("path"), 1);
|
|
|
6804
6953
|
// src/installers/templates.ts
|
|
6805
6954
|
init_cjs_shims();
|
|
6806
6955
|
var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
|
|
6807
|
-
var OTEL_INIT_STAMP = "// neat-template-version:
|
|
6956
|
+
var OTEL_INIT_STAMP = "// neat-template-version: 4 \u2014 layered file-first capture (ADR-090): stack walk + handler-entry + off-stack facades.";
|
|
6808
6957
|
var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer ' + process.env.NEAT_OTEL_TOKEN";
|
|
6809
|
-
function
|
|
6810
|
-
const stackT = ts ? ": string" : "";
|
|
6958
|
+
function neatCaptureSource(ts) {
|
|
6811
6959
|
const spanT = ts ? ": any" : "";
|
|
6812
6960
|
const strOpt = ts ? ": string | undefined" : "";
|
|
6813
|
-
|
|
6961
|
+
const anyT = ts ? ": any" : "";
|
|
6962
|
+
const fnT = ts ? ": any" : "";
|
|
6963
|
+
const arrAny = ts ? ": any[]" : "";
|
|
6964
|
+
return `// Context key shared by the facade/handler wraps (writers) and the
|
|
6965
|
+
// processor fallback (reader). Symbol.for keeps it stable even if the wraps
|
|
6966
|
+
// and the processor end up in different module instances.
|
|
6967
|
+
const NEAT_USER_FRAME = Symbol.for('neat.user-frame')
|
|
6968
|
+
|
|
6969
|
+
function __neatPickUserFrame(stack${strOpt}) {
|
|
6814
6970
|
const lines = String(stack || '').split('\\n')
|
|
6815
6971
|
for (let i = 0; i < lines.length; i++) {
|
|
6816
6972
|
const raw = lines[i].trim()
|
|
@@ -6819,6 +6975,9 @@ function neatCallsiteProcessorSource(ts) {
|
|
|
6819
6975
|
if (raw.indexOf('@opentelemetry') !== -1) continue
|
|
6820
6976
|
if (raw.indexOf('node:') !== -1) continue
|
|
6821
6977
|
if (raw.indexOf('NeatCallSiteSpanProcessor') !== -1) continue
|
|
6978
|
+
// Skip NEAT's own inlined wraps/helpers (all carry the __neat prefix) so a
|
|
6979
|
+
// facade frame never masquerades as the user's call site.
|
|
6980
|
+
if (raw.indexOf('__neat') !== -1) continue
|
|
6822
6981
|
const bodyText = raw.slice(3)
|
|
6823
6982
|
const loc = bodyText.match(/:(\\d+):(\\d+)\\)?$/)
|
|
6824
6983
|
if (!loc) continue
|
|
@@ -6840,32 +6999,245 @@ function neatCallsiteProcessorSource(ts) {
|
|
|
6840
6999
|
return null
|
|
6841
7000
|
}
|
|
6842
7001
|
|
|
7002
|
+
// Layer 2/3 fallback source: the frame a handler-entry or facade wrap pushed
|
|
7003
|
+
// into context. Reads the span's own parent context first, then the active
|
|
7004
|
+
// context \u2014 the capture spike confirmed both return the value for undici.
|
|
7005
|
+
function __neatFrameFromContext(parentContext${anyT}) {
|
|
7006
|
+
try {
|
|
7007
|
+
const fromParent =
|
|
7008
|
+
parentContext && typeof parentContext.getValue === 'function'
|
|
7009
|
+
? parentContext.getValue(NEAT_USER_FRAME)
|
|
7010
|
+
: undefined
|
|
7011
|
+
return fromParent || context.active().getValue(NEAT_USER_FRAME) || null
|
|
7012
|
+
} catch (_e) {
|
|
7013
|
+
return null
|
|
7014
|
+
}
|
|
7015
|
+
}
|
|
7016
|
+
|
|
7017
|
+
function __neatSetCodeAttrs(span${spanT}, frame${anyT}) {
|
|
7018
|
+
span.setAttribute('code.filepath', frame.filepath)
|
|
7019
|
+
if (typeof frame.lineno === 'number') span.setAttribute('code.lineno', frame.lineno)
|
|
7020
|
+
if (frame.function) span.setAttribute('code.function', frame.function)
|
|
7021
|
+
}
|
|
7022
|
+
|
|
6843
7023
|
class NeatCallSiteSpanProcessor {
|
|
6844
|
-
onStart(span${spanT}) {
|
|
7024
|
+
onStart(span${spanT}, parentContext${anyT}) {
|
|
6845
7025
|
if (!span || (span.kind !== 2 && span.kind !== 3)) return
|
|
6846
|
-
|
|
7026
|
+
// Layer 1 \u2014 synchronous stack walk (sync-wrapper instrumentations).
|
|
7027
|
+
let frame = __neatPickUserFrame(new Error().stack)
|
|
7028
|
+
// Layer 2/3 \u2014 the handler-entry or off-stack-facade frame from context.
|
|
7029
|
+
if (!frame) frame = __neatFrameFromContext(parentContext)
|
|
6847
7030
|
if (!frame) return
|
|
6848
|
-
span
|
|
6849
|
-
if (typeof frame.lineno === 'number') span.setAttribute('code.lineno', frame.lineno)
|
|
6850
|
-
if (frame.function) span.setAttribute('code.function', frame.function)
|
|
7031
|
+
__neatSetCodeAttrs(span, frame)
|
|
6851
7032
|
}
|
|
6852
7033
|
onEnd() {}
|
|
6853
7034
|
forceFlush() { return Promise.resolve() }
|
|
6854
7035
|
shutdown() { return Promise.resolve() }
|
|
7036
|
+
}
|
|
7037
|
+
|
|
7038
|
+
// Capture the caller's synchronous frame at the wrap point and run \`fn\` with
|
|
7039
|
+
// that frame pushed into the active context (file-awareness.md \xA74 layer 3). An
|
|
7040
|
+
// off-stack instrumentation that creates its span inside \`fn\` inherits the
|
|
7041
|
+
// frame; the processor reads it via __neatFrameFromContext.
|
|
7042
|
+
function __neatRunWithUserFrame(fn${fnT}) {
|
|
7043
|
+
const frame = __neatPickUserFrame(new Error().stack)
|
|
7044
|
+
if (!frame) return fn()
|
|
7045
|
+
return context.with(context.active().setValue(NEAT_USER_FRAME, frame), fn)
|
|
7046
|
+
}
|
|
7047
|
+
|
|
7048
|
+
// Handler-entry attribution (file-awareness.md \xA74 layer 2). Stamp the framework
|
|
7049
|
+
// SERVER span with the handler frame captured at route registration, and push
|
|
7050
|
+
// the same frame into context so downstream CLIENT/PRODUCER spans inherit the
|
|
7051
|
+
// handler-file floor when their own stack carries none.
|
|
7052
|
+
function __neatStampHandler(frame${anyT}, run${fnT}) {
|
|
7053
|
+
try {
|
|
7054
|
+
const active = trace.getActiveSpan()
|
|
7055
|
+
if (active && active.kind === 1 && typeof active.setAttribute === 'function') {
|
|
7056
|
+
__neatSetCodeAttrs(active, frame)
|
|
7057
|
+
}
|
|
7058
|
+
} catch (_e) {}
|
|
7059
|
+
try {
|
|
7060
|
+
return context.with(context.active().setValue(NEAT_USER_FRAME, frame), run)
|
|
7061
|
+
} catch (_e) {
|
|
7062
|
+
return run()
|
|
7063
|
+
}
|
|
7064
|
+
}
|
|
7065
|
+
|
|
7066
|
+
// require-in-the-middle is a transitive dependency of the OTel instrumentation
|
|
7067
|
+
// packages NEAT installs, so it resolves in any instrumented CJS service.
|
|
7068
|
+
// Guarded: when it's absent (or the host runs as pure ESM, where require isn't
|
|
7069
|
+
// defined) the off-stack/handler wraps degrade to the stack walk + context
|
|
7070
|
+
// floor, never to a crash.
|
|
7071
|
+
function __neatHook(modules${arrAny}, onload${fnT}) {
|
|
7072
|
+
try {
|
|
7073
|
+
const RITM = require('require-in-the-middle')
|
|
7074
|
+
const Hook = RITM && RITM.Hook ? RITM.Hook : RITM
|
|
7075
|
+
new Hook(modules, { internals: false }, onload)
|
|
7076
|
+
return true
|
|
7077
|
+
} catch (_e) {
|
|
7078
|
+
return false
|
|
7079
|
+
}
|
|
7080
|
+
}
|
|
7081
|
+
|
|
7082
|
+
// Off-stack facade: Node's built-in fetch / undici. The instrumentation creates
|
|
7083
|
+
// the CLIENT span inside a diagnostics_channel handler detached from the
|
|
7084
|
+
// caller's stack, so wrap the global so the user frame is in context when the
|
|
7085
|
+
// span is created. The capture spike (2026-05-28) validated this on real
|
|
7086
|
+
// undici. .name is restored to 'fetch' for ecosystem compatibility; the inner
|
|
7087
|
+
// __neat name is what the frame skip keys on.
|
|
7088
|
+
function __neatWrapFetch() {
|
|
7089
|
+
try {
|
|
7090
|
+
const g${anyT} = globalThis
|
|
7091
|
+
if (typeof g.fetch === 'function' && !g.fetch.__neatWrapped) {
|
|
7092
|
+
const realFetch = g.fetch
|
|
7093
|
+
// The wrapper keeps its __neat-prefixed name so the frame skip in
|
|
7094
|
+
// __neatPickUserFrame never mistakes the wrapper's own frame for the
|
|
7095
|
+
// user's call site (renaming it to 'fetch' would defeat the skip).
|
|
7096
|
+
const __neatFetch${anyT} = function (input${anyT}, init${anyT}) {
|
|
7097
|
+
return __neatRunWithUserFrame(function () { return realFetch(input, init) })
|
|
7098
|
+
}
|
|
7099
|
+
__neatFetch.__neatWrapped = true
|
|
7100
|
+
g.fetch = __neatFetch
|
|
7101
|
+
}
|
|
7102
|
+
} catch (_e) {}
|
|
7103
|
+
}
|
|
7104
|
+
|
|
7105
|
+
// Off-stack facade: @prisma/client. Prisma's query engine backdates its spans
|
|
7106
|
+
// from Rust, off the caller's stack. Wrap the model methods on the client
|
|
7107
|
+
// prototype so the call-site frame (still synchronous at \`prisma.user.find\`)
|
|
7108
|
+
// is pushed into context for the engine dispatch.
|
|
7109
|
+
function __neatWrapPrisma() {
|
|
7110
|
+
__neatHook(['@prisma/client'], function (exports${anyT}) {
|
|
7111
|
+
try {
|
|
7112
|
+
const Client = exports && exports.PrismaClient
|
|
7113
|
+
if (typeof Client === 'function' && !Client.__neatWrapped) {
|
|
7114
|
+
const ops = ['findUnique','findUniqueOrThrow','findFirst','findFirstOrThrow','findMany','create','createMany','update','updateMany','upsert','delete','deleteMany','count','aggregate','groupBy']
|
|
7115
|
+
const __neatPrismaWrapModel = function (model${anyT}) {
|
|
7116
|
+
if (!model || model.__neatWrapped) return model
|
|
7117
|
+
for (let i = 0; i < ops.length; i++) {
|
|
7118
|
+
const op = ops[i]
|
|
7119
|
+
const orig = model[op]
|
|
7120
|
+
if (typeof orig !== 'function') continue
|
|
7121
|
+
model[op] = function __neatPrismaOp(${ts ? "...args: any[]" : "...args"}) {
|
|
7122
|
+
const self = this
|
|
7123
|
+
return __neatRunWithUserFrame(function () { return orig.apply(self, args) })
|
|
7124
|
+
}
|
|
7125
|
+
}
|
|
7126
|
+
model.__neatWrapped = true
|
|
7127
|
+
return model
|
|
7128
|
+
}
|
|
7129
|
+
const proto = Client.prototype
|
|
7130
|
+
const handler = function (model${anyT}) { return __neatPrismaWrapModel(model) }
|
|
7131
|
+
// Model accessors are lazily created getters on the instance; wrap the
|
|
7132
|
+
// \`$extends\`-free common path by trapping property access via a proxy
|
|
7133
|
+
// on each constructed client.
|
|
7134
|
+
exports.PrismaClient = new Proxy(Client, {
|
|
7135
|
+
construct(Target${anyT}, argList${anyT}, NewTarget${anyT}) {
|
|
7136
|
+
const instance = Reflect.construct(Target, argList, NewTarget)
|
|
7137
|
+
return new Proxy(instance, {
|
|
7138
|
+
get(target${anyT}, prop${anyT}, receiver${anyT}) {
|
|
7139
|
+
const value = Reflect.get(target, prop, receiver)
|
|
7140
|
+
if (value && typeof value === 'object' && typeof prop === 'string' && prop[0] !== '$' && prop[0] !== '_') {
|
|
7141
|
+
return handler(value)
|
|
7142
|
+
}
|
|
7143
|
+
return value
|
|
7144
|
+
},
|
|
7145
|
+
})
|
|
7146
|
+
},
|
|
7147
|
+
})
|
|
7148
|
+
exports.PrismaClient.__neatWrapped = true
|
|
7149
|
+
void proto
|
|
7150
|
+
}
|
|
7151
|
+
} catch (_e) {}
|
|
7152
|
+
return exports
|
|
7153
|
+
})
|
|
7154
|
+
}
|
|
7155
|
+
|
|
7156
|
+
// Handler-entry facades. express / connect share the Layer model (each route
|
|
7157
|
+
// handler is a function registered on a Router); the wrap captures the
|
|
7158
|
+
// registration frame and stamps + propagates it when the handler runs. The
|
|
7159
|
+
// registry is the extensibility seam \u2014 koa, fastify (via @fastify/otel),
|
|
7160
|
+
// nestjs, restify, and hapi add an entry as their patch surface is wired.
|
|
7161
|
+
function __neatWrapConnectStyle(mod${anyT}) {
|
|
7162
|
+
try {
|
|
7163
|
+
// express() returns an app whose route verbs live on Router.prototype and
|
|
7164
|
+
// the application proto; connect apps expose \`use\`. Wrap the registration
|
|
7165
|
+
// verbs so the user handler is wound with its registration frame.
|
|
7166
|
+
const verbs = ['use','get','post','put','delete','patch','all','options','head']
|
|
7167
|
+
const wrapTarget = function (target${anyT}) {
|
|
7168
|
+
if (!target || target.__neatVerbsWrapped) return
|
|
7169
|
+
for (let i = 0; i < verbs.length; i++) {
|
|
7170
|
+
const verb = verbs[i]
|
|
7171
|
+
const orig = target[verb]
|
|
7172
|
+
if (typeof orig !== 'function') continue
|
|
7173
|
+
target[verb] = function __neatVerb(${ts ? "...args: any[]" : "...args"}) {
|
|
7174
|
+
const frame = __neatPickUserFrame(new Error().stack)
|
|
7175
|
+
if (frame) {
|
|
7176
|
+
for (let a = 0; a < args.length; a++) {
|
|
7177
|
+
const h = args[a]
|
|
7178
|
+
if (typeof h === 'function' && !h.__neatHandlerWrapped && h.length <= 4) {
|
|
7179
|
+
const inner = h
|
|
7180
|
+
const __neatHandler${anyT} = function (${ts ? "...hargs: any[]" : "...hargs"}) {
|
|
7181
|
+
const self = this
|
|
7182
|
+
return __neatStampHandler(frame, function () { return inner.apply(self, hargs) })
|
|
7183
|
+
}
|
|
7184
|
+
__neatHandler.__neatHandlerWrapped = true
|
|
7185
|
+
args[a] = __neatHandler
|
|
7186
|
+
}
|
|
7187
|
+
}
|
|
7188
|
+
}
|
|
7189
|
+
return orig.apply(this, args)
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
target.__neatVerbsWrapped = true
|
|
7193
|
+
}
|
|
7194
|
+
if (mod && mod.Router && mod.Router.prototype) wrapTarget(mod.Router.prototype)
|
|
7195
|
+
if (mod && mod.application) wrapTarget(mod.application)
|
|
7196
|
+
if (mod && mod.prototype) wrapTarget(mod.prototype)
|
|
7197
|
+
} catch (_e) {}
|
|
7198
|
+
}
|
|
7199
|
+
|
|
7200
|
+
function __neatInstallHandlerEntry() {
|
|
7201
|
+
__neatHook(['express'], function (exports${anyT}) { __neatWrapConnectStyle(exports); return exports })
|
|
7202
|
+
__neatHook(['connect'], function (exports${anyT}) { __neatWrapConnectStyle(exports); return exports })
|
|
7203
|
+
}
|
|
7204
|
+
|
|
7205
|
+
// Install every off-stack and handler-entry wrap. Called once after sdk.start()
|
|
7206
|
+
// \u2014 before user code requires the framework / Prisma modules, so the hooks land
|
|
7207
|
+
// on first require. Each wrap is independently guarded.
|
|
7208
|
+
function __neatInstallFacades() {
|
|
7209
|
+
__neatWrapFetch()
|
|
7210
|
+
__neatWrapPrisma()
|
|
7211
|
+
__neatInstallHandlerEntry()
|
|
6855
7212
|
}`;
|
|
6856
7213
|
}
|
|
6857
|
-
var CALLSITE_PROCESSOR_JS =
|
|
6858
|
-
var CALLSITE_PROCESSOR_TS =
|
|
6859
|
-
function
|
|
7214
|
+
var CALLSITE_PROCESSOR_JS = neatCaptureSource(false);
|
|
7215
|
+
var CALLSITE_PROCESSOR_TS = neatCaptureSource(true);
|
|
7216
|
+
function neatWireCaptureSource(ts) {
|
|
6860
7217
|
const providerT = ts ? ": any" : "";
|
|
6861
7218
|
return `try {
|
|
6862
7219
|
const provider${providerT} = trace.getTracerProvider()
|
|
6863
7220
|
const delegate = provider && typeof provider.getDelegate === 'function' ? provider.getDelegate() : provider
|
|
6864
|
-
if (delegate
|
|
6865
|
-
|
|
6866
|
-
}
|
|
6867
|
-
|
|
6868
|
-
|
|
7221
|
+
if (!delegate || typeof delegate.addSpanProcessor !== 'function') {
|
|
7222
|
+
throw new Error('[neat] could not resolve a TracerProvider to attach the call-site processor; file-first OBSERVED capture would be silent (file-awareness.md \xA74)')
|
|
7223
|
+
}
|
|
7224
|
+
const __neatProcessor = new NeatCallSiteSpanProcessor()
|
|
7225
|
+
delegate.addSpanProcessor(__neatProcessor)
|
|
7226
|
+
// Post-init assertion: confirm attachment on providers that expose their
|
|
7227
|
+
// processor list. An un-introspectable provider is trusted (we just called
|
|
7228
|
+
// its addSpanProcessor); a list that exists and lacks our processor throws.
|
|
7229
|
+
const registered = delegate._registeredSpanProcessors
|
|
7230
|
+
if (Array.isArray(registered) && registered.indexOf(__neatProcessor) === -1) {
|
|
7231
|
+
throw new Error('[neat] call-site processor did not attach to the active TracerProvider (file-awareness.md \xA74)')
|
|
7232
|
+
}
|
|
7233
|
+
} catch (err) {
|
|
7234
|
+
throw err
|
|
7235
|
+
}
|
|
7236
|
+
try {
|
|
7237
|
+
__neatInstallFacades()
|
|
7238
|
+
} catch (_e) {
|
|
7239
|
+
// Facade install is best-effort: the stack-walk + handler-entry floor still
|
|
7240
|
+
// attribute the sync-wrapper majority even if an off-stack wrap fails.
|
|
6869
7241
|
}`;
|
|
6870
7242
|
}
|
|
6871
7243
|
var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
|
|
@@ -6876,7 +7248,7 @@ ${OTEL_OTLP_HEADERS_JS}
|
|
|
6876
7248
|
|
|
6877
7249
|
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
6878
7250
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
6879
|
-
const { trace } = require('@opentelemetry/api')
|
|
7251
|
+
const { trace, context } = require('@opentelemetry/api')
|
|
6880
7252
|
|
|
6881
7253
|
${CALLSITE_PROCESSOR_JS}
|
|
6882
7254
|
|
|
@@ -6884,7 +7256,7 @@ const instrumentations = [getNodeAutoInstrumentations()]
|
|
|
6884
7256
|
__INSTRUMENTATION_BLOCK__
|
|
6885
7257
|
const sdk = new NodeSDK({ instrumentations })
|
|
6886
7258
|
sdk.start()
|
|
6887
|
-
${
|
|
7259
|
+
${neatWireCaptureSource(false)}
|
|
6888
7260
|
`;
|
|
6889
7261
|
var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
|
|
6890
7262
|
${OTEL_INIT_STAMP}
|
|
@@ -6894,7 +7266,7 @@ ${OTEL_OTLP_HEADERS_JS}
|
|
|
6894
7266
|
|
|
6895
7267
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6896
7268
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
6897
|
-
import { trace } from '@opentelemetry/api'
|
|
7269
|
+
import { trace, context } from '@opentelemetry/api'
|
|
6898
7270
|
|
|
6899
7271
|
${CALLSITE_PROCESSOR_JS}
|
|
6900
7272
|
|
|
@@ -6902,17 +7274,22 @@ const instrumentations = [getNodeAutoInstrumentations()]
|
|
|
6902
7274
|
__INSTRUMENTATION_BLOCK__
|
|
6903
7275
|
const sdk = new NodeSDK({ instrumentations })
|
|
6904
7276
|
sdk.start()
|
|
6905
|
-
${
|
|
7277
|
+
${neatWireCaptureSource(false)}
|
|
6906
7278
|
`;
|
|
6907
7279
|
var OTEL_INIT_TS = `${OTEL_INIT_HEADER}
|
|
6908
7280
|
${OTEL_INIT_STAMP}
|
|
7281
|
+
// @ts-nocheck \u2014 generated runtime shim. The layered capture mechanism uses
|
|
7282
|
+
// dynamic patterns (facade wraps, Proxy traps, a createRequire bridge) that a
|
|
7283
|
+
// strict user tsconfig would reject; suppressing type-checking here keeps the
|
|
7284
|
+
// generated file from breaking the host project's \`tsc\` gate (#427) without
|
|
7285
|
+
// constraining the runtime logic. The file is regenerated, never hand-edited.
|
|
6909
7286
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6910
7287
|
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6911
7288
|
${OTEL_OTLP_HEADERS_JS}
|
|
6912
7289
|
|
|
6913
7290
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6914
7291
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
6915
|
-
import { trace } from '@opentelemetry/api'
|
|
7292
|
+
import { trace, context } from '@opentelemetry/api'
|
|
6916
7293
|
|
|
6917
7294
|
${CALLSITE_PROCESSOR_TS}
|
|
6918
7295
|
|
|
@@ -6920,7 +7297,7 @@ const instrumentations = [getNodeAutoInstrumentations()]
|
|
|
6920
7297
|
__INSTRUMENTATION_BLOCK__
|
|
6921
7298
|
const sdk = new NodeSDK({ instrumentations })
|
|
6922
7299
|
sdk.start()
|
|
6923
|
-
${
|
|
7300
|
+
${neatWireCaptureSource(true)}
|
|
6924
7301
|
`;
|
|
6925
7302
|
function renderNodeOtelInit(template, serviceName, projectName, registrations = []) {
|
|
6926
7303
|
const block = registrations.length === 0 ? "" : `
|