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
16 changes: 16 additions & 0 deletions .changeset/xcode-plugin-structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@use-voltra/ios-client': minor
---

Restructure the widget Xcode integration and fix signing, versioning, and misconfiguration behavior.

- Widget code signing now mirrors the main app per build configuration (Debug→Debug,
Release→Release) instead of copying the first configuration's settings into both, so
manual-signing release builds get the correct provisioning profile.
- The widget's `MARKETING_VERSION` and `CURRENT_PROJECT_VERSION` now follow `expo.version`
and `expo.ios.buildNumber` (falling back to the previous `1.0`/`1` when unset), matching
App Store Connect's requirement that an appex version match its host app.
- The plugin now throws an actionable error when `expo.ios.bundleIdentifier` is missing
instead of silently skipping widget setup.
- Internally, target setup is collapsed into a single idempotent ensure pipeline and build
phases are matched semantically rather than by comment strings.
25 changes: 25 additions & 0 deletions packages/ios-client/expo-plugin/src/index.node.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { ExpoConfig } from 'expo/config'

import withVoltraIos from './index'

function createConfig(overrides: Partial<ExpoConfig> = {}): ExpoConfig {
return {
name: 'voltraexample',
slug: 'voltraexample',
// withPlugins asserts an expo-cli-provided project root when registering plugins.
_internal: { projectRoot: '/tmp/voltra-example' },
...overrides,
}
}

describe('withVoltraIos', () => {
it('throws an actionable error when ios.bundleIdentifier is missing', () => {
expect(() => withVoltraIos(createConfig(), {})).toThrow(/expo\.ios\.bundleIdentifier/)
expect(() => withVoltraIos(createConfig({ ios: {} }), {})).toThrow(/app\.json or app\.config/)
})

it('does not throw when ios.bundleIdentifier is set', () => {
const config = createConfig({ ios: { bundleIdentifier: 'com.voltra.example' } })
expect(() => withVoltraIos(config, {})).not.toThrow()
})
})
6 changes: 5 additions & 1 deletion packages/ios-client/expo-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ const withVoltraIos: VoltraIosConfigPlugin = (config, props = {}) => {

const iosBundleIdentifier = config.ios?.bundleIdentifier
if (!iosBundleIdentifier) {
return config
throw new Error(
'The Voltra iOS config plugin requires "expo.ios.bundleIdentifier" to be set in the app config — ' +
'it derives the widget extension bundle identifier from it. ' +
'Set "ios.bundleIdentifier" (e.g. "com.example.app") in app.json or app.config.(js|ts) and run prebuild again.'
)
}

const deploymentTarget = props.deploymentTarget || IOS.DEPLOYMENT_TARGET
Expand Down
2 changes: 1 addition & 1 deletion packages/ios-client/expo-plugin/src/ios-widget/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const withIOS: ConfigPlugin<WithIOSProps> = (config, props) => {
...(fonts && fonts.length > 0 ? [[withFonts, { fonts, targetName }] as [ConfigPlugin<any>, any]] : []),

// 2. Configure Xcode project (creates the target - must run before fonts mod executes)
[configureXcodeProject, { targetName, bundleIdentifier, deploymentTarget, widgets }],
[configureXcodeProject, { targetName, bundleIdentifier, deploymentTarget, widgets, version, buildNumber }],

// 3. Configure Podfile for widget extension target
[configurePodfile, { targetName }],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
13B07FBF1A68108700A75B9A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.swift */; };
13B07FC11A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC21A68108700A75B9A /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB61A68108700A75B9A /* Info.plist */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
13B07F961A680F5B00A75B9A /* voltraexample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = voltraexample.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FB01A68108700A75B9A /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = voltraexample/AppDelegate.swift; sourceTree = "<group>"; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = voltraexample/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = voltraexample/Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
13B07FAE1A68108700A75B9A /* voltraexample */,
83CBBA001A601CBA00E9B192 /* Products */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
usesTabs = 0;
};
13B07FAE1A68108700A75B9A /* voltraexample */ = {
isa = PBXGroup;
children = (
13B07FB01A68108700A75B9A /* AppDelegate.swift */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
);
name = voltraexample;
sourceTree = "<group>";
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* voltraexample.app */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
13B07F861A680F5B00A75B9A /* voltraexample */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "voltraexample" */;
buildPhases = (
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = voltraexample;
productName = voltraexample;
productReference = 13B07F961A680F5B00A75B9A /* voltraexample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1210;
TargetAttributes = {
13B07F861A680F5B00A75B9A = {
LastSwiftMigration = 1250;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "voltraexample" */;
compatibilityVersion = "Xcode 12.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 83CBB9F61A601CBA00E9B192;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* voltraexample */,
);
};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
13B07F8E1A680F5B00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13B07FC11A68108700A75B9A /* Images.xcassets in Resources */,
13B07FC21A68108700A75B9A /* Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
13B07F871A680F5B00A75B9A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13B07FBF1A68108700A75B9A /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */

/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = ABCDE12345;
INFOPLIST_FILE = voltraexample/Info.plist;
PRODUCT_BUNDLE_IDENTIFIER = com.voltra.example;
PRODUCT_NAME = voltraexample;
PROVISIONING_PROFILE_SPECIFIER = "Voltra Dev Profile";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = ABCDE12345;
INFOPLIST_FILE = voltraexample/Info.plist;
PRODUCT_BUNDLE_IDENTIFIER = com.voltra.example;
PRODUCT_NAME = voltraexample;
PROVISIONING_PROFILE_SPECIFIER = "Voltra AppStore Profile";
SWIFT_VERSION = 5.0;
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
SDKROOT = iphoneos;
};
name = Debug;
};
83CBBA211A601CBA00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
SDKROOT = iphoneos;
};
name = Release;
};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "voltraexample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13B07F941A680F5B00A75B9A /* Debug */,
13B07F951A680F5B00A75B9A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "voltraexample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */,
83CBBA211A601CBA00E9B192 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}
Loading
Loading