voltra 2.2.0 → 2.3.1
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 +6 -1
- package/build/cjs/apply/index.js.map +1 -1
- package/build/cjs/config/normalize.js +150 -31
- 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/discovery/metro.js +52 -0
- package/build/cjs/discovery/metro.js.map +1 -0
- 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 +6 -1
- package/build/esm/apply/index.js.map +1 -1
- package/build/esm/config/normalize.js +150 -31
- 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/discovery/metro.js +46 -0
- package/build/esm/discovery/metro.js.map +1 -0
- 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/discovery/metro.d.ts +21 -0
- package/build/types/discovery/metro.d.ts.map +1 -0
- 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 +6 -1
- package/src/config/normalize.node.test.ts +105 -0
- package/src/config/normalize.ts +217 -45
- 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/discovery/metro.node.test.ts +91 -0
- package/src/discovery/metro.ts +54 -0
- 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
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { parseStringPromise } from 'xml2js'
|
|
2
2
|
|
|
3
|
+
import { iosWidgetKindOverrides } from '../../config/widgetKind'
|
|
3
4
|
import { readTextFile, writeTextFile } from '../../fs/readWrite'
|
|
4
5
|
import { toRelativePath } from '../../fs/path'
|
|
5
6
|
import { VoltraCliError } from '../../reporting/summary'
|
|
6
7
|
|
|
7
8
|
import type { IOSProjectDiscovery } from '../../discovery/ios'
|
|
8
|
-
import type {
|
|
9
|
+
import type { ResolvedVoltraIOSConfig } from '../../config/types'
|
|
9
10
|
import type { ReportedChange } from '../../reporting/summary'
|
|
10
11
|
|
|
11
12
|
interface OrderedPlistNode {
|
|
@@ -23,12 +24,12 @@ type PlistErrorFactory = (message: string) => Error
|
|
|
23
24
|
|
|
24
25
|
export interface EnsureInfoPlistOptions {
|
|
25
26
|
projectRoot: string
|
|
26
|
-
ios:
|
|
27
|
+
ios: ResolvedVoltraIOSConfig
|
|
27
28
|
discovery: IOSProjectDiscovery
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export interface EnsureInfoPlistResult {
|
|
31
|
-
|
|
32
|
+
changes: ReportedChange[]
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export class IOSInfoPlistMutationError extends VoltraCliError {
|
|
@@ -40,7 +41,26 @@ export class IOSInfoPlistMutationError extends VoltraCliError {
|
|
|
40
41
|
|
|
41
42
|
export async function ensureInfoPlist(options: EnsureInfoPlistOptions): Promise<EnsureInfoPlistResult> {
|
|
42
43
|
const { projectRoot, ios, discovery } = options
|
|
43
|
-
const
|
|
44
|
+
const changes: ReportedChange[] = []
|
|
45
|
+
|
|
46
|
+
// Build configurations of a multi-environment app can each reference their own Info.plist.
|
|
47
|
+
for (const infoPlistPath of discovery.infoPlistPaths) {
|
|
48
|
+
const change = await ensureSingleInfoPlist(projectRoot, ios, infoPlistPath)
|
|
49
|
+
|
|
50
|
+
if (change) {
|
|
51
|
+
changes.push(change)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { changes }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function ensureSingleInfoPlist(
|
|
59
|
+
projectRoot: string,
|
|
60
|
+
ios: ResolvedVoltraIOSConfig,
|
|
61
|
+
infoPlistPath: string
|
|
62
|
+
): Promise<ReportedChange | undefined> {
|
|
63
|
+
const infoPlist = await parsePlistFile(infoPlistPath, 'main app Info.plist', createInfoPlistError)
|
|
44
64
|
|
|
45
65
|
infoPlist.NSSupportsLiveActivities = true
|
|
46
66
|
infoPlist.NSSupportsLiveActivitiesFrequentUpdates = false
|
|
@@ -52,8 +72,18 @@ export async function ensureInfoPlist(options: EnsureInfoPlistOptions): Promise<
|
|
|
52
72
|
const widgetIds = ios.widgets.map((widget) => widget.id)
|
|
53
73
|
setOrDeleteVoltraKey(infoPlist, 'Voltra_WidgetIds', widgetIds.length > 0 ? widgetIds : undefined)
|
|
54
74
|
|
|
75
|
+
// Widgets that pin a custom WidgetKit kind, so VoltraWidgetKind can map ids to kinds and back.
|
|
76
|
+
setOrDeleteVoltraKey(infoPlist, 'Voltra_WidgetKinds', iosWidgetKindOverrides(ios.widgets))
|
|
77
|
+
|
|
78
|
+
// Every server-driven widget gets an interval, so this dictionary's keys are the set of
|
|
79
|
+
// server-driven widget ids the runtime settings store validates against. A URL is written only
|
|
80
|
+
// when app.json set one; otherwise the app supplies it with setWidgetServerUpdate.
|
|
55
81
|
const serverWidgets = ios.widgets.filter((widget) => widget.serverUpdate)
|
|
56
|
-
const serverUrls = Object.fromEntries(
|
|
82
|
+
const serverUrls = Object.fromEntries(
|
|
83
|
+
serverWidgets
|
|
84
|
+
.filter((widget) => widget.serverUpdate?.url !== undefined)
|
|
85
|
+
.map((widget) => [widget.id, widget.serverUpdate?.url])
|
|
86
|
+
)
|
|
57
87
|
const serverIntervals = Object.fromEntries(
|
|
58
88
|
serverWidgets.map((widget) => [widget.id, widget.serverUpdate?.intervalMinutes])
|
|
59
89
|
)
|
|
@@ -70,9 +100,8 @@ export async function ensureInfoPlist(options: EnsureInfoPlistOptions): Promise<
|
|
|
70
100
|
)
|
|
71
101
|
|
|
72
102
|
const nextContent = buildPlistXml(infoPlist, createInfoPlistError)
|
|
73
|
-
const change = await writePlistIfChanged(projectRoot, discovery.infoPlistPath, nextContent)
|
|
74
103
|
|
|
75
|
-
return
|
|
104
|
+
return await writePlistIfChanged(projectRoot, infoPlistPath, nextContent)
|
|
76
105
|
}
|
|
77
106
|
|
|
78
107
|
export async function parsePlistFile(
|
|
@@ -8,7 +8,7 @@ import { resolveIOSWidgetTargetName } from './targetName'
|
|
|
8
8
|
import { VOLTRA_MIN_IOS_DEPLOYMENT_TARGET, isIOSDeploymentTargetAtLeast } from './deploymentTarget'
|
|
9
9
|
|
|
10
10
|
import type { IOSProjectDiscovery } from '../../discovery/ios'
|
|
11
|
-
import type {
|
|
11
|
+
import type { ResolvedVoltraIOSConfig } from '../../config/types'
|
|
12
12
|
import type { ReportedChange } from '../../reporting/summary'
|
|
13
13
|
|
|
14
14
|
const VOLTRA_MANAGED_BLOCK_START = '# >>> VOLTRA MANAGED BLOCK: widget extension target'
|
|
@@ -18,7 +18,7 @@ const LEGACY_BLOCK_NOTICE = '# DO NOT MODIFY THIS FILE - IT IS AUTO-GENERATED BY
|
|
|
18
18
|
|
|
19
19
|
export interface EnsurePodfileBlockOptions {
|
|
20
20
|
projectRoot: string
|
|
21
|
-
ios:
|
|
21
|
+
ios: ResolvedVoltraIOSConfig
|
|
22
22
|
discovery: IOSProjectDiscovery
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IOSProjectDiscovery } from '../../discovery/ios'
|
|
2
|
-
import type {
|
|
2
|
+
import type { ResolvedVoltraIOSConfig } from '../../config/types'
|
|
3
3
|
|
|
4
|
-
export function resolveIOSWidgetTargetName(ios:
|
|
4
|
+
export function resolveIOSWidgetTargetName(ios: ResolvedVoltraIOSConfig, discovery: IOSProjectDiscovery): string {
|
|
5
5
|
if (ios.targetName) {
|
|
6
6
|
return ios.targetName
|
|
7
7
|
}
|
|
@@ -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
|
}
|