Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programmatically declare typescript types for environment keys in my env

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..

Side note

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
    }
  }
}
like image 361
Norfeldt Avatar asked Oct 17 '25 07:10

Norfeldt


1 Answers

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.

Updated version

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')
like image 81
Norfeldt Avatar answered Oct 19 '25 21:10

Norfeldt



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!