voltra 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/apply/index.js +4 -1
- package/build/cjs/apply/index.js.map +1 -1
- package/build/cjs/config/normalize.js +131 -29
- package/build/cjs/config/normalize.js.map +1 -1
- package/build/cjs/config/perConfiguration.js +29 -0
- package/build/cjs/config/perConfiguration.js.map +1 -0
- package/build/cjs/config/serverUpdate.js +119 -0
- package/build/cjs/config/serverUpdate.js.map +1 -0
- package/build/cjs/config/widgetKind.js +25 -0
- package/build/cjs/config/widgetKind.js.map +1 -0
- package/build/cjs/dependencies/platformPackages.js +9 -0
- package/build/cjs/dependencies/platformPackages.js.map +1 -1
- package/build/cjs/discovery/ios.js +50 -31
- package/build/cjs/discovery/ios.js.map +1 -1
- package/build/cjs/index.js +6 -2
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/platforms/android/generated.js +98 -40
- package/build/cjs/platforms/android/generated.js.map +1 -1
- package/build/cjs/platforms/android/widgetSizing.js +89 -0
- package/build/cjs/platforms/android/widgetSizing.js.map +1 -0
- package/build/cjs/platforms/ios/apply.js +21 -11
- package/build/cjs/platforms/ios/apply.js.map +1 -1
- package/build/cjs/platforms/ios/buildConfigurationValues.js +73 -0
- package/build/cjs/platforms/ios/buildConfigurationValues.js.map +1 -0
- package/build/cjs/platforms/ios/entitlements.js +34 -19
- package/build/cjs/platforms/ios/entitlements.js.map +1 -1
- package/build/cjs/platforms/ios/generated.js +79 -32
- package/build/cjs/platforms/ios/generated.js.map +1 -1
- package/build/cjs/platforms/ios/plist.js +22 -4
- package/build/cjs/platforms/ios/plist.js.map +1 -1
- package/build/cjs/platforms/ios/targetName.js.map +1 -1
- package/build/cjs/platforms/ios/xcodeTarget.js +166 -23
- package/build/cjs/platforms/ios/xcodeTarget.js.map +1 -1
- package/build/cjs/platforms/shared/widgetModule.js +18 -145
- package/build/cjs/platforms/shared/widgetModule.js.map +1 -1
- package/build/esm/apply/index.js +4 -1
- package/build/esm/apply/index.js.map +1 -1
- package/build/esm/config/normalize.js +131 -29
- package/build/esm/config/normalize.js.map +1 -1
- package/build/esm/config/perConfiguration.js +25 -0
- package/build/esm/config/perConfiguration.js.map +1 -0
- package/build/esm/config/serverUpdate.js +112 -0
- package/build/esm/config/serverUpdate.js.map +1 -0
- package/build/esm/config/widgetKind.js +20 -0
- package/build/esm/config/widgetKind.js.map +1 -0
- package/build/esm/dependencies/platformPackages.js +8 -0
- package/build/esm/dependencies/platformPackages.js.map +1 -1
- package/build/esm/discovery/ios.js +50 -31
- package/build/esm/discovery/ios.js.map +1 -1
- package/build/esm/index.js +1 -0
- package/build/esm/index.js.map +1 -1
- package/build/esm/platforms/android/generated.js +100 -42
- package/build/esm/platforms/android/generated.js.map +1 -1
- package/build/esm/platforms/android/widgetSizing.js +85 -0
- package/build/esm/platforms/android/widgetSizing.js.map +1 -0
- package/build/esm/platforms/ios/apply.js +21 -11
- package/build/esm/platforms/ios/apply.js.map +1 -1
- package/build/esm/platforms/ios/buildConfigurationValues.js +68 -0
- package/build/esm/platforms/ios/buildConfigurationValues.js.map +1 -0
- package/build/esm/platforms/ios/entitlements.js +34 -19
- package/build/esm/platforms/ios/entitlements.js.map +1 -1
- package/build/esm/platforms/ios/generated.js +80 -33
- package/build/esm/platforms/ios/generated.js.map +1 -1
- package/build/esm/platforms/ios/plist.js +22 -4
- package/build/esm/platforms/ios/plist.js.map +1 -1
- package/build/esm/platforms/ios/targetName.js.map +1 -1
- package/build/esm/platforms/ios/xcodeTarget.js +166 -23
- package/build/esm/platforms/ios/xcodeTarget.js.map +1 -1
- package/build/esm/platforms/shared/widgetModule.js +16 -107
- package/build/esm/platforms/shared/widgetModule.js.map +1 -1
- package/build/types/apply/index.d.ts.map +1 -1
- package/build/types/config/normalize.d.ts.map +1 -1
- package/build/types/config/perConfiguration.d.ts +18 -0
- package/build/types/config/perConfiguration.d.ts.map +1 -0
- package/build/types/config/serverUpdate.d.ts +80 -0
- package/build/types/config/serverUpdate.d.ts.map +1 -0
- package/build/types/config/types.d.ts +91 -30
- package/build/types/config/types.d.ts.map +1 -1
- package/build/types/config/widgetKind.d.ts +11 -0
- package/build/types/config/widgetKind.d.ts.map +1 -0
- package/build/types/dependencies/platformPackages.d.ts +1 -0
- package/build/types/dependencies/platformPackages.d.ts.map +1 -1
- package/build/types/discovery/ios.d.ts +10 -0
- package/build/types/discovery/ios.d.ts.map +1 -1
- package/build/types/index.d.ts +3 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/platforms/android/generated.d.ts.map +1 -1
- package/build/types/platforms/android/widgetSizing.d.ts +50 -0
- package/build/types/platforms/android/widgetSizing.d.ts.map +1 -0
- package/build/types/platforms/ios/apply.d.ts.map +1 -1
- package/build/types/platforms/ios/buildConfigurationValues.d.ts +33 -0
- package/build/types/platforms/ios/buildConfigurationValues.d.ts.map +1 -0
- package/build/types/platforms/ios/entitlements.d.ts +4 -4
- package/build/types/platforms/ios/entitlements.d.ts.map +1 -1
- package/build/types/platforms/ios/generated.d.ts +14 -2
- package/build/types/platforms/ios/generated.d.ts.map +1 -1
- package/build/types/platforms/ios/plist.d.ts +3 -3
- package/build/types/platforms/ios/plist.d.ts.map +1 -1
- package/build/types/platforms/ios/podfile.d.ts +2 -2
- package/build/types/platforms/ios/podfile.d.ts.map +1 -1
- package/build/types/platforms/ios/targetName.d.ts +2 -2
- package/build/types/platforms/ios/targetName.d.ts.map +1 -1
- package/build/types/platforms/ios/xcodeTarget.d.ts +4 -2
- package/build/types/platforms/ios/xcodeTarget.d.ts.map +1 -1
- package/build/types/platforms/shared/widgetModule.d.ts +12 -3
- package/build/types/platforms/shared/widgetModule.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/apply/index.ts +4 -1
- package/src/config/normalize.node.test.ts +61 -0
- package/src/config/normalize.ts +188 -44
- package/src/config/perConfiguration.ts +49 -0
- package/src/config/serverUpdate.ts +160 -0
- package/src/config/types.ts +97 -30
- package/src/config/widgetKind.node.test.ts +27 -0
- package/src/config/widgetKind.ts +27 -0
- package/src/dependencies/platformPackages.ts +11 -0
- package/src/discovery/ios.ts +104 -60
- package/src/index.ts +8 -0
- package/src/platforms/android/generated.ts +144 -47
- package/src/platforms/android/widgetSizing.ts +112 -0
- package/src/platforms/ios/apply.ts +21 -11
- package/src/platforms/ios/buildConfigurationValues.ts +105 -0
- package/src/platforms/ios/entitlements.ts +55 -31
- package/src/platforms/ios/generated.node.test.ts +61 -0
- package/src/platforms/ios/generated.ts +137 -38
- package/src/platforms/ios/plist.ts +36 -7
- package/src/platforms/ios/podfile.ts +2 -2
- package/src/platforms/ios/targetName.ts +2 -2
- package/src/platforms/ios/xcodeTarget.ts +248 -43
- package/src/platforms/shared/widgetModule.ts +22 -140
|
@@ -19,15 +19,21 @@ import { resolveIOSWidgetTargetName } from './targetName'
|
|
|
19
19
|
import { ensureMainGroupChild, openIOSXcodeProject, saveIOSXcodeProject } from './xcode'
|
|
20
20
|
import { resolveMainAppEntitlementsPath } from './mainAppEntitlements'
|
|
21
21
|
import { needsEntitlementsMutation } from './entitlements'
|
|
22
|
+
import { VOLTRA_OWNED_BUILD_SETTINGS } from './buildConfigurationValues'
|
|
22
23
|
import { VOLTRA_MIN_IOS_DEPLOYMENT_TARGET, maxIOSDeploymentTarget } from './deploymentTarget'
|
|
23
24
|
|
|
24
25
|
import type { IOSProjectDiscovery } from '../../discovery/ios'
|
|
25
|
-
import type {
|
|
26
|
+
import type { ResolvedVoltraIOSConfig } from '../../config/types'
|
|
26
27
|
import type { ReportedChange } from '../../reporting/summary'
|
|
27
28
|
import type { IOSXcodeProjectContext } from './xcode'
|
|
28
29
|
import type { BuildSettings } from '@bacons/xcode/build/json/types'
|
|
29
30
|
|
|
30
31
|
const IOS_APP_EXTENSION_PRODUCT_TYPE = 'com.apple.product-type.app-extension'
|
|
32
|
+
const OPTIONAL_WIDGET_CODE_SIGNING_SETTINGS = [
|
|
33
|
+
'CODE_SIGN_STYLE',
|
|
34
|
+
'DEVELOPMENT_TEAM',
|
|
35
|
+
'PROVISIONING_PROFILE_SPECIFIER',
|
|
36
|
+
] as const
|
|
31
37
|
const PRODUCT_FILE_TYPE = 'wrapper.app-extension'
|
|
32
38
|
const SWIFT_FILE_TYPE = 'sourcecode.swift'
|
|
33
39
|
const STRINGS_FILE_TYPE = 'text.plist.strings'
|
|
@@ -84,10 +90,12 @@ echo "Voltra: widget bundler resolved to $BUNDLER"
|
|
|
84
90
|
|
|
85
91
|
export interface EnsureIOSWidgetTargetOptions {
|
|
86
92
|
projectRoot: string
|
|
87
|
-
ios:
|
|
93
|
+
ios: ResolvedVoltraIOSConfig
|
|
88
94
|
discovery: IOSProjectDiscovery
|
|
89
95
|
generatedFiles: string[]
|
|
90
96
|
previousGeneratedFiles?: string[]
|
|
97
|
+
/** User-defined build settings to write at project level, keyed by build configuration name. */
|
|
98
|
+
buildSettings?: Map<string, Record<string, string>>
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
export interface EnsureIOSWidgetTargetResult {
|
|
@@ -105,7 +113,7 @@ export class IOSWidgetTargetMutationError extends VoltraCliError {
|
|
|
105
113
|
export async function ensureIOSWidgetTarget(
|
|
106
114
|
options: EnsureIOSWidgetTargetOptions
|
|
107
115
|
): Promise<EnsureIOSWidgetTargetResult> {
|
|
108
|
-
const { projectRoot, ios, discovery, generatedFiles, previousGeneratedFiles } = options
|
|
116
|
+
const { projectRoot, ios, discovery, generatedFiles, previousGeneratedFiles, buildSettings } = options
|
|
109
117
|
const targetName = resolveIOSWidgetTargetName(ios, discovery)
|
|
110
118
|
const context = openIOSXcodeProject(discovery)
|
|
111
119
|
const beforeSerialized = JSON.stringify(context.project.toJSON())
|
|
@@ -113,16 +121,22 @@ export async function ensureIOSWidgetTarget(
|
|
|
113
121
|
const nextGeneratedFiles = normalizeGeneratedFilePaths(generatedFiles, projectRoot, discovery)
|
|
114
122
|
const previousWidgetFiles = normalizeGeneratedFilePaths(previousGeneratedFiles ?? [], projectRoot, discovery)
|
|
115
123
|
const staleTargetNames = getStaleWidgetTargetNames(previousWidgetFiles, targetName)
|
|
116
|
-
const
|
|
117
|
-
const codeSigning = getMainAppCodeSigningSettings(context)
|
|
124
|
+
const mainAppSettings = getMainAppConfigurationSettings(context, discovery, targetName)
|
|
118
125
|
const mainAppEntitlementsPath = await getMainAppEntitlementsBuildSetting(discovery, ios)
|
|
119
126
|
|
|
120
127
|
removeStaleWidgetTargets(context, staleTargetNames)
|
|
121
|
-
ensureMainAppEntitlementsBuildSetting(
|
|
128
|
+
ensureMainAppEntitlementsBuildSetting(
|
|
129
|
+
context,
|
|
130
|
+
mainAppEntitlementsPath,
|
|
131
|
+
ios.project.entitlementsPath !== undefined,
|
|
132
|
+
getEntitlementsPathByConfiguration(discovery)
|
|
133
|
+
)
|
|
122
134
|
ensureMainAppDeploymentTarget(context)
|
|
123
|
-
ensureWidgetTarget(context, targetName,
|
|
135
|
+
ensureWidgetTarget(context, targetName, ios.deploymentTarget, mainAppSettings)
|
|
124
136
|
|
|
125
137
|
const widgetTarget = getWidgetTarget(context, targetName)
|
|
138
|
+
|
|
139
|
+
ensureVoltraBuildSettings(context, widgetTarget, buildSettings ?? new Map())
|
|
126
140
|
const widgetGroup = ensureWidgetGroup(context, targetName)
|
|
127
141
|
const productFile = ensureProductFile(context, targetName, productPath)
|
|
128
142
|
|
|
@@ -159,24 +173,17 @@ export async function ensureIOSWidgetTarget(
|
|
|
159
173
|
function ensureWidgetTarget(
|
|
160
174
|
context: IOSXcodeProjectContext,
|
|
161
175
|
targetName: string,
|
|
162
|
-
bundleIdentifier: string,
|
|
163
176
|
deploymentTarget: string,
|
|
164
|
-
|
|
177
|
+
mainAppSettings: MainAppSettingsByConfiguration
|
|
165
178
|
): PBXNativeTarget {
|
|
166
179
|
const existingTarget = getWidgetTargetOptional(context, targetName)
|
|
167
180
|
|
|
168
181
|
if (existingTarget) {
|
|
169
|
-
ensureBuildConfigurations(existingTarget, targetName,
|
|
182
|
+
ensureBuildConfigurations(context, existingTarget, targetName, deploymentTarget, mainAppSettings)
|
|
170
183
|
return existingTarget
|
|
171
184
|
}
|
|
172
185
|
|
|
173
|
-
const buildConfigurationList = createBuildConfigurationList(
|
|
174
|
-
context,
|
|
175
|
-
targetName,
|
|
176
|
-
bundleIdentifier,
|
|
177
|
-
deploymentTarget,
|
|
178
|
-
codeSigning
|
|
179
|
-
)
|
|
186
|
+
const buildConfigurationList = createBuildConfigurationList(context, targetName, deploymentTarget, mainAppSettings)
|
|
180
187
|
const target = context.project.rootObject.createNativeTarget({
|
|
181
188
|
buildConfigurationList,
|
|
182
189
|
name: targetName,
|
|
@@ -193,19 +200,17 @@ function ensureWidgetTarget(
|
|
|
193
200
|
function createBuildConfigurationList(
|
|
194
201
|
context: IOSXcodeProjectContext,
|
|
195
202
|
targetName: string,
|
|
196
|
-
bundleIdentifier: string,
|
|
197
203
|
deploymentTarget: string,
|
|
198
|
-
|
|
204
|
+
mainAppSettings: MainAppSettingsByConfiguration
|
|
199
205
|
): XCConfigurationList {
|
|
200
206
|
const configs = context.mainAppTarget.buildConfigurations.all.map((config) => {
|
|
201
207
|
return XCBuildConfiguration.create(context.project, {
|
|
202
208
|
name: config.props.name,
|
|
203
209
|
buildSettings: buildWidgetBuildSettings(
|
|
204
210
|
targetName,
|
|
205
|
-
bundleIdentifier,
|
|
206
211
|
deploymentTarget,
|
|
207
|
-
|
|
208
|
-
config.props.name
|
|
212
|
+
config.props.name,
|
|
213
|
+
getMainAppSettingsFor(mainAppSettings, config.props.name)
|
|
209
214
|
),
|
|
210
215
|
})
|
|
211
216
|
})
|
|
@@ -217,11 +222,11 @@ function createBuildConfigurationList(
|
|
|
217
222
|
}
|
|
218
223
|
|
|
219
224
|
function ensureBuildConfigurations(
|
|
225
|
+
context: IOSXcodeProjectContext,
|
|
220
226
|
target: PBXNativeTarget,
|
|
221
227
|
targetName: string,
|
|
222
|
-
bundleIdentifier: string,
|
|
223
228
|
minimumDeploymentTarget: string,
|
|
224
|
-
|
|
229
|
+
mainAppSettings: MainAppSettingsByConfiguration
|
|
225
230
|
): void {
|
|
226
231
|
const configurationList = target.props.buildConfigurationList
|
|
227
232
|
|
|
@@ -231,23 +236,71 @@ function ensureBuildConfigurations(
|
|
|
231
236
|
)
|
|
232
237
|
}
|
|
233
238
|
|
|
239
|
+
addMissingWidgetBuildConfigurations(context, configurationList, targetName, minimumDeploymentTarget, mainAppSettings)
|
|
240
|
+
|
|
234
241
|
for (const config of configurationList.props.buildConfigurations) {
|
|
235
242
|
const deploymentTarget = resolveBuildConfigurationDeploymentTarget(config, minimumDeploymentTarget)
|
|
236
243
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
244
|
+
const nextBuildSettings = buildWidgetBuildSettings(
|
|
245
|
+
targetName,
|
|
246
|
+
deploymentTarget,
|
|
247
|
+
config.props.name,
|
|
248
|
+
getMainAppSettingsFor(mainAppSettings, config.props.name)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
Object.assign(config.props.buildSettings, nextBuildSettings)
|
|
252
|
+
|
|
253
|
+
// Signing settings the app configuration no longer sets are dropped rather than left behind,
|
|
254
|
+
// now that each configuration can change independently of the default one.
|
|
255
|
+
for (const settingName of OPTIONAL_WIDGET_CODE_SIGNING_SETTINGS) {
|
|
256
|
+
if (nextBuildSettings[settingName] === undefined) {
|
|
257
|
+
delete (config.props.buildSettings as unknown as Record<string, unknown>)[settingName]
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Mirrors build configurations added to the app since the widget target was created, so an
|
|
265
|
+
* environment added later still builds the extension.
|
|
266
|
+
*/
|
|
267
|
+
function addMissingWidgetBuildConfigurations(
|
|
268
|
+
context: IOSXcodeProjectContext,
|
|
269
|
+
configurationList: XCConfigurationList,
|
|
270
|
+
targetName: string,
|
|
271
|
+
minimumDeploymentTarget: string,
|
|
272
|
+
mainAppSettings: MainAppSettingsByConfiguration
|
|
273
|
+
): void {
|
|
274
|
+
const existingNames = new Set(configurationList.props.buildConfigurations.map((config) => config.props.name))
|
|
275
|
+
|
|
276
|
+
for (const config of context.mainAppTarget.buildConfigurations.all) {
|
|
277
|
+
const configurationName = config.props.name
|
|
278
|
+
|
|
279
|
+
if (existingNames.has(configurationName)) {
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
configurationList.props.buildConfigurations.push(
|
|
284
|
+
XCBuildConfiguration.create(context.project, {
|
|
285
|
+
name: configurationName,
|
|
286
|
+
buildSettings: buildWidgetBuildSettings(
|
|
287
|
+
targetName,
|
|
288
|
+
resolveBuildConfigurationDeploymentTarget(config, minimumDeploymentTarget),
|
|
289
|
+
configurationName,
|
|
290
|
+
getMainAppSettingsFor(mainAppSettings, configurationName)
|
|
291
|
+
),
|
|
292
|
+
})
|
|
240
293
|
)
|
|
241
294
|
}
|
|
242
295
|
}
|
|
243
296
|
|
|
244
297
|
function buildWidgetBuildSettings(
|
|
245
298
|
targetName: string,
|
|
246
|
-
bundleIdentifier: string,
|
|
247
299
|
deploymentTarget: string,
|
|
248
|
-
|
|
249
|
-
|
|
300
|
+
configurationName: string,
|
|
301
|
+
mainAppSettings: MainAppConfigurationSettings
|
|
250
302
|
): BuildSettings & Record<string, string | undefined> {
|
|
303
|
+
const { bundleIdentifier, codeSigning } = mainAppSettings
|
|
251
304
|
const buildSettings: BuildSettings & Record<string, string | undefined> = {
|
|
252
305
|
ASSETCATALOG_COMPILER_APPICON_NAME: '',
|
|
253
306
|
CODE_SIGN_ENTITLEMENTS: `${targetName}/${targetName}.entitlements`,
|
|
@@ -792,21 +845,106 @@ function toIOSProjectRelativePath(relativeFilePath: string, iosRootRelativePrefi
|
|
|
792
845
|
return undefined
|
|
793
846
|
}
|
|
794
847
|
|
|
795
|
-
|
|
848
|
+
/**
|
|
849
|
+
* Widget settings are derived per build configuration: apps that ship several environments give each
|
|
850
|
+
* configuration its own bundle identifier and signing setup, and the extension has to match the app
|
|
851
|
+
* it is embedded in for every one of them.
|
|
852
|
+
*/
|
|
853
|
+
function getMainAppConfigurationSettings(
|
|
796
854
|
context: IOSXcodeProjectContext,
|
|
797
855
|
discovery: IOSProjectDiscovery,
|
|
798
856
|
targetName: string
|
|
799
|
-
):
|
|
800
|
-
const
|
|
801
|
-
|
|
857
|
+
): MainAppSettingsByConfiguration {
|
|
858
|
+
const defaultConfiguration = context.mainAppTarget.buildConfigurations.default
|
|
859
|
+
const defaultBundleIdentifier = readMainAppBundleIdentifier(defaultConfiguration)
|
|
802
860
|
|
|
803
|
-
if (
|
|
861
|
+
if (!defaultBundleIdentifier) {
|
|
804
862
|
throw new IOSWidgetTargetMutationError(
|
|
805
863
|
`Main app target '${discovery.mainTargetName}' is missing PRODUCT_BUNDLE_IDENTIFIER in ${discovery.pbxprojPath}`
|
|
806
864
|
)
|
|
807
865
|
}
|
|
808
866
|
|
|
809
|
-
|
|
867
|
+
const byConfigurationName = new Map<string, MainAppConfigurationSettings>()
|
|
868
|
+
|
|
869
|
+
for (const config of context.mainAppTarget.buildConfigurations.all) {
|
|
870
|
+
const bundleIdentifier = readMainAppBundleIdentifier(config) ?? defaultBundleIdentifier
|
|
871
|
+
|
|
872
|
+
byConfigurationName.set(config.props.name, {
|
|
873
|
+
// The raw value is kept, so an app identifier built from build settings keeps expanding.
|
|
874
|
+
bundleIdentifier: `${bundleIdentifier}.${sanitizeBundleIdentifierSegment(targetName)}`,
|
|
875
|
+
codeSigning: getCodeSigningSettings(config),
|
|
876
|
+
})
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return {
|
|
880
|
+
byConfigurationName,
|
|
881
|
+
fallback: {
|
|
882
|
+
bundleIdentifier: `${defaultBundleIdentifier}.${sanitizeBundleIdentifierSegment(targetName)}`,
|
|
883
|
+
codeSigning: getCodeSigningSettings(defaultConfiguration),
|
|
884
|
+
},
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function getMainAppSettingsFor(
|
|
889
|
+
mainAppSettings: MainAppSettingsByConfiguration,
|
|
890
|
+
configurationName: string
|
|
891
|
+
): MainAppConfigurationSettings {
|
|
892
|
+
return mainAppSettings.byConfigurationName.get(configurationName) ?? mainAppSettings.fallback
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function readMainAppBundleIdentifier(config: XCBuildConfiguration): string | undefined {
|
|
896
|
+
const rawBundleIdentifier = readBuildSettingString(config.props.buildSettings?.PRODUCT_BUNDLE_IDENTIFIER)
|
|
897
|
+
|
|
898
|
+
if (rawBundleIdentifier) {
|
|
899
|
+
return substituteTargetScopedBuildSettings(rawBundleIdentifier, config)
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const inheritedBundleIdentifier = config.resolveBuildSetting('PRODUCT_BUNDLE_IDENTIFIER')
|
|
903
|
+
|
|
904
|
+
if (typeof inheritedBundleIdentifier !== 'string' || inheritedBundleIdentifier.length === 0) {
|
|
905
|
+
return undefined
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
return stripQuotes(inheritedBundleIdentifier)
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Substitutes the build settings whose value depends on the target being built, leaving every other
|
|
913
|
+
* reference in place.
|
|
914
|
+
*
|
|
915
|
+
* An app identifier is routinely assembled from build settings. Those that mean something different
|
|
916
|
+
* in the widget target have to be resolved against the app now — the React Native template's
|
|
917
|
+
* `org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)` would otherwise name the widget
|
|
918
|
+
* after itself and stop being a child of the app's identifier. Everything else is left unexpanded,
|
|
919
|
+
* so a setting the project defines per build configuration, such as `$(BUNDLE_SUFFIX)`, keeps
|
|
920
|
+
* expanding per build in the widget target too.
|
|
921
|
+
*/
|
|
922
|
+
function substituteTargetScopedBuildSettings(value: string, config: XCBuildConfiguration): string {
|
|
923
|
+
return value.replace(/\$\((PRODUCT_NAME|TARGET_NAME)(?::([A-Za-z0-9]+))?\)/g, (match, settingName, modifier) => {
|
|
924
|
+
const settingValue = config.resolveBuildSetting(settingName)
|
|
925
|
+
|
|
926
|
+
if (typeof settingValue !== 'string' || settingValue.length === 0) {
|
|
927
|
+
return match
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return applyBuildSettingModifier(stripQuotes(settingValue), modifier) ?? match
|
|
931
|
+
})
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function applyBuildSettingModifier(value: string, modifier: string | undefined): string | undefined {
|
|
935
|
+
switch (modifier) {
|
|
936
|
+
case undefined:
|
|
937
|
+
return value
|
|
938
|
+
case 'rfc1034identifier':
|
|
939
|
+
return value.replace(/[^a-zA-Z0-9]/g, '-')
|
|
940
|
+
case 'lower':
|
|
941
|
+
return value.toLowerCase()
|
|
942
|
+
case 'upper':
|
|
943
|
+
return value.toUpperCase()
|
|
944
|
+
default:
|
|
945
|
+
// An unsupported modifier is left for Xcode rather than guessed at.
|
|
946
|
+
return undefined
|
|
947
|
+
}
|
|
810
948
|
}
|
|
811
949
|
|
|
812
950
|
function sanitizeBundleIdentifierSegment(targetName: string): string {
|
|
@@ -814,8 +952,8 @@ function sanitizeBundleIdentifierSegment(targetName: string): string {
|
|
|
814
952
|
return sanitized.replace(/-+/g, '-').replace(/^-+|-+$/g, '')
|
|
815
953
|
}
|
|
816
954
|
|
|
817
|
-
function
|
|
818
|
-
const buildSettings =
|
|
955
|
+
function getCodeSigningSettings(config: XCBuildConfiguration): MainAppCodeSigningSettings {
|
|
956
|
+
const buildSettings = config.props.buildSettings ?? {}
|
|
819
957
|
|
|
820
958
|
return {
|
|
821
959
|
codeSignStyle: readBuildSettingString(buildSettings.CODE_SIGN_STYLE),
|
|
@@ -828,7 +966,7 @@ function getMainAppCodeSigningSettings(context: IOSXcodeProjectContext): MainApp
|
|
|
828
966
|
|
|
829
967
|
async function getMainAppEntitlementsBuildSetting(
|
|
830
968
|
discovery: IOSProjectDiscovery,
|
|
831
|
-
ios:
|
|
969
|
+
ios: ResolvedVoltraIOSConfig
|
|
832
970
|
): Promise<string | undefined> {
|
|
833
971
|
if (discovery.entitlementsPath) {
|
|
834
972
|
return normalizeRelativePath(path.relative(discovery.iosRoot, discovery.entitlementsPath))
|
|
@@ -847,17 +985,73 @@ async function getMainAppEntitlementsBuildSetting(
|
|
|
847
985
|
return normalizeRelativePath(path.relative(discovery.iosRoot, entitlementsPath))
|
|
848
986
|
}
|
|
849
987
|
|
|
988
|
+
/**
|
|
989
|
+
* Writes the user-defined build settings that hold Voltra's per-build-configuration values.
|
|
990
|
+
*
|
|
991
|
+
* They are set on the app and widget targets rather than on the project, so the build
|
|
992
|
+
* configurations written to are exactly the ones the values were validated against: a target can
|
|
993
|
+
* carry a configuration the project-level list does not have, and a value silently dropped there
|
|
994
|
+
* would expand to an empty App Group at build time. Settings Voltra no longer defines are removed.
|
|
995
|
+
*/
|
|
996
|
+
function ensureVoltraBuildSettings(
|
|
997
|
+
context: IOSXcodeProjectContext,
|
|
998
|
+
widgetTarget: PBXNativeTarget,
|
|
999
|
+
buildSettings: Map<string, Record<string, string>>
|
|
1000
|
+
): void {
|
|
1001
|
+
const configurations = [
|
|
1002
|
+
...context.mainAppTarget.buildConfigurations.all,
|
|
1003
|
+
...(widgetTarget.props.buildConfigurationList?.props.buildConfigurations ?? []),
|
|
1004
|
+
]
|
|
1005
|
+
|
|
1006
|
+
for (const config of configurations) {
|
|
1007
|
+
const configurationSettings = config.props.buildSettings as unknown as Record<string, string | undefined>
|
|
1008
|
+
const nextSettings = buildSettings.get(config.props.name) ?? {}
|
|
1009
|
+
|
|
1010
|
+
for (const settingName of VOLTRA_OWNED_BUILD_SETTINGS) {
|
|
1011
|
+
if (nextSettings[settingName] === undefined) {
|
|
1012
|
+
delete configurationSettings[settingName]
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
Object.assign(configurationSettings, nextSettings)
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function getEntitlementsPathByConfiguration(discovery: IOSProjectDiscovery): Map<string, string> {
|
|
1021
|
+
return new Map(
|
|
1022
|
+
[...(discovery.entitlementsPathByConfiguration ?? [])].map(([buildConfigurationName, entitlementsPath]) => [
|
|
1023
|
+
buildConfigurationName,
|
|
1024
|
+
normalizeRelativePath(path.relative(discovery.iosRoot, entitlementsPath)),
|
|
1025
|
+
])
|
|
1026
|
+
)
|
|
1027
|
+
}
|
|
1028
|
+
|
|
850
1029
|
function ensureMainAppEntitlementsBuildSetting(
|
|
851
1030
|
context: IOSXcodeProjectContext,
|
|
852
|
-
entitlementsPath: string | undefined
|
|
1031
|
+
entitlementsPath: string | undefined,
|
|
1032
|
+
isConfiguredExplicitly: boolean,
|
|
1033
|
+
entitlementsPathByConfiguration: Map<string, string>
|
|
853
1034
|
): void {
|
|
854
1035
|
for (const config of context.mainAppTarget.buildConfigurations.all) {
|
|
855
|
-
|
|
856
|
-
|
|
1036
|
+
const configuredEntitlementsPath = entitlementsPathByConfiguration.get(config.props.name)
|
|
1037
|
+
|
|
1038
|
+
if (configuredEntitlementsPath) {
|
|
1039
|
+
config.props.buildSettings.CODE_SIGN_ENTITLEMENTS = configuredEntitlementsPath
|
|
1040
|
+
continue
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
if (!entitlementsPath) {
|
|
1044
|
+
delete config.props.buildSettings.CODE_SIGN_ENTITLEMENTS
|
|
1045
|
+
continue
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// A build configuration pointing at its own entitlements file keeps it, unless the config file
|
|
1049
|
+
// named one explicitly.
|
|
1050
|
+
if (!isConfiguredExplicitly && readBuildSettingString(config.props.buildSettings.CODE_SIGN_ENTITLEMENTS)) {
|
|
857
1051
|
continue
|
|
858
1052
|
}
|
|
859
1053
|
|
|
860
|
-
|
|
1054
|
+
config.props.buildSettings.CODE_SIGN_ENTITLEMENTS = entitlementsPath
|
|
861
1055
|
}
|
|
862
1056
|
}
|
|
863
1057
|
|
|
@@ -886,6 +1080,17 @@ interface MainAppCodeSigningSettings {
|
|
|
886
1080
|
provisioningProfileSpecifier?: string
|
|
887
1081
|
}
|
|
888
1082
|
|
|
1083
|
+
interface MainAppConfigurationSettings {
|
|
1084
|
+
bundleIdentifier: string
|
|
1085
|
+
codeSigning: MainAppCodeSigningSettings
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
interface MainAppSettingsByConfiguration {
|
|
1089
|
+
byConfigurationName: Map<string, MainAppConfigurationSettings>
|
|
1090
|
+
/** Used for widget build configurations the main app target does not have. */
|
|
1091
|
+
fallback: MainAppConfigurationSettings
|
|
1092
|
+
}
|
|
1093
|
+
|
|
889
1094
|
function readBuildSettingString(value: unknown): string | undefined {
|
|
890
1095
|
return typeof value === 'string' ? stripQuotes(value) : undefined
|
|
891
1096
|
}
|
|
@@ -1,153 +1,35 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import vm from 'node:vm'
|
|
4
|
-
import { createRequire } from 'node:module'
|
|
1
|
+
import { createWidgetModuleLoader, type WidgetModuleLoader } from '@use-voltra/compiler'
|
|
5
2
|
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
const MODULE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '']
|
|
3
|
+
import type { VoltraPlatform } from '../../config/types'
|
|
9
4
|
|
|
10
5
|
export interface WidgetModuleBuildInfo {
|
|
11
6
|
isDev: false
|
|
12
7
|
metroUrl: null
|
|
13
8
|
appVersion: 'unknown'
|
|
14
|
-
voltraVersion:
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const DYNAMIC_WIDGET_BUILD_INFO: WidgetModuleBuildInfo = {
|
|
18
|
-
isDev: false,
|
|
19
|
-
metroUrl: null,
|
|
20
|
-
appVersion: 'unknown',
|
|
21
|
-
voltraVersion: '1.4.1',
|
|
9
|
+
voltraVersion: string
|
|
22
10
|
}
|
|
23
11
|
|
|
24
|
-
export function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const moduleCache = new Map<string, unknown>()
|
|
31
|
-
|
|
32
|
-
const customRequire = (moduleSpecifier: string, currentDir: string): unknown => {
|
|
33
|
-
if (!isLocalModule(moduleSpecifier)) {
|
|
34
|
-
return projectRequire(moduleSpecifier)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const resolvedModulePath = resolveModulePath(moduleSpecifier, currentDir)
|
|
38
|
-
|
|
39
|
-
if (!resolvedModulePath) {
|
|
40
|
-
throw createError(`Cannot resolve module '${moduleSpecifier}' from '${currentDir}'`)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const cachedModule = moduleCache.get(resolvedModulePath)
|
|
44
|
-
if (cachedModule !== undefined) {
|
|
45
|
-
return cachedModule
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const transpiledCode = transpileWidgetModule(projectRoot, resolvedModulePath, projectRequire, createError)
|
|
49
|
-
const moduleDir = path.dirname(resolvedModulePath)
|
|
50
|
-
const moduleRecord = { exports: {} as Record<string, unknown> }
|
|
51
|
-
moduleCache.set(resolvedModulePath, moduleRecord.exports)
|
|
52
|
-
|
|
53
|
-
const context = vm.createContext({
|
|
54
|
-
__dirname: moduleDir,
|
|
55
|
-
__filename: resolvedModulePath,
|
|
56
|
-
console,
|
|
57
|
-
exports: moduleRecord.exports,
|
|
58
|
-
module: moduleRecord,
|
|
59
|
-
process,
|
|
60
|
-
require: (specifier: string) => customRequire(specifier, moduleDir),
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
const script = new vm.Script(transpiledCode, { filename: resolvedModulePath })
|
|
64
|
-
script.runInContext(context)
|
|
65
|
-
|
|
66
|
-
moduleCache.set(resolvedModulePath, moduleRecord.exports)
|
|
67
|
-
return moduleRecord.exports
|
|
12
|
+
export function createDynamicWidgetBuildInfo(voltraVersion: string): WidgetModuleBuildInfo {
|
|
13
|
+
return {
|
|
14
|
+
isDev: false,
|
|
15
|
+
metroUrl: null,
|
|
16
|
+
appVersion: 'unknown',
|
|
17
|
+
voltraVersion,
|
|
68
18
|
}
|
|
69
|
-
|
|
70
|
-
return customRequire(filePath, path.dirname(filePath))
|
|
71
19
|
}
|
|
72
20
|
|
|
73
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Loader used by the apply pipeline to evaluate widget source at build time.
|
|
23
|
+
*
|
|
24
|
+
* The evaluation rules — which imports are allowed and what they resolve to — live in
|
|
25
|
+
* `@use-voltra/compiler` so that `voltra apply`, the Expo config plugins, and the Metro
|
|
26
|
+
* widget bundler all apply the same contract.
|
|
27
|
+
*/
|
|
28
|
+
export function createGeneratedWidgetModuleLoader(
|
|
74
29
|
projectRoot: string,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
):
|
|
79
|
-
|
|
80
|
-
const projectBabelConfigPath = resolveProjectBabelConfig(projectRoot)
|
|
81
|
-
const result = babel.transformSync(source, {
|
|
82
|
-
babelrc: false,
|
|
83
|
-
configFile: projectBabelConfigPath,
|
|
84
|
-
cwd: projectRoot,
|
|
85
|
-
filename: filePath,
|
|
86
|
-
presets: projectBabelConfigPath ? undefined : [resolveFallbackBabelPreset(projectRequire, createError)],
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
if (!result?.code) {
|
|
90
|
-
throw createError(`Babel transpilation failed for ${filePath}`)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return result.code
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function resolveProjectBabelConfig(projectRoot: string): string | undefined {
|
|
97
|
-
const candidates = ['babel.config.js', 'babel.config.cjs', 'babel.config.mjs']
|
|
98
|
-
|
|
99
|
-
for (const candidate of candidates) {
|
|
100
|
-
const candidatePath = path.join(projectRoot, candidate)
|
|
101
|
-
if (fs.existsSync(candidatePath)) {
|
|
102
|
-
return candidatePath
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return undefined
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function resolveFallbackBabelPreset(projectRequire: NodeRequire, createError: (message: string) => Error): string {
|
|
110
|
-
try {
|
|
111
|
-
return projectRequire.resolve('@react-native/babel-preset')
|
|
112
|
-
} catch {
|
|
113
|
-
try {
|
|
114
|
-
return projectRequire.resolve('babel-preset-expo')
|
|
115
|
-
} catch {
|
|
116
|
-
throw createError(
|
|
117
|
-
'Could not resolve a Babel preset for widget evaluation. Add a project babel.config.js or install @react-native/babel-preset.'
|
|
118
|
-
)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function createProjectRequire(projectRoot: string): NodeRequire {
|
|
124
|
-
return createRequire(path.join(projectRoot, 'package.json'))
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function isLocalModule(moduleSpecifier: string): boolean {
|
|
128
|
-
return moduleSpecifier.startsWith('.') || moduleSpecifier.startsWith('/')
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function resolveModulePath(moduleSpecifier: string, fromDir: string): string | null {
|
|
132
|
-
const basePath = path.resolve(fromDir, moduleSpecifier)
|
|
133
|
-
|
|
134
|
-
for (const extension of MODULE_EXTENSIONS) {
|
|
135
|
-
const candidate = `${basePath}${extension}`
|
|
136
|
-
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
137
|
-
return candidate
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (!fs.existsSync(basePath) || !fs.statSync(basePath).isDirectory()) {
|
|
142
|
-
return null
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
for (const extension of MODULE_EXTENSIONS) {
|
|
146
|
-
const indexCandidate = path.join(basePath, `index${extension}`)
|
|
147
|
-
if (fs.existsSync(indexCandidate) && fs.statSync(indexCandidate).isFile()) {
|
|
148
|
-
return indexCandidate
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return null
|
|
30
|
+
platform: VoltraPlatform,
|
|
31
|
+
createError: (message: string) => Error,
|
|
32
|
+
onWarning?: (message: string) => void
|
|
33
|
+
): WidgetModuleLoader {
|
|
34
|
+
return createWidgetModuleLoader({ projectRoot, platform, createError, onWarning })
|
|
153
35
|
}
|