From a640bc1faf708a5f5302c51715020fec283b4e87 Mon Sep 17 00:00:00 2001 From: Fredrik Johansson Date: Thu, 16 Jul 2026 16:52:02 +0200 Subject: [PATCH] keep pull: fill saved template with real values by default A bare key/value dump throws away the comments/structure a template preserves; now a plain pull reconstitutes the real .env from the template when the vault has one, falling back to the flat dump otherwise. --template still returns the redacted structure alone. Co-Authored-By: Claude Sonnet 5 --- README.md | 11 ++++++++--- src/cli/commands/vault.ts | 6 ++++-- src/cli/envFormat.ts | 17 +++++++++++++++++ src/cli/index.ts | 3 ++- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e608f82..b850fc7 100644 --- a/README.md +++ b/README.md @@ -124,14 +124,19 @@ Comments explaining *where a value comes from* or *why it exists* are exactly the kind of context a bare key/value pair throws away, so this keeps it without keeping the secret itself around unencrypted anywhere. +A plain `keep pull` uses that template by default: if the vault has one, +you get real values filled back into it, comments and all — not just a +bare key/value dump. Ask for the redacted structure itself with: + ```bash keep pull myapp/production --template > .env.example ``` Vaults pushed before this existed just don't have a saved template — -`keep pull --template` on one of those fails with a clear error instead -of silently returning nothing. No migration needed; push again and the -template gets backfilled from whatever `.env` you push next. +`keep pull` on one of those falls back to a plain key/value dump, and +`keep pull --template` fails with a clear error instead of silently +returning nothing. No migration needed; push again and the template +gets backfilled from whatever `.env` you push next. ## Granting, revoking, rotating diff --git a/src/cli/commands/vault.ts b/src/cli/commands/vault.ts index fa3c4b0..3006b55 100644 --- a/src/cli/commands/vault.ts +++ b/src/cli/commands/vault.ts @@ -1,7 +1,7 @@ import fs from 'node:fs'; import { requireIdentityAndRecipientId } from '../identityStore.js'; import { signedFetch, adminFetch, expectOk } from '../signedFetch.js'; -import { parseEnv, serializeEnv, buildEnvTemplate } from '../envFormat.js'; +import { parseEnv, serializeEnv, buildEnvTemplate, fillEnvTemplate } from '../envFormat.js'; import { ready, Identity, @@ -89,7 +89,9 @@ export async function vaultPull(vaultKey: string, format: 'env' | 'json', outFil return; } - const out = format === 'json' ? JSON.stringify(values, null, 2) : serializeEnv(values); + const out = format === 'json' + ? JSON.stringify(values, null, 2) + : template !== undefined ? fillEnvTemplate(template, values) : serializeEnv(values); if (outFile) { fs.writeFileSync(outFile, out); console.error(`wrote ${Object.keys(values).length} key(s) to ${outFile}${previous ? ' (previous version)' : ''}`); diff --git a/src/cli/envFormat.ts b/src/cli/envFormat.ts index df72035..60435ef 100644 --- a/src/cli/envFormat.ts +++ b/src/cli/envFormat.ts @@ -25,6 +25,23 @@ export function serializeEnv(values: Record): string { // — the whole point is to preserve the "why"/"where to get this" context // that plain parseEnv() throws away, without keeping the actual secret // around in the template. +// Inverse of buildEnvTemplate: substitutes real values back into a +// template's `KEY=` lines while leaving comments/blank lines/order intact. +export function fillEnvTemplate(template: string, values: Record): string { + return template + .split('\n') + .map(line => { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) return line; + const eq = line.indexOf('='); + if (eq === -1) return line; + const key = line.slice(0, eq); + const value = values[key.trim()] ?? ''; + return `${key}=${/[\s#"']/.test(value) ? JSON.stringify(value) : value}`; + }) + .join('\n'); +} + export function buildEnvTemplate(text: string): string { return text .split('\n') diff --git a/src/cli/index.ts b/src/cli/index.ts index 6d8bf69..5e493f7 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -55,7 +55,8 @@ function printUsage(): void { keep push [--file .env] [--purge] (--purge: don't retain the outgoing version as rollback) keep pull [--format env|json] [--out ] [--previous] [--template] - (--template: comments/structure with values redacted, not the secrets) + (fills the saved template with real values by default, if one exists; + --template: comments/structure with values redacted, not the secrets) keep grant [--read-only] keep revoke (admin — needs KEEP_ADMIN_PASSWORD) keep log