UNPKG

trigger.dev

Version:

A Command-Line Interface for Trigger.dev projects

116 lines • 4.54 kB
import dotenv from "dotenv"; import { constants, existsSync } from "node:fs"; import { access, lstat, mkdtemp, readFile, stat, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; export const DOTENV_FILES = [".env", ".env.development", ".env.local", ".env.development.local"]; export async function readEnvVariable(name, envFile, cwd = process.cwd()) { if (envFile) { return dotenv.parse(await readFile(resolve(cwd, envFile), "utf8"))[name]; } if (process.env[name]) return process.env[name]; let value; for (const file of DOTENV_FILES) { const path = resolve(cwd, file); if (!existsSync(path)) continue; value = dotenv.parse(await readFile(path, "utf8"))[name] ?? value; } return value; } /** * Sets `name` in .env content. Every existing assignment of `name` is removed, including the * continuation lines of a quoted multi-line value, and the new one takes the place of the first, so * dotenv reads the new value even when the file assigned the variable more than once. Lines inside * another variable's quoted value are kept as they are, even when they look like an assignment. */ export function upsertEnvLine(content, name, value) { const line = `${name}=${quoteEnvValue(value)}`; const assignment = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.*)$/; const lines = content.split("\n"); const out = []; let replaced = false; for (let i = 0; i < lines.length; i++) { const match = assignment.exec(lines[i]); if (!match) { out.push(lines[i]); continue; } const last = lastLineOfValue(lines, i, match[2]); if (match[1] === name) { if (!replaced) { out.push(line); replaced = true; } } else { out.push(...lines.slice(i, last + 1)); } i = last; } if (replaced) return out.join("\n"); const separator = content === "" || content.endsWith("\n") ? "" : "\n"; return `${content}${separator}${line}\n`; } function lastLineOfValue(lines, start, value) { const quote = value[0]; if (quote !== '"' && quote !== "'" && quote !== "`") return start; if (value.indexOf(quote, 1) !== -1) return start; for (let i = start + 1; i < lines.length; i++) { if (lines[i].includes(quote)) return i; } return start; } function quoteEnvValue(value) { return /^[A-Za-z0-9_\-+/=.:]+$/.test(value) ? value : JSON.stringify(value); } /** * Checks that `file` can take the secret before the endpoint's secret is rotated, so a bad * destination fails without changing anything. Refuses symlinks, which could point the secret at a * tracked or shared file. */ export async function prepareEnvFile(file, cwd = process.cwd()) { const path = resolve(cwd, file); const stats = await lstat(path).catch((error) => { if (error.code === "ENOENT") return undefined; throw error; }); if (stats?.isSymbolicLink()) { throw new Error(`${file} is a symlink. Pass the file it points to with --to-file instead.`); } if (stats && !stats.isFile()) { throw new Error(`${file} isn't a file.`); } try { await access(stats ? path : dirname(path), stats ? constants.R_OK | constants.W_OK : constants.W_OK); } catch { throw new Error(stats ? `Can't read and write ${file}.` : `Can't create ${file}: ${dirname(path)} doesn't exist or isn't writable.`); } return path; } /** Writes the variable and reports whether other users can read the file. */ export async function saveToEnvFile(path, name, value) { const content = existsSync(path) ? await readFile(path, "utf8") : ""; await writeFile(path, upsertEnvLine(content, name, value), { mode: 0o600 }); return { readableByOthers: ((await stat(path)).mode & 0o077) !== 0 }; } /** Last resort when the destination write fails after rotating: a private temporary file. */ export async function saveToPrivateTempFile(name, value) { const dir = await mkdtemp(join(tmpdir(), "trigger-webhook-secret-")); const path = join(dir, ".env"); await writeFile(path, upsertEnvLine("", name, value), { mode: 0o600 }); return path; } export function trimSecret(value) { return value.replace(/\r?\n$/, "").trim(); } //# sourceMappingURL=envFile.js.map