Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.11.0] - 2026-06-30

Audit of every package's public exports against the documentation, fixing all
import/usage mismatches found (the same class of bug as the missing
`initLogtide` export in 0.10.0).

### Added

- **`@logtide/browser`: `LogtideErrorBoundary` export** — the React error boundary documented at logtide.dev/integrations/react (`import { LogtideErrorBoundary } from '@logtide/browser'`) is now actually exported. `react` is declared as an **optional** peer dependency. `@logtide/nextjs/client` now re-exports this single shared implementation instead of its own copy.
- **`@logtide/nuxt`: `useLogtide()` composable** — auto-imported composable (`const { captureLog, captureError, addBreadcrumb } = useLogtide()`) for manual capture, as shown in the docs. Previously documented but not implemented.
- **`@logtide/cli`: `--path` option and `--apiKey` / `--apiUrl` aliases** for `sourcemaps upload` — the documented invocation `logtide sourcemaps upload --path ./dist --release 1.0.0 --apiKey KEY` now works. The directory may be passed either positionally or via `--path`; the canonical `--api-key` / `--api-url` forms continue to work.

### Fixed

- **`@logtide/nuxt`: `tracesSampleRate`, `apiUrl` and `apiKey` options are now honored.** The module previously dropped `tracesSampleRate` and ignored `apiUrl`/`apiKey` (it bailed out unless a `dsn` was set), contradicting the README. Configuration via `apiUrl` + `apiKey` (instead of `dsn`) now works on both client and server.
- **`@logtide/sdk-node` README**: the Express/Fastify middleware examples now import from `@logtide/sdk-node/middleware` (where `logTideMiddleware` / `logTideFastifyPlugin` actually live) instead of the package root.
- **`@logtide/nuxt` README**: corrected the documented default `service` value to `'nuxt'`.

## [0.10.0] - 2026-06-28

### Added
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"private": true,
"version": "0.10.0",
"version": "0.11.0",
"scripts": {
"build": "pnpm -r --filter @logtide/* build",
"test": "pnpm -r --filter @logtide/* test",
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@logtide/angular",
"version": "0.10.0",
"version": "0.11.0",
"description": "LogTide SDK integration for Angular — ErrorHandler, HTTP Interceptor, trace propagation",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
8 changes: 7 additions & 1 deletion packages/browser/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@logtide/browser",
"version": "0.10.0",
"version": "0.11.0",
"description": "Logtide browser SDK — Web Vitals, breadcrumbs, session context, offline resilience",
"type": "module",
"main": "./dist/index.cjs",
Expand Down Expand Up @@ -46,15 +46,21 @@
"@logtide/types": "workspace:*"
},
"peerDependencies": {
"react": ">=18.0.0",
"web-vitals": ">=4.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"web-vitals": {
"optional": true
}
},
"devDependencies": {
"@types/react": "^18.3.0",
"jsdom": "^28.1.0",
"react": "^18.3.0",
"tsup": "^8.5.1",
"typescript": "^5.5.4",
"web-vitals": "^4.2.4"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ interface State {
/**
* React ErrorBoundary that automatically reports errors to LogTide.
*
* Requires `react` (declared as an optional peer dependency).
*
* @example
* ```tsx
* import { LogtideErrorBoundary } from '@logtide/nextjs/client';
* import { LogtideErrorBoundary } from '@logtide/browser';
*
* <LogtideErrorBoundary fallback={<div>Something went wrong</div>}>
* <App />
Expand Down
3 changes: 3 additions & 0 deletions packages/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export {
type InitLogtideExtraOptions,
} from './init';

// React error boundary (requires the optional `react` peer dependency)
export { LogtideErrorBoundary } from './error-boundary';

// Session
export { getSessionId, resetSessionId } from './session';

Expand Down
33 changes: 33 additions & 0 deletions packages/browser/tests/error-boundary.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import { hub } from '@logtide/core';
import { LogtideErrorBoundary } from '../src/index';

describe('LogtideErrorBoundary', () => {
afterEach(() => {
vi.restoreAllMocks();
});

it('is exported as a React component class', () => {
expect(LogtideErrorBoundary).toBeDefined();
expect(typeof LogtideErrorBoundary).toBe('function');
});

it('derives error state from a thrown error', () => {
const err = new Error('boom');
const state = (LogtideErrorBoundary as any).getDerivedStateFromError(err);
expect(state).toEqual({ error: err });
});

it('reports caught errors to the LogTide hub', () => {
const captureError = vi.spyOn(hub, 'captureError').mockImplementation(() => {});
const err = new Error('kaboom');

const instance = new (LogtideErrorBoundary as any)({ children: null });
instance.componentDidCatch(err, { componentStack: '\n at Foo' });

expect(captureError).toHaveBeenCalledWith(
err,
expect.objectContaining({ mechanism: 'react.error-boundary' }),
);
});
});
1 change: 1 addition & 0 deletions packages/browser/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"jsx": "react-jsx",
"lib": ["ES2022", "DOM"]
},
"include": ["src/**/*"]
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ export default defineConfig({
dts: true,
clean: true,
sourcemap: true,
external: ['@logtide/types', '@logtide/core', 'web-vitals'],
external: ['@logtide/types', '@logtide/core', 'web-vitals', 'react', 'react/jsx-runtime'],
});
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@logtide/cli",
"version": "0.10.0",
"version": "0.11.0",
"description": "LogTide CLI — upload source maps and manage releases",
"type": "module",
"bin": {
Expand Down
82 changes: 60 additions & 22 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,67 @@
import { Command } from 'commander';
import { uploadSourcemaps } from './commands/sourcemaps/upload.js';

const program = new Command()
.name('logtide')
.description('LogTide CLI')
.version('0.1.0');
/**
* Normalize camelCase flag aliases to their canonical kebab-case form, so that
* documented invocations using `--apiKey` / `--apiUrl` work alongside the
* canonical `--api-key` / `--api-url`.
*/
export function normalizeArgv(argv: string[]): string[] {
const aliases: Record<string, string> = {
'--apiKey': '--api-key',
'--apiUrl': '--api-url',
};
return argv.map((arg) => {
const eq = arg.indexOf('=');
if (eq !== -1) {
const flag = arg.slice(0, eq);
return (aliases[flag] ?? flag) + arg.slice(eq);
}
return aliases[arg] ?? arg;
});
}

/** Build the `logtide` CLI program. */
export function buildProgram(): Command {
const program = new Command()
.name('logtide')
.description('LogTide CLI')
.version('0.1.0');

const sourcemaps = program
.command('sourcemaps')
.description('Manage source maps');
const sourcemaps = program
.command('sourcemaps')
.description('Manage source maps');

sourcemaps
.command('upload <directory>')
.description('Upload source map files to LogTide')
.requiredOption('--release <version>', 'Release version (e.g., 1.2.3)')
.option('--api-key <key>', 'LogTide API key (or set LOGTIDE_API_KEY)', process.env.LOGTIDE_API_KEY)
.option('--api-url <url>', 'LogTide API URL (or set LOGTIDE_API_URL)', process.env.LOGTIDE_API_URL ?? 'https://api.logtide.dev')
.option('--concurrency <n>', 'Parallel uploads', '5')
.action(async (directory: string, opts: any) => {
await uploadSourcemaps(directory, {
release: opts.release,
apiKey: opts.apiKey,
apiUrl: opts.apiUrl,
concurrency: parseInt(opts.concurrency, 10),
sourcemaps
.command('upload [directory]')
.description('Upload source map files to LogTide')
.option('--path <directory>', 'Directory containing source maps (alternative to the positional argument)')
.requiredOption('--release <version>', 'Release version (e.g., 1.2.3)')
.option('--api-key <key>', 'LogTide API key (or set LOGTIDE_API_KEY)', process.env.LOGTIDE_API_KEY)
.option('--api-url <url>', 'LogTide API URL (or set LOGTIDE_API_URL)', process.env.LOGTIDE_API_URL ?? 'https://api.logtide.dev')
.option('--concurrency <n>', 'Parallel uploads', '5')
.action(async (
directory: string | undefined,
opts: { path?: string; release: string; apiKey?: string; apiUrl: string; concurrency: string },
) => {
const dir = directory ?? opts.path;
if (!dir) {
console.error('Error: provide a source maps directory (positional argument or --path <directory>)');
process.exit(1);
return;
}
await uploadSourcemaps(dir, {
release: opts.release,
apiKey: opts.apiKey ?? '',
apiUrl: opts.apiUrl,
concurrency: parseInt(opts.concurrency, 10),
});
});
});

program.parseAsync(process.argv);
return program;
}

// Auto-run when executed as the CLI binary (skipped under the test runner).
if (!process.env.VITEST) {
buildProgram().parseAsync(normalizeArgv(process.argv));
}
62 changes: 62 additions & 0 deletions packages/cli/tests/cli.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';

vi.mock('../src/commands/sourcemaps/upload.js', () => ({
uploadSourcemaps: vi.fn().mockResolvedValue(undefined),
}));

import { uploadSourcemaps } from '../src/commands/sourcemaps/upload.js';
import { buildProgram, normalizeArgv } from '../src/index.js';

const run = (args: string[]) =>
buildProgram().parseAsync(['node', 'logtide', ...normalizeArgv(args)]);

describe('normalizeArgv', () => {
it('maps camelCase flag aliases to canonical kebab-case', () => {
expect(normalizeArgv(['--apiKey', 'K', '--apiUrl', 'U'])).toEqual([
'--api-key', 'K', '--api-url', 'U',
]);
});

it('handles the --flag=value form', () => {
expect(normalizeArgv(['--apiKey=K'])).toEqual(['--api-key=K']);
});

it('leaves unrelated args untouched', () => {
expect(normalizeArgv(['upload', './dist', '--release', '1.0.0'])).toEqual([
'upload', './dist', '--release', '1.0.0',
]);
});
});

describe('sourcemaps upload command', () => {
beforeEach(() => {
vi.mocked(uploadSourcemaps).mockClear();
});

it('accepts the directory as a positional argument', async () => {
await run(['sourcemaps', 'upload', './dist', '--release', '1.0.0', '--api-key', 'K']);

expect(uploadSourcemaps).toHaveBeenCalledWith(
'./dist',
expect.objectContaining({ release: '1.0.0', apiKey: 'K' }),
);
});

it('accepts the directory via --path', async () => {
await run(['sourcemaps', 'upload', '--path', './dist', '--release', '1.0.0', '--api-key', 'K']);

expect(uploadSourcemaps).toHaveBeenCalledWith(
'./dist',
expect.objectContaining({ release: '1.0.0' }),
);
});

it('accepts the documented --apiKey alias', async () => {
await run(['sourcemaps', 'upload', '--path', './dist', '--release', '1.0.0', '--apiKey', 'SECRET']);

expect(uploadSourcemaps).toHaveBeenCalledWith(
'./dist',
expect.objectContaining({ apiKey: 'SECRET' }),
);
});
});
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@logtide/core",
"version": "0.10.0",
"version": "0.11.0",
"description": "Core client, hub, scope, transports, and utilities for the LogTide SDK",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/elysia/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@logtide/elysia",
"version": "0.10.0",
"version": "0.11.0",
"description": "LogTide SDK plugin for Elysia — request tracing and error capture via lifecycle hooks",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/express/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@logtide/express",
"version": "0.10.0",
"version": "0.11.0",
"description": "LogTide SDK middleware for Express — request tracing and error capture",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/fastify/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@logtide/fastify",
"version": "0.10.0",
"version": "0.11.0",
"description": "LogTide SDK plugin for Fastify — request tracing and error capture",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/hono/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@logtide/hono",
"version": "0.10.0",
"version": "0.11.0",
"description": "LogTide SDK middleware for Hono — request tracing and error capture",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@logtide/nextjs",
"version": "0.10.0",
"version": "0.11.0",
"description": "LogTide SDK integration for Next.js — auto error capture, request tracing, and performance spans",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { initLogtide as initBrowserLogtide, type BrowserClientOptions } from '@logtide/browser';

export { LogtideErrorBoundary } from './error-boundary';
export { LogtideErrorBoundary } from '@logtide/browser';
export { trackNavigation } from './navigation';

/**
Expand Down
6 changes: 4 additions & 2 deletions packages/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,8 @@ Auto-log all HTTP requests and responses.

```typescript
import express from 'express';
import { LogTideClient, logTideMiddleware } from '@logtide/sdk-node';
import { LogTideClient } from '@logtide/sdk-node';
import { logTideMiddleware } from '@logtide/sdk-node/middleware';

const app = express();
const logger = new LogTideClient({
Expand Down Expand Up @@ -368,7 +369,8 @@ app.listen(3000);

```typescript
import Fastify from 'fastify';
import { LogTideClient, logTideFastifyPlugin } from '@logtide/sdk-node';
import { LogTideClient } from '@logtide/sdk-node';
import { logTideFastifyPlugin } from '@logtide/sdk-node/middleware';

const fastify = Fastify();
const logger = new LogTideClient({
Expand Down
2 changes: 1 addition & 1 deletion packages/node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@logtide/sdk-node",
"version": "0.10.0",
"version": "0.11.0",
"description": "Official Node.js SDK for LogTide (logtide.dev) - Self-hosted log management with advanced features: retry logic, circuit breaker, query API, live streaming, and middleware support",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/nuxt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ All options are set in `nuxt.config.ts` under the `logtide` key:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `dsn` | `string` | **required** | DSN string: `https://lp_KEY@host/PROJECT` |
| `service` | `string` | `'nuxt-app'` | Service name for log attribution |
| `service` | `string` | `'nuxt'` | Service name for log attribution |
| `environment` | `string` | — | Environment (e.g. `production`, `staging`) |
| `release` | `string` | — | Release / version identifier |
| `debug` | `boolean` | `false` | Enable debug logging |
Expand Down
Loading
Loading