OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / scripts / lib / release-env-file.mjs

82 lines · javascript ref:
import fs from 'node:fs'

const MAX_ENV_FILE_BYTES = 64 * 1024
const ENV_NAME_RE = /^[A-Z_][A-Z0-9_]*$/
const ENV_HEREDOC_DELIMITER_RE = /^[A-Za-z0-9_.-]{1,64}$/

export function readEnvFile (file) {
  let stat
  try {
    stat = fs.lstatSync(file)
  } catch (err) {
    throw new Error(`Unable to read env file: ${sanitizeFileError(err)}`)
  }
  if (stat.isSymbolicLink()) throw new Error('Refusing to read symlinked env file')
  if (!stat.isFile()) throw new Error('Refusing to read env file because it is not a regular file')
  if (stat.size > MAX_ENV_FILE_BYTES) {
    throw new Error(`Refusing to read env file larger than ${MAX_ENV_FILE_BYTES} bytes`)
  }

  let text
  try {
    text = fs.readFileSync(file, 'utf8')
  } catch (err) {
    throw new Error(`Unable to read env file: ${sanitizeFileError(err)}`)
  }
  return parseEnvFile(text)
}

export function parseEnvFile (text) {
  if (text.includes('\u0000')) throw new Error('Env file contains a NUL byte')
  const env = {}
  const lines = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n')

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i]
    if (!line.trim() || line.trimStart().startsWith('#')) continue

    const heredoc = /^([A-Z_][A-Z0-9_]*)<<([A-Za-z0-9_.-]+)$/.exec(line)
    if (heredoc) {
      const [, name, delimiter] = heredoc
      assertEnvName(name, i + 1)
      if (!ENV_HEREDOC_DELIMITER_RE.test(delimiter)) {
        throw new Error(`Malformed env-file heredoc delimiter on line ${i + 1}`)
      }
      assertEnvKeyUnset(env, name)
      const valueLines = []
      let closed = false
      for (i = i + 1; i < lines.length; i++) {
        if (lines[i] === delimiter) {
          closed = true
          break
        }
        valueLines.push(lines[i])
      }
      if (!closed) throw new Error(`Unterminated env-file heredoc for ${name}`)
      env[name] = valueLines.join('\n')
      continue
    }

    const assignment = /^([A-Z_][A-Z0-9_]*)=(.*)$/.exec(line)
    if (!assignment) throw new Error(`Malformed env-file line ${i + 1}`)
    const [, name, value] = assignment
    assertEnvName(name, i + 1)
    assertEnvKeyUnset(env, name)
    env[name] = value
  }

  return env
}

function assertEnvName (name, lineNumber) {
  if (!ENV_NAME_RE.test(name)) throw new Error(`Malformed env-file variable name on line ${lineNumber}`)
}

function assertEnvKeyUnset (env, name) {
  if (Object.hasOwn(env, name)) throw new Error(`Duplicate env-file variable: ${name}`)
}

function sanitizeFileError (err) {
  return err && err.code ? String(err.code) : 'unknown error'
}