@microsoft/power-apps-native-host 0.3.0 → 0.3.3
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/README.md +1 -9
- package/compatibility/current.json +31 -3
- package/lib/NOTICE.txt +1323 -16
- package/lib/commonjs/auth/AuthContext.js +36 -15
- package/lib/commonjs/config/babelConfig.js +13 -0
- package/lib/commonjs/config/expoConfig.js +71 -0
- package/lib/commonjs/config/metroConfig.js +70 -0
- package/lib/commonjs/config/metroLogger.js +182 -0
- package/lib/commonjs/config/tamaguiConfig.js +98 -0
- package/lib/commonjs/connections/resolveConnections.js +3 -2
- package/lib/commonjs/connections/useConnectionRefs.js +3 -1
- package/lib/commonjs/index.js +1 -0
- package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +3 -1
- package/lib/commonjs/platform/DataverseDataSources.js +4 -0
- package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +45 -0
- package/lib/commonjs/provider/PowerAppsHostProvider.js +1 -0
- package/lib/commonjs/provider/PowerAppsProvider.js +10 -5
- package/lib/commonjs/provider/SessionDetailsGesture.js +1 -9
- package/lib/module/auth/AuthContext.js +36 -15
- package/lib/module/config/babelConfig.js +9 -0
- package/lib/module/config/expoConfig.js +67 -0
- package/lib/module/config/metroConfig.js +65 -0
- package/{scripts/metro-logger.js → lib/module/config/metroLogger.js} +58 -96
- package/lib/module/config/tamaguiConfig.js +93 -0
- package/lib/module/connections/resolveConnections.js +3 -2
- package/lib/module/connections/useConnectionRefs.js +3 -1
- package/lib/module/index.js +4 -0
- package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +3 -1
- package/lib/module/platform/DataverseDataSources.js +3 -0
- package/lib/module/platform/DataverseGlobalDiscoveryApi.js +44 -0
- package/lib/module/provider/PowerAppsHostProvider.js +2 -1
- package/lib/module/provider/PowerAppsProvider.js +10 -5
- package/lib/module/provider/SessionDetailsGesture.js +1 -9
- package/lib/typescript/commonjs/config/babelConfig.d.ts +12 -0
- package/lib/typescript/commonjs/config/expoConfig.d.ts +21 -0
- package/lib/typescript/commonjs/config/metroConfig.d.ts +6 -0
- package/lib/typescript/commonjs/config/metroLogger.d.ts +21 -0
- package/lib/typescript/commonjs/config/tamaguiConfig.d.ts +1792 -0
- package/lib/typescript/commonjs/connections/resolveConnections.d.ts +3 -1
- package/lib/typescript/commonjs/connections/useConnectionRefs.d.ts +3 -1
- package/lib/typescript/commonjs/index.d.ts +2 -1
- package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +1 -0
- package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +27 -4
- package/lib/typescript/module/config/babelConfig.d.ts +12 -0
- package/lib/typescript/module/config/expoConfig.d.ts +21 -0
- package/lib/typescript/module/config/metroConfig.d.ts +6 -0
- package/lib/typescript/module/config/metroLogger.d.ts +21 -0
- package/lib/typescript/module/config/tamaguiConfig.d.ts +1792 -0
- package/lib/typescript/module/connections/resolveConnections.d.ts +3 -1
- package/lib/typescript/module/connections/useConnectionRefs.d.ts +3 -1
- package/lib/typescript/module/index.d.ts +2 -1
- package/lib/typescript/module/platform/DataverseDataSources.d.ts +1 -0
- package/lib/typescript/module/provider/PowerAppsProvider.d.ts +27 -4
- package/package.json +47 -13
- package/scripts/build-codegen-package.js +283 -0
- package/scripts/start-web.mjs +9 -5
- package/scripts/upgrade/runtime.js +126 -17
- package/scripts/upgrade-authoring.js +274 -179
- package/scripts/upgrade.js +10 -16
- package/src/config/generated-config.d.ts +4 -0
- package/src/config/index.d.ts +3 -0
- package/src/config/tsconfig.json +26 -0
|
@@ -39,6 +39,8 @@ export interface StaticRefsOptions {
|
|
|
39
39
|
* Shape: { [refId]: { id: string; dataSources: string[] } }
|
|
40
40
|
*/
|
|
41
41
|
connectionReferences: Record<string, ConnectionReferenceConfig>;
|
|
42
|
+
/** Whether power.config.json contains Dataverse database references. */
|
|
43
|
+
hasDataverse: boolean;
|
|
42
44
|
/** Connector schema map keyed by data source name. */
|
|
43
45
|
schemaMap: Record<string, ConnectorSchema>;
|
|
44
46
|
environmentId: string;
|
|
@@ -67,7 +69,7 @@ export interface StaticRefsOptions {
|
|
|
67
69
|
* `true`.
|
|
68
70
|
* 5. Build and return an `AppLaunchResult`-shaped object.
|
|
69
71
|
*/
|
|
70
|
-
export declare function resolveViaStaticRefs({ connectionReferences, schemaMap, environmentId, region, acquireToken, token, isvClientId, onDisambiguationNeeded, onMissingConnection, }: StaticRefsOptions): Promise<AppLaunchResult>;
|
|
72
|
+
export declare function resolveViaStaticRefs({ connectionReferences, hasDataverse, schemaMap, environmentId, region, acquireToken, token, isvClientId, onDisambiguationNeeded, onMissingConnection, }: StaticRefsOptions): Promise<AppLaunchResult>;
|
|
71
73
|
/**
|
|
72
74
|
* Run idempotent post-resolution delegated consent grants for the calling app.
|
|
73
75
|
*
|
|
@@ -59,6 +59,8 @@ export interface UseConnectionRefsOptions {
|
|
|
59
59
|
* Shape: { [refId]: { id: string; dataSources: string[] } }
|
|
60
60
|
*/
|
|
61
61
|
connectionReferences: Record<string, ConnectionReferenceConfig>;
|
|
62
|
+
/** Whether power.config.json contains Dataverse database references. */
|
|
63
|
+
hasDataverse: boolean;
|
|
62
64
|
/**
|
|
63
65
|
* Connector schema map keyed by data source name.
|
|
64
66
|
* Used only in Dev Player for unpublished local apps.
|
|
@@ -131,5 +133,5 @@ export type ConnectionResolutionErrorKind = 'app-access-denied' | 'generic';
|
|
|
131
133
|
* Both paths disambiguate candidates, pre-warm Dataverse authentication, and
|
|
132
134
|
* return a fully populated bridge configuration.
|
|
133
135
|
*/
|
|
134
|
-
export declare function useConnectionRefs({ isSignedIn, acquireToken, appId, environmentId, region, connectionReferences, schemaMap, onDisambiguationNeeded, onMissingConnection, isvClientId, }: UseConnectionRefsOptions): UseConnectionRefsResult;
|
|
136
|
+
export declare function useConnectionRefs({ isSignedIn, acquireToken, appId, environmentId, region, connectionReferences, hasDataverse, schemaMap, onDisambiguationNeeded, onMissingConnection, isvClientId, }: UseConnectionRefsOptions): UseConnectionRefsResult;
|
|
135
137
|
//# sourceMappingURL=useConnectionRefs.d.ts.map
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* React Native host integration for @microsoft/power-apps
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
|
+
import '@microsoft/power-apps-native-assets';
|
|
6
7
|
export { NativePowerAppsBridge } from './bridge/NativePowerAppsBridge.js';
|
|
7
8
|
export { PowerPagesBridge, setupPowerPagesHost } from './bridge/PowerPagesBridge.js';
|
|
8
9
|
export type { PowerPagesBridgeOptions } from './bridge/PowerPagesBridge.js';
|
|
@@ -17,7 +18,7 @@ export type { AuthConfig, MsalAuthConfig, IntuneConfig } from '@microsoft/power-
|
|
|
17
18
|
export { PowerAppsHostProvider } from './provider/PowerAppsHostProvider';
|
|
18
19
|
export type { PowerAppsHostProviderProps } from './provider/PowerAppsHostProvider';
|
|
19
20
|
export { PowerAppsProvider } from './provider/PowerAppsProvider.js';
|
|
20
|
-
export type { AppInsightsConfig, PowerAppsProviderProps, PowerConfig } from './provider/PowerAppsProvider.js';
|
|
21
|
+
export type { AppInsightsConfig, PowerAppsAppConfig, PowerAppsProviderProps, PowerConfig, } from './provider/PowerAppsProvider.js';
|
|
21
22
|
export { buildEnvironmentEndpoint, POWER_PLATFORM_API_SCOPE } from './platform/PowerPlatformEnvironmentApi.js';
|
|
22
23
|
export { fetchInstanceUrl } from './platform/DataverseGlobalDiscoveryApi.js';
|
|
23
24
|
export { buildCdsDataSources } from './platform/DataverseDataSources.js';
|
|
@@ -18,6 +18,7 @@ export interface DatabaseReferenceDataSource {
|
|
|
18
18
|
export interface DatabaseReference {
|
|
19
19
|
dataSources?: Record<string, DatabaseReferenceDataSource>;
|
|
20
20
|
}
|
|
21
|
+
export declare function hasDataverseDatabaseReferences<T>(databaseReferences?: Record<string, T>): boolean;
|
|
21
22
|
export declare function buildCdsDataSources(databaseReferences: Record<string, DatabaseReference>, dataverseOrgUrl: string): Array<{
|
|
22
23
|
runtimeUrl: string;
|
|
23
24
|
entitySetName: string;
|
|
@@ -69,7 +69,25 @@ export interface AppInsightsConfig extends Omit<ApplicationInsightsTelemetryProv
|
|
|
69
69
|
/** Explicit opt-in. Application Insights telemetry is disabled unless this is true. */
|
|
70
70
|
enabled: boolean;
|
|
71
71
|
}
|
|
72
|
+
export interface PowerAppsAppConfig {
|
|
73
|
+
expo?: {
|
|
74
|
+
extra?: {
|
|
75
|
+
appInsightsConfig?: unknown;
|
|
76
|
+
[key: string]: unknown;
|
|
77
|
+
};
|
|
78
|
+
[key: string]: unknown;
|
|
79
|
+
};
|
|
80
|
+
[key: string]: unknown;
|
|
81
|
+
}
|
|
72
82
|
export interface PowerAppsProviderProps {
|
|
83
|
+
/**
|
|
84
|
+
* Contents of the generated app.json.
|
|
85
|
+
*
|
|
86
|
+
* Pass this when the app runs inside a host such as Dev Player, where
|
|
87
|
+
* `Constants.expoConfig` describes the host instead of the loaded app.
|
|
88
|
+
* The provider falls back to `Constants.expoConfig` when omitted.
|
|
89
|
+
*/
|
|
90
|
+
appConfig?: PowerAppsAppConfig;
|
|
73
91
|
/** MSAL configuration from auth.config.json (msal section). */
|
|
74
92
|
msalConfig: AuthProviderProps['msalConfig'];
|
|
75
93
|
/** Intune MAM configuration. Defaults to enabled in release builds. */
|
|
@@ -134,16 +152,21 @@ export interface PowerAppsProviderProps {
|
|
|
134
152
|
*/
|
|
135
153
|
logger?: ILogger | null;
|
|
136
154
|
/**
|
|
137
|
-
* Show app and telemetry session details after a three-finger
|
|
155
|
+
* Show app and telemetry session details after a three-finger long press.
|
|
138
156
|
* Available in both development and published native apps. @default true
|
|
139
157
|
*/
|
|
158
|
+
showSessionDetailsOnLongPress?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* @deprecated Use `showSessionDetailsOnLongPress` instead.
|
|
161
|
+
*/
|
|
140
162
|
showSessionDetailsOnSwipe?: boolean;
|
|
141
163
|
/**
|
|
142
164
|
* Optional customer-owned Application Insights destination.
|
|
143
165
|
*
|
|
144
166
|
* Events are sent directly to the selected C1-owned Application Insights
|
|
145
167
|
* resource and fan out alongside the default Microsoft OneDS provider.
|
|
146
|
-
* Defaults to `expo.extra.appInsightsConfig
|
|
168
|
+
* Defaults to `appConfig.expo.extra.appInsightsConfig`, then falls back to
|
|
169
|
+
* `Constants.expoConfig.extra.appInsightsConfig`.
|
|
147
170
|
* Ignored when `logger` is provided or `null`.
|
|
148
171
|
*/
|
|
149
172
|
appInsightsConfig?: AppInsightsConfig;
|
|
@@ -164,7 +187,7 @@ export interface PowerAppsProviderProps {
|
|
|
164
187
|
* component, eliminating all provider boilerplate from the consuming app's
|
|
165
188
|
* root layout.
|
|
166
189
|
*/
|
|
167
|
-
export declare function PowerAppsProvider({ msalConfig, intuneConfig, powerConfig, schemaMap, tamaguiConfig, // override with app's tamagui.config.ts
|
|
168
|
-
defaultTheme, theme, darkTheme, followSystemTheme, renderOAuthWebView, onDisambiguationNeeded, onConnectionConsent, onResourceConsent, onConnectionConsentDeclined, appName, logger: loggerProp, showSessionDetailsOnSwipe, appInsightsConfig, offlineProfile, children, }: PowerAppsProviderProps): React.JSX.Element;
|
|
190
|
+
export declare function PowerAppsProvider({ appConfig, msalConfig, intuneConfig, powerConfig, schemaMap, tamaguiConfig, // override with app's tamagui.config.ts
|
|
191
|
+
defaultTheme, theme, darkTheme, followSystemTheme, renderOAuthWebView, onDisambiguationNeeded, onConnectionConsent, onResourceConsent, onConnectionConsentDeclined, appName, logger: loggerProp, showSessionDetailsOnLongPress, showSessionDetailsOnSwipe, appInsightsConfig, offlineProfile, children, }: PowerAppsProviderProps): React.JSX.Element;
|
|
169
192
|
export {};
|
|
170
193
|
//# sourceMappingURL=PowerAppsProvider.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/power-apps-native-host",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "React Native host integration for @microsoft/power-apps",
|
|
5
5
|
"author": "Microsoft Corporation",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -12,29 +12,44 @@
|
|
|
12
12
|
},
|
|
13
13
|
"main": "./lib/commonjs/index.js",
|
|
14
14
|
"module": "./lib/module/index.js",
|
|
15
|
-
"types": "./
|
|
15
|
+
"types": "./src/config/index.d.ts",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
18
18
|
"browser": {
|
|
19
|
-
"types": "./
|
|
20
|
-
"default": "./lib/
|
|
19
|
+
"types": "./src/config/index.d.ts",
|
|
20
|
+
"default": "./lib/module/index.js"
|
|
21
21
|
},
|
|
22
22
|
"import": {
|
|
23
|
-
"types": "./
|
|
23
|
+
"types": "./src/config/index.d.ts",
|
|
24
24
|
"default": "./lib/module/index.js"
|
|
25
25
|
},
|
|
26
26
|
"require": {
|
|
27
|
-
"types": "./
|
|
27
|
+
"types": "./src/config/index.d.ts",
|
|
28
28
|
"default": "./lib/commonjs/index.js"
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"./package.json": "./package.json",
|
|
32
|
-
"./
|
|
32
|
+
"./config/tsconfig": "./src/config/tsconfig.json",
|
|
33
|
+
"./config/*": {
|
|
34
|
+
"browser": {
|
|
35
|
+
"types": "./lib/typescript/module/config/*.d.ts",
|
|
36
|
+
"default": "./lib/module/config/*.js"
|
|
37
|
+
},
|
|
38
|
+
"import": {
|
|
39
|
+
"types": "./lib/typescript/module/config/*.d.ts",
|
|
40
|
+
"default": "./lib/module/config/*.js"
|
|
41
|
+
},
|
|
42
|
+
"require": {
|
|
43
|
+
"types": "./lib/typescript/commonjs/config/*.d.ts",
|
|
44
|
+
"default": "./lib/commonjs/config/*.js"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
33
47
|
"./app.plugin.js": "./app.plugin.js"
|
|
34
48
|
},
|
|
35
49
|
"bin": {
|
|
36
50
|
"generate-connector-schemas": "scripts/generate-connector-schemas.js",
|
|
37
51
|
"js-bundle": "scripts/bundle.js",
|
|
52
|
+
"build-codegen-package": "scripts/build-codegen-package.js",
|
|
38
53
|
"build-ios-base": "scripts/build-ios-base.js",
|
|
39
54
|
"power-apps-web": "scripts/start-web.mjs",
|
|
40
55
|
"wrap": "scripts/wrap.js",
|
|
@@ -43,21 +58,26 @@
|
|
|
43
58
|
"files": [
|
|
44
59
|
"lib/",
|
|
45
60
|
"!lib/**/*.map",
|
|
61
|
+
"src/config/generated-config.d.ts",
|
|
62
|
+
"src/config/index.d.ts",
|
|
63
|
+
"src/config/tsconfig.json",
|
|
46
64
|
"app.plugin.js",
|
|
47
65
|
"scripts/",
|
|
48
66
|
"compatibility/"
|
|
49
67
|
],
|
|
50
68
|
"scripts": {
|
|
51
69
|
"prepack": "npm run build && npm test",
|
|
52
|
-
"build": "bob build && node ../../scripts/notices.js",
|
|
70
|
+
"build": "bob build && node ../../scripts/package-build/notices.js",
|
|
53
71
|
"type-check": "tsc --noEmit",
|
|
54
|
-
"test": "node --test test/*.test.js",
|
|
55
|
-
"test:upgrade": "node --test test/upgrade.test.js",
|
|
72
|
+
"test": "node --test --test-force-exit test/*.test.js",
|
|
73
|
+
"test:upgrade": "node --test --test-force-exit test/upgrade.test.js",
|
|
56
74
|
"test:offline-compatibility": "bob build && node --test test/OfflineSdkCompatibility.test.js"
|
|
57
75
|
},
|
|
58
76
|
"dependencies": {
|
|
59
|
-
"@
|
|
60
|
-
"@microsoft/power-apps-native-
|
|
77
|
+
"@expo/config-plugins": "55.0.11",
|
|
78
|
+
"@microsoft/power-apps-native-assets": "^0.3.0",
|
|
79
|
+
"@microsoft/power-apps-native-auth": "^0.3.8",
|
|
80
|
+
"@microsoft/power-apps-native-common": "^0.2.2",
|
|
61
81
|
"@ster5/global-mutex": "3.3.0",
|
|
62
82
|
"@tamagui/config": "2.4.5",
|
|
63
83
|
"@tamagui/core": "2.4.5",
|
|
@@ -69,13 +89,20 @@
|
|
|
69
89
|
"write-file-atomic": "7.0.1"
|
|
70
90
|
},
|
|
71
91
|
"peerDependencies": {
|
|
92
|
+
"@expo/vector-icons": ">=15.1.1 <16",
|
|
72
93
|
"@microsoft/power-apps": "*",
|
|
73
94
|
"@microsoft/power-apps-native-offline": ">=0.1.32",
|
|
74
95
|
"@react-native-community/netinfo": ">=11.0.0",
|
|
75
96
|
"burnt": ">=0.12.2",
|
|
97
|
+
"expo-application": ">=55.0.0 <56",
|
|
76
98
|
"expo-constants": ">=55.0.0 <56",
|
|
77
99
|
"expo-clipboard": ">=55.0.0 <56",
|
|
100
|
+
"expo-document-picker": ">=55.0.13 <56",
|
|
101
|
+
"expo-file-system": ">=55.0.19 <56",
|
|
102
|
+
"expo-image-picker": ">=55.0.20 <56",
|
|
78
103
|
"expo-linking": ">=55.0.14 <56",
|
|
104
|
+
"expo-sharing": ">=55.0.18 <56",
|
|
105
|
+
"expo-secure-store": ">=55.0.0 <56",
|
|
79
106
|
"expo-web-browser": ">=55.0.14 <56",
|
|
80
107
|
"react": ">=19.2.0 <20",
|
|
81
108
|
"react-dom": ">=19.2.0 <19.3.0",
|
|
@@ -95,10 +122,13 @@
|
|
|
95
122
|
}
|
|
96
123
|
},
|
|
97
124
|
"devDependencies": {
|
|
125
|
+
"@microsoft/power-apps-cli": "0.15.3",
|
|
98
126
|
"@types/react": "19.2.14",
|
|
99
127
|
"cross-env": "7.0.3",
|
|
128
|
+
"expo-application": "55.0.15",
|
|
100
129
|
"expo-clipboard": "55.0.13",
|
|
101
130
|
"expo-constants": "55.0.16",
|
|
131
|
+
"expo-secure-store": "55.0.13",
|
|
102
132
|
"expo-web-browser": "55.0.15",
|
|
103
133
|
"react": "19.2.0",
|
|
104
134
|
"react-dom": "19.2.0",
|
|
@@ -114,10 +144,14 @@
|
|
|
114
144
|
"publishConfig": {
|
|
115
145
|
"access": "public"
|
|
116
146
|
},
|
|
117
|
-
"sideEffects":
|
|
147
|
+
"sideEffects": [
|
|
148
|
+
"**/index.js",
|
|
149
|
+
"**/index.ts"
|
|
150
|
+
],
|
|
118
151
|
"react-native-builder-bob": {
|
|
119
152
|
"source": "src",
|
|
120
153
|
"output": "lib",
|
|
154
|
+
"exclude": "config/{generated-config.d.ts,index.d.ts,tsconfig.json}",
|
|
121
155
|
"targets": [
|
|
122
156
|
[
|
|
123
157
|
"module",
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Codegen native package producer.
|
|
4
|
+
*
|
|
5
|
+
* Produces a *self-consistent* native asset package for the Power Apps wrap
|
|
6
|
+
* pipeline from a single `expo export`, so the embedded Hermes bundle and the
|
|
7
|
+
* hash-addressed asset files share one Metro AssetRegistry (their hashes line
|
|
8
|
+
* up with what the runtime asset resolver sees). No JavaScript is compiled or
|
|
9
|
+
* downloaded inside the wrap pipeline — this step runs at customer deploy time.
|
|
10
|
+
*
|
|
11
|
+
* Usage (via the app's npm scripts):
|
|
12
|
+
* build-codegen-package android
|
|
13
|
+
* build-codegen-package ios
|
|
14
|
+
*
|
|
15
|
+
* Emits into ./dist (deploy uploads the whole dist/ to blob):
|
|
16
|
+
* dist/index.android.bundle.hbc | dist/main.jsbundle.hbc ← Hermes bundle
|
|
17
|
+
* dist/powerapps-customer-assets-<platform>/manifest.json
|
|
18
|
+
* dist/powerapps-customer-assets-<platform>/assets/<fileHash>.<type>
|
|
19
|
+
*
|
|
20
|
+
* Android and iOS asset packages are kept in separate, platform-suffixed dirs
|
|
21
|
+
* because their scales/densities (and therefore fileHashes) differ. The wrap
|
|
22
|
+
* pipeline downloads the platform-matching dir and injects it into the base app
|
|
23
|
+
* under the neutral name `powerapps-customer-assets/`, which the native module
|
|
24
|
+
* reads at runtime.
|
|
25
|
+
*
|
|
26
|
+
* manifest.json shape (schemaVersion 1):
|
|
27
|
+
* {
|
|
28
|
+
* "schemaVersion": 1,
|
|
29
|
+
* "platform": "android" | "ios",
|
|
30
|
+
* "bundle": { "path": "index.android.bundle.hbc" },
|
|
31
|
+
* "assets": [
|
|
32
|
+
* { "fileHash": "<md5>", "type": "png", "path": "assets/<md5>.png",
|
|
33
|
+
* "hash": "<metroRegistryHash>", "scale": 2 }
|
|
34
|
+
* ]
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* The `hash` (Metro AssetRegistry hash) + `scale` let the native module build a
|
|
38
|
+
* runtime lookup the JS resolver keys by `"<hash>@<scale>"`. Integrity fields
|
|
39
|
+
* (sha256, signing) are intentionally out of scope here (Scope 3).
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
'use strict';
|
|
43
|
+
|
|
44
|
+
const { execFileSync, spawn } = require('child_process');
|
|
45
|
+
const path = require('path');
|
|
46
|
+
const fs = require('fs');
|
|
47
|
+
const os = require('os');
|
|
48
|
+
const crypto = require('crypto');
|
|
49
|
+
|
|
50
|
+
const platform = process.argv[2];
|
|
51
|
+
if (platform !== 'android' && platform !== 'ios') {
|
|
52
|
+
console.error('Usage: build-codegen-package <android|ios>');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const isWin = process.platform === 'win32';
|
|
57
|
+
const isLinux = process.platform === 'linux';
|
|
58
|
+
|
|
59
|
+
const projectRoot = process.cwd();
|
|
60
|
+
const workspaceRoot = path.resolve(projectRoot, '../..');
|
|
61
|
+
|
|
62
|
+
const HERMES_MAGIC = Buffer.from([0xc6, 0x1f, 0xbc, 0x03]);
|
|
63
|
+
// Platform-suffixed in blob so android/ios packages don't clobber each other;
|
|
64
|
+
// the wrap injects into the neutral `powerapps-customer-assets/` on-device.
|
|
65
|
+
const ASSET_DIR_NAME = `powerapps-customer-assets-${platform}`;
|
|
66
|
+
const bundleName =
|
|
67
|
+
platform === 'android' ? 'index.android.bundle.hbc' : 'main.jsbundle.hbc';
|
|
68
|
+
|
|
69
|
+
function resolveExpoBin() {
|
|
70
|
+
const name = isWin ? 'expo.cmd' : 'expo';
|
|
71
|
+
const local = path.join(projectRoot, 'node_modules', '.bin', name);
|
|
72
|
+
const hoisted = path.join(workspaceRoot, 'node_modules', '.bin', name);
|
|
73
|
+
return fs.existsSync(local) ? local : hoisted;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveHermesc() {
|
|
77
|
+
const hcPkg = require.resolve('hermes-compiler/package.json', {
|
|
78
|
+
paths: [projectRoot, workspaceRoot],
|
|
79
|
+
});
|
|
80
|
+
const hcDir = path.dirname(hcPkg);
|
|
81
|
+
const binDir = isWin ? 'win64-bin' : isLinux ? 'linux64-bin' : 'osx-bin';
|
|
82
|
+
const binName = isWin ? 'hermesc.exe' : 'hermesc';
|
|
83
|
+
const flat = path.join(hcDir, binDir, binName);
|
|
84
|
+
const nested = path.join(hcDir, binName, binDir, binName);
|
|
85
|
+
return fs.existsSync(flat) ? flat : nested;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function findFiles(dir, predicate) {
|
|
89
|
+
if (!fs.existsSync(dir)) return [];
|
|
90
|
+
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
91
|
+
const p = path.join(dir, entry.name);
|
|
92
|
+
return entry.isDirectory()
|
|
93
|
+
? findFiles(p, predicate)
|
|
94
|
+
: predicate(p)
|
|
95
|
+
? [p]
|
|
96
|
+
: [];
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function sha256File(filePath) {
|
|
101
|
+
return crypto
|
|
102
|
+
.createHash('sha256')
|
|
103
|
+
.update(fs.readFileSync(filePath))
|
|
104
|
+
.digest('hex');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isHermesBytecode(filePath) {
|
|
108
|
+
const fd = fs.openSync(filePath, 'r');
|
|
109
|
+
try {
|
|
110
|
+
const head = Buffer.alloc(4);
|
|
111
|
+
fs.readSync(fd, head, 0, 4, 0);
|
|
112
|
+
return head.equals(HERMES_MAGIC);
|
|
113
|
+
} finally {
|
|
114
|
+
fs.closeSync(fd);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Run `expo export` for the native platform and wait for completion. Unlike
|
|
120
|
+
* `expo export:embed`, this yields hash-addressed assets and (with
|
|
121
|
+
* `--dump-assetmap`) an assetmap.json we can turn into a manifest.
|
|
122
|
+
*/
|
|
123
|
+
function runExpoExport(expoBin, outputDir) {
|
|
124
|
+
const marker = `Exported: ${outputDir}`;
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
const child = spawn(
|
|
127
|
+
expoBin,
|
|
128
|
+
[
|
|
129
|
+
'export',
|
|
130
|
+
'--platform',
|
|
131
|
+
platform,
|
|
132
|
+
'--output-dir',
|
|
133
|
+
outputDir,
|
|
134
|
+
'--dump-assetmap',
|
|
135
|
+
'--clear',
|
|
136
|
+
],
|
|
137
|
+
{
|
|
138
|
+
cwd: projectRoot,
|
|
139
|
+
env: { ...process.env, CI: process.env.CI ?? '1', EXPO_NO_TELEMETRY: '1' },
|
|
140
|
+
shell: isWin,
|
|
141
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
let tail = '';
|
|
145
|
+
let done = false;
|
|
146
|
+
const forward = (chunk, dest) => {
|
|
147
|
+
dest.write(chunk);
|
|
148
|
+
tail = (tail + chunk).slice(-marker.length * 2);
|
|
149
|
+
if (!done && tail.includes(marker)) {
|
|
150
|
+
done = true;
|
|
151
|
+
if (isWin && child.pid) {
|
|
152
|
+
try {
|
|
153
|
+
execFileSync('taskkill', ['/pid', String(child.pid), '/t', '/f'], {
|
|
154
|
+
stdio: 'ignore',
|
|
155
|
+
});
|
|
156
|
+
} catch {
|
|
157
|
+
child.kill();
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
child.kill('SIGKILL');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
child.stdout.on('data', (c) => forward(c, process.stdout));
|
|
165
|
+
child.stderr.on('data', (c) => forward(c, process.stderr));
|
|
166
|
+
child.once('error', reject);
|
|
167
|
+
child.once('exit', (code, signal) => {
|
|
168
|
+
if (done) resolve();
|
|
169
|
+
else
|
|
170
|
+
reject(
|
|
171
|
+
new Error(
|
|
172
|
+
code === 0
|
|
173
|
+
? `Expo export exited before completion marker: ${marker}`
|
|
174
|
+
: `Expo export failed (${signal ? `signal ${signal}` : `code ${code}`})`,
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function locateBundle(exportDir) {
|
|
182
|
+
const jsDir = path.join(exportDir, '_expo', 'static', 'js', platform);
|
|
183
|
+
const candidates = findFiles(jsDir, (p) => /\.(hbc|js)$/.test(p));
|
|
184
|
+
const hbc = candidates.find((p) => p.endsWith('.hbc'));
|
|
185
|
+
if (hbc) return { file: hbc, isHermes: true };
|
|
186
|
+
if (candidates.length === 1) return { file: candidates[0], isHermes: false };
|
|
187
|
+
throw new Error(
|
|
188
|
+
`Expected exactly one native bundle under ${jsDir}, found ${candidates.length}`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function buildAssetPackage(exportDir, distDir, bundlePath) {
|
|
193
|
+
const assetMapPath = path.join(exportDir, 'assetmap.json');
|
|
194
|
+
const assetsOutDir = path.join(distDir, ASSET_DIR_NAME, 'assets');
|
|
195
|
+
const manifestPath = path.join(distDir, ASSET_DIR_NAME, 'manifest.json');
|
|
196
|
+
|
|
197
|
+
const manifest = {
|
|
198
|
+
schemaVersion: 1,
|
|
199
|
+
platform,
|
|
200
|
+
// BIN-3: sha256 pins every artifact the wrap pipeline injects. The wrap
|
|
201
|
+
// recomputes these before use, so a truncated or swapped blob fails the
|
|
202
|
+
// build instead of shipping inside a signed app.
|
|
203
|
+
bundle: { path: bundleName, sha256: sha256File(bundlePath) },
|
|
204
|
+
assets: [],
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// No assets is valid (an app may reference none). Still emit an empty
|
|
208
|
+
// manifest so the wrap injection + native module behave uniformly.
|
|
209
|
+
if (fs.existsSync(assetMapPath)) {
|
|
210
|
+
fs.mkdirSync(assetsOutDir, { recursive: true });
|
|
211
|
+
const assetMap = JSON.parse(fs.readFileSync(assetMapPath, 'utf8'));
|
|
212
|
+
for (const [registryHash, entry] of Object.entries(assetMap)) {
|
|
213
|
+
const type = String(entry.type ?? '').replace(/^\./, '') || 'dat';
|
|
214
|
+
const fileHashes = entry.fileHashes ?? [];
|
|
215
|
+
const scales = entry.scales ?? [];
|
|
216
|
+
for (let i = 0; i < fileHashes.length; i++) {
|
|
217
|
+
const fileHash = fileHashes[i];
|
|
218
|
+
const scale = scales[i] ?? 1;
|
|
219
|
+
const src = path.join(exportDir, 'assets', fileHash);
|
|
220
|
+
if (!fs.existsSync(src)) {
|
|
221
|
+
throw new Error(`Exported asset missing on disk: assets/${fileHash}`);
|
|
222
|
+
}
|
|
223
|
+
const relPath = `assets/${fileHash}.${type}`;
|
|
224
|
+
const destPath = path.join(distDir, ASSET_DIR_NAME, relPath);
|
|
225
|
+
fs.copyFileSync(src, destPath);
|
|
226
|
+
manifest.assets.push({
|
|
227
|
+
fileHash,
|
|
228
|
+
type,
|
|
229
|
+
path: relPath,
|
|
230
|
+
hash: registryHash,
|
|
231
|
+
scale,
|
|
232
|
+
sha256: sha256File(destPath),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
fs.mkdirSync(path.join(distDir, ASSET_DIR_NAME), { recursive: true });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
241
|
+
return manifest.assets.length;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function main() {
|
|
245
|
+
const distDir = path.join(projectRoot, 'dist');
|
|
246
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
247
|
+
|
|
248
|
+
const exportDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pa-codegen-export-'));
|
|
249
|
+
try {
|
|
250
|
+
console.log(`\n[package] Step 1: expo export (${platform})`);
|
|
251
|
+
await runExpoExport(resolveExpoBin(), exportDir);
|
|
252
|
+
|
|
253
|
+
console.log('[package] Step 2: locate + stage Hermes bundle');
|
|
254
|
+
const { file, isHermes } = locateBundle(exportDir);
|
|
255
|
+
const outBundle = path.join(distDir, bundleName);
|
|
256
|
+
if (isHermes) {
|
|
257
|
+
fs.copyFileSync(file, outBundle);
|
|
258
|
+
} else {
|
|
259
|
+
console.log('[package] plain JS bundle exported; compiling with hermesc');
|
|
260
|
+
execFileSync(resolveHermesc(), ['-emit-binary', '-out', outBundle, file], {
|
|
261
|
+
stdio: 'inherit',
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
if (!isHermesBytecode(outBundle)) {
|
|
265
|
+
throw new Error(`Staged bundle is not Hermes bytecode: ${outBundle}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
console.log('[package] Step 3: build asset package + manifest');
|
|
269
|
+
const count = buildAssetPackage(exportDir, distDir, outBundle);
|
|
270
|
+
|
|
271
|
+
console.log(
|
|
272
|
+
`\n[package] Done. Bundle: "${outBundle}"; assets: ${count}; ` +
|
|
273
|
+
`manifest: "${path.join(distDir, ASSET_DIR_NAME, 'manifest.json')}"`,
|
|
274
|
+
);
|
|
275
|
+
} finally {
|
|
276
|
+
fs.rmSync(exportDir, { recursive: true, force: true });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
main().catch((error) => {
|
|
281
|
+
console.error(error);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
});
|
package/scripts/start-web.mjs
CHANGED
|
@@ -7,7 +7,9 @@ import process from 'node:process';
|
|
|
7
7
|
|
|
8
8
|
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
9
9
|
const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
10
|
-
const
|
|
10
|
+
const localAppPort = process.env.POWER_APPS_WEB_APP_PORT?.trim() || '8082';
|
|
11
|
+
const playerPort = process.env.POWER_APPS_WEB_PLAYER_PORT?.trim() || '8080';
|
|
12
|
+
const localAppUrl = `http://localhost:${localAppPort}`;
|
|
11
13
|
const children = new Set();
|
|
12
14
|
let shuttingDown = false;
|
|
13
15
|
|
|
@@ -106,7 +108,9 @@ function captureLocalPlayUrl(child, environmentId) {
|
|
|
106
108
|
child.stdout.removeAllListeners('data');
|
|
107
109
|
child.stdout.pipe(process.stdout);
|
|
108
110
|
void waitForApp()
|
|
109
|
-
.then(() =>
|
|
111
|
+
.then(() => {
|
|
112
|
+
if (process.env.POWER_APPS_WEB_NO_OPEN !== 'true') openBrowser(playUrl.href);
|
|
113
|
+
})
|
|
110
114
|
.catch((error) => {
|
|
111
115
|
console.error(error instanceof Error ? error.message : String(error));
|
|
112
116
|
shutdown(1);
|
|
@@ -128,10 +132,10 @@ const schemaProcess = spawn(npmCommand, ['run', 'generate-schemas'], {
|
|
|
128
132
|
const schemaExitCode = await new Promise((resolve) => schemaProcess.once('exit', resolve));
|
|
129
133
|
if (schemaExitCode !== 0) process.exit(schemaExitCode ?? 1);
|
|
130
134
|
|
|
131
|
-
run(npxCommand, ['expo', 'start', '--port',
|
|
135
|
+
run(npxCommand, ['expo', 'start', '--port', localAppPort, '--localhost']);
|
|
132
136
|
const powerApps = run(
|
|
133
137
|
npxCommand,
|
|
134
|
-
['
|
|
138
|
+
['pa', 'app', 'run', '--config-only', '--port', playerPort, '--local-app-url', localAppUrl],
|
|
135
139
|
{ stdio: ['inherit', 'pipe', 'inherit'] }
|
|
136
140
|
);
|
|
137
|
-
captureLocalPlayUrl(powerApps, powerConfig.environmentId);
|
|
141
|
+
captureLocalPlayUrl(powerApps, powerConfig.environmentId);
|