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

[PLAT-11399] Bugsnag CLI iOS x React Native CLI #2073

Merged
merged 102 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from 96 commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
7405ee1
add updated commands for react native cli for ios
Jan 10, 2024
bf240fe
fix some liniting and remove unused functions
Jan 10, 2024
295b32b
slim pipeline down
Jan 10, 2024
2d2d43d
fix linting
Jan 10, 2024
5e0d655
update react-native cli test layout
Jan 11, 2024
7e9b033
[full ci]
Jan 11, 2024
a65f336
update rn cli tests
Jan 11, 2024
5dec30c
fix spelling
Jan 11, 2024
564ce42
fix spelling
Jan 11, 2024
2978c83
update cli messages to include ios npm tasks
Jan 23, 2024
3dcbe4d
[full ci]
Jan 23, 2024
8981969
update cli messages to include ios npm tasks
Jan 23, 2024
bc31b0b
update cli messages to include ios npm tasks
Jan 23, 2024
c4c43f8
update cli messages to include ios npm tasks
Jan 23, 2024
a350083
adjust Scenario: opt not to modify the Android project
Jan 23, 2024
d75d31c
adjust Scenario: opt not to modify the Android project
Jan 23, 2024
3f5d30c
adjust Scenario: opt not to modify the Android project
Jan 23, 2024
23c0e83
update xcode.ts to write to file
Jan 23, 2024
e11caad
attempt to fix liniting
Jan 24, 2024
43abd6d
update unit tests for react native cli
Jan 24, 2024
dcf0e4b
update order in which we edit the xcode.env file
Jan 24, 2024
3d35617
update order in which we edit the xcode.env file
Jan 24, 2024
5dfdcab
update order in which we edit the xcode.env file
Jan 24, 2024
7d5c664
update order in which we edit the xcode.env file
Jan 24, 2024
12904ca
update init scripts
Jan 24, 2024
2be4db2
update init scripts
Jan 24, 2024
dfcfd50
update init scripts
Jan 24, 2024
00c32a9
update rn cli init scripts to match output of commands
Jan 25, 2024
62d73c7
update react native cli flow for automation tests
Jan 25, 2024
c61913a
update path and xcode env file for source maps
Jan 26, 2024
bfac1db
update path and xcode env file for source maps
Jan 26, 2024
76d8335
move where we set the sourcemap file path to xcode build phase as the…
Jan 29, 2024
5e04c30
move where we set the sourcemap file path to xcode build phase as the…
Jan 29, 2024
8fd3f37
move where we set the sourcemap file path to xcode build phase as the…
Jan 29, 2024
735e3fd
Revert "move where we set the sourcemap file path to xcode build phas…
Jan 29, 2024
6b63794
Revert "move where we set the sourcemap file path to xcode build phas…
Jan 29, 2024
676cf2a
Revert "move where we set the sourcemap file path to xcode build phas…
Jan 29, 2024
ecf659b
Merge branch 'next' into je/plat-11399-bugsnag-cli-rn-ios
Jan 30, 2024
9c61593
update unit tests
Jan 30, 2024
bbdbc31
update unit tests
Jan 30, 2024
8f1d377
update unit tests
Jan 30, 2024
88e1389
adjust how we set the sourcemap file'
Jan 31, 2024
a356258
update how we handle rn and xcode env
Jan 31, 2024
c759d35
update how we handle rn and xcode env
Jan 31, 2024
fbe28ec
update how we handle rn and xcode env
Jan 31, 2024
5678c79
Merge branch 'next' into je/plat-11399-bugsnag-cli-rn-ios
Feb 7, 2024
fa78e6e
update build app ios tests
Feb 7, 2024
ac6a0a3
update upload endpoint for ios
Feb 8, 2024
1ba5679
update test fixture for ios builds
Feb 8, 2024
bf0f6f9
revert pipeline file
Feb 12, 2024
418ad8e
update react native cli ios unit tests
Feb 12, 2024
33c40eb
[full ci]
Feb 12, 2024
611673f
[full ci]
Feb 12, 2024
6d24c31
Merge branch 'next' into je/plat-11399-bugsnag-cli-rn-ios
joshedney Feb 13, 2024
c8f966a
Merge branch 'next' into je/plat-11399-bugsnag-cli-rn-ios
joshedney Feb 18, 2024
837d647
update buildkite pipeline files
joshedney Feb 18, 2024
d2a9dcc
update buildkite pipeline files
joshedney Feb 18, 2024
ea9b41f
add comments to the env processing for the react native cli
joshedney Feb 18, 2024
f810a4a
Update packages/react-native-cli/src/commands/AutomateSymbolicationCo…
joshedney Feb 26, 2024
ba05f48
Update packages/react-native-cli/src/commands/AutomateSymbolicationCo…
joshedney Feb 26, 2024
9962a39
remove prompt for react native npm tasks and
Feb 26, 2024
aa975d5
[full ci]
Feb 26, 2024
f9469ad
adjust the npm commands
Feb 27, 2024
eba3d92
adjust how we call the bugsnag-cli for the master upload command
Feb 28, 2024
25418fb
update tests for new react native scripts
Feb 28, 2024
12b760f
Merge branch 'next' into je/plat-11399-bugsnag-cli-rn-ios
Mar 27, 2024
43868a6
update how we messaging around outputting sourcemaps for react native
Mar 27, 2024
8440764
update unit tests
Mar 27, 2024
c062cee
update expect script
Mar 27, 2024
ef747b1
update message around package.json
Mar 27, 2024
bc34e79
update mr cli tests
Mar 27, 2024
74d98ef
[full ci]
Mar 27, 2024
29b1613
[full ci]
Mar 27, 2024
4b78c49
[full ci]
Mar 27, 2024
5335994
[full ci]
Mar 27, 2024
0b0d633
[full ci]
Mar 27, 2024
a2c6290
[full ci]
Mar 27, 2024
b1d2a4e
[full ci]
Mar 27, 2024
aabf4a6
[full ci]
Mar 28, 2024
b3c4245
[full ci]
Mar 28, 2024
453bc04
[full ci]
Mar 28, 2024
8e732aa
[full ci]
Mar 28, 2024
5558824
[full ci]
Mar 28, 2024
83f5b5f
debugging parsing json file
Mar 28, 2024
ffa9fbe
debugging parsing json file
Mar 28, 2024
23d50ad
debugging parsing json file
Mar 28, 2024
8a98e12
debugging parsing json file
Mar 28, 2024
202948b
debugging parsing json file
Mar 28, 2024
c70e1e8
remove redunant tests for react native cli
Apr 9, 2024
b5353d9
remove redunant tests for react native cli
Apr 9, 2024
3eae349
remove redunant tests for react native cli
Apr 9, 2024
57993a9
revert react native cli steps.rb
Apr 9, 2024
2aa6d16
update rn CLI helper scripts
Apr 9, 2024
d13f68b
revert pipeline file changes
Apr 9, 2024
98ebc55
adjust formatting of steps.rb file
Apr 9, 2024
b988466
Merge branch 'next' into je/plat-11399-bugsnag-cli-rn-ios
Apr 16, 2024
5cb557c
Update packages/react-native-cli/src/lib/Xcode.ts
joshedney Apr 22, 2024
fbad4f9
update info message when no .xcode.env is found
Apr 22, 2024
7aeff25
update changelog
Apr 24, 2024
23ee277
Merge branch 'next' into je/plat-11399-bugsnag-cli-rn-ios
joshedney Apr 24, 2024
0acdcf1
fix merge
Apr 30, 2024
a1def2d
Merge branch 'je/plat-11399-bugsnag-cli-rn-ios' of github.com:bugsnag…
Apr 30, 2024
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
114 changes: 47 additions & 67 deletions packages/react-native-cli/src/commands/AutomateSymbolicationCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,45 +26,29 @@ const HERMES_INSTRUCTIONS = `You are running a version of React Native that we c

`

const BUGSNAG_CLI_INSTRUCTIONS = `bugsnag:upload-android task added to your package.json - run this task to upload Android source maps after a build.
const BUGSNAG_CLI_INSTRUCTIONS = `The following tasks have been added to your package.json and can be run after a build to upload source maps to BugSnag:

bugsnag:create-build - Creates a new build
bugsnag:upload-android - Uploads Android source maps
bugsnag:upload-rn-android - Uploads React Native Android source maps
bugsnag:upload-dsym - Uploads iOS dSYMs
bugsnag:upload-rn-ios - Uploads React Native iOS source maps
bugsnag:upload - Runs all of the above tasks

See https://docs.bugsnag.com/platforms/react-native/react-native/showing-full-stacktraces for details.

`

export default async function run (projectRoot: string, urls: OnPremiseUrls): Promise<boolean> {
try {
const { iosIntegration } = await prompts({
type: 'confirm',
name: 'iosIntegration',
message: 'Do you want to automatically upload JavaScript source maps as part of the Xcode build?',
initial: true
}, { onCancel })

if (iosIntegration) {
logger.info('Modifying the Xcode project')
await updateXcodeProject(projectRoot, urls[UrlType.UPLOAD], logger)
}

await prompts({
type: 'text',
name: 'dsymUploadInstructions',
message: DSYM_INSTRUCTIONS,
initial: 'Hit enter to continue …'
}, { onCancel })

const { androidIntegration } = await prompts({
const { bugsnagCliIntegration } = await prompts({
type: 'confirm',
name: 'androidIntegration',
name: 'bugsnagCliIntegration',
message: 'Do you want to install the BugSnag CLI to allow you to upload JavaScript source maps?',
initial: true
}, { onCancel })

if (iosIntegration) {
await installJavaScriptPackage(projectRoot)
}

if (androidIntegration) {
if (bugsnagCliIntegration) {
await installBugsnagCliPackage(projectRoot, urls)
const reactNativeVersion = await detectInstalledVersion('react-native', projectRoot)

Expand All @@ -79,23 +63,33 @@ export default async function run (projectRoot: string, urls: OnPremiseUrls): Pr
}
}

const { packageJsonIntegration } = await prompts({
type: 'confirm',
name: 'packageJsonIntegration',
message: 'Do you want to add an NPM task to your package.json that you can run to upload Android source maps?',
initial: true
}, { onCancel })

if (packageJsonIntegration) {
await writeToPackageJson(join(projectRoot, 'package.json'), urls[UrlType.UPLOAD], urls[UrlType.BUILD])
}
await writeToPackageJson(join(projectRoot, 'package.json'), urls[UrlType.UPLOAD], urls[UrlType.BUILD])

await prompts({
type: 'text',
name: 'bugsnagCliInstructions',
message: BUGSNAG_CLI_INSTRUCTIONS,
initial: 'Hit enter to continue …'
}, { onCancel })

const { iosIntegration } = await prompts({
type: 'confirm',
name: 'iosIntegration',
message: 'Do you want to update your Xcode build phase to output JavaScript source maps?',
initial: true
}, { onCancel })

if (iosIntegration) {
logger.info('Modifying the Xcode project')
await updateXcodeProject(projectRoot, urls[UrlType.UPLOAD], reactNativeVersion as string, logger)

await prompts({
type: 'text',
name: 'dsymUploadInstructions',
message: DSYM_INSTRUCTIONS,
initial: 'Hit enter to continue …'
}, { onCancel })
}
}

return true
Expand Down Expand Up @@ -139,30 +133,6 @@ async function installBugsnagCliPackage (projectRoot: string, urls: OnPremiseUrl
logger.success('@bugsnag/cli dependency is installed')
}

async function installJavaScriptPackage (projectRoot: string): Promise<void> {
const alreadyInstalled = await detectInstalled('@bugsnag/source-maps', projectRoot)

if (alreadyInstalled) {
logger.warn('@bugsnag/source-maps is already installed, skipping')
return
}

logger.info('Adding @bugsnag/source-maps dependency')

const packageManager = await guessPackageManager(projectRoot)

const { version } = await prompts({
type: 'text',
name: 'version',
message: 'If you want the latest version of @bugsnag/source-maps hit enter, otherwise type the version you want',
initial: 'latest'
}, { onCancel })

await install(packageManager, '@bugsnag/source-maps', version, true, projectRoot)

logger.success('@bugsnag/source-maps dependency is installed')
}

async function writeToPackageJson (packageJsonPath: string, uploadUrl?: string, buildUrl?: string): Promise<void> {
try {
const data = await fs.readFile(packageJsonPath, 'utf8')
Expand All @@ -171,21 +141,31 @@ async function writeToPackageJson (packageJsonPath: string, uploadUrl?: string,
// Default to two spaces if indent cannot be detected
const existingIndent = detectIndent(data).indent || ' '

let uploadCommand = 'bugsnag-cli upload react-native-android'
let buildCommand = 'bugsnag-cli create-build'
let createBuildCommand = 'bugsnag-cli create-build'
let rnAndroidUploadCommand = 'bugsnag-cli upload react-native-android'
let androidUploadCommand = 'bugsnag-cli upload android android/'
let rnIosUploadCommand = 'bugsnag-cli upload react-native-ios'
let dsymUploadCommand = 'bugsnag-cli upload dsym ios/'

if (uploadUrl) {
uploadCommand += ` --upload-api-root-url=${uploadUrl}`
rnAndroidUploadCommand += ` --upload-api-root-url=${uploadUrl}`
androidUploadCommand += ` --upload-api-root-url=${uploadUrl}`
rnIosUploadCommand += ` --upload-api-root-url=${uploadUrl}`
dsymUploadCommand += ` --upload-api-root-url=${uploadUrl}`
}

if (buildUrl) {
buildCommand += ` --build-api-root-url=${buildUrl}`
createBuildCommand += ` --build-api-root-url=${buildUrl}`
}

packageJson.scripts = {
...packageJson.scripts,
'bugsnag:create-build': buildCommand,
'bugsnag:upload-android': uploadCommand
'bugsnag:create-build': createBuildCommand,
'bugsnag:upload-android': androidUploadCommand,
'bugsnag:upload-rn-android': rnAndroidUploadCommand,
'bugsnag:upload-dsym': dsymUploadCommand,
'bugsnag:upload-rn-ios': rnIosUploadCommand,
'bugsnag:upload': androidUploadCommand + ' && ' + rnAndroidUploadCommand + ' && ' + dsymUploadCommand + ' && ' + rnIosUploadCommand
}

const updatedPackageJson = JSON.stringify(packageJson, null, existingIndent)
Expand Down
113 changes: 67 additions & 46 deletions packages/react-native-cli/src/lib/Xcode.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { Logger } from '../Logger'
import { promises as fs } from 'fs'
import path from 'path'
import xcode, { Project } from 'xcode'
import xcode from 'xcode'
import semver from 'semver'

const DOCS_LINK = 'https://docs.bugsnag.com/platforms/react-native/react-native/showing-full-stacktraces/#ios'
const UNLOCATED_PROJ_MSG = `The Xcode project was not in the expected location and so couldn't be updated automatically.

Update the "Bundle React Native Code And Images" build phase with the following environment variables:
export EXTRA_PACKAGER_ARGS="--sourcemap-output $TMPDIR/$(md5 -qs "$CONFIGURATION_BUILD_DIR")-main.jsbundle.map""
Please see ${DOCS_LINK} for more information`

See ${DOCS_LINK} for more information`
const EXTRA_INPUT_FILES = ['"$(SRCROOT)/.xcode.env.local"', '"$(SRCROOT)/.xcode.env"']
const EXTRA_PACKAGER_ARGS = 'export SOURCE_MAP_PATH=$(pwd)/build/sourcemaps\nif [ ! -d "$SOURCE_MAP_PATH" ]; then\n\tmkdir -p "$SOURCE_MAP_PATH";\nfi\nexport EXTRA_PACKAGER_ARGS="--sourcemap-output $(pwd)/build/sourcemaps/main.jsbundle.map"'

const EXTRA_PACKAGER_ARGS = 'export EXTRA_PACKAGER_ARGS="--sourcemap-output $TMPDIR/$(md5 -qs "$CONFIGURATION_BUILD_DIR")-main.jsbundle.map"'

export async function updateXcodeProject (projectRoot: string, endpoint: string|undefined, logger: Logger) {
export async function updateXcodeProject (projectRoot: string, endpoint: string|undefined, reactNativeVersion: string|undefined, logger: Logger) {
const iosDir = path.join(projectRoot, 'ios')
const xcodeprojDir = (await fs.readdir(iosDir)).find(p => p.endsWith('.xcodeproj'))

Expand All @@ -35,20 +34,18 @@ export async function updateXcodeProject (projectRoot: string, endpoint: string|
const buildPhaseMap = proj?.hash?.project?.objects?.PBXShellScriptBuildPhase || []
logger.info('Ensuring React Native build phase outputs source maps')

const didUpdate = await updateBuildReactNativeTask(buildPhaseMap, logger)
logger.info('Adding build phase to upload source maps to Bugsnag')

const didAdd = await addUploadSourceMapsTask(proj, buildPhaseMap, endpoint, logger)
const didChange = didUpdate || didAdd
const didUpdate = await updateBuildReactNativeTask(buildPhaseMap, iosDir, reactNativeVersion, logger)

if (!didChange) return
if (!didUpdate) return

await fs.writeFile(pbxProjPath, proj.writeSync(), 'utf8')
logger.success('Written changes to Xcode project')
}

async function updateBuildReactNativeTask (buildPhaseMap: Record<string, Record<string, unknown>>, logger: Logger): Promise<boolean> {
async function updateBuildReactNativeTask (buildPhaseMap: Record<string, Record<string, unknown>>, iosDir: string, reactNativeVersion: string | undefined, logger: Logger): Promise<boolean> {
let didAnythingUpdate = false
let didThisUpdate

for (const shellBuildPhaseKey in buildPhaseMap) {
const phase = buildPhaseMap[shellBuildPhaseKey]
// The shell script can vary slightly... Vanilla RN projects contain
Expand All @@ -57,53 +54,77 @@ async function updateBuildReactNativeTask (buildPhaseMap: Record<string, Record<
// `node --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"`
// so we need a little leniency
if (typeof phase.shellScript === 'string' && phase.shellScript.includes('/react-native-xcode.sh')) {
let didThisUpdate
[phase.shellScript, didThisUpdate] = addExtraPackagerArgs(shellBuildPhaseKey, phase.shellScript, logger)
if (didThisUpdate) {
didAnythingUpdate = true
if (reactNativeVersion) {
// If we're dealing with RN >= 0.69.0 setup the .xcode.env file to export the source maps to our happy path
if (semver.gte(reactNativeVersion, '0.69.0')) {
[phase.inputPaths, didThisUpdate] = addExtraInputFiles(shellBuildPhaseKey, phase.inputPaths as string[], logger)
if (didThisUpdate) {
didAnythingUpdate = true
}
} else {
// If we're dealing with RN < 0.69.0 add the extra pacakge arguments to the xcode build phase as use of the .xcode.env file is not supported.
joshedney marked this conversation as resolved.
Show resolved Hide resolved
[phase.shellScript, didThisUpdate] = addExtraPackagerArgs(shellBuildPhaseKey, phase.shellScript, logger)
if (didThisUpdate) {
didAnythingUpdate = true
}
}
}
}
}
return didAnythingUpdate
}

async function addUploadSourceMapsTask (
proj: Project,
buildPhaseMap: Record<string, Record<string, unknown>>,
endpoint: string|undefined,
logger: Logger
): Promise<boolean> {
for (const shellBuildPhaseKey in buildPhaseMap) {
const phase = buildPhaseMap[shellBuildPhaseKey]
if (typeof phase.shellScript === 'string' && phase.shellScript.includes('bugsnag-react-native-xcode.sh')) {
logger.warn('An "Upload source maps to Bugsnag" build phase already exists')
return false
}
}
await updateXcodeEnv(iosDir, logger)

let shellScript = 'SOURCE_MAP="$TMPDIR/$(md5 -qs "$CONFIGURATION_BUILD_DIR")-main.jsbundle.map" ../node_modules/@bugsnag/react-native/bugsnag-react-native-xcode.sh'
return didAnythingUpdate
}

if (endpoint) {
shellScript = `export ENDPOINT='${endpoint}'\\n${shellScript}`
function addExtraInputFiles (phaseId: string, existingInputFiles: string[], logger: Logger): [string[], boolean] {
if (arrayContainsElements(existingInputFiles, EXTRA_INPUT_FILES)) {
logger.info(`The "Bundle React Native Code and Images" build phase (${phaseId}) already includes the required arguments`)
return [existingInputFiles, false]
}
return [EXTRA_INPUT_FILES.concat(existingInputFiles), true]
}

proj.addBuildPhase(
[],
'PBXShellScriptBuildPhase',
'Upload source maps to Bugsnag',
null,
{ shellPath: '/bin/sh', shellScript }
)

return true
function arrayContainsElements (mainArray: any[], subArray: any[]): boolean {
return subArray.every(element => mainArray.some(mainElement => mainElement === element))
}

function addExtraPackagerArgs (phaseId: string, existingShellScript: string, logger: Logger): [string, boolean] {
const parsedExistingShellScript = JSON.parse(existingShellScript) as string
if (parsedExistingShellScript.includes(EXTRA_PACKAGER_ARGS)) {
logger.warn(`The "Bundle React Native Code and Images" build phase (${phaseId}) already includes the required arguments`)
logger.info(`The "Bundle React Native Code and Images" build phase (${phaseId}) already includes the required arguments`)
return [existingShellScript, false]
}
const scriptLines = parsedExistingShellScript.split('\n')
return [JSON.stringify([EXTRA_PACKAGER_ARGS].concat(scriptLines).join('\n')), true]
}

async function updateXcodeEnv (iosDir: string, logger: Logger): Promise<boolean> {
const searchString = 'SOURCEMAP_FILE='
const envFilePath = path.join(iosDir, '.xcode.env')

try {
await fs.readFile(envFilePath)

const xcodeEnvData = await fs.readFile(envFilePath, 'utf8')

if (xcodeEnvData?.includes(searchString)) {
logger.warn(`The .xcode.env file already contains a section for "${searchString}"`)
return false
} else {
const newData = `${xcodeEnvData}\n\n# React Native Source Map File\nexport SOURCE_MAP_PATH=$(pwd)/build/sourcemaps\nif [ ! -d "$SOURCE_MAP_PATH" ]; then\n\tmkdir -p "$SOURCE_MAP_PATH";\nfi\nexport ${searchString}$(pwd)/build/sourcemaps/main.jsbundle.map`
await fs.writeFile(envFilePath, newData, 'utf8')
return true
}
} catch (error) {
if (error.code === 'ENOENT') {
const newData = `export NODE_BINARY=$(command -v node)\n\n# React Native Source Map File\nexport SOURCE_MAP_PATH=$(pwd)/build/sourcemaps\nif [ ! -d "$SOURCE_MAP_PATH" ]; then\n\tmkdir -p "$SOURCE_MAP_PATH";\nfi\nexport ${searchString}$(pwd)/build/sourcemaps/main.jsbundle.map`
joshedney marked this conversation as resolved.
Show resolved Hide resolved

await fs.writeFile(envFilePath, newData, 'utf8')
return true
} else {
console.error(`Error updating .xcode.env file: ${error.message}`)
return false
}
}
}
Loading
Loading