@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
@@ -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 = ['app.config.js'];
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) applyPackageTransitions(packageJson, transitions, changes, warnings);
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}.${name}`);
202
+ changes.push(`remove ${prefix}${name}`);
178
203
  delete section[name];
179
- } else if (current !== undefined) warnings.push(`retain customized ${prefix}.${name}`);
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}.${name}: ${formatValue(current ?? '(missing)')} -> ${formatValue(transition.to)}`);
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}.${name}`);
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, targetVersion, fromVersion) {
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
- if (fromVersion) return { schemaVersion: 1, templateVersion: fromVersion };
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, fromVersion, migrationsDirectory, includeState = true) {
487
+ function buildUpgradePlan(projectRoot, profile, hostVersion, migrationsDirectory, includeState = true) {
398
488
  const migrations = loadMigrations(profile, migrationsDirectory);
399
- const state = readProjectState(projectRoot, profile.targetTemplateVersion, fromVersion);
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 (includeState) addStateOperation(operations, projectRoot, migration?.toTemplateVersion || profile.targetTemplateVersion);
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 = { schemaVersion: 1, templateVersion: targetVersion };
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,