@microsoft/power-apps-native-host 0.2.22 → 0.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/README.md +162 -26
- package/compatibility/current.json +87 -0
- package/lib/NOTICE.txt +140 -1226
- package/lib/commonjs/auth/AuthContext.js +74 -18
- package/lib/commonjs/auth/AuthContext.web.js +32 -0
- package/lib/commonjs/auth/completePowerAppsAuthSession.js +16 -0
- package/lib/commonjs/bridge/NativePowerAppsBridge.js +20 -17
- package/lib/commonjs/bridge/PowerPagesBridge.js +157 -0
- package/lib/commonjs/bridge/bridgeState.js +40 -0
- package/lib/commonjs/connections/ConnectionSetupScreen.js +459 -80
- package/lib/commonjs/connections/PowerPlatformConnectionApi.js +946 -0
- package/lib/commonjs/connections/connectionMetadata.js +127 -0
- package/lib/commonjs/connections/connectionState.js +96 -0
- package/lib/commonjs/connections/disambiguate.js +19 -11
- package/lib/commonjs/connections/resolveConnections.js +347 -0
- package/lib/commonjs/connections/types.js +106 -0
- package/lib/commonjs/{platform → connections}/useConnectionRefs.js +128 -65
- package/lib/commonjs/connections/useConnectionSetup.js +111 -52
- package/lib/commonjs/controls/FilePicker.js +6 -0
- package/lib/commonjs/controls/ImagePicker.js +8 -0
- package/lib/commonjs/index.js +188 -6
- package/lib/commonjs/logger.js +9 -6
- package/lib/commonjs/offline/createOfflineHostActivationCallbacks.js +61 -0
- package/lib/commonjs/offline/useOfflineIntegration.js +12 -0
- package/lib/commonjs/offline/useOfflineIntegration.web.js +11 -0
- package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +46 -0
- package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.web.js +13 -0
- package/lib/commonjs/platform/DataverseDataSources.js +14 -0
- package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +49 -0
- package/lib/commonjs/platform/GlobalDiscovery.js +51 -0
- package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +6 -106
- package/lib/commonjs/provider/PowerAppsHostProvider.js +156 -25
- package/lib/commonjs/provider/PowerAppsHostProvider.web.js +41 -0
- package/lib/commonjs/provider/PowerAppsProvider.js +229 -133
- package/lib/commonjs/provider/SessionDetailsGesture.js +216 -0
- package/lib/commonjs/telemetry/OfflineSdkTelemetry.js +95 -0
- package/lib/commonjs/telemetry/OfflineSdkTelemetry.web.js +9 -0
- package/lib/commonjs/telemetry/index.js +22 -3
- package/lib/module/auth/AuthContext.js +74 -18
- package/lib/module/auth/AuthContext.web.js +26 -0
- package/lib/module/auth/completePowerAppsAuthSession.js +11 -0
- package/lib/module/bridge/NativePowerAppsBridge.js +21 -18
- package/lib/module/bridge/PowerPagesBridge.js +151 -0
- package/lib/module/bridge/bridgeState.js +34 -0
- package/lib/module/connections/ConnectionSetupScreen.js +460 -81
- package/lib/module/connections/PowerPlatformConnectionApi.js +932 -0
- package/lib/module/connections/connectionMetadata.js +112 -0
- package/lib/module/connections/connectionState.js +82 -0
- package/lib/module/connections/disambiguate.js +19 -12
- package/lib/module/connections/resolveConnections.js +342 -0
- package/lib/module/connections/types.js +91 -0
- package/lib/module/{platform → connections}/useConnectionRefs.js +129 -66
- package/lib/module/connections/useConnectionSetup.js +113 -54
- package/lib/module/controls/FilePicker.js +6 -0
- package/lib/module/controls/ImagePicker.js +8 -0
- package/lib/module/index.js +14 -6
- package/lib/module/logger.js +8 -6
- package/lib/module/offline/createOfflineHostActivationCallbacks.js +55 -0
- package/lib/module/offline/useOfflineIntegration.js +3 -0
- package/lib/module/offline/useOfflineIntegration.web.js +7 -0
- package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +42 -0
- package/lib/module/offline/useOfflineIntegrationWithStartupGate.web.js +9 -0
- package/lib/module/platform/DataverseDataSources.js +10 -0
- package/lib/module/platform/DataverseGlobalDiscoveryApi.js +45 -0
- package/lib/module/platform/GlobalDiscovery.js +45 -0
- package/lib/module/platform/PowerPlatformEnvironmentApi.js +5 -104
- package/lib/module/provider/PowerAppsHostProvider.js +159 -28
- package/lib/module/provider/PowerAppsHostProvider.web.js +36 -0
- package/lib/module/provider/PowerAppsProvider.js +233 -137
- package/lib/module/provider/SessionDetailsGesture.js +211 -0
- package/lib/module/telemetry/OfflineSdkTelemetry.js +91 -0
- package/lib/module/telemetry/OfflineSdkTelemetry.web.js +5 -0
- package/lib/module/telemetry/index.js +15 -4
- package/lib/typescript/commonjs/auth/AuthContext.d.ts +3 -1
- package/lib/typescript/commonjs/auth/AuthContext.web.d.ts +21 -0
- package/lib/typescript/commonjs/auth/completePowerAppsAuthSession.d.ts +8 -0
- package/lib/typescript/commonjs/bridge/PowerPagesBridge.d.ts +18 -0
- package/lib/typescript/commonjs/bridge/bridgeState.d.ts +5 -0
- package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +28 -7
- package/lib/typescript/commonjs/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
- package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +66 -0
- package/lib/typescript/commonjs/connections/connectionState.d.ts +16 -0
- package/lib/typescript/commonjs/connections/disambiguate.d.ts +4 -2
- package/lib/typescript/commonjs/{platform → connections}/resolveConnections.d.ts +19 -19
- package/lib/typescript/commonjs/connections/types.d.ts +129 -1
- package/lib/typescript/commonjs/{platform → connections}/useConnectionRefs.d.ts +33 -24
- package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +12 -8
- package/lib/typescript/commonjs/controls/FilePicker.d.ts +7 -1
- package/lib/typescript/commonjs/controls/ImagePicker.d.ts +9 -1
- package/lib/typescript/commonjs/index.d.ts +20 -10
- package/lib/typescript/commonjs/logger.d.ts +4 -4
- package/lib/typescript/commonjs/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegration.d.ts +2 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegration.web.d.ts +4 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
- package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +27 -0
- package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
- package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +4 -0
- package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
- package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +29 -18
- package/lib/typescript/commonjs/provider/PowerAppsHostProvider.web.d.ts +5 -0
- package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +32 -10
- package/lib/typescript/commonjs/provider/SessionDetailsGesture.d.ts +13 -0
- package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.d.ts +15 -0
- package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
- package/lib/typescript/commonjs/telemetry/index.d.ts +13 -1
- package/lib/typescript/module/auth/AuthContext.d.ts +3 -1
- package/lib/typescript/module/auth/AuthContext.web.d.ts +21 -0
- package/lib/typescript/module/auth/completePowerAppsAuthSession.d.ts +8 -0
- package/lib/typescript/module/bridge/PowerPagesBridge.d.ts +18 -0
- package/lib/typescript/module/bridge/bridgeState.d.ts +5 -0
- package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +28 -7
- package/lib/typescript/module/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
- package/lib/typescript/module/connections/connectionMetadata.d.ts +66 -0
- package/lib/typescript/module/connections/connectionState.d.ts +16 -0
- package/lib/typescript/module/connections/disambiguate.d.ts +4 -2
- package/lib/typescript/module/{platform → connections}/resolveConnections.d.ts +19 -19
- package/lib/typescript/module/connections/types.d.ts +129 -1
- package/lib/typescript/module/{platform → connections}/useConnectionRefs.d.ts +33 -24
- package/lib/typescript/module/connections/useConnectionSetup.d.ts +12 -8
- package/lib/typescript/module/controls/FilePicker.d.ts +7 -1
- package/lib/typescript/module/controls/ImagePicker.d.ts +9 -1
- package/lib/typescript/module/index.d.ts +24 -14
- package/lib/typescript/module/logger.d.ts +4 -4
- package/lib/typescript/module/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
- package/lib/typescript/module/offline/useOfflineIntegration.d.ts +2 -0
- package/lib/typescript/module/offline/useOfflineIntegration.web.d.ts +4 -0
- package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
- package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
- package/lib/typescript/module/platform/DataverseDataSources.d.ts +27 -0
- package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
- package/lib/typescript/module/platform/GlobalDiscovery.d.ts +4 -0
- package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
- package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +29 -18
- package/lib/typescript/module/provider/PowerAppsHostProvider.web.d.ts +5 -0
- package/lib/typescript/module/provider/PowerAppsProvider.d.ts +34 -12
- package/lib/typescript/module/provider/SessionDetailsGesture.d.ts +13 -0
- package/lib/typescript/module/telemetry/OfflineSdkTelemetry.d.ts +15 -0
- package/lib/typescript/module/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
- package/lib/typescript/module/telemetry/index.d.ts +13 -1
- package/package.json +43 -18
- package/scripts/build-ios-base.js +66 -27
- package/scripts/bundle.js +212 -5
- package/scripts/generate-connector-schemas.js +8 -6
- package/scripts/metro-logger.js +213 -0
- package/scripts/start-web.mjs +137 -0
- package/scripts/upgrade/runtime.js +463 -0
- package/scripts/upgrade/system.js +244 -0
- package/scripts/upgrade-authoring.js +534 -0
- package/scripts/upgrade.js +188 -0
- package/scripts/wrap.js +536 -0
- package/lib/commonjs/platform/PowerPlatformConnectionApi.js +0 -606
- package/lib/commonjs/platform/resolveConnections.js +0 -264
- package/lib/module/platform/PowerPlatformConnectionApi.js +0 -595
- package/lib/module/platform/resolveConnections.js +0 -259
package/scripts/bundle.js
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Cross-platform
|
|
3
|
+
* Cross-platform Expo bundle script.
|
|
4
4
|
*
|
|
5
5
|
* Runs two steps:
|
|
6
6
|
* 1. expo export:embed — produces a plain Metro JS bundle
|
|
7
|
-
* 2. hermesc — compiles
|
|
7
|
+
* 2. hermesc — compiles native bundles to Hermes bytecode (.hbc)
|
|
8
8
|
*
|
|
9
9
|
* Usage (via npm script in the consuming expo-app):
|
|
10
10
|
* bundle android → dist/index.android.bundle.hbc
|
|
11
11
|
* bundle ios → dist/main.jsbundle.hbc
|
|
12
|
+
* bundle web → dist-web/ (Code Apps static site)
|
|
13
|
+
* bundle web powerpages → dist-web/ (Power Pages static site)
|
|
12
14
|
*
|
|
13
15
|
* Works on macOS, Linux, and Windows.
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
18
|
'use strict';
|
|
17
19
|
|
|
18
|
-
const { execFileSync } = require('child_process');
|
|
20
|
+
const { execFileSync, spawn } = require('child_process');
|
|
19
21
|
const path = require('path');
|
|
20
22
|
const fs = require('fs');
|
|
21
23
|
|
|
22
24
|
const platform = process.argv[2];
|
|
23
|
-
if (platform !== 'android' && platform !== 'ios') {
|
|
24
|
-
console.error('Usage: bundle <android|ios>');
|
|
25
|
+
if (platform !== 'android' && platform !== 'ios' && platform !== 'web') {
|
|
26
|
+
console.error('Usage: bundle <android|ios|web> [powerpages]');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const webTarget = process.argv[3] ?? 'codeapps';
|
|
31
|
+
if (platform === 'web' && webTarget !== 'codeapps' && webTarget !== 'powerpages') {
|
|
32
|
+
console.error('Usage: bundle web [powerpages]');
|
|
25
33
|
process.exit(1);
|
|
26
34
|
}
|
|
27
35
|
|
|
@@ -32,6 +40,204 @@ const isLinux = process.platform === 'linux';
|
|
|
32
40
|
const projectRoot = process.cwd();
|
|
33
41
|
const workspaceRoot = path.resolve(projectRoot, '../..');
|
|
34
42
|
|
|
43
|
+
function validatePowerPagesDataSources() {
|
|
44
|
+
const configPath = path.join(projectRoot, 'power.config.json');
|
|
45
|
+
const powerConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
46
|
+
const unsupportedConnectors = Object.values(powerConfig.connectionReferences ?? {})
|
|
47
|
+
.filter((reference) => {
|
|
48
|
+
const connectorName = String(reference.id ?? '').split('/').pop()?.toLowerCase();
|
|
49
|
+
return connectorName !== 'shared_commondataserviceforapps';
|
|
50
|
+
})
|
|
51
|
+
.map((reference) => reference.displayName || reference.id || '<unknown>');
|
|
52
|
+
|
|
53
|
+
if (unsupportedConnectors.length > 0) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Power Pages bundles support Dataverse only. Remove these connector references: ${unsupportedConnectors.join(', ')}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function findFiles(directory, predicate) {
|
|
61
|
+
return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
62
|
+
const entryPath = path.join(directory, entry.name);
|
|
63
|
+
return entry.isDirectory()
|
|
64
|
+
? findFiles(entryPath, predicate)
|
|
65
|
+
: predicate(entryPath) ? [entryPath] : [];
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function prepareHostedWebOutput(outputDir) {
|
|
70
|
+
const hostedFiles = findFiles(
|
|
71
|
+
outputDir,
|
|
72
|
+
(filePath) => /\.(?:html|js|css)$/.test(filePath),
|
|
73
|
+
);
|
|
74
|
+
for (const hostedFile of hostedFiles) {
|
|
75
|
+
const contents = fs.readFileSync(hostedFile, 'utf8');
|
|
76
|
+
const relativeAssetContents = contents.replace(
|
|
77
|
+
/(["'`])\/(assets|_expo)\//g,
|
|
78
|
+
'$1./$2/',
|
|
79
|
+
);
|
|
80
|
+
if (relativeAssetContents !== contents) {
|
|
81
|
+
fs.writeFileSync(hostedFile, relativeAssetContents);
|
|
82
|
+
}
|
|
83
|
+
if (/(["'`])\/(assets|_expo)\//.test(relativeAssetContents)) {
|
|
84
|
+
throw new Error(`Hosted web output contains a root-relative asset URL: ${hostedFile}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function prepareHostedSpaOutput(outputDir, isPowerPages) {
|
|
90
|
+
const entryBundles = findFiles(
|
|
91
|
+
outputDir,
|
|
92
|
+
(filePath) => /[/\\]_expo[/\\]static[/\\]js[/\\]web[/\\]entry-[^/\\]+\.js$/.test(filePath),
|
|
93
|
+
);
|
|
94
|
+
if (entryBundles.length !== 1) {
|
|
95
|
+
throw new Error(`Expected one hosted web entry bundle, found ${entryBundles.length}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const entryContents = fs.readFileSync(entryBundles[0], 'utf8');
|
|
99
|
+
const reactRuntimeCount = (entryContents.match(/react\.production\.js/g) ?? []).length;
|
|
100
|
+
if (reactRuntimeCount > 1) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Hosted web bundle contains ${reactRuntimeCount} React runtimes. Configure Metro to resolve react and react/* from one node_modules path.`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const routerContextCount = (
|
|
106
|
+
entryContents.match(/No filename found\. This is likely a bug in expo-router\./g) ?? []
|
|
107
|
+
).length;
|
|
108
|
+
if (routerContextCount > 1) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Hosted web bundle contains ${routerContextCount} Expo Router contexts. Build from the canonical workspace path instead of a symlinked project root.`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const entryUrl = `/${path.relative(outputDir, entryBundles[0]).split(path.sep).join('/')}`;
|
|
115
|
+
const htmlFiles = findFiles(outputDir, (filePath) => filePath.endsWith('.html'));
|
|
116
|
+
for (const htmlFile of htmlFiles) {
|
|
117
|
+
fs.rmSync(htmlFile);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fs.writeFileSync(
|
|
121
|
+
path.join(outputDir, 'index.html'),
|
|
122
|
+
`<!DOCTYPE html>
|
|
123
|
+
<html lang="en">
|
|
124
|
+
<head>
|
|
125
|
+
<meta charset="utf-8">
|
|
126
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
127
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
128
|
+
<style>#root,body,html{height:100%}body{margin:0;overflow:hidden}#root{display:flex}</style>
|
|
129
|
+
<script>
|
|
130
|
+
if (location.pathname.includes('/powerapps/appruntime/')) {
|
|
131
|
+
const assetBase = document.createElement('base');
|
|
132
|
+
assetBase.href = new URL('.', location.href).href;
|
|
133
|
+
document.head.appendChild(assetBase);
|
|
134
|
+
history.replaceState(history.state, '', '/' + location.search + location.hash);
|
|
135
|
+
}
|
|
136
|
+
</script>
|
|
137
|
+
</head>
|
|
138
|
+
<body>
|
|
139
|
+
<div id="root"></div>
|
|
140
|
+
<script src="${entryUrl}" defer></script>
|
|
141
|
+
</body>
|
|
142
|
+
</html>
|
|
143
|
+
`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function runExpoExport(expoBin, args, outputDir, env) {
|
|
148
|
+
const completionMarker = `Exported: ${outputDir}`;
|
|
149
|
+
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
const child = spawn(expoBin, args, {
|
|
152
|
+
cwd: projectRoot,
|
|
153
|
+
env,
|
|
154
|
+
shell: isWin,
|
|
155
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
156
|
+
});
|
|
157
|
+
let outputTail = '';
|
|
158
|
+
let exportCompleted = false;
|
|
159
|
+
|
|
160
|
+
const forwardOutput = (chunk, destination) => {
|
|
161
|
+
destination.write(chunk);
|
|
162
|
+
outputTail = (outputTail + chunk).slice(-completionMarker.length * 2);
|
|
163
|
+
if (!exportCompleted && outputTail.includes(completionMarker)) {
|
|
164
|
+
exportCompleted = true;
|
|
165
|
+
if (isWin && child.pid) {
|
|
166
|
+
try {
|
|
167
|
+
execFileSync('taskkill', ['/pid', String(child.pid), '/t', '/f'], { stdio: 'ignore' });
|
|
168
|
+
} catch {
|
|
169
|
+
child.kill();
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
child.kill('SIGKILL');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
child.stdout.on('data', (chunk) => forwardOutput(chunk, process.stdout));
|
|
178
|
+
child.stderr.on('data', (chunk) => forwardOutput(chunk, process.stderr));
|
|
179
|
+
child.once('error', reject);
|
|
180
|
+
child.once('exit', (code, signal) => {
|
|
181
|
+
if (exportCompleted) {
|
|
182
|
+
resolve();
|
|
183
|
+
} else {
|
|
184
|
+
reject(new Error(
|
|
185
|
+
code === 0
|
|
186
|
+
? `Expo export exited before reporting completion: ${completionMarker}`
|
|
187
|
+
: `Expo export failed with ${signal ? `signal ${signal}` : `exit code ${code}`}`,
|
|
188
|
+
));
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function bundleWeb() {
|
|
195
|
+
const isPowerPages = webTarget === 'powerpages';
|
|
196
|
+
if (isPowerPages) {
|
|
197
|
+
validatePowerPagesDataSources();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const outputDir = path.join(projectRoot, 'dist-web');
|
|
201
|
+
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
202
|
+
console.log(`\n[bundle] Expo static export (${isPowerPages ? 'Power Pages' : 'Code Apps'})`);
|
|
203
|
+
|
|
204
|
+
const expoBinName = isWin ? 'expo.cmd' : 'expo';
|
|
205
|
+
const expoBinLocal = path.join(projectRoot, 'node_modules', '.bin', expoBinName);
|
|
206
|
+
const expoBinHoisted = path.join(workspaceRoot, 'node_modules', '.bin', expoBinName);
|
|
207
|
+
const expoBin = fs.existsSync(expoBinLocal) ? expoBinLocal : expoBinHoisted;
|
|
208
|
+
|
|
209
|
+
await runExpoExport(
|
|
210
|
+
expoBin,
|
|
211
|
+
['export', '--platform', 'web', '--output-dir', outputDir, '--clear'],
|
|
212
|
+
outputDir,
|
|
213
|
+
{
|
|
214
|
+
...process.env,
|
|
215
|
+
CI: process.env.CI ?? '1',
|
|
216
|
+
EXPO_NO_TELEMETRY: '1',
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
if (isPowerPages) {
|
|
221
|
+
prepareHostedSpaOutput(outputDir, true);
|
|
222
|
+
} else {
|
|
223
|
+
prepareHostedSpaOutput(outputDir, false);
|
|
224
|
+
prepareHostedWebOutput(outputDir);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log(`\n[bundle] Done. ${isPowerPages ? 'Power Pages' : 'Code Apps'} site: "${outputDir}"`);
|
|
228
|
+
process.exit(0);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (platform === 'web') {
|
|
232
|
+
bundleWeb().catch((error) => {
|
|
233
|
+
console.error(error);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
});
|
|
236
|
+
} else {
|
|
237
|
+
bundleNative();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function bundleNative() {
|
|
35
241
|
const distDir = path.join(projectRoot, 'dist');
|
|
36
242
|
fs.mkdirSync(distDir, { recursive: true });
|
|
37
243
|
|
|
@@ -93,3 +299,4 @@ const hermesc = fs.existsSync(path.join(hcDir, binDir, binName))
|
|
|
93
299
|
execFileSync(hermesc, ['-emit-binary', '-out', outputFile, bundleFile], { stdio: 'inherit' });
|
|
94
300
|
|
|
95
301
|
console.log(`\n[bundle] Done. Output: "${outputFile}"`);
|
|
302
|
+
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* generate-connector-schemas
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Customer-invoked generator for src/generated/connectorSchemas.ts. Scans
|
|
6
|
+
* .power/schemas/ for
|
|
6
7
|
* connector schema JSON files placed there by `pac code add-data-source`.
|
|
7
8
|
*
|
|
8
9
|
* Run from the root of your Expo app (where .power/schemas/ lives):
|
|
@@ -11,9 +12,9 @@
|
|
|
11
12
|
* Or wire it into package.json scripts:
|
|
12
13
|
* "prestart": "generate-connector-schemas"
|
|
13
14
|
*
|
|
14
|
-
* The generated file is
|
|
15
|
-
*
|
|
16
|
-
* compile time.
|
|
15
|
+
* The generated file is customer-owned output: upgrade-template never changes
|
|
16
|
+
* it. Re-run this command after adding or removing connectors, and commit the
|
|
17
|
+
* result so TypeScript can resolve connector schema types at compile time.
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
'use strict';
|
|
@@ -47,8 +48,9 @@ if (fs.existsSync(schemasDir)) {
|
|
|
47
48
|
console.warn('[generate-connector-schemas] .power/schemas/ not found — emitting empty schemaMap.');
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
const content = `// AUTO-GENERATED — do not edit manually.
|
|
51
|
-
//
|
|
51
|
+
const content = `// AUTO-GENERATED CUSTOMER FILE — do not edit manually.
|
|
52
|
+
// Run \`npm run generate-schemas\` after adding or removing connectors.
|
|
53
|
+
// This file is never modified by upgrade-template.
|
|
52
54
|
// To add a connector: pac code add-data-source (no further changes needed).
|
|
53
55
|
// Generator: @microsoft/power-apps-native-host/scripts/generate-connector-schemas.js
|
|
54
56
|
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const SENSITIVE_LINE_PATTERN = /\b(?:authorization|bearer|client[_-]?secret|password|token|access[_-]?token|refresh[_-]?token|id[_-]?token|api[_-]?key|accountkey|sharedaccesskey)\b|[?&](?:sig|se|sp|sv|token|access_token|code|client_secret)=|\b(?:AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{30,}|sk-[A-Za-z0-9]{20,})\b/i;
|
|
7
|
+
const DEFAULT_MAX_ERROR_BODY_BYTES = 256 * 1024;
|
|
8
|
+
|
|
9
|
+
const stateByRoot = new Map();
|
|
10
|
+
|
|
11
|
+
function stripAnsi(value) {
|
|
12
|
+
return String(value).replace(/[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d\/#&.:=?%@~_]+)*)?\u0007)|(?:(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g, '');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function redactMetroLog(value) {
|
|
16
|
+
return stripAnsi(value).replace(/[^\r\n]*(?:\r\n|\r|\n)|[^\r\n]+$/g, (line) => {
|
|
17
|
+
if (!SENSITIVE_LINE_PATTERN.test(line)) return line;
|
|
18
|
+
const ending = line.endsWith('\r\n') ? '\r\n' : line.endsWith('\n') ? '\n' : line.endsWith('\r') ? '\r' : '';
|
|
19
|
+
return `[powernative] [REDACTED_SENSITIVE_LINE]${ending}`;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function toError(value) {
|
|
24
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function writeDiagnostic(state, message) {
|
|
28
|
+
try {
|
|
29
|
+
state.diagnosticWrite(message);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
state.diagnosticWriteError = toError(error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function reportInstrumentationError(state, error, phase) {
|
|
36
|
+
if (state.instrumentationError) return;
|
|
37
|
+
const normalizedError = toError(error);
|
|
38
|
+
const context = { phase, logPath: state.logPath };
|
|
39
|
+
state.instrumentationError = { error: normalizedError, context };
|
|
40
|
+
|
|
41
|
+
if (typeof state.onError === 'function') {
|
|
42
|
+
try {
|
|
43
|
+
state.onError(normalizedError, context);
|
|
44
|
+
return;
|
|
45
|
+
} catch (callbackError) {
|
|
46
|
+
state.onErrorFailure = toError(callbackError);
|
|
47
|
+
writeDiagnostic(
|
|
48
|
+
state,
|
|
49
|
+
`[powernative] Metro logging instrumentation failed during ${phase}: ${normalizedError.message}; onError callback also failed: ${state.onErrorFailure.message}\n`,
|
|
50
|
+
);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
writeDiagnostic(
|
|
56
|
+
state,
|
|
57
|
+
`[powernative] Metro logging instrumentation failed during ${phase}: ${normalizedError.message}\n`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createMetroLogState(projectRoot, onError) {
|
|
62
|
+
const root = path.resolve(projectRoot || process.cwd());
|
|
63
|
+
const logDir = path.join(root, '.powernative', 'metro-logs');
|
|
64
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
65
|
+
const stderrWrite = process.stderr.write.__powerNativeMetroLoggerOriginal || process.stderr.write;
|
|
66
|
+
const state = {
|
|
67
|
+
root,
|
|
68
|
+
logDir,
|
|
69
|
+
stamp,
|
|
70
|
+
port: 'unknown',
|
|
71
|
+
logPath: path.join(logDir, `metro-${stamp}-pid-${process.pid}-port-unknown.log`),
|
|
72
|
+
onError: typeof onError === 'function' ? onError : null,
|
|
73
|
+
instrumentationError: null,
|
|
74
|
+
diagnosticWrite: stderrWrite.bind(process.stderr),
|
|
75
|
+
};
|
|
76
|
+
stateByRoot.set(root, state);
|
|
77
|
+
return state;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getMetroLogState(projectRoot, onError) {
|
|
81
|
+
const root = path.resolve(projectRoot || process.cwd());
|
|
82
|
+
const state = stateByRoot.get(root) || createMetroLogState(root, onError);
|
|
83
|
+
if (typeof onError === 'function') state.onError = onError;
|
|
84
|
+
return state;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function appendMetroLog(state, value) {
|
|
88
|
+
try {
|
|
89
|
+
fs.mkdirSync(state.logDir, { recursive: true });
|
|
90
|
+
fs.appendFileSync(state.logPath, redactMetroLog(value));
|
|
91
|
+
} catch (error) {
|
|
92
|
+
reportInstrumentationError(state, error, 'log persistence');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function setMetroLogPort(state, port) {
|
|
97
|
+
if (!Number.isInteger(port) || port <= 0 || String(port) === state.port) return;
|
|
98
|
+
const previousPath = state.logPath;
|
|
99
|
+
state.port = String(port);
|
|
100
|
+
state.logPath = path.join(state.logDir, `metro-${state.stamp}-pid-${process.pid}-port-${state.port}.log`);
|
|
101
|
+
try {
|
|
102
|
+
fs.mkdirSync(state.logDir, { recursive: true });
|
|
103
|
+
if (fs.existsSync(previousPath) && previousPath !== state.logPath) {
|
|
104
|
+
fs.renameSync(previousPath, state.logPath);
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
state.logPath = previousPath;
|
|
108
|
+
reportInstrumentationError(state, error, 'port-tagged log rename');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function extractMetroPort(value) {
|
|
113
|
+
const match = String(value).match(/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\]):(\d{2,5})/);
|
|
114
|
+
const port = match ? Number(match[1]) : null;
|
|
115
|
+
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function patchStream(stream, key, state) {
|
|
119
|
+
const originalWrite = stream.write;
|
|
120
|
+
if (typeof originalWrite !== 'function' || originalWrite.__powerNativeMetroLoggerPatched) return;
|
|
121
|
+
|
|
122
|
+
function patchedWrite(chunk, encoding, callback) {
|
|
123
|
+
try {
|
|
124
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk);
|
|
125
|
+
appendMetroLog(state, text);
|
|
126
|
+
setMetroLogPort(state, extractMetroPort(text));
|
|
127
|
+
} catch (error) {
|
|
128
|
+
reportInstrumentationError(state, error, `${key} capture`);
|
|
129
|
+
}
|
|
130
|
+
return originalWrite.call(stream, chunk, encoding, callback);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
patchedWrite.__powerNativeMetroLoggerPatched = true;
|
|
134
|
+
patchedWrite.__powerNativeMetroLoggerOriginal = originalWrite;
|
|
135
|
+
stream.write = patchedWrite;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function enablePowerNativeMetroLogging(options = {}) {
|
|
139
|
+
const state = getMetroLogState(options.projectRoot, options.onError);
|
|
140
|
+
try {
|
|
141
|
+
patchStream(process.stdout, 'stdout', state);
|
|
142
|
+
patchStream(process.stderr, 'stderr', state);
|
|
143
|
+
appendMetroLog(state, `[powernative] Metro log started pid=${process.pid}\n`);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
reportInstrumentationError(state, error, 'stream setup');
|
|
146
|
+
}
|
|
147
|
+
return { logDir: state.logDir, logPath: state.logPath };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function withPowerNativeMetroLogging(middleware, options = {}) {
|
|
151
|
+
if (typeof middleware !== 'function') return middleware;
|
|
152
|
+
const state = getMetroLogState(options.projectRoot, options.onError);
|
|
153
|
+
const maxBodyBytes = Number.isInteger(options.maxBodyBytes) && options.maxBodyBytes > 0
|
|
154
|
+
? options.maxBodyBytes
|
|
155
|
+
: DEFAULT_MAX_ERROR_BODY_BYTES;
|
|
156
|
+
enablePowerNativeMetroLogging(options);
|
|
157
|
+
|
|
158
|
+
return (req, res, next) => {
|
|
159
|
+
try {
|
|
160
|
+
setMetroLogPort(state, Number(req.socket && req.socket.localPort));
|
|
161
|
+
const chunks = [];
|
|
162
|
+
let capturedBytes = 0;
|
|
163
|
+
const write = res.write.bind(res);
|
|
164
|
+
const end = res.end.bind(res);
|
|
165
|
+
|
|
166
|
+
function captureChunk(chunk, encoding) {
|
|
167
|
+
if (!chunk || capturedBytes >= maxBodyBytes) return;
|
|
168
|
+
const buf = Buffer.isBuffer(chunk)
|
|
169
|
+
? chunk
|
|
170
|
+
: Buffer.from(String(chunk), typeof encoding === 'string' ? encoding : undefined);
|
|
171
|
+
const remaining = maxBodyBytes - capturedBytes;
|
|
172
|
+
if (buf.length > remaining) {
|
|
173
|
+
chunks.push(buf.subarray(0, remaining));
|
|
174
|
+
capturedBytes = maxBodyBytes;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
chunks.push(buf);
|
|
178
|
+
capturedBytes += buf.length;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
res.write = (chunk, encoding, callback) => {
|
|
182
|
+
if (typeof encoding === 'function') {
|
|
183
|
+
callback = encoding;
|
|
184
|
+
encoding = undefined;
|
|
185
|
+
}
|
|
186
|
+
captureChunk(chunk, encoding);
|
|
187
|
+
return write(chunk, encoding, callback);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
res.end = (chunk, encoding, callback) => {
|
|
191
|
+
if (typeof encoding === 'function') {
|
|
192
|
+
callback = encoding;
|
|
193
|
+
encoding = undefined;
|
|
194
|
+
}
|
|
195
|
+
captureChunk(chunk, encoding);
|
|
196
|
+
if (res.statusCode >= 400) {
|
|
197
|
+
const body = Buffer.concat(chunks).toString('utf8').slice(0, maxBodyBytes);
|
|
198
|
+
appendMetroLog(state, `\n[powernative] HTTP ${req.method} ${req.url} -> ${res.statusCode}\n${body}\n`);
|
|
199
|
+
}
|
|
200
|
+
return end(chunk, encoding, callback);
|
|
201
|
+
};
|
|
202
|
+
} catch (error) {
|
|
203
|
+
reportInstrumentationError(state, error, 'HTTP middleware setup');
|
|
204
|
+
}
|
|
205
|
+
return middleware(req, res, next);
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = {
|
|
210
|
+
enablePowerNativeMetroLogging,
|
|
211
|
+
redactMetroLog,
|
|
212
|
+
withPowerNativeMetroLogging,
|
|
213
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import process from 'node:process';
|
|
7
|
+
|
|
8
|
+
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
9
|
+
const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
10
|
+
const localAppUrl = 'http://localhost:8082';
|
|
11
|
+
const children = new Set();
|
|
12
|
+
let shuttingDown = false;
|
|
13
|
+
|
|
14
|
+
async function readPowerConfig() {
|
|
15
|
+
const configPath = path.resolve(process.cwd(), 'power.config.json');
|
|
16
|
+
let config;
|
|
17
|
+
try {
|
|
18
|
+
config = JSON.parse(await readFile(configPath, 'utf8'));
|
|
19
|
+
} catch (error) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Unable to read ${configPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (typeof config.environmentId !== 'string' || !config.environmentId.trim()) {
|
|
26
|
+
throw new Error(`environmentId is required in ${configPath}`);
|
|
27
|
+
}
|
|
28
|
+
return config;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function run(command, args, options = {}) {
|
|
32
|
+
const child = spawn(command, args, {
|
|
33
|
+
cwd: process.cwd(),
|
|
34
|
+
env: process.env,
|
|
35
|
+
stdio: 'inherit',
|
|
36
|
+
...options,
|
|
37
|
+
});
|
|
38
|
+
children.add(child);
|
|
39
|
+
child.once('exit', (code, signal) => {
|
|
40
|
+
children.delete(child);
|
|
41
|
+
if (!shuttingDown && (code !== 0 || signal)) {
|
|
42
|
+
shutdown(code ?? 1);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return child;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function shutdown(exitCode = 0) {
|
|
49
|
+
if (shuttingDown) return;
|
|
50
|
+
shuttingDown = true;
|
|
51
|
+
for (const child of children) {
|
|
52
|
+
child.kill('SIGTERM');
|
|
53
|
+
}
|
|
54
|
+
process.exitCode = exitCode;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function waitForApp(timeoutMs = 60_000) {
|
|
58
|
+
const deadline = Date.now() + timeoutMs;
|
|
59
|
+
while (Date.now() < deadline) {
|
|
60
|
+
try {
|
|
61
|
+
const response = await fetch(localAppUrl, {
|
|
62
|
+
headers: { Accept: 'text/html' },
|
|
63
|
+
});
|
|
64
|
+
if (response.ok) return;
|
|
65
|
+
} catch {
|
|
66
|
+
// Metro is still starting.
|
|
67
|
+
}
|
|
68
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Timed out waiting for ${localAppUrl}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function openBrowser(url) {
|
|
74
|
+
if (process.platform === 'darwin') {
|
|
75
|
+
spawn('open', [url], { detached: true, stdio: 'ignore' }).unref();
|
|
76
|
+
} else if (process.platform === 'win32') {
|
|
77
|
+
spawn('cmd.exe', ['/d', '/s', '/c', 'start', '', url], {
|
|
78
|
+
detached: true,
|
|
79
|
+
stdio: 'ignore',
|
|
80
|
+
}).unref();
|
|
81
|
+
} else {
|
|
82
|
+
spawn('xdg-open', [url], { detached: true, stdio: 'ignore' }).unref();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function captureLocalPlayUrl(child, environmentId) {
|
|
87
|
+
let output = '';
|
|
88
|
+
child.stdout.on('data', (chunk) => {
|
|
89
|
+
const text = chunk.toString();
|
|
90
|
+
process.stdout.write(text);
|
|
91
|
+
output += text;
|
|
92
|
+
|
|
93
|
+
const match = output.match(/https:\/\/[^\s]+_localAppUrl=[^\s]+/);
|
|
94
|
+
if (!match) return;
|
|
95
|
+
|
|
96
|
+
const playUrl = new URL(match[0]);
|
|
97
|
+
const environmentFromUrl = playUrl.pathname.match(/\/play\/e\/([^/]+)\//)?.[1];
|
|
98
|
+
if (decodeURIComponent(environmentFromUrl ?? '') !== environmentId) {
|
|
99
|
+
console.error(
|
|
100
|
+
`Local Play URL environment mismatch: expected ${environmentId}, received ${environmentFromUrl ?? 'none'}`
|
|
101
|
+
);
|
|
102
|
+
shutdown(1);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
child.stdout.removeAllListeners('data');
|
|
107
|
+
child.stdout.pipe(process.stdout);
|
|
108
|
+
void waitForApp()
|
|
109
|
+
.then(() => openBrowser(playUrl.href))
|
|
110
|
+
.catch((error) => {
|
|
111
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
112
|
+
shutdown(1);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
process.on('SIGINT', () => shutdown());
|
|
118
|
+
process.on('SIGTERM', () => shutdown());
|
|
119
|
+
|
|
120
|
+
const powerConfig = await readPowerConfig();
|
|
121
|
+
console.log(`Starting Power Apps Local Play for environment ${powerConfig.environmentId}`);
|
|
122
|
+
|
|
123
|
+
const schemaProcess = spawn(npmCommand, ['run', 'generate-schemas'], {
|
|
124
|
+
cwd: process.cwd(),
|
|
125
|
+
env: process.env,
|
|
126
|
+
stdio: 'inherit',
|
|
127
|
+
});
|
|
128
|
+
const schemaExitCode = await new Promise((resolve) => schemaProcess.once('exit', resolve));
|
|
129
|
+
if (schemaExitCode !== 0) process.exit(schemaExitCode ?? 1);
|
|
130
|
+
|
|
131
|
+
run(npxCommand, ['expo', 'start', '--port', '8082', '--localhost']);
|
|
132
|
+
const powerApps = run(
|
|
133
|
+
npxCommand,
|
|
134
|
+
['power-apps', 'run', '--port', '8080', '--local-app-url', localAppUrl],
|
|
135
|
+
{ stdio: ['inherit', 'pipe', 'inherit'] }
|
|
136
|
+
);
|
|
137
|
+
captureLocalPlayUrl(powerApps, powerConfig.environmentId);
|