Skip to content

feat: mirror pnpm release-age gate in npm and workspaces, type compiler options encode#139

Merged
spencerbeggs merged 2 commits into
mainfrom
feat/pnpm-gate
Jul 21, 2026
Merged

feat: mirror pnpm release-age gate in npm and workspaces, type compiler options encode#139
spencerbeggs merged 2 commits into
mainfrom
feat/pnpm-gate

Conversation

@spencerbeggs

@spencerbeggs spencerbeggs commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Mirrors pnpm's release-age gate (minimumReleaseAge / minimumReleaseAgeExclude) in the kit and types the tsconfig-json programmatic encode boundary.

@effected/npmReleaseAgeGate (minor → 0.3.0)

Schema-first release-age vocabulary, ported from the rolldown-pnpm-config reference by redesign: ReleaseAgeGate (Schema.Class) with combine (variadic, total, strictest-wins, exclude union) and matchesExclude (flat-string * with @pnpm/matcher parity — deliberately not @effected/glob's dialect, divergence documented), plus instance isExcluded / filterVersions (pure, caller-supplied clock; missing/unparseable publish timestamps dropped). PartialReleaseAgeGate is the permissive contribution schema; combine is the single clamping authority. 99/99 tests, zero-warning prod build.

@effected/workspaces — release-age surfacing (minor → 0.6.0, breaking)

ConfigDependencyHooks.inject now returns HookInjection { catalogs, releaseAge } — one hook replay yields both catalogs and the release-age keys the hooks already produced (previously discarded); keys thread last-hook-wins exactly as pnpm replays them. WorkspaceCatalogs.releaseAgeGate() assembles the effective gate from inline pnpm-workspace.yaml values + hook contributions, strictest-wins, folded into the existing single memoized assembly pass; malformed inline values fail typed (CatalogAssemblyError, manifest source). 273/273 tests, zero-warning prod build.

@effected/tsconfig-jsonProgrammaticCompilerOptions (minor → 0.3.0)

TsEnumCodec.encodeCompilerOptions returns the new exported structural ProgrammaticCompilerOptions instead of Record<string, unknown> — assignable to ts.CompilerOptions with no cast, transcribed from typescript 6.0.3's CompilerOptionsValue with the package's zero-typescript-imports rule intact, proven by a compile-time assignability test. Runtime unchanged. 158/158 tests.

Closes #120

Context

Driven by the savvy-web-systems dogfood loop relaying silk-update-action's ERR_PNPM_NO_MATURE_MATCHING_VERSION fix; downstream adopted via local overrides and confirmed all surfaces (535/535 tests on their side) before this PR. Registry version collision (npm 0.2.3 / workspaces 0.5.2 already published) is resolved by the changesets above.

Signed-off-by: C. Spencer Beggs spencer@beggs.codes

Adds pnpm-compatible release-age gating across @effected/npm and @effected/workspaces, including exclusion matching, version filtering, schema validation, hook replay, and workspace gate composition.

Exports structural ProgrammaticCompilerOptions types from @effected/tsconfig-json, allowing encoded compiler options to be assigned to TypeScript compiler options without casts while preserving runtime behavior.

feat: add release-age gates and typed compiler options

- Add pnpm release-age filtering and workspace gate composition with hook contributions.
- Export structural compiler option types for cast-free encoded configuration.
- Publish changesets for npm, workspaces, and tsconfig-json.

Closes `#120`
Signed-off-by: <PR author name> <PR author email>

…er options encode

- ReleaseAgeGate and PartialReleaseAgeGate in @effected/npm: strictest-wins combine, pnpm-parity flat star exclude matching, pure publish-age version filtering with a caller clock.
- ConfigDependencyHooks.inject now returns HookInjection so one hook replay yields catalogs and release-age keys; WorkspaceCatalogs gains releaseAgeGate assembling the effective gate strictest-wins.
- TsEnumCodec.encodeCompilerOptions returns the new structural ProgrammaticCompilerOptions type so consumers drop their casts.
- Changesets: @effected/npm 0.3.0, @effected/workspaces 0.6.0, @effected/tsconfig-json 0.3.0.

Closes #120
Signed-off-by: C. Spencer Beggs <spencer@beggs.codes>
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@spencerbeggs, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9078d6d1-db3e-459d-a3f5-fcd4a9a8d4fe

📥 Commits

Reviewing files that changed from the base of the PR and between 99fed97 and fff3e99.

📒 Files selected for processing (2)
  • packages/npm/CLAUDE.md
  • packages/npm/src/ReleaseAgeGate.ts
📝 Walkthrough

Walkthrough

The PR adds pnpm release-age gate modeling, hook replay integration, and WorkspaceCatalogs exposure. It also gives TsEnumCodec.encodeCompilerOptions a structural exported return type with assignability tests and updates related documentation.

Changes

Release-age gate integration

Layer / File(s) Summary
Release-age gate vocabulary
packages/npm/src/ReleaseAgeGate.ts, packages/npm/src/index.ts, packages/npm/__test__/ReleaseAgeGate.test.ts, packages/npm/README.md, packages/npm/CLAUDE.md
Defines ReleaseAgeGate and PartialReleaseAgeGate, including strictest-age combination, exclusion matching, version filtering, schemas, exports, tests, and documentation.
Hook release-age injection
packages/workspaces/src/ConfigDependencyHooks.ts, packages/workspaces/src/index.ts, packages/workspaces/__test__/fixtures/*, packages/workspaces/__test__/integration/ConfigDependencyHooks.int.test.ts
Extends hook injection to return catalog and release-age contributions, with tolerant parsing, last-write behavior, noop handling, fixtures, and integration coverage.
Workspace release-age assembly
packages/workspaces/src/WorkspaceCatalogs.ts, packages/workspaces/__test__/WorkspaceCatalogs.test.ts, packages/workspaces/__test__/integration/ConfigDependencyHooks.int.test.ts, packages/workspaces/README.md, packages/workspaces/CLAUDE.md
Parses inline workspace keys, combines them with hook contributions during memoized assembly, exposes releaseAgeGate(), and validates strictest-wins, exclusion union, inert paths, and typed manifest failures.

Typed compiler-options encoding

Layer / File(s) Summary
Typed compiler-options contract
packages/tsconfig-json/src/TsEnumCodec.ts, packages/tsconfig-json/src/index.ts, packages/tsconfig-json/__test__/TsEnumCodec.assignability.test.ts, packages/tsconfig-json/README.md, packages/tsconfig-json/CLAUDE.md
Adds exported structural compiler-options types, narrows encodeCompilerOptions’ return type, re-exports the types, and adds assignability and runtime tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WorkspaceCatalogs
  participant WorkspaceManifest
  participant ConfigDependencyHooks
  participant ReleaseAgeGate
  WorkspaceCatalogs->>WorkspaceManifest: decode inline release-age keys
  WorkspaceCatalogs->>ConfigDependencyHooks: replay hooks
  ConfigDependencyHooks-->>WorkspaceCatalogs: return catalogs and releaseAge
  WorkspaceCatalogs->>ReleaseAgeGate: combine inline and hook contributions
  ReleaseAgeGate-->>WorkspaceCatalogs: return effective gate
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The release-age gate additions in npm and workspaces are unrelated to linked issue #120, which only asked for TsEnumCodec typing changes. Move the release-age gate work to a separate PR or link the corresponding issue so this PR stays focused on #120.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and reflects both the release-age gate work and the tsconfig-json return-type change.
Linked Issues check ✅ Passed The typed return for TsEnumCodec.encodeCompilerOptions and exported structural types are implemented as requested.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/pnpm-gate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/npm/CLAUDE.md`:
- Line 29: Update the ReleaseAgeGate documentation to refer to its public
ageMinutes field instead of minReleaseAge, including the description of the
Schema.Class shape. Keep the existing combine, matchesExclude, isExcluded, and
filterVersions behavior descriptions unchanged.

In `@packages/npm/src/ReleaseAgeGate.ts`:
- Around line 64-72: Replace the RegExp-based wildcard handling in
matchesPattern with a linear-time matcher that processes the pattern and name
without backtracking, while preserving exact matches, * wildcard semantics, and
full-string matching.

In `@packages/workspaces/src/WorkspaceCatalogs.ts`:
- Around line 248-253: Update the Schema.decodeUnknownEffect pipeline’s
SchemaError handler to recover with a failed Effect containing
CatalogAssemblyError, using Effect.fail around the existing error construction.
Preserve the current source, path, and cause values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f1aa4393-0dce-43c9-bc1f-e724c80b6fa1

📥 Commits

Reviewing files that changed from the base of the PR and between 2ce8b9c and 99fed97.

⛔ Files ignored due to path filters (7)
  • .changeset/programmatic-compiler-options.md is excluded by !.changeset/**
  • .changeset/release-age-gate-assembly.md is excluded by !.changeset/**
  • .changeset/release-age-gate-vocabulary.md is excluded by !.changeset/**
  • .claude/design/effected/packages/npm.md is excluded by !.claude/**
  • .claude/design/effected/packages/tsconfig-json.md is excluded by !.claude/**
  • .claude/design/effected/packages/workspaces.md is excluded by !.claude/**
  • .claude/design/refs.json is excluded by !.claude/**
📒 Files selected for processing (20)
  • packages/npm/CLAUDE.md
  • packages/npm/README.md
  • packages/npm/__test__/ReleaseAgeGate.test.ts
  • packages/npm/src/ReleaseAgeGate.ts
  • packages/npm/src/index.ts
  • packages/tsconfig-json/CLAUDE.md
  • packages/tsconfig-json/README.md
  • packages/tsconfig-json/__test__/TsEnumCodec.assignability.test.ts
  • packages/tsconfig-json/src/TsEnumCodec.ts
  • packages/tsconfig-json/src/index.ts
  • packages/workspaces/CLAUDE.md
  • packages/workspaces/README.md
  • packages/workspaces/__test__/WorkspaceCatalogs.test.ts
  • packages/workspaces/__test__/fixtures/hook-pnpmfile-age-1440.mjs
  • packages/workspaces/__test__/fixtures/hook-pnpmfile-age-4320.mjs
  • packages/workspaces/__test__/fixtures/hook-pnpmfile-age-garbage.mjs
  • packages/workspaces/__test__/integration/ConfigDependencyHooks.int.test.ts
  • packages/workspaces/src/ConfigDependencyHooks.ts
  • packages/workspaces/src/WorkspaceCatalogs.ts
  • packages/workspaces/src/index.ts

Comment thread packages/npm/CLAUDE.md Outdated
Comment thread packages/npm/src/ReleaseAgeGate.ts
Comment thread packages/workspaces/src/WorkspaceCatalogs.ts
@spencerbeggs

This comment has been minimized.

- Replace the exclude matcher's star-to-regex compilation with a linear two-pointer wildcard scan so a config-supplied pattern cannot trigger catastrophic regex backtracking; matching semantics unchanged.
- Correct the npm package context file to name the real ageMinutes field.

Signed-off-by: C. Spencer Beggs <spencer@beggs.codes>
@spencerbeggs
spencerbeggs merged commit 0e500f5 into main Jul 21, 2026
19 checks passed
@spencerbeggs
spencerbeggs deleted the feat/pnpm-gate branch July 21, 2026 19:40

@spencerbeggs spencerbeggs Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All checks pass; release-age gate and typed compiler-options boundary are clean, well-documented, and thoroughly tested. No actionable issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@effected/tsconfig-json: typed return for TsEnumCodec.encodeCompilerOptions

1 participant