@neat.is/core 0.4.3 → 0.4.4
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-NTQHMXWE.js → chunk-G3YGPWJL.js} +2 -2
- package/dist/{chunk-CB2UK4EH.js → chunk-J4YBTD24.js} +44 -3
- package/dist/{chunk-CB2UK4EH.js.map → chunk-J4YBTD24.js.map} +1 -1
- package/dist/{chunk-D5PIJFBE.js → chunk-YJOA7BBF.js} +2 -2
- package/dist/{chunk-KYRIQIPG.js → chunk-ZVNP3ZDH.js} +97 -25
- package/dist/chunk-ZVNP3ZDH.js.map +1 -0
- package/dist/cli.cjs +276 -99
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +184 -79
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +136 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +136 -23
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-QTX2YQJZ.js → otel-grpc-FIERFRGD.js} +3 -3
- package/dist/server.cjs +95 -23
- 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 → chunk-G3YGPWJL.js.map} +0 -0
- /package/dist/{chunk-D5PIJFBE.js.map → chunk-YJOA7BBF.js.map} +0 -0
- /package/dist/{otel-grpc-QTX2YQJZ.js.map → otel-grpc-FIERFRGD.js.map} +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -401,6 +401,64 @@ async function buildOtelReceiver(opts) {
|
|
|
401
401
|
for (const s of spans) queue.push(s);
|
|
402
402
|
drainPromise = drainPromise.then(() => drain());
|
|
403
403
|
};
|
|
404
|
+
const projectQueue = [];
|
|
405
|
+
let projectDraining = false;
|
|
406
|
+
let projectDrainPromise = Promise.resolve();
|
|
407
|
+
const drainProject = async () => {
|
|
408
|
+
if (projectDraining) return;
|
|
409
|
+
projectDraining = true;
|
|
410
|
+
try {
|
|
411
|
+
while (projectQueue.length > 0) {
|
|
412
|
+
const { project, span } = projectQueue.shift();
|
|
413
|
+
try {
|
|
414
|
+
if (opts.onProjectSpan) {
|
|
415
|
+
await opts.onProjectSpan(project, span);
|
|
416
|
+
} else {
|
|
417
|
+
await opts.onSpan(span);
|
|
418
|
+
}
|
|
419
|
+
} catch (err) {
|
|
420
|
+
console.warn(`[neat] otel handler error: ${err.message}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
} finally {
|
|
424
|
+
projectDraining = false;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
const enqueueProject = (project, spans) => {
|
|
428
|
+
if (spans.length === 0) return;
|
|
429
|
+
for (const s of spans) projectQueue.push({ project, span: s });
|
|
430
|
+
projectDrainPromise = projectDrainPromise.then(() => drainProject());
|
|
431
|
+
};
|
|
432
|
+
const legacyEndpointWarned = /* @__PURE__ */ new Set();
|
|
433
|
+
function warnLegacyEndpoint(serviceName) {
|
|
434
|
+
if (legacyEndpointWarned.has(serviceName)) return;
|
|
435
|
+
legacyEndpointWarned.add(serviceName);
|
|
436
|
+
console.warn(
|
|
437
|
+
`[neatd] received span on the global endpoint; migrate OTEL_EXPORTER_OTLP_TRACES_ENDPOINT to /projects/<name>/v1/traces (service.name="${serviceName}").`
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
async function readOtlpBody(req) {
|
|
441
|
+
const ct = (req.headers["content-type"] ?? "").toString().split(";")[0].trim().toLowerCase();
|
|
442
|
+
if (ct === "application/x-protobuf") {
|
|
443
|
+
try {
|
|
444
|
+
const body = await decodeProtobufBody(req.body);
|
|
445
|
+
return { ok: true, body, flavor: "protobuf" };
|
|
446
|
+
} catch (err) {
|
|
447
|
+
return { ok: false, code: 400, error: `protobuf decode failed: ${err.message}` };
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (!ct || ct === "application/json") {
|
|
451
|
+
return { ok: true, body: req.body ?? {}, flavor: "json" };
|
|
452
|
+
}
|
|
453
|
+
return { ok: false, code: 415, error: `unsupported content-type: ${ct}` };
|
|
454
|
+
}
|
|
455
|
+
function sendOtlpSuccess(reply, flavor) {
|
|
456
|
+
if (flavor === "protobuf") {
|
|
457
|
+
const buf = encodeProtobufResponseBody();
|
|
458
|
+
return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
|
|
459
|
+
}
|
|
460
|
+
return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
|
|
461
|
+
}
|
|
404
462
|
app.addContentTypeParser(
|
|
405
463
|
"application/x-protobuf",
|
|
406
464
|
{ parseAs: "buffer", bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024 },
|
|
@@ -410,26 +468,44 @@ async function buildOtelReceiver(opts) {
|
|
|
410
468
|
);
|
|
411
469
|
app.get("/health", async () => ({ ok: true }));
|
|
412
470
|
app.post("/v1/traces", async (req, reply) => {
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
471
|
+
const result = await readOtlpBody(req);
|
|
472
|
+
if (!result.ok) {
|
|
473
|
+
return reply.code(result.code).send({ error: result.error });
|
|
474
|
+
}
|
|
475
|
+
const spans = parseOtlpRequest(result.body);
|
|
476
|
+
for (const s of spans) warnLegacyEndpoint(s.service);
|
|
477
|
+
if (opts.onErrorSpanSync) {
|
|
418
478
|
try {
|
|
419
|
-
|
|
479
|
+
for (const span of spans) {
|
|
480
|
+
if (span.statusCode === 2) await opts.onErrorSpanSync(span);
|
|
481
|
+
}
|
|
420
482
|
} catch (err) {
|
|
421
|
-
return reply.code(
|
|
422
|
-
error: `
|
|
483
|
+
return reply.code(500).send({
|
|
484
|
+
error: `error-event write failed: ${err.message}`
|
|
423
485
|
});
|
|
424
486
|
}
|
|
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
487
|
}
|
|
431
|
-
|
|
432
|
-
|
|
488
|
+
enqueue(spans);
|
|
489
|
+
return sendOtlpSuccess(reply, result.flavor);
|
|
490
|
+
});
|
|
491
|
+
app.post("/projects/:project/v1/traces", async (req, reply) => {
|
|
492
|
+
const project = req.params.project;
|
|
493
|
+
const result = await readOtlpBody(req);
|
|
494
|
+
if (!result.ok) {
|
|
495
|
+
return reply.code(result.code).send({ error: result.error });
|
|
496
|
+
}
|
|
497
|
+
const spans = parseOtlpRequest(result.body);
|
|
498
|
+
if (opts.onProjectErrorSpanSync) {
|
|
499
|
+
try {
|
|
500
|
+
for (const span of spans) {
|
|
501
|
+
if (span.statusCode === 2) await opts.onProjectErrorSpanSync(project, span);
|
|
502
|
+
}
|
|
503
|
+
} catch (err) {
|
|
504
|
+
return reply.code(500).send({
|
|
505
|
+
error: `error-event write failed: ${err.message}`
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
} else if (opts.onErrorSpanSync) {
|
|
433
509
|
try {
|
|
434
510
|
for (const span of spans) {
|
|
435
511
|
if (span.statusCode === 2) await opts.onErrorSpanSync(span);
|
|
@@ -440,17 +516,13 @@ async function buildOtelReceiver(opts) {
|
|
|
440
516
|
});
|
|
441
517
|
}
|
|
442
518
|
}
|
|
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: {} });
|
|
519
|
+
enqueueProject(project, spans);
|
|
520
|
+
return sendOtlpSuccess(reply, result.flavor);
|
|
449
521
|
});
|
|
450
522
|
const decorated = app;
|
|
451
523
|
decorated.flushPending = async () => {
|
|
452
|
-
while (queue.length > 0 || draining) {
|
|
453
|
-
await drainPromise;
|
|
524
|
+
while (queue.length > 0 || draining || projectQueue.length > 0 || projectDraining) {
|
|
525
|
+
await Promise.all([drainPromise, projectDrainPromise]);
|
|
454
526
|
}
|
|
455
527
|
};
|
|
456
528
|
return decorated;
|
|
@@ -6484,45 +6556,31 @@ var import_node_path40 = __toESM(require("path"), 1);
|
|
|
6484
6556
|
init_cjs_shims();
|
|
6485
6557
|
var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
|
|
6486
6558
|
var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
try {
|
|
6491
|
-
require('dotenv').config({ path: path.join(__dirname, '.env.neat') })
|
|
6492
|
-
} catch (err) {
|
|
6493
|
-
// dotenv unavailable \u2014 fall through to process.env.
|
|
6494
|
-
}
|
|
6559
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6560
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6561
|
+
|
|
6495
6562
|
require('@opentelemetry/auto-instrumentations-node/register')
|
|
6496
6563
|
`;
|
|
6497
6564
|
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'
|
|
6503
|
-
|
|
6504
|
-
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
6505
|
-
dotenv.config({ path: path.join(here, '.env.neat') })
|
|
6565
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6566
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6506
6567
|
|
|
6507
6568
|
await import('@opentelemetry/auto-instrumentations-node/register')
|
|
6508
6569
|
`;
|
|
6509
6570
|
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'
|
|
6515
|
-
|
|
6516
|
-
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
6517
|
-
dotenv.config({ path: path.join(here, '.env.neat') })
|
|
6571
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6572
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6518
6573
|
|
|
6519
6574
|
await import('@opentelemetry/auto-instrumentations-node/register')
|
|
6520
6575
|
`;
|
|
6521
|
-
function
|
|
6576
|
+
function renderNodeOtelInit(template, serviceName, projectName) {
|
|
6577
|
+
return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName);
|
|
6578
|
+
}
|
|
6579
|
+
function renderEnvNeat(serviceName, projectName) {
|
|
6522
6580
|
return [
|
|
6523
6581
|
"# Generated by `neat init --apply` (ADR-069).",
|
|
6524
|
-
`OTEL_SERVICE_NAME=${
|
|
6525
|
-
|
|
6582
|
+
`OTEL_SERVICE_NAME=${serviceName}`,
|
|
6583
|
+
`OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/projects/${projectName}/v1/traces`,
|
|
6526
6584
|
""
|
|
6527
6585
|
].join("\n");
|
|
6528
6586
|
}
|
|
@@ -6542,8 +6600,8 @@ export async function register() {
|
|
|
6542
6600
|
}
|
|
6543
6601
|
`;
|
|
6544
6602
|
var NEXT_INSTRUMENTATION_NODE_TS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
6545
|
-
process.env.OTEL_SERVICE_NAME ||= '
|
|
6546
|
-
process.env.
|
|
6603
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6604
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6547
6605
|
|
|
6548
6606
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6549
6607
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
@@ -6551,30 +6609,25 @@ import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentation
|
|
|
6551
6609
|
new NodeSDK({ instrumentations: [getNodeAutoInstrumentations()] }).start()
|
|
6552
6610
|
`;
|
|
6553
6611
|
var NEXT_INSTRUMENTATION_NODE_JS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
6554
|
-
process.env.OTEL_SERVICE_NAME ||= '
|
|
6555
|
-
process.env.
|
|
6612
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6613
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6556
6614
|
|
|
6557
6615
|
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
6558
6616
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
6559
6617
|
|
|
6560
6618
|
new NodeSDK({ instrumentations: [getNodeAutoInstrumentations()] }).start()
|
|
6561
6619
|
`;
|
|
6562
|
-
function renderNextInstrumentationNode(template, projectName) {
|
|
6563
|
-
return template.replace(/
|
|
6620
|
+
function renderNextInstrumentationNode(template, serviceName, projectName) {
|
|
6621
|
+
return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName);
|
|
6564
6622
|
}
|
|
6565
6623
|
var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074). OpenTelemetry SDK hook.";
|
|
6566
6624
|
var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
import path from 'node:path'
|
|
6571
|
-
import dotenv from 'dotenv'
|
|
6625
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6626
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6627
|
+
|
|
6572
6628
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6573
6629
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
6574
6630
|
|
|
6575
|
-
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
6576
|
-
dotenv.config({ path: path.join(here, '.env.neat') })
|
|
6577
|
-
|
|
6578
6631
|
const sdk = new NodeSDK({
|
|
6579
6632
|
serviceName: process.env.OTEL_SERVICE_NAME,
|
|
6580
6633
|
instrumentations: [getNodeAutoInstrumentations()],
|
|
@@ -6582,14 +6635,9 @@ const sdk = new NodeSDK({
|
|
|
6582
6635
|
sdk.start()
|
|
6583
6636
|
`;
|
|
6584
6637
|
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
|
-
}
|
|
6638
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6639
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6640
|
+
|
|
6593
6641
|
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
6594
6642
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
6595
6643
|
|
|
@@ -6599,6 +6647,9 @@ const sdk = new NodeSDK({
|
|
|
6599
6647
|
})
|
|
6600
6648
|
sdk.start()
|
|
6601
6649
|
`;
|
|
6650
|
+
function renderFrameworkOtelInit(template, serviceName, projectName) {
|
|
6651
|
+
return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName);
|
|
6652
|
+
}
|
|
6602
6653
|
var REMIX_OTEL_SERVER_TS = FRAMEWORK_OTEL_INIT_TS_BODY;
|
|
6603
6654
|
var REMIX_OTEL_SERVER_JS = FRAMEWORK_OTEL_INIT_JS_BODY;
|
|
6604
6655
|
var SVELTEKIT_OTEL_INIT_TS = FRAMEWORK_OTEL_INIT_TS_BODY;
|
|
@@ -6661,11 +6712,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
6661
6712
|
var SDK_PACKAGES = [
|
|
6662
6713
|
{ name: "@opentelemetry/api", version: "^1.9.0" },
|
|
6663
6714
|
{ 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" }
|
|
6715
|
+
{ name: "@opentelemetry/auto-instrumentations-node", version: "^0.55.0" }
|
|
6669
6716
|
];
|
|
6670
6717
|
var OTEL_ENV = {
|
|
6671
6718
|
// ADR-069 §4 — endpoint moves into the per-package .env.neat (written
|
|
@@ -6673,13 +6720,36 @@ var OTEL_ENV = {
|
|
|
6673
6720
|
// patch render: it documents the key/value the user can inspect in the
|
|
6674
6721
|
// generated .env.neat.
|
|
6675
6722
|
file: null,
|
|
6676
|
-
key: "
|
|
6677
|
-
value: "http://localhost:4318"
|
|
6723
|
+
key: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
|
|
6724
|
+
value: "http://localhost:4318/projects/<project>/v1/traces"
|
|
6678
6725
|
};
|
|
6679
|
-
function
|
|
6726
|
+
function serviceNodeName(pkg, serviceDir) {
|
|
6727
|
+
return pkg.name ?? import_node_path40.default.basename(serviceDir);
|
|
6728
|
+
}
|
|
6729
|
+
function projectToken(pkg, serviceDir, project) {
|
|
6680
6730
|
if (project && project.length > 0) return project;
|
|
6681
6731
|
return pkg.name ?? import_node_path40.default.basename(serviceDir);
|
|
6682
6732
|
}
|
|
6733
|
+
async function readJsonFile(p) {
|
|
6734
|
+
try {
|
|
6735
|
+
const raw = await import_node_fs25.promises.readFile(p, "utf8");
|
|
6736
|
+
return JSON.parse(raw);
|
|
6737
|
+
} catch {
|
|
6738
|
+
return null;
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
async function detectRuntimeKind(pkgRoot, pkg) {
|
|
6742
|
+
const deps = allDeps(pkg);
|
|
6743
|
+
if ("react-native" in deps || "expo" in deps) return "react-native";
|
|
6744
|
+
const appJson = await readJsonFile(import_node_path40.default.join(pkgRoot, "app.json"));
|
|
6745
|
+
if (appJson && typeof appJson === "object" && "expo" in appJson) {
|
|
6746
|
+
return "react-native";
|
|
6747
|
+
}
|
|
6748
|
+
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) {
|
|
6749
|
+
return "browser-bundle";
|
|
6750
|
+
}
|
|
6751
|
+
return "node";
|
|
6752
|
+
}
|
|
6683
6753
|
async function readPackageJson(serviceDir) {
|
|
6684
6754
|
try {
|
|
6685
6755
|
const raw = await import_node_fs25.promises.readFile(import_node_path40.default.join(serviceDir, "package.json"), "utf8");
|
|
@@ -6926,7 +6996,6 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
6926
6996
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
6927
6997
|
const dependencyEdits = [];
|
|
6928
6998
|
for (const sdk of SDK_PACKAGES) {
|
|
6929
|
-
if (sdk.name === "dotenv") continue;
|
|
6930
6999
|
if (sdk.name in existingDeps) continue;
|
|
6931
7000
|
dependencyEdits.push({
|
|
6932
7001
|
file: manifestPath,
|
|
@@ -6935,7 +7004,8 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
6935
7004
|
version: sdk.version
|
|
6936
7005
|
});
|
|
6937
7006
|
}
|
|
6938
|
-
const
|
|
7007
|
+
const svcName = serviceNodeName(pkg, serviceDir);
|
|
7008
|
+
const projectName = projectToken(pkg, serviceDir, project);
|
|
6939
7009
|
const generatedFiles = [];
|
|
6940
7010
|
if (!await exists2(instrumentationFile)) {
|
|
6941
7011
|
generatedFiles.push({
|
|
@@ -6949,6 +7019,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
6949
7019
|
file: instrumentationNodeFile,
|
|
6950
7020
|
contents: renderNextInstrumentationNode(
|
|
6951
7021
|
useTs ? NEXT_INSTRUMENTATION_NODE_TS : NEXT_INSTRUMENTATION_NODE_JS,
|
|
7022
|
+
svcName,
|
|
6952
7023
|
projectName
|
|
6953
7024
|
),
|
|
6954
7025
|
skipIfExists: true
|
|
@@ -6957,7 +7028,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
6957
7028
|
if (!await exists2(envNeatFile)) {
|
|
6958
7029
|
generatedFiles.push({
|
|
6959
7030
|
file: envNeatFile,
|
|
6960
|
-
contents: renderEnvNeat(projectName),
|
|
7031
|
+
contents: renderEnvNeat(svcName, projectName),
|
|
6961
7032
|
skipIfExists: true
|
|
6962
7033
|
});
|
|
6963
7034
|
}
|
|
@@ -7018,11 +7089,21 @@ async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
|
|
|
7018
7089
|
if (!await exists2(envNeatFile)) {
|
|
7019
7090
|
generatedFiles.push({
|
|
7020
7091
|
file: envNeatFile,
|
|
7021
|
-
contents: renderEnvNeat(
|
|
7092
|
+
contents: renderEnvNeat(
|
|
7093
|
+
serviceNodeName(pkg, serviceDir),
|
|
7094
|
+
projectToken(pkg, serviceDir, project)
|
|
7095
|
+
),
|
|
7022
7096
|
skipIfExists: true
|
|
7023
7097
|
});
|
|
7024
7098
|
}
|
|
7025
7099
|
}
|
|
7100
|
+
function renderFrameworkOtelInitForPkg(template, pkg, serviceDir, project) {
|
|
7101
|
+
return renderFrameworkOtelInit(
|
|
7102
|
+
template,
|
|
7103
|
+
serviceNodeName(pkg, serviceDir),
|
|
7104
|
+
projectToken(pkg, serviceDir, project)
|
|
7105
|
+
);
|
|
7106
|
+
}
|
|
7026
7107
|
function fileImportsOtelHook(raw, specifiers) {
|
|
7027
7108
|
const lines = raw.split(/\r?\n/);
|
|
7028
7109
|
for (const line of lines) {
|
|
@@ -7048,7 +7129,12 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
7048
7129
|
if (!await exists2(otelServerFile)) {
|
|
7049
7130
|
generatedFiles.push({
|
|
7050
7131
|
file: otelServerFile,
|
|
7051
|
-
contents:
|
|
7132
|
+
contents: renderFrameworkOtelInitForPkg(
|
|
7133
|
+
useTs ? REMIX_OTEL_SERVER_TS : REMIX_OTEL_SERVER_JS,
|
|
7134
|
+
pkg,
|
|
7135
|
+
serviceDir,
|
|
7136
|
+
project
|
|
7137
|
+
),
|
|
7052
7138
|
skipIfExists: true
|
|
7053
7139
|
});
|
|
7054
7140
|
}
|
|
@@ -7102,7 +7188,12 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
7102
7188
|
if (!await exists2(otelInitFile)) {
|
|
7103
7189
|
generatedFiles.push({
|
|
7104
7190
|
file: otelInitFile,
|
|
7105
|
-
contents:
|
|
7191
|
+
contents: renderFrameworkOtelInitForPkg(
|
|
7192
|
+
useTs ? SVELTEKIT_OTEL_INIT_TS : SVELTEKIT_OTEL_INIT_JS,
|
|
7193
|
+
pkg,
|
|
7194
|
+
serviceDir,
|
|
7195
|
+
project
|
|
7196
|
+
),
|
|
7106
7197
|
skipIfExists: true
|
|
7107
7198
|
});
|
|
7108
7199
|
}
|
|
@@ -7165,7 +7256,12 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
|
7165
7256
|
if (!await exists2(otelInitFile)) {
|
|
7166
7257
|
generatedFiles.push({
|
|
7167
7258
|
file: otelInitFile,
|
|
7168
|
-
contents:
|
|
7259
|
+
contents: renderFrameworkOtelInitForPkg(
|
|
7260
|
+
useTs ? NUXT_OTEL_INIT_TS : NUXT_OTEL_INIT_JS,
|
|
7261
|
+
pkg,
|
|
7262
|
+
serviceDir,
|
|
7263
|
+
project
|
|
7264
|
+
),
|
|
7169
7265
|
skipIfExists: true
|
|
7170
7266
|
});
|
|
7171
7267
|
}
|
|
@@ -7221,7 +7317,12 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
|
7221
7317
|
if (!await exists2(otelInitFile)) {
|
|
7222
7318
|
generatedFiles.push({
|
|
7223
7319
|
file: otelInitFile,
|
|
7224
|
-
contents:
|
|
7320
|
+
contents: renderFrameworkOtelInitForPkg(
|
|
7321
|
+
useTs ? ASTRO_OTEL_INIT_TS : ASTRO_OTEL_INIT_JS,
|
|
7322
|
+
pkg,
|
|
7323
|
+
serviceDir,
|
|
7324
|
+
project
|
|
7325
|
+
),
|
|
7225
7326
|
skipIfExists: true
|
|
7226
7327
|
});
|
|
7227
7328
|
}
|
|
@@ -7313,6 +7414,10 @@ async function plan(serviceDir, opts) {
|
|
|
7313
7414
|
return planAstro(serviceDir, pkg, manifestPath, project);
|
|
7314
7415
|
}
|
|
7315
7416
|
}
|
|
7417
|
+
const runtimeKind = await detectRuntimeKind(serviceDir, pkg);
|
|
7418
|
+
if (runtimeKind !== "node") {
|
|
7419
|
+
return { ...empty, runtimeKind };
|
|
7420
|
+
}
|
|
7316
7421
|
const entryFile = await resolveEntry(serviceDir, pkg);
|
|
7317
7422
|
if (!entryFile) {
|
|
7318
7423
|
return { ...empty, libOnly: true };
|
|
@@ -7347,18 +7452,20 @@ async function plan(serviceDir, opts) {
|
|
|
7347
7452
|
} catch {
|
|
7348
7453
|
return { ...empty, libOnly: true };
|
|
7349
7454
|
}
|
|
7455
|
+
const svcName = serviceNodeName(pkg, serviceDir);
|
|
7456
|
+
const projectName = projectToken(pkg, serviceDir, project);
|
|
7350
7457
|
const generatedFiles = [];
|
|
7351
7458
|
if (!await exists2(otelInitFile)) {
|
|
7352
7459
|
generatedFiles.push({
|
|
7353
7460
|
file: otelInitFile,
|
|
7354
|
-
contents: otelInitContents(flavor),
|
|
7461
|
+
contents: renderNodeOtelInit(otelInitContents(flavor), svcName, projectName),
|
|
7355
7462
|
skipIfExists: true
|
|
7356
7463
|
});
|
|
7357
7464
|
}
|
|
7358
7465
|
if (!await exists2(envNeatFile)) {
|
|
7359
7466
|
generatedFiles.push({
|
|
7360
7467
|
file: envNeatFile,
|
|
7361
|
-
contents: renderEnvNeat(
|
|
7468
|
+
contents: renderEnvNeat(svcName, projectName),
|
|
7362
7469
|
skipIfExists: true
|
|
7363
7470
|
});
|
|
7364
7471
|
}
|
|
@@ -7415,6 +7522,22 @@ async function apply(installPlan) {
|
|
|
7415
7522
|
writtenFiles: []
|
|
7416
7523
|
};
|
|
7417
7524
|
}
|
|
7525
|
+
if (installPlan.runtimeKind === "browser-bundle") {
|
|
7526
|
+
return {
|
|
7527
|
+
serviceDir,
|
|
7528
|
+
outcome: "browser-bundle",
|
|
7529
|
+
reason: "browser bundle; Node OTel SDK cannot run here",
|
|
7530
|
+
writtenFiles: []
|
|
7531
|
+
};
|
|
7532
|
+
}
|
|
7533
|
+
if (installPlan.runtimeKind === "react-native") {
|
|
7534
|
+
return {
|
|
7535
|
+
serviceDir,
|
|
7536
|
+
outcome: "react-native",
|
|
7537
|
+
reason: "React Native / Expo target; Node OTel SDK cannot run here",
|
|
7538
|
+
writtenFiles: []
|
|
7539
|
+
};
|
|
7540
|
+
}
|
|
7418
7541
|
if (installPlan.dependencyEdits.length === 0 && installPlan.entrypointEdits.length === 0 && (installPlan.generatedFiles?.length ?? 0) === 0 && installPlan.nextConfigEdit === void 0) {
|
|
7419
7542
|
return {
|
|
7420
7543
|
serviceDir,
|
|
@@ -7901,11 +8024,13 @@ async function applyInstallersOver(services, project) {
|
|
|
7901
8024
|
let instrumented = 0;
|
|
7902
8025
|
let already = 0;
|
|
7903
8026
|
let libOnly = 0;
|
|
8027
|
+
let browserBundle = 0;
|
|
8028
|
+
let reactNative = 0;
|
|
7904
8029
|
for (const svc of services) {
|
|
7905
8030
|
const installer = await pickInstaller(svc.dir);
|
|
7906
8031
|
if (!installer) continue;
|
|
7907
8032
|
const plan3 = await installer.plan(svc.dir, { project });
|
|
7908
|
-
if (isEmptyPlan(plan3) && !plan3.libOnly) {
|
|
8033
|
+
if (isEmptyPlan(plan3) && !plan3.libOnly && plan3.runtimeKind === void 0) {
|
|
7909
8034
|
already++;
|
|
7910
8035
|
continue;
|
|
7911
8036
|
}
|
|
@@ -7913,8 +8038,15 @@ async function applyInstallersOver(services, project) {
|
|
|
7913
8038
|
if (outcome.outcome === "instrumented") instrumented++;
|
|
7914
8039
|
else if (outcome.outcome === "already-instrumented") already++;
|
|
7915
8040
|
else if (outcome.outcome === "lib-only") libOnly++;
|
|
8041
|
+
else if (outcome.outcome === "browser-bundle") {
|
|
8042
|
+
browserBundle++;
|
|
8043
|
+
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
8044
|
+
} else if (outcome.outcome === "react-native") {
|
|
8045
|
+
reactNative++;
|
|
8046
|
+
console.log(`skipping ${svc.dir}: React Native target; browser-OTel support lands in a future release.`);
|
|
8047
|
+
}
|
|
7916
8048
|
}
|
|
7917
|
-
return { instrumented, alreadyInstrumented: already, libOnly };
|
|
8049
|
+
return { instrumented, alreadyInstrumented: already, libOnly, browserBundle, reactNative };
|
|
7918
8050
|
}
|
|
7919
8051
|
async function promptYesNo(question) {
|
|
7920
8052
|
const rl = import_node_readline.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -8118,13 +8250,15 @@ async function runOrchestrator(opts) {
|
|
|
8118
8250
|
if (gi.action !== "unchanged") {
|
|
8119
8251
|
console.log(`${gi.action} .gitignore (neat-out/)`);
|
|
8120
8252
|
}
|
|
8253
|
+
let currentProjectName = opts.project;
|
|
8121
8254
|
try {
|
|
8122
|
-
await addProject({
|
|
8255
|
+
const entry2 = await addProject({
|
|
8123
8256
|
name: opts.project,
|
|
8124
8257
|
path: opts.scanPath,
|
|
8125
8258
|
languages,
|
|
8126
8259
|
status: "active"
|
|
8127
8260
|
});
|
|
8261
|
+
currentProjectName = entry2.name;
|
|
8128
8262
|
} catch (err) {
|
|
8129
8263
|
if (!(err instanceof ProjectNameCollisionError)) throw err;
|
|
8130
8264
|
console.error(`neat: ${err.message}`);
|
|
@@ -8132,6 +8266,20 @@ async function runOrchestrator(opts) {
|
|
|
8132
8266
|
result.exitCode = 1;
|
|
8133
8267
|
return result;
|
|
8134
8268
|
}
|
|
8269
|
+
const siblings = await listProjects();
|
|
8270
|
+
const paused = [];
|
|
8271
|
+
for (const p of siblings) {
|
|
8272
|
+
if (p.name !== currentProjectName && p.status === "active") {
|
|
8273
|
+
await setStatus(p.name, "paused");
|
|
8274
|
+
paused.push(p.name);
|
|
8275
|
+
}
|
|
8276
|
+
}
|
|
8277
|
+
if (paused.length > 0) {
|
|
8278
|
+
const plural = paused.length === 1 ? "" : "s";
|
|
8279
|
+
console.log(
|
|
8280
|
+
`neat: paused ${paused.length} sibling project${plural}; run \`neat resume <name>\` to bring one back active.`
|
|
8281
|
+
);
|
|
8282
|
+
}
|
|
8135
8283
|
if (!runApply) {
|
|
8136
8284
|
result.steps.apply.skipped = true;
|
|
8137
8285
|
console.log("skipped instrumentation (--no-instrument)");
|
|
@@ -8804,7 +8952,7 @@ async function runSync(opts) {
|
|
|
8804
8952
|
snapshotPath = target;
|
|
8805
8953
|
}
|
|
8806
8954
|
const skipApply = opts.dryRun || opts.noInstrument;
|
|
8807
|
-
const applyTally = skipApply ? { instrumented: 0, alreadyInstrumented: 0, libOnly: 0 } : await applyInstallersOver(persisted.services, entry2.name);
|
|
8955
|
+
const applyTally = skipApply ? { instrumented: 0, alreadyInstrumented: 0, libOnly: 0, browserBundle: 0, reactNative: 0 } : await applyInstallersOver(persisted.services, entry2.name);
|
|
8808
8956
|
const warnings = [];
|
|
8809
8957
|
let daemonState = "skipped";
|
|
8810
8958
|
let exitCode = 0;
|
|
@@ -9152,7 +9300,7 @@ async function buildPatchSections(services, project) {
|
|
|
9152
9300
|
const installer = await pickInstaller(svc.dir);
|
|
9153
9301
|
if (!installer) continue;
|
|
9154
9302
|
const plan3 = await installer.plan(svc.dir, { project });
|
|
9155
|
-
if (isEmptyPlan(plan3) && !plan3.libOnly) continue;
|
|
9303
|
+
if (isEmptyPlan(plan3) && !plan3.libOnly && plan3.runtimeKind === void 0) continue;
|
|
9156
9304
|
sections.push({ installer: installer.name, plan: plan3 });
|
|
9157
9305
|
}
|
|
9158
9306
|
return sections;
|
|
@@ -9196,13 +9344,15 @@ async function runInit(opts) {
|
|
|
9196
9344
|
written.push(gitignoreResult.file);
|
|
9197
9345
|
}
|
|
9198
9346
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
9347
|
+
let currentProjectName = opts.project;
|
|
9199
9348
|
try {
|
|
9200
|
-
await addProject({
|
|
9349
|
+
const entry2 = await addProject({
|
|
9201
9350
|
name: opts.project,
|
|
9202
9351
|
path: opts.scanPath,
|
|
9203
9352
|
languages,
|
|
9204
9353
|
status: "active"
|
|
9205
9354
|
});
|
|
9355
|
+
currentProjectName = entry2.name;
|
|
9206
9356
|
} catch (err) {
|
|
9207
9357
|
if (err instanceof ProjectNameCollisionError) {
|
|
9208
9358
|
console.error(`neat init: ${err.message}`);
|
|
@@ -9211,11 +9361,27 @@ async function runInit(opts) {
|
|
|
9211
9361
|
}
|
|
9212
9362
|
throw err;
|
|
9213
9363
|
}
|
|
9364
|
+
const siblings = await listProjects();
|
|
9365
|
+
const paused = [];
|
|
9366
|
+
for (const p of siblings) {
|
|
9367
|
+
if (p.name !== currentProjectName && p.status === "active") {
|
|
9368
|
+
await setStatus(p.name, "paused");
|
|
9369
|
+
paused.push(p.name);
|
|
9370
|
+
}
|
|
9371
|
+
}
|
|
9372
|
+
if (paused.length > 0) {
|
|
9373
|
+
const plural = paused.length === 1 ? "" : "s";
|
|
9374
|
+
console.log(
|
|
9375
|
+
`neat: paused ${paused.length} sibling project${plural}; run \`neat resume <name>\` to bring one back active.`
|
|
9376
|
+
);
|
|
9377
|
+
}
|
|
9214
9378
|
if (!opts.noInstall) {
|
|
9215
9379
|
if (opts.apply) {
|
|
9216
9380
|
let instrumented = 0;
|
|
9217
9381
|
let alreadyInstrumented = 0;
|
|
9218
9382
|
let libOnly = 0;
|
|
9383
|
+
let browserBundle = 0;
|
|
9384
|
+
let reactNative = 0;
|
|
9219
9385
|
for (const section of sections) {
|
|
9220
9386
|
const installer = INSTALLERS.find((i) => i.name === section.installer);
|
|
9221
9387
|
if (!installer) continue;
|
|
@@ -9227,13 +9393,24 @@ async function runInit(opts) {
|
|
|
9227
9393
|
alreadyInstrumented++;
|
|
9228
9394
|
} else if (outcome.outcome === "lib-only") {
|
|
9229
9395
|
libOnly++;
|
|
9396
|
+
} else if (outcome.outcome === "browser-bundle") {
|
|
9397
|
+
browserBundle++;
|
|
9398
|
+
console.log(`skipping ${section.plan.serviceDir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
9399
|
+
} else if (outcome.outcome === "react-native") {
|
|
9400
|
+
reactNative++;
|
|
9401
|
+
console.log(`skipping ${section.plan.serviceDir}: React Native target; browser-OTel support lands in a future release.`);
|
|
9230
9402
|
}
|
|
9231
9403
|
}
|
|
9232
9404
|
if (sections.length > 0) {
|
|
9233
9405
|
console.log("");
|
|
9234
|
-
|
|
9235
|
-
`
|
|
9236
|
-
|
|
9406
|
+
const parts = [
|
|
9407
|
+
`instrumented ${instrumented}`,
|
|
9408
|
+
`already-instrumented ${alreadyInstrumented}`,
|
|
9409
|
+
`lib-only ${libOnly}`
|
|
9410
|
+
];
|
|
9411
|
+
if (browserBundle > 0) parts.push(`browser-bundle ${browserBundle}`);
|
|
9412
|
+
if (reactNative > 0) parts.push(`react-native ${reactNative}`);
|
|
9413
|
+
console.log(`apply: ${parts.join(", ")}`);
|
|
9237
9414
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
9238
9415
|
}
|
|
9239
9416
|
} else {
|