Skip to content

Add documentation #4

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

Open
wants to merge 9 commits into
base: next
Choose a base branch
from
Open
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ npm install --save-dev vite-plugin-bugsnag

## Usage

### Build reporter plugin

Reports your build to BugSnag. This plugin hooks into the `buildEnd` event once all output files have been generated by the Vite compiler. If anything causes the compilation to fail before this step, the build report will not be sent.

Please see our [documentation](https://docs.bugsnag.com/build-integrations/vite/#reporting-builds) for further details.

### Sourcemap uploader plugin

Upload your application's sourcemap(s) to BugSnag. When Vite is done producing output, this plugin detects sourcemaps for any output chunks and uploads them to BugSnag.

Please see our [documentation](https://docs.bugsnag.com/build-integrations/vite/#uploading-source-maps) for further details.

## Support

- [Search open and closed issues](https://github.com/bugsnag/vite-plugin-bugsnag/issues?q=is%3Aissue) issues for similar problems
Expand Down
35 changes: 8 additions & 27 deletions src/build-reporter-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import Bugsnag from '@bugsnag/cli'
import type { Plugin } from 'vite'
import type { BuildReporterConfig } from './config'
import getLogLevel from './get-log-level'

const LOG_PREFIX = '[BugsnagBuildReporterPlugin]'

export function BugsnagBuildReporterPlugin (configOptions: BugsnagBuildReporterPluginOptions): Plugin {
export function BugsnagBuildReporterPlugin (configOptions: BuildReporterConfig): Plugin {
const buildOptions = getBuildOptions(configOptions)
const target = configOptions.path || process.cwd()

return {
name: 'vite-plugin-bugsnag-build-reporter',
Expand All @@ -18,7 +21,7 @@ export function BugsnagBuildReporterPlugin (configOptions: BugsnagBuildReporterP

logger.info(`${LOG_PREFIX} creating build for version "${buildOptions.versionName}" using the bugsnag-cli`)

Bugsnag.CreateBuild(buildOptions, process.cwd())
Bugsnag.CreateBuild(buildOptions, target)
.then((output) => {
output.split('\n').forEach((line) => {
logger.info(`${LOG_PREFIX} ${line}`)
Expand All @@ -33,10 +36,10 @@ export function BugsnagBuildReporterPlugin (configOptions: BugsnagBuildReporterP
}
}

function getBuildOptions (configOptions: BugsnagBuildReporterPluginOptions) {
function getBuildOptions (configOptions: BuildReporterConfig) {
const buildOptions = {
apiKey: configOptions.apiKey,
versionName: configOptions.appVersion,
versionName: configOptions.appVersion || process.env.npm_package_version,
autoAssignRelease: configOptions.autoAssignRelease,
builderName: configOptions.builderName,
metadata: configOptions.metadata,
Expand All @@ -45,7 +48,7 @@ function getBuildOptions (configOptions: BugsnagBuildReporterPluginOptions) {
repository: configOptions.sourceControl?.repository,
revision: configOptions.sourceControl?.revision,
buildApiRootUrl: configOptions.endpoint,
logLevel: configOptions.logLevel
logLevel: getLogLevel(configOptions.logLevel)
}

for (const [key, value] of Object.entries(buildOptions)) {
Expand All @@ -56,25 +59,3 @@ function getBuildOptions (configOptions: BugsnagBuildReporterPluginOptions) {

return buildOptions
}

export interface BugsnagBuildReporterPluginOptions {
apiKey: string
appVersion: string // versionName
endpoint?: string // buildApiRootUrl
autoAssignRelease?: boolean
builderName?: string
metadata?: object
releaseStage?: string
sourceControl?: {
provider?: string
repository?: string
revision?: string
}
logLevel?: string
logger?: {
debug: (message: string) => void
info: (message: string) => void
warn: (message: string) => void
error: (message: string) => void
}
}
34 changes: 34 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
interface Logger {
debug: (message: string) => void
info: (message: string) => void
warn: (message: string) => void
error: (message: string) => void
}

export interface BaseConfig {
apiKey: string
appVersion?: string
path?: string
endpoint?: string
logLevel?: 'debug' | 'info' | 'warn' | 'error'
logger?: Logger
}

export interface BuildReporterConfig extends BaseConfig {
autoAssignRelease?: boolean
builderName?: string
metadata?: object
releaseStage?: string
sourceControl?: {
provider?: string
repository?: string
revision?: string
}
}

export interface SourceMapUploaderConfig extends BaseConfig {
base?: string // baseURL
codeBundleId?: string
projectRoot?: string
overwrite?: boolean
}
9 changes: 9 additions & 0 deletions src/get-log-level.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// re-map the log level option to match the bugsnag-cli log level
const getLogLevel = (logLevel: string | undefined) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joshedney I've added a helper to ensure the provided log level matches the level in the cli

if (logLevel === 'error') {
return 'fatal'
}
return logLevel
}

export default getLogLevel
80 changes: 30 additions & 50 deletions src/source-map-uploader-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,62 @@
import Bugsnag from '@bugsnag/cli'
import { join, resolve } from 'path'
import getLogLevel from './get-log-level'
import isValidUrl from './is-valid-url'

import type { Plugin } from 'vite'
import type { SourceMapUploaderConfig } from './config'

const LOG_PREFIX = '[BugsnagSourceMapUploaderPlugin]'

interface Logger {
debug: (message: string) => void
info: (message: string) => void
warn: (message: string) => void
error: (message: string) => void
}

export interface ConfigOptions {
apiKey: string
appVersion: string
base?: string
endpoint?: string
codeBundleId?: string
logger?: Logger
mode?: string
projectRoot?: string
overwrite?: boolean
logLevel?: 'info' | 'warn' | 'fatal' | 'debug'
}

export function BugsnagSourceMapUploaderPlugin (configOptions: ConfigOptions): Plugin {
export function BugsnagSourceMapUploaderPlugin (configOptions: SourceMapUploaderConfig): Plugin {
if (typeof configOptions.apiKey !== 'string' || configOptions.apiKey.length < 1) {
throw new Error(`${LOG_PREFIX} "apiKey" is required`)
}

const enableSourcemapUploads = configOptions.mode === 'production' || process.env.NODE_ENV === 'production'

return {
name: 'vite-plugin-bugsnag-source-map-uploader',
async writeBundle (options, bundle) {
const logger = configOptions.logger || this.environment.logger
const projectRoot = configOptions.projectRoot || this.environment.config.root
const outputDir = options.dir || projectRoot
const baseUrl = configOptions.base || this.environment.config.base
const versionName = configOptions.appVersion || process.env.npm_package_version || 'unknown'
const validBaseUrl = isValidUrl(baseUrl)
const target = configOptions.path || outputDir

if (enableSourcemapUploads) {
const uploads = []
for (const [, value] of Object.entries(bundle)) {
if (value.type === 'chunk' && !!value.sourcemapFileName) {
const bundle = resolve(outputDir, value.fileName)
const bundleUrl = validBaseUrl ? new URL(value.fileName, baseUrl).toString() : join(baseUrl, value.fileName)
const sourceMap = value.sourcemapFileName ? join(outputDir, value.sourcemapFileName) : undefined
const uploadOptions = getUploadOptions(bundle, bundleUrl, sourceMap, projectRoot, configOptions)
uploads.push(uploadOptions)
}
const uploads = []
for (const [, value] of Object.entries(bundle)) {
if (value.type === 'chunk' && !!value.sourcemapFileName) {
const bundle = resolve(outputDir, value.fileName)
const bundleUrl = validBaseUrl ? new URL(value.fileName, baseUrl).toString() : join(baseUrl, value.fileName)
const sourceMap = value.sourcemapFileName ? join(outputDir, value.sourcemapFileName) : undefined
const uploadOptions = getUploadOptions(bundle, bundleUrl, sourceMap, projectRoot, versionName, configOptions)
uploads.push(uploadOptions)
}
}

logger.info(`${LOG_PREFIX} uploading sourcemaps using the bugsnag-cli`)
logger.info(`${LOG_PREFIX} uploading sourcemaps using the bugsnag-cli`)

const tasks = uploads.map((uploadOptions) => {
return Bugsnag.Upload.Js(uploadOptions, outputDir)
.then((output) => {
output.split('\n').forEach((line) => {
logger.info(`${LOG_PREFIX} ${line}`)
})
const tasks = uploads.map((uploadOptions) => {
return Bugsnag.Upload.Js(uploadOptions, target)
.then((output) => {
output.split('\n').forEach((line) => {
logger.info(`${LOG_PREFIX} ${line}`)
})
.catch((err) => {
err.toString().split('\n').forEach((line: string) => {
logger.error(`${LOG_PREFIX} ${line}`)
})
})
.catch((err) => {
err.toString().split('\n').forEach((line: string) => {
logger.error(`${LOG_PREFIX} ${line}`)
})
})
})
})

await Promise.all(tasks)
}
await Promise.all(tasks)
}
}
}

function getUploadOptions (bundle: string, bundleUrl: string, sourceMap: string | undefined, projectRoot: string, configOptions: ConfigOptions) {
function getUploadOptions (bundle: string, bundleUrl: string, sourceMap: string | undefined, projectRoot: string, versionName: string, configOptions: SourceMapUploaderConfig) {
const uploadOptions = {
apiKey: configOptions.apiKey, // The BugSnag API key for the application.
bundle, // Path to the minified JavaScript file that the source map relates to.
Expand All @@ -85,9 +65,9 @@ function getUploadOptions (bundle: string, bundleUrl: string, sourceMap: string
projectRoot, // The path to strip from the beginning of source file names referenced in stacktraces on the BugSnag dashboard.
sourceMap, // Path to the source map file. This usually has the .min.js extension.
uploadApiRootUrl: configOptions.endpoint, // The upload server hostname, optionally containing port number.
versionName: configOptions.appVersion, // The version of the app that the source map applies to.
versionName, // The version of the app that the source map applies to.
overwrite: configOptions.overwrite, // Whether to ignore and overwrite existing uploads with same identifier, rather than failing if a matching file exists.
logLevel: configOptions.logLevel // Sets the level of logging to debug, info, warn or fatal.
logLevel: getLogLevel(configOptions.logLevel) // Sets the level of logging to debug, info, warn or fatal.
}

for (const [key, value] of Object.entries(uploadOptions)) {
Expand Down
16 changes: 7 additions & 9 deletions test/source-map-uploader-plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { describe, expect, test, vi } from 'vitest'
import { BugsnagSourceMapUploaderPlugin } from '../src/source-map-uploader-plugin'
import cleanBuildDir from './lib/clean-build-dir'

import { version } from '../package.json'

vi.mock('@bugsnag/cli', () => ({
default: {
Upload: {
Expand Down Expand Up @@ -44,8 +46,7 @@ describe('BugsnagSourceMapUploaderPlugin', () => {
const plugin = BugsnagSourceMapUploaderPlugin({
logger: mockLogger,
apiKey: 'test-api',
appVersion: '1.0.0',
mode: 'production'
appVersion: '1.0.0'
})

const fixturesPath = resolve(__dirname, 'fixtures/basic')
Expand Down Expand Up @@ -81,7 +82,7 @@ describe('BugsnagSourceMapUploaderPlugin', () => {
sourcemapUpload.mockClear()
})

test('should use the relative filepath for bundleUrl is base is not provided in config', async () => {
test('should use the relative filepath for bundleUrl if base is not provided in config', async () => {
const mockLogger = {
info: vi.fn(),
error: vi.fn(),
Expand All @@ -91,9 +92,7 @@ describe('BugsnagSourceMapUploaderPlugin', () => {

const plugin = BugsnagSourceMapUploaderPlugin({
logger: mockLogger,
apiKey: 'test-api',
appVersion: '1.0.0',
mode: 'production'
apiKey: 'test-api'
})

const fixturePath = resolve(__dirname, 'fixtures/basic')
Expand All @@ -120,7 +119,7 @@ describe('BugsnagSourceMapUploaderPlugin', () => {
bundle: bundlePath,
projectRoot: fixturePath,
sourceMap: sourceMapPath,
versionName: '1.0.0'
versionName: version
},
outputDir
)
Expand All @@ -139,8 +138,7 @@ describe('BugsnagSourceMapUploaderPlugin', () => {
const plugin = BugsnagSourceMapUploaderPlugin({
logger: mockLogger,
apiKey: 'test-api',
appVersion: '1.0.0',
mode: 'production'
appVersion: '1.0.0'
})

const fixturePath = resolve(__dirname, 'fixtures/basic')
Expand Down