UNPKG

vercel-env-push

Version:

The missing Vercel CLI command to push environment variables from .env files.

261 lines (247 loc) 8.95 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); const assert = require('node:assert'); const fs = require('node:fs'); const dotenv = require('dotenv'); const dotenvExpand = require('dotenv-expand'); const readline = require('node:readline'); const Table = require('cli-table3'); const kolorist = require('kolorist'); const nanospinner = require('nanospinner'); const wyt = require('wyt'); const node_child_process = require('node:child_process'); const node_util = require('node:util'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; } function _interopNamespace(e) { if (e && e.__esModule) return e; const n = Object.create(null); if (e) { for (const k in e) { n[k] = e[k]; } } n["default"] = e; return n; } const assert__default = /*#__PURE__*/_interopDefaultLegacy(assert); const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); const dotenv__default = /*#__PURE__*/_interopDefaultLegacy(dotenv); const dotenvExpand__default = /*#__PURE__*/_interopDefaultLegacy(dotenvExpand); const readline__default = /*#__PURE__*/_interopDefaultLegacy(readline); const Table__default = /*#__PURE__*/_interopDefaultLegacy(Table); const kolorist__namespace = /*#__PURE__*/_interopNamespace(kolorist); const wyt__default = /*#__PURE__*/_interopDefaultLegacy(wyt); function validateFile(filePath) { assert__default(fs__default.existsSync(filePath), `No file found at '${filePath}'.`); } function parseEnvFile(envFilePath) { const content = fs__default.readFileSync(envFilePath, "utf8"); const envVars = dotenv__default.parse(content); if (Object.keys(envVars).length === 0) { throw new Error(`No environment variables found in '${envFilePath}'.`); } try { const parsedEnvVars = dotenvExpand__default.expand({ ignoreProcessEnv: true, parsed: envVars }); if (!parsedEnvVars.parsed || parsedEnvVars.error) { throw new Error("Unable to expand environment variables."); } return parsedEnvVars.parsed; } catch (error) { throw new Error(`Unable to parse and expand environment variables in '${envFilePath}'.`, { cause: error instanceof Error ? error : void 0 }); } } const tableColumnWidth = Math.floor(((process.stdout.columns ?? 80) - 10) / 2); function text(builder) { console.log(builder(kolorist__namespace)); } function table(builder) { const [headers, values] = builder(kolorist__namespace); const table2 = new Table__default({ colWidths: [tableColumnWidth, tableColumnWidth], head: headers, style: { head: [] }, wordWrap: true, wrapOnWordBoundary: false }); table2.push(...values); console.log(table2.toString()); } function redact(value) { if (value.length < 5) { return "*".repeat(value.length); } return value[0] + "*".repeat(value.length - 2) + value[value.length - 1]; } function spin(message) { return nanospinner.createSpinner(message, { color: "cyan" }).start(); } function confirm(question, defaultYes = true) { const rl = readline__default.createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve, reject) => { const answers = getConfirmAnswers(defaultYes); rl.question(`${question} (${answers[0]}/${answers[1]}) `, (answer) => { rl.close(); const sanitizedAnswer = answer.trim().toLowerCase(); if (sanitizedAnswer === "" && defaultYes || sanitizedAnswer === "y" || sanitizedAnswer === "yes") { return resolve(); } return reject(new Error("User aborted.")); }); }); } function getConfirmAnswers(defaultYes = true) { return [defaultYes ? "Y" : "y", !defaultYes ? "N" : "n"]; } function pluralize(count, wordOrSingular, plural) { if (!plural) { return wordOrSingular + (count === 1 ? "" : "s"); } return count === 1 ? wordOrSingular : plural; } const exec = node_util.promisify(node_child_process.exec); function isExecError(error) { return error instanceof Error && typeof error.stderr === "string"; } function throwIfAnyRejected(results) { for (const result of results) { if (isRejected(result)) { throw result.reason; } } } function isRejected(input) { return input.status === "rejected"; } const vercelEnvs = ["development", "preview", "production"]; let waitForRateLimiter; function validateVercelEnvs(envs, branch) { assert__default(envs.length > 0, "No environments specified."); for (const env of envs) { assert__default(vercelEnvs.includes(env), `Unknown environment '${env}' specified.`); } if (branch && branch.length > 0) { assert__default(envs.length === 1 && envs[0] === "preview", "Only the preview environment can be specified when specifying a branch."); } } async function replaceEnvVars(envs, envVars, options) { await removeEnvVars(envs, envVars, options); await addEnvVars(envs, envVars, options); } async function removeEnvVars(envs, envVars, options) { rateLimit(); const promises = []; for (const envVarKey of Object.keys(envVars)) { for (const env of envs) { promises.push(removeEnvVar(env, envVarKey, options)); } } throwIfAnyRejected(await Promise.allSettled(promises)); } async function addEnvVars(envs, envVars, options) { rateLimit(); const promises = []; for (const [envVarKey, envVarValue] of Object.entries(envVars)) { for (const env of envs) { promises.push(addEnvVar(env, envVarKey, envVarValue, options)); } } throwIfAnyRejected(await Promise.allSettled(promises)); } async function addEnvVar(env, key, value, options) { try { await waitForRateLimiter(); await execCommandWithNpx(`printf "${value}" | npx vercel env add ${key} ${env}${getBranchCommandArgument(options.branch)}${getTokenCommandArgument(options.token)}`); } catch (error) { throw new Error(`Unable to add environment variable '${key}' to '${env}'.`, { cause: error instanceof Error ? error : void 0 }); } } async function removeEnvVar(env, key, options) { try { await waitForRateLimiter(); await execCommandWithNpx(`npx vercel env rm ${key} ${env}${getBranchCommandArgument(options.branch)} -y${getTokenCommandArgument(options.token)}`); } catch (error) { if (!isExecError(error) || !error.stderr.includes("was not found")) { throw new Error(`Unable to remove environment variable '${key}' from '${env}'.`, { cause: error instanceof Error ? error : void 0 }); } } } async function execCommandWithNpx(command) { return exec(command.replace("npx", "npx --yes")); } function getTokenCommandArgument(token) { return token && token.length > 0 ? ` -t ${token}` : ""; } function getBranchCommandArgument(branch) { return branch && branch.length > 0 ? ` ${branch}` : ""; } function rateLimit() { waitForRateLimiter = wyt__default(6, 1e4); } async function pushEnvVars(envFilePath, envs, options) { validateVercelEnvs(envs, options?.branch); validateFile(envFilePath); if (options?.interactive) { logParams(envFilePath, envs, options?.branch); } let envVars = parseEnvFile(envFilePath); const envVarsCount = Object.keys(envVars).length; if (options?.prePush) { envVars = await options.prePush(envVars); } if (options?.interactive) { logEnvVars(envVars, envVarsCount); } if (options?.dryRun) { return; } if (options?.interactive) { await confirm(`Do you want to push ${pluralize(envVarsCount, "this", "these")} environment ${pluralize(envVarsCount, "variable")}?`); } let spinner; if (options?.interactive) { spinner = spin(`Pushing environment ${pluralize(envVarsCount, "variable")}`); } try { await replaceEnvVars(envs, envVars, { branch: options?.branch, token: options?.token }); } catch (error) { if (options?.interactive && spinner) { spinner.error(); } throw error; } if (options?.interactive && spinner) { spinner.success({ text: `Pushed ${envVarsCount} environment ${pluralize(envVarsCount, "variable")} to ${envs.length} ${pluralize(envs.length, "environment")}.` }); } } function logParams(envFilePath, envs, branch) { text(({ cyan, green, red, yellow }) => { const formatter = new Intl.ListFormat("en", { style: "short", type: "conjunction" }); return `Preparing environment variables push from ${cyan(`'${envFilePath}'`)} to ${formatter.format(envs.map((env) => { if (env === "development") { return green(env); } else if (env === "preview") { return yellow(`${env}${branch ? ` (branch: ${branch})` : ""}`); } return red(env); }))}.`; }); } function logEnvVars(envVars, envVarsCount) { text(({ dim }) => dim(`The following environment ${pluralize(envVarsCount, "variable")} will be pushed:`)); table(({ bold }) => [ [bold("Variable"), bold("Value")], Object.entries(envVars).map(([key, value]) => [key, redact(value)]) ]); } exports.pushEnvVars = pushEnvVars;