Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ignorePaths option #1059

Merged
merged 6 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
7 changes: 7 additions & 0 deletions packages/rollup-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ These plugin parameters correspond to the Honeybadger [Source Map Upload API](ht
<dd>This package implements fetch retry functionality via the <a href="https://github.com/vercel/fetch-retry">fetch-retry</a> package. Retrying helps fix issues like `ECONNRESET` and `SOCKETTIMEOUT` errors.
</dd>

<dt><code>ignorePaths</code> (optional &mdash; default: [])</dt>
<dd>An array of paths to ignore when uploading sourcemaps. Regex is also supported.
</dd>

<dt><code>deployEndpoint</code> (optional &mdash; default: "https://api.honeybadger.io/v1/deploys")</dt>
subzero10 marked this conversation as resolved.
Show resolved Hide resolved
<dd>Where to send deployment notifications.</dd>

<dt><code>deploy</code> (optional &mdash; default: false)</dt>
<dd>
Configuration for <a href="https://docs.honeybadger.io/api/reporting-deployments/">deployment notifications</a>. To disable deployment notifications, ignore this option. To enable deployment notifications, set this to <code>true</code>, or to an object containing any of the fields below. Your deploy's <code>revision</code> will be set to the same value as for your sourcemaps (see above).
Expand Down
8 changes: 4 additions & 4 deletions packages/rollup-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ export default function honeybadgerRollupPlugin(
const hbOptions = cleanOptions(options)

return {
name: 'honeybadger',
name: 'honeybadger',
writeBundle: async (
outputOptions: NormalizedOutputOptions,
outputOptions: NormalizedOutputOptions,
bundle: OutputBundle
) => {
if (isNonProdEnv()) {
Expand All @@ -22,12 +22,12 @@ export default function honeybadgerRollupPlugin(
return
}

const sourcemapData = extractSourcemapDataFromBundle(outputOptions, bundle)
const sourcemapData = extractSourcemapDataFromBundle(outputOptions, bundle, hbOptions.ignorePaths)
await uploadSourcemaps(sourcemapData, hbOptions)

if (hbOptions.deploy) {
await sendDeployNotification(hbOptions)
}
}
}
}
}
17 changes: 13 additions & 4 deletions packages/rollup-plugin/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const DEFAULT_REVISION = 'main'
export const DEFAULT_SILENT = false
export const DEFAULT_DEPLOY = false
export const DEPLOY_ENDPOINT = 'https://api.honeybadger.io/v1/deploys'
export const IGNORE_PATHS = []

/******************************
* Everything in this file is designed to be shared with the webpack plugin
Expand All @@ -19,12 +20,13 @@ const required = [
]

const defaultOptions = {
endpoint: DEFAULT_ENDPOINT,
retries: DEFAULT_RETRIES,
revision: DEFAULT_REVISION,
silent: DEFAULT_SILENT,
endpoint: DEFAULT_ENDPOINT,
retries: DEFAULT_RETRIES,
revision: DEFAULT_REVISION,
silent: DEFAULT_SILENT,
deploy: DEFAULT_DEPLOY,
deployEndpoint: DEPLOY_ENDPOINT,
ignorePaths: IGNORE_PATHS,
}

export function cleanOptions(
Expand All @@ -36,13 +38,20 @@ export function cleanOptions(
throw new Error(`${field} is required`)
}
})

// Validate ignorePaths
if (options.ignorePaths && !Array.isArray(options.ignorePaths)) {
throw new Error('ignorePaths must be an array')
}

// Don't allow excessive retries
if (options.retries && options.retries > MAX_RETRIES) {
if (!options.silent) {
console.warn(`Using max retries: ${MAX_RETRIES}`)
}
options.retries = MAX_RETRIES
}

// Merge in our defaults
return { ...defaultOptions, ...options }
}
47 changes: 35 additions & 12 deletions packages/rollup-plugin/src/rollupUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,53 @@ import type { SourcemapInfo } from './types'
/**
* Extracts the data we need for sourcemap upload from the bundle
*/
export function extractSourcemapDataFromBundle (
outputOptions: NormalizedOutputOptions,
bundle: OutputBundle
export function extractSourcemapDataFromBundle (
outputOptions: NormalizedOutputOptions,
bundle: OutputBundle,
ignorePaths: Array<string | RegExp> = []
): SourcemapInfo[] {
const sourceMaps = Object.values(bundle).filter(isSourcemap)

return sourceMaps.map(sourcemap => {
return formatSourcemapData(outputOptions, sourcemap)
})
}).filter((sourcemap) => isNotIgnored(sourcemap, ignorePaths))
}

function isSourcemap(file: OutputAsset | OutputChunk): file is OutputAsset {
return file.type === 'asset' && file.fileName.endsWith('.js.map')
if (file.type !== 'asset' || !file.fileName.endsWith('.js.map') || !file.source) {
return false
}

const source = typeof file.source === 'string' ? file.source : file.source.toString()
const json = JSON.parse(source)

return !!json.sourcesContent && json.sourcesContent.length > 0
}

function isNotIgnored(sourceMapInfo: SourcemapInfo, ignorePaths: Array<string | RegExp>) {
for (const ignorePath of ignorePaths) {
let regex = ignorePath
if (!(regex instanceof RegExp)) {
regex = new RegExp(regex)
subzero10 marked this conversation as resolved.
Show resolved Hide resolved
}

if (regex.test(sourceMapInfo.jsFilePath)) {
return false
}
}

return true
}

function formatSourcemapData(
outputOptions: NormalizedOutputOptions,
outputOptions: NormalizedOutputOptions,
sourcemap: OutputAsset): SourcemapInfo {
// This fileName could include a path like 'subfolder/foo.js.map'
const sourcemapFilename = sourcemap.fileName
const sourcemapFilePath = path.resolve(outputOptions.dir || '', sourcemapFilename)
// It should be safe to assume that rollup will name the map with
// the same name as the js file... however we can pull the file name
// from the sourcemap source just in case.
// It should be safe to assume that rollup will name the map with
// the same name as the js file... however we can pull the file name
// from the sourcemap source just in case.
let jsFilename: string
if (typeof sourcemap.source === 'string') {
const { file } = JSON.parse(sourcemap.source)
Expand All @@ -47,9 +70,9 @@ function formatSourcemapData(
/**
* Determines if we are in a non-production environment
* Note that in Vite setups, NODE_ENV should definitely be available
* In Rollup without Vite, it may or may not be available,
* In Rollup without Vite, it may or may not be available,
* so if it's missing we'll assume prod
*/
export function isNonProdEnv(): boolean {
return !!process.env.NODE_ENV && process.env.NODE_ENV !== 'production'
}
}
5 changes: 3 additions & 2 deletions packages/rollup-plugin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export interface HbPluginOptions {
revision: string;
silent: boolean;
deployEndpoint: string;
deploy: boolean | Deploy
deploy: boolean | Deploy;
ignorePaths: Array<string | RegExp>;
}

export interface Deploy {
Expand All @@ -29,4 +30,4 @@ export interface SourcemapInfo {
sourcemapFilePath: string;
jsFilename: string;
jsFilePath: string;
}
}
13 changes: 10 additions & 3 deletions packages/rollup-plugin/test/fixtures/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const assets = {
fileName: 'index.js.map',
name: undefined,
source: '{"version":3,"file":"index.js","sources":["../src/index.js"],"sourcesContent":["import foo from \'./foo.js\';\\nimport bar from \'./subfolder/bar.js\'\\n\\nexport default function () {\\n console.log(foo)\\n console.log(bar)\\n}"],"names":[],"mappings":";;;;;AAGe,cAAQ,IAAI;AAC3B,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,EAAC;AAClB,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,EAAC;AAClB;;;;"}',
type: 'asset' as const,
type: 'asset' as const,
needsCodeReference: false
},
'subfolder/bar.js.map': {
Expand All @@ -126,8 +126,15 @@ const assets = {
source: '{"version":3,"file":"foo.js","sources":["../src/foo.js"],"sourcesContent":["export default \'hello world!\'"],"names":[],"mappings":";;AAAA,UAAe;;;;"}',
type: 'asset' as const,
needsCodeReference: false
},
'empty.js.map': {
fileName: 'empty.js.map',
name: undefined,
source: '{"version":3,"file":"empty.js","sources":[], "sourcesContent": [], "names":[],"mappings":""}',
type: 'asset' as const,
needsCodeReference: false
}
}
}


export default { ...chunks, ...assets }
export default { ...chunks, ...assets }
46 changes: 23 additions & 23 deletions packages/rollup-plugin/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { HbPluginOptions } from '../src/types'

describe('Index', () => {
let honeybadgerRollupPlugin:(opts: Partial<HbPluginOptions> & Pick<HbPluginOptions, 'apiKey' | 'assetsUrl'>) => Plugin
let cleanOptionsMock,
isNonProdEnvMock,
extractSourcemapDataFromBundleMock,
uploadSourcemapsMock,
let cleanOptionsMock,
isNonProdEnvMock,
extractSourcemapDataFromBundleMock,
uploadSourcemapsMock,
sendDeployNotificationMock
const options = { apiKey: 'test_key', assetsUrl: 'https://foo.bar' }

Expand All @@ -19,18 +19,18 @@ describe('Index', () => {
uploadSourcemapsMock = td.func()
sendDeployNotificationMock = td.func()

td.replace('../src/options', {
cleanOptions: cleanOptionsMock
td.replace('../src/options', {
cleanOptions: cleanOptionsMock
})
td.replace('../src/rollupUtils', {
extractSourcemapDataFromBundle: extractSourcemapDataFromBundleMock,
isNonProdEnv: isNonProdEnvMock
})
td.replace('../src/hbUtils', {
uploadSourcemaps: uploadSourcemapsMock,
uploadSourcemaps: uploadSourcemapsMock,
sendDeployNotification: sendDeployNotificationMock
})

const indexModule = await import('../src/index')
honeybadgerRollupPlugin = indexModule.default
})
Expand All @@ -44,7 +44,7 @@ describe('Index', () => {

td.verify(cleanOptionsMock(options))
expect(plugin.name).to.equal('honeybadger')
expect(plugin.writeBundle).to.be.a('function')
expect(plugin.writeBundle).to.be.a('function')
})

describe('writeBundle', () => {
Expand All @@ -55,13 +55,13 @@ describe('Index', () => {

it('should upload sourcemaps', async () => {
td.when(isNonProdEnvMock()).thenReturn(false)
td.when(extractSourcemapDataFromBundleMock(outputOptions, bundle)).thenReturn(sourcemapData)
td.when(extractSourcemapDataFromBundleMock(outputOptions, bundle, undefined)).thenReturn(sourcemapData)
td.when(cleanOptionsMock(options)).thenReturn(options)

const plugin = honeybadgerRollupPlugin(options)
//@ts-ignore
await plugin.writeBundle(outputOptions, bundle)

td.verify(uploadSourcemapsMock(sourcemapData, options))
})

Expand All @@ -70,11 +70,11 @@ describe('Index', () => {
td.when(isNonProdEnvMock()).thenReturn(false)
td.when(extractSourcemapDataFromBundleMock({ outputOptions, bundle })).thenReturn(sourcemapData)
td.when(cleanOptionsMock(deployTrueOpt)).thenReturn(deployTrueOpt)

const plugin = honeybadgerRollupPlugin(deployTrueOpt)
//@ts-ignore
await plugin.writeBundle(outputOptions, bundle)

td.verify(sendDeployNotificationMock(deployTrueOpt))
})

Expand All @@ -83,11 +83,11 @@ describe('Index', () => {
td.when(isNonProdEnvMock()).thenReturn(false)
td.when(extractSourcemapDataFromBundleMock({ outputOptions, bundle })).thenReturn(sourcemapData)
td.when(cleanOptionsMock(deployObjOpt)).thenReturn(deployObjOpt)

const plugin = honeybadgerRollupPlugin(deployObjOpt)
//@ts-ignore
await plugin.writeBundle(outputOptions, bundle)

td.verify(sendDeployNotificationMock(deployObjOpt))
})

Expand All @@ -96,29 +96,29 @@ describe('Index', () => {
td.when(isNonProdEnvMock()).thenReturn(false)
td.when(extractSourcemapDataFromBundleMock({ outputOptions, bundle })).thenReturn(sourcemapData)
td.when(cleanOptionsMock(deployFalseOpt)).thenReturn(deployFalseOpt)

const plugin = honeybadgerRollupPlugin(deployFalseOpt)
//@ts-ignore
await plugin.writeBundle(outputOptions, bundle)

// Verify not called
td.verify(sendDeployNotificationMock(), { times: 0, ignoreExtraArgs: true })
})

it('should do nothing in non-prod environments', async () => {
td.when(isNonProdEnvMock()).thenReturn(true)
td.when(cleanOptionsMock(options)).thenReturn(options)

const plugin = honeybadgerRollupPlugin(options)
//@ts-ignore
await plugin.writeBundle(outputOptions, bundle)

// Verify these were not called
td.verify(extractSourcemapDataFromBundleMock(), { times: 0, ignoreExtraArgs: true })
td.verify(uploadSourcemapsMock(), { times: 0, ignoreExtraArgs: true })
td.verify(sendDeployNotificationMock(), { times: 0, ignoreExtraArgs: true })
})
})

})

})
Loading