@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
|
@@ -8,14 +8,28 @@ const { applyPatch, formatPatch, parsePatch, reversePatch } = require('diff');
|
|
|
8
8
|
const HOST_PACKAGE = '@microsoft/power-apps-native-host';
|
|
9
9
|
const TEMPLATE_VERSION_PATH = 'app.json';
|
|
10
10
|
const TSCONFIG_PATH = 'tsconfig.json';
|
|
11
|
+
const GITIGNORE_PATH = '.gitignore';
|
|
12
|
+
const GITIGNORE_START = '# POWER APPS MANAGED START - DO NOT REMOVE OR RENAME THE COMMENT';
|
|
13
|
+
const GITIGNORE_END = '# POWER APPS MANAGED END - DO NOT REMOVE OR RENAME THE COMMENT';
|
|
11
14
|
const TEMPLATE_MANAGED_PATHS = [
|
|
15
|
+
GITIGNORE_PATH,
|
|
12
16
|
TEMPLATE_VERSION_PATH,
|
|
13
17
|
'app.config.js',
|
|
18
|
+
'babel.config.js',
|
|
19
|
+
'metro.config.js',
|
|
14
20
|
'package-lock.json',
|
|
15
21
|
'package.json',
|
|
22
|
+
'power-apps-env.d.ts',
|
|
23
|
+
'tamagui.config.ts',
|
|
16
24
|
TSCONFIG_PATH,
|
|
17
25
|
];
|
|
18
|
-
const UNIFIED_PATCH_MANAGED_PATHS = [
|
|
26
|
+
const UNIFIED_PATCH_MANAGED_PATHS = [
|
|
27
|
+
'app.config.js',
|
|
28
|
+
'babel.config.js',
|
|
29
|
+
'metro.config.js',
|
|
30
|
+
'power-apps-env.d.ts',
|
|
31
|
+
'tamagui.config.ts',
|
|
32
|
+
];
|
|
19
33
|
|
|
20
34
|
function isTemplateManagedPath(relativePath) {
|
|
21
35
|
return TEMPLATE_MANAGED_PATHS.includes(relativePath);
|
|
@@ -151,10 +165,21 @@ function patchPackageJson(source, profile, hostVersion, transitions = null) {
|
|
|
151
165
|
packageJson.dependencies ||= {};
|
|
152
166
|
setManagedValues(packageJson, { expo: profile.expoVersion }, 'dependencies', changes);
|
|
153
167
|
setManagedValues(packageJson, { [HOST_PACKAGE]: hostVersion }, 'dependencies', changes);
|
|
154
|
-
if (transitions)
|
|
168
|
+
if (transitions) {
|
|
169
|
+
applyPackageTransitions(packageJson, transitions, changes, warnings);
|
|
170
|
+
applyJsonTransitions(packageJson, transitions.fields, [], changes, warnings);
|
|
171
|
+
applyJsonTransitions(packageJson, transitions.scripts, ['scripts'], changes, warnings);
|
|
172
|
+
}
|
|
155
173
|
ensureManagedValues(packageJson, profile.managedDependencies, 'dependencies', changes, warnings);
|
|
156
174
|
ensureManagedValues(packageJson, profile.managedDevDependencies, 'devDependencies', changes, warnings);
|
|
157
175
|
ensureManagedValues(packageJson, profile.managedOverrides, 'overrides', changes, warnings);
|
|
176
|
+
ensureManagedValues(packageJson, profile.managedScripts, 'scripts', changes, warnings);
|
|
177
|
+
for (const [name, value] of Object.entries(profile.managedFields || {})) {
|
|
178
|
+
if (packageJson[name] === undefined) {
|
|
179
|
+
changes.push(`add missing ${name}: ${formatValue(value)}`);
|
|
180
|
+
packageJson[name] = value;
|
|
181
|
+
} else if (!sameValue(packageJson[name], value)) warnings.push(`retain customized ${name}`);
|
|
182
|
+
}
|
|
158
183
|
return {
|
|
159
184
|
changed: changes.length > 0,
|
|
160
185
|
changes,
|
|
@@ -169,18 +194,18 @@ function applyJsonTransitions(target, transitions, sectionPath, changes, warning
|
|
|
169
194
|
section[segment] ||= {};
|
|
170
195
|
section = section[segment];
|
|
171
196
|
}
|
|
172
|
-
const prefix = sectionPath.join('.');
|
|
197
|
+
const prefix = sectionPath.length ? `${sectionPath.join('.')}.` : '';
|
|
173
198
|
for (const [name, transition] of Object.entries(transitions || {})) {
|
|
174
199
|
const current = section[name];
|
|
175
200
|
if (transition.to === null) {
|
|
176
201
|
if (sameValue(current, transition.from)) {
|
|
177
|
-
changes.push(`remove ${prefix}
|
|
202
|
+
changes.push(`remove ${prefix}${name}`);
|
|
178
203
|
delete section[name];
|
|
179
|
-
} else if (current !== undefined) warnings.push(`retain customized ${prefix}
|
|
204
|
+
} else if (current !== undefined) warnings.push(`retain customized ${prefix}${name}`);
|
|
180
205
|
} else if (current === undefined || sameValue(current, transition.from)) {
|
|
181
|
-
changes.push(`${prefix}
|
|
206
|
+
changes.push(`${prefix}${name}: ${formatValue(current ?? '(missing)')} -> ${formatValue(transition.to)}`);
|
|
182
207
|
section[name] = transition.to;
|
|
183
|
-
} else if (!sameValue(current, transition.to)) warnings.push(`retain customized ${prefix}
|
|
208
|
+
} else if (!sameValue(current, transition.to)) warnings.push(`retain customized ${prefix}${name}`);
|
|
184
209
|
}
|
|
185
210
|
}
|
|
186
211
|
|
|
@@ -188,6 +213,8 @@ function patchTsconfigJson(source, transitions = null) {
|
|
|
188
213
|
const tsconfig = JSON.parse(source);
|
|
189
214
|
const changes = [];
|
|
190
215
|
const warnings = [];
|
|
216
|
+
applyJsonTransitions(tsconfig, transitions?.fields, [], changes, warnings);
|
|
217
|
+
applyJsonTransitions(tsconfig, transitions?.compilerOptions, ['compilerOptions'], changes, warnings);
|
|
191
218
|
applyJsonTransitions(tsconfig, transitions?.paths, ['compilerOptions', 'paths'], changes, warnings);
|
|
192
219
|
return {
|
|
193
220
|
changed: changes.length > 0,
|
|
@@ -197,6 +224,31 @@ function patchTsconfigJson(source, transitions = null) {
|
|
|
197
224
|
};
|
|
198
225
|
}
|
|
199
226
|
|
|
227
|
+
function patchGitignore(source, transition) {
|
|
228
|
+
if (!transition) return { changed: false, changes: [], warnings: [], content: source };
|
|
229
|
+
const start = source.indexOf(GITIGNORE_START);
|
|
230
|
+
const end = source.indexOf(GITIGNORE_END);
|
|
231
|
+
const duplicateStart = start !== -1 && source.indexOf(GITIGNORE_START, start + GITIGNORE_START.length) !== -1;
|
|
232
|
+
const duplicateEnd = end !== -1 && source.indexOf(GITIGNORE_END, end + GITIGNORE_END.length) !== -1;
|
|
233
|
+
const block = `${GITIGNORE_START}\n${transition.to.trimEnd()}\n${GITIGNORE_END}`;
|
|
234
|
+
if (start === -1 && end === -1) {
|
|
235
|
+
const separator = source.length && !source.endsWith('\n\n') ? (source.endsWith('\n') ? '\n' : '\n\n') : '';
|
|
236
|
+
return { changed: true, changes: ['add Power Apps managed .gitignore block'], warnings: [], content: `${source}${separator}${block}\n` };
|
|
237
|
+
}
|
|
238
|
+
if (start === -1 || end < start || duplicateStart || duplicateEnd) {
|
|
239
|
+
throw new Error(`${GITIGNORE_PATH} has invalid Power Apps managed markers`);
|
|
240
|
+
}
|
|
241
|
+
const blockEnd = end + GITIGNORE_END.length;
|
|
242
|
+
const current = source.slice(start + GITIGNORE_START.length, end).replace(/^\r?\n|\r?\n$/g, '');
|
|
243
|
+
if (current === transition.to.trimEnd()) return { changed: false, changes: [], warnings: [], content: source };
|
|
244
|
+
return {
|
|
245
|
+
changed: true,
|
|
246
|
+
changes: ['update Power Apps managed .gitignore block'],
|
|
247
|
+
warnings: current === transition.from?.trimEnd() ? [] : ['replace customized Power Apps managed .gitignore block'],
|
|
248
|
+
content: `${source.slice(0, start)}${block}${source.slice(blockEnd)}`,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
200
252
|
function buildMigrationOperations(projectRoot, migration) {
|
|
201
253
|
if (!migration) return { operations: [], conflicts: [] };
|
|
202
254
|
const operations = [];
|
|
@@ -278,19 +330,18 @@ function addConflict(operations, conflicts, filePath, relativePath, migrationId,
|
|
|
278
330
|
});
|
|
279
331
|
}
|
|
280
332
|
|
|
281
|
-
function readProjectState(projectRoot
|
|
333
|
+
function readProjectState(projectRoot) {
|
|
282
334
|
const statePath = path.join(projectRoot, TEMPLATE_VERSION_PATH);
|
|
283
335
|
if (fs.existsSync(statePath)) {
|
|
284
336
|
const state = readJson(statePath).expo?.extra?.powerappsNative;
|
|
285
337
|
if (state) {
|
|
286
338
|
if (state.schemaVersion !== 1) throw new Error(`${TEMPLATE_VERSION_PATH} uses unsupported schemaVersion ${state.schemaVersion}`);
|
|
287
339
|
if (!Number.isInteger(state.templateVersion)) throw new Error(`${TEMPLATE_VERSION_PATH} has an invalid templateVersion`);
|
|
340
|
+
validateNativeRuntimeVersions(state.nativeRuntimeVersions, `${TEMPLATE_VERSION_PATH} state`);
|
|
288
341
|
return state;
|
|
289
342
|
}
|
|
290
343
|
}
|
|
291
|
-
|
|
292
|
-
if (targetVersion === 1) return { schemaVersion: 1, templateVersion: 1 };
|
|
293
|
-
throw new Error(`Template state is missing. Rerun with --from-version <version> after identifying the app's source template version.`);
|
|
344
|
+
throw new Error(`Template state is missing from ${TEMPLATE_VERSION_PATH}.`);
|
|
294
345
|
}
|
|
295
346
|
|
|
296
347
|
function selectNextMigration(migrations, fromVersion, targetVersion) {
|
|
@@ -308,6 +359,7 @@ function selectNextMigration(migrations, fromVersion, targetVersion) {
|
|
|
308
359
|
function loadMigrations(profile, migrationsDirectory) {
|
|
309
360
|
if (profile.schemaVersion !== 1) throw new Error(`Compatibility profile uses unsupported schemaVersion ${profile.schemaVersion}`);
|
|
310
361
|
if (!Number.isInteger(profile.targetTemplateVersion) || profile.targetTemplateVersion < 1) throw new Error('Compatibility profile has an invalid targetTemplateVersion');
|
|
362
|
+
validateNativeRuntime(profile.nativeRuntime, 'Compatibility profile');
|
|
311
363
|
const ids = profile.migrations || [];
|
|
312
364
|
if (new Set(ids).size !== ids.length) throw new Error('Compatibility profile contains duplicate migration IDs');
|
|
313
365
|
const migrations = ids.map(migrationId => loadMigration(migrationId, migrationsDirectory));
|
|
@@ -332,10 +384,45 @@ function loadMigration(migrationId, migrationsDirectory) {
|
|
|
332
384
|
if (!migration.targetProfile?.hostVersion || !migration.targetProfile?.expoVersion || !migration.targetProfile?.expoDoctorVersion) {
|
|
333
385
|
throw new Error(`Migration ${migration.id} has an incomplete target profile`);
|
|
334
386
|
}
|
|
387
|
+
for (const platform of ['android', 'ios']) {
|
|
388
|
+
const from = migration.nativeRuntimeTransition?.from?.[platform];
|
|
389
|
+
const to = migration.nativeRuntimeTransition?.to?.[platform];
|
|
390
|
+
if (!Number.isInteger(from?.version) || !Number.isInteger(to?.version) || to.version < from.version || to.version > from.version + 1) {
|
|
391
|
+
throw new Error(`Migration ${migration.id} has invalid ${platform} native runtime versions`);
|
|
392
|
+
}
|
|
393
|
+
if (!isHash(from.fingerprint) || !isHash(to.fingerprint)) {
|
|
394
|
+
throw new Error(`Migration ${migration.id} has invalid ${platform} native runtime fingerprints`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
validateNativeRuntime(migration.targetProfile.nativeRuntime, `Migration ${migration.id} target profile`);
|
|
398
|
+
if (JSON.stringify(migration.targetProfile.nativeRuntime) !== JSON.stringify(migration.nativeRuntimeTransition.to)) {
|
|
399
|
+
throw new Error(`Migration ${migration.id} target profile native runtime does not match its transition`);
|
|
400
|
+
}
|
|
335
401
|
validatePatchMetadata(migration, patch);
|
|
336
402
|
return { ...migration, patch };
|
|
337
403
|
}
|
|
338
404
|
|
|
405
|
+
function isHash(value) {
|
|
406
|
+
return typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function validateNativeRuntime(nativeRuntime, label) {
|
|
410
|
+
for (const platform of ['android', 'ios']) {
|
|
411
|
+
const runtime = nativeRuntime?.[platform];
|
|
412
|
+
if (!Number.isInteger(runtime?.version) || runtime.version < 1 || !isHash(runtime.fingerprint)) {
|
|
413
|
+
throw new Error(`${label} has invalid ${platform} native runtime metadata`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function validateNativeRuntimeVersions(versions, label) {
|
|
419
|
+
for (const platform of ['android', 'ios']) {
|
|
420
|
+
if (!Number.isInteger(versions?.[platform]) || versions[platform] < 1) {
|
|
421
|
+
throw new Error(`${label} has invalid ${platform} native runtime version`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
339
426
|
function validatePatchMetadata(migration, patch) {
|
|
340
427
|
try {
|
|
341
428
|
const parsedPatches = parseMigrationPatch(patch);
|
|
@@ -385,18 +472,21 @@ function validateGraph(migrations, targetVersion) {
|
|
|
385
472
|
}
|
|
386
473
|
if (migrations.length) {
|
|
387
474
|
const firstVersion = Math.min(...fromVersions);
|
|
475
|
+
if (firstVersion !== 1) throw new Error('Migration graph must start at template version 1');
|
|
388
476
|
for (let version = firstVersion; version < targetVersion; version += 1) {
|
|
389
477
|
if (!fromVersions.has(version)) throw new Error(`Migration graph is missing template version ${version} -> ${version + 1}`);
|
|
390
478
|
}
|
|
391
479
|
if (Math.max(...migrations.map(migration => migration.toTemplateVersion)) !== targetVersion) {
|
|
392
480
|
throw new Error(`Migration graph does not reach target template version ${targetVersion}`);
|
|
393
481
|
}
|
|
482
|
+
} else if (targetVersion > 1) {
|
|
483
|
+
throw new Error(`Migration graph does not reach target template version ${targetVersion}`);
|
|
394
484
|
}
|
|
395
485
|
}
|
|
396
486
|
|
|
397
|
-
function buildUpgradePlan(projectRoot, profile, hostVersion,
|
|
487
|
+
function buildUpgradePlan(projectRoot, profile, hostVersion, migrationsDirectory, includeState = true) {
|
|
398
488
|
const migrations = loadMigrations(profile, migrationsDirectory);
|
|
399
|
-
const state = readProjectState(projectRoot
|
|
489
|
+
const state = readProjectState(projectRoot);
|
|
400
490
|
const migration = selectNextMigration(migrations, state.templateVersion, profile.targetTemplateVersion);
|
|
401
491
|
const targetProfile = migration ? migration.targetProfile : profile;
|
|
402
492
|
const targetHostVersion = migration ? migration.targetProfile.hostVersion : hostVersion;
|
|
@@ -412,7 +502,18 @@ function buildUpgradePlan(projectRoot, profile, hostVersion, fromVersion, migrat
|
|
|
412
502
|
const tsconfigPatch = patchTsconfigJson(source, migration.tsconfigTransitions);
|
|
413
503
|
if (tsconfigPatch.changed) operations.push({ filePath: tsconfigPath, ...tsconfigPatch });
|
|
414
504
|
}
|
|
415
|
-
if (
|
|
505
|
+
if (migration?.gitignoreTransition) {
|
|
506
|
+
const gitignorePath = path.join(projectRoot, GITIGNORE_PATH);
|
|
507
|
+
const source = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
|
|
508
|
+
const gitignorePatch = patchGitignore(source, migration.gitignoreTransition);
|
|
509
|
+
if (gitignorePatch.changed) operations.push({ filePath: gitignorePath, ...gitignorePatch });
|
|
510
|
+
}
|
|
511
|
+
if (includeState) addStateOperation(
|
|
512
|
+
operations,
|
|
513
|
+
projectRoot,
|
|
514
|
+
migration?.toTemplateVersion || profile.targetTemplateVersion,
|
|
515
|
+
targetProfile.nativeRuntime,
|
|
516
|
+
);
|
|
416
517
|
return {
|
|
417
518
|
operations,
|
|
418
519
|
conflicts: [],
|
|
@@ -424,9 +525,15 @@ function buildUpgradePlan(projectRoot, profile, hostVersion, fromVersion, migrat
|
|
|
424
525
|
};
|
|
425
526
|
}
|
|
426
527
|
|
|
427
|
-
function addStateOperation(operations, projectRoot, targetVersion) {
|
|
528
|
+
function addStateOperation(operations, projectRoot, targetVersion, nativeRuntime) {
|
|
428
529
|
const statePath = path.join(projectRoot, TEMPLATE_VERSION_PATH);
|
|
429
|
-
const nextState = {
|
|
530
|
+
const nextState = {
|
|
531
|
+
schemaVersion: 1,
|
|
532
|
+
templateVersion: targetVersion,
|
|
533
|
+
nativeRuntimeVersions: Object.fromEntries(
|
|
534
|
+
['android', 'ios'].map(platform => [platform, nativeRuntime[platform].version]),
|
|
535
|
+
),
|
|
536
|
+
};
|
|
430
537
|
const appConfig = fs.existsSync(statePath) ? readJson(statePath) : {};
|
|
431
538
|
const currentState = appConfig.expo?.extra?.powerappsNative || null;
|
|
432
539
|
if (JSON.stringify(currentState) !== JSON.stringify(nextState)) {
|
|
@@ -436,7 +543,7 @@ function addStateOperation(operations, projectRoot, targetVersion) {
|
|
|
436
543
|
operations.push({
|
|
437
544
|
filePath: statePath,
|
|
438
545
|
changed: true,
|
|
439
|
-
changes: [`record template version ${targetVersion}`],
|
|
546
|
+
changes: [`record template version ${targetVersion} and native runtime compatibility`],
|
|
440
547
|
content: `${JSON.stringify(appConfig, null, 2)}\n`,
|
|
441
548
|
state: true,
|
|
442
549
|
});
|
|
@@ -454,8 +561,10 @@ module.exports = {
|
|
|
454
561
|
isCustomerOwnedPath,
|
|
455
562
|
isTemplateManagedPath,
|
|
456
563
|
isUnifiedPatchManagedPath,
|
|
564
|
+
loadMigrations,
|
|
457
565
|
parseFileMode,
|
|
458
566
|
patchTsconfigJson,
|
|
567
|
+
patchGitignore,
|
|
459
568
|
patchPackageJson,
|
|
460
569
|
readJson,
|
|
461
570
|
readProjectState,
|