@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 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
- if (dependencyEdits.length === 0 && entrypointEdits.length === 0) {
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.size === 0) {
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
- return { serviceDir, outcome: "instrumented", writtenFiles };
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");