]> git.ardis.lu - envpass/commitdiff
Only get passkey if actually required
authorArdis Lu <ardisglu@gmail.com>
Thu, 21 May 2026 05:59:14 +0000 (22:59 -0700)
committerArdis Lu <ardisglu@gmail.com>
Thu, 21 May 2026 05:59:14 +0000 (22:59 -0700)
Getting passkey is not required when `encrypt` is run on a file where all variables are already encrypted, or `decrypt` is run on a file where all variables are already decrypted.

Previously, the passkey flow would always initiate. Now, it will only initiate the first time it is required.

This change improves the UX in situations where `encrypt` or `decrypt` are called on a continuous basis. For example, when calling `envpass encrypt` in a `prestart` npm script hook.

src/command.js
test/index.test.js

index ad3c9f26228445d9abe1adac9c46545a1694e88b..9c4d0ffcb64340aaf9236e524625ca9a6fe0aada 100644 (file)
@@ -46,15 +46,7 @@ export async function encrypt(options = {}) {
     return;
   }
 
-  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}`); }
-  }
-  const prf = await prfPromise;
-  if (prf === null) {
-    throw new InputError('Unable to get passkey.');
-  }
-
+  let prf = null;
   /** @type {Env} */const encryptedEnv = Object.create(null);
   for (const [envVar, value] of Object.entries(env)) {
     if (value === undefined) {
@@ -74,6 +66,16 @@ 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}`); }
+      }
+      prf = await prfPromise;
+      if (prf === null) {
+        throw new InputError('Unable to get passkey.');
+      }
+    }
     const encryptedValue = await encryptRaw({ prf, value });
     encryptedEnv[envVar] = encryptedValue;
   }
@@ -127,15 +129,7 @@ export async function decrypt(options = {}, { args = [] } = {}) {
     return;
   }
 
-  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}`); }
-  }
-  const prf = await prfPromise;
-  if (prf === null) {
-    throw new InputError('Unable to get passkey.');
-  }
-
+  let prf = null;
   /** @type {Env} */const decryptedEnv = {};
   for (const [envVar, value] of Object.entries(env)) {
     if (value === undefined) {
@@ -153,6 +147,17 @@ export async function decrypt(options = {}, { args = [] } = {}) {
         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}`); }
+      }
+      prf = await prfPromise;
+      if (prf === null) {
+        throw new InputError('Unable to get passkey.');
+      }
+    }
     const decryptedValue = await decryptRaw({ prf, value: parsedValue });
     decryptedEnv[envVar] = decryptedValue;
   }
index b6a8ccc59410377cf32c667622bb8b10d3d3418a..aeb0c1a1fbfd10046991d7e2b5f7ff649a6e67db 100644 (file)
@@ -1,7 +1,8 @@
 import { suite, test } from 'node:test';
-import { readFile } from 'node:fs/promises';
+import { deepStrictEqual } from 'node:assert';
 
 import { encrypt, decrypt } from '#src/index.js';
+import { BROWSER } from '#src/getPrf.js';
 import { setupPlaywright, setupEnv, mockOpen, assertConsole, fileEqual, fileNotEqual } from '#test/setup.js';
 
 /** Standard value of the .env file used for most setups. */
@@ -56,30 +57,25 @@ suite('e2e', () => {
     await decrypt({ inFile: envFile, silent: true });
     await fileEqual(envFile, '');
   });
-  test('ignores variables already encrypted', async (t) => {
-    const { page } = await setupPlaywright(t);
-    const envFile = await setupEnv(t, STD_ENV);
-    mockOpen(t, page);
-    assertConsole(t, { log: 3 });
-
-    await fileEqual(envFile, STD_ENV);
-    await encrypt({ inFile: 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 });
 
-    const newContent = await readFile(envFile, { encoding: 'utf8' });
     await encrypt({ inFile: envFile });
-    await fileEqual(envFile, newContent);
 
-    await decrypt({ inFile: envFile });;
-    await fileEqual(envFile, STD_ENV);
+    await fileEqual(envFile, env);
+    deepStrictEqual(openMock.mock.calls.length, 0);
   });
-  test('ignores variables already decrypted', async (t) => {
-    const { page } = await setupPlaywright(t);
+  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);
-    mockOpen(t, page);
     assertConsole(t, { log: 1 });
 
-    await fileEqual(envFile, STD_ENV);
     await decrypt({ inFile: envFile });
+
     await fileEqual(envFile, STD_ENV);
+    deepStrictEqual(openMock.mock.calls.length, 0);
   });
 });
This page took 0.050364 seconds and 4 git commands to generate.