@oniroproject/core 0.6.2 → 0.8.0
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 +267 -7
- package/dist/index.cjs +2088 -416
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1074 -24
- package/dist/index.d.ts +1074 -24
- package/dist/index.js +2013 -417
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -36,72 +36,148 @@ __export(index_exports, {
|
|
|
36
36
|
CancelledError: () => CancelledError,
|
|
37
37
|
ChecksumMismatchError: () => ChecksumMismatchError,
|
|
38
38
|
CmdToolsNotInstalledError: () => CmdToolsNotInstalledError,
|
|
39
|
+
CommandFailedError: () => CommandFailedError,
|
|
40
|
+
INSTALL_TMP_DIRNAME: () => INSTALL_TMP_DIRNAME,
|
|
41
|
+
InsufficientSpaceError: () => InsufficientSpaceError,
|
|
39
42
|
OHOS_URL_BASE: () => OHOS_URL_BASE,
|
|
40
43
|
OniroError: () => OniroError,
|
|
41
44
|
SdkNotInstalledError: () => SdkNotInstalledError,
|
|
45
|
+
TMP_DIR_HINT: () => TMP_DIR_HINT,
|
|
42
46
|
UnsupportedPlatformError: () => UnsupportedPlatformError,
|
|
43
47
|
VERSION: () => VERSION,
|
|
48
|
+
applyChanges: () => applyChanges,
|
|
44
49
|
attemptHdcConnection: () => attemptHdcConnection,
|
|
50
|
+
buildGestureCommands: () => buildGestureCommands,
|
|
51
|
+
buildGestureUitestCommand: () => buildGestureUitestCommand,
|
|
52
|
+
buildHap: () => buildHap,
|
|
53
|
+
buildHvigorwArgs: () => buildHvigorwArgs,
|
|
54
|
+
buildInputCommand: () => buildInputCommand,
|
|
55
|
+
buildRawTouchCommand: () => buildRawTouchCommand,
|
|
56
|
+
captureBurst: () => captureBurst,
|
|
45
57
|
consoleLogger: () => consoleLogger,
|
|
58
|
+
createInstallTempDir: () => createInstallTempDir,
|
|
46
59
|
createMaterial: () => createMaterial,
|
|
47
60
|
createScaffold: () => createScaffold,
|
|
61
|
+
createTempWorkDir: () => createTempWorkDir,
|
|
62
|
+
decideInstallMethod: () => decideInstallMethod,
|
|
63
|
+
decideReboot: () => decideReboot,
|
|
48
64
|
decryptPwd: () => decryptPwd,
|
|
65
|
+
dedupEntries: () => dedupEntries,
|
|
49
66
|
defaultContext: () => defaultContext,
|
|
50
67
|
defaultPaths: () => defaultPaths,
|
|
68
|
+
detectAaStartFailure: () => detectAaStartFailure,
|
|
51
69
|
detectProjectSdkVersion: () => detectProjectSdkVersion,
|
|
52
70
|
detectSigningConfigNames: () => detectSigningConfigNames,
|
|
71
|
+
diffEntryNames: () => diffEntryNames,
|
|
72
|
+
diffHapAssets: () => diffHapAssets,
|
|
73
|
+
discoverHaps: () => discoverHaps,
|
|
53
74
|
downloadAndInstallSdk: () => downloadAndInstallSdk,
|
|
54
75
|
downloadFile: () => downloadFile,
|
|
76
|
+
dumpLayout: () => dumpLayout,
|
|
77
|
+
dumpLog: () => dumpLog,
|
|
78
|
+
dumpRenderService: () => dumpRenderService,
|
|
79
|
+
dumpScreen: () => dumpScreen,
|
|
80
|
+
dumpWindow: () => dumpWindow,
|
|
55
81
|
encryptPwd: () => encryptPwd,
|
|
82
|
+
ensureFreeSpace: () => ensureFreeSpace,
|
|
83
|
+
ensureOhModules: () => ensureOhModules,
|
|
84
|
+
ensureOk: () => ensureOk,
|
|
85
|
+
ensureRemoteOk: () => ensureRemoteOk,
|
|
56
86
|
extractTarball: () => extractTarball,
|
|
57
87
|
extractZipWithProgress: () => extractZipWithProgress,
|
|
58
88
|
findAppProcessId: () => findAppProcessId,
|
|
59
89
|
findCmdToolsSourceDir: () => findCmdToolsSourceDir,
|
|
90
|
+
findRunningProcess: () => findRunningProcess,
|
|
91
|
+
forceStop: () => forceStop,
|
|
92
|
+
formatBytes: () => formatBytes,
|
|
60
93
|
generateSigningConfigs: () => generateSigningConfigs,
|
|
61
94
|
getBundleName: () => getBundleName,
|
|
62
95
|
getCmdToolsBin: () => getCmdToolsBin,
|
|
63
96
|
getCmdToolsDownloadUrl: () => getCmdToolsDownloadUrl,
|
|
64
97
|
getCmdToolsPath: () => getCmdToolsPath,
|
|
65
98
|
getCmdToolsStatus: () => getCmdToolsStatus,
|
|
99
|
+
getDeviceInfo: () => getDeviceInfo,
|
|
100
|
+
getDisplaySize: () => getDisplaySize,
|
|
66
101
|
getEmulatorDir: () => getEmulatorDir,
|
|
102
|
+
getFreeSpaceBytes: () => getFreeSpaceBytes,
|
|
67
103
|
getHdcPath: () => getHdcPath,
|
|
68
104
|
getHvigorwPath: () => getHvigorwPath,
|
|
69
105
|
getInstalledSdks: () => getInstalledSdks,
|
|
70
106
|
getKey: () => getKey,
|
|
71
107
|
getMainAbility: () => getMainAbility,
|
|
72
108
|
getOhosBaseSdkHome: () => getOhosBaseSdkHome,
|
|
109
|
+
getOhpmPath: () => getOhpmPath,
|
|
73
110
|
getOsFolder: () => getOsFolder,
|
|
74
111
|
getSdkFilename: () => getSdkFilename,
|
|
75
112
|
getSdkRootDir: () => getSdkRootDir,
|
|
76
113
|
getSupportedSdksForUi: () => getSupportedSdksForUi,
|
|
114
|
+
hdcExec: () => hdcExec,
|
|
77
115
|
installApp: () => installApp,
|
|
78
116
|
installCmdTools: () => installCmdTools,
|
|
79
117
|
installEmulator: () => installEmulator,
|
|
80
118
|
isCmdToolsInstalled: () => isCmdToolsInstalled,
|
|
81
119
|
isEmulatorInstalled: () => isEmulatorInstalled,
|
|
120
|
+
isOutOfSpaceError: () => isOutOfSpaceError,
|
|
121
|
+
isRamBackedPath: () => isRamBackedPath,
|
|
82
122
|
isValidBundleName: () => isValidBundleName,
|
|
83
123
|
isValidProjectName: () => isValidProjectName,
|
|
84
124
|
launchApp: () => launchApp,
|
|
125
|
+
listAbilities: () => listAbilities,
|
|
126
|
+
listDevices: () => listDevices,
|
|
127
|
+
listModules: () => listModules,
|
|
85
128
|
listRunningProcesses: () => listRunningProcesses,
|
|
86
129
|
listTemplates: () => listTemplates,
|
|
87
130
|
nonInteractivePrompter: () => nonInteractivePrompter,
|
|
88
131
|
noopLogger: () => noopLogger,
|
|
89
132
|
noopProgress: () => noopProgress,
|
|
133
|
+
paramGet: () => paramGet,
|
|
134
|
+
paramSet: () => paramSet,
|
|
135
|
+
parseBounds: () => parseBounds,
|
|
136
|
+
parseCodelinterFindings: () => parseCodelinterFindings,
|
|
137
|
+
parseDeviceList: () => parseDeviceList,
|
|
90
138
|
parseHilogLine: () => parseHilogLine,
|
|
139
|
+
parseMainAbilityFromBmDump: () => parseMainAbilityFromBmDump,
|
|
140
|
+
parseRemoteExit: () => parseRemoteExit,
|
|
91
141
|
pickSigningKind: () => pickSigningKind,
|
|
142
|
+
prepareSigning: () => prepareSigning,
|
|
143
|
+
pruneLayout: () => pruneLayout,
|
|
92
144
|
readJson5File: () => readJson5File,
|
|
93
145
|
readJsonFile: () => readJsonFile,
|
|
146
|
+
reboot: () => reboot,
|
|
147
|
+
recvFile: () => recvFile,
|
|
94
148
|
removeCmdTools: () => removeCmdTools,
|
|
95
149
|
removeEmulator: () => removeEmulator,
|
|
96
150
|
removeSdk: () => removeSdk,
|
|
151
|
+
removeTempWorkDir: () => removeTempWorkDir,
|
|
152
|
+
resolveTmpRoot: () => resolveTmpRoot,
|
|
153
|
+
runCodelinter: () => runCodelinter,
|
|
97
154
|
runHvigorw: () => runHvigorw,
|
|
155
|
+
runOhpm: () => runOhpm,
|
|
156
|
+
runProcess: () => runProcess,
|
|
157
|
+
scopedLogger: () => scopedLogger,
|
|
158
|
+
selectDevice: () => selectDevice,
|
|
159
|
+
sendFile: () => sendFile,
|
|
160
|
+
sendGesture: () => sendGesture,
|
|
161
|
+
sendInput: () => sendInput,
|
|
162
|
+
sendRawTouch: () => sendRawTouch,
|
|
98
163
|
setHilogLevel: () => setHilogLevel,
|
|
164
|
+
shell: () => shell,
|
|
165
|
+
shellChecked: () => shellChecked,
|
|
99
166
|
startEmulator: () => startEmulator,
|
|
100
167
|
staticConfig: () => staticConfig,
|
|
101
168
|
stopEmulator: () => stopEmulator,
|
|
102
169
|
streamHilog: () => streamHilog,
|
|
170
|
+
takeScreenshot: () => takeScreenshot,
|
|
171
|
+
toSpaceError: () => toSpaceError,
|
|
172
|
+
uninstallApp: () => uninstallApp,
|
|
103
173
|
validateTemplateLayout: () => validateTemplateLayout,
|
|
104
174
|
verifySha256: () => verifySha256,
|
|
175
|
+
waitForBoot: () => waitForBoot,
|
|
176
|
+
waitForBundle: () => waitForBundle,
|
|
177
|
+
waitForCondition: () => waitForCondition,
|
|
178
|
+
waitForLog: () => waitForLog,
|
|
179
|
+
watchLog: () => watchLog,
|
|
180
|
+
waypointsToRawEvents: () => waypointsToRawEvents,
|
|
105
181
|
writeJson5File: () => writeJson5File,
|
|
106
182
|
writeJsonFile: () => writeJsonFile
|
|
107
183
|
});
|
|
@@ -124,6 +200,15 @@ var consoleLogger = {
|
|
|
124
200
|
warn: (m) => console.warn(m),
|
|
125
201
|
error: (m) => console.error(m)
|
|
126
202
|
};
|
|
203
|
+
function scopedLogger(logger, scope) {
|
|
204
|
+
const prefix = `[${scope}] `;
|
|
205
|
+
return {
|
|
206
|
+
debug: (m) => logger.debug(prefix + m),
|
|
207
|
+
info: (m) => logger.info(prefix + m),
|
|
208
|
+
warn: (m) => logger.warn(prefix + m),
|
|
209
|
+
error: (m) => logger.error(prefix + m)
|
|
210
|
+
};
|
|
211
|
+
}
|
|
127
212
|
|
|
128
213
|
// src/ports/progress.ts
|
|
129
214
|
var noopProgress = {
|
|
@@ -205,9 +290,9 @@ var CmdToolsNotInstalledError = class extends OniroError {
|
|
|
205
290
|
expectedPath;
|
|
206
291
|
};
|
|
207
292
|
var UnsupportedPlatformError = class extends OniroError {
|
|
208
|
-
constructor(
|
|
209
|
-
super(`Unsupported platform: ${
|
|
210
|
-
this.platform =
|
|
293
|
+
constructor(platform7) {
|
|
294
|
+
super(`Unsupported platform: ${platform7}.`);
|
|
295
|
+
this.platform = platform7;
|
|
211
296
|
this.name = "UnsupportedPlatformError";
|
|
212
297
|
}
|
|
213
298
|
platform;
|
|
@@ -222,12 +307,33 @@ var ChecksumMismatchError = class extends OniroError {
|
|
|
222
307
|
expected;
|
|
223
308
|
actual;
|
|
224
309
|
};
|
|
310
|
+
var InsufficientSpaceError = class extends OniroError {
|
|
311
|
+
constructor(message, dir, cause) {
|
|
312
|
+
super(message, cause);
|
|
313
|
+
this.dir = dir;
|
|
314
|
+
this.name = "InsufficientSpaceError";
|
|
315
|
+
}
|
|
316
|
+
dir;
|
|
317
|
+
};
|
|
225
318
|
var CancelledError = class extends OniroError {
|
|
226
319
|
constructor(message = "Operation cancelled.") {
|
|
227
320
|
super(message);
|
|
228
321
|
this.name = "CancelledError";
|
|
229
322
|
}
|
|
230
323
|
};
|
|
324
|
+
var CommandFailedError = class extends OniroError {
|
|
325
|
+
constructor(command, exitCode, stderr) {
|
|
326
|
+
super(`Command failed (exit ${exitCode}): ${command}${stderr ? `
|
|
327
|
+
${stderr}` : ""}`);
|
|
328
|
+
this.command = command;
|
|
329
|
+
this.exitCode = exitCode;
|
|
330
|
+
this.stderr = stderr;
|
|
331
|
+
this.name = "CommandFailedError";
|
|
332
|
+
}
|
|
333
|
+
command;
|
|
334
|
+
exitCode;
|
|
335
|
+
stderr;
|
|
336
|
+
};
|
|
231
337
|
|
|
232
338
|
// src/ports/context.ts
|
|
233
339
|
function defaultContext(overrides = {}) {
|
|
@@ -239,45 +345,45 @@ function defaultContext(overrides = {}) {
|
|
|
239
345
|
|
|
240
346
|
// src/sdk/constants.ts
|
|
241
347
|
var ALL_SDKS = [
|
|
242
|
-
{ version: "4.0", api: "10" },
|
|
243
|
-
{ version: "4.1", api: "11" },
|
|
244
|
-
{ version: "5.0.0", api: "12" },
|
|
245
|
-
{ version: "5.0.1", api: "13" },
|
|
246
|
-
{ version: "5.0.2", api: "14" },
|
|
247
|
-
{ version: "5.0.3", api: "15" },
|
|
248
|
-
{ version: "5.1.0", api: "18" },
|
|
249
|
-
{ version: "
|
|
250
|
-
{ version: "6.
|
|
348
|
+
{ version: "4.0", api: "10", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
349
|
+
{ version: "4.1", api: "11", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
350
|
+
{ version: "5.0.0", api: "12", tarballStrip: { linuxWindows: 0, darwin: 3 } },
|
|
351
|
+
{ version: "5.0.1", api: "13", tarballStrip: { linuxWindows: 0, darwin: 3 } },
|
|
352
|
+
{ version: "5.0.2", api: "14", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
353
|
+
{ version: "5.0.3", api: "15", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
354
|
+
{ version: "5.1.0", api: "18", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
355
|
+
{ version: "5.1.1", api: "19", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
356
|
+
{ version: "6.0", api: "20", tarballStrip: { linuxWindows: 0, darwin: 3 } },
|
|
357
|
+
{ version: "6.1", api: "23", tarballStrip: { linuxWindows: 0, darwin: 3 } }
|
|
251
358
|
];
|
|
252
359
|
var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
|
|
253
360
|
|
|
254
361
|
// src/sdk/platform.ts
|
|
255
362
|
var os2 = __toESM(require("os"), 1);
|
|
256
363
|
function getOsFolder() {
|
|
257
|
-
const
|
|
258
|
-
if (
|
|
259
|
-
if (
|
|
260
|
-
if (
|
|
261
|
-
throw new UnsupportedPlatformError(
|
|
364
|
+
const platform7 = os2.platform();
|
|
365
|
+
if (platform7 === "linux") return "linux";
|
|
366
|
+
if (platform7 === "darwin") return "darwin";
|
|
367
|
+
if (platform7 === "win32") return "windows";
|
|
368
|
+
throw new UnsupportedPlatformError(platform7);
|
|
262
369
|
}
|
|
263
370
|
function getSdkFilename(version) {
|
|
264
|
-
const
|
|
371
|
+
const platform7 = os2.platform();
|
|
265
372
|
const v = version ?? ALL_SDKS[ALL_SDKS.length - 1].version;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip };
|
|
373
|
+
const strips = ALL_SDKS.find((r) => r.version === v)?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
|
|
374
|
+
if (platform7 === "linux") {
|
|
375
|
+
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip: strips.linuxWindows };
|
|
269
376
|
}
|
|
270
|
-
if (
|
|
377
|
+
if (platform7 === "darwin") {
|
|
271
378
|
if (os2.arch() === "arm64") {
|
|
272
|
-
return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip:
|
|
379
|
+
return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: strips.darwin };
|
|
273
380
|
}
|
|
274
|
-
return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip:
|
|
381
|
+
return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: strips.darwin };
|
|
275
382
|
}
|
|
276
|
-
if (
|
|
277
|
-
|
|
278
|
-
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip };
|
|
383
|
+
if (platform7 === "win32") {
|
|
384
|
+
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip: strips.linuxWindows };
|
|
279
385
|
}
|
|
280
|
-
throw new UnsupportedPlatformError(
|
|
386
|
+
throw new UnsupportedPlatformError(platform7);
|
|
281
387
|
}
|
|
282
388
|
|
|
283
389
|
// src/sdk/paths.ts
|
|
@@ -302,19 +408,24 @@ function pickExisting(candidates) {
|
|
|
302
408
|
}
|
|
303
409
|
return candidates[0];
|
|
304
410
|
}
|
|
305
|
-
function getCmdToolsBin(config) {
|
|
411
|
+
function getCmdToolsBin(config, name = "ohpm") {
|
|
306
412
|
const binDir = path2.join(getCmdToolsPath(config), "bin");
|
|
307
413
|
if (os3.platform() === "win32") {
|
|
308
414
|
return pickExisting([
|
|
309
|
-
path2.join(binDir,
|
|
310
|
-
path2.join(binDir,
|
|
311
|
-
path2.join(binDir,
|
|
312
|
-
path2.join(binDir,
|
|
415
|
+
path2.join(binDir, `${name}.exe`),
|
|
416
|
+
path2.join(binDir, `${name}.cmd`),
|
|
417
|
+
path2.join(binDir, `${name}.bat`),
|
|
418
|
+
path2.join(binDir, name)
|
|
313
419
|
]);
|
|
314
420
|
}
|
|
315
|
-
return path2.join(binDir,
|
|
421
|
+
return path2.join(binDir, name);
|
|
422
|
+
}
|
|
423
|
+
function getOhpmPath(config) {
|
|
424
|
+
return getCmdToolsBin(config, "ohpm");
|
|
316
425
|
}
|
|
317
426
|
function getHdcPath(config) {
|
|
427
|
+
const override = config.get("hdcPath", "");
|
|
428
|
+
if (override) return override;
|
|
318
429
|
const base = path2.join(getCmdToolsPath(config), "sdk", "default", "openharmony", "toolchains");
|
|
319
430
|
if (os3.platform() === "win32") {
|
|
320
431
|
return pickExisting([
|
|
@@ -327,15 +438,20 @@ function getHdcPath(config) {
|
|
|
327
438
|
return path2.join(base, "hdc");
|
|
328
439
|
}
|
|
329
440
|
function getHvigorwPath(config, projectDir) {
|
|
330
|
-
const
|
|
331
|
-
return pickExisting([
|
|
441
|
+
const local = pickExisting([
|
|
332
442
|
path2.join(projectDir, "hvigorw"),
|
|
333
443
|
path2.join(projectDir, "hvigorw.bat"),
|
|
334
|
-
path2.join(projectDir, "hvigorw.cmd")
|
|
335
|
-
path2.join(cmdToolsBin, "hvigorw"),
|
|
336
|
-
path2.join(cmdToolsBin, "hvigorw.bat"),
|
|
337
|
-
path2.join(cmdToolsBin, "hvigorw.cmd")
|
|
444
|
+
path2.join(projectDir, "hvigorw.cmd")
|
|
338
445
|
]);
|
|
446
|
+
if (fs.existsSync(local) && isProjectLocalHvigorwWorking(projectDir)) {
|
|
447
|
+
return local;
|
|
448
|
+
}
|
|
449
|
+
return getCmdToolsBin(config, "hvigorw");
|
|
450
|
+
}
|
|
451
|
+
function isProjectLocalHvigorwWorking(projectDir) {
|
|
452
|
+
const wrapper = path2.join(projectDir, "hvigor", "hvigor-wrapper.js");
|
|
453
|
+
const nodeModules = path2.join(projectDir, "hvigor", "node_modules");
|
|
454
|
+
return fs.existsSync(wrapper) && fs.existsSync(nodeModules);
|
|
339
455
|
}
|
|
340
456
|
|
|
341
457
|
// src/sdk/detectProjectSdk.ts
|
|
@@ -360,8 +476,164 @@ function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
|
|
|
360
476
|
}
|
|
361
477
|
}
|
|
362
478
|
|
|
363
|
-
// src/sdk/
|
|
479
|
+
// src/sdk/tmp.ts
|
|
364
480
|
var fs3 = __toESM(require("fs"), 1);
|
|
481
|
+
var os4 = __toESM(require("os"), 1);
|
|
482
|
+
var path4 = __toESM(require("path"), 1);
|
|
483
|
+
var INSTALL_TMP_DIRNAME = ".oniro-tmp";
|
|
484
|
+
var TMPFS_MAGIC = 16914836;
|
|
485
|
+
var RAMFS_MAGIC = 2240043254;
|
|
486
|
+
var HINT_EXAMPLE = os4.platform() === "win32" ? "D:\\oniro-tmp" : "~/.cache/oniro-tmp";
|
|
487
|
+
var TMP_DIR_HINT = `Point the scratch directory at a filesystem with more room using --tmp-dir <path> (or the ONIRO_TMP_DIR environment variable), e.g. --tmp-dir ${HINT_EXAMPLE}.`;
|
|
488
|
+
function resolveTmpRoot(config, override, installRoot) {
|
|
489
|
+
const explicit = (override ?? "").trim();
|
|
490
|
+
if (explicit) return path4.resolve(explicit);
|
|
491
|
+
const configured = config.get("tmpDir", "").trim();
|
|
492
|
+
if (configured) return path4.resolve(configured);
|
|
493
|
+
if (installRoot) return path4.join(path4.resolve(installRoot), INSTALL_TMP_DIRNAME);
|
|
494
|
+
return os4.tmpdir();
|
|
495
|
+
}
|
|
496
|
+
function createTempWorkDir(root, prefix) {
|
|
497
|
+
try {
|
|
498
|
+
fs3.mkdirSync(root, { recursive: true });
|
|
499
|
+
return fs3.mkdtempSync(path4.join(root, prefix));
|
|
500
|
+
} catch (err) {
|
|
501
|
+
if (isOutOfSpaceError(err)) throw toSpaceError(err, root, "a temporary directory");
|
|
502
|
+
throw new OniroError(
|
|
503
|
+
`Cannot create a temporary directory in '${root}': ${err instanceof Error ? err.message : String(err)}
|
|
504
|
+
${TMP_DIR_HINT}`,
|
|
505
|
+
err
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function removeTempWorkDir(dir, root) {
|
|
510
|
+
fs3.rmSync(dir, { recursive: true, force: true });
|
|
511
|
+
if (!root || path4.basename(root) !== INSTALL_TMP_DIRNAME) return;
|
|
512
|
+
try {
|
|
513
|
+
fs3.rmdirSync(root);
|
|
514
|
+
} catch {
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function createInstallTempDir(opts) {
|
|
518
|
+
const requested = (opts.override ?? "").trim() || opts.config.get("tmpDir", "").trim();
|
|
519
|
+
const root = resolveTmpRoot(opts.config, opts.override, opts.installRoot);
|
|
520
|
+
try {
|
|
521
|
+
return { dir: createTempWorkDir(root, opts.prefix), root };
|
|
522
|
+
} catch (err) {
|
|
523
|
+
const fallback = os4.tmpdir();
|
|
524
|
+
if (requested || isOutOfSpaceError(err) || path4.resolve(fallback) === path4.resolve(root)) throw err;
|
|
525
|
+
const reason = err instanceof Error ? err.message.split("\n")[0] : String(err);
|
|
526
|
+
(opts.logger ?? noopLogger).warn(`${reason} Falling back to '${fallback}'.`);
|
|
527
|
+
return { dir: createTempWorkDir(fallback, opts.prefix), root: fallback };
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function nearestExisting(dir) {
|
|
531
|
+
let current = path4.resolve(dir);
|
|
532
|
+
for (; ; ) {
|
|
533
|
+
if (fs3.existsSync(current)) return current;
|
|
534
|
+
const parent = path4.dirname(current);
|
|
535
|
+
if (parent === current) return null;
|
|
536
|
+
current = parent;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
function getFreeSpaceBytes(dir) {
|
|
540
|
+
const target = nearestExisting(dir);
|
|
541
|
+
if (!target) return null;
|
|
542
|
+
try {
|
|
543
|
+
const stats = fs3.statfsSync(target);
|
|
544
|
+
return Number(stats.bsize) * Number(stats.bavail);
|
|
545
|
+
} catch {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function isRamBackedPath(dir) {
|
|
550
|
+
const target = nearestExisting(dir);
|
|
551
|
+
if (!target) return false;
|
|
552
|
+
try {
|
|
553
|
+
const type = Number(fs3.statfsSync(target).type);
|
|
554
|
+
return type === TMPFS_MAGIC || type === RAMFS_MAGIC;
|
|
555
|
+
} catch {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function formatBytes(bytes) {
|
|
560
|
+
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
561
|
+
let value = Math.max(0, bytes);
|
|
562
|
+
let unit = 0;
|
|
563
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
564
|
+
value /= 1024;
|
|
565
|
+
unit++;
|
|
566
|
+
}
|
|
567
|
+
return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
|
|
568
|
+
}
|
|
569
|
+
function isOutOfSpaceError(err) {
|
|
570
|
+
if (err instanceof InsufficientSpaceError) return true;
|
|
571
|
+
const seen = /* @__PURE__ */ new Set();
|
|
572
|
+
let current = err;
|
|
573
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
574
|
+
seen.add(current);
|
|
575
|
+
const e = current;
|
|
576
|
+
if (e.code === "ENOSPC" || e.code === "EDQUOT") return true;
|
|
577
|
+
if (typeof e.message === "string" && /ENOSPC|EDQUOT|no space left on device/i.test(e.message)) return true;
|
|
578
|
+
current = e.cause;
|
|
579
|
+
}
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
function spaceMessage(dir, what, opts) {
|
|
583
|
+
const need = typeof opts.requiredBytes === "number" ? `needs about ${formatBytes(opts.requiredBytes)}` : null;
|
|
584
|
+
const free = typeof opts.freeBytes === "number" ? `${formatBytes(opts.freeBytes)} available` : null;
|
|
585
|
+
const amounts = [need, free].filter(Boolean).join(", ");
|
|
586
|
+
const lines = [`Not enough free space in '${dir}' for ${what}${amounts ? ` (${amounts})` : ""}.`];
|
|
587
|
+
if (isRamBackedPath(dir)) {
|
|
588
|
+
lines.push(
|
|
589
|
+
`'${dir}' is on a RAM-backed filesystem (tmpfs), so its capacity is limited by free memory, not by free disk.`
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
if (opts.tmpDirHint) lines.push(TMP_DIR_HINT);
|
|
593
|
+
return lines.join("\n");
|
|
594
|
+
}
|
|
595
|
+
function ensureFreeSpace(dir, requiredBytes, what) {
|
|
596
|
+
if (!Number.isFinite(requiredBytes) || requiredBytes <= 0) return;
|
|
597
|
+
const freeBytes = getFreeSpaceBytes(dir);
|
|
598
|
+
if (freeBytes === null || freeBytes >= requiredBytes) return;
|
|
599
|
+
throw new InsufficientSpaceError(
|
|
600
|
+
spaceMessage(dir, what, { requiredBytes, freeBytes, tmpDirHint: true }),
|
|
601
|
+
dir
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
function errorPath(err) {
|
|
605
|
+
const seen = /* @__PURE__ */ new Set();
|
|
606
|
+
let current = err;
|
|
607
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
608
|
+
seen.add(current);
|
|
609
|
+
const e = current;
|
|
610
|
+
if (typeof e.path === "string" && e.path) return e.path;
|
|
611
|
+
if (typeof e.dest === "string" && e.dest) return e.dest;
|
|
612
|
+
current = e.cause;
|
|
613
|
+
}
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
function isInside(child, parent) {
|
|
617
|
+
const rel = path4.relative(path4.resolve(parent), path4.resolve(child));
|
|
618
|
+
return rel === "" || !rel.startsWith("..") && !path4.isAbsolute(rel);
|
|
619
|
+
}
|
|
620
|
+
function toSpaceError(err, tmpRoot, what) {
|
|
621
|
+
if (err instanceof InsufficientSpaceError) return err;
|
|
622
|
+
if (!isOutOfSpaceError(err)) return err;
|
|
623
|
+
const failed = errorPath(err);
|
|
624
|
+
const failedDir = failed ? path4.dirname(path4.resolve(failed)) : path4.resolve(tmpRoot);
|
|
625
|
+
const inTmp = isInside(failedDir, tmpRoot);
|
|
626
|
+
const dir = inTmp ? path4.resolve(tmpRoot) : failedDir;
|
|
627
|
+
return new InsufficientSpaceError(
|
|
628
|
+
spaceMessage(dir, what, { freeBytes: getFreeSpaceBytes(dir), tmpDirHint: inTmp }),
|
|
629
|
+
dir,
|
|
630
|
+
err
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/sdk/download.ts
|
|
635
|
+
var fs4 = __toESM(require("fs"), 1);
|
|
636
|
+
var path5 = __toESM(require("path"), 1);
|
|
365
637
|
var crypto = __toESM(require("crypto"), 1);
|
|
366
638
|
var import_node_stream = require("stream");
|
|
367
639
|
var import_node_util = require("util");
|
|
@@ -371,36 +643,57 @@ var pipelineAsync = (0, import_node_util.promisify)(import_node_stream.pipeline)
|
|
|
371
643
|
async function downloadFile(opts) {
|
|
372
644
|
const { url, dest, progress, abortSignal } = opts;
|
|
373
645
|
const proto = url.startsWith("https") ? https : http;
|
|
374
|
-
return new Promise((
|
|
646
|
+
return new Promise((resolve4, reject) => {
|
|
375
647
|
const s = Math.max(0, Math.min(100, opts.start ?? 0));
|
|
376
648
|
const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
|
|
377
649
|
let settled = false;
|
|
378
650
|
const done = (err) => {
|
|
379
651
|
if (settled) return;
|
|
380
652
|
settled = true;
|
|
381
|
-
err ? reject(err) :
|
|
653
|
+
err ? reject(err) : resolve4();
|
|
382
654
|
};
|
|
383
655
|
if (abortSignal?.aborted) {
|
|
384
656
|
done(new CancelledError("Download cancelled before start."));
|
|
385
657
|
return;
|
|
386
658
|
}
|
|
387
|
-
const
|
|
659
|
+
const destDir = path5.dirname(path5.resolve(dest));
|
|
660
|
+
const what = opts.what ?? `the download of '${url}'`;
|
|
661
|
+
const writeFailed = (err) => isOutOfSpaceError(err) ? toSpaceError(err, opts.tmpRoot ?? destDir, what) : new OniroError(`Error writing '${dest}': ${err instanceof Error ? err.message : String(err)}`, err);
|
|
662
|
+
const file = fs4.createWriteStream(dest);
|
|
663
|
+
let activeResponse;
|
|
664
|
+
const failWith = (err) => {
|
|
665
|
+
file.close(() => fs4.rm(dest, { force: true }, () => done(err)));
|
|
666
|
+
};
|
|
667
|
+
file.on("error", (err) => {
|
|
668
|
+
try {
|
|
669
|
+
activeResponse?.destroy?.();
|
|
670
|
+
} catch {
|
|
671
|
+
}
|
|
672
|
+
failWith(writeFailed(err));
|
|
673
|
+
});
|
|
388
674
|
const req = proto.get(url, (response) => {
|
|
675
|
+
activeResponse = response;
|
|
389
676
|
if (response.statusCode !== 200) {
|
|
390
677
|
try {
|
|
391
678
|
response.destroy();
|
|
392
679
|
} catch {
|
|
393
680
|
}
|
|
394
|
-
|
|
395
|
-
file.close();
|
|
396
|
-
} catch {
|
|
397
|
-
}
|
|
398
|
-
fs3.unlink(dest, () => {
|
|
399
|
-
});
|
|
400
|
-
done(new OniroError(`Failed to download '${url}' (HTTP ${response.statusCode})`));
|
|
681
|
+
failWith(new OniroError(`Failed to download '${url}' (HTTP ${response.statusCode})`));
|
|
401
682
|
return;
|
|
402
683
|
}
|
|
403
684
|
const total = parseInt(response.headers["content-length"] || "0", 10);
|
|
685
|
+
if (total > 0) {
|
|
686
|
+
try {
|
|
687
|
+
ensureFreeSpace(opts.tmpRoot ?? destDir, total, what);
|
|
688
|
+
} catch (err) {
|
|
689
|
+
try {
|
|
690
|
+
response.destroy();
|
|
691
|
+
} catch {
|
|
692
|
+
}
|
|
693
|
+
failWith(err);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
404
697
|
let downloaded = 0;
|
|
405
698
|
let lastOverall = Math.round(s);
|
|
406
699
|
response.on("data", (chunk) => {
|
|
@@ -424,38 +717,26 @@ async function downloadFile(opts) {
|
|
|
424
717
|
const inc = endOverall - lastOverall;
|
|
425
718
|
if (inc > 0) progress.report({ message: "Downloading: 100%", increment: inc });
|
|
426
719
|
}
|
|
427
|
-
file.close((err) => err ? done(err) : done());
|
|
720
|
+
file.close((err) => err ? done(writeFailed(err)) : done());
|
|
428
721
|
});
|
|
429
722
|
abortSignal?.addEventListener("abort", () => {
|
|
430
723
|
response.destroy();
|
|
431
|
-
|
|
432
|
-
fs3.unlink(dest, () => {
|
|
433
|
-
});
|
|
434
|
-
done(new CancelledError("Download cancelled."));
|
|
724
|
+
failWith(new CancelledError("Download cancelled."));
|
|
435
725
|
});
|
|
436
726
|
});
|
|
437
727
|
req.on("error", (err) => {
|
|
438
|
-
|
|
439
|
-
file.close();
|
|
440
|
-
} catch {
|
|
441
|
-
}
|
|
442
|
-
fs3.unlink(dest, () => {
|
|
443
|
-
});
|
|
444
|
-
done(new OniroError(`Error downloading '${url}': ${err.message}`, err));
|
|
728
|
+
failWith(new OniroError(`Error downloading '${url}': ${err.message}`, err));
|
|
445
729
|
});
|
|
446
730
|
abortSignal?.addEventListener("abort", () => {
|
|
447
731
|
req.destroy();
|
|
448
|
-
|
|
449
|
-
fs3.unlink(dest, () => {
|
|
450
|
-
});
|
|
451
|
-
done(new CancelledError("Download cancelled."));
|
|
732
|
+
failWith(new CancelledError("Download cancelled."));
|
|
452
733
|
});
|
|
453
734
|
});
|
|
454
735
|
}
|
|
455
736
|
async function verifySha256(filePath, sha256Path) {
|
|
456
|
-
const expected =
|
|
737
|
+
const expected = fs4.readFileSync(sha256Path, "utf8").split(/\s+/)[0];
|
|
457
738
|
const hash = crypto.createHash("sha256");
|
|
458
|
-
await pipelineAsync(
|
|
739
|
+
await pipelineAsync(fs4.createReadStream(filePath), hash);
|
|
459
740
|
const actual = hash.digest("hex");
|
|
460
741
|
if (actual !== expected) {
|
|
461
742
|
throw new ChecksumMismatchError(expected, actual);
|
|
@@ -463,9 +744,9 @@ async function verifySha256(filePath, sha256Path) {
|
|
|
463
744
|
}
|
|
464
745
|
|
|
465
746
|
// src/sdk/extract.ts
|
|
466
|
-
var
|
|
467
|
-
var
|
|
468
|
-
var
|
|
747
|
+
var fs5 = __toESM(require("fs"), 1);
|
|
748
|
+
var os5 = __toESM(require("os"), 1);
|
|
749
|
+
var path6 = __toESM(require("path"), 1);
|
|
469
750
|
var import_node_stream2 = require("stream");
|
|
470
751
|
var import_node_util2 = require("util");
|
|
471
752
|
var tar = __toESM(require("tar"), 1);
|
|
@@ -482,12 +763,12 @@ async function extractZipWithProgress(opts) {
|
|
|
482
763
|
const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
|
|
483
764
|
let processed = 0;
|
|
484
765
|
let lastOverall = Math.round(s);
|
|
485
|
-
await
|
|
486
|
-
const destRoot =
|
|
766
|
+
await fs5.promises.mkdir(dest, { recursive: true });
|
|
767
|
+
const destRoot = path6.resolve(dest);
|
|
487
768
|
const safeResolveTarget = (entryName) => {
|
|
488
|
-
const resolved =
|
|
489
|
-
const rel =
|
|
490
|
-
if (rel.startsWith("..") ||
|
|
769
|
+
const resolved = path6.resolve(destRoot, entryName);
|
|
770
|
+
const rel = path6.relative(destRoot, resolved);
|
|
771
|
+
if (rel.startsWith("..") || path6.isAbsolute(rel)) {
|
|
491
772
|
throw new OniroError(`Blocked ZIP entry with illegal path: ${entryName}`);
|
|
492
773
|
}
|
|
493
774
|
return resolved;
|
|
@@ -499,25 +780,25 @@ async function extractZipWithProgress(opts) {
|
|
|
499
780
|
const attr = file.attr ? file.attr >>> 16 : 0;
|
|
500
781
|
const isSymlink = "isSymbolicLink" in file ? Boolean(file.isSymbolicLink) : (attr & 61440) === 40960;
|
|
501
782
|
if (file.isDirectory) {
|
|
502
|
-
await
|
|
783
|
+
await fs5.promises.mkdir(targetPath, { recursive: true });
|
|
503
784
|
} else if (isSymlink) {
|
|
504
|
-
await
|
|
785
|
+
await fs5.promises.mkdir(path6.dirname(targetPath), { recursive: true });
|
|
505
786
|
const linkTargetBuffer = await zip.entryData(file);
|
|
506
787
|
const linkTarget = linkTargetBuffer.toString("utf8");
|
|
507
788
|
try {
|
|
508
|
-
const isWin =
|
|
509
|
-
await
|
|
789
|
+
const isWin = os5.platform() === "win32";
|
|
790
|
+
await fs5.promises.symlink(linkTarget, targetPath, isWin ? "file" : void 0);
|
|
510
791
|
} catch (e) {
|
|
511
792
|
logger.warn(`Failed to create symlink at ${targetPath}: ${String(e)}`);
|
|
512
793
|
}
|
|
513
794
|
} else {
|
|
514
|
-
await
|
|
795
|
+
await fs5.promises.mkdir(path6.dirname(targetPath), { recursive: true });
|
|
515
796
|
const readStream = await zip.stream(file);
|
|
516
|
-
const writeStream =
|
|
797
|
+
const writeStream = fs5.createWriteStream(targetPath);
|
|
517
798
|
await pipelineAsync2(readStream, writeStream);
|
|
518
799
|
if (attr > 0) {
|
|
519
800
|
try {
|
|
520
|
-
await
|
|
801
|
+
await fs5.promises.chmod(targetPath, attr & 511);
|
|
521
802
|
} catch {
|
|
522
803
|
}
|
|
523
804
|
}
|
|
@@ -549,41 +830,41 @@ async function extractTarball(tarPath, dest, strip = 0) {
|
|
|
549
830
|
}
|
|
550
831
|
|
|
551
832
|
// src/sdk/list.ts
|
|
552
|
-
var
|
|
553
|
-
var
|
|
833
|
+
var fs6 = __toESM(require("fs"), 1);
|
|
834
|
+
var path7 = __toESM(require("path"), 1);
|
|
554
835
|
function getSupportedSdksForUi(config) {
|
|
555
|
-
const base =
|
|
836
|
+
const base = path7.join(getSdkRootDir(config), getOsFolder());
|
|
556
837
|
return ALL_SDKS.map((sdk) => ({
|
|
557
838
|
version: sdk.version,
|
|
558
839
|
api: sdk.api,
|
|
559
|
-
installed:
|
|
840
|
+
installed: fs6.existsSync(path7.join(base, sdk.api))
|
|
560
841
|
})).sort((a, b) => Number(b.api) - Number(a.api));
|
|
561
842
|
}
|
|
562
843
|
function getInstalledSdks(config) {
|
|
563
844
|
const sdkRoot = getSdkRootDir(config);
|
|
564
845
|
const versions = /* @__PURE__ */ new Set();
|
|
565
|
-
if (!
|
|
846
|
+
if (!fs6.existsSync(sdkRoot)) return [];
|
|
566
847
|
for (const osFolder of ["linux", "darwin", "windows"]) {
|
|
567
|
-
const osPath =
|
|
568
|
-
if (!
|
|
569
|
-
for (const api of
|
|
570
|
-
const apiPath =
|
|
571
|
-
if (
|
|
848
|
+
const osPath = path7.join(sdkRoot, osFolder);
|
|
849
|
+
if (!fs6.existsSync(osPath) || !fs6.statSync(osPath).isDirectory()) continue;
|
|
850
|
+
for (const api of fs6.readdirSync(osPath)) {
|
|
851
|
+
const apiPath = path7.join(osPath, api);
|
|
852
|
+
if (fs6.statSync(apiPath).isDirectory()) versions.add(api);
|
|
572
853
|
}
|
|
573
854
|
}
|
|
574
855
|
return ALL_SDKS.filter((sdk) => versions.has(sdk.api)).map((sdk) => sdk.version);
|
|
575
856
|
}
|
|
576
857
|
|
|
577
858
|
// src/sdk/remove.ts
|
|
578
|
-
var
|
|
579
|
-
var
|
|
859
|
+
var fs7 = __toESM(require("fs"), 1);
|
|
860
|
+
var path8 = __toESM(require("path"), 1);
|
|
580
861
|
function removeSdk(config, api) {
|
|
581
862
|
const sdkRoot = getSdkRootDir(config);
|
|
582
863
|
let removed = false;
|
|
583
864
|
for (const osFolder of ["linux", "darwin", "windows"]) {
|
|
584
|
-
const sdkPath =
|
|
585
|
-
if (
|
|
586
|
-
|
|
865
|
+
const sdkPath = path8.join(sdkRoot, osFolder, api);
|
|
866
|
+
if (fs7.existsSync(sdkPath)) {
|
|
867
|
+
fs7.rmSync(sdkPath, { recursive: true, force: true });
|
|
587
868
|
removed = true;
|
|
588
869
|
}
|
|
589
870
|
}
|
|
@@ -591,30 +872,52 @@ function removeSdk(config, api) {
|
|
|
591
872
|
}
|
|
592
873
|
|
|
593
874
|
// src/sdk/install.ts
|
|
594
|
-
var
|
|
595
|
-
var
|
|
596
|
-
|
|
875
|
+
var fs9 = __toESM(require("fs"), 1);
|
|
876
|
+
var path9 = __toESM(require("path"), 1);
|
|
877
|
+
|
|
878
|
+
// src/sdk/move.ts
|
|
879
|
+
var fs8 = __toESM(require("fs"), 1);
|
|
880
|
+
function movePath(src, dest) {
|
|
881
|
+
try {
|
|
882
|
+
fs8.renameSync(src, dest);
|
|
883
|
+
} catch (err) {
|
|
884
|
+
if (err.code !== "EXDEV") throw err;
|
|
885
|
+
fs8.cpSync(src, dest, { recursive: true });
|
|
886
|
+
fs8.rmSync(src, { recursive: true, force: true });
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// src/sdk/install.ts
|
|
597
891
|
async function downloadAndInstallSdk(opts) {
|
|
598
892
|
const { config, version, api, progress, abortSignal } = opts;
|
|
599
893
|
const logger = opts.logger ?? noopLogger;
|
|
600
894
|
const { filename, osFolder, strip } = getSdkFilename(version);
|
|
601
|
-
const
|
|
895
|
+
const urlBase = config.get("sdkUrlBase", OHOS_URL_BASE);
|
|
896
|
+
const downloadUrl = `${urlBase}/${version}-Release/${filename}`;
|
|
602
897
|
const sha256Url = `${downloadUrl}.sha256`;
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
898
|
+
const what = `the SDK ${version} install`;
|
|
899
|
+
const sdkRoot = getSdkRootDir(config);
|
|
900
|
+
const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
|
|
901
|
+
config,
|
|
902
|
+
override: opts.tmpDir,
|
|
903
|
+
installRoot: sdkRoot,
|
|
904
|
+
prefix: "oniro-sdk-",
|
|
905
|
+
logger
|
|
906
|
+
});
|
|
907
|
+
const tarPath = path9.join(tmpDir, filename);
|
|
908
|
+
const sha256Path = path9.join(tmpDir, `${filename}.sha256`);
|
|
909
|
+
const extractDir = path9.join(tmpDir, "extract");
|
|
910
|
+
fs9.mkdirSync(extractDir);
|
|
911
|
+
const sdkInstallDir = path9.join(sdkRoot, osFolder, api);
|
|
912
|
+
fs9.mkdirSync(path9.dirname(sdkInstallDir), { recursive: true });
|
|
610
913
|
const checkCancelled = () => {
|
|
611
914
|
if (abortSignal?.aborted) throw new CancelledError();
|
|
612
915
|
};
|
|
613
916
|
try {
|
|
614
917
|
progress?.report({ message: "Downloading SDK archive...", increment: 0 });
|
|
615
|
-
await downloadFile({ url: downloadUrl, dest: tarPath, progress, abortSignal, start: 0, range: 35 });
|
|
918
|
+
await downloadFile({ url: downloadUrl, dest: tarPath, progress, abortSignal, start: 0, range: 35, what, tmpRoot });
|
|
616
919
|
progress?.report({ message: "Downloading checksum...", increment: 0 });
|
|
617
|
-
await downloadFile({ url: sha256Url, dest: sha256Path, progress, abortSignal, start: 35, range: 10 });
|
|
920
|
+
await downloadFile({ url: sha256Url, dest: sha256Path, progress, abortSignal, start: 35, range: 10, what, tmpRoot });
|
|
618
921
|
progress?.report({ message: "Verifying checksum...", increment: 0 });
|
|
619
922
|
await verifySha256(tarPath, sha256Path);
|
|
620
923
|
progress?.report({ message: "Verifying checksum...", increment: 5 });
|
|
@@ -623,13 +926,13 @@ async function downloadAndInstallSdk(opts) {
|
|
|
623
926
|
await extractTarball(tarPath, extractDir, strip);
|
|
624
927
|
progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 10 });
|
|
625
928
|
checkCancelled();
|
|
626
|
-
const osContentPath =
|
|
627
|
-
if (!
|
|
929
|
+
const osContentPath = path9.join(extractDir, osFolder);
|
|
930
|
+
if (!fs9.existsSync(osContentPath)) {
|
|
628
931
|
throw new OniroError(
|
|
629
932
|
`Expected folder '${osFolder}' not found in extracted SDK. Tarball layout may have changed.`
|
|
630
933
|
);
|
|
631
934
|
}
|
|
632
|
-
const zipFiles =
|
|
935
|
+
const zipFiles = fs9.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
|
|
633
936
|
const componentsStart = 60;
|
|
634
937
|
const componentsBudget = 35;
|
|
635
938
|
const n = zipFiles.length;
|
|
@@ -644,39 +947,40 @@ async function downloadAndInstallSdk(opts) {
|
|
|
644
947
|
for (const zipFile of zipFiles) {
|
|
645
948
|
checkCancelled();
|
|
646
949
|
logger.info(`Extracting component ${zipFile}`);
|
|
647
|
-
const zipPath =
|
|
950
|
+
const zipPath = path9.join(osContentPath, zipFile);
|
|
648
951
|
const thisBudget = base + (rem > 0 ? 1 : 0);
|
|
649
952
|
if (rem > 0) rem--;
|
|
650
953
|
await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
|
|
651
954
|
cursor += thisBudget;
|
|
652
|
-
|
|
955
|
+
fs9.unlinkSync(zipPath);
|
|
653
956
|
}
|
|
654
957
|
progress?.report({ message: "Finalizing installation...", increment: 0 });
|
|
655
|
-
if (
|
|
656
|
-
|
|
958
|
+
if (fs9.existsSync(sdkInstallDir)) {
|
|
959
|
+
fs9.rmSync(sdkInstallDir, { recursive: true, force: true });
|
|
657
960
|
}
|
|
658
|
-
|
|
961
|
+
movePath(osContentPath, sdkInstallDir);
|
|
659
962
|
progress?.report({ message: "Finalizing installation...", increment: 3 });
|
|
660
963
|
progress?.report({ message: "Cleaning up...", increment: 0 });
|
|
661
|
-
|
|
964
|
+
removeTempWorkDir(tmpDir, tmpRoot);
|
|
662
965
|
progress?.report({ message: "Cleaning up...", increment: 2 });
|
|
663
966
|
} catch (err) {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
967
|
+
const wrapped = toSpaceError(err, tmpRoot, what);
|
|
968
|
+
logger.error(`SDK install failed: ${wrapped instanceof Error ? wrapped.message : String(wrapped)}`);
|
|
969
|
+
removeTempWorkDir(tmpDir, tmpRoot);
|
|
970
|
+
throw wrapped;
|
|
667
971
|
}
|
|
668
972
|
}
|
|
669
973
|
|
|
670
974
|
// src/cmdTools/install.ts
|
|
671
|
-
var
|
|
975
|
+
var fs10 = __toESM(require("fs"), 1);
|
|
672
976
|
var os6 = __toESM(require("os"), 1);
|
|
673
|
-
var
|
|
977
|
+
var path10 = __toESM(require("path"), 1);
|
|
674
978
|
var import_node_child_process = require("child_process");
|
|
675
|
-
function getCmdToolsDownloadUrl(config,
|
|
676
|
-
if (
|
|
979
|
+
function getCmdToolsDownloadUrl(config, platform7 = os6.platform()) {
|
|
980
|
+
if (platform7 === "linux") {
|
|
677
981
|
return config.get("cmdToolsUrlLinux", defaultPaths.cmdToolsUrlLinux);
|
|
678
982
|
}
|
|
679
|
-
if (
|
|
983
|
+
if (platform7 === "win32") {
|
|
680
984
|
const url = config.get("cmdToolsUrlWindows", "");
|
|
681
985
|
if (!url) {
|
|
682
986
|
throw new OniroError(
|
|
@@ -685,7 +989,7 @@ function getCmdToolsDownloadUrl(config, platform6 = os6.platform()) {
|
|
|
685
989
|
}
|
|
686
990
|
return url;
|
|
687
991
|
}
|
|
688
|
-
if (
|
|
992
|
+
if (platform7 === "darwin") {
|
|
689
993
|
const url = config.get("cmdToolsUrlMac", "");
|
|
690
994
|
if (!url) {
|
|
691
995
|
throw new OniroError(
|
|
@@ -694,20 +998,20 @@ function getCmdToolsDownloadUrl(config, platform6 = os6.platform()) {
|
|
|
694
998
|
}
|
|
695
999
|
return url;
|
|
696
1000
|
}
|
|
697
|
-
throw new UnsupportedPlatformError(
|
|
1001
|
+
throw new UnsupportedPlatformError(platform7);
|
|
698
1002
|
}
|
|
699
1003
|
function findCmdToolsSourceDir(extractPath) {
|
|
700
1004
|
const known = [
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1005
|
+
path10.join(extractPath, "command-line-tools"),
|
|
1006
|
+
path10.join(extractPath, "oh-command-line-tools"),
|
|
1007
|
+
path10.join(extractPath, "commandline-tools")
|
|
704
1008
|
];
|
|
705
1009
|
for (const c of known) {
|
|
706
|
-
if (
|
|
1010
|
+
if (fs10.existsSync(c)) return c;
|
|
707
1011
|
}
|
|
708
|
-
const entries =
|
|
1012
|
+
const entries = fs10.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path10.join(extractPath, e.name));
|
|
709
1013
|
for (const dir of entries) {
|
|
710
|
-
if (
|
|
1014
|
+
if (fs10.existsSync(path10.join(dir, "bin"))) return dir;
|
|
711
1015
|
}
|
|
712
1016
|
throw new OniroError("Could not locate command line tools folder in the extracted archive.");
|
|
713
1017
|
}
|
|
@@ -715,53 +1019,62 @@ async function installCmdTools(opts) {
|
|
|
715
1019
|
const { config, progress, abortSignal } = opts;
|
|
716
1020
|
const logger = opts.logger ?? noopLogger;
|
|
717
1021
|
const CMD_PATH = getCmdToolsPath(config);
|
|
718
|
-
const
|
|
719
|
-
const
|
|
720
|
-
|
|
1022
|
+
const what = "the command-line tools install";
|
|
1023
|
+
const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
|
|
1024
|
+
config,
|
|
1025
|
+
override: opts.tmpDir,
|
|
1026
|
+
installRoot: path10.dirname(CMD_PATH),
|
|
1027
|
+
prefix: "oniro-cmdtools-",
|
|
1028
|
+
logger
|
|
1029
|
+
});
|
|
1030
|
+
const zipPath = opts.localZipPath ?? path10.join(tmpDir, "oh-command-line-tools.zip");
|
|
1031
|
+
const extractPath = path10.join(tmpDir, "oh-command-line-tools");
|
|
721
1032
|
try {
|
|
722
1033
|
if (!opts.localZipPath) {
|
|
723
1034
|
const url = getCmdToolsDownloadUrl(config);
|
|
724
1035
|
progress?.report({ message: "Downloading command line tools...", increment: 0 });
|
|
725
|
-
await downloadFile({ url, dest: zipPath, progress, abortSignal, start: 0, range: 50 });
|
|
1036
|
+
await downloadFile({ url, dest: zipPath, progress, abortSignal, start: 0, range: 50, what, tmpRoot });
|
|
726
1037
|
}
|
|
727
1038
|
progress?.report({ message: "Extracting tools...", increment: 0 });
|
|
728
1039
|
await extractZipWithProgress({ zipPath, dest: extractPath, progress, start: 50, range: 45, logger });
|
|
729
|
-
|
|
1040
|
+
fs10.mkdirSync(CMD_PATH, { recursive: true });
|
|
730
1041
|
const srcDir = findCmdToolsSourceDir(extractPath);
|
|
731
|
-
for (const entry of
|
|
732
|
-
const src =
|
|
733
|
-
const dest =
|
|
734
|
-
if (
|
|
735
|
-
if (
|
|
736
|
-
|
|
1042
|
+
for (const entry of fs10.readdirSync(srcDir)) {
|
|
1043
|
+
const src = path10.join(srcDir, entry);
|
|
1044
|
+
const dest = path10.join(CMD_PATH, entry);
|
|
1045
|
+
if (fs10.statSync(src).isDirectory()) {
|
|
1046
|
+
if (fs10.existsSync(dest)) fs10.rmSync(dest, { recursive: true, force: true });
|
|
1047
|
+
movePath(src, dest);
|
|
737
1048
|
} else {
|
|
738
|
-
|
|
1049
|
+
fs10.copyFileSync(src, dest);
|
|
739
1050
|
}
|
|
740
1051
|
}
|
|
741
|
-
const binDir =
|
|
742
|
-
if (
|
|
743
|
-
for (const file of
|
|
744
|
-
|
|
1052
|
+
const binDir = path10.join(CMD_PATH, "bin");
|
|
1053
|
+
if (fs10.existsSync(binDir) && os6.platform() !== "win32") {
|
|
1054
|
+
for (const file of fs10.readdirSync(binDir)) {
|
|
1055
|
+
fs10.chmodSync(path10.join(binDir, file), 493);
|
|
745
1056
|
}
|
|
746
1057
|
}
|
|
747
1058
|
progress?.report({ message: "Finalizing installation...", increment: 5 });
|
|
748
1059
|
progress?.report({ message: "Cleaning up...", increment: 0 });
|
|
1060
|
+
} catch (err) {
|
|
1061
|
+
throw toSpaceError(err, tmpRoot, what);
|
|
749
1062
|
} finally {
|
|
750
|
-
|
|
1063
|
+
removeTempWorkDir(tmpDir, tmpRoot);
|
|
751
1064
|
}
|
|
752
1065
|
}
|
|
753
1066
|
function getCmdToolsStatus(config) {
|
|
754
1067
|
const cmdPath = getCmdToolsPath(config);
|
|
755
|
-
const binDir =
|
|
1068
|
+
const binDir = path10.join(cmdPath, "bin");
|
|
756
1069
|
const candidates = os6.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
|
|
757
|
-
const bin = candidates.map((c) =>
|
|
1070
|
+
const bin = candidates.map((c) => path10.join(binDir, c)).find((p) => fs10.existsSync(p));
|
|
758
1071
|
if (!bin) {
|
|
759
1072
|
return { installed: false, status: "Not installed" };
|
|
760
1073
|
}
|
|
761
1074
|
try {
|
|
762
|
-
const versionFile =
|
|
763
|
-
if (
|
|
764
|
-
const content =
|
|
1075
|
+
const versionFile = path10.join(cmdPath, "version.txt");
|
|
1076
|
+
if (fs10.existsSync(versionFile)) {
|
|
1077
|
+
const content = fs10.readFileSync(versionFile, "utf8");
|
|
765
1078
|
for (const line of content.split(/\r?\n/)) {
|
|
766
1079
|
const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
|
|
767
1080
|
if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
|
|
@@ -781,14 +1094,14 @@ function isCmdToolsInstalled(config) {
|
|
|
781
1094
|
}
|
|
782
1095
|
function removeCmdTools(config) {
|
|
783
1096
|
const cmdPath = getCmdToolsPath(config);
|
|
784
|
-
if (
|
|
785
|
-
|
|
1097
|
+
if (fs10.existsSync(cmdPath)) {
|
|
1098
|
+
fs10.rmSync(cmdPath, { recursive: true, force: true });
|
|
786
1099
|
}
|
|
787
1100
|
}
|
|
788
1101
|
|
|
789
1102
|
// src/sign/encryptKey.ts
|
|
790
|
-
var
|
|
791
|
-
var
|
|
1103
|
+
var fs11 = __toESM(require("fs"), 1);
|
|
1104
|
+
var path11 = __toESM(require("path"), 1);
|
|
792
1105
|
var crypto2 = __toESM(require("crypto"), 1);
|
|
793
1106
|
var DAEMON_ROOT_KEY_COMPONENT_LENGTH = 16;
|
|
794
1107
|
var DAEMON_SALT_KEY_LENGTH = 16;
|
|
@@ -860,33 +1173,33 @@ function getRootKey(fdComponents, salt) {
|
|
|
860
1173
|
function createAndStoreKey(dir, keyLength) {
|
|
861
1174
|
const key = crypto2.randomBytes(keyLength);
|
|
862
1175
|
const hash = crypto2.createHash("sha256").update(key).digest("hex");
|
|
863
|
-
const filePath =
|
|
864
|
-
|
|
865
|
-
|
|
1176
|
+
const filePath = path11.join(dir, hash);
|
|
1177
|
+
fs11.writeFileSync(filePath, key);
|
|
1178
|
+
fs11.chmodSync(filePath, KEY_FILE_PERMISSIONS);
|
|
866
1179
|
return key;
|
|
867
1180
|
}
|
|
868
1181
|
function createAndStoreEnKey(rootKey, ceDir) {
|
|
869
1182
|
const workKey = crypto2.randomBytes(DAEMON_WORK_KEY_LENGTH);
|
|
870
1183
|
const encrypted = encrypt(rootKey, workKey);
|
|
871
1184
|
const hash = crypto2.createHash("sha256").update(encrypted).digest("hex");
|
|
872
|
-
const filePath =
|
|
873
|
-
|
|
874
|
-
|
|
1185
|
+
const filePath = path11.join(ceDir, hash);
|
|
1186
|
+
fs11.writeFileSync(filePath, encrypted);
|
|
1187
|
+
fs11.chmodSync(filePath, KEY_FILE_PERMISSIONS);
|
|
875
1188
|
}
|
|
876
1189
|
function createMaterial(materialPath) {
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
const fdDir =
|
|
880
|
-
const acDir =
|
|
881
|
-
const ceDir =
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1190
|
+
fs11.mkdirSync(materialPath, { recursive: true });
|
|
1191
|
+
fs11.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
|
|
1192
|
+
const fdDir = path11.join(materialPath, "fd");
|
|
1193
|
+
const acDir = path11.join(materialPath, "ac");
|
|
1194
|
+
const ceDir = path11.join(materialPath, "ce");
|
|
1195
|
+
fs11.mkdirSync(fdDir, { recursive: true });
|
|
1196
|
+
fs11.mkdirSync(acDir, { recursive: true });
|
|
1197
|
+
fs11.mkdirSync(ceDir, { recursive: true });
|
|
885
1198
|
const fdSubDirs = ["0", "1", "2"];
|
|
886
1199
|
const fdComponents = [];
|
|
887
1200
|
for (const sub of fdSubDirs) {
|
|
888
|
-
const subDir =
|
|
889
|
-
|
|
1201
|
+
const subDir = path11.join(fdDir, sub);
|
|
1202
|
+
fs11.mkdirSync(subDir, { recursive: true });
|
|
890
1203
|
const comp = createAndStoreKey(subDir, DAEMON_ROOT_KEY_COMPONENT_LENGTH);
|
|
891
1204
|
fdComponents.push(comp);
|
|
892
1205
|
}
|
|
@@ -895,24 +1208,24 @@ function createMaterial(materialPath) {
|
|
|
895
1208
|
createAndStoreEnKey(rootKey, ceDir);
|
|
896
1209
|
}
|
|
897
1210
|
function readSingleFile(dir) {
|
|
898
|
-
if (!
|
|
1211
|
+
if (!fs11.existsSync(dir) || !fs11.statSync(dir).isDirectory()) {
|
|
899
1212
|
throw new Error(`Missing signing material directory: ${dir}`);
|
|
900
1213
|
}
|
|
901
|
-
const files =
|
|
1214
|
+
const files = fs11.readdirSync(dir).filter((f) => f !== ".DS_Store");
|
|
902
1215
|
if (files.length !== 1) {
|
|
903
1216
|
throw new Error(`Signing material in ${dir} is illegal (expected exactly one file).`);
|
|
904
1217
|
}
|
|
905
|
-
return
|
|
1218
|
+
return fs11.readFileSync(path11.join(dir, files[0]));
|
|
906
1219
|
}
|
|
907
1220
|
function getKey(materialPath) {
|
|
908
|
-
if (!
|
|
1221
|
+
if (!fs11.existsSync(materialPath) || !fs11.statSync(materialPath).isDirectory()) {
|
|
909
1222
|
throw new Error("Material directory does not exist.");
|
|
910
1223
|
}
|
|
911
|
-
const fdDir =
|
|
912
|
-
const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(
|
|
913
|
-
const salt = readSingleFile(
|
|
1224
|
+
const fdDir = path11.join(materialPath, "fd");
|
|
1225
|
+
const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(path11.join(fdDir, sub)));
|
|
1226
|
+
const salt = readSingleFile(path11.join(materialPath, "ac"));
|
|
914
1227
|
const rootKey = getRootKey(fdComponents, salt);
|
|
915
|
-
const workMaterial = readSingleFile(
|
|
1228
|
+
const workMaterial = readSingleFile(path11.join(materialPath, "ce"));
|
|
916
1229
|
return decrypt(rootKey, workMaterial);
|
|
917
1230
|
}
|
|
918
1231
|
function encryptPwd(password, materialPath) {
|
|
@@ -927,8 +1240,8 @@ function decryptPwd(encryptedHex, materialPath) {
|
|
|
927
1240
|
}
|
|
928
1241
|
|
|
929
1242
|
// src/sign/generateSigningConfigs.ts
|
|
930
|
-
var
|
|
931
|
-
var
|
|
1243
|
+
var fs12 = __toESM(require("fs"), 1);
|
|
1244
|
+
var path12 = __toESM(require("path"), 1);
|
|
932
1245
|
var import_node_child_process2 = require("child_process");
|
|
933
1246
|
var import_json52 = __toESM(require("json5"), 1);
|
|
934
1247
|
|
|
@@ -982,6 +1295,7 @@ zAlF08DnbJrOOtOnQq5wHOPlDYB4OtUzOYJk9scotrEnJxJzGsh/
|
|
|
982
1295
|
// src/sign/generateSigningConfigs.ts
|
|
983
1296
|
var APL_VALUES = ["normal", "system_basic", "system_core"];
|
|
984
1297
|
var APP_FEATURE_VALUES = ["hos_normal_app", "hos_system_app"];
|
|
1298
|
+
var DEFAULT_PASSWORDS = { store: "123456", key: "123456" };
|
|
985
1299
|
function pickSigningKind(apl) {
|
|
986
1300
|
return apl === "normal" ? "profile-release" : "application-release";
|
|
987
1301
|
}
|
|
@@ -995,18 +1309,20 @@ var SIGNING_KIND_CONFIG = {
|
|
|
995
1309
|
certBasename: "OpenHarmonyApplication.cer"
|
|
996
1310
|
}
|
|
997
1311
|
};
|
|
998
|
-
function copyFilesToProject(projectDir, paths, kind, logger) {
|
|
1312
|
+
function copyFilesToProject(projectDir, paths, kind, logger, applicationCertPath) {
|
|
999
1313
|
logger.info("[sign] Copying signing material into project...");
|
|
1000
|
-
const signaturesDir =
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1314
|
+
const signaturesDir = path12.join(projectDir, "signatures");
|
|
1315
|
+
fs12.mkdirSync(signaturesDir, { recursive: true });
|
|
1316
|
+
fs12.copyFileSync(paths.keystore, path12.join(signaturesDir, "OpenHarmony.p12"));
|
|
1317
|
+
fs12.copyFileSync(paths.profileCert, path12.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
|
|
1318
|
+
fs12.copyFileSync(paths.unsignedProfileTemplate, path12.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
|
|
1005
1319
|
if (kind === "application-release") {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1320
|
+
const dest = path12.join(signaturesDir, "OpenHarmonyApplication.cer");
|
|
1321
|
+
if (applicationCertPath) {
|
|
1322
|
+
fs12.copyFileSync(applicationCertPath, dest);
|
|
1323
|
+
} else {
|
|
1324
|
+
fs12.writeFileSync(dest, OPEN_HARMONY_APPLICATION_CERT);
|
|
1325
|
+
}
|
|
1010
1326
|
}
|
|
1011
1327
|
}
|
|
1012
1328
|
function resolveAppFeature(apl, override) {
|
|
@@ -1014,7 +1330,7 @@ function resolveAppFeature(apl, override) {
|
|
|
1014
1330
|
return apl === "normal" ? "hos_normal_app" : "hos_system_app";
|
|
1015
1331
|
}
|
|
1016
1332
|
function extractLeafCertificate(chainPath) {
|
|
1017
|
-
const certContent =
|
|
1333
|
+
const certContent = fs12.readFileSync(chainPath, "utf-8");
|
|
1018
1334
|
const parts = certContent.split("-----END CERTIFICATE-----");
|
|
1019
1335
|
if (parts.length < 3) {
|
|
1020
1336
|
throw new OniroError(`${chainPath} does not contain enough certificates.`);
|
|
@@ -1024,20 +1340,20 @@ function extractLeafCertificate(chainPath) {
|
|
|
1024
1340
|
function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}) {
|
|
1025
1341
|
const kind = options.kind ?? pickSigningKind(apl);
|
|
1026
1342
|
logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature}, kind=${kind})...`);
|
|
1027
|
-
const appJsonPath =
|
|
1028
|
-
const profileTemplatePath =
|
|
1029
|
-
if (!
|
|
1343
|
+
const appJsonPath = path12.join(projectDir, "AppScope/app.json5");
|
|
1344
|
+
const profileTemplatePath = path12.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
|
|
1345
|
+
if (!fs12.existsSync(appJsonPath)) {
|
|
1030
1346
|
throw new OniroError(`${appJsonPath} does not exist.`);
|
|
1031
1347
|
}
|
|
1032
1348
|
let appJson;
|
|
1033
1349
|
try {
|
|
1034
|
-
appJson = import_json52.default.parse(
|
|
1350
|
+
appJson = import_json52.default.parse(fs12.readFileSync(appJsonPath, "utf-8"));
|
|
1035
1351
|
} catch (e) {
|
|
1036
1352
|
throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
|
|
1037
1353
|
}
|
|
1038
1354
|
let profileTemplate;
|
|
1039
1355
|
try {
|
|
1040
|
-
profileTemplate = JSON.parse(
|
|
1356
|
+
profileTemplate = JSON.parse(fs12.readFileSync(profileTemplatePath, "utf-8"));
|
|
1041
1357
|
} catch (e) {
|
|
1042
1358
|
throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
|
|
1043
1359
|
}
|
|
@@ -1050,7 +1366,7 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
|
|
|
1050
1366
|
profileTemplate["bundle-info"]["bundle-name"] = appJson.app.bundleName;
|
|
1051
1367
|
profileTemplate["bundle-info"]["apl"] = apl;
|
|
1052
1368
|
profileTemplate["bundle-info"]["app-feature"] = appFeature;
|
|
1053
|
-
const distCertChainPath =
|
|
1369
|
+
const distCertChainPath = path12.join(
|
|
1054
1370
|
projectDir,
|
|
1055
1371
|
"signatures",
|
|
1056
1372
|
SIGNING_KIND_CONFIG[kind].certBasename
|
|
@@ -1060,13 +1376,13 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
|
|
|
1060
1376
|
profileTemplate.acls = profileTemplate.acls ?? {};
|
|
1061
1377
|
profileTemplate.acls["allowed-acls"] = options.acls;
|
|
1062
1378
|
}
|
|
1063
|
-
|
|
1379
|
+
fs12.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
|
|
1064
1380
|
}
|
|
1065
|
-
function generateP7bFile(projectDir, paths, logger) {
|
|
1381
|
+
function generateP7bFile(projectDir, paths, logger, passwords) {
|
|
1066
1382
|
logger.info("[sign] Generating P7b profile via hap-sign-tool...");
|
|
1067
|
-
const signaturesDir =
|
|
1068
|
-
const profileTemplatePath =
|
|
1069
|
-
const outputProfilePath =
|
|
1383
|
+
const signaturesDir = path12.join(projectDir, "signatures");
|
|
1384
|
+
const profileTemplatePath = path12.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
|
|
1385
|
+
const outputProfilePath = path12.join(signaturesDir, "app1-profile.p7b");
|
|
1070
1386
|
const args = [
|
|
1071
1387
|
"-jar",
|
|
1072
1388
|
paths.signTool,
|
|
@@ -1086,9 +1402,9 @@ function generateP7bFile(projectDir, paths, logger) {
|
|
|
1086
1402
|
"-outFile",
|
|
1087
1403
|
outputProfilePath,
|
|
1088
1404
|
"-keyPwd",
|
|
1089
|
-
|
|
1405
|
+
passwords.key,
|
|
1090
1406
|
"-keystorePwd",
|
|
1091
|
-
|
|
1407
|
+
passwords.store
|
|
1092
1408
|
];
|
|
1093
1409
|
try {
|
|
1094
1410
|
(0, import_node_child_process2.execFileSync)("java", args, { stdio: "inherit" });
|
|
@@ -1100,11 +1416,11 @@ function generateP7bFile(projectDir, paths, logger) {
|
|
|
1100
1416
|
}
|
|
1101
1417
|
}
|
|
1102
1418
|
function detectSigningConfigNames(projectDir) {
|
|
1103
|
-
const buildProfilePath =
|
|
1104
|
-
if (!
|
|
1419
|
+
const buildProfilePath = path12.join(projectDir, "build-profile.json5");
|
|
1420
|
+
if (!fs12.existsSync(buildProfilePath)) return ["default"];
|
|
1105
1421
|
let parsed;
|
|
1106
1422
|
try {
|
|
1107
|
-
parsed = import_json52.default.parse(
|
|
1423
|
+
parsed = import_json52.default.parse(fs12.readFileSync(buildProfilePath, "utf-8"));
|
|
1108
1424
|
} catch {
|
|
1109
1425
|
return ["default"];
|
|
1110
1426
|
}
|
|
@@ -1119,16 +1435,16 @@ function detectSigningConfigNames(projectDir) {
|
|
|
1119
1435
|
}
|
|
1120
1436
|
return names.length > 0 ? names : ["default"];
|
|
1121
1437
|
}
|
|
1122
|
-
function updateBuildProfile(projectDir, kind, logger) {
|
|
1438
|
+
function updateBuildProfile(projectDir, kind, logger, passwords) {
|
|
1123
1439
|
logger.info("[sign] Writing signing configs into build-profile.json5...");
|
|
1124
|
-
const materialDir =
|
|
1125
|
-
const buildProfilePath =
|
|
1126
|
-
const encryptedStorePassword = encryptPwd(
|
|
1127
|
-
const encryptedKeyPassword = encryptPwd(
|
|
1440
|
+
const materialDir = path12.join(projectDir, "signatures", "material");
|
|
1441
|
+
const buildProfilePath = path12.join(projectDir, "build-profile.json5");
|
|
1442
|
+
const encryptedStorePassword = encryptPwd(passwords.store, materialDir);
|
|
1443
|
+
const encryptedKeyPassword = encryptPwd(passwords.key, materialDir);
|
|
1128
1444
|
let buildProfile = { app: {} };
|
|
1129
|
-
if (
|
|
1445
|
+
if (fs12.existsSync(buildProfilePath)) {
|
|
1130
1446
|
try {
|
|
1131
|
-
buildProfile = import_json52.default.parse(
|
|
1447
|
+
buildProfile = import_json52.default.parse(fs12.readFileSync(buildProfilePath, "utf-8"));
|
|
1132
1448
|
} catch (e) {
|
|
1133
1449
|
throw new OniroError(`Error parsing ${buildProfilePath}: ${e.message}`, e);
|
|
1134
1450
|
}
|
|
@@ -1149,13 +1465,13 @@ function updateBuildProfile(projectDir, kind, logger) {
|
|
|
1149
1465
|
storeFile: "./signatures/OpenHarmony.p12"
|
|
1150
1466
|
}
|
|
1151
1467
|
}));
|
|
1152
|
-
|
|
1468
|
+
fs12.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
|
|
1153
1469
|
}
|
|
1154
1470
|
function prepareMaterialDirectory(projectDir, logger) {
|
|
1155
1471
|
logger.info("[sign] Generating fresh signing material...");
|
|
1156
|
-
const materialDir =
|
|
1157
|
-
if (
|
|
1158
|
-
|
|
1472
|
+
const materialDir = path12.join(projectDir, "signatures", "material");
|
|
1473
|
+
if (fs12.existsSync(materialDir)) {
|
|
1474
|
+
fs12.rmSync(materialDir, { recursive: true, force: true });
|
|
1159
1475
|
}
|
|
1160
1476
|
createMaterial(materialDir);
|
|
1161
1477
|
}
|
|
@@ -1178,116 +1494,224 @@ function generateSigningConfigs(options) {
|
|
|
1178
1494
|
"Could not detect project SDK version (compileSdkVersion missing in build-profile.json5)."
|
|
1179
1495
|
);
|
|
1180
1496
|
}
|
|
1181
|
-
const sdkPath =
|
|
1182
|
-
if (!
|
|
1497
|
+
const sdkPath = path12.join(sdkHome, String(sdkVersion));
|
|
1498
|
+
if (!fs12.existsSync(sdkPath)) {
|
|
1183
1499
|
throw new OniroError(`SDK path does not exist: ${sdkPath}`);
|
|
1184
1500
|
}
|
|
1185
1501
|
const paths = {
|
|
1186
|
-
signTool:
|
|
1187
|
-
keystore:
|
|
1188
|
-
profileCert:
|
|
1189
|
-
unsignedProfileTemplate:
|
|
1502
|
+
signTool: path12.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
|
|
1503
|
+
keystore: path12.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
|
|
1504
|
+
profileCert: path12.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
|
|
1505
|
+
unsignedProfileTemplate: path12.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
|
|
1190
1506
|
};
|
|
1191
1507
|
const kind = pickSigningKind(apl);
|
|
1508
|
+
const passwords = options.passwords ?? DEFAULT_PASSWORDS;
|
|
1192
1509
|
logger.info("[sign] Starting signing configuration generation...");
|
|
1193
|
-
copyFilesToProject(projectDir, paths, kind, logger);
|
|
1510
|
+
copyFilesToProject(projectDir, paths, kind, logger, options.applicationCertPath);
|
|
1194
1511
|
modifyProfileTemplate(projectDir, apl, appFeature, logger, { kind, acls: options.acls });
|
|
1195
|
-
generateP7bFile(projectDir, paths, logger);
|
|
1512
|
+
generateP7bFile(projectDir, paths, logger, passwords);
|
|
1196
1513
|
prepareMaterialDirectory(projectDir, logger);
|
|
1197
|
-
updateBuildProfile(projectDir, kind, logger);
|
|
1514
|
+
updateBuildProfile(projectDir, kind, logger, passwords);
|
|
1198
1515
|
logger.info("[sign] Signing configuration generated successfully.");
|
|
1199
1516
|
}
|
|
1200
1517
|
|
|
1201
1518
|
// src/emulator/install.ts
|
|
1202
|
-
var
|
|
1203
|
-
var
|
|
1204
|
-
var path11 = __toESM(require("path"), 1);
|
|
1519
|
+
var fs13 = __toESM(require("fs"), 1);
|
|
1520
|
+
var path13 = __toESM(require("path"), 1);
|
|
1205
1521
|
async function installEmulator(opts) {
|
|
1206
1522
|
const { config, progress, abortSignal } = opts;
|
|
1207
1523
|
const logger = opts.logger ?? noopLogger;
|
|
1208
1524
|
const url = config.get("emulatorUrl", defaultPaths.emulatorUrl);
|
|
1209
1525
|
const emulatorDir = getEmulatorDir(config);
|
|
1210
|
-
const
|
|
1211
|
-
const
|
|
1526
|
+
const what = "the emulator install";
|
|
1527
|
+
const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
|
|
1528
|
+
config,
|
|
1529
|
+
override: opts.tmpDir,
|
|
1530
|
+
installRoot: path13.dirname(emulatorDir),
|
|
1531
|
+
prefix: "oniro-emulator-",
|
|
1532
|
+
logger
|
|
1533
|
+
});
|
|
1534
|
+
const tmpZip = path13.join(tmpDir, "oniro_emulator.zip");
|
|
1212
1535
|
try {
|
|
1213
|
-
|
|
1536
|
+
fs13.mkdirSync(emulatorDir, { recursive: true });
|
|
1214
1537
|
progress?.report({ message: "Downloading emulator...", increment: 0 });
|
|
1215
|
-
await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50 });
|
|
1538
|
+
await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50, what, tmpRoot });
|
|
1216
1539
|
progress?.report({ message: "Extracting emulator...", increment: 0 });
|
|
1217
1540
|
await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
|
|
1218
|
-
const runSh =
|
|
1219
|
-
if (
|
|
1220
|
-
|
|
1541
|
+
const runSh = path13.join(emulatorDir, "images", "run.sh");
|
|
1542
|
+
if (fs13.existsSync(runSh)) {
|
|
1543
|
+
fs13.chmodSync(runSh, 493);
|
|
1221
1544
|
}
|
|
1222
1545
|
progress?.report({ message: "Finalizing installation...", increment: 5 });
|
|
1546
|
+
} catch (err) {
|
|
1547
|
+
throw toSpaceError(err, tmpRoot, what);
|
|
1223
1548
|
} finally {
|
|
1224
|
-
|
|
1549
|
+
removeTempWorkDir(tmpDir, tmpRoot);
|
|
1225
1550
|
}
|
|
1226
1551
|
}
|
|
1227
1552
|
function isEmulatorInstalled(config) {
|
|
1228
|
-
|
|
1553
|
+
const imagesDir = path13.join(getEmulatorDir(config), "images");
|
|
1554
|
+
return fs13.existsSync(path13.join(imagesDir, "run.sh")) || fs13.existsSync(path13.join(imagesDir, "run.bat"));
|
|
1229
1555
|
}
|
|
1230
1556
|
function removeEmulator(config) {
|
|
1231
1557
|
const emulatorDir = getEmulatorDir(config);
|
|
1232
|
-
if (
|
|
1233
|
-
|
|
1558
|
+
if (fs13.existsSync(emulatorDir)) {
|
|
1559
|
+
fs13.rmSync(emulatorDir, { recursive: true, force: true });
|
|
1234
1560
|
}
|
|
1235
1561
|
}
|
|
1236
1562
|
|
|
1237
1563
|
// src/emulator/lifecycle.ts
|
|
1238
|
-
var
|
|
1239
|
-
var
|
|
1240
|
-
var
|
|
1564
|
+
var fs14 = __toESM(require("fs"), 1);
|
|
1565
|
+
var os7 = __toESM(require("os"), 1);
|
|
1566
|
+
var path14 = __toESM(require("path"), 1);
|
|
1567
|
+
var import_node_child_process4 = require("child_process");
|
|
1568
|
+
|
|
1569
|
+
// src/hdc/exec.ts
|
|
1241
1570
|
var import_node_child_process3 = require("child_process");
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1571
|
+
function runProcess(opts) {
|
|
1572
|
+
const { command, args, cwd, env, onOutput } = opts;
|
|
1573
|
+
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
1574
|
+
const { abortSignal } = opts;
|
|
1575
|
+
return new Promise((resolve4, reject) => {
|
|
1576
|
+
if (abortSignal?.aborted) {
|
|
1577
|
+
reject(new CancelledError(`Command aborted before start: ${command}`));
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
const child = (0, import_node_child_process3.spawn)(command, [...args], { cwd, env, shell: false });
|
|
1581
|
+
let stdout = "";
|
|
1582
|
+
let stderr = "";
|
|
1583
|
+
let settled = false;
|
|
1584
|
+
let timedOut = false;
|
|
1585
|
+
const cleanup = () => {
|
|
1586
|
+
clearTimeout(timer);
|
|
1587
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
1588
|
+
};
|
|
1589
|
+
const onAbort = () => {
|
|
1590
|
+
if (settled) return;
|
|
1591
|
+
settled = true;
|
|
1592
|
+
cleanup();
|
|
1593
|
+
try {
|
|
1594
|
+
child.kill("SIGKILL");
|
|
1595
|
+
} catch {
|
|
1253
1596
|
}
|
|
1597
|
+
reject(new CancelledError(`Command aborted: ${command} ${args.join(" ")}`));
|
|
1598
|
+
};
|
|
1599
|
+
const timer = setTimeout(() => {
|
|
1600
|
+
timedOut = true;
|
|
1601
|
+
try {
|
|
1602
|
+
child.kill("SIGKILL");
|
|
1603
|
+
} catch {
|
|
1604
|
+
}
|
|
1605
|
+
}, timeoutMs);
|
|
1606
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1607
|
+
child.stdout.on("data", (d) => {
|
|
1608
|
+
const s = d.toString();
|
|
1609
|
+
stdout += s;
|
|
1610
|
+
onOutput?.(s, "stdout");
|
|
1611
|
+
});
|
|
1612
|
+
child.stderr.on("data", (d) => {
|
|
1613
|
+
const s = d.toString();
|
|
1614
|
+
stderr += s;
|
|
1615
|
+
onOutput?.(s, "stderr");
|
|
1616
|
+
});
|
|
1617
|
+
child.on("error", (err) => {
|
|
1618
|
+
if (settled) return;
|
|
1619
|
+
settled = true;
|
|
1620
|
+
cleanup();
|
|
1621
|
+
reject(new OniroError(`Failed to spawn ${command}: ${err.message}`, err));
|
|
1622
|
+
});
|
|
1623
|
+
child.on("close", (code) => {
|
|
1624
|
+
if (settled) return;
|
|
1625
|
+
settled = true;
|
|
1626
|
+
cleanup();
|
|
1627
|
+
if (timedOut) {
|
|
1628
|
+
reject(
|
|
1629
|
+
new OniroError(
|
|
1630
|
+
`Command timed out after ${timeoutMs}ms: ${command} ${args.join(" ")}` + (stderr.trim() ? `
|
|
1631
|
+
${stderr.trim()}` : "")
|
|
1632
|
+
)
|
|
1633
|
+
);
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
resolve4({ code: code ?? -1, stdout, stderr });
|
|
1254
1637
|
});
|
|
1255
1638
|
});
|
|
1256
1639
|
}
|
|
1640
|
+
function hdcExec(opts) {
|
|
1641
|
+
const hdc = getHdcPath(opts.config);
|
|
1642
|
+
const argv = opts.deviceSerial ? ["-t", opts.deviceSerial, ...opts.args] : [...opts.args];
|
|
1643
|
+
return runProcess({
|
|
1644
|
+
command: hdc,
|
|
1645
|
+
args: argv,
|
|
1646
|
+
timeoutMs: opts.timeoutMs,
|
|
1647
|
+
abortSignal: opts.abortSignal,
|
|
1648
|
+
onOutput: opts.onOutput,
|
|
1649
|
+
logger: opts.logger
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
function shell(opts) {
|
|
1653
|
+
const { command, ...rest } = opts;
|
|
1654
|
+
return hdcExec({ ...rest, args: ["shell", command] });
|
|
1655
|
+
}
|
|
1656
|
+
function ensureOk(result, command) {
|
|
1657
|
+
if (result.code !== 0) {
|
|
1658
|
+
throw new CommandFailedError(command, result.code, result.stderr.trim());
|
|
1659
|
+
}
|
|
1660
|
+
return result;
|
|
1661
|
+
}
|
|
1662
|
+
var REMOTE_RC_RE = /__ONIRO_RC=(-?\d+)__/g;
|
|
1663
|
+
function parseRemoteExit(stdout) {
|
|
1664
|
+
let code = null;
|
|
1665
|
+
for (const m of stdout.matchAll(REMOTE_RC_RE)) code = Number(m[1]);
|
|
1666
|
+
if (code === null) return null;
|
|
1667
|
+
return { code, stdout: stdout.replace(REMOTE_RC_RE, "").replace(/[ \t\r\n]+$/, "") };
|
|
1668
|
+
}
|
|
1669
|
+
async function shellChecked(opts) {
|
|
1670
|
+
const { command, ...rest } = opts;
|
|
1671
|
+
const res = await hdcExec({ ...rest, args: ["shell", `${command}; echo "__ONIRO_RC=$?__"`] });
|
|
1672
|
+
const parsed = parseRemoteExit(res.stdout);
|
|
1673
|
+
if (!parsed) return res;
|
|
1674
|
+
return { code: parsed.code, stdout: parsed.stdout, stderr: res.stderr };
|
|
1675
|
+
}
|
|
1676
|
+
function ensureRemoteOk(result, command) {
|
|
1677
|
+
if (result.code !== 0) {
|
|
1678
|
+
throw new CommandFailedError(command, result.code, result.stderr.trim() || result.stdout.trim());
|
|
1679
|
+
}
|
|
1680
|
+
return result;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// src/emulator/lifecycle.ts
|
|
1684
|
+
var PID_FILE = path14.join(os7.tmpdir(), "oniro_emulator.pid");
|
|
1257
1685
|
async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
|
|
1258
|
-
const hdc = getHdcPath(config);
|
|
1259
1686
|
try {
|
|
1260
|
-
await
|
|
1261
|
-
await
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
resolve2(stdout?.includes(address) ?? false);
|
|
1265
|
-
});
|
|
1266
|
-
});
|
|
1687
|
+
await hdcExec({ config, args: ["start", "-r"], logger });
|
|
1688
|
+
await hdcExec({ config, args: ["tconn", address], logger });
|
|
1689
|
+
const targets = await hdcExec({ config, args: ["list", "targets"], logger });
|
|
1690
|
+
return targets.stdout.includes(address);
|
|
1267
1691
|
} catch (err) {
|
|
1268
1692
|
logger.warn(`hdc connection attempt failed: ${err.message}`);
|
|
1269
1693
|
return false;
|
|
1270
1694
|
}
|
|
1271
1695
|
}
|
|
1272
1696
|
function resolveLauncher(imagesPath, headless, connect) {
|
|
1273
|
-
const runSh =
|
|
1274
|
-
const runBat =
|
|
1697
|
+
const runSh = path14.join(imagesPath, "run.sh");
|
|
1698
|
+
const runBat = path14.join(imagesPath, "run.bat");
|
|
1275
1699
|
const extra = [];
|
|
1276
1700
|
if (headless) extra.push("--headless");
|
|
1277
1701
|
if (connect) extra.push("--connect", connect);
|
|
1278
|
-
if (
|
|
1279
|
-
if (
|
|
1702
|
+
if (os7.platform() === "win32") {
|
|
1703
|
+
if (fs14.existsSync(runBat)) {
|
|
1280
1704
|
return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
|
|
1281
1705
|
}
|
|
1282
|
-
if (
|
|
1706
|
+
if (fs14.existsSync(runSh)) {
|
|
1283
1707
|
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1284
1708
|
}
|
|
1285
1709
|
throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
|
|
1286
1710
|
}
|
|
1287
|
-
if (
|
|
1711
|
+
if (fs14.existsSync(runSh)) {
|
|
1288
1712
|
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1289
1713
|
}
|
|
1290
|
-
if (
|
|
1714
|
+
if (fs14.existsSync(runBat)) {
|
|
1291
1715
|
return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
|
|
1292
1716
|
}
|
|
1293
1717
|
throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
|
|
@@ -1295,9 +1719,9 @@ function resolveLauncher(imagesPath, headless, connect) {
|
|
|
1295
1719
|
async function startEmulator(opts) {
|
|
1296
1720
|
const { config } = opts;
|
|
1297
1721
|
const logger = opts.logger ?? noopLogger;
|
|
1298
|
-
if (
|
|
1722
|
+
if (os7.platform() !== "win32" && fs14.existsSync(PID_FILE)) {
|
|
1299
1723
|
try {
|
|
1300
|
-
const pid = Number(
|
|
1724
|
+
const pid = Number(fs14.readFileSync(PID_FILE, "utf8").trim());
|
|
1301
1725
|
if (Number.isFinite(pid) && pid > 0) {
|
|
1302
1726
|
try {
|
|
1303
1727
|
process.kill(pid, 0);
|
|
@@ -1313,35 +1737,35 @@ async function startEmulator(opts) {
|
|
|
1313
1737
|
logger.warn(`Could not read PID file: ${err.message}`);
|
|
1314
1738
|
}
|
|
1315
1739
|
}
|
|
1316
|
-
const imagesPath =
|
|
1317
|
-
if (!
|
|
1740
|
+
const imagesPath = path14.join(getEmulatorDir(config), "images");
|
|
1741
|
+
if (!fs14.existsSync(imagesPath)) {
|
|
1318
1742
|
throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
|
|
1319
1743
|
}
|
|
1320
1744
|
const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
|
|
1321
|
-
const logPath = opts.logFile ?? (
|
|
1322
|
-
const logFd =
|
|
1745
|
+
const logPath = opts.logFile ?? (os7.platform() === "win32" ? "NUL" : "/dev/null");
|
|
1746
|
+
const logFd = fs14.openSync(logPath, "a");
|
|
1323
1747
|
let child;
|
|
1324
1748
|
try {
|
|
1325
|
-
child = (0,
|
|
1749
|
+
child = (0, import_node_child_process4.spawn)(launcher.command, [...launcher.args], {
|
|
1326
1750
|
cwd: launcher.cwd,
|
|
1327
1751
|
detached: true,
|
|
1328
1752
|
stdio: ["ignore", logFd, logFd],
|
|
1329
1753
|
windowsHide: true
|
|
1330
1754
|
});
|
|
1331
1755
|
} finally {
|
|
1332
|
-
|
|
1756
|
+
fs14.closeSync(logFd);
|
|
1333
1757
|
}
|
|
1334
1758
|
if (!child.pid) {
|
|
1335
1759
|
throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
|
|
1336
1760
|
}
|
|
1337
|
-
|
|
1761
|
+
fs14.writeFileSync(PID_FILE, String(child.pid));
|
|
1338
1762
|
child.unref();
|
|
1339
1763
|
logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
|
|
1340
|
-
const earlyExit = new Promise((
|
|
1341
|
-
child.once("exit", (code) =>
|
|
1342
|
-
child.once("error", () =>
|
|
1764
|
+
const earlyExit = new Promise((resolve4) => {
|
|
1765
|
+
child.once("exit", (code) => resolve4(code));
|
|
1766
|
+
child.once("error", () => resolve4(-1));
|
|
1343
1767
|
});
|
|
1344
|
-
const liveness = new Promise((
|
|
1768
|
+
const liveness = new Promise((resolve4) => setTimeout(() => resolve4("alive"), 2e3));
|
|
1345
1769
|
const outcome = await Promise.race([earlyExit, liveness]);
|
|
1346
1770
|
if (outcome !== "alive") {
|
|
1347
1771
|
const tail = readTail(logPath, 4e3);
|
|
@@ -1381,24 +1805,24 @@ ${tail}` : ""}`);
|
|
|
1381
1805
|
}
|
|
1382
1806
|
function readTail(filePath, bytes) {
|
|
1383
1807
|
try {
|
|
1384
|
-
const stat =
|
|
1808
|
+
const stat = fs14.statSync(filePath);
|
|
1385
1809
|
const start = Math.max(0, stat.size - bytes);
|
|
1386
|
-
const fd =
|
|
1810
|
+
const fd = fs14.openSync(filePath, "r");
|
|
1387
1811
|
try {
|
|
1388
1812
|
const buf = Buffer.alloc(stat.size - start);
|
|
1389
|
-
|
|
1813
|
+
fs14.readSync(fd, buf, 0, buf.length, start);
|
|
1390
1814
|
return buf.toString("utf8");
|
|
1391
1815
|
} finally {
|
|
1392
|
-
|
|
1816
|
+
fs14.closeSync(fd);
|
|
1393
1817
|
}
|
|
1394
1818
|
} catch {
|
|
1395
1819
|
return "";
|
|
1396
1820
|
}
|
|
1397
1821
|
}
|
|
1398
1822
|
async function stopEmulator(logger = noopLogger) {
|
|
1399
|
-
if (
|
|
1823
|
+
if (fs14.existsSync(PID_FILE)) {
|
|
1400
1824
|
try {
|
|
1401
|
-
const pid = Number(
|
|
1825
|
+
const pid = Number(fs14.readFileSync(PID_FILE, "utf8").trim());
|
|
1402
1826
|
if (Number.isFinite(pid) && pid > 0) {
|
|
1403
1827
|
try {
|
|
1404
1828
|
process.kill(pid, "SIGTERM");
|
|
@@ -1411,27 +1835,194 @@ async function stopEmulator(logger = noopLogger) {
|
|
|
1411
1835
|
logger.warn(`Could not read PID file: ${err.message}`);
|
|
1412
1836
|
}
|
|
1413
1837
|
}
|
|
1414
|
-
const killCmd =
|
|
1838
|
+
const [killCmd, ...killArgs] = os7.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
|
|
1415
1839
|
try {
|
|
1416
|
-
await
|
|
1417
|
-
} catch {
|
|
1840
|
+
await runProcess({ command: killCmd, args: killArgs, logger });
|
|
1841
|
+
} catch (err) {
|
|
1842
|
+
logger.warn(`[emulator] could not run ${killCmd}: ${err.message}`);
|
|
1418
1843
|
}
|
|
1419
|
-
|
|
1844
|
+
fs14.rm(PID_FILE, { force: true }, (err) => {
|
|
1420
1845
|
if (err) logger.warn(`Could not remove PID file: ${err.message}`);
|
|
1421
1846
|
});
|
|
1422
1847
|
}
|
|
1423
1848
|
|
|
1849
|
+
// src/hdc/devices.ts
|
|
1850
|
+
var STATUS_TOKENS = /* @__PURE__ */ new Set([
|
|
1851
|
+
"Connected",
|
|
1852
|
+
"Offline",
|
|
1853
|
+
"Unauthorized",
|
|
1854
|
+
"Authorizing",
|
|
1855
|
+
"Disconnected",
|
|
1856
|
+
"Recovery"
|
|
1857
|
+
]);
|
|
1858
|
+
var CONNECTION_TOKENS = /* @__PURE__ */ new Set(["USB", "TCP", "UART", "BT", "UNKNOWN"]);
|
|
1859
|
+
function parseDeviceList(stdout) {
|
|
1860
|
+
const devices = [];
|
|
1861
|
+
for (const line of stdout.split("\n")) {
|
|
1862
|
+
const trimmed = line.trim();
|
|
1863
|
+
if (!trimmed || trimmed.startsWith("[Empty]")) continue;
|
|
1864
|
+
const cols = trimmed.split(/\s+/);
|
|
1865
|
+
const serial = cols[0];
|
|
1866
|
+
if (!serial) continue;
|
|
1867
|
+
const rest = cols.slice(1);
|
|
1868
|
+
const connection = rest.find((c) => CONNECTION_TOKENS.has(c));
|
|
1869
|
+
const status = rest.find((c) => STATUS_TOKENS.has(c)) ?? "Unknown";
|
|
1870
|
+
const info = { serial, status };
|
|
1871
|
+
if (connection) info.connection = connection;
|
|
1872
|
+
devices.push(info);
|
|
1873
|
+
}
|
|
1874
|
+
return devices;
|
|
1875
|
+
}
|
|
1876
|
+
async function listDevices(config, opts = {}) {
|
|
1877
|
+
const res = await hdcExec({
|
|
1878
|
+
config,
|
|
1879
|
+
args: ["list", "targets", "-v"],
|
|
1880
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1881
|
+
logger: opts.logger
|
|
1882
|
+
});
|
|
1883
|
+
return parseDeviceList(res.stdout);
|
|
1884
|
+
}
|
|
1885
|
+
async function selectDevice(config, serial, opts = {}) {
|
|
1886
|
+
if (serial && serial !== "auto") return serial;
|
|
1887
|
+
const envSerial = process.env.ONIRO_DEVICE_SERIAL || process.env.DEVICE_SERIAL;
|
|
1888
|
+
if (envSerial && envSerial !== "auto") return envSerial;
|
|
1889
|
+
const usable = (await listDevices(config, opts)).filter(
|
|
1890
|
+
(d) => d.status === "Connected" || d.status === "Unknown"
|
|
1891
|
+
);
|
|
1892
|
+
if (usable.length === 1) return usable[0].serial;
|
|
1893
|
+
if (usable.length === 0) {
|
|
1894
|
+
throw new OniroError("No connected device found. Connect a device/emulator or set ONIRO_DEVICE_SERIAL.");
|
|
1895
|
+
}
|
|
1896
|
+
throw new OniroError(
|
|
1897
|
+
`Multiple devices connected (${usable.map((d) => d.serial).join(", ")}). Set ONIRO_DEVICE_SERIAL to choose one.`
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
// src/hdc/param.ts
|
|
1902
|
+
async function paramGet(config, key, opts = {}) {
|
|
1903
|
+
const res = await shell({
|
|
1904
|
+
config,
|
|
1905
|
+
command: `param get ${key}`,
|
|
1906
|
+
deviceSerial: opts.deviceSerial,
|
|
1907
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1908
|
+
logger: opts.logger
|
|
1909
|
+
});
|
|
1910
|
+
ensureOk(res, `hdc shell param get ${key}`);
|
|
1911
|
+
return res.stdout.trim();
|
|
1912
|
+
}
|
|
1913
|
+
async function paramSet(config, key, value, opts = {}) {
|
|
1914
|
+
const res = await shell({
|
|
1915
|
+
config,
|
|
1916
|
+
command: `param set ${key} ${value}`,
|
|
1917
|
+
deviceSerial: opts.deviceSerial,
|
|
1918
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1919
|
+
logger: opts.logger
|
|
1920
|
+
});
|
|
1921
|
+
ensureOk(res, `hdc shell param set ${key} ${value}`);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
// src/hdc/files.ts
|
|
1925
|
+
async function sendFile(opts) {
|
|
1926
|
+
const res = await hdcExec({
|
|
1927
|
+
config: opts.config,
|
|
1928
|
+
args: ["file", "send", opts.local, opts.remote],
|
|
1929
|
+
deviceSerial: opts.deviceSerial,
|
|
1930
|
+
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
1931
|
+
abortSignal: opts.abortSignal,
|
|
1932
|
+
onOutput: opts.onOutput,
|
|
1933
|
+
logger: opts.logger
|
|
1934
|
+
});
|
|
1935
|
+
ensureOk(res, `hdc file send ${opts.local} ${opts.remote}`);
|
|
1936
|
+
}
|
|
1937
|
+
async function recvFile(opts) {
|
|
1938
|
+
const res = await hdcExec({
|
|
1939
|
+
config: opts.config,
|
|
1940
|
+
args: ["file", "recv", opts.remote, opts.local],
|
|
1941
|
+
deviceSerial: opts.deviceSerial,
|
|
1942
|
+
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
1943
|
+
abortSignal: opts.abortSignal,
|
|
1944
|
+
onOutput: opts.onOutput,
|
|
1945
|
+
logger: opts.logger
|
|
1946
|
+
});
|
|
1947
|
+
ensureOk(res, `hdc file recv ${opts.remote} ${opts.local}`);
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// src/hdc/display.ts
|
|
1951
|
+
async function getDisplaySize(config, opts = {}) {
|
|
1952
|
+
const res = await shell({
|
|
1953
|
+
config,
|
|
1954
|
+
command: "hidumper -s 10 -a screen",
|
|
1955
|
+
deviceSerial: opts.deviceSerial,
|
|
1956
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1957
|
+
logger: opts.logger
|
|
1958
|
+
});
|
|
1959
|
+
if (res.code !== 0) return null;
|
|
1960
|
+
const m = res.stdout.match(/render resolution=(\d+)x(\d+)/);
|
|
1961
|
+
if (!m) return null;
|
|
1962
|
+
return { width: Number(m[1]), height: Number(m[2]) };
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
// src/hdc/wait.ts
|
|
1966
|
+
function delay(ms, signal) {
|
|
1967
|
+
return new Promise((resolve4, reject) => {
|
|
1968
|
+
if (signal?.aborted) {
|
|
1969
|
+
reject(new CancelledError("Wait cancelled."));
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
const onAbort = () => {
|
|
1973
|
+
cleanup();
|
|
1974
|
+
reject(new CancelledError("Wait cancelled."));
|
|
1975
|
+
};
|
|
1976
|
+
const cleanup = () => {
|
|
1977
|
+
clearTimeout(timer);
|
|
1978
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1979
|
+
};
|
|
1980
|
+
const timer = setTimeout(() => {
|
|
1981
|
+
cleanup();
|
|
1982
|
+
resolve4();
|
|
1983
|
+
}, ms);
|
|
1984
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
async function waitForCondition(opts) {
|
|
1988
|
+
const pollMs = opts.pollMs ?? 2e3;
|
|
1989
|
+
const heartbeatEvery = opts.heartbeatEvery ?? 6;
|
|
1990
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
1991
|
+
let attempt = 0;
|
|
1992
|
+
while (Date.now() < deadline) {
|
|
1993
|
+
if (opts.abortSignal?.aborted) throw new CancelledError("Wait cancelled.");
|
|
1994
|
+
attempt++;
|
|
1995
|
+
let ok = false;
|
|
1996
|
+
try {
|
|
1997
|
+
ok = await opts.probe();
|
|
1998
|
+
} catch {
|
|
1999
|
+
ok = false;
|
|
2000
|
+
}
|
|
2001
|
+
if (ok) return;
|
|
2002
|
+
if (opts.onHeartbeat && attempt % heartbeatEvery === 0) {
|
|
2003
|
+
opts.onHeartbeat(attempt, Math.max(0, deadline - Date.now()));
|
|
2004
|
+
}
|
|
2005
|
+
await delay(pollMs, opts.abortSignal);
|
|
2006
|
+
}
|
|
2007
|
+
throw new OniroError(opts.timeoutMessage ?? `Condition not met within ${opts.timeoutMs}ms.`);
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
// src/hdc/app.ts
|
|
2011
|
+
var fs16 = __toESM(require("fs"), 1);
|
|
2012
|
+
var path16 = __toESM(require("path"), 1);
|
|
2013
|
+
var import_node_child_process5 = require("child_process");
|
|
2014
|
+
|
|
1424
2015
|
// src/hdc/project.ts
|
|
1425
|
-
var
|
|
1426
|
-
var
|
|
2016
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
2017
|
+
var path15 = __toESM(require("path"), 1);
|
|
1427
2018
|
var import_json53 = __toESM(require("json5"), 1);
|
|
1428
2019
|
function readJson5(filePath) {
|
|
1429
|
-
const content =
|
|
2020
|
+
const content = fs15.readFileSync(filePath, "utf-8");
|
|
1430
2021
|
return import_json53.default.parse(content);
|
|
1431
2022
|
}
|
|
1432
2023
|
function getBundleName(projectDir) {
|
|
1433
|
-
const appJsonPath =
|
|
1434
|
-
if (!
|
|
2024
|
+
const appJsonPath = path15.join(projectDir, "AppScope", "app.json5");
|
|
2025
|
+
if (!fs15.existsSync(appJsonPath)) {
|
|
1435
2026
|
throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
|
|
1436
2027
|
}
|
|
1437
2028
|
const appJson = readJson5(appJsonPath);
|
|
@@ -1440,59 +2031,118 @@ function getBundleName(projectDir) {
|
|
|
1440
2031
|
}
|
|
1441
2032
|
return appJson.app.bundleName;
|
|
1442
2033
|
}
|
|
1443
|
-
function getMainAbility(projectDir, moduleName = "entry") {
|
|
1444
|
-
const moduleJsonPath =
|
|
1445
|
-
if (!
|
|
2034
|
+
function getMainAbility(projectDir, moduleName = "entry", abilityName) {
|
|
2035
|
+
const moduleJsonPath = path15.join(projectDir, moduleName, "src", "main", "module.json5");
|
|
2036
|
+
if (!fs15.existsSync(moduleJsonPath)) {
|
|
1446
2037
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
1447
2038
|
}
|
|
1448
2039
|
const moduleJson = readJson5(moduleJsonPath);
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
2040
|
+
const module2 = moduleJson.module ?? {};
|
|
2041
|
+
if (abilityName) {
|
|
2042
|
+
const match = module2.abilities?.find((a) => a.name === abilityName);
|
|
2043
|
+
if (match?.name) return match.name;
|
|
2044
|
+
throw new OniroError(`Ability "${abilityName}" not found in module.json5 at ${moduleJsonPath}`);
|
|
2045
|
+
}
|
|
2046
|
+
if (module2.mainElement) return module2.mainElement;
|
|
2047
|
+
const abilities = module2.abilities ?? [];
|
|
2048
|
+
const visible = abilities.find((a) => a.visible && a.name);
|
|
2049
|
+
if (visible?.name) return visible.name;
|
|
2050
|
+
const first = abilities.find((a) => a.name);
|
|
2051
|
+
if (first?.name) return first.name;
|
|
2052
|
+
throw new OniroError("mainElement not found in module.json5");
|
|
1453
2053
|
}
|
|
1454
2054
|
|
|
1455
2055
|
// src/hdc/app.ts
|
|
1456
|
-
var fs14 = __toESM(require("fs"), 1);
|
|
1457
|
-
var path14 = __toESM(require("path"), 1);
|
|
1458
|
-
var import_node_child_process4 = require("child_process");
|
|
1459
|
-
function execPromise2(cmd, logger) {
|
|
1460
|
-
return new Promise((resolve2, reject) => {
|
|
1461
|
-
(0, import_node_child_process4.exec)(cmd, (error, stdout, stderr) => {
|
|
1462
|
-
if (stdout?.trim()) logger.info(`[hdc] ${stdout.trim()}`);
|
|
1463
|
-
if (stderr?.trim()) logger.warn(`[hdc] ${stderr.trim()}`);
|
|
1464
|
-
if (error) {
|
|
1465
|
-
logger.error(`[hdc] ${error.message}`);
|
|
1466
|
-
reject(error);
|
|
1467
|
-
} else {
|
|
1468
|
-
resolve2();
|
|
1469
|
-
}
|
|
1470
|
-
});
|
|
1471
|
-
});
|
|
1472
|
-
}
|
|
1473
2056
|
async function installApp(opts) {
|
|
1474
|
-
const logger = opts.logger ?? noopLogger;
|
|
2057
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1475
2058
|
const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
|
|
1476
|
-
const hapPath =
|
|
1477
|
-
if (!
|
|
2059
|
+
const hapPath = path16.isAbsolute(relativeHapPath) ? relativeHapPath : path16.join(opts.projectDir, relativeHapPath);
|
|
2060
|
+
if (!fs16.existsSync(hapPath)) {
|
|
1478
2061
|
throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
|
|
1479
2062
|
}
|
|
1480
|
-
const
|
|
1481
|
-
|
|
2063
|
+
const result = await hdcExec({ config: opts.config, args: ["install", hapPath], timeoutMs: 3e5 });
|
|
2064
|
+
if (result.stdout.trim()) logger.info(result.stdout.trim());
|
|
2065
|
+
if (result.stderr.trim()) logger.warn(result.stderr.trim());
|
|
2066
|
+
ensureOk(result, `hdc install ${hapPath}`);
|
|
2067
|
+
let bundleName = "";
|
|
2068
|
+
try {
|
|
2069
|
+
bundleName = getBundleName(opts.projectDir);
|
|
2070
|
+
} catch {
|
|
2071
|
+
}
|
|
2072
|
+
return { installed: true, bundleName, hapPath, output: `${result.stdout}${result.stderr}`.trim() };
|
|
2073
|
+
}
|
|
2074
|
+
function detectAaStartFailure(output) {
|
|
2075
|
+
return /failed to start ability|Error Code:\s*\d|Error Message:/i.test(output) ? output.trim() : null;
|
|
2076
|
+
}
|
|
2077
|
+
function parseMainAbilityFromBmDump(stdout) {
|
|
2078
|
+
const start = stdout.indexOf("{");
|
|
2079
|
+
if (start < 0) return null;
|
|
2080
|
+
let info;
|
|
2081
|
+
try {
|
|
2082
|
+
info = JSON.parse(stdout.slice(start));
|
|
2083
|
+
} catch {
|
|
2084
|
+
return null;
|
|
2085
|
+
}
|
|
2086
|
+
const mods = Array.isArray(info?.hapModuleInfos) ? info.hapModuleInfos : [];
|
|
2087
|
+
const abilityOf = (m) => m?.mainElementName || m?.mainAbility || void 0;
|
|
2088
|
+
const isEntry = (m) => m?.moduleType === 1 || m?.moduleType === "1" || m?.moduleType === "entry";
|
|
2089
|
+
const entry = mods.find((m) => isEntry(m) && abilityOf(m));
|
|
2090
|
+
const any = mods.find((m) => abilityOf(m));
|
|
2091
|
+
return entry && abilityOf(entry) || any && abilityOf(any) || null;
|
|
2092
|
+
}
|
|
2093
|
+
async function resolveInstalledMainAbility(opts) {
|
|
2094
|
+
const res = await shell({
|
|
2095
|
+
config: opts.config,
|
|
2096
|
+
command: `bm dump -n ${opts.bundle}`,
|
|
2097
|
+
deviceSerial: opts.deviceSerial,
|
|
2098
|
+
timeoutMs: 15e3,
|
|
2099
|
+
logger: opts.logger
|
|
2100
|
+
});
|
|
2101
|
+
const ability = parseMainAbilityFromBmDump(res.stdout);
|
|
2102
|
+
if (!ability) {
|
|
2103
|
+
throw new OniroError(
|
|
2104
|
+
`Could not determine the main ability for installed bundle ${opts.bundle} (is it installed?). Pass an explicit ability instead.`
|
|
2105
|
+
);
|
|
2106
|
+
}
|
|
2107
|
+
return ability;
|
|
1482
2108
|
}
|
|
1483
2109
|
async function launchApp(opts) {
|
|
1484
|
-
const logger = opts.logger ?? noopLogger;
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
2110
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
2111
|
+
let bundleName;
|
|
2112
|
+
let mainAbility;
|
|
2113
|
+
if (opts.bundleName) {
|
|
2114
|
+
bundleName = opts.bundleName;
|
|
2115
|
+
mainAbility = opts.abilityName ?? await resolveInstalledMainAbility({
|
|
2116
|
+
config: opts.config,
|
|
2117
|
+
bundle: bundleName,
|
|
2118
|
+
deviceSerial: opts.deviceSerial,
|
|
2119
|
+
logger: opts.logger
|
|
2120
|
+
});
|
|
2121
|
+
} else {
|
|
2122
|
+
if (!opts.projectDir) throw new OniroError("launchApp requires either bundleName or projectDir.");
|
|
2123
|
+
bundleName = getBundleName(opts.projectDir);
|
|
2124
|
+
mainAbility = getMainAbility(opts.projectDir, opts.moduleName, opts.abilityName);
|
|
2125
|
+
}
|
|
2126
|
+
const result = await hdcExec({
|
|
2127
|
+
config: opts.config,
|
|
2128
|
+
args: ["shell", "aa", "start", "-a", mainAbility, "-b", bundleName],
|
|
2129
|
+
deviceSerial: opts.deviceSerial
|
|
2130
|
+
});
|
|
2131
|
+
if (result.stdout.trim()) logger.info(result.stdout.trim());
|
|
2132
|
+
if (result.stderr.trim()) logger.warn(result.stderr.trim());
|
|
2133
|
+
ensureOk(result, `hdc shell aa start -a ${mainAbility} -b ${bundleName}`);
|
|
2134
|
+
const failure = detectAaStartFailure(`${result.stdout}
|
|
2135
|
+
${result.stderr}`);
|
|
2136
|
+
if (failure) {
|
|
2137
|
+
throw new OniroError(`Failed to launch ${bundleName} (${mainAbility}): ${failure}`);
|
|
2138
|
+
}
|
|
1489
2139
|
}
|
|
1490
2140
|
function listRunningProcesses(config, options = {}) {
|
|
1491
2141
|
const logger = options.logger ?? noopLogger;
|
|
1492
2142
|
const timeoutMs = options.timeoutMs ?? 1e3;
|
|
1493
2143
|
const hdc = getHdcPath(config);
|
|
1494
|
-
return new Promise((
|
|
1495
|
-
const proc = (0,
|
|
2144
|
+
return new Promise((resolve4, reject) => {
|
|
2145
|
+
const proc = (0, import_node_child_process5.spawn)(hdc, ["track-jpid"]);
|
|
1496
2146
|
const processes = [];
|
|
1497
2147
|
let resolved = false;
|
|
1498
2148
|
proc.stdout.on("data", (data) => {
|
|
@@ -1505,7 +2155,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
1505
2155
|
if (options.targetProcessName && name === options.targetProcessName && !resolved) {
|
|
1506
2156
|
resolved = true;
|
|
1507
2157
|
proc.kill();
|
|
1508
|
-
|
|
2158
|
+
resolve4(pid);
|
|
1509
2159
|
return;
|
|
1510
2160
|
}
|
|
1511
2161
|
}
|
|
@@ -1526,7 +2176,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
1526
2176
|
if (options.targetProcessName) {
|
|
1527
2177
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
1528
2178
|
} else {
|
|
1529
|
-
|
|
2179
|
+
resolve4(processes);
|
|
1530
2180
|
}
|
|
1531
2181
|
}, timeoutMs);
|
|
1532
2182
|
proc.on("close", () => {
|
|
@@ -1536,7 +2186,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
1536
2186
|
if (options.targetProcessName) {
|
|
1537
2187
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
1538
2188
|
} else {
|
|
1539
|
-
|
|
2189
|
+
resolve4(processes);
|
|
1540
2190
|
}
|
|
1541
2191
|
});
|
|
1542
2192
|
});
|
|
@@ -1546,14 +2196,419 @@ async function findAppProcessId(config, projectDir, logger) {
|
|
|
1546
2196
|
const result = await listRunningProcesses(config, { targetProcessName: bundleName, logger });
|
|
1547
2197
|
return result;
|
|
1548
2198
|
}
|
|
2199
|
+
async function findRunningProcess(opts) {
|
|
2200
|
+
const safe = opts.bundle.replace(/'/g, `'\\''`);
|
|
2201
|
+
const res = await shell({
|
|
2202
|
+
config: opts.config,
|
|
2203
|
+
command: `pidof '${safe}'`,
|
|
2204
|
+
deviceSerial: opts.deviceSerial,
|
|
2205
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
2206
|
+
logger: opts.logger
|
|
2207
|
+
});
|
|
2208
|
+
const pid = res.stdout.trim().split(/\s+/).filter(Boolean)[0];
|
|
2209
|
+
if (res.code === 0 && pid && /^\d+$/.test(pid)) return { pid, name: opts.bundle };
|
|
2210
|
+
try {
|
|
2211
|
+
const list = await listRunningProcesses(opts.config, { timeoutMs: 1200, logger: opts.logger });
|
|
2212
|
+
if (Array.isArray(list)) {
|
|
2213
|
+
const mine = (name) => name === opts.bundle || name.startsWith(`${opts.bundle}/`) || name.startsWith(`${opts.bundle}:`);
|
|
2214
|
+
const match = list.find((p) => p.name === opts.bundle) ?? list.find((p) => mine(p.name));
|
|
2215
|
+
if (match) return match;
|
|
2216
|
+
}
|
|
2217
|
+
} catch {
|
|
2218
|
+
}
|
|
2219
|
+
return null;
|
|
2220
|
+
}
|
|
2221
|
+
async function uninstallApp(opts) {
|
|
2222
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
2223
|
+
const res = await hdcExec({
|
|
2224
|
+
config: opts.config,
|
|
2225
|
+
args: ["uninstall", opts.bundle],
|
|
2226
|
+
deviceSerial: opts.deviceSerial,
|
|
2227
|
+
timeoutMs: opts.timeoutMs ?? 12e4
|
|
2228
|
+
});
|
|
2229
|
+
if (res.stdout.trim()) logger.info(res.stdout.trim());
|
|
2230
|
+
if (res.stderr.trim()) logger.warn(res.stderr.trim());
|
|
2231
|
+
ensureOk(res, `hdc uninstall ${opts.bundle}`);
|
|
2232
|
+
}
|
|
2233
|
+
async function forceStop(opts) {
|
|
2234
|
+
const res = await shell({
|
|
2235
|
+
config: opts.config,
|
|
2236
|
+
command: `aa force-stop ${opts.bundle}`,
|
|
2237
|
+
deviceSerial: opts.deviceSerial,
|
|
2238
|
+
timeoutMs: opts.timeoutMs ?? 3e4,
|
|
2239
|
+
logger: opts.logger
|
|
2240
|
+
});
|
|
2241
|
+
ensureOk(res, `hdc shell aa force-stop ${opts.bundle}`);
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
// src/hdc/lifecycle.ts
|
|
2245
|
+
async function isReachable(config, deviceSerial) {
|
|
2246
|
+
try {
|
|
2247
|
+
const res = await shell({ config, command: "echo oniro_ping", deviceSerial, timeoutMs: 5e3 });
|
|
2248
|
+
return res.code === 0 && res.stdout.includes("oniro_ping");
|
|
2249
|
+
} catch {
|
|
2250
|
+
return false;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
var TCP_SERIAL_RE = /^.+:\d+$/;
|
|
2254
|
+
async function reconnectIfTcp(config, deviceSerial, logger) {
|
|
2255
|
+
if (!deviceSerial || !TCP_SERIAL_RE.test(deviceSerial)) return;
|
|
2256
|
+
try {
|
|
2257
|
+
await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
|
|
2258
|
+
} catch {
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
async function waitForBundle(opts) {
|
|
2262
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
2263
|
+
const timeoutMs = opts.timeoutMs ?? 18e4;
|
|
2264
|
+
await waitForCondition({
|
|
2265
|
+
probe: async () => {
|
|
2266
|
+
await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
|
|
2267
|
+
return await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
|
|
2268
|
+
},
|
|
2269
|
+
timeoutMs,
|
|
2270
|
+
pollMs: 2e3,
|
|
2271
|
+
abortSignal: opts.abortSignal,
|
|
2272
|
+
onHeartbeat: (attempt, remaining) => logger.info(`waiting for ${opts.bundle} (attempt ${attempt}, ${Math.floor(remaining / 1e3)}s left)`),
|
|
2273
|
+
timeoutMessage: `Bundle ${opts.bundle} did not start within ${timeoutMs}ms.`
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
2276
|
+
async function waitForBoot(opts) {
|
|
2277
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
2278
|
+
const timeoutMs = opts.timeoutMs ?? 18e4;
|
|
2279
|
+
await waitForCondition({
|
|
2280
|
+
probe: async () => {
|
|
2281
|
+
await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
|
|
2282
|
+
if (!await isReachable(opts.config, opts.deviceSerial)) return false;
|
|
2283
|
+
if (!opts.untilPidOf) return true;
|
|
2284
|
+
return await findRunningProcess({ config: opts.config, bundle: opts.untilPidOf, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
|
|
2285
|
+
},
|
|
2286
|
+
timeoutMs,
|
|
2287
|
+
pollMs: 2e3,
|
|
2288
|
+
abortSignal: opts.abortSignal,
|
|
2289
|
+
onHeartbeat: (attempt, remaining) => logger.info(`waiting for boot (attempt ${attempt}, ${Math.floor(remaining / 1e3)}s left)`),
|
|
2290
|
+
timeoutMessage: `Device did not boot within ${timeoutMs}ms.`
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
async function reboot(opts) {
|
|
2294
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
2295
|
+
const mode = opts.mode ?? "system";
|
|
2296
|
+
const timeoutMs = opts.timeoutMs ?? 18e4;
|
|
2297
|
+
logger.info(`Rebooting device (${mode})...`);
|
|
2298
|
+
try {
|
|
2299
|
+
if (mode === "system") {
|
|
2300
|
+
await paramSet(opts.config, "ohos.startup.powerctrl", "reboot", {
|
|
2301
|
+
deviceSerial: opts.deviceSerial,
|
|
2302
|
+
timeoutMs: 15e3,
|
|
2303
|
+
logger: opts.logger
|
|
2304
|
+
});
|
|
2305
|
+
} else {
|
|
2306
|
+
await shell({ config: opts.config, command: `reboot ${mode}`, deviceSerial: opts.deviceSerial, timeoutMs: 15e3 });
|
|
2307
|
+
}
|
|
2308
|
+
} catch (err) {
|
|
2309
|
+
logger.debug(`reboot command did not return cleanly: ${err.message}`);
|
|
2310
|
+
}
|
|
2311
|
+
if (mode !== "system" || !opts.waitForBundle) return;
|
|
2312
|
+
try {
|
|
2313
|
+
await waitForCondition({
|
|
2314
|
+
probe: async () => !await isReachable(opts.config, opts.deviceSerial),
|
|
2315
|
+
timeoutMs: Math.min(timeoutMs, 3e4),
|
|
2316
|
+
pollMs: 1500,
|
|
2317
|
+
abortSignal: opts.abortSignal,
|
|
2318
|
+
timeoutMessage: "device did not become unreachable"
|
|
2319
|
+
});
|
|
2320
|
+
} catch {
|
|
2321
|
+
}
|
|
2322
|
+
await waitForBundle({
|
|
2323
|
+
config: opts.config,
|
|
2324
|
+
bundle: opts.waitForBundle,
|
|
2325
|
+
deviceSerial: opts.deviceSerial,
|
|
2326
|
+
timeoutMs,
|
|
2327
|
+
abortSignal: opts.abortSignal,
|
|
2328
|
+
logger: opts.logger
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
// src/hdc/screen.ts
|
|
2333
|
+
var fs17 = __toESM(require("fs"), 1);
|
|
2334
|
+
var os8 = __toESM(require("os"), 1);
|
|
2335
|
+
var path17 = __toESM(require("path"), 1);
|
|
2336
|
+
var import_node_crypto = require("crypto");
|
|
2337
|
+
function tmpName(ext) {
|
|
2338
|
+
return path17.join(os8.tmpdir(), `oniro_screen_${(0, import_node_crypto.randomBytes)(6).toString("hex")}.${ext}`);
|
|
2339
|
+
}
|
|
2340
|
+
async function takeScreenshot(opts) {
|
|
2341
|
+
const remote = "/data/local/tmp/oniro_screenshot.jpeg";
|
|
2342
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
2343
|
+
const cap = await shell({ config: opts.config, command: `snapshot_display -f ${remote}`, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2344
|
+
ensureOk(cap, `snapshot_display -f ${remote}`);
|
|
2345
|
+
const local = tmpName("jpeg");
|
|
2346
|
+
try {
|
|
2347
|
+
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2348
|
+
const pixels = await fs17.promises.readFile(local);
|
|
2349
|
+
const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
|
|
2350
|
+
return { pixels, width: size.width, height: size.height };
|
|
2351
|
+
} finally {
|
|
2352
|
+
fs17.promises.unlink(local).catch(() => {
|
|
2353
|
+
});
|
|
2354
|
+
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
async function captureBurst(opts) {
|
|
2359
|
+
const count = Math.max(1, Math.floor(opts.count));
|
|
2360
|
+
const remoteDir = `/data/local/tmp/oniro_burst_${(0, import_node_crypto.randomBytes)(6).toString("hex")}`;
|
|
2361
|
+
const sleepSec = opts.intervalMs > 0 ? (opts.intervalMs / 1e3).toFixed(3) : "0";
|
|
2362
|
+
const loop = `mkdir -p ${remoteDir}; i=0; while [ $i -lt ${count} ]; do snapshot_display -f ${remoteDir}/f$i.jpeg; [ ${count} -gt 1 ] && sleep ${sleepSec}; i=$((i+1)); done`;
|
|
2363
|
+
const cap = await shell({ config: opts.config, command: loop, deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 12e4 });
|
|
2364
|
+
ensureOk(cap, "snapshot_display burst");
|
|
2365
|
+
const frames = [];
|
|
2366
|
+
try {
|
|
2367
|
+
for (let i = 0; i < count; i++) {
|
|
2368
|
+
const local = tmpName(`burst_${i}.jpeg`);
|
|
2369
|
+
try {
|
|
2370
|
+
await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
|
|
2371
|
+
frames.push(await fs17.promises.readFile(local));
|
|
2372
|
+
} finally {
|
|
2373
|
+
fs17.promises.unlink(local).catch(() => {
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
} finally {
|
|
2378
|
+
void shell({ config: opts.config, command: `rm -rf ${remoteDir}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2379
|
+
});
|
|
2380
|
+
}
|
|
2381
|
+
return frames;
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
// src/hdc/input.ts
|
|
2385
|
+
function reqNum(value, name, type) {
|
|
2386
|
+
if (value === void 0 || Number.isNaN(value)) {
|
|
2387
|
+
throw new OniroError(`send input "${type}" requires a numeric ${name}.`);
|
|
2388
|
+
}
|
|
2389
|
+
return value;
|
|
2390
|
+
}
|
|
2391
|
+
function buildInputCommand(opts) {
|
|
2392
|
+
switch (opts.type) {
|
|
2393
|
+
case "click":
|
|
2394
|
+
case "doubleClick":
|
|
2395
|
+
case "longClick":
|
|
2396
|
+
return `uitest uiInput ${opts.type} ${reqNum(opts.pxX, "pxX", opts.type)} ${reqNum(opts.pxY, "pxY", opts.type)}`;
|
|
2397
|
+
case "swipe":
|
|
2398
|
+
case "drag":
|
|
2399
|
+
case "fling": {
|
|
2400
|
+
const base = `uitest uiInput ${opts.type} ${reqNum(opts.pxX, "pxX", opts.type)} ${reqNum(opts.pxY, "pxY", opts.type)} ${reqNum(opts.pxX2, "pxX2", opts.type)} ${reqNum(opts.pxY2, "pxY2", opts.type)}`;
|
|
2401
|
+
return opts.speed !== void 0 ? `${base} ${opts.speed}` : base;
|
|
2402
|
+
}
|
|
2403
|
+
case "keyEvent":
|
|
2404
|
+
if (opts.key === void 0 || opts.key === "") throw new OniroError('send input "keyEvent" requires a key.');
|
|
2405
|
+
return `uitest uiInput keyEvent ${opts.key}`;
|
|
2406
|
+
case "inputText": {
|
|
2407
|
+
const t = String(opts.text ?? "").replace(/'/g, `'\\''`);
|
|
2408
|
+
if (opts.pxX !== void 0 && opts.pxY !== void 0) {
|
|
2409
|
+
return `uitest uiInput inputText ${opts.pxX} ${opts.pxY} '${t}'`;
|
|
2410
|
+
}
|
|
2411
|
+
return `uitest uiInput text '${t}'`;
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
async function sendInput(opts) {
|
|
2416
|
+
const cmd = buildInputCommand(opts);
|
|
2417
|
+
const res = await shellChecked({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
|
|
2418
|
+
ensureRemoteOk(res, `hdc shell ${cmd}`);
|
|
2419
|
+
}
|
|
2420
|
+
function buildGestureCommands(waypoints) {
|
|
2421
|
+
if (waypoints.length < 2) {
|
|
2422
|
+
throw new OniroError("sendGesture requires at least two waypoints.");
|
|
2423
|
+
}
|
|
2424
|
+
const cmds = [];
|
|
2425
|
+
for (let i = 1; i < waypoints.length; i++) {
|
|
2426
|
+
const a = waypoints[i - 1];
|
|
2427
|
+
const b = waypoints[i];
|
|
2428
|
+
const dt = b.t - a.t;
|
|
2429
|
+
const dist = Math.hypot(b.x - a.x, b.y - a.y);
|
|
2430
|
+
let cmd = `uitest uiInput drag ${a.x} ${a.y} ${b.x} ${b.y}`;
|
|
2431
|
+
if (dt > 0 && dist > 0) cmd += ` ${Math.round(dist / (dt / 1e3))}`;
|
|
2432
|
+
cmds.push(cmd);
|
|
2433
|
+
}
|
|
2434
|
+
return cmds;
|
|
2435
|
+
}
|
|
2436
|
+
function buildRawTouchCommand(events) {
|
|
2437
|
+
if (events.length === 0) throw new OniroError("sendRawTouch requires at least one event.");
|
|
2438
|
+
const ops = [];
|
|
2439
|
+
let prev = null;
|
|
2440
|
+
for (const ev of events) {
|
|
2441
|
+
if (prev && ev.t > prev.t) ops.push(`-i ${ev.t - prev.t}`);
|
|
2442
|
+
if (ev.type === "down") ops.push(`-d ${ev.x} ${ev.y}`);
|
|
2443
|
+
else if (ev.type === "up") ops.push(`-u ${ev.x} ${ev.y}`);
|
|
2444
|
+
else ops.push(`-m ${prev ? `${prev.x} ${prev.y} ` : ""}${ev.x} ${ev.y}`);
|
|
2445
|
+
prev = ev;
|
|
2446
|
+
}
|
|
2447
|
+
return `uinput -T ${ops.join(" ")}`;
|
|
2448
|
+
}
|
|
2449
|
+
function waypointsToRawEvents(waypoints, holdStartMs = 0, holdEndMs = 0) {
|
|
2450
|
+
if (waypoints.length < 2) throw new OniroError("sendGesture requires at least two waypoints.");
|
|
2451
|
+
const first = waypoints[0];
|
|
2452
|
+
const events = [{ type: "down", x: first.x, y: first.y, t: 0 }];
|
|
2453
|
+
for (let i = 1; i < waypoints.length; i++) {
|
|
2454
|
+
const w = waypoints[i];
|
|
2455
|
+
events.push({ type: "move", x: w.x, y: w.y, t: w.t + holdStartMs });
|
|
2456
|
+
}
|
|
2457
|
+
const last = waypoints[waypoints.length - 1];
|
|
2458
|
+
events.push({ type: "up", x: last.x, y: last.y, t: last.t + holdStartMs + holdEndMs });
|
|
2459
|
+
return events;
|
|
2460
|
+
}
|
|
2461
|
+
var UITEST_VELOCITY_MIN = 200;
|
|
2462
|
+
var UITEST_VELOCITY_MAX = 4e4;
|
|
2463
|
+
function buildGestureUitestCommand(waypoints, opts = {}) {
|
|
2464
|
+
if (waypoints.length < 2) throw new OniroError("sendGesture requires at least two waypoints.");
|
|
2465
|
+
const a = waypoints[0];
|
|
2466
|
+
const b = waypoints[waypoints.length - 1];
|
|
2467
|
+
const dist = Math.hypot(b.x - a.x, b.y - a.y);
|
|
2468
|
+
const dtMs = b.t - a.t;
|
|
2469
|
+
const op = (opts.holdStartMs ?? 0) > 0 ? "drag" : "swipe";
|
|
2470
|
+
let cmd = `uitest uiInput ${op} ${a.x} ${a.y} ${b.x} ${b.y}`;
|
|
2471
|
+
if (dtMs > 0 && dist > 0) {
|
|
2472
|
+
const v = Math.round(dist / (dtMs / 1e3));
|
|
2473
|
+
cmd += ` ${Math.max(UITEST_VELOCITY_MIN, Math.min(UITEST_VELOCITY_MAX, v))}`;
|
|
2474
|
+
}
|
|
2475
|
+
return cmd;
|
|
2476
|
+
}
|
|
2477
|
+
async function sendGesture(opts) {
|
|
2478
|
+
if ((opts.holdEndMs ?? 0) > 0) {
|
|
2479
|
+
opts.logger?.warn(
|
|
2480
|
+
"sendGesture: hold-end has no uitest equivalent and is ignored; use sendRawTouch for a trailing hold."
|
|
2481
|
+
);
|
|
2482
|
+
}
|
|
2483
|
+
if (opts.waypoints.length > 2) {
|
|
2484
|
+
opts.logger?.debug(
|
|
2485
|
+
`sendGesture: ${opts.waypoints.length} waypoints collapsed to first\u2192last (uitest has no polyline path).`
|
|
2486
|
+
);
|
|
2487
|
+
}
|
|
2488
|
+
const cmd = buildGestureUitestCommand(opts.waypoints, { holdStartMs: opts.holdStartMs });
|
|
2489
|
+
const res = await shellChecked({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
|
|
2490
|
+
ensureRemoteOk(res, `hdc shell ${cmd}`);
|
|
2491
|
+
}
|
|
2492
|
+
async function sendRawTouch(opts) {
|
|
2493
|
+
const cmd = buildRawTouchCommand(opts.events);
|
|
2494
|
+
const res = await shellChecked({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
|
|
2495
|
+
ensureRemoteOk(res, `hdc shell ${cmd}`);
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
// src/hdc/dump.ts
|
|
2499
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
2500
|
+
var os9 = __toESM(require("os"), 1);
|
|
2501
|
+
var path18 = __toESM(require("path"), 1);
|
|
2502
|
+
var import_node_crypto2 = require("crypto");
|
|
2503
|
+
function parseBounds(s) {
|
|
2504
|
+
const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
|
|
2505
|
+
if (!m) return null;
|
|
2506
|
+
return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
|
|
2507
|
+
}
|
|
2508
|
+
var STRUCTURAL_TYPES = /* @__PURE__ */ new Set([
|
|
2509
|
+
"Stack",
|
|
2510
|
+
"Column",
|
|
2511
|
+
"Row",
|
|
2512
|
+
"Flex",
|
|
2513
|
+
"__Common__",
|
|
2514
|
+
"Grid",
|
|
2515
|
+
"GridItem",
|
|
2516
|
+
"List",
|
|
2517
|
+
"ListItem",
|
|
2518
|
+
"Scroll",
|
|
2519
|
+
"Swiper",
|
|
2520
|
+
"SwiperContent",
|
|
2521
|
+
"Navigator",
|
|
2522
|
+
"Tabs",
|
|
2523
|
+
"TabContent",
|
|
2524
|
+
"RelativeContainer",
|
|
2525
|
+
"WaterFlow",
|
|
2526
|
+
"FlowItem"
|
|
2527
|
+
]);
|
|
2528
|
+
var FLAG_MAP = [
|
|
2529
|
+
["clickable", "click"],
|
|
2530
|
+
["checkable", "check"],
|
|
2531
|
+
["checked", "checked"],
|
|
2532
|
+
["selected", "sel"],
|
|
2533
|
+
["focused", "focus"],
|
|
2534
|
+
["scrollable", "scroll"],
|
|
2535
|
+
["longClickable", "longClick"]
|
|
2536
|
+
];
|
|
2537
|
+
function pruneLayout(node, display) {
|
|
2538
|
+
const a = node.attributes ?? {};
|
|
2539
|
+
const px = parseBounds(a.bounds);
|
|
2540
|
+
const out = {};
|
|
2541
|
+
if (px && display) {
|
|
2542
|
+
const w = display.width || 1;
|
|
2543
|
+
const h = display.height || 1;
|
|
2544
|
+
out.b = [
|
|
2545
|
+
+(px[0] / w).toFixed(3),
|
|
2546
|
+
+(px[1] / h).toFixed(3),
|
|
2547
|
+
+(px[2] / w).toFixed(3),
|
|
2548
|
+
+(px[3] / h).toFixed(3)
|
|
2549
|
+
];
|
|
2550
|
+
out.c = [+((px[0] + px[2]) / 2 / w).toFixed(3), +((px[1] + px[3]) / 2 / h).toFixed(3)];
|
|
2551
|
+
}
|
|
2552
|
+
if (a.type && !STRUCTURAL_TYPES.has(a.type)) out.type = a.type;
|
|
2553
|
+
if (a.text) out.text = a.text;
|
|
2554
|
+
if (a.description) out.desc = a.description;
|
|
2555
|
+
if (a.id) out.id = a.id;
|
|
2556
|
+
if (a.key && a.key !== a.id) out.key = a.key;
|
|
2557
|
+
if (a.bundleName) out.bundle = a.bundleName;
|
|
2558
|
+
for (const [attr, short] of FLAG_MAP) {
|
|
2559
|
+
if (a[attr] === "true") out[short] = true;
|
|
2560
|
+
}
|
|
2561
|
+
const children = [];
|
|
2562
|
+
for (const c of node.children ?? []) {
|
|
2563
|
+
const pc = pruneLayout(c, display);
|
|
2564
|
+
if (pc) {
|
|
2565
|
+
if (Array.isArray(pc)) children.push(...pc);
|
|
2566
|
+
else children.push(pc);
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
const hasSignal = Boolean(
|
|
2570
|
+
out.text || out.desc || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
|
|
2571
|
+
);
|
|
2572
|
+
if (!hasSignal && children.length === 0) return null;
|
|
2573
|
+
if (!hasSignal) return children.length === 1 ? children[0] : children;
|
|
2574
|
+
if (children.length) out.children = children;
|
|
2575
|
+
return out;
|
|
2576
|
+
}
|
|
2577
|
+
async function dumpLayout(opts) {
|
|
2578
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
2579
|
+
const bundleArg = opts.bundleName ? ` -b '${opts.bundleName.replace(/'/g, `'\\''`)}'` : "";
|
|
2580
|
+
const dump = await shell({ config: opts.config, command: `uitest dumpLayout${bundleArg}`, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2581
|
+
ensureOk(dump, "uitest dumpLayout");
|
|
2582
|
+
const m = dump.stdout.match(/saved to:\s*(\S+)/i);
|
|
2583
|
+
if (!m) throw new OniroError(`Could not parse dumpLayout output:
|
|
2584
|
+
${dump.stdout}`);
|
|
2585
|
+
const remote = m[1];
|
|
2586
|
+
const local = path18.join(os9.tmpdir(), `oniro_layout_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}.json`);
|
|
2587
|
+
try {
|
|
2588
|
+
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2589
|
+
const parsed = JSON.parse(await fs18.promises.readFile(local, "utf8"));
|
|
2590
|
+
const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
|
|
2591
|
+
if (opts.prune === false) {
|
|
2592
|
+
return { display, tree: parsed };
|
|
2593
|
+
}
|
|
2594
|
+
const prunedRaw = pruneLayout(parsed, display);
|
|
2595
|
+
const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
|
|
2596
|
+
return { display, tree };
|
|
2597
|
+
} finally {
|
|
2598
|
+
fs18.promises.unlink(local).catch(() => {
|
|
2599
|
+
});
|
|
2600
|
+
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2601
|
+
});
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
1549
2604
|
|
|
1550
2605
|
// src/hdc/hilog.ts
|
|
1551
|
-
var
|
|
2606
|
+
var import_node_child_process6 = require("child_process");
|
|
1552
2607
|
function setHilogLevel(opts) {
|
|
1553
2608
|
const logger = opts.logger ?? noopLogger;
|
|
1554
2609
|
const hdc = getHdcPath(opts.config);
|
|
1555
|
-
return new Promise((
|
|
1556
|
-
const child = (0,
|
|
2610
|
+
return new Promise((resolve4, reject) => {
|
|
2611
|
+
const child = (0, import_node_child_process6.spawn)(hdc, ["shell", "hilog", "-b", opts.level]);
|
|
1557
2612
|
let stderr = "";
|
|
1558
2613
|
child.stderr.on("data", (chunk) => {
|
|
1559
2614
|
stderr += chunk.toString();
|
|
@@ -1564,7 +2619,7 @@ function setHilogLevel(opts) {
|
|
|
1564
2619
|
});
|
|
1565
2620
|
child.once("close", (code) => {
|
|
1566
2621
|
if (code === 0) {
|
|
1567
|
-
|
|
2622
|
+
resolve4();
|
|
1568
2623
|
} else {
|
|
1569
2624
|
const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
|
|
1570
2625
|
logger.error(`[hilog] ${msg}`);
|
|
@@ -1575,11 +2630,16 @@ function setHilogLevel(opts) {
|
|
|
1575
2630
|
}
|
|
1576
2631
|
function streamHilog(opts) {
|
|
1577
2632
|
const hdc = getHdcPath(opts.config);
|
|
1578
|
-
const args = [
|
|
2633
|
+
const args = [];
|
|
2634
|
+
if (opts.deviceSerial) args.push("-t", opts.deviceSerial);
|
|
2635
|
+
args.push("shell", "hilog");
|
|
1579
2636
|
if (opts.processId && opts.processId.trim() !== "") {
|
|
1580
2637
|
args.push("-P", opts.processId.trim());
|
|
1581
2638
|
}
|
|
1582
|
-
|
|
2639
|
+
if (opts.domain !== void 0 && `${opts.domain}` !== "") {
|
|
2640
|
+
args.push("-D", `${opts.domain}`);
|
|
2641
|
+
}
|
|
2642
|
+
return (0, import_node_child_process6.spawn)(hdc, args);
|
|
1583
2643
|
}
|
|
1584
2644
|
var HILOG_LINE_RE = /^(\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+(\d+)\s+(\d+)\s+([DIWEF])\s+(\S+):\s+(.*)$/;
|
|
1585
2645
|
function parseHilogLine(line) {
|
|
@@ -1594,30 +2654,194 @@ function parseHilogLine(line) {
|
|
|
1594
2654
|
message: match[6]
|
|
1595
2655
|
};
|
|
1596
2656
|
}
|
|
2657
|
+
function entryText(e) {
|
|
2658
|
+
return `${e.tag}: ${e.message}`;
|
|
2659
|
+
}
|
|
2660
|
+
function dedupEntries(entries) {
|
|
2661
|
+
const out = [];
|
|
2662
|
+
let lastKey = "";
|
|
2663
|
+
for (const e of entries) {
|
|
2664
|
+
const key = `${e.level}|${e.tag}|${e.message}`;
|
|
2665
|
+
if (key === lastKey) continue;
|
|
2666
|
+
out.push(e);
|
|
2667
|
+
lastKey = key;
|
|
2668
|
+
}
|
|
2669
|
+
return out;
|
|
2670
|
+
}
|
|
2671
|
+
async function resolveProcessId(config, bundle, deviceSerial) {
|
|
2672
|
+
if (!bundle) return void 0;
|
|
2673
|
+
const proc = await findRunningProcess({ config, bundle, deviceSerial });
|
|
2674
|
+
return proc?.pid;
|
|
2675
|
+
}
|
|
2676
|
+
function waitForLog(opts) {
|
|
2677
|
+
return new Promise((resolve4, reject) => {
|
|
2678
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
2679
|
+
let settled = false;
|
|
2680
|
+
let child = null;
|
|
2681
|
+
let reconnectTimer;
|
|
2682
|
+
let buf = "";
|
|
2683
|
+
const cleanup = () => {
|
|
2684
|
+
clearTimeout(timer);
|
|
2685
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
2686
|
+
opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
2687
|
+
if (child) {
|
|
2688
|
+
try {
|
|
2689
|
+
child.kill("SIGKILL");
|
|
2690
|
+
} catch {
|
|
2691
|
+
}
|
|
2692
|
+
child = null;
|
|
2693
|
+
}
|
|
2694
|
+
};
|
|
2695
|
+
const finish = (entry) => {
|
|
2696
|
+
if (settled) return;
|
|
2697
|
+
settled = true;
|
|
2698
|
+
cleanup();
|
|
2699
|
+
resolve4(entry);
|
|
2700
|
+
};
|
|
2701
|
+
const fail = (err) => {
|
|
2702
|
+
if (settled) return;
|
|
2703
|
+
settled = true;
|
|
2704
|
+
cleanup();
|
|
2705
|
+
reject(err);
|
|
2706
|
+
};
|
|
2707
|
+
const onAbort = () => fail(new CancelledError("waitForLog cancelled."));
|
|
2708
|
+
const timer = setTimeout(
|
|
2709
|
+
() => fail(new OniroError(`No log line matched ${String(opts.pattern)} within ${opts.timeoutMs}ms.`)),
|
|
2710
|
+
opts.timeoutMs
|
|
2711
|
+
);
|
|
2712
|
+
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
2713
|
+
const spawnStream = async () => {
|
|
2714
|
+
if (settled) return;
|
|
2715
|
+
const processId = await resolveProcessId(opts.config, opts.bundle, opts.deviceSerial);
|
|
2716
|
+
if (settled) return;
|
|
2717
|
+
const stream = streamHilog({ config: opts.config, processId, domain: opts.domain, deviceSerial: opts.deviceSerial });
|
|
2718
|
+
child = stream;
|
|
2719
|
+
stream.stdout.on("data", (d) => {
|
|
2720
|
+
buf += d.toString();
|
|
2721
|
+
let idx;
|
|
2722
|
+
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
2723
|
+
const line = buf.slice(0, idx);
|
|
2724
|
+
buf = buf.slice(idx + 1);
|
|
2725
|
+
const entry = parseHilogLine(line);
|
|
2726
|
+
if (entry && opts.pattern.test(entryText(entry))) {
|
|
2727
|
+
finish(entry);
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
});
|
|
2732
|
+
const onEnd = () => {
|
|
2733
|
+
if (settled || child !== stream) return;
|
|
2734
|
+
child = null;
|
|
2735
|
+
if (Date.now() < deadline) {
|
|
2736
|
+
reconnectTimer = setTimeout(() => void spawnStream(), 1e3);
|
|
2737
|
+
}
|
|
2738
|
+
};
|
|
2739
|
+
stream.once("close", onEnd);
|
|
2740
|
+
stream.once("error", onEnd);
|
|
2741
|
+
};
|
|
2742
|
+
void spawnStream();
|
|
2743
|
+
});
|
|
2744
|
+
}
|
|
2745
|
+
function watchLog(opts) {
|
|
2746
|
+
return new Promise((resolve4, reject) => {
|
|
2747
|
+
const matches = [];
|
|
2748
|
+
let settled = false;
|
|
2749
|
+
let child = null;
|
|
2750
|
+
let buf = "";
|
|
2751
|
+
const cleanup = () => {
|
|
2752
|
+
clearTimeout(timer);
|
|
2753
|
+
opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
2754
|
+
if (child) {
|
|
2755
|
+
try {
|
|
2756
|
+
child.kill("SIGKILL");
|
|
2757
|
+
} catch {
|
|
2758
|
+
}
|
|
2759
|
+
child = null;
|
|
2760
|
+
}
|
|
2761
|
+
};
|
|
2762
|
+
const done = () => {
|
|
2763
|
+
if (settled) return;
|
|
2764
|
+
settled = true;
|
|
2765
|
+
cleanup();
|
|
2766
|
+
resolve4(opts.dedup === false ? matches : dedupEntries(matches));
|
|
2767
|
+
};
|
|
2768
|
+
const onAbort = () => {
|
|
2769
|
+
if (settled) return;
|
|
2770
|
+
settled = true;
|
|
2771
|
+
cleanup();
|
|
2772
|
+
reject(new CancelledError("watchLog cancelled."));
|
|
2773
|
+
};
|
|
2774
|
+
const timer = setTimeout(done, opts.durationMs);
|
|
2775
|
+
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
2776
|
+
void (async () => {
|
|
2777
|
+
const processId = await resolveProcessId(opts.config, opts.bundle, opts.deviceSerial);
|
|
2778
|
+
if (settled) return;
|
|
2779
|
+
const stream = streamHilog({ config: opts.config, processId, domain: opts.domain, deviceSerial: opts.deviceSerial });
|
|
2780
|
+
child = stream;
|
|
2781
|
+
stream.stdout.on("data", (d) => {
|
|
2782
|
+
buf += d.toString();
|
|
2783
|
+
let idx;
|
|
2784
|
+
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
2785
|
+
const line = buf.slice(0, idx);
|
|
2786
|
+
buf = buf.slice(idx + 1);
|
|
2787
|
+
const entry = parseHilogLine(line);
|
|
2788
|
+
if (entry && opts.pattern.test(entryText(entry))) matches.push(entry);
|
|
2789
|
+
}
|
|
2790
|
+
});
|
|
2791
|
+
})();
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
async function dumpLog(opts) {
|
|
2795
|
+
let pipe = "hilog -x";
|
|
2796
|
+
if (opts.domain !== void 0 && `${opts.domain}` !== "") pipe += ` -D ${opts.domain}`;
|
|
2797
|
+
if (opts.bundle) {
|
|
2798
|
+
const proc = await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial });
|
|
2799
|
+
if (!proc) return [];
|
|
2800
|
+
pipe += ` | grep -E '\\b${proc.pid}\\b'`;
|
|
2801
|
+
}
|
|
2802
|
+
if (opts.grep) {
|
|
2803
|
+
const safeGrep = opts.grep.replace(/'/g, `'\\''`);
|
|
2804
|
+
pipe += ` | grep -E '${safeGrep}'`;
|
|
2805
|
+
}
|
|
2806
|
+
if (opts.lines && opts.lines > 0) pipe += ` | tail -n ${Math.floor(opts.lines)}`;
|
|
2807
|
+
const res = await shell({
|
|
2808
|
+
config: opts.config,
|
|
2809
|
+
command: pipe,
|
|
2810
|
+
deviceSerial: opts.deviceSerial,
|
|
2811
|
+
timeoutMs: opts.timeoutMs ?? 3e4,
|
|
2812
|
+
logger: opts.logger
|
|
2813
|
+
});
|
|
2814
|
+
return res.stdout.split("\n").map((line) => parseHilogLine(line)).filter((e) => e !== null);
|
|
2815
|
+
}
|
|
1597
2816
|
|
|
1598
2817
|
// src/build/runHvigorw.ts
|
|
1599
|
-
var
|
|
1600
|
-
var
|
|
1601
|
-
var
|
|
2818
|
+
var fs19 = __toESM(require("fs"), 1);
|
|
2819
|
+
var path19 = __toESM(require("path"), 1);
|
|
2820
|
+
var import_node_child_process7 = require("child_process");
|
|
2821
|
+
function buildHvigorwArgs(opts) {
|
|
2822
|
+
const args = [opts.task ?? "assembleHap", "--mode", "module"];
|
|
2823
|
+
args.push("-p", `product=${opts.product ?? "default"}`);
|
|
2824
|
+
if (opts.module) args.push("-p", `module=${opts.module}`);
|
|
2825
|
+
if (opts.buildMode) args.push("-p", `buildMode=${opts.buildMode}`);
|
|
2826
|
+
args.push("--stacktrace", "--no-daemon");
|
|
2827
|
+
if (opts.parallel === false) args.push("--no-parallel");
|
|
2828
|
+
if (opts.extraArgs?.length) args.push(...opts.extraArgs);
|
|
2829
|
+
return args;
|
|
2830
|
+
}
|
|
1602
2831
|
function runHvigorw(opts) {
|
|
1603
2832
|
const { config, projectDir } = opts;
|
|
1604
2833
|
const logger = opts.logger ?? noopLogger;
|
|
1605
|
-
if (!
|
|
2834
|
+
if (!fs19.existsSync(path19.join(projectDir, "build-profile.json5"))) {
|
|
1606
2835
|
throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
|
|
1607
2836
|
}
|
|
1608
|
-
if (!
|
|
2837
|
+
if (!fs19.existsSync(getCmdToolsPath(config))) {
|
|
1609
2838
|
throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
|
|
1610
2839
|
}
|
|
1611
2840
|
const hvigorw = getHvigorwPath(config, projectDir);
|
|
1612
|
-
const args =
|
|
1613
|
-
args.push("-p", `product=${opts.product ?? "default"}`);
|
|
1614
|
-
if (opts.module) args.push("-p", `module=${opts.module}`);
|
|
1615
|
-
if (opts.buildMode) args.push("-p", `buildMode=${opts.buildMode}`);
|
|
1616
|
-
args.push("--stacktrace", "--no-parallel", "--no-daemon");
|
|
1617
|
-
if (opts.extraArgs?.length) args.push(...opts.extraArgs);
|
|
2841
|
+
const args = buildHvigorwArgs(opts);
|
|
1618
2842
|
if (process.platform !== "win32") {
|
|
1619
2843
|
try {
|
|
1620
|
-
|
|
2844
|
+
fs19.chmodSync(hvigorw, 493);
|
|
1621
2845
|
} catch {
|
|
1622
2846
|
}
|
|
1623
2847
|
}
|
|
@@ -1625,9 +2849,26 @@ function runHvigorw(opts) {
|
|
|
1625
2849
|
...process.env,
|
|
1626
2850
|
OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
|
|
1627
2851
|
};
|
|
1628
|
-
return new Promise((
|
|
2852
|
+
return new Promise((resolve4, reject) => {
|
|
2853
|
+
if (opts.abortSignal?.aborted) {
|
|
2854
|
+
reject(new CancelledError("hvigorw build cancelled."));
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
1629
2857
|
logger.info(`[build] ${hvigorw} ${args.join(" ")}`);
|
|
1630
|
-
const child = (0,
|
|
2858
|
+
const child = (0, import_node_child_process7.spawn)(hvigorw, args, { cwd: projectDir, env, shell: false });
|
|
2859
|
+
let settled = false;
|
|
2860
|
+
const cleanup = () => opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
2861
|
+
const onAbort = () => {
|
|
2862
|
+
if (settled) return;
|
|
2863
|
+
settled = true;
|
|
2864
|
+
cleanup();
|
|
2865
|
+
try {
|
|
2866
|
+
child.kill("SIGKILL");
|
|
2867
|
+
} catch {
|
|
2868
|
+
}
|
|
2869
|
+
reject(new CancelledError("hvigorw build cancelled."));
|
|
2870
|
+
};
|
|
2871
|
+
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1631
2872
|
child.stdout.on("data", (chunk) => {
|
|
1632
2873
|
const text = chunk.toString();
|
|
1633
2874
|
opts.onOutput?.(text, "stdout");
|
|
@@ -1638,15 +2879,153 @@ function runHvigorw(opts) {
|
|
|
1638
2879
|
opts.onOutput?.(text, "stderr");
|
|
1639
2880
|
logger.warn(text.trimEnd());
|
|
1640
2881
|
});
|
|
1641
|
-
child.on("error", (err) =>
|
|
2882
|
+
child.on("error", (err) => {
|
|
2883
|
+
if (settled) return;
|
|
2884
|
+
settled = true;
|
|
2885
|
+
cleanup();
|
|
2886
|
+
reject(new OniroError(`Failed to start hvigorw: ${err.message}`, err));
|
|
2887
|
+
});
|
|
1642
2888
|
child.on("close", (code) => {
|
|
2889
|
+
if (settled) return;
|
|
2890
|
+
settled = true;
|
|
2891
|
+
cleanup();
|
|
1643
2892
|
const exitCode = code ?? 1;
|
|
1644
|
-
if (exitCode === 0)
|
|
2893
|
+
if (exitCode === 0) resolve4({ exitCode });
|
|
1645
2894
|
else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
|
|
1646
2895
|
});
|
|
1647
2896
|
});
|
|
1648
2897
|
}
|
|
1649
2898
|
|
|
2899
|
+
// src/build/discoverHaps.ts
|
|
2900
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
2901
|
+
var path20 = __toESM(require("path"), 1);
|
|
2902
|
+
function moduleOfHap(hapFileName) {
|
|
2903
|
+
const dash = hapFileName.indexOf("-");
|
|
2904
|
+
return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
|
|
2905
|
+
}
|
|
2906
|
+
async function discoverHaps(opts) {
|
|
2907
|
+
const { projectDir } = opts;
|
|
2908
|
+
const maxDepth = opts.maxDepth ?? 8;
|
|
2909
|
+
const result = {};
|
|
2910
|
+
const walk = async (dir, depth) => {
|
|
2911
|
+
if (depth > maxDepth) return;
|
|
2912
|
+
const entries = await fs20.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2913
|
+
for (const e of entries) {
|
|
2914
|
+
const full = path20.join(dir, e.name);
|
|
2915
|
+
if (e.isDirectory()) {
|
|
2916
|
+
if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
|
|
2917
|
+
await walk(full, depth + 1);
|
|
2918
|
+
} else if (e.isFile() && e.name.endsWith(".hap")) {
|
|
2919
|
+
const moduleName = moduleOfHap(e.name);
|
|
2920
|
+
const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
|
|
2921
|
+
if (e.name.includes("unsigned")) bucket.unsigned.push(full);
|
|
2922
|
+
else if (e.name.includes("signed")) bucket.signed.push(full);
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
};
|
|
2926
|
+
await walk(projectDir, 0);
|
|
2927
|
+
return result;
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
// src/build/ohpm.ts
|
|
2931
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
2932
|
+
var path21 = __toESM(require("path"), 1);
|
|
2933
|
+
function runOhpm(opts) {
|
|
2934
|
+
return runProcess({
|
|
2935
|
+
command: getOhpmPath(opts.config),
|
|
2936
|
+
args: [...opts.args],
|
|
2937
|
+
cwd: opts.projectDir,
|
|
2938
|
+
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
2939
|
+
abortSignal: opts.abortSignal,
|
|
2940
|
+
onOutput: opts.onOutput,
|
|
2941
|
+
logger: opts.logger
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
async function ensureOhModules(opts) {
|
|
2945
|
+
if (fs21.existsSync(path21.join(opts.projectDir, "oh_modules"))) {
|
|
2946
|
+
return { installed: false };
|
|
2947
|
+
}
|
|
2948
|
+
const res = await runOhpm({
|
|
2949
|
+
config: opts.config,
|
|
2950
|
+
projectDir: opts.projectDir,
|
|
2951
|
+
args: ["install", "--all"],
|
|
2952
|
+
abortSignal: opts.abortSignal,
|
|
2953
|
+
onOutput: opts.onOutput,
|
|
2954
|
+
logger: opts.logger
|
|
2955
|
+
});
|
|
2956
|
+
ensureOk(res, "ohpm install --all");
|
|
2957
|
+
return { installed: true };
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
// src/build/codelinter.ts
|
|
2961
|
+
var FINDING_RE = /^(.*?):(\d+):(?:\d+:)?\s*(error|warning|warn|info)\b[:\s-]*(.*)$/i;
|
|
2962
|
+
var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
|
|
2963
|
+
function parseCodelinterFindings(output) {
|
|
2964
|
+
const findings = [];
|
|
2965
|
+
for (const raw of output.split("\n")) {
|
|
2966
|
+
const m = FINDING_RE.exec(raw.trim());
|
|
2967
|
+
if (!m) continue;
|
|
2968
|
+
let message = m[4].trim();
|
|
2969
|
+
let code = "";
|
|
2970
|
+
const codeMatch = TRAILING_CODE_RE.exec(message);
|
|
2971
|
+
if (codeMatch) {
|
|
2972
|
+
code = codeMatch[1];
|
|
2973
|
+
message = message.slice(0, codeMatch.index).trim();
|
|
2974
|
+
}
|
|
2975
|
+
findings.push({ file: m[1].trim(), line: Number(m[2]), severity: m[3].toLowerCase(), code, message });
|
|
2976
|
+
}
|
|
2977
|
+
return findings;
|
|
2978
|
+
}
|
|
2979
|
+
async function runCodelinter(opts) {
|
|
2980
|
+
const res = await runProcess({
|
|
2981
|
+
command: getCmdToolsBin(opts.config, "codelinter"),
|
|
2982
|
+
args: opts.files?.length ? [...opts.files] : [],
|
|
2983
|
+
cwd: opts.projectDir,
|
|
2984
|
+
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
2985
|
+
abortSignal: opts.abortSignal,
|
|
2986
|
+
logger: opts.logger
|
|
2987
|
+
});
|
|
2988
|
+
const raw = `${res.stdout}${res.stderr}`;
|
|
2989
|
+
return { code: res.code, findings: parseCodelinterFindings(raw), raw };
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
// src/build/buildHap.ts
|
|
2993
|
+
async function buildHap(opts) {
|
|
2994
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "build");
|
|
2995
|
+
const warnings = [];
|
|
2996
|
+
const start = Date.now();
|
|
2997
|
+
if (opts.autoInstallDeps !== false) {
|
|
2998
|
+
const r = await ensureOhModules({
|
|
2999
|
+
config: opts.config,
|
|
3000
|
+
projectDir: opts.projectDir,
|
|
3001
|
+
abortSignal: opts.abortSignal,
|
|
3002
|
+
onOutput: opts.onOutput,
|
|
3003
|
+
logger: opts.logger
|
|
3004
|
+
});
|
|
3005
|
+
if (r.installed) logger.info("ran ohpm install --all (oh_modules was missing)");
|
|
3006
|
+
}
|
|
3007
|
+
const { exitCode } = await runHvigorw({
|
|
3008
|
+
config: opts.config,
|
|
3009
|
+
projectDir: opts.projectDir,
|
|
3010
|
+
product: opts.product,
|
|
3011
|
+
module: opts.module,
|
|
3012
|
+
buildMode: opts.mode,
|
|
3013
|
+
task: opts.task,
|
|
3014
|
+
parallel: opts.parallel,
|
|
3015
|
+
abortSignal: opts.abortSignal,
|
|
3016
|
+
onOutput: opts.onOutput,
|
|
3017
|
+
logger: opts.logger
|
|
3018
|
+
});
|
|
3019
|
+
const discoveredHaps = await discoverHaps({ projectDir: opts.projectDir });
|
|
3020
|
+
const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
|
|
3021
|
+
if (!anySigned) {
|
|
3022
|
+
warnings.push(
|
|
3023
|
+
"No signed HAPs found \u2014 configure signingConfigs in build-profile.json5 (unsigned HAPs are rejected by most devices)."
|
|
3024
|
+
);
|
|
3025
|
+
}
|
|
3026
|
+
return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
|
|
3027
|
+
}
|
|
3028
|
+
|
|
1650
3029
|
// src/project/validators.ts
|
|
1651
3030
|
function isValidProjectName(name) {
|
|
1652
3031
|
if (!name) return false;
|
|
@@ -1658,21 +3037,21 @@ function isValidBundleName(bundleName) {
|
|
|
1658
3037
|
}
|
|
1659
3038
|
|
|
1660
3039
|
// src/project/templates.ts
|
|
1661
|
-
var
|
|
1662
|
-
var
|
|
3040
|
+
var fs22 = __toESM(require("fs"), 1);
|
|
3041
|
+
var path22 = __toESM(require("path"), 1);
|
|
1663
3042
|
function toHumanTemplateName(folderName) {
|
|
1664
3043
|
return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
1665
3044
|
}
|
|
1666
3045
|
function listTemplates(templateRoot) {
|
|
1667
|
-
if (!
|
|
1668
|
-
const entries =
|
|
3046
|
+
if (!fs22.existsSync(templateRoot)) return [];
|
|
3047
|
+
const entries = fs22.readdirSync(templateRoot, { withFileTypes: true });
|
|
1669
3048
|
return entries.filter((e) => e.isDirectory()).map((e) => {
|
|
1670
|
-
const dir =
|
|
1671
|
-
const metaPath =
|
|
3049
|
+
const dir = path22.join(templateRoot, e.name);
|
|
3050
|
+
const metaPath = path22.join(dir, "template.json");
|
|
1672
3051
|
let meta = {};
|
|
1673
|
-
if (
|
|
3052
|
+
if (fs22.existsSync(metaPath)) {
|
|
1674
3053
|
try {
|
|
1675
|
-
meta = JSON.parse(
|
|
3054
|
+
meta = JSON.parse(fs22.readFileSync(metaPath, "utf8"));
|
|
1676
3055
|
} catch {
|
|
1677
3056
|
}
|
|
1678
3057
|
}
|
|
@@ -1687,57 +3066,57 @@ function listTemplates(templateRoot) {
|
|
|
1687
3066
|
function validateTemplateLayout(templateDir, defaultModuleName) {
|
|
1688
3067
|
const required = [
|
|
1689
3068
|
"build-profile.json5",
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
3069
|
+
path22.join("AppScope", "app.json5"),
|
|
3070
|
+
path22.join(defaultModuleName, "src", "main", "module.json5"),
|
|
3071
|
+
path22.join(defaultModuleName, "oh-package.json5"),
|
|
1693
3072
|
"hvigorfile.ts"
|
|
1694
3073
|
];
|
|
1695
|
-
return required.filter((rel) => !
|
|
3074
|
+
return required.filter((rel) => !fs22.existsSync(path22.join(templateDir, rel)));
|
|
1696
3075
|
}
|
|
1697
3076
|
|
|
1698
3077
|
// src/project/createScaffold.ts
|
|
1699
|
-
var
|
|
1700
|
-
var
|
|
3078
|
+
var fs24 = __toESM(require("fs"), 1);
|
|
3079
|
+
var path23 = __toESM(require("path"), 1);
|
|
1701
3080
|
|
|
1702
3081
|
// src/project/jsonHelpers.ts
|
|
1703
|
-
var
|
|
3082
|
+
var fs23 = __toESM(require("fs"), 1);
|
|
1704
3083
|
var import_json54 = __toESM(require("json5"), 1);
|
|
1705
3084
|
function readJson5File(filePath) {
|
|
1706
|
-
return import_json54.default.parse(
|
|
3085
|
+
return import_json54.default.parse(fs23.readFileSync(filePath, "utf8"));
|
|
1707
3086
|
}
|
|
1708
3087
|
function writeJson5File(filePath, value) {
|
|
1709
|
-
|
|
3088
|
+
fs23.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
1710
3089
|
}
|
|
1711
3090
|
function readJsonFile(filePath) {
|
|
1712
|
-
return JSON.parse(
|
|
3091
|
+
return JSON.parse(fs23.readFileSync(filePath, "utf8"));
|
|
1713
3092
|
}
|
|
1714
3093
|
function writeJsonFile(filePath, value) {
|
|
1715
|
-
|
|
3094
|
+
fs23.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
1716
3095
|
}
|
|
1717
3096
|
|
|
1718
3097
|
// src/project/createScaffold.ts
|
|
1719
3098
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
|
|
1720
3099
|
async function pathExists(p) {
|
|
1721
3100
|
try {
|
|
1722
|
-
await
|
|
3101
|
+
await fs24.promises.access(p);
|
|
1723
3102
|
return true;
|
|
1724
3103
|
} catch {
|
|
1725
3104
|
return false;
|
|
1726
3105
|
}
|
|
1727
3106
|
}
|
|
1728
3107
|
async function copyDirRecursive(srcDir, destDir) {
|
|
1729
|
-
await
|
|
1730
|
-
const entries = await
|
|
3108
|
+
await fs24.promises.mkdir(destDir, { recursive: true });
|
|
3109
|
+
const entries = await fs24.promises.readdir(srcDir, { withFileTypes: true });
|
|
1731
3110
|
for (const entry of entries) {
|
|
1732
|
-
const src =
|
|
1733
|
-
const dest =
|
|
3111
|
+
const src = path23.join(srcDir, entry.name);
|
|
3112
|
+
const dest = path23.join(destDir, entry.name);
|
|
1734
3113
|
if (entry.isDirectory()) {
|
|
1735
3114
|
await copyDirRecursive(src, dest);
|
|
1736
3115
|
} else if (entry.isSymbolicLink()) {
|
|
1737
3116
|
continue;
|
|
1738
3117
|
} else if (entry.isFile()) {
|
|
1739
3118
|
if (entry.name === "template.json") continue;
|
|
1740
|
-
await
|
|
3119
|
+
await fs24.promises.copyFile(src, dest);
|
|
1741
3120
|
}
|
|
1742
3121
|
}
|
|
1743
3122
|
}
|
|
@@ -1745,9 +3124,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
|
|
|
1745
3124
|
const stack = [projectDir];
|
|
1746
3125
|
while (stack.length > 0) {
|
|
1747
3126
|
const currentDir = stack.pop();
|
|
1748
|
-
const entries = await
|
|
3127
|
+
const entries = await fs24.promises.readdir(currentDir, { withFileTypes: true });
|
|
1749
3128
|
for (const entry of entries) {
|
|
1750
|
-
const fullPath =
|
|
3129
|
+
const fullPath = path23.join(currentDir, entry.name);
|
|
1751
3130
|
if (entry.isDirectory()) {
|
|
1752
3131
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
1753
3132
|
stack.push(fullPath);
|
|
@@ -1769,23 +3148,23 @@ function toJavaPropertiesPath(p) {
|
|
|
1769
3148
|
function createOrUpdateLocalProperties(projectDir, sdkDir) {
|
|
1770
3149
|
const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
|
|
1771
3150
|
`;
|
|
1772
|
-
|
|
3151
|
+
fs24.writeFileSync(path23.join(projectDir, "local.properties"), content, "utf8");
|
|
1773
3152
|
}
|
|
1774
3153
|
function renameIfExists(fromPath, toPath) {
|
|
1775
|
-
if (
|
|
1776
|
-
|
|
3154
|
+
if (fs24.existsSync(fromPath) && fromPath !== toPath) {
|
|
3155
|
+
fs24.renameSync(fromPath, toPath);
|
|
1777
3156
|
}
|
|
1778
3157
|
}
|
|
1779
3158
|
function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
1780
|
-
const appJsonPath =
|
|
1781
|
-
if (
|
|
3159
|
+
const appJsonPath = path23.join(projectDir, "AppScope", "app.json5");
|
|
3160
|
+
if (fs24.existsSync(appJsonPath)) {
|
|
1782
3161
|
const appJson = readJson5File(appJsonPath);
|
|
1783
3162
|
appJson.app = appJson.app ?? {};
|
|
1784
3163
|
appJson.app.bundleName = args.bundleName;
|
|
1785
3164
|
writeJson5File(appJsonPath, appJson);
|
|
1786
3165
|
}
|
|
1787
|
-
const stringsPath =
|
|
1788
|
-
if (
|
|
3166
|
+
const stringsPath = path23.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
|
|
3167
|
+
if (fs24.existsSync(stringsPath)) {
|
|
1789
3168
|
const strings = readJsonFile(stringsPath);
|
|
1790
3169
|
if (Array.isArray(strings.string)) {
|
|
1791
3170
|
const appName = strings.string.find((s) => s.name === "app_name");
|
|
@@ -1793,8 +3172,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
1793
3172
|
}
|
|
1794
3173
|
writeJsonFile(stringsPath, strings);
|
|
1795
3174
|
}
|
|
1796
|
-
const buildProfilePath =
|
|
1797
|
-
if (
|
|
3175
|
+
const buildProfilePath = path23.join(projectDir, "build-profile.json5");
|
|
3176
|
+
if (fs24.existsSync(buildProfilePath)) {
|
|
1798
3177
|
const buildProfile = readJson5File(buildProfilePath);
|
|
1799
3178
|
buildProfile.app = buildProfile.app ?? {};
|
|
1800
3179
|
if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
|
|
@@ -1807,23 +3186,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
1807
3186
|
}
|
|
1808
3187
|
writeJson5File(buildProfilePath, buildProfile);
|
|
1809
3188
|
}
|
|
1810
|
-
const moduleJsonPath =
|
|
1811
|
-
if (
|
|
3189
|
+
const moduleJsonPath = path23.join(projectDir, args.moduleName, "src", "main", "module.json5");
|
|
3190
|
+
if (fs24.existsSync(moduleJsonPath)) {
|
|
1812
3191
|
const moduleJson = readJson5File(moduleJsonPath);
|
|
1813
3192
|
moduleJson.module = moduleJson.module ?? {};
|
|
1814
3193
|
moduleJson.module.name = args.moduleName;
|
|
1815
3194
|
writeJson5File(moduleJsonPath, moduleJson);
|
|
1816
3195
|
}
|
|
1817
|
-
const moduleOhPackagePath =
|
|
1818
|
-
if (
|
|
3196
|
+
const moduleOhPackagePath = path23.join(projectDir, args.moduleName, "oh-package.json5");
|
|
3197
|
+
if (fs24.existsSync(moduleOhPackagePath)) {
|
|
1819
3198
|
const pkg = readJson5File(moduleOhPackagePath);
|
|
1820
3199
|
pkg.name = args.moduleName;
|
|
1821
3200
|
writeJson5File(moduleOhPackagePath, pkg);
|
|
1822
3201
|
}
|
|
1823
|
-
const vscodeDir =
|
|
1824
|
-
|
|
3202
|
+
const vscodeDir = path23.join(projectDir, ".vscode");
|
|
3203
|
+
fs24.mkdirSync(vscodeDir, { recursive: true });
|
|
1825
3204
|
const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
|
|
1826
|
-
writeJsonFile(
|
|
3205
|
+
writeJsonFile(path23.join(vscodeDir, "settings.json"), {
|
|
1827
3206
|
"oniro.hapPath": hapPath,
|
|
1828
3207
|
"files.associations": { "*.json5": "jsonc" }
|
|
1829
3208
|
});
|
|
@@ -1840,7 +3219,7 @@ async function createScaffold(opts) {
|
|
|
1840
3219
|
if (!opts.location || !await pathExists(opts.location)) {
|
|
1841
3220
|
throw new OniroError(`Location does not exist: ${opts.location}`);
|
|
1842
3221
|
}
|
|
1843
|
-
const templateDir =
|
|
3222
|
+
const templateDir = path23.join(opts.templateRoot, opts.templateId);
|
|
1844
3223
|
if (!await pathExists(templateDir)) {
|
|
1845
3224
|
throw new OniroError(`Template not found: ${templateDir}`);
|
|
1846
3225
|
}
|
|
@@ -1853,17 +3232,17 @@ async function createScaffold(opts) {
|
|
|
1853
3232
|
throw new OniroError(`Template '${opts.templateId}' is missing required files:
|
|
1854
3233
|
- ${missing.join("\n- ")}`);
|
|
1855
3234
|
}
|
|
1856
|
-
const projectDir =
|
|
3235
|
+
const projectDir = path23.join(opts.location, opts.projectName);
|
|
1857
3236
|
if (await pathExists(projectDir)) {
|
|
1858
3237
|
if (!opts.overwrite) {
|
|
1859
3238
|
throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
|
|
1860
3239
|
}
|
|
1861
|
-
await
|
|
3240
|
+
await fs24.promises.rm(projectDir, { recursive: true, force: true });
|
|
1862
3241
|
}
|
|
1863
3242
|
logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
|
|
1864
3243
|
await copyDirRecursive(templateDir, projectDir);
|
|
1865
3244
|
if (moduleName !== defaultModuleName) {
|
|
1866
|
-
renameIfExists(
|
|
3245
|
+
renameIfExists(path23.join(projectDir, defaultModuleName), path23.join(projectDir, moduleName));
|
|
1867
3246
|
}
|
|
1868
3247
|
updateTemplateConfigs(
|
|
1869
3248
|
projectDir,
|
|
@@ -1879,8 +3258,225 @@ async function createScaffold(opts) {
|
|
|
1879
3258
|
return { projectDir };
|
|
1880
3259
|
}
|
|
1881
3260
|
|
|
3261
|
+
// src/project/listModules.ts
|
|
3262
|
+
var fs25 = __toESM(require("fs"), 1);
|
|
3263
|
+
var path24 = __toESM(require("path"), 1);
|
|
3264
|
+
function listModules(opts) {
|
|
3265
|
+
const buildProfile = path24.join(opts.projectDir, "build-profile.json5");
|
|
3266
|
+
if (!fs25.existsSync(buildProfile)) {
|
|
3267
|
+
throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
|
|
3268
|
+
}
|
|
3269
|
+
const parsed = readJson5File(buildProfile);
|
|
3270
|
+
return (parsed.modules ?? []).filter((m) => Boolean(m.name)).map((m) => ({
|
|
3271
|
+
name: m.name,
|
|
3272
|
+
srcPath: m.srcPath ?? `./${m.name}`,
|
|
3273
|
+
targets: (m.targets ?? []).map((t) => t.name).filter((n) => Boolean(n))
|
|
3274
|
+
}));
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
// src/project/abilities.ts
|
|
3278
|
+
var fs26 = __toESM(require("fs"), 1);
|
|
3279
|
+
var path25 = __toESM(require("path"), 1);
|
|
3280
|
+
function listAbilities(opts) {
|
|
3281
|
+
const moduleName = opts.moduleName ?? "entry";
|
|
3282
|
+
const moduleJsonPath = path25.join(opts.projectDir, moduleName, "src", "main", "module.json5");
|
|
3283
|
+
if (!fs26.existsSync(moduleJsonPath)) {
|
|
3284
|
+
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
3285
|
+
}
|
|
3286
|
+
const parsed = readJson5File(moduleJsonPath);
|
|
3287
|
+
return (parsed.module?.abilities ?? []).filter((a) => Boolean(a.name)).map((a) => ({ name: a.name, type: a.type, visible: a.visible, srcEntry: a.srcEntry }));
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
// src/project/prepareSigning.ts
|
|
3291
|
+
var fs27 = __toESM(require("fs"), 1);
|
|
3292
|
+
var path26 = __toESM(require("path"), 1);
|
|
3293
|
+
function hasSigningArtifacts(dir) {
|
|
3294
|
+
if (!fs27.existsSync(path26.join(dir, "signatures"))) return false;
|
|
3295
|
+
const buildProfile = path26.join(dir, "build-profile.json5");
|
|
3296
|
+
if (!fs27.existsSync(buildProfile)) return false;
|
|
3297
|
+
try {
|
|
3298
|
+
const parsed = readJson5File(buildProfile);
|
|
3299
|
+
return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
|
|
3300
|
+
} catch {
|
|
3301
|
+
return false;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
function prepareSigning(opts) {
|
|
3305
|
+
if (hasSigningArtifacts(opts.projectDir)) {
|
|
3306
|
+
return { source: "present" };
|
|
3307
|
+
}
|
|
3308
|
+
generateSigningConfigs({
|
|
3309
|
+
projectDir: opts.projectDir,
|
|
3310
|
+
sdkHome: getOhosBaseSdkHome(opts.config),
|
|
3311
|
+
apl: opts.apl,
|
|
3312
|
+
appFeature: opts.appFeature,
|
|
3313
|
+
acls: opts.acls,
|
|
3314
|
+
passwords: opts.passwords,
|
|
3315
|
+
logger: opts.logger
|
|
3316
|
+
});
|
|
3317
|
+
return { source: "fresh" };
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
// src/device/hidumper.ts
|
|
3321
|
+
async function dumpScreen(opts) {
|
|
3322
|
+
const res = await shell({ config: opts.config, command: "hidumper -s 10 -a screen", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
|
|
3323
|
+
if (res.code !== 0) return null;
|
|
3324
|
+
const m = res.stdout.match(/render resolution=(\d+)x(\d+)/);
|
|
3325
|
+
if (!m) return null;
|
|
3326
|
+
return { width: Number(m[1]), height: Number(m[2]), raw: res.stdout };
|
|
3327
|
+
}
|
|
3328
|
+
async function dumpWindow(opts) {
|
|
3329
|
+
const res = await shell({ config: opts.config, command: "hidumper -s WindowManagerService -a -a", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 15e3, logger: opts.logger });
|
|
3330
|
+
return res.stdout;
|
|
3331
|
+
}
|
|
3332
|
+
async function dumpRenderService(opts) {
|
|
3333
|
+
const res = await shell({ config: opts.config, command: "hidumper -s RenderService -a allInfo", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 15e3, logger: opts.logger });
|
|
3334
|
+
return res.stdout;
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
// src/device/info.ts
|
|
3338
|
+
var PROPS = [
|
|
3339
|
+
["model", "const.product.model"],
|
|
3340
|
+
["manufacturer", "const.product.manufacturer"],
|
|
3341
|
+
["brand", "const.product.brand"],
|
|
3342
|
+
["osFullName", "const.ohos.fullname"],
|
|
3343
|
+
["osReleaseType", "const.ohos.releasetype"],
|
|
3344
|
+
["sdkApiVersion", "const.ohos.apiversion"],
|
|
3345
|
+
["buildVersion", "const.product.software.version"]
|
|
3346
|
+
];
|
|
3347
|
+
async function getDeviceInfo(opts) {
|
|
3348
|
+
const info = { serial: opts.deviceSerial ?? "(default)", display: null };
|
|
3349
|
+
for (const [field, prop] of PROPS) {
|
|
3350
|
+
try {
|
|
3351
|
+
const value = await paramGet(opts.config, prop, { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
|
|
3352
|
+
if (value) info[field] = value;
|
|
3353
|
+
} catch {
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
const screen = await dumpScreen({ config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger });
|
|
3357
|
+
if (screen) info.display = { width: screen.width, height: screen.height };
|
|
3358
|
+
return info;
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
// src/install/diffHapAssets.ts
|
|
3362
|
+
var import_node_stream_zip2 = __toESM(require("node-stream-zip"), 1);
|
|
3363
|
+
function diffEntryNames(installed, next) {
|
|
3364
|
+
const installedSet = new Set(installed);
|
|
3365
|
+
const nextSet = new Set(next);
|
|
3366
|
+
return {
|
|
3367
|
+
addedAssetPaths: [...nextSet].filter((n) => !installedSet.has(n)).sort(),
|
|
3368
|
+
removedAssetPaths: [...installedSet].filter((n) => !nextSet.has(n)).sort()
|
|
3369
|
+
};
|
|
3370
|
+
}
|
|
3371
|
+
async function listFileEntries(hapPath) {
|
|
3372
|
+
const zip = new import_node_stream_zip2.default.async({ file: hapPath });
|
|
3373
|
+
try {
|
|
3374
|
+
const entries = await zip.entries();
|
|
3375
|
+
return Object.values(entries).filter((e) => !e.isDirectory).map((e) => e.name);
|
|
3376
|
+
} finally {
|
|
3377
|
+
await zip.close();
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
async function diffHapAssets(opts) {
|
|
3381
|
+
const [installed, next] = await Promise.all([listFileEntries(opts.installedHap), listFileEntries(opts.newHap)]);
|
|
3382
|
+
return diffEntryNames(installed, next);
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
// src/install/apply.ts
|
|
3386
|
+
var fs28 = __toESM(require("fs"), 1);
|
|
3387
|
+
var path27 = __toESM(require("path"), 1);
|
|
3388
|
+
function decideInstallMethod(s) {
|
|
3389
|
+
if (!s.signInfoInconsistent) return "replace";
|
|
3390
|
+
if (s.isSystemBundle && !s.allowUninstall) return "refuse";
|
|
3391
|
+
return "uninstall-install";
|
|
3392
|
+
}
|
|
3393
|
+
function decideReboot(s) {
|
|
3394
|
+
if (s.assetsChanged) return { reboot: true, reason: "asset-cache" };
|
|
3395
|
+
if (s.isSystemBundle && s.preInstallPid !== null && s.postInstallPid === s.preInstallPid) {
|
|
3396
|
+
return { reboot: true, reason: "pid-unchanged" };
|
|
3397
|
+
}
|
|
3398
|
+
return { reboot: false, reason: "none" };
|
|
3399
|
+
}
|
|
3400
|
+
var SIGN_INFO_INCONSISTENT = /9568332|sign.*info.*inconsistent/i;
|
|
3401
|
+
async function pidOf(config, bundle, deviceSerial) {
|
|
3402
|
+
const proc = await findRunningProcess({ config, bundle, deviceSerial });
|
|
3403
|
+
return proc ? Number(proc.pid) : null;
|
|
3404
|
+
}
|
|
3405
|
+
async function resolveHapPath(opts) {
|
|
3406
|
+
let hapPath = opts.hapPath;
|
|
3407
|
+
if (hapPath) {
|
|
3408
|
+
if (!path27.isAbsolute(hapPath) && opts.projectDir) hapPath = path27.join(opts.projectDir, hapPath);
|
|
3409
|
+
} else {
|
|
3410
|
+
if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
|
|
3411
|
+
const haps = await discoverHaps({ projectDir: opts.projectDir });
|
|
3412
|
+
if (opts.module) {
|
|
3413
|
+
const signed = haps[opts.module]?.signed ?? [];
|
|
3414
|
+
if (signed.length === 0) throw new OniroError(`No signed HAP for module ${opts.module}. Build first.`);
|
|
3415
|
+
hapPath = signed[0];
|
|
3416
|
+
} else {
|
|
3417
|
+
const withSigned = Object.entries(haps).filter(([, m]) => m.signed.length > 0);
|
|
3418
|
+
if (withSigned.length === 0) throw new OniroError("No signed HAP found. Build first.");
|
|
3419
|
+
if (withSigned.length > 1) {
|
|
3420
|
+
throw new OniroError(
|
|
3421
|
+
`Multiple modules have signed HAPs (${withSigned.map(([k]) => k).join(", ")}). Pass module to choose.`
|
|
3422
|
+
);
|
|
3423
|
+
}
|
|
3424
|
+
hapPath = withSigned[0][1].signed[0];
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
if (!fs28.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
|
|
3428
|
+
return hapPath;
|
|
3429
|
+
}
|
|
3430
|
+
async function applyChanges(opts) {
|
|
3431
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "apply");
|
|
3432
|
+
const { config, bundle, deviceSerial } = opts;
|
|
3433
|
+
const isSystemBundle = opts.isSystemBundle ?? false;
|
|
3434
|
+
const allowUninstall = opts.allowUninstall ?? false;
|
|
3435
|
+
const hapPath = await resolveHapPath(opts);
|
|
3436
|
+
let assetsChanged = false;
|
|
3437
|
+
if (opts.installedHapPath && fs28.existsSync(opts.installedHapPath)) {
|
|
3438
|
+
try {
|
|
3439
|
+
const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
|
|
3440
|
+
assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
|
|
3441
|
+
} catch (err) {
|
|
3442
|
+
logger.debug(`asset diff skipped: ${err.message}`);
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
const preInstallPid = await pidOf(config, bundle, deviceSerial);
|
|
3446
|
+
const replaceRes = await hdcExec({ config, args: ["install", "-r", hapPath], deviceSerial, timeoutMs: 3e5 });
|
|
3447
|
+
const signInfoInconsistent = SIGN_INFO_INCONSISTENT.test(`${replaceRes.stdout}${replaceRes.stderr}`);
|
|
3448
|
+
const decision = decideInstallMethod({ signInfoInconsistent, isSystemBundle, allowUninstall });
|
|
3449
|
+
let installMethod;
|
|
3450
|
+
if (decision === "refuse") {
|
|
3451
|
+
throw new OniroError(
|
|
3452
|
+
`Refusing to reinstall system bundle ${bundle}: signing certs are inconsistent (9568332). Align the signing certs first \u2014 uninstalling a system bundle can brick the device. Pass allowUninstall to override.`
|
|
3453
|
+
);
|
|
3454
|
+
}
|
|
3455
|
+
if (decision === "uninstall-install") {
|
|
3456
|
+
logger.warn(`sign-info inconsistent for ${bundle}; uninstalling then installing fresh.`);
|
|
3457
|
+
await uninstallApp({ config, bundle, deviceSerial });
|
|
3458
|
+
ensureOk(await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }), `hdc install ${hapPath}`);
|
|
3459
|
+
installMethod = "uninstall-install";
|
|
3460
|
+
} else {
|
|
3461
|
+
ensureOk(replaceRes, `hdc install -r ${hapPath}`);
|
|
3462
|
+
installMethod = "replace";
|
|
3463
|
+
}
|
|
3464
|
+
let postInstallPid = await pidOf(config, bundle, deviceSerial);
|
|
3465
|
+
const rebootDecision = decideReboot({ assetsChanged, isSystemBundle, preInstallPid, postInstallPid });
|
|
3466
|
+
let method = installMethod;
|
|
3467
|
+
let cacheCleared = false;
|
|
3468
|
+
if (rebootDecision.reboot) {
|
|
3469
|
+
logger.info(`rebooting (${rebootDecision.reason}) to apply changes to ${bundle}.`);
|
|
3470
|
+
await reboot({ config, waitForBundle: bundle, deviceSerial, abortSignal: opts.abortSignal, logger: opts.logger });
|
|
3471
|
+
postInstallPid = await pidOf(config, bundle, deviceSerial);
|
|
3472
|
+
method = "reboot";
|
|
3473
|
+
cacheCleared = rebootDecision.reason === "asset-cache";
|
|
3474
|
+
}
|
|
3475
|
+
return { method, preInstallPid, postInstallPid, replaced: installMethod === "replace", cacheCleared };
|
|
3476
|
+
}
|
|
3477
|
+
|
|
1882
3478
|
// src/index.ts
|
|
1883
|
-
var VERSION = "0.
|
|
3479
|
+
var VERSION = "0.8.0";
|
|
1884
3480
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1885
3481
|
0 && (module.exports = {
|
|
1886
3482
|
ALL_SDKS,
|
|
@@ -1889,72 +3485,148 @@ var VERSION = "0.6.2";
|
|
|
1889
3485
|
CancelledError,
|
|
1890
3486
|
ChecksumMismatchError,
|
|
1891
3487
|
CmdToolsNotInstalledError,
|
|
3488
|
+
CommandFailedError,
|
|
3489
|
+
INSTALL_TMP_DIRNAME,
|
|
3490
|
+
InsufficientSpaceError,
|
|
1892
3491
|
OHOS_URL_BASE,
|
|
1893
3492
|
OniroError,
|
|
1894
3493
|
SdkNotInstalledError,
|
|
3494
|
+
TMP_DIR_HINT,
|
|
1895
3495
|
UnsupportedPlatformError,
|
|
1896
3496
|
VERSION,
|
|
3497
|
+
applyChanges,
|
|
1897
3498
|
attemptHdcConnection,
|
|
3499
|
+
buildGestureCommands,
|
|
3500
|
+
buildGestureUitestCommand,
|
|
3501
|
+
buildHap,
|
|
3502
|
+
buildHvigorwArgs,
|
|
3503
|
+
buildInputCommand,
|
|
3504
|
+
buildRawTouchCommand,
|
|
3505
|
+
captureBurst,
|
|
1898
3506
|
consoleLogger,
|
|
3507
|
+
createInstallTempDir,
|
|
1899
3508
|
createMaterial,
|
|
1900
3509
|
createScaffold,
|
|
3510
|
+
createTempWorkDir,
|
|
3511
|
+
decideInstallMethod,
|
|
3512
|
+
decideReboot,
|
|
1901
3513
|
decryptPwd,
|
|
3514
|
+
dedupEntries,
|
|
1902
3515
|
defaultContext,
|
|
1903
3516
|
defaultPaths,
|
|
3517
|
+
detectAaStartFailure,
|
|
1904
3518
|
detectProjectSdkVersion,
|
|
1905
3519
|
detectSigningConfigNames,
|
|
3520
|
+
diffEntryNames,
|
|
3521
|
+
diffHapAssets,
|
|
3522
|
+
discoverHaps,
|
|
1906
3523
|
downloadAndInstallSdk,
|
|
1907
3524
|
downloadFile,
|
|
3525
|
+
dumpLayout,
|
|
3526
|
+
dumpLog,
|
|
3527
|
+
dumpRenderService,
|
|
3528
|
+
dumpScreen,
|
|
3529
|
+
dumpWindow,
|
|
1908
3530
|
encryptPwd,
|
|
3531
|
+
ensureFreeSpace,
|
|
3532
|
+
ensureOhModules,
|
|
3533
|
+
ensureOk,
|
|
3534
|
+
ensureRemoteOk,
|
|
1909
3535
|
extractTarball,
|
|
1910
3536
|
extractZipWithProgress,
|
|
1911
3537
|
findAppProcessId,
|
|
1912
3538
|
findCmdToolsSourceDir,
|
|
3539
|
+
findRunningProcess,
|
|
3540
|
+
forceStop,
|
|
3541
|
+
formatBytes,
|
|
1913
3542
|
generateSigningConfigs,
|
|
1914
3543
|
getBundleName,
|
|
1915
3544
|
getCmdToolsBin,
|
|
1916
3545
|
getCmdToolsDownloadUrl,
|
|
1917
3546
|
getCmdToolsPath,
|
|
1918
3547
|
getCmdToolsStatus,
|
|
3548
|
+
getDeviceInfo,
|
|
3549
|
+
getDisplaySize,
|
|
1919
3550
|
getEmulatorDir,
|
|
3551
|
+
getFreeSpaceBytes,
|
|
1920
3552
|
getHdcPath,
|
|
1921
3553
|
getHvigorwPath,
|
|
1922
3554
|
getInstalledSdks,
|
|
1923
3555
|
getKey,
|
|
1924
3556
|
getMainAbility,
|
|
1925
3557
|
getOhosBaseSdkHome,
|
|
3558
|
+
getOhpmPath,
|
|
1926
3559
|
getOsFolder,
|
|
1927
3560
|
getSdkFilename,
|
|
1928
3561
|
getSdkRootDir,
|
|
1929
3562
|
getSupportedSdksForUi,
|
|
3563
|
+
hdcExec,
|
|
1930
3564
|
installApp,
|
|
1931
3565
|
installCmdTools,
|
|
1932
3566
|
installEmulator,
|
|
1933
3567
|
isCmdToolsInstalled,
|
|
1934
3568
|
isEmulatorInstalled,
|
|
3569
|
+
isOutOfSpaceError,
|
|
3570
|
+
isRamBackedPath,
|
|
1935
3571
|
isValidBundleName,
|
|
1936
3572
|
isValidProjectName,
|
|
1937
3573
|
launchApp,
|
|
3574
|
+
listAbilities,
|
|
3575
|
+
listDevices,
|
|
3576
|
+
listModules,
|
|
1938
3577
|
listRunningProcesses,
|
|
1939
3578
|
listTemplates,
|
|
1940
3579
|
nonInteractivePrompter,
|
|
1941
3580
|
noopLogger,
|
|
1942
3581
|
noopProgress,
|
|
3582
|
+
paramGet,
|
|
3583
|
+
paramSet,
|
|
3584
|
+
parseBounds,
|
|
3585
|
+
parseCodelinterFindings,
|
|
3586
|
+
parseDeviceList,
|
|
1943
3587
|
parseHilogLine,
|
|
3588
|
+
parseMainAbilityFromBmDump,
|
|
3589
|
+
parseRemoteExit,
|
|
1944
3590
|
pickSigningKind,
|
|
3591
|
+
prepareSigning,
|
|
3592
|
+
pruneLayout,
|
|
1945
3593
|
readJson5File,
|
|
1946
3594
|
readJsonFile,
|
|
3595
|
+
reboot,
|
|
3596
|
+
recvFile,
|
|
1947
3597
|
removeCmdTools,
|
|
1948
3598
|
removeEmulator,
|
|
1949
3599
|
removeSdk,
|
|
3600
|
+
removeTempWorkDir,
|
|
3601
|
+
resolveTmpRoot,
|
|
3602
|
+
runCodelinter,
|
|
1950
3603
|
runHvigorw,
|
|
3604
|
+
runOhpm,
|
|
3605
|
+
runProcess,
|
|
3606
|
+
scopedLogger,
|
|
3607
|
+
selectDevice,
|
|
3608
|
+
sendFile,
|
|
3609
|
+
sendGesture,
|
|
3610
|
+
sendInput,
|
|
3611
|
+
sendRawTouch,
|
|
1951
3612
|
setHilogLevel,
|
|
3613
|
+
shell,
|
|
3614
|
+
shellChecked,
|
|
1952
3615
|
startEmulator,
|
|
1953
3616
|
staticConfig,
|
|
1954
3617
|
stopEmulator,
|
|
1955
3618
|
streamHilog,
|
|
3619
|
+
takeScreenshot,
|
|
3620
|
+
toSpaceError,
|
|
3621
|
+
uninstallApp,
|
|
1956
3622
|
validateTemplateLayout,
|
|
1957
3623
|
verifySha256,
|
|
3624
|
+
waitForBoot,
|
|
3625
|
+
waitForBundle,
|
|
3626
|
+
waitForCondition,
|
|
3627
|
+
waitForLog,
|
|
3628
|
+
watchLog,
|
|
3629
|
+
waypointsToRawEvents,
|
|
1958
3630
|
writeJson5File,
|
|
1959
3631
|
writeJsonFile
|
|
1960
3632
|
});
|