From 89ae30007cf250499ab8f996802c354254af4ca5 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 7 Jul 2026 01:55:29 -0700 Subject: [PATCH] Fix widget asset wiring order for ExpoWidgetsTarget - Register the widget asset plugin before expo-widgets so its mods run after target creation - Fail fast when the widget catalog or target is missing instead of silently skipping - Update the wiring script log to reflect the new behavior --- apps/mobile/app.config.ts | 9 ++++++--- .../plugins/lib/addWidgetAssetCatalog.cjs | 18 +++++++++++++----- apps/mobile/plugins/withWidgetLogoAsset.cjs | 12 +++++++++--- .../scripts/wire-widget-asset-catalog.cjs | 2 +- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/apps/mobile/app.config.ts b/apps/mobile/app.config.ts index d03e52e66bd..1dfc062910c 100644 --- a/apps/mobile/app.config.ts +++ b/apps/mobile/app.config.ts @@ -155,6 +155,12 @@ const config: ExpoConfig = { }, ], "./plugins/withIosCocoaPodsUuidCache.cjs", + // Must be listed BEFORE expo-widgets: same-type mods run last-registered- + // first, so registering earlier makes this plugin's mods run AFTER + // expo-widgets' — its dangerous mod wipes ios/ExpoWidgetsTarget/ (which + // would delete the asset catalog) and its xcodeproj mod creates the widget + // target (which must exist before the compile phase can be attached). + "./plugins/withWidgetLogoAsset.cjs", [ "expo-widgets", { @@ -174,9 +180,6 @@ const config: ExpoConfig = { ], }, ], - // Must run after expo-widgets so the widget target exists when it wires in - // the branded logo asset catalog. - "./plugins/withWidgetLogoAsset.cjs", "./plugins/withIosSceneLifecycle.cjs", "./plugins/withAndroidCleartextTraffic.cjs", ], diff --git a/apps/mobile/plugins/lib/addWidgetAssetCatalog.cjs b/apps/mobile/plugins/lib/addWidgetAssetCatalog.cjs index e4907be509d..817b7b9f6af 100644 --- a/apps/mobile/plugins/lib/addWidgetAssetCatalog.cjs +++ b/apps/mobile/plugins/lib/addWidgetAssetCatalog.cjs @@ -9,8 +9,10 @@ // drops the compiled Assets.car into the extension bundle. Marked // alwaysOutOfDate so the build system always runs it. // -// Idempotent across re-runs. Returns true when it added the phase, false when it -// was already present or the target was not found. +// Idempotent across re-runs. Returns true when it added the phase, false when +// it was already present. Throws when the target does not exist — that means +// this ran before expo-widgets created the target (plugin ordering bug) and +// silently skipping would ship a widget without its assets. const PHASE_NAME = "Compile Widget Assets"; @@ -20,8 +22,8 @@ const ACTOOL_SCRIPT = [ "set -e", 'CATALOG="${SRCROOT}/ExpoWidgetsTarget/Assets.xcassets"', 'if [ ! -d "$CATALOG" ]; then', - ' echo "warning: widget asset catalog not found at $CATALOG"', - " exit 0", + ' echo "error: widget asset catalog not found at $CATALOG (expo-widgets wiped it? check plugin ordering in app.config.ts)"', + " exit 1", "fi", 'DEST="${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"', 'mkdir -p "$DEST"', @@ -51,7 +53,13 @@ function findByName(map, name) { function addWidgetAssetCatalog(proj, opts) { const objects = proj.hash.project.objects; const target = findByName(objects.PBXNativeTarget, opts.targetName); - if (!target) return false; + if (!target) { + throw new Error( + `addWidgetAssetCatalog: target "${opts.targetName}" not found — ` + + "withWidgetLogoAsset must be registered before expo-widgets so its " + + "xcodeproj mod runs after the widget target is created.", + ); + } const phases = target.value.buildPhases || []; const existing = objects.PBXShellScriptBuildPhase || {}; diff --git a/apps/mobile/plugins/withWidgetLogoAsset.cjs b/apps/mobile/plugins/withWidgetLogoAsset.cjs index 939a92af4c4..5c976c2a0cb 100644 --- a/apps/mobile/plugins/withWidgetLogoAsset.cjs +++ b/apps/mobile/plugins/withWidgetLogoAsset.cjs @@ -5,9 +5,15 @@ // expo-widgets generates ExpoWidgetsTarget without a Resources build phase and // has no asset support, so this plugin (a) writes an SVG template image set into // the generated widget asset catalog and (b) wires that catalog into the widget -// target with a dedicated Resources build phase. Both steps are idempotent and -// survive `expo prebuild --clean`. Must be listed AFTER "expo-widgets" in the -// plugins array so the widget target exists when this runs. +// target with an actool build phase. +// +// ORDERING: must be listed BEFORE "expo-widgets" in the plugins array. Expo +// chains same-type mods so the last-registered runs FIRST; registering this +// plugin earlier makes its mods run AFTER expo-widgets' mods. That matters +// twice: expo-widgets' dangerous mod rmSync's ios/ExpoWidgetsTarget/ (deleting +// any catalog written before it), and its xcodeproj mod is what creates the +// widget target. Listed after expo-widgets, both steps silently no-op on a +// fresh prebuild — which is how prod build 8 shipped without the logo. const path = require("path"); const fs = require("fs"); diff --git a/apps/mobile/scripts/wire-widget-asset-catalog.cjs b/apps/mobile/scripts/wire-widget-asset-catalog.cjs index ad96a4dabf7..b7c70f0cdcd 100644 --- a/apps/mobile/scripts/wire-widget-asset-catalog.cjs +++ b/apps/mobile/scripts/wire-widget-asset-catalog.cjs @@ -26,5 +26,5 @@ if (added) { fs.writeFileSync(pbxprojPath, proj.writeSync()); console.log("Added widget asset-compile phase to ExpoWidgetsTarget."); } else { - console.log("No change: phase already present or target not found."); + console.log("No change: phase already present."); }