diff --git a/README.md b/README.md index 100b3a5..0e15fc6 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,20 @@ # EnvTypesWebpack + +
+ + + +
-[![npm version](https://img.shields.io/npm/v/@xxanderwp/env-types-webpack-plugin.svg)](https://www.npmjs.com/package/EnvTypesWebpack) +[![npm version](https://img.shields.io/npm/v/@xxanderwp/env-types-webpack-plugin.svg)](https://www.npmjs.com/package/@xxanderwp/env-types-webpack-plugin) +![NPM Downloads](https://img.shields.io/npm/dm/%40xxanderwp%2Fenv-types-webpack-plugin) [![Tests](https://github.com/XXanderWP/EnvTypesWebpack/workflows/Tests/badge.svg)](https://github.com/XXanderWP/EnvTypesWebpack/actions) [![license](https://img.shields.io/github/license/XXanderWP/EnvTypesWebpack.svg)](https://github.com/XXanderWP/EnvTypesWebpack/blob/main/LICENSE)
+ Webpack plugin that automatically generates TypeScript definitions for environment variables from `.env` files. ## Features @@ -132,6 +140,7 @@ const apiKey = process.env.API_KEY; // Type: string | undefined | `addExportEnds` | `boolean` | `false` | Add `export {};` at end | | `namespace` | `string` | `NodeJS` | Optional namespace for the generated types | | `interface` | `string` | `ProcessEnv` | Optional interface name for the generated types | +| `useValuesAsTypes` | `boolean` | `false` | Use values as types instead of string literals | #### Shorthand diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..70adab8 Binary files /dev/null and b/icon.png differ diff --git a/package-lock.json b/package-lock.json index c4223de..b067a13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@xxanderwp/env-types-webpack-plugin", - "version": "1.3.1", + "version": "1.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@xxanderwp/env-types-webpack-plugin", - "version": "1.3.1", + "version": "1.4.0", "license": "MIT", "devDependencies": { "@types/eslint__eslintrc": "^2.1.2", diff --git a/package.json b/package.json index 11e0fa0..c7f6ad1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xxanderwp/env-types-webpack-plugin", - "version": "1.3.1", + "version": "1.4.0", "description": "Webpack plugin that automatically generates TypeScript definitions for environment variables from .env files", "main": "dist/EnvTypesPlugin.js", "types": "dist/EnvTypesPlugin.d.ts", diff --git a/src/EnvTypesGenerator.ts b/src/EnvTypesGenerator.ts index 7a47266..b0d0045 100644 --- a/src/EnvTypesGenerator.ts +++ b/src/EnvTypesGenerator.ts @@ -10,6 +10,8 @@ export interface EnvEntry { key: string; /** JSDoc comment describing the variable */ comment: string | null; + /** Value for useValuesAsTypes mode */ + value: string; } /** @@ -30,6 +32,7 @@ export class EnvTypesGenerator { private readonly addExportEnds: boolean; private readonly interface: string; private readonly namespace: string; + private readonly useValuesAsTypes: boolean; constructor(options: EnvTypesGeneratorOptions) { this.envFiles = options.envFiles || ['.env', '.env.example']; @@ -39,6 +42,7 @@ export class EnvTypesGenerator { this.addExportEnds = options.addExportEnds ?? false; this.interface = options.interface || 'ProcessEnv'; this.namespace = options.namespace || 'NodeJS'; + this.useValuesAsTypes = options.useValuesAsTypes ?? false; } /** @@ -103,6 +107,7 @@ export class EnvTypesGenerator { entries.push({ key, comment: comments.length ? comments.join('\n') : null, + value: valuePart.split('#')[0].trim(), }); commentBuffer = []; @@ -128,13 +133,23 @@ export class EnvTypesGenerator { */ private generateInterfaceBody(entries: EnvEntry[]): string { return entries - .map(({ key, comment }) => { + .map(({ key, comment, value }) => { + // Определяем тип или конкретное значение + let typeOrValue: string; + if (this.useValuesAsTypes) { + typeOrValue = `"${value.replace(/"/g, '\\"')}"`; + } else { + typeOrValue = 'string'; + } + + const optional = this.disablePartialType ? '' : '?'; + if (!comment) { - return ` ${key}${this.disablePartialType ? '' : '?'}: string;`; + return ` ${key}${optional}: ${typeOrValue};`; } const jsdoc = this.generateJSDoc(comment); - return `${jsdoc}\n ${key}${this.disablePartialType ? '' : '?'}: string;`; + return `${jsdoc}\n ${key}${optional}: ${typeOrValue};`; }) .join('\n'); } @@ -240,6 +255,7 @@ if (require.main === module) { const addExportEnds = process.env.ADD_END === '1'; const interfaceName = process.env.INTERFACE || 'ProcessEnv'; const namespace = process.env.NAMESPACE || 'NodeJS'; + const useValuesAsTypes = process.env.USE_VALUES_AS_TYPES === '1'; if (!outputPath) { console.error( @@ -256,6 +272,7 @@ if (require.main === module) { addExportEnds, interface: interfaceName, namespace, + useValuesAsTypes, }); generator.generate(); } diff --git a/src/EnvTypesPlugin.ts b/src/EnvTypesPlugin.ts index 5cba4f7..f17b7b0 100644 --- a/src/EnvTypesPlugin.ts +++ b/src/EnvTypesPlugin.ts @@ -58,6 +58,7 @@ export class EnvTypesPlugin { addExportEnds: options.addExportEnds || false, interface: options.interface || 'ProcessEnv', namespace: options.namespace || 'NodeJS', + useValuesAsTypes: options.useValuesAsTypes || false, }; this.outputAbsolutePath = path.resolve(this.options.outputPath); @@ -101,6 +102,7 @@ export class EnvTypesPlugin { ADD_END: this.options.addExportEnds ? '1' : '0', INTERFACE: this.options.interface, NAMESPACE: this.options.namespace, + USE_VALUES_AS_TYPES: this.options.useValuesAsTypes ? '1' : '0', }; execSync(`node ${this.options.generatorScript}`, { diff --git a/src/types/EnvTypesGenerator.d.ts b/src/types/EnvTypesGenerator.d.ts index 8977313..a10bd53 100644 --- a/src/types/EnvTypesGenerator.d.ts +++ b/src/types/EnvTypesGenerator.d.ts @@ -36,4 +36,9 @@ export interface EnvTypesGeneratorOptions { * @default "ProcessEnv" */ interface?: string; + /** Use values as types instead of string literals + * @default false + * If true, the generated types will use the actual values from the .env files as types instead of string literals. For example, if you have a variable `API_URL=http://localhost:3000`, the generated type will be `API_URL: "http://localhost:3000"` instead of `API_URL: string`. + */ + useValuesAsTypes?: boolean; } diff --git a/tests/EnvTypesGenerator.test.ts b/tests/EnvTypesGenerator.test.ts index f87c81a..f1cf324 100644 --- a/tests/EnvTypesGenerator.test.ts +++ b/tests/EnvTypesGenerator.test.ts @@ -24,6 +24,7 @@ describe('EnvTypesGenerator', () => { describe('constructor', () => { it('should create instance with valid options', () => { const generator = new EnvTypesGenerator({ + silent: true, envFiles: ['.env'], outputPath: 'test.d.ts', }); @@ -44,6 +45,7 @@ API_KEY=secret fs.writeFileSync(envFile, envContent); const generator = new EnvTypesGenerator({ + silent: true, envFiles: [envFile], outputPath: outputFile, }); @@ -75,6 +77,7 @@ DB_PORT=5432 fs.writeFileSync(envFile, envContent); const generator = new EnvTypesGenerator({ + silent: true, envFiles: [envFile], outputPath: outputFile, }); @@ -97,6 +100,7 @@ DB_PORT=5432 fs.writeFileSync(envFile, envContent); const generator = new EnvTypesGenerator({ + silent: true, envFiles: [envFile], outputPath: outputFile, }); @@ -117,6 +121,7 @@ DB_PORT=5432 fs.writeFileSync(envFile, envContent); const generator = new EnvTypesGenerator({ + silent: true, envFiles: [envFile], outputPath: outputFile, }); @@ -142,6 +147,7 @@ DB_HOST=localhost fs.writeFileSync(envFile, envContent); const generator = new EnvTypesGenerator({ + silent: true, envFiles: [envFile], outputPath: outputFile, }); @@ -193,6 +199,7 @@ DB_HOST=localhost fs.writeFileSync(envFile, 'DB_HOST=localhost'); const generator = new EnvTypesGenerator({ + silent: true, envFiles: [envFile], outputPath: nestedOutput, }); @@ -207,6 +214,7 @@ DB_HOST=localhost it('should throw error if no env file found', () => { // Arrange const generator = new EnvTypesGenerator({ + silent: true, envFiles: ['.env.nonexistent'], outputPath: outputFile, }); @@ -224,6 +232,7 @@ DB_HOST=localhost fs.writeFileSync(env2, 'PROD_VAR=prod'); const generator = new EnvTypesGenerator({ + silent: true, envFiles: [env1, env2], outputPath: outputFile, }); @@ -263,6 +272,30 @@ DB_HOST=localhost expect(content).toContain('Line 3 of comment'); }); + it('should handle useValuesAsTypes option', () => { + // Arrange + const envContent = ` +DB_HOST=localhost +`.trim(); + + fs.writeFileSync(envFile, envContent); + + const generator = new EnvTypesGenerator({ + silent: true, + envFiles: [envFile], + outputPath: outputFile, + useValuesAsTypes: true, + }); + + // Act + generator.generate(); + + // Assert + const content = fs.readFileSync(outputFile, 'utf-8'); + expect(content).toContain('DB_HOST'); + expect(content).toContain('"localhost"'); + }); + it('should handle values with equals signs', () => { // Arrange const envContent = 'CONNECTION_STRING=host=localhost;port=5432'; @@ -270,6 +303,7 @@ DB_HOST=localhost fs.writeFileSync(envFile, envContent); const generator = new EnvTypesGenerator({ + silent: true, envFiles: [envFile], outputPath: outputFile, }); @@ -287,6 +321,7 @@ DB_HOST=localhost fs.writeFileSync(envFile, 'DB_HOST=localhost'); const generator = new EnvTypesGenerator({ + silent: true, envFiles: [envFile], outputPath: outputFile, }); @@ -304,6 +339,7 @@ DB_HOST=localhost fs.writeFileSync(envFile, 'DB_HOST=localhost'); const generator = new EnvTypesGenerator({ + silent: true, envFiles: [envFile], outputPath: outputFile, });