@neat.is/core 0.4.6 → 0.4.7

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
@@ -41,7 +41,7 @@ import {
41
41
  setStatus,
42
42
  startPersistLoop,
43
43
  startStalenessLoop
44
- } from "./chunk-RBWL4HRB.js";
44
+ } from "./chunk-7FTK47JQ.js";
45
45
  import {
46
46
  startOtelGrpcReceiver
47
47
  } from "./chunk-3QCRUEQD.js";
@@ -693,38 +693,119 @@ import path4 from "path";
693
693
 
694
694
  // src/installers/templates.ts
695
695
  var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
696
+ var OTEL_INIT_STAMP = "// neat-template-version: 2 \u2014 file-first call-site capture (ADR-089).";
697
+ function neatCallsiteProcessorSource(ts) {
698
+ const stackT = ts ? ": string" : "";
699
+ const spanT = ts ? ": any" : "";
700
+ const strOpt = ts ? ": string | undefined" : "";
701
+ return `function __neatPickUserFrame(stack${stackT}) {
702
+ const lines = String(stack || '').split('\\n')
703
+ for (let i = 0; i < lines.length; i++) {
704
+ const raw = lines[i].trim()
705
+ if (raw.indexOf('at ') !== 0) continue
706
+ if (raw.indexOf('node_modules') !== -1) continue
707
+ if (raw.indexOf('@opentelemetry') !== -1) continue
708
+ if (raw.indexOf('node:') !== -1) continue
709
+ if (raw.indexOf('NeatCallSiteSpanProcessor') !== -1) continue
710
+ const bodyText = raw.slice(3)
711
+ const loc = bodyText.match(/:(\\d+):(\\d+)\\)?$/)
712
+ if (!loc) continue
713
+ const paren = bodyText.lastIndexOf('(')
714
+ let filepath${strOpt}
715
+ let fn${strOpt}
716
+ if (paren !== -1) {
717
+ fn = bodyText.slice(0, paren).trim()
718
+ if (fn.indexOf('async ') === 0) fn = fn.slice(6)
719
+ if (fn.indexOf('new ') === 0) fn = fn.slice(4)
720
+ filepath = bodyText.slice(paren + 1, bodyText.length - loc[0].length)
721
+ } else {
722
+ filepath = bodyText.slice(0, bodyText.length - loc[0].length)
723
+ }
724
+ if (filepath.indexOf('file://') === 0) filepath = filepath.slice(7)
725
+ if (!filepath) continue
726
+ return { filepath: filepath, lineno: Number(loc[1]), function: fn || undefined }
727
+ }
728
+ return null
729
+ }
730
+
731
+ class NeatCallSiteSpanProcessor {
732
+ onStart(span${spanT}) {
733
+ if (!span || (span.kind !== 2 && span.kind !== 3)) return
734
+ const frame = __neatPickUserFrame(new Error().stack)
735
+ if (!frame) return
736
+ span.setAttribute('code.filepath', frame.filepath)
737
+ if (typeof frame.lineno === 'number') span.setAttribute('code.lineno', frame.lineno)
738
+ if (frame.function) span.setAttribute('code.function', frame.function)
739
+ }
740
+ onEnd() {}
741
+ forceFlush() { return Promise.resolve() }
742
+ shutdown() { return Promise.resolve() }
743
+ }`;
744
+ }
745
+ var CALLSITE_PROCESSOR_JS = neatCallsiteProcessorSource(false);
746
+ var CALLSITE_PROCESSOR_TS = neatCallsiteProcessorSource(true);
747
+ function neatRegisterCallsiteSource(ts) {
748
+ const providerT = ts ? ": any" : "";
749
+ return `try {
750
+ const provider${providerT} = trace.getTracerProvider()
751
+ const delegate = provider && typeof provider.getDelegate === 'function' ? provider.getDelegate() : provider
752
+ if (delegate && typeof delegate.addSpanProcessor === 'function') {
753
+ delegate.addSpanProcessor(new NeatCallSiteSpanProcessor())
754
+ }
755
+ } catch {
756
+ // capture is best-effort; span export is unaffected
757
+ }`;
758
+ }
696
759
  var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
760
+ ${OTEL_INIT_STAMP}
697
761
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
698
762
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
699
763
 
700
764
  const { NodeSDK } = require('@opentelemetry/sdk-node')
701
765
  const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
766
+ const { trace } = require('@opentelemetry/api')
767
+
768
+ ${CALLSITE_PROCESSOR_JS}
702
769
 
703
770
  const instrumentations = [getNodeAutoInstrumentations()]
704
771
  __INSTRUMENTATION_BLOCK__
705
- new NodeSDK({ instrumentations }).start()
772
+ const sdk = new NodeSDK({ instrumentations })
773
+ sdk.start()
774
+ ${neatRegisterCallsiteSource(false)}
706
775
  `;
707
776
  var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
777
+ ${OTEL_INIT_STAMP}
708
778
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
709
779
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
710
780
 
711
781
  import { NodeSDK } from '@opentelemetry/sdk-node'
712
782
  import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
783
+ import { trace } from '@opentelemetry/api'
784
+
785
+ ${CALLSITE_PROCESSOR_JS}
713
786
 
714
787
  const instrumentations = [getNodeAutoInstrumentations()]
715
788
  __INSTRUMENTATION_BLOCK__
716
- new NodeSDK({ instrumentations }).start()
789
+ const sdk = new NodeSDK({ instrumentations })
790
+ sdk.start()
791
+ ${neatRegisterCallsiteSource(false)}
717
792
  `;
718
793
  var OTEL_INIT_TS = `${OTEL_INIT_HEADER}
794
+ ${OTEL_INIT_STAMP}
719
795
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
720
796
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
721
797
 
722
798
  import { NodeSDK } from '@opentelemetry/sdk-node'
723
799
  import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
800
+ import { trace } from '@opentelemetry/api'
801
+
802
+ ${CALLSITE_PROCESSOR_TS}
724
803
 
725
804
  const instrumentations = [getNodeAutoInstrumentations()]
726
805
  __INSTRUMENTATION_BLOCK__
727
- new NodeSDK({ instrumentations }).start()
806
+ const sdk = new NodeSDK({ instrumentations })
807
+ sdk.start()
808
+ ${neatRegisterCallsiteSource(true)}
728
809
  `;
729
810
  function renderNodeOtelInit(template, serviceName, projectName, registrations = []) {
730
811
  const block = registrations.length === 0 ? "" : `
@@ -948,6 +1029,23 @@ async function exists(p) {
948
1029
  return false;
949
1030
  }
950
1031
  }
1032
+ async function readFileMaybe(p) {
1033
+ try {
1034
+ return await fs4.readFile(p, "utf8");
1035
+ } catch {
1036
+ return null;
1037
+ }
1038
+ }
1039
+ async function planOtelInitGeneration(file, contents) {
1040
+ const existing = await readFileMaybe(file);
1041
+ if (existing === null) {
1042
+ return { file, contents, skipIfExists: true };
1043
+ }
1044
+ if (existing.includes(OTEL_INIT_HEADER) && !existing.includes(OTEL_INIT_STAMP)) {
1045
+ return { file, contents, skipIfExists: false };
1046
+ }
1047
+ return null;
1048
+ }
951
1049
  async function detect(serviceDir) {
952
1050
  const pkg = await readPackageJson(serviceDir);
953
1051
  return pkg !== null && typeof pkg.name === "string";
@@ -1678,18 +1776,11 @@ async function plan(serviceDir, opts) {
1678
1776
  const projectName = projectToken(pkg, serviceDir, project);
1679
1777
  const registrations = nonBundled.map((i) => i.registration);
1680
1778
  const generatedFiles = [];
1681
- if (!await exists(otelInitFile)) {
1682
- generatedFiles.push({
1683
- file: otelInitFile,
1684
- contents: renderNodeOtelInit(
1685
- otelInitContents(flavor),
1686
- svcName,
1687
- projectName,
1688
- registrations
1689
- ),
1690
- skipIfExists: true
1691
- });
1692
- }
1779
+ const otelInitGen = await planOtelInitGeneration(
1780
+ otelInitFile,
1781
+ renderNodeOtelInit(otelInitContents(flavor), svcName, projectName, registrations)
1782
+ );
1783
+ if (otelInitGen) generatedFiles.push(otelInitGen);
1693
1784
  if (!await exists(envNeatFile)) {
1694
1785
  generatedFiles.push({
1695
1786
  file: envNeatFile,