@expo-harmony/cli 55.0.26-harmony.1 → 55.0.26-harmony.10
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 +100 -0
- package/build/development/protocol.d.ts +2 -0
- package/build/development/protocol.js +26 -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 +216 -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 +35 -0
- package/build/runtime/config.js +118 -0
- package/build/runtime/contract.d.ts +5 -0
- package/build/runtime/contract.js +108 -0
- package/build/runtime/fingerprint.d.ts +1 -0
- package/build/runtime/fingerprint.js +44 -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 +106 -0
- package/src/development/protocol.ts +37 -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 +16 -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 +150 -0
- package/src/runtime/contract.ts +165 -0
- package/src/runtime/fingerprint.ts +39 -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/file.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ import { isInside } from './path';
|
|
|
6
6
|
|
|
7
7
|
async function sha256File(file: string): Promise<string> {
|
|
8
8
|
return await new Promise<string>((resolve, reject) => {
|
|
9
|
-
const hash =
|
|
9
|
+
const hash = crypto.createHash('sha256');
|
|
10
10
|
const input = fs.createReadStream(file);
|
|
11
11
|
|
|
12
12
|
input.on('error', reject);
|
|
@@ -70,26 +70,26 @@ async function ensureSafeParent(root: string, destination: string): Promise<void
|
|
|
70
70
|
async function atomicCopy(source: string, destination: string, root: string): Promise<void> {
|
|
71
71
|
await ensureSafeParent(root, destination);
|
|
72
72
|
|
|
73
|
-
const
|
|
73
|
+
const temp = path.join(path.dirname(destination), `.${path.basename(destination)}.expo-${crypto.randomUUID()}.tmp`);
|
|
74
74
|
|
|
75
75
|
try {
|
|
76
|
-
await fs.promises.copyFile(source,
|
|
77
|
-
await fs.promises.rename(
|
|
76
|
+
await fs.promises.copyFile(source, temp, fs.constants.COPYFILE_EXCL);
|
|
77
|
+
await fs.promises.rename(temp, destination);
|
|
78
78
|
} finally {
|
|
79
|
-
await fs.promises.rm(
|
|
79
|
+
await fs.promises.rm(temp, { force: true });
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
async function atomicWriteJson(value: unknown, destination: string, root: string): Promise<void> {
|
|
84
84
|
await ensureSafeParent(root, destination);
|
|
85
85
|
|
|
86
|
-
const
|
|
86
|
+
const temp = path.join(path.dirname(destination), `.${path.basename(destination)}.expo-${crypto.randomUUID()}.tmp`);
|
|
87
87
|
|
|
88
88
|
try {
|
|
89
|
-
await fs.promises.writeFile(
|
|
90
|
-
await fs.promises.rename(
|
|
89
|
+
await fs.promises.writeFile(temp, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx' });
|
|
90
|
+
await fs.promises.rename(temp, destination);
|
|
91
91
|
} finally {
|
|
92
|
-
await fs.promises.rm(
|
|
92
|
+
await fs.promises.rm(temp, { force: true });
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -107,4 +107,13 @@ async function removeEmptyParents(file: string, stop: string): Promise<void> {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
function isNonEmptyRegularFile(file: string): boolean {
|
|
111
|
+
try {
|
|
112
|
+
const stat = fs.lstatSync(file);
|
|
113
|
+
return !stat.isSymbolicLink() && stat.isFile() && stat.size > 0;
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export { atomicCopy, atomicWriteJson, describeFile, isNonEmptyRegularFile, listFiles, removeEmptyParents };
|
package/src/help.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const Help = `Usage: expo-harmony <command> [project] [options]
|
|
2
|
+
|
|
3
|
+
Commands:
|
|
4
|
+
start Start Expo Metro for Harmony development
|
|
5
|
+
runtime Describe project requirements; --client file checks compatibility
|
|
6
|
+
runtime --publish Embed capabilities from prepared native build inputs
|
|
7
|
+
prebuild Generate the Harmony native project with Expo CNG
|
|
8
|
+
prebuild --clean Safely recreate the managed Harmony directory
|
|
9
|
+
prebuild --check Compare generated desired state without project writes
|
|
10
|
+
build Build a HAP without selecting or contacting a device
|
|
11
|
+
doctor Validate config, versions, Metro, RNOH, SDK, and signing
|
|
12
|
+
modules list List native module candidates and their discovery source
|
|
13
|
+
modules inspect Resolve Harmony metadata (--package narrows the result)
|
|
14
|
+
modules verify Validate module config, registration conflicts and safe paths
|
|
15
|
+
export Export an OTA update, assets, metadata, and source map
|
|
16
|
+
export:embed Export validated Hermes bytecode, assets, and a source map
|
|
17
|
+
run Build, install, and launch the Harmony app
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--no-install Skip dependency install (prebuild) or HAP install (run)
|
|
21
|
+
--npm|--yarn|--pnpm|--bun
|
|
22
|
+
Select the package manager used by prebuild dependency install
|
|
23
|
+
--skip-dependency-update <packages>
|
|
24
|
+
Preserve comma-separated dependency versions
|
|
25
|
+
--device <id-or-name> Select an HDC target or start a local emulator by name
|
|
26
|
+
--variant <mode> Build debug or release (default: debug)
|
|
27
|
+
--no-bundler Use an already-running Expo Metro server
|
|
28
|
+
--app-id <bundleName>
|
|
29
|
+
Launch another installed app (requires --no-install when different)
|
|
30
|
+
--host <hostname> Advertised HTTP LAN hostname (start)
|
|
31
|
+
--private-key-path <path>
|
|
32
|
+
Sign development manifests using the configured Updates certificate
|
|
33
|
+
--port <number> Metro and device reverse port (default: 8081)
|
|
34
|
+
--sync Re-run prebuild before building
|
|
35
|
+
--output-dir <path> OTA export directory (default: dist)
|
|
36
|
+
--asset-url <url> HTTP(S) root hosting exported OTA assets
|
|
37
|
+
--check Validate an existing export without writing
|
|
38
|
+
--reset-cache Reset Metro while exporting or starting
|
|
39
|
+
-c, --clear Alias for --reset-cache (start)
|
|
40
|
+
-h, --help Show this help
|
|
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
|
+
}
|