Skip to content

Commit

Permalink
Merge pull request #2296 from bugsnag/plat-12947
Browse files Browse the repository at this point in the history
core: move to src, add TypeScript and rollup
  • Loading branch information
djskinner authored Feb 11, 2025
2 parents c8b341d + ae50436 commit bb285ca
Show file tree
Hide file tree
Showing 87 changed files with 668 additions and 384 deletions.
5 changes: 4 additions & 1 deletion bin/local-test-util
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ async function buildNotifiers (notifier) {
async function packNotifiers (notifier) {
const notifiers = notifier
? [ notifier ]
: [ 'js', 'browser', 'node', 'web-worker', 'plugin-angular', 'plugin-react', 'plugin-vue' ]
: [ 'core', 'js', 'browser', 'node', 'web-worker', 'plugin-angular', 'plugin-react', 'plugin-vue' ]
for (const n of notifiers) {
let packageLocation = `packages/${n}/`
if (n === 'plugin-angular') packageLocation += 'dist/'
Expand All @@ -138,9 +138,11 @@ async function installNotifiers (notifier) {
`--no-save`,
].concat(notifier
? [
`../../../../bugsnag-core-${require(`../packages/core/package.json`).version}.tgz`,
`../../../../bugsnag-${notifier}-${require(`../packages/${notifier}/package.json`).version}.tgz`
]
: [
`../../../../bugsnag-core-${require('../packages/core/package.json').version}.tgz`,
`../../../../bugsnag-browser-${require('../packages/browser/package.json').version}.tgz`,
`../../../../bugsnag-web-worker-${require('../packages/web-worker/package.json').version}.tgz`,
`../../../../bugsnag-plugin-react-${require('../packages/plugin-react/package.json').version}.tgz`,
Expand All @@ -158,6 +160,7 @@ async function installNgNotifier (notifier, version = '12') {
`install`,
`--no-package-lock`,
`--no-save`,
`../../../../../../bugsnag-core-${require('../packages/core/package.json').version}.tgz`,
`../../../../../../bugsnag-browser-${require('../packages/browser/package.json').version}.tgz`,
`../../../../../../bugsnag-js-${require('../packages/js/package.json').version}.tgz`,
`../../../../../../bugsnag-node-${require('../packages/node/package.json').version}.tgz`,
Expand Down
2 changes: 2 additions & 0 deletions config/electron-jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
projects: [
{
resolver: '<rootDir>/jest/node-exports-resolver',
setupFilesAfterEnv: ['<rootDir>/test/electron/setup.ts'],
clearMocks: true,
modulePathIgnorePatterns: ['.verdaccio', 'fixtures', 'examples'],
Expand All @@ -9,6 +10,7 @@ module.exports = {
testMatch: ['**/test/**/*.test-main.ts']
},
{
resolver: '<rootDir>/jest/node-exports-resolver',
setupFilesAfterEnv: ['<rootDir>/test/electron/setup.ts'],
clearMocks: true,
modulePathIgnorePatterns: ['.verdaccio', 'fixtures', 'examples'],
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const testsForPackage = (packageName) => `<rootDir>/packages/${packageName}/**/*.test.[jt]s?(x)`

const project = (displayName, packageNames, config = {}) => ({
resolver: '<rootDir>/jest/node-exports-resolver',
roots: ['<rootDir>/packages'],
displayName,
testMatch: packageNames.map(testsForPackage),
Expand Down
183 changes: 183 additions & 0 deletions jest/node-exports-resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
const fs = require('fs')
const path = require('path')
const packageUp = require('pkg-up')

const defaultConditions = ['require', 'node', 'default']

function findMainPackageJson (entryPath, packageName) {
entryPath = entryPath.replace(/\//g, path.sep)

let directoryName = path.dirname(entryPath)

let suspect = packageUp.sync({ cwd: directoryName })
if (fs.existsSync(suspect)) {
return JSON.parse(fs.readFileSync(suspect).toString())
}

while (directoryName && !directoryName.endsWith(packageName)) {
const parentDirectoryName = path.resolve(directoryName, '..')

if (parentDirectoryName === directoryName) break

directoryName = parentDirectoryName
}

suspect = path.resolve(directoryName, 'package.json')
if (fs.existsSync(suspect)) {
return JSON.parse(fs.readFileSync(suspect).toString())
}

return null
}

function getSelfReferencePath (packageName) {
let parentDirectoryName = __dirname
let directoryName

while (directoryName !== parentDirectoryName) {
directoryName = parentDirectoryName

try {
const { name } = require(path.resolve(directoryName, 'package.json'))

if (name === packageName) return directoryName
} catch {}

parentDirectoryName = path.resolve(directoryName, '..')
}
}

function getPackageJson (packageName) {
// Require `package.json` from the package, both from exported `exports` field
// in ESM packages, or directly from the file itself in CommonJS packages.
try {
return require(`${packageName}/package.json`)
} catch (requireError) {
if (requireError.code === 'MODULE_NOT_FOUND') {
throw requireError
}
if (requireError.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
return console.error(
`Unexpected error while requiring ${packageName}:`, requireError
)
}
}

// modules's `package.json` does not provide the "./package.json" path at it's
// "exports" field. Get package level export or main field and try to resolve
// the package.json from it.
try {
const requestPath = require.resolve(packageName)

return requestPath && findMainPackageJson(requestPath, packageName)
} catch (resolveError) {
if (resolveError.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
console.log(
`Unexpected error while performing require.resolve(${packageName}):`
)

return console.error(resolveError)
}
}

// modules's `package.json` does not provide a package level export nor main
// field. Try to find the package manually from `node_modules` folder.
const suspect = path.resolve(__dirname, '..', packageName, 'package.json')
if (fs.existsSync(suspect)) {
return JSON.parse(fs.readFileSync(suspect).toString())
}

console.warn(
'Could not retrieve package.json neither through require (package.json ' +
'itself is not within "exports" field), nor through require.resolve ' +
'(package.json does not specify "main" field) - falling back to default ' +
'resolver logic'
)
}

module.exports = (request, options) => {
const { conditions = defaultConditions, defaultResolver } = options

// NOTE: jest-sequencer is a special prefixed jest request
const isNodeModuleRequest =
!(
request.startsWith('.') ||
path.isAbsolute(request) ||
request.startsWith('jest-sequencer')
)

if (isNodeModuleRequest) {
const pkgPathParts = request.split('/')
const { length } = pkgPathParts

let packageName
let submoduleName

if (!request.startsWith('@')) {
packageName = pkgPathParts.shift()
submoduleName = length > 1 ? `./${pkgPathParts.join('/')}` : '.'
} else if (length >= 2) {
packageName = `${pkgPathParts.shift()}/${pkgPathParts.shift()}`
submoduleName = length > 2 ? `./${pkgPathParts.join('/')}` : '.'
}

if (packageName && submoduleName !== '.') {
const selfReferencePath = getSelfReferencePath(packageName)
if (selfReferencePath) packageName = selfReferencePath

const packageJson = getPackageJson(packageName)

if (!packageJson) {
console.error(`Failed to find package.json for ${packageName}`)
}

const { exports } = packageJson || {}
if (exports) {
let targetFilePath

if (typeof exports === 'string') { targetFilePath = exports } else if (Object.keys(exports).every((k) => k.startsWith('.'))) {
const [exportKey, exportValue] = Object.entries(exports)
.find(([k]) => {
if (k === submoduleName) return true
if (k.endsWith('*')) return submoduleName.startsWith(k.slice(0, -1))

return false
}) || []

if (typeof exportValue === 'string') {
targetFilePath = exportKey.endsWith('*')
? exportValue.replace(

Check failure

Code scanning / CodeQL

Incomplete string escaping or encoding High

This replaces only the first occurrence of /\*/.
/\*/, submoduleName.slice(exportKey.length - 1)
)
: exportValue
} else if (
conditions && exportValue != null && typeof exportValue === 'object'
) {
function resolveExport (exportValue, prevKeys) {
for (const [key, value] of Object.entries(exportValue)) {
// Duplicated nested conditions are undefined behaviour (and
// probably a format error or spec loop-hole), abort and
// delegate to Jest default resolver
if (prevKeys.includes(key)) return

if (!conditions.includes(key)) continue

if (typeof value === 'string') return value

return resolveExport(value, prevKeys.concat(key))
}
}

targetFilePath = resolveExport(exportValue, [])
}
}

if (targetFilePath) {
request = targetFilePath.replace('./', `${packageName}/`)
}
}
}
}

return defaultResolver(request, options)
}
Loading

0 comments on commit bb285ca

Please sign in to comment.