UNPKG

vercel-push-env

Version:

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

138 lines (130 loc) 5.89 kB
'use strict'; const assert = require('node:assert'); const fs = require('node:fs'); const commander = require('commander'); const dotenv = require('dotenv'); const dotenvExpand = require('dotenv-expand'); const axios = require('axios'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; } 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 axios__default = /*#__PURE__*/_interopDefaultLegacy(axios); commander.program.addOption(new commander.Option("--projectId <projectId>", "the name of your Vercel project").makeOptionMandatory(true)).addOption(new commander.Option("--teamId <teamId>", "the unique id of your Vercel team")).addOption(new commander.Option("--token <token>", "the access token for your Vercel account").makeOptionMandatory(true)).addOption(new commander.Option("--targets <targets>", "the environments to target the variables, separated by comma").makeOptionMandatory(true).choices(["production", "development", "preview"]).argParser((value) => value ? value.split(",") : [])).addOption(new commander.Option("--varType <varType>", "the type of the variables").makeOptionMandatory(true).choices(["system", "encrypted", "plain", "secret"]).default("encrypted")).addOption(new commander.Option("--envFile <envFile>", "the path to your env file").makeOptionMandatory(true)).addOption(new commander.Option("--apiRoot <apiRoot>", "vercel api root").makeOptionMandatory(true).default("https://api.vercel.com")); commander.program.parse(); const supportedEnvTargets = /* @__PURE__ */ new Set(["development", "preview", "production"]); function getConfig() { const config = commander.program.opts(); for (const target of config.targets) { assert__default(supportedEnvTargets.has(target), `Unknown environment '${target}' specified.`); } assert__default(fs__default.existsSync(config.envFile), `No file found at '${config.envFile}'.`); return config; } function parseEnvFile(config) { const content = fs__default.readFileSync(config.envFile, "utf8"); const envVars = dotenv__default.parse(content); if (Object.keys(envVars).length === 0) { throw new Error(`No environment variables found in '${config.envFile}'.`); } try { const parsedEnvVars = dotenvExpand__default.expand({ ignoreProcessEnv: true, parsed: envVars }); if (!parsedEnvVars.parsed || parsedEnvVars.error) { throw new Error("Unable to expand environment variables."); } const variables = Object.entries(parsedEnvVars.parsed).map(([key, value]) => ({ key, value, targets: config.targets })); return variables; } catch (error) { throw new Error(`Unable to parse and expand environment variables in '${config.envFile}'.`, { cause: error instanceof Error ? error : void 0 }); } } function filterTarget(envs, targets) { const targetsKey = targets.sort().join(","); const filtered = envs.filter((env) => env.targets.sort().join(",").includes(targetsKey)); return filtered; } function diffEnvVars(origin, target) { const create = []; const update = []; for (const targetEnv of target) { const originEnv = origin.find((env) => env.key === targetEnv.key); if (!originEnv) { create.push(targetEnv); } else if (targetEnv.value !== originEnv.value) { update.push({ origin: originEnv, target: targetEnv }); } } return { create, update }; } async function fetchVercelEnv(config) { const { apiRoot, projectId, teamId, token } = config; const url = `${apiRoot}/v8/projects/${projectId}/env`; const response = await axios__default.get(url, { params: { teamId, decrypt: true }, headers: { Authorization: `Bearer ${token}` } }); return response.data.envs.map((env) => ({ id: env.id, key: env.key, value: env.value, targets: env.target })); } async function createVercelEnvVars(envVars, config) { const { apiRoot, projectId, teamId, token, varType } = config; if (envVars.length === 0) { return; } let url = `${apiRoot}/v10/projects/${projectId}/env`; if (teamId) { url = `${url}?teamId=${teamId}`; } const payload = envVars.map((envVar) => ({ key: envVar.key, target: envVar.targets, type: varType, value: envVar.value })); await axios__default.post(url, payload, { headers: { Authorization: `Bearer ${token}` } }); } function updateVercelEnvVars(update, config) { const { apiRoot, projectId, teamId, token, varType } = config; if (update.length === 0) { return; } const envVarsToUpdate = update.map(({ origin, target }) => ({ id: origin.id, ...target })); return envVarsToUpdate.reduce((lastPromise, envVar) => { const { id, key, value, targets } = envVar; return lastPromise.then(async () => { let url = `${apiRoot}/v9/projects/${projectId}/env/${id}`; if (teamId) { url = `${url}?teamId=${teamId}`; } await axios__default.patch(url, { key, value, type: varType, target: targets }, { headers: { Authorization: `Bearer ${token}` } }); }); }, Promise.resolve()); } async function start() { const config = getConfig(); const parsedEnvs = parseEnvFile(config); const vercelEnvs = await fetchVercelEnv(config); const vercelEnvsToValidate = filterTarget(vercelEnvs, config.targets); const { create, update } = diffEnvVars(vercelEnvsToValidate, parsedEnvs); console.log("Variables to create:"); console.table(create); console.log("Variables to update:"); console.table(update.map(({ origin, target }) => ({ id: origin.id, ...target }))); await createVercelEnvVars(create, config); await updateVercelEnvVars(update, config); } start();