diff --git a/.gitignore b/.gitignore index b512c09..0c4f1f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -node_modules \ No newline at end of file +node_modules +test/.env.example +env.d.ts \ No newline at end of file diff --git a/README.md b/README.md index 6e73ab7..5898b5d 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ npx gen-env-types path/to/.env -h, --help Show usage information -o, --types-output Output name/path for types file | defaults to `env.d.ts` -e, --example-env-path Path to save .env.example file + -O, --optional [vars] Make some of the environment variables optional. + Accepts a list of environment variables to be made optional. -r, --rename-example-env Custom name for .env example output file | defaults to `env.example` if omitted -k, --keep-comments Keep comments/blank lines in .env example output file | defaults to false if omitted. Not accepting the value. When specified, it will be true. diff --git a/gen-env-types.js b/gen-env-types.js index adb3213..f1de084 100644 --- a/gen-env-types.js +++ b/gen-env-types.js @@ -26,6 +26,8 @@ const printHelp = (exitCode) => { -h, --help Show usage information -o, --types-output Output name/path for types file | defaults to \`env.d.ts\` -e, --example-env-path Path to save .env.example file + -O, --optional [vars] Make some of the environment variables optional. + Accepts a list of environment variables to be made optional. -r, --rename-example-env Custom name for .env example output file | defaults to \`env.example\` if omitted -k, --keep-comments Keep comments/blank lines in .env example output file | defaults to false if omitted. Not accepting the value. When specified, it will be true. @@ -45,6 +47,7 @@ const parseArgs = (args) => { typesOutput: "env.d.ts", exampleEnvOutput: ".env.example", keepComments: false, + listOfOptionalVariables: [] }; while (args.length > 0) { @@ -61,6 +64,16 @@ const parseArgs = (args) => { case "--version": cliConfig.version = true; break; + case "-O": + case "--optional": + const listOfOptionalVariables = args.shift(); + if (!listOfOptionalVariables) { + showError( + "Expected a list of optional variables, bad input: " + listOfOptionalVariables + ); + } + cliConfig.listOfOptionalVariables = listOfOptionalVariables; + break; case "-o": case "--types-output": const typesOutput = args.shift(); @@ -117,10 +130,10 @@ if (!cliConfig.envPath) { printHelp(1); } if (cliConfig.help) { - return printHelp(0); + printHelp(0); } if (cliConfig.version) { - return printVersion(); + printVersion(); } const envString = readFileSync(cliConfig.envPath, { @@ -141,16 +154,17 @@ function writeEnvTypes(path) { interface ProcessEnv { ${filteredEnvString .map(({key}, i) => { + const isKeyOptional = cliConfig.listOfOptionalVariables.length > 0 && cliConfig.listOfOptionalVariables.includes(key); if (!existingModuleDeclaration) { - return `${i ? " " : ""}${key}: string;`; + return `${i ? " " : ""}${key}${isKeyOptional ? '?' : ''}: string;`; } const existingPropertySignature = existingModuleDeclaration .split("\n") - .find((line) => line.includes(`${key}:`)); + .find((line) => line.includes(`${key}:`) || line.includes(`${key}?:`)); if (!existingPropertySignature) { - return `${i ? " " : ""}${key}: string;`; + return `${i ? " " : ""}${key}${isKeyOptional ? '?' : ''}: string;`; } return `${i ? " " : ""}${existingPropertySignature.trim()}`; @@ -166,10 +180,12 @@ export {} writeFileSync(path, moduleDeclaration); console.log("Wrote env types to: ", path); + + return moduleDeclaration; } function writeExampleEnv(parsedExistingEnvString, path, isNew) { - const out = (cliConfig.keepComments? parsedEnvString: filteredEnvString) + const out = (cliConfig.keepComments ? parsedEnvString: filteredEnvString) .map(({key, isEnvVar,value}) => { if(isEnvVar) return `${key}=`; // Comment or blank value @@ -199,8 +215,17 @@ if (cliConfig.exampleEnvPath) { readFileSync(outputExampleEnvPath, { encoding: "utf-8" }) ); - return writeExampleEnv(parsedExistingEnvString, outputExampleEnvPath); + writeExampleEnv(parsedExistingEnvString, outputExampleEnvPath); } writeExampleEnv(filteredEnvString, outputExampleEnvPath, true); } + +module.exports = { + /** + * @description Writes environment types to a file + * @param {string} path + * @returns the content of the created file + */ + writeEnvTypes +} \ No newline at end of file diff --git a/gen-env-types.test.js b/gen-env-types.test.js new file mode 100644 index 0000000..1b196c0 --- /dev/null +++ b/gen-env-types.test.js @@ -0,0 +1,20 @@ +const { writeEnvTypes } = require('./gen-env-types'); + +describe('Environment variable types generator', function () { + it('should accept a list of optional variables', function () { + const result = writeEnvTypes('env.d.ts'); + expect(result).toMatchInlineSnapshot(` +"declare global { + namespace NodeJS { + interface ProcessEnv { + OPTIONAL_SECRET?: string; + REQUIRED_SECRET: string; + } + } +} + +export {} +" +`); + }); +}); \ No newline at end of file diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..2fe4535 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + setupFiles: ['./testSetup'], +}; \ No newline at end of file diff --git a/test/.env.test b/test/.env.test new file mode 100644 index 0000000..2f0fa35 --- /dev/null +++ b/test/.env.test @@ -0,0 +1,2 @@ +OPTIONAL_SECRET=test_optional +REQUIRED_SECRET=test_required \ No newline at end of file diff --git a/testSetup.js b/testSetup.js new file mode 100644 index 0000000..20febb1 --- /dev/null +++ b/testSetup.js @@ -0,0 +1 @@ +process.argv = ['node', 'jest', 'test/.env.test', '--types-output', 'env.d.ts', '--example-env-path', 'test', '--optional', '[OPTIONAL_SECRET]']; \ No newline at end of file