Skip to content

Modernize tooling and validate the configuration: TypeScript, ESM, Vitest, ESLint 10, zod - #34

Merged
eXaminator merged 10 commits into
mainfrom
update-all-dependencies
Oct 15, 2026
Merged

eXaminator merged 10 commits into
mainfrom
update-all-dependencies

Conversation

@eXaminator

@eXaminator eXaminator commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Summary

Replaces outdated tooling and converts the packages to TypeScript. Prepares the next major release (3.0.0).

  • Dependencies updated – @sentry/node 8 → 11, date-fns 3 → 4, CI on Node 24, actions/checkout / actions/setup-node v7
  • Lerna → npm workspaces – all packages share one version. npm run release -- <major|minor|patch|x.y.z> bumps, commits and tags; CI checks that the tag matches the version and runs npm publish --workspaces
  • TypeScript + ESM – built with tsc, ESM only, generated type declarations replace the handwritten types.d.ts
  • Jest → Vitest – config tests now use real fixture projects instead of virtual jest mocks
  • Config validation with zod 4 – the merged configuration (package.json + Logger options) is validated whenever a Logger is created, after env() placeholders are resolved. All problems are reported at once with their path, unknown options are rejected
  • Plugin config hook – a plugin class can provide a static configSchema; the logger loads the configured plugins first and validates their options together with its own. The Sentry plugin uses it (booleans/numbers accept strings for env(), exceptionLevel must be a configured level)
  • Fixed log levels and stricter types – the eight syslog levels are real Logger methods plus a generic log(level, message, meta) that rejects unknown levels; the levels option is gone, so level methods can't be missing at runtime. Messages are string | Error, Meta is an interface plugins can extend (Sentry adds tags and user), timers take a string label
  • Error classes removed – CustomError, HttpError and ConnectorError aren't used anywhere and don't belong into a logger. The native cause replaces origin; the formatter logs the stack trace, an error's context and the chain of cause errors
  • Sentry plugin – log levels are mapped to Sentry's severity levels (critical → fatal etc., the original level is kept as tag logLevel); strings are sent with captureMessage instead of captureException (the level is kept on the event); breadcrumbs are added to Sentry directly instead of being re-added for every exception, which caused duplicates
  • ESLint 10 – flat config with recommended rules (ESLint, typescript-eslint type checked, import-x, vitest); @stylistic keeps the existing code style
  • Removed: Babel, Jest, Lerna, eslint-config-brainbits, deprecated jest-get-type. Install shrinks from 991 to 187 packages, npm audit reports 0 vulnerabilities

Breaking changes

  • ESM only, requires Node.js >= 22.12
  • @sentry/node 11 (requires Node.js >= 20.19 itself)
  • Unknown options and invalid values throw an error
  • The levels option is removed, the eight syslog levels are fixed
  • Messages are typed as string | Error. Objects and arrays from callers without type checks are still logged like before (message/context/extras, arrays joined with spaces)
  • CustomError, HttpError and ConnectorError are removed
  • Plugins that read their own options from the configuration need to provide a configSchema
  • log() with an unknown level throws a TypeError
  • Formatters and plugins configured in package.json are resolved with Node's module resolution from the project (createRequire) and loaded via require(esm), so the Logger constructor stays synchronous

Example:

Error: Invalid node-logger configuration:
✖ Unrecognized key: "maxlevel"
✖ Invalid option: expected one of "emergency"|"alert"|"critical"|"error"|"warning"|"notice"|"info"|"debug"
  → at sentry.exceptionLevel

Fixes

  • Numbers in the configuration were turned into {} while parsing env() placeholders (e.g. sentry.maxBreadcrumbs → unlimited breadcrumbs)
  • Errors thrown while loading formatters/plugins keep the original error as cause
  • Misconfigurations that previously failed silently, e.g. "sentry": { "exceptionLevel": "fatal" } (no exception ever sent), "sentry": { "debug": "env(SENTRY_DEBUG, false)" } ("false" is truthy) or "plugins" as string (crashed with config.plugins.map is not a function)
  • examples/test-module is runnable again
  • env() without fallback no longer throws when the variable is missing or empty: the option is left out and its default applies, so optional values like the Sentry DSN can stay unset. Empty variables use the fallback as well
  • Formatters and plugins exporting themselves directly (CommonJS module.exports = …) are loaded; modules without default export fail with a clear error
  • The package.json is read once per working directory instead of for every Logger, and is found in the root directory as well
  • Numeric user ids (context.user / meta.user) are sent to Sentry again
  • Breadcrumbs of circular objects or BigInts no longer make the log call throw
  • The formatter logs the whole cause chain, including causes that are no errors and causes of plain objects; missing messages are logged as - again

Notes

  • Babel 8 / TypeScript 7 are not used: typescript-eslint doesn't support TypeScript 7 yet
  • Releasing 3.0.0: npm run release -- major. The release script also sets the peer dependencies of the formatter and the Sentry plugin on @brainbits/node-logger to the new version (setting them before the bump is not possible, npm ci would fail with ERESOLVE)

Test plan

  • npm ci, npm run typecheck, npm run lint, npm test (114 tests), npm run build
  • Config tests with fixture plugins: plugin options validated and defaulted, unknown and conflicting plugin options rejected
  • examples/test-module fails at startup with a readable error for a broken configuration, also when the invalid value comes from env()
  • Sentry plugin smoke-tested against the real @sentry/node 11 (init, console integration removed, user/tags/extras/breadcrumbs/exception in the event)
  • examples/test-module loads formatter and Sentry plugin as ES modules via package.json and logs correctly
  • Release script dry-run in a throwaway copy: major and a following patch bump all versions and peer dependencies, npm ci, build and tests pass, annotated tag matches the CI check
  • Packed 3.0.0 tarballs install in a fresh app without ERESOLVE and log correctly; the Sentry plugin 3.0.0 together with @brainbits/node-logger@2 is rejected by npm

🤖 Generated with Claude Code

eXaminator and others added 5 commits October 13, 2026 11:19
- @sentry/node 8 → 11, date-fns 3 → 4, jest 29 → 30, lerna 8 → 10
- babel and eslint to latest compatible 7.x / 8.x
- drop deprecated @babel/plugin-proposal-class-properties (covered by preset-env)
- CI: Node 24, actions/checkout and actions/setup-node v7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All packages now share one version. `npm run release` bumps every
workspace, commits and tags the release; CI publishes the tag with
`npm publish --workspaces` after checking it matches the version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Sources are TypeScript, built with tsc into ESM + type declarations.
  The handwritten types.d.ts files are replaced by generated ones.
- Tests run with vitest instead of jest. The config tests use real
  fixture projects instead of virtual jest mocks.
- Formatters and plugins are resolved with createRequire() from the
  project's package.json and loaded via require(esm), which keeps the
  Logger constructor synchronous.
- Replace the deprecated jest-get-type with a small local check.
- Fix numbers in the configuration being turned into empty objects
  while parsing env() placeholders.
- Make the example project runnable again.

BREAKING CHANGE: The packages are ESM only and require Node.js >= 22.12.
Plugins and formatters loaded via package.json must be resolvable with
Node's module resolution (ESM or CJS with a default export).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace eslint-config-brainbits with a flat config based on the
recommended rules of ESLint, typescript-eslint (type checked),
import-x and vitest. @Stylistic keeps the existing code style
(4 spaces, semicolons, single quotes, 1tbs).

Errors thrown while loading formatters and plugins now keep the
original error as `cause`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The merged configuration is validated when a Logger is created, after
env() placeholders are resolved. All problems are reported at once with
their path, unknown options are rejected.

Plugins hook into the validation with a static `configSchema`: the
logger loads the configured plugins first and validates their options
together with its own. The Sentry plugin defines its options this way
and accepts strings for booleans and numbers, so they can be set with
env().

BREAKING CHANGE: Unknown options and invalid values throw an error.
Only the standard log levels are allowed. Plugins that read their own
options from the configuration need to provide a `configSchema`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@eXaminator eXaminator mentioned this pull request Oct 13, 2026
3 tasks done
@eXaminator eXaminator changed the title Modernize tooling: TypeScript, ESM, Vitest, ESLint 10, npm workspaces Modernize tooling and validate the configuration: TypeScript, ESM, Vitest, ESLint 10, zod Oct 13, 2026
eXaminator and others added 5 commits October 15, 2026 13:17
- The eight syslog levels are fixed: the Logger has real methods for
  them and a generic `log(level, message, meta)`. The `levels` option
  is removed, so methods can't be missing at runtime anymore.
- Messages are a string or an Error. The formatter logs the stack trace,
  the `context` of an error and the chain of `cause` errors.
- `Meta` is an interface plugins can extend; the Sentry plugin adds
  `tags` and `user`. Users are typed in the config schema.
- Timers take a string label and are removed when stopped.
- The Sentry plugin maps the log levels to Sentry's severity levels
  (keeping the original as tag) and sends strings as messages instead
  of exceptions.
- Remove CustomError, HttpError and ConnectorError: domain errors don't
  belong into the logger, the native `cause` replaces `origin`.

BREAKING CHANGE: The `levels` option is removed, messages must be a
string or an Error, and the error classes are no longer exported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`npm version --workspaces` bumped the versions but left the peer
dependency of the formatter and the Sentry plugin on the core package
at ^2.2.3, so the bump itself and every following `npm ci` failed with
ERESOLVE. The release script now bumps the workspaces itself and sets
their peer dependencies on other workspace packages to the new version
before the lockfile is updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Messages are typed as string or Error, but callers without type checks
still log objects and arrays. They became `[object Object]` and arrays
were joined with commas. The formatter logs them like before again: the
`message` and `context` of an object are used, the other properties
become extras, arrays are joined with spaces. The Sentry plugin sends
them as readable text and keeps objects as extra.

The Sentry plugin buffered breadcrumbs itself and added all of them to
Sentry again for every exception, so later events contained duplicates.
Breadcrumbs are now added to Sentry directly, which keeps the last
`maxBreadcrumbs` and scopes them per request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kage.json once

CommonJS modules exporting the formatter or plugin directly are loaded
again, modules without a default export fail with a clear error.
The package.json is read once per working directory instead of for
every Logger, and is found in the root directory as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An env() without fallback no longer throws if the variable is not set or
empty, the option is left out and its default applies. This way optional
values like the Sentry DSN can stay unset. Empty variables use the
fallback as well, they no longer result in an empty string.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@eXaminator
eXaminator force-pushed the update-all-dependencies branch from 11fa916 to 7d00c0b Compare October 15, 2026 12:49
@eXaminator
eXaminator merged commit a0092b6 into main Oct 15, 2026
2 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