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
1 change: 1 addition & 0 deletions packages/expo-router/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [web] Fix route URL detection in `useLoaderData` ([#42912](https://github.com/expo/expo/pull/42912) by [@hassankhan](https://github.com/hassankhan))
- [web] Key loader data by `contextKey` instead of URL pathname ([#43017](https://github.com/expo/expo/pull/43017) by [@hassankhan]
- [ios] fix immediate navigation when opening Link.Preview ([#43071](https://github.com/expo/expo/pull/43071) by [@Ubax](https://github.com/Ubax))
- [ios] fix link preview background color ([#43120](https://github.com/expo/expo/pull/43120) by [@Ubax](https://github.com/Ubax))

### 💡 Others

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class NativeLinkPreviewView: RouterViewWithLogger, UIContextMenuInteractionDeleg
container: superview, center: self.convert(triggerView.center, to: superview))

let parameters = UIPreviewParameters()
parameters.backgroundColor = .clear
parameters.backgroundColor = triggerView.backgroundColor ?? .clear

return UITargetedPreview(view: triggerView, parameters: parameters, target: target)
}
Expand Down
2 changes: 2 additions & 0 deletions packages/expo-updates/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### 🐛 Bug fixes

- [IOS] Fix optional value handling for asset hash in ExpoUpdatesUpdate. ([#43093](https://github.com/expo/expo/pull/43093) by [@billysutomo](https://github.com/billysutomo))

### 💡 Others

## 55.0.7 — 2026-02-08
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public final class ExpoUpdatesUpdate: Update {
let fileExtension: String = assetDict.requiredValue(forKey: "fileExtension")
let metadata: [String: Any]? = assetDict.optionalValue(forKey: "metadata")
let mainBundleFilename: String? = assetDict.optionalValue(forKey: "mainBundleFilename")
let expectedHash: String = assetDict.requiredValue(forKey: "hash")
let expectedHash: String? = assetDict.optionalValue(forKey: "hash")
let url = URL(string: urlString).require("asset url should be a valid URL")

let asset = UpdateAsset(key: key, type: fileExtension)
Expand Down
19 changes: 19 additions & 0 deletions tools/src/Versions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import semver from 'semver';

import logger from './Logger';

/**
* Utilities for versions API.
*/

/**
* Normalizes an SDK version string - e.g. "54" becomes "54.0.0".
*/
export function normalizeSdkVersion(input: string): string {
if (/^\d+$/.test(input)) {
return `${input}.0.0`;
}
return input;
}

/**
* Returns sorted SDK version keys from a versions schema, in descending order.
*/
export function getSortedSdkVersionKeys(versions: VersionsSchema): string[] {
return Object.keys(versions.sdkVersions).sort(semver.rcompare);
}

export enum VersionsApiHost {
PRODUCTION = 'api.expo.dev',
STAGING = 'staging-api.expo.dev',
Expand Down
6 changes: 4 additions & 2 deletions tools/src/commands/UpdateVersionsEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,10 @@ async function applyChangesToRootAsync(options: ActionOptions, versions: any) {
}

async function applyChangesToSDKVersionAsync(options: ActionOptions, versions: any) {
const sdkVersions = Object.keys(versions.sdkVersions).sort(semver.rcompare);
const sdkVersion = options.sdkVersion || (await chooseSdkVersionAsync(sdkVersions));
const sdkVersions = Versions.getSortedSdkVersionKeys(versions);
const sdkVersion = Versions.normalizeSdkVersion(
options.sdkVersion || (await chooseSdkVersionAsync(sdkVersions))
);
const containsSdk = sdkVersions.includes(sdkVersion);

if (!semver.valid(sdkVersion)) {
Expand Down
2 changes: 2 additions & 0 deletions tools/src/publish-packages/tasks/publishPackagesPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { updateIosProjects } from './updateIosProjects';
import { updateModuleTemplate } from './updateModuleTemplate';
import { updatePackageVersions } from './updatePackageVersions';
import { updateProjectTemplates } from './updateProjectTemplates';
import { updateVersionsEndpoint } from './updateVersionsEndpoint';
import { updateWorkspaceProjects } from './updateWorkspaceProjects';
import Git from '../../Git';
import logger from '../../Logger';
Expand Down Expand Up @@ -89,6 +90,7 @@ export const publishPackagesPipeline = new Task<TaskArgs>(
pushCommittedChanges,
publishAndroidArtifacts,
publishPackages,
updateVersionsEndpoint,
grantTeamAccessToPackages,
addPublishedLabelToPullRequests,
cleanWorkingTree,
Expand Down
106 changes: 106 additions & 0 deletions tools/src/publish-packages/tasks/updateVersionsEndpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import chalk from 'chalk';
import inquirer from 'inquirer';
import semver from 'semver';

import { publishPackages } from './publishPackages';
import logger from '../../Logger';
import { Task } from '../../TasksRunner';
import * as Versions from '../../Versions';
import { CommandOptions, Parcel, TaskArgs } from '../types';

const { cyan, green, yellow } = chalk;

/**
* Updates the versions endpoint with the published expo package version.
* This runs after packages are published and prompts to update the `expoVersion`
* key for a target SDK version.
*/
export const updateVersionsEndpoint = new Task<TaskArgs>(
{
name: 'updateVersionsEndpoint',
dependsOn: [publishPackages],
},
async (parcels: Parcel[], options: CommandOptions) => {
const expoParcel = parcels.find((parcel) => parcel.pkg.packageName === 'expo');

if (!expoParcel || !expoParcel.state.published) {
return;
}

const publishedVersion = expoParcel.state.releaseVersion;
if (!publishedVersion) {
logger.warn(
'\n📡 Skipping versions endpoint update - could not determine published version.'
);
return;
}

const expoVersionValue = `~${publishedVersion}`;
const versions = await Versions.getVersionsAsync();
const sdkVersions = Versions.getSortedSdkVersionKeys(versions);
const recentSdks = sdkVersions.slice(0, 3);

logger.info(`\n📡 Expo package published: ${green(publishedVersion)}`);

const ENTER_SDK = 'Enter SDK version';
const SKIP = 'Skip';

const sdkChoices = [...recentSdks, new inquirer.Separator(), ENTER_SDK, SKIP];

const { selectedSdk } = await inquirer.prompt<{ selectedSdk: string }>([
{
type: 'list',
name: 'selectedSdk',
message: `Update versions endpoint with expoVersion ${green(expoVersionValue)}?`,
choices: sdkChoices,
default: recentSdks[0],
},
]);

if (selectedSdk === SKIP) {
logger.info(yellow(' Skipping versions endpoint update.'));
return;
}

let targetSdkVersion = selectedSdk;

if (selectedSdk === ENTER_SDK) {
const { customSdk } = await inquirer.prompt<{ customSdk: string }>([
{
type: 'input',
name: 'customSdk',
message: 'Enter SDK version:',
validate: (input) => {
const normalized = Versions.normalizeSdkVersion(input.trim());
if (!semver.valid(normalized)) {
return 'Please enter a valid SDK version (e.g., 54 or 54.0.0)';
}
return true;
},
},
]);
targetSdkVersion = Versions.normalizeSdkVersion(customSdk.trim());
}

logger.info(
`\n📡 Updating versions endpoint for SDK ${cyan(targetSdkVersion)} with expoVersion ${green(expoVersionValue)}...`
);

if (options.dry) {
logger.info(yellow(' Dry run - skipping version update.'));
return;
}

try {
await Versions.modifySdkVersionsAsync(targetSdkVersion, (sdkVersions) => ({
...sdkVersions,
expoVersion: expoVersionValue,
}));
logger.success(
`\n📡 Successfully updated versions endpoint: SDK ${cyan(targetSdkVersion)} → expoVersion: ${green(expoVersionValue)}`
);
} catch (error) {
logger.error(`\n📡 Failed to update versions endpoint: ${error.message}`);
}
}
);
Loading