@expo-harmony/cli 55.0.26-harmony.1 → 55.0.26-harmony.11
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 +87 -38
- package/build/args.d.ts +19 -0
- package/build/args.js +27 -0
- package/build/bin/expo-harmony.d.ts +2 -0
- package/build/bin/expo-harmony.js +1 -1
- package/build/buildHap/build.d.ts +18 -0
- package/build/buildHap/build.js +38 -0
- package/build/buildHap/index.d.ts +2 -0
- package/build/buildHap/index.js +19 -0
- package/build/buildHap/options.d.ts +7 -0
- package/build/buildHap/options.js +30 -0
- package/build/cli.d.ts +3 -0
- package/build/cli.js +40 -0
- package/build/command.d.ts +9 -0
- package/build/command.js +20 -0
- package/build/development/manifest.d.ts +1 -0
- package/build/development/manifest.js +121 -0
- package/build/development/protocol.d.ts +3 -0
- package/build/development/protocol.js +33 -0
- package/build/development/session.d.ts +5 -0
- package/build/development/session.js +66 -0
- package/build/doctor/doctor.d.ts +20 -0
- package/build/doctor/doctor.js +226 -0
- package/build/doctor/index.d.ts +2 -0
- package/build/doctor/index.js +16 -0
- package/build/doctor/options.d.ts +5 -0
- package/build/doctor/options.js +15 -0
- package/build/entry.d.ts +2 -0
- package/build/entry.js +47 -0
- package/build/errors.d.ts +12 -0
- package/build/errors.js +16 -0
- package/build/expo.d.ts +19 -0
- package/build/expo.js +53 -0
- package/build/exportEmbed/export.d.ts +18 -0
- package/build/exportEmbed/export.js +124 -0
- package/build/exportEmbed/index.d.ts +2 -0
- package/build/exportEmbed/index.js +19 -0
- package/build/exportEmbed/manifest.d.ts +44 -0
- package/build/exportEmbed/manifest.js +171 -0
- package/build/exportEmbed/options.d.ts +7 -0
- package/build/exportEmbed/options.js +22 -0
- package/build/file.d.ts +10 -0
- package/build/file.js +110 -0
- package/build/help.d.ts +1 -0
- package/build/help.js +44 -0
- package/build/index.d.ts +14 -0
- package/build/index.js +6 -5
- package/build/internal/development.d.ts +1 -0
- package/build/internal/development.js +8 -0
- package/build/internal/export.d.ts +1 -0
- package/build/internal/export.js +80 -0
- package/build/internal/prebuild.d.ts +3 -0
- package/build/internal/prebuild.js +9 -0
- package/build/log.d.ts +4 -0
- package/build/log.js +6 -0
- package/build/modules/index.d.ts +2 -0
- package/build/modules/index.js +16 -0
- package/build/modules/modules.d.ts +42 -0
- package/build/modules/modules.js +84 -0
- package/build/modules/options.d.ts +10 -0
- package/build/modules/options.js +45 -0
- package/build/native/bare.d.ts +4 -0
- package/build/native/bare.js +130 -0
- package/build/native/build.d.ts +15 -0
- package/build/native/build.js +69 -0
- package/build/native/cache.d.ts +11 -0
- package/build/native/cache.js +73 -0
- package/build/native/hvigor.d.ts +3 -0
- package/build/native/hvigor.js +83 -0
- package/build/native/install.d.ts +5 -0
- package/build/native/install.js +13 -0
- package/build/native/prepareProject.d.ts +11 -0
- package/build/native/prepareProject.js +68 -0
- package/build/native/project.d.ts +8 -0
- package/build/native/project.js +58 -0
- package/build/native/toolchain.d.ts +16 -0
- package/build/native/toolchain.js +172 -0
- package/build/native/types.d.ts +47 -0
- package/build/native/types.js +2 -0
- package/build/path.d.ts +4 -0
- package/build/path.js +27 -0
- package/build/prebuild/check.d.ts +10 -0
- package/build/prebuild/check.js +151 -0
- package/build/prebuild/clean.d.ts +2 -0
- package/build/prebuild/clean.js +50 -0
- package/build/prebuild/index.d.ts +2 -0
- package/build/prebuild/index.js +51 -0
- package/build/prebuild/options.d.ts +10 -0
- package/build/prebuild/options.js +50 -0
- package/build/prebuild/prebuild.d.ts +6 -0
- package/build/prebuild/prebuild.js +70 -0
- package/build/prebuild/template.d.ts +8 -0
- package/build/prebuild/template.js +86 -0
- package/build/process.d.ts +41 -0
- package/build/process.js +324 -0
- package/build/profile.d.ts +1 -0
- package/build/profile.js +12 -0
- package/build/project.d.ts +2 -0
- package/build/project.js +23 -0
- package/build/projectLock.d.ts +2 -0
- package/build/projectLock.js +185 -0
- package/build/run/devices.d.ts +29 -0
- package/build/run/devices.js +223 -0
- package/build/run/emulators.d.ts +13 -0
- package/build/run/emulators.js +82 -0
- package/build/run/index.d.ts +2 -0
- package/build/run/index.js +25 -0
- package/build/run/metro.d.ts +21 -0
- package/build/run/metro.js +162 -0
- package/build/run/options.d.ts +14 -0
- package/build/run/options.js +62 -0
- package/build/run/run.d.ts +46 -0
- package/build/run/run.js +135 -0
- package/build/runtime/config.d.ts +37 -0
- package/build/runtime/config.js +169 -0
- package/build/runtime/contract.d.ts +5 -0
- package/build/runtime/contract.js +109 -0
- package/build/runtime/fingerprint.d.ts +1 -0
- package/build/runtime/fingerprint.js +49 -0
- package/build/runtime/index.d.ts +2 -0
- package/build/runtime/index.js +50 -0
- package/build/start/index.d.ts +2 -0
- package/build/start/index.js +38 -0
- package/build/start/options.d.ts +9 -0
- package/build/start/options.js +38 -0
- package/build/updates/export.d.ts +14 -0
- package/build/updates/export.js +46 -0
- package/build/updates/index.d.ts +2 -0
- package/build/updates/index.js +96 -0
- package/build/upstream.d.ts +9 -0
- package/build/upstream.js +17 -0
- package/package.json +21 -5
- package/src/bin/expo-harmony.ts +2 -2
- package/src/buildHap/build.ts +13 -105
- package/src/buildHap/index.ts +19 -0
- package/src/cli.ts +27 -225
- package/src/command.ts +24 -0
- package/src/development/manifest.ts +127 -0
- package/src/development/protocol.ts +42 -0
- package/src/development/session.ts +83 -0
- package/src/doctor/doctor.ts +67 -62
- package/src/doctor/index.ts +16 -0
- package/src/exportEmbed/export.ts +73 -72
- package/src/exportEmbed/index.ts +17 -0
- package/src/file.ts +20 -11
- package/src/help.ts +41 -0
- package/src/index.ts +5 -3
- package/src/internal/development.ts +8 -0
- package/src/internal/export.ts +82 -0
- package/src/internal/prebuild.ts +3 -0
- package/src/log.ts +5 -0
- package/src/modules/index.ts +16 -0
- package/src/native/bare.ts +145 -0
- package/src/native/build.ts +83 -0
- package/src/{run → native}/cache.ts +10 -9
- package/src/native/hvigor.ts +88 -0
- package/src/native/install.ts +17 -0
- package/src/native/prepareProject.ts +99 -0
- package/src/native/project.ts +88 -0
- package/src/{tools.ts → native/toolchain.ts} +38 -140
- package/src/native/types.ts +44 -0
- package/src/prebuild/check.ts +56 -31
- package/src/prebuild/clean.ts +3 -3
- package/src/prebuild/index.ts +56 -0
- package/src/prebuild/prebuild.ts +11 -1
- package/src/prebuild/template.ts +2 -2
- package/src/process.ts +175 -64
- package/src/profile.ts +13 -0
- package/src/projectLock.ts +9 -1
- package/src/run/devices.ts +24 -11
- package/src/run/emulators.ts +4 -6
- package/src/run/index.ts +25 -0
- package/src/run/metro.ts +10 -1
- package/src/run/options.ts +2 -0
- package/src/run/run.ts +63 -129
- package/src/runtime/config.ts +207 -0
- package/src/runtime/contract.ts +167 -0
- package/src/runtime/fingerprint.ts +49 -0
- package/src/runtime/index.ts +54 -0
- package/src/start/index.ts +37 -0
- package/src/start/options.ts +12 -0
- package/src/updates/export.ts +60 -0
- package/src/updates/index.ts +89 -0
- package/src/buildHap/common.ts +0 -145
- package/src/run/install.ts +0 -41
package/src/index.ts
CHANGED
|
@@ -2,11 +2,13 @@ export { runAsync } from './cli';
|
|
|
2
2
|
export { buildHarmonyAsync } from './buildHap/build';
|
|
3
3
|
export type { HarmonyBuildOptions, HarmonyBuildResult } from './buildHap/build';
|
|
4
4
|
export { exportEmbedAsync } from './exportEmbed/export';
|
|
5
|
-
export { resolveHarmonyBuildPlanAsync
|
|
6
|
-
export {
|
|
5
|
+
export { resolveHarmonyBuildPlanAsync } from './native/project';
|
|
6
|
+
export type { HarmonyBuildPlan, HarmonyBuildMode } from './native/types';
|
|
7
|
+
export { resolveHarmonyToolchain } from './native/toolchain';
|
|
8
|
+
export { installHarmonyDependenciesAsync } from './native/install';
|
|
7
9
|
export { formatModulesResult, runModulesCommandAsync } from './modules/modules';
|
|
8
10
|
export type { ModulesCommandResult } from './modules/modules';
|
|
9
|
-
export { commitHarmonyNativeBuildCacheAsync, prepareHarmonyNativeBuildCacheAsync } from './
|
|
11
|
+
export { commitHarmonyNativeBuildCacheAsync, prepareHarmonyNativeBuildCacheAsync } from './native/cache';
|
|
10
12
|
export { runHarmonyAsync } from './run/run';
|
|
11
13
|
export type { HarmonyExportManifest } from './exportEmbed/manifest';
|
|
12
14
|
export type { HarmonyRunOptions, HarmonyRunResult } from './run/run';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { HarmonyCliError } from '../errors';
|
|
6
|
+
|
|
7
|
+
async function main(): Promise<void> {
|
|
8
|
+
const [root, serialized, destination] = process.argv.slice(2);
|
|
9
|
+
const project = createRequire(path.join(root, 'package.json'));
|
|
10
|
+
const expo = createRequire(project.resolve('expo/package.json'));
|
|
11
|
+
const require = createRequire(expo.resolve('@expo/cli/package.json'));
|
|
12
|
+
if (require('./package.json').version !== '55.0.32') {
|
|
13
|
+
throw new HarmonyCliError('ERR_HARMONY_EXPORT_UPSTREAM', 'Harmony asset export requires Expo CLI 55.0.32.', { operation: 'export' });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const options = JSON.parse(serialized);
|
|
17
|
+
require('./build/src/utils/nodeEnv').setNodeEnv('production');
|
|
18
|
+
require('./build/src/utils/nodeEnv').loadEnvFiles(root);
|
|
19
|
+
if (options.update) {
|
|
20
|
+
const directory = path.join(path.dirname(destination), 'export');
|
|
21
|
+
await require('./build/src/export/exportApp').exportAppAsync(root, {
|
|
22
|
+
platforms: ['harmony'], outputDir: directory, clear: options.resetCache,
|
|
23
|
+
dev: false, sourceMaps: true, minify: false, bytecode: false,
|
|
24
|
+
});
|
|
25
|
+
const metadata = JSON.parse(await fs.readFile(path.join(directory, 'metadata.json'), 'utf8')).fileMetadata.harmony;
|
|
26
|
+
const mime = require('mime-types');
|
|
27
|
+
const assets = [];
|
|
28
|
+
for (const asset of metadata.assets) {
|
|
29
|
+
const source = path.join(directory, asset.path);
|
|
30
|
+
const data = await fs.readFile(source);
|
|
31
|
+
const key = path.basename(asset.path, path.extname(asset.path));
|
|
32
|
+
const relative = `assets/${key}.${asset.ext}`;
|
|
33
|
+
await fs.mkdir(path.join(options.assetsDest, 'assets'), { recursive: true });
|
|
34
|
+
await fs.copyFile(source, path.join(options.assetsDest, relative));
|
|
35
|
+
assets.push({ key, hash: crypto.createHash('sha256').update(Uint8Array.from(data)).digest('base64url'),
|
|
36
|
+
contentType: mime.lookup(asset.ext) || 'application/octet-stream', fileExtension: '.' + asset.ext,
|
|
37
|
+
embeddedAssetFilename: relative });
|
|
38
|
+
}
|
|
39
|
+
await require('@expo/metro/metro/shared/output/bundle').save({
|
|
40
|
+
code: await fs.readFile(path.join(directory, metadata.bundle), 'utf8'),
|
|
41
|
+
map: await fs.readFile(path.join(directory, metadata.bundle + '.map'), 'utf8'),
|
|
42
|
+
}, options, console.log);
|
|
43
|
+
await fs.writeFile(destination, JSON.stringify(assets));
|
|
44
|
+
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { bundle, assets, files } = await require('./build/src/export/embed/exportEmbedAsync').exportEmbedBundleAndAssetsAsync(root, options);
|
|
49
|
+
const { getAssetLocalPath } = require('./build/src/export/metroAssetLocalPath');
|
|
50
|
+
const mime = require('mime-types');
|
|
51
|
+
const metadata: Record<string, unknown>[] = [];
|
|
52
|
+
|
|
53
|
+
for (const asset of assets) {
|
|
54
|
+
if (!Array.isArray(asset.fileHashes) || asset.fileHashes.length !== asset.files.length) {
|
|
55
|
+
throw new HarmonyCliError('ERR_HARMONY_EXPORT_ASSETS', 'Metro assets must include expo-asset fileHashes.', { operation: 'export' });
|
|
56
|
+
}
|
|
57
|
+
for (let index = 0; index < asset.files.length; index++) {
|
|
58
|
+
const data = await fs.readFile(asset.files[index]);
|
|
59
|
+
metadata.push({ key: asset.fileHashes[index], hash: crypto.createHash('sha256').update(Uint8Array.from(data)).digest('base64url'),
|
|
60
|
+
contentType: mime.lookup(asset.type) || 'application/octet-stream', fileExtension: '.' + asset.type,
|
|
61
|
+
embeddedAssetFilename: getAssetLocalPath(asset, { platform: 'harmony', scale: asset.scales[index] }) });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await fs.mkdir(path.dirname(options.bundleOutput), { recursive: true });
|
|
66
|
+
await Promise.all([
|
|
67
|
+
require('@expo/metro/metro/shared/output/bundle').save(bundle, options, console.log),
|
|
68
|
+
require('./build/src/export/persistMetroAssets').persistMetroAssetsAsync(root, assets, { platform: 'harmony', outputDirectory: options.assetsDest }),
|
|
69
|
+
]);
|
|
70
|
+
if (files.size > 0) {
|
|
71
|
+
const { copyPublicFolderAsync, getPublicFolderPath } = require('./build/src/export/publicFolder');
|
|
72
|
+
const { DOM_COMPONENTS_BUNDLE_DIR } = require('./build/src/start/server/middleware/DomComponentsMiddleware');
|
|
73
|
+
await require('./build/src/export/saveAssets').persistMetroFilesAsync(files, options.assetsDest);
|
|
74
|
+
await copyPublicFolderAsync(getPublicFolderPath(root), path.join(options.assetsDest, DOM_COMPONENTS_BUNDLE_DIR));
|
|
75
|
+
}
|
|
76
|
+
await fs.writeFile(destination, JSON.stringify(metadata));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main().catch((error) => {
|
|
80
|
+
console.error(error);
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
});
|
package/src/log.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { parseCommandArgs, type Command } from '../command';
|
|
2
|
+
import { parseModulesArgs } from './options';
|
|
3
|
+
|
|
4
|
+
export const command: Command = async (argv, io) => {
|
|
5
|
+
const args = parseCommandArgs(parseModulesArgs, argv, io);
|
|
6
|
+
if (!args) return 0;
|
|
7
|
+
|
|
8
|
+
const { projectRoot: root, options } = args;
|
|
9
|
+
const { formatModulesResult, runModulesCommandAsync } = await import('./modules.js');
|
|
10
|
+
|
|
11
|
+
const result = await runModulesCommandAsync(root, options);
|
|
12
|
+
|
|
13
|
+
io.log(formatModulesResult(result));
|
|
14
|
+
|
|
15
|
+
return result.ok ? 0 : 1;
|
|
16
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import JSON5 from 'json5';
|
|
4
|
+
|
|
5
|
+
import { HarmonyCliError } from '../errors';
|
|
6
|
+
import type { BareHarmonyBuildPlan } from './types';
|
|
7
|
+
|
|
8
|
+
function isBareHarmonyProject(root: string): boolean {
|
|
9
|
+
return fs.existsSync(path.join(root, 'harmony'))
|
|
10
|
+
&& !fs.existsSync(path.join(root, 'harmony/.expo-harmony-template'))
|
|
11
|
+
&& !fs.existsSync(path.join(root, '.expo/harmony/cng-manifest.json'));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function record(value: unknown, field: string): Record<string, unknown> {
|
|
15
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
16
|
+
throw new HarmonyCliError('ERR_HARMONY_NATIVE_PROJECT', `Native ${field} must be an object.`, { operation: 'resolve-build' });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return value as Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function records(value: unknown, field: string): Record<string, unknown>[] {
|
|
23
|
+
if (!Array.isArray(value)) {
|
|
24
|
+
throw new HarmonyCliError('ERR_HARMONY_NATIVE_PROJECT', `Native ${field} must be an array.`, { operation: 'resolve-build' });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return value.map(item => record(item, field));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readJson(file: string): Record<string, unknown> {
|
|
31
|
+
try {
|
|
32
|
+
return record(JSON5.parse(fs.readFileSync(file, 'utf8')), file);
|
|
33
|
+
} catch (cause) {
|
|
34
|
+
throw new HarmonyCliError('ERR_HARMONY_NATIVE_PROJECT', `Cannot read native project file ${file}: ${cause.message}`, { cause, operation: 'resolve-build' });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function identifier(value: unknown, field: string): string {
|
|
39
|
+
if (typeof value !== 'string' || !/^[A-Za-z_][A-Za-z0-9_.]*$/.test(value)) {
|
|
40
|
+
throw new HarmonyCliError('ERR_HARMONY_NATIVE_PROJECT', `The native project must specify a valid ${field}.`, { operation: 'resolve-build' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function selectDefault(items: Record<string, unknown>[], field: string): Record<string, unknown> {
|
|
47
|
+
if (items.length === 0) {
|
|
48
|
+
throw new HarmonyCliError('ERR_HARMONY_NATIVE_PROJECT', `No ${field} is defined in harmony/build-profile.json5.`, { operation: 'resolve-build' });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const defaults = items.filter(item => item.name === 'default');
|
|
52
|
+
|
|
53
|
+
if (defaults.length === 1) return defaults[0];
|
|
54
|
+
if (items.length === 1) return items[0];
|
|
55
|
+
|
|
56
|
+
throw new HarmonyCliError('ERR_HARMONY_NATIVE_PROJECT', `Multiple ${field} are defined; name the intended default "default" in harmony/build-profile.json5.`, { operation: 'resolve-build' });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveBareHarmonyBuildPlan(root: string, mode: 'debug' | 'release'): BareHarmonyBuildPlan {
|
|
60
|
+
const harmony = path.join(root, 'harmony');
|
|
61
|
+
const entry = path.join(harmony, 'entry');
|
|
62
|
+
|
|
63
|
+
for (const directory of [harmony, entry]) {
|
|
64
|
+
if (!fs.existsSync(directory)
|
|
65
|
+
|| !fs.lstatSync(directory).isDirectory() || fs.lstatSync(directory).isSymbolicLink()) {
|
|
66
|
+
throw new HarmonyCliError('ERR_HARMONY_NATIVE_PROJECT', `Bare Harmony projects require a regular directory at ${directory}.`, { operation: 'resolve-build' });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const profile = readJson(path.join(harmony, 'build-profile.json5'));
|
|
71
|
+
const app = record(readJson(path.join(harmony, 'AppScope/app.json5')).app, 'app');
|
|
72
|
+
const module = record(readJson(path.join(entry, 'src/main/module.json5')).module, 'module');
|
|
73
|
+
|
|
74
|
+
const name = identifier(module.name, 'module.name');
|
|
75
|
+
const registration = records(profile.modules, 'modules').find(item => item.name === name);
|
|
76
|
+
if (!registration || typeof registration.srcPath !== 'string'
|
|
77
|
+
|| path.resolve(harmony, registration.srcPath) !== entry || module.type !== 'entry') {
|
|
78
|
+
throw new HarmonyCliError('ERR_HARMONY_NATIVE_PROJECT', 'Bare Harmony autolinking requires an entry module at harmony/entry, registered in build-profile.json5.', { operation: 'resolve-build' });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const config = record(profile.app, 'build-profile.app');
|
|
82
|
+
const selected = selectDefault(records(config.products, 'products'), 'products');
|
|
83
|
+
const product = identifier(selected.name, 'product name');
|
|
84
|
+
const targets = records(registration.targets, 'module targets').filter((item) => {
|
|
85
|
+
if (item.applyToProducts === undefined) return true;
|
|
86
|
+
if (!Array.isArray(item.applyToProducts) || item.applyToProducts.some(value => typeof value !== 'string')) {
|
|
87
|
+
throw new HarmonyCliError('ERR_HARMONY_NATIVE_PROJECT', 'Native target.applyToProducts must be an array of product names.', { operation: 'resolve-build' });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return item.applyToProducts.includes(product);
|
|
91
|
+
});
|
|
92
|
+
const target = identifier(selectDefault(targets, 'module targets').name, 'target name');
|
|
93
|
+
|
|
94
|
+
const ability = identifier(module.mainElement, 'module.mainElement');
|
|
95
|
+
if (!records(module.abilities, 'abilities').some(item => item.name === ability)) {
|
|
96
|
+
throw new HarmonyCliError('ERR_HARMONY_NATIVE_PROJECT', `Ability ${ability} is missing from module.json5.`, { operation: 'resolve-build' });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const bundle = identifier(app.bundleName, 'app.bundleName');
|
|
100
|
+
const signed = Boolean(selected.signingConfig);
|
|
101
|
+
if (signed && !records(config.signingConfigs, 'signingConfigs').some(item => item.name === selected.signingConfig)) {
|
|
102
|
+
throw new HarmonyCliError('ERR_HARMONY_NATIVE_PROJECT', `Signing config ${selected.signingConfig} is referenced by the product but is not defined.`, { operation: 'resolve-build' });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const rawfile = path.join(entry, 'src/main/resources/rawfile');
|
|
106
|
+
const metadata = path.join(root, '.expo/harmony/export');
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
workflow: 'bare',
|
|
110
|
+
abilityName: ability,
|
|
111
|
+
buildMode: mode,
|
|
112
|
+
bundleName: bundle,
|
|
113
|
+
harmonyRoot: harmony,
|
|
114
|
+
moduleName: name,
|
|
115
|
+
moduleRoot: entry,
|
|
116
|
+
productName: product,
|
|
117
|
+
targetName: target,
|
|
118
|
+
expectedHap: path.join(entry, `build/${target}/outputs/${target}/${name}-${target}-${signed ? 'signed' : 'unsigned'}.hap`),
|
|
119
|
+
hvigorArgs: ['--mode', 'module', '-p', `module=${name}@${target}`, '-p', `product=${product}`, '-p', `buildMode=${mode}`, '--no-daemon', 'assembleHap'],
|
|
120
|
+
exportPaths: {
|
|
121
|
+
bundle: path.join(rawfile, 'hermes_bundle.hbc'),
|
|
122
|
+
manifest: path.join(root, '.expo/harmony/export-manifest.json'),
|
|
123
|
+
metadataRoot: metadata,
|
|
124
|
+
rawfileRoot: rawfile,
|
|
125
|
+
sourceMap: path.join(metadata, 'hermes_bundle.hbc.map'),
|
|
126
|
+
},
|
|
127
|
+
nativeCache: {
|
|
128
|
+
invalidationRoots: [path.join(entry, '.cxx'), path.join(entry, 'build')],
|
|
129
|
+
stateFile: path.join(entry, '.cxx/.expo-harmony-native-dependencies.json'),
|
|
130
|
+
},
|
|
131
|
+
nativeInputs: {
|
|
132
|
+
lockfile: path.join(harmony, 'oh-package-lock.json5'),
|
|
133
|
+
manifest: path.join(harmony, 'oh-package.json5'),
|
|
134
|
+
},
|
|
135
|
+
projectFiles: {
|
|
136
|
+
hvigorConfig: path.join(harmony, 'hvigor/hvigor-config.json5'),
|
|
137
|
+
moduleHvigor: path.join(entry, 'hvigorfile.ts'),
|
|
138
|
+
moduleJson: path.join(entry, 'src/main/module.json5'),
|
|
139
|
+
projectBuildProfile: path.join(harmony, 'build-profile.json5'),
|
|
140
|
+
rootHvigor: path.join(harmony, 'hvigorfile.ts'),
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export { isBareHarmonyProject, resolveBareHarmonyBuildPlan };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { getConfig } from '@expo/config';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { commitHarmonyNativeBuildCacheAsync, prepareHarmonyNativeBuildCacheAsync } from './cache';
|
|
5
|
+
import { installHarmonyDependenciesAsync } from './install';
|
|
6
|
+
import type { HarmonyToolchain } from './toolchain';
|
|
7
|
+
import type { HarmonyBuildPlan } from './types';
|
|
8
|
+
import { HarmonyCliError } from '../errors';
|
|
9
|
+
import { exportEmbedAsync } from '../exportEmbed/export';
|
|
10
|
+
import { isNonEmptyRegularFile } from '../file';
|
|
11
|
+
import { progress, type Log } from '../log';
|
|
12
|
+
import { toPosixPath } from '../path';
|
|
13
|
+
import { runCheckedAsync } from '../process';
|
|
14
|
+
import { timedAsync } from '../profile';
|
|
15
|
+
|
|
16
|
+
export interface NativeBuildResult {
|
|
17
|
+
export: null | { assetCount: number; bundleSha256: string; sourceMapSha256: string };
|
|
18
|
+
hapPath: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function buildNativeAsync(
|
|
22
|
+
root: string,
|
|
23
|
+
plan: HarmonyBuildPlan,
|
|
24
|
+
tools: HarmonyToolchain,
|
|
25
|
+
options: { io?: Log; resetCache?: boolean },
|
|
26
|
+
steps: Record<string, number>
|
|
27
|
+
): Promise<NativeBuildResult> {
|
|
28
|
+
let exported: NativeBuildResult['export'] = null;
|
|
29
|
+
const embedded = plan.buildMode === 'release' || getConfig(root).exp.updates?.useNativeDebug === true;
|
|
30
|
+
if (embedded) {
|
|
31
|
+
progress(options, 'Exporting the release Hermes bundle');
|
|
32
|
+
const manifest = await timedAsync(steps, 'export', () => exportEmbedAsync(root, {
|
|
33
|
+
resetCache: options.resetCache,
|
|
34
|
+
skipDoctor: true,
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
exported = {
|
|
38
|
+
assetCount: manifest.assets.length,
|
|
39
|
+
bundleSha256: manifest.bundle.sha256,
|
|
40
|
+
sourceMapSha256: manifest.sourceMap.sha256,
|
|
41
|
+
};
|
|
42
|
+
} else {
|
|
43
|
+
steps.export = 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
progress(options, 'Installing Harmony project dependencies');
|
|
47
|
+
await timedAsync(steps, 'ohpm', () => installHarmonyDependenciesAsync(plan, tools));
|
|
48
|
+
|
|
49
|
+
progress(options, 'Checking Harmony native dependency cache');
|
|
50
|
+
const cache = await timedAsync(steps, 'nativeCache', () => prepareHarmonyNativeBuildCacheAsync(root, plan));
|
|
51
|
+
if (cache.changed) progress(options, 'Invalidated stale Harmony native build objects');
|
|
52
|
+
|
|
53
|
+
progress(options, `Building the ${plan.buildMode} HAP`);
|
|
54
|
+
await timedAsync(steps, 'build', () => runCheckedAsync(tools.hvigor.command, [
|
|
55
|
+
...tools.hvigor.args,
|
|
56
|
+
...plan.hvigorArgs,
|
|
57
|
+
], {
|
|
58
|
+
code: 'ERR_HARMONY_BUILD_FAILED',
|
|
59
|
+
cwd: plan.harmonyRoot,
|
|
60
|
+
env: {
|
|
61
|
+
...process.env,
|
|
62
|
+
EXPO_HARMONY_NODE: process.env.EXPO_HARMONY_NODE || process.execPath,
|
|
63
|
+
EXPO_METRO_TARGET: 'harmony',
|
|
64
|
+
HERMES_V1_ENABLED: 'true',
|
|
65
|
+
...(plan.workflow === 'bare' ? { EXPO_HARMONY_NATIVE_PREPARED: '1' } : {}),
|
|
66
|
+
...(embedded ? { EXPO_HARMONY_BUNDLE_PREBUILT: '1' } : {}),
|
|
67
|
+
...(tools.sdkHome && !process.env.DEVECO_SDK_HOME
|
|
68
|
+
? { DEVECO_SDK_HOME: tools.sdkHome }
|
|
69
|
+
: {}),
|
|
70
|
+
},
|
|
71
|
+
message: 'Hvigor build',
|
|
72
|
+
operation: 'hvigor-build',
|
|
73
|
+
timeoutMs: 15 * 60_000,
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
if (!isNonEmptyRegularFile(plan.expectedHap)) {
|
|
77
|
+
throw new HarmonyCliError('ERR_HARMONY_HAP_MISSING', 'Hvigor completed without producing the expected non-empty regular HAP.', { operation: 'verify-hap' });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await timedAsync(steps, 'nativeCacheCommit', () => commitHarmonyNativeBuildCacheAsync(cache));
|
|
81
|
+
|
|
82
|
+
return { export: exported, hapPath: toPosixPath(path.relative(root, plan.expectedHap)) };
|
|
83
|
+
}
|
|
@@ -4,10 +4,10 @@ import path from 'node:path';
|
|
|
4
4
|
import {
|
|
5
5
|
HarmonyNativeInputsFingerprintVersion,
|
|
6
6
|
fingerprintHarmonyNativeInputsSync,
|
|
7
|
-
} from '@expo-harmony/config-plugins/native-inputs';
|
|
7
|
+
} from '@expo-harmony/config-plugins/internal/native-inputs';
|
|
8
8
|
|
|
9
9
|
import { HarmonyCliError } from '../errors';
|
|
10
|
-
import type
|
|
10
|
+
import { type HarmonyBuildPlan } from './types';
|
|
11
11
|
|
|
12
12
|
export interface HarmonyNativeBuildCacheState {
|
|
13
13
|
artifactCount: number;
|
|
@@ -17,13 +17,14 @@ export interface HarmonyNativeBuildCacheState {
|
|
|
17
17
|
fingerprintVersion: number;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const SchemaVersion = 1;
|
|
21
21
|
|
|
22
22
|
async function readOptionalFile(file) {
|
|
23
23
|
try {
|
|
24
24
|
return await fs.promises.readFile(file);
|
|
25
25
|
} catch (error) {
|
|
26
26
|
if (error?.code === 'ENOENT') return null;
|
|
27
|
+
|
|
27
28
|
throw new HarmonyCliError(
|
|
28
29
|
error.code || 'ERR_HARMONY_NATIVE_CACHE',
|
|
29
30
|
error.message || `Cannot read a Harmony native cache input: ${file}`,
|
|
@@ -33,14 +34,14 @@ async function readOptionalFile(file) {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
async function resolveNativeDependencyFingerprintAsync(
|
|
36
|
-
|
|
37
|
+
root: string,
|
|
37
38
|
plan: HarmonyBuildPlan
|
|
38
39
|
) {
|
|
39
40
|
try {
|
|
40
41
|
return fingerprintHarmonyNativeInputsSync({
|
|
41
42
|
lockfile: plan.nativeInputs.lockfile,
|
|
42
43
|
manifest: plan.nativeInputs.manifest,
|
|
43
|
-
projectRoot,
|
|
44
|
+
projectRoot: root,
|
|
44
45
|
});
|
|
45
46
|
} catch (cause) {
|
|
46
47
|
throw new HarmonyCliError(
|
|
@@ -52,10 +53,10 @@ async function resolveNativeDependencyFingerprintAsync(
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
async function prepareHarmonyNativeBuildCacheAsync(
|
|
55
|
-
|
|
56
|
+
root: string,
|
|
56
57
|
plan: HarmonyBuildPlan
|
|
57
58
|
): Promise<HarmonyNativeBuildCacheState> {
|
|
58
|
-
const current = await resolveNativeDependencyFingerprintAsync(
|
|
59
|
+
const current = await resolveNativeDependencyFingerprintAsync(root, plan);
|
|
59
60
|
const file = plan.nativeCache.stateFile;
|
|
60
61
|
const source = await readOptionalFile(file);
|
|
61
62
|
let saved = null;
|
|
@@ -68,7 +69,7 @@ async function prepareHarmonyNativeBuildCacheAsync(
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
const changed = saved?.schemaVersion !==
|
|
72
|
+
const changed = saved?.schemaVersion !== SchemaVersion
|
|
72
73
|
|| saved?.fingerprintVersion !== HarmonyNativeInputsFingerprintVersion
|
|
73
74
|
|| saved?.fingerprint !== current.fingerprint;
|
|
74
75
|
|
|
@@ -94,7 +95,7 @@ async function commitHarmonyNativeBuildCacheAsync(state: HarmonyNativeBuildCache
|
|
|
94
95
|
artifactCount: state.artifactCount,
|
|
95
96
|
fingerprint: state.fingerprint,
|
|
96
97
|
fingerprintVersion: state.fingerprintVersion,
|
|
97
|
-
schemaVersion:
|
|
98
|
+
schemaVersion: SchemaVersion,
|
|
98
99
|
}, null, 2)}\n`);
|
|
99
100
|
await fs.promises.rename(temp, state.cacheFile);
|
|
100
101
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { fingerprintHarmonyNativeInputsSync } from '@expo-harmony/config-plugins/internal/native-inputs';
|
|
7
|
+
|
|
8
|
+
import { HarmonyCliError } from '../errors';
|
|
9
|
+
import { createHarmonyToolchainEnv, resolveHarmonyToolchain } from './toolchain';
|
|
10
|
+
|
|
11
|
+
function nodeExecutable(): string {
|
|
12
|
+
const candidates = process.env.EXPO_HARMONY_NODE
|
|
13
|
+
? [process.env.EXPO_HARMONY_NODE]
|
|
14
|
+
: [
|
|
15
|
+
process.execPath,
|
|
16
|
+
...(process.env.PATH || '').split(path.delimiter).filter(Boolean)
|
|
17
|
+
.map(directory => path.join(directory, process.platform === 'win32' ? 'node.exe' : 'node')),
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
for (const candidate of new Set(candidates)) {
|
|
21
|
+
const result = spawnSync(candidate, ['-p', 'process.versions.node'], { encoding: 'utf8', timeout: 5_000 });
|
|
22
|
+
if (result.status === 0 && Number.parseInt(result.stdout, 10) >= 20) return candidate;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw new HarmonyCliError('ERR_HARMONY_NODE', 'Harmony native builds require Node.js 20 or newer. Set EXPO_HARMONY_NODE to its executable.', { operation: 'native-build' });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function run(command: string, args: string[], cwd: string): void {
|
|
29
|
+
const result = spawnSync(command, args, {
|
|
30
|
+
cwd,
|
|
31
|
+
env: { ...createHarmonyToolchainEnv(), EXPO_HARMONY: '1', EXPO_METRO_TARGET: 'harmony', HERMES_V1_ENABLED: 'true' },
|
|
32
|
+
stdio: 'inherit',
|
|
33
|
+
timeout: 10 * 60_000,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (result.error || result.status !== 0) {
|
|
37
|
+
throw new HarmonyCliError('ERR_HARMONY_NATIVE_BUILD', `Harmony build command failed: ${command} (${result.error?.message || result.status}).`, { cause: result.error, exitCode: result.status || 1, operation: 'native-build' });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Hvigor configuration is synchronous; linking must finish before task execution.
|
|
42
|
+
export function prepareHarmonyNativeBuild(root: string, mode: 'debug' | 'release'): void {
|
|
43
|
+
const harmony = path.join(root, 'harmony');
|
|
44
|
+
|
|
45
|
+
if (process.env.EXPO_HARMONY_NATIVE_PREPARED !== '1') {
|
|
46
|
+
const require = createRequire(path.join(root, 'package.json'));
|
|
47
|
+
const manifest = path.resolve(path.dirname(require.resolve('@expo-harmony/expo-modules-autolinking')), '../package.json');
|
|
48
|
+
const bin = path.resolve(path.dirname(manifest), require(manifest).bin['expo-harmony-autolinking']);
|
|
49
|
+
|
|
50
|
+
run(nodeExecutable(), [bin, 'link', '--project-root', root, '--harmony-project-path', harmony, '--build-type', mode], root);
|
|
51
|
+
|
|
52
|
+
const { ohpm } = resolveHarmonyToolchain();
|
|
53
|
+
run(ohpm.command, [...ohpm.args, 'install', '--all'], harmony);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
publishHarmonyNativeRuntime(root, mode);
|
|
57
|
+
|
|
58
|
+
const { fingerprint } = fingerprintHarmonyNativeInputsSync({
|
|
59
|
+
projectRoot: root,
|
|
60
|
+
manifest: path.join(harmony, 'oh-package.json5'),
|
|
61
|
+
lockfile: path.join(harmony, 'oh-package-lock.json5'),
|
|
62
|
+
});
|
|
63
|
+
const file = path.join(harmony, '.hvigor/expo-harmony/native-inputs.cmake');
|
|
64
|
+
const content = `# Generated native dependency fingerprint.\nadd_compile_definitions(EXPO_HARMONY_NATIVE_INPUTS_SHA256_${fingerprint}=1)\n`;
|
|
65
|
+
if (fs.existsSync(file) && fs.readFileSync(file, 'utf8') === content) return;
|
|
66
|
+
|
|
67
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
68
|
+
const temp = `${file}.${crypto.randomUUID()}.tmp`;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
fs.writeFileSync(temp, content);
|
|
72
|
+
fs.renameSync(temp, file);
|
|
73
|
+
} finally {
|
|
74
|
+
fs.rmSync(temp, { force: true });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function bundleHarmonyNativeBuild(root: string): void {
|
|
79
|
+
if (process.env.EXPO_HARMONY_BUNDLE_PREBUILT === '1') return;
|
|
80
|
+
|
|
81
|
+
const require = createRequire(path.join(root, 'package.json'));
|
|
82
|
+
run(nodeExecutable(), [require.resolve('@expo-harmony/cli/bin/expo-harmony'), 'export:embed', root], root);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function publishHarmonyNativeRuntime(root: string, mode: 'debug' | 'release'): void {
|
|
86
|
+
const require = createRequire(path.join(root, 'package.json'));
|
|
87
|
+
run(nodeExecutable(), [require.resolve('@expo-harmony/cli/bin/expo-harmony'), 'runtime', root, '--publish', '--variant', mode], root);
|
|
88
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HarmonyBuildPlan } from './types';
|
|
2
|
+
import type { HarmonyToolchain } from './toolchain';
|
|
3
|
+
import { runCheckedAsync } from '../process';
|
|
4
|
+
|
|
5
|
+
export async function installHarmonyDependenciesAsync(
|
|
6
|
+
plan: Pick<HarmonyBuildPlan, 'harmonyRoot'>,
|
|
7
|
+
tools: HarmonyToolchain,
|
|
8
|
+
options: { timeoutMs?: number } = {}
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
await runCheckedAsync(tools.ohpm.command, [...tools.ohpm.args, 'install', '--all'], {
|
|
11
|
+
code: 'ERR_HARMONY_OHPM_FAILED',
|
|
12
|
+
cwd: plan.harmonyRoot,
|
|
13
|
+
message: 'OHPM install',
|
|
14
|
+
operation: 'ohpm-install',
|
|
15
|
+
timeoutMs: options.timeoutMs || 5 * 60_000,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { linkModulesAsync } from '@expo-harmony/expo-modules-autolinking';
|
|
3
|
+
|
|
4
|
+
import { doctorAsync } from '../doctor/doctor';
|
|
5
|
+
import { HarmonyCliError } from '../errors';
|
|
6
|
+
import { progress, type Log } from '../log';
|
|
7
|
+
import { timedAsync } from '../profile';
|
|
8
|
+
import { checkAsync } from '../prebuild/check';
|
|
9
|
+
import { prebuildParsedAsync } from '../prebuild/prebuild';
|
|
10
|
+
import { createHarmonyToolchainEnv } from './toolchain';
|
|
11
|
+
import { resolveHarmonyBuildPlanAsync, resolveHarmonyBuildPlanIfPresentAsync } from './project';
|
|
12
|
+
import type { HarmonyBuildPlan } from './types';
|
|
13
|
+
|
|
14
|
+
interface PrepareProjectOptions {
|
|
15
|
+
io?: Log;
|
|
16
|
+
requireDeviceTools?: boolean;
|
|
17
|
+
check?: boolean;
|
|
18
|
+
sync: boolean;
|
|
19
|
+
variant: 'debug' | 'release';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function prepareNativeProjectAsync(
|
|
23
|
+
root: string,
|
|
24
|
+
options: PrepareProjectOptions,
|
|
25
|
+
steps: Record<string, number>
|
|
26
|
+
): Promise<HarmonyBuildPlan> {
|
|
27
|
+
const plan = await timedAsync(steps, 'buildPlan', () => resolveHarmonyBuildPlanIfPresentAsync(root, {
|
|
28
|
+
buildMode: options.variant,
|
|
29
|
+
}));
|
|
30
|
+
const exists = Boolean(plan && fs.existsSync(plan.harmonyRoot));
|
|
31
|
+
|
|
32
|
+
if (plan?.workflow === 'bare' && options.sync) {
|
|
33
|
+
throw new HarmonyCliError('ERR_HARMONY_BARE_SYNC', '--sync regenerates CNG projects and cannot be used with a manually maintained bare project.', { operation: 'build' });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
progress(options, 'Checking the Harmony project');
|
|
37
|
+
const doctor = await timedAsync(steps, 'doctor', () => doctorAsync(root, {
|
|
38
|
+
requireBuildTools: true,
|
|
39
|
+
requireDeviceTools: options.requireDeviceTools,
|
|
40
|
+
validateGeneratedProject: exists,
|
|
41
|
+
validateModules: false,
|
|
42
|
+
}));
|
|
43
|
+
if (!doctor.ok) {
|
|
44
|
+
const checks = doctor.checks.filter(check => check.status === 'error').map(check => check.id);
|
|
45
|
+
|
|
46
|
+
throw new HarmonyCliError('ERR_HARMONY_DOCTOR_FAILED', `Harmony doctor found blocking checks: ${checks.join(', ') || 'unknown'}.`, { operation: 'doctor' });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (plan?.workflow === 'bare') {
|
|
50
|
+
progress(options, 'Autolinking the bare Harmony project');
|
|
51
|
+
await timedAsync(steps, 'autolinking', () => linkModulesAsync({
|
|
52
|
+
projectRoot: root,
|
|
53
|
+
harmonyProjectPath: plan.harmonyRoot,
|
|
54
|
+
buildType: options.variant,
|
|
55
|
+
env: createHarmonyToolchainEnv(),
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
steps.prebuild = 0;
|
|
59
|
+
steps.cngCheck = 0;
|
|
60
|
+
|
|
61
|
+
return plan;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!exists || options.sync) {
|
|
65
|
+
progress(options, exists
|
|
66
|
+
? 'Synchronizing the generated Harmony project'
|
|
67
|
+
: 'Generating the missing Harmony project');
|
|
68
|
+
await timedAsync(steps, 'prebuild', () => prebuildParsedAsync(
|
|
69
|
+
root,
|
|
70
|
+
[],
|
|
71
|
+
{ buildType: options.variant }
|
|
72
|
+
));
|
|
73
|
+
steps.cngCheck = 0;
|
|
74
|
+
|
|
75
|
+
return timedAsync(steps, 'buildPlan', () => resolveHarmonyBuildPlanAsync(root, { buildMode: options.variant }));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
steps.prebuild = 0;
|
|
79
|
+
if (options.check === false) {
|
|
80
|
+
steps.cngCheck = 0;
|
|
81
|
+
return plan;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
progress(options, 'Checking CNG ownership and drift');
|
|
85
|
+
const check = await timedAsync(steps, 'cngCheck', () => checkAsync(root, { buildType: options.variant }));
|
|
86
|
+
if (!check.clean) {
|
|
87
|
+
const summary = check.changes.slice(0, 8).map(change => `${change.type}:${change.path}`).join(', ');
|
|
88
|
+
|
|
89
|
+
throw new HarmonyCliError(
|
|
90
|
+
'ERR_HARMONY_MANIFEST_DRIFT',
|
|
91
|
+
`Generated Harmony files differ from CNG desired state${summary ? ` (${summary})` : ''}. Run expo-harmony prebuild or retry with --sync.`,
|
|
92
|
+
{ operation: 'check' }
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return plan;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { prepareNativeProjectAsync };
|