diff --git a/package.json b/package.json index 3985a69..71ade74 100644 --- a/package.json +++ b/package.json @@ -169,7 +169,7 @@ ] }, "dependencies": { - "@react-native-community/cli": "^12.2.1", - "commander": "^11.1.0" + "commander": "^11.1.0", + "hermes-profile-transformer": "^0.0.9" } } diff --git a/src/cli.ts b/src/cli.ts index f5c118c..9fc8f1a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,18 +1,13 @@ #!/usr/bin/env node -import getContext from '@react-native-community/cli-config'; import { logger, CLIError } from '@react-native-community/cli-tools'; import fs from 'fs'; import path from 'path'; import os from 'os'; import { execSync } from 'child_process'; -import type { Config } from '@react-native-community/cli-types'; import transformer from 'hermes-profile-transformer'; -import { - findSourcemap, - generateSourcemap, -} from '@react-native-community/cli-hermes/build/profileHermes/sourcemapUtils'; -import { getAndroidProject } from '@react-native-community/cli-platform-android'; -import { getMetroBundleOptions } from '@react-native-community/cli-hermes/build/profileHermes/metroBundleOptions'; +import { getMetroBundleOptions } from './getMetroBundleOptions'; +import { generateSourcemap, findSourcemap } from './sourcemapUtils'; +import getConfig from './getConfig'; // Most of the file is just a copy of https://github.com/react-native-community/cli/blob/main/packages/cli-hermes/src/profileHermes/downloadProfile.ts @@ -89,7 +84,6 @@ function maybeAddLineAndColumn(path: string): void { * @param appIdSuffix */ export async function downloadProfile( - ctx: Config, local: string | undefined, fromDownload: Boolean | undefined, dstPath: string, @@ -101,15 +95,23 @@ export async function downloadProfile( appId?: string, appIdSuffix?: string ) { + let ctx = await getConfig(); + try { - const androidProject = getAndroidProject(ctx); + const androidProject = ctx?.project.android; const packageNameWithSuffix = [ - appId || androidProject.packageName, + appId || androidProject?.packageName, appIdSuffix, ] .filter(Boolean) .join('.'); + if (!packageNameWithSuffix) { + throw new Error( + "Failed to retrieve the package name from the project's Android manifest file. Please provide the package name with the --appId flag." + ); + } + // If file name is not specified, pull the latest file from device let file = filename || @@ -125,7 +127,7 @@ export async function downloadProfile( logger.info(`File to be pulled: ${file}`); // If destination path is not specified, pull to the current directory - dstPath = dstPath || ctx.root; + dstPath = dstPath || ctx?.root || './'; logger.debug('Internal commands run to pull the file:'); @@ -162,7 +164,7 @@ export async function downloadProfile( ); } maybeAddLineAndColumn(tempFilePath); - const bundleOptions = getMetroBundleOptions(tempFilePath); + const bundleOptions = getMetroBundleOptions(tempFilePath, 'localhost'); // If path to source map is not given if (!sourcemapPath) { @@ -237,7 +239,6 @@ program.parse(); const options = program.opts(); const dstPath = './'; downloadProfile( - getContext(), options.local, options.fromDownload, dstPath, diff --git a/src/getConfig.ts b/src/getConfig.ts new file mode 100644 index 0000000..0b53616 --- /dev/null +++ b/src/getConfig.ts @@ -0,0 +1,31 @@ +import { exec } from 'child_process'; + +interface Config { + project: { + android: { + packageName: string; + }; + }; + root: string; +} + +function getConfig(): Promise { + return new Promise((resolve, reject) => { + exec('npx react-native config', (error, stdout, stderr) => { + if (error) { + console.error(`exec error: ${error}`); + reject(error); + return; + } + + resolve(JSON.parse(stdout)); + + if (stderr) { + resolve(null); + throw new Error(stderr); + } + }); + }); +} + +export default getConfig; diff --git a/src/getMetroBundleOptions.ts b/src/getMetroBundleOptions.ts new file mode 100644 index 0000000..98895bf --- /dev/null +++ b/src/getMetroBundleOptions.ts @@ -0,0 +1,68 @@ +// Copy of https://github.com/react-native-community/cli/blob/13.x/packages/cli-hermes/src/profileHermes/metroBundleOptions.ts as `cli-hermes` was recently removed from React Native Community CLI + +import fs from 'fs'; +import type { HermesCPUProfile } from 'hermes-profile-transformer/dist/types/HermesProfile'; + +export interface MetroBundleOptions { + platform: string; + dev: boolean; + minify: boolean; + host: string; +} + +export function getMetroBundleOptions( + downloadedProfileFilePath: string, + host: string +): MetroBundleOptions { + let options: MetroBundleOptions = { + platform: 'android', + dev: true, + minify: false, + host, + }; + + try { + const contents: HermesCPUProfile = JSON.parse( + fs.readFileSync(downloadedProfileFilePath, { + encoding: 'utf8', + }) + ); + const matchBundleUrl = /^.*\((.*index\.bundle.*)\)/; + let containsExpoDevMenu = false; + let hadMatch = false; + for (const frame of Object.values(contents.stackFrames)) { + if (frame.name.includes('EXDevMenuApp')) { + containsExpoDevMenu = true; + } + const match = matchBundleUrl.exec(frame.name); + if (match) { + // @ts-ignore + const parsed = new URL(match[1]); + const platform = parsed.searchParams.get('platform'), + dev = parsed.searchParams.get('dev'), + minify = parsed.searchParams.get('minify'); + if (platform) { + options.platform = platform; + } + if (dev) { + options.dev = dev === 'true'; + } + if (minify) { + options.minify = minify === 'true'; + } + + hadMatch = true; + break; + } + } + if (containsExpoDevMenu && !hadMatch) { + console.warn(`Found references to the Expo Dev Menu in your profiling sample. +You might have accidentally recorded the Expo Dev Menu instead of your own application. +To work around this, please reload your app twice before starting a profiler recording.`); + } + } catch (e) { + throw e; + } + + return options; +} diff --git a/src/sourcemapUtils.ts b/src/sourcemapUtils.ts new file mode 100644 index 0000000..8498304 --- /dev/null +++ b/src/sourcemapUtils.ts @@ -0,0 +1,117 @@ +// Copy of https://github.com/react-native-community/cli/blob/13.x/packages/cli-hermes/src/profileHermes/metroBundleOptions.ts as `cli-hermes` was recently removed from React Native Community CLI + +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import type { SourceMap } from 'hermes-profile-transformer'; +import type { MetroBundleOptions } from './getMetroBundleOptions'; + +type Config = any; + +function getTempFilePath(filename: string) { + return path.join(os.tmpdir(), filename); +} + +function writeJsonSync(targetPath: string, data: any) { + let json; + try { + json = JSON.stringify(data); + } catch (e) { + throw new Error( + `Failed to serialize data to json before writing to ${targetPath}`, + e as Error + ); + } + + try { + fs.writeFileSync(targetPath, json, 'utf-8'); + } catch (e) { + throw new Error(`Failed to write json to ${targetPath}`, e as Error); + } +} + +async function getSourcemapFromServer( + port: string, + { platform, dev, minify, host }: MetroBundleOptions +): Promise { + console.log('Getting source maps from Metro packager server'); + + const requestURL = `http://${host}:${port}/index.map?platform=${platform}&dev=${dev}&minify=${minify}`; + console.log(`Downloading from ${requestURL}`); + try { + // @ts-ignore + const { data } = await fetch(requestURL); + return data as SourceMap; + } catch (e) { + console.log(`Failed to fetch source map from "${requestURL}"`); + return undefined; + } +} + +/** + * Generate a sourcemap by fetching it from a running metro server + */ +export async function generateSourcemap( + port: string, + bundleOptions: MetroBundleOptions +): Promise { + // Fetch the source map to a temp directory + const sourceMapPath = getTempFilePath('index.map'); + const sourceMapResult = await getSourcemapFromServer(port, bundleOptions); + + if (sourceMapResult) { + console.log('Using source maps from Metro packager server'); + writeJsonSync(sourceMapPath, sourceMapResult); + console.log( + `Successfully obtained the source map and stored it in ${sourceMapPath}` + ); + return sourceMapPath; + } else { + console.log('Error: Cannot obtain source maps from Metro packager server'); + return undefined; + } +} + +/** + * + * @param ctx + */ +export async function findSourcemap( + ctx: Config, + port: string, + bundleOptions: MetroBundleOptions +): Promise { + const intermediateBuildPath = path.join( + ctx.root, + 'android', + 'app', + 'build', + 'intermediates', + 'sourcemaps', + 'react', + 'debug', + 'index.android.bundle.packager.map' + ); + + const generatedBuildPath = path.join( + ctx.root, + 'android', + 'app', + 'build', + 'generated', + 'sourcemaps', + 'react', + 'debug', + 'index.android.bundle.map' + ); + + if (fs.existsSync(generatedBuildPath)) { + console.log(`Getting the source map from ${generateSourcemap}`); + return generatedBuildPath; + } else if (fs.existsSync(intermediateBuildPath)) { + console.log(`Getting the source map from ${intermediateBuildPath}`); + return intermediateBuildPath; + } else { + return generateSourcemap(port, bundleOptions); + } +} diff --git a/yarn.lock b/yarn.lock index 5a75532..217666c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2649,17 +2649,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-clean@npm:12.2.1": - version: 12.2.1 - resolution: "@react-native-community/cli-clean@npm:12.2.1" - dependencies: - "@react-native-community/cli-tools": 12.2.1 - chalk: ^4.1.2 - execa: ^5.0.0 - checksum: 69676fc6589fb24419d35a5b090f308c463af8d57f4785e14ea1dba3f18018a2125980e5f84949f1ae02e0478f73654fb297cfaeda3b291c6d833fa1ca4ad2a7 - languageName: node - linkType: hard - "@react-native-community/cli-config@npm:11.3.10": version: 11.3.10 resolution: "@react-native-community/cli-config@npm:11.3.10" @@ -2674,20 +2663,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-config@npm:12.2.1": - version: 12.2.1 - resolution: "@react-native-community/cli-config@npm:12.2.1" - dependencies: - "@react-native-community/cli-tools": 12.2.1 - chalk: ^4.1.2 - cosmiconfig: ^5.1.0 - deepmerge: ^4.3.0 - glob: ^7.1.3 - joi: ^17.2.1 - checksum: 051553c585257ccff489193223ab3c7f98308548b3caa29b4cfb2bf0d43df4d26289417f2f3e6c2d699d15b787c4ad284ed5d1a7c9d30e4a68dc334d9a285fb3 - languageName: node - linkType: hard - "@react-native-community/cli-debugger-ui@npm:11.3.10": version: 11.3.10 resolution: "@react-native-community/cli-debugger-ui@npm:11.3.10" @@ -2697,15 +2672,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-debugger-ui@npm:12.2.1": - version: 12.2.1 - resolution: "@react-native-community/cli-debugger-ui@npm:12.2.1" - dependencies: - serve-static: ^1.13.1 - checksum: 13cd9b3df762f8bd9a13b7a9945c780941716d016aa6fdbf5bfd8bdcb221ea4d0da8ac314bacd4820fc9395486136afcf2fef616e0dbbee3a18411f979c9c890 - languageName: node - linkType: hard - "@react-native-community/cli-doctor@npm:11.3.10": version: 11.3.10 resolution: "@react-native-community/cli-doctor@npm:11.3.10" @@ -2732,31 +2698,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-doctor@npm:12.2.1": - version: 12.2.1 - resolution: "@react-native-community/cli-doctor@npm:12.2.1" - dependencies: - "@react-native-community/cli-config": 12.2.1 - "@react-native-community/cli-platform-android": 12.2.1 - "@react-native-community/cli-platform-ios": 12.2.1 - "@react-native-community/cli-tools": 12.2.1 - chalk: ^4.1.2 - command-exists: ^1.2.8 - deepmerge: ^4.3.0 - envinfo: ^7.10.0 - execa: ^5.0.0 - hermes-profile-transformer: ^0.0.6 - ip: ^1.1.5 - node-stream-zip: ^1.9.1 - ora: ^5.4.1 - semver: ^7.5.2 - strip-ansi: ^5.2.0 - wcwidth: ^1.0.1 - yaml: ^2.2.1 - checksum: f219cb269bad46a33e9cab7de0b60b43e182dedf7911514e5634cc360c76ca95783090feca9a2a4f13d97d9075089c4e262ec1fe3954dff0f88cc05fae137da6 - languageName: node - linkType: hard - "@react-native-community/cli-hermes@npm:11.3.10": version: 11.3.10 resolution: "@react-native-community/cli-hermes@npm:11.3.10" @@ -2770,19 +2711,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-hermes@npm:12.2.1": - version: 12.2.1 - resolution: "@react-native-community/cli-hermes@npm:12.2.1" - dependencies: - "@react-native-community/cli-platform-android": 12.2.1 - "@react-native-community/cli-tools": 12.2.1 - chalk: ^4.1.2 - hermes-profile-transformer: ^0.0.6 - ip: ^1.1.5 - checksum: bfe6fdd2b9e29c1961270b746fcbdc1a122241b94966a985aefc8f1cef85c44ca726218d1294c4f0958cd65748a4ad43751ff4b758b191031262277d626818c7 - languageName: node - linkType: hard - "@react-native-community/cli-platform-android@npm:11.3.10": version: 11.3.10 resolution: "@react-native-community/cli-platform-android@npm:11.3.10" @@ -2796,20 +2724,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-platform-android@npm:12.2.1": - version: 12.2.1 - resolution: "@react-native-community/cli-platform-android@npm:12.2.1" - dependencies: - "@react-native-community/cli-tools": 12.2.1 - chalk: ^4.1.2 - execa: ^5.0.0 - fast-xml-parser: ^4.2.4 - glob: ^7.1.3 - logkitty: ^0.7.1 - checksum: 950e0ad800e1481b3609828739d1117d8a8d98e75cb233e6a26a302913a47f1b9f47d1c54232fdee039c46e4119174b890f8ec2789736b5103a25c64ddbf219f - languageName: node - linkType: hard - "@react-native-community/cli-platform-ios@npm:11.3.10": version: 11.3.10 resolution: "@react-native-community/cli-platform-ios@npm:11.3.10" @@ -2824,20 +2738,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-platform-ios@npm:12.2.1": - version: 12.2.1 - resolution: "@react-native-community/cli-platform-ios@npm:12.2.1" - dependencies: - "@react-native-community/cli-tools": 12.2.1 - chalk: ^4.1.2 - execa: ^5.0.0 - fast-xml-parser: ^4.0.12 - glob: ^7.1.3 - ora: ^5.4.1 - checksum: 57a721ee6270fe447c640b00e79ca776c5bdab396dddcb57002c7bea0d33bdba8e7939496d75bd0ca147f5cf0db3188495fb46364c9f8484bdadd5869a8b89d4 - languageName: node - linkType: hard - "@react-native-community/cli-plugin-metro@npm:11.3.10": version: 11.3.10 resolution: "@react-native-community/cli-plugin-metro@npm:11.3.10" @@ -2857,13 +2757,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-plugin-metro@npm:12.2.1": - version: 12.2.1 - resolution: "@react-native-community/cli-plugin-metro@npm:12.2.1" - checksum: 8b2e182c1f92b490e71aeeeb4825407dcb39f6b18f2d269cf111de6cdbb578cc98d61e22648924adeb67627589e296fb9c9fe1f8e479823eb40cf322a4901285 - languageName: node - linkType: hard - "@react-native-community/cli-server-api@npm:11.3.10": version: 11.3.10 resolution: "@react-native-community/cli-server-api@npm:11.3.10" @@ -2881,23 +2774,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-server-api@npm:12.2.1": - version: 12.2.1 - resolution: "@react-native-community/cli-server-api@npm:12.2.1" - dependencies: - "@react-native-community/cli-debugger-ui": 12.2.1 - "@react-native-community/cli-tools": 12.2.1 - compression: ^1.7.1 - connect: ^3.6.5 - errorhandler: ^1.5.1 - nocache: ^3.0.1 - pretty-format: ^26.6.2 - serve-static: ^1.13.1 - ws: ^7.5.1 - checksum: 36e9c083ce01debf0f0ab8487ecdc3117095931f10a07e90ac0069cd784ebd02999c0550be7da1d40a0210c5d9e853d954a4bcb85ab0717ea47aeb60df2ff928 - languageName: node - linkType: hard - "@react-native-community/cli-tools@npm:11.3.10": version: 11.3.10 resolution: "@react-native-community/cli-tools@npm:11.3.10" @@ -2915,24 +2791,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-tools@npm:12.2.1": - version: 12.2.1 - resolution: "@react-native-community/cli-tools@npm:12.2.1" - dependencies: - appdirsjs: ^1.2.4 - chalk: ^4.1.2 - find-up: ^5.0.0 - mime: ^2.4.1 - node-fetch: ^2.6.0 - open: ^6.2.0 - ora: ^5.4.1 - semver: ^7.5.2 - shell-quote: ^1.7.3 - sudo-prompt: ^9.0.0 - checksum: 12ec68974683d6e21801da25373b56438012aaf1ed2ce5a0bed5578d240e783d732d3a872221dca42b7d5e359f58e3ea22d127bbfcf75e08190b765b20810b55 - languageName: node - linkType: hard - "@react-native-community/cli-types@npm:11.3.10": version: 11.3.10 resolution: "@react-native-community/cli-types@npm:11.3.10" @@ -2942,15 +2800,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-types@npm:12.2.1": - version: 12.2.1 - resolution: "@react-native-community/cli-types@npm:12.2.1" - dependencies: - joi: ^17.2.1 - checksum: 57f8593c896d54d03f5986b3589f1b7b82a123bb4fc7157905649a21d717283392177e09b9ccc854387e1b22d3aae383be292a4414d1c8f6447e6df9436b43cd - languageName: node - linkType: hard - "@react-native-community/cli@npm:11.3.10": version: 11.3.10 resolution: "@react-native-community/cli@npm:11.3.10" @@ -2978,34 +2827,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli@npm:^12.2.1": - version: 12.2.1 - resolution: "@react-native-community/cli@npm:12.2.1" - dependencies: - "@react-native-community/cli-clean": 12.2.1 - "@react-native-community/cli-config": 12.2.1 - "@react-native-community/cli-debugger-ui": 12.2.1 - "@react-native-community/cli-doctor": 12.2.1 - "@react-native-community/cli-hermes": 12.2.1 - "@react-native-community/cli-plugin-metro": 12.2.1 - "@react-native-community/cli-server-api": 12.2.1 - "@react-native-community/cli-tools": 12.2.1 - "@react-native-community/cli-types": 12.2.1 - chalk: ^4.1.2 - commander: ^9.4.1 - deepmerge: ^4.3.0 - execa: ^5.0.0 - find-up: ^4.1.0 - fs-extra: ^8.1.0 - graceful-fs: ^4.1.3 - prompts: ^2.4.2 - semver: ^7.5.2 - bin: - react-native: build/bin.js - checksum: 942d3530d1e06ff012bda12508f00e89171a9d61102e27f926b154a214e3d2c28b7b580bc04b320cc6c451f86edb41226f4cfce48347656845c37ebe0244b684 - languageName: node - linkType: hard - "@react-native/assets-registry@npm:^0.72.0": version: 0.72.0 resolution: "@react-native/assets-registry@npm:0.72.0" @@ -5674,7 +5495,7 @@ __metadata: languageName: node linkType: hard -"envinfo@npm:^7.10.0, envinfo@npm:^7.7.2": +"envinfo@npm:^7.7.2": version: 7.11.0 resolution: "envinfo@npm:7.11.0" bin: @@ -6334,7 +6155,7 @@ __metadata: languageName: node linkType: hard -"fast-xml-parser@npm:^4.0.12, fast-xml-parser@npm:^4.2.4": +"fast-xml-parser@npm:^4.0.12": version: 4.3.2 resolution: "fast-xml-parser@npm:4.3.2" dependencies: @@ -7120,6 +6941,15 @@ __metadata: languageName: node linkType: hard +"hermes-profile-transformer@npm:^0.0.9": + version: 0.0.9 + resolution: "hermes-profile-transformer@npm:0.0.9" + dependencies: + source-map: ^0.7.3 + checksum: 227f8619535807ca58d6f084267c450c21904ebd7b1576b4a522576a7ad24ba6f2a9a41dcb6e2ad5b1a9834e8f09bc4078d1a5d6dc63d72fccc293d618d89749 + languageName: node + linkType: hard + "hosted-git-info@npm:^2.1.4": version: 2.8.9 resolution: "hosted-git-info@npm:2.8.9" @@ -11019,7 +10849,6 @@ __metadata: dependencies: "@commitlint/config-conventional": ^17.0.2 "@evilmartians/lefthook": ^1.5.0 - "@react-native-community/cli": ^12.2.1 "@react-native/eslint-config": ^0.72.2 "@release-it/conventional-changelog": ^5.0.0 "@types/jest": ^28.1.2 @@ -11031,6 +10860,7 @@ __metadata: eslint: ^8.4.1 eslint-config-prettier: ^8.5.0 eslint-plugin-prettier: ^4.0.0 + hermes-profile-transformer: ^0.0.9 jest: ^28.1.1 pod-install: ^0.1.0 prettier: ^2.0.5