@freshguard/freshguard-core 0.13.2 ā 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -1
- package/README.md +74 -1
- package/SKILL.md +229 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +74 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/connectors/azure-sql.d.ts +121 -0
- package/dist/connectors/azure-sql.d.ts.map +1 -0
- package/dist/connectors/azure-sql.js +489 -0
- package/dist/connectors/azure-sql.js.map +1 -0
- package/dist/connectors/base-connector.d.ts +139 -0
- package/dist/connectors/base-connector.d.ts.map +1 -1
- package/dist/connectors/base-connector.js +160 -3
- package/dist/connectors/base-connector.js.map +1 -1
- package/dist/connectors/bigquery.d.ts +100 -0
- package/dist/connectors/bigquery.d.ts.map +1 -1
- package/dist/connectors/bigquery.js +143 -2
- package/dist/connectors/bigquery.js.map +1 -1
- package/dist/connectors/duckdb.d.ts +96 -0
- package/dist/connectors/duckdb.d.ts.map +1 -1
- package/dist/connectors/duckdb.js +144 -7
- package/dist/connectors/duckdb.js.map +1 -1
- package/dist/connectors/index.d.ts +28 -0
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +28 -0
- package/dist/connectors/index.js.map +1 -1
- package/dist/connectors/mssql.d.ts +119 -0
- package/dist/connectors/mssql.d.ts.map +1 -0
- package/dist/connectors/mssql.js +483 -0
- package/dist/connectors/mssql.js.map +1 -0
- package/dist/connectors/mysql.d.ts +85 -0
- package/dist/connectors/mysql.d.ts.map +1 -1
- package/dist/connectors/mysql.js +118 -3
- package/dist/connectors/mysql.js.map +1 -1
- package/dist/connectors/postgres.d.ts +85 -0
- package/dist/connectors/postgres.d.ts.map +1 -1
- package/dist/connectors/postgres.js +113 -6
- package/dist/connectors/postgres.js.map +1 -1
- package/dist/connectors/redshift.d.ts +90 -0
- package/dist/connectors/redshift.d.ts.map +1 -1
- package/dist/connectors/redshift.js +131 -7
- package/dist/connectors/redshift.js.map +1 -1
- package/dist/connectors/snowflake.d.ts +108 -0
- package/dist/connectors/snowflake.d.ts.map +1 -1
- package/dist/connectors/snowflake.js +137 -3
- package/dist/connectors/snowflake.js.map +1 -1
- package/dist/connectors/synapse.d.ts +123 -0
- package/dist/connectors/synapse.d.ts.map +1 -0
- package/dist/connectors/synapse.js +495 -0
- package/dist/connectors/synapse.js.map +1 -0
- package/dist/db/index.d.ts +25 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +23 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrate.d.ts +23 -0
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +38 -0
- package/dist/db/migrate.js.map +1 -1
- package/dist/db/schema.d.ts +11 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +70 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/errors/debug-factory.d.ts +38 -0
- package/dist/errors/debug-factory.d.ts.map +1 -1
- package/dist/errors/debug-factory.js +40 -0
- package/dist/errors/debug-factory.js.map +1 -1
- package/dist/errors/index.d.ts +59 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +110 -7
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +32 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata/duckdb-storage.d.ts +3 -0
- package/dist/metadata/duckdb-storage.d.ts.map +1 -1
- package/dist/metadata/duckdb-storage.js +6 -0
- package/dist/metadata/duckdb-storage.js.map +1 -1
- package/dist/metadata/factory.d.ts +30 -0
- package/dist/metadata/factory.d.ts.map +1 -1
- package/dist/metadata/factory.js +31 -0
- package/dist/metadata/factory.js.map +1 -1
- package/dist/metadata/index.d.ts +26 -0
- package/dist/metadata/index.d.ts.map +1 -1
- package/dist/metadata/index.js +26 -0
- package/dist/metadata/index.js.map +1 -1
- package/dist/metadata/interface.d.ts +33 -0
- package/dist/metadata/interface.d.ts.map +1 -1
- package/dist/metadata/interface.js +3 -0
- package/dist/metadata/interface.js.map +1 -1
- package/dist/metadata/postgresql-storage.d.ts +3 -0
- package/dist/metadata/postgresql-storage.d.ts.map +1 -1
- package/dist/metadata/postgresql-storage.js +12 -2
- package/dist/metadata/postgresql-storage.js.map +1 -1
- package/dist/metadata/schema-config.d.ts +53 -0
- package/dist/metadata/schema-config.d.ts.map +1 -1
- package/dist/metadata/schema-config.js +64 -0
- package/dist/metadata/schema-config.js.map +1 -1
- package/dist/metadata/types.d.ts +3 -0
- package/dist/metadata/types.d.ts.map +1 -1
- package/dist/metadata/types.js +3 -0
- package/dist/metadata/types.js.map +1 -1
- package/dist/monitor/baseline-calculator.d.ts +56 -0
- package/dist/monitor/baseline-calculator.d.ts.map +1 -1
- package/dist/monitor/baseline-calculator.js +72 -0
- package/dist/monitor/baseline-calculator.js.map +1 -1
- package/dist/monitor/baseline-config.d.ts +77 -0
- package/dist/monitor/baseline-config.d.ts.map +1 -1
- package/dist/monitor/baseline-config.js +79 -1
- package/dist/monitor/baseline-config.js.map +1 -1
- package/dist/monitor/freshness.d.ts +40 -0
- package/dist/monitor/freshness.d.ts.map +1 -1
- package/dist/monitor/freshness.js +82 -3
- package/dist/monitor/freshness.js.map +1 -1
- package/dist/monitor/index.d.ts +29 -0
- package/dist/monitor/index.d.ts.map +1 -1
- package/dist/monitor/index.js +29 -0
- package/dist/monitor/index.js.map +1 -1
- package/dist/monitor/schema-baseline.d.ts +45 -0
- package/dist/monitor/schema-baseline.d.ts.map +1 -1
- package/dist/monitor/schema-baseline.js +63 -5
- package/dist/monitor/schema-baseline.js.map +1 -1
- package/dist/monitor/schema-changes.d.ts +45 -0
- package/dist/monitor/schema-changes.d.ts.map +1 -1
- package/dist/monitor/schema-changes.js +85 -0
- package/dist/monitor/schema-changes.js.map +1 -1
- package/dist/monitor/volume.d.ts +43 -0
- package/dist/monitor/volume.d.ts.map +1 -1
- package/dist/monitor/volume.js +89 -0
- package/dist/monitor/volume.js.map +1 -1
- package/dist/observability/logger.d.ts +91 -0
- package/dist/observability/logger.d.ts.map +1 -1
- package/dist/observability/logger.js +108 -0
- package/dist/observability/logger.js.map +1 -1
- package/dist/observability/metrics.d.ts +140 -0
- package/dist/observability/metrics.d.ts.map +1 -1
- package/dist/observability/metrics.js +184 -7
- package/dist/observability/metrics.js.map +1 -1
- package/dist/resilience/circuit-breaker.d.ts +112 -2
- package/dist/resilience/circuit-breaker.d.ts.map +1 -1
- package/dist/resilience/circuit-breaker.js +140 -6
- package/dist/resilience/circuit-breaker.js.map +1 -1
- package/dist/resilience/index.d.ts +9 -0
- package/dist/resilience/index.d.ts.map +1 -1
- package/dist/resilience/index.js +13 -0
- package/dist/resilience/index.js.map +1 -1
- package/dist/resilience/retry-policy.d.ts +105 -0
- package/dist/resilience/retry-policy.d.ts.map +1 -1
- package/dist/resilience/retry-policy.js +158 -7
- package/dist/resilience/retry-policy.js.map +1 -1
- package/dist/resilience/timeout-manager.d.ts +137 -0
- package/dist/resilience/timeout-manager.d.ts.map +1 -1
- package/dist/resilience/timeout-manager.js +151 -4
- package/dist/resilience/timeout-manager.js.map +1 -1
- package/dist/security/query-analyzer.d.ts +124 -0
- package/dist/security/query-analyzer.d.ts.map +1 -1
- package/dist/security/query-analyzer.js +150 -9
- package/dist/security/query-analyzer.js.map +1 -1
- package/dist/security/schema-cache.d.ts +152 -0
- package/dist/security/schema-cache.d.ts.map +1 -1
- package/dist/security/schema-cache.js +144 -12
- package/dist/security/schema-cache.js.map +1 -1
- package/dist/types/connector.d.ts +68 -1
- package/dist/types/connector.d.ts.map +1 -1
- package/dist/types/connector.js +38 -15
- package/dist/types/connector.js.map +1 -1
- package/dist/types/driver-results.d.ts +28 -0
- package/dist/types/driver-results.d.ts.map +1 -1
- package/dist/types/driver-results.js +12 -0
- package/dist/types/driver-results.js.map +1 -1
- package/dist/types.d.ts +113 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -1
- package/dist/validation/index.d.ts +8 -0
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +12 -0
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/runtime-validator.d.ts +98 -0
- package/dist/validation/runtime-validator.d.ts.map +1 -1
- package/dist/validation/runtime-validator.js +114 -1
- package/dist/validation/runtime-validator.js.map +1 -1
- package/dist/validation/sanitizers.d.ts +59 -0
- package/dist/validation/sanitizers.d.ts.map +1 -1
- package/dist/validation/sanitizers.js +104 -20
- package/dist/validation/sanitizers.js.map +1 -1
- package/dist/validation/schemas.d.ts +73 -0
- package/dist/validation/schemas.d.ts.map +1 -1
- package/dist/validation/schemas.js +132 -5
- package/dist/validation/schemas.js.map +1 -1
- package/dist/validators/index.d.ts +54 -0
- package/dist/validators/index.d.ts.map +1 -1
- package/dist/validators/index.js +93 -2
- package/dist/validators/index.js.map +1 -1
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.15.0] - 2026-02-12
|
|
11
|
+
|
|
10
12
|
### Added
|
|
13
|
+
- **SKILL.md agent skills directory** ā instruction-style documentation for AI coding agents, wired via `agentskills` field in package.json
|
|
14
|
+
- **Per-export API reference** in README ā tables for monitoring functions, connectors, metadata storage, errors, and key types
|
|
15
|
+
- **"When to use this"** paragraph in README for quick orientation
|
|
16
|
+
- **Compatibility section** in README documenting Node, TypeScript, and ESM requirements
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- JSDoc comments now preserved in shipped `.d.ts` files (`removeComments: false` in tsconfig)
|
|
20
|
+
- Enhanced JSDoc on all 9 connector constructors with `@param`, `@example` tags
|
|
21
|
+
- Added module-level JSDoc to `connectors/index`, `monitor/index`, `metadata/index`, `db/index`
|
|
22
|
+
- Added `@example` to `MonitoringRule`, `CheckResult`, and `DataSource` type interfaces
|
|
23
|
+
- Improved `createDatabase()` JSDoc with usage example
|
|
11
24
|
- Documentation site with versioned API reference
|
|
12
25
|
|
|
13
26
|
## [0.12.0] - 2026-02-10
|
|
@@ -148,7 +161,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
148
161
|
- Drizzle ORM database schema and migrations
|
|
149
162
|
- MIT license
|
|
150
163
|
|
|
151
|
-
[Unreleased]: https://github.com/freshguard-dev/freshguard-core/compare/v0.
|
|
164
|
+
[Unreleased]: https://github.com/freshguard-dev/freshguard-core/compare/v0.15.0...HEAD
|
|
165
|
+
[0.15.0]: https://github.com/freshguard-dev/freshguard-core/compare/v0.12.0...v0.15.0
|
|
152
166
|
[0.12.0]: https://github.com/freshguard-dev/freshguard-core/compare/v0.11.3...v0.12.0
|
|
153
167
|
[0.11.3]: https://github.com/freshguard-dev/freshguard-core/compare/v0.11.2...v0.11.3
|
|
154
168
|
[0.11.2]: https://github.com/freshguard-dev/freshguard-core/compare/v0.11.1...v0.11.2
|
package/README.md
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/@freshguard/freshguard-core)
|
|
7
7
|
[](https://freshguard-dev.github.io/freshguard-core)
|
|
8
8
|
|
|
9
|
-
Monitor when your data pipelines go stale. Supports PostgreSQL, DuckDB, BigQuery, Snowflake, MySQL, and
|
|
9
|
+
Monitor when your data pipelines go stale. Supports PostgreSQL, DuckDB, BigQuery, Snowflake, MySQL, Redshift, SQL Server, Azure SQL, and Azure Synapse. Self-hosted. Free forever.
|
|
10
|
+
|
|
11
|
+
**When to use this:** You run data pipelines (ETL/ELT) that land data into a warehouse or database and you need to know ā programmatically ā when a table stops updating, row counts look wrong, or the schema drifts. Install this package in a Node.js script, a cron job, or a long-running process and it will check your tables and tell you what's stale. No dashboard, no vendor lock-in, no account required.
|
|
10
12
|
|
|
11
13
|
## Install
|
|
12
14
|
|
|
@@ -52,6 +54,63 @@ if (result.status === 'alert') {
|
|
|
52
54
|
- **Volume anomaly detection** ā Alert when row counts deviate from a calculated baseline
|
|
53
55
|
- **Schema change detection** ā Alert when columns are added, removed, or modified
|
|
54
56
|
|
|
57
|
+
## API at a glance
|
|
58
|
+
|
|
59
|
+
### Monitoring functions
|
|
60
|
+
|
|
61
|
+
| Export | Description |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `checkFreshness(connector, rule)` | Returns `'alert'` when `MAX(timestampColumn)` exceeds `toleranceMinutes` |
|
|
64
|
+
| `checkVolumeAnomaly(connector, rule)` | Returns `'alert'` when current row count deviates from the historical baseline |
|
|
65
|
+
| `checkSchemaChanges(connector, rule)` | Returns `'alert'` when columns are added, removed, or modified |
|
|
66
|
+
|
|
67
|
+
### Connectors
|
|
68
|
+
|
|
69
|
+
| Export | Database |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `PostgresConnector` | PostgreSQL |
|
|
72
|
+
| `DuckDBConnector` | DuckDB (local file / `:memory:`) |
|
|
73
|
+
| `BigQueryConnector` | Google BigQuery |
|
|
74
|
+
| `SnowflakeConnector` | Snowflake |
|
|
75
|
+
| `MySQLConnector` | MySQL |
|
|
76
|
+
| `RedshiftConnector` | Amazon Redshift |
|
|
77
|
+
| `MSSQLConnector` | SQL Server |
|
|
78
|
+
| `AzureSQLConnector` | Azure SQL Database |
|
|
79
|
+
| `SynapseConnector` | Azure Synapse Analytics |
|
|
80
|
+
|
|
81
|
+
All connectors accept a `ConnectorConfig` (host, port, database, username, password, ssl) and an optional `SecurityConfig` override.
|
|
82
|
+
|
|
83
|
+
### Metadata storage
|
|
84
|
+
|
|
85
|
+
| Export | Description |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `createMetadataStorage(config?)` | Factory ā returns DuckDB (default) or PostgreSQL metadata store |
|
|
88
|
+
| `DuckDBMetadataStorage` | Embedded DuckDB storage (zero-setup) |
|
|
89
|
+
| `PostgreSQLMetadataStorage` | PostgreSQL-backed shared storage |
|
|
90
|
+
|
|
91
|
+
### Database utilities
|
|
92
|
+
|
|
93
|
+
| Export | Description |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `createDatabase(connectionString)` | Create a Drizzle ORM connection to the FreshGuard PostgreSQL schema |
|
|
96
|
+
| `schema` | Drizzle table definitions |
|
|
97
|
+
|
|
98
|
+
### Error classes
|
|
99
|
+
|
|
100
|
+
| Export | Thrown when |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `FreshGuardError` | Base class for all FreshGuard errors |
|
|
103
|
+
| `SecurityError` | SQL injection attempt or blocked query |
|
|
104
|
+
| `ConnectionError` | Database connection failure |
|
|
105
|
+
| `TimeoutError` | Query or connection timeout exceeded |
|
|
106
|
+
| `QueryError` | Query execution failure |
|
|
107
|
+
| `ConfigurationError` | Invalid configuration |
|
|
108
|
+
| `MonitoringError` | Monitoring logic failure |
|
|
109
|
+
|
|
110
|
+
### Key types
|
|
111
|
+
|
|
112
|
+
`MonitoringRule`, `CheckResult`, `DataSource`, `SourceCredentials`, `ConnectorConfig`, `CheckStatus`, `RuleType`, `DataSourceType`, `SchemaChanges`, `ColumnChange`, `FreshGuardConfig`
|
|
113
|
+
|
|
55
114
|
## Documentation
|
|
56
115
|
|
|
57
116
|
**[Read the full docs](https://freshguard-dev.github.io/freshguard-core)** ā guides, API reference, examples, and self-hosting instructions.
|
|
@@ -75,6 +134,9 @@ if (result.status === 'alert') {
|
|
|
75
134
|
| Snowflake | `SnowflakeConnector` | 0.2.0 |
|
|
76
135
|
| MySQL | `MySQLConnector` | 0.11.0 |
|
|
77
136
|
| Redshift | `RedshiftConnector` | 0.11.0 |
|
|
137
|
+
| SQL Server | `MSSQLConnector` | 0.14.0 |
|
|
138
|
+
| Azure SQL Database | `AzureSQLConnector` | 0.14.0 |
|
|
139
|
+
| Azure Synapse Analytics | `SynapseConnector` | 0.14.0 |
|
|
78
140
|
|
|
79
141
|
## CLI
|
|
80
142
|
|
|
@@ -85,6 +147,17 @@ pnpm exec freshguard test
|
|
|
85
147
|
pnpm exec freshguard run
|
|
86
148
|
```
|
|
87
149
|
|
|
150
|
+
## Compatibility
|
|
151
|
+
|
|
152
|
+
| Requirement | Version |
|
|
153
|
+
|---|---|
|
|
154
|
+
| Node.js | >=20.0.0 |
|
|
155
|
+
| TypeScript | >=5.3 (ships `.d.ts` + declaration maps) |
|
|
156
|
+
| Module system | ESM only (`"type": "module"`) |
|
|
157
|
+
| Package manager | pnpm >=10 (npm/yarn work for consumers) |
|
|
158
|
+
|
|
159
|
+
This package follows **semver**. Breaking changes only land in major version bumps. The current version is **0.x** (pre-1.0), so minor versions may contain breaking changes ā check [CHANGELOG.md](CHANGELOG.md) before upgrading.
|
|
160
|
+
|
|
88
161
|
## Architecture
|
|
89
162
|
|
|
90
163
|
This is the **open-source core** (MIT licensed). It contains all monitoring logic, connectors, and CLI tooling. It does not include multi-tenant features, authentication, or dashboards ā those are part of [FreshGuard Cloud](https://freshguard.dev).
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# FreshGuard Core ā Agent Skill
|
|
2
|
+
|
|
3
|
+
## When to use this library
|
|
4
|
+
|
|
5
|
+
Use `@freshguard/freshguard-core` when you need to **programmatically check whether data in a database table is fresh, whether row counts are normal, or whether the schema has changed**. It is the right tool when:
|
|
6
|
+
|
|
7
|
+
- A user asks you to monitor a data pipeline for staleness.
|
|
8
|
+
- A user wants alerts when a table stops updating or row counts look wrong.
|
|
9
|
+
- A user needs to detect schema drift (added/removed/modified columns).
|
|
10
|
+
- You need to connect to PostgreSQL, DuckDB, BigQuery, Snowflake, MySQL, Redshift, SQL Server, Azure SQL, or Azure Synapse and run read-only health checks.
|
|
11
|
+
|
|
12
|
+
Do **not** use this library for writing data, running arbitrary queries, or building dashboards.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm install @freshguard/freshguard-core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
All imports come from the single entry point:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { checkFreshness, PostgresConnector, ... } from '@freshguard/freshguard-core';
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Core operations
|
|
31
|
+
|
|
32
|
+
### 1. Check data freshness
|
|
33
|
+
|
|
34
|
+
Detect when a table hasn't been updated within an expected window.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { checkFreshness, PostgresConnector } from '@freshguard/freshguard-core';
|
|
38
|
+
|
|
39
|
+
// 1. Create a connector
|
|
40
|
+
const connector = new PostgresConnector({
|
|
41
|
+
host: 'localhost', port: 5432,
|
|
42
|
+
database: 'analytics',
|
|
43
|
+
username: 'readonly',
|
|
44
|
+
password: process.env.DB_PASSWORD!,
|
|
45
|
+
ssl: true,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 2. Define a rule
|
|
49
|
+
const rule = {
|
|
50
|
+
id: 'orders-freshness',
|
|
51
|
+
sourceId: 'prod_pg',
|
|
52
|
+
name: 'Orders Freshness',
|
|
53
|
+
tableName: 'orders',
|
|
54
|
+
ruleType: 'freshness' as const,
|
|
55
|
+
toleranceMinutes: 60, // alert if data is older than 60 min
|
|
56
|
+
timestampColumn: 'updated_at', // column to check
|
|
57
|
+
checkIntervalMinutes: 5,
|
|
58
|
+
isActive: true,
|
|
59
|
+
createdAt: new Date(),
|
|
60
|
+
updatedAt: new Date(),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// 3. Run the check
|
|
64
|
+
const result = await checkFreshness(connector, rule);
|
|
65
|
+
|
|
66
|
+
// 4. Inspect the result
|
|
67
|
+
if (result.status === 'alert') {
|
|
68
|
+
console.log(`Data is ${result.lagMinutes} minutes stale`);
|
|
69
|
+
}
|
|
70
|
+
// result.status is 'ok' | 'alert' | 'failed'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Detect volume anomalies
|
|
74
|
+
|
|
75
|
+
Alert when a table's row count deviates from its historical baseline.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { checkVolumeAnomaly, PostgresConnector } from '@freshguard/freshguard-core';
|
|
79
|
+
|
|
80
|
+
const connector = new PostgresConnector({ /* ... */ });
|
|
81
|
+
|
|
82
|
+
const rule = {
|
|
83
|
+
id: 'orders-volume',
|
|
84
|
+
sourceId: 'prod_pg',
|
|
85
|
+
name: 'Orders Volume',
|
|
86
|
+
tableName: 'orders',
|
|
87
|
+
ruleType: 'volume_anomaly' as const,
|
|
88
|
+
baselineWindowDays: 14,
|
|
89
|
+
deviationThresholdPercent: 30, // alert if >30% deviation
|
|
90
|
+
checkIntervalMinutes: 60,
|
|
91
|
+
isActive: true,
|
|
92
|
+
createdAt: new Date(),
|
|
93
|
+
updatedAt: new Date(),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Provide metadata storage so the baseline can be calculated from history
|
|
97
|
+
import { createMetadataStorage } from '@freshguard/freshguard-core';
|
|
98
|
+
const storage = await createMetadataStorage(); // embedded DuckDB
|
|
99
|
+
|
|
100
|
+
const result = await checkVolumeAnomaly(connector, rule, storage);
|
|
101
|
+
|
|
102
|
+
if (result.status === 'alert') {
|
|
103
|
+
console.log(`Row count deviation: ${result.deviation}% from baseline ${result.baselineAverage}`);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 3. Detect schema changes
|
|
108
|
+
|
|
109
|
+
Alert when columns are added, removed, or modified.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { checkSchemaChanges, PostgresConnector } from '@freshguard/freshguard-core';
|
|
113
|
+
|
|
114
|
+
const connector = new PostgresConnector({ /* ... */ });
|
|
115
|
+
|
|
116
|
+
const rule = {
|
|
117
|
+
id: 'orders-schema',
|
|
118
|
+
sourceId: 'prod_pg',
|
|
119
|
+
name: 'Orders Schema',
|
|
120
|
+
tableName: 'orders',
|
|
121
|
+
ruleType: 'schema_change' as const,
|
|
122
|
+
trackColumnChanges: true,
|
|
123
|
+
checkIntervalMinutes: 60,
|
|
124
|
+
isActive: true,
|
|
125
|
+
createdAt: new Date(),
|
|
126
|
+
updatedAt: new Date(),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const storage = await createMetadataStorage();
|
|
130
|
+
const result = await checkSchemaChanges(connector, rule, storage);
|
|
131
|
+
|
|
132
|
+
if (result.status === 'alert' && result.schemaChanges) {
|
|
133
|
+
console.log(result.schemaChanges.summary);
|
|
134
|
+
// e.g. "2 columns added, 1 column removed"
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Connector setup
|
|
141
|
+
|
|
142
|
+
Every connector takes a `ConnectorConfig` object:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
interface ConnectorConfig {
|
|
146
|
+
host: string;
|
|
147
|
+
port: number;
|
|
148
|
+
database: string;
|
|
149
|
+
username: string;
|
|
150
|
+
password: string;
|
|
151
|
+
ssl?: boolean; // default: true
|
|
152
|
+
timeout?: number; // connection timeout ms (default: 30000)
|
|
153
|
+
queryTimeout?: number; // query timeout ms (default: 10000)
|
|
154
|
+
maxRows?: number; // max rows returned (default: 1000)
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
| Connector | Notes |
|
|
159
|
+
|---|---|
|
|
160
|
+
| `PostgresConnector` | Standard PostgreSQL |
|
|
161
|
+
| `DuckDBConnector` | `database` = file path or `':memory:'` |
|
|
162
|
+
| `BigQueryConnector` | `database` = GCP project ID |
|
|
163
|
+
| `SnowflakeConnector` | `host` = `<account>.snowflakecomputing.com` |
|
|
164
|
+
| `MySQLConnector` | Standard MySQL, port 3306 |
|
|
165
|
+
| `RedshiftConnector` | PostgreSQL wire protocol, port 5439 |
|
|
166
|
+
| `MSSQLConnector` | SQL Server, port 1433 |
|
|
167
|
+
| `AzureSQLConnector` | `host` = `<server>.database.windows.net` |
|
|
168
|
+
| `SynapseConnector` | `host` = `<workspace>.sql.azuresynapse.net` |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Metadata storage
|
|
173
|
+
|
|
174
|
+
Volume anomaly and schema change checks need historical data. Pass a
|
|
175
|
+
`MetadataStorage` instance as the third argument:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { createMetadataStorage } from '@freshguard/freshguard-core';
|
|
179
|
+
|
|
180
|
+
// Embedded DuckDB (zero-setup, default)
|
|
181
|
+
const storage = await createMetadataStorage();
|
|
182
|
+
|
|
183
|
+
// Custom DuckDB path
|
|
184
|
+
const storage = await createMetadataStorage({ type: 'duckdb', path: './meta.db' });
|
|
185
|
+
|
|
186
|
+
// PostgreSQL (shared across workers)
|
|
187
|
+
const storage = await createMetadataStorage({
|
|
188
|
+
type: 'postgresql',
|
|
189
|
+
url: 'postgresql://user:pass@host:5432/freshguard_meta',
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Error handling
|
|
196
|
+
|
|
197
|
+
All errors extend `FreshGuardError`. Catch specific subclasses:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import {
|
|
201
|
+
SecurityError,
|
|
202
|
+
ConnectionError,
|
|
203
|
+
TimeoutError,
|
|
204
|
+
QueryError,
|
|
205
|
+
ConfigurationError,
|
|
206
|
+
} from '@freshguard/freshguard-core';
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const result = await checkFreshness(connector, rule);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (err instanceof ConnectionError) {
|
|
212
|
+
// database unreachable
|
|
213
|
+
} else if (err instanceof TimeoutError) {
|
|
214
|
+
// query or connection timed out
|
|
215
|
+
} else if (err instanceof SecurityError) {
|
|
216
|
+
// blocked query (SQL injection prevention)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Key types
|
|
224
|
+
|
|
225
|
+
- **`MonitoringRule`** ā defines what table to check, which algorithm, and the thresholds.
|
|
226
|
+
- **`CheckResult`** ā the outcome: `status` (`'ok'` | `'alert'` | `'failed'`), plus `lagMinutes`, `deviation`, `schemaChanges`, etc.
|
|
227
|
+
- **`ConnectorConfig`** ā database connection credentials.
|
|
228
|
+
- **`DataSource`** ā a registered database with metadata.
|
|
229
|
+
- **`FreshGuardConfig`** ā top-level config for self-hosted deployments.
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* FreshGuard CLI - Secure Command-Line Interface
|
|
4
|
+
* Simple command-line interface for self-hosters with built-in security measures
|
|
5
|
+
*
|
|
6
|
+
* Security features:
|
|
7
|
+
* - Secure credential handling with environment variables
|
|
8
|
+
* - Input validation to prevent command injection
|
|
9
|
+
* - No sensitive data logging
|
|
10
|
+
* - Safe file path handling
|
|
11
|
+
* - Connection validation with timeouts
|
|
12
|
+
*
|
|
13
|
+
* @module @freshguard/freshguard-core/cli
|
|
14
|
+
*/
|
|
2
15
|
export {};
|
|
3
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cli/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;GAYG"}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* FreshGuard CLI - Secure Command-Line Interface
|
|
4
|
+
* Simple command-line interface for self-hosters with built-in security measures
|
|
5
|
+
*
|
|
6
|
+
* Security features:
|
|
7
|
+
* - Secure credential handling with environment variables
|
|
8
|
+
* - Input validation to prevent command injection
|
|
9
|
+
* - No sensitive data logging
|
|
10
|
+
* - Safe file path handling
|
|
11
|
+
* - Connection validation with timeouts
|
|
12
|
+
*
|
|
13
|
+
* @module @freshguard/freshguard-core/cli
|
|
14
|
+
*/
|
|
2
15
|
import { readFile, writeFile, existsSync, mkdirSync } from 'fs';
|
|
3
16
|
import { join, resolve, dirname } from 'path';
|
|
4
17
|
import { promisify } from 'util';
|
|
@@ -7,8 +20,11 @@ import { DuckDBConnector } from '../connectors/duckdb.js';
|
|
|
7
20
|
import { BigQueryConnector } from '../connectors/bigquery.js';
|
|
8
21
|
import { SnowflakeConnector } from '../connectors/snowflake.js';
|
|
9
22
|
import { ConfigurationError, ConnectionError, ErrorHandler } from '../errors/index.js';
|
|
23
|
+
// Import validators if needed for future CLI validation
|
|
24
|
+
// import { validateTableName } from '../validators/index.js';
|
|
10
25
|
const readFileAsync = promisify(readFile);
|
|
11
26
|
const writeFileAsync = promisify(writeFile);
|
|
27
|
+
// Security: Define allowed configuration file paths to prevent path traversal
|
|
12
28
|
const ALLOWED_CONFIG_DIRS = [
|
|
13
29
|
process.cwd(),
|
|
14
30
|
join(process.cwd(), '.freshguard'),
|
|
@@ -23,6 +39,7 @@ async function main() {
|
|
|
23
39
|
printHelp();
|
|
24
40
|
process.exit(0);
|
|
25
41
|
}
|
|
42
|
+
// Security: Validate command to prevent injection
|
|
26
43
|
if (!isValidCommand(command)) {
|
|
27
44
|
console.error(`ā Invalid command: ${command}`);
|
|
28
45
|
process.exit(1);
|
|
@@ -55,21 +72,30 @@ async function main() {
|
|
|
55
72
|
}
|
|
56
73
|
}
|
|
57
74
|
catch (error) {
|
|
75
|
+
// Security: Use error sanitization to prevent information disclosure
|
|
58
76
|
const userMessage = ErrorHandler.getUserMessage(error);
|
|
59
77
|
console.error(`ā Error: ${userMessage}`);
|
|
78
|
+
// Only show stack trace in development mode
|
|
60
79
|
if (process.env.NODE_ENV === 'development' && error instanceof Error) {
|
|
61
80
|
console.error('Stack trace:', error.stack);
|
|
62
81
|
}
|
|
63
82
|
process.exit(1);
|
|
64
83
|
}
|
|
65
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Security: Validate command to prevent injection attacks
|
|
87
|
+
*/
|
|
66
88
|
function isValidCommand(cmd) {
|
|
67
89
|
const validCommands = ['init', 'test', 'run', 'version', '-v', '--version', 'help', '-h', '--help'];
|
|
68
90
|
return validCommands.includes(cmd) && /^[a-zA-Z-]+$/.test(cmd);
|
|
69
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Security: Validate file path to prevent directory traversal
|
|
94
|
+
*/
|
|
70
95
|
function validateConfigPath(filePath) {
|
|
71
96
|
try {
|
|
72
97
|
const resolvedPath = resolve(filePath);
|
|
98
|
+
// Check if path is within allowed directories
|
|
73
99
|
const isAllowed = ALLOWED_CONFIG_DIRS.some(allowedDir => {
|
|
74
100
|
const resolvedAllowedDir = resolve(allowedDir);
|
|
75
101
|
return resolvedPath.startsWith(resolvedAllowedDir);
|
|
@@ -83,8 +109,12 @@ function validateConfigPath(filePath) {
|
|
|
83
109
|
throw new ConfigurationError('Invalid configuration file path');
|
|
84
110
|
}
|
|
85
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Initialize FreshGuard configuration with secure credential handling
|
|
114
|
+
*/
|
|
86
115
|
async function handleInit() {
|
|
87
116
|
console.log('š FreshGuard Init\n');
|
|
117
|
+
// Security: Get database URL from environment or prompt securely
|
|
88
118
|
const dbUrl = process.env.FRESHGUARD_DATABASE_URL;
|
|
89
119
|
let dbType = 'postgres';
|
|
90
120
|
if (!dbUrl) {
|
|
@@ -92,6 +122,7 @@ async function handleInit() {
|
|
|
92
122
|
console.log('Please set your database connection string as an environment variable:');
|
|
93
123
|
console.log('');
|
|
94
124
|
console.log('For PostgreSQL:');
|
|
125
|
+
// eslint-disable-next-line no-secrets/no-secrets
|
|
95
126
|
console.log(' export FRESHGUARD_DATABASE_URL="postgresql://user:password@localhost:5432/database?sslmode=require"');
|
|
96
127
|
console.log('');
|
|
97
128
|
console.log('For DuckDB:');
|
|
@@ -100,16 +131,21 @@ async function handleInit() {
|
|
|
100
131
|
console.log('Then run: freshguard init');
|
|
101
132
|
return;
|
|
102
133
|
}
|
|
134
|
+
// Security: Parse URL safely without exposing credentials in logs
|
|
103
135
|
try {
|
|
104
136
|
const config = parseSecureConnectionString(dbUrl);
|
|
105
137
|
dbType = config.type;
|
|
106
138
|
console.log('ā
Database URL detected');
|
|
107
139
|
console.log(`š Database type: ${dbType}`);
|
|
108
140
|
console.log(`š SSL required: ${config.ssl ? 'Yes' : 'No'}`);
|
|
141
|
+
// Create configuration directory if it doesn't exist
|
|
109
142
|
const configDir = dirname(validateConfigPath(DEFAULT_CONFIG_FILE));
|
|
143
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
110
144
|
if (!existsSync(configDir)) {
|
|
145
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
111
146
|
mkdirSync(configDir, { recursive: true });
|
|
112
147
|
}
|
|
148
|
+
// Create initial configuration
|
|
113
149
|
const initialConfig = {
|
|
114
150
|
connections: {
|
|
115
151
|
default: {
|
|
@@ -133,6 +169,9 @@ async function handleInit() {
|
|
|
133
169
|
throw new ConfigurationError('Failed to parse database connection string. Please check the format.');
|
|
134
170
|
}
|
|
135
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Test database connection with security validation
|
|
174
|
+
*/
|
|
136
175
|
async function handleTest() {
|
|
137
176
|
console.log('š Testing database connection...\n');
|
|
138
177
|
const config = await loadConfig();
|
|
@@ -143,11 +182,14 @@ async function handleTest() {
|
|
|
143
182
|
const connConfig = config.connections[connectionName];
|
|
144
183
|
let connector;
|
|
145
184
|
try {
|
|
185
|
+
// Create connector with security validation
|
|
146
186
|
connector = createSecureConnector(connConfig.type, connConfig);
|
|
147
187
|
console.log(`š Testing ${connConfig.type} connection...`);
|
|
188
|
+
// Test connection with timeout
|
|
148
189
|
const testResult = await connector.testConnection();
|
|
149
190
|
if (testResult) {
|
|
150
191
|
console.log('ā
Connection successful!');
|
|
192
|
+
// Test basic operations
|
|
151
193
|
try {
|
|
152
194
|
const tables = await connector.listTables();
|
|
153
195
|
console.log(`š Found ${tables.length} tables`);
|
|
@@ -165,6 +207,7 @@ async function handleTest() {
|
|
|
165
207
|
}
|
|
166
208
|
}
|
|
167
209
|
catch (error) {
|
|
210
|
+
// Security: Don't expose detailed connection errors
|
|
168
211
|
console.log('ā Connection failed');
|
|
169
212
|
if (process.env.NODE_ENV === 'development') {
|
|
170
213
|
console.log('Debug info:', ErrorHandler.getUserMessage(error));
|
|
@@ -175,6 +218,9 @@ async function handleTest() {
|
|
|
175
218
|
process.exit(1);
|
|
176
219
|
}
|
|
177
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Run monitoring scheduler with secure operations
|
|
223
|
+
*/
|
|
178
224
|
async function handleRun() {
|
|
179
225
|
console.log('ā±ļø Starting FreshGuard monitoring scheduler...\n');
|
|
180
226
|
const config = await loadConfig();
|
|
@@ -188,6 +234,7 @@ async function handleRun() {
|
|
|
188
234
|
const connector = createSecureConnector(connConfig.type, connConfig);
|
|
189
235
|
console.log(`š Using ${connConfig.type} connection`);
|
|
190
236
|
console.log(`š Security mode: ${config.securityMode ?? 'strict'}`);
|
|
237
|
+
// Verify connection before starting
|
|
191
238
|
const isConnected = await connector.testConnection();
|
|
192
239
|
if (!isConnected) {
|
|
193
240
|
throw new ConnectionError('Cannot connect to database');
|
|
@@ -201,26 +248,36 @@ async function handleRun() {
|
|
|
201
248
|
console.log(' - Setting up alerting');
|
|
202
249
|
console.log('');
|
|
203
250
|
console.log('Press Ctrl+C to stop...');
|
|
251
|
+
// Simple monitoring loop (production would use proper scheduler)
|
|
204
252
|
process.on('SIGINT', () => {
|
|
205
253
|
console.log('\nš Shutting down gracefully...');
|
|
206
254
|
process.exit(0);
|
|
207
255
|
});
|
|
256
|
+
// Basic monitoring loop - this would be more sophisticated in production
|
|
208
257
|
while (true) {
|
|
209
258
|
try {
|
|
210
259
|
console.log('š Running health checks...');
|
|
260
|
+
// This is where real monitoring logic would go
|
|
261
|
+
// For now, just test the connection periodically
|
|
211
262
|
await connector.testConnection();
|
|
212
263
|
console.log('ā
Health check completed');
|
|
264
|
+
// Wait 1 minute between checks
|
|
213
265
|
await new Promise(resolve => setTimeout(resolve, 60000));
|
|
214
266
|
}
|
|
215
267
|
catch (error) {
|
|
216
268
|
console.error('ā Monitoring check failed:', ErrorHandler.getUserMessage(error));
|
|
269
|
+
// Continue monitoring even if one check fails
|
|
217
270
|
await new Promise(resolve => setTimeout(resolve, 60000));
|
|
218
271
|
}
|
|
219
272
|
}
|
|
220
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Parse connection string securely without exposing credentials
|
|
276
|
+
*/
|
|
221
277
|
function parseSecureConnectionString(connectionString) {
|
|
222
278
|
try {
|
|
223
279
|
const url = new URL(connectionString);
|
|
280
|
+
// Determine database type from protocol
|
|
224
281
|
let type;
|
|
225
282
|
switch (url.protocol) {
|
|
226
283
|
case 'postgresql:':
|
|
@@ -239,11 +296,12 @@ function parseSecureConnectionString(connectionString) {
|
|
|
239
296
|
default:
|
|
240
297
|
throw new ConfigurationError(`Unsupported database protocol: ${url.protocol}`);
|
|
241
298
|
}
|
|
299
|
+
// Security: Extract credentials from environment variables instead of URL when possible
|
|
242
300
|
const config = {
|
|
243
301
|
type,
|
|
244
302
|
host: url.hostname ?? 'localhost',
|
|
245
303
|
port: url.port ? parseInt(url.port, 10) : getDefaultPort(type),
|
|
246
|
-
database: url.pathname.slice(1) ?? '',
|
|
304
|
+
database: url.pathname.slice(1) ?? '', // Remove leading slash
|
|
247
305
|
username: url.username ?? process.env.FRESHGUARD_DB_USER ?? '',
|
|
248
306
|
password: url.password ?? process.env.FRESHGUARD_DB_PASSWORD ?? '',
|
|
249
307
|
ssl: url.searchParams.get('sslmode') !== 'disable' && url.searchParams.get('ssl') !== 'false',
|
|
@@ -254,6 +312,9 @@ function parseSecureConnectionString(connectionString) {
|
|
|
254
312
|
throw new ConfigurationError('Invalid database connection string format');
|
|
255
313
|
}
|
|
256
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Get default port for database type
|
|
317
|
+
*/
|
|
257
318
|
function getDefaultPort(type) {
|
|
258
319
|
switch (type) {
|
|
259
320
|
case 'postgres': return 5432;
|
|
@@ -263,6 +324,9 @@ function getDefaultPort(type) {
|
|
|
263
324
|
default: return 0;
|
|
264
325
|
}
|
|
265
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Create secure database connector
|
|
329
|
+
*/
|
|
266
330
|
function createSecureConnector(type, config) {
|
|
267
331
|
switch (type) {
|
|
268
332
|
case 'postgres':
|
|
@@ -277,14 +341,19 @@ function createSecureConnector(type, config) {
|
|
|
277
341
|
throw new ConfigurationError(`Unsupported connector type: ${type}`);
|
|
278
342
|
}
|
|
279
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* Load configuration from secure file path
|
|
346
|
+
*/
|
|
280
347
|
async function loadConfig() {
|
|
281
348
|
try {
|
|
282
349
|
const configPath = validateConfigPath(DEFAULT_CONFIG_FILE);
|
|
350
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
283
351
|
if (!existsSync(configPath)) {
|
|
284
352
|
throw new ConfigurationError('Configuration file not found. Run: freshguard init');
|
|
285
353
|
}
|
|
286
354
|
const configData = await readFileAsync(configPath, 'utf8');
|
|
287
355
|
const config = JSON.parse(configData);
|
|
356
|
+
// Validate configuration structure
|
|
288
357
|
if (!config.connections || Object.keys(config.connections).length === 0) {
|
|
289
358
|
throw new ConfigurationError('No connections configured');
|
|
290
359
|
}
|
|
@@ -297,6 +366,9 @@ async function loadConfig() {
|
|
|
297
366
|
throw new ConfigurationError('Failed to load configuration file');
|
|
298
367
|
}
|
|
299
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Get command line flag value
|
|
371
|
+
*/
|
|
300
372
|
function getFlag(flag) {
|
|
301
373
|
const flagIndex = args.indexOf(flag);
|
|
302
374
|
if (flagIndex !== -1 && flagIndex + 1 < args.length) {
|
|
@@ -319,6 +391,7 @@ function printHelp() {
|
|
|
319
391
|
console.log('');
|
|
320
392
|
console.log('Examples:');
|
|
321
393
|
console.log(' # Set database URL and initialize');
|
|
394
|
+
// eslint-disable-next-line no-secrets/no-secrets
|
|
322
395
|
console.log(' export FRESHGUARD_DATABASE_URL="postgresql://user:pass@localhost:5432/db"');
|
|
323
396
|
console.log(' freshguard init');
|
|
324
397
|
console.log('');
|