Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/.oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"out",
"public",
"pages/versions/latest",
"next-env.d.ts"
"next-env.d.ts",
"ui/components/EASCLIReference/data/eas-cli-commands.json"
]
}
2 changes: 1 addition & 1 deletion docs/pages/eas/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: EAS CLI reference
sidebar_title: EAS CLI
description: EAS CLI is a command-line tool that allows you to interact with Expo Application Services (EAS) from your terminal.
cliVersion: 18.8.1
cliVersion: 18.9.1
---

import { EASCLIReference } from '~/ui/components/EASCLIReference';
Expand Down
27 changes: 21 additions & 6 deletions docs/ui/components/EASCLIReference/data/eas-cli-commands.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"source": {
"url": "https://raw.githubusercontent.com/expo/eas-cli/main/packages/eas-cli/README.md",
"fetchedAt": "2026-04-23T19:49:08.793Z",
"cliVersion": "18.8.1"
"fetchedAt": "2026-04-30T11:04:31.013Z",
"cliVersion": "18.9.1"
},
"totalCommands": 104,
"totalCommands": 107,
"commands": [
{
"command": "eas account:login",
Expand Down Expand Up @@ -88,8 +88,8 @@
},
{
"command": "eas build:download",
"description": "download simulator/emulator builds for a given fingerprint hash",
"usage": "USAGE\n $ eas build:download --fingerprint <value> [-p ios|android] [--dev-client] [--json] [--non-interactive]\n\nFLAGS\n -p, --platform=<option> <options: ios|android>\n --[no-]dev-client Filter only dev-client builds.\n --fingerprint=<value> (required) Fingerprint hash of the build to download\n --json Enable JSON output, non-JSON messages will be printed to stderr. Implies --non-interactive.\n --non-interactive Run the command in non-interactive mode.\n\nDESCRIPTION\n download simulator/emulator builds for a given fingerprint hash"
"description": "download a simulator/emulator build by build ID or fingerprint hash",
"usage": "USAGE\n $ eas build:download [--build-id <value> | --fingerprint <value> | -p ios|android | --dev-client]\n [--all-artifacts] [--json] [--non-interactive]\n\nFLAGS\n -p, --platform=<option> <options: ios|android>\n --all-artifacts Download all available build artifacts (build artifacts archive, Xcode logs, etc.) in\n addition to the application archive. Without this flag, only the application archive is\n downloaded and the command errors if it is missing.\n --build-id=<value> ID of the build to download. Mutually exclusive with --fingerprint, --platform, and\n --dev-client; the platform is derived from the build itself.\n --[no-]dev-client Filter only dev-client builds.\n --fingerprint=<value> Fingerprint hash of the build to download\n --json Enable JSON output, non-JSON messages will be printed to stderr. Implies --non-interactive.\n --non-interactive Run the command in non-interactive mode.\n\nDESCRIPTION\n download a simulator/emulator build by build ID or fingerprint hash"
},
{
"command": "eas build:inspect",
Expand Down Expand Up @@ -316,6 +316,21 @@
"description": "continue onboarding process started on the https://expo.new website.",
"usage": "USAGE\n $ eas init:onboarding [TARGET_PROJECT_DIRECTORY]\n\nDESCRIPTION\n continue onboarding process started on the https://expo.new website.\n\nALIASES\n $ eas init:onboarding\n $ eas onboarding"
},
{
"command": "eas integrations:asc:connect",
"description": "connect a project to an App Store Connect app",
"usage": "USAGE\n $ eas integrations:asc:connect [--api-key-id <value>] [--asc-app-id <value>] [--bundle-id <value>] [--json]\n [--non-interactive]\n\nFLAGS\n --api-key-id=<value> Apple App Store Connect API Key ID\n --asc-app-id=<value> App Store Connect app identifier\n --bundle-id=<value> Filter discovered apps by bundle identifier\n --json Enable JSON output, non-JSON messages will be printed to stderr. Implies --non-interactive.\n --non-interactive Run the command in non-interactive mode.\n\nDESCRIPTION\n connect a project to an App Store Connect app"
},
{
"command": "eas integrations:asc:disconnect",
"description": "disconnect the current project from its App Store Connect app",
"usage": "USAGE\n $ eas integrations:asc:disconnect [--yes] [--json] [--non-interactive]\n\nFLAGS\n --json Enable JSON output, non-JSON messages will be printed to stderr. Implies --non-interactive.\n --non-interactive Run the command in non-interactive mode.\n --yes Skip confirmation prompt\n\nDESCRIPTION\n disconnect the current project from its App Store Connect app"
},
{
"command": "eas integrations:asc:status",
"description": "show the App Store Connect app link status for the current project",
"usage": "USAGE\n $ eas integrations:asc:status [--json] [--non-interactive]\n\nFLAGS\n --json Enable JSON output, non-JSON messages will be printed to stderr. Implies --non-interactive.\n --non-interactive Run the command in non-interactive mode.\n\nDESCRIPTION\n show the App Store Connect app link status for the current project"
},
{
"command": "eas login",
"description": "log in with your Expo account",
Expand Down Expand Up @@ -527,4 +542,4 @@
"usage": "USAGE\n $ eas workflow:view [ID] [--json] [--non-interactive]\n\nARGUMENTS\n [ID] ID of the workflow run to view\n\nFLAGS\n --json Enable JSON output, non-JSON messages will be printed to stderr.\n --non-interactive Run the command in non-interactive mode.\n\nDESCRIPTION\n view details for a workflow run, including jobs. If no run ID is provided, you will be prompted to select from recent\n workflow runs for the current project."
}
]
}
}
2 changes: 1 addition & 1 deletion packages/expo-modules-core/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ dependencies {
implementation workletsProject
}

implementation("io.github.lukmccall.pika:pika-api:0.1.9-2.1.20")
implementation("io.github.lukmccall.pika:pika-api:0.2.1-2.1.20")

testImplementation 'androidx.test:core:1.7.0'
testImplementation 'junit:junit:4.13.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ sealed interface PropsParsingStrategy<Props : ComposeProps> {
prop.name to ComposeViewProp(
prop.name,
AnyType(propType),
prop.getter as (Any) -> Any?
prop::get as (Any) -> Any?
)
}
}
Expand All @@ -76,7 +76,7 @@ sealed interface PropsParsingStrategy<Props : ComposeProps> {
prop.name to ComposeViewProp(
prop.name,
AnyType(propType),
prop.getter as (Any) -> Any?
prop::get as (Any) -> Any?
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ class IntrospectableRecordConversionStrategy<T : Record>(
PropertyDescriptor(
key = propertyName,
typeDescriptor = propertyTypeDescriptor,
setter = property.setter as (Any, Any?) -> Unit,
setter = property::set as (Any, Any?) -> Unit,
typeConverter = converterProvider.obtainTypeConverter(propertyTypeDescriptor),
isRequired = isRequired
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ dependencies {
compileOnly("com.android.tools.build:gradle:8.5.0")
implementation("com.facebook.react:react-native-gradle-plugin")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
implementation("io.github.lukmccall.pika:pika-gradle:0.1.9-2.1.20")
implementation("io.github.lukmccall.pika:pika-gradle:0.2.1-2.1.20")

if (isExpoAutolinkingSettingsPluginAvailable) {
implementation("expo.modules:expo-autolinking-plugin-shared")
Expand Down
8 changes: 8 additions & 0 deletions packages/expo-modules-core/ios/Tests/SharedObjectTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ struct SharedObjectTests {
#expect(try isReturningItself.asBool() == true)
}

@Test
func `releases the native object when JS reference is garbage-collected`() throws {
let registrySizeBefore = appContext.sharedObjectRegistry.size
try runtime.eval("(() => { new expo.modules.SharedObjectModule.SharedObjectExample() })()")
try runtime.eval("gc() && gc() && gc()")
#expect(appContext.sharedObjectRegistry.size == registrySizeBefore)
}

// MARK: - Native object

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ public struct JavaScriptValuesBuffer: JavaScriptType, ~Copyable {

deinit {
if ownsMemory {
// Run the destructor for each `jsi::Value` so they release their strong refs to JS objects.
// Without this, calling host functions through this buffer leaks every JS-object argument.
bufferPointer.deinitialize()
bufferPointer.deallocate()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Testing
import ExpoModulesJSI

@Suite
@JavaScriptActor
struct JavaScriptValuesBufferTests {
let runtime = JavaScriptRuntime()

@Test
func `allocate with values populates count and subscript`() throws {
let buffer = JavaScriptValuesBuffer.allocate(in: runtime, with: 1, "two", true)
#expect(buffer.count == 3)
#expect(try buffer[0].asInt() == 1)
#expect(try buffer[1].asString() == "two")
#expect(try buffer[2].asBool() == true)
}

@Test
func `empty buffer has zero count and deinits cleanly`() {
let buffer = JavaScriptValuesBuffer.allocate(in: runtime, capacity: 0)
#expect(buffer.count == 0)
// Deinit running cleanly is the assertion — no crash on deallocate of an empty buffer.
}

@Test
func `copy produces an independent buffer with the same values`() throws {
let original = JavaScriptValuesBuffer.allocate(in: runtime, with: 42, "hello")
let duplicate = original.copy()

#expect(duplicate.count == original.count)
#expect(try duplicate[0].asInt() == 42)
#expect(try duplicate[1].asString() == "hello")
}

@Test
func `map enumerates values in order`() {
let buffer = JavaScriptValuesBuffer.allocate(in: runtime, with: 1, 2, 3)
let values = buffer.map { value, _ in (try? value.asInt()) ?? 0 }
#expect(values == [1, 2, 3])
}

@Test
func `does not leak the JS object referenced as an argument`() throws {
// Regression test for a bug where `JavaScriptValuesBuffer.deinit` deallocated
// the underlying memory without first running the destructors of the contained
// `jsi::Value`s, leaking each value's strong ref to its JS object. Calling any
// JS function via `JavaScriptValuesBuffer` would silently leak its arguments.
let object = runtime.createObject()
let weak = object.createWeak()

// Allocate a buffer that captures the JS object as an argument; the result is
// discarded and `deinit` runs at the end of this statement. If `deinit` skipped
// `deinitialize`, the contained `jsi::Value` would still hold a strong ref and
// the weak handle would survive `gc()`.
_ = JavaScriptValuesBuffer.allocate(in: runtime, with: object.asValue())
_ = consume object

try runtime.eval("gc() && gc() && gc()")

let stillAlive = weak.lock() != nil
#expect(stillAlive == false)
}
}
Loading