Description
Symptom
createLogger({ file: '/path/to.log' }) never creates or writes the log file when @objectstack/core/logger is consumed from an ESM entrypoint (which is what os serve / os dev run — the workspace is type: module). The same config works from a CJS consumer.
Repro (against built dist, Node v25)
// probe.mjs
import { createLogger } from '@objectstack/core/logger';
import fs from 'node:fs';
const l = createLogger({ format: 'pretty', file: '/tmp/probe.log' });
l.info('esm file probe');
await l.destroy();
console.log('file exists:', fs.existsSync('/tmp/probe.log')); // → falseRoot cause
openFileStream() in packages/core/src/logger.ts does a lazy require('fs') ("Lazy require to avoid bundling issues" — the module is deliberately browser-safe for @objectstack/client). tsup/esbuild compiles that to the __require shim in the ESM output (dist/logger.js), which throws Dynamic require of "fs" is not supported at runtime; the surrounding try { … } catch { /* ignore — file logging is optional */ } swallows it, so file logging silently never activates. The CJS output (dist/logger.cjs) is unaffected because real require exists there.
Suggested fix
Keep the module browser-safe but stop relying on bundled require:
process.getBuiltinModule?.('node:fs')(Node ≥22.3, sync, already behind the existingtypeof process !== 'undefined'guard and try/catch for older runtimes), orcreateRequire(import.meta.url)via a tsupshims-style banner, or an asyncimport('node:fs').
Also consider logging a one-line warning instead of the bare catch {} when a file destination was explicitly configured but could not be opened — the current silence is exactly how this went unnoticed.
Found while fixing NO_COLOR/TTY handling in the same file (the fix PR keeps the file path plain-text but does not touch this).
Activity
docs(create-objectstack): correct a bogus issue reference in the pnpm…
fix(core): ObjectLogger file destination silently no-ops in ESM builds (