@expo-harmony/cli 55.0.26-harmony.7 → 55.0.26-harmony.9
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 +74 -46
- package/build/bin/expo-harmony.js +1 -1
- package/build/buildHap/build.d.ts +3 -10
- package/build/buildHap/build.js +11 -69
- package/build/buildHap/index.d.ts +2 -0
- package/build/buildHap/index.js +19 -0
- package/build/cli.d.ts +3 -3
- package/build/cli.js +21 -187
- package/build/command.d.ts +9 -0
- package/build/command.js +20 -0
- package/build/doctor/doctor.d.ts +1 -1
- package/build/doctor/doctor.js +64 -56
- package/build/doctor/index.d.ts +2 -0
- package/build/doctor/index.js +16 -0
- package/build/exportEmbed/export.d.ts +2 -2
- package/build/exportEmbed/export.js +53 -49
- package/build/exportEmbed/index.d.ts +2 -0
- package/build/exportEmbed/index.js +19 -0
- package/build/file.d.ts +2 -1
- package/build/file.js +18 -8
- package/build/help.d.ts +1 -0
- package/build/help.js +36 -0
- package/build/index.d.ts +5 -3
- package/build/index.js +6 -5
- 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/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 +67 -0
- package/build/{run → native}/cache.d.ts +2 -2
- package/build/{run → native}/cache.js +8 -8
- package/build/native/hvigor.d.ts +2 -0
- package/build/native/hvigor.js +77 -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/{tools.js → native/toolchain.js} +1 -57
- package/build/native/types.d.ts +47 -0
- package/build/native/types.js +2 -0
- package/build/prebuild/check.d.ts +7 -3
- package/build/prebuild/check.js +26 -20
- package/build/prebuild/clean.js +5 -5
- package/build/prebuild/index.d.ts +2 -0
- package/build/prebuild/index.js +51 -0
- package/build/prebuild/prebuild.js +11 -3
- package/build/prebuild/template.js +2 -2
- package/build/process.d.ts +11 -1
- package/build/process.js +16 -0
- package/build/profile.d.ts +1 -0
- package/build/profile.js +12 -0
- package/build/projectLock.js +7 -2
- package/build/run/devices.d.ts +1 -1
- package/build/run/devices.js +15 -10
- package/build/run/emulators.d.ts +1 -1
- package/build/run/emulators.js +2 -5
- package/build/run/index.d.ts +2 -0
- package/build/run/index.js +25 -0
- package/build/run/metro.js +1 -1
- package/build/run/run.d.ts +4 -10
- package/build/run/run.js +45 -90
- package/build/start/index.d.ts +2 -0
- package/build/start/index.js +33 -0
- package/package.json +14 -4
- 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 +24 -227
- package/src/command.ts +24 -0
- package/src/doctor/doctor.ts +63 -61
- package/src/doctor/index.ts +16 -0
- package/src/exportEmbed/export.ts +64 -55
- package/src/exportEmbed/index.ts +17 -0
- package/src/file.ts +20 -11
- package/src/help.ts +33 -0
- package/src/index.ts +5 -3
- 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 +81 -0
- package/src/{run → native}/cache.ts +10 -9
- package/src/native/hvigor.ts +81 -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} +11 -118
- package/src/native/types.ts +44 -0
- package/src/prebuild/check.ts +30 -19
- 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 +38 -1
- 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 +1 -1
- package/src/run/run.ts +56 -129
- package/src/start/index.ts +31 -0
- package/build/buildHap/common.d.ts +0 -24
- package/build/buildHap/common.js +0 -92
- package/build/run/install.d.ts +0 -7
- package/build/run/install.js +0 -26
- package/build/tools.d.ts +0 -39
- package/src/buildHap/common.ts +0 -145
- package/src/run/install.ts +0 -41
|
@@ -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,81 @@
|
|
|
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
|
+
const { fingerprint } = fingerprintHarmonyNativeInputsSync({
|
|
57
|
+
projectRoot: root,
|
|
58
|
+
manifest: path.join(harmony, 'oh-package.json5'),
|
|
59
|
+
lockfile: path.join(harmony, 'oh-package-lock.json5'),
|
|
60
|
+
});
|
|
61
|
+
const file = path.join(harmony, '.hvigor/expo-harmony/native-inputs.cmake');
|
|
62
|
+
const content = `# Generated native dependency fingerprint.\nadd_compile_definitions(EXPO_HARMONY_NATIVE_INPUTS_SHA256_${fingerprint}=1)\n`;
|
|
63
|
+
if (fs.existsSync(file) && fs.readFileSync(file, 'utf8') === content) return;
|
|
64
|
+
|
|
65
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
66
|
+
const temp = `${file}.${crypto.randomUUID()}.tmp`;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
fs.writeFileSync(temp, content);
|
|
70
|
+
fs.renameSync(temp, file);
|
|
71
|
+
} finally {
|
|
72
|
+
fs.rmSync(temp, { force: true });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function bundleHarmonyNativeBuild(root: string): void {
|
|
77
|
+
if (process.env.EXPO_HARMONY_BUNDLE_PREBUILT === '1') return;
|
|
78
|
+
|
|
79
|
+
const require = createRequire(path.join(root, 'package.json'));
|
|
80
|
+
run(nodeExecutable(), [require.resolve('@expo-harmony/cli/bin/expo-harmony'), 'export:embed', root], root);
|
|
81
|
+
}
|
|
@@ -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 };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readManifestIfPresentAsync, resolveHarmonyBuildPath, type HarmonyBuildDescriptor } from '@expo-harmony/prebuild-config/internal';
|
|
2
|
+
|
|
3
|
+
import { HarmonyCliError } from '../errors';
|
|
4
|
+
import { isBareHarmonyProject, resolveBareHarmonyBuildPlan } from './bare';
|
|
5
|
+
import type { CngHarmonyBuildPlan, HarmonyBuildPlan } from './types';
|
|
6
|
+
|
|
7
|
+
function createHarmonyBuildPlan(
|
|
8
|
+
root: string,
|
|
9
|
+
build: HarmonyBuildDescriptor,
|
|
10
|
+
mode: 'debug' | 'release'
|
|
11
|
+
): CngHarmonyBuildPlan {
|
|
12
|
+
const resolve = relative => resolveHarmonyBuildPath(root, relative);
|
|
13
|
+
const variant = build.variants[mode];
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
workflow: 'cng',
|
|
17
|
+
abilityName: build.identity.abilityName,
|
|
18
|
+
buildMode: mode,
|
|
19
|
+
bundleName: build.identity.bundleName,
|
|
20
|
+
expectedHap: resolve(variant.expectedHap),
|
|
21
|
+
exportPaths: Object.fromEntries(
|
|
22
|
+
Object.entries(build.export).map(([name, relative]) => [name, resolve(relative)])
|
|
23
|
+
) as HarmonyBuildPlan['exportPaths'],
|
|
24
|
+
harmonyRoot: resolve(build.harmonyRoot),
|
|
25
|
+
hvigorArgs: [...variant.hvigorArgs],
|
|
26
|
+
moduleName: build.identity.moduleName,
|
|
27
|
+
moduleRoot: resolve(build.moduleRoot),
|
|
28
|
+
nativeCache: {
|
|
29
|
+
invalidationRoots: build.nativeCache.invalidationRoots.map(resolve),
|
|
30
|
+
stateFile: resolve(build.nativeCache.stateFile),
|
|
31
|
+
},
|
|
32
|
+
nativeInputs: {
|
|
33
|
+
lockfile: resolve(build.nativeInputs.lockfile),
|
|
34
|
+
manifest: resolve(build.nativeInputs.manifest),
|
|
35
|
+
},
|
|
36
|
+
productName: build.identity.productName,
|
|
37
|
+
projectFiles: Object.fromEntries(
|
|
38
|
+
Object.entries(build.projectFiles).map(([name, relative]) => [name, resolve(relative)])
|
|
39
|
+
) as CngHarmonyBuildPlan['projectFiles'],
|
|
40
|
+
targetName: build.identity.targetName,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function resolveHarmonyBuildPlanIfPresentAsync(
|
|
45
|
+
root: string,
|
|
46
|
+
options: { buildMode?: 'debug' | 'release' } = {}
|
|
47
|
+
): Promise<HarmonyBuildPlan | null> {
|
|
48
|
+
const mode = options.buildMode || 'debug';
|
|
49
|
+
if (!['debug', 'release'].includes(mode)) {
|
|
50
|
+
throw new HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', `Harmony buildMode must be debug or release, received: ${mode}`, { operation: 'resolve-build' });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let manifest;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
manifest = await readManifestIfPresentAsync(root);
|
|
57
|
+
} catch (cause) {
|
|
58
|
+
throw new HarmonyCliError(
|
|
59
|
+
cause.code || 'ERR_HARMONY_TEMPLATE_INVALID',
|
|
60
|
+
`Cannot read the generated Harmony build descriptor: ${cause.message}`,
|
|
61
|
+
{ cause, operation: 'resolve-build' }
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!manifest) {
|
|
66
|
+
return isBareHarmonyProject(root) ? resolveBareHarmonyBuildPlan(root, mode) : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return createHarmonyBuildPlan(root, manifest.build, mode);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function resolveHarmonyBuildPlanAsync(
|
|
73
|
+
root: string,
|
|
74
|
+
options: { buildMode?: 'debug' | 'release' } = {}
|
|
75
|
+
): Promise<HarmonyBuildPlan> {
|
|
76
|
+
const plan = await resolveHarmonyBuildPlanIfPresentAsync(root, options);
|
|
77
|
+
if (!plan) {
|
|
78
|
+
throw new HarmonyCliError(
|
|
79
|
+
'ERR_HARMONY_MANIFEST_DRIFT',
|
|
80
|
+
'Cannot read the generated Harmony build descriptor because the CNG manifest is missing.',
|
|
81
|
+
{ operation: 'resolve-build' }
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return plan;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { resolveHarmonyBuildPlanAsync, resolveHarmonyBuildPlanIfPresentAsync };
|
|
@@ -2,14 +2,6 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { resolveHarmonyCommand } from '@expo-harmony/expo-modules-autolinking/tool-command';
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
resolveHarmonyBuildPath,
|
|
7
|
-
type HarmonyBuildDescriptor,
|
|
8
|
-
} from '@expo-harmony/prebuild-config/build-descriptor';
|
|
9
|
-
import { readManifestIfPresentAsync } from '@expo-harmony/prebuild-config/check';
|
|
10
|
-
|
|
11
|
-
import { HarmonyCliError } from './errors';
|
|
12
|
-
|
|
13
5
|
export interface HarmonyTool {
|
|
14
6
|
args: string[];
|
|
15
7
|
command: string;
|
|
@@ -24,23 +16,6 @@ export interface HarmonyToolchain {
|
|
|
24
16
|
toolsRoot: string | null;
|
|
25
17
|
}
|
|
26
18
|
|
|
27
|
-
export interface HarmonyBuildPlan {
|
|
28
|
-
abilityName: string;
|
|
29
|
-
buildMode: 'debug' | 'release';
|
|
30
|
-
bundleName: string;
|
|
31
|
-
expectedHap: string;
|
|
32
|
-
exportPaths: HarmonyBuildDescriptor['export'];
|
|
33
|
-
harmonyRoot: string;
|
|
34
|
-
hvigorArgs: string[];
|
|
35
|
-
moduleName: string;
|
|
36
|
-
moduleRoot: string;
|
|
37
|
-
nativeCache: HarmonyBuildDescriptor['nativeCache'];
|
|
38
|
-
nativeInputs: HarmonyBuildDescriptor['nativeInputs'];
|
|
39
|
-
projectFiles: HarmonyBuildDescriptor['projectFiles'];
|
|
40
|
-
productName: string;
|
|
41
|
-
targetName: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
19
|
function existingFile(candidates: string[]): string | null {
|
|
45
20
|
return candidates.find(candidate => fs.existsSync(candidate)) || null;
|
|
46
21
|
}
|
|
@@ -55,7 +30,7 @@ const RequiredSdkComponents = Object.freeze([
|
|
|
55
30
|
'default/openharmony/toolchains/oh-uni-package.json',
|
|
56
31
|
]);
|
|
57
32
|
|
|
58
|
-
function sdkRootsNear(seed) {
|
|
33
|
+
function sdkRootsNear(seed: string): string[] {
|
|
59
34
|
const roots = [];
|
|
60
35
|
let cursor = path.resolve(seed);
|
|
61
36
|
|
|
@@ -75,7 +50,7 @@ function sdkRootsNear(seed) {
|
|
|
75
50
|
return roots;
|
|
76
51
|
}
|
|
77
52
|
|
|
78
|
-
function resolveHarmonySdkRoot(env, platform) {
|
|
53
|
+
function resolveHarmonySdkRoot(env: NodeJS.ProcessEnv, platform: NodeJS.Platform): string | null {
|
|
79
54
|
const seeds = [
|
|
80
55
|
env.DEVECO_SDK_HOME,
|
|
81
56
|
env.HARMONY_HOME,
|
|
@@ -93,7 +68,7 @@ function resolveHarmonySdkRoot(env, platform) {
|
|
|
93
68
|
)) || null;
|
|
94
69
|
}
|
|
95
70
|
|
|
96
|
-
function devEcoInstallRoots(sdkHome) {
|
|
71
|
+
function devEcoInstallRoots(sdkHome: string): string[] {
|
|
97
72
|
const ancestors = [sdkHome, path.dirname(sdkHome), path.dirname(path.dirname(sdkHome))];
|
|
98
73
|
const sdkDirectory = ancestors.find(candidate => path.basename(candidate).toLowerCase() === 'sdk');
|
|
99
74
|
const roots = [
|
|
@@ -105,7 +80,7 @@ function devEcoInstallRoots(sdkHome) {
|
|
|
105
80
|
return [...new Set(roots)];
|
|
106
81
|
}
|
|
107
82
|
|
|
108
|
-
function devEcoLayouts(sdkHome) {
|
|
83
|
+
function devEcoLayouts(sdkHome: string) {
|
|
109
84
|
return devEcoInstallRoots(sdkHome).flatMap(root => [
|
|
110
85
|
{
|
|
111
86
|
nodeRoot: path.join(root, 'tools', 'node'),
|
|
@@ -118,7 +93,11 @@ function devEcoLayouts(sdkHome) {
|
|
|
118
93
|
]);
|
|
119
94
|
}
|
|
120
95
|
|
|
121
|
-
function devEcoNode(
|
|
96
|
+
function devEcoNode(
|
|
97
|
+
layout: { nodeRoot: string },
|
|
98
|
+
platform: NodeJS.Platform,
|
|
99
|
+
env: NodeJS.ProcessEnv
|
|
100
|
+
): Pick<HarmonyTool, 'command' | 'source'> | null {
|
|
122
101
|
if (env.HARMONY_NODE) return { command: env.HARMONY_NODE, source: 'override' };
|
|
123
102
|
|
|
124
103
|
const executable = platform === 'win32' ? 'node.exe' : 'node';
|
|
@@ -134,9 +113,7 @@ function resolveHarmonyToolchain(): HarmonyToolchain {
|
|
|
134
113
|
const env = process.env;
|
|
135
114
|
const platform = process.platform;
|
|
136
115
|
|
|
137
|
-
// Hvigor
|
|
138
|
-
// Reject metadata-only candidates so doctor cannot claim an SDK is
|
|
139
|
-
// buildable without its HMS, OpenHarmony, native, ETS, and toolchain parts.
|
|
116
|
+
// Hvigor needs the complete SDK root containing default/, not default/ itself.
|
|
140
117
|
const sdkHome = resolveHarmonySdkRoot(env, platform);
|
|
141
118
|
const layouts = sdkHome ? devEcoLayouts(sdkHome) : [];
|
|
142
119
|
|
|
@@ -234,88 +211,4 @@ function resolveHarmonyEmulator(toolchain: HarmonyToolchain): HarmonyTool {
|
|
|
234
211
|
: { args: [], command: executable, source: 'path' };
|
|
235
212
|
}
|
|
236
213
|
|
|
237
|
-
|
|
238
|
-
projectRoot: string,
|
|
239
|
-
build: HarmonyBuildDescriptor,
|
|
240
|
-
mode: 'debug' | 'release'
|
|
241
|
-
): HarmonyBuildPlan {
|
|
242
|
-
const resolve = relative => resolveHarmonyBuildPath(projectRoot, relative);
|
|
243
|
-
const variant = build.variants[mode];
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
abilityName: build.identity.abilityName,
|
|
247
|
-
buildMode: mode,
|
|
248
|
-
bundleName: build.identity.bundleName,
|
|
249
|
-
expectedHap: resolve(variant.expectedHap),
|
|
250
|
-
exportPaths: Object.fromEntries(
|
|
251
|
-
Object.entries(build.export).map(([name, relative]) => [name, resolve(relative)])
|
|
252
|
-
) as HarmonyBuildPlan['exportPaths'],
|
|
253
|
-
harmonyRoot: resolve(build.harmonyRoot),
|
|
254
|
-
hvigorArgs: [...variant.hvigorArgs],
|
|
255
|
-
moduleName: build.identity.moduleName,
|
|
256
|
-
moduleRoot: resolve(build.moduleRoot),
|
|
257
|
-
nativeCache: {
|
|
258
|
-
invalidationRoots: build.nativeCache.invalidationRoots.map(resolve),
|
|
259
|
-
stateFile: resolve(build.nativeCache.stateFile),
|
|
260
|
-
},
|
|
261
|
-
nativeInputs: {
|
|
262
|
-
lockfile: resolve(build.nativeInputs.lockfile),
|
|
263
|
-
manifest: resolve(build.nativeInputs.manifest),
|
|
264
|
-
},
|
|
265
|
-
productName: build.identity.productName,
|
|
266
|
-
projectFiles: Object.fromEntries(
|
|
267
|
-
Object.entries(build.projectFiles).map(([name, relative]) => [name, resolve(relative)])
|
|
268
|
-
) as HarmonyBuildPlan['projectFiles'],
|
|
269
|
-
targetName: build.identity.targetName,
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
async function resolveHarmonyBuildPlanIfPresentAsync(
|
|
274
|
-
projectRoot: string,
|
|
275
|
-
options: { buildMode?: 'debug' | 'release' } = {}
|
|
276
|
-
): Promise<HarmonyBuildPlan | null> {
|
|
277
|
-
const mode = options.buildMode || 'debug';
|
|
278
|
-
if (!['debug', 'release'].includes(mode)) {
|
|
279
|
-
throw new HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', `Harmony buildMode must be debug or release, received: ${mode}`, { operation: 'resolve-build' });
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
let manifest;
|
|
283
|
-
|
|
284
|
-
try {
|
|
285
|
-
manifest = await readManifestIfPresentAsync(projectRoot);
|
|
286
|
-
} catch (cause) {
|
|
287
|
-
throw new HarmonyCliError(
|
|
288
|
-
cause.code || 'ERR_HARMONY_TEMPLATE_INVALID',
|
|
289
|
-
`Cannot read the generated Harmony build descriptor: ${cause.message}`,
|
|
290
|
-
{ cause, operation: 'resolve-build' }
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (!manifest) return null;
|
|
295
|
-
|
|
296
|
-
return createHarmonyBuildPlan(projectRoot, manifest.build, mode);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async function resolveHarmonyBuildPlanAsync(
|
|
300
|
-
projectRoot: string,
|
|
301
|
-
options: { buildMode?: 'debug' | 'release' } = {}
|
|
302
|
-
): Promise<HarmonyBuildPlan> {
|
|
303
|
-
const plan = await resolveHarmonyBuildPlanIfPresentAsync(projectRoot, options);
|
|
304
|
-
if (!plan) {
|
|
305
|
-
throw new HarmonyCliError(
|
|
306
|
-
'ERR_HARMONY_MANIFEST_DRIFT',
|
|
307
|
-
'Cannot read the generated Harmony build descriptor because the CNG manifest is missing.',
|
|
308
|
-
{ operation: 'resolve-build' }
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return plan;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
export {
|
|
316
|
-
createHarmonyToolchainEnv,
|
|
317
|
-
resolveHarmonyBuildPlanAsync,
|
|
318
|
-
resolveHarmonyBuildPlanIfPresentAsync,
|
|
319
|
-
resolveHarmonyEmulator,
|
|
320
|
-
resolveHarmonyToolchain,
|
|
321
|
-
};
|
|
214
|
+
export { createHarmonyToolchainEnv, resolveHarmonyEmulator, resolveHarmonyToolchain };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type HarmonyBuildMode = 'debug' | 'release';
|
|
2
|
+
|
|
3
|
+
interface NativeBuildPlan {
|
|
4
|
+
abilityName: string;
|
|
5
|
+
buildMode: HarmonyBuildMode;
|
|
6
|
+
bundleName: string;
|
|
7
|
+
expectedHap: string;
|
|
8
|
+
exportPaths: {
|
|
9
|
+
bundle: string;
|
|
10
|
+
manifest: string;
|
|
11
|
+
metadataRoot: string;
|
|
12
|
+
rawfileRoot: string;
|
|
13
|
+
sourceMap: string;
|
|
14
|
+
};
|
|
15
|
+
harmonyRoot: string;
|
|
16
|
+
hvigorArgs: string[];
|
|
17
|
+
moduleName: string;
|
|
18
|
+
moduleRoot: string;
|
|
19
|
+
nativeCache: { invalidationRoots: string[]; stateFile: string };
|
|
20
|
+
nativeInputs: { lockfile: string; manifest: string };
|
|
21
|
+
projectFiles: {
|
|
22
|
+
hvigorConfig: string;
|
|
23
|
+
moduleHvigor: string;
|
|
24
|
+
moduleJson: string;
|
|
25
|
+
projectBuildProfile: string;
|
|
26
|
+
rootHvigor: string;
|
|
27
|
+
};
|
|
28
|
+
productName: string;
|
|
29
|
+
targetName: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface BareHarmonyBuildPlan extends NativeBuildPlan {
|
|
33
|
+
workflow: 'bare';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CngHarmonyBuildPlan extends NativeBuildPlan {
|
|
37
|
+
workflow: 'cng';
|
|
38
|
+
projectFiles: NativeBuildPlan['projectFiles'] & {
|
|
39
|
+
nativeInputsStamp: string;
|
|
40
|
+
templateMarker: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type HarmonyBuildPlan = BareHarmonyBuildPlan | CngHarmonyBuildPlan;
|