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
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config rules for `serverUpdate`, shared by the Expo plugins and the `voltra` CLI.
|
|
3
|
+
*
|
|
4
|
+
* `serverUpdate` marks a widget as server-driven for both render engines. On a widget with
|
|
5
|
+
* `entry` the device fetches a JSON object and hands it to the bundled JS as props; without
|
|
6
|
+
* `entry` the server returns a full Voltra payload. Widgets without `entry` keep exactly the
|
|
7
|
+
* rules they had before ADR 0002, so no existing config breaks.
|
|
8
|
+
*
|
|
9
|
+
* `url` is optional. `serverUpdate: {}` means "server-driven, URL supplied at runtime" through
|
|
10
|
+
* `setWidgetServerUpdate`, which covers per-tenant backends whose URL is only known after login.
|
|
11
|
+
*
|
|
12
|
+
* These helpers report problems instead of throwing so each caller can raise its own error type.
|
|
13
|
+
*
|
|
14
|
+
* The `@use-voltra/expo-plugin` package holds the other copy of this module, shared by the
|
|
15
|
+
* Expo config plugins; keep the two in sync.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Interval floor and default for a widget with `entry`. WorkManager cannot run periodic work
|
|
20
|
+
* more often than every 15 minutes, and WidgetKit stretches timelines requested closer together
|
|
21
|
+
* than five minutes, so a smaller number would only mislead.
|
|
22
|
+
*/
|
|
23
|
+
export const DYNAMIC_WIDGET_SERVER_UPDATE_INTERVAL_MINUTES = 15
|
|
24
|
+
|
|
25
|
+
/** Hosts allowed over plain `http`, for talking to a dev server from a simulator or emulator. */
|
|
26
|
+
const LOCAL_HTTP_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '10.0.2.2', '10.0.3.2'])
|
|
27
|
+
|
|
28
|
+
/** Outcome of resolving `serverUpdate.intervalMinutes` against a platform's floor. */
|
|
29
|
+
export type ServerUpdateIntervalResolution =
|
|
30
|
+
| { kind: 'ok'; intervalMinutes: number }
|
|
31
|
+
| { kind: 'clamped'; intervalMinutes: number; warning: string }
|
|
32
|
+
| { kind: 'invalid'; error: string }
|
|
33
|
+
|
|
34
|
+
export interface ResolveServerUpdateIntervalOptions {
|
|
35
|
+
/** Raw `intervalMinutes` from app.json, if the widget set one. */
|
|
36
|
+
intervalMinutes: unknown
|
|
37
|
+
/** Config path used in messages, e.g. `android.widgets[portfolio].serverUpdate`. */
|
|
38
|
+
context: string
|
|
39
|
+
/** True when the widget has an `entry` and therefore renders on device. */
|
|
40
|
+
hasEntry: boolean
|
|
41
|
+
/** Interval used when the widget does not set one. Ignored for widgets with `entry`. */
|
|
42
|
+
defaultIntervalMinutes: number
|
|
43
|
+
/** Platform floor for payload widgets. Ignored for widgets with `entry`. */
|
|
44
|
+
minimumIntervalMinutes: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolves the interval a widget should be scheduled on.
|
|
49
|
+
*
|
|
50
|
+
* A widget with `entry` is clamped up to {@link DYNAMIC_WIDGET_SERVER_UPDATE_INTERVAL_MINUTES}
|
|
51
|
+
* with a warning, because a shorter interval is not something either platform can honour. A
|
|
52
|
+
* payload widget keeps the platform's existing rule and is rejected below the floor.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveServerUpdateInterval(
|
|
55
|
+
options: ResolveServerUpdateIntervalOptions
|
|
56
|
+
): ServerUpdateIntervalResolution {
|
|
57
|
+
const { intervalMinutes, context, hasEntry, defaultIntervalMinutes, minimumIntervalMinutes } = options
|
|
58
|
+
const fallback = hasEntry ? DYNAMIC_WIDGET_SERVER_UPDATE_INTERVAL_MINUTES : defaultIntervalMinutes
|
|
59
|
+
|
|
60
|
+
if (intervalMinutes === undefined) {
|
|
61
|
+
return { kind: 'ok', intervalMinutes: fallback }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (typeof intervalMinutes !== 'number' || !Number.isFinite(intervalMinutes)) {
|
|
65
|
+
return { kind: 'invalid', error: `${context}.intervalMinutes must be a number` }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!Number.isInteger(intervalMinutes)) {
|
|
69
|
+
return { kind: 'invalid', error: `${context}.intervalMinutes must be an integer` }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (hasEntry) {
|
|
73
|
+
if (intervalMinutes < DYNAMIC_WIDGET_SERVER_UPDATE_INTERVAL_MINUTES) {
|
|
74
|
+
return {
|
|
75
|
+
kind: 'clamped',
|
|
76
|
+
intervalMinutes: DYNAMIC_WIDGET_SERVER_UPDATE_INTERVAL_MINUTES,
|
|
77
|
+
warning:
|
|
78
|
+
`${context}.intervalMinutes is ${intervalMinutes}, below the ` +
|
|
79
|
+
`${DYNAMIC_WIDGET_SERVER_UPDATE_INTERVAL_MINUTES} minute floor for widgets with an entry. ` +
|
|
80
|
+
`Using ${DYNAMIC_WIDGET_SERVER_UPDATE_INTERVAL_MINUTES}.`,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { kind: 'ok', intervalMinutes }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (intervalMinutes < minimumIntervalMinutes) {
|
|
88
|
+
return { kind: 'invalid', error: `${context}.intervalMinutes must be at least ${minimumIntervalMinutes}` }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { kind: 'ok', intervalMinutes }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Outcome of checking `serverUpdate.url`. */
|
|
95
|
+
export type ServerUpdateUrlResolution =
|
|
96
|
+
| { kind: 'ok' }
|
|
97
|
+
| { kind: 'insecure'; warning: string }
|
|
98
|
+
| { kind: 'invalid'; error: string }
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Checks `serverUpdate.url`. An absent URL is fine — the app supplies it at runtime through
|
|
102
|
+
* `setWidgetServerUpdate`.
|
|
103
|
+
*
|
|
104
|
+
* A URL that is not an absolute `http`/`https` URL is rejected: no platform HTTP stack here
|
|
105
|
+
* accepts one, so such a config could only ever have failed to fetch. Plain `http` to a
|
|
106
|
+
* non-local host is reported as insecure rather than rejected — App Transport Security and
|
|
107
|
+
* Android's cleartext policy already block it in a release build, and rejecting it outright
|
|
108
|
+
* would break configs that point at a LAN dev server.
|
|
109
|
+
*/
|
|
110
|
+
export function resolveServerUpdateUrl(url: unknown, context: string): ServerUpdateUrlResolution {
|
|
111
|
+
if (url === undefined) {
|
|
112
|
+
return { kind: 'ok' }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (typeof url !== 'string' || !url.trim()) {
|
|
116
|
+
return { kind: 'invalid', error: `${context}.url must be a non-empty string` }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let parsed: URL
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
parsed = new URL(url)
|
|
123
|
+
} catch {
|
|
124
|
+
return { kind: 'invalid', error: `${context}.url must be an absolute http(s) URL, received '${url}'` }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (parsed.protocol === 'https:') {
|
|
128
|
+
return { kind: 'ok' }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (parsed.protocol !== 'http:') {
|
|
132
|
+
return { kind: 'invalid', error: `${context}.url must be an absolute http(s) URL, received '${url}'` }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (isLocalHttpHost(parsed.hostname)) {
|
|
136
|
+
return { kind: 'ok' }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
kind: 'insecure',
|
|
141
|
+
warning:
|
|
142
|
+
`${context}.url uses plain http ('${url}'). Release builds block cleartext traffic, so the ` +
|
|
143
|
+
'widget will not fetch outside a development build. Use https, or a local dev host ' +
|
|
144
|
+
`(${[...LOCAL_HTTP_HOSTS].join(', ')}).`,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** True for the hosts Voltra allows over plain `http` — dev servers reachable from a simulator. */
|
|
149
|
+
export function isLocalHttpHost(hostname: string): boolean {
|
|
150
|
+
return LOCAL_HTTP_HOSTS.has(hostname.replace(/^\[|\]$/g, ''))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Validates `serverUpdate.refresh`. Returns an error message, or `undefined` when it is fine. */
|
|
154
|
+
export function validateServerUpdateRefresh(refresh: unknown, context: string): string | undefined {
|
|
155
|
+
if (refresh !== undefined && typeof refresh !== 'boolean') {
|
|
156
|
+
return `${context}.refresh must be a boolean`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return undefined
|
|
160
|
+
}
|
package/src/config/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CLI_DEFAULTS } from './defaults'
|
|
2
|
+
import type { PerConfiguration } from './perConfiguration'
|
|
2
3
|
|
|
3
4
|
export type VoltraPlatform = 'android' | 'ios'
|
|
4
5
|
|
|
@@ -15,8 +16,11 @@ export type WidgetLabel = string | WidgetLocalizedValue
|
|
|
15
16
|
export type WidgetInitialStatePath = string | WidgetLocalizedValue
|
|
16
17
|
|
|
17
18
|
export interface AndroidWidgetServerUpdateConfig {
|
|
18
|
-
/**
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Server endpoint that returns widget state updates. Optional — omit it to mark the widget
|
|
21
|
+
* server-driven and supply the URL at runtime with `setWidgetServerUpdate`.
|
|
22
|
+
*/
|
|
23
|
+
url?: string
|
|
20
24
|
/** Refresh interval, in minutes, for fetching server updates. */
|
|
21
25
|
intervalMinutes?: number
|
|
22
26
|
/** Whether fetched updates should trigger an immediate widget refresh. */
|
|
@@ -44,14 +48,32 @@ export interface AndroidWidgetConfig {
|
|
|
44
48
|
displayName: WidgetLabel
|
|
45
49
|
/** User-facing widget description shown by the launcher. */
|
|
46
50
|
description: WidgetLabel
|
|
47
|
-
/** Minimum widget width in dp. */
|
|
51
|
+
/** Minimum widget width in dp. Only affects Android 11 and older. */
|
|
48
52
|
minWidth?: number
|
|
49
|
-
/** Minimum widget height in dp. */
|
|
53
|
+
/** Minimum widget height in dp. Only affects Android 11 and older. */
|
|
50
54
|
minHeight?: number
|
|
51
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Minimum widget width in launcher grid cells.
|
|
57
|
+
*
|
|
58
|
+
* @deprecated Use `minWidth` instead. The value is approximated in dp for Android 11 and
|
|
59
|
+
* older.
|
|
60
|
+
*/
|
|
52
61
|
minCellWidth?: number
|
|
53
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Minimum widget height in launcher grid cells.
|
|
64
|
+
*
|
|
65
|
+
* @deprecated Use `minHeight` instead. The value is approximated in dp for Android 11 and
|
|
66
|
+
* older.
|
|
67
|
+
*/
|
|
54
68
|
minCellHeight?: number
|
|
69
|
+
/** Minimum width, in dp, the widget can be resized down to. */
|
|
70
|
+
minResizeWidth?: number
|
|
71
|
+
/** Minimum height, in dp, the widget can be resized down to. */
|
|
72
|
+
minResizeHeight?: number
|
|
73
|
+
/** Maximum width, in dp, the widget can be resized up to. Honoured on Android 12 and newer. */
|
|
74
|
+
maxResizeWidth?: number
|
|
75
|
+
/** Maximum height, in dp, the widget can be resized up to. Honoured on Android 12 and newer. */
|
|
76
|
+
maxResizeHeight?: number
|
|
55
77
|
/** Default widget width in launcher grid cells. */
|
|
56
78
|
targetCellWidth: number
|
|
57
79
|
/** Default widget height in launcher grid cells. */
|
|
@@ -84,8 +106,11 @@ export type IOSWidgetFamily =
|
|
|
84
106
|
| 'accessoryInline'
|
|
85
107
|
|
|
86
108
|
export interface IOSWidgetServerUpdateConfig {
|
|
87
|
-
/**
|
|
88
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Server endpoint that returns widget state updates. Optional — omit it to mark the widget
|
|
111
|
+
* server-driven and supply the URL at runtime with `setWidgetServerUpdate`.
|
|
112
|
+
*/
|
|
113
|
+
url?: string
|
|
89
114
|
/** Refresh interval, in minutes, for fetching server updates. */
|
|
90
115
|
intervalMinutes?: number
|
|
91
116
|
/** Whether fetched updates should trigger an immediate widget refresh. */
|
|
@@ -109,6 +134,12 @@ export interface IOSWidgetAppIntentConfig {
|
|
|
109
134
|
export interface IOSWidgetConfig {
|
|
110
135
|
/** Stable widget identifier used in generated files and registrations. */
|
|
111
136
|
id: string
|
|
137
|
+
/**
|
|
138
|
+
* WidgetKit `kind` of the generated widget. Defaults to `Voltra_Widget_<id>`.
|
|
139
|
+
* Pin it to the kind of a pre-Voltra widget so already placed instances survive the migration
|
|
140
|
+
* (WidgetKit identifies a placed widget by extension bundle id + kind).
|
|
141
|
+
*/
|
|
142
|
+
kind?: string
|
|
112
143
|
/** User-facing widget name shown in iOS widget configuration UI. */
|
|
113
144
|
displayName: WidgetLabel
|
|
114
145
|
/** User-facing widget description shown in iOS widget configuration UI. */
|
|
@@ -145,8 +176,11 @@ export interface IOSProjectOverrides {
|
|
|
145
176
|
mainTargetName?: string
|
|
146
177
|
/** Explicit path to the app target Info.plist file. */
|
|
147
178
|
infoPlistPath?: string
|
|
148
|
-
/**
|
|
149
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Explicit path to the main app entitlements file, or one path per Xcode build configuration
|
|
181
|
+
* name when each environment has its own.
|
|
182
|
+
*/
|
|
183
|
+
entitlementsPath?: PerConfiguration<string>
|
|
150
184
|
/** Explicit path to the Podfile. */
|
|
151
185
|
podfilePath?: string
|
|
152
186
|
}
|
|
@@ -175,8 +209,11 @@ export interface VoltraAndroidConfig {
|
|
|
175
209
|
export interface VoltraIOSConfig {
|
|
176
210
|
/** Whether to enable push-notification-related iOS setup for widgets and Live Activities. */
|
|
177
211
|
enablePushNotifications?: boolean
|
|
178
|
-
/**
|
|
179
|
-
|
|
212
|
+
/**
|
|
213
|
+
* App Group identifier used to share data between the app and widget extension, or one per Xcode
|
|
214
|
+
* build configuration name when each environment has its own.
|
|
215
|
+
*/
|
|
216
|
+
groupIdentifier?: PerConfiguration<string>
|
|
180
217
|
/** iOS widgets to generate and register. */
|
|
181
218
|
widgets?: IOSWidgetConfig[]
|
|
182
219
|
/** Minimum iOS deployment target for generated widget targets. */
|
|
@@ -187,8 +224,11 @@ export interface VoltraIOSConfig {
|
|
|
187
224
|
fonts?: string[]
|
|
188
225
|
/** Directory containing user-provided images for iOS widgets. */
|
|
189
226
|
userImagesPath?: string
|
|
190
|
-
/**
|
|
191
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Keychain access group shared by the app and extension, or one per Xcode build configuration
|
|
229
|
+
* name when each environment has its own.
|
|
230
|
+
*/
|
|
231
|
+
keychainGroup?: PerConfiguration<string>
|
|
192
232
|
/** Native iOS project discovery overrides. */
|
|
193
233
|
project?: IOSProjectOverrides
|
|
194
234
|
}
|
|
@@ -211,28 +251,27 @@ export interface LoadedVoltraConfig {
|
|
|
211
251
|
configDir: string
|
|
212
252
|
}
|
|
213
253
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Build-time server-update defaults after normalization. The device treats these as the lowest
|
|
256
|
+
* settings layer; `setWidgetServerUpdate` overrides `url` and `intervalMinutes` at runtime.
|
|
257
|
+
*/
|
|
258
|
+
export interface NormalizedWidgetServerUpdateConfig {
|
|
259
|
+
/** Server endpoint, when app.json set one. Absent means "URL supplied at runtime". */
|
|
260
|
+
url?: string
|
|
217
261
|
/** Refresh interval, in minutes, for fetching server updates. */
|
|
218
262
|
intervalMinutes: number
|
|
219
|
-
/** Whether
|
|
263
|
+
/** Whether the widget draws a refresh button. Build-time only: it is generated UI structure. */
|
|
220
264
|
refresh: boolean
|
|
221
265
|
}
|
|
222
266
|
|
|
267
|
+
export type NormalizedAndroidWidgetServerUpdateConfig = NormalizedWidgetServerUpdateConfig
|
|
268
|
+
|
|
223
269
|
export interface NormalizedAndroidWidgetConfig extends Omit<AndroidWidgetConfig, 'serverUpdate'> {
|
|
224
270
|
/** Server-driven update settings after defaults have been applied. */
|
|
225
271
|
serverUpdate?: NormalizedAndroidWidgetServerUpdateConfig
|
|
226
272
|
}
|
|
227
273
|
|
|
228
|
-
export
|
|
229
|
-
/** Server endpoint that returns widget state updates. */
|
|
230
|
-
url: string
|
|
231
|
-
/** Refresh interval, in minutes, for fetching server updates. */
|
|
232
|
-
intervalMinutes: number
|
|
233
|
-
/** Whether fetched updates should trigger an immediate widget refresh. */
|
|
234
|
-
refresh: boolean
|
|
235
|
-
}
|
|
274
|
+
export type NormalizedIOSWidgetServerUpdateConfig = NormalizedWidgetServerUpdateConfig
|
|
236
275
|
|
|
237
276
|
export interface NormalizedIOSWidgetConfig extends Omit<IOSWidgetConfig, 'serverUpdate' | 'supportedFamilies'> {
|
|
238
277
|
/** Supported iOS widget families after defaults have been applied. */
|
|
@@ -261,8 +300,8 @@ export interface NormalizedIOSProjectConfig {
|
|
|
261
300
|
mainTargetName?: string
|
|
262
301
|
/** Absolute path to the Info.plist file, if overridden. */
|
|
263
302
|
infoPlistPath?: string
|
|
264
|
-
/** Absolute path to the entitlements file, if overridden. */
|
|
265
|
-
entitlementsPath?: string
|
|
303
|
+
/** Absolute path to the entitlements file, or one per build configuration name, if overridden. */
|
|
304
|
+
entitlementsPath?: PerConfiguration<string>
|
|
266
305
|
/** Absolute path to the Podfile, if overridden. */
|
|
267
306
|
podfilePath?: string
|
|
268
307
|
}
|
|
@@ -284,7 +323,7 @@ export interface NormalizedVoltraIOSConfig {
|
|
|
284
323
|
/** Whether iOS push-notification-related setup should be applied. */
|
|
285
324
|
enablePushNotifications: boolean
|
|
286
325
|
/** App Group identifier used to share data between the app and extension. */
|
|
287
|
-
groupIdentifier?: string
|
|
326
|
+
groupIdentifier?: PerConfiguration<string>
|
|
288
327
|
/** iOS widgets after validation and normalization. */
|
|
289
328
|
widgets: NormalizedIOSWidgetConfig[]
|
|
290
329
|
/** Effective iOS deployment target for generated widget targets. */
|
|
@@ -296,11 +335,37 @@ export interface NormalizedVoltraIOSConfig {
|
|
|
296
335
|
/** Absolute path to the iOS user images directory. */
|
|
297
336
|
userImagesPath: string
|
|
298
337
|
/** Keychain access group shared by the app and extension. */
|
|
299
|
-
keychainGroup?: string
|
|
338
|
+
keychainGroup?: PerConfiguration<string>
|
|
300
339
|
/** Normalized iOS native project discovery overrides. */
|
|
301
340
|
project: NormalizedIOSProjectConfig
|
|
302
341
|
}
|
|
303
342
|
|
|
343
|
+
/**
|
|
344
|
+
* iOS project overrides after per-build-configuration values have been resolved against the Xcode
|
|
345
|
+
* project.
|
|
346
|
+
*/
|
|
347
|
+
export type ResolvedIOSProjectConfig = Omit<NormalizedIOSProjectConfig, 'entitlementsPath'> & {
|
|
348
|
+
/** Absolute path to the entitlements file of the default build configuration, if overridden. */
|
|
349
|
+
entitlementsPath?: string
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* iOS config as the platform mutators consume it: every per-build-configuration value has been
|
|
354
|
+
* collapsed to a single string, either the configured value or a reference to a build setting
|
|
355
|
+
* Voltra writes per build configuration.
|
|
356
|
+
*/
|
|
357
|
+
export type ResolvedVoltraIOSConfig = Omit<
|
|
358
|
+
NormalizedVoltraIOSConfig,
|
|
359
|
+
'groupIdentifier' | 'keychainGroup' | 'project'
|
|
360
|
+
> & {
|
|
361
|
+
/** App Group identifier used to share data between the app and extension. */
|
|
362
|
+
groupIdentifier?: string
|
|
363
|
+
/** Keychain access group shared by the app and extension. */
|
|
364
|
+
keychainGroup?: string
|
|
365
|
+
/** Resolved iOS native project discovery overrides. */
|
|
366
|
+
project: ResolvedIOSProjectConfig
|
|
367
|
+
}
|
|
368
|
+
|
|
304
369
|
export interface NormalizedVoltraConfig {
|
|
305
370
|
/** Absolute path to the loaded config file, when the config came from a file. */
|
|
306
371
|
configPath?: string
|
|
@@ -312,6 +377,8 @@ export interface NormalizedVoltraConfig {
|
|
|
312
377
|
android?: NormalizedVoltraAndroidConfig
|
|
313
378
|
/** Normalized iOS-specific Voltra configuration. */
|
|
314
379
|
ios?: NormalizedVoltraIOSConfig
|
|
380
|
+
/** Non-fatal config problems, surfaced in the `voltra apply` summary. */
|
|
381
|
+
warnings?: string[]
|
|
315
382
|
}
|
|
316
383
|
|
|
317
384
|
export type CliDefaults = typeof CLI_DEFAULTS
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, test } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { iosWidgetKind, iosWidgetKindOverrides } from './widgetKind.ts'
|
|
5
|
+
|
|
6
|
+
describe('iosWidgetKind', () => {
|
|
7
|
+
test('defaults to the prefixed widget id', () => {
|
|
8
|
+
assert.equal(iosWidgetKind({ id: 'weather' }), 'Voltra_Widget_weather')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('uses the pinned kind when the widget has one', () => {
|
|
12
|
+
assert.equal(iosWidgetKind({ id: 'streak', kind: 'StreakWidget' }), 'StreakWidget')
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe('iosWidgetKindOverrides', () => {
|
|
17
|
+
test('is undefined when no widget pins a kind, so the Info.plist key stays absent', () => {
|
|
18
|
+
assert.equal(iosWidgetKindOverrides([{ id: 'weather' }, { id: 'streak' }]), undefined)
|
|
19
|
+
assert.equal(iosWidgetKindOverrides([]), undefined)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('maps only the widgets that pin a kind', () => {
|
|
23
|
+
const overrides = iosWidgetKindOverrides([{ id: 'weather' }, { id: 'streak', kind: 'StreakWidget' }])
|
|
24
|
+
|
|
25
|
+
assert.deepEqual(overrides, { streak: 'StreakWidget' })
|
|
26
|
+
})
|
|
27
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { IOSWidgetConfig } from './types'
|
|
2
|
+
|
|
3
|
+
/** Default prefix of a widget's WidgetKit `kind`: `Voltra_Widget_<id>`. Mirrors `VoltraStorageKeys.widgetKindPrefix`. */
|
|
4
|
+
export const IOS_WIDGET_KIND_PREFIX = 'Voltra_Widget_'
|
|
5
|
+
|
|
6
|
+
/** WidgetKit `kind` of a widget: the `kind` option when set, else `Voltra_Widget_<id>`. */
|
|
7
|
+
export function iosWidgetKind(widget: Pick<IOSWidgetConfig, 'id' | 'kind'>): string {
|
|
8
|
+
return widget.kind ?? `${IOS_WIDGET_KIND_PREFIX}${widget.id}`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `{ id: kind }` for the widgets that pin a custom `kind`, or undefined when none does.
|
|
13
|
+
* Written to both Info.plists (`Voltra_WidgetKinds`) so native code maps ids to kinds and back.
|
|
14
|
+
*/
|
|
15
|
+
export function iosWidgetKindOverrides(
|
|
16
|
+
widgets: Pick<IOSWidgetConfig, 'id' | 'kind'>[]
|
|
17
|
+
): Record<string, string> | undefined {
|
|
18
|
+
const overrides: Record<string, string> = {}
|
|
19
|
+
|
|
20
|
+
for (const widget of widgets) {
|
|
21
|
+
if (widget.kind !== undefined) {
|
|
22
|
+
overrides[widget.id] = widget.kind
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return Object.keys(overrides).length > 0 ? overrides : undefined
|
|
27
|
+
}
|
|
@@ -36,6 +36,17 @@ export function requirePlatformPackage<TPackage>(projectRoot: string, platform:
|
|
|
36
36
|
return createProjectRequire(projectRoot)(getPlatformPackageName(platform)) as TPackage
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export function getPlatformClientPackageVersion(projectRoot: string, platform: VoltraPlatform): string {
|
|
40
|
+
const packageName = getPlatformClientPackageName(platform)
|
|
41
|
+
const manifest = createProjectRequire(projectRoot)(`${packageName}/package.json`) as { version?: unknown }
|
|
42
|
+
|
|
43
|
+
if (typeof manifest.version !== 'string' || manifest.version.length === 0) {
|
|
44
|
+
throw new Error(`Package ${packageName} does not declare a valid version.`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return manifest.version
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
export function getMissingPlatformPackageMessage(
|
|
40
51
|
platform: VoltraPlatform,
|
|
41
52
|
packageNames = getRequiredPlatformPackageNames(platform)
|