From 2ca3398968e8a6b796f34bda433406256d2ddb96 Mon Sep 17 00:00:00 2001 From: Kudo Chien Date: Thu, 26 Mar 2026 15:38:37 -0700 Subject: [PATCH 1/3] [build-tools] resolve repack version --- .../build-tools/src/steps/functions/repack.ts | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/build-tools/src/steps/functions/repack.ts b/packages/build-tools/src/steps/functions/repack.ts index f3d05cdac6..170e8323d4 100644 --- a/packages/build-tools/src/steps/functions/repack.ts +++ b/packages/build-tools/src/steps/functions/repack.ts @@ -108,8 +108,14 @@ export function createRepackBuildFunction(): BuildFunction { : undefined; const jsBundleOnly = (inputs.js_bundle_only.value as boolean | undefined) ?? false; + const resolvedRepackVersion = + (await resolveVersionAsync({ + logger: stepsCtx.logger, + packageName: inputs.repack_package.value as string, + version: inputs.repack_version.value as string, + })) ?? 'unknown'; stepsCtx.logger.info( - `Using repack from: ${inputs.repack_package.value}@${inputs.repack_version.value}` + `Using repack from: ${inputs.repack_package.value}@${inputs.repack_version.value} (${resolvedRepackVersion})` ); const repackApp = await installAndImportRepackAsync({ packageName: inputs.repack_package.value as string, @@ -197,6 +203,34 @@ async function installAndImportRepackAsync({ return require(resolveFrom(sandbox, packageName)); } +async function resolveVersionAsync({ + logger, + packageName, + version, +}: { + logger: bunyan; + packageName: string; + version: string; +}): Promise { + try { + const { stdout: distTagsJson } = await spawnAsync( + 'yarn', + ['info', packageName, 'dist-tags', '--json'], + { + stdio: 'pipe', + } + ); + const distTags = JSON.parse(distTagsJson); + if (version in distTags) { + return distTags[version]; + } + } catch (e: unknown) { + const message = e instanceof Error ? e.message : String(e); + logger.warn(`Unable to resolve version for ${packageName}@${version}: ${message}`); + } + return null; +} + /** * Creates `@expo/steps` based spawnAsync for repack. */ From a9bc6ba41445c6c3546b347305eadb23b726c3cd Mon Sep 17 00:00:00 2001 From: Kudo Chien Date: Thu, 26 Mar 2026 16:02:24 -0700 Subject: [PATCH 2/3] refactor --- .../build-tools/src/steps/functions/repack.ts | 33 ++----------------- .../utils/__tests__/packageManager.test.ts | 30 +++++++++++++++++ .../build-tools/src/utils/packageManager.ts | 29 ++++++++++++++++ 3 files changed, 62 insertions(+), 30 deletions(-) diff --git a/packages/build-tools/src/steps/functions/repack.ts b/packages/build-tools/src/steps/functions/repack.ts index 170e8323d4..ee03be8512 100644 --- a/packages/build-tools/src/steps/functions/repack.ts +++ b/packages/build-tools/src/steps/functions/repack.ts @@ -23,6 +23,7 @@ import resolveFrom from 'resolve-from'; import { COMMON_FASTLANE_ENV } from '../../common/fastlane'; import IosCredentialsManager from '../utils/ios/credentials/manager'; +import { resolvePackageVersionAsync } from '../../utils/packageManager'; export function createRepackBuildFunction(): BuildFunction { return new BuildFunction({ @@ -109,10 +110,10 @@ export function createRepackBuildFunction(): BuildFunction { const jsBundleOnly = (inputs.js_bundle_only.value as boolean | undefined) ?? false; const resolvedRepackVersion = - (await resolveVersionAsync({ + (await resolvePackageVersionAsync({ logger: stepsCtx.logger, packageName: inputs.repack_package.value as string, - version: inputs.repack_version.value as string, + distTag: inputs.repack_version.value as string, })) ?? 'unknown'; stepsCtx.logger.info( `Using repack from: ${inputs.repack_package.value}@${inputs.repack_version.value} (${resolvedRepackVersion})` @@ -203,34 +204,6 @@ async function installAndImportRepackAsync({ return require(resolveFrom(sandbox, packageName)); } -async function resolveVersionAsync({ - logger, - packageName, - version, -}: { - logger: bunyan; - packageName: string; - version: string; -}): Promise { - try { - const { stdout: distTagsJson } = await spawnAsync( - 'yarn', - ['info', packageName, 'dist-tags', '--json'], - { - stdio: 'pipe', - } - ); - const distTags = JSON.parse(distTagsJson); - if (version in distTags) { - return distTags[version]; - } - } catch (e: unknown) { - const message = e instanceof Error ? e.message : String(e); - logger.warn(`Unable to resolve version for ${packageName}@${version}: ${message}`); - } - return null; -} - /** * Creates `@expo/steps` based spawnAsync for repack. */ diff --git a/packages/build-tools/src/utils/__tests__/packageManager.test.ts b/packages/build-tools/src/utils/__tests__/packageManager.test.ts index 2e45197d3a..69f996098f 100644 --- a/packages/build-tools/src/utils/__tests__/packageManager.test.ts +++ b/packages/build-tools/src/utils/__tests__/packageManager.test.ts @@ -1,11 +1,14 @@ +import { type bunyan } from '@expo/logger'; import fs from 'fs-extra'; import { vol } from 'memfs'; import path from 'path'; +import semver from 'semver'; import { findPackagerRootDir, getPackageVersionFromPackageJson, resolvePackageManager, + resolvePackageVersionAsync, shouldUseFrozenLockfile, } from '../packageManager'; @@ -68,6 +71,33 @@ describe(resolvePackageManager, () => { }); }); +describe(resolvePackageVersionAsync, () => { + const logger = { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + } as unknown as bunyan; + + it('should resolve version from package name and dist tag', async () => { + const version = await resolvePackageVersionAsync({ + logger, + packageName: '@expo/repack-app', + distTag: 'latest', + }); + expect(semver.valid(version)).toBeTruthy(); + }); + + it('should return null if version cannot be resolved', async () => { + const version = await resolvePackageVersionAsync({ + logger, + packageName: '@expo/repack-app', + distTag: 'nonexistent-dist-tag', + }); + expect(version).toBeNull(); + }); +}); + describe(findPackagerRootDir, () => { beforeEach(() => { vol.reset(); diff --git a/packages/build-tools/src/utils/packageManager.ts b/packages/build-tools/src/utils/packageManager.ts index 6245a34099..8e6a6b36dc 100644 --- a/packages/build-tools/src/utils/packageManager.ts +++ b/packages/build-tools/src/utils/packageManager.ts @@ -1,3 +1,4 @@ +import { type bunyan } from '@expo/logger'; import * as PackageManagerUtils from '@expo/package-manager'; import spawnAsync from '@expo/turtle-spawn'; import semver from 'semver'; @@ -27,6 +28,34 @@ export function resolvePackageManager(directory: string): PackageManager { } } +/** + * Get the version of a package from the dist-tags. + * Returns null if the version cannot be resolved. + */ +export async function resolvePackageVersionAsync({ + logger, + packageName, + distTag, +}: { + logger: bunyan; + packageName: string; + distTag: string; +}): Promise { + try { + const { stdout } = await spawnAsync('npm', ['view', packageName, 'dist-tags', '--json'], { + stdio: 'pipe', + }); + const distTags = JSON.parse(stdout); + if (distTag in distTags) { + return distTags[distTag]; + } + } catch (e: unknown) { + const message = e instanceof Error ? e.message : String(e); + logger.warn(`Unable to resolve version for ${packageName}@${distTag}: ${message}`); + } + return null; +} + export function findPackagerRootDir(currentDir: string): string { return PackageManagerUtils.resolveWorkspaceRoot(currentDir) ?? currentDir; } From c1eb50510273c6e092d325d78c207c8761639495 Mon Sep 17 00:00:00 2001 From: Kudo Chien Date: Fri, 27 Mar 2026 10:35:43 -0700 Subject: [PATCH 3/3] install using resolved version --- packages/build-tools/src/steps/functions/repack.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/build-tools/src/steps/functions/repack.ts b/packages/build-tools/src/steps/functions/repack.ts index ee03be8512..dcfb9bee00 100644 --- a/packages/build-tools/src/steps/functions/repack.ts +++ b/packages/build-tools/src/steps/functions/repack.ts @@ -114,13 +114,13 @@ export function createRepackBuildFunction(): BuildFunction { logger: stepsCtx.logger, packageName: inputs.repack_package.value as string, distTag: inputs.repack_version.value as string, - })) ?? 'unknown'; + })) ?? (inputs.repack_version.value as string); stepsCtx.logger.info( `Using repack from: ${inputs.repack_package.value}@${inputs.repack_version.value} (${resolvedRepackVersion})` ); const repackApp = await installAndImportRepackAsync({ packageName: inputs.repack_package.value as string, - version: inputs.repack_version.value as string, + version: resolvedRepackVersion, }); const { repackAppIosAsync, repackAppAndroidAsync } = repackApp;