@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/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: "6.0", api: "20" },
250
- { version: "6.1", api: "23" }
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
- const strip = v === "5.0.0" || v === "5.0.1" || v === "6.0" || v === "6.1" ? 0 : 1;
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: 3 };
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: 3 };
354
+ return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: strips.darwin };
275
355
  }
276
356
  if (platform6 === "win32") {
277
- const strip = v === "5.0.0" || v === "5.0.1" || v === "6.0" || v === "6.1" ? 0 : 1;
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, "ohpm.exe"),
310
- path2.join(binDir, "ohpm.cmd"),
311
- path2.join(binDir, "ohpm.bat"),
312
- path2.join(binDir, "ohpm")
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, "ohpm");
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 cmdToolsBin = path2.join(getCmdToolsPath(config), "bin");
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 fs7 = __toESM(require("fs"), 1);
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 downloadUrl = `${OHOS_URL_BASE}/${version}-Release/${filename}`;
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 = fs7.mkdtempSync(path7.join(os5.tmpdir(), "oniro-sdk-"));
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
- fs7.mkdirSync(extractDir);
709
+ fs8.mkdirSync(extractDir);
608
710
  const sdkInstallDir = path7.join(getSdkRootDir(config), osFolder, api);
609
- fs7.mkdirSync(path7.dirname(sdkInstallDir), { recursive: true });
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 (!fs7.existsSync(osContentPath)) {
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 = fs7.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
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
- fs7.unlinkSync(zipPath);
754
+ fs8.unlinkSync(zipPath);
653
755
  }
654
756
  progress?.report({ message: "Finalizing installation...", increment: 0 });
655
- if (fs7.existsSync(sdkInstallDir)) {
656
- fs7.rmSync(sdkInstallDir, { recursive: true, force: true });
757
+ if (fs8.existsSync(sdkInstallDir)) {
758
+ fs8.rmSync(sdkInstallDir, { recursive: true, force: true });
657
759
  }
658
- fs7.renameSync(osContentPath, sdkInstallDir);
760
+ movePath(osContentPath, sdkInstallDir);
659
761
  progress?.report({ message: "Finalizing installation...", increment: 3 });
660
762
  progress?.report({ message: "Cleaning up...", increment: 0 });
661
- fs7.rmSync(tmpDir, { recursive: true, force: true });
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
- fs7.rmSync(tmpDir, { recursive: true, force: true });
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 fs8 = __toESM(require("fs"), 1);
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 (fs8.existsSync(c)) return c;
808
+ if (fs9.existsSync(c)) return c;
707
809
  }
708
- const entries = fs8.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path8.join(extractPath, e.name));
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 (fs8.existsSync(path8.join(dir, "bin"))) return dir;
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 = fs8.mkdtempSync(path8.join(os6.tmpdir(), "oniro-cmdtools-"));
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
- fs8.mkdirSync(CMD_PATH, { recursive: true });
831
+ fs9.mkdirSync(CMD_PATH, { recursive: true });
730
832
  const srcDir = findCmdToolsSourceDir(extractPath);
731
- for (const entry of fs8.readdirSync(srcDir)) {
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 (fs8.statSync(src).isDirectory()) {
735
- if (fs8.existsSync(dest)) fs8.rmSync(dest, { recursive: true, force: true });
736
- fs8.renameSync(src, dest);
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
- fs8.copyFileSync(src, dest);
840
+ fs9.copyFileSync(src, dest);
739
841
  }
740
842
  }
741
843
  const binDir = path8.join(CMD_PATH, "bin");
742
- if (fs8.existsSync(binDir) && os6.platform() !== "win32") {
743
- for (const file of fs8.readdirSync(binDir)) {
744
- fs8.chmodSync(path8.join(binDir, file), 493);
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
- fs8.rmSync(tmpDir, { recursive: true, force: true });
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) => fs8.existsSync(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 (fs8.existsSync(versionFile)) {
764
- const content = fs8.readFileSync(versionFile, "utf8");
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 (fs8.existsSync(cmdPath)) {
785
- fs8.rmSync(cmdPath, { recursive: true, force: true });
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 fs9 = __toESM(require("fs"), 1);
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
- fs9.writeFileSync(filePath, key);
865
- fs9.chmodSync(filePath, KEY_FILE_PERMISSIONS);
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
- fs9.writeFileSync(filePath, encrypted);
874
- fs9.chmodSync(filePath, KEY_FILE_PERMISSIONS);
975
+ fs10.writeFileSync(filePath, encrypted);
976
+ fs10.chmodSync(filePath, KEY_FILE_PERMISSIONS);
875
977
  }
876
978
  function createMaterial(materialPath) {
877
- fs9.mkdirSync(materialPath, { recursive: true });
878
- fs9.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
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
- fs9.mkdirSync(fdDir, { recursive: true });
883
- fs9.mkdirSync(acDir, { recursive: true });
884
- fs9.mkdirSync(ceDir, { recursive: true });
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
- fs9.mkdirSync(subDir, { recursive: true });
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 (!fs9.existsSync(dir) || !fs9.statSync(dir).isDirectory()) {
1000
+ if (!fs10.existsSync(dir) || !fs10.statSync(dir).isDirectory()) {
899
1001
  throw new Error(`Missing signing material directory: ${dir}`);
900
1002
  }
901
- const files = fs9.readdirSync(dir).filter((f) => f !== ".DS_Store");
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 fs9.readFileSync(path9.join(dir, files[0]));
1007
+ return fs10.readFileSync(path9.join(dir, files[0]));
906
1008
  }
907
1009
  function getKey(materialPath) {
908
- if (!fs9.existsSync(materialPath) || !fs9.statSync(materialPath).isDirectory()) {
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 fs10 = __toESM(require("fs"), 1);
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
- fs10.mkdirSync(signaturesDir, { recursive: true });
1002
- fs10.copyFileSync(paths.keystore, path10.join(signaturesDir, "OpenHarmony.p12"));
1003
- fs10.copyFileSync(paths.profileCert, path10.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
1004
- fs10.copyFileSync(paths.unsignedProfileTemplate, path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
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
- fs10.writeFileSync(
1007
- path10.join(signaturesDir, "OpenHarmonyApplication.cer"),
1008
- OPEN_HARMONY_APPLICATION_CERT
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 = fs10.readFileSync(chainPath, "utf-8");
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 (!fs10.existsSync(appJsonPath)) {
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(fs10.readFileSync(appJsonPath, "utf-8"));
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(fs10.readFileSync(profileTemplatePath, "utf-8"));
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
- fs10.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
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
- "123456",
1194
+ passwords.key,
1090
1195
  "-keystorePwd",
1091
- "123456"
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 (!fs10.existsSync(buildProfilePath)) return ["default"];
1209
+ if (!fs11.existsSync(buildProfilePath)) return ["default"];
1105
1210
  let parsed;
1106
1211
  try {
1107
- parsed = import_json52.default.parse(fs10.readFileSync(buildProfilePath, "utf-8"));
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("123456", materialDir);
1127
- const encryptedKeyPassword = encryptPwd("123456", materialDir);
1231
+ const encryptedStorePassword = encryptPwd(passwords.store, materialDir);
1232
+ const encryptedKeyPassword = encryptPwd(passwords.key, materialDir);
1128
1233
  let buildProfile = { app: {} };
1129
- if (fs10.existsSync(buildProfilePath)) {
1234
+ if (fs11.existsSync(buildProfilePath)) {
1130
1235
  try {
1131
- buildProfile = import_json52.default.parse(fs10.readFileSync(buildProfilePath, "utf-8"));
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
- fs10.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
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 (fs10.existsSync(materialDir)) {
1158
- fs10.rmSync(materialDir, { recursive: true, force: true });
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 (!fs10.existsSync(sdkPath)) {
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 fs11 = __toESM(require("fs"), 1);
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 = fs11.mkdtempSync(path11.join(os7.tmpdir(), "oniro-emulator-"));
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
- fs11.mkdirSync(emulatorDir, { recursive: true });
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 (fs11.existsSync(runSh)) {
1220
- fs11.chmodSync(runSh, 493);
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
- fs11.rmSync(tmpDir, { recursive: true, force: true });
1330
+ fs12.rmSync(tmpDir, { recursive: true, force: true });
1225
1331
  }
1226
1332
  }
1227
1333
  function isEmulatorInstalled(config) {
1228
- return fs11.existsSync(path11.join(getEmulatorDir(config), "images", "run.sh"));
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 (fs11.existsSync(emulatorDir)) {
1233
- fs11.rmSync(emulatorDir, { recursive: true, force: true });
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 fs12 = __toESM(require("fs"), 1);
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
- var PID_FILE = path12.join(os8.tmpdir(), "oniro_emulator.pid");
1243
- function execPromise(cmd, cwd, logger) {
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
- (0, import_node_child_process3.exec)(cmd, { cwd }, (error, stdout, stderr) => {
1246
- if (stdout?.trim()) logger.info(`[emulator] ${stdout.trim()}`);
1247
- if (stderr?.trim()) logger.warn(`[emulator] ${stderr.trim()}`);
1248
- if (error) {
1249
- logger.error(`[emulator] ${error.message}`);
1250
- reject(error);
1251
- } else {
1252
- resolve2();
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 execPromise(`"${hdc}" start -r`, void 0, logger);
1261
- await execPromise(`"${hdc}" tconn ${address}`, void 0, logger);
1262
- return await new Promise((resolve2) => {
1263
- (0, import_node_child_process3.exec)(`"${hdc}" list targets`, (_error, stdout) => {
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 (fs12.existsSync(runBat)) {
1464
+ if (fs13.existsSync(runBat)) {
1280
1465
  return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
1281
1466
  }
1282
- if (fs12.existsSync(runSh)) {
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 (fs12.existsSync(runSh)) {
1472
+ if (fs13.existsSync(runSh)) {
1288
1473
  return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1289
1474
  }
1290
- if (fs12.existsSync(runBat)) {
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" && fs12.existsSync(PID_FILE)) {
1483
+ if (os8.platform() !== "win32" && fs13.existsSync(PID_FILE)) {
1299
1484
  try {
1300
- const pid = Number(fs12.readFileSync(PID_FILE, "utf8").trim());
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 (!fs12.existsSync(imagesPath)) {
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 = fs12.openSync(logPath, "a");
1507
+ const logFd = fs13.openSync(logPath, "a");
1323
1508
  let child;
1324
1509
  try {
1325
- child = (0, import_node_child_process3.spawn)(launcher.command, [...launcher.args], {
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
- fs12.closeSync(logFd);
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
- fs12.writeFileSync(PID_FILE, String(child.pid));
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 = fs12.statSync(filePath);
1569
+ const stat = fs13.statSync(filePath);
1385
1570
  const start = Math.max(0, stat.size - bytes);
1386
- const fd = fs12.openSync(filePath, "r");
1571
+ const fd = fs13.openSync(filePath, "r");
1387
1572
  try {
1388
1573
  const buf = Buffer.alloc(stat.size - start);
1389
- fs12.readSync(fd, buf, 0, buf.length, start);
1574
+ fs13.readSync(fd, buf, 0, buf.length, start);
1390
1575
  return buf.toString("utf8");
1391
1576
  } finally {
1392
- fs12.closeSync(fd);
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 (fs12.existsSync(PID_FILE)) {
1584
+ if (fs13.existsSync(PID_FILE)) {
1400
1585
  try {
1401
- const pid = Number(fs12.readFileSync(PID_FILE, "utf8").trim());
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 execPromise(killCmd, void 0, logger);
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
- fs12.rm(PID_FILE, { force: true }, (err) => {
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 fs13 = __toESM(require("fs"), 1);
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 = fs13.readFileSync(filePath, "utf-8");
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 (!fs13.existsSync(appJsonPath)) {
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 (!fs13.existsSync(moduleJsonPath)) {
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
- if (!moduleJson.module?.mainElement) {
1450
- throw new OniroError("mainElement not found in module.json5");
1451
- }
1452
- return moduleJson.module.mainElement;
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 (!fs14.existsSync(hapPath)) {
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 hdc = getHdcPath(opts.config);
1481
- await execPromise2(`"${hdc}" install "${hapPath}"`, logger);
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 hdc = getHdcPath(opts.config);
1488
- await execPromise2(`"${hdc}" shell aa start -a ${mainAbility} -b ${bundleName}`, logger);
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, import_node_child_process4.spawn)(hdc, ["track-jpid"]);
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 import_node_child_process5 = require("child_process");
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, import_node_child_process5.spawn)(hdc, ["shell", "hilog", "-b", opts.level]);
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 = ["shell", "hilog"];
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
- return (0, import_node_child_process5.spawn)(hdc, args);
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 fs15 = __toESM(require("fs"), 1);
1600
- var path15 = __toESM(require("path"), 1);
1601
- var import_node_child_process6 = require("child_process");
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 (!fs15.existsSync(path15.join(projectDir, "build-profile.json5"))) {
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 (!fs15.existsSync(getCmdToolsPath(config))) {
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 = [opts.task ?? "assembleHap", "--mode", "module"];
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
- fs15.chmodSync(hvigorw, 493);
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, import_node_child_process6.spawn)(hvigorw, args, { cwd: projectDir, env, shell: false });
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) => reject(new OniroError(`Failed to start hvigorw: ${err.message}`, 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 fs16 = __toESM(require("fs"), 1);
1662
- var path16 = __toESM(require("path"), 1);
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 (!fs16.existsSync(templateRoot)) return [];
1668
- const entries = fs16.readdirSync(templateRoot, { withFileTypes: true });
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 = path16.join(templateRoot, e.name);
1671
- const metaPath = path16.join(dir, "template.json");
2741
+ const dir = path20.join(templateRoot, e.name);
2742
+ const metaPath = path20.join(dir, "template.json");
1672
2743
  let meta = {};
1673
- if (fs16.existsSync(metaPath)) {
2744
+ if (fs21.existsSync(metaPath)) {
1674
2745
  try {
1675
- meta = JSON.parse(fs16.readFileSync(metaPath, "utf8"));
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
- path16.join("AppScope", "app.json5"),
1691
- path16.join(defaultModuleName, "src", "main", "module.json5"),
1692
- path16.join(defaultModuleName, "oh-package.json5"),
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) => !fs16.existsSync(path16.join(templateDir, rel)));
2766
+ return required.filter((rel) => !fs21.existsSync(path20.join(templateDir, rel)));
1696
2767
  }
1697
2768
 
1698
2769
  // src/project/createScaffold.ts
1699
- var fs18 = __toESM(require("fs"), 1);
1700
- var path17 = __toESM(require("path"), 1);
2770
+ var fs23 = __toESM(require("fs"), 1);
2771
+ var path21 = __toESM(require("path"), 1);
1701
2772
 
1702
2773
  // src/project/jsonHelpers.ts
1703
- var fs17 = __toESM(require("fs"), 1);
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(fs17.readFileSync(filePath, "utf8"));
2777
+ return import_json54.default.parse(fs22.readFileSync(filePath, "utf8"));
1707
2778
  }
1708
2779
  function writeJson5File(filePath, value) {
1709
- fs17.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
2780
+ fs22.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
1710
2781
  }
1711
2782
  function readJsonFile(filePath) {
1712
- return JSON.parse(fs17.readFileSync(filePath, "utf8"));
2783
+ return JSON.parse(fs22.readFileSync(filePath, "utf8"));
1713
2784
  }
1714
2785
  function writeJsonFile(filePath, value) {
1715
- fs17.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
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 fs18.promises.access(p);
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 fs18.promises.mkdir(destDir, { recursive: true });
1730
- const entries = await fs18.promises.readdir(srcDir, { withFileTypes: true });
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 = path17.join(srcDir, entry.name);
1733
- const dest = path17.join(destDir, entry.name);
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 fs18.promises.copyFile(src, dest);
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 fs18.promises.readdir(currentDir, { withFileTypes: true });
2819
+ const entries = await fs23.promises.readdir(currentDir, { withFileTypes: true });
1749
2820
  for (const entry of entries) {
1750
- const fullPath = path17.join(currentDir, entry.name);
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
- fs18.writeFileSync(path17.join(projectDir, "local.properties"), content, "utf8");
2843
+ fs23.writeFileSync(path21.join(projectDir, "local.properties"), content, "utf8");
1773
2844
  }
1774
2845
  function renameIfExists(fromPath, toPath) {
1775
- if (fs18.existsSync(fromPath) && fromPath !== toPath) {
1776
- fs18.renameSync(fromPath, toPath);
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 = path17.join(projectDir, "AppScope", "app.json5");
1781
- if (fs18.existsSync(appJsonPath)) {
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 = path17.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
1788
- if (fs18.existsSync(stringsPath)) {
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 = path17.join(projectDir, "build-profile.json5");
1797
- if (fs18.existsSync(buildProfilePath)) {
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 = path17.join(projectDir, args.moduleName, "src", "main", "module.json5");
1811
- if (fs18.existsSync(moduleJsonPath)) {
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 = path17.join(projectDir, args.moduleName, "oh-package.json5");
1818
- if (fs18.existsSync(moduleOhPackagePath)) {
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 = path17.join(projectDir, ".vscode");
1824
- fs18.mkdirSync(vscodeDir, { recursive: true });
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(path17.join(vscodeDir, "settings.json"), {
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 = path17.join(opts.templateRoot, opts.templateId);
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 = path17.join(opts.location, opts.projectName);
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 fs18.promises.rm(projectDir, { recursive: true, force: true });
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(path17.join(projectDir, defaultModuleName), path17.join(projectDir, moduleName));
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.6.2";
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
  });