securenow 5.6.0 → 5.7.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SecureNow
2
2
 
3
- OpenTelemetry instrumentation for Node.js and Next.js applications - send **traces and logs** to any OTLP-compatible backend (including SecureNow).
3
+ OpenTelemetry instrumentation for Node.js, Next.js, and Nuxt applications - send **traces and logs** to any OTLP-compatible backend (including SecureNow).
4
4
 
5
5
  **Official npm package:** [securenow](http://securenow.ai/)
6
6
 
@@ -8,6 +8,39 @@ OpenTelemetry instrumentation for Node.js and Next.js applications - send **trac
8
8
 
9
9
  ## 🚀 Quick Start
10
10
 
11
+ ### For Any Node.js App (Express, Fastify, NestJS, Koa, Hapi, etc.)
12
+
13
+ ```bash
14
+ # 1. Install
15
+ npm install securenow
16
+
17
+ # 2. Set env vars
18
+ export SECURENOW_APPID=my-app
19
+ export SECURENOW_INSTANCE=https://freetrial.securenow.ai:4318
20
+
21
+ # 3. Add -r securenow/register to your start command
22
+ node -r securenow/register src/app.js
23
+ ```
24
+
25
+ That's it. One `-r` flag is all you need — ESM and CJS apps are handled automatically (Node >=20.6 auto-registers the ESM loader hook).
26
+
27
+ > **package.json** example:
28
+ > ```json
29
+ > "scripts": {
30
+ > "start": "node -r securenow/register src/index.js",
31
+ > "dev": "node -r securenow/register --watch src/index.js"
32
+ > }
33
+ > ```
34
+
35
+ You can also use `NODE_OPTIONS` so your existing scripts stay unchanged:
36
+ ```bash
37
+ NODE_OPTIONS="-r securenow/register" npm start
38
+ ```
39
+
40
+ See the [All Frameworks Quick Start](./docs/ALL-FRAMEWORKS-QUICKSTART.md) for tested setup guides.
41
+
42
+ ---
43
+
11
44
  ### For Next.js Applications
12
45
 
13
46
  **The easiest way to add observability to Next.js!**
@@ -40,6 +73,32 @@ npx securenow init
40
73
 
41
74
  ---
42
75
 
76
+ ### For Nuxt 3 Applications
77
+
78
+ ```bash
79
+ # 1. Install
80
+ npm install securenow
81
+ ```
82
+
83
+ Add the module to your `nuxt.config.ts`:
84
+
85
+ ```ts
86
+ export default defineNuxtConfig({
87
+ modules: ['securenow/nuxt'],
88
+ });
89
+ ```
90
+
91
+ Set environment variables in `.env`:
92
+
93
+ ```bash
94
+ SECURENOW_APPID=my-nuxt-app
95
+ SECURENOW_INSTANCE=https://freetrial.securenow.ai:4318
96
+ ```
97
+
98
+ **Done!** All server-side requests are now traced automatically. See the [Nuxt 3 Complete Guide](./docs/NUXT-GUIDE.md) for details.
99
+
100
+ ---
101
+
43
102
  ### CLI — Manage Everything from the Terminal
44
103
 
45
104
  ```bash
@@ -70,41 +129,6 @@ Run `npx securenow help` for all commands. See the [CLI Reference](#cli-referenc
70
129
 
71
130
  ---
72
131
 
73
- ### For Node.js Applications (Express, Fastify, NestJS, etc.)
74
-
75
- #### Tracing Only
76
-
77
- ```bash
78
- # 1. Install
79
- npm install securenow
80
-
81
- # 2. Set environment variables
82
- export SECURENOW_APPID=my-app
83
- export SECURENOW_INSTANCE=http://your-otlp-collector:4318
84
-
85
- # 3. Run with preload
86
- NODE_OPTIONS="-r securenow/register" node app.js
87
- # or
88
- NODE_OPTIONS="-r securenow/register" npm start
89
- ```
90
-
91
- #### Tracing + Logging (Recommended)
92
-
93
- ```bash
94
- # 1. Install
95
- npm install securenow
96
-
97
- # 2. Set environment variables
98
- export SECURENOW_APPID=my-app
99
- export SECURENOW_INSTANCE=http://your-otlp-collector:4318
100
- export SECURENOW_LOGGING_ENABLED=1
101
-
102
- # 3. Run with preload (adds logging)
103
- NODE_OPTIONS="-r securenow/register -r securenow/console-instrumentation" node app.js
104
-
105
- # Now all console.log/info/warn/error automatically go to your OTLP backend (for example SecureNow)!
106
- ```
107
-
108
132
  ---
109
133
 
110
134
  ## 📦 Installation
@@ -161,6 +185,7 @@ SecureNow automatically instruments:
161
185
 
162
186
  ### Web Frameworks
163
187
  - ✅ Next.js (App Router & Pages Router)
188
+ - ✅ Nuxt 3 (Nitro server)
164
189
  - ✅ Express.js
165
190
  - ✅ Fastify
166
191
  - ✅ NestJS
@@ -191,10 +216,12 @@ SecureNow automatically instruments:
191
216
 
192
217
  ### Quick Starts
193
218
  - **[Next.js Quick Start](./docs/NEXTJS-QUICKSTART.md)** - Get started in 30 seconds
219
+ - **[Nuxt 3 Guide](./docs/NUXT-GUIDE.md)** - One-line Nuxt module setup
194
220
  - **[Logging Quick Start](./docs/LOGGING-QUICKSTART.md)** - Add logging in 2 minutes
195
221
 
196
222
  ### Complete Guides
197
223
  - **[Next.js Complete Guide](./docs/NEXTJS-GUIDE.md)** - Full Next.js integration guide
224
+ - **[Nuxt 3 Complete Guide](./docs/NUXT-GUIDE.md)** - Full Nuxt 3 integration guide
198
225
  - **[Logging Complete Guide](./docs/LOGGING-GUIDE.md)** - Full logging setup for all frameworks
199
226
  - **[📚 Complete Documentation](./docs/INDEX.md)** - All guides and references
200
227
 
@@ -207,6 +234,15 @@ SecureNow automatically instruments:
207
234
 
208
235
  After installing the package, the `securenow` CLI is available via `npx securenow` or globally after `npm install -g securenow`.
209
236
 
237
+ ### Run (convenience wrapper)
238
+
239
+ | Command | Description |
240
+ |---------|-------------|
241
+ | `securenow run <script>` | Run a Node.js app with `-r securenow/register` injected |
242
+ | `securenow run --watch <script>` | Same, with Node.js watch mode |
243
+
244
+ Most users won't need this — just add `-r securenow/register` to your existing start script.
245
+
210
246
  ### Authentication
211
247
 
212
248
  | Command | Description |
package/cli/monitor.js CHANGED
@@ -105,7 +105,19 @@ async function tracesAnalyze(args, flags) {
105
105
 
106
106
  const s = ui.spinner('Analyzing trace with AI');
107
107
  try {
108
- const result = await api.post('/traces/analyze', { traceId });
108
+ let result = await api.post('/traces/analyze', { traceId });
109
+
110
+ if (result.analysisId && result.status === 'running') {
111
+ const analysisId = result.analysisId;
112
+ for (let i = 0; i < 120; i++) {
113
+ await new Promise(r => setTimeout(r, 3000));
114
+ result = await api.get(`/traces/analyze/${analysisId}`);
115
+ if (result.status === 'completed' || result.status === 'failed') break;
116
+ }
117
+ if (result.status === 'failed') throw new Error(result.error || 'Analysis failed');
118
+ if (result.status === 'running') throw new Error('Analysis timed out');
119
+ }
120
+
109
121
  s.stop('Analysis complete');
110
122
 
111
123
  if (flags.json) { ui.json(result); return; }
package/cli/run.js ADDED
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ const { spawn } = require('child_process');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const ui = require('./ui');
7
+
8
+ function isESM(scriptPath) {
9
+ if (scriptPath.endsWith('.mjs')) return true;
10
+ if (scriptPath.endsWith('.cjs')) return false;
11
+
12
+ let dir = path.resolve(path.dirname(scriptPath));
13
+ while (true) {
14
+ const pkgPath = path.join(dir, 'package.json');
15
+ if (fs.existsSync(pkgPath)) {
16
+ try {
17
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
18
+ return pkg.type === 'module';
19
+ } catch { return false; }
20
+ }
21
+ const parent = path.dirname(dir);
22
+ if (parent === dir) break;
23
+ dir = parent;
24
+ }
25
+ return false;
26
+ }
27
+
28
+ function resolveSecurenowRegister() {
29
+ // When running from the published package, securenow/register resolves to our own register.js.
30
+ // When running from the monorepo, resolve relative to this file.
31
+ try {
32
+ return require.resolve('securenow/register');
33
+ } catch {
34
+ return path.resolve(__dirname, '..', 'register.js');
35
+ }
36
+ }
37
+
38
+ function resolveESMHook() {
39
+ // --import uses ESM resolution which requires file:// URLs for absolute paths
40
+ // on Windows. Using the bare specifier lets Node's own resolver handle it.
41
+ try {
42
+ const resolved = require.resolve('@opentelemetry/instrumentation/hook.mjs');
43
+ const { pathToFileURL } = require('url');
44
+ return pathToFileURL(resolved).href;
45
+ } catch {
46
+ return '@opentelemetry/instrumentation/hook.mjs';
47
+ }
48
+ }
49
+
50
+ /**
51
+ * securenow run [node-flags...] <script> [app-args...]
52
+ *
53
+ * Spawns node with the correct OTel preload flags.
54
+ * Passes through Node flags (--watch, --inspect, etc.) and app arguments.
55
+ *
56
+ * We re-parse from process.argv directly so that flags like --watch and
57
+ * --inspect are forwarded to node verbatim (the CLI's own arg parser
58
+ * would otherwise consume them).
59
+ */
60
+ function run(rawArgs) {
61
+ // rawArgs comes directly from process.argv, everything after `run` (or the file path)
62
+ if (!rawArgs || rawArgs.length === 0) {
63
+ ui.error('Missing script path.');
64
+ console.log('');
65
+ console.log(` ${ui.c.bold('Usage:')} securenow run [node-flags] <script> [app-args]`);
66
+ console.log('');
67
+ console.log(` ${ui.c.bold('Examples:')}`);
68
+ console.log(` securenow run src/index.js`);
69
+ console.log(` securenow run --watch src/index.js`);
70
+ console.log(` securenow run --inspect src/server.js --port 3000`);
71
+ console.log('');
72
+ process.exit(1);
73
+ }
74
+
75
+ const args = rawArgs;
76
+
77
+ // Split into: node flags (before the script), script path, and app args (after)
78
+ const nodeFlags = [];
79
+ let scriptIdx = -1;
80
+
81
+ for (let i = 0; i < args.length; i++) {
82
+ if (args[i].startsWith('-')) {
83
+ nodeFlags.push(args[i]);
84
+ } else {
85
+ scriptIdx = i;
86
+ break;
87
+ }
88
+ }
89
+
90
+ if (scriptIdx === -1) {
91
+ ui.error('No script path found. Provide a .js/.mjs/.ts file to run.');
92
+ process.exit(1);
93
+ }
94
+
95
+ const scriptPath = args[scriptIdx];
96
+ const appArgs = args.slice(scriptIdx + 1);
97
+
98
+ const esm = isESM(scriptPath);
99
+ const registerPath = resolveSecurenowRegister();
100
+
101
+ const otelFlags = [];
102
+ if (esm) {
103
+ otelFlags.push('--import', resolveESMHook());
104
+ }
105
+ otelFlags.push('--require', registerPath);
106
+
107
+ const finalArgs = [...otelFlags, ...nodeFlags, scriptPath, ...appArgs];
108
+
109
+ const nodeExe = process.execPath;
110
+
111
+ if (process.env.SECURENOW_DEBUG) {
112
+ console.log(`[securenow] ${ui.c.dim('exec:')} ${nodeExe} ${finalArgs.join(' ')}`);
113
+ }
114
+
115
+ const child = spawn(nodeExe, finalArgs, {
116
+ stdio: 'inherit',
117
+ env: process.env,
118
+ cwd: process.cwd(),
119
+ });
120
+
121
+ child.on('close', (code) => process.exit(code ?? 1));
122
+ child.on('error', (err) => {
123
+ ui.error(`Failed to start: ${err.message}`);
124
+ process.exit(1);
125
+ });
126
+
127
+ // Forward termination signals to the child
128
+ for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
129
+ process.on(sig, () => child.kill(sig));
130
+ }
131
+ }
132
+
133
+ module.exports = { run };
package/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
4
  const ui = require('./cli/ui');
@@ -235,6 +235,13 @@ const COMMANDS = {
235
235
  flags: { typescript: 'Force TypeScript', javascript: 'Force JavaScript', src: 'Create in src/', root: 'Create in root', force: 'Overwrite existing' },
236
236
  run: (a, f) => require('./cli/init').init(a, f),
237
237
  },
238
+ run: {
239
+ desc: 'Run a Node.js app with automatic OTel instrumentation',
240
+ usage: 'securenow run [node-flags] <script> [app-args]',
241
+ flags: { watch: 'Enable Node.js watch mode', inspect: 'Enable Node.js inspector' },
242
+ rawArgv: true,
243
+ run: (rawArgs) => require('./cli/run').run(rawArgs),
244
+ },
238
245
  version: {
239
246
  desc: 'Show CLI version',
240
247
  run: () => {
@@ -293,6 +300,7 @@ function showHelp(commandName) {
293
300
  showBanner();
294
301
 
295
302
  const groups = {
303
+ 'Run': ['run'],
296
304
  'Authentication': ['login', 'logout', 'whoami'],
297
305
  'Applications': ['apps', 'init', 'status'],
298
306
  'Observe': ['traces', 'logs', 'analytics'],
@@ -334,11 +342,24 @@ async function main() {
334
342
 
335
343
  const cmd = COMMANDS[cmdName];
336
344
  if (!cmd) {
345
+ // Auto-detect: if the first arg looks like a file path, treat it as `securenow run <file>`
346
+ if (/\.(m?[jt]sx?|cjs)$/.test(cmdName) || cmdName.includes('/') || cmdName.includes('\\')) {
347
+ return COMMANDS.run.run(process.argv.slice(2));
348
+ }
337
349
  ui.error(`Unknown command: ${cmdName}`);
338
350
  ui.info('Run `securenow help` for a list of commands.');
339
351
  process.exit(1);
340
352
  }
341
353
 
354
+ if (cmd.rawArgv) {
355
+ // Pass raw argv (everything after the command name) so the command can
356
+ // forward flags like --watch, --inspect verbatim to child processes.
357
+ const cmdIdx = process.argv.indexOf(cmdName);
358
+ const rawArgs = cmdIdx !== -1 ? process.argv.slice(cmdIdx + 1) : positional.slice(1);
359
+ await cmd.run(rawArgs);
360
+ return;
361
+ }
362
+
342
363
  if (cmd.run && !cmd.sub) {
343
364
  await cmd.run(positional.slice(1), flags);
344
365
  return;
@@ -71,13 +71,17 @@ function formatMessage(args) {
71
71
  .join(' ');
72
72
  }
73
73
 
74
+ const { context, trace } = require('@opentelemetry/api');
75
+
74
76
  /**
75
- * Emit a log record
77
+ * Emit a log record, correlating with the active trace/span when available
76
78
  */
77
79
  function emitLog(severityNumber, severityText, args) {
78
80
  const message = formatMessage(args);
79
81
 
80
82
  try {
83
+ const activeCtx = context.active();
84
+ const spanCtx = trace.getSpanContext(activeCtx);
81
85
  logger.emit({
82
86
  severityNumber,
83
87
  severityText,
@@ -86,6 +90,7 @@ function emitLog(severityNumber, severityText, args) {
86
90
  'log.source': 'console',
87
91
  'log.method': severityText.toLowerCase(),
88
92
  },
93
+ ...(spanCtx && { context: activeCtx }),
89
94
  });
90
95
  } catch (e) {
91
96
  // Silently fail to avoid breaking the application