Dev/android api alignment - #431
Open
af-obodovskyi wants to merge 40 commits into
Open
Conversation
The greps that print xcodebuild errors to the console only match "Undefined symbols" / ld / clang / error: patterns, so a BUILD FAILED caused by anything else leaves no diagnosable output. Persist the full /tmp/xcode-build.log as an artifact, mirroring the unity-build-log-ios fix.
…ore iOS xcodebuild Root-caused a "'AppsFlyerLib.h' file not found" xcodebuild failure via the downloaded xcode-build.log: the AppsFlyerLib-Dynamic SPM package (fetched over the network as part of xcodebuild's implicit package resolution) never produced a fully-populated framework before the compile step ran, but xcodebuild build didn't surface this as a resolution error — it silently proceeded and failed deep in an unrelated-looking compile. The identical build succeeds locally, confirming this is a CI-only resolution flake, not a plugin/config defect. Add an explicit `xcodebuild -resolvePackageDependencies` step with 3 retries before the compile step, so a network flake fails loudly and recovers on retry instead of masquerading as a missing-header compile error. Also upload its log as an artifact for future diagnosability.
Root cause of the "AppsFlyerLib.h file not found" xcodebuild failure:
Unity's bundled (Mono) System.Xml reader throws "An XML comment cannot
contain '--'" when parsing the em dash (—, U+2014) in these two comments,
even though no literal double-hyphen is present. EDM4U's IOSResolver
catches that XmlException and silently discards ALL iOS dependencies
("iOS dependencies in this file will be ignored"), so no Swift Package
references (AppsFlyerLib, AppsFlyerRPC, PurchaseConnector) ever get added
to the exported Xcode project — confirmed via unity-build.log from the
failing CI run, and via the paired xcode-resolve.log showing zero
resolved packages.
This is a real, deterministic parser bug in the CI Unity Editor's XML
reader, not a network flake — the previous xcodebuild -resolvePackageDependencies
retry step (commit 1cd1476) was chasing the wrong hypothesis and had no
effect, since resolution correctly found nothing to resolve.
scripts/bump-version.sh used unanchored sed patterns like `af-android-plugin-bridge:[^"]*` to bump version specs in AppsFlyerDependencies.xml. These also matched the same coordinate mentioned in free-text comment prose, greedily eating everything up to the next unrelated quote later in the file — including the closing `-->` of a comment. That's what corrupted the file on the 7.0.2-rc1 release branch (commit 00bdfd2), which then made Unity's XML reader throw on the next comment's "<!--" and silently drop all iOS dependencies, causing the "AppsFlyerLib.h file not found" xcodebuild failure. Anchors the three vulnerable patterns to spec="..." so they can only match inside the actual XML attribute value, and removes the two comments whose prose repeated a version-like substring, so there's nothing left for a regression to latch onto.
…tting EDM4U's Google.IOSResolver.SwiftPackageManagerEnabled=True setting lived only in the gitignored test-app/ProjectSettings/GvhProjectSettings.xml, added in 090ffe8 alongside the genuinely-regenerated AndroidResolverDependencies.xml under a "regenerated on every build" comment. It isn't regenerated from scratch - it's a persisted, hand-set setting. Every fresh CI checkout got EDM4U's own default (Podfile mode) instead, and since rc-e2e-ios.yml has no pod install step, iOS builds failed to find AppsFlyerLib.h once the earlier XML corruption bug was fixed.
…upport) test-app (the project rc-e2e-ios.yml actually builds) was still on EDM4U 1.2.183, which has no SwiftPackageManagerEnabled property and doesn't recognize <remoteSwiftPackage> at all - it silently ignores those elements and falls back to the Podfile-style <iosPod> entries instead. rc-e2e-ios.yml has no `pod install` step, so those pods never get installed, leaving AppsFlyerLib.h missing regardless of the XML-corruption fix or the GvhProjectSettings.xml settings fix already applied. The root Unity project (Assets/ExternalDependencyManager) was already on 1.2.187; test-app's copy just hadn't been upgraded to match. xcode-resolve.log confirmed this: "resolved source packages:" came back empty even after SwiftPackageManagerEnabled=True was committed, because the old resolver DLL doesn't have that property at all.
QATestScript implements IAppsFlyerConversionData (onConversionDataSuccess/ onConversionDataFail) but the plugin no longer dispatches conversion data by directly invoking that interface on the init() gameObject - native only ever calls UnitySendMessage(CallBackObjectName, "onRPCEvent", ...), and onRPCEvent only forwards to onConversionDataSuccessCallback/onConversionDataFailCallback, which are populated solely by AppsFlyer.registerConversionListener(). Since QATestScript never called it, the E2E "is_first_launch_true" check always failed waiting for a callback that had nowhere to route to - unrelated to the build-pipeline fixes already applied (XML corruption, GvhProjectSettings.xml, EDM4U 1.2.183->1.2.187).
…plicate registerConversionListener was already called in QATestScript, just after init()/the Android lifecycle nudge instead of before it - my previous commit missed that (grepped for it in other files but not this one) and added a second, redundant call instead of fixing the placement of the existing one. Real bug: the local onConversionDataSuccessCallback/onConversionDataFailCallback delegates are only set once registerConversionListener() runs its synchronous prefix, but native can fire onInstallConversionData as soon as init()'s "initialize" RPC call lands. Registering after init() (and after an Android-only lifecycle nudge) left a window where the event had already fired with no delegate listening, and it was silently dropped. Moved the one real call to before AppsFlyer.init(), removed the now-redundant later one.
Screenshot from unity-e2e-ios-20260907_010443 (phase_1_screen.png) shows the ATT "Allow tracking" system dialog still on screen ~5 minutes after launch - unanswered, because there's no one in CI to tap it. Per ATTPermissionRequest.mm's own comment, once that dialog appears the whole app is suspended at the OS level until it's dismissed. That's why nothing downstream ever happened: AppsFlyer's install_data/conversions network requests sat cancelled/in-flight for the full wait window (see phase_1_logs.txt quic-connection entries) and onInstallConversionData never fired - not a plugin bug, the app was just frozen behind a modal nobody could answer. `xcrun simctl privacy` has no "tracking" service to pre-grant, so the request is now opt-in via .env (REQUEST_ATT=true), defaulting to skipped since none of the six E2E scenarios (E2E-001..006) exercise the ATT flow.
…ocking deep links The Obj-C++ -> Swift RPC transport migration (1507c8c) deleted the old _startSDK's isBridgeReady=YES + AF_BRIDGE_SET notification (previously fired synchronously as _startSDK's first statement, before startWithCompletionHandler) without carrying the equivalent into AppsFlyerRPCWrapper.swift. isBridgeReady gates AppsFlyerAttribution's handleOpenUrl:/continueUserActivity: (see AppsFlyerAttribution.m) and was left permanently NO, so every iOS deep link delivered via those paths was silently and permanently dropped, regardless of timing - a real regression, not a CI/test-app issue, confirmed by phase_2/ phase_3 E2E runs never logging a single onDeepLinking callback. Restore it by having AppsFlyerRPCWrapper.swift's _afFireJson/_afExecuteJson detect the "start" RPC method (raw substring match on the request JSON, same technique already used for response error-detection in this file) and call a new tiny Obj-C shim, _afMarkBridgeReady() in AppsFlyerAttribution.m, that sets isBridgeReady and posts AF_BRIDGE_SET exactly as the old wrapper did.
Latest run confirms deep links now arrive (deepLinkValue correctly populated after the bridge-ready fix), but deeplink_found/deeplink_found_fg still fail: DeepLinkEventsArgs' status ends up ERROR instead of the expected FOUND. DeepLinkEventsArgs' constructor only recognizes literal "FOUND"/"NOT_FOUND" and silently collapses anything else - including a genuinely successful native result under some other spelling - into DeepLinkStatus.ERROR, discarding the original string. AFSDKDeepLinkResultStatus (AppsFlyerLib) is a 3-case ObjC NSUInteger enum (NotFound/Found/Failure) with no built-in string form, and nothing in the vendored AppsFlyerRPC.framework binary contains literal "FOUND"/ "NOT_FOUND"/"FAILURE" strings - suggesting whatever serializes this enum to JSON may not be emitting the casing our switch expects, but this can't be confirmed from a closed-source binary alone. Add DeepLinkEventsArgs.rawStatus (the untouched original string) and log it from QATestScript so the next E2E run shows the literal wire value, before changing the status-mapping logic based on a guess.
The iOS RPC bridge emits lowercase status/error strings (e.g. "found"),
but DeepLinkEventsArgs matched only uppercase literals ("FOUND"),
so valid deep links were misreported as DeepLinkStatus.ERROR.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…emplate.properties CI Android builds failed with: gradleTemplate.properties should contain "unityStreamingAssets=**STREAMING_ASSETS**" Unity requires this placeholder to mark StreamingAssets files as uncompressed in the APK; it was missing from the committed template. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AppsFlyerUnityActivity.java was migrated to extend UnityPlayerGameActivity and the manifest theme to BaseUnityGameActivityTheme in 5b65e70, but Player Settings was never flipped from Activity-only (1) to GameActivity (2) to match — so Unity's Android build never generated the UnityPlayerGameActivity class or theme resource the migration depends on, failing the Build Android APK CI step with "cannot find symbol: UnityPlayerGameActivity" and a missing BaseUnityGameActivityTheme resource. Reproduced locally with the old value and confirmed the fix clears both errors and produces a working APK. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AppsFlyer.cs's BeforeSceneLoad bootstrap calls init() once with an empty callback name before any scene exists, then AppsFlyer.init() calls it again with the real one. Every call used to unconditionally rebuild sHandler, discarding whatever the throwaway first instance had already set up. QATestScript deliberately calls registerConversionListener/ registerDeepLinkListener before AppsFlyer.init() (to avoid missing early native events), so those registrations always ran against the first, disposable handler and were silently orphaned the moment the real init() call replaced it — meaning onInstallConversionData/onConversionDataSuccess/ onConversionDataFail never reached Unity. Deep-link delivery masked this because native resolves deep links independent of listener registration. Verified locally: built the patched wrapper AAR, rebuilt the test APK against it, and confirmed via a fresh install + full-scenario E2E run that onConversionDataSuccess now fires and is_first_launch_true passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tring 090ffe8 switched DeepLinkEventsArgs to read "deepLink" directly as an already-nested Dictionary (matching iOS's native RPC framework, which emits a real nested object), but Android's RPC bridge serializes "deepLink" as a JSON-encoded string inside the envelope. The type check silently failed on Android, so deepLinkValue and every other deep-link parameter always came back empty even though status correctly reported FOUND — deep-link detection worked, but none of the actual deep-link data reached Unity. Now handles both shapes: a nested Dictionary (iOS) or a JSON string that still needs decoding (Android). Verified locally: rebuilt the test APK and confirmed via the E2E scenario suite that deeplink_value_bg/deeplink_value_fg now pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
BuildScript.BuildAndroid targets ARM64 only — Unity 6 dropped x86_64 Android build support (AndroidArchitecture no longer has an X86_64 member), so 8fc9105 removing it was correct. But the E2E job's emulator was still configured for arch: x86_64 on ubuntu-latest (amd64), so once the earlier androidApplicationEntry build fix let the pipeline reach the emulator step, installing the ARM64-only APK failed with INSTALL_FAILED_NO_MATCHING_ABIS. Can't just flip the emulator to arm64-v8a in place: the Build Android APK step runs a custom GHCR image (ghcr.io/appsflyersdk/unity-android) that push-unity-to-ghcr.yml produces via a plain `docker pull` on an amd64 runner with no --platform, so that mirrored image is effectively amd64-only regardless of upstream unityci/editor's own arch support. Running the whole job on an arm64 runner would break the build step's container itself. Splits the job instead: build-android stays on ubuntu-latest/amd64 (unchanged build step) and uploads the APK as an artifact; the new e2e-android job runs on ubuntu-24.04-arm with arch: arm64-v8a so the emulator gets hardware-accelerated (KVM) ARM64 emulation matching the APK, rather than falling back to slow arm64-under-QEMU-on-amd64 emulation or being unable to install the APK at all. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…unner starts The emulator script's default -e meant a failure in adb install (or any other command before af-scenario-runner.sh started) aborted the whole step immediately, before anything was ever written to .af-e2e/reports/ — so the "Upload E2E report" artifact came back empty on exactly the runs that most needed diagnosis, e.g. the recent INSTALL_FAILED_NO_MATCHING_ABIS failure. Now captures a full unfiltered device logcat from before adb install through the end of the run, and adb install's own output/exit code, regardless of where a failure happens — with explicit exit-code handling so the step still fails correctly instead of the -e disablement silently swallowing it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…unner fc48905's arm64 runner + arm64-v8a emulator plan doesn't actually work: ubuntu-24.04-arm failed enabling KVM ("Failed to open the device 'kvm': Invalid argument"), confirming GitHub-hosted arm64 Linux runners don't expose nested virtualization to the guest. The same is true of Apple Silicon macOS runners (Apple's Virtualization Framework doesn't support nested virt either) — there's currently no GitHub-hosted runner with hardware-accelerated ARM64 Android emulation. Reverts the amd64/arm64 job split back to a single ubuntu-latest job, and switches the emulator target from google_apis to google_apis_playstore. Play Store x86_64 system images bundle ARM64-to-x86_64 native-library translation, so the ARM64-only APK (Unity 6 dropped x86_64 Android build support, see 7f288a4 and 8fc9105) can install and run on the existing KVM-accelerated x86_64 emulator without needing ARM64 hardware at all. Play Store images can never be adb-rooted (regular google_apis images just don't have it by default), but af-scenario-runner.sh already has a working non-root fallback for reading QA logs via logcat alone, exercised successfully in every local verification run this session. The 65e10ce log-capture fix (full device logcat + adb-install.log written before any command that could fail) is unaffected and kept as-is. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…r script
reactivecircus/android-emulator-runner executes each line of the `script:`
input as its own separate `sh -c` call rather than one continuous script.
The brace-grouped install block and the cross-line adb-logcat PID handoff
both relied on shared shell state that doesn't exist across lines, so the
bare `{` on its own line errored out immediately (Syntax error: end of file
unexpected) before adb install, log capture, or the scenario runner ever
ran — leaving the uploaded report artifact with nothing but an empty
device-logcat-full.txt. Collapse the install/branch logic into single
self-contained lines and replace PID tracking with pkill for cleanup.
api-level 33's google_apis_playstore x86_64 image still hit INSTALL_FAILED_NO_MATCHING_ABIS for our ARM64-only test APK. API 35 system images ship Google's newer ABI-translation support that actually installs and runs ARM64-only APKs on an x86_64 image; verified locally by installing and launching the real CI-built APK on a local API 35 google_apis x86_64 emulator. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
QATestScript never logged anything or wrote af_qa_logs.txt in the first CI run that got past the emulator install stage — the process was alive and RESUMED at the Android OS level, but Unity's own player loop (Start/Update/coroutines) never ran a single frame. Reproduced locally: a headless (-no-window) emulator briefly grants window focus then permanently loses it, and with Player Settings' Run In Background disabled, Unity pauses its managed script loop whenever the Activity lacks window focus - freezing the app before any script code executes, with no crash or log to show for it. Enabling Run In Background is the standard fix for exactly this class of headless-CI-emulator problem. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
QueryAsync/generateInviteLink/QueryValidateAndLogAsync all hopped to a background thread before calling into AppsFlyerRPCClient.Execute(), a pattern introduced to work around iOS's _afExecuteJson semaphore deadlock. Android's Execute() is a plain synchronous JNI call with no such deadlock risk, and dispatching it from a background thread pool worker instead risks an unattached/unreliable JNI environment - matching the "Empty response from native" RPC failures and the missing start() log seen in CI E2E runs under headless Run In Background. Scope the background/main-thread hop to iOS only.
The plain google_apis image has no Play Store app, so there's no Play Install Referrer for native's GCD/onInstallConversionData flow to query - it silently never fires, failing is_first_launch_true with no error in the logs (confirmed reproducible across two CI runs, unrelated to the QueryAsync threading fix). Local ARM64 hardware/emulators never hit this because they run the APK natively and never needed API 35's ABI-translation fix that motivated the earlier switch off google_apis_playstore in the first place (that switch was verified only for api-level 33's variant, which don't apply here - Google's system-image repo confirms system-images;android-35;google_apis_playstore;x86_64 exists).
libswappywrapper.so's SwappyDisplayManager JNI refresh-rate query segfaults inside the Berberis ARM64-on-x86_64 translator on the google_apis_playstore API 35 x86_64 CI image, aborting the process ~3s after launch before any AF_QA logs are emitted. Unrelated to the AppsFlyer plugin; test-app doesn't need frame pacing for a headless functional E2E run.
…tart flow Correlates Activity pause/resume (e.g. triggerLifecycleNudge), coroutine scheduling, and Time.realtimeSinceStartup/frameCount across HandleSessionReady -> RequestATTThenStart -> RunPostStartApis, to determine whether CI's post-nudge stall (no logs after "[AF_QA][start] result: SUCCESS", GCDSDK tasks never queued) is a coroutine that never gets scheduled, or one that's scheduled but never resumes.
vSyncCount=1 (Android's active quality level) makes Choreographer the engine's only frame-pacing clock. On CI's headless emulator (-gpu swiftshader_indirect, -no-window), Choreographer has been observed disabling itself after an Activity pause/resume (e.g. triggerLifecycleNudge) and never re-enabling, permanently stalling the player loop - and with it every coroutine scheduled after that point (RunPostStartApis, and by extension the native SDK activity that queues GCD conversion-data checks). Set vSyncCount=0 and Application.targetFrameRate=60 so the engine paces itself off its own timer instead of depending solely on Choreographer.
…ndroid google_apis_playstore AVDs keep self-updating Play Store/GMS after boot; installing and launching the app before that settles races AppsFlyer's Install-Referrer-based GCD lookup, so onInstallConversionData never fires and is_first_launch_true fails despite start()/onSessionReady/onDeepLinking all succeeding. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…firing and forgetting Narrows the auto-run driver's init sequence (registerDeepLinkListener, registerConversionListener, init, enableDebug, start) to async Awaitable methods so each RPC's native round trip completes, in order, before the next one dispatches. RunPreStartApis/RunPostStartApis/RunRPCCoverageApis stay coroutine-based and fire-and-forget.
…f-hosted arm64 Mac) Build stays on ubuntu-latest via the existing GHCR Docker image; only the emulator/test job moves to the self-hosted Apple Silicon runner, now matching local dev conditions (arm64-v8a, google_apis_playstore, API 36, pixel_9_pro, native HVF acceleration) instead of GH-hosted x86_64 + ABI translation, which was implicated in the intermittent start()/ conversion-callback flakiness. APK hands off between jobs via artifact upload/download since jobs don't share a filesystem.
…d job split - rc-e2e-android.yml: copy the downloaded APK to test-app/Build/Android/com.appsflyer.engagement.apk before running the scenario runner. af-scenario-runner.sh reads apk_path from the plan JSON (shared with local runs and .af-smoke/rc-test-plan.json) rather than $APK_PATH, so requires_fresh_install's uninstall+reinstall inside run_phase() was failing with "APK not found" on the new self-hosted job, blocking phase_1/3/5 and leaving later phases running with no app installed at all. - af-scenario-runner.sh: add -u to the BSD `date -j -f` branch used to parse RUN_START. Without it, macOS date silently parses the UTC Z-suffixed timestamp as local time, inflating duration_sec by the runner's UTC offset (observed as +10800s on a UTC+3 machine).
triggerLifecycleNudge()'s synthetic Activity pause/resume (used to force AppsFlyerLib's ActivityLifecycleCallbacks to re-evaluate session readiness on cold launch) recreates the rendering Surface. In headless (-no-window) mode there's no real compositor backing that Surface: Android's own APP_CMD_RESUME fired at the native layer, but Unity's managed frame loop never resumed, so OnApplicationPause(false)/start() never ran, and no [AF_QA] logs appeared past the nudge. Confirmed locally: booting the same AVD (Pixel_9_Pro, arm64-v8a, google_apis_playstore, API 36) without -no-window and re-running phase_1 against the exact APK that froze in CI now passes 11/12 checks, with the lifecycle-nudge marker observed after 10s instead of never appearing. Safe to drop here since this job now runs on an interactive self-hosted Mac session rather than a headless GH-hosted runner.
…ependency ac993d9 bumped unity-wrapper to 7.0.14 in AppsFlyerDependencies.xml (the Android Resolver's source of truth) but never touched this generated file, since it's only regenerated when the resolver actually runs inside the Editor UI. No one reopened the Editor and committed the regenerated result afterward, so every CI build since (checkout + batch-mode build, no Editor UI) has shipped an APK missing the unity-wrapper AAR - com.appsflyer.unity.AppsFlyerRPCBridge was absent from classes.dex, throwing ClassNotFoundException and silently dropping every RPC call (init, registerConversionListener, start, etc.) with no visible error except a single Debug.LogError on first bridge access.
…r to 7.0.15 af-android-plugin-bridge 7.0.13 fixes registerConversionListener() being silently dropped when called before init() on Android (handleInit was passing a hardcoded null conversion listener to AppsFlyerLib.init() instead of the handler's own registered listener). unity-wrapper 7.0.14 -> 7.0.15 + ANDROID_PLUGIN_BRIDGE_VERSION 7.0.13 keep the wrapper's declared dependency in sync; the Maven coordinate is left commented out in AppsFlyerDependencies.xml until 7.0.15 is actually published. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reverts the self-hosted Apple Silicon runner in favor of the x86_64/ABI-translation config (api-level 35, google_apis_playstore, pixel_5) that was in place before the self-hosted split, restoring -no-window and KVM setup for the headless Linux runner. Note: this reintroduces the risk of the intermittent start()/conversion-callback lifecycle flakiness the self-hosted move had fixed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
unity-wrapper was temporarily pinned to a locally-built .aar to test the registerConversionListener-before-init fix ahead of the real release. 7.0.15 is now published to Maven/Sonatype, so switch back to resolving it remotely instead of a local artifact. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ure drift mainTemplate.gradle still pinned unity-wrapper:7.0.14 and af-android-plugin-bridge:7.0.12 even though AppsFlyerDependencies.xml (the resolver's source of truth) had moved on to 7.0.15/7.0.13 across two later bump commits. This file is only regenerated by the Android Resolver's Editor UI (per 45df292), never by CI's batch-mode build, so the drift shipped silently - af-android-plugin-bridge 7.0.13 fixes registerConversionListener() being dropped when called before init(), and CI was building against the AAR from before that fix. Root cause: scripts/bump-version.sh patched af-android-plugin-bridge in this file but never patched unity-wrapper, so the wrapper coordinate never moved even as the script kept "succeeding". Fixed the script to patch both, and added a check to rc-release.yml's version-bump verification step that fails the pipeline if this file's coordinates ever diverge from AppsFlyerDependencies.xml again. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tu-latest" This reverts commit e252c62. That revert's own message noted it "reintroduces the risk of the intermittent start()/conversion-callback lifecycle flakiness the self-hosted move had fixed" - GH-hosted's x86_64 + ABI-translation emulator run with -no-window left Unity's frame loop stalled post-resume (OnApplicationPause(false) never reaching script land after a Surface recreation), which is indistinguishable from a real dependency-resolution failure in the E2E report. Restoring the self-hosted Apple Silicon runner (arm64-v8a, HVF-accelerated, windowed) removes that source of flakiness. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.