From caa07083ecb7511fb541440f42ad6fd011f0b8fd Mon Sep 17 00:00:00 2001 From: Abraham Date: Sun, 12 Oct 2025 15:25:03 +0000 Subject: [PATCH 1/3] feat: update logger --- LOGGING.md | 775 +++++++++++++++++++++++++++++++++++ README.md | 139 +++++++ app.json.development.example | 6 + app.json.production.example | 10 + package-lock.json | 774 ++++++++++++++++++++++------------ package.json | 8 +- src/a.json | 1 + src/index.js | 5 +- src/server.js | 3 +- src/utils/configs.js | 25 ++ src/utils/logger.js | 72 ++++ 11 files changed, 1555 insertions(+), 263 deletions(-) create mode 100644 LOGGING.md create mode 100644 app.json.development.example create mode 100644 app.json.production.example create mode 100644 src/utils/logger.js diff --git a/LOGGING.md b/LOGGING.md new file mode 100644 index 0000000..f0048c2 --- /dev/null +++ b/LOGGING.md @@ -0,0 +1,775 @@ +# Logging Guide + +Complete guide to logging in this Node.js service, covering development, production, and troubleshooting. + +## Table of Contents + +1. [Architecture Overview](#architecture-overview) +2. [Configuration](#configuration) +3. [Development Setup](#development-setup) +4. [Production Setup](#production-setup) +5. [Log Format Reference](#log-format-reference) +6. [Kibana Queries](#kibana-queries) +7. [Troubleshooting](#troubleshooting) +8. [Best Practices](#best-practices) + +--- + +## Architecture Overview + +The logging system uses a **stage-based approach** that automatically adapts to your environment: + +### Development Mode (`stage: "dev"`) +``` +┌──────────────┐ +│ Application │ +│ (Pino) │ +└──────┬───────┘ + │ + │ Pretty Console Output + ▼ +┌──────────────┐ +│ Developer │ +│ Terminal │ +└──────────────┘ +``` + +- Human-readable, colorized logs +- Perfect for local debugging +- No external infrastructure needed + +### Production Mode (`stage: "prod"`) +``` +┌──────────────┐ +│ Application │ +│ (Pino ECS) │ +└──────┬───────┘ + │ + │ ECS JSON Logs + ▼ +┌──────────────┐ +│ journald │ +│ (systemd) │ +└──────┬───────┘ + │ + │ Structured Logs + ▼ +┌──────────────┐ +│ Filebeat │ +│ (Shipper) │ +└──────┬───────┘ + │ + │ HTTP/HTTPS + ▼ +┌──────────────┐ +│Elasticsearch │ +│ (Storage) │ +└──────┬───────┘ + │ + │ REST API + ▼ +┌──────────────┐ +│ Kibana │ +│ (Search) │ +└──────────────┘ +``` + +- Structured ECS JSON logs +- Searchable in Elasticsearch +- Visualized in Kibana +- Retained for 6 months + +--- + +## Configuration + +### Basic Configuration + +The logging system is controlled by the `stage` field in `app.json`: + +```json +{ + "stage": "dev", + "authKey": "your-auth-key", + "allowPublicAccess": false, + "port": 5000 +} +``` + +### Production Configuration + +For production, add Elasticsearch credentials: + +```json +{ + "stage": "prod", + "authKey": "your-auth-key", + "allowPublicAccess": true, + "port": 5000, + "log": { + "elasticsearch_host": "https://logs.yourcompany.com", + "elasticsearch_api_key": "YOUR_ELASTICSEARCH_API_KEY" + } +} +``` + +### Stage Values + +The `stage` field supports both short and long names: + +| Configuration | Result | NODE_ENV | +|---------------|--------|----------| +| `"dev"` | Development mode | `"development"` | +| `"development"` | Development mode | `"development"` | +| `"prod"` | Production mode | `"production"` | +| `"production"` | Production mode | `"production"` | + +**Note:** The `getStage()` function automatically sets `NODE_ENV` based on the stage, following Node.js best practices. + +--- + +## Development Setup + +### Installation + +Install the required development dependency: + +```bash +npm install --save-dev pino-pretty +``` + +### Configuration + +Set `stage` to `"dev"` or `"development"` in `app.json`: + +```json +{ + "stage": "dev" +} +``` + +### Running + +Start the development server: + +```bash +npm run serve:dev +``` + +### Output Example + +You'll see beautiful, human-readable logs: + +``` +[14:23:45 INFO] Server started on port 5000 +[14:23:47 INFO] Incoming request + url: "/hello?name=world" + method: "GET" + reqId: "req-1" + ip: ["192.168.1.100"] +[14:23:47 INFO] Request completed + url: "/hello?name=world" + method: "GET" + statusCode: 200 + duration: "142ms" + reqId: "req-1" +``` + +### Development Features + +- **Colorized output**: Levels have distinct colors (info=green, warn=yellow, error=red) +- **Readable timestamps**: Human-friendly format (HH:MM:ss) +- **Structured data**: Nested objects displayed cleanly +- **No PID/hostname**: Clutter removed for local development + +--- + +## Production Setup + +### Prerequisites + +1. Elasticsearch cluster with API key authentication +2. Kibana for log visualization (recommended) +3. Server with systemd and journald + +### Deployment + +The production setup is handled automatically by the deployment scripts. The script: + +1. Installs and configures Filebeat +2. Creates service-specific input configuration +3. Configures ndjson parser for JSON logs +4. Sets up Elasticsearch output with authentication +5. Creates systemd service for Filebeat + +### Configuration Files + +After deployment, Filebeat is configured at: + +**Input Configuration:** +`/etc/filebeat/inputs.d/.yml` + +```yaml +- type: journald + id: myService + seek: tail + include_matches: + match: + - "_SYSTEMD_UNIT=myService.service" + + # Parse JSON logs from journald message field + parsers: + - ndjson: + target: "" + overwrite_keys: true + expand_keys: true + add_error_key: true + + # Add data stream fields for routing + fields_under_root: true + fields: + data_stream: + type: logs + dataset: myService + namespace: production + + processors: + - add_fields: + target: '@metadata' + fields: + raw_index: logs-myService-production +``` + +**Main Configuration:** +`/etc/filebeat/filebeat.yml` + +```yaml +filebeat.config.inputs: + enabled: true + path: /etc/filebeat/inputs.d/*.yml + +output.elasticsearch: + hosts: ["https://logs.yourcompany.com"] + api_key: "YOUR_ELASTICSEARCH_API_KEY" +``` + +### Verification + +Check that logs are flowing: + +```bash +# 1. Verify application is logging to journald +sudo journalctl -f -u myService.service + +# 2. Check Filebeat service status +sudo systemctl status filebeat + +# 3. Test Elasticsearch connection +sudo filebeat test output + +# 4. View Filebeat logs +sudo journalctl -u filebeat -n 50 +``` + +--- + +## Log Format Reference + +### ECS Field Mapping + +The application uses [Elastic Common Schema (ECS)](https://www.elastic.co/guide/en/ecs/current/index.html) for structured logging: + +| ECS Field | Description | Example | +|-----------|-------------|---------| +| `@timestamp` | Log timestamp (ISO 8601) | `2025-10-12T14:23:47.123Z` | +| `log.level` | Log level | `info`, `warn`, `error` | +| `message` | Log message | `Request completed` | +| `trace.id` | Request correlation ID | `req-12345` | +| `http.request.method` | HTTP method | `GET`, `POST` | +| `url.full` | Full request URL | `/hello?name=world` | +| `http.response.status_code` | HTTP status code | `200`, `404`, `500` | +| `event.duration` | Request duration (nanoseconds) | `142000000` (142ms) | +| `client.ip` | Client IP address | `192.168.1.100` | +| `error.message` | Error message | `Invalid parameter` | +| `error.stack_trace` | Stack trace | `Error: ...\n at ...` | + +### Log Levels + +| Level | Usage | Color (dev) | +|-------|-------|-------------| +| `info` | Normal operations, request/response logs | Green | +| `warn` | Warning conditions, unauthorized access | Yellow | +| `error` | Error conditions, exceptions | Red | + +### Example Production Log + +```json +{ + "@timestamp": "2025-10-12T14:23:47.123Z", + "log.level": "info", + "message": "Request completed", + "trace.id": "req-abc123", + "http.request.method": "GET", + "url.full": "/hello?name=world", + "http.response.status_code": 200, + "event.duration": 142000000, + "client.ip": "192.168.1.100", + "data_stream": { + "type": "logs", + "dataset": "myService", + "namespace": "production" + } +} +``` + +--- + +## Kibana Queries + +### Basic Searches + +**All logs for your service:** +``` +data_stream.dataset: "myService" +``` + +**Specific stage (production):** +``` +data_stream.dataset: "myService" AND data_stream.namespace: "production" +``` + +**Specific stage (development):** +``` +data_stream.dataset: "myService" AND data_stream.namespace: "development" +``` + +### Filter by Log Level + +**Errors only:** +``` +data_stream.dataset: "myService" AND log.level: "error" +``` + +**Warnings and errors:** +``` +data_stream.dataset: "myService" AND (log.level: "warn" OR log.level: "error") +``` + +### Search by Request + +**Specific request ID:** +``` +trace.id: "req-12345" +``` + +**Follow a request through the system:** +``` +trace.id: "req-12345" +``` +(Sort by `@timestamp` to see the request flow) + +### Search by HTTP + +**Specific HTTP method:** +``` +http.request.method: "POST" +``` + +**HTTP errors (4xx and 5xx):** +``` +http.response.status_code >= 400 +``` + +**Specific status code:** +``` +http.response.status_code: 500 +``` + +**Search URLs:** +``` +url.full: "/hello" +``` + +### Performance Analysis + +**Slow requests (>1 second):** +``` +event.duration > 1000000000 +``` + +**Very slow requests (>5 seconds):** +``` +event.duration > 5000000000 +``` + +**Average response time:** +``` +Visualize → Metrics → Average of event.duration +``` + +### Search by IP + +**Requests from specific IP:** +``` +client.ip: "192.168.1.100" +``` + +**Unauthorized access attempts:** +``` +log.level: "warn" AND message: "Unauthorized access attempt" +``` + +### Time-Based Searches + +Use the Kibana time picker for: +- Last 15 minutes +- Last hour +- Last 24 hours +- Custom time range + +### Advanced Queries + +**Errors with stack traces:** +``` +log.level: "error" AND _exists_: error.stack_trace +``` + +**High-volume endpoints:** +``` +data_stream.dataset: "myService" +``` +(Use Visualize → Aggregation → Terms on `url.full`) + +**Error rate over time:** +``` +log.level: "error" +``` +(Use Visualize → Line Chart → Date Histogram) + +--- + +## Troubleshooting + +### Problem: Logs not appearing in Elasticsearch + +**Symptoms:** +- No logs visible in Kibana +- Recent logs missing + +**Solutions:** + +1. **Check if logs are being generated:** + ```bash + sudo journalctl -f -u myService.service + ``` + You should see JSON logs like: + ```json + {"@timestamp":"2025-10-12T14:23:47.123Z","log.level":"info","message":"Server started on port 5000"} + ``` + +2. **Check Filebeat service status:** + ```bash + sudo systemctl status filebeat + ``` + Should show "active (running)" + +3. **Test Elasticsearch connection:** + ```bash + sudo filebeat test output + ``` + Should show: + ``` + elasticsearch: https://logs.yourcompany.com... + parse url... OK + connection... OK + ``` + +4. **View Filebeat logs:** + ```bash + sudo journalctl -u filebeat -n 50 + ``` + Look for errors like: + - Connection refused + - Authentication failed + - Invalid API key + +5. **Check Filebeat configuration:** + ```bash + sudo filebeat test config + ``` + Should show "Config OK" + +6. **Verify Elasticsearch credentials:** + Check `/etc/filebeat/filebeat.yml` has correct: + - `hosts`: Elasticsearch URL + - `api_key`: Valid API key + +7. **Restart Filebeat:** + ```bash + sudo systemctl restart filebeat + ``` + +### Problem: Development logs still showing as JSON + +**Symptoms:** +- Logs appear as JSON instead of pretty-printed +- Colors missing in terminal + +**Solutions:** + +1. **Verify stage configuration:** + ```bash + cat src/app.json | grep stage + ``` + Should show: `"stage": "dev"` or `"stage": "development"` + +2. **Check pino-pretty is installed:** + ```bash + npm list pino-pretty + ``` + If missing: + ```bash + npm install --save-dev pino-pretty + ``` + +3. **Restart the service:** + ```bash + npm run serve:dev + ``` + +4. **Check NODE_ENV:** + The app should set this automatically, but verify: + ```bash + # In your code, add temporary logging + console.log('NODE_ENV:', process.env.NODE_ENV); + ``` + Should show: `NODE_ENV: development` + +### Problem: Logs have 5-15 second delay in Elasticsearch + +**Symptoms:** +- Logs appear in Elasticsearch but with delay +- Real-time monitoring seems slow + +**Solutions:** + +**This is normal!** Filebeat batches logs for efficiency. This is not a problem, it's expected behavior. + +For real-time debugging, use journald instead: +```bash +sudo journalctl -f -u myService.service +``` + +If you need faster log shipping, adjust Filebeat configuration: +```yaml +# /etc/filebeat/filebeat.yml +queue.mem: + events: 256 + flush.min_events: 64 + flush.timeout: 1s +``` + +Then restart: +```bash +sudo systemctl restart filebeat +``` + +### Problem: Missing log fields in Elasticsearch + +**Symptoms:** +- Logs appear but fields are not structured +- All data in `message` field as JSON string + +**Solutions:** + +1. **Check ndjson parser is configured:** + ```bash + cat /etc/filebeat/inputs.d/myService.yml | grep -A5 "parsers:" + ``` + Should show: + ```yaml + parsers: + - ndjson: + target: "" + overwrite_keys: true + expand_keys: true + add_error_key: true + ``` + +2. **Verify expand_keys is true:** + This is critical for ECS format. Without it, you get: + ```json + {"log.level": "info"} // Wrong: dotted string key + ``` + With it, you get: + ```json + {"log": {"level": "info"}} // Correct: nested object + ``` + +3. **Restart Filebeat after config changes:** + ```bash + sudo systemctl restart filebeat + ``` + +--- + +## Best Practices + +### 1. Use Correlation IDs + +Always include the request ID (`reqId`) in logs for request tracing: + +```javascript +request.log.info({ + reqId: request.id, + msg: 'Processing request', + // other fields... +}); +``` + +This allows you to track a request through the entire system: +``` +trace.id: "req-12345" +``` + +### 2. Log at Appropriate Levels + +- **info**: Normal operations, request/response logs +- **warn**: Unusual conditions, unauthorized access, deprecated features +- **error**: Errors, exceptions, failures + +**Don't:** +```javascript +logger.info('ERROR: Something failed'); // ❌ Wrong level +``` + +**Do:** +```javascript +logger.error({ error: err }, 'Something failed'); // ✅ Correct +``` + +### 3. Include Context in Logs + +Always include relevant context: + +```javascript +// ❌ Bad: No context +logger.error('Request failed'); + +// ✅ Good: Rich context +logger.error({ + error: err, + url: request.url, + userId: user.id +}, 'Request failed'); +``` + +### 4. Structured Logging Over String Concatenation + +**Don't:** +```javascript +logger.info(`User ${userId} performed action`); // ❌ Unstructured +``` + +**Do:** +```javascript +logger.info({ + userId: userId, + action: action, + msg: 'User performed action' +}); // ✅ Structured +``` + +Structured logs are searchable in Elasticsearch: +``` +userId: "user-123" AND action: "login" +``` + +### 5. Sensitive Data Handling + +**Never log sensitive data:** + +```javascript +// ❌ NEVER do this +logger.info({ password: password }); +logger.info({ apiKey: apiKey }); +logger.info({ creditCard: cardNumber }); + +// ✅ Safe: Omit or redact +logger.info({ userId: userId }); // ID is fine +logger.info({ apiKey: 'REDACTED' }); // Redacted +``` + +### 6. Performance Considerations + +- **Don't log in tight loops**: Use sampling or aggregation +- **Don't log large objects**: Log IDs and metadata instead +- **Use appropriate log levels**: Reduce info logs in production + +### 7. Testing + +Test logging configuration in both modes: + +```bash +# Test development mode +cd src +echo '{"stage":"dev","port":5000,"authKey":"test"}' > app.json +npm run serve:dev +# Verify pretty logs appear + +# Test production mode +echo '{"stage":"prod","port":5000,"authKey":"test"}' > app.json +npm run serve +# Verify JSON logs appear +``` + +### 8. Monitoring + +Set up Kibana alerts for: + +- **Error rate spike**: Alert when error count exceeds threshold +- **No logs received**: Alert when no logs in 5 minutes (service down) +- **Slow requests**: Alert when p99 duration exceeds threshold +- **Unauthorized access**: Alert on multiple failed auth attempts + +### 9. Log Retention + +Balance between cost and compliance: + +- **Development**: 7-30 days (troubleshooting) +- **Production**: 90-180 days (compliance, audit trail) +- **Long-term archive**: S3/Glacier for multi-year retention + +### 10. Documentation + +Document your logging: + +- **What fields mean**: Explain custom fields +- **Log levels**: When to use each level +- **Common queries**: Share useful Kibana queries +- **Troubleshooting**: Document common issues + +--- + +## Additional Resources + +- [Pino Documentation](https://getpino.io/) +- [Elastic Common Schema (ECS)](https://www.elastic.co/guide/en/ecs/current/index.html) +- [Filebeat Documentation](https://www.elastic.co/guide/en/beats/filebeat/current/index.html) +- [Kibana Query Language (KQL)](https://www.elastic.co/guide/en/kibana/current/kuery-query.html) +- [Elasticsearch Index Lifecycle Management](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-lifecycle-management.html) + +--- + +## Support + +For logging issues: + +1. Check this troubleshooting guide +2. Review deployment-scripts documentation +3. Check Filebeat and Elasticsearch logs +4. Review Pino and ECS documentation + +For application issues: +- See main [README.md](./README.md) +- Check [CONTRIBUTING.md](./CONTRIBUTING.md) diff --git a/README.md b/README.md index 6efe68c..a38d00d 100644 --- a/README.md +++ b/README.md @@ -256,3 +256,142 @@ if you want to mock/spy on fn() for unit tests, use sinon. refer docs: https://s we use c8 for coverage https://github.com/bcoe/c8. Its reporting is based on nyc, so detailed docs can be found here: https://github.com/istanbuljs/nyc ; We didn't use nyc as it do not yet have ES module support see: https://github.com/digitalbazaar/bedrock-test/issues/16 . c8 is drop replacement for nyc coverage reporting tool + +--- + +## Logging Configuration + +This template uses a **stage-based logging system** that automatically adapts to your environment. Logs are beautiful and readable in development, while production logs are structured for Elasticsearch. + +### How It Works + +The logging system reads the `stage` field from `app.json` and configures logging accordingly: + +- **Development** (`dev` or `development`): Pretty, colorized console logs with `pino-pretty` +- **Production** (`prod` or `production`): ECS JSON format for Elasticsearch with structured fields + +### Configuration + +Set the `stage` field in your `app.json`: + +```json +{ + "stage": "dev", + "authKey": "your-auth-key", + "allowPublicAccess": false, + "port": 5000 +} +``` + +**Supported stage values:** +- `"dev"` or `"development"` → Pretty logs for development +- `"prod"` or `"production"` → ECS JSON logs for production + +The `getStage()` function automatically sets `NODE_ENV` to either `"development"` or `"production"` based on your stage configuration. + +### Development Logs + +When `stage` is `"dev"` or `"development"`, you get beautiful, readable logs: + +``` +[14:23:45 INFO] Server started on port 5000 +[14:23:47 INFO] Incoming request + url: "/hello?name=world" + method: "GET" + reqId: "req-1" +[14:23:47 INFO] Request completed + statusCode: 200 + duration: "142ms" +``` + +### Production Logs + +When `stage` is `"prod"` or `"production"`, logs are in ECS JSON format: + +```json +{ + "@timestamp": "2025-10-12T14:23:47.123Z", + "log.level": "info", + "message": "Request completed", + "http.request.method": "GET", + "url.full": "/hello?name=world", + "http.response.status_code": 200, + "event.duration": 142000000, + "trace.id": "req-1" +} +``` + +### Elasticsearch Integration + +Production logs are automatically shipped to Elasticsearch using **Filebeat** and **journald**: + +**Architecture:** +``` +Application (Pino ECS) → journald → Filebeat → Elasticsearch → Kibana +``` + +**How it works:** +1. Application outputs ECS JSON logs to stdout/stderr +2. systemd captures logs in journald +3. Filebeat reads from journald with ndjson parser +4. Logs are shipped to Elasticsearch with proper structure +5. Search and visualize in Kibana + +**Query logs in Kibana:** +``` +# Search for errors +log.level: "error" + +# Search by request ID +trace.id: "req-12345" + +# Search by HTTP status +http.response.status_code: 500 + +# Search slow requests +event.duration > 1000000000 +``` + +### Configuration Examples + +- **Development**: See [app.json.development.example](./app.json.development.example) +- **Production**: See [app.json.production.example](./app.json.production.example) + +### Switching Between Modes + +To switch between development and production logging, simply change the `stage` field in `app.json`: + +```bash +# For local development +{ + "stage": "dev" +} + +# For production deployment +{ + "stage": "prod" +} +``` + +Then restart the service: +```bash +npm run serve +``` + +### Troubleshooting + +**Problem:** Logs not appearing in Elasticsearch +- Check Filebeat service: `sudo systemctl status filebeat` +- Test Elasticsearch connection: `sudo filebeat test output` +- View Filebeat logs: `sudo journalctl -u filebeat -n 50` + +**Problem:** Development logs still showing as JSON +- Verify `stage` is set to `"dev"` or `"development"` in app.json +- Ensure pino-pretty is installed: `npm install --save-dev pino-pretty` +- Restart the service + +**Problem:** Logs have 5-15 second delay in Elasticsearch +- This is normal! Filebeat batches logs for efficiency +- For real-time debugging, use `journalctl -f -u .service` + +For more details, see the [LOGGING.md](./LOGGING.md) guide. diff --git a/app.json.development.example b/app.json.development.example new file mode 100644 index 0000000..bb27d0e --- /dev/null +++ b/app.json.development.example @@ -0,0 +1,6 @@ +{ + "stage": "dev", + "authKey": "your-development-auth-key", + "allowPublicAccess": false, + "port": 5000 +} diff --git a/app.json.production.example b/app.json.production.example new file mode 100644 index 0000000..ee90278 --- /dev/null +++ b/app.json.production.example @@ -0,0 +1,10 @@ +{ + "stage": "prod", + "authKey": "your-production-auth-key", + "allowPublicAccess": true, + "port": 5000, + "log": { + "elasticsearch_host": "https://logs.yourcompany.com", + "elasticsearch_api_key": "your-elasticsearch-api-key" + } +} diff --git a/package-lock.json b/package-lock.json index d5fcb8c..a8fe845 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,10 @@ "license": "AGPL-3.0-or-later", "dependencies": { "@aicore/libcommonutils": "1.0.20", + "@elastic/ecs-pino-format": "^1.5.0", "@fastify/compress": "^8.0.1", "@fastify/static": "8.1.0", - "fastify": "5.2.1", + "fastify": "5.6.1", "node-fetch": "3.3.2" }, "devDependencies": { @@ -22,11 +23,12 @@ "chai": "5.1.2", "cli-color": "2.0.4", "documentation": "14.0.3", - "eslint": "9.20.1", + "eslint": "9.37.0", "glob": "11.0.1", "husky": "9.1.7", "mocha": "11.1.0", - "nodemon": "3.1.9" + "nodemon": "3.1.9", + "pino-pretty": "^11.0.0" } }, "node_modules/@aicore/libcommonutils": { @@ -49,15 +51,15 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -237,9 +239,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -247,9 +249,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, "license": "MIT", "engines": { @@ -266,27 +268,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.9" + "@babel/types": "^7.28.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -296,15 +298,15 @@ } }, "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -339,14 +341,14 @@ } }, "node_modules/@babel/types": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -757,10 +759,31 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@elastic/ecs-helpers": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@elastic/ecs-helpers/-/ecs-helpers-2.1.1.tgz", + "integrity": "sha512-ItoNazMnYdlUCmkBYTXc3SG6PF7UlVTbvMdHPvXkfTMPdwGv2G1Xtp5CjDHaGHGOZSwaDrW4RSCXvA/lMSU+rg==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/@elastic/ecs-pino-format": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@elastic/ecs-pino-format/-/ecs-pino-format-1.5.0.tgz", + "integrity": "sha512-7MMVmT50ucEl7no8mUgCIl+pffBVNRl36uZi0vmalWa2xPWISBxM9k9WSP/WTgOkmGj9G35e5g3UfCS1zxshBg==", + "license": "Apache-2.0", + "dependencies": { + "@elastic/ecs-helpers": "^2.1.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -800,9 +823,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -814,10 +837,23 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", + "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz", - "integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -828,9 +864,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -876,13 +912,16 @@ "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz", - "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", + "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { @@ -896,32 +935,19 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.16.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@fastify/accept-negotiator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", @@ -1150,9 +1176,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1568,9 +1594,9 @@ "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -1754,10 +1780,11 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2069,6 +2096,13 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, "node_modules/comma-separated-tokens": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", @@ -2272,6 +2306,16 @@ "node": ">= 12" } }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -2435,10 +2479,11 @@ } }, "node_modules/documentation/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -2813,22 +2858,23 @@ } }, "node_modules/eslint": { - "version": "9.20.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz", - "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", + "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.11.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.20.0", - "@eslint/plugin-kit": "^0.2.5", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.4.0", + "@eslint/core": "^0.16.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.37.0", + "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -2836,9 +2882,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2873,9 +2919,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -2890,9 +2936,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2960,15 +3006,15 @@ "license": "ISC" }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3078,6 +3124,13 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-decode-uri-component": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", @@ -3135,14 +3188,12 @@ "fast-decode-uri-component": "^1.0.1" } }, - "node_modules/fast-redact": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", - "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", - "license": "MIT", - "engines": { - "node": ">=6" - } + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" }, "node_modules/fast-uri": { "version": "3.0.6", @@ -3161,9 +3212,9 @@ "license": "BSD-3-Clause" }, "node_modules/fastify": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.2.1.tgz", - "integrity": "sha512-rslrNBF67eg8/Gyn7P2URV8/6pz8kSAscFL4EThZJ8JBMaXacVdVE4hmUcnPNKERl5o/xTiBSLfdowBRhVF1WA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.6.1.tgz", + "integrity": "sha512-WjjlOciBF0K8pDUPZoGPhqhKrQJ02I8DKaDIfO51EL0kbSMwQFl85cRwhOvmSDWoukNOdTo27gLN549pLCcH7Q==", "funding": [ { "type": "github", @@ -3186,9 +3237,9 @@ "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^9.0.0", - "process-warning": "^4.0.0", + "process-warning": "^5.0.0", "rfdc": "^1.3.1", - "secure-json-parse": "^3.0.1", + "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } @@ -3198,6 +3249,22 @@ "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.0.1.tgz", "integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==" }, + "node_modules/fastify/node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/fastq": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", @@ -3465,9 +3532,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -3612,6 +3679,13 @@ "he": "bin/he" } }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "dev": true, + "license": "MIT" + }, "node_modules/highlight.js": { "version": "11.6.0", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", @@ -4060,6 +4134,16 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5414,9 +5498,9 @@ } }, "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6002,20 +6086,20 @@ } }, "node_modules/pino": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz", - "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.13.1.tgz", + "integrity": "sha512-Szuj+ViDTjKPQYiKumGmEn3frdl+ZPSdosHyt9SnUevFosOkMY2b7ipxlEctNKPmMD/VibeBI+ZcZCJK+4DPuw==", "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", - "process-warning": "^4.0.0", + "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", + "slow-redact": "^0.3.0", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, @@ -6032,12 +6116,78 @@ "split2": "^4.0.0" } }, + "node_modules/pino-pretty": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.3.0.tgz", + "integrity": "sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-pretty/node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/pino-std-serializers": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", "license": "MIT" }, + "node_modules/pino/node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/postcss": { "version": "8.5.2", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", @@ -6458,9 +6608,9 @@ } }, "node_modules/secure-json-parse": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-3.0.2.tgz", - "integrity": "sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", "funding": [ { "type": "github", @@ -6551,6 +6701,12 @@ "node": ">=10" } }, + "node_modules/slow-redact": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/slow-redact/-/slow-redact-0.3.2.tgz", + "integrity": "sha512-MseHyi2+E/hBRqdOi5COy6wZ7j7DxXRz9NkseavNYSvvWC06D8a5cidVZX3tcG5eCW3NIyVU4zT63hw0Q486jw==", + "license": "MIT" + }, "node_modules/sonic-boom": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", @@ -6770,9 +6926,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7570,14 +7726,14 @@ } }, "@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" } }, "@babel/compat-data": { @@ -7713,15 +7869,15 @@ } }, "@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true }, "@babel/helper-validator-option": { @@ -7731,34 +7887,33 @@ "dev": true }, "@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "requires": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" } }, "@babel/parser": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "dev": true, "requires": { - "@babel/types": "^7.26.9" + "@babel/types": "^7.28.4" } }, "@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "requires": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" } }, "@babel/traverse": { @@ -7785,13 +7940,13 @@ } }, "@babel/types": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" } }, "@bcoe/v8-coverage": { @@ -8070,10 +8225,23 @@ } } }, + "@elastic/ecs-helpers": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@elastic/ecs-helpers/-/ecs-helpers-2.1.1.tgz", + "integrity": "sha512-ItoNazMnYdlUCmkBYTXc3SG6PF7UlVTbvMdHPvXkfTMPdwGv2G1Xtp5CjDHaGHGOZSwaDrW4RSCXvA/lMSU+rg==" + }, + "@elastic/ecs-pino-format": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@elastic/ecs-pino-format/-/ecs-pino-format-1.5.0.tgz", + "integrity": "sha512-7MMVmT50ucEl7no8mUgCIl+pffBVNRl36uZi0vmalWa2xPWISBxM9k9WSP/WTgOkmGj9G35e5g3UfCS1zxshBg==", + "requires": { + "@elastic/ecs-helpers": "^2.1.1" + } + }, "@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "requires": { "eslint-visitor-keys": "^3.4.3" @@ -8094,9 +8262,9 @@ "dev": true }, "@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "requires": { "@eslint/object-schema": "^2.1.6", @@ -8104,19 +8272,28 @@ "minimatch": "^3.1.2" } }, + "@eslint/config-helpers": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", + "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", + "dev": true, + "requires": { + "@eslint/core": "^0.16.0" + } + }, "@eslint/core": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz", - "integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", "dev": true, "requires": { "@types/json-schema": "^7.0.15" } }, "@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -8151,9 +8328,9 @@ } }, "@eslint/js": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz", - "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", + "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", "dev": true }, "@eslint/object-schema": { @@ -8163,24 +8340,13 @@ "dev": true }, "@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", "dev": true, "requires": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.16.0", "levn": "^0.4.1" - }, - "dependencies": { - "@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.15" - } - } } }, "@fastify/accept-negotiator": { @@ -8318,9 +8484,9 @@ "dev": true }, "@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true }, "@isaacs/cliui": { @@ -8655,9 +8821,9 @@ "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" }, "acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true }, "acorn-jsx": { @@ -8770,9 +8936,9 @@ "dev": true }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -8963,6 +9129,12 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, "comma-separated-tokens": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", @@ -9102,6 +9274,12 @@ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" }, + "dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true + }, "de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -9218,9 +9396,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { "balanced-match": "^1.0.0" @@ -9497,21 +9675,22 @@ "dev": true }, "eslint": { - "version": "9.20.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz", - "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", + "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "requires": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.11.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.20.0", - "@eslint/plugin-kit": "^0.2.5", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.4.0", + "@eslint/core": "^0.16.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.37.0", + "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -9519,9 +9698,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -9568,9 +9747,9 @@ } }, "eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -9578,9 +9757,9 @@ } }, "eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true }, "esniff": { @@ -9604,14 +9783,14 @@ } }, "espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "requires": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" } }, "esquery": { @@ -9694,6 +9873,12 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "dev": true + }, "fast-decode-uri-component": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", @@ -9737,10 +9922,11 @@ "fast-decode-uri-component": "^1.0.1" } }, - "fast-redact": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", - "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==" + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true }, "fast-uri": { "version": "3.0.6", @@ -9748,9 +9934,9 @@ "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==" }, "fastify": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.2.1.tgz", - "integrity": "sha512-rslrNBF67eg8/Gyn7P2URV8/6pz8kSAscFL4EThZJ8JBMaXacVdVE4hmUcnPNKERl5o/xTiBSLfdowBRhVF1WA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.6.1.tgz", + "integrity": "sha512-WjjlOciBF0K8pDUPZoGPhqhKrQJ02I8DKaDIfO51EL0kbSMwQFl85cRwhOvmSDWoukNOdTo27gLN549pLCcH7Q==", "requires": { "@fastify/ajv-compiler": "^4.0.0", "@fastify/error": "^4.0.0", @@ -9762,11 +9948,18 @@ "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^9.0.0", - "process-warning": "^4.0.0", + "process-warning": "^5.0.0", "rfdc": "^1.3.1", - "secure-json-parse": "^3.0.1", + "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" + }, + "dependencies": { + "process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==" + } } }, "fastify-plugin": { @@ -9949,9 +10142,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "requires": { "balanced-match": "^1.0.0" } @@ -10060,6 +10253,12 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "dev": true + }, "highlight.js": { "version": "11.6.0", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", @@ -10353,6 +10552,12 @@ "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", "dev": true }, + "joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11261,9 +11466,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { "balanced-match": "^1.0.0" @@ -11675,21 +11880,28 @@ "dev": true }, "pino": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz", - "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.13.1.tgz", + "integrity": "sha512-Szuj+ViDTjKPQYiKumGmEn3frdl+ZPSdosHyt9SnUevFosOkMY2b7ipxlEctNKPmMD/VibeBI+ZcZCJK+4DPuw==", "requires": { "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", - "process-warning": "^4.0.0", + "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", + "slow-redact": "^0.3.0", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" + }, + "dependencies": { + "process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==" + } } }, "pino-abstract-transport": { @@ -11700,6 +11912,49 @@ "split2": "^4.0.0" } }, + "pino-pretty": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.3.0.tgz", + "integrity": "sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA==", + "dev": true, + "requires": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + }, + "secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "dev": true + } + } + }, "pino-std-serializers": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", @@ -11983,9 +12238,9 @@ "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" }, "secure-json-parse": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-3.0.2.tgz", - "integrity": "sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==" }, "semver": { "version": "7.7.1", @@ -12038,6 +12293,11 @@ "semver": "^7.5.3" } }, + "slow-redact": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/slow-redact/-/slow-redact-0.3.2.tgz", + "integrity": "sha512-MseHyi2+E/hBRqdOi5COy6wZ7j7DxXRz9NkseavNYSvvWC06D8a5cidVZX3tcG5eCW3NIyVU4zT63hw0Q486jw==" + }, "sonic-boom": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", @@ -12200,9 +12460,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { "balanced-match": "^1.0.0" diff --git a/package.json b/package.json index 7c3a8d2..e129dd6 100644 --- a/package.json +++ b/package.json @@ -55,17 +55,19 @@ "chai": "5.1.2", "cli-color": "2.0.4", "documentation": "14.0.3", - "eslint": "9.20.1", + "eslint": "9.37.0", "glob": "11.0.1", "husky": "9.1.7", "mocha": "11.1.0", - "nodemon": "3.1.9" + "nodemon": "3.1.9", + "pino-pretty": "^11.0.0" }, "dependencies": { "@aicore/libcommonutils": "1.0.20", + "@elastic/ecs-pino-format": "^1.5.0", "@fastify/compress": "^8.0.1", "@fastify/static": "8.1.0", - "fastify": "5.2.1", + "fastify": "5.6.1", "node-fetch": "3.3.2" } } diff --git a/src/a.json b/src/a.json index fba95c1..b0ebf95 100644 --- a/src/a.json +++ b/src/a.json @@ -1,4 +1,5 @@ { + "stage": "dev", "authKey": "123", "allowPublicAccess": false, "port": 5000 diff --git a/src/index.js b/src/index.js index 719dd2b..34d08de 100644 --- a/src/index.js +++ b/src/index.js @@ -31,9 +31,10 @@ * @module hello */ -import {pino} from 'pino'; +import {createLogger} from "./utils/logger.js"; import {startServer} from "./server.js"; -global.Logger = pino(); + +global.Logger = createLogger(); startServer(); diff --git a/src/server.js b/src/server.js index fc6d447..50193be 100644 --- a/src/server.js +++ b/src/server.js @@ -4,6 +4,7 @@ */ import fastify from "fastify"; +import {createFastifyLogger} from "./utils/logger.js"; import {init, isAuthenticated, addUnAuthenticatedAPI} from "./auth/auth.js"; import {HTTP_STATUS_CODES} from "@aicore/libcommonutils"; import {getConfigs} from "./utils/configs.js"; @@ -20,7 +21,7 @@ const __dirname = path.dirname(__filename); const CLEANUP_GRACE_TIME_5SEC = 5000; const server = fastify({ - logger: true, + logger: createFastifyLogger(), trustProxy: true, connectionTimeout: 30000, keepAliveTimeout: 30000 diff --git a/src/utils/configs.js b/src/utils/configs.js index f86157b..9f97b4a 100644 --- a/src/utils/configs.js +++ b/src/utils/configs.js @@ -38,3 +38,28 @@ function _getAppConfig(file) { export function clearAppConfig() { APP_CONFIG = null; } + +/** + * Gets the stage from app.json and sets NODE_ENV accordingly + * Supports both short and long stage names: + * - "dev" or "development" → NODE_ENV="development" + * - "prod" or "production" → NODE_ENV="production" + * @returns {string} The stage value from config + */ +export function getStage() { + const config = getConfigs(); + const stage = config.stage || 'development'; + + // Normalize stage names (case-insensitive) + const normalizedStage = stage.toLowerCase(); + const isDev = normalizedStage === 'dev' || normalizedStage === 'development'; + + // Set NODE_ENV based on stage + if (isDev) { + process.env.NODE_ENV = 'development'; + } else { + process.env.NODE_ENV = 'production'; + } + + return stage; +} diff --git a/src/utils/logger.js b/src/utils/logger.js new file mode 100644 index 0000000..79aa9fa --- /dev/null +++ b/src/utils/logger.js @@ -0,0 +1,72 @@ +/* + * GNU AGPL-3.0 License + * Copyright (c) 2021 - present core.ai . All rights reserved. + */ + +import {pino} from 'pino'; +import {ecsFormat} from '@elastic/ecs-pino-format'; +import {getStage} from './configs.js'; + +/** + * Creates a logger instance based on the stage configuration + * - Development: Pretty formatted, colorized logs + * - Production/Staging: ECS JSON format for Elasticsearch + * @returns {pino.Logger} Configured Pino logger instance + */ +export function createLogger() { + // This sets NODE_ENV internally based on app.json stage + getStage(); + + const isDevelopment = process.env.NODE_ENV === 'development'; + + if (isDevelopment) { + // Pretty logs for development + return pino({ + transport: { + target: 'pino-pretty', + options: { + colorize: true, + translateTime: 'HH:MM:ss Z', + ignore: 'pid,hostname', + singleLine: false + } + } + }); + } + + // ECS format for production/staging + return pino(ecsFormat()); +} + +/** + * Creates a Fastify logger configuration based on the stage + * - Development: Pretty formatted, colorized logs + * - Production/Staging: ECS JSON format for Elasticsearch + * @returns {object} Fastify logger configuration + */ +export function createFastifyLogger() { + // This sets NODE_ENV internally based on app.json stage + getStage(); + + const isDevelopment = process.env.NODE_ENV === 'development'; + + if (isDevelopment) { + // Pretty logs for development + return { + transport: { + target: 'pino-pretty', + options: { + colorize: true, + translateTime: 'HH:MM:ss', + ignore: 'pid,hostname', + singleLine: false + } + } + }; + } + + // ECS format for production/staging + return ecsFormat({ + convertReqRes: true // Converts req/res to ECS HTTP fields + }); +} From 30b17ae961eb43d572bb2c8d534f0923160171a9 Mon Sep 17 00:00:00 2001 From: Abraham Date: Sun, 12 Oct 2025 17:34:41 +0000 Subject: [PATCH 2/3] docs: update docs --- CONTRIBUTING.md | 14 +- README.md | 55 +++-- docs/How-To-Write-Docs.md | 463 ++++++++++++++++++++++++-------------- src/utils/logger.js | 2 + 4 files changed, 333 insertions(+), 201 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9359a9c..0f59127 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,19 +6,19 @@ 1. [Getting Started](#getting-started) ## Community engagement -1. [Code of Conduct](https://github.com/aicore/template-nodejs-ts/blob/main/CODE_OF_CONDUCT.md) -1. [License](https://github.com/aicore/template-nodejs-ts/blob/main/LICENSE) +1. [Code of Conduct](https://github.com/aicore/template-nodejs/blob/main/CODE_OF_CONDUCT.md) +1. [License](https://github.com/aicore/template-nodejs/blob/main/LICENSE) ### Need support? - * Log an issue with us [here](https://github.com/aicore/template-nodejs-ts/issues/new/choose) -### Engage with the community? - * Discuss the project [here](https://github.com/aicore/template-nodejs-ts/discussions) + * Log an issue with us [here](https://github.com/aicore/template-nodejs/issues/new/choose) +### Engage with the community? + * Discuss the project [here](https://github.com/aicore/template-nodejs/discussions) * Join our community discord channel [here](https://discord.gg/d3vr5bG57r). # About the Project -This is a template repository for nodejs and python. +This is a template repository for Node.js server applications using Fastify, with integrated logging, testing, and deployment tools. ## Project Status * Active. # Getting Started -* see https://github.com/aicore/template-nodejs-ts/blob/main/README.md +* see https://github.com/aicore/template-nodejs/blob/main/README.md diff --git a/README.md b/README.md index a38d00d..bcc3e2c 100644 --- a/README.md +++ b/README.md @@ -4,36 +4,48 @@ coverage, reporting, GitHub actions for publishing to npm repository, dependency Easily use this template to quick start a production ready nodejs project template. +## Quick Start + ```shell -# do this to start server -cp ./src/a.json ./src/app.json +# 1. Install dependencies npm install -npm run serve -# To hit end point, go to browser url http://127.0.0.1:5000/hello?name=rambo -# or to hit the authenticated url, do the curl command below -curl -X GET 'http://127.0.0.1:5000/helloAuth?name=rambo' -H 'authorization: Basic 123' -H 'Content-Type: application/json' -v +# 2. Create your configuration file +cp ./src/a.json ./src/app.json +# 3. Configure logging (REQUIRED) +# Edit src/app.json and set the "stage" field: +# - "dev" or "development" for local development (pretty logs) +# - "prod" or "production" for production (ECS JSON logs for Elasticsearch) +# See example configs: app.json.development.example and app.json.production.example + +# 4. Start the server +npm run serve # Production mode +npm run serve:dev # Development mode with auto-reload + +# 5. Test the endpoints +# Open browser: http://127.0.0.1:5000/hello?name=rambo +# Or use curl: +curl -X GET 'http://127.0.0.1:5000/helloAuth?name=rambo' \ + -H 'authorization: Basic 123' \ + -H 'Content-Type: application/json' -v ``` -```shell -# use this for continuous reload while development -npm run serve:dev -``` +**⚠️ Note:** The `stage` field in `app.json` is required for proper logging configuration. See [Logging Configuration](#logging-configuration) section for details. ## Code Guardian [![ build verification](https://github.com/aicore/template-nodejs/actions/workflows/build_verify.yml/badge.svg)](https://github.com/aicore/template-nodejs/actions/workflows/build_verify.yml) - - Sonar code quality check - Security rating - vulnerabilities - Code Coverage - Code Bugs - Reliability Rating - Maintainability Rating - Lines of Code - Technical debt + + Sonar code quality check + Security rating + vulnerabilities + Code Coverage + Code Bugs + Reliability Rating + Maintainability Rating + Lines of Code + Technical debt # TODOs after template use @@ -49,8 +61,9 @@ npm run serve:dev Analysis Method` for the first time before a pull request is raised: ![image](https://user-images.githubusercontent.com/5336369/148695840-65585d04-5e59-450b-8794-54ca3c62b9fe.png) 6. Check codacy runs on pull requests, set codacy defaults. You may remove codacy if sonar cloud is only needed. -7. Update the above Code Guardian badges; change all `id=aicore_template-nodejs-ts` to the sonar id of your project +7. Update the above Code Guardian badges; change all `id=aicore_template-nodejs` to the sonar id of your project fields. see this PR: https://github.com/aicore/libcache/pull/13 +8. Configure logging stage in app.json (see [Logging Configuration](#logging-configuration) section below) # Commands available diff --git a/docs/How-To-Write-Docs.md b/docs/How-To-Write-Docs.md index 2247095..ea9d683 100644 --- a/docs/How-To-Write-Docs.md +++ b/docs/How-To-Write-Docs.md @@ -1,256 +1,373 @@ -This document outlines how documentation is handled in this repo. +# How To Write Documentation -## The `docs` folder -The `docs` folder in this repo is the main code/API/Extension -docs folder for phoenix. We can autogenerate -most of the GitHub Wiki from the contents of this folder. +This document outlines how documentation is handled in template-nodejs. -### Docs Folder Structure -There are two main components to the docs folder: +## Table of Contents -1. The contents of the `docs` folder are automatically pushed into gitHub wiki - as a sub folder `generatedDocs`. Make changes to Markdown files in this folder - to update wiki entries. -2. The docs in the `docs/generatedApiDocs` are autoGenerated API docs from source. These - `markdown` docs should never be manually edited as their contents will be reset when autogenerated - and all manually made changes will be lost. +1. [The `docs` Folder](#the-docs-folder) +2. [API Documentation](#api-documentation) +3. [Writing JSDoc](#writing-jsdoc) +4. [JSDoc Examples](#jsdoc-examples) +5. [Generating Documentation](#generating-documentation) -## How To Write API docs -This section covers how API docs are auto generated nad how you can update API docs. +--- -To Include any `.js` file containing [JSDocs](https://jsdoc.app/) in Phoenix API docs, -have this comment in any part of the file: -```js -// @INCLUDE_IN_API_DOCS +## The `docs` Folder + +The `docs` folder contains both manual documentation and auto-generated API documentation. + +### Docs Folder Structure + +``` +docs/ +├── How-To-Write-Docs.md # This file +├── generatedApiDocs/ # Auto-generated API docs (DO NOT EDIT MANUALLY) +│ └── index-API.md +└── (your manual documentation) # Any additional markdown docs you create ``` -### Writing JSDoc in source -Let us consider this example source file [src/utils/Metrics.js](https://github.com/phcode-dev/phoenix/blob/main/src/utils/Metrics.js). -The file is created with [JSDoc](https://jsdoc.app/) compliant source code comments. Make changes to -the source code comments as required. +**Important Notes:** +- Files in `docs/generatedApiDocs/` are auto-generated and should **never** be manually edited +- Any manual changes to generated docs will be lost when regenerated +- Manual documentation can be added directly to the `docs/` folder -When you run `npm run createJSDocs` or `npm run build` the corresponding docs for the js file are automatically generated at [docs/generatedApiDocs/utils/Metrics-API.md](https://github.com/phcode-dev/phoenix/blob/main/docs/generatedApiDocs/utils/Metrics-API.md). +--- -The generated docs should be pushed to phoenix repo if there are any doc changes. -Once the changes are pushed, the build system will update the docs in all -[wikis](https://github.com/phcode-dev/phoenix/wiki)/ -[doc](https://docs.phcode.dev/) sites automatically. +## API Documentation -## JSDoc Examples +This project uses [JSDoc](https://jsdoc.app/) to automatically generate API documentation from source code comments. -### Declaring a Module with `@module` tag +### Including Files in API Docs -This document outlines how documentation is handled in phoenix. +To include any `.js` file in the generated API documentation, add this comment anywhere in the file: -## The `docs` folder -The `docs` folder in Phoenix repo is the main code/API/Extension -docs folder for phoenix. Most of the Phoenix GitHub Wiki is -autogenerated from the contents of this folder. +```js +// @INCLUDE_IN_API_DOCS +``` -### Docs Folder Structure -There are two main components to the docs folder: +**Example:** +```js +/* + * GNU AGPL-3.0 License + * Copyright (c) 2021 - present core.ai . All rights reserved. + */ -1. The contents of the `docs` folder are automatically pushed into gitHub wiki - as a sub folder `generatedDocs`. Make changes to Markdown files in this folder - to update wiki entries. -2. The docs in the `docs/generatedApiDocs` are autoGenerated API docs from source. These - `markdown` docs should never be manually edited as their contents will be reset when autogenerated - and all manually made changes will be lost. +// @INCLUDE_IN_API_DOCS -## How To Write API docs -This section covers how API docs are auto generated nad how you can update API docs. +/** + * Write your module docs here. tell something about this module in markdown. + * + * @module hello + */ + +export function greet(name) { + return `Hello ${name}!`; +} +``` + +--- + +## Writing JSDoc + +JSDoc comments use the `/** */` format and support various tags to document your code. + +### Basic Structure -To Include any `.js` file containing [JSDocs](https://jsdoc.app/) in Phoenix API docs, -have this comment in any part of the file: ```js -// @INCLUDE_IN_API_DOCS +/** + * Brief description of the function + * + * @param {string} name - Parameter description + * @returns {string} Return value description + */ +function greet(name) { + return `Hello ${name}!`; +} ``` -### Writing JSDoc in source -Let us consider this example source file [src/utils/Metrics.js](https://github.com/phcode-dev/phoenix/blob/main/src/utils/Metrics.js). -The file is created with [JSDoc](https://jsdoc.app/) compliant source code comments. Make changes to -the source code comments as required. +### Common JSDoc Tags -When you run `npm run createJSDocs` or `npm run build` the corresponding docs for the js file are automatically generated at [docs/generatedApiDocs/utils/Metrics-API.md](https://github.com/phcode-dev/phoenix/blob/main/docs/generatedApiDocs/utils/Metrics-API.md). +| Tag | Purpose | Example | +|-----|---------|---------| +| `@module` | Declare a module | `@module utils/logger` | +| `@param` | Document parameter | `@param {string} name - User name` | +| `@returns` | Document return value | `@returns {boolean} Success status` | +| `@type` | Declare type | `@type {number}` | +| `@typedef` | Define custom type | `@typedef {Object} Config` | +| `@example` | Add usage example | `@example greet("World")` | +| `@private` | Mark as private (excluded from docs) | `@private` | -The generated docs should be pushed to phoenix repo if there are any doc changes. -Once the changes are pushed, the build system will update the docs in all -[wikis](https://github.com/phcode-dev/phoenix/wiki)/ -[doc](https://docs.phcode.dev/) sites automatically. +--- ## JSDoc Examples -This section outlines the various tags available in JSDoc to style docs with examples. -note: All comment blocks wit `@private` tag will be ignored and won't appear in the docs. +### 1. Declaring a Module with `@module` -### 1. Declaring a Module with `@module` tag - -Use this to declare top level JS modules +Use this to declare top-level JS modules: ```js /** - * The Metrics API can be used to send analytics data to track feature usage in accordance with users privacy settings. + * Logger utility for stage-based logging * - *`Status: Internal - Not to be used by third party extensions.` + * Provides createLogger() for app logging and createFastifyLogger() + * for Fastify server logging. Automatically adapts based on stage: + * - Development: Pretty, colorized logs + * - Production: ECS JSON format for Elasticsearch * - * ### Import - * ``` -* // usage within core: -* const Metrics = require("utils/Metrics"); -* -* // usage within default extensions: -* const Metrics = brackets.getModule("utils/Metrics"); -* ``` -* -* @module utils/Metrics - */ + * @module utils/logger + */ ``` -Will Result in the following Markdown -> ## utils/Metrics -> The Metrics API can be used to send analytics data to track feature usage in accordance with users privacy settings. ->`Status: Internal - Not to be used by third party extensions.` ->### Import ->```js ->// usage within core: ->const Metrics = require("utils/Metrics"); ->// usage within default extensions: ->const Metrics = brackets.getModule("utils/Metrics"); ->``` - -### 2. Putting custom section in Markdown using `@name` tag +**Result in docs:** +> ## utils/logger +> Logger utility for stage-based logging +> +> Provides createLogger() for app logging and createFastifyLogger() +> for Fastify server logging. Automatically adapts based on stage: +> - Development: Pretty, colorized logs +> - Production: ECS JSON format for Elasticsearch -Use this to put custom markdown anywhere in the doc file. +### 2. Documenting Functions ```js /** - * This section outlines the properties and methods available in this module - * @name API + * Gets the stage from app.json and sets NODE_ENV accordingly + * + * Supports both short and long stage names: + * - "dev" or "development" → NODE_ENV="development" + * - "prod" or "production" → NODE_ENV="production" + * + * @returns {string} The stage value from config + * @example + * const stage = getStage(); + * console.log(stage); // "dev" or "prod" */ +export function getStage() { + // implementation +} ``` -Will Result in the following Markdown -> ## API ->This section outlines the properties and methods available in this module +### 3. Documenting Parameters -### 3. types/constants using `@type` or `const` +```js +/** + * Creates a Fastify logger configuration based on the stage + * + * @param {Object} options - Logger options + * @param {boolean} options.colorize - Enable colors in development + * @param {string} options.level - Log level (default: 'info') + * @returns {Object} Fastify logger configuration + */ +export function createFastifyLogger(options = {}) { + // implementation +} +``` -Use this to put constants or type definitions +### 4. Constants and Types ```js /** - * The max audit entries, default is 3000 + * The maximum number of retry attempts * @const {number} */ -const MAX_AUDIT_ENTRIES = 3000; +const MAX_RETRIES = 3; + +/** + * HTTP status codes + * @typedef {Object} StatusCodes + * @property {number} OK - Success status (200) + * @property {number} NOT_FOUND - Not found status (404) + * @property {number} ERROR - Server error status (500) + */ +const STATUS_CODES = { + OK: 200, + NOT_FOUND: 404, + ERROR: 500 +}; ``` -Will Result in the following Markdown -> ## MAX_AUDIT_ENTRIES -> ->The max audit entries, default is 3000 -> ->Type: [number]() +### 5. Complex Types with @typedef + +```js +/** + * Application configuration object + * + * @typedef {Object} AppConfig + * @property {string} stage - Environment stage ('dev' or 'prod') + * @property {number} port - Server port number + * @property {string} authKey - Authentication key + * @property {boolean} allowPublicAccess - Allow public access + * @property {LogConfig} [log] - Optional logging configuration + */ + +/** + * Logging configuration for Elasticsearch + * + * @typedef {Object} LogConfig + * @property {string} elasticsearch_host - Elasticsearch host URL + * @property {string} elasticsearch_api_key - API key for authentication + */ +``` +### 6. Private Documentation -### 4. Objects using `@typedef` and `@type` +Use `@private` to exclude from generated documentation: -Use this to doccment Objects +```js +/** + * Internal helper function + * @private + */ +function _internalHelper() { + // This won't appear in generated docs +} +``` -#### Metrhord 1 +### 7. Examples with @example ```js /** - * The Type of events that can be specified as an `eventType` in the API calls. + * Validates configuration object * - * ### Properties - * `PLATFORM`, `PROJECT`, `THEMES` + * @param {Object} config - Configuration to validate + * @returns {boolean} True if valid + * @throws {Error} If configuration is invalid * - * @typedef EVENT_TYPE - * @type {Object} + * @example + * // Valid configuration + * const valid = validateConfig({ + * stage: 'dev', + * port: 5000 + * }); + * + * @example Invalid configuration throws error + * validateConfig({}); // throws Error: Missing stage field */ -const EVENT_TYPE = { - PLATFORM: "platform", - PROJECT: "project", - THEMES: "themes" -}; +function validateConfig(config) { + // implementation +} ``` -Will Result in the following Markdown -> ## EVENT_TYPE -> The Type of events that can be specified as an `eventType` in the API calls. ->### Properties ->`PLATFORM`, `PROJECT`, `THEMES` -> -> Type: [Object]() +--- + +## Generating Documentation + +### Build Documentation + +Run one of these commands to generate API documentation: + +```bash +# Generate docs only +npm run createJSDocs + +# Run full build (includes docs generation) +npm run build +``` + +### Where Docs Are Generated + +Generated documentation is created in `docs/generatedApiDocs/`: + +``` +docs/generatedApiDocs/ +├── index-API.md # Main API documentation +└── (other generated files) +``` + +### Review Generated Docs + +After generation: +1. Review the generated markdown files in `docs/generatedApiDocs/` +2. Commit the generated docs if there are changes +3. The documentation will be available in your repository + +--- + +## Best Practices -#### Metrhord 2 +### 1. Always Include Module Documentation + +Start each file with a module declaration: ```js +// @INCLUDE_IN_API_DOCS + /** - * The Type of events that can be specified as an `eventType` in the API calls. + * Brief description of what this module does * - * @typedef EVENT_TYPE - * @type {Object} - * @property {string} PLATFORM - * @property {string} PROJECT - * @property {string} THEMES + * @module path/to/module */ -const EVENT_TYPE = { - PLATFORM: "platform", - PROJECT: "project", - THEMES: "themes" -}; ``` -Will Result in the following Markdown -> ## EVENT_TYPE -> The Type of events that can be specified as an `eventType` in the API calls. -> -> Type: [Object]() ->### Properties -> ->* `PLATFORM` **[string]()** ->* `PROJECT` **[string]()** ->* `THEMES` **[string]()** +### 2. Document All Public Functions +Every exported function should have JSDoc: -### 5. Function docs with `@function` or `method` +```js +/** + * What the function does + * + * @param {Type} param - Parameter description + * @returns {Type} Return value description + */ +export function myFunction(param) { + // implementation +} +``` -Use this to document a function and its arguments/returns/exceptions. +### 3. Use Examples Liberally -**Note that the `EVENT_TYPE` is autodetected and hyperlinked by the framework** +Examples help users understand how to use your API: ```js /** - * log a numeric count >=0 - * - * @example To log that user clicked searchButton 5 times: - * Metrics.countEvent(Metrics.EVENT_TYPE.UI, "searchButton", "click", 5); + * Function description * - * @param {EVENT_TYPE|string} eventType The kind of Event Type that needs to be logged- should be a js var compatible string. - * Some standard event types are available as `EVENT_TYPE`. - * @param {number} count >=0 - * @type {function} + * @example + * const result = myFunction('input'); + * console.log(result); // 'output' + */ +``` + +### 4. Mark Internal Functions as Private + +Don't pollute public docs with internal helpers: + +```js +/** + * @private */ -function countEvent(eventType, count) {} +function _internalHelper() { + // not in public docs +} ``` -Will Result in the following Markdown: +### 5. Keep Descriptions Clear and Concise + +- Start with a brief summary +- Add details in subsequent paragraphs +- Use markdown formatting for clarity + +### 6. Update Docs When Changing Code + +- Regenerate docs after API changes +- Commit documentation changes with code changes +- Keep docs in sync with implementation + +--- + +## Additional Resources + +- [JSDoc Official Documentation](https://jsdoc.app/) +- [JSDoc Tags Reference](https://jsdoc.app/index.html#block-tags) +- [documentation.js](https://documentation.js.org/) - The tool we use for generation +- [Markdown Guide](https://www.markdownguide.org/) - For formatting docs -> ## countEvent -> log a numeric count >=0 -> Type: [function][2] ->### Parameters ->* `eventType` **([EVENT_TYPE]() | [string]())** The kind of Event Type that needs to be logged- should be a js var compatible string. - Some standard event types are available as `EVENT_TYPE`. ->* `count` **[number]()** > \=0 ->### Examples ->To log that user clicked searchButton 5 times: ->```javascript ->Metrics.countEvent(Metrics.EVENT_TYPE.UI, 5); ->``` +--- -### 6. More tags +## Questions or Issues? -Additional tags can be found at the JSDocs docs at [JSDocs](https://jsdoc.app/) +If you have questions about documentation: +1. Check the [JSDoc documentation](https://jsdoc.app/) +2. Look at existing well-documented files in the `src/` directory +3. Open an issue in the repository diff --git a/src/utils/logger.js b/src/utils/logger.js index 79aa9fa..3ecc58d 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -3,6 +3,8 @@ * Copyright (c) 2021 - present core.ai . All rights reserved. */ +// @INCLUDE_IN_API_DOCS + import {pino} from 'pino'; import {ecsFormat} from '@elastic/ecs-pino-format'; import {getStage} from './configs.js'; From cee995d4ef2c1d2f49443cc858e7cf04ce42dfd8 Mon Sep 17 00:00:00 2001 From: Abraham Date: Sun, 12 Oct 2025 17:41:32 +0000 Subject: [PATCH 3/3] test: update tests --- docs/generatedApiDocs/GitHub-API-Index.md | 3 +- docs/generatedApiDocs/utils/logger-API.md | 21 +++ test/unit/utils/.app.json | 1 + test/unit/utils/configs-test.spec.js | 121 ++++++++++++++- test/unit/utils/logger.spec.js | 172 ++++++++++++++++++++++ 5 files changed, 315 insertions(+), 3 deletions(-) create mode 100644 docs/generatedApiDocs/utils/logger-API.md create mode 100644 test/unit/utils/logger.spec.js diff --git a/docs/generatedApiDocs/GitHub-API-Index.md b/docs/generatedApiDocs/GitHub-API-Index.md index 435ccb6..2907743 100644 --- a/docs/generatedApiDocs/GitHub-API-Index.md +++ b/docs/generatedApiDocs/GitHub-API-Index.md @@ -1,4 +1,5 @@ # API docs The list of all APIs: -1. [index](index-API) \ No newline at end of file +1. [index](index-API) +1. [utils/logger](logger-API) \ No newline at end of file diff --git a/docs/generatedApiDocs/utils/logger-API.md b/docs/generatedApiDocs/utils/logger-API.md new file mode 100644 index 0000000..20bea9c --- /dev/null +++ b/docs/generatedApiDocs/utils/logger-API.md @@ -0,0 +1,21 @@ + + +## createLogger + +Creates a logger instance based on the stage configuration + +* Development: Pretty formatted, colorized logs +* Production/Staging: ECS JSON format for Elasticsearch + +Returns **pino.Logger** Configured Pino logger instance + +## createFastifyLogger + +Creates a Fastify logger configuration based on the stage + +* Development: Pretty formatted, colorized logs +* Production/Staging: ECS JSON format for Elasticsearch + +Returns **[object][1]** Fastify logger configuration + +[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object diff --git a/test/unit/utils/.app.json b/test/unit/utils/.app.json index 8499c4a..ab8b7b5 100644 --- a/test/unit/utils/.app.json +++ b/test/unit/utils/.app.json @@ -1,4 +1,5 @@ { + "stage": "dev", "port": 5000, "authKey": "hehe", "allowPublicAccess": false, diff --git a/test/unit/utils/configs-test.spec.js b/test/unit/utils/configs-test.spec.js index aa816f3..b65fc95 100644 --- a/test/unit/utils/configs-test.spec.js +++ b/test/unit/utils/configs-test.spec.js @@ -1,6 +1,6 @@ -/*global describe, it*/ +/*global describe, it, beforeEach, afterEach*/ import * as chai from 'chai'; -import {clearAppConfig, getConfigs} from "../../../src/utils/configs.js"; +import {clearAppConfig, getConfigs, getStage} from "../../../src/utils/configs.js"; let expect = chai.expect; @@ -49,3 +49,120 @@ function _verifyConfigs(configs) { expect(configs.mysql.database.length).to.gt(0); } + +describe('unit Tests for getStage', function () { + let originalNodeEnv; + + beforeEach(function () { + originalNodeEnv = process.env.NODE_ENV; + clearAppConfig(); + }); + + afterEach(function () { + process.env.NODE_ENV = originalNodeEnv; + clearAppConfig(); + }); + + it('should return "dev" and set NODE_ENV to "development" for stage "dev"', function () { + const stage = getStage(); + expect(stage).to.eql('dev'); + expect(process.env.NODE_ENV).to.eql('development'); + }); + + it('should handle "development" stage and set NODE_ENV correctly', async function () { + // Temporarily modify config to test different stage + const fs = await import('fs'); + const originalConfig = fs.readFileSync(process.env.APP_CONFIG, 'utf8'); + const config = JSON.parse(originalConfig); + config.stage = 'development'; + fs.writeFileSync(process.env.APP_CONFIG, JSON.stringify(config, null, 2)); + clearAppConfig(); + + const stage = getStage(); + expect(stage).to.eql('development'); + expect(process.env.NODE_ENV).to.eql('development'); + + // Restore original + fs.writeFileSync(process.env.APP_CONFIG, originalConfig); + }); + + it('should handle "prod" stage and set NODE_ENV to "production"', async function () { + const fs = await import('fs'); + const originalConfig = fs.readFileSync(process.env.APP_CONFIG, 'utf8'); + const config = JSON.parse(originalConfig); + config.stage = 'prod'; + fs.writeFileSync(process.env.APP_CONFIG, JSON.stringify(config, null, 2)); + clearAppConfig(); + + const stage = getStage(); + expect(stage).to.eql('prod'); + expect(process.env.NODE_ENV).to.eql('production'); + + // Restore original + fs.writeFileSync(process.env.APP_CONFIG, originalConfig); + }); + + it('should handle "production" stage and set NODE_ENV correctly', async function () { + const fs = await import('fs'); + const originalConfig = fs.readFileSync(process.env.APP_CONFIG, 'utf8'); + const config = JSON.parse(originalConfig); + config.stage = 'production'; + fs.writeFileSync(process.env.APP_CONFIG, JSON.stringify(config, null, 2)); + clearAppConfig(); + + const stage = getStage(); + expect(stage).to.eql('production'); + expect(process.env.NODE_ENV).to.eql('production'); + + // Restore original + fs.writeFileSync(process.env.APP_CONFIG, originalConfig); + }); + + it('should default to "development" when stage is missing', async function () { + const fs = await import('fs'); + const originalConfig = fs.readFileSync(process.env.APP_CONFIG, 'utf8'); + const config = JSON.parse(originalConfig); + delete config.stage; + fs.writeFileSync(process.env.APP_CONFIG, JSON.stringify(config, null, 2)); + clearAppConfig(); + + const stage = getStage(); + expect(stage).to.eql('development'); + expect(process.env.NODE_ENV).to.eql('development'); + + // Restore original + fs.writeFileSync(process.env.APP_CONFIG, originalConfig); + }); + + it('should be case insensitive - DEV should work', async function () { + const fs = await import('fs'); + const originalConfig = fs.readFileSync(process.env.APP_CONFIG, 'utf8'); + const config = JSON.parse(originalConfig); + config.stage = 'DEV'; + fs.writeFileSync(process.env.APP_CONFIG, JSON.stringify(config, null, 2)); + clearAppConfig(); + + const stage = getStage(); + expect(stage).to.eql('DEV'); + expect(process.env.NODE_ENV).to.eql('development'); + + // Restore original + fs.writeFileSync(process.env.APP_CONFIG, originalConfig); + }); + + it('should be case insensitive - PROD should work', async function () { + const fs = await import('fs'); + const originalConfig = fs.readFileSync(process.env.APP_CONFIG, 'utf8'); + const config = JSON.parse(originalConfig); + config.stage = 'PROD'; + fs.writeFileSync(process.env.APP_CONFIG, JSON.stringify(config, null, 2)); + clearAppConfig(); + + const stage = getStage(); + expect(stage).to.eql('PROD'); + expect(process.env.NODE_ENV).to.eql('production'); + + // Restore original + fs.writeFileSync(process.env.APP_CONFIG, originalConfig); + }); +}); diff --git a/test/unit/utils/logger.spec.js b/test/unit/utils/logger.spec.js new file mode 100644 index 0000000..590b21f --- /dev/null +++ b/test/unit/utils/logger.spec.js @@ -0,0 +1,172 @@ +/*global describe, it, beforeEach, afterEach*/ +import * as chai from 'chai'; +import {createLogger, createFastifyLogger} from "../../../src/utils/logger.js"; +import {clearAppConfig} from "../../../src/utils/configs.js"; + +let expect = chai.expect; + +describe('unit Tests for logger', function () { + let originalNodeEnv; + + beforeEach(function () { + originalNodeEnv = process.env.NODE_ENV; + clearAppConfig(); + }); + + afterEach(function () { + process.env.NODE_ENV = originalNodeEnv; + clearAppConfig(); + }); + + describe('createLogger', function () { + it('should create a logger in development mode', function () { + // Config has stage: "dev" by default + const logger = createLogger(); + + expect(logger).to.exist; + expect(typeof logger.info).to.eql('function'); + expect(typeof logger.warn).to.eql('function'); + expect(typeof logger.error).to.eql('function'); + expect(typeof logger.debug).to.eql('function'); + }); + + it('should create a logger in production mode', async function () { + const fs = await import('fs'); + const originalConfig = fs.readFileSync(process.env.APP_CONFIG, 'utf8'); + const config = JSON.parse(originalConfig); + config.stage = 'prod'; + fs.writeFileSync(process.env.APP_CONFIG, JSON.stringify(config, null, 2)); + clearAppConfig(); + + const logger = createLogger(); + + expect(logger).to.exist; + expect(typeof logger.info).to.eql('function'); + expect(typeof logger.warn).to.eql('function'); + expect(typeof logger.error).to.eql('function'); + + // Restore original + fs.writeFileSync(process.env.APP_CONFIG, originalConfig); + }); + + it('should be able to log messages in development', function () { + const logger = createLogger(); + + // Should not throw + expect(() => logger.info('Test message')).to.not.throw(); + expect(() => logger.warn('Warning message')).to.not.throw(); + expect(() => logger.error('Error message')).to.not.throw(); + }); + + it('should be able to log messages in production', async function () { + const fs = await import('fs'); + const originalConfig = fs.readFileSync(process.env.APP_CONFIG, 'utf8'); + const config = JSON.parse(originalConfig); + config.stage = 'production'; + fs.writeFileSync(process.env.APP_CONFIG, JSON.stringify(config, null, 2)); + clearAppConfig(); + + const logger = createLogger(); + + // Should not throw + expect(() => logger.info('Test message')).to.not.throw(); + expect(() => logger.warn('Warning message')).to.not.throw(); + expect(() => logger.error('Error message')).to.not.throw(); + + // Restore original + fs.writeFileSync(process.env.APP_CONFIG, originalConfig); + }); + + it('should set NODE_ENV to development for dev stage', function () { + createLogger(); + expect(process.env.NODE_ENV).to.eql('development'); + }); + + it('should set NODE_ENV to production for prod stage', async function () { + const fs = await import('fs'); + const originalConfig = fs.readFileSync(process.env.APP_CONFIG, 'utf8'); + const config = JSON.parse(originalConfig); + config.stage = 'prod'; + fs.writeFileSync(process.env.APP_CONFIG, JSON.stringify(config, null, 2)); + clearAppConfig(); + + createLogger(); + expect(process.env.NODE_ENV).to.eql('production'); + + // Restore original + fs.writeFileSync(process.env.APP_CONFIG, originalConfig); + }); + }); + + describe('createFastifyLogger', function () { + it('should create Fastify logger config in development mode', function () { + const loggerConfig = createFastifyLogger(); + + expect(loggerConfig).to.exist; + expect(typeof loggerConfig).to.eql('object'); + + // In development mode, should have transport property + if (loggerConfig.transport) { + expect(loggerConfig.transport.target).to.exist; + } + }); + + it('should create Fastify logger config in production mode', async function () { + const fs = await import('fs'); + const originalConfig = fs.readFileSync(process.env.APP_CONFIG, 'utf8'); + const config = JSON.parse(originalConfig); + config.stage = 'prod'; + fs.writeFileSync(process.env.APP_CONFIG, JSON.stringify(config, null, 2)); + clearAppConfig(); + + const loggerConfig = createFastifyLogger(); + + expect(loggerConfig).to.exist; + expect(typeof loggerConfig).to.eql('object'); + + // Restore original + fs.writeFileSync(process.env.APP_CONFIG, originalConfig); + }); + + it('should return different configs for dev vs prod', async function () { + // Get dev config + const devConfig = createFastifyLogger(); + + // Change to prod + const fs = await import('fs'); + const originalConfig = fs.readFileSync(process.env.APP_CONFIG, 'utf8'); + const config = JSON.parse(originalConfig); + config.stage = 'prod'; + fs.writeFileSync(process.env.APP_CONFIG, JSON.stringify(config, null, 2)); + clearAppConfig(); + + const prodConfig = createFastifyLogger(); + + // Configs should be different objects + expect(devConfig).to.not.deep.equal(prodConfig); + + // Restore original + fs.writeFileSync(process.env.APP_CONFIG, originalConfig); + }); + + it('should set NODE_ENV to development for dev stage', function () { + createFastifyLogger(); + expect(process.env.NODE_ENV).to.eql('development'); + }); + + it('should set NODE_ENV to production for prod stage', async function () { + const fs = await import('fs'); + const originalConfig = fs.readFileSync(process.env.APP_CONFIG, 'utf8'); + const config = JSON.parse(originalConfig); + config.stage = 'production'; + fs.writeFileSync(process.env.APP_CONFIG, JSON.stringify(config, null, 2)); + clearAppConfig(); + + createFastifyLogger(); + expect(process.env.NODE_ENV).to.eql('production'); + + // Restore original + fs.writeFileSync(process.env.APP_CONFIG, originalConfig); + }); + }); +});