@neat.is/core 0.6.1 → 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.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  resolveNeatVersion,
13
13
  validateConnectorEntry,
14
14
  writeDaemonRecord
15
- } from "./chunk-3YUNROLT.js";
15
+ } from "./chunk-64JJOXES.js";
16
16
  import {
17
17
  buildSearchIndex
18
18
  } from "./chunk-BIY46Q6U.js";
@@ -71,10 +71,10 @@ import {
71
71
  startPersistLoop,
72
72
  startStalenessLoop,
73
73
  upsertConnectorEntry
74
- } from "./chunk-7C6ZULNU.js";
74
+ } from "./chunk-ZZ3VUCWL.js";
75
75
  import {
76
76
  startOtelGrpcReceiver
77
- } from "./chunk-MVINCLQM.js";
77
+ } from "./chunk-OY7FGJAX.js";
78
78
  import {
79
79
  __dirname,
80
80
  __require,
@@ -82,7 +82,7 @@ import {
82
82
  buildOtelReceiver,
83
83
  listenSteppingOtlp,
84
84
  readAuthEnv
85
- } from "./chunk-BZ3AJVAC.js";
85
+ } from "./chunk-2MAGU6RB.js";
86
86
 
87
87
  // src/cli.ts
88
88
  import path10 from "path";
@@ -2609,6 +2609,132 @@ var OTEL_ENV2 = {
2609
2609
  key: "OTEL_EXPORTER_OTLP_ENDPOINT",
2610
2610
  value: "http://localhost:4318"
2611
2611
  };
2612
+ var NEAT_OTEL_FILENAME = "neat_otel.py";
2613
+ var NEAT_OTEL_STAMP = "neat-otel-init v1";
2614
+ var NEAT_IMPORT_LINE = "import neat_otel # neat: call-site file attribution";
2615
+ function neatOtelPy() {
2616
+ return `# ${NEAT_OTEL_STAMP} \u2014 generated by NEAT. Safe to re-generate; do not edit.
2617
+ # Call-site file attribution (docs/contracts/file-awareness.md section 4). Stamps
2618
+ # code.file.path / code.line.number / code.function.name on spans so NEAT fuses
2619
+ # runtime spans onto your source files. Import it once at your entry point.
2620
+ import os
2621
+ import sys
2622
+
2623
+ try:
2624
+ from opentelemetry import trace as _neat_trace
2625
+ from opentelemetry.sdk.trace import SpanProcessor as _NeatSpanProcessor
2626
+ from opentelemetry.trace import SpanKind as _NeatSpanKind
2627
+
2628
+ _NEAT_OTEL = True
2629
+ except Exception:
2630
+ _NEAT_OTEL = False
2631
+
2632
+ if _NEAT_OTEL:
2633
+ _NEAT_SELF = os.path.abspath(__file__)
2634
+ _NEAT_PREFIXES = tuple(
2635
+ os.path.abspath(p) + os.sep
2636
+ for p in {sys.prefix, sys.base_prefix, sys.exec_prefix, sys.base_exec_prefix}
2637
+ )
2638
+ _NEAT_KINDS = {_NeatSpanKind.CLIENT, _NeatSpanKind.PRODUCER, _NeatSpanKind.SERVER}
2639
+
2640
+ def _neat_is_user_frame(filename):
2641
+ if not filename:
2642
+ return False
2643
+ if filename.startswith("<") and filename.endswith(">"):
2644
+ return False
2645
+ abs_name = os.path.abspath(filename)
2646
+ if abs_name == _NEAT_SELF:
2647
+ return False
2648
+ parts = abs_name.split(os.sep)
2649
+ if "opentelemetry" in parts or "site-packages" in parts:
2650
+ return False
2651
+ for pref in _NEAT_PREFIXES:
2652
+ if abs_name.startswith(pref):
2653
+ return False
2654
+ return True
2655
+
2656
+ class NeatCallSiteSpanProcessor(_NeatSpanProcessor):
2657
+ def on_start(self, span, parent_context=None):
2658
+ try:
2659
+ if span.kind not in _NEAT_KINDS:
2660
+ return
2661
+ frame = sys._getframe(1)
2662
+ while frame is not None:
2663
+ if _neat_is_user_frame(frame.f_code.co_filename):
2664
+ span.set_attribute("code.file.path", os.path.abspath(frame.f_code.co_filename))
2665
+ span.set_attribute("code.line.number", frame.f_lineno)
2666
+ span.set_attribute("code.function.name", frame.f_code.co_name)
2667
+ return
2668
+ frame = frame.f_back
2669
+ except Exception:
2670
+ pass # never break the host application
2671
+
2672
+ def on_end(self, span):
2673
+ pass
2674
+
2675
+ def shutdown(self):
2676
+ pass
2677
+
2678
+ def force_flush(self, timeout_millis=30000):
2679
+ return True
2680
+
2681
+ def _neat_register():
2682
+ try:
2683
+ provider = _neat_trace.get_tracer_provider()
2684
+ add = getattr(provider, "add_span_processor", None)
2685
+ if callable(add):
2686
+ add(NeatCallSiteSpanProcessor())
2687
+ except Exception:
2688
+ pass
2689
+
2690
+ if os.environ.get("NEAT_CALLSITE_DISABLED", "") != "1":
2691
+ _neat_register()
2692
+ `;
2693
+ }
2694
+ async function writeFileAtomic(file, contents) {
2695
+ const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
2696
+ await fs5.writeFile(tmp, contents, "utf8");
2697
+ await fs5.rename(tmp, file);
2698
+ }
2699
+ async function resolvePyEntrypoint(serviceDir) {
2700
+ const procfile = path6.join(serviceDir, "Procfile");
2701
+ if (await exists2(procfile)) {
2702
+ const raw = await fs5.readFile(procfile, "utf8");
2703
+ for (const line of raw.split(/\r?\n/)) {
2704
+ const m = line.match(/^[a-zA-Z0-9_-]+:\s*(.+)$/);
2705
+ if (!m) continue;
2706
+ const cmd = m[1];
2707
+ const asgi = cmd.match(/\b(?:uvicorn|gunicorn|hypercorn|daphne)\s+([\w.]+):/);
2708
+ if (asgi) {
2709
+ const modPath = asgi[1].replace(/\./g, "/");
2710
+ const asFile = path6.join(serviceDir, `${modPath}.py`);
2711
+ if (await exists2(asFile)) return asFile;
2712
+ const asPkg = path6.join(serviceDir, modPath, "__init__.py");
2713
+ if (await exists2(asPkg)) return asPkg;
2714
+ }
2715
+ const runFile = cmd.match(/\b(?:python3?|fastapi\s+(?:run|dev))\s+([\w./-]+\.py)\b/);
2716
+ if (runFile) {
2717
+ const p = path6.join(serviceDir, runFile[1]);
2718
+ if (await exists2(p)) return p;
2719
+ }
2720
+ }
2721
+ }
2722
+ for (const name of ["main.py", "app.py", "asgi.py", "wsgi.py", "manage.py", "server.py"]) {
2723
+ const p = path6.join(serviceDir, name);
2724
+ if (await exists2(p)) return p;
2725
+ }
2726
+ return null;
2727
+ }
2728
+ function injectNeatImport(source) {
2729
+ if (/^\s*import\s+neat_otel\b/m.test(source)) return null;
2730
+ const lines = source.split("\n");
2731
+ let insertAt = lines[0]?.startsWith("#!") ? 1 : 0;
2732
+ for (let i = 0; i < Math.min(lines.length, 15); i++) {
2733
+ if (/^\s*from\s+__future__\s+import\b/.test(lines[i] ?? "")) insertAt = i + 1;
2734
+ }
2735
+ lines.splice(insertAt, 0, NEAT_IMPORT_LINE);
2736
+ return lines.join("\n");
2737
+ }
2612
2738
  async function exists2(p) {
2613
2739
  try {
2614
2740
  await fs5.stat(p);
@@ -2677,7 +2803,9 @@ async function plan2(serviceDir) {
2677
2803
  }
2678
2804
  }
2679
2805
  const entrypointEdits = await planProcfileEdits(serviceDir);
2680
- if (dependencyEdits.length === 0 && entrypointEdits.length === 0) {
2806
+ const entryFile = await resolvePyEntrypoint(serviceDir);
2807
+ const generatedFiles = entryFile ? [{ file: path6.join(serviceDir, NEAT_OTEL_FILENAME), contents: neatOtelPy() }] : [];
2808
+ if (dependencyEdits.length === 0 && entrypointEdits.length === 0 && !entryFile) {
2681
2809
  return empty;
2682
2810
  }
2683
2811
  return {
@@ -2685,7 +2813,9 @@ async function plan2(serviceDir) {
2685
2813
  serviceDir,
2686
2814
  dependencyEdits,
2687
2815
  entrypointEdits,
2688
- envEdits: [OTEL_ENV2]
2816
+ envEdits: [OTEL_ENV2],
2817
+ ...generatedFiles.length > 0 ? { generatedFiles } : {},
2818
+ ...entryFile ? { entryFile } : {}
2689
2819
  };
2690
2820
  }
2691
2821
  async function applyRequirementsTxt(manifest, edits, original) {
@@ -2708,11 +2838,13 @@ async function applyProcfile(procfile, edits, original) {
2708
2838
  await fs5.rename(tmp, procfile);
2709
2839
  }
2710
2840
  async function apply2(installPlan) {
2711
- const { serviceDir } = installPlan;
2841
+ const { serviceDir, entryFile } = installPlan;
2842
+ const generatedFiles = installPlan.generatedFiles ?? [];
2712
2843
  const touched = /* @__PURE__ */ new Set();
2713
2844
  for (const e of installPlan.dependencyEdits) touched.add(e.file);
2714
2845
  for (const e of installPlan.entrypointEdits) touched.add(e.file);
2715
- if (touched.size === 0) {
2846
+ if (entryFile) touched.add(entryFile);
2847
+ if (touched.size === 0 && generatedFiles.length === 0) {
2716
2848
  return { serviceDir, outcome: "already-instrumented", writtenFiles: [] };
2717
2849
  }
2718
2850
  const originals = /* @__PURE__ */ new Map();
@@ -2723,7 +2855,18 @@ async function apply2(installPlan) {
2723
2855
  }
2724
2856
  }
2725
2857
  const writtenFiles = [];
2858
+ const createdFiles = [];
2726
2859
  try {
2860
+ for (const gf of generatedFiles) {
2861
+ const already = await exists2(gf.file);
2862
+ if (already) {
2863
+ const cur = await fs5.readFile(gf.file, "utf8").catch(() => null);
2864
+ if (cur === gf.contents) continue;
2865
+ }
2866
+ await writeFileAtomic(gf.file, gf.contents);
2867
+ writtenFiles.push(gf.file);
2868
+ if (!already) createdFiles.push(gf.file);
2869
+ }
2727
2870
  for (const file of touched) {
2728
2871
  const raw = originals.get(file);
2729
2872
  if (raw === void 0) {
@@ -2742,15 +2885,22 @@ async function apply2(installPlan) {
2742
2885
  await applyProcfile(file, edits, raw);
2743
2886
  writtenFiles.push(file);
2744
2887
  }
2888
+ } else if (file === entryFile) {
2889
+ const injected = injectNeatImport(raw);
2890
+ if (injected !== null) {
2891
+ await writeFileAtomic(file, injected);
2892
+ writtenFiles.push(file);
2893
+ }
2745
2894
  }
2746
2895
  }
2747
2896
  } catch (err) {
2748
- await rollback2(installPlan, originals);
2897
+ await rollback2(installPlan, originals, createdFiles);
2749
2898
  throw err;
2750
2899
  }
2751
- return { serviceDir, outcome: "instrumented", writtenFiles };
2900
+ const outcome = writtenFiles.length > 0 ? "instrumented" : "already-instrumented";
2901
+ return { serviceDir, outcome, writtenFiles };
2752
2902
  }
2753
- async function rollback2(installPlan, originals) {
2903
+ async function rollback2(installPlan, originals, createdFiles = []) {
2754
2904
  const restored = [];
2755
2905
  for (const [file, raw] of originals.entries()) {
2756
2906
  try {
@@ -2759,6 +2909,14 @@ async function rollback2(installPlan, originals) {
2759
2909
  } catch {
2760
2910
  }
2761
2911
  }
2912
+ const removed = [];
2913
+ for (const file of createdFiles) {
2914
+ try {
2915
+ await fs5.rm(file, { force: true });
2916
+ removed.push(file);
2917
+ } catch {
2918
+ }
2919
+ }
2762
2920
  const lines = [
2763
2921
  "# neat-rollback.patch",
2764
2922
  "",
@@ -2766,6 +2924,7 @@ async function rollback2(installPlan, originals) {
2766
2924
  "# Files listed below were restored to their pre-apply contents.",
2767
2925
  "",
2768
2926
  ...restored.map((f) => `restored: ${f}`),
2927
+ ...removed.map((f) => `removed: ${f}`),
2769
2928
  ""
2770
2929
  ];
2771
2930
  const rollbackPath = path6.join(installPlan.serviceDir, "neat-rollback.patch");