@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
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const childProcess = require('child_process');
|
|
4
|
-
const crypto = require('crypto');
|
|
5
4
|
const fs = require('fs');
|
|
6
5
|
const path = require('path');
|
|
7
6
|
const { setLockProvider, withLock } = require('@ster5/global-mutex');
|
|
8
7
|
const { createTwoFilesPatch, parsePatch } = require('diff');
|
|
9
8
|
const writeFileAtomic = require('write-file-atomic');
|
|
10
|
-
const { HOST_PACKAGE, TEMPLATE_VERSION_PATH, assertSafeFilePath, hashContent, isCustomerOwnedPath,
|
|
9
|
+
const { HOST_PACKAGE, TEMPLATE_VERSION_PATH, assertSafeFilePath, hashContent, isCustomerOwnedPath, readJson } = require('./upgrade/runtime');
|
|
11
10
|
|
|
12
11
|
const AUTHORING_LOCK_PATH = 'packages/power-apps-native-host/compatibility/.migration-build.lock';
|
|
13
12
|
const AUTHORING_JOURNAL_PATH = 'packages/power-apps-native-host/compatibility/.migration-build-journal.json';
|
|
14
13
|
const TEMPLATE_PATH = 'templates/expo-app-standalone';
|
|
15
|
-
const MIGRATION_CHANGE_PATH = 'migration-change';
|
|
16
14
|
const TSCONFIG_PATH = 'tsconfig.json';
|
|
15
|
+
const GITIGNORE_PATH = '.gitignore';
|
|
16
|
+
const GITIGNORE_START = '# POWER APPS MANAGED START - DO NOT REMOVE OR RENAME THE COMMENT';
|
|
17
|
+
const GITIGNORE_END = '# POWER APPS MANAGED END - DO NOT REMOVE OR RENAME THE COMMENT';
|
|
18
|
+
const MANAGED_PACKAGE_FIELDS = ['main'];
|
|
19
|
+
const PROFILE_PATH = 'packages/power-apps-native-host/compatibility/current.json';
|
|
20
|
+
const TEMPLATE_BASELINE_TAG_PREFIX = 'standalone-template-migration-baseline-v';
|
|
21
|
+
const LEGACY_TEMPLATE_BASELINE_TAG_PATTERN = /^template-migration-baseline-v\d+$/;
|
|
22
|
+
const RELEASE_HOST_VERSION = '__RELEASE_HOST_VERSION__';
|
|
23
|
+
const NATIVE_RUNTIME_FINGERPRINT_SCRIPT = 'scripts/repository/native-runtime-fingerprint.js';
|
|
24
|
+
const NATIVE_RUNTIME_PLATFORMS = ['android', 'ios'];
|
|
17
25
|
const CUSTOMER_SECTIONS = [
|
|
18
26
|
{
|
|
19
27
|
name: 'customer app settings',
|
|
@@ -43,7 +51,7 @@ function git(repositoryRoot, args, allowFailure = false) {
|
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
function resolveVersionTag(repositoryRoot, tag) {
|
|
46
|
-
if (!tag) throw new Error('
|
|
54
|
+
if (!tag) throw new Error('build requires --from-tag <version-tag>');
|
|
47
55
|
const tagRef = `refs/tags/${tag}`;
|
|
48
56
|
if (git(repositoryRoot, ['show-ref', '--verify', '--quiet', tagRef], true) === null) {
|
|
49
57
|
throw new Error(`${tag} is not a local Git tag; fetch tags or provide a valid --from-tag`);
|
|
@@ -55,6 +63,26 @@ function resolveVersionTag(repositoryRoot, tag) {
|
|
|
55
63
|
return { tag, commit };
|
|
56
64
|
}
|
|
57
65
|
|
|
66
|
+
function templateBaselineTag(templateVersion) {
|
|
67
|
+
return `${TEMPLATE_BASELINE_TAG_PREFIX}${templateVersion}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function inferBuildBaseTag(repositoryRoot) {
|
|
71
|
+
const profile = readJson(path.join(repositoryRoot, PROFILE_PATH));
|
|
72
|
+
if (!Number.isInteger(profile.targetTemplateVersion)) throw new Error('Current compatibility profile has an invalid targetTemplateVersion');
|
|
73
|
+
const currentTag = templateBaselineTag(profile.targetTemplateVersion);
|
|
74
|
+
if (git(repositoryRoot, ['show-ref', '--verify', '--quiet', `refs/tags/${currentTag}`], true) !== null) return currentTag;
|
|
75
|
+
const legacyTags = git(repositoryRoot, ['tag', '--merged', 'HEAD', '--list', 'template-migration-baseline-v*', '--sort=-version:refname'])
|
|
76
|
+
.trim().split('\n').filter(Boolean);
|
|
77
|
+
const legacyTag = legacyTags.find(tag => {
|
|
78
|
+
if (!LEGACY_TEMPLATE_BASELINE_TAG_PATTERN.test(tag)) return false;
|
|
79
|
+
const base = resolveVersionTag(repositoryRoot, tag);
|
|
80
|
+
return readTemplateState(repositoryRoot, base.commit)?.templateVersion === profile.targetTemplateVersion;
|
|
81
|
+
});
|
|
82
|
+
if (legacyTag) return legacyTag;
|
|
83
|
+
throw new Error(`Could not infer template migration baseline. Expected ${currentTag}.`);
|
|
84
|
+
}
|
|
85
|
+
|
|
58
86
|
function getTemplateChanges(repositoryRoot, fromTag) {
|
|
59
87
|
const base = resolveVersionTag(repositoryRoot, fromTag);
|
|
60
88
|
const tracked = git(repositoryRoot, ['diff', '--no-renames', '--name-only', '--diff-filter=ACDMT', base.commit, '--', TEMPLATE_PATH])
|
|
@@ -65,93 +93,12 @@ function getTemplateChanges(repositoryRoot, fromTag) {
|
|
|
65
93
|
return {
|
|
66
94
|
baseTag: base.tag,
|
|
67
95
|
baseCommit: base.commit,
|
|
68
|
-
headCommit: git(repositoryRoot, ['rev-parse', 'HEAD']).trim(),
|
|
69
96
|
files: paths.map(filePath => {
|
|
70
|
-
|
|
71
|
-
const content = fs.existsSync(absolutePath) ? fs.readFileSync(absolutePath) : null;
|
|
72
|
-
return { path: filePath.slice(`${TEMPLATE_PATH}/`.length), targetHash: hashContent(content) };
|
|
97
|
+
return { path: filePath.slice(`${TEMPLATE_PATH}/`.length) };
|
|
73
98
|
}),
|
|
74
99
|
};
|
|
75
100
|
}
|
|
76
101
|
|
|
77
|
-
function readMigrationChanges(repositoryRoot) {
|
|
78
|
-
const directory = path.join(repositoryRoot, MIGRATION_CHANGE_PATH);
|
|
79
|
-
if (!fs.existsSync(directory)) return [];
|
|
80
|
-
return fs.readdirSync(directory)
|
|
81
|
-
.filter(name => name.endsWith('.json'))
|
|
82
|
-
.sort()
|
|
83
|
-
.map(name => validateMigrationChange(name, readJson(path.join(directory, name))));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function validateMigrationChange(name, record) {
|
|
87
|
-
if (record.schemaVersion !== 1 || record.type !== 'template-migration' || typeof record.baseTag !== 'string' || typeof record.baseCommit !== 'string') {
|
|
88
|
-
throw new Error(`Migration changelog ${name} has an invalid schema`);
|
|
89
|
-
}
|
|
90
|
-
if (!Array.isArray(record.files)) throw new Error(`Migration changelog ${name} has an invalid files list`);
|
|
91
|
-
const seen = new Set();
|
|
92
|
-
for (const file of record.files) {
|
|
93
|
-
if (!file || !isSafeTemplatePath(file.path) || !isHashOrNull(file.targetHash)) throw new Error(`Migration changelog ${name} contains invalid file metadata`);
|
|
94
|
-
if (seen.has(file.path)) throw new Error(`Migration changelog ${name} contains duplicate path ${file.path}`);
|
|
95
|
-
seen.add(file.path);
|
|
96
|
-
}
|
|
97
|
-
return { name, ...record };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function isSafeTemplatePath(relativePath) {
|
|
101
|
-
return typeof relativePath === 'string'
|
|
102
|
-
&& relativePath.length > 0
|
|
103
|
-
&& !relativePath.includes('\\')
|
|
104
|
-
&& !path.posix.isAbsolute(relativePath)
|
|
105
|
-
&& path.posix.normalize(relativePath) === relativePath
|
|
106
|
-
&& !relativePath.split('/').includes('..');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function createMigrationChange(repositoryRoot, options) {
|
|
110
|
-
if (!options.message) throw new Error('change requires --message');
|
|
111
|
-
if (!options.fromTag) throw new Error('change requires --from-tag <version-tag> or MIGRATION_BASE_TAG');
|
|
112
|
-
const templateChanges = getTemplateChanges(repositoryRoot, options.fromTag);
|
|
113
|
-
assertNoCustomerOwnedChanges(templateChanges.files);
|
|
114
|
-
if (!templateChanges.files.length) throw new Error('No template changes were found');
|
|
115
|
-
const record = {
|
|
116
|
-
schemaVersion: 1,
|
|
117
|
-
type: 'template-migration',
|
|
118
|
-
comment: options.message,
|
|
119
|
-
createdAt: new Date().toISOString(),
|
|
120
|
-
...templateChanges,
|
|
121
|
-
};
|
|
122
|
-
const slug = options.message.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40) || 'template-change';
|
|
123
|
-
const relativePath = path.join(MIGRATION_CHANGE_PATH, `${slug}-${crypto.randomUUID().slice(0, 8)}.json`);
|
|
124
|
-
console.log(`${options.dryRun ? '[dry-run] ' : ''}Create ${relativePath}`);
|
|
125
|
-
if (!options.dryRun) {
|
|
126
|
-
fs.mkdirSync(path.dirname(path.join(repositoryRoot, relativePath)), { recursive: true });
|
|
127
|
-
fs.writeFileSync(path.join(repositoryRoot, relativePath), `${JSON.stringify(record, null, 2)}\n`);
|
|
128
|
-
}
|
|
129
|
-
return record;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function checkMigrationChanges(repositoryRoot, options) {
|
|
133
|
-
const records = readMigrationChanges(repositoryRoot);
|
|
134
|
-
const recordedTags = [...new Set(records.map(record => record.baseTag).filter(Boolean))];
|
|
135
|
-
const changes = getTemplateChanges(repositoryRoot, options.fromTag || (recordedTags.length === 1 ? recordedTags[0] : null));
|
|
136
|
-
assertNoCustomerOwnedChanges(changes.files);
|
|
137
|
-
const covered = new Set(records.flatMap(record => record.files || []).map(file => `${file.path}:${file.targetHash}`));
|
|
138
|
-
const current = new Set(changes.files.map(file => `${file.path}:${file.targetHash}`));
|
|
139
|
-
if (!changes.files.length) {
|
|
140
|
-
if (covered.size) throw new Error('Migration changelogs contain stale template changes but the template matches the version tag');
|
|
141
|
-
console.log('No template changes require a migration changelog.');
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const wrongBase = records.filter(record => record.baseTag !== changes.baseTag || record.baseCommit !== changes.baseCommit);
|
|
145
|
-
if (wrongBase.length) throw new Error(`Migration changelogs must all use version tag ${changes.baseTag}; regenerate: ${wrongBase.map(record => record.name).join(', ')}`);
|
|
146
|
-
const missing = changes.files.filter(file => !covered.has(`${file.path}:${file.targetHash}`));
|
|
147
|
-
const stale = [...covered].filter(file => !current.has(file));
|
|
148
|
-
if (missing.length) {
|
|
149
|
-
throw new Error(`Template changes require an up-to-date migration changelog:\n${missing.map(file => ` - ${file.path}`).join('\n')}\nRun upgrade-template change --from-tag ${changes.baseTag} --message "description"`);
|
|
150
|
-
}
|
|
151
|
-
if (stale.length) throw new Error(`Migration changelogs contain stale template changes:\n${stale.map(file => ` - ${file.split(':')[0]}`).join('\n')}`);
|
|
152
|
-
console.log(`${changes.files.length} template change(s) are covered by migration changelogs.`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
102
|
function assertNoCustomerOwnedChanges(files) {
|
|
156
103
|
const protectedFiles = files.filter(file => isCustomerOwnedPath(file.path));
|
|
157
104
|
if (protectedFiles.length) {
|
|
@@ -176,10 +123,9 @@ function fileMode(repositoryRoot, commit, relativePath) {
|
|
|
176
123
|
|
|
177
124
|
async function runAuthoringCommand(command, startPath, options) {
|
|
178
125
|
const repositoryRoot = findRepositoryRoot(startPath);
|
|
179
|
-
if (command
|
|
180
|
-
if (command === 'check') return checkMigrationChanges(repositoryRoot, options);
|
|
126
|
+
if (command !== 'build') throw new Error(`Unsupported template authoring command: ${command}`);
|
|
181
127
|
return withAuthoringLock(repositoryRoot, () => {
|
|
182
|
-
recoverAuthoringJournal(repositoryRoot);
|
|
128
|
+
if (recoverAuthoringJournal(repositoryRoot) === 'committed') return null;
|
|
183
129
|
return buildMigrationStep(repositoryRoot, options);
|
|
184
130
|
});
|
|
185
131
|
}
|
|
@@ -200,28 +146,40 @@ async function withAuthoringLock(repositoryRoot, action) {
|
|
|
200
146
|
|
|
201
147
|
function recoverAuthoringJournal(repositoryRoot) {
|
|
202
148
|
const journalPath = path.join(repositoryRoot, AUTHORING_JOURNAL_PATH);
|
|
203
|
-
if (!fs.existsSync(journalPath)) return;
|
|
149
|
+
if (!fs.existsSync(journalPath)) return null;
|
|
204
150
|
const journal = readJson(journalPath);
|
|
205
151
|
validateAuthoringJournal(repositoryRoot, journal);
|
|
206
152
|
if (journal.status !== 'committed') {
|
|
207
153
|
writeFileAtomic.sync(journal.profilePath, Buffer.from(journal.originalProfile, 'base64'));
|
|
154
|
+
if (journal.appJsonPath && journal.originalAppJson) writeFileAtomic.sync(journal.appJsonPath, Buffer.from(journal.originalAppJson, 'base64'));
|
|
208
155
|
for (const outputPath of journal.outputPaths) fs.rmSync(outputPath, { force: true });
|
|
209
|
-
for (const record of journal.records) {
|
|
210
|
-
fs.mkdirSync(path.dirname(record.path), { recursive: true });
|
|
211
|
-
writeFileAtomic.sync(record.path, Buffer.from(record.content, 'base64'));
|
|
212
|
-
}
|
|
213
156
|
console.warn('[upgrade-template] Recovered an interrupted migration build.');
|
|
214
157
|
}
|
|
158
|
+
for (const filePath of [journal.profilePath, journal.appJsonPath, ...journal.outputPaths].filter(Boolean)) {
|
|
159
|
+
removeTemporarySiblings(filePath);
|
|
160
|
+
}
|
|
215
161
|
fs.rmSync(journalPath, { force: true });
|
|
162
|
+
return journal.status;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function removeTemporarySiblings(filePath) {
|
|
166
|
+
const directory = path.dirname(filePath);
|
|
167
|
+
const temporaryPrefix = `${path.basename(filePath)}.tmp-`;
|
|
168
|
+
for (const name of fs.readdirSync(directory)) {
|
|
169
|
+
if (name.startsWith(temporaryPrefix)) fs.rmSync(path.join(directory, name), { force: true });
|
|
170
|
+
}
|
|
216
171
|
}
|
|
217
172
|
|
|
218
173
|
function validateAuthoringJournal(repositoryRoot, journal) {
|
|
219
174
|
if (journal.schemaVersion !== 1 || !['preparing', 'committed'].includes(journal.status)) throw new Error('Migration build journal has an unsupported schema');
|
|
220
175
|
const profilePath = path.join(repositoryRoot, 'packages/power-apps-native-host/compatibility/current.json');
|
|
176
|
+
const appJsonPath = path.join(repositoryRoot, TEMPLATE_PATH, TEMPLATE_VERSION_PATH);
|
|
221
177
|
const migrationDirectory = path.join(repositoryRoot, 'packages/power-apps-native-host/compatibility/migrations');
|
|
222
|
-
const changeDirectory = path.join(repositoryRoot, MIGRATION_CHANGE_PATH);
|
|
223
178
|
if (path.resolve(journal.profilePath || '') !== profilePath || !isCanonicalBase64(journal.originalProfile)) throw new Error('Migration build journal has invalid profile backup data');
|
|
224
|
-
if (
|
|
179
|
+
if (journal.appJsonPath !== undefined || journal.originalAppJson !== undefined) {
|
|
180
|
+
if (path.resolve(journal.appJsonPath || '') !== appJsonPath || !isCanonicalBase64(journal.originalAppJson)) throw new Error('Migration build journal has invalid app.json backup data');
|
|
181
|
+
}
|
|
182
|
+
if (!Array.isArray(journal.outputPaths)) throw new Error('Migration build journal has invalid file entries');
|
|
225
183
|
const outputPaths = new Set();
|
|
226
184
|
for (const outputPath of journal.outputPaths) {
|
|
227
185
|
if (!isPathWithin(migrationDirectory, outputPath) || !/\.(?:json|patch)$/.test(outputPath) || outputPaths.has(path.resolve(outputPath))) {
|
|
@@ -230,14 +188,6 @@ function validateAuthoringJournal(repositoryRoot, journal) {
|
|
|
230
188
|
assertSafeFilePath(repositoryRoot, path.resolve(outputPath));
|
|
231
189
|
outputPaths.add(path.resolve(outputPath));
|
|
232
190
|
}
|
|
233
|
-
const recordPaths = new Set();
|
|
234
|
-
for (const record of journal.records) {
|
|
235
|
-
if (!record || !isPathWithin(changeDirectory, record.path) || !record.path.endsWith('.json') || !isCanonicalBase64(record.content) || recordPaths.has(path.resolve(record.path))) {
|
|
236
|
-
throw new Error('Migration build journal has invalid changelog backup data');
|
|
237
|
-
}
|
|
238
|
-
assertSafeFilePath(repositoryRoot, path.resolve(record.path));
|
|
239
|
-
recordPaths.add(path.resolve(record.path));
|
|
240
|
-
}
|
|
241
191
|
}
|
|
242
192
|
|
|
243
193
|
function isPathWithin(directory, filePath) {
|
|
@@ -253,13 +203,76 @@ function isCanonicalBase64(value) {
|
|
|
253
203
|
|
|
254
204
|
function updateProfileFromTemplate(profile, repositoryRoot) {
|
|
255
205
|
const templatePackage = readJson(path.join(repositoryRoot, TEMPLATE_PATH, 'package.json'));
|
|
206
|
+
validateDevPlayerDependencies(repositoryRoot, templatePackage);
|
|
207
|
+
updateProfileFromPackage(profile, templatePackage);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function nextNativeRuntime(current, fingerprint) {
|
|
211
|
+
return {
|
|
212
|
+
version: current.version + (fingerprint === current.fingerprint ? 0 : 1),
|
|
213
|
+
fingerprint,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function updateNativeRuntime(profile, repositoryRoot) {
|
|
218
|
+
const previous = JSON.parse(JSON.stringify(profile.nativeRuntime));
|
|
219
|
+
for (const platform of NATIVE_RUNTIME_PLATFORMS) {
|
|
220
|
+
const current = profile.nativeRuntime[platform];
|
|
221
|
+
if (!Number.isInteger(current?.version) || current.version < 1 || !/^[a-f0-9]{64}$/.test(current.fingerprint || '')) {
|
|
222
|
+
throw new Error(`Current compatibility profile has invalid nativeRuntime.${platform}`);
|
|
223
|
+
}
|
|
224
|
+
const result = childProcess.spawnSync(
|
|
225
|
+
process.execPath,
|
|
226
|
+
[path.join(repositoryRoot, NATIVE_RUNTIME_FINGERPRINT_SCRIPT), platform, path.join(repositoryRoot, TEMPLATE_PATH)],
|
|
227
|
+
{ cwd: repositoryRoot, encoding: 'utf8' },
|
|
228
|
+
);
|
|
229
|
+
if (result.error) throw result.error;
|
|
230
|
+
if (result.status !== 0) {
|
|
231
|
+
throw new Error(`Could not fingerprint the ${platform} native runtime: ${String(result.stderr || '').trim()}`);
|
|
232
|
+
}
|
|
233
|
+
const fingerprint = String(result.stdout || '').trim();
|
|
234
|
+
if (!/^[a-f0-9]{64}$/.test(fingerprint)) throw new Error(`Invalid ${platform} native runtime fingerprint`);
|
|
235
|
+
profile.nativeRuntime[platform] = nextNativeRuntime(current, fingerprint);
|
|
236
|
+
}
|
|
237
|
+
return previous;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function updateProfileFromPackage(profile, templatePackage) {
|
|
256
241
|
const dependencies = { ...templatePackage.dependencies };
|
|
257
|
-
|
|
242
|
+
const expoVersion = dependencies.expo;
|
|
243
|
+
const expoMajorMatch = typeof expoVersion === 'string' && expoVersion.match(/^(?:[~^])?(\d+)\./);
|
|
244
|
+
if (!expoMajorMatch) throw new Error('Standalone template package.json must define a supported Expo version');
|
|
245
|
+
profile.expoSdk = Number(expoMajorMatch[1]);
|
|
246
|
+
profile.expoVersion = expoVersion;
|
|
258
247
|
delete dependencies.expo;
|
|
259
248
|
delete dependencies[HOST_PACKAGE];
|
|
260
249
|
profile.managedDependencies = dependencies;
|
|
261
250
|
profile.managedDevDependencies = { ...templatePackage.devDependencies };
|
|
262
251
|
profile.managedOverrides = { ...templatePackage.overrides };
|
|
252
|
+
profile.managedScripts = { ...templatePackage.scripts };
|
|
253
|
+
profile.managedFields = Object.fromEntries(MANAGED_PACKAGE_FIELDS
|
|
254
|
+
.filter(name => templatePackage[name] !== undefined)
|
|
255
|
+
.map(name => [name, templatePackage[name]]));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function validateDevPlayerDependencies(repositoryRoot, templatePackage) {
|
|
259
|
+
const devPlayerPackagePath = path.join(repositoryRoot, 'apps/dev-player/package.json');
|
|
260
|
+
if (!fs.existsSync(devPlayerPackagePath)) {
|
|
261
|
+
throw new Error('Dev Player package.json is missing; cannot validate standalone template dependencies');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const devPlayerPackage = readJson(devPlayerPackagePath);
|
|
265
|
+
const missingDependencies = Object.keys(templatePackage.dependencies || {})
|
|
266
|
+
.filter(name => !Object.hasOwn(devPlayerPackage.dependencies || {}, name));
|
|
267
|
+
const missingDevDependencies = Object.keys(templatePackage.devDependencies || {})
|
|
268
|
+
.filter(name => !Object.hasOwn(devPlayerPackage.devDependencies || {}, name));
|
|
269
|
+
const missing = [
|
|
270
|
+
...missingDependencies.map(name => `dependencies.${name}`),
|
|
271
|
+
...missingDevDependencies.map(name => `devDependencies.${name}`),
|
|
272
|
+
].sort();
|
|
273
|
+
if (missing.length > 0) {
|
|
274
|
+
throw new Error(`Dev Player package.json is missing standalone template dependencies: ${missing.join(', ')}`);
|
|
275
|
+
}
|
|
263
276
|
}
|
|
264
277
|
|
|
265
278
|
function packageTransitions(baseValues = {}, targetValues = {}) {
|
|
@@ -272,23 +285,48 @@ function packageTransitions(baseValues = {}, targetValues = {}) {
|
|
|
272
285
|
}]));
|
|
273
286
|
}
|
|
274
287
|
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
288
|
+
function buildPackageTransitions(basePackage, targetPackage) {
|
|
289
|
+
return {
|
|
290
|
+
fields: packageTransitions(
|
|
291
|
+
Object.fromEntries(MANAGED_PACKAGE_FIELDS.filter(name => basePackage[name] !== undefined).map(name => [name, basePackage[name]])),
|
|
292
|
+
Object.fromEntries(MANAGED_PACKAGE_FIELDS.filter(name => targetPackage[name] !== undefined).map(name => [name, targetPackage[name]])),
|
|
293
|
+
),
|
|
294
|
+
scripts: packageTransitions(basePackage.scripts, targetPackage.scripts),
|
|
295
|
+
dependencies: Object.fromEntries(Object.entries(packageTransitions(basePackage.dependencies, targetPackage.dependencies))
|
|
296
|
+
.filter(([name]) => name !== HOST_PACKAGE && name !== 'expo')),
|
|
297
|
+
devDependencies: packageTransitions(basePackage.devDependencies, targetPackage.devDependencies),
|
|
298
|
+
overrides: packageTransitions(basePackage.overrides, targetPackage.overrides),
|
|
299
|
+
};
|
|
282
300
|
}
|
|
283
301
|
|
|
284
302
|
function buildTsconfigTransitions(relativePath, baseContent, targetContent) {
|
|
285
303
|
const base = baseContent ? JSON.parse(baseContent.toString('utf8')) : {};
|
|
286
304
|
const target = targetContent ? JSON.parse(targetContent.toString('utf8')) : {};
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
305
|
+
const fields = packageTransitions(
|
|
306
|
+
Object.fromEntries(['extends', 'include', 'exclude'].filter(name => base[name] !== undefined).map(name => [name, base[name]])),
|
|
307
|
+
Object.fromEntries(['extends', 'include', 'exclude'].filter(name => target[name] !== undefined).map(name => [name, target[name]])),
|
|
308
|
+
);
|
|
309
|
+
const baseCompilerOptions = { ...base.compilerOptions };
|
|
310
|
+
const targetCompilerOptions = { ...target.compilerOptions };
|
|
311
|
+
delete baseCompilerOptions.paths;
|
|
312
|
+
delete targetCompilerOptions.paths;
|
|
313
|
+
const compilerOptions = packageTransitions(baseCompilerOptions, targetCompilerOptions);
|
|
290
314
|
const paths = packageTransitions(base.compilerOptions?.paths, target.compilerOptions?.paths);
|
|
291
|
-
|
|
315
|
+
const transitions = {};
|
|
316
|
+
if (Object.keys(fields).length) transitions.fields = fields;
|
|
317
|
+
if (Object.keys(compilerOptions).length) transitions.compilerOptions = compilerOptions;
|
|
318
|
+
if (Object.keys(paths).length) transitions.paths = paths;
|
|
319
|
+
return Object.keys(transitions).length ? transitions : null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function readManagedGitignoreBlock(relativePath, content, required) {
|
|
323
|
+
if (content === null) return null;
|
|
324
|
+
const source = content.toString('utf8');
|
|
325
|
+
const start = source.indexOf(GITIGNORE_START);
|
|
326
|
+
const end = source.indexOf(GITIGNORE_END);
|
|
327
|
+
if (start === -1 && end === -1 && !required) return null;
|
|
328
|
+
if (start === -1 || end < start) throw new Error(`Template file ${relativePath} has invalid Power Apps managed markers`);
|
|
329
|
+
return source.slice(start + GITIGNORE_START.length, end).replace(/^\r?\n|\r?\n$/g, '');
|
|
292
330
|
}
|
|
293
331
|
|
|
294
332
|
function assertPatchableMode(relativePath, mode) {
|
|
@@ -297,22 +335,78 @@ function assertPatchableMode(relativePath, mode) {
|
|
|
297
335
|
}
|
|
298
336
|
}
|
|
299
337
|
|
|
338
|
+
function readTemplateState(repositoryRoot, commit) {
|
|
339
|
+
const content = commit
|
|
340
|
+
? readGitFile(repositoryRoot, commit, TEMPLATE_VERSION_PATH)
|
|
341
|
+
: fs.readFileSync(path.join(repositoryRoot, TEMPLATE_PATH, TEMPLATE_VERSION_PATH));
|
|
342
|
+
return content ? JSON.parse(content.toString('utf8')).expo?.extra?.powerappsNative : null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function buildTemplateVersionContent(repositoryRoot, templateVersion, nativeRuntime) {
|
|
346
|
+
const appJsonPath = path.join(repositoryRoot, TEMPLATE_PATH, TEMPLATE_VERSION_PATH);
|
|
347
|
+
const appConfig = readJson(appJsonPath);
|
|
348
|
+
appConfig.expo ||= {};
|
|
349
|
+
appConfig.expo.extra ||= {};
|
|
350
|
+
appConfig.expo.extra.powerappsNative ||= { schemaVersion: 1 };
|
|
351
|
+
appConfig.expo.extra.powerappsNative.schemaVersion ||= 1;
|
|
352
|
+
appConfig.expo.extra.powerappsNative.templateVersion = templateVersion;
|
|
353
|
+
appConfig.expo.extra.powerappsNative.nativeRuntimeVersions = Object.fromEntries(
|
|
354
|
+
NATIVE_RUNTIME_PLATFORMS.map(platform => [platform, nativeRuntime[platform].version]),
|
|
355
|
+
);
|
|
356
|
+
return `${JSON.stringify(appConfig, null, 2)}\n`;
|
|
357
|
+
}
|
|
358
|
+
|
|
300
359
|
function buildMigrationStep(repositoryRoot, options) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
360
|
+
const fromTag = options.fromTag || inferBuildBaseTag(repositoryRoot);
|
|
361
|
+
const templateChanges = getTemplateChanges(repositoryRoot, fromTag);
|
|
362
|
+
const base = { tag: templateChanges.baseTag, commit: templateChanges.baseCommit };
|
|
363
|
+
const isLegacyBootstrap = !options.fromTag && LEGACY_TEMPLATE_BASELINE_TAG_PATTERN.test(fromTag);
|
|
364
|
+
const migrationFiles = isLegacyBootstrap
|
|
365
|
+
? templateChanges.files.filter(file => !isCustomerOwnedPath(file.path))
|
|
366
|
+
: templateChanges.files;
|
|
367
|
+
assertNoCustomerOwnedChanges(migrationFiles);
|
|
368
|
+
if (!migrationFiles.length) {
|
|
369
|
+
if (options.skipIfNoChanges) {
|
|
370
|
+
console.log(`No standalone template changes found since ${base.tag}.`);
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
throw new Error('No template changes were found');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const appJsonPath = path.join(repositoryRoot, TEMPLATE_PATH, TEMPLATE_VERSION_PATH);
|
|
377
|
+
const originalAppJson = fs.readFileSync(appJsonPath);
|
|
378
|
+
const baseState = readTemplateState(repositoryRoot, base.commit);
|
|
379
|
+
if (!Number.isInteger(baseState?.templateVersion)) {
|
|
380
|
+
throw new Error(`Version tag ${base.tag} does not contain ${TEMPLATE_VERSION_PATH} expo.extra.powerappsNative.templateVersion`);
|
|
381
|
+
}
|
|
382
|
+
const currentState = readTemplateState(repositoryRoot, null);
|
|
383
|
+
if (currentState?.templateVersion !== baseState.templateVersion) {
|
|
384
|
+
throw new Error(`Template version must not be advanced before release build (${TEMPLATE_VERSION_PATH} is ${currentState?.templateVersion ?? 'missing'}, expected ${baseState.templateVersion})`);
|
|
385
|
+
}
|
|
386
|
+
if (migrationFiles.some(file => file.path === TEMPLATE_VERSION_PATH)) {
|
|
387
|
+
throw new Error(`${TEMPLATE_VERSION_PATH} changes are limited to the release-owned templateVersion bump; add a structured migration handler before changing other fields`);
|
|
388
|
+
}
|
|
389
|
+
const expectedTargetVersion = baseState.templateVersion + 1;
|
|
390
|
+
const migrationId = options.id || `template-v${expectedTargetVersion}`;
|
|
391
|
+
const migrationMessage = options.message || `Release standalone template v${expectedTargetVersion}`;
|
|
392
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(migrationId)) throw new Error('--id may contain only letters, numbers, dots, underscores, and hyphens');
|
|
393
|
+
const profilePath = path.join(repositoryRoot, 'packages/power-apps-native-host/compatibility/current.json');
|
|
394
|
+
const profile = readJson(profilePath);
|
|
395
|
+
if (profile.targetTemplateVersion !== undefined && profile.targetTemplateVersion !== baseState.templateVersion) {
|
|
396
|
+
throw new Error(`Current profile targets template ${profile.targetTemplateVersion}, but migration starts at ${baseState.templateVersion}`);
|
|
397
|
+
}
|
|
398
|
+
const previousNativeRuntime = updateNativeRuntime(profile, repositoryRoot);
|
|
399
|
+
updateProfileFromTemplate(profile, repositoryRoot);
|
|
400
|
+
profile.targetTemplateVersion = expectedTargetVersion;
|
|
401
|
+
const targetAppJson = buildTemplateVersionContent(repositoryRoot, expectedTargetVersion, profile.nativeRuntime);
|
|
402
|
+
|
|
403
|
+
const paths = migrationFiles.map(file => file.path).sort();
|
|
311
404
|
const patches = [];
|
|
312
405
|
const patchFiles = {};
|
|
313
406
|
let tsconfigTransitions = null;
|
|
407
|
+
let gitignoreTransition = null;
|
|
314
408
|
for (const relativePath of paths) {
|
|
315
|
-
const baseContent = readGitFile(repositoryRoot,
|
|
409
|
+
const baseContent = readGitFile(repositoryRoot, base.commit, relativePath);
|
|
316
410
|
const targetPath = path.join(repositoryRoot, TEMPLATE_PATH, relativePath);
|
|
317
411
|
if (fs.existsSync(targetPath) && fs.lstatSync(targetPath).isSymbolicLink()) {
|
|
318
412
|
throw new Error(`Symbolic link template change ${relativePath} is not supported`);
|
|
@@ -323,11 +417,18 @@ function buildMigrationStep(repositoryRoot, options) {
|
|
|
323
417
|
tsconfigTransitions = buildTsconfigTransitions(relativePath, baseContent, targetContent);
|
|
324
418
|
continue;
|
|
325
419
|
}
|
|
420
|
+
if (relativePath === GITIGNORE_PATH) {
|
|
421
|
+
gitignoreTransition = {
|
|
422
|
+
from: readManagedGitignoreBlock(relativePath, baseContent, false),
|
|
423
|
+
to: readManagedGitignoreBlock(relativePath, targetContent, true),
|
|
424
|
+
};
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
326
427
|
if ([TEMPLATE_VERSION_PATH, 'package.json', 'package-lock.json'].includes(relativePath)) continue;
|
|
327
428
|
if (!isPatchableText(baseContent) || !isPatchableText(targetContent)) {
|
|
328
429
|
throw new Error(`Binary template change ${relativePath} cannot be represented by a unified migration patch`);
|
|
329
430
|
}
|
|
330
|
-
const fromMode = fileMode(repositoryRoot,
|
|
431
|
+
const fromMode = fileMode(repositoryRoot, base.commit, relativePath);
|
|
331
432
|
const toMode = fileMode(repositoryRoot, null, relativePath);
|
|
332
433
|
assertPatchableMode(relativePath, fromMode);
|
|
333
434
|
assertPatchableMode(relativePath, toMode);
|
|
@@ -342,60 +443,48 @@ function buildMigrationStep(repositoryRoot, options) {
|
|
|
342
443
|
targetContent === null ? '/dev/null' : relativePath,
|
|
343
444
|
baseContent?.toString('utf8') || '',
|
|
344
445
|
targetContent?.toString('utf8') || '',
|
|
345
|
-
|
|
346
|
-
|
|
446
|
+
base.tag,
|
|
447
|
+
migrationId,
|
|
347
448
|
{ context: 3 },
|
|
348
449
|
));
|
|
349
450
|
}
|
|
350
451
|
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
if (!recordedBaseState && !options.fromVersion) {
|
|
354
|
-
throw new Error(`Version tag ${baseTags[0]} does not contain ${TEMPLATE_VERSION_PATH} expo.extra.powerappsNative; rerun build with --from-version <version>`);
|
|
355
|
-
}
|
|
356
|
-
const baseState = recordedBaseState || { schemaVersion: 1, templateVersion: options.fromVersion };
|
|
357
|
-
const targetState = readJson(path.join(repositoryRoot, TEMPLATE_PATH, TEMPLATE_VERSION_PATH)).expo?.extra?.powerappsNative;
|
|
358
|
-
if (targetState.templateVersion !== baseState.templateVersion + 1) {
|
|
359
|
-
throw new Error(`Migration must advance exactly one template version (${baseState.templateVersion} -> ${baseState.templateVersion + 1})`);
|
|
360
|
-
}
|
|
452
|
+
const targetState = JSON.parse(targetAppJson).expo?.extra?.powerappsNative;
|
|
453
|
+
if (targetState.templateVersion !== expectedTargetVersion) throw new Error(`Migration must advance exactly one template version (${baseState.templateVersion} -> ${expectedTargetVersion})`);
|
|
361
454
|
|
|
362
|
-
const basePackageContent = readGitFile(repositoryRoot,
|
|
363
|
-
if (!basePackageContent) throw new Error(`Version tag ${
|
|
455
|
+
const basePackageContent = readGitFile(repositoryRoot, base.commit, 'package.json');
|
|
456
|
+
if (!basePackageContent) throw new Error(`Version tag ${base.tag} does not contain template package.json`);
|
|
364
457
|
const basePackage = JSON.parse(basePackageContent.toString('utf8'));
|
|
365
458
|
const targetPackage = readJson(path.join(repositoryRoot, TEMPLATE_PATH, 'package.json'));
|
|
366
|
-
const profilePath = path.join(repositoryRoot, 'packages/power-apps-native-host/compatibility/current.json');
|
|
367
|
-
const profile = readJson(profilePath);
|
|
368
|
-
if (profile.targetTemplateVersion !== undefined && profile.targetTemplateVersion !== baseState.templateVersion) {
|
|
369
|
-
throw new Error(`Current profile targets template ${profile.targetTemplateVersion}, but migration starts at ${baseState.templateVersion}`);
|
|
370
|
-
}
|
|
371
|
-
updateProfileFromTemplate(profile, repositoryRoot);
|
|
372
|
-
profile.targetTemplateVersion = targetState.templateVersion;
|
|
373
459
|
const step = {
|
|
374
460
|
schemaVersion: 1,
|
|
375
|
-
id:
|
|
376
|
-
fromTag:
|
|
377
|
-
fromCommit:
|
|
461
|
+
id: migrationId,
|
|
462
|
+
fromTag: base.tag,
|
|
463
|
+
fromCommit: base.commit,
|
|
378
464
|
fromTemplateVersion: baseState.templateVersion,
|
|
379
465
|
toTemplateVersion: targetState.templateVersion,
|
|
380
|
-
comments:
|
|
381
|
-
patchFile: `${
|
|
466
|
+
comments: [migrationMessage],
|
|
467
|
+
patchFile: `${migrationId}.patch`,
|
|
382
468
|
patchFiles,
|
|
383
|
-
packageTransitions:
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
overrides: packageTransitions(basePackage.overrides, targetPackage.overrides),
|
|
469
|
+
packageTransitions: buildPackageTransitions(basePackage, targetPackage),
|
|
470
|
+
nativeRuntimeTransition: {
|
|
471
|
+
from: previousNativeRuntime,
|
|
472
|
+
to: profile.nativeRuntime,
|
|
388
473
|
},
|
|
389
474
|
...(tsconfigTransitions ? { tsconfigTransitions } : {}),
|
|
475
|
+
...(gitignoreTransition ? { gitignoreTransition } : {}),
|
|
390
476
|
targetProfile: {
|
|
391
477
|
id: profile.id,
|
|
392
|
-
hostVersion:
|
|
478
|
+
hostVersion: RELEASE_HOST_VERSION,
|
|
393
479
|
expoSdk: profile.expoSdk,
|
|
394
480
|
expoVersion: profile.expoVersion,
|
|
395
481
|
expoDoctorVersion: profile.expoDoctorVersion,
|
|
396
482
|
managedDependencies: profile.managedDependencies,
|
|
397
483
|
managedDevDependencies: profile.managedDevDependencies,
|
|
398
484
|
managedOverrides: profile.managedOverrides,
|
|
485
|
+
managedScripts: profile.managedScripts,
|
|
486
|
+
managedFields: profile.managedFields,
|
|
487
|
+
nativeRuntime: profile.nativeRuntime,
|
|
399
488
|
},
|
|
400
489
|
};
|
|
401
490
|
if (!step.targetProfile.expoDoctorVersion) throw new Error('Current compatibility profile must define expoDoctorVersion');
|
|
@@ -406,62 +495,56 @@ function buildMigrationStep(repositoryRoot, options) {
|
|
|
406
495
|
return readJson(metadataPath);
|
|
407
496
|
});
|
|
408
497
|
validateMigrationEdges([...existingEdges, step], targetState.templateVersion);
|
|
409
|
-
if (!profile.migrations.includes(
|
|
498
|
+
if (!profile.migrations.includes(migrationId)) profile.migrations.push(migrationId);
|
|
410
499
|
|
|
411
500
|
const migrationDirectory = path.join(repositoryRoot, 'packages/power-apps-native-host/compatibility/migrations');
|
|
412
|
-
const migrationPath = path.join(migrationDirectory, `${
|
|
501
|
+
const migrationPath = path.join(migrationDirectory, `${migrationId}.json`);
|
|
413
502
|
const patchPath = path.join(migrationDirectory, step.patchFile);
|
|
414
|
-
if (fs.existsSync(migrationPath) || fs.existsSync(patchPath)) throw new Error(`Migration ${
|
|
415
|
-
console.log(`${options.dryRun ? '[dry-run] ' : ''}Build migration ${
|
|
503
|
+
if (fs.existsSync(migrationPath) || fs.existsSync(patchPath)) throw new Error(`Migration ${migrationId} already exists`);
|
|
504
|
+
console.log(`${options.dryRun ? '[dry-run] ' : ''}Build migration ${migrationId} from ${base.tag}`);
|
|
416
505
|
if (!options.dryRun) {
|
|
417
506
|
fs.mkdirSync(migrationDirectory, { recursive: true });
|
|
418
507
|
const temporarySuffix = `.tmp-${process.pid}-${Date.now()}`;
|
|
419
508
|
const temporaryMigrationPath = `${migrationPath}${temporarySuffix}`;
|
|
420
509
|
const temporaryPatchPath = `${patchPath}${temporarySuffix}`;
|
|
421
510
|
const temporaryProfilePath = `${profilePath}${temporarySuffix}`;
|
|
511
|
+
const temporaryAppJsonPath = `${appJsonPath}${temporarySuffix}`;
|
|
422
512
|
const originalProfile = fs.readFileSync(profilePath);
|
|
423
|
-
const recordContents = new Map(records.map(record => {
|
|
424
|
-
const recordPath = path.join(repositoryRoot, MIGRATION_CHANGE_PATH, record.name);
|
|
425
|
-
return [recordPath, fs.readFileSync(recordPath)];
|
|
426
|
-
}));
|
|
427
513
|
const journalPath = path.join(repositoryRoot, AUTHORING_JOURNAL_PATH);
|
|
428
514
|
const journal = {
|
|
429
515
|
schemaVersion: 1,
|
|
430
516
|
status: 'preparing',
|
|
431
517
|
profilePath,
|
|
432
518
|
originalProfile: originalProfile.toString('base64'),
|
|
519
|
+
appJsonPath,
|
|
520
|
+
originalAppJson: originalAppJson.toString('base64'),
|
|
433
521
|
outputPaths: [migrationPath, patchPath],
|
|
434
|
-
records: [...recordContents].map(([recordPath, content]) => ({
|
|
435
|
-
path: recordPath,
|
|
436
|
-
content: content.toString('base64'),
|
|
437
|
-
})),
|
|
438
522
|
};
|
|
439
523
|
writeFileAtomic.sync(journalPath, `${JSON.stringify(journal, null, 2)}\n`, { mode: 0o600 });
|
|
440
524
|
try {
|
|
441
525
|
fs.writeFileSync(temporaryMigrationPath, `${JSON.stringify(step, null, 2)}\n`);
|
|
442
526
|
fs.writeFileSync(temporaryPatchPath, patches.join('\n'), 'utf8');
|
|
443
527
|
fs.writeFileSync(temporaryProfilePath, `${JSON.stringify(profile, null, 2)}\n`);
|
|
528
|
+
fs.writeFileSync(temporaryAppJsonPath, targetAppJson, 'utf8');
|
|
444
529
|
readJson(temporaryMigrationPath);
|
|
445
530
|
readJson(temporaryProfilePath);
|
|
531
|
+
readJson(temporaryAppJsonPath);
|
|
446
532
|
parsePatch(fs.readFileSync(temporaryPatchPath, 'utf8'));
|
|
447
533
|
fs.renameSync(temporaryPatchPath, patchPath);
|
|
448
534
|
fs.renameSync(temporaryMigrationPath, migrationPath);
|
|
535
|
+
fs.renameSync(temporaryAppJsonPath, appJsonPath);
|
|
449
536
|
fs.renameSync(temporaryProfilePath, profilePath);
|
|
450
|
-
for (const recordPath of recordContents.keys()) fs.unlinkSync(recordPath);
|
|
451
537
|
journal.status = 'committed';
|
|
452
538
|
writeFileAtomic.sync(journalPath, `${JSON.stringify(journal, null, 2)}\n`, { mode: 0o600 });
|
|
453
539
|
fs.rmSync(journalPath, { force: true });
|
|
454
540
|
} catch (error) {
|
|
455
541
|
fs.writeFileSync(profilePath, originalProfile);
|
|
542
|
+
fs.writeFileSync(appJsonPath, originalAppJson);
|
|
456
543
|
fs.rmSync(migrationPath, { force: true });
|
|
457
544
|
fs.rmSync(patchPath, { force: true });
|
|
458
|
-
for (const temporaryPath of [temporaryMigrationPath, temporaryPatchPath, temporaryProfilePath]) {
|
|
545
|
+
for (const temporaryPath of [temporaryMigrationPath, temporaryPatchPath, temporaryProfilePath, temporaryAppJsonPath]) {
|
|
459
546
|
fs.rmSync(temporaryPath, { force: true });
|
|
460
547
|
}
|
|
461
|
-
for (const [recordPath, content] of recordContents) {
|
|
462
|
-
fs.mkdirSync(path.dirname(recordPath), { recursive: true });
|
|
463
|
-
fs.writeFileSync(recordPath, content);
|
|
464
|
-
}
|
|
465
548
|
fs.rmSync(journalPath, { force: true });
|
|
466
549
|
throw error;
|
|
467
550
|
}
|
|
@@ -522,13 +605,25 @@ function validateMigrationEdges(migrations, targetVersion) {
|
|
|
522
605
|
}
|
|
523
606
|
if (migrations.length) {
|
|
524
607
|
const firstVersion = Math.min(...fromVersions);
|
|
608
|
+
if (firstVersion !== 1) throw new Error('Migration graph must start at template version 1');
|
|
525
609
|
for (let version = firstVersion; version < targetVersion; version += 1) {
|
|
526
610
|
if (!fromVersions.has(version)) throw new Error(`Migration graph is missing template version ${version} -> ${version + 1}`);
|
|
527
611
|
}
|
|
528
612
|
if (Math.max(...migrations.map(migration => migration.toTemplateVersion)) !== targetVersion) {
|
|
529
613
|
throw new Error(`Migration graph does not reach target template version ${targetVersion}`);
|
|
530
614
|
}
|
|
615
|
+
} else if (targetVersion > 1) {
|
|
616
|
+
throw new Error(`Migration graph does not reach target template version ${targetVersion}`);
|
|
531
617
|
}
|
|
532
618
|
}
|
|
533
619
|
|
|
534
|
-
module.exports = {
|
|
620
|
+
module.exports = {
|
|
621
|
+
RELEASE_HOST_VERSION,
|
|
622
|
+
buildPackageTransitions,
|
|
623
|
+
buildTsconfigTransitions,
|
|
624
|
+
getTemplateChanges,
|
|
625
|
+
nextNativeRuntime,
|
|
626
|
+
runAuthoringCommand,
|
|
627
|
+
updateProfileFromPackage,
|
|
628
|
+
validateDevPlayerDependencies,
|
|
629
|
+
};
|