fix: discover workspaces in pnpm and yarn monorepos (EGB-1232)

Workspace patterns came from package.json's `workspaces` key only, at both
call sites. pnpm declares them in pnpm-workspace.yaml instead, so no pnpm
monorepo ever resolved a workspace: `push -w` refused outright, and plain
`push` failed silently — _maybe_workspace_env_files returned 0 the moment
the key was absent, leaving the auto-discovery that covers push's root-only
scan inert and printing "Nothing new to add", indistinguishable from a repo
with genuinely nothing new. That silence cost two sessions on the same repo.

Both call sites now resolve through one shared source, _workspace_patterns:
package.json when it declares any, else pnpm-workspace.yaml's `packages:`
block. The YAML read is deliberately not a parser — block sequence only,
stopping at the next top-level key so pnpm 10's onlyBuiltDependencies:/
catalog: cannot leak in as globs, with quote/comment handling and a symlink
refusal.

Also fixes yarn's object form. `.workspaces // .workspaces.packages | .[]`
short-circuits on the truthy object, iterating its values and yielding the
pattern array as one token; only npm's array form ever worked. Note the
obvious reorder is NOT the fix — `.workspaces.packages` errors on an array —
so the filter is type-aware.

Patterns are validated before reaching the unquoted glob expansion (no
absolute paths, `..`, metacharacters, or whitespace; pnpm `!` negations
skipped), matching the .secrets-store/.secrets-files posture. jq is now
required only when package.json is the source. A monorepo-shaped root that
resolves nothing warns and points at `secrets add` instead of returning in
silence, and `-w`'s error names pnpm-workspace.yaml when that is the file
present.

Scope note: the workspace re-scan still runs only for projects that already
have a .secrets.json — push's root-scan-only first push is by design
(EGB-677 E13), and this bug is the fallback covering it never engaging.

test/workspaces.bats: 18 new tests. Full suite 371/371 green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrUoYuUMoTj91rzV4vxGPB
This commit is contained in:
Brian Majewski 2026-09-08 15:22:04 -07:00
parent b3a727c8fb
commit c09ac38b16
6 changed files with 517 additions and 14 deletions

View file

@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to a four-digit MAJOR.MINOR.PATCH.MICRO version scheme.
## [0.7.7.0] - 2026-09-08
### Fixed
- **pnpm monorepos discovered no workspace secrets (EGB-1232)** — both
workspace call sites resolved patterns from `package.json`'s `workspaces`
key only, which pnpm does not use (it declares `packages:` in
`pnpm-workspace.yaml`). `secrets push -w` refused outright; plain `secrets
push` failed *silently* — `_maybe_workspace_env_files` returned 0 the moment
the key was absent, so the auto-discovery that exists to cover push's
root-only scan was inert on every pnpm repo and printed "Nothing new to
add", indistinguishable from a repo that genuinely had nothing. Workspace
patterns now resolve through one shared source that falls back to
`pnpm-workspace.yaml`.
- **yarn's object `workspaces` form was never expanded (EGB-1232)** — the
filter `.workspaces // .workspaces.packages | .[]` short-circuits on yarn's
truthy object, so `.workspaces.packages` was never evaluated and `.[]`
iterated the object's values, yielding the pattern array itself as a single
token. Only npm's array form worked. Now type-aware, handling npm's array,
yarn's object, and absent/null alike.
### Added
- **`pnpm-workspace.yaml` support** — the `packages:` block sequence is read
without a YAML dependency: block form only, stopping at the next top-level
key so pnpm 10's `onlyBuiltDependencies:`/`catalog:` cannot leak in as glob
patterns, with quote and inline-comment handling and a symlink refusal.
`package.json` wins when it declares workspaces; `pnpm-workspace.yaml` is the
fallback. jq is now required only when `package.json` is the source, so a
pnpm-only repo resolves workspaces jq-free.
- **Workspace patterns are validated before glob expansion** — no absolute
paths, `..` traversal, shell metacharacters, or whitespace reach the
unquoted expansion; pnpm `!` negations are skipped. Same conservative rail
as `.secrets-store` / `.secrets-files`.
- **A monorepo-shaped root that resolves no workspaces now says so** — if a
`pnpm-workspace.yaml` or `packages/` directory is present but no workspace
packages can be read, `push` warns on stderr and points at `secrets add`,
instead of returning in silence. `secrets push -w`'s error now names
`pnpm-workspace.yaml` when that is the file present, rather than blaming a
`package.json` the repo may not use for workspaces.
## [0.7.6.0] - 2026-09-08
### Added