netlify-plugin-bundle-env
Version:
A Netlify Build Plugin to inject environment variables in Netlify Functions during Netlify Builds.
217 lines (216 loc) • 12 kB
JavaScript
import { basename, extname, join } from 'node:path';
import chalk from 'chalk';
import { copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { cwd, env } from 'node:process';
export default function bundleEnv(inputs) {
const filesOrDirs = [];
const processedVars = [];
const workingDir = cwd();
let countFile = 0;
function logDebug(message) {
if (inputs.debug && !inputs.quiet) {
console.log(chalk.blue(message));
}
}
function logSuccess(message) {
if (!inputs.quiet) {
console.log(chalk.green(message));
}
}
function logWarn(message) {
if (!inputs.quiet) {
console.log(chalk.yellow(message));
}
}
function recursiveProcess(path, callback) {
logDebug(`recursiveProcess: checking ${path}`);
if (lstatSync(path).isDirectory()) {
logDebug(`${path} is a directory, performing recursive call`);
readdirSync(path).forEach(newPath => {
const absolutePath = join(path, newPath);
logDebug(`recursiveProcess: absolutePath: ${absolutePath}, checking if directory`);
recursiveProcess(absolutePath, callback);
});
}
else {
logDebug(`${path} is a file, calling callback`);
callback(path);
}
}
return {
onEnd: () => {
if (inputs['backup-dir'].length) {
const backupDirAbsolute = join(workingDir, inputs['backup-dir']);
recursiveProcess(backupDirAbsolute, pathToProcess => {
if (extname(pathToProcess) === '.path') {
logDebug(`restoring backup from ${pathToProcess}`);
copyFileSync(pathToProcess.slice(0, -5), readFileSync(pathToProcess, 'utf-8'));
}
});
logDebug(`deleting ${backupDirAbsolute}`);
rmSync(backupDirAbsolute, {
recursive: true
});
logSuccess('Backup successfully processed and files have been restored.');
}
else {
filesOrDirs.forEach(fileOrDirectory => {
logDebug(`processing ${fileOrDirectory}`);
recursiveProcess(join(workingDir, fileOrDirectory), pathToProcess => {
if (inputs.files.length && inputs.extensions.includes(extname(pathToProcess).slice(1))) {
const backupFile = `${pathToProcess}.bak`;
logDebug(`restoring backup from ${backupFile}`);
writeFileSync(pathToProcess, readFileSync(backupFile, 'utf-8'));
logDebug(`deleting ${backupFile}`);
rmSync(backupFile);
logSuccess(`${pathToProcess} successfully processed and restored.`);
}
else if (extname(pathToProcess) === '.bak') {
logDebug(`restoring backup from ${pathToProcess}`);
const originalName = pathToProcess.slice(0, -4);
writeFileSync(originalName, readFileSync(pathToProcess, 'utf-8'));
logDebug(`deleting ${pathToProcess}`);
rmSync(pathToProcess);
logSuccess(`${originalName} successfully processed and restored.`);
}
});
});
}
},
onPreBuild: plugin => {
logDebug(`resolved config:\n - backup-dir: ${inputs['backup-dir']}\n - debug: ${inputs.debug}\n - directories: ${inputs.directories.join(', ')}\n - exclude: ${inputs.exclude.join(', ')}\n - extensions: ${inputs.extensions.join(', ')}\n - files: ${inputs.files.join(', ')}\n - include: ${inputs.include.join(', ')}\n - quiet: ${inputs.quiet}`);
logDebug('checking for debug/quiet conflict');
if (inputs.debug && inputs.quiet) {
logWarn('debug and quiet both are enabled, debug would be ignored');
}
logDebug('checking extensions');
inputs.extensions.forEach((extension, extensionIndex) => {
if (extension.startsWith('.')) {
logWarn(`${extension} should not start with '.'. The plugin will remove the '.' and continue processing.`);
inputs.extensions[extensionIndex] = extension.slice(1);
}
});
logDebug('checking for excluded/included conflict');
inputs.exclude.forEach(excludedEnv => {
if (inputs.include.includes(excludedEnv)) {
logWarn(`${excludedEnv} exists in include as well as exclude list. This is not supported and can produce unexpected results.`);
}
});
logDebug('checking for directories/files conflict');
if (inputs.directories.length && inputs.files.length) {
logWarn('directories and files, both are provided, the former will be ignored');
inputs.files.forEach(file => {
filesOrDirs.push(file);
});
}
else if (inputs.files.length) {
logWarn('files is provided, Netlify Functions would not be automatically processed');
inputs.files.forEach(file => {
filesOrDirs.push(file);
});
}
else {
logDebug('directories and files not provided, adding functions src to directories');
if (plugin.constants.FUNCTIONS_SRC) {
filesOrDirs.push(plugin.constants.FUNCTIONS_SRC);
}
else {
plugin.utils.build.failPlugin('No source directory is specified.');
}
}
filesOrDirs.forEach(fileOrDirectory => {
logDebug(`processing: ${fileOrDirectory}`);
const absolutePath = join(workingDir, fileOrDirectory);
logDebug(`absolute path: ${absolutePath}, checking its existence`);
if (existsSync(absolutePath)) {
logDebug(`${absolutePath} found`);
recursiveProcess(absolutePath, pathToProcess => {
logDebug(`checking if ${pathToProcess} has an included extension`);
if (inputs.extensions.includes(extname(pathToProcess).slice(1))) {
logDebug(`${pathToProcess} has an included extension`);
countFile++;
function processVariable(varName) {
logDebug(`validating if ${varName} should be processed`);
if (inputs.exclude.includes(varName)) {
logWarn(`${varName} will not be replaced because it is in the exclude list.`);
return false;
}
else if (inputs.include.length) {
if (inputs.include.includes(varName)) {
return true;
}
else {
logWarn(`${varName} will not be replaced because it is in not in the include list.`);
return false;
}
}
else {
return true;
}
}
logDebug(`reading ${pathToProcess}`);
const originalCode = readFileSync(pathToProcess, 'utf-8').trim();
writeFileSync(pathToProcess, `${Object.keys(env).map(varName => {
if (processVariable(varName)) {
if (!processedVars.includes(varName)) {
logDebug(`adding ${varName} to processedVars`);
processedVars.push(varName);
}
logDebug(`writing ${varName} to file`);
return `process.env[${JSON.stringify(String(varName))}] = ${JSON.stringify(String(env[varName]))}`;
}
else {
if (!env[varName]) {
logWarn(`skipping ${varName} because its value is undefined`);
}
return false;
}
}).filter(mappedVarName => {
return mappedVarName;
}).join(';')};\n${originalCode}`);
if (inputs['backup-dir'].length) {
logDebug('backup-dir provided');
const backupDirResolved = join(workingDir, inputs['backup-dir']);
logDebug(`backupDir absolute path: ${backupDirResolved}, checking if it exists`);
if (!existsSync(backupDirResolved)) {
logDebug('backupDir doesn\'t exist, creating it');
mkdirSync(backupDirResolved, {
recursive: true
});
}
logDebug(`checking file-tree in backupDir`);
const dirInBackupDir = join(backupDirResolved, fileOrDirectory);
logDebug(`backupDir file-tree absolute path: ${dirInBackupDir}, checking if it exists`);
if (!existsSync(dirInBackupDir)) {
logDebug(`backupDir file-tree doesn't exist, creating it`);
mkdirSync(dirInBackupDir, {
recursive: true
});
}
const fileInBackupDir = join(dirInBackupDir, basename(pathToProcess));
logDebug(`writing ${pathToProcess} in backupDir at: ${fileInBackupDir}`);
writeFileSync(fileInBackupDir, originalCode);
logDebug(`writing ${fileInBackupDir}'s original path`);
writeFileSync(`${fileInBackupDir}.path`, pathToProcess);
}
else {
logDebug('no backup-dir provided, backing up along-side original file');
writeFileSync(`${pathToProcess}.bak`, originalCode);
}
}
else {
logWarn(`Skipping ${basename(pathToProcess)} because its extension is not listed in plugin's "extensions" options.`);
}
});
}
else {
plugin.utils.build.failPlugin(`${fileOrDirectory} does not exist.`);
}
});
plugin.utils.status.show({
summary: `Successfully processed ${countFile} file(s) containing ${processedVars.length} variable(s)`,
title: 'Netlify Plugin Bundle ENV'
});
}
};
}