@expo-harmony/cli 55.0.26-harmony.4 → 55.0.26-harmony.6
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 +45 -26
- package/build/buildHap/build.js +1 -0
- package/build/buildHap/common.d.ts +1 -1
- package/build/doctor/doctor.js +17 -4
- package/build/exportEmbed/export.js +1 -0
- package/build/prebuild/check.js +14 -8
- package/build/process.d.ts +1 -1
- package/build/process.js +147 -75
- package/build/run/metro.js +1 -0
- package/build/run/run.js +5 -1
- package/build/tools.js +25 -23
- package/package.json +3 -3
- package/src/buildHap/common.ts +1 -1
- package/src/doctor/doctor.ts +6 -3
- package/src/prebuild/check.ts +28 -14
- package/src/process.ts +137 -63
- package/src/run/run.ts +4 -1
- package/src/tools.ts +27 -22
package/build/run/run.js
CHANGED
|
@@ -43,7 +43,10 @@ async function runHarmonyUnlockedAsync(projectRoot, options = {}) {
|
|
|
43
43
|
variant: options.variant || 'debug',
|
|
44
44
|
};
|
|
45
45
|
const steps = {};
|
|
46
|
-
await (0, common_1.ensureGeneratedProjectAsync)(projectRoot,
|
|
46
|
+
await (0, common_1.ensureGeneratedProjectAsync)(projectRoot, {
|
|
47
|
+
...normalizedOptions,
|
|
48
|
+
skipGeneratedProjectCheck: true,
|
|
49
|
+
}, steps);
|
|
47
50
|
const plan = await (0, common_1.timed)(steps, 'buildPlan', () => (0, tools_1.resolveHarmonyBuildPlanAsync)(projectRoot, { buildMode: normalizedOptions.variant }));
|
|
48
51
|
const identity = resolveRunIdentity(plan, normalizedOptions);
|
|
49
52
|
const toolchain = (0, tools_1.resolveHarmonyToolchain)();
|
|
@@ -80,6 +83,7 @@ async function runHarmonyUnlockedAsync(projectRoot, options = {}) {
|
|
|
80
83
|
const buildEnv = {
|
|
81
84
|
...process.env,
|
|
82
85
|
EXPO_HARMONY_NODE: process.env.EXPO_HARMONY_NODE || process.execPath,
|
|
86
|
+
EXPO_METRO_TARGET: 'harmony',
|
|
83
87
|
HERMES_V1_ENABLED: 'true',
|
|
84
88
|
...(normalizedOptions.variant === 'release' ? { EXPO_HARMONY_BUNDLE_PREBUILT: '1' } : {}),
|
|
85
89
|
...(toolchain.sdkHome && !process.env.DEVECO_SDK_HOME
|
package/build/tools.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.resolveHarmonyEmulator = resolveHarmonyEmulator;
|
|
|
10
10
|
exports.resolveHarmonyToolchain = resolveHarmonyToolchain;
|
|
11
11
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
12
|
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const tool_command_1 = require("@expo-harmony/expo-modules-autolinking/tool-command");
|
|
13
14
|
const build_descriptor_1 = require("@expo-harmony/prebuild-config/build-descriptor");
|
|
14
15
|
const check_1 = require("@expo-harmony/prebuild-config/check");
|
|
15
16
|
const errors_1 = require("./errors");
|
|
@@ -99,38 +100,35 @@ function resolveHarmonyToolchain() {
|
|
|
99
100
|
const layouts = sdkHome ? devEcoLayouts(sdkHome) : [];
|
|
100
101
|
let ohpm;
|
|
101
102
|
if (env.HARMONY_OHPM) {
|
|
102
|
-
ohpm = {
|
|
103
|
+
ohpm = { ...(0, tool_command_1.resolveHarmonyCommand)('ohpm', [], env), source: 'override' };
|
|
103
104
|
}
|
|
104
105
|
else {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
const entry = layouts.map(layout => ({
|
|
107
|
+
node: devEcoNode(layout, platform, { ...env, HARMONY_NODE: env.HARMONY_OHPM_NODE || env.HARMONY_NODE }),
|
|
108
|
+
script: node_path_1.default.join(layout.toolsRoot, 'ohpm', 'bin', 'pm-cli.js'),
|
|
109
|
+
})).find(candidate => candidate.node && node_fs_1.default.existsSync(candidate.script));
|
|
110
|
+
const command = existingFile(layouts.map(layout => node_path_1.default.join(layout.toolsRoot, 'ohpm', 'bin', platform === 'win32' ? 'ohpm.bat' : 'ohpm')));
|
|
111
|
+
ohpm = entry
|
|
112
|
+
? { args: [entry.script], command: entry.node.command, source: 'deveco' }
|
|
113
|
+
: command
|
|
114
|
+
? { args: [], command, source: 'deveco' }
|
|
115
|
+
: { args: [], command: platform === 'win32' ? 'ohpm.bat' : 'ohpm', source: 'path' };
|
|
109
116
|
}
|
|
110
117
|
let hvigor;
|
|
111
118
|
if (env.HARMONY_HVIGORW) {
|
|
112
|
-
|
|
113
|
-
hvigor = {
|
|
114
|
-
args: [env.HARMONY_HVIGORW],
|
|
115
|
-
command: env.HARMONY_NODE || process.execPath,
|
|
116
|
-
source: 'override',
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
hvigor = { args: [], command: env.HARMONY_HVIGORW, source: 'override' };
|
|
121
|
-
}
|
|
119
|
+
hvigor = { ...(0, tool_command_1.resolveHarmonyCommand)('hvigorw', [], env), source: 'override' };
|
|
122
120
|
}
|
|
123
121
|
else {
|
|
124
|
-
const
|
|
125
|
-
layout,
|
|
126
|
-
node: devEcoNode(layout, platform, env),
|
|
122
|
+
const entry = layouts.map(layout => ({
|
|
123
|
+
node: devEcoNode(layout, platform, { ...env, HARMONY_NODE: env.HARMONY_HVIGOR_NODE || env.HARMONY_NODE }),
|
|
127
124
|
script: node_path_1.default.join(layout.toolsRoot, 'hvigor', 'bin', 'hvigorw.js'),
|
|
128
125
|
})).find(candidate => candidate.node && node_fs_1.default.existsSync(candidate.script));
|
|
129
|
-
hvigor =
|
|
130
|
-
? { args: [
|
|
126
|
+
hvigor = entry
|
|
127
|
+
? { args: [entry.script], command: entry.node.command, source: 'deveco' }
|
|
131
128
|
: { args: [], command: platform === 'win32' ? 'hvigorw.bat' : 'hvigorw', source: 'path' };
|
|
132
129
|
}
|
|
133
130
|
const toolsRoot = layouts.find(layout => (hvigor.args[0]?.startsWith(`${layout.toolsRoot}${node_path_1.default.sep}`)
|
|
131
|
+
|| ohpm.args[0]?.startsWith(`${layout.toolsRoot}${node_path_1.default.sep}`)
|
|
134
132
|
|| ohpm.command.startsWith(`${layout.toolsRoot}${node_path_1.default.sep}`)))?.toolsRoot || null;
|
|
135
133
|
let hdc;
|
|
136
134
|
if (env.HARMONY_HDC) {
|
|
@@ -151,10 +149,14 @@ function resolveHarmonyToolchain() {
|
|
|
151
149
|
function createHarmonyToolchainEnv(toolchain = resolveHarmonyToolchain()) {
|
|
152
150
|
return {
|
|
153
151
|
...process.env,
|
|
154
|
-
HARMONY_OHPM: toolchain.ohpm.command,
|
|
155
|
-
|
|
152
|
+
HARMONY_OHPM: toolchain.ohpm.args[0] || toolchain.ohpm.command,
|
|
153
|
+
HARMONY_OHPM_NODE: toolchain.ohpm.args.length > 0 ? toolchain.ohpm.command : undefined,
|
|
154
|
+
// Script-based tools must retain both their script and Node executable.
|
|
156
155
|
HARMONY_HVIGORW: toolchain.hvigor.args[0] || toolchain.hvigor.command,
|
|
157
|
-
|
|
156
|
+
HARMONY_HVIGOR_NODE: toolchain.hvigor.args.length > 0 ? toolchain.hvigor.command : undefined,
|
|
157
|
+
...(toolchain.hvigor.args.length > 0
|
|
158
|
+
? { HARMONY_NODE: toolchain.hvigor.command }
|
|
159
|
+
: toolchain.ohpm.args.length > 0 ? { HARMONY_NODE: toolchain.ohpm.command } : {}),
|
|
158
160
|
...(toolchain.sdkHome && !process.env.DEVECO_SDK_HOME
|
|
159
161
|
? { DEVECO_SDK_HOME: toolchain.sdkHome }
|
|
160
162
|
: {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo-harmony/cli",
|
|
3
|
-
"version": "55.0.26-harmony.
|
|
3
|
+
"version": "55.0.26-harmony.6",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"react-native",
|
|
6
6
|
"expo",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@expo-harmony/config-plugins": "55.0.10-harmony.2",
|
|
40
|
-
"@expo-harmony/expo-modules-autolinking": "55.0.25-harmony.
|
|
41
|
-
"@expo-harmony/prebuild-config": "55.0.0-harmony.
|
|
40
|
+
"@expo-harmony/expo-modules-autolinking": "55.0.25-harmony.3",
|
|
41
|
+
"@expo-harmony/prebuild-config": "55.0.0-harmony.5",
|
|
42
42
|
"@expo/config": "55.0.17",
|
|
43
43
|
"cross-spawn": "^7.0.6",
|
|
44
44
|
"json5": "2.2.3"
|
package/src/buildHap/common.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { resolveHarmonyBuildPlanIfPresentAsync } from '../tools';
|
|
|
10
10
|
interface HarmonyBuildPipelineOptions {
|
|
11
11
|
io?: Pick<Console, 'error' | 'log' | 'warn'>;
|
|
12
12
|
requireDeviceTools?: boolean;
|
|
13
|
-
/** @internal
|
|
13
|
+
/** @internal Reuse the native project without an isolated drift check. */
|
|
14
14
|
skipGeneratedProjectCheck?: boolean;
|
|
15
15
|
sync: boolean;
|
|
16
16
|
variant: 'debug' | 'release';
|
package/src/doctor/doctor.ts
CHANGED
|
@@ -208,6 +208,8 @@ async function doctorUnlockedAsync(projectRoot: string, options: DoctorOptions =
|
|
|
208
208
|
tools.unshift(['hdc', toolchain.hdc, ['-v'], unavailableToolStatus]);
|
|
209
209
|
}
|
|
210
210
|
for (const [id, tool, versionArgs, unavailableStatus] of tools) {
|
|
211
|
+
const command = [tool.command, ...tool.args].map(value => JSON.stringify(value)).join(' ');
|
|
212
|
+
|
|
211
213
|
try {
|
|
212
214
|
const result = await spawnAsync(tool.command, [...tool.args, ...versionArgs], {
|
|
213
215
|
capture: true,
|
|
@@ -215,11 +217,12 @@ async function doctorUnlockedAsync(projectRoot: string, options: DoctorOptions =
|
|
|
215
217
|
operation: `doctor-${id}`,
|
|
216
218
|
timeoutMs: 10_000,
|
|
217
219
|
});
|
|
220
|
+
|
|
218
221
|
checks.push(result.code === 0 && !result.timedOut
|
|
219
|
-
? check(id, 'pass', `${
|
|
220
|
-
: check(id, unavailableStatus, `${
|
|
222
|
+
? check(id, 'pass', `${command} is available through ${tool.source}.`)
|
|
223
|
+
: check(id, unavailableStatus, `${command} is unavailable or unhealthy; HAP build cannot be verified.`));
|
|
221
224
|
} catch {
|
|
222
|
-
checks.push(check(id, unavailableStatus, `${
|
|
225
|
+
checks.push(check(id, unavailableStatus, `${command} is unavailable; generation remains available.`));
|
|
223
226
|
}
|
|
224
227
|
}
|
|
225
228
|
|
package/src/prebuild/check.ts
CHANGED
|
@@ -40,7 +40,28 @@ function mirrorRoot(temp, project) {
|
|
|
40
40
|
return path.join(temp, 'filesystem', volume, ...segments);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
async function
|
|
43
|
+
async function linkModuleEntryAsync(
|
|
44
|
+
source: string,
|
|
45
|
+
target: string,
|
|
46
|
+
entry: fs.Dirent
|
|
47
|
+
) {
|
|
48
|
+
const directory = entry.isSymbolicLink()
|
|
49
|
+
? (await fs.promises.stat(source)).isDirectory()
|
|
50
|
+
: entry.isDirectory();
|
|
51
|
+
|
|
52
|
+
if (process.platform === 'win32' && !directory) {
|
|
53
|
+
await fs.promises.copyFile(source, target);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await fs.promises.symlink(
|
|
58
|
+
path.resolve(source),
|
|
59
|
+
target,
|
|
60
|
+
directory ? process.platform === 'win32' ? 'junction' : 'dir' : 'file'
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function linkModulesAsync(source: string, target: string) {
|
|
44
65
|
await fs.promises.mkdir(target, { recursive: true });
|
|
45
66
|
|
|
46
67
|
for (const entry of await fs.promises.readdir(source, { withFileTypes: true })) {
|
|
@@ -49,25 +70,18 @@ async function linkModulesAsync(source, target) {
|
|
|
49
70
|
|
|
50
71
|
if (entry.name.startsWith('@') && entry.isDirectory() && !entry.isSymbolicLink()) {
|
|
51
72
|
await fs.promises.mkdir(to);
|
|
73
|
+
|
|
52
74
|
for (const child of await fs.promises.readdir(from, { withFileTypes: true })) {
|
|
53
|
-
await
|
|
75
|
+
await linkModuleEntryAsync(
|
|
54
76
|
path.join(from, child.name),
|
|
55
77
|
path.join(to, child.name),
|
|
56
|
-
|
|
57
|
-
? 'junction'
|
|
58
|
-
: child.isDirectory() ? 'dir' : 'file'
|
|
78
|
+
child
|
|
59
79
|
);
|
|
60
80
|
}
|
|
61
81
|
continue;
|
|
62
82
|
}
|
|
63
83
|
|
|
64
|
-
await
|
|
65
|
-
from,
|
|
66
|
-
to,
|
|
67
|
-
process.platform === 'win32' && (entry.isDirectory() || entry.isSymbolicLink())
|
|
68
|
-
? 'junction'
|
|
69
|
-
: entry.isDirectory() ? 'dir' : 'file'
|
|
70
|
-
);
|
|
84
|
+
await linkModuleEntryAsync(from, to, entry);
|
|
71
85
|
}
|
|
72
86
|
}
|
|
73
87
|
|
|
@@ -126,8 +140,8 @@ async function copyAsync(
|
|
|
126
140
|
// Keep a real node_modules directory in the isolated project and link its
|
|
127
141
|
// entries. Dependency scanners then retain the isolated lexical package path
|
|
128
142
|
// (including scoped packages) instead of collapsing the entire node_modules
|
|
129
|
-
// root to the source project's realpath.
|
|
130
|
-
//
|
|
143
|
+
// root to the source project's realpath. Package directories stay linked;
|
|
144
|
+
// Windows copies loose files to avoid requiring file-symlink privileges.
|
|
131
145
|
await linkModulesAsync(modules, path.join(target, 'node_modules'));
|
|
132
146
|
await stageAsync(project, target, temp);
|
|
133
147
|
}
|
package/src/process.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import spawn from 'cross-spawn';
|
|
2
|
+
import { terminateProcess } from '@expo-harmony/expo-modules-autolinking/tool-command';
|
|
2
3
|
|
|
3
4
|
import { HarmonyCliError } from './errors';
|
|
4
5
|
|
|
@@ -76,8 +77,15 @@ function formatDiagnostics(result: Pick<ProcessResult, 'stderr' | 'stdout'>, lim
|
|
|
76
77
|
|
|
77
78
|
function spawnAsync(command: string, args: string[], options: ProcessOptions = {}): Promise<ProcessResult> {
|
|
78
79
|
return new Promise<ProcessResult>((resolve, reject) => {
|
|
80
|
+
if (options.signal?.aborted) {
|
|
81
|
+
throw new HarmonyCliError('ERR_HARMONY_PROCESS_FAILED', `Cannot run ${command}: operation aborted.`, {
|
|
82
|
+
cause: options.signal.reason,
|
|
83
|
+
operation: options.operation || 'spawn',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
79
87
|
const piped = Boolean(options.capture || options.onStdout || options.onStderr);
|
|
80
|
-
const
|
|
88
|
+
const limit = options.outputLimit || DefaultOutputLimit;
|
|
81
89
|
const child = spawn(command, args, {
|
|
82
90
|
cwd: options.cwd,
|
|
83
91
|
env: options.env || process.env,
|
|
@@ -86,11 +94,12 @@ function spawnAsync(command: string, args: string[], options: ProcessOptions = {
|
|
|
86
94
|
windowsHide: true,
|
|
87
95
|
});
|
|
88
96
|
|
|
89
|
-
const stdout = new BoundedCapture(
|
|
90
|
-
const stderr = new BoundedCapture(
|
|
97
|
+
const stdout = new BoundedCapture(limit);
|
|
98
|
+
const stderr = new BoundedCapture(limit);
|
|
91
99
|
let timedOut = false;
|
|
92
100
|
let settled = false;
|
|
93
|
-
let
|
|
101
|
+
let escalation: NodeJS.Timeout | null = null;
|
|
102
|
+
let stopping: Promise<void> | undefined;
|
|
94
103
|
|
|
95
104
|
if (piped) {
|
|
96
105
|
child.stdout.on('data', (chunk) => {
|
|
@@ -103,72 +112,96 @@ function spawnAsync(command: string, args: string[], options: ProcessOptions = {
|
|
|
103
112
|
});
|
|
104
113
|
}
|
|
105
114
|
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
const stop = (signal: NodeJS.Signals = 'SIGTERM') => {
|
|
116
|
+
if (stopping || settled) return;
|
|
117
|
+
|
|
118
|
+
stopping = Promise.resolve().then(() => terminateProcess(child, signal));
|
|
119
|
+
if (process.platform === 'win32') {
|
|
120
|
+
void finish(null, signal);
|
|
121
|
+
} else {
|
|
122
|
+
stopping.catch((cause) => {
|
|
123
|
+
void finish(null, null, cause);
|
|
124
|
+
});
|
|
125
|
+
escalation = setTimeout(
|
|
126
|
+
() => {
|
|
127
|
+
stopping = terminateProcess(child, 'SIGKILL');
|
|
128
|
+
stopping.catch((cause) => {
|
|
129
|
+
void finish(null, null, cause);
|
|
130
|
+
});
|
|
131
|
+
},
|
|
111
132
|
options.stopGraceMs || DefaultStopGraceMs
|
|
112
133
|
);
|
|
113
|
-
|
|
134
|
+
escalation.unref?.();
|
|
114
135
|
}
|
|
115
136
|
};
|
|
116
137
|
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
process.once('SIGINT',
|
|
120
|
-
process.once('SIGTERM',
|
|
138
|
+
const interrupt = () => stop('SIGINT');
|
|
139
|
+
const terminate = () => stop('SIGTERM');
|
|
140
|
+
process.once('SIGINT', interrupt);
|
|
141
|
+
process.once('SIGTERM', terminate);
|
|
121
142
|
|
|
122
143
|
const timeout = options.timeoutMs
|
|
123
144
|
? setTimeout(() => {
|
|
124
145
|
timedOut = true;
|
|
125
|
-
|
|
146
|
+
stop('SIGTERM');
|
|
126
147
|
}, options.timeoutMs)
|
|
127
148
|
: null;
|
|
128
149
|
timeout?.unref?.();
|
|
129
150
|
|
|
130
151
|
const cleanup = () => {
|
|
131
152
|
if (timeout) clearTimeout(timeout);
|
|
132
|
-
if (
|
|
133
|
-
process.removeListener('SIGINT',
|
|
134
|
-
process.removeListener('SIGTERM',
|
|
153
|
+
if (escalation) clearTimeout(escalation);
|
|
154
|
+
process.removeListener('SIGINT', interrupt);
|
|
155
|
+
process.removeListener('SIGTERM', terminate);
|
|
135
156
|
options.signal?.removeEventListener('abort', abort);
|
|
136
157
|
};
|
|
137
158
|
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
159
|
+
const finish = async (code, signal, cause?) => {
|
|
160
|
+
try {
|
|
161
|
+
await stopping;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
cause = error;
|
|
164
|
+
}
|
|
141
165
|
|
|
142
|
-
child.once('error', (cause) => {
|
|
143
166
|
if (settled) return;
|
|
144
167
|
settled = true;
|
|
145
168
|
cleanup();
|
|
146
169
|
|
|
147
|
-
reject(new HarmonyCliError('ERR_HARMONY_PROCESS_FAILED', `Cannot
|
|
170
|
+
if (cause) reject(new HarmonyCliError('ERR_HARMONY_PROCESS_FAILED', `Cannot run ${command}: ${cause.message}`, {
|
|
148
171
|
cause,
|
|
149
172
|
operation: options.operation || 'spawn',
|
|
150
173
|
}));
|
|
151
|
-
|
|
152
|
-
// `close` runs after stdout/stderr have closed, so captured diagnostics are
|
|
153
|
-
// complete. `exit` can fire while pipe data is still pending.
|
|
154
|
-
child.once('close', (code, signal) => {
|
|
155
|
-
if (settled) return;
|
|
156
|
-
settled = true;
|
|
157
|
-
cleanup();
|
|
158
|
-
|
|
159
|
-
resolve({
|
|
174
|
+
else resolve({
|
|
160
175
|
code: code === null ? 1 : code,
|
|
161
176
|
signal,
|
|
162
177
|
stderr: stderr.toString(),
|
|
163
178
|
stdout: stdout.toString(),
|
|
164
179
|
timedOut,
|
|
165
180
|
});
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
child.once('error', (cause) => {
|
|
184
|
+
void finish(null, null, cause);
|
|
166
185
|
});
|
|
186
|
+
child.once('close', (code, signal) => {
|
|
187
|
+
void finish(code, signal);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const abort = () => stop('SIGTERM');
|
|
191
|
+
options.signal?.addEventListener('abort', abort, { once: true });
|
|
192
|
+
if (options.signal?.aborted) abort();
|
|
167
193
|
});
|
|
168
194
|
}
|
|
169
195
|
|
|
170
196
|
function startManagedProcess(command: string, args: string[], options: ProcessOptions = {}) {
|
|
171
|
-
|
|
197
|
+
if (options.signal?.aborted) {
|
|
198
|
+
throw new HarmonyCliError('ERR_HARMONY_PROCESS_FAILED', `Cannot launch ${command}: operation aborted.`, {
|
|
199
|
+
cause: options.signal.reason,
|
|
200
|
+
operation: options.operation || 'spawn',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const limit = options.outputLimit || DefaultOutputLimit;
|
|
172
205
|
const piped = options.stdio !== 'inherit';
|
|
173
206
|
const child = spawn(command, args, {
|
|
174
207
|
cwd: options.cwd,
|
|
@@ -178,11 +211,14 @@ function startManagedProcess(command: string, args: string[], options: ProcessOp
|
|
|
178
211
|
windowsHide: true,
|
|
179
212
|
});
|
|
180
213
|
|
|
181
|
-
const stdout = new BoundedCapture(
|
|
182
|
-
const stderr = new BoundedCapture(
|
|
183
|
-
let
|
|
214
|
+
const stdout = new BoundedCapture(limit);
|
|
215
|
+
const stderr = new BoundedCapture(limit);
|
|
216
|
+
let failure: HarmonyCliError | null = null;
|
|
184
217
|
let closed = false;
|
|
185
|
-
let
|
|
218
|
+
let stopped = false;
|
|
219
|
+
let stopping: Promise<void> | undefined;
|
|
220
|
+
let stopPromise: Promise<ProcessResult | undefined> | undefined;
|
|
221
|
+
let finish: (code: number | null, signal: NodeJS.Signals | null) => Promise<void>;
|
|
186
222
|
|
|
187
223
|
if (piped) {
|
|
188
224
|
child.stdout.on('data', (chunk) => {
|
|
@@ -195,34 +231,43 @@ function startManagedProcess(command: string, args: string[], options: ProcessOp
|
|
|
195
231
|
});
|
|
196
232
|
}
|
|
197
233
|
|
|
198
|
-
const
|
|
199
|
-
void stop('SIGINT');
|
|
234
|
+
const interrupt = () => {
|
|
235
|
+
void stop('SIGINT').catch(() => {});
|
|
200
236
|
};
|
|
201
|
-
const
|
|
202
|
-
void stop('SIGTERM');
|
|
237
|
+
const terminate = () => {
|
|
238
|
+
void stop('SIGTERM').catch(() => {});
|
|
203
239
|
};
|
|
204
240
|
const abort = () => {
|
|
205
|
-
void stop('SIGTERM');
|
|
241
|
+
void stop('SIGTERM').catch(() => {});
|
|
206
242
|
};
|
|
207
243
|
|
|
208
244
|
const cleanup = () => {
|
|
209
|
-
process.removeListener('SIGINT',
|
|
210
|
-
process.removeListener('SIGTERM',
|
|
245
|
+
process.removeListener('SIGINT', interrupt);
|
|
246
|
+
process.removeListener('SIGTERM', terminate);
|
|
211
247
|
options.signal?.removeEventListener('abort', abort);
|
|
212
248
|
};
|
|
213
249
|
|
|
214
250
|
const completion = new Promise<ProcessResult>((resolve, reject) => {
|
|
215
251
|
child.once('error', (cause) => {
|
|
216
|
-
|
|
252
|
+
failure = new HarmonyCliError('ERR_HARMONY_PROCESS_FAILED', `Cannot launch ${command}: ${cause.message}`, {
|
|
217
253
|
cause,
|
|
218
254
|
operation: options.operation || 'spawn',
|
|
219
255
|
});
|
|
220
256
|
});
|
|
221
|
-
|
|
257
|
+
finish = async (code, signal) => {
|
|
258
|
+
try {
|
|
259
|
+
await stopping;
|
|
260
|
+
} catch (cause) {
|
|
261
|
+
failure = new HarmonyCliError('ERR_HARMONY_PROCESS_FAILED', `Cannot stop ${command}: ${cause.message}`, {
|
|
262
|
+
cause, operation: options.operation || 'spawn',
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (closed) return;
|
|
222
267
|
closed = true;
|
|
223
268
|
cleanup();
|
|
224
269
|
|
|
225
|
-
if (
|
|
270
|
+
if (failure) reject(failure);
|
|
226
271
|
else resolve({
|
|
227
272
|
code: code === null ? 1 : code,
|
|
228
273
|
signal,
|
|
@@ -230,6 +275,10 @@ function startManagedProcess(command: string, args: string[], options: ProcessOp
|
|
|
230
275
|
stdout: stdout.toString(),
|
|
231
276
|
timedOut: false,
|
|
232
277
|
});
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
child.once('close', (code, signal) => {
|
|
281
|
+
void finish(code, signal);
|
|
233
282
|
});
|
|
234
283
|
});
|
|
235
284
|
// A readiness probe may be the first consumer. Keep early spawn failures from
|
|
@@ -237,28 +286,53 @@ function startManagedProcess(command: string, args: string[], options: ProcessOp
|
|
|
237
286
|
completion.catch(() => {});
|
|
238
287
|
|
|
239
288
|
async function stop(signal: NodeJS.Signals = 'SIGTERM', graceMs = DefaultStopGraceMs) {
|
|
289
|
+
if (stopPromise) return stopPromise;
|
|
240
290
|
if (closed) return completion;
|
|
241
291
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
292
|
+
stopped = true;
|
|
293
|
+
stopping = Promise.resolve().then(() => terminateProcess(child, signal));
|
|
294
|
+
stopPromise = (async () => {
|
|
295
|
+
if (process.platform === 'win32') {
|
|
296
|
+
void finish(null, signal);
|
|
297
|
+
return completion;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
await stopping;
|
|
302
|
+
} catch {
|
|
303
|
+
await finish(null, signal);
|
|
304
|
+
return completion;
|
|
305
|
+
}
|
|
245
306
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
307
|
+
let timer: NodeJS.Timeout | undefined;
|
|
308
|
+
|
|
309
|
+
await Promise.race([
|
|
310
|
+
completion.catch(() => undefined),
|
|
311
|
+
new Promise((resolve) => {
|
|
312
|
+
timer = setTimeout(resolve, graceMs);
|
|
313
|
+
timer.unref?.();
|
|
314
|
+
}),
|
|
315
|
+
]);
|
|
316
|
+
|
|
317
|
+
if (timer) clearTimeout(timer);
|
|
318
|
+
if (!closed) {
|
|
319
|
+
stopping = terminateProcess(child, 'SIGKILL');
|
|
320
|
+
try {
|
|
321
|
+
await stopping;
|
|
322
|
+
} catch {
|
|
323
|
+
await finish(null, signal);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
253
326
|
|
|
254
|
-
|
|
255
|
-
|
|
327
|
+
return completion.catch(() => undefined);
|
|
328
|
+
})();
|
|
329
|
+
stopPromise.catch(() => {});
|
|
256
330
|
|
|
257
|
-
return
|
|
331
|
+
return stopPromise;
|
|
258
332
|
}
|
|
259
333
|
|
|
260
|
-
process.once('SIGINT',
|
|
261
|
-
process.once('SIGTERM',
|
|
334
|
+
process.once('SIGINT', interrupt);
|
|
335
|
+
process.once('SIGTERM', terminate);
|
|
262
336
|
if (options.signal?.aborted) abort();
|
|
263
337
|
else options.signal?.addEventListener('abort', abort, { once: true });
|
|
264
338
|
|
|
@@ -268,7 +342,7 @@ function startManagedProcess(command: string, args: string[], options: ProcessOp
|
|
|
268
342
|
getStderr: () => stderr.toString(),
|
|
269
343
|
getStdout: () => stdout.toString(),
|
|
270
344
|
stop,
|
|
271
|
-
wasStopped: () =>
|
|
345
|
+
wasStopped: () => stopped,
|
|
272
346
|
};
|
|
273
347
|
}
|
|
274
348
|
|
package/src/run/run.ts
CHANGED
|
@@ -118,7 +118,10 @@ async function runHarmonyUnlockedAsync(
|
|
|
118
118
|
};
|
|
119
119
|
const steps: Record<string, number> = {};
|
|
120
120
|
|
|
121
|
-
await ensureGeneratedProjectAsync(projectRoot,
|
|
121
|
+
await ensureGeneratedProjectAsync(projectRoot, {
|
|
122
|
+
...normalizedOptions,
|
|
123
|
+
skipGeneratedProjectCheck: true,
|
|
124
|
+
}, steps);
|
|
122
125
|
|
|
123
126
|
const plan = await timed(steps, 'buildPlan', () => resolveHarmonyBuildPlanAsync(
|
|
124
127
|
projectRoot,
|
package/src/tools.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { resolveHarmonyCommand } from '@expo-harmony/expo-modules-autolinking/tool-command';
|
|
3
4
|
|
|
4
5
|
import {
|
|
5
6
|
resolveHarmonyBuildPath,
|
|
@@ -141,43 +142,43 @@ function resolveHarmonyToolchain(): HarmonyToolchain {
|
|
|
141
142
|
|
|
142
143
|
let ohpm: HarmonyTool;
|
|
143
144
|
if (env.HARMONY_OHPM) {
|
|
144
|
-
ohpm = {
|
|
145
|
+
ohpm = { ...resolveHarmonyCommand('ohpm', [], env), source: 'override' };
|
|
145
146
|
} else {
|
|
146
|
-
const
|
|
147
|
+
const entry = layouts.map(layout => ({
|
|
148
|
+
node: devEcoNode(layout, platform, { ...env, HARMONY_NODE: env.HARMONY_OHPM_NODE || env.HARMONY_NODE }),
|
|
149
|
+
script: path.join(layout.toolsRoot, 'ohpm', 'bin', 'pm-cli.js'),
|
|
150
|
+
})).find(candidate => candidate.node && fs.existsSync(candidate.script));
|
|
151
|
+
const command = existingFile(layouts.map(layout => path.join(
|
|
147
152
|
layout.toolsRoot,
|
|
148
153
|
'ohpm',
|
|
149
154
|
'bin',
|
|
150
155
|
platform === 'win32' ? 'ohpm.bat' : 'ohpm'
|
|
151
156
|
)));
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
|
|
158
|
+
ohpm = entry
|
|
159
|
+
? { args: [entry.script], command: entry.node.command, source: 'deveco' }
|
|
160
|
+
: command
|
|
161
|
+
? { args: [], command, source: 'deveco' }
|
|
162
|
+
: { args: [], command: platform === 'win32' ? 'ohpm.bat' : 'ohpm', source: 'path' };
|
|
155
163
|
}
|
|
156
164
|
|
|
157
165
|
let hvigor: HarmonyTool;
|
|
158
166
|
if (env.HARMONY_HVIGORW) {
|
|
159
|
-
|
|
160
|
-
hvigor = {
|
|
161
|
-
args: [env.HARMONY_HVIGORW],
|
|
162
|
-
command: env.HARMONY_NODE || process.execPath,
|
|
163
|
-
source: 'override',
|
|
164
|
-
};
|
|
165
|
-
} else {
|
|
166
|
-
hvigor = { args: [], command: env.HARMONY_HVIGORW, source: 'override' };
|
|
167
|
-
}
|
|
167
|
+
hvigor = { ...resolveHarmonyCommand('hvigorw', [], env), source: 'override' };
|
|
168
168
|
} else {
|
|
169
|
-
const
|
|
170
|
-
layout,
|
|
171
|
-
node: devEcoNode(layout, platform, env),
|
|
169
|
+
const entry = layouts.map(layout => ({
|
|
170
|
+
node: devEcoNode(layout, platform, { ...env, HARMONY_NODE: env.HARMONY_HVIGOR_NODE || env.HARMONY_NODE }),
|
|
172
171
|
script: path.join(layout.toolsRoot, 'hvigor', 'bin', 'hvigorw.js'),
|
|
173
172
|
})).find(candidate => candidate.node && fs.existsSync(candidate.script));
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
|
|
174
|
+
hvigor = entry
|
|
175
|
+
? { args: [entry.script], command: entry.node.command, source: 'deveco' }
|
|
176
176
|
: { args: [], command: platform === 'win32' ? 'hvigorw.bat' : 'hvigorw', source: 'path' };
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
const toolsRoot = layouts.find(layout => (
|
|
180
180
|
hvigor.args[0]?.startsWith(`${layout.toolsRoot}${path.sep}`)
|
|
181
|
+
|| ohpm.args[0]?.startsWith(`${layout.toolsRoot}${path.sep}`)
|
|
181
182
|
|| ohpm.command.startsWith(`${layout.toolsRoot}${path.sep}`)
|
|
182
183
|
))?.toolsRoot || null;
|
|
183
184
|
|
|
@@ -201,10 +202,14 @@ function resolveHarmonyToolchain(): HarmonyToolchain {
|
|
|
201
202
|
function createHarmonyToolchainEnv(toolchain = resolveHarmonyToolchain()): NodeJS.ProcessEnv {
|
|
202
203
|
return {
|
|
203
204
|
...process.env,
|
|
204
|
-
HARMONY_OHPM: toolchain.ohpm.command,
|
|
205
|
-
|
|
205
|
+
HARMONY_OHPM: toolchain.ohpm.args[0] || toolchain.ohpm.command,
|
|
206
|
+
HARMONY_OHPM_NODE: toolchain.ohpm.args.length > 0 ? toolchain.ohpm.command : undefined,
|
|
207
|
+
// Script-based tools must retain both their script and Node executable.
|
|
206
208
|
HARMONY_HVIGORW: toolchain.hvigor.args[0] || toolchain.hvigor.command,
|
|
207
|
-
|
|
209
|
+
HARMONY_HVIGOR_NODE: toolchain.hvigor.args.length > 0 ? toolchain.hvigor.command : undefined,
|
|
210
|
+
...(toolchain.hvigor.args.length > 0
|
|
211
|
+
? { HARMONY_NODE: toolchain.hvigor.command }
|
|
212
|
+
: toolchain.ohpm.args.length > 0 ? { HARMONY_NODE: toolchain.ohpm.command } : {}),
|
|
208
213
|
...(toolchain.sdkHome && !process.env.DEVECO_SDK_HOME
|
|
209
214
|
? { DEVECO_SDK_HOME: toolchain.sdkHome }
|
|
210
215
|
: {}),
|