securenow 5.3.3 → 5.4.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/cli/apps.js CHANGED
@@ -4,32 +4,75 @@ const { api, requireAuth } = require('./client');
4
4
  const config = require('./config');
5
5
  const ui = require('./ui');
6
6
 
7
+ const FREE_TRIAL_URL = 'https://freetrial.securenow.ai:4318';
8
+ const DOMAIN_REGEX = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
9
+
10
+ function instanceUrl(inst) {
11
+ if (!inst) return FREE_TRIAL_URL;
12
+ return `${inst.protocol || 'https'}://${inst.host}:4318`;
13
+ }
14
+
15
+ async function fetchInstanceMap() {
16
+ try {
17
+ const data = await api.get('/instances');
18
+ const instances = data.instances || [];
19
+ const map = {};
20
+ for (const inst of instances) {
21
+ map[inst._id] = inst;
22
+ }
23
+ return map;
24
+ } catch {
25
+ return {};
26
+ }
27
+ }
28
+
7
29
  async function list(args, flags) {
8
30
  requireAuth();
9
31
  const s = ui.spinner('Fetching applications');
10
32
 
11
33
  try {
12
- const data = await api.get('/applications');
13
- const apps = data.applications || [];
34
+ const [appData, instMap] = await Promise.all([
35
+ api.get('/applications'),
36
+ fetchInstanceMap(),
37
+ ]);
38
+ const apps = appData.applications || [];
14
39
  s.stop(`Found ${apps.length} application${apps.length !== 1 ? 's' : ''}`);
15
40
  console.log('');
16
41
 
17
42
  if (flags.json) {
18
- ui.json(apps);
43
+ const enriched = apps.map(app => ({
44
+ ...app,
45
+ instanceUrl: instanceUrl(app.instanceId ? instMap[app.instanceId] : null),
46
+ }));
47
+ ui.json(enriched);
19
48
  return;
20
49
  }
21
50
 
22
51
  const defaultApp = config.getDefaultApp();
23
- const rows = apps.map(app => [
24
- app.name + (app.key === defaultApp ? ui.c.cyan(' (default)') : ''),
25
- ui.c.dim(app.key),
26
- app.hosts?.length ? app.hosts.join(', ') : ui.c.dim(''),
27
- ui.timeAgo(app.createdAt),
28
- ]);
52
+ const rows = apps.map(app => {
53
+ const inst = app.instanceId ? instMap[app.instanceId] : null;
54
+ const url = instanceUrl(inst);
55
+ const instLabel = inst ? `${inst.name} ${ui.c.dim(url)}` : `${ui.c.green('Free Trial')} ${ui.c.dim(url)}`;
56
+ return [
57
+ app.name + (app.key === defaultApp ? ui.c.cyan(' (default)') : ''),
58
+ ui.c.dim(app.key),
59
+ instLabel,
60
+ ui.timeAgo(app.createdAt),
61
+ ];
62
+ });
29
63
 
30
- ui.table(['Name', 'Key', 'Hosts', 'Created'], rows);
64
+ ui.table(['Name', 'Key', 'Instance', 'Created'], rows);
31
65
  console.log('');
32
66
 
67
+ if (apps.length > 0) {
68
+ console.log(` ${ui.c.bold('Add to your .env:')}`);
69
+ const first = apps.find(a => a.key === defaultApp) || apps[0];
70
+ const firstInst = first.instanceId ? instMap[first.instanceId] : null;
71
+ console.log(` SECURENOW_APPID=${first.key}`);
72
+ console.log(` SECURENOW_INSTANCE=${instanceUrl(firstInst)}`);
73
+ console.log('');
74
+ }
75
+
33
76
  if (!defaultApp && apps.length > 0) {
34
77
  ui.info(`Tip: Set a default app with ${ui.c.bold('securenow config set defaultApp <key>')}`);
35
78
  console.log('');
@@ -63,17 +106,29 @@ async function create(args, flags) {
63
106
  body.instanceId = await pickInstance();
64
107
  }
65
108
 
109
+ const selectedInstanceId = body.instanceId;
110
+
66
111
  const s = ui.spinner(`Creating application "${name}"`);
67
112
  try {
68
113
  const result = await api.post('/applications', body);
69
114
  const app = result.application || result;
70
115
  s.stop(`Application created`);
71
116
 
117
+ let inst = null;
118
+ if (selectedInstanceId) {
119
+ try {
120
+ const instData = await api.get(`/instances/${selectedInstanceId}`);
121
+ inst = instData.instance || null;
122
+ } catch {}
123
+ }
124
+ const envUrl = instanceUrl(inst);
125
+
72
126
  console.log('');
73
127
  ui.keyValue([
74
128
  ['Name', app.name],
75
129
  ['Key', ui.c.green(ui.c.bold(app.key))],
76
130
  ['ID', ui.c.dim(app._id)],
131
+ ['Instance', inst ? `${inst.name} ${ui.c.dim(`(${envUrl})`)}` : `${ui.c.green('Free Trial')} ${ui.c.dim(`(${envUrl})`)}`],
77
132
  ['Hosts', app.hosts?.length ? app.hosts.join(', ') : ui.c.dim('none')],
78
133
  ]);
79
134
 
@@ -81,12 +136,17 @@ async function create(args, flags) {
81
136
  console.log(` ${ui.c.bold('Add to your .env.local:')}`);
82
137
  console.log('');
83
138
  console.log(` SECURENOW_APPID=${app.key}`);
139
+ console.log(` SECURENOW_INSTANCE=${envUrl}`);
84
140
  console.log('');
85
141
  ui.info(`Set as default: ${ui.c.bold(`securenow config set defaultApp ${app.key}`)}`);
86
142
  console.log('');
87
143
 
88
144
  if (flags.json) {
89
- ui.json(app);
145
+ ui.json({ ...app, instanceUrl: envUrl });
146
+ }
147
+
148
+ if (app.hosts && app.hosts.length > 0) {
149
+ await offerSubdomainDiscovery(app.hosts, selectedInstanceId, flags);
90
150
  }
91
151
  } catch (err) {
92
152
  s.fail('Failed to create application');
@@ -139,11 +199,11 @@ async function pickInstance() {
139
199
  }
140
200
 
141
201
  function openBrowser(url) {
142
- const { execSync } = require('child_process');
202
+ const { execFileSync } = require('child_process');
143
203
  try {
144
- if (process.platform === 'darwin') execSync(`open "${url}"`);
145
- else if (process.platform === 'win32') execSync(`start "" "${url}"`);
146
- else execSync(`xdg-open "${url}"`);
204
+ if (process.platform === 'darwin') execFileSync('open', [url], { stdio: 'ignore' });
205
+ else if (process.platform === 'win32') execFileSync('cmd', ['/c', 'start', '', url], { stdio: 'ignore' });
206
+ else execFileSync('xdg-open', [url], { stdio: 'ignore' });
147
207
  } catch {
148
208
  ui.warn(`Could not open browser. Visit: ${url}`);
149
209
  }
@@ -162,10 +222,19 @@ async function info(args, flags) {
162
222
  try {
163
223
  const data = await api.get(`/applications/${id}`);
164
224
  const app = data.application || data;
225
+
226
+ let inst = null;
227
+ if (app.instanceId) {
228
+ try {
229
+ const instData = await api.get(`/instances/${app.instanceId}`);
230
+ inst = instData.instance || null;
231
+ } catch {}
232
+ }
233
+ const envUrl = instanceUrl(inst);
165
234
  s.stop('Application details loaded');
166
235
 
167
236
  if (flags.json) {
168
- ui.json(app);
237
+ ui.json({ ...app, instanceUrl: envUrl });
169
238
  return;
170
239
  }
171
240
 
@@ -175,11 +244,16 @@ async function info(args, flags) {
175
244
  ui.keyValue([
176
245
  ['Key', ui.c.bold(app.key)],
177
246
  ['ID', ui.c.dim(app._id)],
247
+ ['Instance', inst ? `${inst.name} ${ui.c.dim(`(${envUrl})`)}` : `${ui.c.green('Free Trial')} ${ui.c.dim(`(${envUrl})`)}`],
178
248
  ['Hosts', app.hosts?.length ? app.hosts.join(', ') : ui.c.dim('none')],
179
- ['Instance', app.instanceId || ui.c.dim('default')],
180
249
  ['Created', app.createdAt ? new Date(app.createdAt).toLocaleString() : '—'],
181
250
  ['Updated', app.updatedAt ? new Date(app.updatedAt).toLocaleString() : '—'],
182
251
  ]);
252
+
253
+ console.log('');
254
+ console.log(` ${ui.c.bold('Environment variables:')}`);
255
+ console.log(` SECURENOW_APPID=${app.key}`);
256
+ console.log(` SECURENOW_INSTANCE=${envUrl}`);
183
257
  console.log('');
184
258
  } catch (err) {
185
259
  s.fail('Failed to fetch application');
@@ -224,4 +298,288 @@ async function setDefault(args) {
224
298
  ui.success(`Default application set to ${ui.c.bold(key)}`);
225
299
  }
226
300
 
227
- module.exports = { list, create, info, remove, setDefault };
301
+ function extractRootDomains(hosts) {
302
+ const domains = new Set();
303
+ for (const host of hosts || []) {
304
+ let cleaned = host.trim().toLowerCase();
305
+ try {
306
+ if (cleaned.startsWith('http://') || cleaned.startsWith('https://')) {
307
+ cleaned = new URL(cleaned).hostname;
308
+ }
309
+ } catch {}
310
+ if (DOMAIN_REGEX.test(cleaned)) {
311
+ const parts = cleaned.split('.');
312
+ const root = parts.length > 2 ? parts.slice(-2).join('.') : cleaned;
313
+ domains.add(root);
314
+ }
315
+ }
316
+ return [...domains];
317
+ }
318
+
319
+ async function offerSubdomainDiscovery(hosts, instanceId, flags) {
320
+ if (!process.stdin.isTTY) return;
321
+
322
+ const domains = extractRootDomains(hosts);
323
+ if (domains.length === 0) return;
324
+
325
+ const shouldDiscover = await ui.confirm(
326
+ `Domain${domains.length > 1 ? 's' : ''} detected (${domains.join(', ')}). Discover subdomains and add them as apps?`
327
+ );
328
+ if (!shouldDiscover) return;
329
+
330
+ await discoverAndAdd(domains, instanceId, flags);
331
+ }
332
+
333
+ async function discoverAndAdd(domains, instanceId, flags) {
334
+ const s = ui.spinner(`Scanning ${domains.length} domain${domains.length > 1 ? 's' : ''} for subdomains`);
335
+
336
+ let allSubdomains = [];
337
+ try {
338
+ for (const domain of domains) {
339
+ s.update(`Scanning ${domain}...`);
340
+ const result = await api.get(`/subdomains`, { query: { domain } });
341
+ if (result.success && result.subdomains) {
342
+ allSubdomains.push(...result.subdomains);
343
+ }
344
+ }
345
+ s.stop(`Found ${allSubdomains.length} subdomain${allSubdomains.length !== 1 ? 's' : ''}`);
346
+ } catch (err) {
347
+ s.fail('Failed to discover subdomains');
348
+ throw err;
349
+ }
350
+
351
+ if (allSubdomains.length === 0) {
352
+ ui.info('No subdomains found.');
353
+ return;
354
+ }
355
+
356
+ // Filter out subdomains already registered as apps
357
+ let existingHosts;
358
+ try {
359
+ const appData = await api.get('/applications');
360
+ const apps = appData.applications || [];
361
+ existingHosts = new Set(apps.flatMap(a => (a.hosts || []).map(h => h.toLowerCase())));
362
+ } catch {
363
+ existingHosts = new Set();
364
+ }
365
+
366
+ const newSubdomains = allSubdomains.filter(
367
+ (s) => !existingHosts.has(s.subdomain.toLowerCase())
368
+ );
369
+
370
+ if (newSubdomains.length === 0) {
371
+ ui.info('All discovered subdomains are already registered as apps.');
372
+ return;
373
+ }
374
+
375
+ console.log('');
376
+ ui.info(`${newSubdomains.length} new subdomain${newSubdomains.length !== 1 ? 's' : ''} found (${allSubdomains.length - newSubdomains.length} already tracked):`);
377
+
378
+ if (flags && flags.json) {
379
+ ui.json(newSubdomains);
380
+ return;
381
+ }
382
+
383
+ const choices = newSubdomains.map((sub) => ({
384
+ label: sub.subdomain,
385
+ detail: `IP: ${sub.ip || 'none'}${sub.cloudflare ? ' · Cloudflare' : ''}`,
386
+ value: sub.subdomain,
387
+ }));
388
+
389
+ const selected = await ui.multiSelect(
390
+ 'Which subdomains should be added as monitored apps?',
391
+ choices
392
+ );
393
+
394
+ if (selected.length === 0) {
395
+ ui.info('No subdomains selected.');
396
+ return;
397
+ }
398
+
399
+ const createSpinner = ui.spinner(`Creating ${selected.length} application${selected.length !== 1 ? 's' : ''}`);
400
+ try {
401
+ const result = await api.post('/applications/bulk', {
402
+ subdomains: selected,
403
+ instanceId: instanceId || null,
404
+ });
405
+ const count = result.count || selected.length;
406
+ createSpinner.stop(`Created ${count} application${count !== 1 ? 's' : ''}`);
407
+
408
+ console.log('');
409
+ if (result.applications) {
410
+ const rows = result.applications.map((app) => [
411
+ app.name,
412
+ ui.c.dim(app.key),
413
+ app.hosts?.join(', ') || '',
414
+ ]);
415
+ ui.table(['Name', 'Key', 'Hosts'], rows);
416
+ }
417
+ console.log('');
418
+ } catch (err) {
419
+ createSpinner.fail('Failed to create applications');
420
+ throw err;
421
+ }
422
+ }
423
+
424
+ async function discover(args, flags) {
425
+ requireAuth();
426
+
427
+ const appId = args[0];
428
+ let domains = [];
429
+ let instanceId = null;
430
+
431
+ if (appId) {
432
+ const s = ui.spinner('Fetching application');
433
+ try {
434
+ const data = await api.get(`/applications/${appId}`);
435
+ const app = data.application || data;
436
+ s.stop(`Application: ${app.name}`);
437
+ domains = extractRootDomains(app.hosts);
438
+ instanceId = app.instanceId;
439
+ } catch (err) {
440
+ s.fail('Failed to fetch application');
441
+ throw err;
442
+ }
443
+ } else if (flags.domain) {
444
+ domains = flags.domain.split(',').map(d => d.trim());
445
+ } else {
446
+ const domain = await ui.prompt('Domain to scan (e.g. example.com)');
447
+ if (!domain) {
448
+ ui.error('Domain is required');
449
+ process.exit(1);
450
+ }
451
+ domains = domain.split(',').map(d => d.trim());
452
+ }
453
+
454
+ if (domains.length === 0) {
455
+ ui.error('No domains found in application hosts. Specify a domain with --domain');
456
+ process.exit(1);
457
+ }
458
+
459
+ if (flags.instance) {
460
+ instanceId = flags.instance;
461
+ }
462
+
463
+ await discoverAndAdd(domains, instanceId, flags);
464
+ }
465
+
466
+ async function scan(args, flags) {
467
+ requireAuth();
468
+ const s = ui.spinner('Running subdomain discovery across all applications');
469
+
470
+ try {
471
+ const appData = await api.get('/applications');
472
+ const apps = appData.applications || [];
473
+
474
+ const allDomains = new Set();
475
+ let firstInstanceId = null;
476
+ for (const app of apps) {
477
+ const roots = extractRootDomains(app.hosts);
478
+ for (const r of roots) allDomains.add(r);
479
+ if (!firstInstanceId && app.instanceId) firstInstanceId = app.instanceId;
480
+ }
481
+
482
+ if (allDomains.size === 0) {
483
+ s.stop('No domains found across your applications');
484
+ ui.info('Add domains to your application hosts first, then run this command again.');
485
+ return;
486
+ }
487
+
488
+ s.update(`Found ${allDomains.size} unique domain${allDomains.size !== 1 ? 's' : ''}, scanning...`);
489
+
490
+ const existingHosts = new Set(
491
+ apps.flatMap((a) => (a.hosts || []).map((h) => h.toLowerCase()))
492
+ );
493
+
494
+ let totalNew = 0;
495
+ let totalCreated = 0;
496
+ const allNew = [];
497
+
498
+ for (const domain of allDomains) {
499
+ s.update(`Scanning ${domain}...`);
500
+ try {
501
+ const result = await api.get('/subdomains', { query: { domain } });
502
+ if (result.success && result.subdomains) {
503
+ const newSubs = result.subdomains.filter(
504
+ (sub) => !existingHosts.has(sub.subdomain.toLowerCase())
505
+ );
506
+ totalNew += newSubs.length;
507
+ allNew.push(...newSubs);
508
+ for (const sub of newSubs) {
509
+ existingHosts.add(sub.subdomain.toLowerCase());
510
+ }
511
+ }
512
+ } catch {
513
+ ui.warn(`Failed to scan ${domain}`);
514
+ }
515
+ }
516
+
517
+ s.stop(`Scanned ${allDomains.size} domain${allDomains.size !== 1 ? 's' : ''}, found ${totalNew} new subdomain${totalNew !== 1 ? 's' : ''}`);
518
+
519
+ if (totalNew === 0) {
520
+ ui.success('All subdomains are already tracked. Nothing to add.');
521
+ return;
522
+ }
523
+
524
+ if (flags.yes || flags.force) {
525
+ const createSpinner = ui.spinner(`Creating ${totalNew} application${totalNew !== 1 ? 's' : ''}`);
526
+ try {
527
+ const subdomainNames = allNew.map((s) => s.subdomain);
528
+ const result = await api.post('/applications/bulk', {
529
+ subdomains: subdomainNames,
530
+ instanceId: firstInstanceId || null,
531
+ });
532
+ totalCreated = result.count || subdomainNames.length;
533
+ createSpinner.stop(`Created ${totalCreated} application${totalCreated !== 1 ? 's' : ''}`);
534
+ } catch (err) {
535
+ createSpinner.fail('Failed to create applications');
536
+ throw err;
537
+ }
538
+ } else if (process.stdin.isTTY) {
539
+ const choices = allNew.map((sub) => ({
540
+ label: sub.subdomain,
541
+ detail: `IP: ${sub.ip || 'none'}${sub.cloudflare ? ' · Cloudflare' : ''}`,
542
+ value: sub.subdomain,
543
+ }));
544
+
545
+ const selected = await ui.multiSelect(
546
+ 'Which subdomains should be added as monitored apps?',
547
+ choices
548
+ );
549
+
550
+ if (selected.length === 0) {
551
+ ui.info('No subdomains selected.');
552
+ return;
553
+ }
554
+
555
+ const createSpinner = ui.spinner(`Creating ${selected.length} application${selected.length !== 1 ? 's' : ''}`);
556
+ try {
557
+ const result = await api.post('/applications/bulk', {
558
+ subdomains: selected,
559
+ instanceId: firstInstanceId || null,
560
+ });
561
+ totalCreated = result.count || selected.length;
562
+ createSpinner.stop(`Created ${totalCreated} application${totalCreated !== 1 ? 's' : ''}`);
563
+ } catch (err) {
564
+ createSpinner.fail('Failed to create applications');
565
+ throw err;
566
+ }
567
+ } else {
568
+ console.log('');
569
+ for (const sub of allNew) {
570
+ console.log(` ${sub.subdomain} ${ui.c.dim(`(IP: ${sub.ip || 'none'})`)}`);
571
+ }
572
+ console.log('');
573
+ ui.info(`Run with --yes to auto-create all ${totalNew} apps.`);
574
+ }
575
+
576
+ if (flags.json) {
577
+ ui.json({ domainsScanned: allDomains.size, newSubdomains: totalNew, appsCreated: totalCreated });
578
+ }
579
+ } catch (err) {
580
+ s.fail('Subdomain scan failed');
581
+ throw err;
582
+ }
583
+ }
584
+
585
+ module.exports = { list, create, info, remove, setDefault, discover, scan };
package/cli/auth.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const http = require('http');
4
- const { execSync } = require('child_process');
4
+ const { execFileSync } = require('child_process');
5
5
  const config = require('./config');
6
6
  const { api, CLIError } = require('./client');
7
7
  const ui = require('./ui');
@@ -9,9 +9,9 @@ const ui = require('./ui');
9
9
  function openBrowser(url) {
10
10
  try {
11
11
  const platform = process.platform;
12
- if (platform === 'darwin') execSync(`open "${url}"`);
13
- else if (platform === 'win32') execSync(`start "" "${url}"`);
14
- else execSync(`xdg-open "${url}"`);
12
+ if (platform === 'darwin') execFileSync('open', [url], { stdio: 'ignore' });
13
+ else if (platform === 'win32') execFileSync('cmd', ['/c', 'start', '', url], { stdio: 'ignore' });
14
+ else execFileSync('xdg-open', [url], { stdio: 'ignore' });
15
15
  return true;
16
16
  } catch {
17
17
  return false;
package/cli/ui.js CHANGED
@@ -213,6 +213,50 @@ async function select(question, choices) {
213
213
  return choices[idx].value ?? choices[idx];
214
214
  }
215
215
 
216
+ async function multiSelect(question, choices) {
217
+ console.log(`\n ${c.cyan('?')} ${question}\n`);
218
+ choices.forEach((choice, i) => {
219
+ const label = choice.label || choice;
220
+ const detail = choice.detail ? c.dim(` — ${choice.detail}`) : '';
221
+ console.log(` ${c.bold(String(i + 1).padStart(3))} ${label}${detail}`);
222
+ });
223
+ console.log('');
224
+ console.log(c.dim(` Enter numbers separated by commas, a range (e.g. 1-5), or "all"`));
225
+ const answer = await prompt('Selection');
226
+
227
+ if (!answer) return [];
228
+
229
+ if (answer.toLowerCase() === 'all') {
230
+ return choices.map((ch) => ch.value ?? ch);
231
+ }
232
+
233
+ const indices = new Set();
234
+ for (const part of answer.split(',')) {
235
+ const trimmed = part.trim();
236
+ if (trimmed.includes('-')) {
237
+ const [startStr, endStr] = trimmed.split('-');
238
+ const start = parseInt(startStr, 10);
239
+ const end = parseInt(endStr, 10);
240
+ if (!isNaN(start) && !isNaN(end)) {
241
+ for (let i = Math.min(start, end); i <= Math.max(start, end); i++) {
242
+ indices.add(i);
243
+ }
244
+ }
245
+ } else {
246
+ const num = parseInt(trimmed, 10);
247
+ if (!isNaN(num)) indices.add(num);
248
+ }
249
+ }
250
+
251
+ const selected = [];
252
+ for (const idx of indices) {
253
+ if (idx >= 1 && idx <= choices.length) {
254
+ selected.push(choices[idx - 1].value ?? choices[idx - 1]);
255
+ }
256
+ }
257
+ return selected;
258
+ }
259
+
216
260
  function json(data) {
217
261
  console.log(JSON.stringify(data, null, 2));
218
262
  }
@@ -302,6 +346,7 @@ module.exports = {
302
346
  prompt,
303
347
  confirm,
304
348
  select,
349
+ multiSelect,
305
350
  json,
306
351
  hr,
307
352
  timeAgo,
package/cli.js CHANGED
@@ -64,6 +64,8 @@ const COMMANDS = {
64
64
  info: { desc: 'Show application details', usage: 'securenow apps info <id>', run: (a, f) => require('./cli/apps').info(a, f) },
65
65
  delete: { desc: 'Delete an application', usage: 'securenow apps delete <id> [--force]', run: (a, f) => require('./cli/apps').remove(a, f) },
66
66
  default: { desc: 'Set default application', usage: 'securenow apps default <key>', run: (a, f) => require('./cli/apps').setDefault(a, f) },
67
+ discover: { desc: 'Discover subdomains and add as apps', usage: 'securenow apps discover [appId] [--domain example.com]', run: (a, f) => require('./cli/apps').discover(a, f) },
68
+ scan: { desc: 'Scan all app domains for new subdomains', usage: 'securenow apps scan [--yes]', run: (a, f) => require('./cli/apps').scan(a, f) },
67
69
  },
68
70
  defaultSub: 'list',
69
71
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "5.3.3",
3
+ "version": "5.4.0",
4
4
  "description": "OpenTelemetry instrumentation for Node.js and Next.js - Send traces and logs to any OTLP-compatible backend",
5
5
  "type": "commonjs",
6
6
  "main": "register.js",