@danielszlaski/envguard 0.1.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.
Files changed (75) hide show
  1. package/.envguardrc.example.json +17 -0
  2. package/LICENSE +21 -0
  3. package/README.md +320 -0
  4. package/dist/analyzer/envAnalyzer.d.ts +8 -0
  5. package/dist/analyzer/envAnalyzer.d.ts.map +1 -0
  6. package/dist/analyzer/envAnalyzer.js +112 -0
  7. package/dist/analyzer/envAnalyzer.js.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +52 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/commands/check.d.ts +5 -0
  13. package/dist/commands/check.d.ts.map +1 -0
  14. package/dist/commands/check.js +9 -0
  15. package/dist/commands/check.js.map +1 -0
  16. package/dist/commands/fix.d.ts +4 -0
  17. package/dist/commands/fix.d.ts.map +1 -0
  18. package/dist/commands/fix.js +115 -0
  19. package/dist/commands/fix.js.map +1 -0
  20. package/dist/commands/scan.d.ts +9 -0
  21. package/dist/commands/scan.d.ts.map +1 -0
  22. package/dist/commands/scan.js +274 -0
  23. package/dist/commands/scan.js.map +1 -0
  24. package/dist/config/configLoader.d.ts +35 -0
  25. package/dist/config/configLoader.d.ts.map +1 -0
  26. package/dist/config/configLoader.js +141 -0
  27. package/dist/config/configLoader.js.map +1 -0
  28. package/dist/constants/knownEnvVars.d.ts +38 -0
  29. package/dist/constants/knownEnvVars.d.ts.map +1 -0
  30. package/dist/constants/knownEnvVars.js +111 -0
  31. package/dist/constants/knownEnvVars.js.map +1 -0
  32. package/dist/index.d.ts +5 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +25 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/parser/envParser.d.ts +13 -0
  37. package/dist/parser/envParser.d.ts.map +1 -0
  38. package/dist/parser/envParser.js +126 -0
  39. package/dist/parser/envParser.js.map +1 -0
  40. package/dist/parser/serverlessParser.d.ts +27 -0
  41. package/dist/parser/serverlessParser.d.ts.map +1 -0
  42. package/dist/parser/serverlessParser.js +162 -0
  43. package/dist/parser/serverlessParser.js.map +1 -0
  44. package/dist/scanner/codeScanner.d.ts +13 -0
  45. package/dist/scanner/codeScanner.d.ts.map +1 -0
  46. package/dist/scanner/codeScanner.js +157 -0
  47. package/dist/scanner/codeScanner.js.map +1 -0
  48. package/dist/types.d.ts +24 -0
  49. package/dist/types.d.ts.map +1 -0
  50. package/dist/types.js +3 -0
  51. package/dist/types.js.map +1 -0
  52. package/package.json +40 -0
  53. package/src/analyzer/envAnalyzer.ts +133 -0
  54. package/src/cli.ts +54 -0
  55. package/src/commands/check.ts +6 -0
  56. package/src/commands/fix.ts +104 -0
  57. package/src/commands/scan.ts +289 -0
  58. package/src/config/configLoader.ts +131 -0
  59. package/src/constants/knownEnvVars.ts +108 -0
  60. package/src/index.ts +4 -0
  61. package/src/parser/envParser.ts +114 -0
  62. package/src/parser/serverlessParser.ts +146 -0
  63. package/src/scanner/codeScanner.ts +148 -0
  64. package/src/types.ts +26 -0
  65. package/test-project/.envguardrc.json +7 -0
  66. package/test-project/src/lambda1/.env.example +11 -0
  67. package/test-project/src/lambda1/handler.js +14 -0
  68. package/test-project/src/lambda2/.env.example +9 -0
  69. package/test-project/src/lambda2/handler.js +11 -0
  70. package/test-project/src/lambda2/handler2.js +13 -0
  71. package/test-project/src/lambda2/serverless.yml +50 -0
  72. package/test-project/src/payment/.env.example +23 -0
  73. package/test-project/src/payment/payment.js +14 -0
  74. package/test-project/src/payment/server.ts +11 -0
  75. package/tsconfig.json +19 -0
@@ -0,0 +1,146 @@
1
+ import * as fs from 'fs';
2
+ import * as yaml from 'js-yaml';
3
+
4
+ export interface ServerlessEnvEntry {
5
+ key: string;
6
+ valueExpression: string; // e.g., "${ssm:/path}" or "hardcoded-value"
7
+ isReference: boolean; // true if it references SSM, secrets manager, etc.
8
+ source: string; // file path
9
+ lineNumber?: number;
10
+ }
11
+
12
+ // CloudFormation intrinsic function types for js-yaml
13
+ const CF_SCHEMA = yaml.FAILSAFE_SCHEMA.extend([
14
+ new yaml.Type('!Ref', { kind: 'scalar', construct: (data) => ({ Ref: data }) }),
15
+ new yaml.Type('!GetAtt', { kind: 'scalar', construct: (data) => ({ 'Fn::GetAtt': data }) }),
16
+ new yaml.Type('!GetAtt', { kind: 'sequence', construct: (data) => ({ 'Fn::GetAtt': data }) }),
17
+ new yaml.Type('!Join', { kind: 'sequence', construct: (data) => ({ 'Fn::Join': data }) }),
18
+ new yaml.Type('!Sub', { kind: 'scalar', construct: (data) => ({ 'Fn::Sub': data }) }),
19
+ new yaml.Type('!Sub', { kind: 'sequence', construct: (data) => ({ 'Fn::Sub': data }) }),
20
+ new yaml.Type('!ImportValue', { kind: 'scalar', construct: (data) => ({ 'Fn::ImportValue': data }) }),
21
+ new yaml.Type('!Select', { kind: 'sequence', construct: (data) => ({ 'Fn::Select': data }) }),
22
+ new yaml.Type('!Split', { kind: 'sequence', construct: (data) => ({ 'Fn::Split': data }) }),
23
+ new yaml.Type('!FindInMap', { kind: 'sequence', construct: (data) => ({ 'Fn::FindInMap': data }) }),
24
+ new yaml.Type('!GetAZs', { kind: 'scalar', construct: (data) => ({ 'Fn::GetAZs': data }) }),
25
+ new yaml.Type('!Base64', { kind: 'scalar', construct: (data) => ({ 'Fn::Base64': data }) }),
26
+ new yaml.Type('!Equals', { kind: 'sequence', construct: (data) => ({ 'Fn::Equals': data }) }),
27
+ new yaml.Type('!Not', { kind: 'sequence', construct: (data) => ({ 'Fn::Not': data }) }),
28
+ new yaml.Type('!And', { kind: 'sequence', construct: (data) => ({ 'Fn::And': data }) }),
29
+ new yaml.Type('!Or', { kind: 'sequence', construct: (data) => ({ 'Fn::Or': data }) }),
30
+ new yaml.Type('!If', { kind: 'sequence', construct: (data) => ({ 'Fn::If': data }) }),
31
+ new yaml.Type('!Condition', { kind: 'scalar', construct: (data) => ({ Condition: data }) }),
32
+ ]);
33
+
34
+ export class ServerlessParser {
35
+ /**
36
+ * Parse a serverless.yml file and extract environment variables
37
+ * from the provider.environment section
38
+ */
39
+ parse(filePath: string): Map<string, ServerlessEnvEntry> {
40
+ const envVars = new Map<string, ServerlessEnvEntry>();
41
+
42
+ if (!fs.existsSync(filePath)) {
43
+ return envVars;
44
+ }
45
+
46
+ try {
47
+ const content = fs.readFileSync(filePath, 'utf-8');
48
+ const doc = yaml.load(content, { schema: CF_SCHEMA }) as any;
49
+
50
+ if (!doc || typeof doc !== 'object') {
51
+ return envVars;
52
+ }
53
+
54
+ // Extract from provider.environment
55
+ const providerEnv = doc.provider?.environment;
56
+ if (providerEnv && typeof providerEnv === 'object') {
57
+ for (const [key, value] of Object.entries(providerEnv)) {
58
+ // Only include uppercase env var names (convention)
59
+ if (this.isValidEnvVarName(key)) {
60
+ const valueStr = String(value);
61
+ envVars.set(key, {
62
+ key,
63
+ valueExpression: valueStr,
64
+ isReference: this.isReference(valueStr),
65
+ source: filePath,
66
+ });
67
+ }
68
+ }
69
+ }
70
+
71
+ // Also check for function-level environment variables
72
+ const functions = doc.functions;
73
+ if (functions && typeof functions === 'object') {
74
+ for (const [funcName, funcConfig] of Object.entries(functions)) {
75
+ const funcEnv = (funcConfig as any)?.environment;
76
+ if (funcEnv && typeof funcEnv === 'object') {
77
+ for (const [key, value] of Object.entries(funcEnv)) {
78
+ if (this.isValidEnvVarName(key)) {
79
+ const valueStr = String(value);
80
+ // Don't overwrite if already exists from provider level
81
+ if (!envVars.has(key)) {
82
+ envVars.set(key, {
83
+ key,
84
+ valueExpression: valueStr,
85
+ isReference: this.isReference(valueStr),
86
+ source: `${filePath} (function: ${funcName})`,
87
+ });
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ } catch (error) {
95
+ console.error(`Error parsing ${filePath}:`, error);
96
+ }
97
+
98
+ return envVars;
99
+ }
100
+
101
+ /**
102
+ * Check if a value references external sources like SSM, Secrets Manager, etc.
103
+ */
104
+ private isReference(value: string): boolean {
105
+ if (typeof value !== 'string') {
106
+ return false;
107
+ }
108
+
109
+ // Check for common serverless variable patterns
110
+ const referencePatterns = [
111
+ /\$\{ssm:/, // SSM Parameter Store
112
+ /\$\{aws:reference/, // AWS reference
113
+ /\$\{file\(/, // File reference
114
+ /\$\{self:custom\./, // Custom variables
115
+ /\$\{opt:/, // CLI options
116
+ /\$\{env:/, // Environment variables
117
+ /\$\{cf:/, // CloudFormation outputs
118
+ ];
119
+
120
+ return referencePatterns.some(pattern => pattern.test(value));
121
+ }
122
+
123
+ /**
124
+ * Validate if a key is a valid environment variable name
125
+ */
126
+ private isValidEnvVarName(key: string): boolean {
127
+ // Environment variables should typically be uppercase with underscores
128
+ // But we'll be flexible and accept mixed case
129
+ return /^[A-Z_][A-Z0-9_]*$/i.test(key);
130
+ }
131
+
132
+ /**
133
+ * Find all serverless.yml files in a directory
134
+ */
135
+ async findServerlessFiles(rootDir: string): Promise<string[]> {
136
+ const { glob } = await import('glob');
137
+
138
+ const files = await glob('**/serverless.{yml,yaml}', {
139
+ cwd: rootDir,
140
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'],
141
+ absolute: true,
142
+ });
143
+
144
+ return files;
145
+ }
146
+ }
@@ -0,0 +1,148 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { glob } from 'glob';
4
+ import { ServerlessParser } from '../parser/serverlessParser';
5
+
6
+ export class CodeScanner {
7
+ private rootDir: string;
8
+ private excludePatterns: string[];
9
+ private serverlessParser: ServerlessParser;
10
+
11
+ constructor(rootDir: string, excludePatterns: string[] = ['node_modules', 'dist', 'build', '.git']) {
12
+ this.rootDir = rootDir;
13
+ this.excludePatterns = excludePatterns;
14
+ this.serverlessParser = new ServerlessParser();
15
+ }
16
+
17
+ async scan(): Promise<Map<string, string[]>> {
18
+ const envVars = new Map<string, string[]>();
19
+
20
+ // Scan for JavaScript/TypeScript files
21
+ const files = await glob('**/*.{js,ts,jsx,tsx,mjs,cjs}', {
22
+ cwd: this.rootDir,
23
+ ignore: this.excludePatterns.map(p => `**/${p}/**`),
24
+ absolute: true,
25
+ });
26
+
27
+ for (const file of files) {
28
+ const vars = await this.scanFile(file);
29
+ for (const varName of vars) {
30
+ const relativePath = path.relative(this.rootDir, file);
31
+ if (!envVars.has(varName)) {
32
+ envVars.set(varName, []);
33
+ }
34
+ envVars.get(varName)!.push(relativePath);
35
+ }
36
+ }
37
+
38
+ // Also scan serverless.yml files for environment variable definitions
39
+ const serverlessFiles = await this.findServerlessFiles();
40
+ for (const file of serverlessFiles) {
41
+ const vars = this.scanServerlessFile(file);
42
+ for (const [varName, entry] of vars.entries()) {
43
+ const relativePath = path.relative(this.rootDir, file);
44
+ if (!envVars.has(varName)) {
45
+ envVars.set(varName, []);
46
+ }
47
+ envVars.get(varName)!.push(`${relativePath} (serverless config)`);
48
+ }
49
+ }
50
+
51
+ return envVars;
52
+ }
53
+
54
+ async scanFile(filePath: string): Promise<Set<string>> {
55
+ const envVars = new Set<string>();
56
+
57
+ try {
58
+ const content = fs.readFileSync(filePath, 'utf-8');
59
+
60
+ // Pattern 1: process.env.VAR_NAME
61
+ const processEnvPattern = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
62
+ let match;
63
+
64
+ while ((match = processEnvPattern.exec(content)) !== null) {
65
+ envVars.add(match[1]);
66
+ }
67
+
68
+ // Pattern 2: process.env['VAR_NAME'] or process.env["VAR_NAME"]
69
+ const processEnvBracketPattern = /process\.env\[['"]([A-Z_][A-Z0-9_]*)['"\]]/g;
70
+
71
+ while ((match = processEnvBracketPattern.exec(content)) !== null) {
72
+ envVars.add(match[1]);
73
+ }
74
+
75
+ // Pattern 3: Destructuring - const { VAR_NAME } = process.env
76
+ const destructuringPattern = /const\s+\{\s*([^}]+)\s*\}\s*=\s*process\.env/g;
77
+
78
+ while ((match = destructuringPattern.exec(content)) !== null) {
79
+ const vars = match[1].split(',').map(v => v.trim().split(':')[0].trim());
80
+ vars.forEach(v => {
81
+ if (/^[A-Z_][A-Z0-9_]*$/.test(v)) {
82
+ envVars.add(v);
83
+ }
84
+ });
85
+ }
86
+
87
+ } catch (error) {
88
+ console.error(`Error scanning file ${filePath}:`, error);
89
+ }
90
+
91
+ return envVars;
92
+ }
93
+
94
+ async findEnvFiles(): Promise<string[]> {
95
+ const envFiles = await glob('**/.env', {
96
+ cwd: this.rootDir,
97
+ ignore: this.excludePatterns.map(p => `**/${p}/**`),
98
+ absolute: true,
99
+ });
100
+
101
+ return envFiles;
102
+ }
103
+
104
+ async findServerlessFiles(): Promise<string[]> {
105
+ const serverlessFiles = await glob('**/serverless.{yml,yaml}', {
106
+ cwd: this.rootDir,
107
+ ignore: this.excludePatterns.map(p => `**/${p}/**`),
108
+ absolute: true,
109
+ });
110
+
111
+ return serverlessFiles;
112
+ }
113
+
114
+ scanServerlessFile(filePath: string): Map<string, any> {
115
+ return this.serverlessParser.parse(filePath);
116
+ }
117
+
118
+ async scanByDirectory(): Promise<Map<string, Map<string, string[]>>> {
119
+ // Returns a map of directory -> (varName -> file locations)
120
+ const dirMap = new Map<string, Map<string, string[]>>();
121
+
122
+ // Scan for JavaScript/TypeScript files
123
+ const files = await glob('**/*.{js,ts,jsx,tsx,mjs,cjs}', {
124
+ cwd: this.rootDir,
125
+ ignore: this.excludePatterns.map(p => `**/${p}/**`),
126
+ absolute: true,
127
+ });
128
+
129
+ for (const file of files) {
130
+ const vars = await this.scanFile(file);
131
+ const fileDir = path.dirname(file);
132
+ const relativePath = path.relative(this.rootDir, file);
133
+
134
+ for (const varName of vars) {
135
+ if (!dirMap.has(fileDir)) {
136
+ dirMap.set(fileDir, new Map());
137
+ }
138
+ const varMap = dirMap.get(fileDir)!;
139
+ if (!varMap.has(varName)) {
140
+ varMap.set(varName, []);
141
+ }
142
+ varMap.get(varName)!.push(relativePath);
143
+ }
144
+ }
145
+
146
+ return dirMap;
147
+ }
148
+ }
package/src/types.ts ADDED
@@ -0,0 +1,26 @@
1
+ export interface EnvUsage {
2
+ varName: string;
3
+ locations: string[];
4
+ }
5
+
6
+ export interface EnvDefinition {
7
+ varName: string;
8
+ value?: string;
9
+ comment?: string;
10
+ source?: 'dotenv' | 'serverless' | 'both';
11
+ isReference?: boolean; // For serverless variables that reference external sources
12
+ }
13
+
14
+ export interface Issue {
15
+ type: 'missing' | 'unused' | 'undocumented';
16
+ varName: string;
17
+ details: string;
18
+ locations?: string[];
19
+ }
20
+
21
+ export interface ScanResult {
22
+ issues: Issue[];
23
+ usedVars: Map<string, string[]>;
24
+ definedVars: Set<string>;
25
+ exampleVars: Set<string>;
26
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "ignoreVars": [
3
+ "STAGE",
4
+ "OKTA_API_PATH"
5
+ ],
6
+ "strict": false
7
+ }
@@ -0,0 +1,11 @@
1
+ # Auto-generated by envguard
2
+ # Do not put actual secrets in this file - use .env instead
3
+
4
+ # Used in: test-project/src/lambda1/handler.js
5
+ # Format: your-secret-here
6
+ CUSTOM_KEY=
7
+
8
+ # Used in: test-project/src/lambda1/handler.js
9
+ # Format: your-secret-here
10
+ STRIPE_SECRET_KEY=
11
+
@@ -0,0 +1,14 @@
1
+ const stripe = require('stripe');
2
+
3
+ const { STRIPE_SECRET_KEY, CUSTOM_KEY } = process.env;
4
+
5
+ async function createPayment(amount) {
6
+ const stripeClient = stripe(STRIPE_SECRET_KEY);
7
+
8
+ return await stripeClient.paymentIntents.create({
9
+ amount,
10
+ currency: 'usd',
11
+ });
12
+ }
13
+
14
+ module.exports = { createPayment };
@@ -0,0 +1,9 @@
1
+ # Auto-generated by envguard
2
+ # Do not put actual secrets in this file - use .env instead
3
+
4
+ # Used in: test-project/src/lambda2/handler.js
5
+ OKTA_CLIENT_ID=
6
+
7
+ # Used in: test-project/src/lambda2/handler2.js
8
+ OKTA_DEV_CLIENT_ID=
9
+
@@ -0,0 +1,11 @@
1
+
2
+ const OKTA_KEY = process.env.OKTA_CLIENT_ID;
3
+
4
+
5
+ module.exports.getPermissions = async (event) => {
6
+ return {
7
+ statusCode: 200,
8
+ body: JSON.stringify({ message: `Your OKTA_CLIENT_ID is ${OKTA_KEY}` }),
9
+ };
10
+
11
+ };
@@ -0,0 +1,13 @@
1
+
2
+ const OKTA_KEY = process.env.OKTA_DEV_CLIENT_ID;
3
+
4
+ const client = new SecretsManagerClient({ region: process.env.AWS_REGION });
5
+
6
+
7
+ module.exports.getPermissions = async (event) => {
8
+ return {
9
+ statusCode: 200,
10
+ body: JSON.stringify({ message: `Your OKTA_CLIENT_ID is ${OKTA_KEY}` }),
11
+ };
12
+
13
+ };
@@ -0,0 +1,50 @@
1
+ service: ${self:app}-lambda2
2
+ app: my-app
3
+
4
+ custom:
5
+ cors-config:
6
+ origins:
7
+ - '*'
8
+ headers:
9
+ - Content-Type
10
+ - X-Amz-Date
11
+ - Authorization
12
+ - X-Api-Key
13
+ - X-Amz-Security-Token
14
+ - X-Amz-User-Agent
15
+ - token
16
+ - env
17
+ - provider
18
+ allowCredentials: false
19
+
20
+ secrets_INTERNAL_KEYS: ${ssm:/aws/reference/secretsmanager/${self:app}/${opt:stage, self:provider.stage}/internal-keys}
21
+
22
+ provider:
23
+ name: aws
24
+ runtime: nodejs20.x
25
+ memorySize: 8192
26
+ tags:
27
+ Product: ${opt:stage, self:provider.stage}-${self:app}
28
+ profile: abc
29
+ stage: dev
30
+ region: eu-central-1
31
+ deploymentBucket:
32
+ name: ${self:app}-deployment
33
+ versionFunctions: false
34
+ timeout: 29
35
+ environment:
36
+ NODE_ENV: ${file(./.env.yml):${opt:stage, self:provider.stage}.NODE_ENV}
37
+ STAGE: ${opt:stage, self:provider.stage}
38
+ OKTA_CLIENT_ID: ${self:custom.secrets_INTERNAL_KEYS.okta_client_id}
39
+ OKTA_API_PATH: ${self:custom.secrets_INTERNAL_KEYS.okta_api_path}
40
+
41
+ functions:
42
+ getPermissions:
43
+ handler: handler.getPermissions
44
+ description: 'Retrieves a list of all permissions available in the system.'
45
+ events:
46
+ - http:
47
+ path: /permissions
48
+ method: get
49
+ cors: ${self:custom.cors-config}
50
+
@@ -0,0 +1,23 @@
1
+ # Auto-generated by envguard
2
+ # Do not put actual secrets in this file - use .env instead
3
+
4
+ # Used in: test-project/src/payment/server.ts
5
+ # Format: your-api-key-here
6
+ API_KEY=
7
+
8
+ # Used in: test-project/src/payment/server.ts
9
+ # Format: postgresql://user:pass@host:5432/db
10
+ DATABASE_URL=
11
+
12
+ # Used in: test-project/src/payment/server.ts
13
+ # Format: 3000
14
+ PORT=
15
+
16
+ # Used in: test-project/src/payment/payment.js
17
+ # Format: your-secret-here
18
+ STRIPE_SECRET_KEY=
19
+
20
+ # Used in: test-project/src/payment/payment.js
21
+ # Format: your-secret-here
22
+ STRIPE_WEBHOOK_SECRET=
23
+
@@ -0,0 +1,14 @@
1
+ const stripe = require('stripe');
2
+
3
+ const { STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET } = process.env;
4
+
5
+ async function createPayment(amount) {
6
+ const stripeClient = stripe(STRIPE_SECRET_KEY);
7
+
8
+ return await stripeClient.paymentIntents.create({
9
+ amount,
10
+ currency: 'usd',
11
+ });
12
+ }
13
+
14
+ module.exports = { createPayment };
@@ -0,0 +1,11 @@
1
+ import express from 'express';
2
+
3
+ const app = express();
4
+ const PORT = process.env.PORT || 3000;
5
+ const DATABASE_URL = process.env.DATABASE_URL;
6
+ const API_KEY = process.env.API_KEY;
7
+
8
+ app.listen(PORT, () => {
9
+ console.log(`Server running on port ${PORT}`);
10
+ console.log(`Database: ${DATABASE_URL}`);
11
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }