@neat.is/core 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-4ASCXBZF.js → chunk-75IBA4UL.js} +34 -5
- package/dist/chunk-75IBA4UL.js.map +1 -0
- package/dist/{chunk-G3PDTGOW.js → chunk-CY67YKNO.js} +2 -2
- package/dist/{chunk-KCEZSFU2.js → chunk-EDHOCFOG.js} +87 -15
- package/dist/chunk-EDHOCFOG.js.map +1 -0
- package/dist/{chunk-BUB3ASD5.js → chunk-ZU2RQRCN.js} +3 -3
- package/dist/chunk-ZU2RQRCN.js.map +1 -0
- package/dist/cli.cjs +433 -65
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +400 -62
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +119 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +200 -18
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +83 -3
- package/dist/neatd.js.map +1 -1
- package/dist/{otel-grpc-J4O2SIBZ.js → otel-grpc-PM4SWPZE.js} +3 -3
- package/dist/server.cjs +34 -5
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-4ASCXBZF.js.map +0 -1
- package/dist/chunk-BUB3ASD5.js.map +0 -1
- package/dist/chunk-KCEZSFU2.js.map +0 -1
- /package/dist/{chunk-G3PDTGOW.js.map → chunk-CY67YKNO.js.map} +0 -0
- /package/dist/{otel-grpc-J4O2SIBZ.js.map → otel-grpc-PM4SWPZE.js.map} +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -358,6 +358,29 @@ interface DaemonHandle {
|
|
|
358
358
|
*
|
|
359
359
|
* Pure function. Daemon callers pass a snapshot of the registry to avoid
|
|
360
360
|
* per-span fs reads.
|
|
361
|
+
*
|
|
362
|
+
* Matching order (ADR-072 — real-world `service.name` rarely equals project
|
|
363
|
+
* name; monorepos publish per-package names like `brief-api` under a
|
|
364
|
+
* project named `brief`):
|
|
365
|
+
*
|
|
366
|
+
* 1. Exact: `entry.name === serviceName`.
|
|
367
|
+
* 2. Hyphen/underscore-separated prefix: `entry.name` is a leading token
|
|
368
|
+
* of `serviceName` (`brief` matches `brief-api`, `brief_worker`).
|
|
369
|
+
* Longest-match wins so `brief-api` beats `brief` when both are
|
|
370
|
+
* registered.
|
|
371
|
+
* 3. Containment as a separator-delimited token (`api` inside
|
|
372
|
+
* `brief-api-staging`).
|
|
373
|
+
*
|
|
374
|
+
* Routing eligibility (ADR-071):
|
|
375
|
+
* - `active` matches at every pass (the steady-state path).
|
|
376
|
+
* - `broken` also matches — the daemon needs the span to reach the broken
|
|
377
|
+
* slot so the ingest-time auto-recover path can attempt a bootstrap and
|
|
378
|
+
* lift the project back to `active`. The router only chooses the target;
|
|
379
|
+
* whether the span actually lands is the ingest handler's decision.
|
|
380
|
+
* - `paused` is intentionally not routed; the operator paused it on
|
|
381
|
+
* purpose, so the span falls through to the default-project flow.
|
|
382
|
+
*
|
|
383
|
+
* Falls back to `DEFAULT_PROJECT` when nothing matches.
|
|
361
384
|
*/
|
|
362
385
|
declare function routeSpanToProject(serviceName: string | undefined, projects: ReadonlyArray<RegistryEntry>): string;
|
|
363
386
|
declare function startDaemon(opts?: DaemonOptions): Promise<DaemonHandle>;
|
package/dist/index.d.ts
CHANGED
|
@@ -358,6 +358,29 @@ interface DaemonHandle {
|
|
|
358
358
|
*
|
|
359
359
|
* Pure function. Daemon callers pass a snapshot of the registry to avoid
|
|
360
360
|
* per-span fs reads.
|
|
361
|
+
*
|
|
362
|
+
* Matching order (ADR-072 — real-world `service.name` rarely equals project
|
|
363
|
+
* name; monorepos publish per-package names like `brief-api` under a
|
|
364
|
+
* project named `brief`):
|
|
365
|
+
*
|
|
366
|
+
* 1. Exact: `entry.name === serviceName`.
|
|
367
|
+
* 2. Hyphen/underscore-separated prefix: `entry.name` is a leading token
|
|
368
|
+
* of `serviceName` (`brief` matches `brief-api`, `brief_worker`).
|
|
369
|
+
* Longest-match wins so `brief-api` beats `brief` when both are
|
|
370
|
+
* registered.
|
|
371
|
+
* 3. Containment as a separator-delimited token (`api` inside
|
|
372
|
+
* `brief-api-staging`).
|
|
373
|
+
*
|
|
374
|
+
* Routing eligibility (ADR-071):
|
|
375
|
+
* - `active` matches at every pass (the steady-state path).
|
|
376
|
+
* - `broken` also matches — the daemon needs the span to reach the broken
|
|
377
|
+
* slot so the ingest-time auto-recover path can attempt a bootstrap and
|
|
378
|
+
* lift the project back to `active`. The router only chooses the target;
|
|
379
|
+
* whether the span actually lands is the ingest handler's decision.
|
|
380
|
+
* - `paused` is intentionally not routed; the operator paused it on
|
|
381
|
+
* purpose, so the span falls through to the default-project flow.
|
|
382
|
+
*
|
|
383
|
+
* Falls back to `DEFAULT_PROJECT` when nothing matches.
|
|
361
384
|
*/
|
|
362
385
|
declare function routeSpanToProject(serviceName: string | undefined, projects: ReadonlyArray<RegistryEntry>): string;
|
|
363
386
|
declare function startDaemon(opts?: DaemonOptions): Promise<DaemonHandle>;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
routeSpanToProject,
|
|
3
3
|
startDaemon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EDHOCFOG.js";
|
|
5
5
|
import {
|
|
6
6
|
ProjectNameCollisionError,
|
|
7
7
|
addProject,
|
|
@@ -37,15 +37,15 @@ import {
|
|
|
37
37
|
thresholdForEdgeType,
|
|
38
38
|
touchLastSeen,
|
|
39
39
|
writeAtomically
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-ZU2RQRCN.js";
|
|
41
41
|
import {
|
|
42
42
|
startOtelGrpcReceiver
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-CY67YKNO.js";
|
|
44
44
|
import {
|
|
45
45
|
buildOtelReceiver,
|
|
46
46
|
logSpanHandler,
|
|
47
47
|
parseOtlpRequest
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-75IBA4UL.js";
|
|
49
49
|
export {
|
|
50
50
|
ProjectNameCollisionError,
|
|
51
51
|
addProject,
|
package/dist/neatd.cjs
CHANGED
|
@@ -257,8 +257,7 @@ function parseOtlpRequest(body) {
|
|
|
257
257
|
}
|
|
258
258
|
return out;
|
|
259
259
|
}
|
|
260
|
-
function
|
|
261
|
-
if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
|
|
260
|
+
function loadProtoRoot() {
|
|
262
261
|
const here = import_node_path35.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
263
262
|
const protoRoot = import_node_path35.default.resolve(here, "..", "proto");
|
|
264
263
|
const root = new import_protobufjs.default.Root();
|
|
@@ -267,11 +266,32 @@ function loadProtobufDecoder() {
|
|
|
267
266
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
268
267
|
{ keepCase: true }
|
|
269
268
|
);
|
|
269
|
+
return root;
|
|
270
|
+
}
|
|
271
|
+
function loadProtobufDecoder() {
|
|
272
|
+
if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
|
|
273
|
+
const root = loadProtoRoot();
|
|
270
274
|
exportTraceServiceRequestType = root.lookupType(
|
|
271
275
|
"opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest"
|
|
272
276
|
);
|
|
273
277
|
return exportTraceServiceRequestType;
|
|
274
278
|
}
|
|
279
|
+
function loadProtobufResponseEncoder() {
|
|
280
|
+
if (exportTraceServiceResponseType) return exportTraceServiceResponseType;
|
|
281
|
+
const root = loadProtoRoot();
|
|
282
|
+
exportTraceServiceResponseType = root.lookupType(
|
|
283
|
+
"opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse"
|
|
284
|
+
);
|
|
285
|
+
return exportTraceServiceResponseType;
|
|
286
|
+
}
|
|
287
|
+
function encodeProtobufResponseBody() {
|
|
288
|
+
if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
|
|
289
|
+
const Type = loadProtobufResponseEncoder();
|
|
290
|
+
const msg = Type.create({});
|
|
291
|
+
const encoded = Type.encode(msg).finish();
|
|
292
|
+
cachedProtobufResponseBody = Buffer.from(encoded);
|
|
293
|
+
return cachedProtobufResponseBody;
|
|
294
|
+
}
|
|
275
295
|
async function decodeProtobufBody(buf) {
|
|
276
296
|
const Type = loadProtobufDecoder();
|
|
277
297
|
const decoded = Type.decode(buf).toJSON();
|
|
@@ -318,7 +338,9 @@ async function buildOtelReceiver(opts) {
|
|
|
318
338
|
app.post("/v1/traces", async (req2, reply) => {
|
|
319
339
|
const ct = (req2.headers["content-type"] ?? "").toString().split(";")[0].trim().toLowerCase();
|
|
320
340
|
let body;
|
|
341
|
+
let responseFlavor;
|
|
321
342
|
if (ct === "application/x-protobuf") {
|
|
343
|
+
responseFlavor = "protobuf";
|
|
322
344
|
try {
|
|
323
345
|
body = await decodeProtobufBody(req2.body);
|
|
324
346
|
} catch (err) {
|
|
@@ -327,6 +349,7 @@ async function buildOtelReceiver(opts) {
|
|
|
327
349
|
});
|
|
328
350
|
}
|
|
329
351
|
} else if (!ct || ct === "application/json") {
|
|
352
|
+
responseFlavor = "json";
|
|
330
353
|
body = req2.body ?? {};
|
|
331
354
|
} else {
|
|
332
355
|
return reply.code(415).send({ error: `unsupported content-type: ${ct}` });
|
|
@@ -344,7 +367,11 @@ async function buildOtelReceiver(opts) {
|
|
|
344
367
|
}
|
|
345
368
|
}
|
|
346
369
|
enqueue(spans);
|
|
347
|
-
|
|
370
|
+
if (responseFlavor === "protobuf") {
|
|
371
|
+
const buf = encodeProtobufResponseBody();
|
|
372
|
+
return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
|
|
373
|
+
}
|
|
374
|
+
return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
|
|
348
375
|
});
|
|
349
376
|
const decorated = app;
|
|
350
377
|
decorated.flushPending = async () => {
|
|
@@ -354,7 +381,7 @@ async function buildOtelReceiver(opts) {
|
|
|
354
381
|
};
|
|
355
382
|
return decorated;
|
|
356
383
|
}
|
|
357
|
-
var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
|
|
384
|
+
var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
358
385
|
var init_otel = __esm({
|
|
359
386
|
"src/otel.ts"() {
|
|
360
387
|
"use strict";
|
|
@@ -364,6 +391,8 @@ var init_otel = __esm({
|
|
|
364
391
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
365
392
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
366
393
|
exportTraceServiceRequestType = null;
|
|
394
|
+
exportTraceServiceResponseType = null;
|
|
395
|
+
cachedProtobufResponseBody = null;
|
|
367
396
|
}
|
|
368
397
|
});
|
|
369
398
|
|
|
@@ -371,6 +400,7 @@ var init_otel = __esm({
|
|
|
371
400
|
init_cjs_shims();
|
|
372
401
|
var import_node_fs22 = require("fs");
|
|
373
402
|
var import_node_path38 = __toESM(require("path"), 1);
|
|
403
|
+
var import_node_module = require("module");
|
|
374
404
|
|
|
375
405
|
// src/daemon.ts
|
|
376
406
|
init_cjs_shims();
|
|
@@ -1585,7 +1615,7 @@ function buildErrorEventForReceiver(span) {
|
|
|
1585
1615
|
service: span.service,
|
|
1586
1616
|
traceId: span.traceId,
|
|
1587
1617
|
spanId: span.spanId,
|
|
1588
|
-
errorMessage: span.exception?.message ??
|
|
1618
|
+
errorMessage: span.exception?.message ?? "unknown error",
|
|
1589
1619
|
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
1590
1620
|
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
1591
1621
|
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
@@ -1677,7 +1707,7 @@ async function handleSpan(ctx, span) {
|
|
|
1677
1707
|
service: span.service,
|
|
1678
1708
|
traceId: span.traceId,
|
|
1679
1709
|
spanId: span.spanId,
|
|
1680
|
-
errorMessage: span.exception?.message ??
|
|
1710
|
+
errorMessage: span.exception?.message ?? "unknown error",
|
|
1681
1711
|
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
1682
1712
|
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
1683
1713
|
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
@@ -5054,13 +5084,35 @@ function neatHomeFor(opts) {
|
|
|
5054
5084
|
function routeSpanToProject(serviceName, projects) {
|
|
5055
5085
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
5056
5086
|
for (const entry2 of projects) {
|
|
5057
|
-
if (entry2.status
|
|
5058
|
-
if (entry2.languages.length === 0) {
|
|
5059
|
-
}
|
|
5087
|
+
if (entry2.status === "paused") continue;
|
|
5060
5088
|
if (entry2.name === serviceName) return entry2.name;
|
|
5061
5089
|
}
|
|
5090
|
+
const candidates = [];
|
|
5091
|
+
for (const entry2 of projects) {
|
|
5092
|
+
if (entry2.status === "paused") continue;
|
|
5093
|
+
if (isTokenPrefix(entry2.name, serviceName)) candidates.push(entry2);
|
|
5094
|
+
}
|
|
5095
|
+
if (candidates.length > 0) {
|
|
5096
|
+
candidates.sort((a, b) => b.name.length - a.name.length);
|
|
5097
|
+
return candidates[0].name;
|
|
5098
|
+
}
|
|
5099
|
+
for (const entry2 of projects) {
|
|
5100
|
+
if (entry2.status === "paused") continue;
|
|
5101
|
+
if (isTokenContained(entry2.name, serviceName)) return entry2.name;
|
|
5102
|
+
}
|
|
5062
5103
|
return DEFAULT_PROJECT;
|
|
5063
5104
|
}
|
|
5105
|
+
function isTokenPrefix(prefix, full) {
|
|
5106
|
+
if (prefix.length >= full.length) return false;
|
|
5107
|
+
if (!full.startsWith(prefix)) return false;
|
|
5108
|
+
const sep = full.charAt(prefix.length);
|
|
5109
|
+
return sep === "-" || sep === "_";
|
|
5110
|
+
}
|
|
5111
|
+
function isTokenContained(needle, haystack) {
|
|
5112
|
+
if (!haystack.includes(needle)) return false;
|
|
5113
|
+
const tokens = haystack.split(/[-_]/);
|
|
5114
|
+
return tokens.includes(needle);
|
|
5115
|
+
}
|
|
5064
5116
|
async function bootstrapProject(entry2) {
|
|
5065
5117
|
const paths = pathsForProject(entry2.name, import_node_path36.default.join(entry2.path, "neat-out"));
|
|
5066
5118
|
try {
|
|
@@ -5140,6 +5192,17 @@ async function startDaemon(opts = {}) {
|
|
|
5140
5192
|
`);
|
|
5141
5193
|
const slots = /* @__PURE__ */ new Map();
|
|
5142
5194
|
const registry = new Projects();
|
|
5195
|
+
const DROP_WARN_INTERVAL_MS = 6e4;
|
|
5196
|
+
const lastDropWarnAt = /* @__PURE__ */ new Map();
|
|
5197
|
+
function warnDroppedSpan(project, reason) {
|
|
5198
|
+
const now = Date.now();
|
|
5199
|
+
const prev = lastDropWarnAt.get(project) ?? 0;
|
|
5200
|
+
if (now - prev < DROP_WARN_INTERVAL_MS) return;
|
|
5201
|
+
lastDropWarnAt.set(project, now);
|
|
5202
|
+
console.warn(
|
|
5203
|
+
`[neatd] dropping span for project "${project}" \u2014 project status: broken (${reason}). Run \`neatd reload\` to retry bootstrap.`
|
|
5204
|
+
);
|
|
5205
|
+
}
|
|
5143
5206
|
function upsertRegistryFromSlot(slot) {
|
|
5144
5207
|
if (slot.status !== "active") return;
|
|
5145
5208
|
registry.set(slot.entry.name, {
|
|
@@ -5148,12 +5211,38 @@ async function startDaemon(opts = {}) {
|
|
|
5148
5211
|
graph: slot.graph
|
|
5149
5212
|
});
|
|
5150
5213
|
}
|
|
5214
|
+
async function tryRecoverSlot(entry2) {
|
|
5215
|
+
try {
|
|
5216
|
+
const fresh = await bootstrapProject(entry2);
|
|
5217
|
+
slots.set(entry2.name, fresh);
|
|
5218
|
+
upsertRegistryFromSlot(fresh);
|
|
5219
|
+
if (fresh.status === "active") {
|
|
5220
|
+
await setStatus(entry2.name, "active").catch(() => {
|
|
5221
|
+
});
|
|
5222
|
+
console.log(
|
|
5223
|
+
`neatd: project "${entry2.name}" recovered from broken \u2014 active`
|
|
5224
|
+
);
|
|
5225
|
+
}
|
|
5226
|
+
return fresh;
|
|
5227
|
+
} catch (err) {
|
|
5228
|
+
console.warn(
|
|
5229
|
+
`neatd: project "${entry2.name}" still broken after recovery attempt \u2014 ${err.message}`
|
|
5230
|
+
);
|
|
5231
|
+
return slots.get(entry2.name);
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5151
5234
|
async function loadAll() {
|
|
5152
5235
|
const projects = await listProjects();
|
|
5153
5236
|
const seen = /* @__PURE__ */ new Set();
|
|
5154
5237
|
for (const entry2 of projects) {
|
|
5155
5238
|
seen.add(entry2.name);
|
|
5156
|
-
|
|
5239
|
+
const existing = slots.get(entry2.name);
|
|
5240
|
+
if (existing) {
|
|
5241
|
+
if (existing.status === "broken") {
|
|
5242
|
+
await tryRecoverSlot(entry2);
|
|
5243
|
+
}
|
|
5244
|
+
continue;
|
|
5245
|
+
}
|
|
5157
5246
|
try {
|
|
5158
5247
|
const slot = await bootstrapProject(entry2);
|
|
5159
5248
|
slots.set(entry2.name, slot);
|
|
@@ -5210,13 +5299,28 @@ async function startDaemon(opts = {}) {
|
|
|
5210
5299
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
5211
5300
|
);
|
|
5212
5301
|
}
|
|
5302
|
+
async function resolveTargetSlot(serviceName) {
|
|
5303
|
+
const liveEntries = await listProjects().catch(() => []);
|
|
5304
|
+
const target = routeSpanToProject(serviceName, liveEntries);
|
|
5305
|
+
let slot = slots.get(target) ?? slots.get(DEFAULT_PROJECT);
|
|
5306
|
+
if (!slot) return null;
|
|
5307
|
+
if (slot.status === "broken") {
|
|
5308
|
+
const entry2 = liveEntries.find((e) => e.name === slot.entry.name);
|
|
5309
|
+
if (entry2) {
|
|
5310
|
+
slot = await tryRecoverSlot(entry2);
|
|
5311
|
+
}
|
|
5312
|
+
if (slot.status !== "active") {
|
|
5313
|
+
warnDroppedSpan(slot.entry.name, slot.errorReason ?? "unknown");
|
|
5314
|
+
return null;
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
5317
|
+
return slot.status === "active" ? slot : null;
|
|
5318
|
+
}
|
|
5213
5319
|
try {
|
|
5214
5320
|
otlpApp = await buildOtelReceiver({
|
|
5215
5321
|
onSpan: async (span) => {
|
|
5216
|
-
const
|
|
5217
|
-
|
|
5218
|
-
const slot = slots.get(target) ?? slots.get(DEFAULT_PROJECT);
|
|
5219
|
-
if (!slot || slot.status !== "active") return;
|
|
5322
|
+
const slot = await resolveTargetSlot(span.service);
|
|
5323
|
+
if (!slot) return;
|
|
5220
5324
|
await handleSpan(
|
|
5221
5325
|
{
|
|
5222
5326
|
graph: slot.graph,
|
|
@@ -5229,10 +5333,8 @@ async function startDaemon(opts = {}) {
|
|
|
5229
5333
|
);
|
|
5230
5334
|
},
|
|
5231
5335
|
onErrorSpanSync: async (span) => {
|
|
5232
|
-
const
|
|
5233
|
-
|
|
5234
|
-
const slot = slots.get(target) ?? slots.get(DEFAULT_PROJECT);
|
|
5235
|
-
if (!slot || slot.status !== "active") return;
|
|
5336
|
+
const slot = await resolveTargetSlot(span.service);
|
|
5337
|
+
if (!slot) return;
|
|
5236
5338
|
await makeErrorSpanWriter(slot.paths.errorsPath)(span);
|
|
5237
5339
|
}
|
|
5238
5340
|
});
|
|
@@ -5389,7 +5491,83 @@ async function spawnWebUI(restPort) {
|
|
|
5389
5491
|
return { child, port, stop };
|
|
5390
5492
|
}
|
|
5391
5493
|
|
|
5494
|
+
// src/version-skew.ts
|
|
5495
|
+
init_cjs_shims();
|
|
5496
|
+
var import_promises = require("timers/promises");
|
|
5497
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/neat.is/latest";
|
|
5498
|
+
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
5499
|
+
async function fetchRegistryVersion(url, timeoutMs, fetchImpl) {
|
|
5500
|
+
const controller = new AbortController();
|
|
5501
|
+
let timedOut = false;
|
|
5502
|
+
const timer = (0, import_promises.setTimeout)(timeoutMs).then(() => {
|
|
5503
|
+
timedOut = true;
|
|
5504
|
+
controller.abort();
|
|
5505
|
+
});
|
|
5506
|
+
try {
|
|
5507
|
+
const res = await Promise.race([
|
|
5508
|
+
fetchImpl(url, { signal: controller.signal, headers: { accept: "application/json" } }),
|
|
5509
|
+
timer.then(() => null)
|
|
5510
|
+
]);
|
|
5511
|
+
if (timedOut || !res) return null;
|
|
5512
|
+
if (!res.ok) return null;
|
|
5513
|
+
const body = await res.json();
|
|
5514
|
+
if (typeof body.version !== "string" || body.version.length === 0) return null;
|
|
5515
|
+
return body.version;
|
|
5516
|
+
} catch {
|
|
5517
|
+
return null;
|
|
5518
|
+
} finally {
|
|
5519
|
+
if (!timedOut) controller.abort();
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5522
|
+
function isLocalBehind(local, remote) {
|
|
5523
|
+
if (local === remote) return false;
|
|
5524
|
+
const parse10 = (v) => {
|
|
5525
|
+
const core = v.split(/[-+]/)[0] ?? v;
|
|
5526
|
+
return core.split(".").map((s) => {
|
|
5527
|
+
const n = Number.parseInt(s, 10);
|
|
5528
|
+
return Number.isFinite(n) ? n : 0;
|
|
5529
|
+
});
|
|
5530
|
+
};
|
|
5531
|
+
const a = parse10(local);
|
|
5532
|
+
const b = parse10(remote);
|
|
5533
|
+
const len = Math.max(a.length, b.length);
|
|
5534
|
+
for (let i = 0; i < len; i++) {
|
|
5535
|
+
const av = a[i] ?? 0;
|
|
5536
|
+
const bv = b[i] ?? 0;
|
|
5537
|
+
if (av < bv) return true;
|
|
5538
|
+
if (av > bv) return false;
|
|
5539
|
+
}
|
|
5540
|
+
return false;
|
|
5541
|
+
}
|
|
5542
|
+
async function checkVersionSkew(opts) {
|
|
5543
|
+
const url = opts.registryUrl ?? NPM_REGISTRY_URL;
|
|
5544
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
5545
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
5546
|
+
const warn = opts.warn ?? ((m) => console.warn(m));
|
|
5547
|
+
const remote = await fetchRegistryVersion(url, timeoutMs, fetchImpl);
|
|
5548
|
+
if (!remote) return { remoteVersion: null, skewed: false, warned: false };
|
|
5549
|
+
if (!isLocalBehind(opts.localVersion, remote)) {
|
|
5550
|
+
return { remoteVersion: remote, skewed: false, warned: false };
|
|
5551
|
+
}
|
|
5552
|
+
warn(
|
|
5553
|
+
`[neatd] running neat.is@${opts.localVersion} but neat.is@${remote} is on npm \u2014 run \`npm install -g neat.is@latest\` to update`
|
|
5554
|
+
);
|
|
5555
|
+
return { remoteVersion: remote, skewed: true, warned: true };
|
|
5556
|
+
}
|
|
5557
|
+
|
|
5392
5558
|
// src/neatd.ts
|
|
5559
|
+
function localVersion() {
|
|
5560
|
+
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
5561
|
+
return process.env.NEAT_LOCAL_VERSION;
|
|
5562
|
+
}
|
|
5563
|
+
try {
|
|
5564
|
+
const req2 = (0, import_node_module.createRequire)(importMetaUrl);
|
|
5565
|
+
const pkg = req2("../package.json");
|
|
5566
|
+
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
5567
|
+
} catch {
|
|
5568
|
+
return "0.0.0";
|
|
5569
|
+
}
|
|
5570
|
+
}
|
|
5393
5571
|
function neatHome2() {
|
|
5394
5572
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
5395
5573
|
return import_node_path38.default.resolve(process.env.NEAT_HOME);
|
|
@@ -5419,6 +5597,10 @@ async function cmdStart() {
|
|
|
5419
5597
|
console.log(`neatd: registry at ${registryPath()}`);
|
|
5420
5598
|
if (handle.restAddress) console.log(`neatd: REST \u2192 ${handle.restAddress}`);
|
|
5421
5599
|
if (handle.otlpAddress) console.log(`neatd: OTLP \u2192 ${handle.otlpAddress}`);
|
|
5600
|
+
if (process.env.NEAT_DISABLE_VERSION_CHECK !== "1") {
|
|
5601
|
+
void checkVersionSkew({ localVersion: localVersion() }).catch(() => {
|
|
5602
|
+
});
|
|
5603
|
+
}
|
|
5422
5604
|
const skipWeb = process.env.NEAT_WEB_DISABLED === "1";
|
|
5423
5605
|
let web = null;
|
|
5424
5606
|
if (!skipWeb) {
|