mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24f8a62fbb |
@@ -30,7 +30,6 @@ module.exports = {
|
||||
// These rules are not required with hermes-eslint
|
||||
'ft-flow/define-flow-type': 0,
|
||||
'ft-flow/use-flow-type': 0,
|
||||
'lint/sort-imports': 1,
|
||||
// flow handles this check for us, so it's not required
|
||||
'no-undef': 0,
|
||||
},
|
||||
|
||||
+1
-7
@@ -26,9 +26,6 @@
|
||||
; helloworld
|
||||
<PROJECT_ROOT>/packages/helloworld/ios/Pods/
|
||||
|
||||
; Ignore rn-tester Pods
|
||||
<PROJECT_ROOT>/packages/rn-tester/Pods/
|
||||
|
||||
[untyped]
|
||||
.*/node_modules/@react-native-community/cli/.*/.*
|
||||
|
||||
@@ -44,7 +41,6 @@ packages/react-native/flow/
|
||||
|
||||
[options]
|
||||
enums=true
|
||||
as_const=true
|
||||
casting_syntax=both
|
||||
|
||||
emoji=true
|
||||
@@ -73,8 +69,6 @@ suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
suppress_type=$FlowFixMeEmpty
|
||||
|
||||
ban_spread_key_props=true
|
||||
|
||||
[lints]
|
||||
sketchy-null-number=warn
|
||||
sketchy-null-mixed=warn
|
||||
@@ -96,4 +90,4 @@ untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.255.0
|
||||
^0.247.1
|
||||
|
||||
@@ -9,7 +9,7 @@ inputs:
|
||||
description: The version of Hermes
|
||||
slice:
|
||||
required: true
|
||||
description: The slice of hermes you want to build. It could be iphone, iphonesimulator, macos, catalyst, appletvos, appletvsimulator, xros, or xrossimulator
|
||||
description: The slice of hermes you want to build. It could be iphone, iphonesimulator, macos, catalyst, xros, or xrossimulator
|
||||
flavor:
|
||||
required: true
|
||||
description: The flavor we want to build. It can be Debug or Release
|
||||
|
||||
@@ -22,7 +22,7 @@ runs:
|
||||
- name: Restore Cached Artifacts
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
key: v4-hermes-artifacts-${{ inputs.flavor }}-${{ inputs.hermes-version }}-${{ inputs.react-native-version }}-${{ hashFiles('./packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh') }}
|
||||
key: v4-hermes-artifacts-${{ inputs.flavor }}-${{ inputs.hermes-version }}-${{ inputs.react-native-version }}-${{ hashFiles('./packages/react-native/sdks/hermes/utils/build-apple-frameworks.sh') }}
|
||||
path: |
|
||||
/tmp/hermes/osx-bin/${{ inputs.flavor }}
|
||||
/tmp/hermes/dSYM/${{ inputs.flavor }}
|
||||
@@ -66,18 +66,6 @@ runs:
|
||||
with:
|
||||
path: ./packages/react-native/sdks/hermes/
|
||||
name: slice-iphonesimulator-${{ inputs.flavor }}
|
||||
- name: Slice cache appletvos
|
||||
if: ${{ steps.check_if_apple_artifacts_are_there.outputs.ARTIFACTS_EXIST != 'true' }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ./packages/react-native/sdks/hermes/
|
||||
name: slice-appletvos-${{ inputs.flavor }}
|
||||
- name: Slice cache appletvsimulator
|
||||
if: ${{ steps.check_if_apple_artifacts_are_there.outputs.ARTIFACTS_EXIST != 'true' }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ./packages/react-native/sdks/hermes/
|
||||
name: slice-appletvsimulator-${{ inputs.flavor }}
|
||||
- name: Slice cache catalyst
|
||||
if: ${{ steps.check_if_apple_artifacts_are_there.outputs.ARTIFACTS_EXIST != 'true' }}
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -105,8 +93,6 @@ runs:
|
||||
tar -xzv -f build_catalyst_${{ matrix.flavor }}.tar.gz
|
||||
tar -xzv -f build_iphoneos_${{ matrix.flavor }}.tar.gz
|
||||
tar -xzv -f build_iphonesimulator_${{ matrix.flavor }}.tar.gz
|
||||
tar -xzv -f build_appletvos_${{ matrix.flavor }}.tar.gz
|
||||
tar -xzv -f build_appletvsimulator_${{ matrix.flavor }}.tar.gz
|
||||
tar -xzv -f build_macosx_${{ matrix.flavor }}.tar.gz
|
||||
tar -xzv -f build_xros_${{ matrix.flavor }}.tar.gz
|
||||
tar -xzv -f build_xrsimulator_${{ matrix.flavor }}.tar.gz
|
||||
@@ -119,8 +105,6 @@ runs:
|
||||
mv build_macosx_${{ inputs.flavor }} build_macosx
|
||||
mv build_iphoneos_${{ inputs.flavor }} build_iphoneos
|
||||
mv build_iphonesimulator_${{ inputs.flavor }} build_iphonesimulator
|
||||
mv build_appletvos_${{ inputs.flavor }} build_appletvos
|
||||
mv build_appletvsimulator_${{ inputs.flavor }} build_appletvsimulator
|
||||
mv build_catalyst_${{ inputs.flavor }} build_catalyst
|
||||
mv build_xros_${{ inputs.flavor }} build_xros
|
||||
mv build_xrsimulator_${{ inputs.flavor }} build_xrsimulator
|
||||
@@ -179,8 +163,6 @@ runs:
|
||||
mkdir -p "$WORKING_DIR/catalyst"
|
||||
mkdir -p "$WORKING_DIR/iphoneos"
|
||||
mkdir -p "$WORKING_DIR/iphonesimulator"
|
||||
mkdir -p "$WORKING_DIR/appletvos"
|
||||
mkdir -p "$WORKING_DIR/appletvsimulator"
|
||||
mkdir -p "$WORKING_DIR/xros"
|
||||
mkdir -p "$WORKING_DIR/xrsimulator"
|
||||
|
||||
@@ -191,8 +173,6 @@ runs:
|
||||
cp -r build_catalyst/$DSYM_FILE_PATH "$WORKING_DIR/catalyst/"
|
||||
cp -r build_iphoneos/$DSYM_FILE_PATH "$WORKING_DIR/iphoneos/"
|
||||
cp -r build_iphonesimulator/$DSYM_FILE_PATH "$WORKING_DIR/iphonesimulator/"
|
||||
cp -r build_appletvos/$DSYM_FILE_PATH "$WORKING_DIR/appletvos/"
|
||||
cp -r build_appletvsimulator/$DSYM_FILE_PATH "$WORKING_DIR/appletvsimulator/"
|
||||
cp -r build_xros/$DSYM_FILE_PATH "$WORKING_DIR/xros/"
|
||||
cp -r build_xrsimulator/$DSYM_FILE_PATH "$WORKING_DIR/xrsimulator/"
|
||||
|
||||
@@ -220,7 +200,7 @@ runs:
|
||||
uses: actions/cache/save@v4
|
||||
if: ${{ github.ref == 'refs/heads/main' || contains(github.ref, '-stable') }} # To avoid that the cache explode.
|
||||
with:
|
||||
key: v4-hermes-artifacts-${{ inputs.flavor }}-${{ inputs.hermes-version }}-${{ inputs.react-native-version }}-${{ hashFiles('./packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh') }}
|
||||
key: v4-hermes-artifacts-${{ inputs.flavor }}-${{ inputs.hermes-version }}-${{ inputs.react-native-version }}-${{ hashFiles('./packages/react-native/sdks/hermes/utils/build-apple-frameworks.sh') }}
|
||||
path: |
|
||||
/tmp/hermes/osx-bin/${{ inputs.flavor }}
|
||||
/tmp/hermes/dSYM/${{ inputs.flavor }}
|
||||
|
||||
@@ -43,7 +43,7 @@ runs:
|
||||
shell: powershell
|
||||
run: |
|
||||
if (-not(Test-Path -Path $Env:HERMES_WS_DIR\win64-bin\hermesc.exe)) {
|
||||
choco install --no-progress cmake --version 3.14.7 --allow-downgrade
|
||||
choco install --no-progress cmake --version 3.14.7
|
||||
if (-not $?) { throw "Failed to install CMake" }
|
||||
|
||||
cd $Env:HERMES_WS_DIR\icu
|
||||
|
||||
@@ -44,7 +44,6 @@ runs:
|
||||
run: |
|
||||
cd ${{ inputs.working-directory }}
|
||||
yarn start &
|
||||
sleep 5 # to give metro time to load
|
||||
- name: Run tests
|
||||
id: run-tests
|
||||
shell: bash
|
||||
@@ -69,13 +68,8 @@ runs:
|
||||
echo "Launch the app"
|
||||
xcrun simctl launch $UDID ${{ inputs.app-id }}
|
||||
|
||||
if [[ ${{ inputs.flavor }} == 'Debug' ]]; then
|
||||
# To give the app time to warm the metro's cache
|
||||
sleep 20
|
||||
fi
|
||||
|
||||
echo "Running tests with Maestro"
|
||||
export MAESTRO_DRIVER_STARTUP_TIMEOUT=1500000 # 25 min. CI is extremely slow
|
||||
export MAESTRO_DRIVER_STARTUP_TIMEOUT=1800000 # 30 min. CI is extremely slow
|
||||
|
||||
# Add retries for flakyness
|
||||
MAX_ATTEMPTS=5
|
||||
@@ -83,6 +77,11 @@ runs:
|
||||
RESULT=1
|
||||
|
||||
while [[ $CURR_ATTEMPT -lt $MAX_ATTEMPTS ]] && [[ $RESULT -ne 0 ]]; do
|
||||
if [[ $CURR_ATTEMPT -ne 0 ]]; then
|
||||
echo "Rebooting simulator for stability"
|
||||
xcrun simctl boot "iPhone 15 Pro"
|
||||
fi
|
||||
|
||||
CURR_ATTEMPT=$((CURR_ATTEMPT+1))
|
||||
echo "Attempt number $CURR_ATTEMPT"
|
||||
|
||||
@@ -98,6 +97,9 @@ runs:
|
||||
|
||||
# Stop video
|
||||
kill -SIGINT $(cat video_record_${{ inputs.jsengine }}_$CURR_ATTEMPT.pid)
|
||||
|
||||
echo "Shutting down simulator for stability"
|
||||
xcrun simctl shutdown "iPhone 15 Pro"
|
||||
done
|
||||
|
||||
exit $RESULT
|
||||
@@ -110,8 +112,6 @@ runs:
|
||||
video_record_1.mov
|
||||
video_record_2.mov
|
||||
video_record_3.mov
|
||||
video_record_4.mov
|
||||
video_record_5.mov
|
||||
report.xml
|
||||
- name: Store Logs
|
||||
if: failure() && steps.run-tests.outcome == 'failure'
|
||||
|
||||
@@ -3,6 +3,9 @@ description: Prepare iOS Tests
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: brew install xcbeautify
|
||||
run: brew install xcbeautify
|
||||
shell: bash
|
||||
- name: Run Ruby Tests
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -8,7 +8,7 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
gradle-version: wrapper
|
||||
# We want the Gradle cache to be written only on main/-stable branches run, and only for jobs with `cache-read-only` == false (i.e. `build_android`).
|
||||
|
||||
@@ -121,21 +121,21 @@ runs:
|
||||
if: ${{ inputs.run-unit-tests != 'true' && inputs.run-e2e-tests == 'false' }}
|
||||
shell: bash
|
||||
run: |
|
||||
xcodebuild build \
|
||||
set -o pipefail && xcodebuild build \
|
||||
-workspace packages/rn-tester/RNTesterPods.xcworkspace \
|
||||
-scheme RNTester \
|
||||
-sdk iphonesimulator
|
||||
-sdk iphonesimulator | xcbeautify
|
||||
- name: Build RNTester (E2E Tests)
|
||||
shell: bash
|
||||
if: ${{ inputs.run-e2e-tests == 'true' }}
|
||||
run: |
|
||||
xcodebuild \
|
||||
set -o pipefail && xcodebuild \
|
||||
-scheme "RNTester" \
|
||||
-workspace packages/rn-tester/RNTesterPods.xcworkspace \
|
||||
-configuration "${{ inputs.flavor }}" \
|
||||
-sdk "iphonesimulator" \
|
||||
-destination "generic/platform=iOS Simulator" \
|
||||
-derivedDataPath "/tmp/RNTesterBuild"
|
||||
-derivedDataPath "/tmp/RNTesterBuild" | xcbeautify
|
||||
|
||||
echo "Print path to *.app file"
|
||||
find "/tmp/RNTesterBuild" -type d -name "*.app"
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
name: test-library-on-nightly
|
||||
description: Tests a library on a nightly
|
||||
inputs:
|
||||
library-npm-package:
|
||||
description: The library npm package to add
|
||||
required: true
|
||||
platform:
|
||||
description: whether we want to build for iOS or Android
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Create new app
|
||||
shell: bash
|
||||
run: |
|
||||
cd /tmp
|
||||
npx @react-native-community/cli init RNApp --skip-install --version nightly
|
||||
- name: Add library
|
||||
shell: bash
|
||||
run: |
|
||||
cd /tmp/RNApp
|
||||
yarn add ${{ inputs.library-npm-package }}
|
||||
|
||||
- name: Build iOS
|
||||
shell: bash
|
||||
if: ${{ inputs.platform == 'ios' }}
|
||||
run: |
|
||||
cd /tmp/RNApp/ios
|
||||
bundle install
|
||||
bundle exec pod install
|
||||
xcodebuild build \
|
||||
-workspace RNApp.xcworkspace \
|
||||
-scheme RNApp \
|
||||
-sdk iphonesimulator
|
||||
- name: Setup Java for Android
|
||||
if: ${{ inputs.platform == 'android' }}
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'zulu'
|
||||
- name: Build Android
|
||||
shell: bash
|
||||
if: ${{ inputs.platform == 'android' }}
|
||||
run: |
|
||||
cd /tmp/RNApp/android
|
||||
./gradlew assembleDebug
|
||||
@@ -46,7 +46,7 @@ describe('#publishTemplate', () => {
|
||||
expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith({
|
||||
owner: 'react-native-community',
|
||||
repo: 'template',
|
||||
workflow_id: 'release.yaml',
|
||||
workflow_id: 'release.yml',
|
||||
ref: '0.76-stable',
|
||||
inputs: {
|
||||
dry_run: true,
|
||||
@@ -66,7 +66,7 @@ describe('#publishTemplate', () => {
|
||||
expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith({
|
||||
owner: 'react-native-community',
|
||||
repo: 'template',
|
||||
workflow_id: 'release.yaml',
|
||||
workflow_id: 'release.yml',
|
||||
ref: '0.76-stable',
|
||||
inputs: {
|
||||
dry_run: false,
|
||||
|
||||
@@ -48,84 +48,96 @@ module.exports = async (github, context, labelWithContext) => {
|
||||
switch (labelWithContext.label) {
|
||||
case 'Type: Invalid':
|
||||
await addComment(
|
||||
`> [!CAUTION]\n` +
|
||||
`> **Invalid issue**: This issue is not valid, either is not a bug in React Native, it doesn't match any of the issue template, or we can't help further with this.`,
|
||||
`| :warning: | Issue is Invalid |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | This issue doesn't match any of the expected types for this repository - closing. |`,
|
||||
);
|
||||
await closeIssue();
|
||||
return;
|
||||
case 'Type: Question':
|
||||
await addComment(
|
||||
`> [!NOTE]\n` +
|
||||
`> **Not a bug report**: This issue looks like a question. We are using GitHub issues exclusively to track bugs in React Native. GitHub may not be the ideal place to ask a question, but you can try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native), or on [Reactiflux](https://www.reactiflux.com/).`,
|
||||
);
|
||||
await closeIssue();
|
||||
return;
|
||||
case 'Resolution: For Stack Overflow':
|
||||
await addComment(
|
||||
`> [!NOTE]\n` +
|
||||
`> **Not a bug report**: This issue looks like a question. We are using GitHub issues exclusively to track bugs in React Native. GitHub may not be the ideal place to ask a question, but you can try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native), or on [Reactiflux](https://www.reactiflux.com/).`,
|
||||
`| :warning: | Issue is a Question |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | We are using GitHub issues exclusively to track bugs in React Native. GitHub may not be the ideal place to ask a question, but you can try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native), or on [Reactiflux](https://www.reactiflux.com/). |`,
|
||||
);
|
||||
await closeIssue();
|
||||
return;
|
||||
case 'Type: Docs':
|
||||
await addComment(
|
||||
`> [!NOTE]\n` +
|
||||
`> **Docs issue**: This issue looks like an issue related to our docs. Please report documentation issues in the [react-native-website](https://github.com/facebook/react-native-website/issues) repository.`,
|
||||
`| :warning: | Documentation Issue |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | Please report documentation issues in the [react-native-website](https://github.com/facebook/react-native-website/issues) repository. |`,
|
||||
);
|
||||
await closeIssue();
|
||||
return;
|
||||
case 'Resolution: For Stack Overflow':
|
||||
await addComment(
|
||||
`| :warning: | Issue is a Question |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | We are using GitHub issues exclusively to track bugs in the core React Native library. Please try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native) as it is better suited for this type of question. |`,
|
||||
);
|
||||
await closeIssue();
|
||||
return;
|
||||
case 'Type: Expo':
|
||||
await addComment(
|
||||
`> [!NOTE]\n` +
|
||||
`> **Expo related**: It looks like your issue is related to Expo and not React Native core. Please open your issue in [Expo's repository](https://github.com/expo/expo/issues/new). If you are able to create a repro that showcases that this issue is also happening in React Native vanilla, we will be happy to re-open.`,
|
||||
`| :warning: | Issue is Related to Expo |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | It looks like your issue is related to Expo and not React Native core. Please open your issue in [Expo's repository](https://github.com/expo/expo/issues/new). If you are able to create a repro that showcases that this issue is also happening in React Native vanilla, we will be happy to re-open. |`,
|
||||
);
|
||||
await closeIssue();
|
||||
return;
|
||||
case 'Needs: Issue Template':
|
||||
await addComment(
|
||||
`> [!WARNING]\n` +
|
||||
`> **Missing issue template**: It looks like your issue may be missing some necessary information. GitHub provides an example template whenever a [new issue is created](https://github.com/facebook/react-native/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A&projects=&template=bug_report.yml). Could you go back and make sure to fill out the template? You may edit this issue, or close it and open a new one.`,
|
||||
`| :warning: | Missing Required Fields |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | It looks like your issue may be missing some necessary information. GitHub provides an example template whenever a [new issue is created](https://github.com/facebook/react-native/issues/new?template=bug_report.md). Could you go back and make sure to fill out the template? You may edit this issue, or close it and open a new one. |`,
|
||||
);
|
||||
await requestAuthorFeedback();
|
||||
return;
|
||||
case 'Needs: Environment Info':
|
||||
await addComment(
|
||||
`> [!WARNING]\n` +
|
||||
`> **Missing info**: It looks like your issue may be missing information about your development environment. You can obtain the missing information by running <code>react-native info</code> in a console.`,
|
||||
`| :warning: | Missing Environment Information |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | Your issue may be missing information about your development environment. You can obtain the missing information by running <code>react-native info</code> in a console. |`,
|
||||
);
|
||||
await requestAuthorFeedback();
|
||||
return;
|
||||
case 'Newer Patch Available':
|
||||
await addComment(
|
||||
`> [!TIP]\n` +
|
||||
`> **Newer version available**: You are on a supported minor version, but it looks like there's a newer patch available - ${labelWithContext.newestPatch}. Please [upgrade](https://reactnative.dev/docs/upgrading) to the highest patch for your minor or latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If it does not repro, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the most recent releases.`,
|
||||
`| :warning: | Newer Version of React Native is Available! |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | You are on a supported minor version, but it looks like there's a newer patch available - ${labelWithContext.newestPatch}. Please [upgrade](https://reactnative.dev/docs/upgrading) to the highest patch for your minor or latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If it does not repro, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the most recent releases. |`,
|
||||
);
|
||||
return;
|
||||
case 'Needs: Version Info':
|
||||
await addComment(
|
||||
`> [!WARNING]\n` +
|
||||
`> **Could not parse version**: We could not find or parse the version number of React Native in your issue report. Please use the template, and report your version including major, minor, and patch numbers - e.g. 0.76.2.`,
|
||||
`| :warning: | Add or Reformat Version Info |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | We could not find or parse the version number of React Native in your issue report. Please use the template, and report your version including major, minor, and patch numbers - e.g. 0.70.2 |`,
|
||||
);
|
||||
await requestAuthorFeedback();
|
||||
return;
|
||||
case 'Needs: Repro':
|
||||
await addComment(
|
||||
`> [!WARNING]\n` +
|
||||
`> **Missing reproducer**: We could not detect a reproducible example in your issue report. Please provide either: <br/><ul><li>If your bug is UI related: a [Snack](https://snack.expo.dev)</li><li> If your bug is build/upgrade related: a project using our [Reproducer Template](https://github.com/react-native-community/reproducer-react-native/generate)</li><li>Otherwise send us a Pull Request with the [RNTesterPlayground.js](https://github.com/facebook/react-native/blob/main/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js) edited to reproduce your bug.</li></ul>`,
|
||||
`| :warning: | Missing Reproducible Example |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | We could not detect a reproducible example in your issue report. Please provide either: <br /><ul><li>If your bug is UI related: a [Snack](https://snack.expo.dev)</li><li> If your bug is build/update related: use our [Reproducer Template](https://github.com/react-native-community/reproducer-react-native/generate)</li></ul> |`,
|
||||
);
|
||||
await requestAuthorFeedback();
|
||||
return;
|
||||
case 'Type: Unsupported Version':
|
||||
await addComment(
|
||||
`> [!WARNING]\n` +
|
||||
`> **Unsupported version**: It looks like your issue or the example you provided uses an [unsupported version of React Native](https://github.com/reactwg/react-native-releases/blob/main/docs/support.md).<br/><br/>Due to the number of issues we receive, we're currently only accepting new issues against one of the supported versions. Please [upgrade](https://reactnative.dev/docs/upgrading) to latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If you cannot upgrade, please open your issue on [StackOverflow](https://stackoverflow.com/questions/tagged/react-native) to get further community support.`,
|
||||
`| :warning: | Unsupported Version of React Native |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | It looks like your issue or the example you provided uses an [unsupported version of React Native](https://github.com/reactwg/react-native-releases/blob/main/README.md#releases-support-policy).<br/><br/>Due to the number of issues we receive, we're currently only accepting new issues against one of the supported versions. Please [upgrade](https://reactnative.dev/docs/upgrading) to latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If you cannot upgrade, please open your issue on [StackOverflow](https://stackoverflow.com/questions/tagged/react-native) to get further community support. |`,
|
||||
);
|
||||
await requestAuthorFeedback();
|
||||
return;
|
||||
case 'Type: Too Old Version':
|
||||
await addComment(
|
||||
`> [!CAUTION]\n` +
|
||||
`> **Too old version**: It looks like your issue or the example you provided uses a [**Too Old Version of React Native**](https://github.com/reactwg/react-native-releases/blob/main/docs/support.md).<br/><br/>Due to the number of issues we receive, we're currently only accepting new issues against one of the supported versions. Please [upgrade](https://reactnative.dev/docs/upgrading) to latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If you cannot upgrade, please open your issue on [StackOverflow](https://stackoverflow.com/questions/tagged/react-native) to get further community support.`,
|
||||
`| :warning: | Too Old Version of React Native |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | It looks like your issue or the example you provided uses a [**Too Old Version of React Native**](https://github.com/reactwg/react-native-releases/blob/main/README.md#releases-support-policy).<br/><br/>Due to the number of issues we receive, we're currently only accepting new issues against one of the supported versions. Please [upgrade](https://reactnative.dev/docs/upgrading) to latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If you cannot upgrade, please open your issue on [StackOverflow](https://stackoverflow.com/questions/tagged/react-native) to get further community support. |`,
|
||||
);
|
||||
await closeIssue();
|
||||
return;
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
|
||||
const NEEDS_REPRO_LABEL = 'Needs: Repro';
|
||||
const NEEDS_AUTHOR_FEEDBACK_LABEL = 'Needs: Author Feedback';
|
||||
const NEEDS_REPRO_HEADER = 'Missing Reproducible Example';
|
||||
const NEEDS_REPRO_MESSAGE =
|
||||
`| :warning: | Missing Reproducible Example |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | We could not detect a reproducible example in your issue report. Please provide either: <br /><ul><li>If your bug is UI related: a [Snack](https://snack.expo.dev)</li><li> If your bug is build/update related: use our [Reproducer Template](https://github.com/react-native-community/reproducer-react-native/generate). A reproducer needs to be in a GitHub repository under your username.</li></ul> |`;
|
||||
const SKIP_ISSUES_OLDER_THAN = '2023-07-01T00:00:00Z';
|
||||
|
||||
module.exports = async (github, context) => {
|
||||
@@ -20,6 +25,7 @@ module.exports = async (github, context) => {
|
||||
|
||||
const issue = await github.rest.issues.get(issueData);
|
||||
const comments = await github.rest.issues.listComments(issueData);
|
||||
|
||||
const author = issue.data.user.login;
|
||||
|
||||
const issueDate = issue.data.created_at;
|
||||
@@ -37,6 +43,10 @@ module.exports = async (github, context) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const botComment = comments.data.find(comment =>
|
||||
comment.body.includes(NEEDS_REPRO_HEADER),
|
||||
);
|
||||
|
||||
const entities = [issue.data, ...comments.data];
|
||||
|
||||
// Look for Snack or a GH repo associated with the user that added an issue or comment
|
||||
@@ -64,11 +74,25 @@ module.exports = async (github, context) => {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!botComment) return;
|
||||
|
||||
await github.rest.issues.deleteComment({
|
||||
...issueData,
|
||||
comment_id: botComment.id,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.addLabels({
|
||||
...issueData,
|
||||
labels: [NEEDS_REPRO_LABEL, NEEDS_AUTHOR_FEEDBACK_LABEL],
|
||||
});
|
||||
|
||||
if (botComment) return;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
...issueData,
|
||||
body: NEEDS_REPRO_MESSAGE,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,7 +101,7 @@ function containsPattern(body, pattern) {
|
||||
return body.search(regexp) !== -1;
|
||||
}
|
||||
|
||||
// Prevents the bot from responding when maintainer has changed the 'Needs: Repro' label
|
||||
// Prevents the bot from responding when maintainer has changed Needs: Repro the label
|
||||
async function hasMaintainerChangedLabel(github, issueData, author) {
|
||||
const timeline = await github.rest.issues.listEventsForTimeline(issueData);
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ module.exports.publishTemplate = async (github, version, dryRun = true) => {
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner: 'react-native-community',
|
||||
repo: 'template',
|
||||
workflow_id: 'release.yaml',
|
||||
workflow_id: 'release.yml',
|
||||
ref,
|
||||
inputs: {
|
||||
dry_run: dryRun,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
const {execSync} = require('child_process');
|
||||
|
||||
function run(cmd) {
|
||||
function run(...cmd) {
|
||||
return execSync(cmd, 'utf8').toString().trim();
|
||||
}
|
||||
module.exports.run = run;
|
||||
@@ -21,7 +21,7 @@ module.exports.sleep = sleep;
|
||||
|
||||
async function getNpmPackageInfo(pkg, versionOrTag) {
|
||||
return fetch(`https://registry.npmjs.org/${pkg}/${versionOrTag}`).then(resp =>
|
||||
resp.json(),
|
||||
res.json(),
|
||||
);
|
||||
}
|
||||
module.exports.getNpmPackageInfo = getNpmPackageInfo;
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
module.exports = async (github, context) => {
|
||||
const issue = context.payload.issue;
|
||||
|
||||
// Ignore issues using upgrade template (they use a special label)
|
||||
if (issue.labels.find(label => label.name === 'Type: Upgrade Issue')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const issueVersionUnparsed =
|
||||
getReactNativeVersionFromIssueBodyIfExists(issue);
|
||||
const issueVersion = parseVersionFromString(issueVersionUnparsed);
|
||||
|
||||
@@ -13,8 +13,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'facebook/react-native'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Check nightly
|
||||
run: |
|
||||
TODAY=$(date "+%Y%m%d")
|
||||
@@ -26,7 +24,3 @@ jobs:
|
||||
else
|
||||
echo 'Nightly Worked, All Good!'
|
||||
fi
|
||||
|
||||
test-libraries:
|
||||
uses: ./.github/workflows/test-libraries-on-nightlies.yml
|
||||
needs: check-nightly
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
name: "Validate Gradle Wrapper"
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: gradle/actions/wrapper-validation@v3
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavor: [Debug, Release]
|
||||
slice: [macosx, iphoneos, iphonesimulator, appletvos, appletvsimulator, catalyst, xros, xrsimulator]
|
||||
slice: [macosx, iphoneos, iphonesimulator, catalyst, xros, xrsimulator]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavor: [Debug, Release]
|
||||
slice: [macosx, iphoneos, iphonesimulator, appletvos, appletvsimulator, catalyst, xros, xrsimulator]
|
||||
slice: [macosx, iphoneos, iphonesimulator, catalyst, xros, xrsimulator]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -205,9 +205,9 @@ jobs:
|
||||
with:
|
||||
github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }}
|
||||
script: |
|
||||
const {verifyPublishedTemplate, isLatest} = require('./.github/workflow-scripts/publishTemplate.js')
|
||||
const {verifyPublished, isLatest} = require('./.github/workflow-scripts/publishTemplate.js')
|
||||
const version = "${{ github.ref_name }}"
|
||||
await verifyPublishedTemplate(version, isLatest());
|
||||
await verifyPublished(version, isLatest());
|
||||
- name: Update rn-diff-purge to generate upgrade-support diff
|
||||
run: |
|
||||
curl -X POST https://api.github.com/repos/react-native-community/rn-diff-purge/dispatches \
|
||||
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavor: [Debug, Release]
|
||||
slice: [macosx, iphoneos, iphonesimulator, appletvos, appletvsimulator, catalyst, xros, xrsimulator]
|
||||
slice: [macosx, iphoneos, iphonesimulator, catalyst, xros, xrsimulator]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -184,7 +184,7 @@ jobs:
|
||||
|
||||
test_e2e_ios_rntester:
|
||||
if: ${{ github.ref == 'refs/heads/main' || contains(github.ref, 'stable') || inputs.run-e2e-tests }}
|
||||
runs-on: macos-13-large
|
||||
runs-on: macos-13
|
||||
needs:
|
||||
[build_apple_slices_hermes, prepare_hermes_workspace, build_hermes_macos]
|
||||
env:
|
||||
@@ -222,7 +222,7 @@ jobs:
|
||||
|
||||
test_e2e_ios_templateapp:
|
||||
if: ${{ github.ref == 'refs/heads/main' || contains(github.ref, 'stable') || inputs.run-e2e-tests }}
|
||||
runs-on: macos-13-large
|
||||
runs-on: macos-13
|
||||
needs: build_npm_package
|
||||
env:
|
||||
HERMES_WS_DIR: /tmp/hermes
|
||||
@@ -421,9 +421,7 @@ jobs:
|
||||
run-e2e-tests: ${{ github.ref == 'refs/heads/main' || contains(github.ref, 'stable') || inputs.run-e2e-tests }}
|
||||
|
||||
test_e2e_android_rntester:
|
||||
# Temporarily disable RNTester tests on Android as they are quite flaky and they make CI always red
|
||||
# if: ${{ github.ref == 'refs/heads/main' || contains(github.ref, 'stable') || inputs.run-e2e-tests }}
|
||||
if: ${{ contains(github.ref, 'stable') || inputs.run-e2e-tests }}
|
||||
if: ${{ github.ref == 'refs/heads/main' || contains(github.ref, 'stable') || inputs.run-e2e-tests }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_android]
|
||||
strategy:
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
name: Test Libraries on Nightlies
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
test-library-on-nightly-android:
|
||||
name: "[Android] ${{ matrix.library }}"
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
library: [
|
||||
"react-native-async-storage",
|
||||
"react-native-blob-util",
|
||||
"@react-native-clipboard/clipboard",
|
||||
"@react-native-community/datetimepicker",
|
||||
"react-native-gesture-handler",
|
||||
"react-native-image-picker",
|
||||
"react-native-linear-gradient",
|
||||
"@react-native-masked-view/masked-view",
|
||||
"react-native-maps",
|
||||
"@react-native-community/netinfo",
|
||||
"react-native-reanimated",
|
||||
"react-native-svg",
|
||||
"react-native-video",
|
||||
"react-native-webview",
|
||||
"react-native-mmkv",
|
||||
"react-native-screens",
|
||||
"react-native-pager-view",
|
||||
"@react-native-community/slider"
|
||||
]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Test ${{ inputs.library-name }}
|
||||
uses: ./.github/actions/test-library-on-nightly
|
||||
with:
|
||||
library-npm-package: ${{ matrix.library }}
|
||||
platform: android
|
||||
|
||||
test-library-on-nightly-ios:
|
||||
name: "[iOS] ${{ matrix.library }}"
|
||||
runs-on: macos-13-large
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
library: [
|
||||
"react-native-async-storage",
|
||||
"react-native-blob-util",
|
||||
"@react-native-clipboard/clipboard",
|
||||
"@react-native-community/datetimepicker",
|
||||
"react-native-gesture-handler",
|
||||
"react-native-image-picker",
|
||||
"react-native-linear-gradient",
|
||||
"@react-native-masked-view/masked-view",
|
||||
"react-native-maps",
|
||||
"@react-native-community/netinfo",
|
||||
"react-native-reanimated",
|
||||
"react-native-svg",
|
||||
"react-native-video",
|
||||
"react-native-webview",
|
||||
"react-native-mmkv",
|
||||
"react-native-screens",
|
||||
"react-native-pager-view",
|
||||
"@react-native-community/slider"
|
||||
]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Test ${{ inputs.library-name }}
|
||||
uses: ./.github/actions/test-library-on-nightly
|
||||
with:
|
||||
library-npm-package: ${{ matrix.library }}
|
||||
platform: ios
|
||||
+1
-4
@@ -124,7 +124,7 @@ vendor/
|
||||
!/packages/rn-tester/Pods/__offline_mirrors_jsc__
|
||||
|
||||
# @react-native/codegen
|
||||
/packages/react-native/React/FBReactNativeSpec/
|
||||
/packages/react-native/React/FBReactNativeSpec/FBReactNativeSpec
|
||||
/packages/react-native-codegen/lib
|
||||
/packages/react-native-codegen/tmp/
|
||||
/packages/react-native/ReactCommon/react/renderer/components/rncore/
|
||||
@@ -155,6 +155,3 @@ vendor/
|
||||
|
||||
# CircleCI
|
||||
.circleci/generated_config.yml
|
||||
|
||||
# Jest Integration
|
||||
/jest/integration/build/
|
||||
|
||||
+399
-789
File diff suppressed because it is too large
Load Diff
@@ -5,4 +5,3 @@ ruby ">= 2.6.10"
|
||||
|
||||
gem 'cocoapods', '~> 1.13', '!= 1.15.0', '!= 1.15.1'
|
||||
gem 'activesupport', '>= 6.1.7.5', '< 7.1.0'
|
||||
gem 'xcodeproj', '< 1.26.0'
|
||||
|
||||
@@ -87,7 +87,7 @@ The full documentation for React Native can be found on our [website][docs].
|
||||
|
||||
The React Native documentation discusses components, APIs, and topics that are specific to React Native. For further documentation on the React API that is shared between React Native and React DOM, refer to the [React documentation][r-docs].
|
||||
|
||||
The source for the React Native documentation and website is hosted on a separate repository, [**@facebook/react-native-website**][repo-website].
|
||||
The source for the React Native documentation and website is hosted on a separate repo, [**@facebook/react-native-website**][repo-website].
|
||||
|
||||
[docs]: https://reactnative.dev/docs/getting-started
|
||||
[r-docs]: https://react.dev/learn
|
||||
|
||||
Vendored
-17
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
declare module 'deep-equal' {
|
||||
declare module.exports: (
|
||||
actual: mixed,
|
||||
expected: mixed,
|
||||
options?: {strict: boolean},
|
||||
) => boolean;
|
||||
}
|
||||
Vendored
-40
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
|
||||
*
|
||||
* @flow strict
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
declare module 'jest-diff' {
|
||||
import type {CompareKeys} from 'pretty-format';
|
||||
|
||||
declare export type DiffOptionsColor = (arg: string) => string; // subset of Chalk type
|
||||
|
||||
declare export type DiffOptions = {
|
||||
aAnnotation?: string,
|
||||
aColor?: DiffOptionsColor,
|
||||
aIndicator?: string,
|
||||
bAnnotation?: string,
|
||||
bColor?: DiffOptionsColor,
|
||||
bIndicator?: string,
|
||||
changeColor?: DiffOptionsColor,
|
||||
changeLineTrailingSpaceColor?: DiffOptionsColor,
|
||||
commonColor?: DiffOptionsColor,
|
||||
commonIndicator?: string,
|
||||
commonLineTrailingSpaceColor?: DiffOptionsColor,
|
||||
contextLines?: number,
|
||||
emptyFirstOrLastLinePlaceholder?: string,
|
||||
expand?: boolean,
|
||||
includeChangeCounts?: boolean,
|
||||
omitAnnotationLines?: boolean,
|
||||
patchColor?: DiffOptionsColor,
|
||||
compareKeys?: CompareKeys,
|
||||
};
|
||||
|
||||
declare export function diff(
|
||||
a: mixed,
|
||||
b: mixed,
|
||||
options?: DiffOptions,
|
||||
): string | null;
|
||||
}
|
||||
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
declare module '@pkgjs/parseargs' {
|
||||
declare export function parseArgs<
|
||||
TOptions: {[string]: util$ParseArgsOption} = {},
|
||||
>(config: {
|
||||
args?: Array<string>,
|
||||
options?: TOptions,
|
||||
strict?: boolean,
|
||||
allowPositionals?: boolean,
|
||||
tokens?: false,
|
||||
}): {
|
||||
values: util$ParseArgsOptionsToValues<TOptions>,
|
||||
positionals: Array<string>,
|
||||
};
|
||||
|
||||
declare export function parseArgs<
|
||||
TOptions: {[string]: util$ParseArgsOption} = {},
|
||||
>(config: {
|
||||
args?: Array<string>,
|
||||
options?: TOptions,
|
||||
strict?: boolean,
|
||||
allowPositionals?: boolean,
|
||||
tokens: true,
|
||||
}): {
|
||||
values: util$ParseArgsOptionsToValues<TOptions>,
|
||||
positionals: Array<string>,
|
||||
tokens: Array<util$ParseArgsToken>,
|
||||
};
|
||||
}
|
||||
+1
-4
@@ -19,6 +19,7 @@ declare type Colors = {
|
||||
tag: {close: string, open: string},
|
||||
value: {close: string, open: string},
|
||||
};
|
||||
declare type CompareKeys = ((a: string, b: string) => number) | null | void;
|
||||
|
||||
declare type PrettyFormatPlugin =
|
||||
| {
|
||||
@@ -37,10 +38,6 @@ declare type PrettyFormatPlugin =
|
||||
};
|
||||
|
||||
declare module 'pretty-format' {
|
||||
declare export type CompareKeys =
|
||||
| ((a: string, b: string) => number)
|
||||
| null
|
||||
| void;
|
||||
declare export function format(
|
||||
value: mixed,
|
||||
options?: ?{
|
||||
|
||||
Vendored
-20
@@ -16,13 +16,6 @@ declare interface undici$Agent$Options {
|
||||
}
|
||||
|
||||
declare module 'undici' {
|
||||
declare export type RequestOptions = $ReadOnly<{
|
||||
dispatcher?: Dispatcher,
|
||||
method?: string,
|
||||
headers?: HeadersInit,
|
||||
...
|
||||
}>;
|
||||
|
||||
declare export class Dispatcher extends events$EventEmitter {
|
||||
constructor(): void;
|
||||
}
|
||||
@@ -30,17 +23,4 @@ declare module 'undici' {
|
||||
declare export class Agent extends Dispatcher {
|
||||
constructor(opts?: undici$Agent$Options): void;
|
||||
}
|
||||
|
||||
declare export function request(
|
||||
url: string | URL,
|
||||
options: RequestOptions,
|
||||
): Promise<{
|
||||
statusCode: number,
|
||||
headers: Headers,
|
||||
body: {
|
||||
read(): Promise<Buffer>,
|
||||
...
|
||||
},
|
||||
...
|
||||
}>;
|
||||
}
|
||||
|
||||
Vendored
-1
@@ -323,7 +323,6 @@ declare module "yargs" {
|
||||
updateStrings(obj: { [key: string]: string, ... }): this;
|
||||
|
||||
usage(message: string, opts?: { [key: string]: Options, ... }): this;
|
||||
usage(message: string, desc?: string, builder: CommonModuleObject["builder"], handler: CommonModuleObject["handler"]): this;
|
||||
|
||||
version(): this;
|
||||
version(version: string | false): this;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
getHasteName(filePath) {
|
||||
if (filePath.endsWith('ReactNativeInternalFeatureFlags.js')) {
|
||||
return 'ReactNativeInternalFeatureFlags';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
getCacheKey() {
|
||||
return crypto
|
||||
.createHash('sha1')
|
||||
.update(fs.readFileSync(__filename))
|
||||
.digest('hex');
|
||||
},
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const baseConfig = require('../../../jest.config');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
rootDir: path.resolve(__dirname, '../../..'),
|
||||
roots: [
|
||||
'<rootDir>/packages/react-native',
|
||||
'<rootDir>/jest/integration/runtime',
|
||||
],
|
||||
moduleFileExtensions: [...baseConfig.moduleFileExtensions, 'cpp', 'h'],
|
||||
// This allows running Meta-internal tests with the `-test.fb.js` suffix.
|
||||
testRegex: '/__tests__/.*-itest(\\.fb)?\\.js$',
|
||||
testPathIgnorePatterns: baseConfig.testPathIgnorePatterns,
|
||||
transformIgnorePatterns: ['.*'],
|
||||
testRunner: './jest/integration/runner/index.js',
|
||||
watchPathIgnorePatterns: ['<rootDir>/jest/integration/build/'],
|
||||
globalSetup: './jest/integration/runner/warmup/index.js',
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
require('../../../scripts/build/babel-register').registerForMonorepo();
|
||||
module.exports = require('@react-native/metro-babel-transformer');
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {getDefaultConfig} = require('@react-native/metro-config');
|
||||
const {mergeConfig} = require('metro-config');
|
||||
const path = require('path');
|
||||
|
||||
const rnTesterConfig = getDefaultConfig(
|
||||
path.resolve('../../../packages/rn-tester'),
|
||||
);
|
||||
|
||||
const JS_DIR = process.env.JS_DIR
|
||||
? path.resolve(process.cwd(), process.env.JS_DIR)
|
||||
: null;
|
||||
|
||||
const config = {
|
||||
projectRoot: path.resolve(__dirname, '../../..'),
|
||||
reporter: {
|
||||
update: () => {},
|
||||
},
|
||||
resolver: {
|
||||
blockList: /\/RendererProxy\.fb\.js$/, // Disable dependency injection for the renderer
|
||||
disableHierarchicalLookup: !!JS_DIR,
|
||||
sourceExts: ['fb.js', ...rnTesterConfig.resolver.sourceExts],
|
||||
nodeModulesPaths: JS_DIR
|
||||
? [path.join(JS_DIR, 'public', 'node_modules')]
|
||||
: [],
|
||||
hasteImplModulePath: path.resolve(__dirname, 'hasteImpl.js'),
|
||||
},
|
||||
transformer: {
|
||||
// We need to wrap the default transformer so we can run it from source
|
||||
// using babel-register.
|
||||
babelTransformerPath: path.resolve(__dirname, 'metro-babel-transformer.js'),
|
||||
},
|
||||
watchFolders: JS_DIR
|
||||
? [
|
||||
path.join(JS_DIR, 'RKJSModules', 'vendor', 'react'),
|
||||
path.join(JS_DIR, 'tools', 'metro'),
|
||||
path.join(JS_DIR, 'node_modules'),
|
||||
path.join(JS_DIR, 'public', 'node_modules'),
|
||||
]
|
||||
: [],
|
||||
};
|
||||
|
||||
module.exports = mergeConfig(rnTesterConfig, config);
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
module.exports = function entrypointTemplate({
|
||||
testPath,
|
||||
setupModulePath,
|
||||
}: {
|
||||
testPath: string,
|
||||
setupModulePath: string,
|
||||
}): string {
|
||||
return `/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* ${'@'}generated
|
||||
* @noformat
|
||||
* @noflow
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import {registerTest} from '${setupModulePath}';
|
||||
|
||||
registerTest(() => require('${testPath}'));
|
||||
`;
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
require('../../../scripts/build/babel-register').registerForMonorepo();
|
||||
|
||||
module.exports = require('./runner');
|
||||
@@ -1,229 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {TestSuiteResult} from '../runtime/setup';
|
||||
|
||||
import entrypointTemplate from './entrypoint-template';
|
||||
import {
|
||||
getBuckModeForPlatform,
|
||||
getDebugInfoFromCommandResult,
|
||||
getFantomTestConfig,
|
||||
getShortHash,
|
||||
runBuck2,
|
||||
symbolicateStackTrace,
|
||||
} from './utils';
|
||||
import fs from 'fs';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import {formatResultsErrors} from 'jest-message-util';
|
||||
import Metro from 'metro';
|
||||
import nullthrows from 'nullthrows';
|
||||
import path from 'path';
|
||||
|
||||
const BUILD_OUTPUT_PATH = path.resolve(__dirname, '..', 'build');
|
||||
|
||||
const PRINT_FANTOM_OUTPUT: false = false;
|
||||
|
||||
function parseRNTesterCommandResult(result: ReturnType<typeof runBuck2>): {
|
||||
logs: string,
|
||||
testResult: TestSuiteResult,
|
||||
} {
|
||||
const stdout = result.stdout.toString();
|
||||
|
||||
const outputArray = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(log => !log.startsWith('Running "')); // remove AppRegistry logs.
|
||||
|
||||
// The last line should be the test output in JSON format
|
||||
const testResultJSON = outputArray.pop();
|
||||
|
||||
let testResult;
|
||||
try {
|
||||
testResult = JSON.parse(nullthrows(testResultJSON));
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
'Failed to parse test results from RN tester binary result.\n' +
|
||||
getDebugInfoFromCommandResult(result),
|
||||
);
|
||||
}
|
||||
|
||||
return {logs: outputArray.join('\n'), testResult};
|
||||
}
|
||||
|
||||
function generateBytecodeBundle({
|
||||
sourcePath,
|
||||
bytecodePath,
|
||||
isOptimizedMode,
|
||||
}: {
|
||||
sourcePath: string,
|
||||
bytecodePath: string,
|
||||
isOptimizedMode: boolean,
|
||||
}): void {
|
||||
const hermesCompilerCommandResult = runBuck2([
|
||||
'run',
|
||||
getBuckModeForPlatform(isOptimizedMode),
|
||||
'//xplat/hermes/tools/hermesc:hermesc',
|
||||
'--',
|
||||
'-emit-binary',
|
||||
'-O',
|
||||
'-max-diagnostic-width',
|
||||
'80',
|
||||
'-out',
|
||||
bytecodePath,
|
||||
sourcePath,
|
||||
]);
|
||||
|
||||
if (hermesCompilerCommandResult.status !== 0) {
|
||||
throw new Error(getDebugInfoFromCommandResult(hermesCompilerCommandResult));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = async function runTest(
|
||||
globalConfig: {...},
|
||||
config: {...},
|
||||
environment: {...},
|
||||
runtime: {...},
|
||||
testPath: string,
|
||||
): mixed {
|
||||
const startTime = Date.now();
|
||||
|
||||
const testConfig = getFantomTestConfig(testPath);
|
||||
|
||||
const isOptimizedMode = testConfig.mode === 'opt';
|
||||
|
||||
const metroConfig = await Metro.loadConfig({
|
||||
config: path.resolve(__dirname, '..', 'config', 'metro.config.js'),
|
||||
});
|
||||
|
||||
const setupModulePath = path.resolve(__dirname, '../runtime/setup.js');
|
||||
|
||||
const entrypointContents = entrypointTemplate({
|
||||
testPath: `${path.relative(BUILD_OUTPUT_PATH, testPath)}`,
|
||||
setupModulePath: `${path.relative(BUILD_OUTPUT_PATH, setupModulePath)}`,
|
||||
});
|
||||
|
||||
const entrypointPath = path.join(
|
||||
BUILD_OUTPUT_PATH,
|
||||
`${getShortHash(entrypointContents)}-${path.basename(testPath)}`,
|
||||
);
|
||||
const testBundlePath = entrypointPath + '.bundle';
|
||||
const testJSBundlePath = testBundlePath + '.js';
|
||||
const testBytecodeBundlePath = testJSBundlePath + '.hbc';
|
||||
|
||||
fs.mkdirSync(path.dirname(entrypointPath), {recursive: true});
|
||||
fs.writeFileSync(entrypointPath, entrypointContents, 'utf8');
|
||||
|
||||
const sourceMapPath = path.join(
|
||||
path.dirname(testJSBundlePath),
|
||||
path.basename(testJSBundlePath, '.js') + '.map',
|
||||
);
|
||||
|
||||
await Metro.runBuild(metroConfig, {
|
||||
entry: entrypointPath,
|
||||
out: testJSBundlePath,
|
||||
platform: 'android',
|
||||
minify: isOptimizedMode,
|
||||
dev: !isOptimizedMode,
|
||||
sourceMap: true,
|
||||
sourceMapUrl: sourceMapPath,
|
||||
});
|
||||
|
||||
if (isOptimizedMode) {
|
||||
generateBytecodeBundle({
|
||||
sourcePath: testJSBundlePath,
|
||||
bytecodePath: testBytecodeBundlePath,
|
||||
isOptimizedMode,
|
||||
});
|
||||
}
|
||||
|
||||
const rnTesterCommandResult = runBuck2([
|
||||
'run',
|
||||
getBuckModeForPlatform(isOptimizedMode),
|
||||
'//xplat/ReactNative/react-native-cxx/samples/tester:tester',
|
||||
'--',
|
||||
'--bundlePath',
|
||||
testBundlePath,
|
||||
]);
|
||||
|
||||
if (rnTesterCommandResult.status !== 0) {
|
||||
throw new Error(getDebugInfoFromCommandResult(rnTesterCommandResult));
|
||||
}
|
||||
|
||||
if (PRINT_FANTOM_OUTPUT) {
|
||||
console.log(getDebugInfoFromCommandResult(rnTesterCommandResult));
|
||||
}
|
||||
|
||||
const rnTesterParsedOutput = parseRNTesterCommandResult(
|
||||
rnTesterCommandResult,
|
||||
);
|
||||
|
||||
const testResultError = rnTesterParsedOutput.testResult.error;
|
||||
if (testResultError) {
|
||||
const error = new Error(testResultError.message);
|
||||
error.stack = symbolicateStackTrace(sourceMapPath, testResultError.stack);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const endTime = Date.now();
|
||||
|
||||
if (process.env.SANDCASTLE == null) {
|
||||
console.log(rnTesterParsedOutput.logs);
|
||||
}
|
||||
|
||||
const testResults =
|
||||
nullthrows(rnTesterParsedOutput.testResult.testResults).map(testResult => ({
|
||||
ancestorTitles: [] as Array<string>,
|
||||
failureDetails: [] as Array<string>,
|
||||
testFilePath: testPath,
|
||||
...testResult,
|
||||
failureMessages: testResult.failureMessages.map(maybeStackTrace =>
|
||||
symbolicateStackTrace(sourceMapPath, maybeStackTrace),
|
||||
),
|
||||
})) ?? [];
|
||||
|
||||
return {
|
||||
testFilePath: testPath,
|
||||
failureMessage: formatResultsErrors(
|
||||
testResults,
|
||||
config,
|
||||
globalConfig,
|
||||
testPath,
|
||||
),
|
||||
leaks: false,
|
||||
openHandles: [],
|
||||
perfStats: {
|
||||
start: startTime,
|
||||
end: endTime,
|
||||
duration: endTime - startTime,
|
||||
runtime: endTime - startTime,
|
||||
slow: false,
|
||||
},
|
||||
snapshot: {
|
||||
added: 0,
|
||||
fileDeleted: false,
|
||||
matched: 0,
|
||||
unchecked: 0,
|
||||
uncheckedKeys: [],
|
||||
unmatched: 0,
|
||||
updated: 0,
|
||||
},
|
||||
numTotalTests: testResults.length,
|
||||
numPassingTests: testResults.filter(test => test.status === 'passed')
|
||||
.length,
|
||||
numFailingTests: testResults.filter(test => test.status === 'failed')
|
||||
.length,
|
||||
numPendingTests: testResults.filter(test => test.status === 'pending')
|
||||
.length,
|
||||
numTodoTests: 0,
|
||||
skipped: false,
|
||||
testResults,
|
||||
};
|
||||
};
|
||||
@@ -1,158 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import {spawnSync} from 'child_process';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import {extract, parse} from 'jest-docblock';
|
||||
import os from 'os';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import {SourceMapConsumer} from 'source-map';
|
||||
|
||||
type DocblockPragmas = {[key: string]: string | string[]};
|
||||
type FantomTestMode = 'dev' | 'opt';
|
||||
type FantomTestConfig = {
|
||||
mode: FantomTestMode,
|
||||
};
|
||||
|
||||
const DEFAULT_MODE: FantomTestMode = 'dev';
|
||||
|
||||
/**
|
||||
* Extracts the Fantom configuration from the test file, specified as part of
|
||||
* the docblock comment. E.g.:
|
||||
*
|
||||
* ```
|
||||
* /**
|
||||
* * @flow strict-local
|
||||
* * @fantom mode:opt
|
||||
* *
|
||||
* ```
|
||||
*
|
||||
* So far the only supported option is `mode`, which can be 'dev' or 'opt'.
|
||||
*/
|
||||
export function getFantomTestConfig(testPath: string): FantomTestConfig {
|
||||
const docblock = extract(fs.readFileSync(testPath, 'utf8'));
|
||||
const pragmas = parse(docblock) as DocblockPragmas;
|
||||
|
||||
const config = {
|
||||
mode: DEFAULT_MODE,
|
||||
};
|
||||
|
||||
const maybeMode = pragmas.fantom_mode;
|
||||
|
||||
if (maybeMode != null) {
|
||||
if (Array.isArray(maybeMode)) {
|
||||
throw new Error('Expected a single value for @fantom_mode');
|
||||
}
|
||||
|
||||
const mode = maybeMode;
|
||||
|
||||
if (mode === 'dev' || mode === 'opt') {
|
||||
config.mode = mode;
|
||||
} else {
|
||||
throw new Error(`Invalid Fantom mode: ${mode}`);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function getBuckModeForPlatform(enableRelease: boolean = false): string {
|
||||
const mode = enableRelease ? 'opt' : 'dev';
|
||||
|
||||
switch (os.platform()) {
|
||||
case 'linux':
|
||||
return `@//arvr/mode/linux/${mode}`;
|
||||
case 'darwin':
|
||||
return os.arch() === 'arm64'
|
||||
? `@//arvr/mode/mac-arm/${mode}`
|
||||
: `@//arvr/mode/mac/${mode}`;
|
||||
case 'win32':
|
||||
return `@//arvr/mode/win/${mode}`;
|
||||
default:
|
||||
throw new Error(`Unsupported platform: ${os.platform()}`);
|
||||
}
|
||||
}
|
||||
|
||||
type SpawnResultWithOriginalCommand = {
|
||||
...ReturnType<typeof spawnSync>,
|
||||
originalCommand: string,
|
||||
...
|
||||
};
|
||||
|
||||
export function runBuck2(args: Array<string>): SpawnResultWithOriginalCommand {
|
||||
const result = spawnSync('buck2', args, {
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `/usr/local/bin:${process.env.PATH ?? ''}`,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
originalCommand: `buck2 ${args.join(' ')}`,
|
||||
};
|
||||
}
|
||||
|
||||
export function getDebugInfoFromCommandResult(
|
||||
commandResult: SpawnResultWithOriginalCommand,
|
||||
): string {
|
||||
const logLines = [
|
||||
`Command ${commandResult.status === 0 ? 'succeeded' : 'failed'}: ${commandResult.originalCommand}`,
|
||||
'',
|
||||
'stdout:',
|
||||
commandResult.stdout,
|
||||
'',
|
||||
'stderr:',
|
||||
commandResult.stderr,
|
||||
];
|
||||
|
||||
if (commandResult.error) {
|
||||
logLines.push('', 'error:', String(commandResult.error));
|
||||
}
|
||||
|
||||
return logLines.join('\n');
|
||||
}
|
||||
|
||||
export function getShortHash(contents: string): string {
|
||||
return crypto.createHash('md5').update(contents).digest('hex').slice(0, 8);
|
||||
}
|
||||
|
||||
export function symbolicateStackTrace(
|
||||
sourceMapPath: string,
|
||||
stackTrace: string,
|
||||
): string {
|
||||
const sourceMapData = JSON.parse(fs.readFileSync(sourceMapPath, 'utf8'));
|
||||
const consumer = new SourceMapConsumer(sourceMapData);
|
||||
|
||||
return stackTrace
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
const match = line.match(/at (.*) \((.*):(\d+):(\d+)\)/);
|
||||
if (match) {
|
||||
const functionName = match[1];
|
||||
// const fileName = match[2];
|
||||
const lineNumber = parseInt(match[3], 10);
|
||||
const columnNumber = parseInt(match[4], 10);
|
||||
// Get the original position
|
||||
const originalPosition = consumer.originalPositionFor({
|
||||
line: lineNumber,
|
||||
column: columnNumber,
|
||||
});
|
||||
return `at ${originalPosition.name ?? functionName} (${originalPosition.source}:${originalPosition.line}:${originalPosition.column})`;
|
||||
} else {
|
||||
return line;
|
||||
}
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
require('../../../../scripts/build/babel-register').registerForMonorepo();
|
||||
|
||||
module.exports = require('./warmup');
|
||||
@@ -1,97 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import {
|
||||
getBuckModeForPlatform,
|
||||
getDebugInfoFromCommandResult,
|
||||
runBuck2,
|
||||
} from '../utils';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import fs from 'fs';
|
||||
import Metro from 'metro';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
export default async function warmUp(
|
||||
globalConfig: {...},
|
||||
projectConfig: {...},
|
||||
): Promise<void> {
|
||||
try {
|
||||
warmUpHermesCompiler();
|
||||
warmUpRNTesterCLI();
|
||||
await warmUpMetro();
|
||||
} catch (e) {
|
||||
// Sandcastle fails to parse the test output if we log stuff to stdout/stderr.
|
||||
if ((process.env.SANDCASTLE ?? '') !== '') {
|
||||
console.error(
|
||||
'Global warmup failed. Tests will continue to run but will likely fail. Details:\n',
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function warmUpMetro(): Promise<void> {
|
||||
const metroConfig = await Metro.loadConfig({
|
||||
config: path.resolve(__dirname, '..', '..', 'config', 'metro.config.js'),
|
||||
});
|
||||
|
||||
const entrypointPath = path.resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'runtime',
|
||||
'WarmUpEntryPoint.js',
|
||||
);
|
||||
|
||||
const bundlePath = path.join(
|
||||
os.tmpdir(),
|
||||
`fantom-warmup-bundle-${Date.now()}.js`,
|
||||
);
|
||||
|
||||
await Metro.runBuild(metroConfig, {
|
||||
entry: entrypointPath,
|
||||
out: bundlePath,
|
||||
platform: 'android',
|
||||
minify: false,
|
||||
dev: true,
|
||||
});
|
||||
|
||||
try {
|
||||
fs.unlinkSync(bundlePath);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function warmUpHermesCompiler(): void {
|
||||
const buildHermesCompilerCommandResult = runBuck2([
|
||||
'build',
|
||||
getBuckModeForPlatform(),
|
||||
'//xplat/hermes/tools/hermesc:hermesc',
|
||||
]);
|
||||
|
||||
if (buildHermesCompilerCommandResult.status !== 0) {
|
||||
throw new Error(
|
||||
getDebugInfoFromCommandResult(buildHermesCompilerCommandResult),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function warmUpRNTesterCLI(): void {
|
||||
const buildRNTesterCommandResult = runBuck2([
|
||||
'build',
|
||||
getBuckModeForPlatform(),
|
||||
'//xplat/ReactNative/react-native-cxx/samples/tester:tester',
|
||||
]);
|
||||
|
||||
if (buildRNTesterCommandResult.status !== 0) {
|
||||
throw new Error(getDebugInfoFromCommandResult(buildRNTesterCommandResult));
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is just an entrypoint to warm up the Metro cache before the tests run.
|
||||
*/
|
||||
|
||||
import 'react-native/Libraries/Core/InitializeCore.js';
|
||||
import 'react-native/src/private/__tests__/ReactNativeTester';
|
||||
import './setup';
|
||||
@@ -1,451 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import deepEqual from 'deep-equal';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
export type TestCaseResult = {
|
||||
ancestorTitles: Array<string>,
|
||||
title: string,
|
||||
fullName: string,
|
||||
status: 'passed' | 'failed' | 'pending',
|
||||
duration: number,
|
||||
failureMessages: Array<string>,
|
||||
numPassingAsserts: number,
|
||||
// location: string,
|
||||
};
|
||||
|
||||
export type TestSuiteResult =
|
||||
| {
|
||||
testResults: Array<TestCaseResult>,
|
||||
}
|
||||
| {
|
||||
error: {
|
||||
message: string,
|
||||
stack: string,
|
||||
},
|
||||
};
|
||||
|
||||
const tests: Array<{
|
||||
title: string,
|
||||
ancestorTitles: Array<string>,
|
||||
implementation: () => mixed,
|
||||
isFocused: boolean,
|
||||
isSkipped: boolean,
|
||||
result?: TestCaseResult,
|
||||
}> = [];
|
||||
|
||||
const ancestorTitles: Array<string> = [];
|
||||
|
||||
const globalModifiers: Array<'focused' | 'skipped'> = [];
|
||||
|
||||
const globalDescribe = (global.describe = (
|
||||
title: string,
|
||||
implementation: () => mixed,
|
||||
) => {
|
||||
ancestorTitles.push(title);
|
||||
implementation();
|
||||
ancestorTitles.pop();
|
||||
});
|
||||
|
||||
const globalIt =
|
||||
(global.it =
|
||||
global.test =
|
||||
(title: string, implementation: () => mixed) =>
|
||||
tests.push({
|
||||
title,
|
||||
implementation,
|
||||
ancestorTitles: ancestorTitles.slice(),
|
||||
isFocused:
|
||||
globalModifiers.length > 0 &&
|
||||
globalModifiers[globalModifiers.length - 1] === 'focused',
|
||||
isSkipped:
|
||||
globalModifiers.length > 0 &&
|
||||
globalModifiers[globalModifiers.length - 1] === 'skipped',
|
||||
}));
|
||||
|
||||
// $FlowExpectedError[prop-missing]
|
||||
global.fdescribe = global.describe.only = (
|
||||
title: string,
|
||||
implementation: () => mixed,
|
||||
) => {
|
||||
globalModifiers.push('focused');
|
||||
globalDescribe(title, implementation);
|
||||
globalModifiers.pop();
|
||||
};
|
||||
|
||||
// $FlowExpectedError[prop-missing]
|
||||
global.it.only =
|
||||
global.fit =
|
||||
// $FlowExpectedError[prop-missing]
|
||||
global.test.only =
|
||||
(title: string, implementation: () => mixed) => {
|
||||
globalModifiers.push('focused');
|
||||
globalIt(title, implementation);
|
||||
globalModifiers.pop();
|
||||
};
|
||||
|
||||
// $FlowExpectedError[prop-missing]
|
||||
global.xdescribe = global.describe.skip = (
|
||||
title: string,
|
||||
implementation: () => mixed,
|
||||
) => {
|
||||
globalModifiers.push('skipped');
|
||||
globalDescribe(title, implementation);
|
||||
globalModifiers.pop();
|
||||
};
|
||||
|
||||
// $FlowExpectedError[prop-missing]
|
||||
global.it.skip =
|
||||
global.xit =
|
||||
// $FlowExpectedError[prop-missing]
|
||||
global.test.skip =
|
||||
global.xtest =
|
||||
(title: string, implementation: () => mixed) => {
|
||||
globalModifiers.push('skipped');
|
||||
globalIt(title, implementation);
|
||||
globalModifiers.pop();
|
||||
};
|
||||
|
||||
global.jest = {
|
||||
fn: createMockFunction,
|
||||
};
|
||||
|
||||
const MOCK_FN_TAG = Symbol('mock function');
|
||||
|
||||
function createMockFunction<TArgs: $ReadOnlyArray<mixed>, TReturn>(
|
||||
initialImplementation?: (...TArgs) => TReturn,
|
||||
): JestMockFn<TArgs, TReturn> {
|
||||
let implementation: ?(...TArgs) => TReturn = initialImplementation;
|
||||
|
||||
const mock: JestMockFn<TArgs, TReturn>['mock'] = {
|
||||
calls: [],
|
||||
// $FlowExpectedError[incompatible-type]
|
||||
lastCall: undefined,
|
||||
instances: [],
|
||||
contexts: [],
|
||||
results: [],
|
||||
};
|
||||
|
||||
const mockFunction = function (this: mixed, ...args: TArgs): TReturn {
|
||||
let result: JestMockFn<TArgs, TReturn>['mock']['results'][number] = {
|
||||
isThrow: false,
|
||||
// $FlowExpectedError[incompatible-type]
|
||||
value: undefined,
|
||||
};
|
||||
|
||||
if (implementation != null) {
|
||||
try {
|
||||
result.value = implementation.apply(this, args);
|
||||
} catch (error) {
|
||||
result.isThrow = true;
|
||||
result.value = error;
|
||||
}
|
||||
}
|
||||
|
||||
mock.calls.push(args);
|
||||
mock.lastCall = args;
|
||||
// $FlowExpectedError[incompatible-call]
|
||||
mock.instances.push(new.target ? this : undefined);
|
||||
mock.contexts.push(this);
|
||||
mock.results.push(result);
|
||||
|
||||
if (result.isThrow) {
|
||||
throw result.value;
|
||||
}
|
||||
|
||||
return result.value;
|
||||
};
|
||||
|
||||
mockFunction.mock = mock;
|
||||
// $FlowExpectedError[invalid-computed-prop]
|
||||
mockFunction[MOCK_FN_TAG] = true;
|
||||
|
||||
// $FlowExpectedError[prop-missing]
|
||||
return mockFunction;
|
||||
}
|
||||
|
||||
// flowlint unsafe-getters-setters:off
|
||||
|
||||
class ErrorWithCustomBlame extends Error {
|
||||
// Initially 5 to ignore all the frames from Babel helpers to instantiate this
|
||||
// custom error class.
|
||||
#ignoredFrameCount: number = 5;
|
||||
#cachedProcessedStack: ?string;
|
||||
|
||||
blameToPreviousFrame(): this {
|
||||
this.#ignoredFrameCount++;
|
||||
return this;
|
||||
}
|
||||
|
||||
get stack(): string {
|
||||
if (this.#cachedProcessedStack == null) {
|
||||
const originalStack = super.stack;
|
||||
|
||||
if (originalStack == null) {
|
||||
this.#cachedProcessedStack = originalStack;
|
||||
} else {
|
||||
const lines = originalStack.split('\n');
|
||||
const index = lines.findIndex(line =>
|
||||
/at (.*) \((.*):(\d+):(\d+)\)/.test(line),
|
||||
);
|
||||
lines.splice(index > -1 ? index : 1, this.#ignoredFrameCount);
|
||||
this.#cachedProcessedStack = lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
return this.#cachedProcessedStack;
|
||||
}
|
||||
|
||||
set stack(value: string) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
class Expect {
|
||||
#received: mixed;
|
||||
#isNot: boolean = false;
|
||||
|
||||
constructor(received: mixed) {
|
||||
this.#received = received;
|
||||
}
|
||||
|
||||
get not(): this {
|
||||
this.#isNot = !this.#isNot;
|
||||
return this;
|
||||
}
|
||||
|
||||
toEqual(expected: mixed): void {
|
||||
const pass = deepEqual(this.#received, expected, {strict: true});
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected${this.#maybeNotLabel()} to equal ${String(expected)} but received ${String(this.#received)}.`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBe(expected: mixed): void {
|
||||
const pass = this.#received === expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected${this.#maybeNotLabel()} ${String(expected)} but received ${String(this.#received)}.`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeInstanceOf(expected: Class<mixed>): void {
|
||||
const pass = this.#received instanceof expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`expected ${String(this.#received)}${this.#maybeNotLabel()} to be an instance of ${String(expected)}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeCloseTo(expected: number, precision: number = 2): void {
|
||||
const pass =
|
||||
Math.abs(expected - Number(this.#received)) < Math.pow(10, -precision);
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be close to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeNull(): void {
|
||||
const pass = this.#received == null;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be null`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toThrow(expected?: string): void {
|
||||
if (expected != null && typeof expected !== 'string') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
'toThrow() implementation only accepts strings as arguments.',
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
let pass = false;
|
||||
try {
|
||||
// $FlowExpectedError[not-a-function]
|
||||
this.#received();
|
||||
} catch (error) {
|
||||
pass = expected != null ? error.message === expected : true;
|
||||
}
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to throw`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toHaveBeenCalled(): void {
|
||||
const mock = this.#requireMock();
|
||||
const pass = mock.calls.length > 0;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called, but it was${this.#isNot ? '' : "n't"}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toHaveBeenCalledTimes(times: number): void {
|
||||
const mock = this.#requireMock();
|
||||
const pass = mock.calls.length === times;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called ${times} times, but it was called ${mock.calls.length} times`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeGreaterThanOrEqual(expected: number): void {
|
||||
if (typeof this.#received !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a number but it was a ${typeof this.#received}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
if (typeof expected !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(expected)} to be a number but it was a ${typeof expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
const pass = this.#received >= expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be greater than or equal to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeLessThanOrEqual(expected: number): void {
|
||||
if (typeof this.#received !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a number but it was a ${typeof this.#received}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
if (typeof expected !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(expected)} to be a number but it was a ${typeof expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
const pass = this.#received <= expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be less than or equal to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
#isExpectedResult(pass: boolean): boolean {
|
||||
return this.#isNot ? !pass : pass;
|
||||
}
|
||||
|
||||
#maybeNotLabel(): string {
|
||||
return this.#isNot ? ' not' : '';
|
||||
}
|
||||
|
||||
#requireMock(): JestMockFn<$ReadOnlyArray<mixed>, mixed>['mock'] {
|
||||
// $FlowExpectedError[incompatible-use]
|
||||
if (!this.#received?.[MOCK_FN_TAG]) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a mock function, but it wasn't`,
|
||||
)
|
||||
.blameToPreviousFrame()
|
||||
.blameToPreviousFrame();
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-use]
|
||||
return this.#received.mock;
|
||||
}
|
||||
}
|
||||
|
||||
global.expect = (received: mixed) => new Expect(received);
|
||||
|
||||
function runWithGuard(fn: () => void) {
|
||||
try {
|
||||
fn();
|
||||
} catch (error) {
|
||||
let reportedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
reportTestSuiteResult({
|
||||
error: {
|
||||
message: reportedError.message,
|
||||
stack: reportedError.stack,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function executeTests() {
|
||||
const hasFocusedTests = tests.some(test => test.isFocused);
|
||||
|
||||
for (const test of tests) {
|
||||
const result: TestCaseResult = {
|
||||
title: test.title,
|
||||
fullName: [...test.ancestorTitles, test.title].join(' '),
|
||||
ancestorTitles: test.ancestorTitles,
|
||||
status: 'pending',
|
||||
duration: 0,
|
||||
failureMessages: [],
|
||||
numPassingAsserts: 0,
|
||||
};
|
||||
|
||||
test.result = result;
|
||||
|
||||
if (!test.isSkipped && (!hasFocusedTests || test.isFocused)) {
|
||||
let status;
|
||||
let error;
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
try {
|
||||
test.implementation();
|
||||
status = 'passed';
|
||||
} catch (e) {
|
||||
error = e;
|
||||
status = 'failed';
|
||||
}
|
||||
|
||||
result.status = status;
|
||||
result.duration = Date.now() - start;
|
||||
result.failureMessages =
|
||||
status === 'failed' && error
|
||||
? [error.stack ?? error.message ?? String(error)]
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
||||
reportTestSuiteResult({
|
||||
testResults: tests.map(test => nullthrows(test.result)),
|
||||
});
|
||||
}
|
||||
|
||||
function reportTestSuiteResult(testSuiteResult: TestSuiteResult): void {
|
||||
console.log(JSON.stringify(testSuiteResult));
|
||||
}
|
||||
|
||||
global.$$RunTests$$ = () => {
|
||||
executeTests();
|
||||
};
|
||||
|
||||
export function registerTest(setUpTest: () => void) {
|
||||
runWithGuard(() => {
|
||||
setUpTest();
|
||||
});
|
||||
}
|
||||
@@ -12,16 +12,16 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
// eslint-disable-next-line lint/sort-imports
|
||||
const {
|
||||
transformFromAstSync: babelTransformFromAstSync,
|
||||
transformSync: babelTransformSync,
|
||||
} = require('@babel/core');
|
||||
const generate = require('@babel/generator').default;
|
||||
const createCacheKeyFunction =
|
||||
require('@jest/create-cache-key-function').default;
|
||||
const metroBabelRegister = require('metro-babel-register');
|
||||
const nullthrows = require('nullthrows');
|
||||
const createCacheKeyFunction =
|
||||
require('@jest/create-cache-key-function').default;
|
||||
|
||||
const {
|
||||
transformSync: babelTransformSync,
|
||||
transformFromAstSync: babelTransformFromAstSync,
|
||||
} = require('@babel/core');
|
||||
const generate = require('@babel/generator').default;
|
||||
|
||||
if (process.env.FBSOURCE_ENV === '1') {
|
||||
// If we're running in the Meta-internal monorepo, use the central Babel
|
||||
|
||||
+15
-16
@@ -3,7 +3,6 @@
|
||||
"private": true,
|
||||
"version": "1000.0.0",
|
||||
"license": "MIT",
|
||||
"packageManager": "yarn@1.22.22",
|
||||
"scripts": {
|
||||
"android": "cd packages/rn-tester && npm run android",
|
||||
"build-android": "./gradlew :packages:react-native:ReactAndroid:build",
|
||||
@@ -14,7 +13,8 @@
|
||||
"flow": "flow",
|
||||
"format-check": "prettier --list-different \"./**/*.{js,md,yml,ts,tsx}\"",
|
||||
"format": "npm run prettier && npm run clang-format",
|
||||
"featureflags": "cd packages/react-native && yarn featureflags",
|
||||
"featureflags-check": "cd packages/react-native && yarn featureflags-check",
|
||||
"featureflags-update": "cd packages/react-native && yarn featureflags-update",
|
||||
"lint-ci": "./scripts/circleci/analyze_code.sh && yarn shellcheck",
|
||||
"lint-java": "node ./scripts/lint-java.js",
|
||||
"lint": "eslint .",
|
||||
@@ -48,6 +48,7 @@
|
||||
"@babel/preset-flow": "^7.24.7",
|
||||
"@definitelytyped/dtslint": "^0.0.127",
|
||||
"@jest/create-cache-key-function": "^29.6.3",
|
||||
"@pkgjs/parseargs": "^0.11.0",
|
||||
"@react-native/metro-babel-transformer": "0.77.0-main",
|
||||
"@react-native/metro-config": "0.77.0-main",
|
||||
"@tsconfig/node18": "1.0.1",
|
||||
@@ -55,13 +56,12 @@
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"ansi-styles": "^4.2.1",
|
||||
"babel-plugin-minify-dead-code-elimination": "^0.5.2",
|
||||
"babel-plugin-syntax-hermes-parser": "0.25.1",
|
||||
"babel-plugin-syntax-hermes-parser": "0.24.0",
|
||||
"babel-plugin-transform-define": "^2.1.4",
|
||||
"babel-plugin-transform-flow-enums": "^0.0.2",
|
||||
"chalk": "^4.0.0",
|
||||
"clang-format": "^1.8.0",
|
||||
"connect": "^3.6.5",
|
||||
"deep-equal": "1.1.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-babel": "^5.3.1",
|
||||
@@ -76,26 +76,25 @@
|
||||
"eslint-plugin-react-native": "^4.0.0",
|
||||
"eslint-plugin-redundant-undefined": "^0.4.0",
|
||||
"eslint-plugin-relay": "^1.8.3",
|
||||
"flow-api-translator": "0.25.1",
|
||||
"flow-bin": "^0.255.0",
|
||||
"flow-api-translator": "0.24.0",
|
||||
"flow-bin": "^0.247.1",
|
||||
"glob": "^7.1.1",
|
||||
"hermes-eslint": "0.25.1",
|
||||
"hermes-transform": "0.25.1",
|
||||
"hermes-eslint": "0.24.0",
|
||||
"hermes-transform": "0.24.0",
|
||||
"inquirer": "^7.1.0",
|
||||
"jest": "^29.6.3",
|
||||
"jest-diff": "^29.7.0",
|
||||
"jest-junit": "^10.0.0",
|
||||
"jscodeshift": "^0.14.0",
|
||||
"metro-babel-register": "^0.81.0",
|
||||
"metro-memory-fs": "^0.81.0",
|
||||
"metro-transform-plugins": "^0.81.0",
|
||||
"metro-babel-register": "^0.81.0-alpha.2",
|
||||
"metro-memory-fs": "^0.81.0-alpha.2",
|
||||
"metro-transform-plugins": "^0.80.10",
|
||||
"micromatch": "^4.0.4",
|
||||
"node-fetch": "^2.2.0",
|
||||
"nullthrows": "^1.1.1",
|
||||
"prettier": "2.8.8",
|
||||
"prettier-plugin-hermes-parser": "0.25.1",
|
||||
"react": "18.3.1",
|
||||
"react-test-renderer": "18.3.1",
|
||||
"prettier-plugin-hermes-parser": "0.24.0",
|
||||
"react": "19.0.0-rc-fb9a90fa48-20240614",
|
||||
"react-test-renderer": "19.0.0-rc-fb9a90fa48-20240614",
|
||||
"rimraf": "^3.0.2",
|
||||
"shelljs": "^0.8.5",
|
||||
"signedsource": "^1.0.0",
|
||||
@@ -104,6 +103,6 @@
|
||||
"ws": "^6.2.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"react-is": "18.3.1"
|
||||
"react-is": "19.0.0-rc-fb9a90fa48-20240614"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
let FlowParser, TypeScriptParser, RNCodegen;
|
||||
|
||||
const {cheap: traverseCheap} = require('@babel/traverse').default;
|
||||
const {basename} = require('path');
|
||||
const {cheap: traverseCheap} = require('@babel/traverse').default;
|
||||
|
||||
try {
|
||||
FlowParser =
|
||||
|
||||
@@ -27,14 +27,15 @@
|
||||
"chalk": "^4.0.0",
|
||||
"debug": "^2.2.0",
|
||||
"invariant": "^2.2.4",
|
||||
"metro": "^0.81.0",
|
||||
"metro-config": "^0.81.0",
|
||||
"metro-core": "^0.81.0",
|
||||
"metro": "^0.81.0-alpha.2",
|
||||
"metro-config": "^0.81.0-alpha.2",
|
||||
"metro-core": "^0.81.0-alpha.2",
|
||||
"node-fetch": "^2.2.0",
|
||||
"readline": "^1.3.0",
|
||||
"semver": "^7.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"metro-resolver": "^0.81.0"
|
||||
"metro-resolver": "^0.81.0-alpha.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-native-community/cli-server-api": "*"
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import type TerminalReporter from 'metro/src/lib/TerminalReporter';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
type PageDescription = $ReadOnly<{
|
||||
id: string,
|
||||
|
||||
@@ -19,7 +19,10 @@ import isDevServerRunning from '../../utils/isDevServerRunning';
|
||||
import loadMetroConfig from '../../utils/loadMetroConfig';
|
||||
import * as version from '../../utils/version';
|
||||
import attachKeyHandlers from './attachKeyHandlers';
|
||||
import {createDevServerMiddleware, indexPageMiddleware} from './middleware';
|
||||
import {
|
||||
createDevServerMiddleware,
|
||||
indexPageMiddleware,
|
||||
} from '@react-native-community/cli-server-api';
|
||||
import {createDevMiddleware} from '@react-native/dev-middleware';
|
||||
import chalk from 'chalk';
|
||||
import Metro from 'metro';
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
import net from 'net';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
/**
|
||||
* Determine whether we can run the dev server.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@generated SignedSource<<cecad43da3fd917e15322ee3efbed875>>
|
||||
Git revision: 486803f6bf272e0629297265dee8048a2f1269dd
|
||||
@generated SignedSource<<be159160e305455e8919c5e323fb1784>>
|
||||
Git revision: 87b2feb14627b3e8713bc2637992b9a8c76bbe61
|
||||
Built with --nohooks: false
|
||||
Is local checkout: false
|
||||
Remote URL: https://github.com/facebookexperimental/rn-chrome-devtools-frontend
|
||||
|
||||
@@ -20,6 +20,7 @@ style.setProperty('--image-file-nodeIcon', 'url(\"' + new URL('./nodeIcon.avif',
|
||||
style.setProperty('--image-file-popoverArrows', 'url(\"' + new URL('./popoverArrows.png', import.meta.url).toString() + '\")');
|
||||
style.setProperty('--image-file-react_native/learn-debugging-basics', 'url(\"' + new URL('./react_native/learn-debugging-basics.jpg', import.meta.url).toString() + '\")');
|
||||
style.setProperty('--image-file-react_native/learn-native-debugging', 'url(\"' + new URL('./react_native/learn-native-debugging.jpg', import.meta.url).toString() + '\")');
|
||||
style.setProperty('--image-file-react_native/learn-react-devtools', 'url(\"' + new URL('./react_native/learn-react-devtools.jpg', import.meta.url).toString() + '\")');
|
||||
style.setProperty('--image-file-react_native/learn-react-native-devtools', 'url(\"' + new URL('./react_native/learn-react-native-devtools.jpg', import.meta.url).toString() + '\")');
|
||||
style.setProperty('--image-file-react_native/welcomeIcon', 'url(\"' + new URL('./react_native/welcomeIcon.png', import.meta.url).toString() + '\")');
|
||||
style.setProperty('--image-file-toolbarResizerVertical', 'url(\"' + new URL('./toolbarResizerVertical.png', import.meta.url).toString() + '\")');
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+5
-4
@@ -1,4 +1,4 @@
|
||||
import*as e from"../../ui/legacy/legacy.js";import*as t from"../../core/host/host.js";import*as o from"../../core/i18n/i18n.js";import*as i from"../../core/sdk/sdk.js";import*as n from"../../ui/lit-html/lit-html.js";const r=new CSSStyleSheet;r.replaceSync('.rn-welcome-panel{display:flex;flex-direction:column;flex-shrink:0;align-items:stretch;justify-content:center;padding:36px;background-color:var(--color-background-elevation-0);min-height:100%}@media (min-width: 1000px){.rn-welcome-panel{flex-direction:row;align-items:center;justify-content:stretch;height:100%;padding:0}}.rn-welcome-hero{display:flex;flex-direction:column;flex-grow:1;flex-shrink:0;align-items:center;justify-content:center;padding:16px;text-align:center}@media (min-width: 1000px){.rn-welcome-hero{margin-left:24px}}.rn-welcome-heading{display:flex;align-items:center;margin-bottom:16px}.rn-welcome-icon{width:30px;height:30px;border-radius:4px;margin-right:12px}.rn-welcome-title{font-size:20px;font-weight:normal;color:var(--color-text-primary)}.rn-welcome-title-accessory{margin-left:12px;padding:4px 8px;border-radius:4px;background-color:var(--color-green);font-size:12px;color:var(--color-on-primary)}.rn-welcome-title-accessory-purple{background-color:var(--color-purple-bright)}.rn-welcome-tagline{margin-bottom:24px;font-size:1rem;line-height:1.3;color:var(--color-text-secondary)}.rn-welcome-links{display:flex;align-items:center}.rn-welcome-links > .devtools-link{position:relative;margin:0 16px;font-size:14px}.rn-welcome-links > .devtools-link:not(:last-child)::after{content:"";position:absolute;right:-16px;height:16px;border-right:1px solid var(--color-details-hairline)}.rn-welcome-version{position:fixed;top:8px;right:8px;margin-top:24px;padding:4px 12px;border-radius:6px;background-color:var(--color-background-hover-overlay);color:var(--color-text-secondary);font-size:11px}.rn-welcome-docsfeed{display:flex;flex-direction:column;flex-shrink:0;align-items:stretch;max-width:700px;margin:0 auto;padding:24px}@media (min-width: 1000px){.rn-welcome-docsfeed{flex-shrink:1;width:45%;max-height:100%;margin:0;padding:20px 24px;padding-right:80px;overflow:auto}}.rn-welcome-h2{flex-shrink:0;font-size:16px;font-weight:normal;color:var(--color-text-primary)}.rn-welcome-docsfeed-item{display:flex;flex-shrink:0;align-items:center;margin-bottom:8px;padding:8px;padding-right:16px;border:1px solid var(--color-details-hairline);border-radius:4px;background-color:var(--color-background);text-align:left;font-size:14px;cursor:pointer}.rn-welcome-docsfeed-item:hover{background-color:var(--color-background-elevation-0)}.rn-welcome-docsfeed-item:focus{outline:solid var(--color-button-outline-focus)}.rn-welcome-docsfeed-item p{margin:0;margin-bottom:4px;text-decoration:none}.rn-welcome-docsfeed-item :not(.devtools-link){color:var(--color-text-secondary)}.rn-welcome-image{flex-shrink:0;aspect-ratio:calc(16 / 9);height:64px;margin-right:16px;border-radius:2px;background-color:var(--color-gray-100);background-position:center;background-size:cover}\n/*# sourceURL=rnWelcome.css */\n');const a={betaLabel:"Beta",techPreviewLabel:"Tech Preview",welcomeMessage:"Welcome to debugging in React Native",docsLabel:"Debugging docs",whatsNewLabel:"What's new",docsDebuggingBasics:"Debugging Basics",docsDebuggingBasicsDetail:"Overview of debugging tools in React Native",docsReactNativeDevTools:"React Native DevTools",docsReactDevToolsDetail:"Explore features available in React Native DevTools",docsNativeDebugging:"Native Debugging",docsNativeDebuggingDetail:"Find out more about native debugging tools"},{render:s,html:l}=n,c=o.i18n.registerUIStrings("panels/rn_welcome/RNWelcome.ts",a),d=o.i18n.getLocalizedString.bind(void 0,c);let g;class p extends e.Widget.VBox{options;#e;static instance(e){return g||(g=new p(e)),g}constructor(e){super(!0,!0),this.options=e,i.TargetManager.TargetManager.instance().observeModels(i.ReactNativeApplicationModel.ReactNativeApplicationModel,this)}wasShown(){super.wasShown(),this.registerCSSFiles([r]),this.render(),e.InspectorView.InspectorView.instance().showDrawer({focus:!0,hasTargetDrawer:!1})}modelAdded(e){e.ensureEnabled(),e.addEventListener("MetadataUpdated",this.#t,this),this.#e=e.metadataCached?.reactNativeVersion}modelRemoved(e){e.removeEventListener("MetadataUpdated",this.#t,this)}#t(e){this.#e=e.data.reactNativeVersion,this.isShowing()&&this.render()}#o(e){t.InspectorFrontendHost.InspectorFrontendHostInstance.openInNewTab(e)}render(){const{debuggerBrandName:e,showBetaLabel:t=!1,showTechPreviewLabel:o=!1,showDocs:i=!1}=this.options,n=new URL("../../Images/react_native/welcomeIcon.png",import.meta.url).toString(),r=new URL("../../Images/react_native/learn-debugging-basics.jpg",import.meta.url).toString(),c=new URL("../../Images/react_native/learn-react-native-devtools.jpg",import.meta.url).toString(),g=new URL("../../Images/react_native/learn-native-debugging.jpg",import.meta.url).toString();s(l`
|
||||
import*as e from"../../ui/legacy/legacy.js";import*as t from"../../core/host/host.js";import*as o from"../../core/i18n/i18n.js";import*as i from"../../core/sdk/sdk.js";import*as n from"../../ui/lit-html/lit-html.js";const r=new CSSStyleSheet;r.replaceSync('.rn-welcome-panel{display:flex;flex-direction:column;flex-shrink:0;align-items:stretch;justify-content:center;padding:36px;background-color:var(--color-background-elevation-0);min-height:100%}@media (min-width: 1000px){.rn-welcome-panel{flex-direction:row;align-items:center;justify-content:stretch;height:100%;padding:0}}.rn-welcome-hero{display:flex;flex-direction:column;flex-grow:1;flex-shrink:0;align-items:center;justify-content:center;padding:16px;text-align:center}@media (min-width: 1000px){.rn-welcome-hero{margin-left:24px}}.rn-welcome-heading{display:flex;align-items:center;margin-bottom:16px}.rn-welcome-icon{width:30px;height:30px;border-radius:4px;margin-right:12px}.rn-welcome-title{font-size:20px;font-weight:normal;color:var(--color-text-primary)}.rn-welcome-title-accessory{margin-left:12px;padding:4px 8px;border-radius:4px;background-color:var(--color-green);font-size:12px;color:var(--color-on-primary)}.rn-welcome-title-accessory-purple{background-color:var(--color-purple-bright)}.rn-welcome-tagline{margin-bottom:24px;font-size:1rem;line-height:1.3;color:var(--color-text-secondary)}.rn-welcome-links{display:flex;align-items:center}.rn-welcome-links > .devtools-link{position:relative;margin:0 16px;font-size:14px}.rn-welcome-links > .devtools-link:not(:last-child)::after{content:"";position:absolute;right:-16px;height:16px;border-right:1px solid var(--color-details-hairline)}.rn-welcome-version{position:fixed;top:8px;right:8px;margin-top:24px;padding:4px 12px;border-radius:6px;background-color:var(--color-background-hover-overlay);color:var(--color-text-secondary);font-size:11px}.rn-welcome-docsfeed{display:flex;flex-direction:column;flex-shrink:0;align-items:stretch;max-width:700px;margin:0 auto;padding:24px}@media (min-width: 1000px){.rn-welcome-docsfeed{flex-shrink:1;width:45%;max-height:100%;margin:0;padding:20px 24px;padding-right:80px;overflow:auto}}.rn-welcome-h2{flex-shrink:0;font-size:16px;font-weight:normal;color:var(--color-text-primary)}.rn-welcome-docsfeed-item{display:flex;flex-shrink:0;align-items:center;margin-bottom:8px;padding:8px;padding-right:16px;border:1px solid var(--color-details-hairline);border-radius:4px;background-color:var(--color-background);text-align:left;font-size:14px;cursor:pointer}.rn-welcome-docsfeed-item:hover{background-color:var(--color-background-elevation-0)}.rn-welcome-docsfeed-item:focus{outline:solid var(--color-button-outline-focus)}.rn-welcome-docsfeed-item p{margin:0;margin-bottom:4px;text-decoration:none}.rn-welcome-docsfeed-item :not(.devtools-link){color:var(--color-text-secondary)}.rn-welcome-image{aspect-ratio:calc(16 / 9);height:64px;margin-right:16px;border-radius:2px;background-color:var(--color-gray-100);background-position:center;background-size:cover}\n/*# sourceURL=rnWelcome.css */\n');const a={betaLabel:"Beta",techPreviewLabel:"Tech Preview",welcomeMessage:"Welcome to debugging in React Native",docsLabel:"Debugging docs",whatsNewLabel:"What's new",docsDebuggingBasics:"Debugging Basics",docsDebuggingBasicsDetail:"Overview of debugging tools in React Native",docsReactDevTools:"React DevTools",docsReactDevToolsDetail:"Debug React components with React DevTools",docsNativeDebugging:"Native Debugging",docsNativeDebuggingDetail:"Find out more about native debugging tools"},{render:s,html:l}=n,c=o.i18n.registerUIStrings("panels/rn_welcome/RNWelcome.ts",a),d=o.i18n.getLocalizedString.bind(void 0,c);let g;class p extends e.Widget.VBox{options;#e;static instance(e){return g||(g=new p(e)),g}constructor(e){super(!0,!0),this.options=e,i.TargetManager.TargetManager.instance().observeModels(i.ReactNativeApplicationModel.ReactNativeApplicationModel,this)}wasShown(){super.wasShown(),this.registerCSSFiles([r]),this.render(),e.InspectorView.InspectorView.instance().showDrawer({focus:!0,hasTargetDrawer:!1})}modelAdded(e){e.ensureEnabled(),e.addEventListener("MetadataUpdated",this.#t,this),this.#e=e.metadataCached?.reactNativeVersion}modelRemoved(e){e.removeEventListener("MetadataUpdated",this.#t,this)}#t(e){this.#e=e.data.reactNativeVersion,this.isShowing()&&this.render()}#o(e){t.InspectorFrontendHost.InspectorFrontendHostInstance.openInNewTab(e)}render(){const{debuggerBrandName:e,showBetaLabel:t=!1,showTechPreviewLabel:o=!1,showDocs:i=!1}=this.options,n=new URL("../../Images/react_native/welcomeIcon.png",import.meta.url).toString(),r=new URL("../../Images/react_native/learn-debugging-basics.jpg",import.meta.url).toString(),c=new URL("../../Images/react_native/learn-react-devtools.jpg",import.meta.url).toString(),g=new URL("../../Images/react_native/learn-native-debugging.jpg",import.meta.url).toString();s(l`
|
||||
<div class="rn-welcome-panel">
|
||||
<header class="rn-welcome-hero">
|
||||
<div class="rn-welcome-heading">
|
||||
@@ -42,14 +42,15 @@ import*as e from"../../ui/legacy/legacy.js";import*as t from"../../core/host/hos
|
||||
<p>${d(a.docsDebuggingBasicsDetail)}</p>
|
||||
</div>
|
||||
</button>
|
||||
<button class="rn-welcome-docsfeed-item" type="button" role="link" @click=${this.#o.bind(this,"https://reactnative.dev/docs/react-native-devtools")} title=${d(a.docsReactNativeDevTools)}>
|
||||
<!-- TODO(huntie): Replace this item when React Native DevTools docs are complete -->
|
||||
<button class="rn-welcome-docsfeed-item" type="button" role="link" @click=${this.#o.bind(this,"https://reactnative.dev/docs/debugging/react-devtools")} title=${d(a.docsReactDevTools)}>
|
||||
<div class="rn-welcome-image" style="background-image: url('${c}')"></div>
|
||||
<div>
|
||||
<p class="devtools-link">${d(a.docsReactNativeDevTools)}</p>
|
||||
<p class="devtools-link">${d(a.docsReactDevTools)}</p>
|
||||
<p>${d(a.docsReactDevToolsDetail)}</p>
|
||||
</div>
|
||||
</button>
|
||||
<button class="rn-welcome-docsfeed-item" type="button" role="link" @click=${this.#o.bind(this,"https://reactnative.dev/docs/debugging-native-code")} title=${d(a.docsNativeDebugging)}>
|
||||
<button class="rn-welcome-docsfeed-item" type="button" role="link" @click=${this.#o.bind(this,"https://reactnative.dev/docs/debugging/debugging-native-code")} title=${d(a.docsNativeDebugging)}>
|
||||
<div class="rn-welcome-image" style="background-image: url('${g}')"></div>
|
||||
<div>
|
||||
<p class="devtools-link">${d(a.docsNativeDebugging)}</p>
|
||||
|
||||
+14
-14
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+6
-6
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
import*as e from"../../lit-html/lit-html.js";import*as t from"../../visual_logging/visual_logging.js";import*as i from"../icon_button/icon_button.js";import*as n from"../input/input.js";import*as s from"../../../core/common/common.js";const a=new CSSStyleSheet;a.replaceSync(":host{display:block;padding:0;margin:0}input{height:12px;width:12px;min-height:12px;min-width:12px}label{display:inline-flex;align-items:center;overflow:hidden;text-overflow:ellipsis}p{margin:12px 0}.disabled-reason{box-sizing:border-box;margin-left:3px;width:16px;height:16px}\n/*# sourceURL=settingCheckbox.css */\n");const o=new CSSStyleSheet;o.replaceSync(".clickable{cursor:pointer}devtools-icon{vertical-align:text-bottom;padding-left:2px}\n/*# sourceURL=settingDeprecationWarning.css */\n");class c extends HTMLElement{static litTagName=e.literal`devtools-setting-deprecation-warning`;#e=this.attachShadow({mode:"open"});connectedCallback(){this.#e.adoptedStyleSheets=[o]}set data(e){this.#t(e)}#t({disabled:t,warning:n,experiment:a}){const o={clickable:!1};let c;t&&a&&(o.clickable=!0,c=()=>{s.Revealer.reveal(a)}),e.render(e.html`<${i.Icon.Icon.litTagName} class=${e.Directives.classMap(o)} .data=${{iconName:"info",color:"var(--icon-default)",width:"16px"}} title=${n} @click=${c}></${i.Icon.Icon.litTagName}>`,this.#e,{host:this})}}customElements.define("devtools-setting-deprecation-warning",c);var l=Object.freeze({__proto__:null,SettingDeprecationWarning:c});class r extends HTMLElement{static litTagName=e.literal`setting-checkbox`;#e=this.attachShadow({mode:"open"});#i;#n;connectedCallback(){this.#e.adoptedStyleSheets=[n.checkboxStyles,a]}set data(e){this.#n&&this.#i&&this.#i.removeChangeListener(this.#n.listener),this.#i=e.setting,this.#n=this.#i.addChangeListener((()=>{this.#t()})),this.#t()}#s(){if(this.#i?.deprecation)return e.html`<${c.litTagName} .data=${this.#i.deprecation}></${c.litTagName}>`}#t(){if(!this.#i)throw new Error('No "Setting" object provided for rendering');const n=this.#s(),s=this.#i.disabledReason()?e.html`
|
||||
import*as e from"../../lit-html/lit-html.js";import*as t from"../../visual_logging/visual_logging.js";import*as i from"../icon_button/icon_button.js";import*as n from"../input/input.js";import*as s from"../../../core/common/common.js";const a=new CSSStyleSheet;a.replaceSync(":host{padding:0;margin:0}input{height:12px;width:12px;min-height:12px;min-width:12px}label{display:inline-flex;align-items:center;overflow:hidden;text-overflow:ellipsis}p{margin:12px 0}.disabled-reason{box-sizing:border-box;margin-left:3px;width:16px;height:16px}\n/*# sourceURL=settingCheckbox.css */\n");const o=new CSSStyleSheet;o.replaceSync(".clickable{cursor:pointer}devtools-icon{vertical-align:text-bottom;padding-left:2px}\n/*# sourceURL=settingDeprecationWarning.css */\n");class c extends HTMLElement{static litTagName=e.literal`devtools-setting-deprecation-warning`;#e=this.attachShadow({mode:"open"});connectedCallback(){this.#e.adoptedStyleSheets=[o]}set data(e){this.#t(e)}#t({disabled:t,warning:n,experiment:a}){const o={clickable:!1};let c;t&&a&&(o.clickable=!0,c=()=>{s.Revealer.reveal(a)}),e.render(e.html`<${i.Icon.Icon.litTagName} class=${e.Directives.classMap(o)} .data=${{iconName:"info",color:"var(--icon-default)",width:"16px"}} title=${n} @click=${c}></${i.Icon.Icon.litTagName}>`,this.#e,{host:this})}}customElements.define("devtools-setting-deprecation-warning",c);var l=Object.freeze({__proto__:null,SettingDeprecationWarning:c});class r extends HTMLElement{static litTagName=e.literal`setting-checkbox`;#e=this.attachShadow({mode:"open"});#i;#n;connectedCallback(){this.#e.adoptedStyleSheets=[n.checkboxStyles,a]}set data(e){this.#n&&this.#i&&this.#i.removeChangeListener(this.#n.listener),this.#i=e.setting,this.#n=this.#i.addChangeListener((()=>{this.#t()})),this.#t()}#s(){if(this.#i?.deprecation)return e.html`<${c.litTagName} .data=${this.#i.deprecation}></${c.litTagName}>`}#t(){if(!this.#i)throw new Error('No "Setting" object provided for rendering');const n=this.#s(),s=this.#i.disabledReason()?e.html`
|
||||
<${i.Icon.Icon.litTagName} class="disabled-reason" name="info" title=${this.#i.disabledReason()} @click=${onclick}></${i.Icon.Icon.litTagName}>
|
||||
`:e.nothing;e.render(e.html`
|
||||
<p>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -64,16 +64,12 @@ Subpaths of this endpoint are reserved to serve the JavaScript debugger frontend
|
||||
|
||||
#### POST `/open-debugger`
|
||||
|
||||
Open the JavaScript debugger for a given CDP target. Must be provided with one of the following query params:
|
||||
|
||||
- `device` — An ID unique to a combination of device and app, stable across installs. Implemented by `getInspectorDeviceId` on each native platform.
|
||||
- `target` — The target page ID as returned by `/json/list` for the current dev server session.
|
||||
- `appId` (deprecated, legacy only) — The application bundle identifier to match (non-unique across multiple connected devices). This param will only match legacy Hermes debugger targets.
|
||||
Open the JavaScript debugger for a given CDP target (direct Hermes debugging).
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
|
||||
curl -X POST 'http://localhost:8081/open-debugger?target=<targetId>'
|
||||
curl -X POST 'http://localhost:8081/open-debugger?appId=com.meta.RNTester'
|
||||
</details>
|
||||
|
||||
### WebSocket endpoints
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"nullthrows": "^1.1.1",
|
||||
"open": "^7.0.3",
|
||||
"selfsigned": "^2.4.1",
|
||||
"serve-static": "^1.16.2",
|
||||
"serve-static": "^1.13.1",
|
||||
"ws": "^6.2.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -10,30 +10,24 @@
|
||||
*/
|
||||
|
||||
import type {JSONSerializable} from '../inspector-proxy/types';
|
||||
import type {RequestOptions} from 'undici';
|
||||
|
||||
import {Agent, request} from 'undici';
|
||||
import {Agent} from 'undici';
|
||||
|
||||
declare var globalThis: $FlowFixMe;
|
||||
|
||||
/**
|
||||
* A version of `fetch` that is usable with the HTTPS server created in
|
||||
* ServerUtils (which uses a self-signed certificate).
|
||||
*/
|
||||
export async function requestLocal(
|
||||
export async function fetchLocal(
|
||||
url: string,
|
||||
options?: RequestOptions,
|
||||
): Promise<{
|
||||
statusCode: number,
|
||||
headers: Headers,
|
||||
bodyBuffer: Buffer,
|
||||
}> {
|
||||
const {
|
||||
statusCode,
|
||||
headers: rawHeaders,
|
||||
body,
|
||||
} = await request(url, {
|
||||
options?: Partial<Parameters<typeof fetch>[1] & {dispatcher?: mixed}>,
|
||||
): ReturnType<typeof fetch> {
|
||||
return await fetch(url, {
|
||||
...options,
|
||||
|
||||
// Use undici's `dispatcher` to make it accept self-signed certificates.
|
||||
// Node's native `fetch` comes from undici and supports the same options,
|
||||
// including `dispatcher` which we use to make it accept self-signed
|
||||
// certificates.
|
||||
dispatcher:
|
||||
options?.dispatcher ??
|
||||
new Agent({
|
||||
@@ -42,48 +36,41 @@ export async function requestLocal(
|
||||
},
|
||||
}),
|
||||
});
|
||||
return {
|
||||
statusCode,
|
||||
bodyBuffer: await body.read(),
|
||||
headers: new Headers(rawHeaders),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchJson<T: JSONSerializable>(url: string): Promise<T> {
|
||||
const response = await requestLocal(url);
|
||||
if (response.statusCode !== 200) {
|
||||
throw new Error(`HTTP ${response.statusCode}`);
|
||||
const response = await fetchLocal(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
||||
}
|
||||
if (!response.headers.get('Content-Type')?.startsWith('application/json')) {
|
||||
throw new Error('Expected Content-Type: application/json');
|
||||
}
|
||||
return JSON.parse(response.bodyBuffer.toString());
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the global fetch dispatcher to allow self-signed certificates.
|
||||
* This runs with Jest's `beforeAll` and `afterAll`, and restores the original dispatcher.
|
||||
*/
|
||||
export function withFetchSelfSignedCertsForAllTests(
|
||||
fetchSpy: JestMockFn<Parameters<typeof fetch>, ReturnType<typeof fetch>>,
|
||||
fetchOriginal: typeof fetch,
|
||||
) {
|
||||
export function withFetchSelfSignedCertsForAllTests() {
|
||||
const fetchOriginal = globalThis.fetch;
|
||||
const selfSignedCertDispatcher = new Agent({
|
||||
connect: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
|
||||
let fetchSpy;
|
||||
|
||||
beforeAll(() => {
|
||||
// For some reason, setting the `selfSignedCertDispatcher` with `setGlobalDispatcher` doesn't work.
|
||||
// Instead of using `setGlobalDispatcher`, we'll use a spy to intercept the fetch calls and add the dispatcher.
|
||||
fetchSpy.mockImplementation((url, options) =>
|
||||
fetchOriginal(url, {
|
||||
...options,
|
||||
// $FlowFixMe[prop-missing]: dispatcher
|
||||
dispatcher: options?.dispatcher ?? selfSignedCertDispatcher,
|
||||
}),
|
||||
);
|
||||
fetchSpy = jest
|
||||
.spyOn(globalThis, 'fetch')
|
||||
.mockImplementation((url, options) =>
|
||||
fetchOriginal(url, {
|
||||
...options,
|
||||
dispatcher: options?.dispatcher ?? selfSignedCertDispatcher,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
|
||||
@@ -23,11 +23,10 @@ export class DebuggerAgent {
|
||||
#ws: ?WebSocket;
|
||||
#readyPromise: Promise<void>;
|
||||
|
||||
constructor(url: string, signal?: AbortSignal, hostHeader?: ?string) {
|
||||
constructor(url: string, signal?: AbortSignal) {
|
||||
const ws = new WebSocket(url, {
|
||||
// The mock server uses a self-signed certificate.
|
||||
rejectUnauthorized: false,
|
||||
...(hostHeader != null ? {headers: {Host: hostHeader}} : {}),
|
||||
});
|
||||
this.#ws = ws;
|
||||
ws.on('message', data => {
|
||||
@@ -116,9 +115,8 @@ export class DebuggerMock extends DebuggerAgent {
|
||||
export async function createDebuggerMock(
|
||||
url: string,
|
||||
signal: AbortSignal,
|
||||
hostHeader?: ?string,
|
||||
): Promise<DebuggerMock> {
|
||||
const debuggerMock = new DebuggerMock(url, signal, hostHeader);
|
||||
const debuggerMock = new DebuggerMock(url, signal);
|
||||
await debuggerMock.ready();
|
||||
return debuggerMock;
|
||||
}
|
||||
|
||||
@@ -27,17 +27,10 @@ export class DeviceAgent {
|
||||
#ws: ?WebSocket;
|
||||
#readyPromise: Promise<void>;
|
||||
|
||||
constructor(url: string, signal?: AbortSignal, host?: ?string) {
|
||||
constructor(url: string, signal?: AbortSignal) {
|
||||
const ws = new WebSocket(url, {
|
||||
// The mock server uses a self-signed certificate.
|
||||
rejectUnauthorized: false,
|
||||
...(host != null
|
||||
? {
|
||||
headers: {
|
||||
Host: host,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
this.#ws = ws;
|
||||
ws.on('message', data => {
|
||||
@@ -167,9 +160,8 @@ export class DeviceMock extends DeviceAgent {
|
||||
export async function createDeviceMock(
|
||||
url: string,
|
||||
signal: AbortSignal,
|
||||
host?: ?string,
|
||||
): Promise<DeviceMock> {
|
||||
const device = new DeviceMock(url, signal, host);
|
||||
const device = new DeviceMock(url, signal);
|
||||
await device.ready();
|
||||
return device;
|
||||
}
|
||||
|
||||
@@ -126,15 +126,7 @@ export async function createAndConnectTarget(
|
||||
}>,
|
||||
signal: AbortSignal,
|
||||
page: PageFromDevice,
|
||||
{
|
||||
debuggerHostHeader = null,
|
||||
deviceId = null,
|
||||
deviceHostHeader = null,
|
||||
}: $ReadOnly<{
|
||||
debuggerHostHeader?: ?string,
|
||||
deviceId?: ?string,
|
||||
deviceHostHeader?: ?string,
|
||||
}> = {},
|
||||
deviceId: ?string = null,
|
||||
): Promise<{device: DeviceMock, debugger_: DebuggerMock}> {
|
||||
let device;
|
||||
let debugger_;
|
||||
@@ -144,7 +136,6 @@ export async function createAndConnectTarget(
|
||||
deviceId ?? 'device' + Date.now()
|
||||
}&name=foo&app=bar`,
|
||||
signal,
|
||||
deviceHostHeader,
|
||||
);
|
||||
device.getPages.mockImplementation(() => [page]);
|
||||
|
||||
@@ -159,11 +150,7 @@ export async function createAndConnectTarget(
|
||||
const [{webSocketDebuggerUrl}] = pageList;
|
||||
expect(webSocketDebuggerUrl).toBeDefined();
|
||||
|
||||
debugger_ = await createDebuggerMock(
|
||||
webSocketDebuggerUrl,
|
||||
signal,
|
||||
debuggerHostHeader,
|
||||
);
|
||||
debugger_ = await createDebuggerMock(webSocketDebuggerUrl, signal);
|
||||
await until(() => expect(device.connect).toBeCalled());
|
||||
} catch (e) {
|
||||
device?.close();
|
||||
|
||||
@@ -33,18 +33,12 @@ jest.useRealTimers();
|
||||
|
||||
jest.setTimeout(10000);
|
||||
|
||||
const fetchOriginal = fetch;
|
||||
const fetchSpy: JestMockFn<
|
||||
Parameters<typeof fetch>,
|
||||
ReturnType<typeof fetch>,
|
||||
> = jest.spyOn(globalThis, 'fetch');
|
||||
|
||||
describe.each(['HTTP', 'HTTPS'])(
|
||||
'inspector proxy CDP rewriting hacks over %s',
|
||||
protocol => {
|
||||
// Inspector proxy tests are using a self-signed certificate for HTTPS tests.
|
||||
if (protocol === 'HTTPS') {
|
||||
withFetchSelfSignedCertsForAllTests(fetchSpy, fetchOriginal);
|
||||
withFetchSelfSignedCertsForAllTests();
|
||||
}
|
||||
|
||||
const serverRef = withServerForEachTest({
|
||||
@@ -52,7 +46,6 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
projectRoot: __dirname,
|
||||
secure: protocol === 'HTTPS',
|
||||
});
|
||||
|
||||
const autoCleanup = withAbortSignalForEachTest();
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -195,52 +188,7 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
}
|
||||
});
|
||||
|
||||
test("does not rewrite urls in Debugger.scriptParsed that don't match the device connection host", async () => {
|
||||
serverRef.app.use('/source-map', serveStaticJson({version: 3}));
|
||||
const {device, debugger_} = await createAndConnectTarget(
|
||||
serverRef,
|
||||
autoCleanup.signal,
|
||||
{
|
||||
app: 'bar-app',
|
||||
id: 'page1',
|
||||
title: 'bar-title',
|
||||
vm: 'bar-vm',
|
||||
},
|
||||
{
|
||||
deviceHostHeader: '192.168.0.123:' + serverRef.port,
|
||||
},
|
||||
);
|
||||
try {
|
||||
let fetchCalledWithURL;
|
||||
fetchSpy.mockImplementationOnce(async url => {
|
||||
fetchCalledWithURL = url instanceof URL ? url : null;
|
||||
throw new Error('Unreachable');
|
||||
});
|
||||
const sourceMapURL = `${protocol.toLowerCase()}://127.0.0.1:${
|
||||
serverRef.port
|
||||
}/source-map`;
|
||||
const scriptParsedMessage = await sendFromTargetToDebugger(
|
||||
device,
|
||||
debugger_,
|
||||
'page1',
|
||||
{
|
||||
method: 'Debugger.scriptParsed',
|
||||
params: {
|
||||
sourceMapURL,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(fetchCalledWithURL?.href).toEqual(sourceMapURL);
|
||||
expect(scriptParsedMessage.params.sourceMapURL).toEqual(
|
||||
`${protocol.toLowerCase()}://127.0.0.1:${serverRef.port}/source-map`,
|
||||
);
|
||||
} finally {
|
||||
device.close();
|
||||
debugger_.close();
|
||||
}
|
||||
});
|
||||
|
||||
describe.each(['10.0.2.2:8080', '[::1]', 'example.com:2000'])(
|
||||
describe.each(['10.0.2.2', '10.0.3.2', '127.0.0.1'])(
|
||||
'%s aliasing to and from localhost',
|
||||
sourceHost => {
|
||||
test('in source map fetching during Debugger.scriptParsed', async () => {
|
||||
@@ -254,9 +202,6 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
title: 'bar-title',
|
||||
vm: 'bar-vm',
|
||||
},
|
||||
{
|
||||
deviceHostHeader: sourceHost,
|
||||
},
|
||||
);
|
||||
try {
|
||||
const scriptParsedMessage = await sendFromTargetToDebugger(
|
||||
@@ -266,7 +211,9 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
{
|
||||
method: 'Debugger.scriptParsed',
|
||||
params: {
|
||||
sourceMapURL: `${protocol.toLowerCase()}://${sourceHost}/source-map`,
|
||||
sourceMapURL: `${protocol.toLowerCase()}://${sourceHost}:${
|
||||
serverRef.port
|
||||
}/source-map`,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -289,10 +236,6 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
title: 'bar-title',
|
||||
vm: 'bar-vm',
|
||||
},
|
||||
{
|
||||
debuggerHostHeader: 'localhost:' + serverRef.port,
|
||||
deviceHostHeader: sourceHost,
|
||||
},
|
||||
);
|
||||
try {
|
||||
const scriptParsedMessage = await sendFromTargetToDebugger(
|
||||
@@ -302,7 +245,9 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
{
|
||||
method: 'Debugger.scriptParsed',
|
||||
params: {
|
||||
url: `${protocol.toLowerCase()}://${sourceHost}/some/file.js`,
|
||||
url: `${protocol.toLowerCase()}://${sourceHost}:${
|
||||
serverRef.port
|
||||
}/some/file.js`,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -328,7 +273,9 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
},
|
||||
);
|
||||
expect(setBreakpointByUrlMessage.params.url).toEqual(
|
||||
`${protocol.toLowerCase()}://${sourceHost}/some/file.js`,
|
||||
`${protocol.toLowerCase()}://${sourceHost}:${
|
||||
serverRef.port
|
||||
}/some/file.js`,
|
||||
);
|
||||
|
||||
const setBreakpointByUrlRegexMessage =
|
||||
@@ -337,67 +284,17 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
method: 'Debugger.setBreakpointByUrl',
|
||||
params: {
|
||||
lineNumber: 1,
|
||||
urlRegex: `localhost:${serverRef.port}|example.com:2000`,
|
||||
urlRegex: 'localhost:1000|localhost:2000',
|
||||
},
|
||||
});
|
||||
|
||||
// urlRegex rewriting is restricted to specific Android IPs that
|
||||
// are well-known to route to the host. In this case we only
|
||||
// replace hostname - longstanding behaviour.
|
||||
if (sourceHost === '10.0.2.2:8080') {
|
||||
expect(setBreakpointByUrlRegexMessage.params.urlRegex).toEqual(
|
||||
`10\\.0\\.2\\.2:${serverRef.port}|example.com:2000`,
|
||||
);
|
||||
} else {
|
||||
// Otherwise expect no change.
|
||||
expect(setBreakpointByUrlRegexMessage.params.urlRegex).toEqual(
|
||||
`localhost:${serverRef.port}|example.com:2000`,
|
||||
);
|
||||
}
|
||||
expect(setBreakpointByUrlRegexMessage.params.urlRegex).toEqual(
|
||||
`${sourceHost}:1000|${sourceHost}:2000`,
|
||||
);
|
||||
} finally {
|
||||
device.close();
|
||||
debugger_.close();
|
||||
}
|
||||
});
|
||||
|
||||
describe('Network.loadNetworkResource', () => {
|
||||
test('should respond with an error without forwarding to the client', async () => {
|
||||
const {device, debugger_} = await createAndConnectTarget(
|
||||
serverRef,
|
||||
autoCleanup.signal,
|
||||
{
|
||||
app: 'bar-app',
|
||||
id: 'page1',
|
||||
title: 'bar-title',
|
||||
vm: 'bar-vm',
|
||||
},
|
||||
{
|
||||
deviceHostHeader: sourceHost,
|
||||
},
|
||||
);
|
||||
try {
|
||||
const response = await debugger_.sendAndGetResponse({
|
||||
id: 1,
|
||||
method: 'Network.loadNetworkResource',
|
||||
params: {
|
||||
url: 'http://example.com',
|
||||
},
|
||||
});
|
||||
expect(response.result).toEqual(
|
||||
expect.objectContaining({
|
||||
error: {
|
||||
code: -32601,
|
||||
message:
|
||||
'[inspector-proxy]: Page lacks nativeSourceCodeFetching capability.',
|
||||
},
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
device.close();
|
||||
debugger_.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -411,9 +308,6 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
title: 'bar-title',
|
||||
vm: 'bar-vm',
|
||||
},
|
||||
{
|
||||
deviceHostHeader: '127.0.0.1:' + serverRef.port,
|
||||
},
|
||||
);
|
||||
try {
|
||||
const scriptParsedMessage = await sendFromTargetToDebugger(
|
||||
@@ -687,33 +581,6 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Network.loadNetworkResource', () => {
|
||||
test('should forward event directly to client (does not rewrite url host)', async () => {
|
||||
const {device, debugger_} = await createAndConnectTarget(
|
||||
serverRef,
|
||||
autoCleanup.signal,
|
||||
pageDescription,
|
||||
);
|
||||
try {
|
||||
const message = {
|
||||
id: 1,
|
||||
method: 'Network.loadNetworkResource',
|
||||
params: {
|
||||
url: `${protocol.toLowerCase()}://10.0.2.2:${serverRef.port}`,
|
||||
},
|
||||
};
|
||||
await sendFromDebuggerToTarget(debugger_, device, 'page1', message);
|
||||
expect(device.wrappedEventParsed).toBeCalledWith({
|
||||
pageId: 'page1',
|
||||
wrappedEvent: message,
|
||||
});
|
||||
} finally {
|
||||
device.close();
|
||||
debugger_.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -14,11 +14,11 @@ import type {
|
||||
JsonVersionResponse,
|
||||
} from '../inspector-proxy/types';
|
||||
|
||||
import DefaultBrowserLauncher from '../utils/DefaultBrowserLauncher';
|
||||
import {fetchJson, requestLocal} from './FetchUtils';
|
||||
import {fetchJson, fetchLocal} from './FetchUtils';
|
||||
import {createDeviceMock} from './InspectorDeviceUtils';
|
||||
import {withAbortSignalForEachTest} from './ResourceUtils';
|
||||
import {withServerForEachTest} from './ServerUtils';
|
||||
import DefaultBrowserLauncher from '../utils/DefaultBrowserLauncher';
|
||||
|
||||
// Must be greater than or equal to PAGES_POLLING_INTERVAL in `InspectorProxy.js`.
|
||||
const PAGES_POLLING_DELAY = 1000;
|
||||
@@ -185,7 +185,6 @@ describe('inspector proxy HTTP API', () => {
|
||||
);
|
||||
expect(json).toEqual([
|
||||
{
|
||||
appId: 'bar-app',
|
||||
description: 'bar-app',
|
||||
deviceName: 'foo',
|
||||
devtoolsFrontendUrl: expect.any(String),
|
||||
@@ -200,7 +199,6 @@ describe('inspector proxy HTTP API', () => {
|
||||
webSocketDebuggerUrl: expect.any(String),
|
||||
},
|
||||
{
|
||||
appId: 'bar-app',
|
||||
description: 'bar-app',
|
||||
deviceName: 'foo',
|
||||
devtoolsFrontendUrl: expect.any(String),
|
||||
@@ -362,7 +360,7 @@ describe('inspector proxy HTTP API', () => {
|
||||
|
||||
jest.advanceTimersByTime(PAGES_POLLING_DELAY);
|
||||
|
||||
const response = await requestLocal(
|
||||
const response = await fetchLocal(
|
||||
`${serverRef.serverBaseUrl}${endpoint}`,
|
||||
);
|
||||
expect(response.headers.get('Content-Length')).not.toBeNull();
|
||||
@@ -373,7 +371,7 @@ describe('inspector proxy HTTP API', () => {
|
||||
});
|
||||
|
||||
describe('/open-debugger endpoint', () => {
|
||||
test('opens requested device using device and target query params', async () => {
|
||||
it('opens requested device using appId, device, and target', async () => {
|
||||
// Connect a device to use when opening the debugger
|
||||
const device = await createDeviceMock(
|
||||
`${serverRef.serverBaseWsUrl}/inspector/device?device=device1&name=foo&app=bar`,
|
||||
@@ -409,18 +407,17 @@ describe('inspector proxy HTTP API', () => {
|
||||
|
||||
// Build the URL for the debugger
|
||||
const openUrl = new URL('/open-debugger', serverRef.serverBaseUrl);
|
||||
openUrl.searchParams.set('appId', firstPage.description);
|
||||
openUrl.searchParams.set(
|
||||
'device',
|
||||
firstPage.reactNative.logicalDeviceId,
|
||||
);
|
||||
openUrl.searchParams.set('target', firstPage.id);
|
||||
// Request to open the debugger for the first device
|
||||
const response = await requestLocal(openUrl.toString(), {
|
||||
method: 'POST',
|
||||
});
|
||||
const response = await fetchLocal(openUrl.toString(), {method: 'POST'});
|
||||
|
||||
// Ensure the request was handled properly
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.status).toBe(200);
|
||||
// Ensure the debugger was launched
|
||||
expect(launchDebuggerSpy).toHaveBeenCalledWith(expect.any(String));
|
||||
} finally {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import {requestLocal} from './FetchUtils';
|
||||
import {fetchLocal} from './FetchUtils';
|
||||
import {withServerForEachTest} from './ServerUtils';
|
||||
|
||||
jest.useRealTimers();
|
||||
@@ -22,10 +22,11 @@ describe('embedder script', () => {
|
||||
});
|
||||
|
||||
test('is always served', async () => {
|
||||
const resp = await requestLocal(
|
||||
const resp = await fetchLocal(
|
||||
serverRef.serverBaseUrl +
|
||||
'/debugger-frontend/embedder-static/embedderScript.js',
|
||||
);
|
||||
expect(resp.statusCode).toBe(200);
|
||||
expect(resp.ok).toBeTruthy();
|
||||
expect(resp.status).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import getBaseUrlFromRequest from '../utils/getBaseUrlFromRequest';
|
||||
|
||||
test('returns a base url based on req.headers.host', () => {
|
||||
expect(
|
||||
getBaseUrlFromRequest(makeRequest('localhost:8081', false))?.href,
|
||||
).toEqual('http://localhost:8081/');
|
||||
});
|
||||
|
||||
test('identifies https using socket.encrypted', () => {
|
||||
expect(
|
||||
getBaseUrlFromRequest(makeRequest('secure.net:8443', true))?.href,
|
||||
).toEqual('https://secure.net:8443/');
|
||||
});
|
||||
|
||||
test('works with ipv6 hosts', () => {
|
||||
expect(getBaseUrlFromRequest(makeRequest('[::1]:8081', false))?.href).toEqual(
|
||||
'http://[::1]:8081/',
|
||||
);
|
||||
});
|
||||
|
||||
test('returns null on an invalid host header', () => {
|
||||
expect(getBaseUrlFromRequest(makeRequest('local[]host', false))).toBeNull();
|
||||
});
|
||||
|
||||
test('returns null on an empty host header', () => {
|
||||
expect(getBaseUrlFromRequest(makeRequest(null, false))).toBeNull();
|
||||
});
|
||||
|
||||
function makeRequest(
|
||||
host: ?string,
|
||||
encrypted: boolean,
|
||||
): http$IncomingMessage<> | http$IncomingMessage<tls$TLSSocket> {
|
||||
// $FlowIgnore[incompatible-return] Partial mock of request
|
||||
return {
|
||||
socket: encrypted ? {encrypted: true} : {},
|
||||
headers: host != null ? {host} : {},
|
||||
};
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
import type {CreateCustomMessageHandlerFn} from './inspector-proxy/CustomMessageHandler';
|
||||
import type {BrowserLauncher} from './types/BrowserLauncher';
|
||||
import type {EventReporter, ReportableEvent} from './types/EventReporter';
|
||||
import type {EventReporter} from './types/EventReporter';
|
||||
import type {Experiments, ExperimentsConfig} from './types/Experiments';
|
||||
import type {Logger} from './types/Logger';
|
||||
import type {NextHandleFunction} from 'connect';
|
||||
@@ -28,8 +28,11 @@ type Options = $ReadOnly<{
|
||||
projectRoot: string,
|
||||
|
||||
/**
|
||||
* The base URL to the dev server, as reachable from the machine on which
|
||||
* dev-middleware is hosted. Typically `http://localhost:${metroPort}`.
|
||||
* The base URL to the dev server, as addressible from the local developer
|
||||
* machine. This is used in responses which return URLs to other endpoints,
|
||||
* e.g. the debugger frontend and inspector proxy targets.
|
||||
*
|
||||
* Example: `'http://localhost:8081'`.
|
||||
*/
|
||||
serverBaseUrl: string,
|
||||
|
||||
@@ -81,15 +84,11 @@ export default function createDevMiddleware({
|
||||
unstable_customInspectorMessageHandler,
|
||||
}: Options): DevMiddlewareAPI {
|
||||
const experiments = getExperiments(experimentConfig);
|
||||
const eventReporter = createWrappedEventReporter(
|
||||
unstable_eventReporter,
|
||||
logger,
|
||||
);
|
||||
|
||||
const inspectorProxy = new InspectorProxy(
|
||||
projectRoot,
|
||||
serverBaseUrl,
|
||||
eventReporter,
|
||||
unstable_eventReporter,
|
||||
experiments,
|
||||
unstable_customInspectorMessageHandler,
|
||||
);
|
||||
@@ -101,7 +100,7 @@ export default function createDevMiddleware({
|
||||
serverBaseUrl,
|
||||
inspectorProxy,
|
||||
browserLauncher: unstable_browserLauncher,
|
||||
eventReporter,
|
||||
eventReporter: unstable_eventReporter,
|
||||
experiments,
|
||||
logger,
|
||||
}),
|
||||
@@ -133,26 +132,3 @@ function getExperiments(config: ExperimentsConfig): Experiments {
|
||||
enableNetworkInspector: config.enableNetworkInspector ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wrapped EventReporter that locally intercepts events to
|
||||
* log to the terminal.
|
||||
*/
|
||||
function createWrappedEventReporter(
|
||||
reporter: ?EventReporter,
|
||||
logger: ?Logger,
|
||||
): EventReporter {
|
||||
return {
|
||||
logEvent(event: ReportableEvent) {
|
||||
switch (event.type) {
|
||||
case 'profiling_target_registered':
|
||||
logger?.info(
|
||||
`Profiling build target "${event.appId}" registered for debugging`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
reporter?.logEvent(event);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,6 +37,22 @@ const debug = require('debug')('Metro:InspectorProxy');
|
||||
|
||||
const PAGES_POLLING_INTERVAL = 1000;
|
||||
|
||||
// Replace hosts appearing in the `url` and `sourceMapURL` fields of
|
||||
// `Debugger.scriptParsed`, and back again in messages from the debugger,
|
||||
// to account for device/debugger/proxy running on different networks.
|
||||
const REWRITE_HOSTS_TO_LOCALHOST: Array<string> = [
|
||||
// A device may retrieve a bundle through 127.0.0.1 via a (SSH) tunnel, but
|
||||
// the (remote) Metro server may be on a host without an IPv4 loopback, so
|
||||
// 127.0.0.1 may not be addressible locally for (e.g., for source map
|
||||
// fetching). Replacing with the more general 'localhost' should always be
|
||||
// safe while also more compatible with IPv6-only setups.
|
||||
'127.0.0.1',
|
||||
// Android's stock emulator and other emulators such as genymotion use a
|
||||
// standard localhost alias.
|
||||
'10.0.2.2',
|
||||
'10.0.3.2',
|
||||
];
|
||||
|
||||
// Prefix for script URLs that are alphanumeric IDs. See comment in #processMessageFromDeviceLegacy method for
|
||||
// more details.
|
||||
const FILE_PREFIX = 'file://';
|
||||
@@ -44,28 +60,17 @@ const FILE_PREFIX = 'file://';
|
||||
type DebuggerConnection = {
|
||||
// Debugger web socket connection
|
||||
socket: WS,
|
||||
// If we replaced address (like '10.0.2.2') to localhost we need to store original
|
||||
// address because Chrome uses URL or urlRegex params (instead of scriptId) to set breakpoints.
|
||||
originalSourceURLAddress?: string,
|
||||
prependedFilePrefix: boolean,
|
||||
pageId: string,
|
||||
userAgent: string | null,
|
||||
customHandler: ?CustomMessageHandler,
|
||||
debuggerRelativeBaseUrl: URL,
|
||||
};
|
||||
|
||||
const REACT_NATIVE_RELOADABLE_PAGE_ID = '-1';
|
||||
|
||||
export type DeviceOptions = $ReadOnly<{
|
||||
id: string,
|
||||
name: string,
|
||||
app: string,
|
||||
socket: WS,
|
||||
projectRoot: string,
|
||||
eventReporter: ?EventReporter,
|
||||
createMessageMiddleware: ?CreateCustomMessageHandlerFn,
|
||||
deviceRelativeBaseUrl: URL,
|
||||
serverRelativeBaseUrl: URL,
|
||||
isProfilingBuild: boolean,
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Device class represents single device connection to Inspector Proxy. Each device
|
||||
* can have multiple inspectable pages.
|
||||
@@ -120,36 +125,40 @@ export default class Device {
|
||||
|
||||
#connectedPageIds: Set<string> = new Set();
|
||||
|
||||
// A base HTTP(S) URL to this server, reachable from the device. Derived from
|
||||
// the http request that created the connection.
|
||||
#deviceRelativeBaseUrl: URL;
|
||||
|
||||
// A base HTTP(S) URL to the server, relative to this server.
|
||||
#serverRelativeBaseUrl: URL;
|
||||
|
||||
constructor(deviceOptions: DeviceOptions) {
|
||||
this.#dangerouslyConstruct(deviceOptions);
|
||||
constructor(
|
||||
id: string,
|
||||
name: string,
|
||||
app: string,
|
||||
socket: WS,
|
||||
projectRoot: string,
|
||||
eventReporter: ?EventReporter,
|
||||
createMessageMiddleware: ?CreateCustomMessageHandlerFn,
|
||||
) {
|
||||
this.#dangerouslyConstruct(
|
||||
id,
|
||||
name,
|
||||
app,
|
||||
socket,
|
||||
projectRoot,
|
||||
eventReporter,
|
||||
createMessageMiddleware,
|
||||
);
|
||||
}
|
||||
|
||||
#dangerouslyConstruct({
|
||||
id,
|
||||
name,
|
||||
app,
|
||||
socket,
|
||||
projectRoot,
|
||||
eventReporter,
|
||||
createMessageMiddleware,
|
||||
serverRelativeBaseUrl,
|
||||
deviceRelativeBaseUrl,
|
||||
isProfilingBuild,
|
||||
}: DeviceOptions) {
|
||||
#dangerouslyConstruct(
|
||||
id: string,
|
||||
name: string,
|
||||
app: string,
|
||||
socket: WS,
|
||||
projectRoot: string,
|
||||
eventReporter: ?EventReporter,
|
||||
createMessageMiddleware: ?CreateCustomMessageHandlerFn,
|
||||
) {
|
||||
this.#id = id;
|
||||
this.#name = name;
|
||||
this.#app = app;
|
||||
this.#deviceSocket = socket;
|
||||
this.#projectRoot = projectRoot;
|
||||
this.#serverRelativeBaseUrl = serverRelativeBaseUrl;
|
||||
this.#deviceRelativeBaseUrl = deviceRelativeBaseUrl;
|
||||
this.#deviceEventReporter = eventReporter
|
||||
? new DeviceEventReporter(eventReporter, {
|
||||
deviceId: id,
|
||||
@@ -159,10 +168,6 @@ export default class Device {
|
||||
: null;
|
||||
this.#createCustomMessageHandler = createMessageMiddleware;
|
||||
|
||||
if (isProfilingBuild) {
|
||||
this.#deviceEventReporter?.logProfilingTargetRegistered();
|
||||
}
|
||||
|
||||
// $FlowFixMe[incompatible-call]
|
||||
this.#deviceSocket.on('message', (message: string) => {
|
||||
this.#messageFromDeviceQueue = this.#messageFromDeviceQueue
|
||||
@@ -233,15 +238,23 @@ export default class Device {
|
||||
* This hack attempts to allow users to reload the app, either as result of a
|
||||
* crash, or manually reloading, without having to restart the debugger.
|
||||
*/
|
||||
dangerouslyRecreateDevice(deviceOptions: DeviceOptions) {
|
||||
dangerouslyRecreateDevice(
|
||||
id: string,
|
||||
name: string,
|
||||
app: string,
|
||||
socket: WS,
|
||||
projectRoot: string,
|
||||
eventReporter: ?EventReporter,
|
||||
createMessageMiddleware: ?CreateCustomMessageHandlerFn,
|
||||
) {
|
||||
invariant(
|
||||
deviceOptions.id === this.#id,
|
||||
id === this.#id,
|
||||
'dangerouslyRecreateDevice() can only be used for the same device ID',
|
||||
);
|
||||
|
||||
const oldDebugger = this.#debuggerConnection;
|
||||
|
||||
if (this.#app !== deviceOptions.app || this.#name !== deviceOptions.name) {
|
||||
if (this.#app !== app || this.#name !== name) {
|
||||
this.#deviceSocket.close();
|
||||
this.#terminateDebuggerConnection();
|
||||
}
|
||||
@@ -252,12 +265,19 @@ export default class Device {
|
||||
oldDebugger.socket.removeAllListeners();
|
||||
this.#deviceSocket.close();
|
||||
this.handleDebuggerConnection(oldDebugger.socket, oldDebugger.pageId, {
|
||||
debuggerRelativeBaseUrl: oldDebugger.debuggerRelativeBaseUrl,
|
||||
userAgent: oldDebugger.userAgent,
|
||||
});
|
||||
}
|
||||
|
||||
this.#dangerouslyConstruct(deviceOptions);
|
||||
this.#dangerouslyConstruct(
|
||||
id,
|
||||
name,
|
||||
app,
|
||||
socket,
|
||||
projectRoot,
|
||||
eventReporter,
|
||||
createMessageMiddleware,
|
||||
);
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
@@ -283,11 +303,7 @@ export default class Device {
|
||||
handleDebuggerConnection(
|
||||
socket: WS,
|
||||
pageId: string,
|
||||
{
|
||||
debuggerRelativeBaseUrl,
|
||||
userAgent,
|
||||
}: $ReadOnly<{
|
||||
debuggerRelativeBaseUrl: URL,
|
||||
metadata: $ReadOnly<{
|
||||
userAgent: string | null,
|
||||
}>,
|
||||
) {
|
||||
@@ -298,8 +314,7 @@ export default class Device {
|
||||
|
||||
if (!page) {
|
||||
debug(
|
||||
`Got new debugger connection via ${debuggerRelativeBaseUrl.href} for ` +
|
||||
`page ${pageId} of ${this.#name}, but no such page exists`,
|
||||
`Got new debugger connection for page ${pageId} of ${this.#name}, but no such page exists`,
|
||||
);
|
||||
socket.close();
|
||||
return;
|
||||
@@ -313,24 +328,20 @@ export default class Device {
|
||||
|
||||
this.#deviceEventReporter?.logConnection('debugger', {
|
||||
pageId,
|
||||
frontendUserAgent: userAgent,
|
||||
frontendUserAgent: metadata.userAgent,
|
||||
});
|
||||
|
||||
const debuggerInfo = {
|
||||
socket,
|
||||
prependedFilePrefix: false,
|
||||
pageId,
|
||||
userAgent: userAgent,
|
||||
userAgent: metadata.userAgent,
|
||||
customHandler: null,
|
||||
debuggerRelativeBaseUrl,
|
||||
};
|
||||
|
||||
this.#debuggerConnection = debuggerInfo;
|
||||
|
||||
debug(
|
||||
`Got new debugger connection via ${debuggerRelativeBaseUrl.href} for ` +
|
||||
`page ${pageId} of ${this.#name}`,
|
||||
);
|
||||
debug(`Got new debugger connection for page ${pageId} of ${this.#name}`);
|
||||
|
||||
if (this.#debuggerConnection && this.#createCustomMessageHandler) {
|
||||
this.#debuggerConnection.customHandler = this.#createCustomMessageHandler(
|
||||
@@ -384,7 +395,7 @@ export default class Device {
|
||||
const debuggerRequest = JSON.parse(message);
|
||||
this.#deviceEventReporter?.logRequest(debuggerRequest, 'debugger', {
|
||||
pageId: this.#debuggerConnection?.pageId ?? null,
|
||||
frontendUserAgent: userAgent,
|
||||
frontendUserAgent: metadata.userAgent,
|
||||
prefersFuseboxFrontend: this.#isPageFuseboxFrontend(
|
||||
this.#debuggerConnection?.pageId,
|
||||
),
|
||||
@@ -661,54 +672,6 @@ export default class Device {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a URL from the debugger frontend, returns the equivalent URL
|
||||
* reachable from the device.
|
||||
*/
|
||||
#debuggerRelativeToDeviceRelativeUrl(
|
||||
debuggerRelativeUrl: URL,
|
||||
{debuggerRelativeBaseUrl}: DebuggerConnection,
|
||||
): URL {
|
||||
const deviceRelativeUrl = new URL(debuggerRelativeUrl.href);
|
||||
if (debuggerRelativeUrl.origin === debuggerRelativeBaseUrl.origin) {
|
||||
deviceRelativeUrl.hostname = this.#deviceRelativeBaseUrl.hostname;
|
||||
deviceRelativeUrl.port = this.#deviceRelativeBaseUrl.port;
|
||||
deviceRelativeUrl.protocol = this.#deviceRelativeBaseUrl.protocol;
|
||||
}
|
||||
return deviceRelativeUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a URL from the device, returns the equivalent URL reachable from
|
||||
* the debugger frontend.
|
||||
*/
|
||||
#deviceRelativeUrlToDebuggerRelativeUrl(
|
||||
deviceRelativeUrl: URL,
|
||||
{debuggerRelativeBaseUrl}: DebuggerConnection,
|
||||
): URL {
|
||||
const debuggerRelativeUrl = new URL(deviceRelativeUrl.href);
|
||||
if (deviceRelativeUrl.origin === this.#deviceRelativeBaseUrl.origin) {
|
||||
debuggerRelativeUrl.hostname = debuggerRelativeBaseUrl.hostname;
|
||||
debuggerRelativeUrl.port = debuggerRelativeBaseUrl.port;
|
||||
debuggerRelativeUrl.protocol = debuggerRelativeUrl.protocol;
|
||||
}
|
||||
return debuggerRelativeUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a URL from the device, returns the equivalent URL reachable from
|
||||
* this proxy.
|
||||
*/
|
||||
#deviceRelativeUrlToServerRelativeUrl(deviceRelativeUrl: URL): URL {
|
||||
const debuggerRelativeUrl = new URL(deviceRelativeUrl.href);
|
||||
if (deviceRelativeUrl.origin === this.#deviceRelativeBaseUrl.origin) {
|
||||
debuggerRelativeUrl.hostname = this.#serverRelativeBaseUrl.hostname;
|
||||
debuggerRelativeUrl.port = this.#serverRelativeBaseUrl.port;
|
||||
debuggerRelativeUrl.protocol = this.#serverRelativeBaseUrl.protocol;
|
||||
}
|
||||
return debuggerRelativeUrl;
|
||||
}
|
||||
|
||||
// Allows to make changes in incoming message from device.
|
||||
async #processMessageFromDeviceLegacy(
|
||||
payload: CDPServerMessage,
|
||||
@@ -728,25 +691,25 @@ export default class Device {
|
||||
) {
|
||||
const params = payload.params;
|
||||
if ('sourceMapURL' in params) {
|
||||
for (const hostToRewrite of REWRITE_HOSTS_TO_LOCALHOST) {
|
||||
if (params.sourceMapURL.includes(hostToRewrite)) {
|
||||
payload.params.sourceMapURL = params.sourceMapURL.replace(
|
||||
hostToRewrite,
|
||||
'localhost',
|
||||
);
|
||||
debuggerInfo.originalSourceURLAddress = hostToRewrite;
|
||||
}
|
||||
}
|
||||
|
||||
const sourceMapURL = this.#tryParseHTTPURL(params.sourceMapURL);
|
||||
if (sourceMapURL) {
|
||||
// Rewrite device-relative URLs to de debugger-relative URLs for the
|
||||
// frontend.
|
||||
payload.params.sourceMapURL =
|
||||
this.#deviceRelativeUrlToDebuggerRelativeUrl(
|
||||
sourceMapURL,
|
||||
debuggerInfo,
|
||||
).href;
|
||||
|
||||
// Some debug clients do not support fetching HTTP URLs. If the
|
||||
// message headed to the debug client identifies the source map with
|
||||
// an HTTP URL, fetch the content here and convert the content to a
|
||||
// Data URL (which is more widely supported) before passing the
|
||||
// message to the debug client.
|
||||
try {
|
||||
const sourceMap = await this.#fetchText(
|
||||
this.#deviceRelativeUrlToServerRelativeUrl(sourceMapURL),
|
||||
);
|
||||
const sourceMap = await this.#fetchText(sourceMapURL);
|
||||
payload.params.sourceMapURL =
|
||||
'data:application/json;charset=utf-8;base64,' +
|
||||
Buffer.from(sourceMap).toString('base64');
|
||||
@@ -758,19 +721,11 @@ export default class Device {
|
||||
}
|
||||
}
|
||||
if ('url' in params) {
|
||||
let serverRelativeUrl = params.url;
|
||||
const parsedUrl = this.#tryParseHTTPURL(params.url);
|
||||
if (parsedUrl) {
|
||||
// Rewrite device-relative URLs pointing to the server so that they're
|
||||
// reachable from the frontend.
|
||||
payload.params.url = this.#deviceRelativeUrlToDebuggerRelativeUrl(
|
||||
parsedUrl,
|
||||
debuggerInfo,
|
||||
).href;
|
||||
|
||||
// Determine the server-relative URL.
|
||||
serverRelativeUrl =
|
||||
this.#deviceRelativeUrlToServerRelativeUrl(parsedUrl).href;
|
||||
for (const hostToRewrite of REWRITE_HOSTS_TO_LOCALHOST) {
|
||||
if (params.url.includes(hostToRewrite)) {
|
||||
payload.params.url = params.url.replace(hostToRewrite, 'localhost');
|
||||
debuggerInfo.originalSourceURLAddress = hostToRewrite;
|
||||
}
|
||||
}
|
||||
|
||||
// Chrome doesn't download source maps if URL param is not a valid
|
||||
@@ -782,13 +737,9 @@ export default class Device {
|
||||
debuggerInfo.prependedFilePrefix = true;
|
||||
}
|
||||
|
||||
if ('scriptId' in params && params.scriptId != null) {
|
||||
// Set a server-relative URL to locally fetch source by script ID
|
||||
// on Debugger.getScriptSource.
|
||||
this.#scriptIdToSourcePathMapping.set(
|
||||
params.scriptId,
|
||||
serverRelativeUrl,
|
||||
);
|
||||
// $FlowFixMe[prop-missing]
|
||||
if (params.scriptId != null) {
|
||||
this.#scriptIdToSourcePathMapping.set(params.scriptId, params.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -848,31 +799,6 @@ export default class Device {
|
||||
// Sends response to debugger via side-effect
|
||||
this.#processDebuggerGetScriptSource(req, socket);
|
||||
return null;
|
||||
case 'Network.loadNetworkResource':
|
||||
// If we're rewriting URLs (to frontend-relative), we don't want to
|
||||
// pass these URLs to the device, since it may try to fetch, return a
|
||||
// CDP *result* (not error) with a network failure, and CDT
|
||||
// will *not* then fall back to fetching locally.
|
||||
//
|
||||
// Instead, take the absence of a nativeSourceCodeFetching
|
||||
// capability as a signal to never pass a loadNetworkResource request
|
||||
// to the device. By returning a CDP error, the frontend should fetch.
|
||||
const result = {
|
||||
error: {
|
||||
code: -32601, // Method not found
|
||||
message:
|
||||
'[inspector-proxy]: Page lacks nativeSourceCodeFetching capability.',
|
||||
},
|
||||
};
|
||||
const response = {id: req.id, result};
|
||||
socket.send(JSON.stringify(response));
|
||||
const pageId = this.#debuggerConnection?.pageId ?? null;
|
||||
this.#deviceEventReporter?.logResponse(response, 'proxy', {
|
||||
pageId,
|
||||
frontendUserAgent: this.#debuggerConnection?.userAgent ?? null,
|
||||
prefersFuseboxFrontend: this.#isPageFuseboxFrontend(pageId),
|
||||
});
|
||||
return null;
|
||||
default:
|
||||
return req;
|
||||
}
|
||||
@@ -883,52 +809,36 @@ export default class Device {
|
||||
debuggerInfo: DebuggerConnection,
|
||||
): CDPRequest<'Debugger.setBreakpointByUrl'> {
|
||||
// If we replaced Android emulator's address to localhost we need to change it back.
|
||||
const {debuggerRelativeBaseUrl, prependedFilePrefix} = debuggerInfo;
|
||||
if (debuggerInfo.originalSourceURLAddress != null) {
|
||||
const processedReq = {...req, params: {...req.params}};
|
||||
if (processedReq.params.url != null) {
|
||||
processedReq.params.url = processedReq.params.url.replace(
|
||||
'localhost',
|
||||
debuggerInfo.originalSourceURLAddress,
|
||||
);
|
||||
|
||||
const processedReq = {...req, params: {...req.params}};
|
||||
if (processedReq.params.url != null) {
|
||||
const originalUrlParam = processedReq.params.url;
|
||||
const httpUrl = this.#tryParseHTTPURL(originalUrlParam);
|
||||
if (httpUrl) {
|
||||
processedReq.params.url = this.#debuggerRelativeToDeviceRelativeUrl(
|
||||
httpUrl,
|
||||
debuggerInfo,
|
||||
).href;
|
||||
} else if (
|
||||
originalUrlParam.startsWith(FILE_PREFIX) &&
|
||||
prependedFilePrefix
|
||||
) {
|
||||
// Remove fake URL prefix if we modified URL in #processMessageFromDeviceLegacy.
|
||||
processedReq.params.url = originalUrlParam.slice(FILE_PREFIX.length);
|
||||
if (
|
||||
processedReq.params.url &&
|
||||
processedReq.params.url.startsWith(FILE_PREFIX) &&
|
||||
debuggerInfo.prependedFilePrefix
|
||||
) {
|
||||
// Remove fake URL prefix if we modified URL in #processMessageFromDeviceLegacy.
|
||||
// $FlowFixMe[incompatible-use]
|
||||
processedReq.params.url = processedReq.params.url.slice(
|
||||
FILE_PREFIX.length,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (processedReq.params.urlRegex != null) {
|
||||
processedReq.params.urlRegex = processedReq.params.urlRegex.replace(
|
||||
/localhost/g,
|
||||
// $FlowFixMe[incompatible-call]
|
||||
debuggerInfo.originalSourceURLAddress,
|
||||
);
|
||||
}
|
||||
return processedReq;
|
||||
}
|
||||
|
||||
// Retain special case rewriting of localhost to device-relative IPs
|
||||
// within regex patterns. We don't rewrite the protocol here because
|
||||
// these patterns typically come from CDT reinterpreting the source URL
|
||||
// `file://host/path` into the regex `host/path|file://host/path`. See:
|
||||
//
|
||||
// https://github.com/ChromeDevTools/devtools-frontend/blob/f913cc6d76f2e2639c05b11ba673fc880b5490dd/front_end/core/sdk/DebuggerModel.ts#L505
|
||||
//
|
||||
// This has always been fragile and probably unnecessary - we don't set
|
||||
// `file://` source URLs. It can be removed when we drop support for
|
||||
// legacy targets, if not sooner.
|
||||
if (
|
||||
// Android's stock emulator and other emulators such as genymotion use a
|
||||
// standard localhost alias.
|
||||
new Set(['10.0.2.2', '10.0.3.2']).has(
|
||||
this.#deviceRelativeBaseUrl.hostname,
|
||||
) &&
|
||||
debuggerRelativeBaseUrl.hostname === 'localhost' &&
|
||||
processedReq.params.urlRegex != null
|
||||
) {
|
||||
processedReq.params.urlRegex = processedReq.params.urlRegex.replaceAll(
|
||||
'localhost',
|
||||
// regex-escape IPv4
|
||||
this.#deviceRelativeBaseUrl.hostname.replaceAll('.', '\\.'),
|
||||
);
|
||||
}
|
||||
return processedReq;
|
||||
return req;
|
||||
}
|
||||
|
||||
#processDebuggerGetScriptSource(
|
||||
@@ -971,7 +881,6 @@ export default class Device {
|
||||
if (pathToSource != null) {
|
||||
const httpURL = this.#tryParseHTTPURL(pathToSource);
|
||||
if (httpURL) {
|
||||
// URL is server-relatve, so we should be able to fetch it from here.
|
||||
this.#fetchText(httpURL).then(
|
||||
text => sendSuccessResponse(text),
|
||||
err =>
|
||||
|
||||
@@ -145,17 +145,6 @@ class DeviceEventReporter {
|
||||
});
|
||||
}
|
||||
|
||||
logProfilingTargetRegistered() {
|
||||
this.#eventReporter.logEvent({
|
||||
type: 'profiling_target_registered',
|
||||
status: 'success',
|
||||
appId: this.#metadata.appId,
|
||||
deviceName: this.#metadata.deviceName,
|
||||
deviceId: this.#metadata.deviceId,
|
||||
pageId: null,
|
||||
});
|
||||
}
|
||||
|
||||
logConnection(
|
||||
connectedEntity: 'debugger',
|
||||
metadata: $ReadOnly<{
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
import type {EventReporter} from '../types/EventReporter';
|
||||
import type {Experiments} from '../types/Experiments';
|
||||
import type {CreateCustomMessageHandlerFn} from './CustomMessageHandler';
|
||||
import type {DeviceOptions} from './Device';
|
||||
import type {
|
||||
JsonPagesListResponse,
|
||||
JsonVersionResponse,
|
||||
@@ -23,7 +22,6 @@ import type {IncomingMessage, ServerResponse} from 'http';
|
||||
// $FlowFixMe[cannot-resolve-module] libdef missing in RN OSS
|
||||
import type {Timeout} from 'timers';
|
||||
|
||||
import getBaseUrlFromRequest from '../utils/getBaseUrlFromRequest';
|
||||
import Device from './Device';
|
||||
import nullthrows from 'nullthrows';
|
||||
// Import these from node:timers to get the correct Flow types.
|
||||
@@ -49,7 +47,7 @@ export interface InspectorProxyQueries {
|
||||
* Returns list of page descriptions ordered by device connection order, then
|
||||
* page addition order.
|
||||
*/
|
||||
getPageDescriptions(requestorRelativeBaseUrl: URL): Array<PageDescription>;
|
||||
getPageDescriptions(): Array<PageDescription>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,8 +57,8 @@ export default class InspectorProxy implements InspectorProxyQueries {
|
||||
// Root of the project used for relative to absolute source path conversion.
|
||||
#projectRoot: string;
|
||||
|
||||
// The base URL to the dev server from the dev-middleware host.
|
||||
#serverBaseUrl: URL;
|
||||
/** The base URL to the dev server from the developer machine. */
|
||||
#serverBaseUrl: string;
|
||||
|
||||
// Maps device ID to Device instance.
|
||||
#devices: Map<string, Device>;
|
||||
@@ -83,14 +81,14 @@ export default class InspectorProxy implements InspectorProxyQueries {
|
||||
customMessageHandler: ?CreateCustomMessageHandlerFn,
|
||||
) {
|
||||
this.#projectRoot = projectRoot;
|
||||
this.#serverBaseUrl = new URL(serverBaseUrl);
|
||||
this.#serverBaseUrl = serverBaseUrl;
|
||||
this.#devices = new Map();
|
||||
this.#eventReporter = eventReporter;
|
||||
this.#experiments = experiments;
|
||||
this.#customMessageHandler = customMessageHandler;
|
||||
}
|
||||
|
||||
getPageDescriptions(requestorRelativeBaseUrl: URL): Array<PageDescription> {
|
||||
getPageDescriptions(): Array<PageDescription> {
|
||||
// Build list of pages from all devices.
|
||||
let result: Array<PageDescription> = [];
|
||||
Array.from(this.#devices.entries()).forEach(([deviceId, device]) => {
|
||||
@@ -98,12 +96,7 @@ export default class InspectorProxy implements InspectorProxyQueries {
|
||||
device
|
||||
.getPagesList()
|
||||
.map((page: Page) =>
|
||||
this.#buildPageDescription(
|
||||
deviceId,
|
||||
device,
|
||||
page,
|
||||
requestorRelativeBaseUrl,
|
||||
),
|
||||
this.#buildPageDescription(deviceId, device, page),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -124,12 +117,7 @@ export default class InspectorProxy implements InspectorProxyQueries {
|
||||
pathname === PAGES_LIST_JSON_URL ||
|
||||
pathname === PAGES_LIST_JSON_URL_2
|
||||
) {
|
||||
this.#sendJsonResponse(
|
||||
response,
|
||||
this.getPageDescriptions(
|
||||
getBaseUrlFromRequest(request) ?? this.#serverBaseUrl,
|
||||
),
|
||||
);
|
||||
this.#sendJsonResponse(response, this.getPageDescriptions());
|
||||
} else if (pathname === PAGES_LIST_JSON_VERSION_URL) {
|
||||
this.#sendJsonResponse(response, {
|
||||
Browser: 'Mobile JavaScript',
|
||||
@@ -155,9 +143,8 @@ export default class InspectorProxy implements InspectorProxyQueries {
|
||||
deviceId: string,
|
||||
device: Device,
|
||||
page: Page,
|
||||
requestorRelativeBaseUrl: URL,
|
||||
): PageDescription {
|
||||
const {host, protocol} = requestorRelativeBaseUrl;
|
||||
const {host, protocol} = new URL(this.#serverBaseUrl);
|
||||
const webSocketScheme = protocol === 'https:' ? 'wss' : 'ws';
|
||||
|
||||
const webSocketUrlWithoutProtocol = `${host}${WS_DEBUGGER_URL}?device=${deviceId}&page=${page.id}`;
|
||||
@@ -174,7 +161,6 @@ export default class InspectorProxy implements InspectorProxyQueries {
|
||||
id: `${deviceId}-${page.id}`,
|
||||
title: page.title,
|
||||
description: page.description ?? page.app,
|
||||
appId: page.app,
|
||||
type: 'node',
|
||||
devtoolsFrontendUrl,
|
||||
webSocketDebuggerUrl,
|
||||
@@ -225,38 +211,36 @@ export default class InspectorProxy implements InspectorProxyQueries {
|
||||
const deviceId = query.device || fallbackDeviceId;
|
||||
const deviceName = query.name || 'Unknown';
|
||||
const appName = query.app || 'Unknown';
|
||||
const isProfilingBuild = query.profiling === 'true';
|
||||
|
||||
const deviceRelativeBaseUrl =
|
||||
getBaseUrlFromRequest(req) ?? this.#serverBaseUrl;
|
||||
|
||||
const oldDevice = this.#devices.get(deviceId);
|
||||
|
||||
let newDevice;
|
||||
const deviceOptions: DeviceOptions = {
|
||||
id: deviceId,
|
||||
name: deviceName,
|
||||
app: appName,
|
||||
socket,
|
||||
projectRoot: this.#projectRoot,
|
||||
eventReporter: this.#eventReporter,
|
||||
createMessageMiddleware: this.#customMessageHandler,
|
||||
deviceRelativeBaseUrl,
|
||||
serverRelativeBaseUrl: this.#serverBaseUrl,
|
||||
isProfilingBuild,
|
||||
};
|
||||
|
||||
if (oldDevice) {
|
||||
oldDevice.dangerouslyRecreateDevice(deviceOptions);
|
||||
oldDevice.dangerouslyRecreateDevice(
|
||||
deviceId,
|
||||
deviceName,
|
||||
appName,
|
||||
socket,
|
||||
this.#projectRoot,
|
||||
this.#eventReporter,
|
||||
this.#customMessageHandler,
|
||||
);
|
||||
newDevice = oldDevice;
|
||||
} else {
|
||||
newDevice = new Device(deviceOptions);
|
||||
newDevice = new Device(
|
||||
deviceId,
|
||||
deviceName,
|
||||
appName,
|
||||
socket,
|
||||
this.#projectRoot,
|
||||
this.#eventReporter,
|
||||
this.#customMessageHandler,
|
||||
);
|
||||
}
|
||||
|
||||
this.#devices.set(deviceId, newDevice);
|
||||
|
||||
debug(
|
||||
`Got new connection: name=${deviceName}, app=${appName}, device=${deviceId}, via=${deviceRelativeBaseUrl.origin}`,
|
||||
`Got new connection: name=${deviceName}, app=${appName}, device=${deviceId}`,
|
||||
);
|
||||
|
||||
socket.on('close', () => {
|
||||
@@ -292,8 +276,6 @@ export default class InspectorProxy implements InspectorProxyQueries {
|
||||
const query = url.parse(req.url || '', true).query || {};
|
||||
const deviceId = query.device;
|
||||
const pageId = query.page;
|
||||
const debuggerRelativeBaseUrl =
|
||||
getBaseUrlFromRequest(req) ?? this.#serverBaseUrl;
|
||||
|
||||
if (deviceId == null || pageId == null) {
|
||||
throw new Error('Incorrect URL - must provide device and page IDs');
|
||||
@@ -307,7 +289,6 @@ export default class InspectorProxy implements InspectorProxyQueries {
|
||||
this.#startHeartbeat(socket, DEBUGGER_HEARTBEAT_INTERVAL_MS);
|
||||
|
||||
device.handleDebuggerConnection(socket, pageId, {
|
||||
debuggerRelativeBaseUrl,
|
||||
userAgent: req.headers['user-agent'] ?? query.userAgent ?? null,
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
@@ -44,7 +44,6 @@ export type CDPClientMessage =
|
||||
| CDPRequest<'Debugger.getScriptSource'>
|
||||
| CDPRequest<'Debugger.scriptParsed'>
|
||||
| CDPRequest<'Debugger.setBreakpointByUrl'>
|
||||
| CDPRequest<'Network.loadNetworkResource'>
|
||||
| CDPRequest<>;
|
||||
|
||||
export type CDPServerMessage =
|
||||
|
||||
@@ -18,10 +18,9 @@ export type TargetCapabilityFlags = $ReadOnly<{
|
||||
* The target supports a stable page representation across reloads.
|
||||
*
|
||||
* In the proxy, this disables legacy page reload emulation and the
|
||||
* additional 'React Native Experimental' target in `/json/list`.
|
||||
* additional '(Experimental)' target in `/json/list`.
|
||||
*
|
||||
* In the launch flow, this allows targets to be matched directly by
|
||||
* `logicalDeviceId`.
|
||||
* In the launch flow, this allows targets to be matched directly by `appId`.
|
||||
*/
|
||||
nativePageReloads?: boolean,
|
||||
|
||||
@@ -116,7 +115,6 @@ export type MessageToDevice =
|
||||
export type PageDescription = $ReadOnly<{
|
||||
id: string,
|
||||
title: string,
|
||||
appId: string,
|
||||
description: string,
|
||||
type: string,
|
||||
devtoolsFrontendUrl: string,
|
||||
|
||||
@@ -20,9 +20,6 @@ import type {IncomingMessage, ServerResponse} from 'http';
|
||||
import getDevToolsFrontendUrl from '../utils/getDevToolsFrontendUrl';
|
||||
import url from 'url';
|
||||
|
||||
const LEGACY_SYNTHETIC_PAGE_TITLE =
|
||||
'React Native Experimental (Improved Chrome Reloads)';
|
||||
|
||||
type Options = $ReadOnly<{
|
||||
serverBaseUrl: string,
|
||||
logger?: Logger,
|
||||
@@ -35,8 +32,8 @@ type Options = $ReadOnly<{
|
||||
/**
|
||||
* Open the debugger frontend for a given CDP target.
|
||||
*
|
||||
* Currently supports React Native DevTools (rn_fusebox.html) and legacy Hermes
|
||||
* (rn_inspector.html) targets.
|
||||
* Currently supports Hermes targets, opening debugger websocket URL in Chrome
|
||||
* DevTools.
|
||||
*
|
||||
* @see https://chromedevtools.github.io/devtools-protocol/
|
||||
*/
|
||||
@@ -64,7 +61,6 @@ export default function openDebuggerMiddleware({
|
||||
launchId,
|
||||
target: targetId,
|
||||
}: {
|
||||
/** @deprecated Will only match legacy Hermes targets */
|
||||
appId?: string,
|
||||
device?: string,
|
||||
launchId?: string,
|
||||
@@ -72,14 +68,12 @@ export default function openDebuggerMiddleware({
|
||||
...
|
||||
} = query;
|
||||
|
||||
const targets = inspectorProxy
|
||||
.getPageDescriptions(new URL(serverBaseUrl))
|
||||
.filter(
|
||||
// Only use targets with better reloading support
|
||||
app =>
|
||||
app.title === LEGACY_SYNTHETIC_PAGE_TITLE ||
|
||||
app.reactNative.capabilities?.nativePageReloads === true,
|
||||
);
|
||||
const targets = inspectorProxy.getPageDescriptions().filter(
|
||||
// Only use targets with better reloading support
|
||||
app =>
|
||||
app.title === 'React Native Experimental (Improved Chrome Reloads)' ||
|
||||
app.reactNative.capabilities?.nativePageReloads === true,
|
||||
);
|
||||
|
||||
let target;
|
||||
|
||||
@@ -98,9 +92,7 @@ export default function openDebuggerMiddleware({
|
||||
target = targets.find(
|
||||
_target =>
|
||||
(targetId == null || _target.id === targetId) &&
|
||||
(appId == null ||
|
||||
(_target.appId === appId &&
|
||||
_target.title === LEGACY_SYNTHETIC_PAGE_TITLE)) &&
|
||||
(appId == null || _target.description === appId) &&
|
||||
(device == null || _target.reactNative.logicalDeviceId === device),
|
||||
);
|
||||
} else if (targets.length > 0) {
|
||||
@@ -164,7 +156,6 @@ export default function openDebuggerMiddleware({
|
||||
appId: appId ?? null,
|
||||
deviceId: device ?? null,
|
||||
resolvedTargetDescription: target.description,
|
||||
resolvedTargetAppId: target.appId,
|
||||
prefersFuseboxFrontend: useFuseboxEntryPoint ?? false,
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -18,10 +18,6 @@ export interface BrowserLauncher {
|
||||
* Attempt to open a debugger frontend URL in a browser app window,
|
||||
* optionally returning an object to control the launched browser instance.
|
||||
* The browser used should be capable of running Chrome DevTools.
|
||||
*
|
||||
* The provided url is based on serverBaseUrl, and therefore reachable from
|
||||
* the host of dev-middleware. Implementations are responsible for rewriting
|
||||
* this as necessary where the server is remote.
|
||||
*/
|
||||
launchDebuggerAppWindow: (url: string) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ export type ReportableEvent =
|
||||
appId: string | null,
|
||||
deviceId: string | null,
|
||||
resolvedTargetDescription: string,
|
||||
resolvedTargetAppId: string,
|
||||
prefersFuseboxFrontend: boolean,
|
||||
}>
|
||||
| ErrorResult<mixed>
|
||||
@@ -77,11 +76,6 @@ export type ReportableEvent =
|
||||
| 'PROTOCOL_ERROR',
|
||||
>,
|
||||
}
|
||||
| {
|
||||
type: 'profiling_target_registered',
|
||||
status: 'success',
|
||||
...DebuggerSessionIDs,
|
||||
}
|
||||
| {
|
||||
type: 'proxy_error',
|
||||
status: 'error',
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
// Determine the base URL (scheme and host) used by a client to reach this
|
||||
// server.
|
||||
//
|
||||
// TODO: Support X-Forwarded-Host, etc. for trusted proxies
|
||||
export default function getBaseUrlFromRequest(
|
||||
req: http$IncomingMessage<tls$TLSSocket> | http$IncomingMessage<net$Socket>,
|
||||
): ?URL {
|
||||
const hostHeader = req.headers.host;
|
||||
if (hostHeader == null) {
|
||||
return null;
|
||||
}
|
||||
// `encrypted` is always true for TLS sockets and undefined for net
|
||||
// https://github.com/nodejs/node/issues/41863#issuecomment-1030709186
|
||||
const scheme = req.socket.encrypted === true ? 'https' : 'http';
|
||||
const url = `${scheme}://${req.headers.host}`;
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -65,11 +65,7 @@ function getWsParam({
|
||||
const serverHost = new URL(devServerUrl).host;
|
||||
let value;
|
||||
if (wsUrl.host === serverHost) {
|
||||
// Use a path-absolute (host-relative) URL if the WS server and frontend
|
||||
// server are colocated. This is more robust for cases where the frontend
|
||||
// may actually load through a tunnel or proxy, and the WS connection
|
||||
// should therefore do the same.
|
||||
//
|
||||
// Use a path-absolute (host-relative) URL
|
||||
// Depends on https://github.com/facebookexperimental/rn-chrome-devtools-frontend/pull/4
|
||||
value = wsUrl.pathname + wsUrl.search + wsUrl.hash;
|
||||
} else {
|
||||
|
||||
@@ -39,7 +39,7 @@ module.exports = {
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.js'],
|
||||
parser: '@babel/eslint-parser',
|
||||
parser: 'hermes-eslint',
|
||||
plugins: ['ft-flow'],
|
||||
rules: {
|
||||
// Flow Plugin
|
||||
@@ -51,7 +51,7 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
files: ['*.jsx'],
|
||||
parser: '@babel/eslint-parser',
|
||||
parser: 'hermes-eslint',
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/eslint-parser": "^7.25.1",
|
||||
"@react-native/eslint-plugin": "0.77.0-main",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
@@ -31,7 +30,8 @@
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-native": "^4.0.0"
|
||||
"eslint-plugin-react-native": "^4.0.0",
|
||||
"hermes-eslint": "0.24.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=8",
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"main": "index.js",
|
||||
"devDependencies": {
|
||||
"babel-plugin-syntax-hermes-parser": "0.25.1",
|
||||
"hermes-eslint": "0.25.1"
|
||||
"babel-plugin-syntax-hermes-parser": "0.24.0",
|
||||
"hermes-eslint": "0.24.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"source-map-support": "0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-syntax-hermes-parser": "0.25.1",
|
||||
"hermes-eslint": "0.25.1"
|
||||
"babel-plugin-syntax-hermes-parser": "0.24.0",
|
||||
"hermes-eslint": "0.24.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[versions]
|
||||
agp = "8.7.2"
|
||||
agp = "8.6.0"
|
||||
gson = "2.8.9"
|
||||
guava = "31.0.1-jre"
|
||||
javapoet = "1.13.0"
|
||||
junit = "4.13.2"
|
||||
kotlin = "2.0.21"
|
||||
kotlin = "1.9.24"
|
||||
assertj = "3.25.1"
|
||||
|
||||
[libraries]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
*/
|
||||
|
||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
@@ -62,10 +60,10 @@ java { targetCompatibility = JavaVersion.VERSION_11 }
|
||||
kotlin { jvmToolchain(17) }
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
compilerOptions {
|
||||
apiVersion.set(KotlinVersion.KOTLIN_1_7)
|
||||
kotlinOptions {
|
||||
apiVersion = "1.6"
|
||||
// See comment above on JDK 11 support
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
jvmTarget = "11"
|
||||
allWarningsAsErrors =
|
||||
project.properties["enableWarningsAsErrors"]?.toString()?.toBoolean() ?: false
|
||||
}
|
||||
|
||||
+3
-7
@@ -12,7 +12,6 @@ import com.facebook.react.utils.detectOSAwareHermesCommand
|
||||
import com.facebook.react.utils.moveTo
|
||||
import com.facebook.react.utils.windowsAwareCommandLine
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileTree
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
@@ -20,7 +19,6 @@ import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.process.ExecOperations
|
||||
|
||||
abstract class BundleHermesCTask : DefaultTask() {
|
||||
|
||||
@@ -28,8 +26,6 @@ abstract class BundleHermesCTask : DefaultTask() {
|
||||
group = "react"
|
||||
}
|
||||
|
||||
@get:Inject abstract val execOperations: ExecOperations
|
||||
|
||||
@get:Internal abstract val root: DirectoryProperty
|
||||
|
||||
@get:InputFiles
|
||||
@@ -131,9 +127,9 @@ abstract class BundleHermesCTask : DefaultTask() {
|
||||
File(jsIntermediateSourceMapsDir.get().asFile, "$bundleAssetName.compiler.map")
|
||||
|
||||
private fun runCommand(command: List<Any>) {
|
||||
execOperations.exec { exec ->
|
||||
exec.workingDir(root.get().asFile)
|
||||
exec.commandLine(command)
|
||||
project.exec {
|
||||
it.workingDir(root.get().asFile)
|
||||
it.commandLine(command)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user