@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
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { createNativeModuleContracts, ohpmDependenciesFromManifest, verifyModulesAsync, type Manifest, type ModuleDescriptor } from '@expo-harmony/expo-modules-autolinking';
|
|
6
|
+
import { assertHarmonyCompatibility, validateHarmonyRuntime, type HarmonyRuntimeContract } from '@expo-harmony/expo-modules-autolinking/runtime';
|
|
7
|
+
import JSON5 from 'json5';
|
|
8
|
+
|
|
9
|
+
import { publishUpdatesConfigurationAsync } from '../updates/export';
|
|
10
|
+
import { HarmonyCliError } from '../errors';
|
|
11
|
+
import { atomicCopy, atomicWriteJson } from '../file';
|
|
12
|
+
import type { HarmonyBuildPlan } from '../native/types';
|
|
13
|
+
import { readMetroRuntimeAsync, readNativeRuntime, readProjectRuntimeAsync, resolveRuntimeVersionAsync, type HarmonyMetroRuntime } from './config';
|
|
14
|
+
|
|
15
|
+
async function createRuntimeContractAsync(
|
|
16
|
+
root: string,
|
|
17
|
+
project: Awaited<ReturnType<typeof readProjectRuntimeAsync>>,
|
|
18
|
+
modules: readonly ModuleDescriptor[],
|
|
19
|
+
metro?: HarmonyMetroRuntime
|
|
20
|
+
): Promise<HarmonyRuntimeContract> {
|
|
21
|
+
const resolution = metro ?? await readMetroRuntimeAsync(root);
|
|
22
|
+
if (!resolution?.fixedRuntime || resolution.harmonyPackage !== '@react-native-oh/react-native-harmony'
|
|
23
|
+
|| typeof resolution.reactPackage !== 'string' || !resolution.reactPackage) {
|
|
24
|
+
throw new HarmonyCliError(
|
|
25
|
+
'ERR_HARMONY_RUNTIME_CONFIG',
|
|
26
|
+
'Runtime contracts require withHarmonyConfig with the standard RNOH package and supported React resolution.',
|
|
27
|
+
{ operation: 'runtime-contract' }
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const require = createRequire(path.join(root, 'package.json'));
|
|
32
|
+
const version = (name: string) => require(`${name}/package.json`).version as string;
|
|
33
|
+
const native = createRequire(require.resolve(resolution.harmonyPackage + '/package.json'));
|
|
34
|
+
|
|
35
|
+
const contract: HarmonyRuntimeContract = {
|
|
36
|
+
schemaVersion: 1,
|
|
37
|
+
platform: 'harmony',
|
|
38
|
+
runtimeVersion: project.runtimeVersion,
|
|
39
|
+
development: true,
|
|
40
|
+
expo: version('expo'),
|
|
41
|
+
expoModulesCore: version('expo-modules-core'),
|
|
42
|
+
rnoh: native('./package.json').version,
|
|
43
|
+
react: version(resolution.reactPackage),
|
|
44
|
+
hermes: native('hermes-compiler/package.json').version,
|
|
45
|
+
engine: 'hermes-v1',
|
|
46
|
+
bundleFormat: 'js-source',
|
|
47
|
+
renderer: 'rnoh',
|
|
48
|
+
modules: createNativeModuleContracts(modules),
|
|
49
|
+
config: project.native,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (contract.rnoh !== '0.84.1' || contract.react !== '19.2.3' || contract.hermes !== '250829098.0.9'
|
|
53
|
+
|| !contract.expo.startsWith('55.') || !modules.some(module => module.packageName === '@expo-harmony/expo-modules-core')) {
|
|
54
|
+
throw new HarmonyCliError(
|
|
55
|
+
'ERR_HARMONY_RUNTIME_UNSUPPORTED',
|
|
56
|
+
'The fixed development runtime requires Expo SDK 55, Harmony Expo Modules Core, RNOH 0.84.1, React 19.2.3, and Hermes 250829098.0.9. Check the Metro React alias.',
|
|
57
|
+
{ operation: 'runtime-contract' }
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const issues = validateHarmonyRuntime(contract);
|
|
62
|
+
if (issues.length > 0) {
|
|
63
|
+
throw new HarmonyCliError(
|
|
64
|
+
'ERR_HARMONY_RUNTIME_CONFIG',
|
|
65
|
+
issues.map(issue => issue.message).join('\n'),
|
|
66
|
+
{ operation: 'runtime-contract' }
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return contract;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function resolveRuntimeRequirementsAsync(
|
|
74
|
+
root: string,
|
|
75
|
+
metro?: HarmonyMetroRuntime
|
|
76
|
+
): Promise<HarmonyRuntimeContract> {
|
|
77
|
+
const result = await verifyModulesAsync({ projectRoot: root });
|
|
78
|
+
if (!result.valid) {
|
|
79
|
+
throw new HarmonyCliError(
|
|
80
|
+
'ERR_HARMONY_RUNTIME_MODULES',
|
|
81
|
+
result.diagnostics.filter(item => item.severity === 'error').map(item => item.message).join('\n'),
|
|
82
|
+
{ operation: 'runtime-contract' }
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const project = await readProjectRuntimeAsync(root);
|
|
87
|
+
|
|
88
|
+
return createRuntimeContractAsync(root, project, result.modules, metro);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function publishRuntimeContractAsync(
|
|
92
|
+
root: string,
|
|
93
|
+
plan: HarmonyBuildPlan
|
|
94
|
+
): Promise<HarmonyRuntimeContract> {
|
|
95
|
+
const manifest = JSON.parse(await fs.promises.readFile(path.join(root, '.expo/harmony/autolinking.json'), 'utf8')) as Manifest;
|
|
96
|
+
if (manifest.schemaVersion !== 4 || manifest.platform !== 'harmony' || manifest.buildType !== plan.buildMode) {
|
|
97
|
+
throw new HarmonyCliError(
|
|
98
|
+
'ERR_HARMONY_RUNTIME_BUILD',
|
|
99
|
+
'Runtime publication requires autolinking from this native build.',
|
|
100
|
+
{ operation: 'runtime-contract' }
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const project = await readProjectRuntimeAsync(root);
|
|
105
|
+
const requirements = await createRuntimeContractAsync(root, project, manifest.modules);
|
|
106
|
+
if (!manifest.runtimeModules || JSON.stringify(requirements.modules) !== JSON.stringify(manifest.runtimeModules)) {
|
|
107
|
+
throw new HarmonyCliError(
|
|
108
|
+
'ERR_HARMONY_RUNTIME_BUILD',
|
|
109
|
+
'Native inputs changed or lack a runtime snapshot. Run autolinking/prebuild again before publishing capabilities.',
|
|
110
|
+
{ operation: 'runtime-contract' }
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const read = (file: string) => JSON5.parse(fs.readFileSync(file, 'utf8'));
|
|
115
|
+
const dependencies = read(plan.nativeInputs.manifest);
|
|
116
|
+
const declared = { ...dependencies.dependencies, ...dependencies.overrides };
|
|
117
|
+
const linked = ohpmDependenciesFromManifest(manifest, { harmonyProjectPath: plan.harmonyRoot });
|
|
118
|
+
for (const [name, expected] of Object.entries(linked)) {
|
|
119
|
+
const actual = declared[name];
|
|
120
|
+
const target = fs.realpathSync(path.resolve(plan.harmonyRoot, expected));
|
|
121
|
+
if (typeof actual !== 'string' || !actual.startsWith('.')
|
|
122
|
+
|| fs.realpathSync(path.resolve(plan.harmonyRoot, actual)) !== target) {
|
|
123
|
+
throw new HarmonyCliError(
|
|
124
|
+
'ERR_HARMONY_RUNTIME_BUILD',
|
|
125
|
+
`Native dependency ${name} does not use the HAR recorded by autolinking.`,
|
|
126
|
+
{ operation: 'runtime-contract' }
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const directory = path.join(plan.harmonyRoot, 'oh_modules/@rnoh/react-native-openharmony');
|
|
132
|
+
const rnoh = read(path.join(directory, 'oh-package.json5'));
|
|
133
|
+
const hermes = read(path.join(directory, 'src/main/cpp/third-party/hermes/npm/hermes-compiler/package.json'));
|
|
134
|
+
if (rnoh.version !== requirements.rnoh || hermes.version !== requirements.hermes
|
|
135
|
+
|| declared['@rnoh/react-native-openharmony'] !== requirements.rnoh
|
|
136
|
+
|| process.env.HERMES_V1_ENABLED === 'false') {
|
|
137
|
+
throw new HarmonyCliError(
|
|
138
|
+
'ERR_HARMONY_RUNTIME_BUILD',
|
|
139
|
+
'Runtime publication requires matching installed RNOH and Hermes v1.',
|
|
140
|
+
{ operation: 'runtime-contract' }
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const native = readNativeRuntime(plan);
|
|
145
|
+
requirements.development = plan.buildMode === 'debug';
|
|
146
|
+
const contract: HarmonyRuntimeContract = {
|
|
147
|
+
...requirements,
|
|
148
|
+
runtimeVersion: await resolveRuntimeVersionAsync(root, project.config, native.app),
|
|
149
|
+
config: native.config,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
assertHarmonyCompatibility(contract, requirements);
|
|
153
|
+
|
|
154
|
+
const require = createRequire(path.join(root, 'package.json'));
|
|
155
|
+
const file = path.join(plan.exportPaths.rawfileRoot, 'expo-harmony-runtime.json');
|
|
156
|
+
const source = require.resolve('@expo-harmony/expo-modules-autolinking/runtime-source');
|
|
157
|
+
const target = path.join(plan.moduleRoot, 'src/main/ets/generated/HarmonyRuntime.ts');
|
|
158
|
+
|
|
159
|
+
await atomicCopy(source, target, root);
|
|
160
|
+
await atomicWriteJson(contract, file, root);
|
|
161
|
+
await atomicWriteJson(contract, path.join(root, '.expo/harmony/runtime.json'), root);
|
|
162
|
+
await publishUpdatesConfigurationAsync(root, plan.exportPaths.rawfileRoot, project.config, contract.runtimeVersion);
|
|
163
|
+
|
|
164
|
+
return contract;
|
|
165
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { createFingerprintAsync, type HashSource } from '@expo/fingerprint';
|
|
5
|
+
import { createNativeModuleContracts, verifyModulesAsync } from '@expo-harmony/expo-modules-autolinking';
|
|
6
|
+
import { HarmonyCliError } from '../errors';
|
|
7
|
+
|
|
8
|
+
export async function fingerprintHarmonyAsync(root: string) {
|
|
9
|
+
const result = await verifyModulesAsync({ projectRoot: root });
|
|
10
|
+
if (!result.valid) throw new HarmonyCliError('ERR_HARMONY_FINGERPRINT', 'Build and verify Harmony native modules before calculating the runtime fingerprint.', { operation: 'fingerprint' });
|
|
11
|
+
|
|
12
|
+
const require = createRequire(path.join(root, 'package.json'));
|
|
13
|
+
const dependencies = createNativeModuleContracts(result.modules);
|
|
14
|
+
const generated: string[] = [];
|
|
15
|
+
const previous = path.join(root, '.expo/harmony/export-manifest.json');
|
|
16
|
+
if (fs.existsSync(previous)) {
|
|
17
|
+
const manifest = JSON.parse(fs.readFileSync(previous, 'utf8'));
|
|
18
|
+
generated.push(...manifest.assets.map(asset => `**/harmony/**/src/main/resources/rawfile/${asset.path}`));
|
|
19
|
+
}
|
|
20
|
+
const sources: HashSource[] = [{ type: 'contents', id: 'harmonyAutolinking', contents: JSON.stringify(dependencies), reasons: ['harmonyAutolinking'] }];
|
|
21
|
+
for (const name of ['@react-native-oh/react-native-harmony', '@expo-harmony/template', '@expo-harmony/config-plugins', '@expo-harmony/prebuild-config']) {
|
|
22
|
+
const file = require.resolve(name + '/package.json');
|
|
23
|
+
sources.push({ type: 'dir', filePath: path.relative(root, path.dirname(file)), reasons: ['harmonyNativeToolchain'] });
|
|
24
|
+
}
|
|
25
|
+
if (fs.existsSync(path.join(root, 'harmony'))) sources.push({ type: 'dir', filePath: 'harmony', reasons: ['harmonyNativeProject'] });
|
|
26
|
+
|
|
27
|
+
return createFingerprintAsync(root, {
|
|
28
|
+
platforms: [], extraSources: sources,
|
|
29
|
+
ignorePaths: [
|
|
30
|
+
...generated,
|
|
31
|
+
'**/harmony/**/build/**', '**/harmony/**/.hvigor/**', '**/harmony/**/.cxx/**', '**/harmony/**/oh_modules/**',
|
|
32
|
+
'**/harmony/**/oh-package-lock.json5', '**/harmony/**/local.properties', '**/harmony/**/native-inputs.cmake',
|
|
33
|
+
'**/harmony/**/HarmonyRuntime.ts', '**/harmony/**/expo-harmony-runtime.json', '**/harmony/**/expo-updates/**',
|
|
34
|
+
'**/harmony/**/hermes_bundle.hbc', '**/harmony/**/hermes_bundle.hbc.*', '**/harmony/**/.expo-harmony/**',
|
|
35
|
+
'**/harmony/**/autolinking.cmake', '**/harmony/**/autolinking.cpp',
|
|
36
|
+
'**/harmony/**/generated/**', '**/*.har', '**/*.hap', '**/.git/**', '**/node_modules/**/node_modules/**',
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { assertHarmonyCompatibility } from '@expo-harmony/expo-modules-autolinking/runtime';
|
|
5
|
+
|
|
6
|
+
import { CommonOptions, parseArgs } from '../args';
|
|
7
|
+
import { parseCommandArgs, type Command } from '../command';
|
|
8
|
+
import { HarmonyCliError } from '../errors';
|
|
9
|
+
import { resolveHarmonyBuildPlanAsync } from '../native/project';
|
|
10
|
+
import { publishRuntimeContractAsync, resolveRuntimeRequirementsAsync } from './contract';
|
|
11
|
+
|
|
12
|
+
export const command: Command = async (argv, io) => {
|
|
13
|
+
const args = parseCommandArgs((argv) => {
|
|
14
|
+
const { values, positionals } = parseArgs({
|
|
15
|
+
...CommonOptions,
|
|
16
|
+
publish: { type: 'boolean' },
|
|
17
|
+
client: { type: 'string' },
|
|
18
|
+
variant: { type: 'string' },
|
|
19
|
+
}, argv);
|
|
20
|
+
|
|
21
|
+
if (positionals.length > 1 || (values.variant && !['debug', 'release'].includes(values.variant))
|
|
22
|
+
|| (values.publish && values.client)) {
|
|
23
|
+
throw new HarmonyCliError(
|
|
24
|
+
'ERR_HARMONY_CONFIG_INVALID',
|
|
25
|
+
'Use runtime [project] [--client file | --publish] [--variant debug|release].',
|
|
26
|
+
{ operation: 'parse-arguments' }
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { help: Boolean(values.help), project: positionals[0], ...values };
|
|
31
|
+
}, argv, io);
|
|
32
|
+
if (!args) return 0;
|
|
33
|
+
|
|
34
|
+
const { projectRoot: root, options } = args;
|
|
35
|
+
if (options.publish) {
|
|
36
|
+
const plan = await resolveHarmonyBuildPlanAsync(root, { buildMode: options.variant as 'debug' | 'release' });
|
|
37
|
+
await publishRuntimeContractAsync(root, plan);
|
|
38
|
+
|
|
39
|
+
io.log('Published Harmony runtime capabilities and host validator.');
|
|
40
|
+
} else {
|
|
41
|
+
const contract = await resolveRuntimeRequirementsAsync(root);
|
|
42
|
+
|
|
43
|
+
if (options.client) {
|
|
44
|
+
const client = JSON.parse(await fs.promises.readFile(path.resolve(options.client), 'utf8'));
|
|
45
|
+
assertHarmonyCompatibility(client, contract);
|
|
46
|
+
|
|
47
|
+
io.log('Project is compatible with the fixed Harmony client.');
|
|
48
|
+
} else {
|
|
49
|
+
io.log(JSON.stringify(contract, null, 2));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return 0;
|
|
54
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { parseCommandArgs, type Command } from '../command';
|
|
2
|
+
import { parseStartArgs } from './options';
|
|
3
|
+
|
|
4
|
+
export const command: Command = async (argv, io) => {
|
|
5
|
+
const args = parseCommandArgs(parseStartArgs, argv, io);
|
|
6
|
+
if (!args) return 0;
|
|
7
|
+
|
|
8
|
+
const { projectRoot: root, options } = args;
|
|
9
|
+
const { startExpoMetroAsync } = await import('../run/metro.js');
|
|
10
|
+
|
|
11
|
+
const metro = await startExpoMetroAsync(root, {
|
|
12
|
+
...options,
|
|
13
|
+
interactive: true,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
if (metro.development) {
|
|
18
|
+
io.log(`Harmony manifest: ${metro.development.manifestUrl}`);
|
|
19
|
+
io.log(`Harmony JS bundle: ${metro.development.bundleUrl}`);
|
|
20
|
+
io.log(`Harmony launch link: ${metro.development.launchLink}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (metro.owner === 'existing') {
|
|
24
|
+
io.log(`Expo Metro is already running on port ${metro.port}.`);
|
|
25
|
+
if (options.resetCache) {
|
|
26
|
+
io.warn('Stop the existing Metro server and run this command again to reset its cache.');
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
io.log('\n› Logs for your project will appear below. Press Ctrl+C to exit.');
|
|
30
|
+
await metro.waitAsync();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return 0;
|
|
34
|
+
} finally {
|
|
35
|
+
await metro.stop();
|
|
36
|
+
}
|
|
37
|
+
};
|
package/src/start/options.ts
CHANGED
|
@@ -4,7 +4,9 @@ import { HarmonyCliError } from '../errors';
|
|
|
4
4
|
const StartOptions = {
|
|
5
5
|
...CommonOptions,
|
|
6
6
|
'clear': { short: 'c', type: 'boolean' },
|
|
7
|
+
'host': { type: 'string' },
|
|
7
8
|
'port': { type: 'string' },
|
|
9
|
+
'private-key-path': { type: 'string' },
|
|
8
10
|
'reset-cache': { type: 'boolean' },
|
|
9
11
|
} as const;
|
|
10
12
|
|
|
@@ -25,9 +27,19 @@ function parseStartArgs(argv: string[]) {
|
|
|
25
27
|
});
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
if (values.host !== undefined && !/^[A-Za-z0-9.-]+$/.test(values.host)) {
|
|
31
|
+
throw new HarmonyCliError(
|
|
32
|
+
'ERR_HARMONY_CONFIG_INVALID',
|
|
33
|
+
'--host must be a hostname or IPv4 address without a port or scheme.',
|
|
34
|
+
{ operation: 'parse-arguments' }
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
return {
|
|
29
39
|
help: Boolean(values.help),
|
|
40
|
+
host: values.host,
|
|
30
41
|
port,
|
|
42
|
+
privateKeyPath: values['private-key-path'],
|
|
31
43
|
project: positionals[0],
|
|
32
44
|
resetCache: Boolean(values['reset-cache'] || values.clear),
|
|
33
45
|
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import type { ExpoConfigWithHarmony } from '@expo-harmony/config-plugins';
|
|
5
|
+
import { atomicWriteJson } from '../file';
|
|
6
|
+
import { HarmonyCliError } from '../errors';
|
|
7
|
+
import { readProjectRuntimeAsync } from '../runtime/config';
|
|
8
|
+
|
|
9
|
+
export interface UpdateAsset {
|
|
10
|
+
key: string;
|
|
11
|
+
hash: string;
|
|
12
|
+
contentType: string;
|
|
13
|
+
fileExtension: string;
|
|
14
|
+
embeddedAssetFilename: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function publishUpdatesConfigurationAsync(
|
|
18
|
+
root: string,
|
|
19
|
+
directory: string,
|
|
20
|
+
config: ExpoConfigWithHarmony,
|
|
21
|
+
runtime: string
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
const updates = { ...config.updates, runtimeVersion: runtime };
|
|
24
|
+
if (updates.url) {
|
|
25
|
+
const url = new URL(updates.url);
|
|
26
|
+
if (!['http:', 'https:'].includes(url.protocol)) throw new HarmonyCliError('ERR_HARMONY_UPDATES_CONFIG', 'Updates URL must use HTTP or HTTPS.', { operation: 'updates-config' });
|
|
27
|
+
}
|
|
28
|
+
if (updates.codeSigningCertificate) {
|
|
29
|
+
const certificate = await fs.readFile(path.resolve(root, updates.codeSigningCertificate), 'utf8');
|
|
30
|
+
new crypto.X509Certificate(certificate);
|
|
31
|
+
updates.codeSigningCertificate = certificate;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await atomicWriteJson(updates, path.join(directory, 'expo-updates/config.json'), directory);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function publishEmbeddedUpdateAsync(
|
|
38
|
+
root: string,
|
|
39
|
+
paths: { rawfileRoot: string; bundle: string; metadataRoot: string },
|
|
40
|
+
assets: UpdateAsset[]
|
|
41
|
+
): Promise<void> {
|
|
42
|
+
const project = await readProjectRuntimeAsync(root);
|
|
43
|
+
const bundle = await fs.readFile(paths.bundle);
|
|
44
|
+
const hash = crypto.createHash('sha256').update(Uint8Array.from(bundle)).digest('base64url');
|
|
45
|
+
const directory = path.join(paths.rawfileRoot, 'expo-updates');
|
|
46
|
+
const id = crypto.randomUUID();
|
|
47
|
+
const manifest = {
|
|
48
|
+
id, commitTime: Date.now(),
|
|
49
|
+
launchAsset: { packagerHash: `bundle-${id}`, hash, type: 'hbc',
|
|
50
|
+
embeddedAssetFilename: path.relative(paths.rawfileRoot, paths.bundle).split(path.sep).join('/') },
|
|
51
|
+
assets: [...new Map(assets.map(asset => [asset.key, asset])).values()].map(asset => ({
|
|
52
|
+
packagerHash: asset.key, hash: asset.hash, type: asset.fileExtension.replace(/^\./, ''),
|
|
53
|
+
embeddedAssetFilename: asset.embeddedAssetFilename,
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
await publishUpdatesConfigurationAsync(root, paths.rawfileRoot, project.config, project.runtimeVersion);
|
|
58
|
+
await atomicWriteJson(manifest, path.join(directory, 'manifest.json'), paths.rawfileRoot);
|
|
59
|
+
await atomicWriteJson(manifest, path.join(paths.metadataRoot, 'update.json'), paths.metadataRoot);
|
|
60
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { getConfig } from '@expo/config';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { CommonOptions, parseArgs } from '../args';
|
|
8
|
+
import { parseCommandArgs, type Command } from '../command';
|
|
9
|
+
import { exportEmbedAsync } from '../exportEmbed/export';
|
|
10
|
+
import { HarmonyCliError } from '../errors';
|
|
11
|
+
import { atomicCopy, atomicWriteJson } from '../file';
|
|
12
|
+
import { assertSafeRelative } from '../path';
|
|
13
|
+
import { resolveExpoCli } from '../expo';
|
|
14
|
+
import { readProjectRuntimeAsync } from '../runtime/config';
|
|
15
|
+
|
|
16
|
+
export const command: Command = async (argv, io) => {
|
|
17
|
+
const args = parseCommandArgs((values) => {
|
|
18
|
+
const { values: options, positionals } = parseArgs({ ...CommonOptions,
|
|
19
|
+
'output-dir': { type: 'string' }, 'asset-url': { type: 'string' },
|
|
20
|
+
'private-key-path': { type: 'string' }, 'reset-cache': { type: 'boolean' },
|
|
21
|
+
}, values);
|
|
22
|
+
if (positionals.length > 1) throw new HarmonyCliError('ERR_HARMONY_EXPORT_ARGUMENTS', 'Expected at most one project directory.', { operation: 'export' });
|
|
23
|
+
return { ...options, project: positionals[0], help: Boolean(options.help) };
|
|
24
|
+
}, argv, io);
|
|
25
|
+
if (!args) return 0;
|
|
26
|
+
|
|
27
|
+
const { projectRoot: root, options } = args;
|
|
28
|
+
const base = options['asset-url'];
|
|
29
|
+
if (!base || !['http:', 'https:'].includes(new URL(base).protocol)) {
|
|
30
|
+
throw new HarmonyCliError('ERR_HARMONY_EXPORT_ARGUMENTS', 'OTA export requires --asset-url with the HTTP(S) directory that will host its assets.', { operation: 'export' });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const output = path.resolve(root, options['output-dir'] ?? 'dist');
|
|
34
|
+
const temporary = await fs.mkdtemp(path.join(os.tmpdir(), 'expo-harmony-update-'));
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await exportEmbedAsync(root, { outputDirectory: temporary, resetCache: options['reset-cache'] });
|
|
38
|
+
const embedded = JSON.parse(await fs.readFile(path.join(temporary, 'update.json'), 'utf8'));
|
|
39
|
+
const project = await readProjectRuntimeAsync(root);
|
|
40
|
+
const { exp } = getConfig(root, { isPublicConfig: true });
|
|
41
|
+
const require = createRequire(resolveExpoCli(root).cliPath);
|
|
42
|
+
const upstream = createRequire(require.resolve('@expo/cli/package.json'));
|
|
43
|
+
const mime = upstream('mime-types');
|
|
44
|
+
const assets = [embedded.launchAsset, ...embedded.assets].map(asset => ({
|
|
45
|
+
key: asset.packagerHash, hash: asset.hash,
|
|
46
|
+
contentType: asset.type === 'hbc' ? 'application/javascript' : mime.lookup(asset.type) || 'application/octet-stream',
|
|
47
|
+
fileExtension: asset.type ? '.' + asset.type : '', embeddedAssetFilename: asset.embeddedAssetFilename,
|
|
48
|
+
}));
|
|
49
|
+
const manifest = {
|
|
50
|
+
id: embedded.id, createdAt: new Date(embedded.commitTime).toISOString(), runtimeVersion: project.runtimeVersion,
|
|
51
|
+
launchAsset: assets[0], assets: assets.slice(1), metadata: {},
|
|
52
|
+
extra: { expoClient: exp, eas: { projectId: exp.extra?.eas?.projectId },
|
|
53
|
+
scopeKey: exp.originalFullName ?? `@${exp.owner ?? 'anonymous'}/${exp.slug}` },
|
|
54
|
+
};
|
|
55
|
+
const metadata: { path: string; ext: string }[] = [];
|
|
56
|
+
for (const asset of assets) {
|
|
57
|
+
const relative = `assets/${asset.hash}${asset.fileExtension}`;
|
|
58
|
+
await atomicCopy(path.join(temporary, 'embedded', assertSafeRelative(asset.embeddedAssetFilename, 'Embedded update asset')), path.join(output, relative), output);
|
|
59
|
+
Object.assign(asset, { url: new URL(relative, base.endsWith('/') ? base : base + '/').href });
|
|
60
|
+
delete asset.embeddedAssetFilename;
|
|
61
|
+
if (asset !== manifest.launchAsset) metadata.push({ path: relative, ext: asset.fileExtension.replace(/^\./, '') });
|
|
62
|
+
}
|
|
63
|
+
await atomicCopy(path.join(temporary, 'hermes_bundle.hbc.map'), path.join(output, 'bundle.hbc.map'), output);
|
|
64
|
+
await atomicWriteJson({ version: 0, bundler: 'metro', fileMetadata: { harmony: {
|
|
65
|
+
bundle: `assets/${manifest.launchAsset.hash}.hbc`, assets: metadata,
|
|
66
|
+
} } }, path.join(output, 'metadata.json'), output);
|
|
67
|
+
await atomicWriteJson(manifest, path.join(output, 'manifest.json'), output);
|
|
68
|
+
if (options['private-key-path']) {
|
|
69
|
+
const key = await fs.readFile(path.resolve(root, options['private-key-path']), 'utf8');
|
|
70
|
+
if (crypto.createPrivateKey(key).asymmetricKeyType !== 'rsa') throw new HarmonyCliError('ERR_HARMONY_UPDATES_SIGNING', 'Expo Updates signing requires an RSA private key.', { operation: 'export' });
|
|
71
|
+
|
|
72
|
+
const body = await fs.readFile(path.join(output, 'manifest.json'));
|
|
73
|
+
const signature = crypto.sign('RSA-SHA256', Uint8Array.from(body), key).toString('base64');
|
|
74
|
+
const id = exp.updates?.codeSigningMetadata?.keyid ?? 'root';
|
|
75
|
+
const { serializeDictionary } = upstream('structured-headers');
|
|
76
|
+
await fs.writeFile(path.join(temporary, 'signature.txt'), serializeDictionary(new Map([
|
|
77
|
+
['sig', [signature, new Map()]], ['keyid', [id, new Map()]], ['alg', ['rsa-v1_5-sha256', new Map()]],
|
|
78
|
+
])));
|
|
79
|
+
await atomicCopy(path.join(temporary, 'signature.txt'), path.join(output, 'signature.txt'), output);
|
|
80
|
+
} else {
|
|
81
|
+
await fs.rm(path.join(output, 'signature.txt'), { force: true });
|
|
82
|
+
}
|
|
83
|
+
io.log(`Exported Harmony update ${manifest.id} for runtime ${manifest.runtimeVersion} to ${output}.`);
|
|
84
|
+
} finally {
|
|
85
|
+
await fs.rm(temporary, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return 0;
|
|
89
|
+
};
|
package/src/buildHap/common.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
|
|
3
|
-
import { doctorAsync, type DoctorResult } from '../doctor/doctor';
|
|
4
|
-
import { HarmonyCliError } from '../errors';
|
|
5
|
-
import { checkAsync } from '../prebuild/check';
|
|
6
|
-
import { prebuildParsedAsync } from '../prebuild/prebuild';
|
|
7
|
-
import { formatDiagnostics, spawnAsync } from '../process';
|
|
8
|
-
import { resolveHarmonyBuildPlanIfPresentAsync } from '../tools';
|
|
9
|
-
|
|
10
|
-
interface HarmonyBuildPipelineOptions {
|
|
11
|
-
io?: Pick<Console, 'error' | 'log' | 'warn'>;
|
|
12
|
-
requireDeviceTools?: boolean;
|
|
13
|
-
/** @internal The caller already completed an isolated prebuild check. */
|
|
14
|
-
skipGeneratedProjectCheck?: boolean;
|
|
15
|
-
sync: boolean;
|
|
16
|
-
variant: 'debug' | 'release';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface CheckedProcessOptions {
|
|
20
|
-
code: string;
|
|
21
|
-
cwd: string;
|
|
22
|
-
env?: NodeJS.ProcessEnv;
|
|
23
|
-
message: string;
|
|
24
|
-
operation: string;
|
|
25
|
-
outputLimit?: number;
|
|
26
|
-
timeoutMs?: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function progress(options: HarmonyBuildPipelineOptions, message: string): void {
|
|
30
|
-
options.io?.log?.(`› ${message}`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function timed<T>(
|
|
34
|
-
steps: Record<string, number>,
|
|
35
|
-
name: string,
|
|
36
|
-
operation: () => Promise<T> | T
|
|
37
|
-
): Promise<T> {
|
|
38
|
-
const started = performance.now();
|
|
39
|
-
try {
|
|
40
|
-
return await operation();
|
|
41
|
-
} finally {
|
|
42
|
-
steps[name] = Math.max(0, Math.round(performance.now() - started));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function assertDoctor(result: DoctorResult): void {
|
|
47
|
-
if (result.ok) return;
|
|
48
|
-
|
|
49
|
-
const failing = result.checks.filter(check => check.status === 'error').map(check => check.id);
|
|
50
|
-
|
|
51
|
-
throw new HarmonyCliError('ERR_HARMONY_DOCTOR_FAILED', `Harmony doctor found blocking checks: ${failing.join(', ') || 'unknown'}.`, { operation: 'doctor' });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function isNonEmptyRegularFile(file: string): boolean {
|
|
55
|
-
try {
|
|
56
|
-
const stat = fs.lstatSync(file);
|
|
57
|
-
return !stat.isSymbolicLink() && stat.isFile() && stat.size > 0;
|
|
58
|
-
} catch {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function runCheckedAsync(
|
|
64
|
-
command: string,
|
|
65
|
-
args: string[],
|
|
66
|
-
options: CheckedProcessOptions
|
|
67
|
-
) {
|
|
68
|
-
const result = await spawnAsync(command, args, {
|
|
69
|
-
capture: true,
|
|
70
|
-
cwd: options.cwd,
|
|
71
|
-
env: options.env,
|
|
72
|
-
operation: options.operation,
|
|
73
|
-
outputLimit: options.outputLimit || 2 * 1024 * 1024,
|
|
74
|
-
timeoutMs: options.timeoutMs,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
if (result.code !== 0 || result.timedOut) {
|
|
78
|
-
const diagnostics = formatDiagnostics(result);
|
|
79
|
-
throw new HarmonyCliError(
|
|
80
|
-
options.code,
|
|
81
|
-
`${options.message} exited with code ${result.code}${result.timedOut ? ' after timing out' : ''}.${diagnostics ? `\n${diagnostics}` : ''}`,
|
|
82
|
-
{ exitCode: result.code || 1, operation: options.operation }
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return result;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function ensureGeneratedProjectAsync(
|
|
90
|
-
projectRoot: string,
|
|
91
|
-
options: HarmonyBuildPipelineOptions,
|
|
92
|
-
steps: Record<string, number>
|
|
93
|
-
) {
|
|
94
|
-
const plan = await resolveHarmonyBuildPlanIfPresentAsync(projectRoot, {
|
|
95
|
-
buildMode: options.variant,
|
|
96
|
-
});
|
|
97
|
-
const exists = Boolean(plan && fs.existsSync(plan.harmonyRoot));
|
|
98
|
-
|
|
99
|
-
progress(options, 'Checking the Harmony project');
|
|
100
|
-
const doctor = await timed(steps, 'doctor', () => doctorAsync(projectRoot, {
|
|
101
|
-
requireBuildTools: true,
|
|
102
|
-
requireDeviceTools: options.requireDeviceTools,
|
|
103
|
-
validateGeneratedProject: exists,
|
|
104
|
-
validateModules: false,
|
|
105
|
-
}));
|
|
106
|
-
assertDoctor(doctor);
|
|
107
|
-
|
|
108
|
-
if (!exists || options.sync) {
|
|
109
|
-
progress(options, exists
|
|
110
|
-
? 'Synchronizing the generated Harmony project'
|
|
111
|
-
: 'Generating the missing Harmony project');
|
|
112
|
-
await timed(steps, 'prebuild', () => prebuildParsedAsync(
|
|
113
|
-
projectRoot,
|
|
114
|
-
[],
|
|
115
|
-
{ buildType: options.variant }
|
|
116
|
-
));
|
|
117
|
-
steps.cngCheck = 0;
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
steps.prebuild = 0;
|
|
122
|
-
if (options.skipGeneratedProjectCheck) {
|
|
123
|
-
steps.cngCheck = 0;
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
progress(options, 'Checking CNG ownership and drift');
|
|
127
|
-
const check = await timed(steps, 'cngCheck', () => checkAsync(projectRoot, { buildType: options.variant }));
|
|
128
|
-
if (!check.clean) {
|
|
129
|
-
const summary = check.changes.slice(0, 8).map(change => `${change.type}:${change.path}`).join(', ');
|
|
130
|
-
throw new HarmonyCliError(
|
|
131
|
-
'ERR_HARMONY_MANIFEST_DRIFT',
|
|
132
|
-
`Generated Harmony files differ from CNG desired state${summary ? ` (${summary})` : ''}. Run expo-harmony prebuild or retry with --sync.`,
|
|
133
|
-
{ operation: 'check' }
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export {
|
|
139
|
-
ensureGeneratedProjectAsync,
|
|
140
|
-
isNonEmptyRegularFile,
|
|
141
|
-
progress,
|
|
142
|
-
runCheckedAsync,
|
|
143
|
-
timed,
|
|
144
|
-
};
|
|
145
|
-
export type { CheckedProcessOptions, HarmonyBuildPipelineOptions };
|
package/src/run/install.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { HarmonyCliError } from '../errors';
|
|
2
|
-
import type { HarmonyBuildPlan, HarmonyToolchain } from '../tools';
|
|
3
|
-
import { formatDiagnostics, spawnAsync, type ProcessResult } from '../process';
|
|
4
|
-
|
|
5
|
-
interface InstallOptions {
|
|
6
|
-
timeoutMs?: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function installMessage(result: ProcessResult): string {
|
|
10
|
-
const diagnostics = formatDiagnostics(result);
|
|
11
|
-
return `OHPM install exited with code ${result.code}${result.timedOut ? ' after timing out' : ''}.`
|
|
12
|
-
+ `${diagnostics ? `\n${diagnostics}` : ''}`;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Runs OHPM against the generated project without rewriting its manifest. */
|
|
16
|
-
async function installHarmonyDependenciesAsync(
|
|
17
|
-
plan: HarmonyBuildPlan,
|
|
18
|
-
toolchain: HarmonyToolchain,
|
|
19
|
-
options: InstallOptions = {}
|
|
20
|
-
): Promise<void> {
|
|
21
|
-
const result = await spawnAsync(
|
|
22
|
-
toolchain.ohpm.command,
|
|
23
|
-
[...toolchain.ohpm.args, 'install', '--all'],
|
|
24
|
-
{
|
|
25
|
-
capture: true,
|
|
26
|
-
cwd: plan.harmonyRoot,
|
|
27
|
-
operation: 'ohpm-install',
|
|
28
|
-
outputLimit: 2 * 1024 * 1024,
|
|
29
|
-
timeoutMs: options.timeoutMs || 5 * 60_000,
|
|
30
|
-
}
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
if (result.code !== 0 || result.timedOut) {
|
|
34
|
-
throw new HarmonyCliError('ERR_HARMONY_OHPM_FAILED', installMessage(result), {
|
|
35
|
-
exitCode: result.code || 1,
|
|
36
|
-
operation: 'ohpm-install',
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export { installHarmonyDependenciesAsync };
|