Skip to content

Correctness, security and packaging overhaul — release 0.4.0 - #3

Merged
mt3o merged 9 commits into
mainfrom
feature/improvements1
Sep 8, 2026
Merged

mt3o merged 9 commits into
mainfrom
feature/improvements1

Conversation

@mt3o

@mt3o mt3o commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Seven commits of correctness, security and packaging work, released as 0.4.0. Full detail in CHANGELOG.md; this is the summary.

Security

  • Prototype pollution fixed. A layer could write to Object.prototype via __proto__ or constructor.prototype — reachable in practice, since a layer parsed from JSON carries __proto__ as a real own property that hasOwnProperty does not filter out.

Breaking

  • A missing key now warns (once per config) and resolves to undefined instead of throwing.
  • cfg['a.b'] and cfg.a.b were separate resolvers that disagreed — inverted layer precedence, one-level-deep merging, arrays returned as {"0":"a"}, array merge strategies invisible. They are now one resolver.
  • Key paths match own properties only, so cfg['constructor.name'] and friends no longer resolve.
  • The config deep-copies everything it exposes, so mutating a layer afterwards no longer changes what it resolves, and freeze: true no longer freezes objects you still hold.
  • Protocol lookups (toJSON, toString, unknown symbols, $$/@@ sentinels) resolve to undefined instead of throwing.
  • UMD global renamed Config LayersConfigLayers; types corrected to dist/index.d.ts.

Notable fixes

  • The library crashed on iOS 15 / Safari 15. Key splitting used a regex lookbehind, unsupported below Safari 16.4; the bundler silently rewrites it to new RegExp("…"), which parses and then throws on the first key lookup. The splitter is now hand-written and the bundle contains no regexes at all.
  • Merging destroyed Date, Buffer and class instances, and threw when a layer overrode a scalar with an object.
  • __derive dropped symbol-named layers and reordered numeric-looking ones, inverting precedence.
  • String(config), JSON.stringify(config), config.name and Object.getOwnPropertyDescriptor all misbehaved or threw.

Performance

Measured against 0.2.1, both in one process:

operation 0.2.1 0.4.0
config['a.b.c.d'] 1174 ns 154 ns 7.6× faster
config('a.b.c.d') 1144 ns 129 ns 8.9× faster
config.flat 164 ns 49 ns 3.4× faster
fromLayers(...) 3120 ns 6392 ns 2× slower

Construction pays for the deep copy that makes the ownership guarantees real — microseconds, once at startup. Read paths are guarded against regressing by tests/performance.test.ts.

Tooling

  • Tests: 78 (2 failing) → 290 passing, 18 files, with coverage thresholds.
  • ESLint added; CI on every push and PR across Node 20/22/24; release workflow publishes the version named by the tag.
  • Minified builds: CJS 13.0 → 7.3 kB, UMD 14.2 → 7.5 kB. sideEffects: false added.

Why 0.4.0 and not 0.3.0

config-layers@0.3.0 is already on npm (published 2026-04-21) from a separate line of work not in this history, and npm does not allow republishing a version. The changelog records the gap.

Releasing

After merge, tag from main — that fires release.yml, which verifies, builds, packs, creates the GitHub Release and publishes to npm with provenance:

git checkout main && git pull
git tag v0.4.0 && git push origin v0.4.0

Requires NPM_TOKEN in Actions secrets; without it the workflow completes but skips publishing with a notice rather than failing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EKcR1Xs7sAKKYYgn5vs69q

mt3o and others added 7 commits September 8, 2026 09:23
…onfigurations

Added `dist-smoke.test.ts` to validate packaged build artifacts including module formats, export maps, and syntax compatibility. Updated `package.json` and documentation to better support package consumers. Adjusted test resolver to ensure examples use source files during development.
… integrate freezing and cloning improvements, and update `package.json` with browser compatibility and build settings adjustments.
…async operations, proxy conformance, and advanced value handling. Introduced ESLint configuration for consistent code quality.
…ersions, and release automation

Configured `.github/workflows/ci.yml` to run quality checks (lint, typecheck) and tests on Node.js versions 20, 22, and 24. Updated `release.yml` to streamline version handling, artifact publishing, and npm releases. Adjusted `package.json` to include `pnpm` package manager specification.
Configured `LayeredConfig` to warn and resolve missing keys to `undefined` by default. Updated tests and documentation to reflect this behavior and added the ability to customize the handler for different use cases. Integrated performance guard tests and adjusted CI workflow to exclude them from coverage runs.
Documents the correctness, security and packaging work in CHANGELOG.md, and
ships it in the published package (`files` is an allowlist, and npm's
always-included set does not cover it).

Released as 0.4.0 rather than 0.3.0: that version is already on npm from a
separate line of work, so it cannot be republished. The changelog records the
gap so 0.2.1 upgraders are not left wondering.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKcR1Xs7sAKKYYgn5vs69q
@mt3o mt3o closed this Sep 8, 2026
@mt3o mt3o reopened this Sep 8, 2026
mt3o and others added 2 commits September 8, 2026 12:56
`cache: pnpm` and `pnpm install --frozen-lockfile` both require a committed
lockfile. This repository deliberately ignores one - the note at the top of
.gitignore says libraries should not impose specific versions - so setup-node
failed with "Dependencies lock file is not found" before any step could run.

Drops both, with a comment recording why, so the constraint is not
reintroduced by someone tidying up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKcR1Xs7sAKKYYgn5vs69q
pnpm 12 refuses to run a dependency's install script unless it is approved, and
this project's approval for esbuild lives in pnpm-workspace.yaml - which, like
pnpm-lock.yaml, is gitignored. CI therefore failed with ERR_PNPM_IGNORED_BUILDS.

Skipping dependency install scripts sidesteps the approval entirely and is the
safer default for CI anyway; esbuild's binary comes from its platform package,
so the build is unaffected. Verified on a tree with neither file present:
install, lint, typecheck, build and all 290 tests pass.

Also drops a `pnpm.onlyBuiltDependencies` field added while diagnosing this -
pnpm 12 reads that setting from pnpm-workspace.yaml, not package.json, so it
had no effect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKcR1Xs7sAKKYYgn5vs69q
@mt3o
mt3o merged commit 51b7c23 into main Sep 8, 2026
4 checks passed
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.

1 participant