feat: jq gating, platform-aware install hints, stage-1 docs (EGB-677 stage 1)
- jq required only when .secrets.json exists or is being written; manifest-less projects run jq-free (features skipped with a notice) — clone-and-run for v1 users survives (no-jq fixture excludes /usr/bin, macOS ships jq there now) - check_cmd: platform-aware hints (brew/apt-get/dnf/generic) instead of hardcoded brew — correct guidance on Linux/CI - cmd_help: add command, push flags, manifest section with example - README: manifest section, external files rewritten around .secrets.json (legacy .secrets-files documented as absorbed), troubleshooting entries, command table, test instructions - CLAUDE.md: manifest architecture notes, bash-3.2 '[[ ]] || false' testing convention, project structure refresh
This commit is contained in:
parent
0049584d9b
commit
89e851278b
4 changed files with 168 additions and 22 deletions
55
secrets
55
secrets
|
|
@ -37,7 +37,18 @@ die() { echo "ERROR: $*" >&2; exit 1; }
|
|||
info() { echo "==> $*"; }
|
||||
|
||||
check_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || die "'$1' is not installed. Run: brew install $1"
|
||||
command -v "$1" >/dev/null 2>&1 && return
|
||||
# Platform-aware install hint — hardcoding brew is wrong guidance on a
|
||||
# Linux box or CI runner (EGB-677 DX review).
|
||||
local hint="install '$1' with your package manager"
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
hint="brew install $1"
|
||||
elif command -v apt-get >/dev/null 2>&1; then
|
||||
hint="sudo apt-get install $1"
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
hint="sudo dnf install $1"
|
||||
fi
|
||||
die "'$1' is not installed. Run: $hint"
|
||||
}
|
||||
|
||||
check_initialized() {
|
||||
|
|
@ -1216,6 +1227,12 @@ cmd_push() {
|
|||
pubkey=$(get_pubkey)
|
||||
|
||||
# ── Manifest read (validated; absence = bootstrap) ──
|
||||
# jq is required only when a manifest exists (authoritative, can't be
|
||||
# ignored) or is being written. Without jq on a manifest-less project,
|
||||
# manifest features are skipped with a notice — clone-and-run for v1
|
||||
# users survives.
|
||||
local have_jq=true
|
||||
command -v jq >/dev/null 2>&1 || have_jq=false
|
||||
local manifest="$PWD/$SECRETS_JSON_NAME"
|
||||
local have_manifest=false auto_add=true declared=""
|
||||
if [ -e "$manifest" ]; then
|
||||
|
|
@ -1312,8 +1329,11 @@ cmd_push() {
|
|||
# Two independent reasons to write: dotenv auto-adds, and absorbing a
|
||||
# legacy .secrets-files (gradle-properties → properties) so the two
|
||||
# external sources converge on the manifest.
|
||||
if [ "$have_jq" = false ]; then
|
||||
echo "NOTE: jq not found — skipping $SECRETS_JSON_NAME manifest features (auto-add, absorb). Install jq to enable them." >&2
|
||||
fi
|
||||
local absorbed_json="[]" n_absorbed=0
|
||||
if [ "$frozen" = false ]; then
|
||||
if [ "$frozen" = false ] && [ "$have_jq" = true ]; then
|
||||
absorbed_json=$(_legacy_absorb_json "$PWD")
|
||||
n_absorbed=$(printf '%s' "$absorbed_json" | jq 'length')
|
||||
fi
|
||||
|
|
@ -1321,7 +1341,7 @@ cmd_push() {
|
|||
if [ -n "$to_add" ] && { [ "$auto_add" = true ] || [ "$have_manifest" = false ]; }; then
|
||||
write_adds=true
|
||||
fi
|
||||
if [ "$did" -eq 1 ] && [ "$frozen" = false ] \
|
||||
if [ "$did" -eq 1 ] && [ "$frozen" = false ] && [ "$have_jq" = true ] \
|
||||
&& { [ "$write_adds" = true ] || [ "$n_absorbed" -gt 0 ]; }; then
|
||||
local add_json="[]"
|
||||
[ "$write_adds" = true ] && add_json=$(printf '%s' "$to_add" | jq -R -s 'split("\n") | map(select(length > 0))')
|
||||
|
|
@ -1905,21 +1925,48 @@ secrets — encrypted secret file sync between machines
|
|||
Usage:
|
||||
secrets init Initialize the secrets repo and generate an age key
|
||||
secrets push [project] Encrypt secret files and push to the secrets repo
|
||||
secrets push --frozen Sync only manifest-declared files (skip auto-add)
|
||||
secrets push --dry-run Show what would be added/synced; change nothing
|
||||
secrets push -w|--workspaces Push secrets from all workspaces in package.json
|
||||
secrets pull [project] Pull and decrypt secret files into current directory
|
||||
secrets pull -w|--workspaces Pull secrets into all workspaces from package.json
|
||||
secrets add <path> Declare a project-relative file in .secrets.json
|
||||
secrets clear Remove plaintext secret files from current directory
|
||||
secrets clear -w|--workspaces Clear secrets from all workspaces in package.json
|
||||
secrets run [-w] <command> Pull secrets, run command, clear secrets on exit
|
||||
secrets list List all projects and their secret files
|
||||
secrets rm <project> Remove a project's secrets from the repo
|
||||
secrets rekey Re-encrypt all secrets with a new key
|
||||
secrets which Show the active store path and which rule chose it
|
||||
secrets which Show the active store, manifest, and external entries
|
||||
secrets where Alias for `which`
|
||||
secrets status Alias for `which`
|
||||
|
||||
Tracked files: .env, .env.*, .dev.vars
|
||||
|
||||
Manifest (.secrets.json):
|
||||
A committed project-root manifest declaring everything the project
|
||||
syncs (requires jq). `secrets push` discovers conventional files and
|
||||
auto-adds them with a notice; set {"options":{"autoAdd":false}} to
|
||||
require explicit `secrets add` instead. Dotenv paths are project-
|
||||
relative (nested workspace paths welcome); external entries use
|
||||
{"type":"properties"|"file","path":...,"keys":[...]}:
|
||||
|
||||
{
|
||||
"version": 2,
|
||||
"options": { "autoAdd": true },
|
||||
"dotenv": [".env", "packages/web/.env.development"],
|
||||
"external": [
|
||||
{ "type": "properties", "path": "~/.gradle/gradle.properties",
|
||||
"keys": ["beaconClerkPkTest"] },
|
||||
{ "type": "file", "path": "~/keystores/upload.keystore" }
|
||||
]
|
||||
}
|
||||
|
||||
A legacy .secrets-files is absorbed into .secrets.json on the next
|
||||
push (gradle-properties entries become type "properties") and can be
|
||||
deleted afterwards. Without jq, manifest-less projects keep working;
|
||||
manifest features are skipped with a notice.
|
||||
|
||||
If [project] is omitted, it is derived from the current directory's
|
||||
git remote (if available) or the directory name.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue