@neat.is/core 0.4.4 → 0.4.5

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
@@ -301,12 +301,15 @@ function parseOtlpRequest(body) {
301
301
  const out = [];
302
302
  for (const rs of body.resourceSpans ?? []) {
303
303
  const resourceAttrs = attrsToRecord(rs.resource?.attributes);
304
- const service = typeof resourceAttrs["service.name"] === "string" ? resourceAttrs["service.name"] : "unknown";
304
+ const rawServiceName = resourceAttrs["service.name"];
305
+ const resourceServiceNamePresent = typeof rawServiceName === "string" && rawServiceName.length > 0;
306
+ const service = resourceServiceNamePresent ? rawServiceName : "unidentified";
305
307
  for (const ss of rs.scopeSpans ?? []) {
306
308
  for (const span of ss.spans ?? []) {
307
309
  const attrs = attrsToRecord(span.attributes);
308
310
  const parsed = {
309
311
  service,
312
+ resourceServiceNamePresent,
310
313
  traceId: span.traceId ?? "",
311
314
  spanId: span.spanId ?? "",
312
315
  parentSpanId: span.parentSpanId || void 0,
@@ -1620,6 +1623,14 @@ function thresholdForEdgeType(edgeType, overrides) {
1620
1623
  function nowIso(ctx) {
1621
1624
  return new Date(ctx.now ? ctx.now() : Date.now()).toISOString();
1622
1625
  }
1626
+ var unidentifiedWarnedProjects = /* @__PURE__ */ new Set();
1627
+ function warnUnidentifiedSpan(project) {
1628
+ if (unidentifiedWarnedProjects.has(project)) return;
1629
+ unidentifiedWarnedProjects.add(project);
1630
+ console.warn(
1631
+ `[neatd] span lacked service.name; routed to 'unidentified' in project ${project}; check your OTel SDK config.`
1632
+ );
1633
+ }
1623
1634
  function pickAttr(span, ...keys) {
1624
1635
  for (const k of keys) {
1625
1636
  const v = span.attributes[k];
@@ -1874,6 +1885,9 @@ async function handleSpan(ctx, span) {
1874
1885
  const ts = span.startTimeIso ?? nowIso(ctx);
1875
1886
  const nowMs = ctx.now ? ctx.now() : Date.now();
1876
1887
  const env = span.env ?? "unknown";
1888
+ if (span.resourceServiceNamePresent === false) {
1889
+ warnUnidentifiedSpan(ctx.project ?? DEFAULT_PROJECT);
1890
+ }
1877
1891
  const sourceId = ensureServiceNode(ctx.graph, span.service, env);
1878
1892
  const isError = span.statusCode === 2;
1879
1893
  cacheSpanService(span, nowMs);
@@ -6559,22 +6573,40 @@ var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
6559
6573
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
6560
6574
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
6561
6575
 
6562
- require('@opentelemetry/auto-instrumentations-node/register')
6576
+ const { NodeSDK } = require('@opentelemetry/sdk-node')
6577
+ const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
6578
+
6579
+ const instrumentations = [getNodeAutoInstrumentations()]
6580
+ __INSTRUMENTATION_BLOCK__
6581
+ new NodeSDK({ instrumentations }).start()
6563
6582
  `;
6564
6583
  var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
6565
6584
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
6566
6585
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
6567
6586
 
6568
- await import('@opentelemetry/auto-instrumentations-node/register')
6587
+ import { NodeSDK } from '@opentelemetry/sdk-node'
6588
+ import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
6589
+
6590
+ const instrumentations = [getNodeAutoInstrumentations()]
6591
+ __INSTRUMENTATION_BLOCK__
6592
+ new NodeSDK({ instrumentations }).start()
6569
6593
  `;
6570
6594
  var OTEL_INIT_TS = `${OTEL_INIT_HEADER}
6571
6595
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
6572
6596
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
6573
6597
 
6574
- await import('@opentelemetry/auto-instrumentations-node/register')
6598
+ import { NodeSDK } from '@opentelemetry/sdk-node'
6599
+ import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
6600
+
6601
+ const instrumentations = [getNodeAutoInstrumentations()]
6602
+ __INSTRUMENTATION_BLOCK__
6603
+ new NodeSDK({ instrumentations }).start()
6575
6604
  `;
6576
- function renderNodeOtelInit(template, serviceName, projectName) {
6577
- return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName);
6605
+ function renderNodeOtelInit(template, serviceName, projectName, registrations = []) {
6606
+ const block = registrations.length === 0 ? "" : `
6607
+ ${registrations.join("\n")}
6608
+ `;
6609
+ return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
6578
6610
  }
6579
6611
  function renderEnvNeat(serviceName, projectName) {
6580
6612
  return [
@@ -6606,7 +6638,9 @@ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projec
6606
6638
  import { NodeSDK } from '@opentelemetry/sdk-node'
6607
6639
  import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
6608
6640
 
6609
- new NodeSDK({ instrumentations: [getNodeAutoInstrumentations()] }).start()
6641
+ const instrumentations = [getNodeAutoInstrumentations()]
6642
+ __INSTRUMENTATION_BLOCK__
6643
+ new NodeSDK({ instrumentations }).start()
6610
6644
  `;
6611
6645
  var NEXT_INSTRUMENTATION_NODE_JS = `${NEXT_INSTRUMENTATION_HEADER}
6612
6646
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
@@ -6615,10 +6649,15 @@ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projec
6615
6649
  const { NodeSDK } = require('@opentelemetry/sdk-node')
6616
6650
  const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
6617
6651
 
6618
- new NodeSDK({ instrumentations: [getNodeAutoInstrumentations()] }).start()
6652
+ const instrumentations = [getNodeAutoInstrumentations()]
6653
+ __INSTRUMENTATION_BLOCK__
6654
+ new NodeSDK({ instrumentations }).start()
6619
6655
  `;
6620
- function renderNextInstrumentationNode(template, serviceName, projectName) {
6621
- return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName);
6656
+ function renderNextInstrumentationNode(template, serviceName, projectName, registrations = []) {
6657
+ const block = registrations.length === 0 ? "" : `
6658
+ ${registrations.join("\n")}
6659
+ `;
6660
+ return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
6622
6661
  }
6623
6662
  var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074). OpenTelemetry SDK hook.";
6624
6663
  var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
@@ -6714,6 +6753,18 @@ var SDK_PACKAGES = [
6714
6753
  { name: "@opentelemetry/sdk-node", version: "^0.57.0" },
6715
6754
  { name: "@opentelemetry/auto-instrumentations-node", version: "^0.55.0" }
6716
6755
  ];
6756
+ function detectNonBundledInstrumentations(pkg) {
6757
+ const deps = allDeps(pkg);
6758
+ const out = [];
6759
+ if ("@prisma/client" in deps) {
6760
+ out.push({
6761
+ pkg: "@prisma/instrumentation",
6762
+ version: "^5.0.0",
6763
+ registration: "instrumentations.push(new (require('@prisma/instrumentation').PrismaInstrumentation)())"
6764
+ });
6765
+ }
6766
+ return out;
6767
+ }
6717
6768
  var OTEL_ENV = {
6718
6769
  // ADR-069 §4 — endpoint moves into the per-package .env.neat (written
6719
6770
  // by the apply phase). The envEdits surface stays for the dry-run
@@ -7004,8 +7055,19 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
7004
7055
  version: sdk.version
7005
7056
  });
7006
7057
  }
7058
+ const nonBundled = detectNonBundledInstrumentations(pkg);
7059
+ for (const inst of nonBundled) {
7060
+ if (inst.pkg in existingDeps) continue;
7061
+ dependencyEdits.push({
7062
+ file: manifestPath,
7063
+ kind: "add",
7064
+ name: inst.pkg,
7065
+ version: inst.version
7066
+ });
7067
+ }
7007
7068
  const svcName = serviceNodeName(pkg, serviceDir);
7008
7069
  const projectName = projectToken(pkg, serviceDir, project);
7070
+ const registrations = nonBundled.map((i) => i.registration);
7009
7071
  const generatedFiles = [];
7010
7072
  if (!await exists2(instrumentationFile)) {
7011
7073
  generatedFiles.push({
@@ -7020,7 +7082,8 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
7020
7082
  contents: renderNextInstrumentationNode(
7021
7083
  useTs ? NEXT_INSTRUMENTATION_NODE_TS : NEXT_INSTRUMENTATION_NODE_JS,
7022
7084
  svcName,
7023
- projectName
7085
+ projectName,
7086
+ registrations
7024
7087
  ),
7025
7088
  skipIfExists: true
7026
7089
  });
@@ -7370,55 +7433,73 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
7370
7433
  framework: "astro"
7371
7434
  };
7372
7435
  }
7373
- async function plan(serviceDir, opts) {
7374
- const pkg = await readPackageJson(serviceDir);
7375
- const manifestPath = import_node_path40.default.join(serviceDir, "package.json");
7376
- const project = opts?.project;
7377
- const empty = {
7378
- language: "javascript",
7379
- serviceDir,
7380
- dependencyEdits: [],
7381
- entrypointEdits: [],
7382
- envEdits: [],
7383
- generatedFiles: []
7384
- };
7385
- if (!pkg) return empty;
7436
+ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
7386
7437
  if (hasNextDependency(pkg)) {
7387
7438
  const nextConfig = await findNextConfig(serviceDir);
7388
7439
  if (nextConfig) {
7389
- return planNext(serviceDir, pkg, manifestPath, nextConfig, project);
7440
+ return () => planNext(serviceDir, pkg, manifestPath, nextConfig, project);
7390
7441
  }
7391
7442
  }
7392
7443
  if (hasRemixDependency(pkg)) {
7393
7444
  const remixEntry = await findRemixEntry(serviceDir);
7394
7445
  if (remixEntry) {
7395
- return planRemix(serviceDir, pkg, manifestPath, remixEntry, project);
7446
+ return () => planRemix(serviceDir, pkg, manifestPath, remixEntry, project);
7396
7447
  }
7397
7448
  }
7398
7449
  if (hasSvelteKitDependency(pkg)) {
7399
7450
  const hooks = await findSvelteKitHooks(serviceDir);
7400
7451
  const config = await findSvelteKitConfig(serviceDir);
7401
7452
  if (hooks || config) {
7402
- return planSvelteKit(serviceDir, pkg, manifestPath, hooks, project);
7453
+ return () => planSvelteKit(serviceDir, pkg, manifestPath, hooks, project);
7403
7454
  }
7404
7455
  }
7405
7456
  if (hasNuxtDependency(pkg)) {
7406
7457
  const nuxtConfig = await findNuxtConfig(serviceDir);
7407
7458
  if (nuxtConfig) {
7408
- return planNuxt(serviceDir, pkg, manifestPath, project);
7459
+ return () => planNuxt(serviceDir, pkg, manifestPath, project);
7409
7460
  }
7410
7461
  }
7411
7462
  if (hasAstroDependency(pkg)) {
7412
7463
  const astroConfig = await findAstroConfig(serviceDir);
7413
7464
  if (astroConfig) {
7414
- return planAstro(serviceDir, pkg, manifestPath, project);
7465
+ return () => planAstro(serviceDir, pkg, manifestPath, project);
7466
+ }
7467
+ }
7468
+ return null;
7469
+ }
7470
+ async function plan(serviceDir, opts) {
7471
+ const pkg = await readPackageJson(serviceDir);
7472
+ const manifestPath = import_node_path40.default.join(serviceDir, "package.json");
7473
+ const project = opts?.project;
7474
+ const empty = {
7475
+ language: "javascript",
7476
+ serviceDir,
7477
+ dependencyEdits: [],
7478
+ entrypointEdits: [],
7479
+ envEdits: [],
7480
+ generatedFiles: []
7481
+ };
7482
+ if (!pkg) return empty;
7483
+ const frameworkDispatch = await findFrameworkDispatch(
7484
+ serviceDir,
7485
+ pkg,
7486
+ manifestPath,
7487
+ project
7488
+ );
7489
+ let entryFile = null;
7490
+ if (!frameworkDispatch) {
7491
+ entryFile = await resolveEntry(serviceDir, pkg);
7492
+ if (!entryFile) {
7493
+ return { ...empty, libOnly: true };
7415
7494
  }
7416
7495
  }
7496
+ if (frameworkDispatch) {
7497
+ return frameworkDispatch();
7498
+ }
7417
7499
  const runtimeKind = await detectRuntimeKind(serviceDir, pkg);
7418
7500
  if (runtimeKind !== "node") {
7419
7501
  return { ...empty, runtimeKind };
7420
7502
  }
7421
- const entryFile = await resolveEntry(serviceDir, pkg);
7422
7503
  if (!entryFile) {
7423
7504
  return { ...empty, libOnly: true };
7424
7505
  }
@@ -7436,6 +7517,16 @@ async function plan(serviceDir, opts) {
7436
7517
  version: sdk.version
7437
7518
  });
7438
7519
  }
7520
+ const nonBundled = detectNonBundledInstrumentations(pkg);
7521
+ for (const inst of nonBundled) {
7522
+ if (inst.pkg in existingDeps) continue;
7523
+ dependencyEdits.push({
7524
+ file: manifestPath,
7525
+ kind: "add",
7526
+ name: inst.pkg,
7527
+ version: inst.version
7528
+ });
7529
+ }
7439
7530
  const entrypointEdits = [];
7440
7531
  try {
7441
7532
  const raw = await import_node_fs25.promises.readFile(entryFile, "utf8");
@@ -7454,11 +7545,17 @@ async function plan(serviceDir, opts) {
7454
7545
  }
7455
7546
  const svcName = serviceNodeName(pkg, serviceDir);
7456
7547
  const projectName = projectToken(pkg, serviceDir, project);
7548
+ const registrations = nonBundled.map((i) => i.registration);
7457
7549
  const generatedFiles = [];
7458
7550
  if (!await exists2(otelInitFile)) {
7459
7551
  generatedFiles.push({
7460
7552
  file: otelInitFile,
7461
- contents: renderNodeOtelInit(otelInitContents(flavor), svcName, projectName),
7553
+ contents: renderNodeOtelInit(
7554
+ otelInitContents(flavor),
7555
+ svcName,
7556
+ projectName,
7557
+ registrations
7558
+ ),
7462
7559
  skipIfExists: true
7463
7560
  });
7464
7561
  }
@@ -7993,6 +8090,7 @@ function renderPatch(sections) {
7993
8090
  init_cjs_shims();
7994
8091
  var import_node_fs27 = require("fs");
7995
8092
  var import_node_http = __toESM(require("http"), 1);
8093
+ var import_node_net = __toESM(require("net"), 1);
7996
8094
  var import_node_path42 = __toESM(require("path"), 1);
7997
8095
  var import_node_child_process2 = require("child_process");
7998
8096
  var import_node_readline = __toESM(require("readline"), 1);
@@ -8159,6 +8257,28 @@ async function waitForDaemonReady(restPort, timeoutMs) {
8159
8257
  stillBootstrapping: projects.filter((p) => p.status === "bootstrapping").map((p) => p.name)
8160
8258
  };
8161
8259
  }
8260
+ var NEAT_PORTS = [8080, 4318, 6328];
8261
+ async function isPortFree(port) {
8262
+ return new Promise((resolve) => {
8263
+ const server = import_node_net.default.createServer();
8264
+ server.once("error", () => resolve(false));
8265
+ server.once("listening", () => server.close(() => resolve(true)));
8266
+ server.listen(port, "127.0.0.1");
8267
+ });
8268
+ }
8269
+ async function probePortsFree() {
8270
+ for (const port of NEAT_PORTS) {
8271
+ if (!await isPortFree(port)) return { free: false, held: port };
8272
+ }
8273
+ return { free: true };
8274
+ }
8275
+ function formatPortCollisionMessage(port) {
8276
+ return [
8277
+ `neat: port ${port} is in use; the NEAT daemon needs it.`,
8278
+ ` run \`neatd stop\` to release the previous daemon, or`,
8279
+ ` \`lsof -i :${port}\` to find the holding process.`
8280
+ ];
8281
+ }
8162
8282
  function spawnDaemonDetached() {
8163
8283
  const here = import_node_path42.default.dirname(new URL(importMetaUrl).pathname);
8164
8284
  const candidates = [
@@ -8295,6 +8415,14 @@ async function runOrchestrator(opts) {
8295
8415
  if (await checkDaemonHealth(restPort)) {
8296
8416
  result.steps.daemon = "already-running";
8297
8417
  } else {
8418
+ const probe = await probePortsFree();
8419
+ if (!probe.free) {
8420
+ for (const line of formatPortCollisionMessage(probe.held)) {
8421
+ console.error(line);
8422
+ }
8423
+ result.exitCode = 3;
8424
+ return result;
8425
+ }
8298
8426
  try {
8299
8427
  spawnDaemonDetached();
8300
8428
  } catch (err) {
@@ -9102,7 +9230,8 @@ function usage() {
9102
9230
  console.log(" 0 success");
9103
9231
  console.log(" 1 server error (4xx/5xx body printed to stderr)");
9104
9232
  console.log(" 2 misuse (missing args, bad flags) \u2014 handled before any network call");
9105
- console.log(" 3 daemon not reachable (connection refused / timeout)");
9233
+ console.log(" 3 environmental \u2014 daemon unreachable, or one of ports 8080 / 4318 / 6328");
9234
+ console.log(" is held by another process when `neat <path>` tries to spawn the daemon");
9106
9235
  console.log("");
9107
9236
  console.log("environment:");
9108
9237
  console.log(" NEAT_API_URL base URL for the core REST API (default http://localhost:8080)");