Allowing the user to pass their own logger is far superior to hardcoding `console`, and libraries should default to silent anyway.
npm i -g @ardislu/envpass
```
-2. Decrypt using the `--inject-in-process` (`-i`) flag (and optionally `--silent`/`-s`) before you run your desired command:
+2. Decrypt using the `--inject-in-process` (`-i`) flag before you run your desired command:
```
-envpass decrypt -is -- node index.js
+envpass decrypt -i -- node index.js
```
## Library usage
await decrypt({
outFile: false,
- injectInProcess: true,
- silent: true
+ injectInProcess: true
});
```
controller.abort();
```
+### Log diagnostic information
+
+The same option is available for `decrypt` as well.
+
+```javascript
+import { encrypt } from '@ardislu/envpass';
+
+await encrypt({
+ logger: console // Or whatever logger library you prefer
+});
+```
+
## CLI reference
```
Options:
--in-file <path> path to the unencrypted .env file (default: '.env')
--out-file <path> path to write the encrypted .env file to (default: value of --in-file)
- -s, --silent do not log anything to console
-h, --help display help for command
```
--in-file <path> path to the encrypted .env file (default: '.env')
--out-file <path> path to write the decrypted .env file to (default: value of --in-file)
-i, --inject-in-process inject the decrypted environment variables to process.env (and ignore --out-file)
- -s, --silent do not log anything to console
-h, --help display help for command
```
.description('encrypt your .env file with a passkey')
.option('--in-file <path>', "path to the unencrypted .env file (default: '.env')")
.option('--out-file <path>', 'path to write the encrypted .env file to (default: value of --in-file)')
- .option('-s, --silent', 'do not log anything to console')
.action(encryptAction);
program.command('decrypt')
.option('--in-file <path>', "path to the encrypted .env file (default: '.env')")
.option('--out-file <path>', 'path to write the decrypted .env file to (default: value of --in-file)')
.option('-i, --inject-in-process', 'inject the decrypted environment variables to process.env (and ignore --out-file)')
- .option('-s, --silent', 'do not log anything to console')
.allowExcessArguments(true)
.action(decryptAction);
import { encryptRaw, decryptRaw } from '#src/crypto.js';
import { toEnvString, parseEnvpassEncrypted, InputError } from '#src/utils.js';
/** @import { GetPrfOptions } from '#src/getPrf.js'; */
-/** @import { Env } from '#src/utils.js'; */
+/** @import { Logger, Env } from '#src/utils.js'; */
/**
* @typedef {Object} EncryptOptions
* @property {'ignore'|'log'|'encrypt'|'error'} [alreadyEncryptedValue] What to do when encountering a value
* that is already envpass encrypted (i.e., the value is already in the format `'envpass:{version}:{string}'`).
* - `'ignore'` will not encrypt the value and instead passthrough the value as-is.
- * - `'log'` will do the same as `'ignore'`, but also log the variable name to console.
+ * - `'log'` will do the same as `'ignore'`, but also log the variable name to `logger.info()`.
* - `'encrypt'` will encrypt the value again anyway.
* - `'error'` will throw an error.
*
* The default value is `'ignore'`.
* @property {GetPrfOptions} [getPrfOptions] Options for the passkey page and server.
- * @property {boolean} [silent] If `true`, do not log to console. The default value is `false`.
+ * @property {Logger} [logger] Logger to use to record diagnostic information. The default value is `{}` (i.e.,
+ * drop all logs).
*/
/**
outFile = inFile,
alreadyEncryptedValue = 'ignore',
getPrfOptions = { autoOpen: true, port: undefined },
- silent = false
+ logger = {}
} = options;
const env = await readFile(inFile, { encoding: 'utf-8' }).then(parseEnv);
if (Object.keys(env).length === 0) {
- if (!silent) { console.log('No environment variables found, aborting.'); }
+ logger.info?.('No environment variables found, aborting.');
return;
}
/** @type {Env} */const encryptedEnv = Object.create(null);
for (const [envVar, value] of Object.entries(env)) {
if (value === undefined) {
- if (!silent) { console.log(`Environment variable "${envVar}" is unset, skipping encryption.`); }
+ logger.info?.(`Environment variable "${envVar}" is unset, skipping encryption.`);
encryptedEnv[envVar] = '';
continue;
}
encryptedEnv[envVar] = parsedValue;
switch (alreadyEncryptedValue) {
case 'ignore': continue;
- case 'log': if (!silent) { console.log(`Environment variable "${envVar}" is already encrypted, ignoring.`); } continue;
+ case 'log': logger.info?.(`Environment variable "${envVar}" is already encrypted, ignoring.`); continue;
case 'encrypt': break;
case 'error': throw new Error(`Environment variable "${envVar}" is already encrypted.`);
}
if (prf === null) {
const { url, prf: prfPromise } = await getPrf(getPrfOptions);
if (!getPrfOptions.autoOpen) {
- if (!silent) { console.log(`Open this page in a web browser to complete the passkey flow: ${url}`); }
+ logger.info?.(`Open this page in a web browser to complete the passkey flow: ${url}`);
}
prf = await prfPromise;
if (prf === null) {
if (outFile) {
await writeFile(outFile, toEnvString(encryptedEnv));
}
- if (!silent) {
- console.log(styleText('green', '🎉 Successfully encrypted environment variables!'));
- }
+ logger.info?.(styleText('green', '🎉 Successfully encrypted environment variables!'));
}
/**
* @property {'ignore'|'log'|'error'} [notEncryptedValue] What to do when encountering a value
* that is not envpass encrypted (i.e., the value is not in the format `'envpass:{version}:{string}'`).
* - `'ignore'` will passthrough the value as-is.
- * - `'log'` will do the same as `'ignore'`, but also log the variable name to console.
+ * - `'log'` will do the same as `'ignore'`, but also log the variable name to `logger.info()`.
* - `'error'` will throw an error.
*
* The default value is `'ignore'`.
* @property {GetPrfOptions} [getPrfOptions] Options for the passkey page and server.
- * @property {boolean} [silent] If `true`, do not log to console. The default value is `false`.
+ * @property {Logger} [logger] Logger to use to record diagnostic information. The default value is `{}` (i.e.,
+ * drop all logs).
*/
/**
injectInProcess = false,
notEncryptedValue = 'ignore',
getPrfOptions = { autoOpen: true, port: undefined },
- silent = false
+ logger = {}
} = options;
const env = await readFile(inFile, { encoding: 'utf-8' }).then(parseEnv);
if (Object.keys(env).length === 0) {
- if (!silent) { console.log('No environment variables found, aborting.'); }
+ logger.info?.('No environment variables found, aborting.');
return;
}
/** @type {Env} */const decryptedEnv = {};
for (const [envVar, value] of Object.entries(env)) {
if (value === undefined) {
- if (!silent) { console.log(`Environment variable "${envVar}" is unset, skipping decryption.`); }
+ logger.info?.(`Environment variable "${envVar}" is unset, skipping decryption.`);
decryptedEnv[envVar] = '';
continue;
}
decryptedEnv[envVar] = value;
switch (notEncryptedValue) {
case 'ignore': continue;
- case 'log': if (!silent) { console.log(`Environment variable "${envVar}" is not encrypted, ignoring.`); } continue;
+ case 'log': logger.info?.(`Environment variable "${envVar}" is not encrypted, ignoring.`); continue;
case 'error': throw new Error(`Environment variable "${envVar}" is not encrypted.`);
}
}
if (prf === null) {
const { url, prf: prfPromise } = await getPrf(getPrfOptions);
if (!getPrfOptions.autoOpen) {
- if (!silent) { console.log(`Open this page in a web browser to complete the passkey flow: ${url}`); }
+ logger.info?.(`Open this page in a web browser to complete the passkey flow: ${url}`);
}
prf = await prfPromise;
if (prf === null) {
await writeFile(outFile, toEnvString(decryptedEnv));
}
}
- if (!silent) {
- console.log(styleText('green', '🎉 Successfully decrypted environment variables!'));
- }
+ logger.info?.(styleText('green', '🎉 Successfully decrypted environment variables!'));
if (args.length > 0) {
execSync(args.join(' '), { stdio: 'inherit' });
}
await decrypt({
outFile: false,
- injectInProcess: true,
- silent: true
+ injectInProcess: true
});
\ No newline at end of file
+/**
+ * @typedef {object} Logger Logger interface for diagnostic output. Compatible with `console` and
+ * most logging libraries.
+ * @property {(...args: unknown[]) => void} [debug] Logs verbose diagnostic information.
+ * @property {(...args: unknown[]) => void} [info] Logs informational messages.
+ * @property {(...args: unknown[]) => void} [warn] Logs warnings.
+ * @property {(...args: unknown[]) => void} [error] Logs errors.
+ */
+
/**
* @typedef {{[key:string]:string|undefined}} Env A simple null prototype object dictionary to map environment
* variable names to values.
const command = program.commands.find(c => c.name() === 'encrypt');
ok(command);
- const { inFile, outFile, silent } = command.opts();
+ const { inFile, outFile } = command.opts();
deepStrictEqual(inFile, undefined);
deepStrictEqual(outFile, undefined);
- deepStrictEqual(silent, undefined);
});
test('envpass encrypt options work', (t) => {
const program = makeProgram(t.mock.fn(), t.mock.fn());
- program.parse('node envpass encrypt --in-file TEST_IN --out-file TEST_OUT --silent'.split(' '));
+ program.parse('node envpass encrypt --in-file TEST_IN --out-file TEST_OUT'.split(' '));
const command = program.commands.find(c => c.name() === 'encrypt');
ok(command);
- const { inFile, outFile, silent } = command.opts();
+ const { inFile, outFile } = command.opts();
deepStrictEqual(inFile, 'TEST_IN');
deepStrictEqual(outFile, 'TEST_OUT');
- deepStrictEqual(silent, true);
});
test('envpass decrypt works', (t) => {
const program = makeProgram(t.mock.fn(), t.mock.fn());
const command = program.commands.find(c => c.name() === 'decrypt');
ok(command);
- const { inFile, outFile, injectInProcess, silent } = command.opts();
+ const { inFile, outFile, injectInProcess } = command.opts();
deepStrictEqual(inFile, undefined);
deepStrictEqual(outFile, undefined);
deepStrictEqual(injectInProcess, undefined);
- deepStrictEqual(silent, undefined);
});
test('envpass decrypt options work', (t) => {
const program = makeProgram(t.mock.fn(), t.mock.fn());
- program.parse('node envpass decrypt --in-file TEST_IN --out-file TEST_OUT --inject-in-process --silent'.split(' '));
+ program.parse('node envpass decrypt --in-file TEST_IN --out-file TEST_OUT --inject-in-process'.split(' '));
const command = program.commands.find(c => c.name() === 'decrypt');
ok(command);
- const { inFile, outFile, injectInProcess, silent } = command.opts();
+ const { inFile, outFile, injectInProcess } = command.opts();
deepStrictEqual(inFile, 'TEST_IN');
deepStrictEqual(outFile, 'TEST_OUT');
deepStrictEqual(injectInProcess, true);
- deepStrictEqual(silent, true);
});
- test('envpass decrypt -is works', (t) => {
+ test('envpass decrypt -i works', (t) => {
const program = makeProgram(t.mock.fn(), t.mock.fn());
- program.parse('node envpass decrypt -is'.split(' '));
+ program.parse('node envpass decrypt -i'.split(' '));
const command = program.commands.find(c => c.name() === 'decrypt');
ok(command);
- const { injectInProcess, silent } = command.opts();
+ const { injectInProcess } = command.opts();
deepStrictEqual(injectInProcess, true);
- deepStrictEqual(silent, true);
});
});
\ No newline at end of file
import { chdir, cwd } from 'node:process';
import { encrypt } from '#src/index.js';
-import { setupPlaywright, mockOpen, assertConsole, setupFolder, fileEqual, fileNotEqual } from '#test/setup.js';
+import { setupPlaywright, mockOpen, setupFolder, fileEqual, fileNotEqual } from '#test/setup.js';
suite('import @ardislu/envpass/decrypt', () => {
test('it works', async (t) => {
const { page } = await setupPlaywright(t);
const { folder, envFile } = await setupFolder(t, env);
mockOpen(t, page);
- assertConsole(t, { log: 1 }); // Decrypt should be silent, so only 1 log
const oldDir = cwd();
chdir(folder);
const { page } = await setupPlaywright(t);
const envFile = await setupEnv(t, STD_ENV);
mockOpen(t, page);
- assertConsole(t, { log: 2 });
+ assertConsole(t, { info: 0 });
await fileEqual(envFile, STD_ENV);
await encrypt({ inFile: envFile });
await decrypt({ inFile: envFile });
await fileEqual(envFile, STD_ENV);
});
- test('encrypt and decrypt .env silently', async (t) => {
+ test('console logger works', async (t) => {
const { page } = await setupPlaywright(t);
const envFile = await setupEnv(t, STD_ENV);
mockOpen(t, page);
- assertConsole(t, { log: 0 });
+ assertConsole(t, { info: 2 });
await fileEqual(envFile, STD_ENV);
- await encrypt({ inFile: envFile, silent: true });
+ await encrypt({ inFile: envFile, logger: console });
await fileNotEqual(envFile, STD_ENV);
- await decrypt({ inFile: envFile, silent: true });
+ await decrypt({ inFile: envFile, logger: console });
await fileEqual(envFile, STD_ENV);
});
test('does nothing to empty .env file', async (t) => {
const { page } = await setupPlaywright(t);
const envFile = await setupEnv(t, '');
mockOpen(t, page);
- assertConsole(t, { log: 2 });
+ assertConsole(t, { info: 0 });
await fileEqual(envFile, '');
await encrypt({ inFile: envFile });
await decrypt({ inFile: envFile });
await fileEqual(envFile, '');
});
- test('does nothing to empty .env file silently', async (t) => {
- const { page } = await setupPlaywright(t);
- const envFile = await setupEnv(t, '');
- mockOpen(t, page);
- assertConsole(t, { log: 0 });
-
- await fileEqual(envFile, '');
- await encrypt({ inFile: envFile, silent: true });
- await fileEqual(envFile, '');
- await decrypt({ inFile: envFile, silent: true });
- await fileEqual(envFile, '');
- });
test('encrypt does nothing when all variables are already encrypted', async (t) => {
const openMock = t.mock.method(BROWSER, 'open');
const env = 'A=envpass:v1:AAA\nB=envpass:v1:BBB\nC=envpass:v1:CCC\n';
const envFile = await setupEnv(t, env);
- assertConsole(t, { log: 1 });
+ assertConsole(t, { info: 0 });
await encrypt({ inFile: envFile });
test('decrypt does nothing when all variables are already decrypted', async (t) => {
const openMock = t.mock.method(BROWSER, 'open');
const envFile = await setupEnv(t, STD_ENV);
- assertConsole(t, { log: 1 });
+ assertConsole(t, { info: 0 });
await decrypt({ inFile: envFile });