@neat.is/core 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,15 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- PROVIDER_DISPATCH,
4
3
  clearDaemonRecord,
5
- getProviderDispatch,
4
+ deprovisionConnector,
5
+ getProviderFieldSchema,
6
+ isPushProvider,
7
+ knownProviderNames,
6
8
  portFromListenAddress,
9
+ provisionConnector,
7
10
  readDaemonRecord,
8
11
  resolveHost,
9
12
  resolveNeatVersion,
10
13
  validateConnectorEntry,
11
14
  writeDaemonRecord
12
- } from "./chunk-X2AMX3QZ.js";
15
+ } from "./chunk-5PVQJLPR.js";
13
16
  import {
14
17
  buildSearchIndex
15
18
  } from "./chunk-BIY46Q6U.js";
@@ -68,10 +71,10 @@ import {
68
71
  startPersistLoop,
69
72
  startStalenessLoop,
70
73
  upsertConnectorEntry
71
- } from "./chunk-GJHEZC5K.js";
74
+ } from "./chunk-CS4GHQO3.js";
72
75
  import {
73
76
  startOtelGrpcReceiver
74
- } from "./chunk-I72HTUOG.js";
77
+ } from "./chunk-MVINCLQM.js";
75
78
  import {
76
79
  __dirname,
77
80
  __require,
@@ -79,7 +82,7 @@ import {
79
82
  buildOtelReceiver,
80
83
  listenSteppingOtlp,
81
84
  readAuthEnv
82
- } from "./chunk-CFDPIMRP.js";
85
+ } from "./chunk-BZ3AJVAC.js";
83
86
 
84
87
  // src/cli.ts
85
88
  import path10 from "path";
@@ -1182,9 +1185,12 @@ ${OTEL_ENDPOINT_RESOLVER_ESM}
1182
1185
  ${OTEL_OTLP_PROTOCOL_JS}
1183
1186
  ${OTEL_OTLP_HEADERS_JS}
1184
1187
 
1185
- import { NodeSDK } from '@opentelemetry/sdk-node'
1186
- import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
1187
- import { trace, context } from '@opentelemetry/api'
1188
+ // Keep dependency loading inside the guard. Static ESM imports are evaluated
1189
+ // before any try/catch and would crash the host app when an install failed.
1190
+ try {
1191
+ const { NodeSDK } = await import('@opentelemetry/sdk-node')
1192
+ const { getNodeAutoInstrumentations } = await import('@opentelemetry/auto-instrumentations-node')
1193
+ const { trace, context } = await import('@opentelemetry/api')
1188
1194
 
1189
1195
  ${CALLSITE_PROCESSOR_JS}
1190
1196
 
@@ -1193,6 +1199,14 @@ __INSTRUMENTATION_BLOCK__
1193
1199
  const sdk = new NodeSDK({ instrumentations })
1194
1200
  sdk.start()
1195
1201
  ${neatWireCaptureSource(false)}
1202
+ } catch (__neatOtelErr) {
1203
+ const __neatMsg = String((__neatOtelErr && __neatOtelErr.message) || __neatOtelErr)
1204
+ if (/Cannot find (?:module|package)|MODULE_NOT_FOUND|ERR_MODULE_NOT_FOUND/.test(__neatMsg)) {
1205
+ console.warn('[neat] OpenTelemetry is not active: its packages are not installed, so this app is running without OBSERVED tracing. Run your package manager install and restart to enable it.')
1206
+ } else {
1207
+ console.warn('[neat] OpenTelemetry failed to start; the app is running without OBSERVED tracing: ' + __neatMsg)
1208
+ }
1209
+ }
1196
1210
  `;
1197
1211
  var OTEL_INIT_TS = `${OTEL_INIT_HEADER}
1198
1212
  ${OTEL_INIT_STAMP}
@@ -1207,9 +1221,12 @@ ${OTEL_ENDPOINT_RESOLVER_ESM}
1207
1221
  ${OTEL_OTLP_PROTOCOL_JS}
1208
1222
  ${OTEL_OTLP_HEADERS_JS}
1209
1223
 
1210
- import { NodeSDK } from '@opentelemetry/sdk-node'
1211
- import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
1212
- import { trace, context } from '@opentelemetry/api'
1224
+ // Top-level await preserves the static import's ordering: the host entry does
1225
+ // not run until instrumentation has started (or safely degraded).
1226
+ try {
1227
+ const { NodeSDK } = await import('@opentelemetry/sdk-node')
1228
+ const { getNodeAutoInstrumentations } = await import('@opentelemetry/auto-instrumentations-node')
1229
+ const { trace, context } = await import('@opentelemetry/api')
1213
1230
 
1214
1231
  ${CALLSITE_PROCESSOR_TS}
1215
1232
 
@@ -1218,6 +1235,14 @@ __INSTRUMENTATION_BLOCK__
1218
1235
  const sdk = new NodeSDK({ instrumentations })
1219
1236
  sdk.start()
1220
1237
  ${neatWireCaptureSource(true)}
1238
+ } catch (__neatOtelErr) {
1239
+ const __neatMsg = String((__neatOtelErr && __neatOtelErr.message) || __neatOtelErr)
1240
+ if (/Cannot find (?:module|package)|MODULE_NOT_FOUND|ERR_MODULE_NOT_FOUND/.test(__neatMsg)) {
1241
+ console.warn('[neat] OpenTelemetry is not active: its packages are not installed, so this app is running without OBSERVED tracing. Run your package manager install and restart to enable it.')
1242
+ } else {
1243
+ console.warn('[neat] OpenTelemetry failed to start; the app is running without OBSERVED tracing: ' + __neatMsg)
1244
+ }
1245
+ }
1221
1246
  `;
1222
1247
  function renderNodeOtelInit(template, serviceName, projectName, registrations = []) {
1223
1248
  const block = registrations.length === 0 ? "" : `
@@ -3513,9 +3538,26 @@ function printSummary(result, graph, dashboardUrl, daemonLog) {
3513
3538
  } else {
3514
3539
  console.log("running locally \u2014 open the dashboard, no token needed");
3515
3540
  }
3541
+ const failedInstalls = (result.steps.apply.packageManagerInstalls ?? []).filter(
3542
+ (i) => i.exitCode !== 0
3543
+ );
3516
3544
  if (daemonLog !== null) {
3517
3545
  console.log("");
3518
3546
  console.log(`daemon running in the background (logs: ${daemonLog})`);
3547
+ }
3548
+ if (failedInstalls.length > 0) {
3549
+ if (daemonLog === null) console.log("");
3550
+ console.log(
3551
+ "instrumentation is wired into your manifests, but it is NOT yet active:"
3552
+ );
3553
+ console.log(
3554
+ "the dependency install did not complete, so the OTel SDK never landed and"
3555
+ );
3556
+ console.log("OBSERVED edges will stay empty until you finish the install. Fix it:");
3557
+ for (const i of failedInstalls) {
3558
+ console.log(` run \`${i.pm} install\` in ${i.cwd}`);
3559
+ }
3560
+ } else if (daemonLog !== null) {
3519
3561
  console.log(
3520
3562
  "next: run your app or your test suite \u2014 OBSERVED edges fill in as it executes,"
3521
3563
  );
@@ -3566,6 +3608,8 @@ function parseConnectorArgs(args) {
3566
3608
  positional: [],
3567
3609
  skipValidate: false,
3568
3610
  plaintext: false,
3611
+ help: false,
3612
+ json: false,
3569
3613
  fields: {}
3570
3614
  };
3571
3615
  const positional = [];
@@ -3579,6 +3623,14 @@ function parseConnectorArgs(args) {
3579
3623
  out.plaintext = true;
3580
3624
  continue;
3581
3625
  }
3626
+ if (arg === "--help" || arg === "-h") {
3627
+ out.help = true;
3628
+ continue;
3629
+ }
3630
+ if (arg === "--json") {
3631
+ out.json = true;
3632
+ continue;
3633
+ }
3582
3634
  if (arg.startsWith("--")) {
3583
3635
  let flag = arg;
3584
3636
  let value;
@@ -3655,19 +3707,20 @@ function plaintextFields(ref) {
3655
3707
  async function connectorAdd(args, deps) {
3656
3708
  let provider = args.positional[0];
3657
3709
  if (!provider && deps.interactive) {
3658
- provider = (await deps.prompt(`Provider (${Object.keys(PROVIDER_DISPATCH).sort().join(", ")}):`)).trim();
3710
+ provider = (await deps.prompt(`Provider (${knownProviderNames().join(", ")}):`)).trim();
3659
3711
  }
3660
3712
  if (!provider) {
3661
3713
  deps.err("neat connector add: a provider is required (e.g. `neat connector add supabase`)");
3662
3714
  return 2;
3663
3715
  }
3664
- const dispatch = getProviderDispatch(provider);
3716
+ const dispatch = getProviderFieldSchema(provider);
3665
3717
  if (!dispatch) {
3666
3718
  deps.err(
3667
- `neat connector add: unknown provider "${provider}". known providers: ${Object.keys(PROVIDER_DISPATCH).sort().join(", ")}`
3719
+ `neat connector add: unknown provider "${provider}". known providers: ${knownProviderNames().join(", ")}`
3668
3720
  );
3669
3721
  return 2;
3670
3722
  }
3723
+ const push = isPushProvider(provider);
3671
3724
  let project = args.project;
3672
3725
  if (project === void 0 && deps.interactive) {
3673
3726
  const answer = (await deps.prompt("Project (blank = the project the daemon is bootstrapping):")).trim();
@@ -3706,7 +3759,7 @@ async function connectorAdd(args, deps) {
3706
3759
  credential,
3707
3760
  ...Object.keys(options).length > 0 ? { options } : {}
3708
3761
  };
3709
- if (!args.skipValidate) {
3762
+ if (!push && !args.skipValidate) {
3710
3763
  const outcome = await validateConnectorEntry(entry2, deps.env, deps.fetchImpl);
3711
3764
  const code = reportPreWriteValidation(outcome, deps);
3712
3765
  if (code !== 0) return code;
@@ -3717,14 +3770,32 @@ async function connectorAdd(args, deps) {
3717
3770
  `neat connector add: storing a literal secret at rest for ${plaintext.join(", ")} \u2014 prefer an env-var reference like \`$PROVIDER_TOKEN\` (pass --plaintext to silence this).`
3718
3771
  );
3719
3772
  }
3773
+ if (push) {
3774
+ if (args.skipValidate) {
3775
+ deps.out("note: --skip-validate has no effect for a push provider \u2014 provisioning the drain is the add.");
3776
+ }
3777
+ const outcome = await provisionConnector(entry2, deps.env, deps.fetchImpl);
3778
+ if (outcome.status !== "ok") {
3779
+ deps.err(
3780
+ `neat connector add: could not provision the ${provider} connector \u2014 ${outcome.reason}. Nothing was written.`
3781
+ );
3782
+ return 1;
3783
+ }
3784
+ if (outcome.options) entry2.options = { ...entry2.options ?? {}, ...outcome.options };
3785
+ if (outcome.note) deps.out(`note: ${outcome.note}`);
3786
+ }
3720
3787
  const { replaced } = await upsertConnectorEntry(entry2, deps.home);
3721
3788
  const verb = replaced ? "updated" : "added";
3722
3789
  const where = project ? `project "${project}"` : "the bootstrapping project";
3723
3790
  deps.out(`${verb} connector "${id}" (${provider}) for ${where}.`);
3724
- if (args.skipValidate) {
3725
- deps.out("skipped validation (--skip-validate) \u2014 the credential is checked at the next daemon poll.");
3791
+ if (push) {
3792
+ deps.out("The drain is live \u2014 traces arrive at the daemon's OTLP receiver. No poll or restart needed.");
3793
+ } else {
3794
+ if (args.skipValidate) {
3795
+ deps.out("skipped validation (--skip-validate) \u2014 the credential is checked at the next daemon poll.");
3796
+ }
3797
+ deps.out("Restart the project's daemon (or start it) to begin polling this connector.");
3726
3798
  }
3727
- deps.out("Restart the project's daemon (or start it) to begin polling this connector.");
3728
3799
  return 0;
3729
3800
  }
3730
3801
  function reportPreWriteValidation(outcome, deps) {
@@ -3750,10 +3821,26 @@ function reportPreWriteValidation(outcome, deps) {
3750
3821
  return 2;
3751
3822
  }
3752
3823
  }
3753
- async function connectorList(deps, filterProject) {
3824
+ async function connectorList(deps, filterProject, json = false) {
3754
3825
  const config = await readConnectorsConfig(deps.home);
3755
3826
  let entries = config.connectors;
3756
3827
  if (filterProject) entries = entries.filter((e) => e.project === filterProject);
3828
+ if (json) {
3829
+ const rows = entries.map((e) => ({
3830
+ id: e.id,
3831
+ provider: e.provider,
3832
+ project: e.project ?? null,
3833
+ kind: isPushProvider(e.provider) ? "push" : "pull",
3834
+ credential: describeCredential(e.credential, deps.env).map((c) => ({
3835
+ ...c.field ? { field: c.field } : {},
3836
+ ref: c.display,
3837
+ ...c.status ? { status: c.status } : { kind: c.kind }
3838
+ })),
3839
+ ...e.options ? { options: e.options } : {}
3840
+ }));
3841
+ deps.out(JSON.stringify(rows, null, 2));
3842
+ return 0;
3843
+ }
3757
3844
  if (entries.length === 0) {
3758
3845
  deps.out(
3759
3846
  filterProject ? `no connectors configured for project "${filterProject}".` : "no connectors configured. run `neat connector add <provider>` to add one."
@@ -3776,6 +3863,22 @@ async function connectorRemove(deps, id) {
3776
3863
  deps.err("neat connector remove: missing <id>. run `neat connector list` to see configured ids.");
3777
3864
  return 2;
3778
3865
  }
3866
+ const config = await readConnectorsConfig(deps.home);
3867
+ const entry2 = config.connectors.find((c) => c.id === id);
3868
+ if (!entry2) {
3869
+ deps.err(`neat connector remove: no connector with id "${id}". run \`neat connector list\` to see configured ids.`);
3870
+ return 1;
3871
+ }
3872
+ if (isPushProvider(entry2.provider)) {
3873
+ const outcome = await deprovisionConnector(entry2, deps.env, deps.fetchImpl);
3874
+ if (outcome.status !== "ok") {
3875
+ deps.err(
3876
+ `neat connector remove: could not delete the ${entry2.provider} drain \u2014 ${outcome.reason}. The entry was kept so you can retry (or delete the drain in the Vercel dashboard, then re-run).`
3877
+ );
3878
+ return 1;
3879
+ }
3880
+ if (outcome.note) deps.out(`note: ${outcome.note}`);
3881
+ }
3779
3882
  const removed = await removeConnectorEntry(id, deps.home);
3780
3883
  if (!removed) {
3781
3884
  deps.err(`neat connector remove: no connector with id "${id}". run \`neat connector list\` to see configured ids.`);
@@ -3816,13 +3919,20 @@ async function connectorTest(deps, id) {
3816
3919
  }
3817
3920
  function printConnectorUsage(write) {
3818
3921
  write("usage: neat connector <add|list|remove|test> [args]");
3922
+ write(` providers: ${knownProviderNames().join(", ")}`);
3923
+ write(" pull (polled): supabase, railway, firebase, cloudflare");
3924
+ write(" push (drains): vercel \u2014 provisions a Vercel trace Drain that forwards");
3925
+ write(" traces to the daemon's OTLP receiver; no app instrumentation");
3819
3926
  write(" add <provider> add a connector; validates the credential first (--skip-validate to skip)");
3820
3927
  write(" flags: --project <name> --credential/--token <$VAR|value> --id <id>");
3821
3928
  write(" --<option> <value> (provider-specific) --plaintext --skip-validate");
3929
+ write(" vercel: neat connector add vercel --token $VERCEL_TOKEN \\");
3930
+ write(" --otel-token $NEAT_OTEL_TOKEN --team-id <teamId> \\");
3931
+ write(" --endpoint https://<public-host>/v1/traces [--project-ids <id,id>]");
3822
3932
  write(" list list configured connectors (credentials shown redacted)");
3823
3933
  write(" flags: --project <name>");
3824
- write(" remove <id> remove a connector by id");
3825
- write(" test <id> re-run the credential validation for an existing connector");
3934
+ write(" remove <id> remove a connector by id (a push provider also has its drain deleted)");
3935
+ write(" test <id> re-run validation for an existing connector (a push provider re-tests its drain)");
3826
3936
  }
3827
3937
  async function runConnectorCommand(rawArgs, deps = {}) {
3828
3938
  const resolved = resolveDeps(deps);
@@ -3832,11 +3942,15 @@ async function runConnectorCommand(rawArgs, deps = {}) {
3832
3942
  return 2;
3833
3943
  }
3834
3944
  const a = parsed.value;
3945
+ if (a.help) {
3946
+ printConnectorUsage(resolved.out);
3947
+ return 0;
3948
+ }
3835
3949
  switch (a.subcommand) {
3836
3950
  case "add":
3837
3951
  return connectorAdd(a, resolved);
3838
3952
  case "list":
3839
- return connectorList(resolved, a.project);
3953
+ return connectorList(resolved, a.project, a.json);
3840
3954
  case "remove":
3841
3955
  return connectorRemove(resolved, a.positional[0]);
3842
3956
  case "test":
@@ -4787,8 +4901,9 @@ function usage2() {
4787
4901
  console.log(" --dry-run run extraction in-memory; do not write");
4788
4902
  console.log(" --no-instrument skip the SDK install apply step");
4789
4903
  console.log(" --json emit the delta summary as JSON");
4790
- console.log(" connector Configure pull-based OBSERVED connectors (supabase, railway,");
4791
- console.log(" firebase, cloudflare). Subcommands:");
4904
+ console.log(" connector Configure OBSERVED connectors \u2014 pull (supabase, railway,");
4905
+ console.log(" firebase, cloudflare) and push (vercel, via a trace Drain).");
4906
+ console.log(" Subcommands:");
4792
4907
  console.log(" add <provider> add a connector; validates the credential");
4793
4908
  console.log(" against the provider first (--skip-validate to skip)");
4794
4909
  console.log(" flags: --project <name>, --credential/--token <$VAR|value>,");