@neat.is/core 0.6.2-dev.20260722 → 0.6.2-dev.20260723
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +166 -7
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +166 -7
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.cjs
CHANGED
|
@@ -15271,6 +15271,132 @@ var OTEL_ENV2 = {
|
|
|
15271
15271
|
key: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
15272
15272
|
value: "http://localhost:4318"
|
|
15273
15273
|
};
|
|
15274
|
+
var NEAT_OTEL_FILENAME = "neat_otel.py";
|
|
15275
|
+
var NEAT_OTEL_STAMP = "neat-otel-init v1";
|
|
15276
|
+
var NEAT_IMPORT_LINE = "import neat_otel # neat: call-site file attribution";
|
|
15277
|
+
function neatOtelPy() {
|
|
15278
|
+
return `# ${NEAT_OTEL_STAMP} \u2014 generated by NEAT. Safe to re-generate; do not edit.
|
|
15279
|
+
# Call-site file attribution (docs/contracts/file-awareness.md section 4). Stamps
|
|
15280
|
+
# code.file.path / code.line.number / code.function.name on spans so NEAT fuses
|
|
15281
|
+
# runtime spans onto your source files. Import it once at your entry point.
|
|
15282
|
+
import os
|
|
15283
|
+
import sys
|
|
15284
|
+
|
|
15285
|
+
try:
|
|
15286
|
+
from opentelemetry import trace as _neat_trace
|
|
15287
|
+
from opentelemetry.sdk.trace import SpanProcessor as _NeatSpanProcessor
|
|
15288
|
+
from opentelemetry.trace import SpanKind as _NeatSpanKind
|
|
15289
|
+
|
|
15290
|
+
_NEAT_OTEL = True
|
|
15291
|
+
except Exception:
|
|
15292
|
+
_NEAT_OTEL = False
|
|
15293
|
+
|
|
15294
|
+
if _NEAT_OTEL:
|
|
15295
|
+
_NEAT_SELF = os.path.abspath(__file__)
|
|
15296
|
+
_NEAT_PREFIXES = tuple(
|
|
15297
|
+
os.path.abspath(p) + os.sep
|
|
15298
|
+
for p in {sys.prefix, sys.base_prefix, sys.exec_prefix, sys.base_exec_prefix}
|
|
15299
|
+
)
|
|
15300
|
+
_NEAT_KINDS = {_NeatSpanKind.CLIENT, _NeatSpanKind.PRODUCER, _NeatSpanKind.SERVER}
|
|
15301
|
+
|
|
15302
|
+
def _neat_is_user_frame(filename):
|
|
15303
|
+
if not filename:
|
|
15304
|
+
return False
|
|
15305
|
+
if filename.startswith("<") and filename.endswith(">"):
|
|
15306
|
+
return False
|
|
15307
|
+
abs_name = os.path.abspath(filename)
|
|
15308
|
+
if abs_name == _NEAT_SELF:
|
|
15309
|
+
return False
|
|
15310
|
+
parts = abs_name.split(os.sep)
|
|
15311
|
+
if "opentelemetry" in parts or "site-packages" in parts:
|
|
15312
|
+
return False
|
|
15313
|
+
for pref in _NEAT_PREFIXES:
|
|
15314
|
+
if abs_name.startswith(pref):
|
|
15315
|
+
return False
|
|
15316
|
+
return True
|
|
15317
|
+
|
|
15318
|
+
class NeatCallSiteSpanProcessor(_NeatSpanProcessor):
|
|
15319
|
+
def on_start(self, span, parent_context=None):
|
|
15320
|
+
try:
|
|
15321
|
+
if span.kind not in _NEAT_KINDS:
|
|
15322
|
+
return
|
|
15323
|
+
frame = sys._getframe(1)
|
|
15324
|
+
while frame is not None:
|
|
15325
|
+
if _neat_is_user_frame(frame.f_code.co_filename):
|
|
15326
|
+
span.set_attribute("code.file.path", os.path.abspath(frame.f_code.co_filename))
|
|
15327
|
+
span.set_attribute("code.line.number", frame.f_lineno)
|
|
15328
|
+
span.set_attribute("code.function.name", frame.f_code.co_name)
|
|
15329
|
+
return
|
|
15330
|
+
frame = frame.f_back
|
|
15331
|
+
except Exception:
|
|
15332
|
+
pass # never break the host application
|
|
15333
|
+
|
|
15334
|
+
def on_end(self, span):
|
|
15335
|
+
pass
|
|
15336
|
+
|
|
15337
|
+
def shutdown(self):
|
|
15338
|
+
pass
|
|
15339
|
+
|
|
15340
|
+
def force_flush(self, timeout_millis=30000):
|
|
15341
|
+
return True
|
|
15342
|
+
|
|
15343
|
+
def _neat_register():
|
|
15344
|
+
try:
|
|
15345
|
+
provider = _neat_trace.get_tracer_provider()
|
|
15346
|
+
add = getattr(provider, "add_span_processor", None)
|
|
15347
|
+
if callable(add):
|
|
15348
|
+
add(NeatCallSiteSpanProcessor())
|
|
15349
|
+
except Exception:
|
|
15350
|
+
pass
|
|
15351
|
+
|
|
15352
|
+
if os.environ.get("NEAT_CALLSITE_DISABLED", "") != "1":
|
|
15353
|
+
_neat_register()
|
|
15354
|
+
`;
|
|
15355
|
+
}
|
|
15356
|
+
async function writeFileAtomic(file, contents) {
|
|
15357
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
15358
|
+
await import_node_fs38.promises.writeFile(tmp, contents, "utf8");
|
|
15359
|
+
await import_node_fs38.promises.rename(tmp, file);
|
|
15360
|
+
}
|
|
15361
|
+
async function resolvePyEntrypoint(serviceDir) {
|
|
15362
|
+
const procfile = import_node_path59.default.join(serviceDir, "Procfile");
|
|
15363
|
+
if (await exists4(procfile)) {
|
|
15364
|
+
const raw = await import_node_fs38.promises.readFile(procfile, "utf8");
|
|
15365
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
15366
|
+
const m = line.match(/^[a-zA-Z0-9_-]+:\s*(.+)$/);
|
|
15367
|
+
if (!m) continue;
|
|
15368
|
+
const cmd = m[1];
|
|
15369
|
+
const asgi = cmd.match(/\b(?:uvicorn|gunicorn|hypercorn|daphne)\s+([\w.]+):/);
|
|
15370
|
+
if (asgi) {
|
|
15371
|
+
const modPath = asgi[1].replace(/\./g, "/");
|
|
15372
|
+
const asFile = import_node_path59.default.join(serviceDir, `${modPath}.py`);
|
|
15373
|
+
if (await exists4(asFile)) return asFile;
|
|
15374
|
+
const asPkg = import_node_path59.default.join(serviceDir, modPath, "__init__.py");
|
|
15375
|
+
if (await exists4(asPkg)) return asPkg;
|
|
15376
|
+
}
|
|
15377
|
+
const runFile = cmd.match(/\b(?:python3?|fastapi\s+(?:run|dev))\s+([\w./-]+\.py)\b/);
|
|
15378
|
+
if (runFile) {
|
|
15379
|
+
const p = import_node_path59.default.join(serviceDir, runFile[1]);
|
|
15380
|
+
if (await exists4(p)) return p;
|
|
15381
|
+
}
|
|
15382
|
+
}
|
|
15383
|
+
}
|
|
15384
|
+
for (const name of ["main.py", "app.py", "asgi.py", "wsgi.py", "manage.py", "server.py"]) {
|
|
15385
|
+
const p = import_node_path59.default.join(serviceDir, name);
|
|
15386
|
+
if (await exists4(p)) return p;
|
|
15387
|
+
}
|
|
15388
|
+
return null;
|
|
15389
|
+
}
|
|
15390
|
+
function injectNeatImport(source) {
|
|
15391
|
+
if (/^\s*import\s+neat_otel\b/m.test(source)) return null;
|
|
15392
|
+
const lines = source.split("\n");
|
|
15393
|
+
let insertAt = lines[0]?.startsWith("#!") ? 1 : 0;
|
|
15394
|
+
for (let i = 0; i < Math.min(lines.length, 15); i++) {
|
|
15395
|
+
if (/^\s*from\s+__future__\s+import\b/.test(lines[i] ?? "")) insertAt = i + 1;
|
|
15396
|
+
}
|
|
15397
|
+
lines.splice(insertAt, 0, NEAT_IMPORT_LINE);
|
|
15398
|
+
return lines.join("\n");
|
|
15399
|
+
}
|
|
15274
15400
|
async function exists4(p) {
|
|
15275
15401
|
try {
|
|
15276
15402
|
await import_node_fs38.promises.stat(p);
|
|
@@ -15339,7 +15465,9 @@ async function plan2(serviceDir) {
|
|
|
15339
15465
|
}
|
|
15340
15466
|
}
|
|
15341
15467
|
const entrypointEdits = await planProcfileEdits(serviceDir);
|
|
15342
|
-
|
|
15468
|
+
const entryFile = await resolvePyEntrypoint(serviceDir);
|
|
15469
|
+
const generatedFiles = entryFile ? [{ file: import_node_path59.default.join(serviceDir, NEAT_OTEL_FILENAME), contents: neatOtelPy() }] : [];
|
|
15470
|
+
if (dependencyEdits.length === 0 && entrypointEdits.length === 0 && !entryFile) {
|
|
15343
15471
|
return empty;
|
|
15344
15472
|
}
|
|
15345
15473
|
return {
|
|
@@ -15347,7 +15475,9 @@ async function plan2(serviceDir) {
|
|
|
15347
15475
|
serviceDir,
|
|
15348
15476
|
dependencyEdits,
|
|
15349
15477
|
entrypointEdits,
|
|
15350
|
-
envEdits: [OTEL_ENV2]
|
|
15478
|
+
envEdits: [OTEL_ENV2],
|
|
15479
|
+
...generatedFiles.length > 0 ? { generatedFiles } : {},
|
|
15480
|
+
...entryFile ? { entryFile } : {}
|
|
15351
15481
|
};
|
|
15352
15482
|
}
|
|
15353
15483
|
async function applyRequirementsTxt(manifest, edits, original) {
|
|
@@ -15370,11 +15500,13 @@ async function applyProcfile(procfile, edits, original) {
|
|
|
15370
15500
|
await import_node_fs38.promises.rename(tmp, procfile);
|
|
15371
15501
|
}
|
|
15372
15502
|
async function apply2(installPlan) {
|
|
15373
|
-
const { serviceDir } = installPlan;
|
|
15503
|
+
const { serviceDir, entryFile } = installPlan;
|
|
15504
|
+
const generatedFiles = installPlan.generatedFiles ?? [];
|
|
15374
15505
|
const touched = /* @__PURE__ */ new Set();
|
|
15375
15506
|
for (const e of installPlan.dependencyEdits) touched.add(e.file);
|
|
15376
15507
|
for (const e of installPlan.entrypointEdits) touched.add(e.file);
|
|
15377
|
-
if (touched.
|
|
15508
|
+
if (entryFile) touched.add(entryFile);
|
|
15509
|
+
if (touched.size === 0 && generatedFiles.length === 0) {
|
|
15378
15510
|
return { serviceDir, outcome: "already-instrumented", writtenFiles: [] };
|
|
15379
15511
|
}
|
|
15380
15512
|
const originals = /* @__PURE__ */ new Map();
|
|
@@ -15385,7 +15517,18 @@ async function apply2(installPlan) {
|
|
|
15385
15517
|
}
|
|
15386
15518
|
}
|
|
15387
15519
|
const writtenFiles = [];
|
|
15520
|
+
const createdFiles = [];
|
|
15388
15521
|
try {
|
|
15522
|
+
for (const gf of generatedFiles) {
|
|
15523
|
+
const already = await exists4(gf.file);
|
|
15524
|
+
if (already) {
|
|
15525
|
+
const cur = await import_node_fs38.promises.readFile(gf.file, "utf8").catch(() => null);
|
|
15526
|
+
if (cur === gf.contents) continue;
|
|
15527
|
+
}
|
|
15528
|
+
await writeFileAtomic(gf.file, gf.contents);
|
|
15529
|
+
writtenFiles.push(gf.file);
|
|
15530
|
+
if (!already) createdFiles.push(gf.file);
|
|
15531
|
+
}
|
|
15389
15532
|
for (const file of touched) {
|
|
15390
15533
|
const raw = originals.get(file);
|
|
15391
15534
|
if (raw === void 0) {
|
|
@@ -15404,15 +15547,22 @@ async function apply2(installPlan) {
|
|
|
15404
15547
|
await applyProcfile(file, edits, raw);
|
|
15405
15548
|
writtenFiles.push(file);
|
|
15406
15549
|
}
|
|
15550
|
+
} else if (file === entryFile) {
|
|
15551
|
+
const injected = injectNeatImport(raw);
|
|
15552
|
+
if (injected !== null) {
|
|
15553
|
+
await writeFileAtomic(file, injected);
|
|
15554
|
+
writtenFiles.push(file);
|
|
15555
|
+
}
|
|
15407
15556
|
}
|
|
15408
15557
|
}
|
|
15409
15558
|
} catch (err) {
|
|
15410
|
-
await rollback2(installPlan, originals);
|
|
15559
|
+
await rollback2(installPlan, originals, createdFiles);
|
|
15411
15560
|
throw err;
|
|
15412
15561
|
}
|
|
15413
|
-
|
|
15562
|
+
const outcome = writtenFiles.length > 0 ? "instrumented" : "already-instrumented";
|
|
15563
|
+
return { serviceDir, outcome, writtenFiles };
|
|
15414
15564
|
}
|
|
15415
|
-
async function rollback2(installPlan, originals) {
|
|
15565
|
+
async function rollback2(installPlan, originals, createdFiles = []) {
|
|
15416
15566
|
const restored = [];
|
|
15417
15567
|
for (const [file, raw] of originals.entries()) {
|
|
15418
15568
|
try {
|
|
@@ -15421,6 +15571,14 @@ async function rollback2(installPlan, originals) {
|
|
|
15421
15571
|
} catch {
|
|
15422
15572
|
}
|
|
15423
15573
|
}
|
|
15574
|
+
const removed = [];
|
|
15575
|
+
for (const file of createdFiles) {
|
|
15576
|
+
try {
|
|
15577
|
+
await import_node_fs38.promises.rm(file, { force: true });
|
|
15578
|
+
removed.push(file);
|
|
15579
|
+
} catch {
|
|
15580
|
+
}
|
|
15581
|
+
}
|
|
15424
15582
|
const lines = [
|
|
15425
15583
|
"# neat-rollback.patch",
|
|
15426
15584
|
"",
|
|
@@ -15428,6 +15586,7 @@ async function rollback2(installPlan, originals) {
|
|
|
15428
15586
|
"# Files listed below were restored to their pre-apply contents.",
|
|
15429
15587
|
"",
|
|
15430
15588
|
...restored.map((f) => `restored: ${f}`),
|
|
15589
|
+
...removed.map((f) => `removed: ${f}`),
|
|
15431
15590
|
""
|
|
15432
15591
|
];
|
|
15433
15592
|
const rollbackPath = import_node_path59.default.join(installPlan.serviceDir, "neat-rollback.patch");
|