@microsoft/power-apps-native-host 0.2.25 → 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 (154) hide show
  1. package/README.md +21 -10
  2. package/compatibility/current.json +31 -4
  3. package/lib/NOTICE.txt +1325 -24
  4. package/lib/commonjs/auth/AuthContext.js +110 -33
  5. package/lib/commonjs/bridge/NativePowerAppsBridge.js +11 -18
  6. package/lib/commonjs/bridge/PowerPagesBridge.js +27 -8
  7. package/lib/commonjs/bridge/bridgeState.js +40 -0
  8. package/lib/commonjs/config/babelConfig.js +13 -0
  9. package/lib/commonjs/config/expoConfig.js +71 -0
  10. package/lib/commonjs/config/metroConfig.js +70 -0
  11. package/lib/commonjs/config/metroLogger.js +182 -0
  12. package/lib/commonjs/config/tamaguiConfig.js +98 -0
  13. package/lib/commonjs/connections/ConnectionSetupScreen.js +459 -80
  14. package/lib/commonjs/connections/PowerPlatformConnectionApi.js +946 -0
  15. package/lib/commonjs/connections/connectionMetadata.js +127 -0
  16. package/lib/commonjs/connections/connectionState.js +96 -0
  17. package/lib/commonjs/connections/disambiguate.js +19 -11
  18. package/lib/commonjs/connections/resolveConnections.js +348 -0
  19. package/lib/commonjs/connections/types.js +106 -0
  20. package/lib/commonjs/{platform → connections}/useConnectionRefs.js +130 -65
  21. package/lib/commonjs/connections/useConnectionSetup.js +111 -52
  22. package/lib/commonjs/controls/FilePicker.js +6 -0
  23. package/lib/commonjs/controls/ImagePicker.js +8 -0
  24. package/lib/commonjs/index.js +167 -4
  25. package/lib/commonjs/logger.js +9 -6
  26. package/lib/commonjs/offline/createOfflineHostActivationCallbacks.js +61 -0
  27. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +48 -0
  28. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.web.js +13 -0
  29. package/lib/commonjs/platform/DataverseDataSources.js +18 -0
  30. package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +94 -0
  31. package/lib/commonjs/platform/GlobalDiscovery.js +51 -0
  32. package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +6 -106
  33. package/lib/commonjs/provider/PowerAppsHostProvider.js +157 -25
  34. package/lib/commonjs/provider/PowerAppsProvider.js +178 -134
  35. package/lib/commonjs/provider/SessionDetailsGesture.js +208 -0
  36. package/lib/commonjs/telemetry/OfflineSdkTelemetry.js +95 -0
  37. package/lib/commonjs/telemetry/OfflineSdkTelemetry.web.js +9 -0
  38. package/lib/commonjs/telemetry/index.js +22 -3
  39. package/lib/module/auth/AuthContext.js +110 -33
  40. package/lib/module/bridge/NativePowerAppsBridge.js +12 -19
  41. package/lib/module/bridge/PowerPagesBridge.js +27 -8
  42. package/lib/module/bridge/bridgeState.js +34 -0
  43. package/lib/module/config/babelConfig.js +9 -0
  44. package/lib/module/config/expoConfig.js +67 -0
  45. package/lib/module/config/metroConfig.js +65 -0
  46. package/lib/module/config/metroLogger.js +175 -0
  47. package/lib/module/config/tamaguiConfig.js +93 -0
  48. package/lib/module/connections/ConnectionSetupScreen.js +460 -81
  49. package/lib/module/connections/PowerPlatformConnectionApi.js +932 -0
  50. package/lib/module/connections/connectionMetadata.js +112 -0
  51. package/lib/module/connections/connectionState.js +82 -0
  52. package/lib/module/connections/disambiguate.js +19 -12
  53. package/lib/module/connections/resolveConnections.js +343 -0
  54. package/lib/module/connections/types.js +91 -0
  55. package/lib/module/{platform → connections}/useConnectionRefs.js +131 -66
  56. package/lib/module/connections/useConnectionSetup.js +113 -54
  57. package/lib/module/controls/FilePicker.js +6 -0
  58. package/lib/module/controls/ImagePicker.js +8 -0
  59. package/lib/module/index.js +14 -4
  60. package/lib/module/logger.js +8 -6
  61. package/lib/module/offline/createOfflineHostActivationCallbacks.js +55 -0
  62. package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +44 -0
  63. package/lib/module/offline/useOfflineIntegrationWithStartupGate.web.js +9 -0
  64. package/lib/module/platform/DataverseDataSources.js +13 -0
  65. package/lib/module/platform/DataverseGlobalDiscoveryApi.js +89 -0
  66. package/lib/module/platform/GlobalDiscovery.js +45 -0
  67. package/lib/module/platform/PowerPlatformEnvironmentApi.js +5 -104
  68. package/lib/module/provider/PowerAppsHostProvider.js +160 -28
  69. package/lib/module/provider/PowerAppsProvider.js +182 -138
  70. package/lib/module/provider/SessionDetailsGesture.js +203 -0
  71. package/lib/module/telemetry/OfflineSdkTelemetry.js +91 -0
  72. package/lib/module/telemetry/OfflineSdkTelemetry.web.js +5 -0
  73. package/lib/module/telemetry/index.js +15 -4
  74. package/lib/typescript/commonjs/auth/AuthContext.d.ts +3 -1
  75. package/lib/typescript/commonjs/auth/AuthContext.web.d.ts +1 -0
  76. package/lib/typescript/commonjs/bridge/bridgeState.d.ts +5 -0
  77. package/lib/typescript/commonjs/config/babelConfig.d.ts +12 -0
  78. package/lib/typescript/commonjs/config/expoConfig.d.ts +21 -0
  79. package/lib/typescript/commonjs/config/metroConfig.d.ts +6 -0
  80. package/lib/typescript/commonjs/config/metroLogger.d.ts +21 -0
  81. package/lib/typescript/commonjs/config/tamaguiConfig.d.ts +1792 -0
  82. package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +28 -7
  83. package/lib/typescript/commonjs/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  84. package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +66 -0
  85. package/lib/typescript/commonjs/connections/connectionState.d.ts +16 -0
  86. package/lib/typescript/commonjs/connections/disambiguate.d.ts +4 -2
  87. package/lib/typescript/commonjs/{platform → connections}/resolveConnections.d.ts +22 -20
  88. package/lib/typescript/commonjs/connections/types.d.ts +129 -1
  89. package/lib/typescript/commonjs/{platform → connections}/useConnectionRefs.d.ts +36 -25
  90. package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +12 -8
  91. package/lib/typescript/commonjs/controls/FilePicker.d.ts +7 -1
  92. package/lib/typescript/commonjs/controls/ImagePicker.d.ts +9 -1
  93. package/lib/typescript/commonjs/index.d.ts +18 -10
  94. package/lib/typescript/commonjs/logger.d.ts +4 -4
  95. package/lib/typescript/commonjs/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  96. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  97. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  98. package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +28 -0
  99. package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  100. package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +4 -0
  101. package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  102. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +29 -18
  103. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +54 -9
  104. package/lib/typescript/commonjs/provider/SessionDetailsGesture.d.ts +13 -0
  105. package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.d.ts +15 -0
  106. package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
  107. package/lib/typescript/commonjs/telemetry/index.d.ts +13 -1
  108. package/lib/typescript/module/auth/AuthContext.d.ts +3 -1
  109. package/lib/typescript/module/auth/AuthContext.web.d.ts +1 -0
  110. package/lib/typescript/module/bridge/bridgeState.d.ts +5 -0
  111. package/lib/typescript/module/config/babelConfig.d.ts +12 -0
  112. package/lib/typescript/module/config/expoConfig.d.ts +21 -0
  113. package/lib/typescript/module/config/metroConfig.d.ts +6 -0
  114. package/lib/typescript/module/config/metroLogger.d.ts +21 -0
  115. package/lib/typescript/module/config/tamaguiConfig.d.ts +1792 -0
  116. package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +28 -7
  117. package/lib/typescript/module/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  118. package/lib/typescript/module/connections/connectionMetadata.d.ts +66 -0
  119. package/lib/typescript/module/connections/connectionState.d.ts +16 -0
  120. package/lib/typescript/module/connections/disambiguate.d.ts +4 -2
  121. package/lib/typescript/module/{platform → connections}/resolveConnections.d.ts +22 -20
  122. package/lib/typescript/module/connections/types.d.ts +129 -1
  123. package/lib/typescript/module/{platform → connections}/useConnectionRefs.d.ts +36 -25
  124. package/lib/typescript/module/connections/useConnectionSetup.d.ts +12 -8
  125. package/lib/typescript/module/controls/FilePicker.d.ts +7 -1
  126. package/lib/typescript/module/controls/ImagePicker.d.ts +9 -1
  127. package/lib/typescript/module/index.d.ts +18 -10
  128. package/lib/typescript/module/logger.d.ts +4 -4
  129. package/lib/typescript/module/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  130. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  131. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  132. package/lib/typescript/module/platform/DataverseDataSources.d.ts +28 -0
  133. package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  134. package/lib/typescript/module/platform/GlobalDiscovery.d.ts +4 -0
  135. package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  136. package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +29 -18
  137. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +54 -9
  138. package/lib/typescript/module/provider/SessionDetailsGesture.d.ts +13 -0
  139. package/lib/typescript/module/telemetry/OfflineSdkTelemetry.d.ts +15 -0
  140. package/lib/typescript/module/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
  141. package/lib/typescript/module/telemetry/index.d.ts +13 -1
  142. package/package.json +52 -13
  143. package/scripts/build-codegen-package.js +283 -0
  144. package/scripts/start-web.mjs +9 -5
  145. package/scripts/upgrade/runtime.js +139 -24
  146. package/scripts/upgrade-authoring.js +274 -180
  147. package/scripts/upgrade.js +10 -16
  148. package/src/config/generated-config.d.ts +4 -0
  149. package/src/config/index.d.ts +3 -0
  150. package/src/config/tsconfig.json +26 -0
  151. package/lib/commonjs/platform/PowerPlatformConnectionApi.js +0 -606
  152. package/lib/commonjs/platform/resolveConnections.js +0 -264
  153. package/lib/module/platform/PowerPlatformConnectionApi.js +0 -595
  154. package/lib/module/platform/resolveConnections.js +0 -259
@@ -6,16 +6,30 @@ const path = require('path');
6
6
  const { applyPatch, formatPatch, parsePatch, reversePatch } = require('diff');
7
7
 
8
8
  const HOST_PACKAGE = '@microsoft/power-apps-native-host';
9
- const TEMPLATE_VERSION_PATH = '.powerapps-native/version.json';
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,17 +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
- const state = readJson(statePath);
285
- if (state.schemaVersion !== 1) throw new Error(`${TEMPLATE_VERSION_PATH} uses unsupported schemaVersion ${state.schemaVersion}`);
286
- if (!Number.isInteger(state.templateVersion)) throw new Error(`${TEMPLATE_VERSION_PATH} has an invalid templateVersion`);
287
- return state;
336
+ const state = readJson(statePath).expo?.extra?.powerappsNative;
337
+ if (state) {
338
+ if (state.schemaVersion !== 1) throw new Error(`${TEMPLATE_VERSION_PATH} uses unsupported schemaVersion ${state.schemaVersion}`);
339
+ if (!Number.isInteger(state.templateVersion)) throw new Error(`${TEMPLATE_VERSION_PATH} has an invalid templateVersion`);
340
+ validateNativeRuntimeVersions(state.nativeRuntimeVersions, `${TEMPLATE_VERSION_PATH} state`);
341
+ return state;
342
+ }
288
343
  }
289
- if (fromVersion) return { schemaVersion: 1, templateVersion: fromVersion };
290
- if (targetVersion === 1) return { schemaVersion: 1, templateVersion: 1 };
291
- 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}.`);
292
345
  }
293
346
 
294
347
  function selectNextMigration(migrations, fromVersion, targetVersion) {
@@ -306,6 +359,7 @@ function selectNextMigration(migrations, fromVersion, targetVersion) {
306
359
  function loadMigrations(profile, migrationsDirectory) {
307
360
  if (profile.schemaVersion !== 1) throw new Error(`Compatibility profile uses unsupported schemaVersion ${profile.schemaVersion}`);
308
361
  if (!Number.isInteger(profile.targetTemplateVersion) || profile.targetTemplateVersion < 1) throw new Error('Compatibility profile has an invalid targetTemplateVersion');
362
+ validateNativeRuntime(profile.nativeRuntime, 'Compatibility profile');
309
363
  const ids = profile.migrations || [];
310
364
  if (new Set(ids).size !== ids.length) throw new Error('Compatibility profile contains duplicate migration IDs');
311
365
  const migrations = ids.map(migrationId => loadMigration(migrationId, migrationsDirectory));
@@ -330,10 +384,45 @@ function loadMigration(migrationId, migrationsDirectory) {
330
384
  if (!migration.targetProfile?.hostVersion || !migration.targetProfile?.expoVersion || !migration.targetProfile?.expoDoctorVersion) {
331
385
  throw new Error(`Migration ${migration.id} has an incomplete target profile`);
332
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
+ }
333
401
  validatePatchMetadata(migration, patch);
334
402
  return { ...migration, patch };
335
403
  }
336
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
+
337
426
  function validatePatchMetadata(migration, patch) {
338
427
  try {
339
428
  const parsedPatches = parseMigrationPatch(patch);
@@ -383,18 +472,21 @@ function validateGraph(migrations, targetVersion) {
383
472
  }
384
473
  if (migrations.length) {
385
474
  const firstVersion = Math.min(...fromVersions);
475
+ if (firstVersion !== 1) throw new Error('Migration graph must start at template version 1');
386
476
  for (let version = firstVersion; version < targetVersion; version += 1) {
387
477
  if (!fromVersions.has(version)) throw new Error(`Migration graph is missing template version ${version} -> ${version + 1}`);
388
478
  }
389
479
  if (Math.max(...migrations.map(migration => migration.toTemplateVersion)) !== targetVersion) {
390
480
  throw new Error(`Migration graph does not reach target template version ${targetVersion}`);
391
481
  }
482
+ } else if (targetVersion > 1) {
483
+ throw new Error(`Migration graph does not reach target template version ${targetVersion}`);
392
484
  }
393
485
  }
394
486
 
395
- function buildUpgradePlan(projectRoot, profile, hostVersion, fromVersion, migrationsDirectory, includeState = true) {
487
+ function buildUpgradePlan(projectRoot, profile, hostVersion, migrationsDirectory, includeState = true) {
396
488
  const migrations = loadMigrations(profile, migrationsDirectory);
397
- const state = readProjectState(projectRoot, profile.targetTemplateVersion, fromVersion);
489
+ const state = readProjectState(projectRoot);
398
490
  const migration = selectNextMigration(migrations, state.templateVersion, profile.targetTemplateVersion);
399
491
  const targetProfile = migration ? migration.targetProfile : profile;
400
492
  const targetHostVersion = migration ? migration.targetProfile.hostVersion : hostVersion;
@@ -410,7 +502,18 @@ function buildUpgradePlan(projectRoot, profile, hostVersion, fromVersion, migrat
410
502
  const tsconfigPatch = patchTsconfigJson(source, migration.tsconfigTransitions);
411
503
  if (tsconfigPatch.changed) operations.push({ filePath: tsconfigPath, ...tsconfigPatch });
412
504
  }
413
- 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
+ );
414
517
  return {
415
518
  operations,
416
519
  conflicts: [],
@@ -422,16 +525,26 @@ function buildUpgradePlan(projectRoot, profile, hostVersion, fromVersion, migrat
422
525
  };
423
526
  }
424
527
 
425
- function addStateOperation(operations, projectRoot, targetVersion) {
528
+ function addStateOperation(operations, projectRoot, targetVersion, nativeRuntime) {
426
529
  const statePath = path.join(projectRoot, TEMPLATE_VERSION_PATH);
427
- const nextState = { schemaVersion: 1, templateVersion: targetVersion };
428
- const currentState = fs.existsSync(statePath) ? readJson(statePath) : null;
530
+ const nextState = {
531
+ schemaVersion: 1,
532
+ templateVersion: targetVersion,
533
+ nativeRuntimeVersions: Object.fromEntries(
534
+ ['android', 'ios'].map(platform => [platform, nativeRuntime[platform].version]),
535
+ ),
536
+ };
537
+ const appConfig = fs.existsSync(statePath) ? readJson(statePath) : {};
538
+ const currentState = appConfig.expo?.extra?.powerappsNative || null;
429
539
  if (JSON.stringify(currentState) !== JSON.stringify(nextState)) {
540
+ appConfig.expo ||= {};
541
+ appConfig.expo.extra ||= {};
542
+ appConfig.expo.extra.powerappsNative = nextState;
430
543
  operations.push({
431
544
  filePath: statePath,
432
545
  changed: true,
433
- changes: [`record template version ${targetVersion}`],
434
- content: `${JSON.stringify(nextState, null, 2)}\n`,
546
+ changes: [`record template version ${targetVersion} and native runtime compatibility`],
547
+ content: `${JSON.stringify(appConfig, null, 2)}\n`,
435
548
  state: true,
436
549
  });
437
550
  }
@@ -448,8 +561,10 @@ module.exports = {
448
561
  isCustomerOwnedPath,
449
562
  isTemplateManagedPath,
450
563
  isUnifiedPatchManagedPath,
564
+ loadMigrations,
451
565
  parseFileMode,
452
566
  patchTsconfigJson,
567
+ patchGitignore,
453
568
  patchPackageJson,
454
569
  readJson,
455
570
  readProjectState,