@oniroproject/core 0.6.2 → 0.7.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 +255 -7
- package/dist/index.cjs +1602 -257
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +876 -21
- package/dist/index.d.ts +876 -21
- package/dist/index.js +1546 -258
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -36,33 +36,59 @@ __export(index_exports, {
|
|
|
36
36
|
CancelledError: () => CancelledError,
|
|
37
37
|
ChecksumMismatchError: () => ChecksumMismatchError,
|
|
38
38
|
CmdToolsNotInstalledError: () => CmdToolsNotInstalledError,
|
|
39
|
+
CommandFailedError: () => CommandFailedError,
|
|
39
40
|
OHOS_URL_BASE: () => OHOS_URL_BASE,
|
|
40
41
|
OniroError: () => OniroError,
|
|
41
42
|
SdkNotInstalledError: () => SdkNotInstalledError,
|
|
42
43
|
UnsupportedPlatformError: () => UnsupportedPlatformError,
|
|
43
44
|
VERSION: () => VERSION,
|
|
45
|
+
applyChanges: () => applyChanges,
|
|
44
46
|
attemptHdcConnection: () => attemptHdcConnection,
|
|
47
|
+
buildGestureCommands: () => buildGestureCommands,
|
|
48
|
+
buildHap: () => buildHap,
|
|
49
|
+
buildHvigorwArgs: () => buildHvigorwArgs,
|
|
50
|
+
buildInputCommand: () => buildInputCommand,
|
|
51
|
+
buildRawTouchCommand: () => buildRawTouchCommand,
|
|
52
|
+
captureBurst: () => captureBurst,
|
|
45
53
|
consoleLogger: () => consoleLogger,
|
|
46
54
|
createMaterial: () => createMaterial,
|
|
47
55
|
createScaffold: () => createScaffold,
|
|
56
|
+
decideInstallMethod: () => decideInstallMethod,
|
|
57
|
+
decideReboot: () => decideReboot,
|
|
48
58
|
decryptPwd: () => decryptPwd,
|
|
59
|
+
dedupEntries: () => dedupEntries,
|
|
49
60
|
defaultContext: () => defaultContext,
|
|
50
61
|
defaultPaths: () => defaultPaths,
|
|
62
|
+
detectAaStartFailure: () => detectAaStartFailure,
|
|
51
63
|
detectProjectSdkVersion: () => detectProjectSdkVersion,
|
|
52
64
|
detectSigningConfigNames: () => detectSigningConfigNames,
|
|
65
|
+
diffEntryNames: () => diffEntryNames,
|
|
66
|
+
diffHapAssets: () => diffHapAssets,
|
|
67
|
+
discoverHaps: () => discoverHaps,
|
|
53
68
|
downloadAndInstallSdk: () => downloadAndInstallSdk,
|
|
54
69
|
downloadFile: () => downloadFile,
|
|
70
|
+
dumpLayout: () => dumpLayout,
|
|
71
|
+
dumpLog: () => dumpLog,
|
|
72
|
+
dumpRenderService: () => dumpRenderService,
|
|
73
|
+
dumpScreen: () => dumpScreen,
|
|
74
|
+
dumpWindow: () => dumpWindow,
|
|
55
75
|
encryptPwd: () => encryptPwd,
|
|
76
|
+
ensureOhModules: () => ensureOhModules,
|
|
77
|
+
ensureOk: () => ensureOk,
|
|
56
78
|
extractTarball: () => extractTarball,
|
|
57
79
|
extractZipWithProgress: () => extractZipWithProgress,
|
|
58
80
|
findAppProcessId: () => findAppProcessId,
|
|
59
81
|
findCmdToolsSourceDir: () => findCmdToolsSourceDir,
|
|
82
|
+
findRunningProcess: () => findRunningProcess,
|
|
83
|
+
forceStop: () => forceStop,
|
|
60
84
|
generateSigningConfigs: () => generateSigningConfigs,
|
|
61
85
|
getBundleName: () => getBundleName,
|
|
62
86
|
getCmdToolsBin: () => getCmdToolsBin,
|
|
63
87
|
getCmdToolsDownloadUrl: () => getCmdToolsDownloadUrl,
|
|
64
88
|
getCmdToolsPath: () => getCmdToolsPath,
|
|
65
89
|
getCmdToolsStatus: () => getCmdToolsStatus,
|
|
90
|
+
getDeviceInfo: () => getDeviceInfo,
|
|
91
|
+
getDisplaySize: () => getDisplaySize,
|
|
66
92
|
getEmulatorDir: () => getEmulatorDir,
|
|
67
93
|
getHdcPath: () => getHdcPath,
|
|
68
94
|
getHvigorwPath: () => getHvigorwPath,
|
|
@@ -70,10 +96,12 @@ __export(index_exports, {
|
|
|
70
96
|
getKey: () => getKey,
|
|
71
97
|
getMainAbility: () => getMainAbility,
|
|
72
98
|
getOhosBaseSdkHome: () => getOhosBaseSdkHome,
|
|
99
|
+
getOhpmPath: () => getOhpmPath,
|
|
73
100
|
getOsFolder: () => getOsFolder,
|
|
74
101
|
getSdkFilename: () => getSdkFilename,
|
|
75
102
|
getSdkRootDir: () => getSdkRootDir,
|
|
76
103
|
getSupportedSdksForUi: () => getSupportedSdksForUi,
|
|
104
|
+
hdcExec: () => hdcExec,
|
|
77
105
|
installApp: () => installApp,
|
|
78
106
|
installCmdTools: () => installCmdTools,
|
|
79
107
|
installEmulator: () => installEmulator,
|
|
@@ -82,26 +110,55 @@ __export(index_exports, {
|
|
|
82
110
|
isValidBundleName: () => isValidBundleName,
|
|
83
111
|
isValidProjectName: () => isValidProjectName,
|
|
84
112
|
launchApp: () => launchApp,
|
|
113
|
+
listAbilities: () => listAbilities,
|
|
114
|
+
listDevices: () => listDevices,
|
|
115
|
+
listModules: () => listModules,
|
|
85
116
|
listRunningProcesses: () => listRunningProcesses,
|
|
86
117
|
listTemplates: () => listTemplates,
|
|
87
118
|
nonInteractivePrompter: () => nonInteractivePrompter,
|
|
88
119
|
noopLogger: () => noopLogger,
|
|
89
120
|
noopProgress: () => noopProgress,
|
|
121
|
+
paramGet: () => paramGet,
|
|
122
|
+
paramSet: () => paramSet,
|
|
123
|
+
parseBounds: () => parseBounds,
|
|
124
|
+
parseCodelinterFindings: () => parseCodelinterFindings,
|
|
125
|
+
parseDeviceList: () => parseDeviceList,
|
|
90
126
|
parseHilogLine: () => parseHilogLine,
|
|
91
127
|
pickSigningKind: () => pickSigningKind,
|
|
128
|
+
prepareSigning: () => prepareSigning,
|
|
129
|
+
pruneLayout: () => pruneLayout,
|
|
92
130
|
readJson5File: () => readJson5File,
|
|
93
131
|
readJsonFile: () => readJsonFile,
|
|
132
|
+
reboot: () => reboot,
|
|
133
|
+
recvFile: () => recvFile,
|
|
94
134
|
removeCmdTools: () => removeCmdTools,
|
|
95
135
|
removeEmulator: () => removeEmulator,
|
|
96
136
|
removeSdk: () => removeSdk,
|
|
137
|
+
runCodelinter: () => runCodelinter,
|
|
97
138
|
runHvigorw: () => runHvigorw,
|
|
139
|
+
runOhpm: () => runOhpm,
|
|
140
|
+
runProcess: () => runProcess,
|
|
141
|
+
scopedLogger: () => scopedLogger,
|
|
142
|
+
selectDevice: () => selectDevice,
|
|
143
|
+
sendFile: () => sendFile,
|
|
144
|
+
sendGesture: () => sendGesture,
|
|
145
|
+
sendInput: () => sendInput,
|
|
146
|
+
sendRawTouch: () => sendRawTouch,
|
|
98
147
|
setHilogLevel: () => setHilogLevel,
|
|
148
|
+
shell: () => shell,
|
|
99
149
|
startEmulator: () => startEmulator,
|
|
100
150
|
staticConfig: () => staticConfig,
|
|
101
151
|
stopEmulator: () => stopEmulator,
|
|
102
152
|
streamHilog: () => streamHilog,
|
|
153
|
+
takeScreenshot: () => takeScreenshot,
|
|
154
|
+
uninstallApp: () => uninstallApp,
|
|
103
155
|
validateTemplateLayout: () => validateTemplateLayout,
|
|
104
156
|
verifySha256: () => verifySha256,
|
|
157
|
+
waitForBoot: () => waitForBoot,
|
|
158
|
+
waitForBundle: () => waitForBundle,
|
|
159
|
+
waitForCondition: () => waitForCondition,
|
|
160
|
+
waitForLog: () => waitForLog,
|
|
161
|
+
watchLog: () => watchLog,
|
|
105
162
|
writeJson5File: () => writeJson5File,
|
|
106
163
|
writeJsonFile: () => writeJsonFile
|
|
107
164
|
});
|
|
@@ -124,6 +181,15 @@ var consoleLogger = {
|
|
|
124
181
|
warn: (m) => console.warn(m),
|
|
125
182
|
error: (m) => console.error(m)
|
|
126
183
|
};
|
|
184
|
+
function scopedLogger(logger, scope) {
|
|
185
|
+
const prefix = `[${scope}] `;
|
|
186
|
+
return {
|
|
187
|
+
debug: (m) => logger.debug(prefix + m),
|
|
188
|
+
info: (m) => logger.info(prefix + m),
|
|
189
|
+
warn: (m) => logger.warn(prefix + m),
|
|
190
|
+
error: (m) => logger.error(prefix + m)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
127
193
|
|
|
128
194
|
// src/ports/progress.ts
|
|
129
195
|
var noopProgress = {
|
|
@@ -228,6 +294,19 @@ var CancelledError = class extends OniroError {
|
|
|
228
294
|
this.name = "CancelledError";
|
|
229
295
|
}
|
|
230
296
|
};
|
|
297
|
+
var CommandFailedError = class extends OniroError {
|
|
298
|
+
constructor(command, exitCode, stderr) {
|
|
299
|
+
super(`Command failed (exit ${exitCode}): ${command}${stderr ? `
|
|
300
|
+
${stderr}` : ""}`);
|
|
301
|
+
this.command = command;
|
|
302
|
+
this.exitCode = exitCode;
|
|
303
|
+
this.stderr = stderr;
|
|
304
|
+
this.name = "CommandFailedError";
|
|
305
|
+
}
|
|
306
|
+
command;
|
|
307
|
+
exitCode;
|
|
308
|
+
stderr;
|
|
309
|
+
};
|
|
231
310
|
|
|
232
311
|
// src/ports/context.ts
|
|
233
312
|
function defaultContext(overrides = {}) {
|
|
@@ -239,15 +318,16 @@ function defaultContext(overrides = {}) {
|
|
|
239
318
|
|
|
240
319
|
// src/sdk/constants.ts
|
|
241
320
|
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.
|
|
321
|
+
{ version: "4.0", api: "10", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
322
|
+
{ version: "4.1", api: "11", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
323
|
+
{ version: "5.0.0", api: "12", tarballStrip: { linuxWindows: 0, darwin: 3 } },
|
|
324
|
+
{ version: "5.0.1", api: "13", tarballStrip: { linuxWindows: 0, darwin: 3 } },
|
|
325
|
+
{ version: "5.0.2", api: "14", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
326
|
+
{ version: "5.0.3", api: "15", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
327
|
+
{ version: "5.1.0", api: "18", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
328
|
+
{ version: "5.1.1", api: "19", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
329
|
+
{ version: "6.0", api: "20", tarballStrip: { linuxWindows: 0, darwin: 3 } },
|
|
330
|
+
{ version: "6.1", api: "23", tarballStrip: { linuxWindows: 0, darwin: 3 } }
|
|
251
331
|
];
|
|
252
332
|
var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
|
|
253
333
|
|
|
@@ -263,19 +343,18 @@ function getOsFolder() {
|
|
|
263
343
|
function getSdkFilename(version) {
|
|
264
344
|
const platform6 = os2.platform();
|
|
265
345
|
const v = version ?? ALL_SDKS[ALL_SDKS.length - 1].version;
|
|
346
|
+
const strips = ALL_SDKS.find((r) => r.version === v)?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
|
|
266
347
|
if (platform6 === "linux") {
|
|
267
|
-
|
|
268
|
-
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip };
|
|
348
|
+
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip: strips.linuxWindows };
|
|
269
349
|
}
|
|
270
350
|
if (platform6 === "darwin") {
|
|
271
351
|
if (os2.arch() === "arm64") {
|
|
272
|
-
return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip:
|
|
352
|
+
return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: strips.darwin };
|
|
273
353
|
}
|
|
274
|
-
return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip:
|
|
354
|
+
return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: strips.darwin };
|
|
275
355
|
}
|
|
276
356
|
if (platform6 === "win32") {
|
|
277
|
-
|
|
278
|
-
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip };
|
|
357
|
+
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip: strips.linuxWindows };
|
|
279
358
|
}
|
|
280
359
|
throw new UnsupportedPlatformError(platform6);
|
|
281
360
|
}
|
|
@@ -302,17 +381,20 @@ function pickExisting(candidates) {
|
|
|
302
381
|
}
|
|
303
382
|
return candidates[0];
|
|
304
383
|
}
|
|
305
|
-
function getCmdToolsBin(config) {
|
|
384
|
+
function getCmdToolsBin(config, name = "ohpm") {
|
|
306
385
|
const binDir = path2.join(getCmdToolsPath(config), "bin");
|
|
307
386
|
if (os3.platform() === "win32") {
|
|
308
387
|
return pickExisting([
|
|
309
|
-
path2.join(binDir,
|
|
310
|
-
path2.join(binDir,
|
|
311
|
-
path2.join(binDir,
|
|
312
|
-
path2.join(binDir,
|
|
388
|
+
path2.join(binDir, `${name}.exe`),
|
|
389
|
+
path2.join(binDir, `${name}.cmd`),
|
|
390
|
+
path2.join(binDir, `${name}.bat`),
|
|
391
|
+
path2.join(binDir, name)
|
|
313
392
|
]);
|
|
314
393
|
}
|
|
315
|
-
return path2.join(binDir,
|
|
394
|
+
return path2.join(binDir, name);
|
|
395
|
+
}
|
|
396
|
+
function getOhpmPath(config) {
|
|
397
|
+
return getCmdToolsBin(config, "ohpm");
|
|
316
398
|
}
|
|
317
399
|
function getHdcPath(config) {
|
|
318
400
|
const base = path2.join(getCmdToolsPath(config), "sdk", "default", "openharmony", "toolchains");
|
|
@@ -327,15 +409,20 @@ function getHdcPath(config) {
|
|
|
327
409
|
return path2.join(base, "hdc");
|
|
328
410
|
}
|
|
329
411
|
function getHvigorwPath(config, projectDir) {
|
|
330
|
-
const
|
|
331
|
-
return pickExisting([
|
|
412
|
+
const local = pickExisting([
|
|
332
413
|
path2.join(projectDir, "hvigorw"),
|
|
333
414
|
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")
|
|
415
|
+
path2.join(projectDir, "hvigorw.cmd")
|
|
338
416
|
]);
|
|
417
|
+
if (fs.existsSync(local) && isProjectLocalHvigorwWorking(projectDir)) {
|
|
418
|
+
return local;
|
|
419
|
+
}
|
|
420
|
+
return getCmdToolsBin(config, "hvigorw");
|
|
421
|
+
}
|
|
422
|
+
function isProjectLocalHvigorwWorking(projectDir) {
|
|
423
|
+
const wrapper = path2.join(projectDir, "hvigor", "hvigor-wrapper.js");
|
|
424
|
+
const nodeModules = path2.join(projectDir, "hvigor", "node_modules");
|
|
425
|
+
return fs.existsSync(wrapper) && fs.existsSync(nodeModules);
|
|
339
426
|
}
|
|
340
427
|
|
|
341
428
|
// src/sdk/detectProjectSdk.ts
|
|
@@ -591,22 +678,37 @@ function removeSdk(config, api) {
|
|
|
591
678
|
}
|
|
592
679
|
|
|
593
680
|
// src/sdk/install.ts
|
|
594
|
-
var
|
|
681
|
+
var fs8 = __toESM(require("fs"), 1);
|
|
595
682
|
var os5 = __toESM(require("os"), 1);
|
|
596
683
|
var path7 = __toESM(require("path"), 1);
|
|
684
|
+
|
|
685
|
+
// src/sdk/move.ts
|
|
686
|
+
var fs7 = __toESM(require("fs"), 1);
|
|
687
|
+
function movePath(src, dest) {
|
|
688
|
+
try {
|
|
689
|
+
fs7.renameSync(src, dest);
|
|
690
|
+
} catch (err) {
|
|
691
|
+
if (err.code !== "EXDEV") throw err;
|
|
692
|
+
fs7.cpSync(src, dest, { recursive: true });
|
|
693
|
+
fs7.rmSync(src, { recursive: true, force: true });
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// src/sdk/install.ts
|
|
597
698
|
async function downloadAndInstallSdk(opts) {
|
|
598
699
|
const { config, version, api, progress, abortSignal } = opts;
|
|
599
700
|
const logger = opts.logger ?? noopLogger;
|
|
600
701
|
const { filename, osFolder, strip } = getSdkFilename(version);
|
|
601
|
-
const
|
|
702
|
+
const urlBase = config.get("sdkUrlBase", OHOS_URL_BASE);
|
|
703
|
+
const downloadUrl = `${urlBase}/${version}-Release/${filename}`;
|
|
602
704
|
const sha256Url = `${downloadUrl}.sha256`;
|
|
603
|
-
const tmpDir =
|
|
705
|
+
const tmpDir = fs8.mkdtempSync(path7.join(os5.tmpdir(), "oniro-sdk-"));
|
|
604
706
|
const tarPath = path7.join(tmpDir, filename);
|
|
605
707
|
const sha256Path = path7.join(tmpDir, `${filename}.sha256`);
|
|
606
708
|
const extractDir = path7.join(tmpDir, "extract");
|
|
607
|
-
|
|
709
|
+
fs8.mkdirSync(extractDir);
|
|
608
710
|
const sdkInstallDir = path7.join(getSdkRootDir(config), osFolder, api);
|
|
609
|
-
|
|
711
|
+
fs8.mkdirSync(path7.dirname(sdkInstallDir), { recursive: true });
|
|
610
712
|
const checkCancelled = () => {
|
|
611
713
|
if (abortSignal?.aborted) throw new CancelledError();
|
|
612
714
|
};
|
|
@@ -624,12 +726,12 @@ async function downloadAndInstallSdk(opts) {
|
|
|
624
726
|
progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 10 });
|
|
625
727
|
checkCancelled();
|
|
626
728
|
const osContentPath = path7.join(extractDir, osFolder);
|
|
627
|
-
if (!
|
|
729
|
+
if (!fs8.existsSync(osContentPath)) {
|
|
628
730
|
throw new OniroError(
|
|
629
731
|
`Expected folder '${osFolder}' not found in extracted SDK. Tarball layout may have changed.`
|
|
630
732
|
);
|
|
631
733
|
}
|
|
632
|
-
const zipFiles =
|
|
734
|
+
const zipFiles = fs8.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
|
|
633
735
|
const componentsStart = 60;
|
|
634
736
|
const componentsBudget = 35;
|
|
635
737
|
const n = zipFiles.length;
|
|
@@ -649,26 +751,26 @@ async function downloadAndInstallSdk(opts) {
|
|
|
649
751
|
if (rem > 0) rem--;
|
|
650
752
|
await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
|
|
651
753
|
cursor += thisBudget;
|
|
652
|
-
|
|
754
|
+
fs8.unlinkSync(zipPath);
|
|
653
755
|
}
|
|
654
756
|
progress?.report({ message: "Finalizing installation...", increment: 0 });
|
|
655
|
-
if (
|
|
656
|
-
|
|
757
|
+
if (fs8.existsSync(sdkInstallDir)) {
|
|
758
|
+
fs8.rmSync(sdkInstallDir, { recursive: true, force: true });
|
|
657
759
|
}
|
|
658
|
-
|
|
760
|
+
movePath(osContentPath, sdkInstallDir);
|
|
659
761
|
progress?.report({ message: "Finalizing installation...", increment: 3 });
|
|
660
762
|
progress?.report({ message: "Cleaning up...", increment: 0 });
|
|
661
|
-
|
|
763
|
+
fs8.rmSync(tmpDir, { recursive: true, force: true });
|
|
662
764
|
progress?.report({ message: "Cleaning up...", increment: 2 });
|
|
663
765
|
} catch (err) {
|
|
664
766
|
logger.error(`SDK install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
665
|
-
|
|
767
|
+
fs8.rmSync(tmpDir, { recursive: true, force: true });
|
|
666
768
|
throw err;
|
|
667
769
|
}
|
|
668
770
|
}
|
|
669
771
|
|
|
670
772
|
// src/cmdTools/install.ts
|
|
671
|
-
var
|
|
773
|
+
var fs9 = __toESM(require("fs"), 1);
|
|
672
774
|
var os6 = __toESM(require("os"), 1);
|
|
673
775
|
var path8 = __toESM(require("path"), 1);
|
|
674
776
|
var import_node_child_process = require("child_process");
|
|
@@ -703,11 +805,11 @@ function findCmdToolsSourceDir(extractPath) {
|
|
|
703
805
|
path8.join(extractPath, "commandline-tools")
|
|
704
806
|
];
|
|
705
807
|
for (const c of known) {
|
|
706
|
-
if (
|
|
808
|
+
if (fs9.existsSync(c)) return c;
|
|
707
809
|
}
|
|
708
|
-
const entries =
|
|
810
|
+
const entries = fs9.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path8.join(extractPath, e.name));
|
|
709
811
|
for (const dir of entries) {
|
|
710
|
-
if (
|
|
812
|
+
if (fs9.existsSync(path8.join(dir, "bin"))) return dir;
|
|
711
813
|
}
|
|
712
814
|
throw new OniroError("Could not locate command line tools folder in the extracted archive.");
|
|
713
815
|
}
|
|
@@ -715,7 +817,7 @@ async function installCmdTools(opts) {
|
|
|
715
817
|
const { config, progress, abortSignal } = opts;
|
|
716
818
|
const logger = opts.logger ?? noopLogger;
|
|
717
819
|
const CMD_PATH = getCmdToolsPath(config);
|
|
718
|
-
const tmpDir =
|
|
820
|
+
const tmpDir = fs9.mkdtempSync(path8.join(os6.tmpdir(), "oniro-cmdtools-"));
|
|
719
821
|
const zipPath = opts.localZipPath ?? path8.join(tmpDir, "oh-command-line-tools.zip");
|
|
720
822
|
const extractPath = path8.join(tmpDir, "oh-command-line-tools");
|
|
721
823
|
try {
|
|
@@ -726,42 +828,42 @@ async function installCmdTools(opts) {
|
|
|
726
828
|
}
|
|
727
829
|
progress?.report({ message: "Extracting tools...", increment: 0 });
|
|
728
830
|
await extractZipWithProgress({ zipPath, dest: extractPath, progress, start: 50, range: 45, logger });
|
|
729
|
-
|
|
831
|
+
fs9.mkdirSync(CMD_PATH, { recursive: true });
|
|
730
832
|
const srcDir = findCmdToolsSourceDir(extractPath);
|
|
731
|
-
for (const entry of
|
|
833
|
+
for (const entry of fs9.readdirSync(srcDir)) {
|
|
732
834
|
const src = path8.join(srcDir, entry);
|
|
733
835
|
const dest = path8.join(CMD_PATH, entry);
|
|
734
|
-
if (
|
|
735
|
-
if (
|
|
736
|
-
|
|
836
|
+
if (fs9.statSync(src).isDirectory()) {
|
|
837
|
+
if (fs9.existsSync(dest)) fs9.rmSync(dest, { recursive: true, force: true });
|
|
838
|
+
movePath(src, dest);
|
|
737
839
|
} else {
|
|
738
|
-
|
|
840
|
+
fs9.copyFileSync(src, dest);
|
|
739
841
|
}
|
|
740
842
|
}
|
|
741
843
|
const binDir = path8.join(CMD_PATH, "bin");
|
|
742
|
-
if (
|
|
743
|
-
for (const file of
|
|
744
|
-
|
|
844
|
+
if (fs9.existsSync(binDir) && os6.platform() !== "win32") {
|
|
845
|
+
for (const file of fs9.readdirSync(binDir)) {
|
|
846
|
+
fs9.chmodSync(path8.join(binDir, file), 493);
|
|
745
847
|
}
|
|
746
848
|
}
|
|
747
849
|
progress?.report({ message: "Finalizing installation...", increment: 5 });
|
|
748
850
|
progress?.report({ message: "Cleaning up...", increment: 0 });
|
|
749
851
|
} finally {
|
|
750
|
-
|
|
852
|
+
fs9.rmSync(tmpDir, { recursive: true, force: true });
|
|
751
853
|
}
|
|
752
854
|
}
|
|
753
855
|
function getCmdToolsStatus(config) {
|
|
754
856
|
const cmdPath = getCmdToolsPath(config);
|
|
755
857
|
const binDir = path8.join(cmdPath, "bin");
|
|
756
858
|
const candidates = os6.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
|
|
757
|
-
const bin = candidates.map((c) => path8.join(binDir, c)).find((p) =>
|
|
859
|
+
const bin = candidates.map((c) => path8.join(binDir, c)).find((p) => fs9.existsSync(p));
|
|
758
860
|
if (!bin) {
|
|
759
861
|
return { installed: false, status: "Not installed" };
|
|
760
862
|
}
|
|
761
863
|
try {
|
|
762
864
|
const versionFile = path8.join(cmdPath, "version.txt");
|
|
763
|
-
if (
|
|
764
|
-
const content =
|
|
865
|
+
if (fs9.existsSync(versionFile)) {
|
|
866
|
+
const content = fs9.readFileSync(versionFile, "utf8");
|
|
765
867
|
for (const line of content.split(/\r?\n/)) {
|
|
766
868
|
const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
|
|
767
869
|
if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
|
|
@@ -781,13 +883,13 @@ function isCmdToolsInstalled(config) {
|
|
|
781
883
|
}
|
|
782
884
|
function removeCmdTools(config) {
|
|
783
885
|
const cmdPath = getCmdToolsPath(config);
|
|
784
|
-
if (
|
|
785
|
-
|
|
886
|
+
if (fs9.existsSync(cmdPath)) {
|
|
887
|
+
fs9.rmSync(cmdPath, { recursive: true, force: true });
|
|
786
888
|
}
|
|
787
889
|
}
|
|
788
890
|
|
|
789
891
|
// src/sign/encryptKey.ts
|
|
790
|
-
var
|
|
892
|
+
var fs10 = __toESM(require("fs"), 1);
|
|
791
893
|
var path9 = __toESM(require("path"), 1);
|
|
792
894
|
var crypto2 = __toESM(require("crypto"), 1);
|
|
793
895
|
var DAEMON_ROOT_KEY_COMPONENT_LENGTH = 16;
|
|
@@ -861,8 +963,8 @@ function createAndStoreKey(dir, keyLength) {
|
|
|
861
963
|
const key = crypto2.randomBytes(keyLength);
|
|
862
964
|
const hash = crypto2.createHash("sha256").update(key).digest("hex");
|
|
863
965
|
const filePath = path9.join(dir, hash);
|
|
864
|
-
|
|
865
|
-
|
|
966
|
+
fs10.writeFileSync(filePath, key);
|
|
967
|
+
fs10.chmodSync(filePath, KEY_FILE_PERMISSIONS);
|
|
866
968
|
return key;
|
|
867
969
|
}
|
|
868
970
|
function createAndStoreEnKey(rootKey, ceDir) {
|
|
@@ -870,23 +972,23 @@ function createAndStoreEnKey(rootKey, ceDir) {
|
|
|
870
972
|
const encrypted = encrypt(rootKey, workKey);
|
|
871
973
|
const hash = crypto2.createHash("sha256").update(encrypted).digest("hex");
|
|
872
974
|
const filePath = path9.join(ceDir, hash);
|
|
873
|
-
|
|
874
|
-
|
|
975
|
+
fs10.writeFileSync(filePath, encrypted);
|
|
976
|
+
fs10.chmodSync(filePath, KEY_FILE_PERMISSIONS);
|
|
875
977
|
}
|
|
876
978
|
function createMaterial(materialPath) {
|
|
877
|
-
|
|
878
|
-
|
|
979
|
+
fs10.mkdirSync(materialPath, { recursive: true });
|
|
980
|
+
fs10.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
|
|
879
981
|
const fdDir = path9.join(materialPath, "fd");
|
|
880
982
|
const acDir = path9.join(materialPath, "ac");
|
|
881
983
|
const ceDir = path9.join(materialPath, "ce");
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
984
|
+
fs10.mkdirSync(fdDir, { recursive: true });
|
|
985
|
+
fs10.mkdirSync(acDir, { recursive: true });
|
|
986
|
+
fs10.mkdirSync(ceDir, { recursive: true });
|
|
885
987
|
const fdSubDirs = ["0", "1", "2"];
|
|
886
988
|
const fdComponents = [];
|
|
887
989
|
for (const sub of fdSubDirs) {
|
|
888
990
|
const subDir = path9.join(fdDir, sub);
|
|
889
|
-
|
|
991
|
+
fs10.mkdirSync(subDir, { recursive: true });
|
|
890
992
|
const comp = createAndStoreKey(subDir, DAEMON_ROOT_KEY_COMPONENT_LENGTH);
|
|
891
993
|
fdComponents.push(comp);
|
|
892
994
|
}
|
|
@@ -895,17 +997,17 @@ function createMaterial(materialPath) {
|
|
|
895
997
|
createAndStoreEnKey(rootKey, ceDir);
|
|
896
998
|
}
|
|
897
999
|
function readSingleFile(dir) {
|
|
898
|
-
if (!
|
|
1000
|
+
if (!fs10.existsSync(dir) || !fs10.statSync(dir).isDirectory()) {
|
|
899
1001
|
throw new Error(`Missing signing material directory: ${dir}`);
|
|
900
1002
|
}
|
|
901
|
-
const files =
|
|
1003
|
+
const files = fs10.readdirSync(dir).filter((f) => f !== ".DS_Store");
|
|
902
1004
|
if (files.length !== 1) {
|
|
903
1005
|
throw new Error(`Signing material in ${dir} is illegal (expected exactly one file).`);
|
|
904
1006
|
}
|
|
905
|
-
return
|
|
1007
|
+
return fs10.readFileSync(path9.join(dir, files[0]));
|
|
906
1008
|
}
|
|
907
1009
|
function getKey(materialPath) {
|
|
908
|
-
if (!
|
|
1010
|
+
if (!fs10.existsSync(materialPath) || !fs10.statSync(materialPath).isDirectory()) {
|
|
909
1011
|
throw new Error("Material directory does not exist.");
|
|
910
1012
|
}
|
|
911
1013
|
const fdDir = path9.join(materialPath, "fd");
|
|
@@ -927,7 +1029,7 @@ function decryptPwd(encryptedHex, materialPath) {
|
|
|
927
1029
|
}
|
|
928
1030
|
|
|
929
1031
|
// src/sign/generateSigningConfigs.ts
|
|
930
|
-
var
|
|
1032
|
+
var fs11 = __toESM(require("fs"), 1);
|
|
931
1033
|
var path10 = __toESM(require("path"), 1);
|
|
932
1034
|
var import_node_child_process2 = require("child_process");
|
|
933
1035
|
var import_json52 = __toESM(require("json5"), 1);
|
|
@@ -982,6 +1084,7 @@ zAlF08DnbJrOOtOnQq5wHOPlDYB4OtUzOYJk9scotrEnJxJzGsh/
|
|
|
982
1084
|
// src/sign/generateSigningConfigs.ts
|
|
983
1085
|
var APL_VALUES = ["normal", "system_basic", "system_core"];
|
|
984
1086
|
var APP_FEATURE_VALUES = ["hos_normal_app", "hos_system_app"];
|
|
1087
|
+
var DEFAULT_PASSWORDS = { store: "123456", key: "123456" };
|
|
985
1088
|
function pickSigningKind(apl) {
|
|
986
1089
|
return apl === "normal" ? "profile-release" : "application-release";
|
|
987
1090
|
}
|
|
@@ -995,18 +1098,20 @@ var SIGNING_KIND_CONFIG = {
|
|
|
995
1098
|
certBasename: "OpenHarmonyApplication.cer"
|
|
996
1099
|
}
|
|
997
1100
|
};
|
|
998
|
-
function copyFilesToProject(projectDir, paths, kind, logger) {
|
|
1101
|
+
function copyFilesToProject(projectDir, paths, kind, logger, applicationCertPath) {
|
|
999
1102
|
logger.info("[sign] Copying signing material into project...");
|
|
1000
1103
|
const signaturesDir = path10.join(projectDir, "signatures");
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1104
|
+
fs11.mkdirSync(signaturesDir, { recursive: true });
|
|
1105
|
+
fs11.copyFileSync(paths.keystore, path10.join(signaturesDir, "OpenHarmony.p12"));
|
|
1106
|
+
fs11.copyFileSync(paths.profileCert, path10.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
|
|
1107
|
+
fs11.copyFileSync(paths.unsignedProfileTemplate, path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
|
|
1005
1108
|
if (kind === "application-release") {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1109
|
+
const dest = path10.join(signaturesDir, "OpenHarmonyApplication.cer");
|
|
1110
|
+
if (applicationCertPath) {
|
|
1111
|
+
fs11.copyFileSync(applicationCertPath, dest);
|
|
1112
|
+
} else {
|
|
1113
|
+
fs11.writeFileSync(dest, OPEN_HARMONY_APPLICATION_CERT);
|
|
1114
|
+
}
|
|
1010
1115
|
}
|
|
1011
1116
|
}
|
|
1012
1117
|
function resolveAppFeature(apl, override) {
|
|
@@ -1014,7 +1119,7 @@ function resolveAppFeature(apl, override) {
|
|
|
1014
1119
|
return apl === "normal" ? "hos_normal_app" : "hos_system_app";
|
|
1015
1120
|
}
|
|
1016
1121
|
function extractLeafCertificate(chainPath) {
|
|
1017
|
-
const certContent =
|
|
1122
|
+
const certContent = fs11.readFileSync(chainPath, "utf-8");
|
|
1018
1123
|
const parts = certContent.split("-----END CERTIFICATE-----");
|
|
1019
1124
|
if (parts.length < 3) {
|
|
1020
1125
|
throw new OniroError(`${chainPath} does not contain enough certificates.`);
|
|
@@ -1026,18 +1131,18 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
|
|
|
1026
1131
|
logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature}, kind=${kind})...`);
|
|
1027
1132
|
const appJsonPath = path10.join(projectDir, "AppScope/app.json5");
|
|
1028
1133
|
const profileTemplatePath = path10.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
|
|
1029
|
-
if (!
|
|
1134
|
+
if (!fs11.existsSync(appJsonPath)) {
|
|
1030
1135
|
throw new OniroError(`${appJsonPath} does not exist.`);
|
|
1031
1136
|
}
|
|
1032
1137
|
let appJson;
|
|
1033
1138
|
try {
|
|
1034
|
-
appJson = import_json52.default.parse(
|
|
1139
|
+
appJson = import_json52.default.parse(fs11.readFileSync(appJsonPath, "utf-8"));
|
|
1035
1140
|
} catch (e) {
|
|
1036
1141
|
throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
|
|
1037
1142
|
}
|
|
1038
1143
|
let profileTemplate;
|
|
1039
1144
|
try {
|
|
1040
|
-
profileTemplate = JSON.parse(
|
|
1145
|
+
profileTemplate = JSON.parse(fs11.readFileSync(profileTemplatePath, "utf-8"));
|
|
1041
1146
|
} catch (e) {
|
|
1042
1147
|
throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
|
|
1043
1148
|
}
|
|
@@ -1060,9 +1165,9 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
|
|
|
1060
1165
|
profileTemplate.acls = profileTemplate.acls ?? {};
|
|
1061
1166
|
profileTemplate.acls["allowed-acls"] = options.acls;
|
|
1062
1167
|
}
|
|
1063
|
-
|
|
1168
|
+
fs11.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
|
|
1064
1169
|
}
|
|
1065
|
-
function generateP7bFile(projectDir, paths, logger) {
|
|
1170
|
+
function generateP7bFile(projectDir, paths, logger, passwords) {
|
|
1066
1171
|
logger.info("[sign] Generating P7b profile via hap-sign-tool...");
|
|
1067
1172
|
const signaturesDir = path10.join(projectDir, "signatures");
|
|
1068
1173
|
const profileTemplatePath = path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
|
|
@@ -1086,9 +1191,9 @@ function generateP7bFile(projectDir, paths, logger) {
|
|
|
1086
1191
|
"-outFile",
|
|
1087
1192
|
outputProfilePath,
|
|
1088
1193
|
"-keyPwd",
|
|
1089
|
-
|
|
1194
|
+
passwords.key,
|
|
1090
1195
|
"-keystorePwd",
|
|
1091
|
-
|
|
1196
|
+
passwords.store
|
|
1092
1197
|
];
|
|
1093
1198
|
try {
|
|
1094
1199
|
(0, import_node_child_process2.execFileSync)("java", args, { stdio: "inherit" });
|
|
@@ -1101,10 +1206,10 @@ function generateP7bFile(projectDir, paths, logger) {
|
|
|
1101
1206
|
}
|
|
1102
1207
|
function detectSigningConfigNames(projectDir) {
|
|
1103
1208
|
const buildProfilePath = path10.join(projectDir, "build-profile.json5");
|
|
1104
|
-
if (!
|
|
1209
|
+
if (!fs11.existsSync(buildProfilePath)) return ["default"];
|
|
1105
1210
|
let parsed;
|
|
1106
1211
|
try {
|
|
1107
|
-
parsed = import_json52.default.parse(
|
|
1212
|
+
parsed = import_json52.default.parse(fs11.readFileSync(buildProfilePath, "utf-8"));
|
|
1108
1213
|
} catch {
|
|
1109
1214
|
return ["default"];
|
|
1110
1215
|
}
|
|
@@ -1119,16 +1224,16 @@ function detectSigningConfigNames(projectDir) {
|
|
|
1119
1224
|
}
|
|
1120
1225
|
return names.length > 0 ? names : ["default"];
|
|
1121
1226
|
}
|
|
1122
|
-
function updateBuildProfile(projectDir, kind, logger) {
|
|
1227
|
+
function updateBuildProfile(projectDir, kind, logger, passwords) {
|
|
1123
1228
|
logger.info("[sign] Writing signing configs into build-profile.json5...");
|
|
1124
1229
|
const materialDir = path10.join(projectDir, "signatures", "material");
|
|
1125
1230
|
const buildProfilePath = path10.join(projectDir, "build-profile.json5");
|
|
1126
|
-
const encryptedStorePassword = encryptPwd(
|
|
1127
|
-
const encryptedKeyPassword = encryptPwd(
|
|
1231
|
+
const encryptedStorePassword = encryptPwd(passwords.store, materialDir);
|
|
1232
|
+
const encryptedKeyPassword = encryptPwd(passwords.key, materialDir);
|
|
1128
1233
|
let buildProfile = { app: {} };
|
|
1129
|
-
if (
|
|
1234
|
+
if (fs11.existsSync(buildProfilePath)) {
|
|
1130
1235
|
try {
|
|
1131
|
-
buildProfile = import_json52.default.parse(
|
|
1236
|
+
buildProfile = import_json52.default.parse(fs11.readFileSync(buildProfilePath, "utf-8"));
|
|
1132
1237
|
} catch (e) {
|
|
1133
1238
|
throw new OniroError(`Error parsing ${buildProfilePath}: ${e.message}`, e);
|
|
1134
1239
|
}
|
|
@@ -1149,13 +1254,13 @@ function updateBuildProfile(projectDir, kind, logger) {
|
|
|
1149
1254
|
storeFile: "./signatures/OpenHarmony.p12"
|
|
1150
1255
|
}
|
|
1151
1256
|
}));
|
|
1152
|
-
|
|
1257
|
+
fs11.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
|
|
1153
1258
|
}
|
|
1154
1259
|
function prepareMaterialDirectory(projectDir, logger) {
|
|
1155
1260
|
logger.info("[sign] Generating fresh signing material...");
|
|
1156
1261
|
const materialDir = path10.join(projectDir, "signatures", "material");
|
|
1157
|
-
if (
|
|
1158
|
-
|
|
1262
|
+
if (fs11.existsSync(materialDir)) {
|
|
1263
|
+
fs11.rmSync(materialDir, { recursive: true, force: true });
|
|
1159
1264
|
}
|
|
1160
1265
|
createMaterial(materialDir);
|
|
1161
1266
|
}
|
|
@@ -1179,7 +1284,7 @@ function generateSigningConfigs(options) {
|
|
|
1179
1284
|
);
|
|
1180
1285
|
}
|
|
1181
1286
|
const sdkPath = path10.join(sdkHome, String(sdkVersion));
|
|
1182
|
-
if (!
|
|
1287
|
+
if (!fs11.existsSync(sdkPath)) {
|
|
1183
1288
|
throw new OniroError(`SDK path does not exist: ${sdkPath}`);
|
|
1184
1289
|
}
|
|
1185
1290
|
const paths = {
|
|
@@ -1189,17 +1294,18 @@ function generateSigningConfigs(options) {
|
|
|
1189
1294
|
unsignedProfileTemplate: path10.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
|
|
1190
1295
|
};
|
|
1191
1296
|
const kind = pickSigningKind(apl);
|
|
1297
|
+
const passwords = options.passwords ?? DEFAULT_PASSWORDS;
|
|
1192
1298
|
logger.info("[sign] Starting signing configuration generation...");
|
|
1193
|
-
copyFilesToProject(projectDir, paths, kind, logger);
|
|
1299
|
+
copyFilesToProject(projectDir, paths, kind, logger, options.applicationCertPath);
|
|
1194
1300
|
modifyProfileTemplate(projectDir, apl, appFeature, logger, { kind, acls: options.acls });
|
|
1195
|
-
generateP7bFile(projectDir, paths, logger);
|
|
1301
|
+
generateP7bFile(projectDir, paths, logger, passwords);
|
|
1196
1302
|
prepareMaterialDirectory(projectDir, logger);
|
|
1197
|
-
updateBuildProfile(projectDir, kind, logger);
|
|
1303
|
+
updateBuildProfile(projectDir, kind, logger, passwords);
|
|
1198
1304
|
logger.info("[sign] Signing configuration generated successfully.");
|
|
1199
1305
|
}
|
|
1200
1306
|
|
|
1201
1307
|
// src/emulator/install.ts
|
|
1202
|
-
var
|
|
1308
|
+
var fs12 = __toESM(require("fs"), 1);
|
|
1203
1309
|
var os7 = __toESM(require("os"), 1);
|
|
1204
1310
|
var path11 = __toESM(require("path"), 1);
|
|
1205
1311
|
async function installEmulator(opts) {
|
|
@@ -1207,63 +1313,142 @@ async function installEmulator(opts) {
|
|
|
1207
1313
|
const logger = opts.logger ?? noopLogger;
|
|
1208
1314
|
const url = config.get("emulatorUrl", defaultPaths.emulatorUrl);
|
|
1209
1315
|
const emulatorDir = getEmulatorDir(config);
|
|
1210
|
-
const tmpDir =
|
|
1316
|
+
const tmpDir = fs12.mkdtempSync(path11.join(os7.tmpdir(), "oniro-emulator-"));
|
|
1211
1317
|
const tmpZip = path11.join(tmpDir, "oniro_emulator.zip");
|
|
1212
1318
|
try {
|
|
1213
|
-
|
|
1319
|
+
fs12.mkdirSync(emulatorDir, { recursive: true });
|
|
1214
1320
|
progress?.report({ message: "Downloading emulator...", increment: 0 });
|
|
1215
1321
|
await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50 });
|
|
1216
1322
|
progress?.report({ message: "Extracting emulator...", increment: 0 });
|
|
1217
1323
|
await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
|
|
1218
1324
|
const runSh = path11.join(emulatorDir, "images", "run.sh");
|
|
1219
|
-
if (
|
|
1220
|
-
|
|
1325
|
+
if (fs12.existsSync(runSh)) {
|
|
1326
|
+
fs12.chmodSync(runSh, 493);
|
|
1221
1327
|
}
|
|
1222
1328
|
progress?.report({ message: "Finalizing installation...", increment: 5 });
|
|
1223
1329
|
} finally {
|
|
1224
|
-
|
|
1330
|
+
fs12.rmSync(tmpDir, { recursive: true, force: true });
|
|
1225
1331
|
}
|
|
1226
1332
|
}
|
|
1227
1333
|
function isEmulatorInstalled(config) {
|
|
1228
|
-
|
|
1334
|
+
const imagesDir = path11.join(getEmulatorDir(config), "images");
|
|
1335
|
+
return fs12.existsSync(path11.join(imagesDir, "run.sh")) || fs12.existsSync(path11.join(imagesDir, "run.bat"));
|
|
1229
1336
|
}
|
|
1230
1337
|
function removeEmulator(config) {
|
|
1231
1338
|
const emulatorDir = getEmulatorDir(config);
|
|
1232
|
-
if (
|
|
1233
|
-
|
|
1339
|
+
if (fs12.existsSync(emulatorDir)) {
|
|
1340
|
+
fs12.rmSync(emulatorDir, { recursive: true, force: true });
|
|
1234
1341
|
}
|
|
1235
1342
|
}
|
|
1236
1343
|
|
|
1237
1344
|
// src/emulator/lifecycle.ts
|
|
1238
|
-
var
|
|
1345
|
+
var fs13 = __toESM(require("fs"), 1);
|
|
1239
1346
|
var os8 = __toESM(require("os"), 1);
|
|
1240
1347
|
var path12 = __toESM(require("path"), 1);
|
|
1348
|
+
var import_node_child_process4 = require("child_process");
|
|
1349
|
+
|
|
1350
|
+
// src/hdc/exec.ts
|
|
1241
1351
|
var import_node_child_process3 = require("child_process");
|
|
1242
|
-
|
|
1243
|
-
|
|
1352
|
+
function runProcess(opts) {
|
|
1353
|
+
const { command, args, cwd, env, onOutput } = opts;
|
|
1354
|
+
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
1355
|
+
const { abortSignal } = opts;
|
|
1244
1356
|
return new Promise((resolve2, reject) => {
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1357
|
+
if (abortSignal?.aborted) {
|
|
1358
|
+
reject(new CancelledError(`Command aborted before start: ${command}`));
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
const child = (0, import_node_child_process3.spawn)(command, [...args], { cwd, env, shell: false });
|
|
1362
|
+
let stdout = "";
|
|
1363
|
+
let stderr = "";
|
|
1364
|
+
let settled = false;
|
|
1365
|
+
let timedOut = false;
|
|
1366
|
+
const cleanup = () => {
|
|
1367
|
+
clearTimeout(timer);
|
|
1368
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
1369
|
+
};
|
|
1370
|
+
const onAbort = () => {
|
|
1371
|
+
if (settled) return;
|
|
1372
|
+
settled = true;
|
|
1373
|
+
cleanup();
|
|
1374
|
+
try {
|
|
1375
|
+
child.kill("SIGKILL");
|
|
1376
|
+
} catch {
|
|
1377
|
+
}
|
|
1378
|
+
reject(new CancelledError(`Command aborted: ${command} ${args.join(" ")}`));
|
|
1379
|
+
};
|
|
1380
|
+
const timer = setTimeout(() => {
|
|
1381
|
+
timedOut = true;
|
|
1382
|
+
try {
|
|
1383
|
+
child.kill("SIGKILL");
|
|
1384
|
+
} catch {
|
|
1385
|
+
}
|
|
1386
|
+
}, timeoutMs);
|
|
1387
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1388
|
+
child.stdout.on("data", (d) => {
|
|
1389
|
+
const s = d.toString();
|
|
1390
|
+
stdout += s;
|
|
1391
|
+
onOutput?.(s, "stdout");
|
|
1392
|
+
});
|
|
1393
|
+
child.stderr.on("data", (d) => {
|
|
1394
|
+
const s = d.toString();
|
|
1395
|
+
stderr += s;
|
|
1396
|
+
onOutput?.(s, "stderr");
|
|
1397
|
+
});
|
|
1398
|
+
child.on("error", (err) => {
|
|
1399
|
+
if (settled) return;
|
|
1400
|
+
settled = true;
|
|
1401
|
+
cleanup();
|
|
1402
|
+
reject(new OniroError(`Failed to spawn ${command}: ${err.message}`, err));
|
|
1403
|
+
});
|
|
1404
|
+
child.on("close", (code) => {
|
|
1405
|
+
if (settled) return;
|
|
1406
|
+
settled = true;
|
|
1407
|
+
cleanup();
|
|
1408
|
+
if (timedOut) {
|
|
1409
|
+
reject(
|
|
1410
|
+
new OniroError(
|
|
1411
|
+
`Command timed out after ${timeoutMs}ms: ${command} ${args.join(" ")}` + (stderr.trim() ? `
|
|
1412
|
+
${stderr.trim()}` : "")
|
|
1413
|
+
)
|
|
1414
|
+
);
|
|
1415
|
+
return;
|
|
1253
1416
|
}
|
|
1417
|
+
resolve2({ code: code ?? -1, stdout, stderr });
|
|
1254
1418
|
});
|
|
1255
1419
|
});
|
|
1256
1420
|
}
|
|
1421
|
+
function hdcExec(opts) {
|
|
1422
|
+
const hdc = getHdcPath(opts.config);
|
|
1423
|
+
const argv = opts.deviceSerial ? ["-t", opts.deviceSerial, ...opts.args] : [...opts.args];
|
|
1424
|
+
return runProcess({
|
|
1425
|
+
command: hdc,
|
|
1426
|
+
args: argv,
|
|
1427
|
+
timeoutMs: opts.timeoutMs,
|
|
1428
|
+
abortSignal: opts.abortSignal,
|
|
1429
|
+
onOutput: opts.onOutput,
|
|
1430
|
+
logger: opts.logger
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
function shell(opts) {
|
|
1434
|
+
const { command, ...rest } = opts;
|
|
1435
|
+
return hdcExec({ ...rest, args: ["shell", command] });
|
|
1436
|
+
}
|
|
1437
|
+
function ensureOk(result, command) {
|
|
1438
|
+
if (result.code !== 0) {
|
|
1439
|
+
throw new CommandFailedError(command, result.code, result.stderr.trim());
|
|
1440
|
+
}
|
|
1441
|
+
return result;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// src/emulator/lifecycle.ts
|
|
1445
|
+
var PID_FILE = path12.join(os8.tmpdir(), "oniro_emulator.pid");
|
|
1257
1446
|
async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
|
|
1258
|
-
const hdc = getHdcPath(config);
|
|
1259
1447
|
try {
|
|
1260
|
-
await
|
|
1261
|
-
await
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
resolve2(stdout?.includes(address) ?? false);
|
|
1265
|
-
});
|
|
1266
|
-
});
|
|
1448
|
+
await hdcExec({ config, args: ["start", "-r"], logger });
|
|
1449
|
+
await hdcExec({ config, args: ["tconn", address], logger });
|
|
1450
|
+
const targets = await hdcExec({ config, args: ["list", "targets"], logger });
|
|
1451
|
+
return targets.stdout.includes(address);
|
|
1267
1452
|
} catch (err) {
|
|
1268
1453
|
logger.warn(`hdc connection attempt failed: ${err.message}`);
|
|
1269
1454
|
return false;
|
|
@@ -1276,18 +1461,18 @@ function resolveLauncher(imagesPath, headless, connect) {
|
|
|
1276
1461
|
if (headless) extra.push("--headless");
|
|
1277
1462
|
if (connect) extra.push("--connect", connect);
|
|
1278
1463
|
if (os8.platform() === "win32") {
|
|
1279
|
-
if (
|
|
1464
|
+
if (fs13.existsSync(runBat)) {
|
|
1280
1465
|
return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
|
|
1281
1466
|
}
|
|
1282
|
-
if (
|
|
1467
|
+
if (fs13.existsSync(runSh)) {
|
|
1283
1468
|
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1284
1469
|
}
|
|
1285
1470
|
throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
|
|
1286
1471
|
}
|
|
1287
|
-
if (
|
|
1472
|
+
if (fs13.existsSync(runSh)) {
|
|
1288
1473
|
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1289
1474
|
}
|
|
1290
|
-
if (
|
|
1475
|
+
if (fs13.existsSync(runBat)) {
|
|
1291
1476
|
return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
|
|
1292
1477
|
}
|
|
1293
1478
|
throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
|
|
@@ -1295,9 +1480,9 @@ function resolveLauncher(imagesPath, headless, connect) {
|
|
|
1295
1480
|
async function startEmulator(opts) {
|
|
1296
1481
|
const { config } = opts;
|
|
1297
1482
|
const logger = opts.logger ?? noopLogger;
|
|
1298
|
-
if (os8.platform() !== "win32" &&
|
|
1483
|
+
if (os8.platform() !== "win32" && fs13.existsSync(PID_FILE)) {
|
|
1299
1484
|
try {
|
|
1300
|
-
const pid = Number(
|
|
1485
|
+
const pid = Number(fs13.readFileSync(PID_FILE, "utf8").trim());
|
|
1301
1486
|
if (Number.isFinite(pid) && pid > 0) {
|
|
1302
1487
|
try {
|
|
1303
1488
|
process.kill(pid, 0);
|
|
@@ -1314,27 +1499,27 @@ async function startEmulator(opts) {
|
|
|
1314
1499
|
}
|
|
1315
1500
|
}
|
|
1316
1501
|
const imagesPath = path12.join(getEmulatorDir(config), "images");
|
|
1317
|
-
if (!
|
|
1502
|
+
if (!fs13.existsSync(imagesPath)) {
|
|
1318
1503
|
throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
|
|
1319
1504
|
}
|
|
1320
1505
|
const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
|
|
1321
1506
|
const logPath = opts.logFile ?? (os8.platform() === "win32" ? "NUL" : "/dev/null");
|
|
1322
|
-
const logFd =
|
|
1507
|
+
const logFd = fs13.openSync(logPath, "a");
|
|
1323
1508
|
let child;
|
|
1324
1509
|
try {
|
|
1325
|
-
child = (0,
|
|
1510
|
+
child = (0, import_node_child_process4.spawn)(launcher.command, [...launcher.args], {
|
|
1326
1511
|
cwd: launcher.cwd,
|
|
1327
1512
|
detached: true,
|
|
1328
1513
|
stdio: ["ignore", logFd, logFd],
|
|
1329
1514
|
windowsHide: true
|
|
1330
1515
|
});
|
|
1331
1516
|
} finally {
|
|
1332
|
-
|
|
1517
|
+
fs13.closeSync(logFd);
|
|
1333
1518
|
}
|
|
1334
1519
|
if (!child.pid) {
|
|
1335
1520
|
throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
|
|
1336
1521
|
}
|
|
1337
|
-
|
|
1522
|
+
fs13.writeFileSync(PID_FILE, String(child.pid));
|
|
1338
1523
|
child.unref();
|
|
1339
1524
|
logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
|
|
1340
1525
|
const earlyExit = new Promise((resolve2) => {
|
|
@@ -1381,24 +1566,24 @@ ${tail}` : ""}`);
|
|
|
1381
1566
|
}
|
|
1382
1567
|
function readTail(filePath, bytes) {
|
|
1383
1568
|
try {
|
|
1384
|
-
const stat =
|
|
1569
|
+
const stat = fs13.statSync(filePath);
|
|
1385
1570
|
const start = Math.max(0, stat.size - bytes);
|
|
1386
|
-
const fd =
|
|
1571
|
+
const fd = fs13.openSync(filePath, "r");
|
|
1387
1572
|
try {
|
|
1388
1573
|
const buf = Buffer.alloc(stat.size - start);
|
|
1389
|
-
|
|
1574
|
+
fs13.readSync(fd, buf, 0, buf.length, start);
|
|
1390
1575
|
return buf.toString("utf8");
|
|
1391
1576
|
} finally {
|
|
1392
|
-
|
|
1577
|
+
fs13.closeSync(fd);
|
|
1393
1578
|
}
|
|
1394
1579
|
} catch {
|
|
1395
1580
|
return "";
|
|
1396
1581
|
}
|
|
1397
1582
|
}
|
|
1398
1583
|
async function stopEmulator(logger = noopLogger) {
|
|
1399
|
-
if (
|
|
1584
|
+
if (fs13.existsSync(PID_FILE)) {
|
|
1400
1585
|
try {
|
|
1401
|
-
const pid = Number(
|
|
1586
|
+
const pid = Number(fs13.readFileSync(PID_FILE, "utf8").trim());
|
|
1402
1587
|
if (Number.isFinite(pid) && pid > 0) {
|
|
1403
1588
|
try {
|
|
1404
1589
|
process.kill(pid, "SIGTERM");
|
|
@@ -1411,27 +1596,194 @@ async function stopEmulator(logger = noopLogger) {
|
|
|
1411
1596
|
logger.warn(`Could not read PID file: ${err.message}`);
|
|
1412
1597
|
}
|
|
1413
1598
|
}
|
|
1414
|
-
const killCmd = os8.platform() === "win32" ? "taskkill /IM qemu-system-x86_64.exe /F" : "pkill -f qemu-system-x86_64";
|
|
1599
|
+
const [killCmd, ...killArgs] = os8.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
|
|
1415
1600
|
try {
|
|
1416
|
-
await
|
|
1417
|
-
} catch {
|
|
1601
|
+
await runProcess({ command: killCmd, args: killArgs, logger });
|
|
1602
|
+
} catch (err) {
|
|
1603
|
+
logger.warn(`[emulator] could not run ${killCmd}: ${err.message}`);
|
|
1418
1604
|
}
|
|
1419
|
-
|
|
1605
|
+
fs13.rm(PID_FILE, { force: true }, (err) => {
|
|
1420
1606
|
if (err) logger.warn(`Could not remove PID file: ${err.message}`);
|
|
1421
1607
|
});
|
|
1422
1608
|
}
|
|
1423
1609
|
|
|
1610
|
+
// src/hdc/devices.ts
|
|
1611
|
+
var STATUS_TOKENS = /* @__PURE__ */ new Set([
|
|
1612
|
+
"Connected",
|
|
1613
|
+
"Offline",
|
|
1614
|
+
"Unauthorized",
|
|
1615
|
+
"Authorizing",
|
|
1616
|
+
"Disconnected",
|
|
1617
|
+
"Recovery"
|
|
1618
|
+
]);
|
|
1619
|
+
var CONNECTION_TOKENS = /* @__PURE__ */ new Set(["USB", "TCP", "UART", "BT", "UNKNOWN"]);
|
|
1620
|
+
function parseDeviceList(stdout) {
|
|
1621
|
+
const devices = [];
|
|
1622
|
+
for (const line of stdout.split("\n")) {
|
|
1623
|
+
const trimmed = line.trim();
|
|
1624
|
+
if (!trimmed || trimmed.startsWith("[Empty]")) continue;
|
|
1625
|
+
const cols = trimmed.split(/\s+/);
|
|
1626
|
+
const serial = cols[0];
|
|
1627
|
+
if (!serial) continue;
|
|
1628
|
+
const rest = cols.slice(1);
|
|
1629
|
+
const connection = rest.find((c) => CONNECTION_TOKENS.has(c));
|
|
1630
|
+
const status = rest.find((c) => STATUS_TOKENS.has(c)) ?? "Unknown";
|
|
1631
|
+
const info = { serial, status };
|
|
1632
|
+
if (connection) info.connection = connection;
|
|
1633
|
+
devices.push(info);
|
|
1634
|
+
}
|
|
1635
|
+
return devices;
|
|
1636
|
+
}
|
|
1637
|
+
async function listDevices(config, opts = {}) {
|
|
1638
|
+
const res = await hdcExec({
|
|
1639
|
+
config,
|
|
1640
|
+
args: ["list", "targets", "-v"],
|
|
1641
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1642
|
+
logger: opts.logger
|
|
1643
|
+
});
|
|
1644
|
+
return parseDeviceList(res.stdout);
|
|
1645
|
+
}
|
|
1646
|
+
async function selectDevice(config, serial, opts = {}) {
|
|
1647
|
+
if (serial && serial !== "auto") return serial;
|
|
1648
|
+
const envSerial = process.env.ONIRO_DEVICE_SERIAL || process.env.DEVICE_SERIAL;
|
|
1649
|
+
if (envSerial && envSerial !== "auto") return envSerial;
|
|
1650
|
+
const usable = (await listDevices(config, opts)).filter(
|
|
1651
|
+
(d) => d.status === "Connected" || d.status === "Unknown"
|
|
1652
|
+
);
|
|
1653
|
+
if (usable.length === 1) return usable[0].serial;
|
|
1654
|
+
if (usable.length === 0) {
|
|
1655
|
+
throw new OniroError("No connected device found. Connect a device/emulator or set ONIRO_DEVICE_SERIAL.");
|
|
1656
|
+
}
|
|
1657
|
+
throw new OniroError(
|
|
1658
|
+
`Multiple devices connected (${usable.map((d) => d.serial).join(", ")}). Set ONIRO_DEVICE_SERIAL to choose one.`
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// src/hdc/param.ts
|
|
1663
|
+
async function paramGet(config, key, opts = {}) {
|
|
1664
|
+
const res = await shell({
|
|
1665
|
+
config,
|
|
1666
|
+
command: `param get ${key}`,
|
|
1667
|
+
deviceSerial: opts.deviceSerial,
|
|
1668
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1669
|
+
logger: opts.logger
|
|
1670
|
+
});
|
|
1671
|
+
ensureOk(res, `hdc shell param get ${key}`);
|
|
1672
|
+
return res.stdout.trim();
|
|
1673
|
+
}
|
|
1674
|
+
async function paramSet(config, key, value, opts = {}) {
|
|
1675
|
+
const res = await shell({
|
|
1676
|
+
config,
|
|
1677
|
+
command: `param set ${key} ${value}`,
|
|
1678
|
+
deviceSerial: opts.deviceSerial,
|
|
1679
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1680
|
+
logger: opts.logger
|
|
1681
|
+
});
|
|
1682
|
+
ensureOk(res, `hdc shell param set ${key} ${value}`);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// src/hdc/files.ts
|
|
1686
|
+
async function sendFile(opts) {
|
|
1687
|
+
const res = await hdcExec({
|
|
1688
|
+
config: opts.config,
|
|
1689
|
+
args: ["file", "send", opts.local, opts.remote],
|
|
1690
|
+
deviceSerial: opts.deviceSerial,
|
|
1691
|
+
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
1692
|
+
abortSignal: opts.abortSignal,
|
|
1693
|
+
onOutput: opts.onOutput,
|
|
1694
|
+
logger: opts.logger
|
|
1695
|
+
});
|
|
1696
|
+
ensureOk(res, `hdc file send ${opts.local} ${opts.remote}`);
|
|
1697
|
+
}
|
|
1698
|
+
async function recvFile(opts) {
|
|
1699
|
+
const res = await hdcExec({
|
|
1700
|
+
config: opts.config,
|
|
1701
|
+
args: ["file", "recv", opts.remote, opts.local],
|
|
1702
|
+
deviceSerial: opts.deviceSerial,
|
|
1703
|
+
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
1704
|
+
abortSignal: opts.abortSignal,
|
|
1705
|
+
onOutput: opts.onOutput,
|
|
1706
|
+
logger: opts.logger
|
|
1707
|
+
});
|
|
1708
|
+
ensureOk(res, `hdc file recv ${opts.remote} ${opts.local}`);
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
// src/hdc/display.ts
|
|
1712
|
+
async function getDisplaySize(config, opts = {}) {
|
|
1713
|
+
const res = await shell({
|
|
1714
|
+
config,
|
|
1715
|
+
command: "hidumper -s 10 -a screen",
|
|
1716
|
+
deviceSerial: opts.deviceSerial,
|
|
1717
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1718
|
+
logger: opts.logger
|
|
1719
|
+
});
|
|
1720
|
+
if (res.code !== 0) return null;
|
|
1721
|
+
const m = res.stdout.match(/render resolution=(\d+)x(\d+)/);
|
|
1722
|
+
if (!m) return null;
|
|
1723
|
+
return { width: Number(m[1]), height: Number(m[2]) };
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// src/hdc/wait.ts
|
|
1727
|
+
function delay(ms, signal) {
|
|
1728
|
+
return new Promise((resolve2, reject) => {
|
|
1729
|
+
if (signal?.aborted) {
|
|
1730
|
+
reject(new CancelledError("Wait cancelled."));
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
const onAbort = () => {
|
|
1734
|
+
cleanup();
|
|
1735
|
+
reject(new CancelledError("Wait cancelled."));
|
|
1736
|
+
};
|
|
1737
|
+
const cleanup = () => {
|
|
1738
|
+
clearTimeout(timer);
|
|
1739
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1740
|
+
};
|
|
1741
|
+
const timer = setTimeout(() => {
|
|
1742
|
+
cleanup();
|
|
1743
|
+
resolve2();
|
|
1744
|
+
}, ms);
|
|
1745
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
async function waitForCondition(opts) {
|
|
1749
|
+
const pollMs = opts.pollMs ?? 2e3;
|
|
1750
|
+
const heartbeatEvery = opts.heartbeatEvery ?? 6;
|
|
1751
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
1752
|
+
let attempt = 0;
|
|
1753
|
+
while (Date.now() < deadline) {
|
|
1754
|
+
if (opts.abortSignal?.aborted) throw new CancelledError("Wait cancelled.");
|
|
1755
|
+
attempt++;
|
|
1756
|
+
let ok = false;
|
|
1757
|
+
try {
|
|
1758
|
+
ok = await opts.probe();
|
|
1759
|
+
} catch {
|
|
1760
|
+
ok = false;
|
|
1761
|
+
}
|
|
1762
|
+
if (ok) return;
|
|
1763
|
+
if (opts.onHeartbeat && attempt % heartbeatEvery === 0) {
|
|
1764
|
+
opts.onHeartbeat(attempt, Math.max(0, deadline - Date.now()));
|
|
1765
|
+
}
|
|
1766
|
+
await delay(pollMs, opts.abortSignal);
|
|
1767
|
+
}
|
|
1768
|
+
throw new OniroError(opts.timeoutMessage ?? `Condition not met within ${opts.timeoutMs}ms.`);
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
// src/hdc/app.ts
|
|
1772
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
1773
|
+
var path14 = __toESM(require("path"), 1);
|
|
1774
|
+
var import_node_child_process5 = require("child_process");
|
|
1775
|
+
|
|
1424
1776
|
// src/hdc/project.ts
|
|
1425
|
-
var
|
|
1777
|
+
var fs14 = __toESM(require("fs"), 1);
|
|
1426
1778
|
var path13 = __toESM(require("path"), 1);
|
|
1427
1779
|
var import_json53 = __toESM(require("json5"), 1);
|
|
1428
1780
|
function readJson5(filePath) {
|
|
1429
|
-
const content =
|
|
1781
|
+
const content = fs14.readFileSync(filePath, "utf-8");
|
|
1430
1782
|
return import_json53.default.parse(content);
|
|
1431
1783
|
}
|
|
1432
1784
|
function getBundleName(projectDir) {
|
|
1433
1785
|
const appJsonPath = path13.join(projectDir, "AppScope", "app.json5");
|
|
1434
|
-
if (!
|
|
1786
|
+
if (!fs14.existsSync(appJsonPath)) {
|
|
1435
1787
|
throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
|
|
1436
1788
|
}
|
|
1437
1789
|
const appJson = readJson5(appJsonPath);
|
|
@@ -1440,59 +1792,72 @@ function getBundleName(projectDir) {
|
|
|
1440
1792
|
}
|
|
1441
1793
|
return appJson.app.bundleName;
|
|
1442
1794
|
}
|
|
1443
|
-
function getMainAbility(projectDir, moduleName = "entry") {
|
|
1795
|
+
function getMainAbility(projectDir, moduleName = "entry", abilityName) {
|
|
1444
1796
|
const moduleJsonPath = path13.join(projectDir, moduleName, "src", "main", "module.json5");
|
|
1445
|
-
if (!
|
|
1797
|
+
if (!fs14.existsSync(moduleJsonPath)) {
|
|
1446
1798
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
1447
1799
|
}
|
|
1448
1800
|
const moduleJson = readJson5(moduleJsonPath);
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1801
|
+
const module2 = moduleJson.module ?? {};
|
|
1802
|
+
if (abilityName) {
|
|
1803
|
+
const match = module2.abilities?.find((a) => a.name === abilityName);
|
|
1804
|
+
if (match?.name) return match.name;
|
|
1805
|
+
throw new OniroError(`Ability "${abilityName}" not found in module.json5 at ${moduleJsonPath}`);
|
|
1806
|
+
}
|
|
1807
|
+
if (module2.mainElement) return module2.mainElement;
|
|
1808
|
+
const abilities = module2.abilities ?? [];
|
|
1809
|
+
const visible = abilities.find((a) => a.visible && a.name);
|
|
1810
|
+
if (visible?.name) return visible.name;
|
|
1811
|
+
const first = abilities.find((a) => a.name);
|
|
1812
|
+
if (first?.name) return first.name;
|
|
1813
|
+
throw new OniroError("mainElement not found in module.json5");
|
|
1453
1814
|
}
|
|
1454
1815
|
|
|
1455
1816
|
// 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
1817
|
async function installApp(opts) {
|
|
1474
|
-
const logger = opts.logger ?? noopLogger;
|
|
1818
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1475
1819
|
const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
|
|
1476
1820
|
const hapPath = path14.isAbsolute(relativeHapPath) ? relativeHapPath : path14.join(opts.projectDir, relativeHapPath);
|
|
1477
|
-
if (!
|
|
1821
|
+
if (!fs15.existsSync(hapPath)) {
|
|
1478
1822
|
throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
|
|
1479
1823
|
}
|
|
1480
|
-
const
|
|
1481
|
-
|
|
1824
|
+
const result = await hdcExec({ config: opts.config, args: ["install", hapPath], timeoutMs: 3e5 });
|
|
1825
|
+
if (result.stdout.trim()) logger.info(result.stdout.trim());
|
|
1826
|
+
if (result.stderr.trim()) logger.warn(result.stderr.trim());
|
|
1827
|
+
ensureOk(result, `hdc install ${hapPath}`);
|
|
1828
|
+
let bundleName = "";
|
|
1829
|
+
try {
|
|
1830
|
+
bundleName = getBundleName(opts.projectDir);
|
|
1831
|
+
} catch {
|
|
1832
|
+
}
|
|
1833
|
+
return { installed: true, bundleName, hapPath, output: `${result.stdout}${result.stderr}`.trim() };
|
|
1834
|
+
}
|
|
1835
|
+
function detectAaStartFailure(output) {
|
|
1836
|
+
return /failed to start ability|Error Code:\s*\d|Error Message:/i.test(output) ? output.trim() : null;
|
|
1482
1837
|
}
|
|
1483
1838
|
async function launchApp(opts) {
|
|
1484
|
-
const logger = opts.logger ?? noopLogger;
|
|
1839
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1485
1840
|
const bundleName = getBundleName(opts.projectDir);
|
|
1486
|
-
const mainAbility = getMainAbility(opts.projectDir, opts.moduleName);
|
|
1487
|
-
const
|
|
1488
|
-
|
|
1841
|
+
const mainAbility = getMainAbility(opts.projectDir, opts.moduleName, opts.abilityName);
|
|
1842
|
+
const result = await hdcExec({
|
|
1843
|
+
config: opts.config,
|
|
1844
|
+
args: ["shell", "aa", "start", "-a", mainAbility, "-b", bundleName]
|
|
1845
|
+
});
|
|
1846
|
+
if (result.stdout.trim()) logger.info(result.stdout.trim());
|
|
1847
|
+
if (result.stderr.trim()) logger.warn(result.stderr.trim());
|
|
1848
|
+
ensureOk(result, `hdc shell aa start -a ${mainAbility} -b ${bundleName}`);
|
|
1849
|
+
const failure = detectAaStartFailure(`${result.stdout}
|
|
1850
|
+
${result.stderr}`);
|
|
1851
|
+
if (failure) {
|
|
1852
|
+
throw new OniroError(`Failed to launch ${bundleName} (${mainAbility}): ${failure}`);
|
|
1853
|
+
}
|
|
1489
1854
|
}
|
|
1490
1855
|
function listRunningProcesses(config, options = {}) {
|
|
1491
1856
|
const logger = options.logger ?? noopLogger;
|
|
1492
1857
|
const timeoutMs = options.timeoutMs ?? 1e3;
|
|
1493
1858
|
const hdc = getHdcPath(config);
|
|
1494
1859
|
return new Promise((resolve2, reject) => {
|
|
1495
|
-
const proc = (0,
|
|
1860
|
+
const proc = (0, import_node_child_process5.spawn)(hdc, ["track-jpid"]);
|
|
1496
1861
|
const processes = [];
|
|
1497
1862
|
let resolved = false;
|
|
1498
1863
|
proc.stdout.on("data", (data) => {
|
|
@@ -1546,14 +1911,395 @@ async function findAppProcessId(config, projectDir, logger) {
|
|
|
1546
1911
|
const result = await listRunningProcesses(config, { targetProcessName: bundleName, logger });
|
|
1547
1912
|
return result;
|
|
1548
1913
|
}
|
|
1914
|
+
async function findRunningProcess(opts) {
|
|
1915
|
+
const safe = opts.bundle.replace(/'/g, `'\\''`);
|
|
1916
|
+
const res = await shell({
|
|
1917
|
+
config: opts.config,
|
|
1918
|
+
command: `pidof '${safe}'`,
|
|
1919
|
+
deviceSerial: opts.deviceSerial,
|
|
1920
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1921
|
+
logger: opts.logger
|
|
1922
|
+
});
|
|
1923
|
+
const pid = res.stdout.trim().split(/\s+/).filter(Boolean)[0];
|
|
1924
|
+
if (res.code !== 0 || !pid || !/^\d+$/.test(pid)) return null;
|
|
1925
|
+
return { pid, name: opts.bundle };
|
|
1926
|
+
}
|
|
1927
|
+
async function uninstallApp(opts) {
|
|
1928
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1929
|
+
const res = await hdcExec({
|
|
1930
|
+
config: opts.config,
|
|
1931
|
+
args: ["uninstall", opts.bundle],
|
|
1932
|
+
deviceSerial: opts.deviceSerial,
|
|
1933
|
+
timeoutMs: opts.timeoutMs ?? 12e4
|
|
1934
|
+
});
|
|
1935
|
+
if (res.stdout.trim()) logger.info(res.stdout.trim());
|
|
1936
|
+
if (res.stderr.trim()) logger.warn(res.stderr.trim());
|
|
1937
|
+
ensureOk(res, `hdc uninstall ${opts.bundle}`);
|
|
1938
|
+
}
|
|
1939
|
+
async function forceStop(opts) {
|
|
1940
|
+
const res = await shell({
|
|
1941
|
+
config: opts.config,
|
|
1942
|
+
command: `aa force-stop ${opts.bundle}`,
|
|
1943
|
+
deviceSerial: opts.deviceSerial,
|
|
1944
|
+
timeoutMs: opts.timeoutMs ?? 3e4,
|
|
1945
|
+
logger: opts.logger
|
|
1946
|
+
});
|
|
1947
|
+
ensureOk(res, `hdc shell aa force-stop ${opts.bundle}`);
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// src/hdc/lifecycle.ts
|
|
1951
|
+
async function isReachable(config, deviceSerial) {
|
|
1952
|
+
try {
|
|
1953
|
+
const res = await shell({ config, command: "echo oniro_ping", deviceSerial, timeoutMs: 5e3 });
|
|
1954
|
+
return res.code === 0 && res.stdout.includes("oniro_ping");
|
|
1955
|
+
} catch {
|
|
1956
|
+
return false;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
var TCP_SERIAL_RE = /^.+:\d+$/;
|
|
1960
|
+
async function reconnectIfTcp(config, deviceSerial, logger) {
|
|
1961
|
+
if (!deviceSerial || !TCP_SERIAL_RE.test(deviceSerial)) return;
|
|
1962
|
+
try {
|
|
1963
|
+
await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
|
|
1964
|
+
} catch {
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
async function waitForBundle(opts) {
|
|
1968
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1969
|
+
const timeoutMs = opts.timeoutMs ?? 18e4;
|
|
1970
|
+
await waitForCondition({
|
|
1971
|
+
probe: async () => {
|
|
1972
|
+
await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
|
|
1973
|
+
return await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
|
|
1974
|
+
},
|
|
1975
|
+
timeoutMs,
|
|
1976
|
+
pollMs: 2e3,
|
|
1977
|
+
abortSignal: opts.abortSignal,
|
|
1978
|
+
onHeartbeat: (attempt, remaining) => logger.info(`waiting for ${opts.bundle} (attempt ${attempt}, ${Math.floor(remaining / 1e3)}s left)`),
|
|
1979
|
+
timeoutMessage: `Bundle ${opts.bundle} did not start within ${timeoutMs}ms.`
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
async function waitForBoot(opts) {
|
|
1983
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1984
|
+
const timeoutMs = opts.timeoutMs ?? 18e4;
|
|
1985
|
+
await waitForCondition({
|
|
1986
|
+
probe: async () => {
|
|
1987
|
+
await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
|
|
1988
|
+
if (!await isReachable(opts.config, opts.deviceSerial)) return false;
|
|
1989
|
+
if (!opts.untilPidOf) return true;
|
|
1990
|
+
return await findRunningProcess({ config: opts.config, bundle: opts.untilPidOf, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
|
|
1991
|
+
},
|
|
1992
|
+
timeoutMs,
|
|
1993
|
+
pollMs: 2e3,
|
|
1994
|
+
abortSignal: opts.abortSignal,
|
|
1995
|
+
onHeartbeat: (attempt, remaining) => logger.info(`waiting for boot (attempt ${attempt}, ${Math.floor(remaining / 1e3)}s left)`),
|
|
1996
|
+
timeoutMessage: `Device did not boot within ${timeoutMs}ms.`
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
async function reboot(opts) {
|
|
2000
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
2001
|
+
const mode = opts.mode ?? "system";
|
|
2002
|
+
const timeoutMs = opts.timeoutMs ?? 18e4;
|
|
2003
|
+
logger.info(`Rebooting device (${mode})...`);
|
|
2004
|
+
try {
|
|
2005
|
+
if (mode === "system") {
|
|
2006
|
+
await paramSet(opts.config, "ohos.startup.powerctrl", "reboot", {
|
|
2007
|
+
deviceSerial: opts.deviceSerial,
|
|
2008
|
+
timeoutMs: 15e3,
|
|
2009
|
+
logger: opts.logger
|
|
2010
|
+
});
|
|
2011
|
+
} else {
|
|
2012
|
+
await shell({ config: opts.config, command: `reboot ${mode}`, deviceSerial: opts.deviceSerial, timeoutMs: 15e3 });
|
|
2013
|
+
}
|
|
2014
|
+
} catch (err) {
|
|
2015
|
+
logger.debug(`reboot command did not return cleanly: ${err.message}`);
|
|
2016
|
+
}
|
|
2017
|
+
if (mode !== "system" || !opts.waitForBundle) return;
|
|
2018
|
+
try {
|
|
2019
|
+
await waitForCondition({
|
|
2020
|
+
probe: async () => !await isReachable(opts.config, opts.deviceSerial),
|
|
2021
|
+
timeoutMs: Math.min(timeoutMs, 3e4),
|
|
2022
|
+
pollMs: 1500,
|
|
2023
|
+
abortSignal: opts.abortSignal,
|
|
2024
|
+
timeoutMessage: "device did not become unreachable"
|
|
2025
|
+
});
|
|
2026
|
+
} catch {
|
|
2027
|
+
}
|
|
2028
|
+
await waitForBundle({
|
|
2029
|
+
config: opts.config,
|
|
2030
|
+
bundle: opts.waitForBundle,
|
|
2031
|
+
deviceSerial: opts.deviceSerial,
|
|
2032
|
+
timeoutMs,
|
|
2033
|
+
abortSignal: opts.abortSignal,
|
|
2034
|
+
logger: opts.logger
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
// src/hdc/screen.ts
|
|
2039
|
+
var fs16 = __toESM(require("fs"), 1);
|
|
2040
|
+
var os9 = __toESM(require("os"), 1);
|
|
2041
|
+
var path15 = __toESM(require("path"), 1);
|
|
2042
|
+
var import_node_crypto = require("crypto");
|
|
2043
|
+
function tmpName(ext) {
|
|
2044
|
+
return path15.join(os9.tmpdir(), `oniro_screen_${(0, import_node_crypto.randomBytes)(6).toString("hex")}.${ext}`);
|
|
2045
|
+
}
|
|
2046
|
+
async function takeScreenshot(opts) {
|
|
2047
|
+
const remote = "/data/local/tmp/oniro_screenshot.jpeg";
|
|
2048
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
2049
|
+
const cap = await shell({ config: opts.config, command: `snapshot_display -f ${remote}`, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2050
|
+
ensureOk(cap, `snapshot_display -f ${remote}`);
|
|
2051
|
+
const local = tmpName("jpeg");
|
|
2052
|
+
try {
|
|
2053
|
+
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2054
|
+
const pixels = await fs16.promises.readFile(local);
|
|
2055
|
+
const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
|
|
2056
|
+
return { pixels, width: size.width, height: size.height };
|
|
2057
|
+
} finally {
|
|
2058
|
+
fs16.promises.unlink(local).catch(() => {
|
|
2059
|
+
});
|
|
2060
|
+
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
async function captureBurst(opts) {
|
|
2065
|
+
const count = Math.max(1, Math.floor(opts.count));
|
|
2066
|
+
const remoteDir = `/data/local/tmp/oniro_burst_${(0, import_node_crypto.randomBytes)(6).toString("hex")}`;
|
|
2067
|
+
const sleepSec = opts.intervalMs > 0 ? (opts.intervalMs / 1e3).toFixed(3) : "0";
|
|
2068
|
+
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`;
|
|
2069
|
+
const cap = await shell({ config: opts.config, command: loop, deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 12e4 });
|
|
2070
|
+
ensureOk(cap, "snapshot_display burst");
|
|
2071
|
+
const frames = [];
|
|
2072
|
+
try {
|
|
2073
|
+
for (let i = 0; i < count; i++) {
|
|
2074
|
+
const local = tmpName(`burst_${i}.jpeg`);
|
|
2075
|
+
try {
|
|
2076
|
+
await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
|
|
2077
|
+
frames.push(await fs16.promises.readFile(local));
|
|
2078
|
+
} finally {
|
|
2079
|
+
fs16.promises.unlink(local).catch(() => {
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
} finally {
|
|
2084
|
+
void shell({ config: opts.config, command: `rm -rf ${remoteDir}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
return frames;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
// src/hdc/input.ts
|
|
2091
|
+
function reqNum(value, name, type) {
|
|
2092
|
+
if (value === void 0 || Number.isNaN(value)) {
|
|
2093
|
+
throw new OniroError(`send input "${type}" requires a numeric ${name}.`);
|
|
2094
|
+
}
|
|
2095
|
+
return value;
|
|
2096
|
+
}
|
|
2097
|
+
function buildInputCommand(opts) {
|
|
2098
|
+
switch (opts.type) {
|
|
2099
|
+
case "click":
|
|
2100
|
+
case "doubleClick":
|
|
2101
|
+
case "longClick":
|
|
2102
|
+
return `uitest uiInput ${opts.type} ${reqNum(opts.pxX, "pxX", opts.type)} ${reqNum(opts.pxY, "pxY", opts.type)}`;
|
|
2103
|
+
case "swipe":
|
|
2104
|
+
case "drag":
|
|
2105
|
+
case "fling": {
|
|
2106
|
+
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)}`;
|
|
2107
|
+
return opts.speed !== void 0 ? `${base} ${opts.speed}` : base;
|
|
2108
|
+
}
|
|
2109
|
+
case "keyEvent":
|
|
2110
|
+
if (opts.key === void 0 || opts.key === "") throw new OniroError('send input "keyEvent" requires a key.');
|
|
2111
|
+
return `uitest uiInput keyEvent ${opts.key}`;
|
|
2112
|
+
case "inputText": {
|
|
2113
|
+
const t = String(opts.text ?? "").replace(/'/g, `'\\''`);
|
|
2114
|
+
if (opts.pxX !== void 0 && opts.pxY !== void 0) {
|
|
2115
|
+
return `uitest uiInput inputText ${opts.pxX} ${opts.pxY} '${t}'`;
|
|
2116
|
+
}
|
|
2117
|
+
return `uitest uiInput text '${t}'`;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
async function sendInput(opts) {
|
|
2122
|
+
const cmd = buildInputCommand(opts);
|
|
2123
|
+
const res = await shell({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
|
|
2124
|
+
ensureOk(res, `hdc shell ${cmd}`);
|
|
2125
|
+
}
|
|
2126
|
+
function buildGestureCommands(waypoints) {
|
|
2127
|
+
if (waypoints.length < 2) {
|
|
2128
|
+
throw new OniroError("sendGesture requires at least two waypoints.");
|
|
2129
|
+
}
|
|
2130
|
+
const cmds = [];
|
|
2131
|
+
for (let i = 1; i < waypoints.length; i++) {
|
|
2132
|
+
const a = waypoints[i - 1];
|
|
2133
|
+
const b = waypoints[i];
|
|
2134
|
+
const dt = b.t - a.t;
|
|
2135
|
+
const dist = Math.hypot(b.x - a.x, b.y - a.y);
|
|
2136
|
+
let cmd = `uitest uiInput drag ${a.x} ${a.y} ${b.x} ${b.y}`;
|
|
2137
|
+
if (dt > 0 && dist > 0) cmd += ` ${Math.round(dist / (dt / 1e3))}`;
|
|
2138
|
+
cmds.push(cmd);
|
|
2139
|
+
}
|
|
2140
|
+
return cmds;
|
|
2141
|
+
}
|
|
2142
|
+
function buildRawTouchCommand(events) {
|
|
2143
|
+
if (events.length === 0) throw new OniroError("sendRawTouch requires at least one event.");
|
|
2144
|
+
const ops = [];
|
|
2145
|
+
let prev = null;
|
|
2146
|
+
for (const ev of events) {
|
|
2147
|
+
if (prev && ev.t > prev.t) ops.push(`-i ${ev.t - prev.t}`);
|
|
2148
|
+
if (ev.type === "down") ops.push(`-d ${ev.x} ${ev.y}`);
|
|
2149
|
+
else if (ev.type === "up") ops.push(`-u ${ev.x} ${ev.y}`);
|
|
2150
|
+
else ops.push(`-m ${prev ? `${prev.x} ${prev.y} ` : ""}${ev.x} ${ev.y}`);
|
|
2151
|
+
prev = ev;
|
|
2152
|
+
}
|
|
2153
|
+
return `uinput -T ${ops.join(" ")}`;
|
|
2154
|
+
}
|
|
2155
|
+
function waypointsToRawEvents(waypoints, holdStartMs = 0, holdEndMs = 0) {
|
|
2156
|
+
if (waypoints.length < 2) throw new OniroError("sendGesture requires at least two waypoints.");
|
|
2157
|
+
const first = waypoints[0];
|
|
2158
|
+
const events = [{ type: "down", x: first.x, y: first.y, t: 0 }];
|
|
2159
|
+
for (let i = 1; i < waypoints.length; i++) {
|
|
2160
|
+
const w = waypoints[i];
|
|
2161
|
+
events.push({ type: "move", x: w.x, y: w.y, t: w.t + holdStartMs });
|
|
2162
|
+
}
|
|
2163
|
+
const last = waypoints[waypoints.length - 1];
|
|
2164
|
+
events.push({ type: "up", x: last.x, y: last.y, t: last.t + holdStartMs + holdEndMs });
|
|
2165
|
+
return events;
|
|
2166
|
+
}
|
|
2167
|
+
async function sendGesture(opts) {
|
|
2168
|
+
const wantsHold = (opts.holdStartMs ?? 0) > 0 || (opts.holdEndMs ?? 0) > 0;
|
|
2169
|
+
if (wantsHold) {
|
|
2170
|
+
await sendRawTouch({
|
|
2171
|
+
config: opts.config,
|
|
2172
|
+
events: waypointsToRawEvents(opts.waypoints, opts.holdStartMs, opts.holdEndMs),
|
|
2173
|
+
deviceSerial: opts.deviceSerial,
|
|
2174
|
+
logger: opts.logger
|
|
2175
|
+
});
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
for (const cmd of buildGestureCommands(opts.waypoints)) {
|
|
2179
|
+
const res = await shell({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
|
|
2180
|
+
ensureOk(res, `hdc shell ${cmd}`);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
async function sendRawTouch(opts) {
|
|
2184
|
+
const cmd = buildRawTouchCommand(opts.events);
|
|
2185
|
+
const res = await shell({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
|
|
2186
|
+
ensureOk(res, `hdc shell ${cmd}`);
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
// src/hdc/dump.ts
|
|
2190
|
+
var fs17 = __toESM(require("fs"), 1);
|
|
2191
|
+
var os10 = __toESM(require("os"), 1);
|
|
2192
|
+
var path16 = __toESM(require("path"), 1);
|
|
2193
|
+
var import_node_crypto2 = require("crypto");
|
|
2194
|
+
function parseBounds(s) {
|
|
2195
|
+
const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
|
|
2196
|
+
if (!m) return null;
|
|
2197
|
+
return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
|
|
2198
|
+
}
|
|
2199
|
+
var STRUCTURAL_TYPES = /* @__PURE__ */ new Set([
|
|
2200
|
+
"Stack",
|
|
2201
|
+
"Column",
|
|
2202
|
+
"Row",
|
|
2203
|
+
"Flex",
|
|
2204
|
+
"__Common__",
|
|
2205
|
+
"Grid",
|
|
2206
|
+
"GridItem",
|
|
2207
|
+
"List",
|
|
2208
|
+
"ListItem",
|
|
2209
|
+
"Scroll",
|
|
2210
|
+
"Swiper",
|
|
2211
|
+
"SwiperContent",
|
|
2212
|
+
"Navigator",
|
|
2213
|
+
"Tabs",
|
|
2214
|
+
"TabContent",
|
|
2215
|
+
"RelativeContainer",
|
|
2216
|
+
"WaterFlow",
|
|
2217
|
+
"FlowItem"
|
|
2218
|
+
]);
|
|
2219
|
+
var FLAG_MAP = [
|
|
2220
|
+
["clickable", "click"],
|
|
2221
|
+
["checkable", "check"],
|
|
2222
|
+
["checked", "checked"],
|
|
2223
|
+
["selected", "sel"],
|
|
2224
|
+
["focused", "focus"],
|
|
2225
|
+
["scrollable", "scroll"],
|
|
2226
|
+
["longClickable", "longClick"]
|
|
2227
|
+
];
|
|
2228
|
+
function pruneLayout(node, display) {
|
|
2229
|
+
const a = node.attributes ?? {};
|
|
2230
|
+
const px = parseBounds(a.bounds);
|
|
2231
|
+
const out = {};
|
|
2232
|
+
if (px && display) {
|
|
2233
|
+
const w = display.width || 1;
|
|
2234
|
+
const h = display.height || 1;
|
|
2235
|
+
out.b = [
|
|
2236
|
+
+(px[0] / w).toFixed(3),
|
|
2237
|
+
+(px[1] / h).toFixed(3),
|
|
2238
|
+
+(px[2] / w).toFixed(3),
|
|
2239
|
+
+(px[3] / h).toFixed(3)
|
|
2240
|
+
];
|
|
2241
|
+
out.c = [+((px[0] + px[2]) / 2 / w).toFixed(3), +((px[1] + px[3]) / 2 / h).toFixed(3)];
|
|
2242
|
+
}
|
|
2243
|
+
if (a.type && !STRUCTURAL_TYPES.has(a.type)) out.type = a.type;
|
|
2244
|
+
if (a.text) out.text = a.text;
|
|
2245
|
+
if (a.description) out.desc = a.description;
|
|
2246
|
+
if (a.id) out.id = a.id;
|
|
2247
|
+
if (a.key && a.key !== a.id) out.key = a.key;
|
|
2248
|
+
if (a.bundleName) out.bundle = a.bundleName;
|
|
2249
|
+
for (const [attr, short] of FLAG_MAP) {
|
|
2250
|
+
if (a[attr] === "true") out[short] = true;
|
|
2251
|
+
}
|
|
2252
|
+
const children = [];
|
|
2253
|
+
for (const c of node.children ?? []) {
|
|
2254
|
+
const pc = pruneLayout(c, display);
|
|
2255
|
+
if (pc) {
|
|
2256
|
+
if (Array.isArray(pc)) children.push(...pc);
|
|
2257
|
+
else children.push(pc);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
const hasSignal = Boolean(
|
|
2261
|
+
out.text || out.desc || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
|
|
2262
|
+
);
|
|
2263
|
+
if (!hasSignal && children.length === 0) return null;
|
|
2264
|
+
if (!hasSignal) return children.length === 1 ? children[0] : children;
|
|
2265
|
+
if (children.length) out.children = children;
|
|
2266
|
+
return out;
|
|
2267
|
+
}
|
|
2268
|
+
async function dumpLayout(opts) {
|
|
2269
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
2270
|
+
const bundleArg = opts.bundleName ? ` -b '${opts.bundleName.replace(/'/g, `'\\''`)}'` : "";
|
|
2271
|
+
const dump = await shell({ config: opts.config, command: `uitest dumpLayout${bundleArg}`, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2272
|
+
ensureOk(dump, "uitest dumpLayout");
|
|
2273
|
+
const m = dump.stdout.match(/saved to:\s*(\S+)/i);
|
|
2274
|
+
if (!m) throw new OniroError(`Could not parse dumpLayout output:
|
|
2275
|
+
${dump.stdout}`);
|
|
2276
|
+
const remote = m[1];
|
|
2277
|
+
const local = path16.join(os10.tmpdir(), `oniro_layout_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}.json`);
|
|
2278
|
+
try {
|
|
2279
|
+
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2280
|
+
const parsed = JSON.parse(await fs17.promises.readFile(local, "utf8"));
|
|
2281
|
+
const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
|
|
2282
|
+
if (opts.prune === false) {
|
|
2283
|
+
return { display, tree: parsed };
|
|
2284
|
+
}
|
|
2285
|
+
const prunedRaw = pruneLayout(parsed, display);
|
|
2286
|
+
const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
|
|
2287
|
+
return { display, tree };
|
|
2288
|
+
} finally {
|
|
2289
|
+
fs17.promises.unlink(local).catch(() => {
|
|
2290
|
+
});
|
|
2291
|
+
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
1549
2295
|
|
|
1550
2296
|
// src/hdc/hilog.ts
|
|
1551
|
-
var
|
|
2297
|
+
var import_node_child_process6 = require("child_process");
|
|
1552
2298
|
function setHilogLevel(opts) {
|
|
1553
2299
|
const logger = opts.logger ?? noopLogger;
|
|
1554
2300
|
const hdc = getHdcPath(opts.config);
|
|
1555
2301
|
return new Promise((resolve2, reject) => {
|
|
1556
|
-
const child = (0,
|
|
2302
|
+
const child = (0, import_node_child_process6.spawn)(hdc, ["shell", "hilog", "-b", opts.level]);
|
|
1557
2303
|
let stderr = "";
|
|
1558
2304
|
child.stderr.on("data", (chunk) => {
|
|
1559
2305
|
stderr += chunk.toString();
|
|
@@ -1575,11 +2321,16 @@ function setHilogLevel(opts) {
|
|
|
1575
2321
|
}
|
|
1576
2322
|
function streamHilog(opts) {
|
|
1577
2323
|
const hdc = getHdcPath(opts.config);
|
|
1578
|
-
const args = [
|
|
2324
|
+
const args = [];
|
|
2325
|
+
if (opts.deviceSerial) args.push("-t", opts.deviceSerial);
|
|
2326
|
+
args.push("shell", "hilog");
|
|
1579
2327
|
if (opts.processId && opts.processId.trim() !== "") {
|
|
1580
2328
|
args.push("-P", opts.processId.trim());
|
|
1581
2329
|
}
|
|
1582
|
-
|
|
2330
|
+
if (opts.domain !== void 0 && `${opts.domain}` !== "") {
|
|
2331
|
+
args.push("-D", `${opts.domain}`);
|
|
2332
|
+
}
|
|
2333
|
+
return (0, import_node_child_process6.spawn)(hdc, args);
|
|
1583
2334
|
}
|
|
1584
2335
|
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
2336
|
function parseHilogLine(line) {
|
|
@@ -1594,30 +2345,194 @@ function parseHilogLine(line) {
|
|
|
1594
2345
|
message: match[6]
|
|
1595
2346
|
};
|
|
1596
2347
|
}
|
|
2348
|
+
function entryText(e) {
|
|
2349
|
+
return `${e.tag}: ${e.message}`;
|
|
2350
|
+
}
|
|
2351
|
+
function dedupEntries(entries) {
|
|
2352
|
+
const out = [];
|
|
2353
|
+
let lastKey = "";
|
|
2354
|
+
for (const e of entries) {
|
|
2355
|
+
const key = `${e.level}|${e.tag}|${e.message}`;
|
|
2356
|
+
if (key === lastKey) continue;
|
|
2357
|
+
out.push(e);
|
|
2358
|
+
lastKey = key;
|
|
2359
|
+
}
|
|
2360
|
+
return out;
|
|
2361
|
+
}
|
|
2362
|
+
async function resolveProcessId(config, bundle, deviceSerial) {
|
|
2363
|
+
if (!bundle) return void 0;
|
|
2364
|
+
const proc = await findRunningProcess({ config, bundle, deviceSerial });
|
|
2365
|
+
return proc?.pid;
|
|
2366
|
+
}
|
|
2367
|
+
function waitForLog(opts) {
|
|
2368
|
+
return new Promise((resolve2, reject) => {
|
|
2369
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
2370
|
+
let settled = false;
|
|
2371
|
+
let child = null;
|
|
2372
|
+
let reconnectTimer;
|
|
2373
|
+
let buf = "";
|
|
2374
|
+
const cleanup = () => {
|
|
2375
|
+
clearTimeout(timer);
|
|
2376
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
2377
|
+
opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
2378
|
+
if (child) {
|
|
2379
|
+
try {
|
|
2380
|
+
child.kill("SIGKILL");
|
|
2381
|
+
} catch {
|
|
2382
|
+
}
|
|
2383
|
+
child = null;
|
|
2384
|
+
}
|
|
2385
|
+
};
|
|
2386
|
+
const finish = (entry) => {
|
|
2387
|
+
if (settled) return;
|
|
2388
|
+
settled = true;
|
|
2389
|
+
cleanup();
|
|
2390
|
+
resolve2(entry);
|
|
2391
|
+
};
|
|
2392
|
+
const fail = (err) => {
|
|
2393
|
+
if (settled) return;
|
|
2394
|
+
settled = true;
|
|
2395
|
+
cleanup();
|
|
2396
|
+
reject(err);
|
|
2397
|
+
};
|
|
2398
|
+
const onAbort = () => fail(new CancelledError("waitForLog cancelled."));
|
|
2399
|
+
const timer = setTimeout(
|
|
2400
|
+
() => fail(new OniroError(`No log line matched ${String(opts.pattern)} within ${opts.timeoutMs}ms.`)),
|
|
2401
|
+
opts.timeoutMs
|
|
2402
|
+
);
|
|
2403
|
+
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
2404
|
+
const spawnStream = async () => {
|
|
2405
|
+
if (settled) return;
|
|
2406
|
+
const processId = await resolveProcessId(opts.config, opts.bundle, opts.deviceSerial);
|
|
2407
|
+
if (settled) return;
|
|
2408
|
+
const stream = streamHilog({ config: opts.config, processId, domain: opts.domain, deviceSerial: opts.deviceSerial });
|
|
2409
|
+
child = stream;
|
|
2410
|
+
stream.stdout.on("data", (d) => {
|
|
2411
|
+
buf += d.toString();
|
|
2412
|
+
let idx;
|
|
2413
|
+
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
2414
|
+
const line = buf.slice(0, idx);
|
|
2415
|
+
buf = buf.slice(idx + 1);
|
|
2416
|
+
const entry = parseHilogLine(line);
|
|
2417
|
+
if (entry && opts.pattern.test(entryText(entry))) {
|
|
2418
|
+
finish(entry);
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
});
|
|
2423
|
+
const onEnd = () => {
|
|
2424
|
+
if (settled || child !== stream) return;
|
|
2425
|
+
child = null;
|
|
2426
|
+
if (Date.now() < deadline) {
|
|
2427
|
+
reconnectTimer = setTimeout(() => void spawnStream(), 1e3);
|
|
2428
|
+
}
|
|
2429
|
+
};
|
|
2430
|
+
stream.once("close", onEnd);
|
|
2431
|
+
stream.once("error", onEnd);
|
|
2432
|
+
};
|
|
2433
|
+
void spawnStream();
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2436
|
+
function watchLog(opts) {
|
|
2437
|
+
return new Promise((resolve2, reject) => {
|
|
2438
|
+
const matches = [];
|
|
2439
|
+
let settled = false;
|
|
2440
|
+
let child = null;
|
|
2441
|
+
let buf = "";
|
|
2442
|
+
const cleanup = () => {
|
|
2443
|
+
clearTimeout(timer);
|
|
2444
|
+
opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
2445
|
+
if (child) {
|
|
2446
|
+
try {
|
|
2447
|
+
child.kill("SIGKILL");
|
|
2448
|
+
} catch {
|
|
2449
|
+
}
|
|
2450
|
+
child = null;
|
|
2451
|
+
}
|
|
2452
|
+
};
|
|
2453
|
+
const done = () => {
|
|
2454
|
+
if (settled) return;
|
|
2455
|
+
settled = true;
|
|
2456
|
+
cleanup();
|
|
2457
|
+
resolve2(opts.dedup === false ? matches : dedupEntries(matches));
|
|
2458
|
+
};
|
|
2459
|
+
const onAbort = () => {
|
|
2460
|
+
if (settled) return;
|
|
2461
|
+
settled = true;
|
|
2462
|
+
cleanup();
|
|
2463
|
+
reject(new CancelledError("watchLog cancelled."));
|
|
2464
|
+
};
|
|
2465
|
+
const timer = setTimeout(done, opts.durationMs);
|
|
2466
|
+
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
2467
|
+
void (async () => {
|
|
2468
|
+
const processId = await resolveProcessId(opts.config, opts.bundle, opts.deviceSerial);
|
|
2469
|
+
if (settled) return;
|
|
2470
|
+
const stream = streamHilog({ config: opts.config, processId, domain: opts.domain, deviceSerial: opts.deviceSerial });
|
|
2471
|
+
child = stream;
|
|
2472
|
+
stream.stdout.on("data", (d) => {
|
|
2473
|
+
buf += d.toString();
|
|
2474
|
+
let idx;
|
|
2475
|
+
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
2476
|
+
const line = buf.slice(0, idx);
|
|
2477
|
+
buf = buf.slice(idx + 1);
|
|
2478
|
+
const entry = parseHilogLine(line);
|
|
2479
|
+
if (entry && opts.pattern.test(entryText(entry))) matches.push(entry);
|
|
2480
|
+
}
|
|
2481
|
+
});
|
|
2482
|
+
})();
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
async function dumpLog(opts) {
|
|
2486
|
+
let pipe = "hilog -x";
|
|
2487
|
+
if (opts.domain !== void 0 && `${opts.domain}` !== "") pipe += ` -D ${opts.domain}`;
|
|
2488
|
+
if (opts.bundle) {
|
|
2489
|
+
const proc = await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial });
|
|
2490
|
+
if (!proc) return [];
|
|
2491
|
+
pipe += ` | grep -E '\\b${proc.pid}\\b'`;
|
|
2492
|
+
}
|
|
2493
|
+
if (opts.grep) {
|
|
2494
|
+
const safeGrep = opts.grep.replace(/'/g, `'\\''`);
|
|
2495
|
+
pipe += ` | grep -E '${safeGrep}'`;
|
|
2496
|
+
}
|
|
2497
|
+
if (opts.lines && opts.lines > 0) pipe += ` | tail -n ${Math.floor(opts.lines)}`;
|
|
2498
|
+
const res = await shell({
|
|
2499
|
+
config: opts.config,
|
|
2500
|
+
command: pipe,
|
|
2501
|
+
deviceSerial: opts.deviceSerial,
|
|
2502
|
+
timeoutMs: opts.timeoutMs ?? 3e4,
|
|
2503
|
+
logger: opts.logger
|
|
2504
|
+
});
|
|
2505
|
+
return res.stdout.split("\n").map((line) => parseHilogLine(line)).filter((e) => e !== null);
|
|
2506
|
+
}
|
|
1597
2507
|
|
|
1598
2508
|
// src/build/runHvigorw.ts
|
|
1599
|
-
var
|
|
1600
|
-
var
|
|
1601
|
-
var
|
|
2509
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
2510
|
+
var path17 = __toESM(require("path"), 1);
|
|
2511
|
+
var import_node_child_process7 = require("child_process");
|
|
2512
|
+
function buildHvigorwArgs(opts) {
|
|
2513
|
+
const args = [opts.task ?? "assembleHap", "--mode", "module"];
|
|
2514
|
+
args.push("-p", `product=${opts.product ?? "default"}`);
|
|
2515
|
+
if (opts.module) args.push("-p", `module=${opts.module}`);
|
|
2516
|
+
if (opts.buildMode) args.push("-p", `buildMode=${opts.buildMode}`);
|
|
2517
|
+
args.push("--stacktrace", "--no-daemon");
|
|
2518
|
+
if (opts.parallel === false) args.push("--no-parallel");
|
|
2519
|
+
if (opts.extraArgs?.length) args.push(...opts.extraArgs);
|
|
2520
|
+
return args;
|
|
2521
|
+
}
|
|
1602
2522
|
function runHvigorw(opts) {
|
|
1603
2523
|
const { config, projectDir } = opts;
|
|
1604
2524
|
const logger = opts.logger ?? noopLogger;
|
|
1605
|
-
if (!
|
|
2525
|
+
if (!fs18.existsSync(path17.join(projectDir, "build-profile.json5"))) {
|
|
1606
2526
|
throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
|
|
1607
2527
|
}
|
|
1608
|
-
if (!
|
|
2528
|
+
if (!fs18.existsSync(getCmdToolsPath(config))) {
|
|
1609
2529
|
throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
|
|
1610
2530
|
}
|
|
1611
2531
|
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);
|
|
2532
|
+
const args = buildHvigorwArgs(opts);
|
|
1618
2533
|
if (process.platform !== "win32") {
|
|
1619
2534
|
try {
|
|
1620
|
-
|
|
2535
|
+
fs18.chmodSync(hvigorw, 493);
|
|
1621
2536
|
} catch {
|
|
1622
2537
|
}
|
|
1623
2538
|
}
|
|
@@ -1626,8 +2541,25 @@ function runHvigorw(opts) {
|
|
|
1626
2541
|
OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
|
|
1627
2542
|
};
|
|
1628
2543
|
return new Promise((resolve2, reject) => {
|
|
2544
|
+
if (opts.abortSignal?.aborted) {
|
|
2545
|
+
reject(new CancelledError("hvigorw build cancelled."));
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
1629
2548
|
logger.info(`[build] ${hvigorw} ${args.join(" ")}`);
|
|
1630
|
-
const child = (0,
|
|
2549
|
+
const child = (0, import_node_child_process7.spawn)(hvigorw, args, { cwd: projectDir, env, shell: false });
|
|
2550
|
+
let settled = false;
|
|
2551
|
+
const cleanup = () => opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
2552
|
+
const onAbort = () => {
|
|
2553
|
+
if (settled) return;
|
|
2554
|
+
settled = true;
|
|
2555
|
+
cleanup();
|
|
2556
|
+
try {
|
|
2557
|
+
child.kill("SIGKILL");
|
|
2558
|
+
} catch {
|
|
2559
|
+
}
|
|
2560
|
+
reject(new CancelledError("hvigorw build cancelled."));
|
|
2561
|
+
};
|
|
2562
|
+
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1631
2563
|
child.stdout.on("data", (chunk) => {
|
|
1632
2564
|
const text = chunk.toString();
|
|
1633
2565
|
opts.onOutput?.(text, "stdout");
|
|
@@ -1638,8 +2570,16 @@ function runHvigorw(opts) {
|
|
|
1638
2570
|
opts.onOutput?.(text, "stderr");
|
|
1639
2571
|
logger.warn(text.trimEnd());
|
|
1640
2572
|
});
|
|
1641
|
-
child.on("error", (err) =>
|
|
2573
|
+
child.on("error", (err) => {
|
|
2574
|
+
if (settled) return;
|
|
2575
|
+
settled = true;
|
|
2576
|
+
cleanup();
|
|
2577
|
+
reject(new OniroError(`Failed to start hvigorw: ${err.message}`, err));
|
|
2578
|
+
});
|
|
1642
2579
|
child.on("close", (code) => {
|
|
2580
|
+
if (settled) return;
|
|
2581
|
+
settled = true;
|
|
2582
|
+
cleanup();
|
|
1643
2583
|
const exitCode = code ?? 1;
|
|
1644
2584
|
if (exitCode === 0) resolve2({ exitCode });
|
|
1645
2585
|
else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
|
|
@@ -1647,6 +2587,137 @@ function runHvigorw(opts) {
|
|
|
1647
2587
|
});
|
|
1648
2588
|
}
|
|
1649
2589
|
|
|
2590
|
+
// src/build/discoverHaps.ts
|
|
2591
|
+
var fs19 = __toESM(require("fs"), 1);
|
|
2592
|
+
var path18 = __toESM(require("path"), 1);
|
|
2593
|
+
function moduleOf(projectDir, hapPath) {
|
|
2594
|
+
const rel = path18.relative(projectDir, hapPath);
|
|
2595
|
+
const first = rel.split(path18.sep)[0];
|
|
2596
|
+
return first && first !== ".." ? first : "(root)";
|
|
2597
|
+
}
|
|
2598
|
+
async function discoverHaps(opts) {
|
|
2599
|
+
const { projectDir } = opts;
|
|
2600
|
+
const maxDepth = opts.maxDepth ?? 8;
|
|
2601
|
+
const result = {};
|
|
2602
|
+
const walk = async (dir, depth) => {
|
|
2603
|
+
if (depth > maxDepth) return;
|
|
2604
|
+
const entries = await fs19.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2605
|
+
for (const e of entries) {
|
|
2606
|
+
const full = path18.join(dir, e.name);
|
|
2607
|
+
if (e.isDirectory()) {
|
|
2608
|
+
if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
|
|
2609
|
+
await walk(full, depth + 1);
|
|
2610
|
+
} else if (e.isFile() && e.name.endsWith(".hap")) {
|
|
2611
|
+
const moduleName = moduleOf(projectDir, full);
|
|
2612
|
+
const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
|
|
2613
|
+
if (e.name.includes("unsigned")) bucket.unsigned.push(full);
|
|
2614
|
+
else if (e.name.includes("signed")) bucket.signed.push(full);
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
};
|
|
2618
|
+
await walk(projectDir, 0);
|
|
2619
|
+
return result;
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
// src/build/ohpm.ts
|
|
2623
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
2624
|
+
var path19 = __toESM(require("path"), 1);
|
|
2625
|
+
function runOhpm(opts) {
|
|
2626
|
+
return runProcess({
|
|
2627
|
+
command: getOhpmPath(opts.config),
|
|
2628
|
+
args: [...opts.args],
|
|
2629
|
+
cwd: opts.projectDir,
|
|
2630
|
+
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
2631
|
+
abortSignal: opts.abortSignal,
|
|
2632
|
+
onOutput: opts.onOutput,
|
|
2633
|
+
logger: opts.logger
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
async function ensureOhModules(opts) {
|
|
2637
|
+
if (fs20.existsSync(path19.join(opts.projectDir, "oh_modules"))) {
|
|
2638
|
+
return { installed: false };
|
|
2639
|
+
}
|
|
2640
|
+
const res = await runOhpm({
|
|
2641
|
+
config: opts.config,
|
|
2642
|
+
projectDir: opts.projectDir,
|
|
2643
|
+
args: ["install", "--all"],
|
|
2644
|
+
abortSignal: opts.abortSignal,
|
|
2645
|
+
onOutput: opts.onOutput,
|
|
2646
|
+
logger: opts.logger
|
|
2647
|
+
});
|
|
2648
|
+
ensureOk(res, "ohpm install --all");
|
|
2649
|
+
return { installed: true };
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
// src/build/codelinter.ts
|
|
2653
|
+
var FINDING_RE = /^(.*?):(\d+):(?:\d+:)?\s*(error|warning|warn|info)\b[:\s-]*(.*)$/i;
|
|
2654
|
+
var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
|
|
2655
|
+
function parseCodelinterFindings(output) {
|
|
2656
|
+
const findings = [];
|
|
2657
|
+
for (const raw of output.split("\n")) {
|
|
2658
|
+
const m = FINDING_RE.exec(raw.trim());
|
|
2659
|
+
if (!m) continue;
|
|
2660
|
+
let message = m[4].trim();
|
|
2661
|
+
let code = "";
|
|
2662
|
+
const codeMatch = TRAILING_CODE_RE.exec(message);
|
|
2663
|
+
if (codeMatch) {
|
|
2664
|
+
code = codeMatch[1];
|
|
2665
|
+
message = message.slice(0, codeMatch.index).trim();
|
|
2666
|
+
}
|
|
2667
|
+
findings.push({ file: m[1].trim(), line: Number(m[2]), severity: m[3].toLowerCase(), code, message });
|
|
2668
|
+
}
|
|
2669
|
+
return findings;
|
|
2670
|
+
}
|
|
2671
|
+
async function runCodelinter(opts) {
|
|
2672
|
+
const res = await runProcess({
|
|
2673
|
+
command: getCmdToolsBin(opts.config, "codelinter"),
|
|
2674
|
+
args: opts.files?.length ? [...opts.files] : [],
|
|
2675
|
+
cwd: opts.projectDir,
|
|
2676
|
+
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
2677
|
+
abortSignal: opts.abortSignal,
|
|
2678
|
+
logger: opts.logger
|
|
2679
|
+
});
|
|
2680
|
+
const raw = `${res.stdout}${res.stderr}`;
|
|
2681
|
+
return { code: res.code, findings: parseCodelinterFindings(raw), raw };
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
// src/build/buildHap.ts
|
|
2685
|
+
async function buildHap(opts) {
|
|
2686
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "build");
|
|
2687
|
+
const warnings = [];
|
|
2688
|
+
const start = Date.now();
|
|
2689
|
+
if (opts.autoInstallDeps !== false) {
|
|
2690
|
+
const r = await ensureOhModules({
|
|
2691
|
+
config: opts.config,
|
|
2692
|
+
projectDir: opts.projectDir,
|
|
2693
|
+
abortSignal: opts.abortSignal,
|
|
2694
|
+
onOutput: opts.onOutput,
|
|
2695
|
+
logger: opts.logger
|
|
2696
|
+
});
|
|
2697
|
+
if (r.installed) logger.info("ran ohpm install --all (oh_modules was missing)");
|
|
2698
|
+
}
|
|
2699
|
+
const { exitCode } = await runHvigorw({
|
|
2700
|
+
config: opts.config,
|
|
2701
|
+
projectDir: opts.projectDir,
|
|
2702
|
+
product: opts.product,
|
|
2703
|
+
module: opts.module,
|
|
2704
|
+
buildMode: opts.mode,
|
|
2705
|
+
task: opts.task,
|
|
2706
|
+
parallel: opts.parallel,
|
|
2707
|
+
abortSignal: opts.abortSignal,
|
|
2708
|
+
onOutput: opts.onOutput,
|
|
2709
|
+
logger: opts.logger
|
|
2710
|
+
});
|
|
2711
|
+
const discoveredHaps = await discoverHaps({ projectDir: opts.projectDir });
|
|
2712
|
+
const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
|
|
2713
|
+
if (!anySigned) {
|
|
2714
|
+
warnings.push(
|
|
2715
|
+
"No signed HAPs found \u2014 configure signingConfigs in build-profile.json5 (unsigned HAPs are rejected by most devices)."
|
|
2716
|
+
);
|
|
2717
|
+
}
|
|
2718
|
+
return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
|
|
2719
|
+
}
|
|
2720
|
+
|
|
1650
2721
|
// src/project/validators.ts
|
|
1651
2722
|
function isValidProjectName(name) {
|
|
1652
2723
|
if (!name) return false;
|
|
@@ -1658,21 +2729,21 @@ function isValidBundleName(bundleName) {
|
|
|
1658
2729
|
}
|
|
1659
2730
|
|
|
1660
2731
|
// src/project/templates.ts
|
|
1661
|
-
var
|
|
1662
|
-
var
|
|
2732
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
2733
|
+
var path20 = __toESM(require("path"), 1);
|
|
1663
2734
|
function toHumanTemplateName(folderName) {
|
|
1664
2735
|
return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
1665
2736
|
}
|
|
1666
2737
|
function listTemplates(templateRoot) {
|
|
1667
|
-
if (!
|
|
1668
|
-
const entries =
|
|
2738
|
+
if (!fs21.existsSync(templateRoot)) return [];
|
|
2739
|
+
const entries = fs21.readdirSync(templateRoot, { withFileTypes: true });
|
|
1669
2740
|
return entries.filter((e) => e.isDirectory()).map((e) => {
|
|
1670
|
-
const dir =
|
|
1671
|
-
const metaPath =
|
|
2741
|
+
const dir = path20.join(templateRoot, e.name);
|
|
2742
|
+
const metaPath = path20.join(dir, "template.json");
|
|
1672
2743
|
let meta = {};
|
|
1673
|
-
if (
|
|
2744
|
+
if (fs21.existsSync(metaPath)) {
|
|
1674
2745
|
try {
|
|
1675
|
-
meta = JSON.parse(
|
|
2746
|
+
meta = JSON.parse(fs21.readFileSync(metaPath, "utf8"));
|
|
1676
2747
|
} catch {
|
|
1677
2748
|
}
|
|
1678
2749
|
}
|
|
@@ -1687,57 +2758,57 @@ function listTemplates(templateRoot) {
|
|
|
1687
2758
|
function validateTemplateLayout(templateDir, defaultModuleName) {
|
|
1688
2759
|
const required = [
|
|
1689
2760
|
"build-profile.json5",
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
2761
|
+
path20.join("AppScope", "app.json5"),
|
|
2762
|
+
path20.join(defaultModuleName, "src", "main", "module.json5"),
|
|
2763
|
+
path20.join(defaultModuleName, "oh-package.json5"),
|
|
1693
2764
|
"hvigorfile.ts"
|
|
1694
2765
|
];
|
|
1695
|
-
return required.filter((rel) => !
|
|
2766
|
+
return required.filter((rel) => !fs21.existsSync(path20.join(templateDir, rel)));
|
|
1696
2767
|
}
|
|
1697
2768
|
|
|
1698
2769
|
// src/project/createScaffold.ts
|
|
1699
|
-
var
|
|
1700
|
-
var
|
|
2770
|
+
var fs23 = __toESM(require("fs"), 1);
|
|
2771
|
+
var path21 = __toESM(require("path"), 1);
|
|
1701
2772
|
|
|
1702
2773
|
// src/project/jsonHelpers.ts
|
|
1703
|
-
var
|
|
2774
|
+
var fs22 = __toESM(require("fs"), 1);
|
|
1704
2775
|
var import_json54 = __toESM(require("json5"), 1);
|
|
1705
2776
|
function readJson5File(filePath) {
|
|
1706
|
-
return import_json54.default.parse(
|
|
2777
|
+
return import_json54.default.parse(fs22.readFileSync(filePath, "utf8"));
|
|
1707
2778
|
}
|
|
1708
2779
|
function writeJson5File(filePath, value) {
|
|
1709
|
-
|
|
2780
|
+
fs22.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
1710
2781
|
}
|
|
1711
2782
|
function readJsonFile(filePath) {
|
|
1712
|
-
return JSON.parse(
|
|
2783
|
+
return JSON.parse(fs22.readFileSync(filePath, "utf8"));
|
|
1713
2784
|
}
|
|
1714
2785
|
function writeJsonFile(filePath, value) {
|
|
1715
|
-
|
|
2786
|
+
fs22.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
1716
2787
|
}
|
|
1717
2788
|
|
|
1718
2789
|
// src/project/createScaffold.ts
|
|
1719
2790
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
|
|
1720
2791
|
async function pathExists(p) {
|
|
1721
2792
|
try {
|
|
1722
|
-
await
|
|
2793
|
+
await fs23.promises.access(p);
|
|
1723
2794
|
return true;
|
|
1724
2795
|
} catch {
|
|
1725
2796
|
return false;
|
|
1726
2797
|
}
|
|
1727
2798
|
}
|
|
1728
2799
|
async function copyDirRecursive(srcDir, destDir) {
|
|
1729
|
-
await
|
|
1730
|
-
const entries = await
|
|
2800
|
+
await fs23.promises.mkdir(destDir, { recursive: true });
|
|
2801
|
+
const entries = await fs23.promises.readdir(srcDir, { withFileTypes: true });
|
|
1731
2802
|
for (const entry of entries) {
|
|
1732
|
-
const src =
|
|
1733
|
-
const dest =
|
|
2803
|
+
const src = path21.join(srcDir, entry.name);
|
|
2804
|
+
const dest = path21.join(destDir, entry.name);
|
|
1734
2805
|
if (entry.isDirectory()) {
|
|
1735
2806
|
await copyDirRecursive(src, dest);
|
|
1736
2807
|
} else if (entry.isSymbolicLink()) {
|
|
1737
2808
|
continue;
|
|
1738
2809
|
} else if (entry.isFile()) {
|
|
1739
2810
|
if (entry.name === "template.json") continue;
|
|
1740
|
-
await
|
|
2811
|
+
await fs23.promises.copyFile(src, dest);
|
|
1741
2812
|
}
|
|
1742
2813
|
}
|
|
1743
2814
|
}
|
|
@@ -1745,9 +2816,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
|
|
|
1745
2816
|
const stack = [projectDir];
|
|
1746
2817
|
while (stack.length > 0) {
|
|
1747
2818
|
const currentDir = stack.pop();
|
|
1748
|
-
const entries = await
|
|
2819
|
+
const entries = await fs23.promises.readdir(currentDir, { withFileTypes: true });
|
|
1749
2820
|
for (const entry of entries) {
|
|
1750
|
-
const fullPath =
|
|
2821
|
+
const fullPath = path21.join(currentDir, entry.name);
|
|
1751
2822
|
if (entry.isDirectory()) {
|
|
1752
2823
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
1753
2824
|
stack.push(fullPath);
|
|
@@ -1769,23 +2840,23 @@ function toJavaPropertiesPath(p) {
|
|
|
1769
2840
|
function createOrUpdateLocalProperties(projectDir, sdkDir) {
|
|
1770
2841
|
const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
|
|
1771
2842
|
`;
|
|
1772
|
-
|
|
2843
|
+
fs23.writeFileSync(path21.join(projectDir, "local.properties"), content, "utf8");
|
|
1773
2844
|
}
|
|
1774
2845
|
function renameIfExists(fromPath, toPath) {
|
|
1775
|
-
if (
|
|
1776
|
-
|
|
2846
|
+
if (fs23.existsSync(fromPath) && fromPath !== toPath) {
|
|
2847
|
+
fs23.renameSync(fromPath, toPath);
|
|
1777
2848
|
}
|
|
1778
2849
|
}
|
|
1779
2850
|
function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
1780
|
-
const appJsonPath =
|
|
1781
|
-
if (
|
|
2851
|
+
const appJsonPath = path21.join(projectDir, "AppScope", "app.json5");
|
|
2852
|
+
if (fs23.existsSync(appJsonPath)) {
|
|
1782
2853
|
const appJson = readJson5File(appJsonPath);
|
|
1783
2854
|
appJson.app = appJson.app ?? {};
|
|
1784
2855
|
appJson.app.bundleName = args.bundleName;
|
|
1785
2856
|
writeJson5File(appJsonPath, appJson);
|
|
1786
2857
|
}
|
|
1787
|
-
const stringsPath =
|
|
1788
|
-
if (
|
|
2858
|
+
const stringsPath = path21.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
|
|
2859
|
+
if (fs23.existsSync(stringsPath)) {
|
|
1789
2860
|
const strings = readJsonFile(stringsPath);
|
|
1790
2861
|
if (Array.isArray(strings.string)) {
|
|
1791
2862
|
const appName = strings.string.find((s) => s.name === "app_name");
|
|
@@ -1793,8 +2864,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
1793
2864
|
}
|
|
1794
2865
|
writeJsonFile(stringsPath, strings);
|
|
1795
2866
|
}
|
|
1796
|
-
const buildProfilePath =
|
|
1797
|
-
if (
|
|
2867
|
+
const buildProfilePath = path21.join(projectDir, "build-profile.json5");
|
|
2868
|
+
if (fs23.existsSync(buildProfilePath)) {
|
|
1798
2869
|
const buildProfile = readJson5File(buildProfilePath);
|
|
1799
2870
|
buildProfile.app = buildProfile.app ?? {};
|
|
1800
2871
|
if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
|
|
@@ -1807,23 +2878,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
1807
2878
|
}
|
|
1808
2879
|
writeJson5File(buildProfilePath, buildProfile);
|
|
1809
2880
|
}
|
|
1810
|
-
const moduleJsonPath =
|
|
1811
|
-
if (
|
|
2881
|
+
const moduleJsonPath = path21.join(projectDir, args.moduleName, "src", "main", "module.json5");
|
|
2882
|
+
if (fs23.existsSync(moduleJsonPath)) {
|
|
1812
2883
|
const moduleJson = readJson5File(moduleJsonPath);
|
|
1813
2884
|
moduleJson.module = moduleJson.module ?? {};
|
|
1814
2885
|
moduleJson.module.name = args.moduleName;
|
|
1815
2886
|
writeJson5File(moduleJsonPath, moduleJson);
|
|
1816
2887
|
}
|
|
1817
|
-
const moduleOhPackagePath =
|
|
1818
|
-
if (
|
|
2888
|
+
const moduleOhPackagePath = path21.join(projectDir, args.moduleName, "oh-package.json5");
|
|
2889
|
+
if (fs23.existsSync(moduleOhPackagePath)) {
|
|
1819
2890
|
const pkg = readJson5File(moduleOhPackagePath);
|
|
1820
2891
|
pkg.name = args.moduleName;
|
|
1821
2892
|
writeJson5File(moduleOhPackagePath, pkg);
|
|
1822
2893
|
}
|
|
1823
|
-
const vscodeDir =
|
|
1824
|
-
|
|
2894
|
+
const vscodeDir = path21.join(projectDir, ".vscode");
|
|
2895
|
+
fs23.mkdirSync(vscodeDir, { recursive: true });
|
|
1825
2896
|
const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
|
|
1826
|
-
writeJsonFile(
|
|
2897
|
+
writeJsonFile(path21.join(vscodeDir, "settings.json"), {
|
|
1827
2898
|
"oniro.hapPath": hapPath,
|
|
1828
2899
|
"files.associations": { "*.json5": "jsonc" }
|
|
1829
2900
|
});
|
|
@@ -1840,7 +2911,7 @@ async function createScaffold(opts) {
|
|
|
1840
2911
|
if (!opts.location || !await pathExists(opts.location)) {
|
|
1841
2912
|
throw new OniroError(`Location does not exist: ${opts.location}`);
|
|
1842
2913
|
}
|
|
1843
|
-
const templateDir =
|
|
2914
|
+
const templateDir = path21.join(opts.templateRoot, opts.templateId);
|
|
1844
2915
|
if (!await pathExists(templateDir)) {
|
|
1845
2916
|
throw new OniroError(`Template not found: ${templateDir}`);
|
|
1846
2917
|
}
|
|
@@ -1853,17 +2924,17 @@ async function createScaffold(opts) {
|
|
|
1853
2924
|
throw new OniroError(`Template '${opts.templateId}' is missing required files:
|
|
1854
2925
|
- ${missing.join("\n- ")}`);
|
|
1855
2926
|
}
|
|
1856
|
-
const projectDir =
|
|
2927
|
+
const projectDir = path21.join(opts.location, opts.projectName);
|
|
1857
2928
|
if (await pathExists(projectDir)) {
|
|
1858
2929
|
if (!opts.overwrite) {
|
|
1859
2930
|
throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
|
|
1860
2931
|
}
|
|
1861
|
-
await
|
|
2932
|
+
await fs23.promises.rm(projectDir, { recursive: true, force: true });
|
|
1862
2933
|
}
|
|
1863
2934
|
logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
|
|
1864
2935
|
await copyDirRecursive(templateDir, projectDir);
|
|
1865
2936
|
if (moduleName !== defaultModuleName) {
|
|
1866
|
-
renameIfExists(
|
|
2937
|
+
renameIfExists(path21.join(projectDir, defaultModuleName), path21.join(projectDir, moduleName));
|
|
1867
2938
|
}
|
|
1868
2939
|
updateTemplateConfigs(
|
|
1869
2940
|
projectDir,
|
|
@@ -1879,8 +2950,225 @@ async function createScaffold(opts) {
|
|
|
1879
2950
|
return { projectDir };
|
|
1880
2951
|
}
|
|
1881
2952
|
|
|
2953
|
+
// src/project/listModules.ts
|
|
2954
|
+
var fs24 = __toESM(require("fs"), 1);
|
|
2955
|
+
var path22 = __toESM(require("path"), 1);
|
|
2956
|
+
function listModules(opts) {
|
|
2957
|
+
const buildProfile = path22.join(opts.projectDir, "build-profile.json5");
|
|
2958
|
+
if (!fs24.existsSync(buildProfile)) {
|
|
2959
|
+
throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
|
|
2960
|
+
}
|
|
2961
|
+
const parsed = readJson5File(buildProfile);
|
|
2962
|
+
return (parsed.modules ?? []).filter((m) => Boolean(m.name)).map((m) => ({
|
|
2963
|
+
name: m.name,
|
|
2964
|
+
srcPath: m.srcPath ?? `./${m.name}`,
|
|
2965
|
+
targets: (m.targets ?? []).map((t) => t.name).filter((n) => Boolean(n))
|
|
2966
|
+
}));
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
// src/project/abilities.ts
|
|
2970
|
+
var fs25 = __toESM(require("fs"), 1);
|
|
2971
|
+
var path23 = __toESM(require("path"), 1);
|
|
2972
|
+
function listAbilities(opts) {
|
|
2973
|
+
const moduleName = opts.moduleName ?? "entry";
|
|
2974
|
+
const moduleJsonPath = path23.join(opts.projectDir, moduleName, "src", "main", "module.json5");
|
|
2975
|
+
if (!fs25.existsSync(moduleJsonPath)) {
|
|
2976
|
+
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
2977
|
+
}
|
|
2978
|
+
const parsed = readJson5File(moduleJsonPath);
|
|
2979
|
+
return (parsed.module?.abilities ?? []).filter((a) => Boolean(a.name)).map((a) => ({ name: a.name, type: a.type, visible: a.visible, srcEntry: a.srcEntry }));
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
// src/project/prepareSigning.ts
|
|
2983
|
+
var fs26 = __toESM(require("fs"), 1);
|
|
2984
|
+
var path24 = __toESM(require("path"), 1);
|
|
2985
|
+
function hasSigningArtifacts(dir) {
|
|
2986
|
+
if (!fs26.existsSync(path24.join(dir, "signatures"))) return false;
|
|
2987
|
+
const buildProfile = path24.join(dir, "build-profile.json5");
|
|
2988
|
+
if (!fs26.existsSync(buildProfile)) return false;
|
|
2989
|
+
try {
|
|
2990
|
+
const parsed = readJson5File(buildProfile);
|
|
2991
|
+
return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
|
|
2992
|
+
} catch {
|
|
2993
|
+
return false;
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
function prepareSigning(opts) {
|
|
2997
|
+
if (hasSigningArtifacts(opts.projectDir)) {
|
|
2998
|
+
return { source: "present" };
|
|
2999
|
+
}
|
|
3000
|
+
generateSigningConfigs({
|
|
3001
|
+
projectDir: opts.projectDir,
|
|
3002
|
+
sdkHome: getOhosBaseSdkHome(opts.config),
|
|
3003
|
+
apl: opts.apl,
|
|
3004
|
+
appFeature: opts.appFeature,
|
|
3005
|
+
acls: opts.acls,
|
|
3006
|
+
passwords: opts.passwords,
|
|
3007
|
+
logger: opts.logger
|
|
3008
|
+
});
|
|
3009
|
+
return { source: "fresh" };
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
// src/device/hidumper.ts
|
|
3013
|
+
async function dumpScreen(opts) {
|
|
3014
|
+
const res = await shell({ config: opts.config, command: "hidumper -s 10 -a screen", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
|
|
3015
|
+
if (res.code !== 0) return null;
|
|
3016
|
+
const m = res.stdout.match(/render resolution=(\d+)x(\d+)/);
|
|
3017
|
+
if (!m) return null;
|
|
3018
|
+
return { width: Number(m[1]), height: Number(m[2]), raw: res.stdout };
|
|
3019
|
+
}
|
|
3020
|
+
async function dumpWindow(opts) {
|
|
3021
|
+
const res = await shell({ config: opts.config, command: "hidumper -s WindowManagerService -a -a", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 15e3, logger: opts.logger });
|
|
3022
|
+
return res.stdout;
|
|
3023
|
+
}
|
|
3024
|
+
async function dumpRenderService(opts) {
|
|
3025
|
+
const res = await shell({ config: opts.config, command: "hidumper -s RenderService -a allInfo", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 15e3, logger: opts.logger });
|
|
3026
|
+
return res.stdout;
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
// src/device/info.ts
|
|
3030
|
+
var PROPS = [
|
|
3031
|
+
["model", "const.product.model"],
|
|
3032
|
+
["manufacturer", "const.product.manufacturer"],
|
|
3033
|
+
["brand", "const.product.brand"],
|
|
3034
|
+
["osFullName", "const.ohos.fullname"],
|
|
3035
|
+
["osReleaseType", "const.ohos.releasetype"],
|
|
3036
|
+
["sdkApiVersion", "const.ohos.apiversion"],
|
|
3037
|
+
["buildVersion", "const.product.software.version"]
|
|
3038
|
+
];
|
|
3039
|
+
async function getDeviceInfo(opts) {
|
|
3040
|
+
const info = { serial: opts.deviceSerial ?? "(default)", display: null };
|
|
3041
|
+
for (const [field, prop] of PROPS) {
|
|
3042
|
+
try {
|
|
3043
|
+
const value = await paramGet(opts.config, prop, { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
|
|
3044
|
+
if (value) info[field] = value;
|
|
3045
|
+
} catch {
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
const screen = await dumpScreen({ config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger });
|
|
3049
|
+
if (screen) info.display = { width: screen.width, height: screen.height };
|
|
3050
|
+
return info;
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
// src/install/diffHapAssets.ts
|
|
3054
|
+
var import_node_stream_zip2 = __toESM(require("node-stream-zip"), 1);
|
|
3055
|
+
function diffEntryNames(installed, next) {
|
|
3056
|
+
const installedSet = new Set(installed);
|
|
3057
|
+
const nextSet = new Set(next);
|
|
3058
|
+
return {
|
|
3059
|
+
addedAssetPaths: [...nextSet].filter((n) => !installedSet.has(n)).sort(),
|
|
3060
|
+
removedAssetPaths: [...installedSet].filter((n) => !nextSet.has(n)).sort()
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
async function listFileEntries(hapPath) {
|
|
3064
|
+
const zip = new import_node_stream_zip2.default.async({ file: hapPath });
|
|
3065
|
+
try {
|
|
3066
|
+
const entries = await zip.entries();
|
|
3067
|
+
return Object.values(entries).filter((e) => !e.isDirectory).map((e) => e.name);
|
|
3068
|
+
} finally {
|
|
3069
|
+
await zip.close();
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
async function diffHapAssets(opts) {
|
|
3073
|
+
const [installed, next] = await Promise.all([listFileEntries(opts.installedHap), listFileEntries(opts.newHap)]);
|
|
3074
|
+
return diffEntryNames(installed, next);
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
// src/install/apply.ts
|
|
3078
|
+
var fs27 = __toESM(require("fs"), 1);
|
|
3079
|
+
var path25 = __toESM(require("path"), 1);
|
|
3080
|
+
function decideInstallMethod(s) {
|
|
3081
|
+
if (!s.signInfoInconsistent) return "replace";
|
|
3082
|
+
if (s.isSystemBundle && !s.allowUninstall) return "refuse";
|
|
3083
|
+
return "uninstall-install";
|
|
3084
|
+
}
|
|
3085
|
+
function decideReboot(s) {
|
|
3086
|
+
if (s.assetsChanged) return { reboot: true, reason: "asset-cache" };
|
|
3087
|
+
if (s.isSystemBundle && s.preInstallPid !== null && s.postInstallPid === s.preInstallPid) {
|
|
3088
|
+
return { reboot: true, reason: "pid-unchanged" };
|
|
3089
|
+
}
|
|
3090
|
+
return { reboot: false, reason: "none" };
|
|
3091
|
+
}
|
|
3092
|
+
var SIGN_INFO_INCONSISTENT = /9568332|sign.*info.*inconsistent/i;
|
|
3093
|
+
async function pidOf(config, bundle, deviceSerial) {
|
|
3094
|
+
const proc = await findRunningProcess({ config, bundle, deviceSerial });
|
|
3095
|
+
return proc ? Number(proc.pid) : null;
|
|
3096
|
+
}
|
|
3097
|
+
async function resolveHapPath(opts) {
|
|
3098
|
+
let hapPath = opts.hapPath;
|
|
3099
|
+
if (hapPath) {
|
|
3100
|
+
if (!path25.isAbsolute(hapPath) && opts.projectDir) hapPath = path25.join(opts.projectDir, hapPath);
|
|
3101
|
+
} else {
|
|
3102
|
+
if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
|
|
3103
|
+
const haps = await discoverHaps({ projectDir: opts.projectDir });
|
|
3104
|
+
if (opts.module) {
|
|
3105
|
+
const signed = haps[opts.module]?.signed ?? [];
|
|
3106
|
+
if (signed.length === 0) throw new OniroError(`No signed HAP for module ${opts.module}. Build first.`);
|
|
3107
|
+
hapPath = signed[0];
|
|
3108
|
+
} else {
|
|
3109
|
+
const withSigned = Object.entries(haps).filter(([, m]) => m.signed.length > 0);
|
|
3110
|
+
if (withSigned.length === 0) throw new OniroError("No signed HAP found. Build first.");
|
|
3111
|
+
if (withSigned.length > 1) {
|
|
3112
|
+
throw new OniroError(
|
|
3113
|
+
`Multiple modules have signed HAPs (${withSigned.map(([k]) => k).join(", ")}). Pass module to choose.`
|
|
3114
|
+
);
|
|
3115
|
+
}
|
|
3116
|
+
hapPath = withSigned[0][1].signed[0];
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
if (!fs27.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
|
|
3120
|
+
return hapPath;
|
|
3121
|
+
}
|
|
3122
|
+
async function applyChanges(opts) {
|
|
3123
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "apply");
|
|
3124
|
+
const { config, bundle, deviceSerial } = opts;
|
|
3125
|
+
const isSystemBundle = opts.isSystemBundle ?? false;
|
|
3126
|
+
const allowUninstall = opts.allowUninstall ?? false;
|
|
3127
|
+
const hapPath = await resolveHapPath(opts);
|
|
3128
|
+
let assetsChanged = false;
|
|
3129
|
+
if (opts.installedHapPath && fs27.existsSync(opts.installedHapPath)) {
|
|
3130
|
+
try {
|
|
3131
|
+
const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
|
|
3132
|
+
assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
|
|
3133
|
+
} catch (err) {
|
|
3134
|
+
logger.debug(`asset diff skipped: ${err.message}`);
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
const preInstallPid = await pidOf(config, bundle, deviceSerial);
|
|
3138
|
+
const replaceRes = await hdcExec({ config, args: ["install", "-r", hapPath], deviceSerial, timeoutMs: 3e5 });
|
|
3139
|
+
const signInfoInconsistent = SIGN_INFO_INCONSISTENT.test(`${replaceRes.stdout}${replaceRes.stderr}`);
|
|
3140
|
+
const decision = decideInstallMethod({ signInfoInconsistent, isSystemBundle, allowUninstall });
|
|
3141
|
+
let installMethod;
|
|
3142
|
+
if (decision === "refuse") {
|
|
3143
|
+
throw new OniroError(
|
|
3144
|
+
`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.`
|
|
3145
|
+
);
|
|
3146
|
+
}
|
|
3147
|
+
if (decision === "uninstall-install") {
|
|
3148
|
+
logger.warn(`sign-info inconsistent for ${bundle}; uninstalling then installing fresh.`);
|
|
3149
|
+
await uninstallApp({ config, bundle, deviceSerial });
|
|
3150
|
+
ensureOk(await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }), `hdc install ${hapPath}`);
|
|
3151
|
+
installMethod = "uninstall-install";
|
|
3152
|
+
} else {
|
|
3153
|
+
ensureOk(replaceRes, `hdc install -r ${hapPath}`);
|
|
3154
|
+
installMethod = "replace";
|
|
3155
|
+
}
|
|
3156
|
+
let postInstallPid = await pidOf(config, bundle, deviceSerial);
|
|
3157
|
+
const rebootDecision = decideReboot({ assetsChanged, isSystemBundle, preInstallPid, postInstallPid });
|
|
3158
|
+
let method = installMethod;
|
|
3159
|
+
let cacheCleared = false;
|
|
3160
|
+
if (rebootDecision.reboot) {
|
|
3161
|
+
logger.info(`rebooting (${rebootDecision.reason}) to apply changes to ${bundle}.`);
|
|
3162
|
+
await reboot({ config, waitForBundle: bundle, deviceSerial, abortSignal: opts.abortSignal, logger: opts.logger });
|
|
3163
|
+
postInstallPid = await pidOf(config, bundle, deviceSerial);
|
|
3164
|
+
method = "reboot";
|
|
3165
|
+
cacheCleared = rebootDecision.reason === "asset-cache";
|
|
3166
|
+
}
|
|
3167
|
+
return { method, preInstallPid, postInstallPid, replaced: installMethod === "replace", cacheCleared };
|
|
3168
|
+
}
|
|
3169
|
+
|
|
1882
3170
|
// src/index.ts
|
|
1883
|
-
var VERSION = "0.
|
|
3171
|
+
var VERSION = "0.7.0";
|
|
1884
3172
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1885
3173
|
0 && (module.exports = {
|
|
1886
3174
|
ALL_SDKS,
|
|
@@ -1889,33 +3177,59 @@ var VERSION = "0.6.2";
|
|
|
1889
3177
|
CancelledError,
|
|
1890
3178
|
ChecksumMismatchError,
|
|
1891
3179
|
CmdToolsNotInstalledError,
|
|
3180
|
+
CommandFailedError,
|
|
1892
3181
|
OHOS_URL_BASE,
|
|
1893
3182
|
OniroError,
|
|
1894
3183
|
SdkNotInstalledError,
|
|
1895
3184
|
UnsupportedPlatformError,
|
|
1896
3185
|
VERSION,
|
|
3186
|
+
applyChanges,
|
|
1897
3187
|
attemptHdcConnection,
|
|
3188
|
+
buildGestureCommands,
|
|
3189
|
+
buildHap,
|
|
3190
|
+
buildHvigorwArgs,
|
|
3191
|
+
buildInputCommand,
|
|
3192
|
+
buildRawTouchCommand,
|
|
3193
|
+
captureBurst,
|
|
1898
3194
|
consoleLogger,
|
|
1899
3195
|
createMaterial,
|
|
1900
3196
|
createScaffold,
|
|
3197
|
+
decideInstallMethod,
|
|
3198
|
+
decideReboot,
|
|
1901
3199
|
decryptPwd,
|
|
3200
|
+
dedupEntries,
|
|
1902
3201
|
defaultContext,
|
|
1903
3202
|
defaultPaths,
|
|
3203
|
+
detectAaStartFailure,
|
|
1904
3204
|
detectProjectSdkVersion,
|
|
1905
3205
|
detectSigningConfigNames,
|
|
3206
|
+
diffEntryNames,
|
|
3207
|
+
diffHapAssets,
|
|
3208
|
+
discoverHaps,
|
|
1906
3209
|
downloadAndInstallSdk,
|
|
1907
3210
|
downloadFile,
|
|
3211
|
+
dumpLayout,
|
|
3212
|
+
dumpLog,
|
|
3213
|
+
dumpRenderService,
|
|
3214
|
+
dumpScreen,
|
|
3215
|
+
dumpWindow,
|
|
1908
3216
|
encryptPwd,
|
|
3217
|
+
ensureOhModules,
|
|
3218
|
+
ensureOk,
|
|
1909
3219
|
extractTarball,
|
|
1910
3220
|
extractZipWithProgress,
|
|
1911
3221
|
findAppProcessId,
|
|
1912
3222
|
findCmdToolsSourceDir,
|
|
3223
|
+
findRunningProcess,
|
|
3224
|
+
forceStop,
|
|
1913
3225
|
generateSigningConfigs,
|
|
1914
3226
|
getBundleName,
|
|
1915
3227
|
getCmdToolsBin,
|
|
1916
3228
|
getCmdToolsDownloadUrl,
|
|
1917
3229
|
getCmdToolsPath,
|
|
1918
3230
|
getCmdToolsStatus,
|
|
3231
|
+
getDeviceInfo,
|
|
3232
|
+
getDisplaySize,
|
|
1919
3233
|
getEmulatorDir,
|
|
1920
3234
|
getHdcPath,
|
|
1921
3235
|
getHvigorwPath,
|
|
@@ -1923,10 +3237,12 @@ var VERSION = "0.6.2";
|
|
|
1923
3237
|
getKey,
|
|
1924
3238
|
getMainAbility,
|
|
1925
3239
|
getOhosBaseSdkHome,
|
|
3240
|
+
getOhpmPath,
|
|
1926
3241
|
getOsFolder,
|
|
1927
3242
|
getSdkFilename,
|
|
1928
3243
|
getSdkRootDir,
|
|
1929
3244
|
getSupportedSdksForUi,
|
|
3245
|
+
hdcExec,
|
|
1930
3246
|
installApp,
|
|
1931
3247
|
installCmdTools,
|
|
1932
3248
|
installEmulator,
|
|
@@ -1935,26 +3251,55 @@ var VERSION = "0.6.2";
|
|
|
1935
3251
|
isValidBundleName,
|
|
1936
3252
|
isValidProjectName,
|
|
1937
3253
|
launchApp,
|
|
3254
|
+
listAbilities,
|
|
3255
|
+
listDevices,
|
|
3256
|
+
listModules,
|
|
1938
3257
|
listRunningProcesses,
|
|
1939
3258
|
listTemplates,
|
|
1940
3259
|
nonInteractivePrompter,
|
|
1941
3260
|
noopLogger,
|
|
1942
3261
|
noopProgress,
|
|
3262
|
+
paramGet,
|
|
3263
|
+
paramSet,
|
|
3264
|
+
parseBounds,
|
|
3265
|
+
parseCodelinterFindings,
|
|
3266
|
+
parseDeviceList,
|
|
1943
3267
|
parseHilogLine,
|
|
1944
3268
|
pickSigningKind,
|
|
3269
|
+
prepareSigning,
|
|
3270
|
+
pruneLayout,
|
|
1945
3271
|
readJson5File,
|
|
1946
3272
|
readJsonFile,
|
|
3273
|
+
reboot,
|
|
3274
|
+
recvFile,
|
|
1947
3275
|
removeCmdTools,
|
|
1948
3276
|
removeEmulator,
|
|
1949
3277
|
removeSdk,
|
|
3278
|
+
runCodelinter,
|
|
1950
3279
|
runHvigorw,
|
|
3280
|
+
runOhpm,
|
|
3281
|
+
runProcess,
|
|
3282
|
+
scopedLogger,
|
|
3283
|
+
selectDevice,
|
|
3284
|
+
sendFile,
|
|
3285
|
+
sendGesture,
|
|
3286
|
+
sendInput,
|
|
3287
|
+
sendRawTouch,
|
|
1951
3288
|
setHilogLevel,
|
|
3289
|
+
shell,
|
|
1952
3290
|
startEmulator,
|
|
1953
3291
|
staticConfig,
|
|
1954
3292
|
stopEmulator,
|
|
1955
3293
|
streamHilog,
|
|
3294
|
+
takeScreenshot,
|
|
3295
|
+
uninstallApp,
|
|
1956
3296
|
validateTemplateLayout,
|
|
1957
3297
|
verifySha256,
|
|
3298
|
+
waitForBoot,
|
|
3299
|
+
waitForBundle,
|
|
3300
|
+
waitForCondition,
|
|
3301
|
+
waitForLog,
|
|
3302
|
+
watchLog,
|
|
1958
3303
|
writeJson5File,
|
|
1959
3304
|
writeJsonFile
|
|
1960
3305
|
});
|