]> git.ardis.lu - envpass/commitdiff
Replace `silent` with `logger` option
authorArdis Lu <ardisglu@gmail.com>
Tue, 14 Jul 2026 05:57:38 +0000 (22:57 -0700)
committerArdis Lu <ardisglu@gmail.com>
Tue, 14 Jul 2026 05:58:24 +0000 (22:58 -0700)
Allowing the user to pass their own logger is far superior to hardcoding `console`, and libraries should default to silent anyway.

README.md
src/cli.js
src/command.js
src/decrypt.js
src/utils.js
test/cli.test.js
test/decrypt.test.js
test/index.test.js

index bb45c47c0c7c0a51f681a2af8debf414983ec0ed..2781b8867c508f2b1ce7ed2f41123c1a0f859351 100644 (file)
--- a/README.md
+++ b/README.md
@@ -46,10 +46,10 @@ If you can't or don't want to install the package in your project, you can insta
 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
@@ -65,8 +65,7 @@ import { decrypt } from '@ardislu/envpass';
 
 await decrypt({
   outFile: false,
-  injectInProcess: true,
-  silent: true
+  injectInProcess: true
 });
 ```
 
@@ -96,6 +95,18 @@ const encryptionPromise = encrypt({
 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
 
 ```
@@ -121,7 +132,6 @@ encrypt your .env file with a passkey
 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
 ```
 
@@ -134,7 +144,6 @@ Options:
   --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
 ```
 
index f19fbcc464923a58c1303ce01993a5908740a78d..cf76e6e568390ead4341b872b595ce5b5f912229 100644 (file)
@@ -21,7 +21,6 @@ export function makeProgram(encryptAction, decryptAction) {
     .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')
@@ -29,7 +28,6 @@ export function makeProgram(encryptAction, decryptAction) {
     .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);
 
index 9c4d0ffcb64340aaf9236e524625ca9a6fe0aada..218f00226143860e7237775419c5e02f2e27f727 100644 (file)
@@ -7,7 +7,7 @@ import { getPrf } from '#src/getPrf.js';
 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
@@ -17,13 +17,14 @@ import { toEnvString, parseEnvpassEncrypted, InputError } from '#src/utils.js';
  * @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).
  */
 
 /**
@@ -37,12 +38,12 @@ export async function encrypt(options = {}) {
     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;
   }
 
@@ -50,7 +51,7 @@ export async function encrypt(options = {}) {
   /** @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;
     }
@@ -60,7 +61,7 @@ export async function encrypt(options = {}) {
       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.`);
       }
@@ -69,7 +70,7 @@ export async function encrypt(options = {}) {
     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) {
@@ -83,9 +84,7 @@ export async function encrypt(options = {}) {
   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!'));
 }
 
 /**
@@ -98,12 +97,13 @@ export async function encrypt(options = {}) {
  * @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).
  */
 
 /**
@@ -120,12 +120,12 @@ export async function decrypt(options = {}, { args = [] } = {}) {
     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;
   }
 
@@ -133,7 +133,7 @@ export async function decrypt(options = {}, { args = [] } = {}) {
   /** @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;
     }
@@ -143,7 +143,7 @@ export async function decrypt(options = {}, { args = [] } = {}) {
       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.`);
       }
     }
@@ -151,7 +151,7 @@ export async function decrypt(options = {}, { args = [] } = {}) {
     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) {
@@ -172,9 +172,7 @@ export async function decrypt(options = {}, { args = [] } = {}) {
       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' });
   }
index d60240a4b8822971cecc6636ee82e164324b8054..efd464b596d8d460c27a93ea007332d39493af9d 100644 (file)
@@ -14,6 +14,5 @@ import { decrypt } from '#src/index.js';
 
 await decrypt({
   outFile: false,
-  injectInProcess: true,
-  silent: true
+  injectInProcess: true
 });
\ No newline at end of file
index d0d38044a95e5b63657983e6ab62d4c743807499..d9ee0ea93043c6722e310dfc7d27698763e32c50 100644 (file)
@@ -1,3 +1,12 @@
+/**
+ * @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.
index c81e7e410d165af56ca31191c86d55d4f512f264..0a201825c434e799a93d98cbcbffeca7729c865d 100644 (file)
@@ -10,21 +10,19 @@ suite('cli', () => {
 
     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());
@@ -32,32 +30,29 @@ suite('cli', () => {
 
     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
index e3cda63f33b2bf16b2178792311aa9d9845f9c3c..6d91a76af1f0e6e56f54ff1621402ad0af35f082 100644 (file)
@@ -3,7 +3,7 @@ import { deepStrictEqual } from 'node:assert/strict';
 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) => {
@@ -11,7 +11,6 @@ suite('import @ardislu/envpass/decrypt', () => {
     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);
index aeb0c1a1fbfd10046991d7e2b5f7ff649a6e67db..d6dbb812ad17162a6db41331ca0aec8d3205c5e0 100644 (file)
@@ -13,7 +13,7 @@ suite('e2e', () => {
     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 });
@@ -21,23 +21,23 @@ suite('e2e', () => {
     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 });
@@ -45,23 +45,11 @@ suite('e2e', () => {
     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 });
 
@@ -71,7 +59,7 @@ suite('e2e', () => {
   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 });
 
This page took 0.059356 seconds and 4 git commands to generate.