@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.
Files changed (62) hide show
  1. package/README.md +1 -9
  2. package/compatibility/current.json +31 -3
  3. package/lib/NOTICE.txt +1323 -16
  4. package/lib/commonjs/auth/AuthContext.js +36 -15
  5. package/lib/commonjs/config/babelConfig.js +13 -0
  6. package/lib/commonjs/config/expoConfig.js +71 -0
  7. package/lib/commonjs/config/metroConfig.js +70 -0
  8. package/lib/commonjs/config/metroLogger.js +182 -0
  9. package/lib/commonjs/config/tamaguiConfig.js +98 -0
  10. package/lib/commonjs/connections/resolveConnections.js +3 -2
  11. package/lib/commonjs/connections/useConnectionRefs.js +3 -1
  12. package/lib/commonjs/index.js +1 -0
  13. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +3 -1
  14. package/lib/commonjs/platform/DataverseDataSources.js +4 -0
  15. package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +45 -0
  16. package/lib/commonjs/provider/PowerAppsHostProvider.js +1 -0
  17. package/lib/commonjs/provider/PowerAppsProvider.js +10 -5
  18. package/lib/commonjs/provider/SessionDetailsGesture.js +1 -9
  19. package/lib/module/auth/AuthContext.js +36 -15
  20. package/lib/module/config/babelConfig.js +9 -0
  21. package/lib/module/config/expoConfig.js +67 -0
  22. package/lib/module/config/metroConfig.js +65 -0
  23. package/{scripts/metro-logger.js → lib/module/config/metroLogger.js} +58 -96
  24. package/lib/module/config/tamaguiConfig.js +93 -0
  25. package/lib/module/connections/resolveConnections.js +3 -2
  26. package/lib/module/connections/useConnectionRefs.js +3 -1
  27. package/lib/module/index.js +4 -0
  28. package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +3 -1
  29. package/lib/module/platform/DataverseDataSources.js +3 -0
  30. package/lib/module/platform/DataverseGlobalDiscoveryApi.js +44 -0
  31. package/lib/module/provider/PowerAppsHostProvider.js +2 -1
  32. package/lib/module/provider/PowerAppsProvider.js +10 -5
  33. package/lib/module/provider/SessionDetailsGesture.js +1 -9
  34. package/lib/typescript/commonjs/config/babelConfig.d.ts +12 -0
  35. package/lib/typescript/commonjs/config/expoConfig.d.ts +21 -0
  36. package/lib/typescript/commonjs/config/metroConfig.d.ts +6 -0
  37. package/lib/typescript/commonjs/config/metroLogger.d.ts +21 -0
  38. package/lib/typescript/commonjs/config/tamaguiConfig.d.ts +1792 -0
  39. package/lib/typescript/commonjs/connections/resolveConnections.d.ts +3 -1
  40. package/lib/typescript/commonjs/connections/useConnectionRefs.d.ts +3 -1
  41. package/lib/typescript/commonjs/index.d.ts +2 -1
  42. package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +1 -0
  43. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +27 -4
  44. package/lib/typescript/module/config/babelConfig.d.ts +12 -0
  45. package/lib/typescript/module/config/expoConfig.d.ts +21 -0
  46. package/lib/typescript/module/config/metroConfig.d.ts +6 -0
  47. package/lib/typescript/module/config/metroLogger.d.ts +21 -0
  48. package/lib/typescript/module/config/tamaguiConfig.d.ts +1792 -0
  49. package/lib/typescript/module/connections/resolveConnections.d.ts +3 -1
  50. package/lib/typescript/module/connections/useConnectionRefs.d.ts +3 -1
  51. package/lib/typescript/module/index.d.ts +2 -1
  52. package/lib/typescript/module/platform/DataverseDataSources.d.ts +1 -0
  53. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +27 -4
  54. package/package.json +47 -13
  55. package/scripts/build-codegen-package.js +283 -0
  56. package/scripts/start-web.mjs +9 -5
  57. package/scripts/upgrade/runtime.js +126 -17
  58. package/scripts/upgrade-authoring.js +274 -179
  59. package/scripts/upgrade.js +10 -16
  60. package/src/config/generated-config.d.ts +4 -0
  61. package/src/config/index.d.ts +3 -0
  62. 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 downward swipe.
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` from app.json.
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.0",
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": "./lib/typescript/commonjs/index.d.ts",
15
+ "types": "./src/config/index.d.ts",
16
16
  "exports": {
17
17
  ".": {
18
18
  "browser": {
19
- "types": "./lib/typescript/commonjs/index.d.ts",
20
- "default": "./lib/commonjs/index.js"
19
+ "types": "./src/config/index.d.ts",
20
+ "default": "./lib/module/index.js"
21
21
  },
22
22
  "import": {
23
- "types": "./lib/typescript/module/index.d.ts",
23
+ "types": "./src/config/index.d.ts",
24
24
  "default": "./lib/module/index.js"
25
25
  },
26
26
  "require": {
27
- "types": "./lib/typescript/commonjs/index.d.ts",
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
- "./metro-logger": "./scripts/metro-logger.js",
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
- "@microsoft/power-apps-native-auth": "^0.3.6",
60
- "@microsoft/power-apps-native-common": "^0.2.0",
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": false,
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
+ });
@@ -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 localAppUrl = 'http://localhost:8082';
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(() => openBrowser(playUrl.href))
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', '8082', '--localhost']);
135
+ run(npxCommand, ['expo', 'start', '--port', localAppPort, '--localhost']);
132
136
  const powerApps = run(
133
137
  npxCommand,
134
- ['power-apps', 'run', '--port', '8080', '--local-app-url', localAppUrl],
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);