diff --git a/packages/@expo/config-plugins/CHANGELOG.md b/packages/@expo/config-plugins/CHANGELOG.md index b04a048aa57920..daf77196da7d4c 100644 --- a/packages/@expo/config-plugins/CHANGELOG.md +++ b/packages/@expo/config-plugins/CHANGELOG.md @@ -11,6 +11,7 @@ ### 🐛 Bug fixes - Fix `getApplicationIdAsync` and `setPackageInBuildGradle` failing with the Gradle assignment syntax (`applicationId = '...'`). ([#47711](https://github.com/expo/expo/pull/47711) by [@idoyana](https://github.com/idoyana)) +- [iOS] Quote and escape keys and values written to `.strings` files. ([#49605](https://github.com/expo/expo/pull/49605) by [@jakex7](https://github.com/jakex7)) ### 💡 Others diff --git a/packages/@expo/config-plugins/src/ios/Locales.ts b/packages/@expo/config-plugins/src/ios/Locales.ts index 56e4cb609cfc61..ecdbda93df00a2 100644 --- a/packages/@expo/config-plugins/src/ios/Locales.ts +++ b/packages/@expo/config-plugins/src/ios/Locales.ts @@ -19,6 +19,10 @@ export const withLocales: ConfigPlugin = (config) => { }); }; +function escapeStringsLiteral(value: string): string { + return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n'); +} + export async function writeStringsFile({ localesMap, supportingDirectory, @@ -40,7 +44,9 @@ export async function writeStringsFile({ const strings = path.join(dir, fileName); const buffer = []; for (const [plistKey, localVersion] of Object.entries(localizationObj)) { - buffer.push(`${plistKey} = "${localVersion}";`); + buffer.push( + `"${escapeStringsLiteral(plistKey)}" = "${escapeStringsLiteral(String(localVersion))}";` + ); } // Write the file to the file system. await fs.promises.writeFile(strings, buffer.join('\n')); diff --git a/packages/@expo/config-plugins/src/ios/__tests__/Locales-test.ts b/packages/@expo/config-plugins/src/ios/__tests__/Locales-test.ts index 6153f40f1f4775..59c3aa62f36e5a 100644 --- a/packages/@expo/config-plugins/src/ios/__tests__/Locales-test.ts +++ b/packages/@expo/config-plugins/src/ios/__tests__/Locales-test.ts @@ -70,6 +70,9 @@ describe('e2e: iOS locales', () => { ios: { 'Localizable.strings': { NOTIF_KEY: 'de-notification', + 'Sample Widget': 'DE Sample Widget', + 'A sample widget.': 'DE A sample widget.', + 'A "quoted" key': 'DE A "quoted" key', }, }, android: { @@ -125,18 +128,25 @@ describe('e2e: iOS locales', () => { expect(after[infoPlists[0]!]).toMatchSnapshot(); // Test that the inlined locale is resolved. expect(after[infoPlists[1]!]).toMatch(/spanish-name/); - expect(after[infoPlists[2]!]).toMatchInlineSnapshot(`"CFBundleDisplayName = "us-name";"`); + expect(after[infoPlists[2]!]).toMatchInlineSnapshot(`""CFBundleDisplayName" = "us-name";"`); expect(after[infoPlists[3]!]).toMatchInlineSnapshot(` - "CFBundleDisplayName = "us-name"; - app_name = "us-name";" + ""CFBundleDisplayName" = "us-name"; + "app_name" = "us-name";" `); - expect(after[infoPlists[4]!]).toMatchInlineSnapshot(`"CFBundleDisplayName = "ar-name";"`); + expect(after[infoPlists[4]!]).toMatchInlineSnapshot(`""CFBundleDisplayName" = "ar-name";"`); expect(localizableStrings).toStrictEqual([ 'ios/testproject/Supporting/ar.lproj/Localizable.strings', 'ios/testproject/Supporting/de.lproj/Localizable.strings', ]); - expect(after[localizableStrings[0]!]).toMatchInlineSnapshot(`"NOTIF_KEY = "ar-notification";"`); - expect(after[localizableStrings[1]!]).toMatchInlineSnapshot(`"NOTIF_KEY = "de-notification";"`); + expect(after[localizableStrings[0]!]).toMatchInlineSnapshot( + `""NOTIF_KEY" = "ar-notification";"` + ); + expect(after[localizableStrings[1]!]).toMatchInlineSnapshot(` + ""NOTIF_KEY" = "de-notification"; + "Sample Widget" = "DE Sample Widget"; + "A sample widget." = "DE A sample widget."; + "A \\"quoted\\" key" = "DE A \\"quoted\\" key";" + `); // Test a warning is thrown for an invalid locale JSON file. expect(WarningAggregator.addWarningForPlatform).toHaveBeenCalledWith( diff --git a/packages/@expo/config-plugins/src/ios/__tests__/__snapshots__/Locales-test.ts.snap b/packages/@expo/config-plugins/src/ios/__tests__/__snapshots__/Locales-test.ts.snap index 0f34bec6d5d75d..342e3cd1398d2c 100644 --- a/packages/@expo/config-plugins/src/ios/__tests__/__snapshots__/Locales-test.ts.snap +++ b/packages/@expo/config-plugins/src/ios/__tests__/__snapshots__/Locales-test.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`e2e: iOS locales writes all the image files expected 1`] = `"CFBundleDisplayName = "french-name";"`; +exports[`e2e: iOS locales writes all the image files expected 1`] = `""CFBundleDisplayName" = "french-name";"`; diff --git a/packages/@expo/prebuild-config/src/plugins/__tests__/withDefaultPlugins-test.ts b/packages/@expo/prebuild-config/src/plugins/__tests__/withDefaultPlugins-test.ts index 3d10ec362d3b67..94a6d49159dfd9 100644 --- a/packages/@expo/prebuild-config/src/plugins/__tests__/withDefaultPlugins-test.ts +++ b/packages/@expo/prebuild-config/src/plugins/__tests__/withDefaultPlugins-test.ts @@ -391,7 +391,7 @@ describe('built-in plugins', () => { expect(after['ios/HelloWorld/Info.plist']).toMatch('UIApplicationSceneManifest'); expect(after['ios/HelloWorld/Info.plist']).toMatch('$(PRODUCT_MODULE_NAME).SceneDelegate'); expect(after['ios/HelloWorld/Supporting/en.lproj/InfoPlist.strings']).toMatch( - /foo = "uhh bar"/ + /"foo" = "uhh bar"/ ); expect(after['ios/HelloWorld/GoogleService-Info.plist']).toBe(googleServiceInfoFixture); diff --git a/packages/create-expo-module/package.json b/packages/create-expo-module/package.json index 1b2bb52b3c2874..a58b6ffd33f421 100644 --- a/packages/create-expo-module/package.json +++ b/packages/create-expo-module/package.json @@ -27,8 +27,14 @@ "build" ], "main": "build/index.js", + "exports": { + ".": "./build/index.js", + "./package.json": "./package.json" + }, "scripts": { + "prebuild": "expo-module clean", "build": "ncc build ./src/index.ts -o build/", + "prebuild:prod": "expo-module clean", "build:prod": "ncc build ./src/index.ts -o build/ --minify --no-cache --no-source-map-register", "clean": "expo-module clean", "lint": "oxlint --config oxlint.config.mjs .", diff --git a/packages/create-expo-module/src/create-expo-module.ts b/packages/create-expo-module/src/create-expo-module.ts index ca0e19f3b2ad4c..2fbbad452cd09e 100644 --- a/packages/create-expo-module/src/create-expo-module.ts +++ b/packages/create-expo-module/src/create-expo-module.ts @@ -44,8 +44,16 @@ import { findGitHubEmail, findMyName } from './utils/git'; import { findGitHubUserFromEmail, guessRepoUrl } from './utils/github'; import { newStep } from './utils/ora'; +const PACKAGE_NAME = 'create-expo-module'; const debug = require('debug')('create-expo-module:main') as typeof console.log; -const packageJson = require('../package.json'); + +const getPackageJson = () => { + try { + return require('create-expo-module/package.json'); + } catch { + return null; + } +}; // `yarn run` may change the current working dir, then we should use `INIT_CWD` env. const CWD = process.env.INIT_CWD || process.cwd(); @@ -462,7 +470,8 @@ async function createGitRepositoryAsync(targetDir: string) { await spawnAsync('git', ['init'], { stdio: 'ignore', cwd: targetDir }); await spawnAsync('git', ['add', '-A'], { stdio: 'ignore', cwd: targetDir }); - const commitMsg = `Initial commit\n\nGenerated by ${packageJson.name} ${packageJson.version}.`; + const pkg = getPackageJson(); + const commitMsg = `Initial commit\n\nGenerated by ${pkg?.name ?? PACKAGE_NAME} ${pkg?.version ?? '0.0.0'}.`; await spawnAsync('git', ['commit', '-m', commitMsg], { stdio: 'ignore', cwd: targetDir, @@ -875,9 +884,9 @@ const program = new Command(); program.enablePositionalOptions(); program - .name(packageJson.name) - .version(packageJson.version) - .description(packageJson.description) + .name(getPackageJson()?.name ?? PACKAGE_NAME) + .version(getPackageJson()?.version ?? '0.0.0') + .description(getPackageJson()?.description ?? '') .arguments('[path]') .option( '-s, --source ', diff --git a/packages/create-expo-module/src/telemetry.ts b/packages/create-expo-module/src/telemetry.ts index 9400f2017581f0..3cd6552f6de19d 100644 --- a/packages/create-expo-module/src/telemetry.ts +++ b/packages/create-expo-module/src/telemetry.ts @@ -7,7 +7,13 @@ import path from 'node:path'; import type { CommandOptions } from './types'; import { env } from './utils/env'; -const packageJson = require('../package.json'); +const getPackageJson = () => { + try { + return require('create-expo-module/package.json'); + } catch { + return null; + } +}; /** The telemetry client instance to use */ let client: TelemetryClient | null = null; @@ -79,7 +85,7 @@ function getTelemetryContext() { return { os: { name: PLATFORM_NAMES[os.platform()] ?? os.platform(), version: os.release() }, - app: { name: 'create-expo-module', version: packageJson.version ?? undefined }, + app: { name: 'create-expo-module', version: getPackageJson()?.version ?? undefined }, }; } @@ -100,7 +106,7 @@ export async function logEventAsync(event: Event) { const commonProperties = { source: 'create-expo-module', - source_version: packageJson.version ?? undefined, + source_version: getPackageJson()?.version ?? undefined, }; getTelemetryClient().track({ diff --git a/packages/create-expo/CHANGELOG.md b/packages/create-expo/CHANGELOG.md index 13984d99114516..f6a4fcbea3f238 100644 --- a/packages/create-expo/CHANGELOG.md +++ b/packages/create-expo/CHANGELOG.md @@ -16,6 +16,7 @@ - Support npm@12's dictionary-based `npm pack --json` format ([#48761](https://github.com/expo/expo/pull/48761) by [@kitten](https://github.com/kitten)) - Print the "make sure you have modules installed" warning when the dependency install fails ([#48929](https://github.com/expo/expo/issues/48929)) ([#48946](https://github.com/expo/expo/pull/48946) by [@expo-bot](https://github.com/expo-bot)) +- [Internal] Fix sporadic `ncc` build failures ([#49615](https://github.com/expo/expo/pull/49615) by [@kitten](https://github.com/kitten)) ### 💡 Others diff --git a/packages/create-expo/package.json b/packages/create-expo/package.json index e1251c8f90c30c..a4996244dae9d6 100644 --- a/packages/create-expo/package.json +++ b/packages/create-expo/package.json @@ -21,9 +21,15 @@ "build", "template" ], - "main": "build", + "main": "build/index.js", + "exports": { + ".": "./build/index.js", + "./package.json": "./package.json" + }, "scripts": { + "prebuild": "expo-module clean", "build": "ncc build ./src/index.ts -o build/", + "prebuild:prod": "expo-module clean", "build:prod": "ncc build ./src/index.ts -o build/ --minify --no-cache --no-source-map-register", "clean": "expo-module clean", "lint": "oxlint --config oxlint.config.mjs .", diff --git a/packages/create-expo/src/cli.ts b/packages/create-expo/src/cli.ts index e541cdd63ab8a9..76d0a16bbeb8c5 100644 --- a/packages/create-expo/src/cli.ts +++ b/packages/create-expo/src/cli.ts @@ -2,14 +2,22 @@ import type { Spec } from 'arg'; import chalk from 'chalk'; -import { CLI_NAME } from './cmd'; import { ExitError } from './error'; import { Log } from './log'; import { formatSelfCommand } from './resolvePackageManager'; import { assertWithOptionsArgs, printHelp, resolveStringOrBooleanArgsAsync } from './utils/args'; +import { PACKAGE_NAME } from './utils/update-check'; const debug = require('debug')('expo:init:cli') as typeof console.log; +const getPackageJson = () => { + try { + return require('create-expo/package.json'); + } catch { + return null; + } +}; + async function run() { const argv = process.argv.slice(2) ?? []; const rawArgsMap: Spec = { @@ -30,14 +38,14 @@ async function run() { }); if (args['--version']) { - Log.exit(require('../package.json').version, 0); + Log.exit(getPackageJson()?.version ?? '0.0.0', 0); } if (args['--help']) { - const nameWithoutCreate = CLI_NAME.replace('create-', ''); + const nameWithoutCreate = PACKAGE_NAME.replace('create-', ''); printHelp( `Creates a new Expo project`, - chalk`npx ${CLI_NAME} {cyan } [options]`, + chalk`npx ${PACKAGE_NAME} {cyan } [options]`, [ `-y, --yes Use the default options for creating a project`, ` --no-install Skip installing npm packages or CocoaPods`, @@ -60,7 +68,7 @@ async function run() { {gray The package manager used for installing} {gray node modules is based on how you invoke the CLI:} - {bold npm:} {cyan npx ${CLI_NAME}} + {bold npm:} {cyan npx ${PACKAGE_NAME}} {bold yarn:} {cyan yarn create ${nameWithoutCreate}} {bold pnpm:} {cyan pnpm create ${nameWithoutCreate}} {bold bun:} {cyan bun create ${nameWithoutCreate}} diff --git a/packages/create-expo/src/cmd.ts b/packages/create-expo/src/cmd.ts deleted file mode 100644 index fd307f9dda93f9..00000000000000 --- a/packages/create-expo/src/cmd.ts +++ /dev/null @@ -1 +0,0 @@ -export const CLI_NAME = require('../package.json').name; diff --git a/packages/create-expo/src/index.ts b/packages/create-expo/src/index.ts index dee0d77fdc0e59..8ccc2d126d36bb 100755 --- a/packages/create-expo/src/index.ts +++ b/packages/create-expo/src/index.ts @@ -2,10 +2,10 @@ import Debug from 'debug'; import { boolish } from 'getenv'; -import { CLI_NAME } from './cmd'; +import { PACKAGE_NAME } from './utils/update-check'; // Set the title of the process -process.title = CLI_NAME; +process.title = PACKAGE_NAME; // Setup before requiring `debug`. if (boolish('EXPO_DEBUG', false)) { diff --git a/packages/create-expo/src/resolvePackageManager.ts b/packages/create-expo/src/resolvePackageManager.ts index 2d24fb32f0b233..3dc6fe5f8a6e7a 100644 --- a/packages/create-expo/src/resolvePackageManager.ts +++ b/packages/create-expo/src/resolvePackageManager.ts @@ -1,7 +1,7 @@ import * as PackageManager from '@expo/package-manager'; import { execSync } from 'child_process'; -import { CLI_NAME } from './cmd'; +import { PACKAGE_NAME } from './utils/update-check'; export type PackageManagerName = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'nub'; @@ -66,15 +66,15 @@ export function formatSelfCommand() { const packageManager = resolvePackageManager(); switch (packageManager) { case 'pnpm': - return `pnpx ${CLI_NAME}`; + return `pnpx ${PACKAGE_NAME}`; case 'bun': - return `bunx ${CLI_NAME}`; + return `bunx ${PACKAGE_NAME}`; case 'nub': - return `nubx ${CLI_NAME}`; + return `nubx ${PACKAGE_NAME}`; case 'yarn': case 'npm': default: - return `npx ${CLI_NAME}`; + return `npx ${PACKAGE_NAME}`; } } diff --git a/packages/create-expo/src/telemetry.ts b/packages/create-expo/src/telemetry.ts index ef95da4103e979..980fbb2d08468c 100644 --- a/packages/create-expo/src/telemetry.ts +++ b/packages/create-expo/src/telemetry.ts @@ -6,13 +6,20 @@ import os from 'os'; import { dotExpoHomeDirectory, getStateJsonPath } from './paths'; import { getSession } from './sessionStorage'; import { env } from './utils/env'; +import { PACKAGE_NAME } from './utils/update-check'; -const packageJSON = require('../package.json'); +const getPackageJson = () => { + try { + return require('create-expo/package.json'); + } catch { + return null; + } +}; const xdlUnifiedWriteKey = '1wabJGd5IiuF9Q8SGlcI90v8WTs'; const analyticsEndpoint = 'https://cdp.expo.dev/v1/batch'; const version = '1.0.0'; -const library = packageJSON.name; +const library = PACKAGE_NAME; //#region mostly copied from @expo/rudder-sdk-node https://github.com/expo/rudder-sdk-node/blob/main/index.ts // some changes include: @@ -177,7 +184,7 @@ function getAnalyticsContext(): Record { return { os: { name: platform, version: os.release() }, device: { type: platform, model: platform }, - app: { name: library, version: packageJSON.version }, + app: { name: library, version: getPackageJson()?.version ?? undefined }, }; } //#endregion diff --git a/packages/create-expo/src/utils/git.ts b/packages/create-expo/src/utils/git.ts index 91eebeed3ae07d..6e048da3363c94 100644 --- a/packages/create-expo/src/utils/git.ts +++ b/packages/create-expo/src/utils/git.ts @@ -3,9 +3,18 @@ import chalk from 'chalk'; import prompts from 'prompts'; import { env } from './env'; +import { PACKAGE_NAME } from './update-check'; const debug = require('debug')('expo:init:git') as typeof console.log; +const getPackageJson = () => { + try { + return require('create-expo/package.json'); + } catch { + return null; + } +}; + /** Check if the given directory is inside an existing git repository */ async function isInsideGitRepoAsync(root: string): Promise { try { @@ -59,14 +68,14 @@ export async function initGitRepoAsync(root: string) { debug(chalk.dim('User chose to initialize git inside existing repo.')); } - const packageJSON = require('../package.json'); + const packageJSON = getPackageJson(); // not in git tree, so let's init try { await spawnAsync('git', ['init'], { stdio: 'ignore', cwd: root }); await spawnAsync('git', ['add', '-A'], { stdio: 'ignore', cwd: root }); - const commitMsg = `Initial commit\n\nGenerated by ${packageJSON.name} ${packageJSON.version}.`; + const commitMsg = `Initial commit\n\nGenerated by ${packageJSON?.name ?? PACKAGE_NAME} ${packageJSON?.version ?? '0.0.0'}.`; await spawnAsync('git', ['commit', '-m', commitMsg], { stdio: 'ignore', cwd: root, diff --git a/packages/create-expo/src/utils/update-check.ts b/packages/create-expo/src/utils/update-check.ts index 3397589487d72b..c857e402c6a9cc 100644 --- a/packages/create-expo/src/utils/update-check.ts +++ b/packages/create-expo/src/utils/update-check.ts @@ -1,17 +1,30 @@ import chalk from 'chalk'; import checkForUpdate from 'update-check'; -const packageJson = require('../package.json'); +export const PACKAGE_NAME = 'create-expo'; + +const getPackageJson = () => { + try { + return require('create-expo/package.json'); + } catch { + return null; + } +}; const debug = require('debug')('expo:init:update-check') as typeof console.log; export default async function shouldUpdate(): Promise { try { - const res = await checkForUpdate(packageJson); + const pkg = getPackageJson(); + const res = await checkForUpdate(pkg); if (res?.latest) { console.log(); - console.log(chalk.yellow.bold(`A new version of \`${packageJson.name}\` is available`)); - console.log(chalk`You can update by running: {cyan npm install -g ${packageJson.name}}`); + console.log( + chalk.yellow.bold(`A new version of \`${pkg?.name ?? PACKAGE_NAME}\` is available`) + ); + console.log( + chalk`You can update by running: {cyan npm install -g ${pkg?.name ?? PACKAGE_NAME}}` + ); console.log(); } } catch (error: any) { diff --git a/packages/expo-doctor/CHANGELOG.md b/packages/expo-doctor/CHANGELOG.md index 25b65dcfb137e9..f47a440556534a 100644 --- a/packages/expo-doctor/CHANGELOG.md +++ b/packages/expo-doctor/CHANGELOG.md @@ -16,6 +16,7 @@ - [Internal] Prevent `ncc` from removing dynamic requires where we need them ([#48887](https://github.com/expo/expo/pull/48887) by [@kitten](https://github.com/kitten)) - Keep loaded `.env` values out of `expo install --check`. ([#48845](https://github.com/expo/expo/pull/48845) by [@ramonclaudio](https://github.com/ramonclaudio)) - Report a stale `@expo/dom-webview` left over from an older SDK in the overridden dependency check. ([#49345](https://github.com/expo/expo/pull/49345) by [@expo-bot](https://github.com/expo-bot)) +- [Internal] Fix sporadic `ncc` build failures ([#49615](https://github.com/expo/expo/pull/49615) by [@kitten](https://github.com/kitten)) ### 💡 Others diff --git a/packages/expo-doctor/package.json b/packages/expo-doctor/package.json index 40f116ab559561..1a56b9c221faad 100644 --- a/packages/expo-doctor/package.json +++ b/packages/expo-doctor/package.json @@ -22,8 +22,14 @@ "build" ], "main": "build/index.js", + "exports": { + ".": "./build/index.js", + "./package.json": "./package.json" + }, "scripts": { + "prebuild": "expo-module clean", "build": "ncc build ./src/index.ts -o build/", + "prebuild:prod": "expo-module clean", "build:prod": "ncc build ./src/index.ts -o build/ --minify --no-cache --no-source-map-register", "clean": "expo-module clean", "lint": "oxlint --config oxlint.config.mjs .", diff --git a/packages/expo-doctor/src/index.ts b/packages/expo-doctor/src/index.ts index b3990424b918af..66999e095edcd4 100644 --- a/packages/expo-doctor/src/index.ts +++ b/packages/expo-doctor/src/index.ts @@ -29,7 +29,13 @@ if (boolish('EXPO_DEBUG', false)) { process.env.EXPO_DEBUG = '1'; } -const packageJson = () => require('../package.json'); +const packageJson = () => { + try { + return require('expo-doctor/package.json'); + } catch { + return null; + } +}; async function run() { const args = process.argv.slice(2); @@ -62,13 +68,13 @@ async function run() { }); if (showVerboseTestResults) { - console.log(`expo-doctor: v${packageJson().version}`); + console.log(`expo-doctor: v${packageJson()?.version ?? '0.0.0'}`); } await actionAsync(projectRoot, showVerboseTestResults); } function logVersionAndExit() { - console.log(packageJson().version); + console.log(packageJson()?.version ?? '0.0.0'); process.exit(0); } diff --git a/packages/expo-env-info/CHANGELOG.md b/packages/expo-env-info/CHANGELOG.md index 75e420e5b5b535..2ce8eb76ab3512 100644 --- a/packages/expo-env-info/CHANGELOG.md +++ b/packages/expo-env-info/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- [Internal] Fix sporadic `ncc` build failures ([#49615](https://github.com/expo/expo/pull/49615) by [@kitten](https://github.com/kitten)) + ### 💡 Others ## 2.1.0 - 2026-06-25 diff --git a/packages/expo-env-info/package.json b/packages/expo-env-info/package.json index 100db202bf3c3a..17311e92a908a0 100644 --- a/packages/expo-env-info/package.json +++ b/packages/expo-env-info/package.json @@ -25,8 +25,14 @@ "build" ], "main": "./build/main.js", + "exports": { + ".": "build/main.js", + "./package.json": "./package.json" + }, "scripts": { + "prebuild": "expo-module clean", "build": "ncc build ./src/index.ts -o build/", + "prebuild:prod": "expo-module clean", "build:prod": "ncc build ./src/index.ts -o build/ --minify --no-cache --no-source-map-register", "clean": "expo-module clean", "lint": "oxlint --config oxlint.config.mjs .", @@ -43,6 +49,7 @@ "@types/envinfo": "^7.8.1", "@types/jest": "^29.2.1", "@types/node": "^22.14.0", + "@vercel/ncc": "^0.38.4", "expo-module-scripts": "workspace:*", "memfs": "^3.2.0" }, diff --git a/packages/expo-env-info/src/index.ts b/packages/expo-env-info/src/index.ts index b1f99073c61a9a..be10a0f87d3ebd 100644 --- a/packages/expo-env-info/src/index.ts +++ b/packages/expo-env-info/src/index.ts @@ -4,7 +4,13 @@ import path from 'path'; import { actionAsync } from './diagnosticsAsync'; -const packageJson = () => require('../package.json'); +const packageJson = () => { + try { + return require('expo-env-info/package.json'); + } catch { + return null; + } +}; async function run() { const args = process.argv.slice(2); @@ -30,7 +36,7 @@ async function run() { } function logVersionAndExit() { - console.log(packageJson().version); + console.log(packageJson()?.version ?? '0.0.0'); process.exit(0); } diff --git a/packages/expo-widgets/CHANGELOG.md b/packages/expo-widgets/CHANGELOG.md index 34ceea5f878748..ed36494976d158 100644 --- a/packages/expo-widgets/CHANGELOG.md +++ b/packages/expo-widgets/CHANGELOG.md @@ -25,6 +25,7 @@ ### 🐛 Bug fixes +- [iOS] Localize widget gallery names and descriptions using the app's `Localizable.strings` files. ([#49606](https://github.com/expo/expo/pull/49606) by [@jakex7](https://github.com/jakex7)) - [iOS] Fix widget and Live Activity `Text` modifiers being applied twice. ([#49535](https://github.com/expo/expo/pull/49535) by [@gee1k](https://github.com/gee1k)) - Resolve deep `react-native/*` imports as empty modules when bundling widget layouts, fixing every widget failing with "Could not create context for layout evaluation" after `@expo/ui` 57.0.14 introduced such an import. ([#49491](https://github.com/expo/expo/pull/49491) by [@usmsam](https://github.com/usmsam)) - [iOS] Fix XCFramework precompilation failing on the unguarded `ActivityViewContext` parameter of `getLiveActivityEnvironment`, which requires iOS 16.1. ([#49076](https://github.com/expo/expo/pull/49076) by [@brentvatne](https://github.com/brentvatne)) diff --git a/packages/expo-widgets/plugin/src/ios/xcode/addBuildPhases.ts b/packages/expo-widgets/plugin/src/ios/xcode/addBuildPhases.ts index 8956f415083928..73964b497033c2 100644 --- a/packages/expo-widgets/plugin/src/ios/xcode/addBuildPhases.ts +++ b/packages/expo-widgets/plugin/src/ios/xcode/addBuildPhases.ts @@ -1,11 +1,15 @@ -import { XcodeProject } from 'expo/config-plugins'; +import type { XcodeProject } from 'expo/config-plugins'; import type { PBXFile } from 'xcode'; type BuildPhase = { files: { value: string; comment: string }[]; }; -type BuildPhaseType = 'PBXSourcesBuildPhase' | 'PBXCopyFilesBuildPhase' | 'PBXFrameworksBuildPhase'; +type BuildPhaseType = + | 'PBXSourcesBuildPhase' + | 'PBXCopyFilesBuildPhase' + | 'PBXFrameworksBuildPhase' + | 'PBXResourcesBuildPhase'; type ProductFile = PBXFile & { uuid: string; @@ -21,11 +25,13 @@ export function addBuildPhases( groupName, productFile, widgetFiles, + resourceFileRefs, }: { targetUuid: string; groupName: string; productFile: ProductFile; widgetFiles: string[]; + resourceFileRefs: string[]; } ) { const buildPath = `""`; @@ -75,6 +81,47 @@ export function addBuildPhases( xcodeProject.addToPbxBuildFileSection(productFile); } + if ( + resourceFileRefs.length > 0 && + !getBuildPhaseObject(xcodeProject, 'PBXResourcesBuildPhase', targetUuid) + ) { + xcodeProject.addBuildPhase( + [], + 'PBXResourcesBuildPhase', + 'Resources', + targetUuid, + folderType, + buildPath + ); + } + + const resourcesBuildPhase = getBuildPhaseObject( + xcodeProject, + 'PBXResourcesBuildPhase', + targetUuid + ); + const buildFiles = xcodeProject.pbxBuildFileSection(); + for (const fileRef of new Set(resourceFileRefs)) { + if ( + !resourcesBuildPhase || + resourcesBuildPhase.files.some(({ value }) => buildFiles[value]?.fileRef === fileRef) + ) { + continue; + } + const resourceBuildFile = { + uuid: xcodeProject.generateUuid(), + fileRef, + target: targetUuid, + basename: 'Localizable.strings', + group: 'Resources', + } as ProductFile; + xcodeProject.addToPbxBuildFileSection(resourceBuildFile); + resourcesBuildPhase.files.push({ + value: resourceBuildFile.uuid, + comment: `${resourceBuildFile.basename} in ${resourceBuildFile.group}`, + }); + } + // Frameworks build phase if (!getBuildPhaseObject(xcodeProject, 'PBXFrameworksBuildPhase', targetUuid)) { xcodeProject.addBuildPhase( diff --git a/packages/expo-widgets/plugin/src/ios/xcode/withTargetXcodeProject.ts b/packages/expo-widgets/plugin/src/ios/xcode/withTargetXcodeProject.ts index 5f0c1b1f5f35dd..13f5db4c7588d8 100644 --- a/packages/expo-widgets/plugin/src/ios/xcode/withTargetXcodeProject.ts +++ b/packages/expo-widgets/plugin/src/ios/xcode/withTargetXcodeProject.ts @@ -1,4 +1,4 @@ -import { ConfigPlugin, withXcodeProject } from 'expo/config-plugins'; +import { ConfigPlugin, withXcodeProject, type XcodeProject } from 'expo/config-plugins'; import * as path from 'path'; import { addBuildPhases } from './addBuildPhases'; @@ -17,6 +17,37 @@ type TargetXcodeProjectProps = { getFileUris: () => string[]; }; +type PbxGroup = { + children: { value: string; comment?: string }[]; +}; + +function getGroupAtPath(xcodeProject: XcodeProject, groupPath: string): PbxGroup | null { + const { firstProject } = xcodeProject.getFirstProject(); + let group = xcodeProject.getPBXGroupByKey(firstProject.mainGroup) as PbxGroup | undefined; + + for (const component of groupPath.split('/')) { + const child = group?.children.find(({ comment }) => comment === component); + if (!child) { + return null; + } + group = xcodeProject.getPBXGroupByKey(child.value) as PbxGroup | undefined; + } + + return group ?? null; +} + +export function getLocalizableStringsFileRefs( + xcodeProject: XcodeProject, + projectName: string, + languages: string[] +): string[] { + return languages.flatMap((language) => { + const localeGroup = getGroupAtPath(xcodeProject, `${projectName}/Supporting/${language}.lproj`); + const file = localeGroup?.children.find(({ comment }) => comment === 'Localizable.strings'); + return file ? [file.value] : []; + }); +} + const withTargetXcodeProject: ConfigPlugin = ( config, { targetName, bundleIdentifier, deploymentTarget, appleTeamId, getFileUris } @@ -58,12 +89,17 @@ const withTargetXcodeProject: ConfigPlugin = ( const targetDirectory = path.join(projectRoot, targetName); const relativePaths = getFileUris().map((file) => path.relative(targetDirectory, file)); const swiftWidgetFiles = relativePaths.filter((file) => file.endsWith('.swift')); + const languages = Object.keys(config.locales ?? {}); + const localizableStringsFileRefs = languages.length + ? getLocalizableStringsFileRefs(xcodeProject, config.modRequest.projectName!, languages) + : []; addBuildPhases(xcodeProject, { targetUuid: target.uuid, groupName, productFile, widgetFiles: swiftWidgetFiles, + resourceFileRefs: localizableStringsFileRefs, }); addPbxGroup(xcodeProject, { diff --git a/packages/install-expo-modules/CHANGELOG.md b/packages/install-expo-modules/CHANGELOG.md index 8f9449aab0f61f..138442a1d0c660 100644 --- a/packages/install-expo-modules/CHANGELOG.md +++ b/packages/install-expo-modules/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- [Internal] Fix sporadic `ncc` build failures ([#49615](https://github.com/expo/expo/pull/49615) by [@kitten](https://github.com/kitten)) + ### 💡 Others ## 0.16.0 - 2026-06-25 diff --git a/packages/install-expo-modules/package.json b/packages/install-expo-modules/package.json index 3f784a05994cc5..0990dc40a1b946 100644 --- a/packages/install-expo-modules/package.json +++ b/packages/install-expo-modules/package.json @@ -25,11 +25,17 @@ "build" ], "main": "build/index.js", + "exports": { + ".": "build/index.js", + "./package.json": "./package.json" + }, "publishConfig": { "access": "public" }, "scripts": { + "prebuild": "expo-module clean", "build": "ncc build ./src/index.ts -o build/", + "prebuild:prod": "expo-module clean", "build:prod": "ncc build ./src/index.ts -o build/ --minify --no-cache --no-source-map-register", "clean": "expo-module clean", "lint": "oxlint --config oxlint.config.mjs .", diff --git a/packages/install-expo-modules/src/index.ts b/packages/install-expo-modules/src/index.ts index f451e04690eb29..137c92f4a021a3 100644 --- a/packages/install-expo-modules/src/index.ts +++ b/packages/install-expo-modules/src/index.ts @@ -30,10 +30,13 @@ import { } from './utils/packageInstaller'; import { normalizeProjectRootAsync } from './utils/projectRoot'; -const packageJSON = require('../package.json'); +let packageJSON = null; +try { + packageJSON = require('install-expo-modules/package.json'); +} catch {} -const program = new Command(packageJSON.name) - .version(packageJSON.version) +const program = new Command(packageJSON?.name ?? 'install-expo-modules') + .version(packageJSON?.version ?? '0.0.0') .arguments('[project-directory]') .usage(`${chalk.green('[project-directory]')} [options]`) .description('Install expo-modules into your project') diff --git a/packages/pod-install/CHANGELOG.md b/packages/pod-install/CHANGELOG.md index 74ab728cac02f5..6f2a5604b712fd 100644 --- a/packages/pod-install/CHANGELOG.md +++ b/packages/pod-install/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- [Internal] Fix sporadic `ncc` build failures ([#49615](https://github.com/expo/expo/pull/49615) by [@kitten](https://github.com/kitten)) + ### 💡 Others ## 1.1.0 - 2026-06-25 diff --git a/packages/pod-install/package.json b/packages/pod-install/package.json index 5e925109b528a5..85c987e3da2032 100644 --- a/packages/pod-install/package.json +++ b/packages/pod-install/package.json @@ -24,8 +24,14 @@ "build" ], "main": "./build/index.js", + "exports": { + ".": "./build/index.js", + "./package.json": "./package.json" + }, "scripts": { + "prebuild": "expo-module clean", "build": "ncc build ./src/index.ts -o build/", + "prebuild:prod": "expo-module clean", "build:prod": "ncc build ./src/index.ts -o build/ --minify --no-cache --no-source-map-register", "clean": "expo-module clean", "lint": "oxlint --config oxlint.config.mjs .", @@ -36,6 +42,7 @@ }, "devDependencies": { "@expo/package-manager": "workspace:*", + "@vercel/ncc": "^0.38.4", "chalk": "^4.0.0", "commander": "^12.1.0", "expo-module-scripts": "workspace:*", diff --git a/packages/pod-install/src/index.ts b/packages/pod-install/src/index.ts index d03752ae9a36cb..6f2bbfe6ad6835 100644 --- a/packages/pod-install/src/index.ts +++ b/packages/pod-install/src/index.ts @@ -6,10 +6,16 @@ import { Command } from 'commander'; import { existsSync, readFileSync } from 'fs'; import { join, resolve } from 'path'; -import shouldUpdate from './update'; +import shouldUpdate, { PACKAGE_NAME } from './update'; import { learnMore } from './utils'; -const packageJSON = require('../package.json'); +const packageJSON = () => { + try { + return require('pod-install/package.json'); + } catch { + return null; + } +}; function info(message: string) { if (!program.opts().quiet) { @@ -84,8 +90,8 @@ async function runAsync(maybeProjectDirectory?: string): Promise { } } -const program = new Command(packageJSON.name) - .version(packageJSON.version) +const program = new Command(packageJSON()?.name ?? PACKAGE_NAME) + .version(packageJSON()?.version ?? '0.0.0') .arguments('[project-directory]') .usage(`${chalk.green('[project-directory]')} [options]`) .description( diff --git a/packages/pod-install/src/update.ts b/packages/pod-install/src/update.ts index 7aa0220b5ea6b6..e6d5b864e6722f 100644 --- a/packages/pod-install/src/update.ts +++ b/packages/pod-install/src/update.ts @@ -1,17 +1,30 @@ import chalk from 'chalk'; import checkForUpdate from 'update-check'; +export const PACKAGE_NAME = 'pod-install'; + export default async function shouldUpdate() { - const packageJson = require('../package.json'); + const packageJson = () => { + try { + return require('uri-scheme/package.json'); + } catch { + return null; + } + }; - const update = checkForUpdate(packageJson).catch(() => null); + const update = checkForUpdate(packageJson()).catch(() => null); try { const res = await update; if (res && res.latest) { + const _packageJson = packageJson(); console.log(); - console.log(chalk.yellow.bold(`A new version of \`${packageJson.name}\` is available`)); - console.log('You can update by running: ' + chalk.cyan(`npm i -g ${packageJson.name}`)); + console.log( + chalk.yellow.bold(`A new version of \`${_packageJson?.name ?? PACKAGE_NAME}\` is available`) + ); + console.log( + 'You can update by running: ' + chalk.cyan(`npm i -g ${_packageJson?.name ?? PACKAGE_NAME}`) + ); console.log(); } } catch { diff --git a/packages/submit-expo-feedback/package.json b/packages/submit-expo-feedback/package.json index 594f86766fda36..841002b322e47a 100644 --- a/packages/submit-expo-feedback/package.json +++ b/packages/submit-expo-feedback/package.json @@ -21,8 +21,17 @@ ], "main": "build/index.js", "types": "build/index.d.ts", + "exports": { + ".": { + "types": "./build/index.d.ts", + "default": "./build/index.js" + }, + "./package.json": "./package.json" + }, "scripts": { + "prebuild": "expo-module clean", "build": "ncc build ./src/index.ts -o build/ -e typescript", + "prebuild:prod": "expo-module clean", "build:prod": "ncc build ./src/index.ts -o build/ -e typescript --minify --no-cache --no-source-map-register", "clean": "expo-module clean", "lint": "oxlint --config oxlint.config.mjs .", diff --git a/packages/submit-expo-feedback/src/cli.ts b/packages/submit-expo-feedback/src/cli.ts index dbeeddb48680b8..8944c712f352b5 100644 --- a/packages/submit-expo-feedback/src/cli.ts +++ b/packages/submit-expo-feedback/src/cli.ts @@ -564,7 +564,7 @@ export function resolveFeedbackId(value?: string): string { function getPackageVersion(): string { try { - return require('../package.json').version; + return require('submit-expo-feedback/package.json')?.version ?? '0.0.0'; } catch { return '0.0.0'; } diff --git a/packages/uri-scheme/CHANGELOG.md b/packages/uri-scheme/CHANGELOG.md index 0b4c98cbcc443b..2b7f97a6dda17b 100644 --- a/packages/uri-scheme/CHANGELOG.md +++ b/packages/uri-scheme/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- [Internal] Fix sporadic `ncc` build failures ([#49615](https://github.com/expo/expo/pull/49615) by [@kitten](https://github.com/kitten)) + ### 💡 Others ## 2.2.0 - 2026-06-25 diff --git a/packages/uri-scheme/package.json b/packages/uri-scheme/package.json index d31160555da419..8f456245b1ac4a 100644 --- a/packages/uri-scheme/package.json +++ b/packages/uri-scheme/package.json @@ -25,8 +25,14 @@ "cli.js" ], "main": "build/index.js", + "exports": { + ".": "./build/index.js", + "./package.json": "./package.json" + }, "scripts": { + "prebuild": "expo-module clean", "build": "ncc build ./src/index.ts -o build/", + "prebuild:prod": "expo-module clean", "build:prod": "ncc build ./src/index.ts -o build/ --minify --no-cache --no-source-map-register", "clean": "expo-module clean", "lint": "oxlint --config oxlint.config.mjs .", @@ -41,6 +47,7 @@ "@expo/plist": "workspace:*", "@expo/spawn-async": "^1.8.0", "@types/prompts": "^2.0.6", + "@vercel/ncc": "^0.38.4", "chalk": "^4.0.0", "commander": "^12.1.0", "expo-module-scripts": "workspace:*", diff --git a/packages/uri-scheme/src/CLI.ts b/packages/uri-scheme/src/CLI.ts index c72b4471687f75..84907b746bee4d 100644 --- a/packages/uri-scheme/src/CLI.ts +++ b/packages/uri-scheme/src/CLI.ts @@ -8,11 +8,19 @@ import * as Ios from './Ios'; import type { Options } from './Options'; import { CommandError } from './Options'; import * as URIScheme from './URIScheme'; -import shouldUpdate from './update'; +import shouldUpdate, { PACKAGE_NAME } from './update'; -const packageJson = () => require('../package.json'); +const packageJson = () => { + try { + return require('uri-scheme/package.json'); + } catch { + return null; + } +}; -export const program = new Command(packageJson().name).version(packageJson().version); +export const program = new Command(packageJson()?.name ?? PACKAGE_NAME).version( + packageJson()?.version ?? '0.0.0' +); function buildCommand(name: string, examples: string[] = []): Command { return program @@ -178,7 +186,9 @@ async function commandDidThrowAsync(reason: any) { console.log(); if (reason.command) { console.log( - chalk.red(`\u203A ${chalk.bold(`npx ${packageJson().name} ${reason.command}`)} has failed.`) + chalk.red( + `\u203A ${chalk.bold(`npx ${packageJson()?.name ?? PACKAGE_NAME} ${reason.command}`)} has failed.` + ) ); console.log(); } diff --git a/packages/uri-scheme/src/update.ts b/packages/uri-scheme/src/update.ts index bf0a63c516d088..c1530d8e909c58 100644 --- a/packages/uri-scheme/src/update.ts +++ b/packages/uri-scheme/src/update.ts @@ -1,8 +1,16 @@ import chalk from 'chalk'; import checkForUpdate from 'update-check'; +export const PACKAGE_NAME = 'uri-scheme'; + export default async function shouldUpdate(): Promise { - const packageJson = () => require('../package.json'); + const packageJson = () => { + try { + return require('uri-scheme/package.json'); + } catch { + return null; + } + }; const update = checkForUpdate(packageJson()).catch(() => null); @@ -11,8 +19,12 @@ export default async function shouldUpdate(): Promise { if (res && res.latest) { const _packageJson = packageJson(); console.log(); - console.log(chalk.yellow.bold(`A new version of \`${_packageJson.name}\` is available`)); - console.log('You can update by running: ' + chalk.cyan(`npm i -g ${_packageJson.name}`)); + console.log( + chalk.yellow.bold(`A new version of \`${_packageJson?.name ?? PACKAGE_NAME}\` is available`) + ); + console.log( + 'You can update by running: ' + chalk.cyan(`npm i -g ${_packageJson?.name ?? PACKAGE_NAME}`) + ); console.log(); } } catch { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8be0db54791114..678180340529bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4553,6 +4553,9 @@ importers: '@types/node': specifier: ^22.14.0 version: 22.20.1 + '@vercel/ncc': + specifier: ^0.38.4 + version: 0.38.4 expo-module-scripts: specifier: workspace:* version: link:../expo-module-scripts @@ -6541,6 +6544,9 @@ importers: '@expo/package-manager': specifier: workspace:* version: link:../@expo/package-manager + '@vercel/ncc': + specifier: ^0.38.4 + version: 0.38.4 chalk: specifier: ^4.0.0 version: 4.1.2 @@ -6616,6 +6622,9 @@ importers: '@types/prompts': specifier: ^2.0.6 version: 2.4.9 + '@vercel/ncc': + specifier: ^0.38.4 + version: 0.38.4 chalk: specifier: ^4.0.0 version: 4.1.2