81 lines · 3.1 KB · JavaScript
RawDownload
"use strict"; // Bundle the widget asset catalog into an app-extension (widget) target.//// expo-widgets generates the widget target without a Resources build phase, and// hand-adding a PBXResourcesBuildPhase does NOT get picked up — xcodebuild's// planner never schedules `actool` for it (verified: even a full re-plan skips// it). So instead we add a shell-script phase that runs `actool` directly and// 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. 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"; // Compiles ExpoWidgetsTarget/Assets.xcassets into the extension's resources dir.// Uses only Xcode-provided build settings so it works for device + simulator.const ACTOOL_SCRIPT = [  "set -e",  'CATALOG="${SRCROOT}/ExpoWidgetsTarget/Assets.xcassets"',  'if [ ! -d "$CATALOG" ]; then',  '  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"',  'xcrun actool "$CATALOG" --compile "$DEST" --platform "${PLATFORM_NAME}" --minimum-deployment-target "${IPHONEOS_DEPLOYMENT_TARGET:-16.0}" --output-format human-readable-text',].join("\n"); function stripComments(map) {  const out = {};  for (const key of Object.keys(map || {})) {    if (key.endsWith("_comment")) continue;    out[key] = map[key];  }  return out;} function findByName(map, name) {  for (const [uuid, value] of Object.entries(stripComments(map))) {    if (value && value.name === name) return { uuid, value };  }  return null;} /** * @param {import('xcode').XcodeProject} proj * @param {{ targetName: string }} opts */function addWidgetAssetCatalog(proj, opts) {  const objects = proj.hash.project.objects;  const target = findByName(objects.PBXNativeTarget, opts.targetName);  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 || {};  const already = Object.entries(stripComments(existing)).some(    ([uuid, value]) =>      value && value.name === `"${PHASE_NAME}"` && phases.some((p) => p.value === uuid),  );  if (already) return false;   const { uuid } = proj.addBuildPhase([], "PBXShellScriptBuildPhase", PHASE_NAME, target.uuid, {    shellPath: "/bin/sh",    shellScript: ACTOOL_SCRIPT,  });  // Always run: input-analysis is exactly what skipped the Resources phase.  objects.PBXShellScriptBuildPhase[uuid].alwaysOutOfDate = 1;  return true;} module.exports = { addWidgetAssetCatalog };