@neat.is/core 0.4.3 → 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/{chunk-D5PIJFBE.js → chunk-3QCRUEQD.js} +2 -2
- package/dist/{chunk-CB2UK4EH.js → chunk-6GXBAR3M.js} +44 -3
- package/dist/{chunk-CB2UK4EH.js.map → chunk-6GXBAR3M.js.map} +1 -1
- package/dist/{chunk-KYRIQIPG.js → chunk-HVF4S7J3.js} +101 -26
- package/dist/chunk-HVF4S7J3.js.map +1 -0
- package/dist/{chunk-NTQHMXWE.js → chunk-RBWL4HRB.js} +13 -2
- package/dist/chunk-RBWL4HRB.js.map +1 -0
- package/dist/cli.cjs +430 -124
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +323 -103
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +151 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +151 -24
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-QTX2YQJZ.js → otel-grpc-VSPMP3OS.js} +3 -3
- package/dist/server.cjs +110 -24
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-KYRIQIPG.js.map +0 -1
- package/dist/chunk-NTQHMXWE.js.map +0 -1
- /package/dist/{chunk-D5PIJFBE.js.map → chunk-3QCRUEQD.js.map} +0 -0
- /package/dist/{otel-grpc-QTX2YQJZ.js.map → otel-grpc-VSPMP3OS.js.map} +0 -0
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
|
|
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,
|
|
@@ -401,6 +404,64 @@ async function buildOtelReceiver(opts) {
|
|
|
401
404
|
for (const s of spans) queue.push(s);
|
|
402
405
|
drainPromise = drainPromise.then(() => drain());
|
|
403
406
|
};
|
|
407
|
+
const projectQueue = [];
|
|
408
|
+
let projectDraining = false;
|
|
409
|
+
let projectDrainPromise = Promise.resolve();
|
|
410
|
+
const drainProject = async () => {
|
|
411
|
+
if (projectDraining) return;
|
|
412
|
+
projectDraining = true;
|
|
413
|
+
try {
|
|
414
|
+
while (projectQueue.length > 0) {
|
|
415
|
+
const { project, span } = projectQueue.shift();
|
|
416
|
+
try {
|
|
417
|
+
if (opts.onProjectSpan) {
|
|
418
|
+
await opts.onProjectSpan(project, span);
|
|
419
|
+
} else {
|
|
420
|
+
await opts.onSpan(span);
|
|
421
|
+
}
|
|
422
|
+
} catch (err) {
|
|
423
|
+
console.warn(`[neat] otel handler error: ${err.message}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
} finally {
|
|
427
|
+
projectDraining = false;
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
const enqueueProject = (project, spans) => {
|
|
431
|
+
if (spans.length === 0) return;
|
|
432
|
+
for (const s of spans) projectQueue.push({ project, span: s });
|
|
433
|
+
projectDrainPromise = projectDrainPromise.then(() => drainProject());
|
|
434
|
+
};
|
|
435
|
+
const legacyEndpointWarned = /* @__PURE__ */ new Set();
|
|
436
|
+
function warnLegacyEndpoint(serviceName) {
|
|
437
|
+
if (legacyEndpointWarned.has(serviceName)) return;
|
|
438
|
+
legacyEndpointWarned.add(serviceName);
|
|
439
|
+
console.warn(
|
|
440
|
+
`[neatd] received span on the global endpoint; migrate OTEL_EXPORTER_OTLP_TRACES_ENDPOINT to /projects/<name>/v1/traces (service.name="${serviceName}").`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
async function readOtlpBody(req) {
|
|
444
|
+
const ct = (req.headers["content-type"] ?? "").toString().split(";")[0].trim().toLowerCase();
|
|
445
|
+
if (ct === "application/x-protobuf") {
|
|
446
|
+
try {
|
|
447
|
+
const body = await decodeProtobufBody(req.body);
|
|
448
|
+
return { ok: true, body, flavor: "protobuf" };
|
|
449
|
+
} catch (err) {
|
|
450
|
+
return { ok: false, code: 400, error: `protobuf decode failed: ${err.message}` };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (!ct || ct === "application/json") {
|
|
454
|
+
return { ok: true, body: req.body ?? {}, flavor: "json" };
|
|
455
|
+
}
|
|
456
|
+
return { ok: false, code: 415, error: `unsupported content-type: ${ct}` };
|
|
457
|
+
}
|
|
458
|
+
function sendOtlpSuccess(reply, flavor) {
|
|
459
|
+
if (flavor === "protobuf") {
|
|
460
|
+
const buf = encodeProtobufResponseBody();
|
|
461
|
+
return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
|
|
462
|
+
}
|
|
463
|
+
return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
|
|
464
|
+
}
|
|
404
465
|
app.addContentTypeParser(
|
|
405
466
|
"application/x-protobuf",
|
|
406
467
|
{ parseAs: "buffer", bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024 },
|
|
@@ -410,26 +471,44 @@ async function buildOtelReceiver(opts) {
|
|
|
410
471
|
);
|
|
411
472
|
app.get("/health", async () => ({ ok: true }));
|
|
412
473
|
app.post("/v1/traces", async (req, reply) => {
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
474
|
+
const result = await readOtlpBody(req);
|
|
475
|
+
if (!result.ok) {
|
|
476
|
+
return reply.code(result.code).send({ error: result.error });
|
|
477
|
+
}
|
|
478
|
+
const spans = parseOtlpRequest(result.body);
|
|
479
|
+
for (const s of spans) warnLegacyEndpoint(s.service);
|
|
480
|
+
if (opts.onErrorSpanSync) {
|
|
418
481
|
try {
|
|
419
|
-
|
|
482
|
+
for (const span of spans) {
|
|
483
|
+
if (span.statusCode === 2) await opts.onErrorSpanSync(span);
|
|
484
|
+
}
|
|
420
485
|
} catch (err) {
|
|
421
|
-
return reply.code(
|
|
422
|
-
error: `
|
|
486
|
+
return reply.code(500).send({
|
|
487
|
+
error: `error-event write failed: ${err.message}`
|
|
423
488
|
});
|
|
424
489
|
}
|
|
425
|
-
} else if (!ct || ct === "application/json") {
|
|
426
|
-
responseFlavor = "json";
|
|
427
|
-
body = req.body ?? {};
|
|
428
|
-
} else {
|
|
429
|
-
return reply.code(415).send({ error: `unsupported content-type: ${ct}` });
|
|
430
490
|
}
|
|
431
|
-
|
|
432
|
-
|
|
491
|
+
enqueue(spans);
|
|
492
|
+
return sendOtlpSuccess(reply, result.flavor);
|
|
493
|
+
});
|
|
494
|
+
app.post("/projects/:project/v1/traces", async (req, reply) => {
|
|
495
|
+
const project = req.params.project;
|
|
496
|
+
const result = await readOtlpBody(req);
|
|
497
|
+
if (!result.ok) {
|
|
498
|
+
return reply.code(result.code).send({ error: result.error });
|
|
499
|
+
}
|
|
500
|
+
const spans = parseOtlpRequest(result.body);
|
|
501
|
+
if (opts.onProjectErrorSpanSync) {
|
|
502
|
+
try {
|
|
503
|
+
for (const span of spans) {
|
|
504
|
+
if (span.statusCode === 2) await opts.onProjectErrorSpanSync(project, span);
|
|
505
|
+
}
|
|
506
|
+
} catch (err) {
|
|
507
|
+
return reply.code(500).send({
|
|
508
|
+
error: `error-event write failed: ${err.message}`
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
} else if (opts.onErrorSpanSync) {
|
|
433
512
|
try {
|
|
434
513
|
for (const span of spans) {
|
|
435
514
|
if (span.statusCode === 2) await opts.onErrorSpanSync(span);
|
|
@@ -440,17 +519,13 @@ async function buildOtelReceiver(opts) {
|
|
|
440
519
|
});
|
|
441
520
|
}
|
|
442
521
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const buf = encodeProtobufResponseBody();
|
|
446
|
-
return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
|
|
447
|
-
}
|
|
448
|
-
return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
|
|
522
|
+
enqueueProject(project, spans);
|
|
523
|
+
return sendOtlpSuccess(reply, result.flavor);
|
|
449
524
|
});
|
|
450
525
|
const decorated = app;
|
|
451
526
|
decorated.flushPending = async () => {
|
|
452
|
-
while (queue.length > 0 || draining) {
|
|
453
|
-
await drainPromise;
|
|
527
|
+
while (queue.length > 0 || draining || projectQueue.length > 0 || projectDraining) {
|
|
528
|
+
await Promise.all([drainPromise, projectDrainPromise]);
|
|
454
529
|
}
|
|
455
530
|
};
|
|
456
531
|
return decorated;
|
|
@@ -1548,6 +1623,14 @@ function thresholdForEdgeType(edgeType, overrides) {
|
|
|
1548
1623
|
function nowIso(ctx) {
|
|
1549
1624
|
return new Date(ctx.now ? ctx.now() : Date.now()).toISOString();
|
|
1550
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
|
+
}
|
|
1551
1634
|
function pickAttr(span, ...keys) {
|
|
1552
1635
|
for (const k of keys) {
|
|
1553
1636
|
const v = span.attributes[k];
|
|
@@ -1802,6 +1885,9 @@ async function handleSpan(ctx, span) {
|
|
|
1802
1885
|
const ts = span.startTimeIso ?? nowIso(ctx);
|
|
1803
1886
|
const nowMs = ctx.now ? ctx.now() : Date.now();
|
|
1804
1887
|
const env = span.env ?? "unknown";
|
|
1888
|
+
if (span.resourceServiceNamePresent === false) {
|
|
1889
|
+
warnUnidentifiedSpan(ctx.project ?? DEFAULT_PROJECT);
|
|
1890
|
+
}
|
|
1805
1891
|
const sourceId = ensureServiceNode(ctx.graph, span.service, env);
|
|
1806
1892
|
const isError = span.statusCode === 2;
|
|
1807
1893
|
cacheSpanService(span, nowMs);
|
|
@@ -6484,45 +6570,49 @@ var import_node_path40 = __toESM(require("path"), 1);
|
|
|
6484
6570
|
init_cjs_shims();
|
|
6485
6571
|
var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
|
|
6486
6572
|
var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6573
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6574
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6575
|
+
|
|
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()
|
|
6496
6582
|
`;
|
|
6497
6583
|
var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
import { fileURLToPath } from 'node:url'
|
|
6501
|
-
import path from 'node:path'
|
|
6502
|
-
import dotenv from 'dotenv'
|
|
6584
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6585
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6503
6586
|
|
|
6504
|
-
|
|
6505
|
-
|
|
6587
|
+
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6588
|
+
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
6506
6589
|
|
|
6507
|
-
|
|
6590
|
+
const instrumentations = [getNodeAutoInstrumentations()]
|
|
6591
|
+
__INSTRUMENTATION_BLOCK__
|
|
6592
|
+
new NodeSDK({ instrumentations }).start()
|
|
6508
6593
|
`;
|
|
6509
6594
|
var OTEL_INIT_TS = `${OTEL_INIT_HEADER}
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
import { fileURLToPath } from 'node:url'
|
|
6513
|
-
import path from 'node:path'
|
|
6514
|
-
import dotenv from 'dotenv'
|
|
6595
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6596
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6515
6597
|
|
|
6516
|
-
|
|
6517
|
-
|
|
6598
|
+
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6599
|
+
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
6518
6600
|
|
|
6519
|
-
|
|
6601
|
+
const instrumentations = [getNodeAutoInstrumentations()]
|
|
6602
|
+
__INSTRUMENTATION_BLOCK__
|
|
6603
|
+
new NodeSDK({ instrumentations }).start()
|
|
6520
6604
|
`;
|
|
6521
|
-
function
|
|
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);
|
|
6610
|
+
}
|
|
6611
|
+
function renderEnvNeat(serviceName, projectName) {
|
|
6522
6612
|
return [
|
|
6523
6613
|
"# Generated by `neat init --apply` (ADR-069).",
|
|
6524
|
-
`OTEL_SERVICE_NAME=${
|
|
6525
|
-
|
|
6614
|
+
`OTEL_SERVICE_NAME=${serviceName}`,
|
|
6615
|
+
`OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/projects/${projectName}/v1/traces`,
|
|
6526
6616
|
""
|
|
6527
6617
|
].join("\n");
|
|
6528
6618
|
}
|
|
@@ -6542,39 +6632,41 @@ export async function register() {
|
|
|
6542
6632
|
}
|
|
6543
6633
|
`;
|
|
6544
6634
|
var NEXT_INSTRUMENTATION_NODE_TS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
6545
|
-
process.env.OTEL_SERVICE_NAME ||= '
|
|
6546
|
-
process.env.
|
|
6635
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6636
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6547
6637
|
|
|
6548
6638
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6549
6639
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
6550
6640
|
|
|
6551
|
-
|
|
6641
|
+
const instrumentations = [getNodeAutoInstrumentations()]
|
|
6642
|
+
__INSTRUMENTATION_BLOCK__
|
|
6643
|
+
new NodeSDK({ instrumentations }).start()
|
|
6552
6644
|
`;
|
|
6553
6645
|
var NEXT_INSTRUMENTATION_NODE_JS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
6554
|
-
process.env.OTEL_SERVICE_NAME ||= '
|
|
6555
|
-
process.env.
|
|
6646
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6647
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6556
6648
|
|
|
6557
6649
|
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
6558
6650
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
6559
6651
|
|
|
6560
|
-
|
|
6652
|
+
const instrumentations = [getNodeAutoInstrumentations()]
|
|
6653
|
+
__INSTRUMENTATION_BLOCK__
|
|
6654
|
+
new NodeSDK({ instrumentations }).start()
|
|
6561
6655
|
`;
|
|
6562
|
-
function renderNextInstrumentationNode(template, projectName) {
|
|
6563
|
-
|
|
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);
|
|
6564
6661
|
}
|
|
6565
6662
|
var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074). OpenTelemetry SDK hook.";
|
|
6566
6663
|
var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
import path from 'node:path'
|
|
6571
|
-
import dotenv from 'dotenv'
|
|
6664
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6665
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6666
|
+
|
|
6572
6667
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6573
6668
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
6574
6669
|
|
|
6575
|
-
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
6576
|
-
dotenv.config({ path: path.join(here, '.env.neat') })
|
|
6577
|
-
|
|
6578
6670
|
const sdk = new NodeSDK({
|
|
6579
6671
|
serviceName: process.env.OTEL_SERVICE_NAME,
|
|
6580
6672
|
instrumentations: [getNodeAutoInstrumentations()],
|
|
@@ -6582,14 +6674,9 @@ const sdk = new NodeSDK({
|
|
|
6582
6674
|
sdk.start()
|
|
6583
6675
|
`;
|
|
6584
6676
|
var FRAMEWORK_OTEL_INIT_JS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
try {
|
|
6589
|
-
require('dotenv').config({ path: path.join(__dirname, '.env.neat') })
|
|
6590
|
-
} catch (err) {
|
|
6591
|
-
// dotenv unavailable \u2014 fall through to process.env.
|
|
6592
|
-
}
|
|
6677
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6678
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6679
|
+
|
|
6593
6680
|
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
6594
6681
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
6595
6682
|
|
|
@@ -6599,6 +6686,9 @@ const sdk = new NodeSDK({
|
|
|
6599
6686
|
})
|
|
6600
6687
|
sdk.start()
|
|
6601
6688
|
`;
|
|
6689
|
+
function renderFrameworkOtelInit(template, serviceName, projectName) {
|
|
6690
|
+
return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName);
|
|
6691
|
+
}
|
|
6602
6692
|
var REMIX_OTEL_SERVER_TS = FRAMEWORK_OTEL_INIT_TS_BODY;
|
|
6603
6693
|
var REMIX_OTEL_SERVER_JS = FRAMEWORK_OTEL_INIT_JS_BODY;
|
|
6604
6694
|
var SVELTEKIT_OTEL_INIT_TS = FRAMEWORK_OTEL_INIT_TS_BODY;
|
|
@@ -6661,25 +6751,56 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
6661
6751
|
var SDK_PACKAGES = [
|
|
6662
6752
|
{ name: "@opentelemetry/api", version: "^1.9.0" },
|
|
6663
6753
|
{ name: "@opentelemetry/sdk-node", version: "^0.57.0" },
|
|
6664
|
-
{ name: "@opentelemetry/auto-instrumentations-node", version: "^0.55.0" }
|
|
6665
|
-
// ADR-069 §5 — dotenv is the fourth dep. The generated otel-init loads
|
|
6666
|
-
// .env.neat through it so OTEL_SERVICE_NAME and the endpoint are in scope
|
|
6667
|
-
// before the auto-instrumentation hook attaches.
|
|
6668
|
-
{ name: "dotenv", version: "^16.4.5" }
|
|
6754
|
+
{ name: "@opentelemetry/auto-instrumentations-node", version: "^0.55.0" }
|
|
6669
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
|
+
}
|
|
6670
6768
|
var OTEL_ENV = {
|
|
6671
6769
|
// ADR-069 §4 — endpoint moves into the per-package .env.neat (written
|
|
6672
6770
|
// by the apply phase). The envEdits surface stays for the dry-run
|
|
6673
6771
|
// patch render: it documents the key/value the user can inspect in the
|
|
6674
6772
|
// generated .env.neat.
|
|
6675
6773
|
file: null,
|
|
6676
|
-
key: "
|
|
6677
|
-
value: "http://localhost:4318"
|
|
6774
|
+
key: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
|
|
6775
|
+
value: "http://localhost:4318/projects/<project>/v1/traces"
|
|
6678
6776
|
};
|
|
6679
|
-
function
|
|
6777
|
+
function serviceNodeName(pkg, serviceDir) {
|
|
6778
|
+
return pkg.name ?? import_node_path40.default.basename(serviceDir);
|
|
6779
|
+
}
|
|
6780
|
+
function projectToken(pkg, serviceDir, project) {
|
|
6680
6781
|
if (project && project.length > 0) return project;
|
|
6681
6782
|
return pkg.name ?? import_node_path40.default.basename(serviceDir);
|
|
6682
6783
|
}
|
|
6784
|
+
async function readJsonFile(p) {
|
|
6785
|
+
try {
|
|
6786
|
+
const raw = await import_node_fs25.promises.readFile(p, "utf8");
|
|
6787
|
+
return JSON.parse(raw);
|
|
6788
|
+
} catch {
|
|
6789
|
+
return null;
|
|
6790
|
+
}
|
|
6791
|
+
}
|
|
6792
|
+
async function detectRuntimeKind(pkgRoot, pkg) {
|
|
6793
|
+
const deps = allDeps(pkg);
|
|
6794
|
+
if ("react-native" in deps || "expo" in deps) return "react-native";
|
|
6795
|
+
const appJson = await readJsonFile(import_node_path40.default.join(pkgRoot, "app.json"));
|
|
6796
|
+
if (appJson && typeof appJson === "object" && "expo" in appJson) {
|
|
6797
|
+
return "react-native";
|
|
6798
|
+
}
|
|
6799
|
+
if (await exists2(import_node_path40.default.join(pkgRoot, "vite.config.js")) || await exists2(import_node_path40.default.join(pkgRoot, "vite.config.ts")) || await exists2(import_node_path40.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
|
|
6800
|
+
return "browser-bundle";
|
|
6801
|
+
}
|
|
6802
|
+
return "node";
|
|
6803
|
+
}
|
|
6683
6804
|
async function readPackageJson(serviceDir) {
|
|
6684
6805
|
try {
|
|
6685
6806
|
const raw = await import_node_fs25.promises.readFile(import_node_path40.default.join(serviceDir, "package.json"), "utf8");
|
|
@@ -6926,7 +7047,6 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
6926
7047
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
6927
7048
|
const dependencyEdits = [];
|
|
6928
7049
|
for (const sdk of SDK_PACKAGES) {
|
|
6929
|
-
if (sdk.name === "dotenv") continue;
|
|
6930
7050
|
if (sdk.name in existingDeps) continue;
|
|
6931
7051
|
dependencyEdits.push({
|
|
6932
7052
|
file: manifestPath,
|
|
@@ -6935,7 +7055,19 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
6935
7055
|
version: sdk.version
|
|
6936
7056
|
});
|
|
6937
7057
|
}
|
|
6938
|
-
const
|
|
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
|
+
}
|
|
7068
|
+
const svcName = serviceNodeName(pkg, serviceDir);
|
|
7069
|
+
const projectName = projectToken(pkg, serviceDir, project);
|
|
7070
|
+
const registrations = nonBundled.map((i) => i.registration);
|
|
6939
7071
|
const generatedFiles = [];
|
|
6940
7072
|
if (!await exists2(instrumentationFile)) {
|
|
6941
7073
|
generatedFiles.push({
|
|
@@ -6949,7 +7081,9 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
6949
7081
|
file: instrumentationNodeFile,
|
|
6950
7082
|
contents: renderNextInstrumentationNode(
|
|
6951
7083
|
useTs ? NEXT_INSTRUMENTATION_NODE_TS : NEXT_INSTRUMENTATION_NODE_JS,
|
|
6952
|
-
|
|
7084
|
+
svcName,
|
|
7085
|
+
projectName,
|
|
7086
|
+
registrations
|
|
6953
7087
|
),
|
|
6954
7088
|
skipIfExists: true
|
|
6955
7089
|
});
|
|
@@ -6957,7 +7091,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
6957
7091
|
if (!await exists2(envNeatFile)) {
|
|
6958
7092
|
generatedFiles.push({
|
|
6959
7093
|
file: envNeatFile,
|
|
6960
|
-
contents: renderEnvNeat(projectName),
|
|
7094
|
+
contents: renderEnvNeat(svcName, projectName),
|
|
6961
7095
|
skipIfExists: true
|
|
6962
7096
|
});
|
|
6963
7097
|
}
|
|
@@ -7018,11 +7152,21 @@ async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
|
|
|
7018
7152
|
if (!await exists2(envNeatFile)) {
|
|
7019
7153
|
generatedFiles.push({
|
|
7020
7154
|
file: envNeatFile,
|
|
7021
|
-
contents: renderEnvNeat(
|
|
7155
|
+
contents: renderEnvNeat(
|
|
7156
|
+
serviceNodeName(pkg, serviceDir),
|
|
7157
|
+
projectToken(pkg, serviceDir, project)
|
|
7158
|
+
),
|
|
7022
7159
|
skipIfExists: true
|
|
7023
7160
|
});
|
|
7024
7161
|
}
|
|
7025
7162
|
}
|
|
7163
|
+
function renderFrameworkOtelInitForPkg(template, pkg, serviceDir, project) {
|
|
7164
|
+
return renderFrameworkOtelInit(
|
|
7165
|
+
template,
|
|
7166
|
+
serviceNodeName(pkg, serviceDir),
|
|
7167
|
+
projectToken(pkg, serviceDir, project)
|
|
7168
|
+
);
|
|
7169
|
+
}
|
|
7026
7170
|
function fileImportsOtelHook(raw, specifiers) {
|
|
7027
7171
|
const lines = raw.split(/\r?\n/);
|
|
7028
7172
|
for (const line of lines) {
|
|
@@ -7048,7 +7192,12 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
7048
7192
|
if (!await exists2(otelServerFile)) {
|
|
7049
7193
|
generatedFiles.push({
|
|
7050
7194
|
file: otelServerFile,
|
|
7051
|
-
contents:
|
|
7195
|
+
contents: renderFrameworkOtelInitForPkg(
|
|
7196
|
+
useTs ? REMIX_OTEL_SERVER_TS : REMIX_OTEL_SERVER_JS,
|
|
7197
|
+
pkg,
|
|
7198
|
+
serviceDir,
|
|
7199
|
+
project
|
|
7200
|
+
),
|
|
7052
7201
|
skipIfExists: true
|
|
7053
7202
|
});
|
|
7054
7203
|
}
|
|
@@ -7102,7 +7251,12 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
7102
7251
|
if (!await exists2(otelInitFile)) {
|
|
7103
7252
|
generatedFiles.push({
|
|
7104
7253
|
file: otelInitFile,
|
|
7105
|
-
contents:
|
|
7254
|
+
contents: renderFrameworkOtelInitForPkg(
|
|
7255
|
+
useTs ? SVELTEKIT_OTEL_INIT_TS : SVELTEKIT_OTEL_INIT_JS,
|
|
7256
|
+
pkg,
|
|
7257
|
+
serviceDir,
|
|
7258
|
+
project
|
|
7259
|
+
),
|
|
7106
7260
|
skipIfExists: true
|
|
7107
7261
|
});
|
|
7108
7262
|
}
|
|
@@ -7165,7 +7319,12 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
|
7165
7319
|
if (!await exists2(otelInitFile)) {
|
|
7166
7320
|
generatedFiles.push({
|
|
7167
7321
|
file: otelInitFile,
|
|
7168
|
-
contents:
|
|
7322
|
+
contents: renderFrameworkOtelInitForPkg(
|
|
7323
|
+
useTs ? NUXT_OTEL_INIT_TS : NUXT_OTEL_INIT_JS,
|
|
7324
|
+
pkg,
|
|
7325
|
+
serviceDir,
|
|
7326
|
+
project
|
|
7327
|
+
),
|
|
7169
7328
|
skipIfExists: true
|
|
7170
7329
|
});
|
|
7171
7330
|
}
|
|
@@ -7221,7 +7380,12 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
|
7221
7380
|
if (!await exists2(otelInitFile)) {
|
|
7222
7381
|
generatedFiles.push({
|
|
7223
7382
|
file: otelInitFile,
|
|
7224
|
-
contents:
|
|
7383
|
+
contents: renderFrameworkOtelInitForPkg(
|
|
7384
|
+
useTs ? ASTRO_OTEL_INIT_TS : ASTRO_OTEL_INIT_JS,
|
|
7385
|
+
pkg,
|
|
7386
|
+
serviceDir,
|
|
7387
|
+
project
|
|
7388
|
+
),
|
|
7225
7389
|
skipIfExists: true
|
|
7226
7390
|
});
|
|
7227
7391
|
}
|
|
@@ -7269,51 +7433,73 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
|
7269
7433
|
framework: "astro"
|
|
7270
7434
|
};
|
|
7271
7435
|
}
|
|
7272
|
-
async function
|
|
7273
|
-
const pkg = await readPackageJson(serviceDir);
|
|
7274
|
-
const manifestPath = import_node_path40.default.join(serviceDir, "package.json");
|
|
7275
|
-
const project = opts?.project;
|
|
7276
|
-
const empty = {
|
|
7277
|
-
language: "javascript",
|
|
7278
|
-
serviceDir,
|
|
7279
|
-
dependencyEdits: [],
|
|
7280
|
-
entrypointEdits: [],
|
|
7281
|
-
envEdits: [],
|
|
7282
|
-
generatedFiles: []
|
|
7283
|
-
};
|
|
7284
|
-
if (!pkg) return empty;
|
|
7436
|
+
async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
|
|
7285
7437
|
if (hasNextDependency(pkg)) {
|
|
7286
7438
|
const nextConfig = await findNextConfig(serviceDir);
|
|
7287
7439
|
if (nextConfig) {
|
|
7288
|
-
return planNext(serviceDir, pkg, manifestPath, nextConfig, project);
|
|
7440
|
+
return () => planNext(serviceDir, pkg, manifestPath, nextConfig, project);
|
|
7289
7441
|
}
|
|
7290
7442
|
}
|
|
7291
7443
|
if (hasRemixDependency(pkg)) {
|
|
7292
7444
|
const remixEntry = await findRemixEntry(serviceDir);
|
|
7293
7445
|
if (remixEntry) {
|
|
7294
|
-
return planRemix(serviceDir, pkg, manifestPath, remixEntry, project);
|
|
7446
|
+
return () => planRemix(serviceDir, pkg, manifestPath, remixEntry, project);
|
|
7295
7447
|
}
|
|
7296
7448
|
}
|
|
7297
7449
|
if (hasSvelteKitDependency(pkg)) {
|
|
7298
7450
|
const hooks = await findSvelteKitHooks(serviceDir);
|
|
7299
7451
|
const config = await findSvelteKitConfig(serviceDir);
|
|
7300
7452
|
if (hooks || config) {
|
|
7301
|
-
return planSvelteKit(serviceDir, pkg, manifestPath, hooks, project);
|
|
7453
|
+
return () => planSvelteKit(serviceDir, pkg, manifestPath, hooks, project);
|
|
7302
7454
|
}
|
|
7303
7455
|
}
|
|
7304
7456
|
if (hasNuxtDependency(pkg)) {
|
|
7305
7457
|
const nuxtConfig = await findNuxtConfig(serviceDir);
|
|
7306
7458
|
if (nuxtConfig) {
|
|
7307
|
-
return planNuxt(serviceDir, pkg, manifestPath, project);
|
|
7459
|
+
return () => planNuxt(serviceDir, pkg, manifestPath, project);
|
|
7308
7460
|
}
|
|
7309
7461
|
}
|
|
7310
7462
|
if (hasAstroDependency(pkg)) {
|
|
7311
7463
|
const astroConfig = await findAstroConfig(serviceDir);
|
|
7312
7464
|
if (astroConfig) {
|
|
7313
|
-
return planAstro(serviceDir, pkg, manifestPath, project);
|
|
7465
|
+
return () => planAstro(serviceDir, pkg, manifestPath, project);
|
|
7314
7466
|
}
|
|
7315
7467
|
}
|
|
7316
|
-
|
|
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 };
|
|
7494
|
+
}
|
|
7495
|
+
}
|
|
7496
|
+
if (frameworkDispatch) {
|
|
7497
|
+
return frameworkDispatch();
|
|
7498
|
+
}
|
|
7499
|
+
const runtimeKind = await detectRuntimeKind(serviceDir, pkg);
|
|
7500
|
+
if (runtimeKind !== "node") {
|
|
7501
|
+
return { ...empty, runtimeKind };
|
|
7502
|
+
}
|
|
7317
7503
|
if (!entryFile) {
|
|
7318
7504
|
return { ...empty, libOnly: true };
|
|
7319
7505
|
}
|
|
@@ -7331,6 +7517,16 @@ async function plan(serviceDir, opts) {
|
|
|
7331
7517
|
version: sdk.version
|
|
7332
7518
|
});
|
|
7333
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
|
+
}
|
|
7334
7530
|
const entrypointEdits = [];
|
|
7335
7531
|
try {
|
|
7336
7532
|
const raw = await import_node_fs25.promises.readFile(entryFile, "utf8");
|
|
@@ -7347,18 +7543,26 @@ async function plan(serviceDir, opts) {
|
|
|
7347
7543
|
} catch {
|
|
7348
7544
|
return { ...empty, libOnly: true };
|
|
7349
7545
|
}
|
|
7546
|
+
const svcName = serviceNodeName(pkg, serviceDir);
|
|
7547
|
+
const projectName = projectToken(pkg, serviceDir, project);
|
|
7548
|
+
const registrations = nonBundled.map((i) => i.registration);
|
|
7350
7549
|
const generatedFiles = [];
|
|
7351
7550
|
if (!await exists2(otelInitFile)) {
|
|
7352
7551
|
generatedFiles.push({
|
|
7353
7552
|
file: otelInitFile,
|
|
7354
|
-
contents:
|
|
7553
|
+
contents: renderNodeOtelInit(
|
|
7554
|
+
otelInitContents(flavor),
|
|
7555
|
+
svcName,
|
|
7556
|
+
projectName,
|
|
7557
|
+
registrations
|
|
7558
|
+
),
|
|
7355
7559
|
skipIfExists: true
|
|
7356
7560
|
});
|
|
7357
7561
|
}
|
|
7358
7562
|
if (!await exists2(envNeatFile)) {
|
|
7359
7563
|
generatedFiles.push({
|
|
7360
7564
|
file: envNeatFile,
|
|
7361
|
-
contents: renderEnvNeat(
|
|
7565
|
+
contents: renderEnvNeat(svcName, projectName),
|
|
7362
7566
|
skipIfExists: true
|
|
7363
7567
|
});
|
|
7364
7568
|
}
|
|
@@ -7415,6 +7619,22 @@ async function apply(installPlan) {
|
|
|
7415
7619
|
writtenFiles: []
|
|
7416
7620
|
};
|
|
7417
7621
|
}
|
|
7622
|
+
if (installPlan.runtimeKind === "browser-bundle") {
|
|
7623
|
+
return {
|
|
7624
|
+
serviceDir,
|
|
7625
|
+
outcome: "browser-bundle",
|
|
7626
|
+
reason: "browser bundle; Node OTel SDK cannot run here",
|
|
7627
|
+
writtenFiles: []
|
|
7628
|
+
};
|
|
7629
|
+
}
|
|
7630
|
+
if (installPlan.runtimeKind === "react-native") {
|
|
7631
|
+
return {
|
|
7632
|
+
serviceDir,
|
|
7633
|
+
outcome: "react-native",
|
|
7634
|
+
reason: "React Native / Expo target; Node OTel SDK cannot run here",
|
|
7635
|
+
writtenFiles: []
|
|
7636
|
+
};
|
|
7637
|
+
}
|
|
7418
7638
|
if (installPlan.dependencyEdits.length === 0 && installPlan.entrypointEdits.length === 0 && (installPlan.generatedFiles?.length ?? 0) === 0 && installPlan.nextConfigEdit === void 0) {
|
|
7419
7639
|
return {
|
|
7420
7640
|
serviceDir,
|
|
@@ -7870,6 +8090,7 @@ function renderPatch(sections) {
|
|
|
7870
8090
|
init_cjs_shims();
|
|
7871
8091
|
var import_node_fs27 = require("fs");
|
|
7872
8092
|
var import_node_http = __toESM(require("http"), 1);
|
|
8093
|
+
var import_node_net = __toESM(require("net"), 1);
|
|
7873
8094
|
var import_node_path42 = __toESM(require("path"), 1);
|
|
7874
8095
|
var import_node_child_process2 = require("child_process");
|
|
7875
8096
|
var import_node_readline = __toESM(require("readline"), 1);
|
|
@@ -7901,11 +8122,13 @@ async function applyInstallersOver(services, project) {
|
|
|
7901
8122
|
let instrumented = 0;
|
|
7902
8123
|
let already = 0;
|
|
7903
8124
|
let libOnly = 0;
|
|
8125
|
+
let browserBundle = 0;
|
|
8126
|
+
let reactNative = 0;
|
|
7904
8127
|
for (const svc of services) {
|
|
7905
8128
|
const installer = await pickInstaller(svc.dir);
|
|
7906
8129
|
if (!installer) continue;
|
|
7907
8130
|
const plan3 = await installer.plan(svc.dir, { project });
|
|
7908
|
-
if (isEmptyPlan(plan3) && !plan3.libOnly) {
|
|
8131
|
+
if (isEmptyPlan(plan3) && !plan3.libOnly && plan3.runtimeKind === void 0) {
|
|
7909
8132
|
already++;
|
|
7910
8133
|
continue;
|
|
7911
8134
|
}
|
|
@@ -7913,8 +8136,15 @@ async function applyInstallersOver(services, project) {
|
|
|
7913
8136
|
if (outcome.outcome === "instrumented") instrumented++;
|
|
7914
8137
|
else if (outcome.outcome === "already-instrumented") already++;
|
|
7915
8138
|
else if (outcome.outcome === "lib-only") libOnly++;
|
|
8139
|
+
else if (outcome.outcome === "browser-bundle") {
|
|
8140
|
+
browserBundle++;
|
|
8141
|
+
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
8142
|
+
} else if (outcome.outcome === "react-native") {
|
|
8143
|
+
reactNative++;
|
|
8144
|
+
console.log(`skipping ${svc.dir}: React Native target; browser-OTel support lands in a future release.`);
|
|
8145
|
+
}
|
|
7916
8146
|
}
|
|
7917
|
-
return { instrumented, alreadyInstrumented: already, libOnly };
|
|
8147
|
+
return { instrumented, alreadyInstrumented: already, libOnly, browserBundle, reactNative };
|
|
7918
8148
|
}
|
|
7919
8149
|
async function promptYesNo(question) {
|
|
7920
8150
|
const rl = import_node_readline.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -8027,6 +8257,28 @@ async function waitForDaemonReady(restPort, timeoutMs) {
|
|
|
8027
8257
|
stillBootstrapping: projects.filter((p) => p.status === "bootstrapping").map((p) => p.name)
|
|
8028
8258
|
};
|
|
8029
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
|
+
}
|
|
8030
8282
|
function spawnDaemonDetached() {
|
|
8031
8283
|
const here = import_node_path42.default.dirname(new URL(importMetaUrl).pathname);
|
|
8032
8284
|
const candidates = [
|
|
@@ -8118,13 +8370,15 @@ async function runOrchestrator(opts) {
|
|
|
8118
8370
|
if (gi.action !== "unchanged") {
|
|
8119
8371
|
console.log(`${gi.action} .gitignore (neat-out/)`);
|
|
8120
8372
|
}
|
|
8373
|
+
let currentProjectName = opts.project;
|
|
8121
8374
|
try {
|
|
8122
|
-
await addProject({
|
|
8375
|
+
const entry2 = await addProject({
|
|
8123
8376
|
name: opts.project,
|
|
8124
8377
|
path: opts.scanPath,
|
|
8125
8378
|
languages,
|
|
8126
8379
|
status: "active"
|
|
8127
8380
|
});
|
|
8381
|
+
currentProjectName = entry2.name;
|
|
8128
8382
|
} catch (err) {
|
|
8129
8383
|
if (!(err instanceof ProjectNameCollisionError)) throw err;
|
|
8130
8384
|
console.error(`neat: ${err.message}`);
|
|
@@ -8132,6 +8386,20 @@ async function runOrchestrator(opts) {
|
|
|
8132
8386
|
result.exitCode = 1;
|
|
8133
8387
|
return result;
|
|
8134
8388
|
}
|
|
8389
|
+
const siblings = await listProjects();
|
|
8390
|
+
const paused = [];
|
|
8391
|
+
for (const p of siblings) {
|
|
8392
|
+
if (p.name !== currentProjectName && p.status === "active") {
|
|
8393
|
+
await setStatus(p.name, "paused");
|
|
8394
|
+
paused.push(p.name);
|
|
8395
|
+
}
|
|
8396
|
+
}
|
|
8397
|
+
if (paused.length > 0) {
|
|
8398
|
+
const plural = paused.length === 1 ? "" : "s";
|
|
8399
|
+
console.log(
|
|
8400
|
+
`neat: paused ${paused.length} sibling project${plural}; run \`neat resume <name>\` to bring one back active.`
|
|
8401
|
+
);
|
|
8402
|
+
}
|
|
8135
8403
|
if (!runApply) {
|
|
8136
8404
|
result.steps.apply.skipped = true;
|
|
8137
8405
|
console.log("skipped instrumentation (--no-instrument)");
|
|
@@ -8147,6 +8415,14 @@ async function runOrchestrator(opts) {
|
|
|
8147
8415
|
if (await checkDaemonHealth(restPort)) {
|
|
8148
8416
|
result.steps.daemon = "already-running";
|
|
8149
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
|
+
}
|
|
8150
8426
|
try {
|
|
8151
8427
|
spawnDaemonDetached();
|
|
8152
8428
|
} catch (err) {
|
|
@@ -8804,7 +9080,7 @@ async function runSync(opts) {
|
|
|
8804
9080
|
snapshotPath = target;
|
|
8805
9081
|
}
|
|
8806
9082
|
const skipApply = opts.dryRun || opts.noInstrument;
|
|
8807
|
-
const applyTally = skipApply ? { instrumented: 0, alreadyInstrumented: 0, libOnly: 0 } : await applyInstallersOver(persisted.services, entry2.name);
|
|
9083
|
+
const applyTally = skipApply ? { instrumented: 0, alreadyInstrumented: 0, libOnly: 0, browserBundle: 0, reactNative: 0 } : await applyInstallersOver(persisted.services, entry2.name);
|
|
8808
9084
|
const warnings = [];
|
|
8809
9085
|
let daemonState = "skipped";
|
|
8810
9086
|
let exitCode = 0;
|
|
@@ -8954,7 +9230,8 @@ function usage() {
|
|
|
8954
9230
|
console.log(" 0 success");
|
|
8955
9231
|
console.log(" 1 server error (4xx/5xx body printed to stderr)");
|
|
8956
9232
|
console.log(" 2 misuse (missing args, bad flags) \u2014 handled before any network call");
|
|
8957
|
-
console.log(" 3 daemon
|
|
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");
|
|
8958
9235
|
console.log("");
|
|
8959
9236
|
console.log("environment:");
|
|
8960
9237
|
console.log(" NEAT_API_URL base URL for the core REST API (default http://localhost:8080)");
|
|
@@ -9152,7 +9429,7 @@ async function buildPatchSections(services, project) {
|
|
|
9152
9429
|
const installer = await pickInstaller(svc.dir);
|
|
9153
9430
|
if (!installer) continue;
|
|
9154
9431
|
const plan3 = await installer.plan(svc.dir, { project });
|
|
9155
|
-
if (isEmptyPlan(plan3) && !plan3.libOnly) continue;
|
|
9432
|
+
if (isEmptyPlan(plan3) && !plan3.libOnly && plan3.runtimeKind === void 0) continue;
|
|
9156
9433
|
sections.push({ installer: installer.name, plan: plan3 });
|
|
9157
9434
|
}
|
|
9158
9435
|
return sections;
|
|
@@ -9196,13 +9473,15 @@ async function runInit(opts) {
|
|
|
9196
9473
|
written.push(gitignoreResult.file);
|
|
9197
9474
|
}
|
|
9198
9475
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
9476
|
+
let currentProjectName = opts.project;
|
|
9199
9477
|
try {
|
|
9200
|
-
await addProject({
|
|
9478
|
+
const entry2 = await addProject({
|
|
9201
9479
|
name: opts.project,
|
|
9202
9480
|
path: opts.scanPath,
|
|
9203
9481
|
languages,
|
|
9204
9482
|
status: "active"
|
|
9205
9483
|
});
|
|
9484
|
+
currentProjectName = entry2.name;
|
|
9206
9485
|
} catch (err) {
|
|
9207
9486
|
if (err instanceof ProjectNameCollisionError) {
|
|
9208
9487
|
console.error(`neat init: ${err.message}`);
|
|
@@ -9211,11 +9490,27 @@ async function runInit(opts) {
|
|
|
9211
9490
|
}
|
|
9212
9491
|
throw err;
|
|
9213
9492
|
}
|
|
9493
|
+
const siblings = await listProjects();
|
|
9494
|
+
const paused = [];
|
|
9495
|
+
for (const p of siblings) {
|
|
9496
|
+
if (p.name !== currentProjectName && p.status === "active") {
|
|
9497
|
+
await setStatus(p.name, "paused");
|
|
9498
|
+
paused.push(p.name);
|
|
9499
|
+
}
|
|
9500
|
+
}
|
|
9501
|
+
if (paused.length > 0) {
|
|
9502
|
+
const plural = paused.length === 1 ? "" : "s";
|
|
9503
|
+
console.log(
|
|
9504
|
+
`neat: paused ${paused.length} sibling project${plural}; run \`neat resume <name>\` to bring one back active.`
|
|
9505
|
+
);
|
|
9506
|
+
}
|
|
9214
9507
|
if (!opts.noInstall) {
|
|
9215
9508
|
if (opts.apply) {
|
|
9216
9509
|
let instrumented = 0;
|
|
9217
9510
|
let alreadyInstrumented = 0;
|
|
9218
9511
|
let libOnly = 0;
|
|
9512
|
+
let browserBundle = 0;
|
|
9513
|
+
let reactNative = 0;
|
|
9219
9514
|
for (const section of sections) {
|
|
9220
9515
|
const installer = INSTALLERS.find((i) => i.name === section.installer);
|
|
9221
9516
|
if (!installer) continue;
|
|
@@ -9227,13 +9522,24 @@ async function runInit(opts) {
|
|
|
9227
9522
|
alreadyInstrumented++;
|
|
9228
9523
|
} else if (outcome.outcome === "lib-only") {
|
|
9229
9524
|
libOnly++;
|
|
9525
|
+
} else if (outcome.outcome === "browser-bundle") {
|
|
9526
|
+
browserBundle++;
|
|
9527
|
+
console.log(`skipping ${section.plan.serviceDir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
9528
|
+
} else if (outcome.outcome === "react-native") {
|
|
9529
|
+
reactNative++;
|
|
9530
|
+
console.log(`skipping ${section.plan.serviceDir}: React Native target; browser-OTel support lands in a future release.`);
|
|
9230
9531
|
}
|
|
9231
9532
|
}
|
|
9232
9533
|
if (sections.length > 0) {
|
|
9233
9534
|
console.log("");
|
|
9234
|
-
|
|
9235
|
-
`
|
|
9236
|
-
|
|
9535
|
+
const parts = [
|
|
9536
|
+
`instrumented ${instrumented}`,
|
|
9537
|
+
`already-instrumented ${alreadyInstrumented}`,
|
|
9538
|
+
`lib-only ${libOnly}`
|
|
9539
|
+
];
|
|
9540
|
+
if (browserBundle > 0) parts.push(`browser-bundle ${browserBundle}`);
|
|
9541
|
+
if (reactNative > 0) parts.push(`react-native ${reactNative}`);
|
|
9542
|
+
console.log(`apply: ${parts.join(", ")}`);
|
|
9237
9543
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
9238
9544
|
}
|
|
9239
9545
|
} else {
|