javascript - Déclarer de manière programmatique des types TypeScript pour les clés d'environnement dans mon env


Disons que j'ai ce fichier .env.local :

SOME_VAR="this is very secret"
SOME_OTHER_VAR="this is not so secret, but needs to be different during tests"

y a-t-il un moyen pour que je puisse, par programmation, faire en sorte que TypeScript crée un type comme ceci :

type TEnv = 'SOME_VAR' | 'SOME_OTHER_VAR'

Le cas d'utilisation que j'ai se présente comme suit

export default function env(key: TEnv): string {
  return process.env[key] || Cypress.env(key)
}

Je pense que je pourrais peut-être déclencher un script qui lit les clés via dotenv (ou bash) puis écrit un fichier types.d.ts avec elles... Mais je voulais juste savoir s'il existe déjà une façon TS de faire cela ?

Ma dernière tentative ressemble à ceci

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)
}

Mais ça ne fonctionne pas..

Note en marge

Le webpack utilisé par NextJS n'autorise pas process.env[key] et Cypress générera une erreur de référence. Il a donc fallu faire ainsi :

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

Réponse #1

J'ai fait un truc qui fonctionne :)

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"
  },

Ensuite, je fais juste yarn types (et >Reload Window) dans vscode).

Ne changez pas mes variables si souvent, donc c'est correct.

Version mise à jour

modèle :

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')