Say I got this .env.local
file:
SOME_VAR="this is very secret"
SOME_OTHER_VAR="this is not so secret, but needs to be different during tests"
is there any way I can programmatically get typescript to make a type like this:
type TEnv = 'SOME_VAR' | 'SOME_OTHER_VAR'
The use case I have goes like this
export default function env(key: TEnv): string {
return process.env[key] || Cypress.env(key)
}
I'm thinking that I could perhaps trigger a script that read the keys via dotenv
(or bash) and then writes a types.d.ts file with them... But just wanted to hear if there is already a TS way to do this?
My latest attempt looks like this
import dotenv from 'dotenv'
class DynamicArray<T> {
add(value: T): Array<T> {
let collection = new Array<T>()
collection.push(value)
return collection
}
}
const TEnvKey = new DynamicArray<string>()
const envFile = dotenv.config({ path: '.env.local' }).parsed
Object.keys(envFile).forEach((key) => TEnvKey.add(key))
export default function env(variable: TEnvKey): string {
return process.env[variable] || Cypress.env(variable)
}
But that does not work..
The webpack used by NextJS does not allow process.env[key]
and Cypress will throw a reference error. So had to do it like this:
export default function env(key: TEnvKey): string {
try {
// have to try catch because of Cypress reference error
return Cypress.env(key)
} catch {
switch (key) {
case 'SOME_VAR':
return process.env.SOME_VAR
case 'SOME_OTHER_VAR':
return process.env.SOME_OTHER_VAR
default:
return undefined
}
}
}
I made a thing that works :)
envTypeWriter.mjs
import dotenv from 'dotenv'
import fs from 'fs'
const env = dotenv.config({ path: '.env.local' }).parsed
const typeText = `type TEnvKey =\n | "${Object.keys(env).join('"\n | "')}"`
fs.writeFileSync('env.d.ts', typeText)
package.json
"scripts": {
...
"types": "node envTypeWriter.mjs"
},
Then I just do yarn types
(and >Reload Window
) in vscode).
Don't change my variables that often so this is fine.
template:
utils/env.ts
export default function env(key: TEnvKey): string {
try {
// have to try catch because of Cypress reference error
return Cypress.env(key);
} catch {
switch (key) {
// START OF AUTO GENERATED CASES -- DO NOT REMOVE OR EDIT THIS COMMENT
// this part will be replaced
// END OF AUTO GENERATED CASES -- DO NOT REMOVE OR EDIT THIS COMMENT
default:
return undefined;
}
}
}
// START OF AUTO GENERATED TYPES -- DO NOT REMOVE OR EDIT THIS COMMENT
// this will be replaced
envTypesWriter.ts
import { config } from 'dotenv'
import { writeFileSync, readFileSync } from 'fs'
const envLocal = config({ path: '.env.local' }).parsed // NOTE: Script has to be executed from the project root
const typeText = `type TEnvKey =\n | "${Object.keys(envLocal).join('"\n | "')}"`
const casesText = Object.keys(envLocal).reduce((acc, key) => {
acc += ` case '${key}':\n`
acc += ` return process.env.${key}\n`
return acc
}, '')
const filePath = 'utils/env.ts'
let text = readFileSync(filePath, { encoding: 'utf8' })
console.log(`reading ${filePath}`)
if (!text) throw 'nothing read'
const casesStartLineMarker =
'// START OF AUTO GENERATED CASES -- DO NOT REMOVE OR EDIT THIS COMMENT\n'
const casesEndLineMarker =
' // END OF AUTO GENERATED CASES -- DO NOT REMOVE OR EDIT THIS COMMENT\n'
text = `${
text.split(casesStartLineMarker)[0]
}${casesStartLineMarker}${casesText}${casesEndLineMarker}${
text.split(casesStartLineMarker)[1].split(casesEndLineMarker)[1]
}`
const typesLineMarker = '// START OF AUTO GENERATED TYPES -- DO NOT REMOVE OR EDIT THIS COMMENT\n'
const [untouchedCode, _types] = text.split(typesLineMarker)
const updatedText = `${untouchedCode}${typesLineMarker}${typeText}`
writeFileSync(filePath, updatedText, 'utf8')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With