From: Ardis Lu Date: Tue, 14 Jul 2026 05:57:38 +0000 (-0700) Subject: Replace `silent` with `logger` option X-Git-Tag: v0.1.4~17 X-Git-Url: https://git.ardis.lu/?a=commitdiff_plain;h=1785c7f83fe770f9cf5190388c6d33c7a526403d;p=envpass Replace `silent` with `logger` option Allowing the user to pass their own logger is far superior to hardcoding `console`, and libraries should default to silent anyway. --- diff --git a/README.md b/README.md index bb45c47..2781b88 100644 --- 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 to the unencrypted .env file (default: '.env') --out-file 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 to the encrypted .env file (default: '.env') --out-file 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 ``` diff --git a/src/cli.js b/src/cli.js index f19fbcc..cf76e6e 100644 --- a/src/cli.js +++ b/src/cli.js @@ -21,7 +21,6 @@ export function makeProgram(encryptAction, decryptAction) { .description('encrypt your .env file with a passkey') .option('--in-file ', "path to the unencrypted .env file (default: '.env')") .option('--out-file ', '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 to the encrypted .env file (default: '.env')") .option('--out-file ', '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); diff --git a/src/command.js b/src/command.js index 9c4d0ff..218f002 100644 --- a/src/command.js +++ b/src/command.js @@ -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' }); } diff --git a/src/decrypt.js b/src/decrypt.js index d60240a..efd464b 100644 --- a/src/decrypt.js +++ b/src/decrypt.js @@ -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 diff --git a/src/utils.js b/src/utils.js index d0d3804..d9ee0ea 100644 --- a/src/utils.js +++ b/src/utils.js @@ -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. diff --git a/test/cli.test.js b/test/cli.test.js index c81e7e4..0a20182 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -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 diff --git a/test/decrypt.test.js b/test/decrypt.test.js index e3cda63..6d91a76 100644 --- a/test/decrypt.test.js +++ b/test/decrypt.test.js @@ -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); diff --git a/test/index.test.js b/test/index.test.js index aeb0c1a..d6dbb81 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -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 });