diff --git a/packages/core/lib/es-utils/reduce.d.ts b/packages/core/lib/es-utils/reduce.d.ts index 576384d933..4a468b0a72 100644 --- a/packages/core/lib/es-utils/reduce.d.ts +++ b/packages/core/lib/es-utils/reduce.d.ts @@ -1 +1 @@ -export default function reduce(arr: T[], fn: (accum: any, item: T) => any, accum: any): any +export default function reduce(arr: T[], fn: (accum: any, item: T, index: number, arr: T[]) => any, accum: any): any diff --git a/packages/plugin-console-breadcrumbs/console-breadcrumbs.js b/packages/plugin-console-breadcrumbs/console-breadcrumbs.js deleted file mode 100644 index f009c48247..0000000000 --- a/packages/plugin-console-breadcrumbs/console-breadcrumbs.js +++ /dev/null @@ -1,47 +0,0 @@ -const map = require('@bugsnag/core/lib/es-utils/map') -const reduce = require('@bugsnag/core/lib/es-utils/reduce') -const filter = require('@bugsnag/core/lib/es-utils/filter') - -/* - * Leaves breadcrumbs when console log methods are called - */ -exports.load = (client) => { - const isDev = /^(local-)?dev(elopment)?$/.test(client._config.releaseStage) - - if (isDev || !client._isBreadcrumbTypeEnabled('log')) return - - map(CONSOLE_LOG_METHODS, method => { - const original = console[method] - console[method] = (...args) => { - client.leaveBreadcrumb('Console output', reduce(args, (accum, arg, i) => { - // do the best/simplest stringification of each argument - let stringified = '[Unknown value]' - // this may fail if the input is: - // - an object whose [[Prototype]] is null (no toString) - // - an object with a broken toString or @@toPrimitive implementation - try { stringified = String(arg) } catch (e) {} - // if it stringifies to [object Object] attempt to JSON stringify - if (stringified === '[object Object]') { - // catch stringify errors and fallback to [object Object] - try { stringified = JSON.stringify(arg) } catch (e) {} - } - accum[`[${i}]`] = stringified - return accum - }, { - severity: method.indexOf('group') === 0 ? 'log' : method - }), 'log') - original.apply(console, args) - } - console[method]._restore = () => { console[method] = original } - }) -} - -if (process.env.NODE_ENV !== 'production') { - exports.destroy = () => CONSOLE_LOG_METHODS.forEach(method => { - if (typeof console[method]._restore === 'function') console[method]._restore() - }) -} - -const CONSOLE_LOG_METHODS = filter(['log', 'debug', 'info', 'warn', 'error'], method => - typeof console !== 'undefined' && typeof console[method] === 'function' -) diff --git a/packages/plugin-console-breadcrumbs/package.json b/packages/plugin-console-breadcrumbs/package.json index d8f6ad0965..41d5c4e47c 100644 --- a/packages/plugin-console-breadcrumbs/package.json +++ b/packages/plugin-console-breadcrumbs/package.json @@ -1,7 +1,15 @@ { "name": "@bugsnag/plugin-console-breadcrumbs", "version": "8.1.1", - "main": "console-breadcrumbs.js", + "main": "dist/console-breadcrumbs.js", + "types": "dist/types/console-breadcrumbs.d.ts", + "exports": { + ".": { + "types": "./dist/types/console-breadcrumbs.d.ts", + "default": "./dist/console-breadcrumbs.js", + "import": "./dist/console-breadcrumbs.mjs" + } + }, "description": "@bugsnag/js plugin to record console log method calls as breadcrumbs", "homepage": "https://www.bugsnag.com/", "repository": { @@ -12,7 +20,7 @@ "access": "public" }, "files": [ - "*.js" + "dist" ], "author": "Bugsnag", "license": "MIT", @@ -21,5 +29,10 @@ }, "peerDependencies": { "@bugsnag/core": "^8.0.0" + }, + "scripts": { + "build": "npm run build:npm", + "build:npm": "rollup --config rollup.config.npm.mjs", + "clean": "rm -rf dist/*" } } diff --git a/packages/plugin-console-breadcrumbs/rollup.config.npm.mjs b/packages/plugin-console-breadcrumbs/rollup.config.npm.mjs new file mode 100644 index 0000000000..6cc0db1836 --- /dev/null +++ b/packages/plugin-console-breadcrumbs/rollup.config.npm.mjs @@ -0,0 +1,6 @@ +import createRollupConfig from "../../.rollup/index.mjs"; + +export default createRollupConfig({ + input: "src/console-breadcrumbs.ts", + external: ["@bugsnag/core/lib/es-utils/filter", "@bugsnag/core/lib/es-utils/map", "@bugsnag/core/lib/es-utils/reduce"] +}); diff --git a/packages/plugin-console-breadcrumbs/src/console-breadcrumbs.ts b/packages/plugin-console-breadcrumbs/src/console-breadcrumbs.ts new file mode 100644 index 0000000000..15b2eb14f9 --- /dev/null +++ b/packages/plugin-console-breadcrumbs/src/console-breadcrumbs.ts @@ -0,0 +1,84 @@ +import { Client, Config, Plugin } from '@bugsnag/core' +import filter from '@bugsnag/core/lib/es-utils/filter' +import map from '@bugsnag/core/lib/es-utils/map' +import reduce from '@bugsnag/core/lib/es-utils/reduce' + +type ConsoleMethod = 'log' | 'debug' | 'info' | 'warn' | 'error' + +interface ClientWithInternals extends Client { + _config: Required + _isBreadcrumbTypeEnabled: (type: ConsoleMethod) => boolean +} + +type ConsoleWithRestore = Console & { + [key in ConsoleMethod]: typeof console[ConsoleMethod] & { _restore: () => void } +} + +/* + * Leaves breadcrumbs when console log methods are called + */ +const plugin: Plugin = { + load: (client) => { + const isDev = /^(local-)?dev(elopment)?$/.test((client as ClientWithInternals)._config.releaseStage) + + if (isDev || !(client as ClientWithInternals)._isBreadcrumbTypeEnabled('log')) return + + map(CONSOLE_LOG_METHODS, method => { + const original = console[method] + console[method] = (...args: any) => { + client.leaveBreadcrumb( + 'Console output', + reduce( + args, + (accum, arg, i) => { + // do the best/simplest stringification of each argument + let stringified = '[Unknown value]' + // this may fail if the input is: + // - an object whose [[Prototype]] is null (no toString) + // - an object with a broken toString or @@toPrimitive implementation + try { + stringified = String(arg) + } catch (e) {} + // if it stringifies to [object Object] attempt to JSON stringify + if (stringified === '[object Object]') { + // catch stringify errors and fallback to [object Object] + try { + stringified = JSON.stringify(arg) + } catch (e) {} + } + accum[`[${i}]`] = stringified + return accum + }, + { + severity: method.indexOf('group') === 0 ? 'log' : method + } + ), + 'log' + ) + original.apply(console, args) + } + (console as ConsoleWithRestore)[method]._restore = () => { + console[method] = original + } + }) + } +} + +if (process.env.NODE_ENV !== 'production') { + plugin.destroy = () => { + const consoleWithRestore = console as ConsoleWithRestore + CONSOLE_LOG_METHODS.forEach(method => { + if (typeof consoleWithRestore[method]._restore === 'function') { + consoleWithRestore[method]._restore() + } + }) + } +} + +const CONSOLE_LOG_METHODS: ConsoleMethod[] = filter( + ['log', 'debug', 'info', 'warn', 'error'], + method => + typeof console !== 'undefined' && typeof console[method] === 'function' +) + +export default plugin diff --git a/packages/plugin-console-breadcrumbs/test/console-breadcrumbs.test.ts b/packages/plugin-console-breadcrumbs/test/console-breadcrumbs.test.ts index a6f5799b13..46ca7a8f78 100644 --- a/packages/plugin-console-breadcrumbs/test/console-breadcrumbs.test.ts +++ b/packages/plugin-console-breadcrumbs/test/console-breadcrumbs.test.ts @@ -1,4 +1,4 @@ -import plugin from '../' +import plugin from '../src/console-breadcrumbs' import Client from '@bugsnag/core/client' @@ -27,7 +27,7 @@ describe('plugin: console breadcrumbs', () => { expect(c._breadcrumbs[2].metadata['[0]']).toBe('{"foo":[1,2,3,"four"]}') expect(c._breadcrumbs[2].metadata['[1]']).toBe('{"pets":{"cat":"scratcher","dog":"pupper","rabbit":"sniffer"}}') // undo the global side effects of wrapping console.* for the rest of the tests - plugin.destroy() + plugin.destroy?.() }) it('should not throw when an object without toString is logged', () => { @@ -36,48 +36,48 @@ describe('plugin: console breadcrumbs', () => { expect(c._breadcrumbs.length).toBe(1) expect(c._breadcrumbs[0].message).toBe('Console output') expect(c._breadcrumbs[0].metadata['[0]']).toBe('[Unknown value]') - plugin.destroy() + plugin.destroy?.() }) it('should not be enabled when enabledBreadcrumbTypes=[]', () => { const c = new Client({ apiKey: 'aaaa-aaaa-aaaa-aaaa', enabledBreadcrumbTypes: [], plugins: [plugin] }) console.log(123) expect(c._breadcrumbs.length).toBe(0) - plugin.destroy() + plugin.destroy?.() }) it('should be enabled when enabledBreadcrumbTypes=null', () => { const c = new Client({ apiKey: 'aaaa-aaaa-aaaa-aaaa', enabledBreadcrumbTypes: null, plugins: [plugin] }) console.log(123) expect(c._breadcrumbs).toHaveLength(1) - plugin.destroy() + plugin.destroy?.() }) it('should be enabled when enabledBreadcrumbTypes=["log"]', () => { const c = new Client({ apiKey: 'aaaa-aaaa-aaaa-aaaa', enabledBreadcrumbTypes: ['log'], plugins: [plugin] }) console.log(123) expect(c._breadcrumbs.length).toBe(1) - plugin.destroy() + plugin.destroy?.() }) it('should be not enabled by default when releaseStage=development', () => { const c = new Client({ apiKey: 'aaaa-aaaa-aaaa-aaaa', releaseStage: 'development', plugins: [plugin] }) console.log(123) expect(c._breadcrumbs.length).toBe(0) - plugin.destroy() + plugin.destroy?.() }) it('should be not enabled by default when releaseStage=dev', () => { const c = new Client({ apiKey: 'aaaa-aaaa-aaaa-aaaa', releaseStage: 'dev', plugins: [plugin] }) console.log(123) expect(c._breadcrumbs.length).toBe(0) - plugin.destroy() + plugin.destroy?.() }) it('should be not enabled by default when releaseStage=local-dev', () => { const c = new Client({ apiKey: 'aaaa-aaaa-aaaa-aaaa', releaseStage: 'local-dev', plugins: [plugin] }) console.log(123) expect(c._breadcrumbs.length).toBe(0) - plugin.destroy() + plugin.destroy?.() }) }) diff --git a/packages/plugin-console-breadcrumbs/tsconfig.json b/packages/plugin-console-breadcrumbs/tsconfig.json new file mode 100644 index 0000000000..4fe20b6abd --- /dev/null +++ b/packages/plugin-console-breadcrumbs/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*.ts"], + "compilerOptions": { + "target": "ES2020", + "types": ["node"] + } +} diff --git a/tsconfig.json b/tsconfig.json index 25fad7868e..3ba120f783 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -88,7 +88,6 @@ "packages/plugin-node-device", "packages/plugin-node-surrounding-code", "packages/plugin-node-uncaught-exception", - "packages/plugin-console-breadcrumbs", "packages/plugin-browser-session", "packages/browser" ],