1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00
joplin/packages/utils/env.ts

140 lines
3.7 KiB
TypeScript

// Based on https://github.com/grimen/node-env-file with various improvements to
// remove global state and conversion to TypeScript.
import { existsSync, readFileSync } from 'fs-extra';
import { resolve } from 'path';
export interface Options {
verbose?: boolean;
overwrite?: boolean;
raise?: boolean;
logger?: typeof console;
allowDuplicateKeys?: boolean;
}
export const parseEnvFile = (env_file: string, options: Options = {}) => {
options = {
logger: console,
overwrite: false,
raise: true,
verbose: false,
allowDuplicateKeys: false,
...options,
};
if (typeof env_file !== 'string') {
if (options.raise) {
throw new TypeError(`Environment file argument is not a valid \`String\`: ${env_file}`);
} else {
if (options.verbose && options.logger) {
options.logger.error('[ENV]: ERROR Environment file argument is not a valid `String`:', env_file);
}
return {};
}
}
try {
env_file = resolve(env_file);
} catch (error) {
if (options.raise) {
throw new TypeError(`Environment file path could not be resolved: ${error}`);
} else {
if (options.verbose && options.logger) {
options.logger.error('[ENV]: ERROR Environment file path could not be resolved:', env_file);
}
return {};
}
}
const data: Record<string, Record<string, string>> = {};
data[env_file] = {};
if (options.verbose && options.logger) {
options.logger.info('[ENV]: Loading environment:', env_file);
}
if (existsSync(env_file)) {
let lines: string[] = [];
try {
lines = (readFileSync(env_file, 'utf8') || '')
.split(/\r?\n|\r/)
.filter((line) => {
return /\s*=\s*/i.test(line);
})
.map((line) => {
return line.replace('exports ', '');
});
} catch (error) {
if (options.raise) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(error as any).message = `Environment file could not be read: ${env_file}: ${(error as any).message}`;
throw error;
} else {
if (options.verbose && options.logger) {
options.logger.error('[ENV]: ERROR Environment file could not be read:', env_file);
}
return {};
}
}
const lineComments: string[] = [];
const lineVariables: string[] = [];
let is_comment = false;
for (const line of lines) {
is_comment = /^\s*#/i.test(line); // ignore comment lines (starting with #).
if (is_comment) {
lineComments.push(line);
if (options.verbose && options.logger) {
options.logger.info('[ENV]: Ignored line:', line);
}
} else {
lineVariables.push(line);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const key_value = line.match(/^([^=]+)\s*=\s*(.*)$/) as any;
const env_key = key_value[1];
// remove ' and " characters if right side of = is quoted
const env_value = key_value[2].match(/^(['"]?)([^\n]*)\1$/m)[2];
if ((env_key in data[env_file]) && !options.allowDuplicateKeys) throw new Error(`Found duplicate key: ${env_key}`);
if (options.overwrite) {
data[env_file][env_key] = env_value;
if (options.verbose && options.logger && data[env_file][env_key] !== env_value) {
options.logger.info('[ENV]: Overwritten ', data[env_file][env_key], ' => ', env_value);
}
} else {
data[env_file][env_key] = process.env[env_key] || env_value;
}
if (options.verbose && options.logger) {
options.logger.info('[ENV]:', data[env_file]);
}
}
}
} else {
if (options.raise) {
throw new TypeError(`Environment file doesn't exist: ${env_file}`);
} else {
if (options.verbose && options.logger) {
options.logger.error('[ENV]: ERROR Environment file path could not be resolved:', env_file);
}
return {};
}
}
return data[env_file];
};