以编程方式为我的环境中的环境键声明打字稿类型

Programmatically declare typescript types for environment keys in my env

假设我得到了这个 .env.local 文件:

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

有什么方法可以通过编程方式获取打字稿来制作这样的类型:

type TEnv = 'SOME_VAR' | 'SOME_OTHER_VAR'

我的用例是这样的

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

我想我也许可以触发一个脚本,通过 dotenv(或 bash)读取密钥,然后用它们写入一个 types.d.ts 文件...但是只是想听听是否已经有 TS 方法来做到这一点?

我最近的尝试是这样的

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

但这不起作用..

旁注

NextJS 使用的 webpack 不允许 process.env[key] 并且 Cypress 会抛出引用错误。所以不得不这样做:

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

我做了一个有用的东西:)

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

然后我就在vscode中做yarn types(和>Reload Window)。

不要经常更改我的变量,这样就可以了。

更新版本

模板:

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