eerawlog
Version:
EmailEngine raw log viewer
62 lines (40 loc) • 5.88 kB
Markdown
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this is
`eerawlog` is a small Node CLI that pretty-prints [EmailEngine](https://emailengine.app/) logs. EmailEngine emits NDJSON Pino logs, and when run with `EENGINE_LOG_RAW=true` it also includes base64-encoded raw IMAP socket traffic. This tool reads those log lines from stdin and renders selected entry types in a human-readable, color-coded form.
Typical invocation: `EENGINE_LOG_RAW=true emailengine | eerawlog [--filter.<key>=<value> ...]`
There is no build step. Source is plain CommonJS; `bin/eerawlog.js` is a one-line shim that `require`s `index.js`.
## Commands
- `npm test` — runs `test/cli.test.js` via `node --test`. These are **black-box CLI tests**: each case spawns `bin/eerawlog.js` as a subprocess, pipes NDJSON to its stdin, strips ANSI with `clc.strip`, and asserts on stdout. There are no unit tests of internal functions — behavior is pinned only through the CLI surface, so a change to rendering must be validated by feeding representative log lines.
- Run one test by name: `node --test --test-name-pattern="filter.account back-fill" test/cli.test.js`.
- Lint/format config exists (`.eslintrc` extends `nodemailer` + `prettier`, `ecmaVersion` 2018; `.prettierrc.js`: 4-space, single quotes, 160 col, no trailing commas) but ESLint/Prettier are **not** in `devDependencies` and there is no lint/format npm script — run them via `npx` if you need to.
- `npm run update` — wipes `node_modules`/`package-lock.json`, runs `ncu -u`, reinstalls. See the dependency constraint below before accepting any bump.
## Dependencies: CommonJS only
eerawlog sits in the EmailEngine dependency tree, which is loaded with `require()` and **cannot** use ESM. Every dependency — direct and transitive — must ship real CommonJS, not ESM and not a thin CommonJS wrapper around an ESM module. After `npm run update` or any dependency bump, verify this before committing:
- No installed `package.json` may declare `"type": "module"` unless it also exposes a genuine CommonJS entry via an `exports` `require` condition. Quick scan (expect no matches): `grep -rl '"type": *"module"' node_modules/*/package.json node_modules/@*/*/package.json`.
- Do **not** rely on "the tests still pass". Node 24 enables `require(esm)`, so an ESM-only dependency loads transparently at runtime and hides the problem — inspect the package metadata, not the runtime behavior.
- If a dependency's new major has gone ESM-only, pin it to the last CommonJS release (via `.ncurc.js` or an explicit version in `package.json`) instead of upgrading.
## Architecture
Almost everything lives in `index.js` (~180 lines). The pipeline is:
```
process.stdin → split2(Parse) → Logger (Writable, objectMode) → process.stdout
```
- `Parse(data)` is a per-line factory invoked by `split2`. It returns an object with either `{value: <parsedJSON>}` on success or `{err, input}` on failure (so malformed lines are still printed in yellow rather than crashing).
- `Logger._write` is a chain of independent `if` blocks, one per recognized log shape. A single chunk may match multiple blocks (e.g. a connection event also containing TLS info). The recognized shapes are:
- `action === 'onPreHandler'` with `path` starting `/v1/` — Hapi route handler entry.
- `component === 'api'` with `req`/`res`/`msg` — API request completion line.
- `action === 'renewAccessToken'` — OAuth2 token renewal (red on error, yellow on success); also dumps `expires`, `scopes`, and `response.error` details.
- `src === 'connection'` with `host`+`port` — IMAP connection establishment.
- `src === 'tls'` with `algo`+`version` — TLS session info.
- `src` + `data` (base64) — raw socket bytes; `src === 's'` is server→client (xterm color 39), anything else is client→server (color 119). The `[SC]` prefix flags secure/compressed.
- `this.prevConn` tracks the last printed `cid` (connection id). Whenever a new `cid` is seen, a bold connection header is emitted before the line, so multi-line raw dumps stay grouped under the connection they belong to.
### Filtering
`minimist` parses `--filter.<key>=<value>` into `argv.filter`, which becomes the `filter` object. Repeating the same flag yields an array of values for that key.
Filter semantics in `_write`:
- All filter keys must match (AND across keys); within a key, any listed value matches (OR across values).
- Numeric coercion is applied when the log field is a number; string coercion when it is a string.
- Special case: filtering by `account` also matches API log entries whose `req.url` contains `/<account>/`, even when the entry has no `account` field. In that case `chunk.value.account` is back-filled from the filter so downstream output shows it. Keep this behavior in mind when adding new filterable keys — the rest of the matcher is generic, this branch is not.
### Adding a new log shape
Add another `if (chunk && chunk.value && ...)` block in `_write`. Follow the existing conventions: build a `${timePrefix}LABEL:` prefix (`timePrefix` is empty when the user passed `--no-time`), use `clc.xterm(<color>)` for coloring, and if the entry belongs to a connection, emit a `prevConn` header first so it groups correctly with surrounding raw traffic (`_printConnHeader` is a no-op under `--no-cid`).
## Releases
Releases are automated with release-please (`.github/workflows/release.yaml`, `release-please-config.json`, `release-type: node`). Pushes to `master` with Conventional Commit messages drive the version bump and `CHANGELOG.md`: `fix:`/`feat:` open a release PR, while `chore:`/docs/CI commits do not trigger a release. Merging the release PR publishes to npm with provenance from the `npm-publish` GitHub environment — so commit messages are load-bearing, not cosmetic.