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
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
import type { NormalizedVoltraConfig } from '../config/types'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A Dynamic Widget renders on device from a per-widget JS bundle that `@use-voltra/metro` produces:
|
|
8
|
+
* the dev server serves it at `/voltra/widgets/<id>.bundle`, and the release bundler reads the same
|
|
9
|
+
* registry. Without the package there is no bundle, and the widget silently draws its prerendered
|
|
10
|
+
* `initialStatePath` forever while its server updates keep fetching — a failure that otherwise shows
|
|
11
|
+
* up only as one `Log.w` line in logcat.
|
|
12
|
+
*
|
|
13
|
+
* Resolution from the project root is the same signal the generated Xcode build phase and Gradle
|
|
14
|
+
* task already use (`platforms/ios/xcodeTarget.ts`, `platforms/android/gradle.ts`), so a project
|
|
15
|
+
* that passes here is a project whose release bundling can run.
|
|
16
|
+
*
|
|
17
|
+
* This deliberately does not try to decide whether the app's Metro config is *wired up* — whether
|
|
18
|
+
* `withVoltra` wraps it. That cannot be read off the config source without warning at correct
|
|
19
|
+
* projects: a config that delegates to a shared preset (`module.exports =
|
|
20
|
+
* require('@myorg/metro-config')(__dirname)`) applies the wrapper somewhere this cannot see, and a
|
|
21
|
+
* config hand-composed from `createVoltraMiddleware` and `createWidgetRegistry` never names
|
|
22
|
+
* `withVoltra` at all. A warning that fires on a working setup costs more than the one it catches.
|
|
23
|
+
*/
|
|
24
|
+
export function findMissingMetroPackageWarning(config: NormalizedVoltraConfig): string | undefined {
|
|
25
|
+
if (!hasDynamicWidget(config)) {
|
|
26
|
+
return undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (canResolveVoltraMetro(config.projectRoot)) {
|
|
30
|
+
return undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
`This project declares a widget with an 'entry', but '@use-voltra/metro' cannot be resolved from ` +
|
|
35
|
+
`${config.projectRoot}. Dynamic Widgets render from a bundle only that package produces, so they ` +
|
|
36
|
+
`will keep showing their prerendered initial state. Install '@use-voltra/metro' and wrap the app's ` +
|
|
37
|
+
`Metro config: module.exports = withVoltra(config).`
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function hasDynamicWidget(config: NormalizedVoltraConfig): boolean {
|
|
42
|
+
const widgets = [...(config.android?.widgets ?? []), ...(config.ios?.widgets ?? [])]
|
|
43
|
+
|
|
44
|
+
return widgets.some((widget) => widget.entry !== undefined)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function canResolveVoltraMetro(projectRoot: string): boolean {
|
|
48
|
+
try {
|
|
49
|
+
createRequire(path.join(projectRoot, 'package.json')).resolve('@use-voltra/metro/bundle-widgets')
|
|
50
|
+
return true
|
|
51
|
+
} catch {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -117,6 +117,11 @@ export type { GenerateIOSFilesOptions, GenerateIOSFilesResult } from './platform
|
|
|
117
117
|
export { resolveIOSWidgetTargetName } from './platforms/ios/targetName'
|
|
118
118
|
export { IOSWidgetTargetMutationError, ensureIOSWidgetTarget } from './platforms/ios/xcodeTarget'
|
|
119
119
|
export { applyIOSPlatform, createIOSPreflightRunner } from './platforms/ios/apply'
|
|
120
|
+
export {
|
|
121
|
+
IOSBuildConfigurationValueError,
|
|
122
|
+
VOLTRA_BUILD_SETTING_PREFIX,
|
|
123
|
+
resolveIOSBuildConfigurationValues,
|
|
124
|
+
} from './platforms/ios/buildConfigurationValues'
|
|
120
125
|
export { IOSEntitlementsMutationError, ensureEntitlements } from './platforms/ios/entitlements'
|
|
121
126
|
export type { EnsureEntitlementsOptions, EnsureEntitlementsResult } from './platforms/ios/entitlements'
|
|
122
127
|
export { IOSInfoPlistMutationError, ensureInfoPlist } from './platforms/ios/plist'
|
|
@@ -187,6 +192,8 @@ export type {
|
|
|
187
192
|
NormalizedVoltraAndroidConfig,
|
|
188
193
|
NormalizedVoltraConfig,
|
|
189
194
|
NormalizedVoltraIOSConfig,
|
|
195
|
+
ResolvedIOSProjectConfig,
|
|
196
|
+
ResolvedVoltraIOSConfig,
|
|
190
197
|
VoltraAndroidConfig,
|
|
191
198
|
VoltraConfig,
|
|
192
199
|
VoltraIOSConfig,
|
|
@@ -195,6 +202,7 @@ export type {
|
|
|
195
202
|
WidgetLabel,
|
|
196
203
|
WidgetLocalizedValue,
|
|
197
204
|
} from './config/types'
|
|
205
|
+
export type { PerConfiguration } from './config/perConfiguration'
|
|
198
206
|
export type {
|
|
199
207
|
ApplySummary,
|
|
200
208
|
PreflightFailureReport,
|
|
@@ -4,11 +4,14 @@ import path from 'node:path'
|
|
|
4
4
|
import { createRequire } from 'node:module'
|
|
5
5
|
import { vdConvert } from 'vd-tool'
|
|
6
6
|
|
|
7
|
-
import { requirePlatformPackage } from '../../dependencies/platformPackages'
|
|
7
|
+
import { getPlatformClientPackageVersion, requirePlatformPackage } from '../../dependencies/platformPackages'
|
|
8
8
|
import { ensureDirectory, pathExists, readTextFile, writeTextFile } from '../../fs/readWrite'
|
|
9
9
|
import { normalizeRelativePath, toRelativePath } from '../../fs/path'
|
|
10
10
|
import { VoltraCliError } from '../../reporting/summary'
|
|
11
|
-
import {
|
|
11
|
+
import { createDynamicWidgetBuildInfo, createGeneratedWidgetModuleLoader } from '../shared/widgetModule'
|
|
12
|
+
import { androidWidgetSizingAttributes, androidWidgetSizingWarnings } from './widgetSizing'
|
|
13
|
+
|
|
14
|
+
import type { WidgetModuleLoader } from '@use-voltra/compiler'
|
|
12
15
|
|
|
13
16
|
import type { AndroidProjectDiscovery } from '../../discovery/android'
|
|
14
17
|
import type {
|
|
@@ -27,6 +30,7 @@ const LOCALIZED_INITIAL_STATE_KEY = '__voltraLocales'
|
|
|
27
30
|
const DEFAULT_WIDGET_LOCALE_QUALIFIER = 'en'
|
|
28
31
|
const ANDROID_DYNAMIC_WIDGET_MANIFEST_PATH = path.join('.voltra', 'manifest.android.json')
|
|
29
32
|
const ANDROID_WIDGET_CONFIG_DEFAULTS_PATH = path.join('assets', 'voltra', 'widget_config_defaults.json')
|
|
33
|
+
const ANDROID_WIDGET_SERVER_DEFAULTS_PATH = path.join('assets', 'voltra', 'widget_server_defaults.json')
|
|
30
34
|
|
|
31
35
|
export interface GenerateAndroidFilesOptions {
|
|
32
36
|
projectRoot: string
|
|
@@ -76,11 +80,18 @@ type DetectedAndroidWidget =
|
|
|
76
80
|
|
|
77
81
|
export async function generateAndroidFiles(options: GenerateAndroidFilesOptions): Promise<GenerateAndroidFilesResult> {
|
|
78
82
|
const { projectRoot, android, discovery } = options
|
|
83
|
+
const voltraVersion = getPlatformClientPackageVersion(projectRoot, 'android')
|
|
79
84
|
const resourceRoot = path.join(discovery.appModuleRoot, 'src', 'main')
|
|
80
|
-
const detectedWidgets = detectClientRenderedWidgets(projectRoot, android.widgets)
|
|
81
85
|
const changes: ReportedChange[] = []
|
|
82
86
|
const warnings: string[] = []
|
|
83
87
|
const generatedFiles = new Set<string>()
|
|
88
|
+
const widgetModuleLoader = createGeneratedWidgetModuleLoader(
|
|
89
|
+
projectRoot,
|
|
90
|
+
'android',
|
|
91
|
+
createAndroidGeneratedFilesError,
|
|
92
|
+
(message) => warnings.push(message)
|
|
93
|
+
)
|
|
94
|
+
const detectedWidgets = detectClientRenderedWidgets(projectRoot, widgetModuleLoader, android.widgets)
|
|
84
95
|
|
|
85
96
|
mergeSingleResult(
|
|
86
97
|
await writeGeneratedTextFile(
|
|
@@ -104,7 +115,13 @@ export async function generateAndroidFiles(options: GenerateAndroidFilesOptions)
|
|
|
104
115
|
const fontFiles = await copyAndroidFonts(projectRoot, resourceRoot, android.fonts)
|
|
105
116
|
mergeResult(fontFiles, changes, warnings, generatedFiles)
|
|
106
117
|
|
|
107
|
-
const initialStateFiles = await generateAndroidInitialStates(
|
|
118
|
+
const initialStateFiles = await generateAndroidInitialStates(
|
|
119
|
+
projectRoot,
|
|
120
|
+
widgetModuleLoader,
|
|
121
|
+
resourceRoot,
|
|
122
|
+
detectedWidgets,
|
|
123
|
+
voltraVersion
|
|
124
|
+
)
|
|
108
125
|
mergeResult(initialStateFiles, changes, warnings, generatedFiles)
|
|
109
126
|
|
|
110
127
|
const configDefaultsResult = await generateAndroidConfigDefaults(projectRoot, resourceRoot, detectedWidgets)
|
|
@@ -112,6 +129,11 @@ export async function generateAndroidFiles(options: GenerateAndroidFilesOptions)
|
|
|
112
129
|
mergeSingleResult(configDefaultsResult, changes, generatedFiles)
|
|
113
130
|
}
|
|
114
131
|
|
|
132
|
+
const serverDefaultsResult = await generateAndroidServerDefaults(projectRoot, resourceRoot, detectedWidgets)
|
|
133
|
+
if (serverDefaultsResult) {
|
|
134
|
+
mergeSingleResult(serverDefaultsResult, changes, generatedFiles)
|
|
135
|
+
}
|
|
136
|
+
|
|
115
137
|
return {
|
|
116
138
|
changes,
|
|
117
139
|
files: [...generatedFiles].sort(),
|
|
@@ -216,6 +238,7 @@ async function generateAndroidXmlFiles(
|
|
|
216
238
|
const widgetInfoResult = await writeGeneratedTextFile(projectRoot, widgetInfoPath, widgetInfoContent)
|
|
217
239
|
pushChange(changes, widgetInfoResult.change)
|
|
218
240
|
generatedFiles.add(widgetInfoResult.relativePath)
|
|
241
|
+
warnings.push(...androidWidgetSizingWarnings(widget, widget.id))
|
|
219
242
|
}
|
|
220
243
|
|
|
221
244
|
return {
|
|
@@ -408,19 +431,21 @@ async function copyAndroidFonts(
|
|
|
408
431
|
|
|
409
432
|
async function generateAndroidInitialStates(
|
|
410
433
|
projectRoot: string,
|
|
434
|
+
loader: WidgetModuleLoader,
|
|
411
435
|
resourceRoot: string,
|
|
412
|
-
widgets: DetectedAndroidWidget[]
|
|
436
|
+
widgets: DetectedAndroidWidget[],
|
|
437
|
+
voltraVersion: string
|
|
413
438
|
): Promise<GenerateAndroidFilesResult> {
|
|
414
439
|
const serverWidgets = widgets.filter(
|
|
415
440
|
(widget): widget is Extract<DetectedAndroidWidget, { clientRendered: false }> => !widget.clientRendered
|
|
416
441
|
)
|
|
417
442
|
const prerenderableServerWidgets = serverWidgets.filter((widget) => widget.initialStatePath)
|
|
418
443
|
const serverStates = await prerenderWidgetStates(
|
|
419
|
-
|
|
444
|
+
loader,
|
|
420
445
|
prerenderableServerWidgets,
|
|
421
446
|
loadAndroidWidgetRenderer(projectRoot)
|
|
422
447
|
)
|
|
423
|
-
const clientStates = await prerenderClientRenderedAndroidWidgets(projectRoot, widgets)
|
|
448
|
+
const clientStates = await prerenderClientRenderedAndroidWidgets(projectRoot, loader, widgets, voltraVersion)
|
|
424
449
|
const prerenderedStates = new Map([...serverStates, ...clientStates])
|
|
425
450
|
|
|
426
451
|
if (prerenderedStates.size === 0) {
|
|
@@ -443,7 +468,7 @@ async function generateAndroidInitialStates(
|
|
|
443
468
|
}
|
|
444
469
|
|
|
445
470
|
async function prerenderWidgetStates(
|
|
446
|
-
|
|
471
|
+
loader: WidgetModuleLoader,
|
|
447
472
|
widgets: NormalizedAndroidWidgetConfig[],
|
|
448
473
|
renderer: AndroidWidgetRenderer
|
|
449
474
|
): Promise<PrerenderedWidgetStates> {
|
|
@@ -468,7 +493,7 @@ async function prerenderWidgetStates(
|
|
|
468
493
|
throw new AndroidGeneratedFilesError(`Initial state file not found for widget '${widget.id}' at ${modulePath}`)
|
|
469
494
|
}
|
|
470
495
|
|
|
471
|
-
const widgetVariants = evaluateLegacyWidgetModule(
|
|
496
|
+
const widgetVariants = evaluateLegacyWidgetModule(loader, modulePath)
|
|
472
497
|
localeStates.set(localeKey, renderer(widgetVariants))
|
|
473
498
|
}
|
|
474
499
|
|
|
@@ -478,11 +503,8 @@ async function prerenderWidgetStates(
|
|
|
478
503
|
return prerenderedStates
|
|
479
504
|
}
|
|
480
505
|
|
|
481
|
-
function evaluateLegacyWidgetModule(
|
|
482
|
-
const
|
|
483
|
-
default?: unknown
|
|
484
|
-
}
|
|
485
|
-
const widgetVariants = exports.default ?? exports
|
|
506
|
+
function evaluateLegacyWidgetModule(loader: WidgetModuleLoader, filePath: string): AndroidWidgetVariants {
|
|
507
|
+
const widgetVariants = loader.loadDefaultExport(filePath)
|
|
486
508
|
|
|
487
509
|
if (!widgetVariants || typeof widgetVariants !== 'object') {
|
|
488
510
|
throw new AndroidGeneratedFilesError(`Widget file must export widget variants: ${filePath}`)
|
|
@@ -530,12 +552,17 @@ function createDynamicWidgetsManifest(widgets: DetectedAndroidWidget[]): Dynamic
|
|
|
530
552
|
|
|
531
553
|
function detectClientRenderedWidgets(
|
|
532
554
|
projectRoot: string,
|
|
555
|
+
loader: WidgetModuleLoader,
|
|
533
556
|
widgets: NormalizedAndroidWidgetConfig[]
|
|
534
557
|
): DetectedAndroidWidget[] {
|
|
535
|
-
return widgets.map((widget) => detectSingleWidget(projectRoot, widget))
|
|
558
|
+
return widgets.map((widget) => detectSingleWidget(projectRoot, loader, widget))
|
|
536
559
|
}
|
|
537
560
|
|
|
538
|
-
function detectSingleWidget(
|
|
561
|
+
function detectSingleWidget(
|
|
562
|
+
projectRoot: string,
|
|
563
|
+
loader: WidgetModuleLoader,
|
|
564
|
+
widget: NormalizedAndroidWidgetConfig
|
|
565
|
+
): DetectedAndroidWidget {
|
|
539
566
|
if (!widget.entry) {
|
|
540
567
|
return {
|
|
541
568
|
...widget,
|
|
@@ -549,7 +576,7 @@ function detectSingleWidget(projectRoot: string, widget: NormalizedAndroidWidget
|
|
|
549
576
|
throw createAndroidGeneratedFilesError(`[voltra] Dynamic Widget "${widget.id}" entry not found at ${widget.entry}`)
|
|
550
577
|
}
|
|
551
578
|
|
|
552
|
-
const widgetModule = safelyEvaluateDynamicWidget(
|
|
579
|
+
const widgetModule = safelyEvaluateDynamicWidget(loader, widget.id, widget.entry, clientSourcePath)
|
|
553
580
|
const widgetFn = readDynamicWidgetExport(widget.id, widget.entry, widgetModule)
|
|
554
581
|
|
|
555
582
|
if (typeof widgetFn !== 'function') {
|
|
@@ -568,7 +595,9 @@ function detectSingleWidget(projectRoot: string, widget: NormalizedAndroidWidget
|
|
|
568
595
|
|
|
569
596
|
async function prerenderClientRenderedAndroidWidgets(
|
|
570
597
|
projectRoot: string,
|
|
571
|
-
|
|
598
|
+
loader: WidgetModuleLoader,
|
|
599
|
+
widgets: DetectedAndroidWidget[],
|
|
600
|
+
voltraVersion: string
|
|
572
601
|
): Promise<PrerenderedWidgetStates> {
|
|
573
602
|
const clientWidgets = widgets.filter(
|
|
574
603
|
(widget): widget is Extract<DetectedAndroidWidget, { clientRendered: true }> => widget.clientRendered
|
|
@@ -585,13 +614,13 @@ async function prerenderClientRenderedAndroidWidgets(
|
|
|
585
614
|
colorScheme: 'light',
|
|
586
615
|
locale: 'en-US',
|
|
587
616
|
configuration: undefined,
|
|
588
|
-
build:
|
|
617
|
+
build: createDynamicWidgetBuildInfo(voltraVersion),
|
|
589
618
|
}
|
|
590
619
|
const prerenderedStates: PrerenderedWidgetStates = new Map()
|
|
591
620
|
|
|
592
621
|
for (const widget of clientWidgets) {
|
|
593
622
|
try {
|
|
594
|
-
const widgetModule = safelyEvaluateDynamicWidget(
|
|
623
|
+
const widgetModule = safelyEvaluateDynamicWidget(loader, widget.id, widget.entry, widget.clientSourcePath)
|
|
595
624
|
const widgetFn = readDynamicWidgetExport(widget.id, widget.entry, widgetModule)
|
|
596
625
|
const element = widgetFn({}, placeholderEnv)
|
|
597
626
|
prerenderedStates.set(widget.id, new Map([[DEFAULT_INITIAL_STATE_LOCALE, JSON.stringify(renderer(element))]]))
|
|
@@ -612,13 +641,13 @@ async function prerenderClientRenderedAndroidWidgets(
|
|
|
612
641
|
}
|
|
613
642
|
|
|
614
643
|
function safelyEvaluateDynamicWidget(
|
|
615
|
-
|
|
644
|
+
loader: WidgetModuleLoader,
|
|
616
645
|
widgetId: string,
|
|
617
646
|
widgetEntry: string,
|
|
618
647
|
clientSourcePath: string
|
|
619
648
|
): unknown {
|
|
620
649
|
try {
|
|
621
|
-
return
|
|
650
|
+
return loader.load(clientSourcePath)
|
|
622
651
|
} catch (error) {
|
|
623
652
|
if (error instanceof AndroidGeneratedFilesError) {
|
|
624
653
|
throw error
|
|
@@ -670,6 +699,59 @@ async function generateAndroidConfigDefaults(
|
|
|
670
699
|
)
|
|
671
700
|
}
|
|
672
701
|
|
|
702
|
+
/**
|
|
703
|
+
* Emits the build-time `serverUpdate` defaults the runtime settings resolver reads as its lowest
|
|
704
|
+
* layer.
|
|
705
|
+
*
|
|
706
|
+
* These used to be Kotlin literals inside each generated receiver. They live in an asset now
|
|
707
|
+
* because the app can override the URL and the interval with `setWidgetServerUpdate`, and a
|
|
708
|
+
* receiver compiled at build time cannot be asked what the interval is today.
|
|
709
|
+
*
|
|
710
|
+
* A widget id present in this file is server-driven. `url` is omitted when app.json declared
|
|
711
|
+
* `serverUpdate` without one, which means the app supplies it at runtime.
|
|
712
|
+
*/
|
|
713
|
+
async function generateAndroidServerDefaults(
|
|
714
|
+
projectRoot: string,
|
|
715
|
+
resourceRoot: string,
|
|
716
|
+
widgets: DetectedAndroidWidget[]
|
|
717
|
+
): Promise<GeneratedFileResult | undefined> {
|
|
718
|
+
const defaults = createWidgetServerDefaults(widgets)
|
|
719
|
+
|
|
720
|
+
if (Object.keys(defaults).length === 0) {
|
|
721
|
+
return undefined
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return writeGeneratedTextFile(
|
|
725
|
+
projectRoot,
|
|
726
|
+
path.join(resourceRoot, ANDROID_WIDGET_SERVER_DEFAULTS_PATH),
|
|
727
|
+
`${JSON.stringify(defaults, null, 2)}\n`
|
|
728
|
+
)
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
interface WidgetServerDefaults {
|
|
732
|
+
url?: string
|
|
733
|
+
intervalMinutes: number
|
|
734
|
+
refresh: boolean
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function createWidgetServerDefaults(widgets: DetectedAndroidWidget[]): Record<string, WidgetServerDefaults> {
|
|
738
|
+
const defaults: Record<string, WidgetServerDefaults> = {}
|
|
739
|
+
|
|
740
|
+
for (const widget of widgets) {
|
|
741
|
+
if (!widget.serverUpdate) {
|
|
742
|
+
continue
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
defaults[widget.id] = {
|
|
746
|
+
...(widget.serverUpdate.url !== undefined ? { url: widget.serverUpdate.url } : {}),
|
|
747
|
+
intervalMinutes: widget.serverUpdate.intervalMinutes,
|
|
748
|
+
refresh: widget.serverUpdate.refresh,
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
return defaults
|
|
753
|
+
}
|
|
754
|
+
|
|
673
755
|
function createWidgetConfigDefaults(widgets: DetectedAndroidWidget[]): WidgetConfigDefaults {
|
|
674
756
|
const defaults: WidgetConfigDefaults = {}
|
|
675
757
|
|
|
@@ -916,15 +998,41 @@ async function getLargeImageWarning(imagePath: string, fileName: string): Promis
|
|
|
916
998
|
return `Image '${fileName}' is ${stat.size} bytes. Large Android widget images may not display correctly.`
|
|
917
999
|
}
|
|
918
1000
|
|
|
1001
|
+
/**
|
|
1002
|
+
* `entry` picks the render engine and `serverUpdate` picks where the data comes from, so the two
|
|
1003
|
+
* keys together select one of four base classes. Runtime code never asks "is this server-driven?" —
|
|
1004
|
+
* the answer is baked in here, once, at generate time.
|
|
1005
|
+
*/
|
|
919
1006
|
function generateWidgetReceiverContent(widget: DetectedAndroidWidget, packageName: string): string {
|
|
920
1007
|
const className = `VoltraWidget_${widget.id}Receiver`
|
|
921
1008
|
const labelForComment = widgetLabelEnglish(widget.displayName)
|
|
922
1009
|
|
|
1010
|
+
// A widget with an entry and a serverUpdate renders bundled JS from props fetched in the
|
|
1011
|
+
// background. The scheduling lives in the base class, so the generated receiver stays a name and
|
|
1012
|
+
// an id — the URL and the interval come from widget_server_defaults.json at runtime, where
|
|
1013
|
+
// setWidgetServerUpdate can override them.
|
|
1014
|
+
if (widget.clientRendered && widget.serverUpdate) {
|
|
1015
|
+
return [
|
|
1016
|
+
`package ${packageName}.widget`,
|
|
1017
|
+
'',
|
|
1018
|
+
'import voltra.dynamicwidget.serverupdate.VoltraServerDrivenClientWidgetReceiver',
|
|
1019
|
+
'',
|
|
1020
|
+
'/**',
|
|
1021
|
+
` * Auto-generated server-driven Dynamic Widget receiver for ${labelForComment}`,
|
|
1022
|
+
` * Widget ID: ${widget.id}`,
|
|
1023
|
+
' */',
|
|
1024
|
+
`class ${className} : VoltraServerDrivenClientWidgetReceiver() {`,
|
|
1025
|
+
` override val widgetId: String = "${widget.id}"`,
|
|
1026
|
+
'}',
|
|
1027
|
+
'',
|
|
1028
|
+
].join('\n')
|
|
1029
|
+
}
|
|
1030
|
+
|
|
923
1031
|
if (widget.clientRendered) {
|
|
924
1032
|
return [
|
|
925
1033
|
`package ${packageName}.widget`,
|
|
926
1034
|
'',
|
|
927
|
-
'import voltra.
|
|
1035
|
+
'import voltra.dynamicwidget.VoltraClientWidgetReceiver',
|
|
928
1036
|
'',
|
|
929
1037
|
'/**',
|
|
930
1038
|
` * Auto-generated Dynamic Widget receiver for ${labelForComment}`,
|
|
@@ -938,35 +1046,30 @@ function generateWidgetReceiverContent(widget: DetectedAndroidWidget, packageNam
|
|
|
938
1046
|
}
|
|
939
1047
|
|
|
940
1048
|
if (widget.serverUpdate) {
|
|
941
|
-
const refreshEnabled = widget.serverUpdate.refresh === true
|
|
942
|
-
|
|
943
1049
|
return [
|
|
944
1050
|
`package ${packageName}.widget`,
|
|
945
1051
|
'',
|
|
946
1052
|
'import android.appwidget.AppWidgetManager',
|
|
947
1053
|
'import android.content.Context',
|
|
948
|
-
'import
|
|
949
|
-
'import voltra.widget.
|
|
1054
|
+
'import kotlinx.coroutines.runBlocking',
|
|
1055
|
+
'import voltra.widget.payload.VoltraPayloadWidgetReceiver',
|
|
1056
|
+
'import voltra.widget.payload.VoltraWidgetUpdateScheduler',
|
|
950
1057
|
'',
|
|
951
1058
|
'/**',
|
|
952
1059
|
` * Auto-generated widget receiver for ${labelForComment}`,
|
|
953
1060
|
` * Widget ID: ${widget.id}`,
|
|
954
|
-
` * Server Update: ${widget.serverUpdate.url} (every ${widget.serverUpdate.intervalMinutes} minutes)`,
|
|
955
|
-
` * Refresh Button: ${String(refreshEnabled)}`,
|
|
956
1061
|
' */',
|
|
957
|
-
`class ${className} :
|
|
1062
|
+
`class ${className} : VoltraPayloadWidgetReceiver() {`,
|
|
958
1063
|
` override val widgetId: String = "${widget.id}"`,
|
|
959
1064
|
'',
|
|
960
1065
|
' override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {',
|
|
961
1066
|
' super.onUpdate(context, appWidgetManager, appWidgetIds)',
|
|
962
1067
|
'',
|
|
963
|
-
'
|
|
964
|
-
'
|
|
965
|
-
|
|
966
|
-
`
|
|
967
|
-
|
|
968
|
-
` refreshEnabled = ${String(refreshEnabled)}`,
|
|
969
|
-
' )',
|
|
1068
|
+
' // Blocking rather than launching: onReceive must not return before the work is',
|
|
1069
|
+
' // enqueued, or a widget added while the app is not running loses its schedule.',
|
|
1070
|
+
' runBlocking {',
|
|
1071
|
+
` VoltraWidgetUpdateScheduler.schedulePeriodicUpdate(context.applicationContext, "${widget.id}")`,
|
|
1072
|
+
' }',
|
|
970
1073
|
' }',
|
|
971
1074
|
'',
|
|
972
1075
|
' override fun onDeleted(context: Context, appWidgetIds: IntArray) {',
|
|
@@ -992,13 +1095,13 @@ function generateWidgetReceiverContent(widget: DetectedAndroidWidget, packageNam
|
|
|
992
1095
|
return [
|
|
993
1096
|
`package ${packageName}.widget`,
|
|
994
1097
|
'',
|
|
995
|
-
'import voltra.widget.
|
|
1098
|
+
'import voltra.widget.payload.VoltraPayloadWidgetReceiver',
|
|
996
1099
|
'',
|
|
997
1100
|
'/**',
|
|
998
1101
|
` * Auto-generated widget receiver for ${labelForComment}`,
|
|
999
1102
|
` * Widget ID: ${widget.id}`,
|
|
1000
1103
|
' */',
|
|
1001
|
-
`class ${className} :
|
|
1104
|
+
`class ${className} : VoltraPayloadWidgetReceiver() {`,
|
|
1002
1105
|
` override val widgetId: String = "${widget.id}"`,
|
|
1003
1106
|
'}',
|
|
1004
1107
|
'',
|
|
@@ -1010,18 +1113,12 @@ function generateWidgetInfoXml(
|
|
|
1010
1113
|
previewImageResourceName?: string,
|
|
1011
1114
|
previewLayoutResourceName?: string
|
|
1012
1115
|
): string {
|
|
1013
|
-
const minWidth = widget.minWidth ?? (widget.minCellWidth !== undefined ? widget.minCellWidth * 70 - 30 : undefined)
|
|
1014
|
-
const minHeight =
|
|
1015
|
-
widget.minHeight ?? (widget.minCellHeight !== undefined ? widget.minCellHeight * 70 - 30 : undefined)
|
|
1016
1116
|
const resizeMode = widget.resizeMode ?? 'horizontal|vertical'
|
|
1017
1117
|
const widgetCategory = widget.widgetCategory ?? 'home_screen'
|
|
1018
1118
|
const lines = [
|
|
1019
1119
|
'<?xml version="1.0" encoding="utf-8"?>',
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
}${minHeight !== undefined ? ` android:minHeight="${minHeight}dp"` : ''}`,
|
|
1023
|
-
` android:targetCellWidth="${widget.targetCellWidth}"`,
|
|
1024
|
-
` android:targetCellHeight="${widget.targetCellHeight}"`,
|
|
1120
|
+
'<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"',
|
|
1121
|
+
...androidWidgetSizingAttributes(widget).map((attribute) => ` ${attribute}`),
|
|
1025
1122
|
' android:updatePeriodMillis="0"',
|
|
1026
1123
|
' android:initialLayout="@layout/voltra_widget_placeholder"',
|
|
1027
1124
|
` android:resizeMode="${resizeMode}"`,
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Android widget sizing.
|
|
3
|
+
*
|
|
4
|
+
* Android 12+ (API 31+) places widgets by `targetCellWidth`/`targetCellHeight`, expressed in
|
|
5
|
+
* launcher grid cells. Android 11 and older ignore those attributes entirely and place widgets by
|
|
6
|
+
* `minWidth`/`minHeight`, expressed in dp, so a widget has to declare both sets to be placed
|
|
7
|
+
* correctly everywhere. Voltra widgets build against the host app's `minSdkVersion` — React
|
|
8
|
+
* Native's template sets 24 — so the pre-Android-12 path is not a legacy edge case.
|
|
9
|
+
* https://developer.android.com/develop/ui/compose/glance/create-app-widget
|
|
10
|
+
*
|
|
11
|
+
* Cell size varies by device, launcher and orientation, so converting cells to dp can only ever be
|
|
12
|
+
* an approximation. These are the figures Google publishes for a 5x4 handset grid, following the
|
|
13
|
+
* accompanying guidance to size widths from the portrait table and heights from the landscape
|
|
14
|
+
* table, because those are the smaller cells on each axis.
|
|
15
|
+
* https://developer.android.com/develop/ui/views/appwidgets/layouts
|
|
16
|
+
*
|
|
17
|
+
* The `voltra` CLI and the `@use-voltra/android-client` Expo plugin generate the same
|
|
18
|
+
* `appwidget-provider` XML from their own copy of this module; keep the two in sync.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Widget fields that determine the generated `appwidget-provider` sizing attributes. */
|
|
22
|
+
export interface AndroidWidgetSizingInput {
|
|
23
|
+
targetCellWidth: number
|
|
24
|
+
targetCellHeight: number
|
|
25
|
+
minWidth?: number
|
|
26
|
+
minHeight?: number
|
|
27
|
+
minCellWidth?: number
|
|
28
|
+
minCellHeight?: number
|
|
29
|
+
minResizeWidth?: number
|
|
30
|
+
minResizeHeight?: number
|
|
31
|
+
maxResizeWidth?: number
|
|
32
|
+
maxResizeHeight?: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Resize bounds, in dp, emitted only when set. `maxResize*` is honoured from Android 12 on. */
|
|
36
|
+
const RESIZE_BOUNDS = ['minResizeWidth', 'minResizeHeight', 'maxResizeWidth', 'maxResizeHeight'] as const
|
|
37
|
+
|
|
38
|
+
function cellsToWidthDp(cells: number): number {
|
|
39
|
+
return cells * 73 - 16
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function cellsToHeightDp(cells: number): number {
|
|
43
|
+
return cells * 66 - 15
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveMinimumSize(widget: AndroidWidgetSizingInput): { minWidth: number; minHeight: number } {
|
|
47
|
+
return {
|
|
48
|
+
minWidth: widget.minWidth ?? cellsToWidthDp(widget.minCellWidth ?? widget.targetCellWidth),
|
|
49
|
+
minHeight: widget.minHeight ?? cellsToHeightDp(widget.minCellHeight ?? widget.targetCellHeight),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Sizing attributes for a single widget, ordered as in Android's own reference example.
|
|
55
|
+
*
|
|
56
|
+
* `minWidth` and `minHeight` are always emitted, so a widget is placed at its intended size on
|
|
57
|
+
* Android 11 and older: an explicit dp value wins, then the deprecated cell count, then the
|
|
58
|
+
* target cell count.
|
|
59
|
+
*/
|
|
60
|
+
export function androidWidgetSizingAttributes(widget: AndroidWidgetSizingInput): string[] {
|
|
61
|
+
const { minWidth, minHeight } = resolveMinimumSize(widget)
|
|
62
|
+
|
|
63
|
+
const attributes = [
|
|
64
|
+
`android:minWidth="${minWidth}dp"`,
|
|
65
|
+
`android:minHeight="${minHeight}dp"`,
|
|
66
|
+
`android:targetCellWidth="${widget.targetCellWidth}"`,
|
|
67
|
+
`android:targetCellHeight="${widget.targetCellHeight}"`,
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
for (const bound of RESIZE_BOUNDS) {
|
|
71
|
+
const value = widget[bound]
|
|
72
|
+
if (value !== undefined) {
|
|
73
|
+
attributes.push(`android:${bound}="${value}dp"`)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return attributes
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resize bounds Android will silently ignore, described for the developer who set them.
|
|
82
|
+
*
|
|
83
|
+
* `minResizeWidth` has no effect when it is greater than `minWidth`, and `maxResizeWidth` has no
|
|
84
|
+
* effect when it is smaller; likewise for the height pair. This reports rather than throws,
|
|
85
|
+
* because `minWidth` is usually the approximation derived from the widget's cell count.
|
|
86
|
+
* https://developer.android.com/reference/android/appwidget/AppWidgetProviderInfo
|
|
87
|
+
*/
|
|
88
|
+
export function androidWidgetSizingWarnings(widget: AndroidWidgetSizingInput, widgetId: string): string[] {
|
|
89
|
+
const { minWidth, minHeight } = resolveMinimumSize(widget)
|
|
90
|
+
const warnings: string[] = []
|
|
91
|
+
|
|
92
|
+
const report = (bound: string, value: number, relation: string, minimum: string, minimumValue: number): void => {
|
|
93
|
+
warnings.push(
|
|
94
|
+
`Widget '${widgetId}': ${bound} (${value}dp) is ${relation} ${minimum} (${minimumValue}dp), so Android ignores it.`
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (widget.minResizeWidth !== undefined && widget.minResizeWidth > minWidth) {
|
|
99
|
+
report('minResizeWidth', widget.minResizeWidth, 'greater than', 'minWidth', minWidth)
|
|
100
|
+
}
|
|
101
|
+
if (widget.minResizeHeight !== undefined && widget.minResizeHeight > minHeight) {
|
|
102
|
+
report('minResizeHeight', widget.minResizeHeight, 'greater than', 'minHeight', minHeight)
|
|
103
|
+
}
|
|
104
|
+
if (widget.maxResizeWidth !== undefined && widget.maxResizeWidth < minWidth) {
|
|
105
|
+
report('maxResizeWidth', widget.maxResizeWidth, 'smaller than', 'minWidth', minWidth)
|
|
106
|
+
}
|
|
107
|
+
if (widget.maxResizeHeight !== undefined && widget.maxResizeHeight < minHeight) {
|
|
108
|
+
report('maxResizeHeight', widget.maxResizeHeight, 'smaller than', 'minHeight', minHeight)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return warnings
|
|
112
|
+
}
|
|
@@ -2,6 +2,7 @@ import { discoverIOSProject } from '../../discovery/ios'
|
|
|
2
2
|
import { getMissingPlatformPackageMessage, getMissingPlatformPackages } from '../../dependencies/platformPackages'
|
|
3
3
|
import { VoltraCliError } from '../../reporting/summary'
|
|
4
4
|
|
|
5
|
+
import { resolveIOSBuildConfigurationValues } from './buildConfigurationValues'
|
|
5
6
|
import { ensureEntitlements } from './entitlements'
|
|
6
7
|
import { generateIOSFiles } from './generated'
|
|
7
8
|
import { ensureInfoPlist } from './plist'
|
|
@@ -52,32 +53,36 @@ export async function applyIOSPlatform(context: PlatformApplyContext): Promise<P
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
const discovery = getIOSDiscovery(context.preflight)
|
|
56
|
+
// Values given per build configuration are collapsed once, so every mutator below sees a plain
|
|
57
|
+
// string and the project carries the per-configuration values.
|
|
58
|
+
const { ios, buildSettings } = resolveIOSBuildConfigurationValues(iosConfig, discovery)
|
|
55
59
|
const generatedResult = await generateIOSFiles({
|
|
56
60
|
projectRoot: context.config.projectRoot,
|
|
57
|
-
ios
|
|
61
|
+
ios,
|
|
58
62
|
discovery,
|
|
59
63
|
})
|
|
60
64
|
const entitlementsResult = await ensureEntitlements({
|
|
61
65
|
projectRoot: context.config.projectRoot,
|
|
62
|
-
ios
|
|
66
|
+
ios,
|
|
63
67
|
discovery,
|
|
64
68
|
})
|
|
65
69
|
const infoPlistResult = await ensureInfoPlist({
|
|
66
70
|
projectRoot: context.config.projectRoot,
|
|
67
|
-
ios
|
|
71
|
+
ios,
|
|
68
72
|
discovery,
|
|
69
73
|
})
|
|
70
74
|
const podfileResult = await ensurePodfileBlock({
|
|
71
75
|
projectRoot: context.config.projectRoot,
|
|
72
|
-
ios
|
|
76
|
+
ios,
|
|
73
77
|
discovery,
|
|
74
78
|
})
|
|
75
79
|
const xcodeTargetResult = await ensureIOSWidgetTarget({
|
|
76
80
|
projectRoot: context.config.projectRoot,
|
|
77
|
-
ios
|
|
81
|
+
ios,
|
|
78
82
|
discovery,
|
|
79
83
|
generatedFiles: generatedResult.files,
|
|
80
84
|
previousGeneratedFiles: context.previousState?.files,
|
|
85
|
+
buildSettings,
|
|
81
86
|
})
|
|
82
87
|
|
|
83
88
|
if (generatedResult.targetName !== xcodeTargetResult.targetName) {
|
|
@@ -93,11 +98,10 @@ export async function applyIOSPlatform(context: PlatformApplyContext): Promise<P
|
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
const changes = [
|
|
96
|
-
infoPlistResult.
|
|
97
|
-
entitlementsResult.
|
|
98
|
-
podfileResult.change,
|
|
99
|
-
|
|
100
|
-
].filter(isDefined)
|
|
101
|
+
...infoPlistResult.changes,
|
|
102
|
+
...entitlementsResult.changes,
|
|
103
|
+
...[podfileResult.change, xcodeTargetResult.change].filter(isDefined),
|
|
104
|
+
]
|
|
101
105
|
|
|
102
106
|
return {
|
|
103
107
|
platform: 'ios',
|
|
@@ -130,7 +134,13 @@ function isIOSProjectDiscovery(value: unknown): value is IOSProjectDiscovery {
|
|
|
130
134
|
candidate.podfilePath,
|
|
131
135
|
candidate.mainTargetName,
|
|
132
136
|
candidate.infoPlistPath,
|
|
133
|
-
].every((entry) => typeof entry === 'string' && entry.length > 0) &&
|
|
137
|
+
].every((entry) => typeof entry === 'string' && entry.length > 0) &&
|
|
138
|
+
[
|
|
139
|
+
candidate.mainTargetCandidates,
|
|
140
|
+
candidate.buildConfigurationNames,
|
|
141
|
+
candidate.infoPlistPaths,
|
|
142
|
+
candidate.entitlementsPaths,
|
|
143
|
+
].every((entry) => Array.isArray(entry))
|
|
134
144
|
)
|
|
135
145
|
}
|
|
136
146
|
|