@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.js CHANGED
@@ -15,6 +15,15 @@ var consoleLogger = {
15
15
  warn: (m) => console.warn(m),
16
16
  error: (m) => console.error(m)
17
17
  };
18
+ function scopedLogger(logger, scope) {
19
+ const prefix = `[${scope}] `;
20
+ return {
21
+ debug: (m) => logger.debug(prefix + m),
22
+ info: (m) => logger.info(prefix + m),
23
+ warn: (m) => logger.warn(prefix + m),
24
+ error: (m) => logger.error(prefix + m)
25
+ };
26
+ }
18
27
 
19
28
  // src/ports/progress.ts
20
29
  var noopProgress = {
@@ -119,6 +128,19 @@ var CancelledError = class extends OniroError {
119
128
  this.name = "CancelledError";
120
129
  }
121
130
  };
131
+ var CommandFailedError = class extends OniroError {
132
+ constructor(command, exitCode, stderr) {
133
+ super(`Command failed (exit ${exitCode}): ${command}${stderr ? `
134
+ ${stderr}` : ""}`);
135
+ this.command = command;
136
+ this.exitCode = exitCode;
137
+ this.stderr = stderr;
138
+ this.name = "CommandFailedError";
139
+ }
140
+ command;
141
+ exitCode;
142
+ stderr;
143
+ };
122
144
 
123
145
  // src/ports/context.ts
124
146
  function defaultContext(overrides = {}) {
@@ -130,15 +152,16 @@ function defaultContext(overrides = {}) {
130
152
 
131
153
  // src/sdk/constants.ts
132
154
  var ALL_SDKS = [
133
- { version: "4.0", api: "10" },
134
- { version: "4.1", api: "11" },
135
- { version: "5.0.0", api: "12" },
136
- { version: "5.0.1", api: "13" },
137
- { version: "5.0.2", api: "14" },
138
- { version: "5.0.3", api: "15" },
139
- { version: "5.1.0", api: "18" },
140
- { version: "6.0", api: "20" },
141
- { version: "6.1", api: "23" }
155
+ { version: "4.0", api: "10", tarballStrip: { linuxWindows: 1, darwin: 3 } },
156
+ { version: "4.1", api: "11", tarballStrip: { linuxWindows: 1, darwin: 3 } },
157
+ { version: "5.0.0", api: "12", tarballStrip: { linuxWindows: 0, darwin: 3 } },
158
+ { version: "5.0.1", api: "13", tarballStrip: { linuxWindows: 0, darwin: 3 } },
159
+ { version: "5.0.2", api: "14", tarballStrip: { linuxWindows: 1, darwin: 3 } },
160
+ { version: "5.0.3", api: "15", tarballStrip: { linuxWindows: 1, darwin: 3 } },
161
+ { version: "5.1.0", api: "18", tarballStrip: { linuxWindows: 1, darwin: 3 } },
162
+ { version: "5.1.1", api: "19", tarballStrip: { linuxWindows: 1, darwin: 3 } },
163
+ { version: "6.0", api: "20", tarballStrip: { linuxWindows: 0, darwin: 3 } },
164
+ { version: "6.1", api: "23", tarballStrip: { linuxWindows: 0, darwin: 3 } }
142
165
  ];
143
166
  var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
144
167
 
@@ -154,19 +177,18 @@ function getOsFolder() {
154
177
  function getSdkFilename(version) {
155
178
  const platform6 = os2.platform();
156
179
  const v = version ?? ALL_SDKS[ALL_SDKS.length - 1].version;
180
+ const strips = ALL_SDKS.find((r) => r.version === v)?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
157
181
  if (platform6 === "linux") {
158
- const strip = v === "5.0.0" || v === "5.0.1" || v === "6.0" || v === "6.1" ? 0 : 1;
159
- return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip };
182
+ return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip: strips.linuxWindows };
160
183
  }
161
184
  if (platform6 === "darwin") {
162
185
  if (os2.arch() === "arm64") {
163
- return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: 3 };
186
+ return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: strips.darwin };
164
187
  }
165
- return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: 3 };
188
+ return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: strips.darwin };
166
189
  }
167
190
  if (platform6 === "win32") {
168
- const strip = v === "5.0.0" || v === "5.0.1" || v === "6.0" || v === "6.1" ? 0 : 1;
169
- return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip };
191
+ return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip: strips.linuxWindows };
170
192
  }
171
193
  throw new UnsupportedPlatformError(platform6);
172
194
  }
@@ -193,17 +215,20 @@ function pickExisting(candidates) {
193
215
  }
194
216
  return candidates[0];
195
217
  }
196
- function getCmdToolsBin(config) {
218
+ function getCmdToolsBin(config, name = "ohpm") {
197
219
  const binDir = path2.join(getCmdToolsPath(config), "bin");
198
220
  if (os3.platform() === "win32") {
199
221
  return pickExisting([
200
- path2.join(binDir, "ohpm.exe"),
201
- path2.join(binDir, "ohpm.cmd"),
202
- path2.join(binDir, "ohpm.bat"),
203
- path2.join(binDir, "ohpm")
222
+ path2.join(binDir, `${name}.exe`),
223
+ path2.join(binDir, `${name}.cmd`),
224
+ path2.join(binDir, `${name}.bat`),
225
+ path2.join(binDir, name)
204
226
  ]);
205
227
  }
206
- return path2.join(binDir, "ohpm");
228
+ return path2.join(binDir, name);
229
+ }
230
+ function getOhpmPath(config) {
231
+ return getCmdToolsBin(config, "ohpm");
207
232
  }
208
233
  function getHdcPath(config) {
209
234
  const base = path2.join(getCmdToolsPath(config), "sdk", "default", "openharmony", "toolchains");
@@ -218,15 +243,20 @@ function getHdcPath(config) {
218
243
  return path2.join(base, "hdc");
219
244
  }
220
245
  function getHvigorwPath(config, projectDir) {
221
- const cmdToolsBin = path2.join(getCmdToolsPath(config), "bin");
222
- return pickExisting([
246
+ const local = pickExisting([
223
247
  path2.join(projectDir, "hvigorw"),
224
248
  path2.join(projectDir, "hvigorw.bat"),
225
- path2.join(projectDir, "hvigorw.cmd"),
226
- path2.join(cmdToolsBin, "hvigorw"),
227
- path2.join(cmdToolsBin, "hvigorw.bat"),
228
- path2.join(cmdToolsBin, "hvigorw.cmd")
249
+ path2.join(projectDir, "hvigorw.cmd")
229
250
  ]);
251
+ if (fs.existsSync(local) && isProjectLocalHvigorwWorking(projectDir)) {
252
+ return local;
253
+ }
254
+ return getCmdToolsBin(config, "hvigorw");
255
+ }
256
+ function isProjectLocalHvigorwWorking(projectDir) {
257
+ const wrapper = path2.join(projectDir, "hvigor", "hvigor-wrapper.js");
258
+ const nodeModules = path2.join(projectDir, "hvigor", "node_modules");
259
+ return fs.existsSync(wrapper) && fs.existsSync(nodeModules);
230
260
  }
231
261
 
232
262
  // src/sdk/detectProjectSdk.ts
@@ -482,22 +512,37 @@ function removeSdk(config, api) {
482
512
  }
483
513
 
484
514
  // src/sdk/install.ts
485
- import * as fs7 from "fs";
515
+ import * as fs8 from "fs";
486
516
  import * as os5 from "os";
487
517
  import * as path7 from "path";
518
+
519
+ // src/sdk/move.ts
520
+ import * as fs7 from "fs";
521
+ function movePath(src, dest) {
522
+ try {
523
+ fs7.renameSync(src, dest);
524
+ } catch (err) {
525
+ if (err.code !== "EXDEV") throw err;
526
+ fs7.cpSync(src, dest, { recursive: true });
527
+ fs7.rmSync(src, { recursive: true, force: true });
528
+ }
529
+ }
530
+
531
+ // src/sdk/install.ts
488
532
  async function downloadAndInstallSdk(opts) {
489
533
  const { config, version, api, progress, abortSignal } = opts;
490
534
  const logger = opts.logger ?? noopLogger;
491
535
  const { filename, osFolder, strip } = getSdkFilename(version);
492
- const downloadUrl = `${OHOS_URL_BASE}/${version}-Release/${filename}`;
536
+ const urlBase = config.get("sdkUrlBase", OHOS_URL_BASE);
537
+ const downloadUrl = `${urlBase}/${version}-Release/${filename}`;
493
538
  const sha256Url = `${downloadUrl}.sha256`;
494
- const tmpDir = fs7.mkdtempSync(path7.join(os5.tmpdir(), "oniro-sdk-"));
539
+ const tmpDir = fs8.mkdtempSync(path7.join(os5.tmpdir(), "oniro-sdk-"));
495
540
  const tarPath = path7.join(tmpDir, filename);
496
541
  const sha256Path = path7.join(tmpDir, `${filename}.sha256`);
497
542
  const extractDir = path7.join(tmpDir, "extract");
498
- fs7.mkdirSync(extractDir);
543
+ fs8.mkdirSync(extractDir);
499
544
  const sdkInstallDir = path7.join(getSdkRootDir(config), osFolder, api);
500
- fs7.mkdirSync(path7.dirname(sdkInstallDir), { recursive: true });
545
+ fs8.mkdirSync(path7.dirname(sdkInstallDir), { recursive: true });
501
546
  const checkCancelled = () => {
502
547
  if (abortSignal?.aborted) throw new CancelledError();
503
548
  };
@@ -515,12 +560,12 @@ async function downloadAndInstallSdk(opts) {
515
560
  progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 10 });
516
561
  checkCancelled();
517
562
  const osContentPath = path7.join(extractDir, osFolder);
518
- if (!fs7.existsSync(osContentPath)) {
563
+ if (!fs8.existsSync(osContentPath)) {
519
564
  throw new OniroError(
520
565
  `Expected folder '${osFolder}' not found in extracted SDK. Tarball layout may have changed.`
521
566
  );
522
567
  }
523
- const zipFiles = fs7.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
568
+ const zipFiles = fs8.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
524
569
  const componentsStart = 60;
525
570
  const componentsBudget = 35;
526
571
  const n = zipFiles.length;
@@ -540,26 +585,26 @@ async function downloadAndInstallSdk(opts) {
540
585
  if (rem > 0) rem--;
541
586
  await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
542
587
  cursor += thisBudget;
543
- fs7.unlinkSync(zipPath);
588
+ fs8.unlinkSync(zipPath);
544
589
  }
545
590
  progress?.report({ message: "Finalizing installation...", increment: 0 });
546
- if (fs7.existsSync(sdkInstallDir)) {
547
- fs7.rmSync(sdkInstallDir, { recursive: true, force: true });
591
+ if (fs8.existsSync(sdkInstallDir)) {
592
+ fs8.rmSync(sdkInstallDir, { recursive: true, force: true });
548
593
  }
549
- fs7.renameSync(osContentPath, sdkInstallDir);
594
+ movePath(osContentPath, sdkInstallDir);
550
595
  progress?.report({ message: "Finalizing installation...", increment: 3 });
551
596
  progress?.report({ message: "Cleaning up...", increment: 0 });
552
- fs7.rmSync(tmpDir, { recursive: true, force: true });
597
+ fs8.rmSync(tmpDir, { recursive: true, force: true });
553
598
  progress?.report({ message: "Cleaning up...", increment: 2 });
554
599
  } catch (err) {
555
600
  logger.error(`SDK install failed: ${err instanceof Error ? err.message : String(err)}`);
556
- fs7.rmSync(tmpDir, { recursive: true, force: true });
601
+ fs8.rmSync(tmpDir, { recursive: true, force: true });
557
602
  throw err;
558
603
  }
559
604
  }
560
605
 
561
606
  // src/cmdTools/install.ts
562
- import * as fs8 from "fs";
607
+ import * as fs9 from "fs";
563
608
  import * as os6 from "os";
564
609
  import * as path8 from "path";
565
610
  import { execFileSync } from "child_process";
@@ -594,11 +639,11 @@ function findCmdToolsSourceDir(extractPath) {
594
639
  path8.join(extractPath, "commandline-tools")
595
640
  ];
596
641
  for (const c of known) {
597
- if (fs8.existsSync(c)) return c;
642
+ if (fs9.existsSync(c)) return c;
598
643
  }
599
- const entries = fs8.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path8.join(extractPath, e.name));
644
+ const entries = fs9.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path8.join(extractPath, e.name));
600
645
  for (const dir of entries) {
601
- if (fs8.existsSync(path8.join(dir, "bin"))) return dir;
646
+ if (fs9.existsSync(path8.join(dir, "bin"))) return dir;
602
647
  }
603
648
  throw new OniroError("Could not locate command line tools folder in the extracted archive.");
604
649
  }
@@ -606,7 +651,7 @@ async function installCmdTools(opts) {
606
651
  const { config, progress, abortSignal } = opts;
607
652
  const logger = opts.logger ?? noopLogger;
608
653
  const CMD_PATH = getCmdToolsPath(config);
609
- const tmpDir = fs8.mkdtempSync(path8.join(os6.tmpdir(), "oniro-cmdtools-"));
654
+ const tmpDir = fs9.mkdtempSync(path8.join(os6.tmpdir(), "oniro-cmdtools-"));
610
655
  const zipPath = opts.localZipPath ?? path8.join(tmpDir, "oh-command-line-tools.zip");
611
656
  const extractPath = path8.join(tmpDir, "oh-command-line-tools");
612
657
  try {
@@ -617,42 +662,42 @@ async function installCmdTools(opts) {
617
662
  }
618
663
  progress?.report({ message: "Extracting tools...", increment: 0 });
619
664
  await extractZipWithProgress({ zipPath, dest: extractPath, progress, start: 50, range: 45, logger });
620
- fs8.mkdirSync(CMD_PATH, { recursive: true });
665
+ fs9.mkdirSync(CMD_PATH, { recursive: true });
621
666
  const srcDir = findCmdToolsSourceDir(extractPath);
622
- for (const entry of fs8.readdirSync(srcDir)) {
667
+ for (const entry of fs9.readdirSync(srcDir)) {
623
668
  const src = path8.join(srcDir, entry);
624
669
  const dest = path8.join(CMD_PATH, entry);
625
- if (fs8.statSync(src).isDirectory()) {
626
- if (fs8.existsSync(dest)) fs8.rmSync(dest, { recursive: true, force: true });
627
- fs8.renameSync(src, dest);
670
+ if (fs9.statSync(src).isDirectory()) {
671
+ if (fs9.existsSync(dest)) fs9.rmSync(dest, { recursive: true, force: true });
672
+ movePath(src, dest);
628
673
  } else {
629
- fs8.copyFileSync(src, dest);
674
+ fs9.copyFileSync(src, dest);
630
675
  }
631
676
  }
632
677
  const binDir = path8.join(CMD_PATH, "bin");
633
- if (fs8.existsSync(binDir) && os6.platform() !== "win32") {
634
- for (const file of fs8.readdirSync(binDir)) {
635
- fs8.chmodSync(path8.join(binDir, file), 493);
678
+ if (fs9.existsSync(binDir) && os6.platform() !== "win32") {
679
+ for (const file of fs9.readdirSync(binDir)) {
680
+ fs9.chmodSync(path8.join(binDir, file), 493);
636
681
  }
637
682
  }
638
683
  progress?.report({ message: "Finalizing installation...", increment: 5 });
639
684
  progress?.report({ message: "Cleaning up...", increment: 0 });
640
685
  } finally {
641
- fs8.rmSync(tmpDir, { recursive: true, force: true });
686
+ fs9.rmSync(tmpDir, { recursive: true, force: true });
642
687
  }
643
688
  }
644
689
  function getCmdToolsStatus(config) {
645
690
  const cmdPath = getCmdToolsPath(config);
646
691
  const binDir = path8.join(cmdPath, "bin");
647
692
  const candidates = os6.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
648
- const bin = candidates.map((c) => path8.join(binDir, c)).find((p) => fs8.existsSync(p));
693
+ const bin = candidates.map((c) => path8.join(binDir, c)).find((p) => fs9.existsSync(p));
649
694
  if (!bin) {
650
695
  return { installed: false, status: "Not installed" };
651
696
  }
652
697
  try {
653
698
  const versionFile = path8.join(cmdPath, "version.txt");
654
- if (fs8.existsSync(versionFile)) {
655
- const content = fs8.readFileSync(versionFile, "utf8");
699
+ if (fs9.existsSync(versionFile)) {
700
+ const content = fs9.readFileSync(versionFile, "utf8");
656
701
  for (const line of content.split(/\r?\n/)) {
657
702
  const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
658
703
  if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
@@ -672,13 +717,13 @@ function isCmdToolsInstalled(config) {
672
717
  }
673
718
  function removeCmdTools(config) {
674
719
  const cmdPath = getCmdToolsPath(config);
675
- if (fs8.existsSync(cmdPath)) {
676
- fs8.rmSync(cmdPath, { recursive: true, force: true });
720
+ if (fs9.existsSync(cmdPath)) {
721
+ fs9.rmSync(cmdPath, { recursive: true, force: true });
677
722
  }
678
723
  }
679
724
 
680
725
  // src/sign/encryptKey.ts
681
- import * as fs9 from "fs";
726
+ import * as fs10 from "fs";
682
727
  import * as path9 from "path";
683
728
  import * as crypto2 from "crypto";
684
729
  var DAEMON_ROOT_KEY_COMPONENT_LENGTH = 16;
@@ -752,8 +797,8 @@ function createAndStoreKey(dir, keyLength) {
752
797
  const key = crypto2.randomBytes(keyLength);
753
798
  const hash = crypto2.createHash("sha256").update(key).digest("hex");
754
799
  const filePath = path9.join(dir, hash);
755
- fs9.writeFileSync(filePath, key);
756
- fs9.chmodSync(filePath, KEY_FILE_PERMISSIONS);
800
+ fs10.writeFileSync(filePath, key);
801
+ fs10.chmodSync(filePath, KEY_FILE_PERMISSIONS);
757
802
  return key;
758
803
  }
759
804
  function createAndStoreEnKey(rootKey, ceDir) {
@@ -761,23 +806,23 @@ function createAndStoreEnKey(rootKey, ceDir) {
761
806
  const encrypted = encrypt(rootKey, workKey);
762
807
  const hash = crypto2.createHash("sha256").update(encrypted).digest("hex");
763
808
  const filePath = path9.join(ceDir, hash);
764
- fs9.writeFileSync(filePath, encrypted);
765
- fs9.chmodSync(filePath, KEY_FILE_PERMISSIONS);
809
+ fs10.writeFileSync(filePath, encrypted);
810
+ fs10.chmodSync(filePath, KEY_FILE_PERMISSIONS);
766
811
  }
767
812
  function createMaterial(materialPath) {
768
- fs9.mkdirSync(materialPath, { recursive: true });
769
- fs9.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
813
+ fs10.mkdirSync(materialPath, { recursive: true });
814
+ fs10.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
770
815
  const fdDir = path9.join(materialPath, "fd");
771
816
  const acDir = path9.join(materialPath, "ac");
772
817
  const ceDir = path9.join(materialPath, "ce");
773
- fs9.mkdirSync(fdDir, { recursive: true });
774
- fs9.mkdirSync(acDir, { recursive: true });
775
- fs9.mkdirSync(ceDir, { recursive: true });
818
+ fs10.mkdirSync(fdDir, { recursive: true });
819
+ fs10.mkdirSync(acDir, { recursive: true });
820
+ fs10.mkdirSync(ceDir, { recursive: true });
776
821
  const fdSubDirs = ["0", "1", "2"];
777
822
  const fdComponents = [];
778
823
  for (const sub of fdSubDirs) {
779
824
  const subDir = path9.join(fdDir, sub);
780
- fs9.mkdirSync(subDir, { recursive: true });
825
+ fs10.mkdirSync(subDir, { recursive: true });
781
826
  const comp = createAndStoreKey(subDir, DAEMON_ROOT_KEY_COMPONENT_LENGTH);
782
827
  fdComponents.push(comp);
783
828
  }
@@ -786,17 +831,17 @@ function createMaterial(materialPath) {
786
831
  createAndStoreEnKey(rootKey, ceDir);
787
832
  }
788
833
  function readSingleFile(dir) {
789
- if (!fs9.existsSync(dir) || !fs9.statSync(dir).isDirectory()) {
834
+ if (!fs10.existsSync(dir) || !fs10.statSync(dir).isDirectory()) {
790
835
  throw new Error(`Missing signing material directory: ${dir}`);
791
836
  }
792
- const files = fs9.readdirSync(dir).filter((f) => f !== ".DS_Store");
837
+ const files = fs10.readdirSync(dir).filter((f) => f !== ".DS_Store");
793
838
  if (files.length !== 1) {
794
839
  throw new Error(`Signing material in ${dir} is illegal (expected exactly one file).`);
795
840
  }
796
- return fs9.readFileSync(path9.join(dir, files[0]));
841
+ return fs10.readFileSync(path9.join(dir, files[0]));
797
842
  }
798
843
  function getKey(materialPath) {
799
- if (!fs9.existsSync(materialPath) || !fs9.statSync(materialPath).isDirectory()) {
844
+ if (!fs10.existsSync(materialPath) || !fs10.statSync(materialPath).isDirectory()) {
800
845
  throw new Error("Material directory does not exist.");
801
846
  }
802
847
  const fdDir = path9.join(materialPath, "fd");
@@ -818,7 +863,7 @@ function decryptPwd(encryptedHex, materialPath) {
818
863
  }
819
864
 
820
865
  // src/sign/generateSigningConfigs.ts
821
- import * as fs10 from "fs";
866
+ import * as fs11 from "fs";
822
867
  import * as path10 from "path";
823
868
  import { execFileSync as execFileSync2 } from "child_process";
824
869
  import JSON52 from "json5";
@@ -873,6 +918,7 @@ zAlF08DnbJrOOtOnQq5wHOPlDYB4OtUzOYJk9scotrEnJxJzGsh/
873
918
  // src/sign/generateSigningConfigs.ts
874
919
  var APL_VALUES = ["normal", "system_basic", "system_core"];
875
920
  var APP_FEATURE_VALUES = ["hos_normal_app", "hos_system_app"];
921
+ var DEFAULT_PASSWORDS = { store: "123456", key: "123456" };
876
922
  function pickSigningKind(apl) {
877
923
  return apl === "normal" ? "profile-release" : "application-release";
878
924
  }
@@ -886,18 +932,20 @@ var SIGNING_KIND_CONFIG = {
886
932
  certBasename: "OpenHarmonyApplication.cer"
887
933
  }
888
934
  };
889
- function copyFilesToProject(projectDir, paths, kind, logger) {
935
+ function copyFilesToProject(projectDir, paths, kind, logger, applicationCertPath) {
890
936
  logger.info("[sign] Copying signing material into project...");
891
937
  const signaturesDir = path10.join(projectDir, "signatures");
892
- fs10.mkdirSync(signaturesDir, { recursive: true });
893
- fs10.copyFileSync(paths.keystore, path10.join(signaturesDir, "OpenHarmony.p12"));
894
- fs10.copyFileSync(paths.profileCert, path10.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
895
- fs10.copyFileSync(paths.unsignedProfileTemplate, path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
938
+ fs11.mkdirSync(signaturesDir, { recursive: true });
939
+ fs11.copyFileSync(paths.keystore, path10.join(signaturesDir, "OpenHarmony.p12"));
940
+ fs11.copyFileSync(paths.profileCert, path10.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
941
+ fs11.copyFileSync(paths.unsignedProfileTemplate, path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
896
942
  if (kind === "application-release") {
897
- fs10.writeFileSync(
898
- path10.join(signaturesDir, "OpenHarmonyApplication.cer"),
899
- OPEN_HARMONY_APPLICATION_CERT
900
- );
943
+ const dest = path10.join(signaturesDir, "OpenHarmonyApplication.cer");
944
+ if (applicationCertPath) {
945
+ fs11.copyFileSync(applicationCertPath, dest);
946
+ } else {
947
+ fs11.writeFileSync(dest, OPEN_HARMONY_APPLICATION_CERT);
948
+ }
901
949
  }
902
950
  }
903
951
  function resolveAppFeature(apl, override) {
@@ -905,7 +953,7 @@ function resolveAppFeature(apl, override) {
905
953
  return apl === "normal" ? "hos_normal_app" : "hos_system_app";
906
954
  }
907
955
  function extractLeafCertificate(chainPath) {
908
- const certContent = fs10.readFileSync(chainPath, "utf-8");
956
+ const certContent = fs11.readFileSync(chainPath, "utf-8");
909
957
  const parts = certContent.split("-----END CERTIFICATE-----");
910
958
  if (parts.length < 3) {
911
959
  throw new OniroError(`${chainPath} does not contain enough certificates.`);
@@ -917,18 +965,18 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
917
965
  logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature}, kind=${kind})...`);
918
966
  const appJsonPath = path10.join(projectDir, "AppScope/app.json5");
919
967
  const profileTemplatePath = path10.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
920
- if (!fs10.existsSync(appJsonPath)) {
968
+ if (!fs11.existsSync(appJsonPath)) {
921
969
  throw new OniroError(`${appJsonPath} does not exist.`);
922
970
  }
923
971
  let appJson;
924
972
  try {
925
- appJson = JSON52.parse(fs10.readFileSync(appJsonPath, "utf-8"));
973
+ appJson = JSON52.parse(fs11.readFileSync(appJsonPath, "utf-8"));
926
974
  } catch (e) {
927
975
  throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
928
976
  }
929
977
  let profileTemplate;
930
978
  try {
931
- profileTemplate = JSON.parse(fs10.readFileSync(profileTemplatePath, "utf-8"));
979
+ profileTemplate = JSON.parse(fs11.readFileSync(profileTemplatePath, "utf-8"));
932
980
  } catch (e) {
933
981
  throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
934
982
  }
@@ -951,9 +999,9 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
951
999
  profileTemplate.acls = profileTemplate.acls ?? {};
952
1000
  profileTemplate.acls["allowed-acls"] = options.acls;
953
1001
  }
954
- fs10.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
1002
+ fs11.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
955
1003
  }
956
- function generateP7bFile(projectDir, paths, logger) {
1004
+ function generateP7bFile(projectDir, paths, logger, passwords) {
957
1005
  logger.info("[sign] Generating P7b profile via hap-sign-tool...");
958
1006
  const signaturesDir = path10.join(projectDir, "signatures");
959
1007
  const profileTemplatePath = path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
@@ -977,9 +1025,9 @@ function generateP7bFile(projectDir, paths, logger) {
977
1025
  "-outFile",
978
1026
  outputProfilePath,
979
1027
  "-keyPwd",
980
- "123456",
1028
+ passwords.key,
981
1029
  "-keystorePwd",
982
- "123456"
1030
+ passwords.store
983
1031
  ];
984
1032
  try {
985
1033
  execFileSync2("java", args, { stdio: "inherit" });
@@ -992,10 +1040,10 @@ function generateP7bFile(projectDir, paths, logger) {
992
1040
  }
993
1041
  function detectSigningConfigNames(projectDir) {
994
1042
  const buildProfilePath = path10.join(projectDir, "build-profile.json5");
995
- if (!fs10.existsSync(buildProfilePath)) return ["default"];
1043
+ if (!fs11.existsSync(buildProfilePath)) return ["default"];
996
1044
  let parsed;
997
1045
  try {
998
- parsed = JSON52.parse(fs10.readFileSync(buildProfilePath, "utf-8"));
1046
+ parsed = JSON52.parse(fs11.readFileSync(buildProfilePath, "utf-8"));
999
1047
  } catch {
1000
1048
  return ["default"];
1001
1049
  }
@@ -1010,16 +1058,16 @@ function detectSigningConfigNames(projectDir) {
1010
1058
  }
1011
1059
  return names.length > 0 ? names : ["default"];
1012
1060
  }
1013
- function updateBuildProfile(projectDir, kind, logger) {
1061
+ function updateBuildProfile(projectDir, kind, logger, passwords) {
1014
1062
  logger.info("[sign] Writing signing configs into build-profile.json5...");
1015
1063
  const materialDir = path10.join(projectDir, "signatures", "material");
1016
1064
  const buildProfilePath = path10.join(projectDir, "build-profile.json5");
1017
- const encryptedStorePassword = encryptPwd("123456", materialDir);
1018
- const encryptedKeyPassword = encryptPwd("123456", materialDir);
1065
+ const encryptedStorePassword = encryptPwd(passwords.store, materialDir);
1066
+ const encryptedKeyPassword = encryptPwd(passwords.key, materialDir);
1019
1067
  let buildProfile = { app: {} };
1020
- if (fs10.existsSync(buildProfilePath)) {
1068
+ if (fs11.existsSync(buildProfilePath)) {
1021
1069
  try {
1022
- buildProfile = JSON52.parse(fs10.readFileSync(buildProfilePath, "utf-8"));
1070
+ buildProfile = JSON52.parse(fs11.readFileSync(buildProfilePath, "utf-8"));
1023
1071
  } catch (e) {
1024
1072
  throw new OniroError(`Error parsing ${buildProfilePath}: ${e.message}`, e);
1025
1073
  }
@@ -1040,13 +1088,13 @@ function updateBuildProfile(projectDir, kind, logger) {
1040
1088
  storeFile: "./signatures/OpenHarmony.p12"
1041
1089
  }
1042
1090
  }));
1043
- fs10.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1091
+ fs11.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1044
1092
  }
1045
1093
  function prepareMaterialDirectory(projectDir, logger) {
1046
1094
  logger.info("[sign] Generating fresh signing material...");
1047
1095
  const materialDir = path10.join(projectDir, "signatures", "material");
1048
- if (fs10.existsSync(materialDir)) {
1049
- fs10.rmSync(materialDir, { recursive: true, force: true });
1096
+ if (fs11.existsSync(materialDir)) {
1097
+ fs11.rmSync(materialDir, { recursive: true, force: true });
1050
1098
  }
1051
1099
  createMaterial(materialDir);
1052
1100
  }
@@ -1070,7 +1118,7 @@ function generateSigningConfigs(options) {
1070
1118
  );
1071
1119
  }
1072
1120
  const sdkPath = path10.join(sdkHome, String(sdkVersion));
1073
- if (!fs10.existsSync(sdkPath)) {
1121
+ if (!fs11.existsSync(sdkPath)) {
1074
1122
  throw new OniroError(`SDK path does not exist: ${sdkPath}`);
1075
1123
  }
1076
1124
  const paths = {
@@ -1080,17 +1128,18 @@ function generateSigningConfigs(options) {
1080
1128
  unsignedProfileTemplate: path10.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
1081
1129
  };
1082
1130
  const kind = pickSigningKind(apl);
1131
+ const passwords = options.passwords ?? DEFAULT_PASSWORDS;
1083
1132
  logger.info("[sign] Starting signing configuration generation...");
1084
- copyFilesToProject(projectDir, paths, kind, logger);
1133
+ copyFilesToProject(projectDir, paths, kind, logger, options.applicationCertPath);
1085
1134
  modifyProfileTemplate(projectDir, apl, appFeature, logger, { kind, acls: options.acls });
1086
- generateP7bFile(projectDir, paths, logger);
1135
+ generateP7bFile(projectDir, paths, logger, passwords);
1087
1136
  prepareMaterialDirectory(projectDir, logger);
1088
- updateBuildProfile(projectDir, kind, logger);
1137
+ updateBuildProfile(projectDir, kind, logger, passwords);
1089
1138
  logger.info("[sign] Signing configuration generated successfully.");
1090
1139
  }
1091
1140
 
1092
1141
  // src/emulator/install.ts
1093
- import * as fs11 from "fs";
1142
+ import * as fs12 from "fs";
1094
1143
  import * as os7 from "os";
1095
1144
  import * as path11 from "path";
1096
1145
  async function installEmulator(opts) {
@@ -1098,63 +1147,142 @@ async function installEmulator(opts) {
1098
1147
  const logger = opts.logger ?? noopLogger;
1099
1148
  const url = config.get("emulatorUrl", defaultPaths.emulatorUrl);
1100
1149
  const emulatorDir = getEmulatorDir(config);
1101
- const tmpDir = fs11.mkdtempSync(path11.join(os7.tmpdir(), "oniro-emulator-"));
1150
+ const tmpDir = fs12.mkdtempSync(path11.join(os7.tmpdir(), "oniro-emulator-"));
1102
1151
  const tmpZip = path11.join(tmpDir, "oniro_emulator.zip");
1103
1152
  try {
1104
- fs11.mkdirSync(emulatorDir, { recursive: true });
1153
+ fs12.mkdirSync(emulatorDir, { recursive: true });
1105
1154
  progress?.report({ message: "Downloading emulator...", increment: 0 });
1106
1155
  await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50 });
1107
1156
  progress?.report({ message: "Extracting emulator...", increment: 0 });
1108
1157
  await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
1109
1158
  const runSh = path11.join(emulatorDir, "images", "run.sh");
1110
- if (fs11.existsSync(runSh)) {
1111
- fs11.chmodSync(runSh, 493);
1159
+ if (fs12.existsSync(runSh)) {
1160
+ fs12.chmodSync(runSh, 493);
1112
1161
  }
1113
1162
  progress?.report({ message: "Finalizing installation...", increment: 5 });
1114
1163
  } finally {
1115
- fs11.rmSync(tmpDir, { recursive: true, force: true });
1164
+ fs12.rmSync(tmpDir, { recursive: true, force: true });
1116
1165
  }
1117
1166
  }
1118
1167
  function isEmulatorInstalled(config) {
1119
- return fs11.existsSync(path11.join(getEmulatorDir(config), "images", "run.sh"));
1168
+ const imagesDir = path11.join(getEmulatorDir(config), "images");
1169
+ return fs12.existsSync(path11.join(imagesDir, "run.sh")) || fs12.existsSync(path11.join(imagesDir, "run.bat"));
1120
1170
  }
1121
1171
  function removeEmulator(config) {
1122
1172
  const emulatorDir = getEmulatorDir(config);
1123
- if (fs11.existsSync(emulatorDir)) {
1124
- fs11.rmSync(emulatorDir, { recursive: true, force: true });
1173
+ if (fs12.existsSync(emulatorDir)) {
1174
+ fs12.rmSync(emulatorDir, { recursive: true, force: true });
1125
1175
  }
1126
1176
  }
1127
1177
 
1128
1178
  // src/emulator/lifecycle.ts
1129
- import * as fs12 from "fs";
1179
+ import * as fs13 from "fs";
1130
1180
  import * as os8 from "os";
1131
1181
  import * as path12 from "path";
1132
- import { exec, spawn } from "child_process";
1133
- var PID_FILE = path12.join(os8.tmpdir(), "oniro_emulator.pid");
1134
- function execPromise(cmd, cwd, logger) {
1182
+ import { spawn as spawn2 } from "child_process";
1183
+
1184
+ // src/hdc/exec.ts
1185
+ import { spawn } from "child_process";
1186
+ function runProcess(opts) {
1187
+ const { command, args, cwd, env, onOutput } = opts;
1188
+ const timeoutMs = opts.timeoutMs ?? 6e4;
1189
+ const { abortSignal } = opts;
1135
1190
  return new Promise((resolve2, reject) => {
1136
- exec(cmd, { cwd }, (error, stdout, stderr) => {
1137
- if (stdout?.trim()) logger.info(`[emulator] ${stdout.trim()}`);
1138
- if (stderr?.trim()) logger.warn(`[emulator] ${stderr.trim()}`);
1139
- if (error) {
1140
- logger.error(`[emulator] ${error.message}`);
1141
- reject(error);
1142
- } else {
1143
- resolve2();
1191
+ if (abortSignal?.aborted) {
1192
+ reject(new CancelledError(`Command aborted before start: ${command}`));
1193
+ return;
1194
+ }
1195
+ const child = spawn(command, [...args], { cwd, env, shell: false });
1196
+ let stdout = "";
1197
+ let stderr = "";
1198
+ let settled = false;
1199
+ let timedOut = false;
1200
+ const cleanup = () => {
1201
+ clearTimeout(timer);
1202
+ abortSignal?.removeEventListener("abort", onAbort);
1203
+ };
1204
+ const onAbort = () => {
1205
+ if (settled) return;
1206
+ settled = true;
1207
+ cleanup();
1208
+ try {
1209
+ child.kill("SIGKILL");
1210
+ } catch {
1211
+ }
1212
+ reject(new CancelledError(`Command aborted: ${command} ${args.join(" ")}`));
1213
+ };
1214
+ const timer = setTimeout(() => {
1215
+ timedOut = true;
1216
+ try {
1217
+ child.kill("SIGKILL");
1218
+ } catch {
1219
+ }
1220
+ }, timeoutMs);
1221
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
1222
+ child.stdout.on("data", (d) => {
1223
+ const s = d.toString();
1224
+ stdout += s;
1225
+ onOutput?.(s, "stdout");
1226
+ });
1227
+ child.stderr.on("data", (d) => {
1228
+ const s = d.toString();
1229
+ stderr += s;
1230
+ onOutput?.(s, "stderr");
1231
+ });
1232
+ child.on("error", (err) => {
1233
+ if (settled) return;
1234
+ settled = true;
1235
+ cleanup();
1236
+ reject(new OniroError(`Failed to spawn ${command}: ${err.message}`, err));
1237
+ });
1238
+ child.on("close", (code) => {
1239
+ if (settled) return;
1240
+ settled = true;
1241
+ cleanup();
1242
+ if (timedOut) {
1243
+ reject(
1244
+ new OniroError(
1245
+ `Command timed out after ${timeoutMs}ms: ${command} ${args.join(" ")}` + (stderr.trim() ? `
1246
+ ${stderr.trim()}` : "")
1247
+ )
1248
+ );
1249
+ return;
1144
1250
  }
1251
+ resolve2({ code: code ?? -1, stdout, stderr });
1145
1252
  });
1146
1253
  });
1147
1254
  }
1255
+ function hdcExec(opts) {
1256
+ const hdc = getHdcPath(opts.config);
1257
+ const argv = opts.deviceSerial ? ["-t", opts.deviceSerial, ...opts.args] : [...opts.args];
1258
+ return runProcess({
1259
+ command: hdc,
1260
+ args: argv,
1261
+ timeoutMs: opts.timeoutMs,
1262
+ abortSignal: opts.abortSignal,
1263
+ onOutput: opts.onOutput,
1264
+ logger: opts.logger
1265
+ });
1266
+ }
1267
+ function shell(opts) {
1268
+ const { command, ...rest } = opts;
1269
+ return hdcExec({ ...rest, args: ["shell", command] });
1270
+ }
1271
+ function ensureOk(result, command) {
1272
+ if (result.code !== 0) {
1273
+ throw new CommandFailedError(command, result.code, result.stderr.trim());
1274
+ }
1275
+ return result;
1276
+ }
1277
+
1278
+ // src/emulator/lifecycle.ts
1279
+ var PID_FILE = path12.join(os8.tmpdir(), "oniro_emulator.pid");
1148
1280
  async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
1149
- const hdc = getHdcPath(config);
1150
1281
  try {
1151
- await execPromise(`"${hdc}" start -r`, void 0, logger);
1152
- await execPromise(`"${hdc}" tconn ${address}`, void 0, logger);
1153
- return await new Promise((resolve2) => {
1154
- exec(`"${hdc}" list targets`, (_error, stdout) => {
1155
- resolve2(stdout?.includes(address) ?? false);
1156
- });
1157
- });
1282
+ await hdcExec({ config, args: ["start", "-r"], logger });
1283
+ await hdcExec({ config, args: ["tconn", address], logger });
1284
+ const targets = await hdcExec({ config, args: ["list", "targets"], logger });
1285
+ return targets.stdout.includes(address);
1158
1286
  } catch (err) {
1159
1287
  logger.warn(`hdc connection attempt failed: ${err.message}`);
1160
1288
  return false;
@@ -1167,18 +1295,18 @@ function resolveLauncher(imagesPath, headless, connect) {
1167
1295
  if (headless) extra.push("--headless");
1168
1296
  if (connect) extra.push("--connect", connect);
1169
1297
  if (os8.platform() === "win32") {
1170
- if (fs12.existsSync(runBat)) {
1298
+ if (fs13.existsSync(runBat)) {
1171
1299
  return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
1172
1300
  }
1173
- if (fs12.existsSync(runSh)) {
1301
+ if (fs13.existsSync(runSh)) {
1174
1302
  return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1175
1303
  }
1176
1304
  throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
1177
1305
  }
1178
- if (fs12.existsSync(runSh)) {
1306
+ if (fs13.existsSync(runSh)) {
1179
1307
  return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1180
1308
  }
1181
- if (fs12.existsSync(runBat)) {
1309
+ if (fs13.existsSync(runBat)) {
1182
1310
  return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
1183
1311
  }
1184
1312
  throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
@@ -1186,9 +1314,9 @@ function resolveLauncher(imagesPath, headless, connect) {
1186
1314
  async function startEmulator(opts) {
1187
1315
  const { config } = opts;
1188
1316
  const logger = opts.logger ?? noopLogger;
1189
- if (os8.platform() !== "win32" && fs12.existsSync(PID_FILE)) {
1317
+ if (os8.platform() !== "win32" && fs13.existsSync(PID_FILE)) {
1190
1318
  try {
1191
- const pid = Number(fs12.readFileSync(PID_FILE, "utf8").trim());
1319
+ const pid = Number(fs13.readFileSync(PID_FILE, "utf8").trim());
1192
1320
  if (Number.isFinite(pid) && pid > 0) {
1193
1321
  try {
1194
1322
  process.kill(pid, 0);
@@ -1205,27 +1333,27 @@ async function startEmulator(opts) {
1205
1333
  }
1206
1334
  }
1207
1335
  const imagesPath = path12.join(getEmulatorDir(config), "images");
1208
- if (!fs12.existsSync(imagesPath)) {
1336
+ if (!fs13.existsSync(imagesPath)) {
1209
1337
  throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
1210
1338
  }
1211
1339
  const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
1212
1340
  const logPath = opts.logFile ?? (os8.platform() === "win32" ? "NUL" : "/dev/null");
1213
- const logFd = fs12.openSync(logPath, "a");
1341
+ const logFd = fs13.openSync(logPath, "a");
1214
1342
  let child;
1215
1343
  try {
1216
- child = spawn(launcher.command, [...launcher.args], {
1344
+ child = spawn2(launcher.command, [...launcher.args], {
1217
1345
  cwd: launcher.cwd,
1218
1346
  detached: true,
1219
1347
  stdio: ["ignore", logFd, logFd],
1220
1348
  windowsHide: true
1221
1349
  });
1222
1350
  } finally {
1223
- fs12.closeSync(logFd);
1351
+ fs13.closeSync(logFd);
1224
1352
  }
1225
1353
  if (!child.pid) {
1226
1354
  throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
1227
1355
  }
1228
- fs12.writeFileSync(PID_FILE, String(child.pid));
1356
+ fs13.writeFileSync(PID_FILE, String(child.pid));
1229
1357
  child.unref();
1230
1358
  logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
1231
1359
  const earlyExit = new Promise((resolve2) => {
@@ -1272,24 +1400,24 @@ ${tail}` : ""}`);
1272
1400
  }
1273
1401
  function readTail(filePath, bytes) {
1274
1402
  try {
1275
- const stat = fs12.statSync(filePath);
1403
+ const stat = fs13.statSync(filePath);
1276
1404
  const start = Math.max(0, stat.size - bytes);
1277
- const fd = fs12.openSync(filePath, "r");
1405
+ const fd = fs13.openSync(filePath, "r");
1278
1406
  try {
1279
1407
  const buf = Buffer.alloc(stat.size - start);
1280
- fs12.readSync(fd, buf, 0, buf.length, start);
1408
+ fs13.readSync(fd, buf, 0, buf.length, start);
1281
1409
  return buf.toString("utf8");
1282
1410
  } finally {
1283
- fs12.closeSync(fd);
1411
+ fs13.closeSync(fd);
1284
1412
  }
1285
1413
  } catch {
1286
1414
  return "";
1287
1415
  }
1288
1416
  }
1289
1417
  async function stopEmulator(logger = noopLogger) {
1290
- if (fs12.existsSync(PID_FILE)) {
1418
+ if (fs13.existsSync(PID_FILE)) {
1291
1419
  try {
1292
- const pid = Number(fs12.readFileSync(PID_FILE, "utf8").trim());
1420
+ const pid = Number(fs13.readFileSync(PID_FILE, "utf8").trim());
1293
1421
  if (Number.isFinite(pid) && pid > 0) {
1294
1422
  try {
1295
1423
  process.kill(pid, "SIGTERM");
@@ -1302,27 +1430,194 @@ async function stopEmulator(logger = noopLogger) {
1302
1430
  logger.warn(`Could not read PID file: ${err.message}`);
1303
1431
  }
1304
1432
  }
1305
- const killCmd = os8.platform() === "win32" ? "taskkill /IM qemu-system-x86_64.exe /F" : "pkill -f qemu-system-x86_64";
1433
+ const [killCmd, ...killArgs] = os8.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
1306
1434
  try {
1307
- await execPromise(killCmd, void 0, logger);
1308
- } catch {
1435
+ await runProcess({ command: killCmd, args: killArgs, logger });
1436
+ } catch (err) {
1437
+ logger.warn(`[emulator] could not run ${killCmd}: ${err.message}`);
1309
1438
  }
1310
- fs12.rm(PID_FILE, { force: true }, (err) => {
1439
+ fs13.rm(PID_FILE, { force: true }, (err) => {
1311
1440
  if (err) logger.warn(`Could not remove PID file: ${err.message}`);
1312
1441
  });
1313
1442
  }
1314
1443
 
1444
+ // src/hdc/devices.ts
1445
+ var STATUS_TOKENS = /* @__PURE__ */ new Set([
1446
+ "Connected",
1447
+ "Offline",
1448
+ "Unauthorized",
1449
+ "Authorizing",
1450
+ "Disconnected",
1451
+ "Recovery"
1452
+ ]);
1453
+ var CONNECTION_TOKENS = /* @__PURE__ */ new Set(["USB", "TCP", "UART", "BT", "UNKNOWN"]);
1454
+ function parseDeviceList(stdout) {
1455
+ const devices = [];
1456
+ for (const line of stdout.split("\n")) {
1457
+ const trimmed = line.trim();
1458
+ if (!trimmed || trimmed.startsWith("[Empty]")) continue;
1459
+ const cols = trimmed.split(/\s+/);
1460
+ const serial = cols[0];
1461
+ if (!serial) continue;
1462
+ const rest = cols.slice(1);
1463
+ const connection = rest.find((c) => CONNECTION_TOKENS.has(c));
1464
+ const status = rest.find((c) => STATUS_TOKENS.has(c)) ?? "Unknown";
1465
+ const info = { serial, status };
1466
+ if (connection) info.connection = connection;
1467
+ devices.push(info);
1468
+ }
1469
+ return devices;
1470
+ }
1471
+ async function listDevices(config, opts = {}) {
1472
+ const res = await hdcExec({
1473
+ config,
1474
+ args: ["list", "targets", "-v"],
1475
+ timeoutMs: opts.timeoutMs ?? 1e4,
1476
+ logger: opts.logger
1477
+ });
1478
+ return parseDeviceList(res.stdout);
1479
+ }
1480
+ async function selectDevice(config, serial, opts = {}) {
1481
+ if (serial && serial !== "auto") return serial;
1482
+ const envSerial = process.env.ONIRO_DEVICE_SERIAL || process.env.DEVICE_SERIAL;
1483
+ if (envSerial && envSerial !== "auto") return envSerial;
1484
+ const usable = (await listDevices(config, opts)).filter(
1485
+ (d) => d.status === "Connected" || d.status === "Unknown"
1486
+ );
1487
+ if (usable.length === 1) return usable[0].serial;
1488
+ if (usable.length === 0) {
1489
+ throw new OniroError("No connected device found. Connect a device/emulator or set ONIRO_DEVICE_SERIAL.");
1490
+ }
1491
+ throw new OniroError(
1492
+ `Multiple devices connected (${usable.map((d) => d.serial).join(", ")}). Set ONIRO_DEVICE_SERIAL to choose one.`
1493
+ );
1494
+ }
1495
+
1496
+ // src/hdc/param.ts
1497
+ async function paramGet(config, key, opts = {}) {
1498
+ const res = await shell({
1499
+ config,
1500
+ command: `param get ${key}`,
1501
+ deviceSerial: opts.deviceSerial,
1502
+ timeoutMs: opts.timeoutMs ?? 1e4,
1503
+ logger: opts.logger
1504
+ });
1505
+ ensureOk(res, `hdc shell param get ${key}`);
1506
+ return res.stdout.trim();
1507
+ }
1508
+ async function paramSet(config, key, value, opts = {}) {
1509
+ const res = await shell({
1510
+ config,
1511
+ command: `param set ${key} ${value}`,
1512
+ deviceSerial: opts.deviceSerial,
1513
+ timeoutMs: opts.timeoutMs ?? 1e4,
1514
+ logger: opts.logger
1515
+ });
1516
+ ensureOk(res, `hdc shell param set ${key} ${value}`);
1517
+ }
1518
+
1519
+ // src/hdc/files.ts
1520
+ async function sendFile(opts) {
1521
+ const res = await hdcExec({
1522
+ config: opts.config,
1523
+ args: ["file", "send", opts.local, opts.remote],
1524
+ deviceSerial: opts.deviceSerial,
1525
+ timeoutMs: opts.timeoutMs ?? 6e5,
1526
+ abortSignal: opts.abortSignal,
1527
+ onOutput: opts.onOutput,
1528
+ logger: opts.logger
1529
+ });
1530
+ ensureOk(res, `hdc file send ${opts.local} ${opts.remote}`);
1531
+ }
1532
+ async function recvFile(opts) {
1533
+ const res = await hdcExec({
1534
+ config: opts.config,
1535
+ args: ["file", "recv", opts.remote, opts.local],
1536
+ deviceSerial: opts.deviceSerial,
1537
+ timeoutMs: opts.timeoutMs ?? 6e5,
1538
+ abortSignal: opts.abortSignal,
1539
+ onOutput: opts.onOutput,
1540
+ logger: opts.logger
1541
+ });
1542
+ ensureOk(res, `hdc file recv ${opts.remote} ${opts.local}`);
1543
+ }
1544
+
1545
+ // src/hdc/display.ts
1546
+ async function getDisplaySize(config, opts = {}) {
1547
+ const res = await shell({
1548
+ config,
1549
+ command: "hidumper -s 10 -a screen",
1550
+ deviceSerial: opts.deviceSerial,
1551
+ timeoutMs: opts.timeoutMs ?? 1e4,
1552
+ logger: opts.logger
1553
+ });
1554
+ if (res.code !== 0) return null;
1555
+ const m = res.stdout.match(/render resolution=(\d+)x(\d+)/);
1556
+ if (!m) return null;
1557
+ return { width: Number(m[1]), height: Number(m[2]) };
1558
+ }
1559
+
1560
+ // src/hdc/wait.ts
1561
+ function delay(ms, signal) {
1562
+ return new Promise((resolve2, reject) => {
1563
+ if (signal?.aborted) {
1564
+ reject(new CancelledError("Wait cancelled."));
1565
+ return;
1566
+ }
1567
+ const onAbort = () => {
1568
+ cleanup();
1569
+ reject(new CancelledError("Wait cancelled."));
1570
+ };
1571
+ const cleanup = () => {
1572
+ clearTimeout(timer);
1573
+ signal?.removeEventListener("abort", onAbort);
1574
+ };
1575
+ const timer = setTimeout(() => {
1576
+ cleanup();
1577
+ resolve2();
1578
+ }, ms);
1579
+ signal?.addEventListener("abort", onAbort, { once: true });
1580
+ });
1581
+ }
1582
+ async function waitForCondition(opts) {
1583
+ const pollMs = opts.pollMs ?? 2e3;
1584
+ const heartbeatEvery = opts.heartbeatEvery ?? 6;
1585
+ const deadline = Date.now() + opts.timeoutMs;
1586
+ let attempt = 0;
1587
+ while (Date.now() < deadline) {
1588
+ if (opts.abortSignal?.aborted) throw new CancelledError("Wait cancelled.");
1589
+ attempt++;
1590
+ let ok = false;
1591
+ try {
1592
+ ok = await opts.probe();
1593
+ } catch {
1594
+ ok = false;
1595
+ }
1596
+ if (ok) return;
1597
+ if (opts.onHeartbeat && attempt % heartbeatEvery === 0) {
1598
+ opts.onHeartbeat(attempt, Math.max(0, deadline - Date.now()));
1599
+ }
1600
+ await delay(pollMs, opts.abortSignal);
1601
+ }
1602
+ throw new OniroError(opts.timeoutMessage ?? `Condition not met within ${opts.timeoutMs}ms.`);
1603
+ }
1604
+
1605
+ // src/hdc/app.ts
1606
+ import * as fs15 from "fs";
1607
+ import * as path14 from "path";
1608
+ import { spawn as spawn3 } from "child_process";
1609
+
1315
1610
  // src/hdc/project.ts
1316
- import * as fs13 from "fs";
1611
+ import * as fs14 from "fs";
1317
1612
  import * as path13 from "path";
1318
1613
  import JSON53 from "json5";
1319
1614
  function readJson5(filePath) {
1320
- const content = fs13.readFileSync(filePath, "utf-8");
1615
+ const content = fs14.readFileSync(filePath, "utf-8");
1321
1616
  return JSON53.parse(content);
1322
1617
  }
1323
1618
  function getBundleName(projectDir) {
1324
1619
  const appJsonPath = path13.join(projectDir, "AppScope", "app.json5");
1325
- if (!fs13.existsSync(appJsonPath)) {
1620
+ if (!fs14.existsSync(appJsonPath)) {
1326
1621
  throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
1327
1622
  }
1328
1623
  const appJson = readJson5(appJsonPath);
@@ -1331,59 +1626,72 @@ function getBundleName(projectDir) {
1331
1626
  }
1332
1627
  return appJson.app.bundleName;
1333
1628
  }
1334
- function getMainAbility(projectDir, moduleName = "entry") {
1629
+ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
1335
1630
  const moduleJsonPath = path13.join(projectDir, moduleName, "src", "main", "module.json5");
1336
- if (!fs13.existsSync(moduleJsonPath)) {
1631
+ if (!fs14.existsSync(moduleJsonPath)) {
1337
1632
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
1338
1633
  }
1339
1634
  const moduleJson = readJson5(moduleJsonPath);
1340
- if (!moduleJson.module?.mainElement) {
1341
- throw new OniroError("mainElement not found in module.json5");
1342
- }
1343
- return moduleJson.module.mainElement;
1635
+ const module = moduleJson.module ?? {};
1636
+ if (abilityName) {
1637
+ const match = module.abilities?.find((a) => a.name === abilityName);
1638
+ if (match?.name) return match.name;
1639
+ throw new OniroError(`Ability "${abilityName}" not found in module.json5 at ${moduleJsonPath}`);
1640
+ }
1641
+ if (module.mainElement) return module.mainElement;
1642
+ const abilities = module.abilities ?? [];
1643
+ const visible = abilities.find((a) => a.visible && a.name);
1644
+ if (visible?.name) return visible.name;
1645
+ const first = abilities.find((a) => a.name);
1646
+ if (first?.name) return first.name;
1647
+ throw new OniroError("mainElement not found in module.json5");
1344
1648
  }
1345
1649
 
1346
1650
  // src/hdc/app.ts
1347
- import * as fs14 from "fs";
1348
- import * as path14 from "path";
1349
- import { exec as exec2, spawn as spawn2 } from "child_process";
1350
- function execPromise2(cmd, logger) {
1351
- return new Promise((resolve2, reject) => {
1352
- exec2(cmd, (error, stdout, stderr) => {
1353
- if (stdout?.trim()) logger.info(`[hdc] ${stdout.trim()}`);
1354
- if (stderr?.trim()) logger.warn(`[hdc] ${stderr.trim()}`);
1355
- if (error) {
1356
- logger.error(`[hdc] ${error.message}`);
1357
- reject(error);
1358
- } else {
1359
- resolve2();
1360
- }
1361
- });
1362
- });
1363
- }
1364
1651
  async function installApp(opts) {
1365
- const logger = opts.logger ?? noopLogger;
1652
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
1366
1653
  const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
1367
1654
  const hapPath = path14.isAbsolute(relativeHapPath) ? relativeHapPath : path14.join(opts.projectDir, relativeHapPath);
1368
- if (!fs14.existsSync(hapPath)) {
1655
+ if (!fs15.existsSync(hapPath)) {
1369
1656
  throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
1370
1657
  }
1371
- const hdc = getHdcPath(opts.config);
1372
- await execPromise2(`"${hdc}" install "${hapPath}"`, logger);
1658
+ const result = await hdcExec({ config: opts.config, args: ["install", hapPath], timeoutMs: 3e5 });
1659
+ if (result.stdout.trim()) logger.info(result.stdout.trim());
1660
+ if (result.stderr.trim()) logger.warn(result.stderr.trim());
1661
+ ensureOk(result, `hdc install ${hapPath}`);
1662
+ let bundleName = "";
1663
+ try {
1664
+ bundleName = getBundleName(opts.projectDir);
1665
+ } catch {
1666
+ }
1667
+ return { installed: true, bundleName, hapPath, output: `${result.stdout}${result.stderr}`.trim() };
1668
+ }
1669
+ function detectAaStartFailure(output) {
1670
+ return /failed to start ability|Error Code:\s*\d|Error Message:/i.test(output) ? output.trim() : null;
1373
1671
  }
1374
1672
  async function launchApp(opts) {
1375
- const logger = opts.logger ?? noopLogger;
1673
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
1376
1674
  const bundleName = getBundleName(opts.projectDir);
1377
- const mainAbility = getMainAbility(opts.projectDir, opts.moduleName);
1378
- const hdc = getHdcPath(opts.config);
1379
- await execPromise2(`"${hdc}" shell aa start -a ${mainAbility} -b ${bundleName}`, logger);
1675
+ const mainAbility = getMainAbility(opts.projectDir, opts.moduleName, opts.abilityName);
1676
+ const result = await hdcExec({
1677
+ config: opts.config,
1678
+ args: ["shell", "aa", "start", "-a", mainAbility, "-b", bundleName]
1679
+ });
1680
+ if (result.stdout.trim()) logger.info(result.stdout.trim());
1681
+ if (result.stderr.trim()) logger.warn(result.stderr.trim());
1682
+ ensureOk(result, `hdc shell aa start -a ${mainAbility} -b ${bundleName}`);
1683
+ const failure = detectAaStartFailure(`${result.stdout}
1684
+ ${result.stderr}`);
1685
+ if (failure) {
1686
+ throw new OniroError(`Failed to launch ${bundleName} (${mainAbility}): ${failure}`);
1687
+ }
1380
1688
  }
1381
1689
  function listRunningProcesses(config, options = {}) {
1382
1690
  const logger = options.logger ?? noopLogger;
1383
1691
  const timeoutMs = options.timeoutMs ?? 1e3;
1384
1692
  const hdc = getHdcPath(config);
1385
1693
  return new Promise((resolve2, reject) => {
1386
- const proc = spawn2(hdc, ["track-jpid"]);
1694
+ const proc = spawn3(hdc, ["track-jpid"]);
1387
1695
  const processes = [];
1388
1696
  let resolved = false;
1389
1697
  proc.stdout.on("data", (data) => {
@@ -1437,14 +1745,395 @@ async function findAppProcessId(config, projectDir, logger) {
1437
1745
  const result = await listRunningProcesses(config, { targetProcessName: bundleName, logger });
1438
1746
  return result;
1439
1747
  }
1748
+ async function findRunningProcess(opts) {
1749
+ const safe = opts.bundle.replace(/'/g, `'\\''`);
1750
+ const res = await shell({
1751
+ config: opts.config,
1752
+ command: `pidof '${safe}'`,
1753
+ deviceSerial: opts.deviceSerial,
1754
+ timeoutMs: opts.timeoutMs ?? 1e4,
1755
+ logger: opts.logger
1756
+ });
1757
+ const pid = res.stdout.trim().split(/\s+/).filter(Boolean)[0];
1758
+ if (res.code !== 0 || !pid || !/^\d+$/.test(pid)) return null;
1759
+ return { pid, name: opts.bundle };
1760
+ }
1761
+ async function uninstallApp(opts) {
1762
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
1763
+ const res = await hdcExec({
1764
+ config: opts.config,
1765
+ args: ["uninstall", opts.bundle],
1766
+ deviceSerial: opts.deviceSerial,
1767
+ timeoutMs: opts.timeoutMs ?? 12e4
1768
+ });
1769
+ if (res.stdout.trim()) logger.info(res.stdout.trim());
1770
+ if (res.stderr.trim()) logger.warn(res.stderr.trim());
1771
+ ensureOk(res, `hdc uninstall ${opts.bundle}`);
1772
+ }
1773
+ async function forceStop(opts) {
1774
+ const res = await shell({
1775
+ config: opts.config,
1776
+ command: `aa force-stop ${opts.bundle}`,
1777
+ deviceSerial: opts.deviceSerial,
1778
+ timeoutMs: opts.timeoutMs ?? 3e4,
1779
+ logger: opts.logger
1780
+ });
1781
+ ensureOk(res, `hdc shell aa force-stop ${opts.bundle}`);
1782
+ }
1783
+
1784
+ // src/hdc/lifecycle.ts
1785
+ async function isReachable(config, deviceSerial) {
1786
+ try {
1787
+ const res = await shell({ config, command: "echo oniro_ping", deviceSerial, timeoutMs: 5e3 });
1788
+ return res.code === 0 && res.stdout.includes("oniro_ping");
1789
+ } catch {
1790
+ return false;
1791
+ }
1792
+ }
1793
+ var TCP_SERIAL_RE = /^.+:\d+$/;
1794
+ async function reconnectIfTcp(config, deviceSerial, logger) {
1795
+ if (!deviceSerial || !TCP_SERIAL_RE.test(deviceSerial)) return;
1796
+ try {
1797
+ await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
1798
+ } catch {
1799
+ }
1800
+ }
1801
+ async function waitForBundle(opts) {
1802
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
1803
+ const timeoutMs = opts.timeoutMs ?? 18e4;
1804
+ await waitForCondition({
1805
+ probe: async () => {
1806
+ await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
1807
+ return await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
1808
+ },
1809
+ timeoutMs,
1810
+ pollMs: 2e3,
1811
+ abortSignal: opts.abortSignal,
1812
+ onHeartbeat: (attempt, remaining) => logger.info(`waiting for ${opts.bundle} (attempt ${attempt}, ${Math.floor(remaining / 1e3)}s left)`),
1813
+ timeoutMessage: `Bundle ${opts.bundle} did not start within ${timeoutMs}ms.`
1814
+ });
1815
+ }
1816
+ async function waitForBoot(opts) {
1817
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
1818
+ const timeoutMs = opts.timeoutMs ?? 18e4;
1819
+ await waitForCondition({
1820
+ probe: async () => {
1821
+ await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
1822
+ if (!await isReachable(opts.config, opts.deviceSerial)) return false;
1823
+ if (!opts.untilPidOf) return true;
1824
+ return await findRunningProcess({ config: opts.config, bundle: opts.untilPidOf, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
1825
+ },
1826
+ timeoutMs,
1827
+ pollMs: 2e3,
1828
+ abortSignal: opts.abortSignal,
1829
+ onHeartbeat: (attempt, remaining) => logger.info(`waiting for boot (attempt ${attempt}, ${Math.floor(remaining / 1e3)}s left)`),
1830
+ timeoutMessage: `Device did not boot within ${timeoutMs}ms.`
1831
+ });
1832
+ }
1833
+ async function reboot(opts) {
1834
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
1835
+ const mode = opts.mode ?? "system";
1836
+ const timeoutMs = opts.timeoutMs ?? 18e4;
1837
+ logger.info(`Rebooting device (${mode})...`);
1838
+ try {
1839
+ if (mode === "system") {
1840
+ await paramSet(opts.config, "ohos.startup.powerctrl", "reboot", {
1841
+ deviceSerial: opts.deviceSerial,
1842
+ timeoutMs: 15e3,
1843
+ logger: opts.logger
1844
+ });
1845
+ } else {
1846
+ await shell({ config: opts.config, command: `reboot ${mode}`, deviceSerial: opts.deviceSerial, timeoutMs: 15e3 });
1847
+ }
1848
+ } catch (err) {
1849
+ logger.debug(`reboot command did not return cleanly: ${err.message}`);
1850
+ }
1851
+ if (mode !== "system" || !opts.waitForBundle) return;
1852
+ try {
1853
+ await waitForCondition({
1854
+ probe: async () => !await isReachable(opts.config, opts.deviceSerial),
1855
+ timeoutMs: Math.min(timeoutMs, 3e4),
1856
+ pollMs: 1500,
1857
+ abortSignal: opts.abortSignal,
1858
+ timeoutMessage: "device did not become unreachable"
1859
+ });
1860
+ } catch {
1861
+ }
1862
+ await waitForBundle({
1863
+ config: opts.config,
1864
+ bundle: opts.waitForBundle,
1865
+ deviceSerial: opts.deviceSerial,
1866
+ timeoutMs,
1867
+ abortSignal: opts.abortSignal,
1868
+ logger: opts.logger
1869
+ });
1870
+ }
1871
+
1872
+ // src/hdc/screen.ts
1873
+ import * as fs16 from "fs";
1874
+ import * as os9 from "os";
1875
+ import * as path15 from "path";
1876
+ import { randomBytes as randomBytes2 } from "crypto";
1877
+ function tmpName(ext) {
1878
+ return path15.join(os9.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
1879
+ }
1880
+ async function takeScreenshot(opts) {
1881
+ const remote = "/data/local/tmp/oniro_screenshot.jpeg";
1882
+ const timeoutMs = opts.timeoutMs ?? 3e4;
1883
+ const cap = await shell({ config: opts.config, command: `snapshot_display -f ${remote}`, deviceSerial: opts.deviceSerial, timeoutMs });
1884
+ ensureOk(cap, `snapshot_display -f ${remote}`);
1885
+ const local = tmpName("jpeg");
1886
+ try {
1887
+ await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
1888
+ const pixels = await fs16.promises.readFile(local);
1889
+ const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
1890
+ return { pixels, width: size.width, height: size.height };
1891
+ } finally {
1892
+ fs16.promises.unlink(local).catch(() => {
1893
+ });
1894
+ void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
1895
+ });
1896
+ }
1897
+ }
1898
+ async function captureBurst(opts) {
1899
+ const count = Math.max(1, Math.floor(opts.count));
1900
+ const remoteDir = `/data/local/tmp/oniro_burst_${randomBytes2(6).toString("hex")}`;
1901
+ const sleepSec = opts.intervalMs > 0 ? (opts.intervalMs / 1e3).toFixed(3) : "0";
1902
+ 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`;
1903
+ const cap = await shell({ config: opts.config, command: loop, deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 12e4 });
1904
+ ensureOk(cap, "snapshot_display burst");
1905
+ const frames = [];
1906
+ try {
1907
+ for (let i = 0; i < count; i++) {
1908
+ const local = tmpName(`burst_${i}.jpeg`);
1909
+ try {
1910
+ await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
1911
+ frames.push(await fs16.promises.readFile(local));
1912
+ } finally {
1913
+ fs16.promises.unlink(local).catch(() => {
1914
+ });
1915
+ }
1916
+ }
1917
+ } finally {
1918
+ void shell({ config: opts.config, command: `rm -rf ${remoteDir}`, deviceSerial: opts.deviceSerial }).catch(() => {
1919
+ });
1920
+ }
1921
+ return frames;
1922
+ }
1923
+
1924
+ // src/hdc/input.ts
1925
+ function reqNum(value, name, type) {
1926
+ if (value === void 0 || Number.isNaN(value)) {
1927
+ throw new OniroError(`send input "${type}" requires a numeric ${name}.`);
1928
+ }
1929
+ return value;
1930
+ }
1931
+ function buildInputCommand(opts) {
1932
+ switch (opts.type) {
1933
+ case "click":
1934
+ case "doubleClick":
1935
+ case "longClick":
1936
+ return `uitest uiInput ${opts.type} ${reqNum(opts.pxX, "pxX", opts.type)} ${reqNum(opts.pxY, "pxY", opts.type)}`;
1937
+ case "swipe":
1938
+ case "drag":
1939
+ case "fling": {
1940
+ 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)}`;
1941
+ return opts.speed !== void 0 ? `${base} ${opts.speed}` : base;
1942
+ }
1943
+ case "keyEvent":
1944
+ if (opts.key === void 0 || opts.key === "") throw new OniroError('send input "keyEvent" requires a key.');
1945
+ return `uitest uiInput keyEvent ${opts.key}`;
1946
+ case "inputText": {
1947
+ const t = String(opts.text ?? "").replace(/'/g, `'\\''`);
1948
+ if (opts.pxX !== void 0 && opts.pxY !== void 0) {
1949
+ return `uitest uiInput inputText ${opts.pxX} ${opts.pxY} '${t}'`;
1950
+ }
1951
+ return `uitest uiInput text '${t}'`;
1952
+ }
1953
+ }
1954
+ }
1955
+ async function sendInput(opts) {
1956
+ const cmd = buildInputCommand(opts);
1957
+ const res = await shell({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
1958
+ ensureOk(res, `hdc shell ${cmd}`);
1959
+ }
1960
+ function buildGestureCommands(waypoints) {
1961
+ if (waypoints.length < 2) {
1962
+ throw new OniroError("sendGesture requires at least two waypoints.");
1963
+ }
1964
+ const cmds = [];
1965
+ for (let i = 1; i < waypoints.length; i++) {
1966
+ const a = waypoints[i - 1];
1967
+ const b = waypoints[i];
1968
+ const dt = b.t - a.t;
1969
+ const dist = Math.hypot(b.x - a.x, b.y - a.y);
1970
+ let cmd = `uitest uiInput drag ${a.x} ${a.y} ${b.x} ${b.y}`;
1971
+ if (dt > 0 && dist > 0) cmd += ` ${Math.round(dist / (dt / 1e3))}`;
1972
+ cmds.push(cmd);
1973
+ }
1974
+ return cmds;
1975
+ }
1976
+ function buildRawTouchCommand(events) {
1977
+ if (events.length === 0) throw new OniroError("sendRawTouch requires at least one event.");
1978
+ const ops = [];
1979
+ let prev = null;
1980
+ for (const ev of events) {
1981
+ if (prev && ev.t > prev.t) ops.push(`-i ${ev.t - prev.t}`);
1982
+ if (ev.type === "down") ops.push(`-d ${ev.x} ${ev.y}`);
1983
+ else if (ev.type === "up") ops.push(`-u ${ev.x} ${ev.y}`);
1984
+ else ops.push(`-m ${prev ? `${prev.x} ${prev.y} ` : ""}${ev.x} ${ev.y}`);
1985
+ prev = ev;
1986
+ }
1987
+ return `uinput -T ${ops.join(" ")}`;
1988
+ }
1989
+ function waypointsToRawEvents(waypoints, holdStartMs = 0, holdEndMs = 0) {
1990
+ if (waypoints.length < 2) throw new OniroError("sendGesture requires at least two waypoints.");
1991
+ const first = waypoints[0];
1992
+ const events = [{ type: "down", x: first.x, y: first.y, t: 0 }];
1993
+ for (let i = 1; i < waypoints.length; i++) {
1994
+ const w = waypoints[i];
1995
+ events.push({ type: "move", x: w.x, y: w.y, t: w.t + holdStartMs });
1996
+ }
1997
+ const last = waypoints[waypoints.length - 1];
1998
+ events.push({ type: "up", x: last.x, y: last.y, t: last.t + holdStartMs + holdEndMs });
1999
+ return events;
2000
+ }
2001
+ async function sendGesture(opts) {
2002
+ const wantsHold = (opts.holdStartMs ?? 0) > 0 || (opts.holdEndMs ?? 0) > 0;
2003
+ if (wantsHold) {
2004
+ await sendRawTouch({
2005
+ config: opts.config,
2006
+ events: waypointsToRawEvents(opts.waypoints, opts.holdStartMs, opts.holdEndMs),
2007
+ deviceSerial: opts.deviceSerial,
2008
+ logger: opts.logger
2009
+ });
2010
+ return;
2011
+ }
2012
+ for (const cmd of buildGestureCommands(opts.waypoints)) {
2013
+ const res = await shell({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
2014
+ ensureOk(res, `hdc shell ${cmd}`);
2015
+ }
2016
+ }
2017
+ async function sendRawTouch(opts) {
2018
+ const cmd = buildRawTouchCommand(opts.events);
2019
+ const res = await shell({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
2020
+ ensureOk(res, `hdc shell ${cmd}`);
2021
+ }
2022
+
2023
+ // src/hdc/dump.ts
2024
+ import * as fs17 from "fs";
2025
+ import * as os10 from "os";
2026
+ import * as path16 from "path";
2027
+ import { randomBytes as randomBytes3 } from "crypto";
2028
+ function parseBounds(s) {
2029
+ const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
2030
+ if (!m) return null;
2031
+ return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
2032
+ }
2033
+ var STRUCTURAL_TYPES = /* @__PURE__ */ new Set([
2034
+ "Stack",
2035
+ "Column",
2036
+ "Row",
2037
+ "Flex",
2038
+ "__Common__",
2039
+ "Grid",
2040
+ "GridItem",
2041
+ "List",
2042
+ "ListItem",
2043
+ "Scroll",
2044
+ "Swiper",
2045
+ "SwiperContent",
2046
+ "Navigator",
2047
+ "Tabs",
2048
+ "TabContent",
2049
+ "RelativeContainer",
2050
+ "WaterFlow",
2051
+ "FlowItem"
2052
+ ]);
2053
+ var FLAG_MAP = [
2054
+ ["clickable", "click"],
2055
+ ["checkable", "check"],
2056
+ ["checked", "checked"],
2057
+ ["selected", "sel"],
2058
+ ["focused", "focus"],
2059
+ ["scrollable", "scroll"],
2060
+ ["longClickable", "longClick"]
2061
+ ];
2062
+ function pruneLayout(node, display) {
2063
+ const a = node.attributes ?? {};
2064
+ const px = parseBounds(a.bounds);
2065
+ const out = {};
2066
+ if (px && display) {
2067
+ const w = display.width || 1;
2068
+ const h = display.height || 1;
2069
+ out.b = [
2070
+ +(px[0] / w).toFixed(3),
2071
+ +(px[1] / h).toFixed(3),
2072
+ +(px[2] / w).toFixed(3),
2073
+ +(px[3] / h).toFixed(3)
2074
+ ];
2075
+ out.c = [+((px[0] + px[2]) / 2 / w).toFixed(3), +((px[1] + px[3]) / 2 / h).toFixed(3)];
2076
+ }
2077
+ if (a.type && !STRUCTURAL_TYPES.has(a.type)) out.type = a.type;
2078
+ if (a.text) out.text = a.text;
2079
+ if (a.description) out.desc = a.description;
2080
+ if (a.id) out.id = a.id;
2081
+ if (a.key && a.key !== a.id) out.key = a.key;
2082
+ if (a.bundleName) out.bundle = a.bundleName;
2083
+ for (const [attr, short] of FLAG_MAP) {
2084
+ if (a[attr] === "true") out[short] = true;
2085
+ }
2086
+ const children = [];
2087
+ for (const c of node.children ?? []) {
2088
+ const pc = pruneLayout(c, display);
2089
+ if (pc) {
2090
+ if (Array.isArray(pc)) children.push(...pc);
2091
+ else children.push(pc);
2092
+ }
2093
+ }
2094
+ const hasSignal = Boolean(
2095
+ out.text || out.desc || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
2096
+ );
2097
+ if (!hasSignal && children.length === 0) return null;
2098
+ if (!hasSignal) return children.length === 1 ? children[0] : children;
2099
+ if (children.length) out.children = children;
2100
+ return out;
2101
+ }
2102
+ async function dumpLayout(opts) {
2103
+ const timeoutMs = opts.timeoutMs ?? 3e4;
2104
+ const bundleArg = opts.bundleName ? ` -b '${opts.bundleName.replace(/'/g, `'\\''`)}'` : "";
2105
+ const dump = await shell({ config: opts.config, command: `uitest dumpLayout${bundleArg}`, deviceSerial: opts.deviceSerial, timeoutMs });
2106
+ ensureOk(dump, "uitest dumpLayout");
2107
+ const m = dump.stdout.match(/saved to:\s*(\S+)/i);
2108
+ if (!m) throw new OniroError(`Could not parse dumpLayout output:
2109
+ ${dump.stdout}`);
2110
+ const remote = m[1];
2111
+ const local = path16.join(os10.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
2112
+ try {
2113
+ await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2114
+ const parsed = JSON.parse(await fs17.promises.readFile(local, "utf8"));
2115
+ const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
2116
+ if (opts.prune === false) {
2117
+ return { display, tree: parsed };
2118
+ }
2119
+ const prunedRaw = pruneLayout(parsed, display);
2120
+ const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
2121
+ return { display, tree };
2122
+ } finally {
2123
+ fs17.promises.unlink(local).catch(() => {
2124
+ });
2125
+ void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2126
+ });
2127
+ }
2128
+ }
1440
2129
 
1441
2130
  // src/hdc/hilog.ts
1442
- import { spawn as spawn3 } from "child_process";
2131
+ import { spawn as spawn4 } from "child_process";
1443
2132
  function setHilogLevel(opts) {
1444
2133
  const logger = opts.logger ?? noopLogger;
1445
2134
  const hdc = getHdcPath(opts.config);
1446
2135
  return new Promise((resolve2, reject) => {
1447
- const child = spawn3(hdc, ["shell", "hilog", "-b", opts.level]);
2136
+ const child = spawn4(hdc, ["shell", "hilog", "-b", opts.level]);
1448
2137
  let stderr = "";
1449
2138
  child.stderr.on("data", (chunk) => {
1450
2139
  stderr += chunk.toString();
@@ -1466,11 +2155,16 @@ function setHilogLevel(opts) {
1466
2155
  }
1467
2156
  function streamHilog(opts) {
1468
2157
  const hdc = getHdcPath(opts.config);
1469
- const args = ["shell", "hilog"];
2158
+ const args = [];
2159
+ if (opts.deviceSerial) args.push("-t", opts.deviceSerial);
2160
+ args.push("shell", "hilog");
1470
2161
  if (opts.processId && opts.processId.trim() !== "") {
1471
2162
  args.push("-P", opts.processId.trim());
1472
2163
  }
1473
- return spawn3(hdc, args);
2164
+ if (opts.domain !== void 0 && `${opts.domain}` !== "") {
2165
+ args.push("-D", `${opts.domain}`);
2166
+ }
2167
+ return spawn4(hdc, args);
1474
2168
  }
1475
2169
  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+(.*)$/;
1476
2170
  function parseHilogLine(line) {
@@ -1485,30 +2179,194 @@ function parseHilogLine(line) {
1485
2179
  message: match[6]
1486
2180
  };
1487
2181
  }
2182
+ function entryText(e) {
2183
+ return `${e.tag}: ${e.message}`;
2184
+ }
2185
+ function dedupEntries(entries) {
2186
+ const out = [];
2187
+ let lastKey = "";
2188
+ for (const e of entries) {
2189
+ const key = `${e.level}|${e.tag}|${e.message}`;
2190
+ if (key === lastKey) continue;
2191
+ out.push(e);
2192
+ lastKey = key;
2193
+ }
2194
+ return out;
2195
+ }
2196
+ async function resolveProcessId(config, bundle, deviceSerial) {
2197
+ if (!bundle) return void 0;
2198
+ const proc = await findRunningProcess({ config, bundle, deviceSerial });
2199
+ return proc?.pid;
2200
+ }
2201
+ function waitForLog(opts) {
2202
+ return new Promise((resolve2, reject) => {
2203
+ const deadline = Date.now() + opts.timeoutMs;
2204
+ let settled = false;
2205
+ let child = null;
2206
+ let reconnectTimer;
2207
+ let buf = "";
2208
+ const cleanup = () => {
2209
+ clearTimeout(timer);
2210
+ if (reconnectTimer) clearTimeout(reconnectTimer);
2211
+ opts.abortSignal?.removeEventListener("abort", onAbort);
2212
+ if (child) {
2213
+ try {
2214
+ child.kill("SIGKILL");
2215
+ } catch {
2216
+ }
2217
+ child = null;
2218
+ }
2219
+ };
2220
+ const finish = (entry) => {
2221
+ if (settled) return;
2222
+ settled = true;
2223
+ cleanup();
2224
+ resolve2(entry);
2225
+ };
2226
+ const fail = (err) => {
2227
+ if (settled) return;
2228
+ settled = true;
2229
+ cleanup();
2230
+ reject(err);
2231
+ };
2232
+ const onAbort = () => fail(new CancelledError("waitForLog cancelled."));
2233
+ const timer = setTimeout(
2234
+ () => fail(new OniroError(`No log line matched ${String(opts.pattern)} within ${opts.timeoutMs}ms.`)),
2235
+ opts.timeoutMs
2236
+ );
2237
+ opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
2238
+ const spawnStream = async () => {
2239
+ if (settled) return;
2240
+ const processId = await resolveProcessId(opts.config, opts.bundle, opts.deviceSerial);
2241
+ if (settled) return;
2242
+ const stream = streamHilog({ config: opts.config, processId, domain: opts.domain, deviceSerial: opts.deviceSerial });
2243
+ child = stream;
2244
+ stream.stdout.on("data", (d) => {
2245
+ buf += d.toString();
2246
+ let idx;
2247
+ while ((idx = buf.indexOf("\n")) >= 0) {
2248
+ const line = buf.slice(0, idx);
2249
+ buf = buf.slice(idx + 1);
2250
+ const entry = parseHilogLine(line);
2251
+ if (entry && opts.pattern.test(entryText(entry))) {
2252
+ finish(entry);
2253
+ return;
2254
+ }
2255
+ }
2256
+ });
2257
+ const onEnd = () => {
2258
+ if (settled || child !== stream) return;
2259
+ child = null;
2260
+ if (Date.now() < deadline) {
2261
+ reconnectTimer = setTimeout(() => void spawnStream(), 1e3);
2262
+ }
2263
+ };
2264
+ stream.once("close", onEnd);
2265
+ stream.once("error", onEnd);
2266
+ };
2267
+ void spawnStream();
2268
+ });
2269
+ }
2270
+ function watchLog(opts) {
2271
+ return new Promise((resolve2, reject) => {
2272
+ const matches = [];
2273
+ let settled = false;
2274
+ let child = null;
2275
+ let buf = "";
2276
+ const cleanup = () => {
2277
+ clearTimeout(timer);
2278
+ opts.abortSignal?.removeEventListener("abort", onAbort);
2279
+ if (child) {
2280
+ try {
2281
+ child.kill("SIGKILL");
2282
+ } catch {
2283
+ }
2284
+ child = null;
2285
+ }
2286
+ };
2287
+ const done = () => {
2288
+ if (settled) return;
2289
+ settled = true;
2290
+ cleanup();
2291
+ resolve2(opts.dedup === false ? matches : dedupEntries(matches));
2292
+ };
2293
+ const onAbort = () => {
2294
+ if (settled) return;
2295
+ settled = true;
2296
+ cleanup();
2297
+ reject(new CancelledError("watchLog cancelled."));
2298
+ };
2299
+ const timer = setTimeout(done, opts.durationMs);
2300
+ opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
2301
+ void (async () => {
2302
+ const processId = await resolveProcessId(opts.config, opts.bundle, opts.deviceSerial);
2303
+ if (settled) return;
2304
+ const stream = streamHilog({ config: opts.config, processId, domain: opts.domain, deviceSerial: opts.deviceSerial });
2305
+ child = stream;
2306
+ stream.stdout.on("data", (d) => {
2307
+ buf += d.toString();
2308
+ let idx;
2309
+ while ((idx = buf.indexOf("\n")) >= 0) {
2310
+ const line = buf.slice(0, idx);
2311
+ buf = buf.slice(idx + 1);
2312
+ const entry = parseHilogLine(line);
2313
+ if (entry && opts.pattern.test(entryText(entry))) matches.push(entry);
2314
+ }
2315
+ });
2316
+ })();
2317
+ });
2318
+ }
2319
+ async function dumpLog(opts) {
2320
+ let pipe = "hilog -x";
2321
+ if (opts.domain !== void 0 && `${opts.domain}` !== "") pipe += ` -D ${opts.domain}`;
2322
+ if (opts.bundle) {
2323
+ const proc = await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial });
2324
+ if (!proc) return [];
2325
+ pipe += ` | grep -E '\\b${proc.pid}\\b'`;
2326
+ }
2327
+ if (opts.grep) {
2328
+ const safeGrep = opts.grep.replace(/'/g, `'\\''`);
2329
+ pipe += ` | grep -E '${safeGrep}'`;
2330
+ }
2331
+ if (opts.lines && opts.lines > 0) pipe += ` | tail -n ${Math.floor(opts.lines)}`;
2332
+ const res = await shell({
2333
+ config: opts.config,
2334
+ command: pipe,
2335
+ deviceSerial: opts.deviceSerial,
2336
+ timeoutMs: opts.timeoutMs ?? 3e4,
2337
+ logger: opts.logger
2338
+ });
2339
+ return res.stdout.split("\n").map((line) => parseHilogLine(line)).filter((e) => e !== null);
2340
+ }
1488
2341
 
1489
2342
  // src/build/runHvigorw.ts
1490
- import * as fs15 from "fs";
1491
- import * as path15 from "path";
1492
- import { spawn as spawn4 } from "child_process";
2343
+ import * as fs18 from "fs";
2344
+ import * as path17 from "path";
2345
+ import { spawn as spawn5 } from "child_process";
2346
+ function buildHvigorwArgs(opts) {
2347
+ const args = [opts.task ?? "assembleHap", "--mode", "module"];
2348
+ args.push("-p", `product=${opts.product ?? "default"}`);
2349
+ if (opts.module) args.push("-p", `module=${opts.module}`);
2350
+ if (opts.buildMode) args.push("-p", `buildMode=${opts.buildMode}`);
2351
+ args.push("--stacktrace", "--no-daemon");
2352
+ if (opts.parallel === false) args.push("--no-parallel");
2353
+ if (opts.extraArgs?.length) args.push(...opts.extraArgs);
2354
+ return args;
2355
+ }
1493
2356
  function runHvigorw(opts) {
1494
2357
  const { config, projectDir } = opts;
1495
2358
  const logger = opts.logger ?? noopLogger;
1496
- if (!fs15.existsSync(path15.join(projectDir, "build-profile.json5"))) {
2359
+ if (!fs18.existsSync(path17.join(projectDir, "build-profile.json5"))) {
1497
2360
  throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
1498
2361
  }
1499
- if (!fs15.existsSync(getCmdToolsPath(config))) {
2362
+ if (!fs18.existsSync(getCmdToolsPath(config))) {
1500
2363
  throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
1501
2364
  }
1502
2365
  const hvigorw = getHvigorwPath(config, projectDir);
1503
- const args = [opts.task ?? "assembleHap", "--mode", "module"];
1504
- args.push("-p", `product=${opts.product ?? "default"}`);
1505
- if (opts.module) args.push("-p", `module=${opts.module}`);
1506
- if (opts.buildMode) args.push("-p", `buildMode=${opts.buildMode}`);
1507
- args.push("--stacktrace", "--no-parallel", "--no-daemon");
1508
- if (opts.extraArgs?.length) args.push(...opts.extraArgs);
2366
+ const args = buildHvigorwArgs(opts);
1509
2367
  if (process.platform !== "win32") {
1510
2368
  try {
1511
- fs15.chmodSync(hvigorw, 493);
2369
+ fs18.chmodSync(hvigorw, 493);
1512
2370
  } catch {
1513
2371
  }
1514
2372
  }
@@ -1517,8 +2375,25 @@ function runHvigorw(opts) {
1517
2375
  OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
1518
2376
  };
1519
2377
  return new Promise((resolve2, reject) => {
2378
+ if (opts.abortSignal?.aborted) {
2379
+ reject(new CancelledError("hvigorw build cancelled."));
2380
+ return;
2381
+ }
1520
2382
  logger.info(`[build] ${hvigorw} ${args.join(" ")}`);
1521
- const child = spawn4(hvigorw, args, { cwd: projectDir, env, shell: false });
2383
+ const child = spawn5(hvigorw, args, { cwd: projectDir, env, shell: false });
2384
+ let settled = false;
2385
+ const cleanup = () => opts.abortSignal?.removeEventListener("abort", onAbort);
2386
+ const onAbort = () => {
2387
+ if (settled) return;
2388
+ settled = true;
2389
+ cleanup();
2390
+ try {
2391
+ child.kill("SIGKILL");
2392
+ } catch {
2393
+ }
2394
+ reject(new CancelledError("hvigorw build cancelled."));
2395
+ };
2396
+ opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
1522
2397
  child.stdout.on("data", (chunk) => {
1523
2398
  const text = chunk.toString();
1524
2399
  opts.onOutput?.(text, "stdout");
@@ -1529,8 +2404,16 @@ function runHvigorw(opts) {
1529
2404
  opts.onOutput?.(text, "stderr");
1530
2405
  logger.warn(text.trimEnd());
1531
2406
  });
1532
- child.on("error", (err) => reject(new OniroError(`Failed to start hvigorw: ${err.message}`, err)));
2407
+ child.on("error", (err) => {
2408
+ if (settled) return;
2409
+ settled = true;
2410
+ cleanup();
2411
+ reject(new OniroError(`Failed to start hvigorw: ${err.message}`, err));
2412
+ });
1533
2413
  child.on("close", (code) => {
2414
+ if (settled) return;
2415
+ settled = true;
2416
+ cleanup();
1534
2417
  const exitCode = code ?? 1;
1535
2418
  if (exitCode === 0) resolve2({ exitCode });
1536
2419
  else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
@@ -1538,6 +2421,137 @@ function runHvigorw(opts) {
1538
2421
  });
1539
2422
  }
1540
2423
 
2424
+ // src/build/discoverHaps.ts
2425
+ import * as fs19 from "fs";
2426
+ import * as path18 from "path";
2427
+ function moduleOf(projectDir, hapPath) {
2428
+ const rel = path18.relative(projectDir, hapPath);
2429
+ const first = rel.split(path18.sep)[0];
2430
+ return first && first !== ".." ? first : "(root)";
2431
+ }
2432
+ async function discoverHaps(opts) {
2433
+ const { projectDir } = opts;
2434
+ const maxDepth = opts.maxDepth ?? 8;
2435
+ const result = {};
2436
+ const walk = async (dir, depth) => {
2437
+ if (depth > maxDepth) return;
2438
+ const entries = await fs19.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
2439
+ for (const e of entries) {
2440
+ const full = path18.join(dir, e.name);
2441
+ if (e.isDirectory()) {
2442
+ if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
2443
+ await walk(full, depth + 1);
2444
+ } else if (e.isFile() && e.name.endsWith(".hap")) {
2445
+ const moduleName = moduleOf(projectDir, full);
2446
+ const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
2447
+ if (e.name.includes("unsigned")) bucket.unsigned.push(full);
2448
+ else if (e.name.includes("signed")) bucket.signed.push(full);
2449
+ }
2450
+ }
2451
+ };
2452
+ await walk(projectDir, 0);
2453
+ return result;
2454
+ }
2455
+
2456
+ // src/build/ohpm.ts
2457
+ import * as fs20 from "fs";
2458
+ import * as path19 from "path";
2459
+ function runOhpm(opts) {
2460
+ return runProcess({
2461
+ command: getOhpmPath(opts.config),
2462
+ args: [...opts.args],
2463
+ cwd: opts.projectDir,
2464
+ timeoutMs: opts.timeoutMs ?? 6e5,
2465
+ abortSignal: opts.abortSignal,
2466
+ onOutput: opts.onOutput,
2467
+ logger: opts.logger
2468
+ });
2469
+ }
2470
+ async function ensureOhModules(opts) {
2471
+ if (fs20.existsSync(path19.join(opts.projectDir, "oh_modules"))) {
2472
+ return { installed: false };
2473
+ }
2474
+ const res = await runOhpm({
2475
+ config: opts.config,
2476
+ projectDir: opts.projectDir,
2477
+ args: ["install", "--all"],
2478
+ abortSignal: opts.abortSignal,
2479
+ onOutput: opts.onOutput,
2480
+ logger: opts.logger
2481
+ });
2482
+ ensureOk(res, "ohpm install --all");
2483
+ return { installed: true };
2484
+ }
2485
+
2486
+ // src/build/codelinter.ts
2487
+ var FINDING_RE = /^(.*?):(\d+):(?:\d+:)?\s*(error|warning|warn|info)\b[:\s-]*(.*)$/i;
2488
+ var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
2489
+ function parseCodelinterFindings(output) {
2490
+ const findings = [];
2491
+ for (const raw of output.split("\n")) {
2492
+ const m = FINDING_RE.exec(raw.trim());
2493
+ if (!m) continue;
2494
+ let message = m[4].trim();
2495
+ let code = "";
2496
+ const codeMatch = TRAILING_CODE_RE.exec(message);
2497
+ if (codeMatch) {
2498
+ code = codeMatch[1];
2499
+ message = message.slice(0, codeMatch.index).trim();
2500
+ }
2501
+ findings.push({ file: m[1].trim(), line: Number(m[2]), severity: m[3].toLowerCase(), code, message });
2502
+ }
2503
+ return findings;
2504
+ }
2505
+ async function runCodelinter(opts) {
2506
+ const res = await runProcess({
2507
+ command: getCmdToolsBin(opts.config, "codelinter"),
2508
+ args: opts.files?.length ? [...opts.files] : [],
2509
+ cwd: opts.projectDir,
2510
+ timeoutMs: opts.timeoutMs ?? 6e5,
2511
+ abortSignal: opts.abortSignal,
2512
+ logger: opts.logger
2513
+ });
2514
+ const raw = `${res.stdout}${res.stderr}`;
2515
+ return { code: res.code, findings: parseCodelinterFindings(raw), raw };
2516
+ }
2517
+
2518
+ // src/build/buildHap.ts
2519
+ async function buildHap(opts) {
2520
+ const logger = scopedLogger(opts.logger ?? noopLogger, "build");
2521
+ const warnings = [];
2522
+ const start = Date.now();
2523
+ if (opts.autoInstallDeps !== false) {
2524
+ const r = await ensureOhModules({
2525
+ config: opts.config,
2526
+ projectDir: opts.projectDir,
2527
+ abortSignal: opts.abortSignal,
2528
+ onOutput: opts.onOutput,
2529
+ logger: opts.logger
2530
+ });
2531
+ if (r.installed) logger.info("ran ohpm install --all (oh_modules was missing)");
2532
+ }
2533
+ const { exitCode } = await runHvigorw({
2534
+ config: opts.config,
2535
+ projectDir: opts.projectDir,
2536
+ product: opts.product,
2537
+ module: opts.module,
2538
+ buildMode: opts.mode,
2539
+ task: opts.task,
2540
+ parallel: opts.parallel,
2541
+ abortSignal: opts.abortSignal,
2542
+ onOutput: opts.onOutput,
2543
+ logger: opts.logger
2544
+ });
2545
+ const discoveredHaps = await discoverHaps({ projectDir: opts.projectDir });
2546
+ const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
2547
+ if (!anySigned) {
2548
+ warnings.push(
2549
+ "No signed HAPs found \u2014 configure signingConfigs in build-profile.json5 (unsigned HAPs are rejected by most devices)."
2550
+ );
2551
+ }
2552
+ return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
2553
+ }
2554
+
1541
2555
  // src/project/validators.ts
1542
2556
  function isValidProjectName(name) {
1543
2557
  if (!name) return false;
@@ -1549,21 +2563,21 @@ function isValidBundleName(bundleName) {
1549
2563
  }
1550
2564
 
1551
2565
  // src/project/templates.ts
1552
- import * as fs16 from "fs";
1553
- import * as path16 from "path";
2566
+ import * as fs21 from "fs";
2567
+ import * as path20 from "path";
1554
2568
  function toHumanTemplateName(folderName) {
1555
2569
  return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
1556
2570
  }
1557
2571
  function listTemplates(templateRoot) {
1558
- if (!fs16.existsSync(templateRoot)) return [];
1559
- const entries = fs16.readdirSync(templateRoot, { withFileTypes: true });
2572
+ if (!fs21.existsSync(templateRoot)) return [];
2573
+ const entries = fs21.readdirSync(templateRoot, { withFileTypes: true });
1560
2574
  return entries.filter((e) => e.isDirectory()).map((e) => {
1561
- const dir = path16.join(templateRoot, e.name);
1562
- const metaPath = path16.join(dir, "template.json");
2575
+ const dir = path20.join(templateRoot, e.name);
2576
+ const metaPath = path20.join(dir, "template.json");
1563
2577
  let meta = {};
1564
- if (fs16.existsSync(metaPath)) {
2578
+ if (fs21.existsSync(metaPath)) {
1565
2579
  try {
1566
- meta = JSON.parse(fs16.readFileSync(metaPath, "utf8"));
2580
+ meta = JSON.parse(fs21.readFileSync(metaPath, "utf8"));
1567
2581
  } catch {
1568
2582
  }
1569
2583
  }
@@ -1578,57 +2592,57 @@ function listTemplates(templateRoot) {
1578
2592
  function validateTemplateLayout(templateDir, defaultModuleName) {
1579
2593
  const required = [
1580
2594
  "build-profile.json5",
1581
- path16.join("AppScope", "app.json5"),
1582
- path16.join(defaultModuleName, "src", "main", "module.json5"),
1583
- path16.join(defaultModuleName, "oh-package.json5"),
2595
+ path20.join("AppScope", "app.json5"),
2596
+ path20.join(defaultModuleName, "src", "main", "module.json5"),
2597
+ path20.join(defaultModuleName, "oh-package.json5"),
1584
2598
  "hvigorfile.ts"
1585
2599
  ];
1586
- return required.filter((rel) => !fs16.existsSync(path16.join(templateDir, rel)));
2600
+ return required.filter((rel) => !fs21.existsSync(path20.join(templateDir, rel)));
1587
2601
  }
1588
2602
 
1589
2603
  // src/project/createScaffold.ts
1590
- import * as fs18 from "fs";
1591
- import * as path17 from "path";
2604
+ import * as fs23 from "fs";
2605
+ import * as path21 from "path";
1592
2606
 
1593
2607
  // src/project/jsonHelpers.ts
1594
- import * as fs17 from "fs";
2608
+ import * as fs22 from "fs";
1595
2609
  import JSON54 from "json5";
1596
2610
  function readJson5File(filePath) {
1597
- return JSON54.parse(fs17.readFileSync(filePath, "utf8"));
2611
+ return JSON54.parse(fs22.readFileSync(filePath, "utf8"));
1598
2612
  }
1599
2613
  function writeJson5File(filePath, value) {
1600
- fs17.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
2614
+ fs22.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
1601
2615
  }
1602
2616
  function readJsonFile(filePath) {
1603
- return JSON.parse(fs17.readFileSync(filePath, "utf8"));
2617
+ return JSON.parse(fs22.readFileSync(filePath, "utf8"));
1604
2618
  }
1605
2619
  function writeJsonFile(filePath, value) {
1606
- fs17.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
2620
+ fs22.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
1607
2621
  }
1608
2622
 
1609
2623
  // src/project/createScaffold.ts
1610
2624
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
1611
2625
  async function pathExists(p) {
1612
2626
  try {
1613
- await fs18.promises.access(p);
2627
+ await fs23.promises.access(p);
1614
2628
  return true;
1615
2629
  } catch {
1616
2630
  return false;
1617
2631
  }
1618
2632
  }
1619
2633
  async function copyDirRecursive(srcDir, destDir) {
1620
- await fs18.promises.mkdir(destDir, { recursive: true });
1621
- const entries = await fs18.promises.readdir(srcDir, { withFileTypes: true });
2634
+ await fs23.promises.mkdir(destDir, { recursive: true });
2635
+ const entries = await fs23.promises.readdir(srcDir, { withFileTypes: true });
1622
2636
  for (const entry of entries) {
1623
- const src = path17.join(srcDir, entry.name);
1624
- const dest = path17.join(destDir, entry.name);
2637
+ const src = path21.join(srcDir, entry.name);
2638
+ const dest = path21.join(destDir, entry.name);
1625
2639
  if (entry.isDirectory()) {
1626
2640
  await copyDirRecursive(src, dest);
1627
2641
  } else if (entry.isSymbolicLink()) {
1628
2642
  continue;
1629
2643
  } else if (entry.isFile()) {
1630
2644
  if (entry.name === "template.json") continue;
1631
- await fs18.promises.copyFile(src, dest);
2645
+ await fs23.promises.copyFile(src, dest);
1632
2646
  }
1633
2647
  }
1634
2648
  }
@@ -1636,9 +2650,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
1636
2650
  const stack = [projectDir];
1637
2651
  while (stack.length > 0) {
1638
2652
  const currentDir = stack.pop();
1639
- const entries = await fs18.promises.readdir(currentDir, { withFileTypes: true });
2653
+ const entries = await fs23.promises.readdir(currentDir, { withFileTypes: true });
1640
2654
  for (const entry of entries) {
1641
- const fullPath = path17.join(currentDir, entry.name);
2655
+ const fullPath = path21.join(currentDir, entry.name);
1642
2656
  if (entry.isDirectory()) {
1643
2657
  if (IGNORED_DIRS.has(entry.name)) continue;
1644
2658
  stack.push(fullPath);
@@ -1660,23 +2674,23 @@ function toJavaPropertiesPath(p) {
1660
2674
  function createOrUpdateLocalProperties(projectDir, sdkDir) {
1661
2675
  const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
1662
2676
  `;
1663
- fs18.writeFileSync(path17.join(projectDir, "local.properties"), content, "utf8");
2677
+ fs23.writeFileSync(path21.join(projectDir, "local.properties"), content, "utf8");
1664
2678
  }
1665
2679
  function renameIfExists(fromPath, toPath) {
1666
- if (fs18.existsSync(fromPath) && fromPath !== toPath) {
1667
- fs18.renameSync(fromPath, toPath);
2680
+ if (fs23.existsSync(fromPath) && fromPath !== toPath) {
2681
+ fs23.renameSync(fromPath, toPath);
1668
2682
  }
1669
2683
  }
1670
2684
  function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
1671
- const appJsonPath = path17.join(projectDir, "AppScope", "app.json5");
1672
- if (fs18.existsSync(appJsonPath)) {
2685
+ const appJsonPath = path21.join(projectDir, "AppScope", "app.json5");
2686
+ if (fs23.existsSync(appJsonPath)) {
1673
2687
  const appJson = readJson5File(appJsonPath);
1674
2688
  appJson.app = appJson.app ?? {};
1675
2689
  appJson.app.bundleName = args.bundleName;
1676
2690
  writeJson5File(appJsonPath, appJson);
1677
2691
  }
1678
- const stringsPath = path17.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
1679
- if (fs18.existsSync(stringsPath)) {
2692
+ const stringsPath = path21.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
2693
+ if (fs23.existsSync(stringsPath)) {
1680
2694
  const strings = readJsonFile(stringsPath);
1681
2695
  if (Array.isArray(strings.string)) {
1682
2696
  const appName = strings.string.find((s) => s.name === "app_name");
@@ -1684,8 +2698,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
1684
2698
  }
1685
2699
  writeJsonFile(stringsPath, strings);
1686
2700
  }
1687
- const buildProfilePath = path17.join(projectDir, "build-profile.json5");
1688
- if (fs18.existsSync(buildProfilePath)) {
2701
+ const buildProfilePath = path21.join(projectDir, "build-profile.json5");
2702
+ if (fs23.existsSync(buildProfilePath)) {
1689
2703
  const buildProfile = readJson5File(buildProfilePath);
1690
2704
  buildProfile.app = buildProfile.app ?? {};
1691
2705
  if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
@@ -1698,23 +2712,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
1698
2712
  }
1699
2713
  writeJson5File(buildProfilePath, buildProfile);
1700
2714
  }
1701
- const moduleJsonPath = path17.join(projectDir, args.moduleName, "src", "main", "module.json5");
1702
- if (fs18.existsSync(moduleJsonPath)) {
2715
+ const moduleJsonPath = path21.join(projectDir, args.moduleName, "src", "main", "module.json5");
2716
+ if (fs23.existsSync(moduleJsonPath)) {
1703
2717
  const moduleJson = readJson5File(moduleJsonPath);
1704
2718
  moduleJson.module = moduleJson.module ?? {};
1705
2719
  moduleJson.module.name = args.moduleName;
1706
2720
  writeJson5File(moduleJsonPath, moduleJson);
1707
2721
  }
1708
- const moduleOhPackagePath = path17.join(projectDir, args.moduleName, "oh-package.json5");
1709
- if (fs18.existsSync(moduleOhPackagePath)) {
2722
+ const moduleOhPackagePath = path21.join(projectDir, args.moduleName, "oh-package.json5");
2723
+ if (fs23.existsSync(moduleOhPackagePath)) {
1710
2724
  const pkg = readJson5File(moduleOhPackagePath);
1711
2725
  pkg.name = args.moduleName;
1712
2726
  writeJson5File(moduleOhPackagePath, pkg);
1713
2727
  }
1714
- const vscodeDir = path17.join(projectDir, ".vscode");
1715
- fs18.mkdirSync(vscodeDir, { recursive: true });
2728
+ const vscodeDir = path21.join(projectDir, ".vscode");
2729
+ fs23.mkdirSync(vscodeDir, { recursive: true });
1716
2730
  const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
1717
- writeJsonFile(path17.join(vscodeDir, "settings.json"), {
2731
+ writeJsonFile(path21.join(vscodeDir, "settings.json"), {
1718
2732
  "oniro.hapPath": hapPath,
1719
2733
  "files.associations": { "*.json5": "jsonc" }
1720
2734
  });
@@ -1731,7 +2745,7 @@ async function createScaffold(opts) {
1731
2745
  if (!opts.location || !await pathExists(opts.location)) {
1732
2746
  throw new OniroError(`Location does not exist: ${opts.location}`);
1733
2747
  }
1734
- const templateDir = path17.join(opts.templateRoot, opts.templateId);
2748
+ const templateDir = path21.join(opts.templateRoot, opts.templateId);
1735
2749
  if (!await pathExists(templateDir)) {
1736
2750
  throw new OniroError(`Template not found: ${templateDir}`);
1737
2751
  }
@@ -1744,17 +2758,17 @@ async function createScaffold(opts) {
1744
2758
  throw new OniroError(`Template '${opts.templateId}' is missing required files:
1745
2759
  - ${missing.join("\n- ")}`);
1746
2760
  }
1747
- const projectDir = path17.join(opts.location, opts.projectName);
2761
+ const projectDir = path21.join(opts.location, opts.projectName);
1748
2762
  if (await pathExists(projectDir)) {
1749
2763
  if (!opts.overwrite) {
1750
2764
  throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
1751
2765
  }
1752
- await fs18.promises.rm(projectDir, { recursive: true, force: true });
2766
+ await fs23.promises.rm(projectDir, { recursive: true, force: true });
1753
2767
  }
1754
2768
  logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
1755
2769
  await copyDirRecursive(templateDir, projectDir);
1756
2770
  if (moduleName !== defaultModuleName) {
1757
- renameIfExists(path17.join(projectDir, defaultModuleName), path17.join(projectDir, moduleName));
2771
+ renameIfExists(path21.join(projectDir, defaultModuleName), path21.join(projectDir, moduleName));
1758
2772
  }
1759
2773
  updateTemplateConfigs(
1760
2774
  projectDir,
@@ -1770,8 +2784,225 @@ async function createScaffold(opts) {
1770
2784
  return { projectDir };
1771
2785
  }
1772
2786
 
2787
+ // src/project/listModules.ts
2788
+ import * as fs24 from "fs";
2789
+ import * as path22 from "path";
2790
+ function listModules(opts) {
2791
+ const buildProfile = path22.join(opts.projectDir, "build-profile.json5");
2792
+ if (!fs24.existsSync(buildProfile)) {
2793
+ throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
2794
+ }
2795
+ const parsed = readJson5File(buildProfile);
2796
+ return (parsed.modules ?? []).filter((m) => Boolean(m.name)).map((m) => ({
2797
+ name: m.name,
2798
+ srcPath: m.srcPath ?? `./${m.name}`,
2799
+ targets: (m.targets ?? []).map((t) => t.name).filter((n) => Boolean(n))
2800
+ }));
2801
+ }
2802
+
2803
+ // src/project/abilities.ts
2804
+ import * as fs25 from "fs";
2805
+ import * as path23 from "path";
2806
+ function listAbilities(opts) {
2807
+ const moduleName = opts.moduleName ?? "entry";
2808
+ const moduleJsonPath = path23.join(opts.projectDir, moduleName, "src", "main", "module.json5");
2809
+ if (!fs25.existsSync(moduleJsonPath)) {
2810
+ throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
2811
+ }
2812
+ const parsed = readJson5File(moduleJsonPath);
2813
+ return (parsed.module?.abilities ?? []).filter((a) => Boolean(a.name)).map((a) => ({ name: a.name, type: a.type, visible: a.visible, srcEntry: a.srcEntry }));
2814
+ }
2815
+
2816
+ // src/project/prepareSigning.ts
2817
+ import * as fs26 from "fs";
2818
+ import * as path24 from "path";
2819
+ function hasSigningArtifacts(dir) {
2820
+ if (!fs26.existsSync(path24.join(dir, "signatures"))) return false;
2821
+ const buildProfile = path24.join(dir, "build-profile.json5");
2822
+ if (!fs26.existsSync(buildProfile)) return false;
2823
+ try {
2824
+ const parsed = readJson5File(buildProfile);
2825
+ return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
2826
+ } catch {
2827
+ return false;
2828
+ }
2829
+ }
2830
+ function prepareSigning(opts) {
2831
+ if (hasSigningArtifacts(opts.projectDir)) {
2832
+ return { source: "present" };
2833
+ }
2834
+ generateSigningConfigs({
2835
+ projectDir: opts.projectDir,
2836
+ sdkHome: getOhosBaseSdkHome(opts.config),
2837
+ apl: opts.apl,
2838
+ appFeature: opts.appFeature,
2839
+ acls: opts.acls,
2840
+ passwords: opts.passwords,
2841
+ logger: opts.logger
2842
+ });
2843
+ return { source: "fresh" };
2844
+ }
2845
+
2846
+ // src/device/hidumper.ts
2847
+ async function dumpScreen(opts) {
2848
+ const res = await shell({ config: opts.config, command: "hidumper -s 10 -a screen", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
2849
+ if (res.code !== 0) return null;
2850
+ const m = res.stdout.match(/render resolution=(\d+)x(\d+)/);
2851
+ if (!m) return null;
2852
+ return { width: Number(m[1]), height: Number(m[2]), raw: res.stdout };
2853
+ }
2854
+ async function dumpWindow(opts) {
2855
+ const res = await shell({ config: opts.config, command: "hidumper -s WindowManagerService -a -a", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 15e3, logger: opts.logger });
2856
+ return res.stdout;
2857
+ }
2858
+ async function dumpRenderService(opts) {
2859
+ const res = await shell({ config: opts.config, command: "hidumper -s RenderService -a allInfo", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 15e3, logger: opts.logger });
2860
+ return res.stdout;
2861
+ }
2862
+
2863
+ // src/device/info.ts
2864
+ var PROPS = [
2865
+ ["model", "const.product.model"],
2866
+ ["manufacturer", "const.product.manufacturer"],
2867
+ ["brand", "const.product.brand"],
2868
+ ["osFullName", "const.ohos.fullname"],
2869
+ ["osReleaseType", "const.ohos.releasetype"],
2870
+ ["sdkApiVersion", "const.ohos.apiversion"],
2871
+ ["buildVersion", "const.product.software.version"]
2872
+ ];
2873
+ async function getDeviceInfo(opts) {
2874
+ const info = { serial: opts.deviceSerial ?? "(default)", display: null };
2875
+ for (const [field, prop] of PROPS) {
2876
+ try {
2877
+ const value = await paramGet(opts.config, prop, { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
2878
+ if (value) info[field] = value;
2879
+ } catch {
2880
+ }
2881
+ }
2882
+ const screen = await dumpScreen({ config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger });
2883
+ if (screen) info.display = { width: screen.width, height: screen.height };
2884
+ return info;
2885
+ }
2886
+
2887
+ // src/install/diffHapAssets.ts
2888
+ import StreamZip2 from "node-stream-zip";
2889
+ function diffEntryNames(installed, next) {
2890
+ const installedSet = new Set(installed);
2891
+ const nextSet = new Set(next);
2892
+ return {
2893
+ addedAssetPaths: [...nextSet].filter((n) => !installedSet.has(n)).sort(),
2894
+ removedAssetPaths: [...installedSet].filter((n) => !nextSet.has(n)).sort()
2895
+ };
2896
+ }
2897
+ async function listFileEntries(hapPath) {
2898
+ const zip = new StreamZip2.async({ file: hapPath });
2899
+ try {
2900
+ const entries = await zip.entries();
2901
+ return Object.values(entries).filter((e) => !e.isDirectory).map((e) => e.name);
2902
+ } finally {
2903
+ await zip.close();
2904
+ }
2905
+ }
2906
+ async function diffHapAssets(opts) {
2907
+ const [installed, next] = await Promise.all([listFileEntries(opts.installedHap), listFileEntries(opts.newHap)]);
2908
+ return diffEntryNames(installed, next);
2909
+ }
2910
+
2911
+ // src/install/apply.ts
2912
+ import * as fs27 from "fs";
2913
+ import * as path25 from "path";
2914
+ function decideInstallMethod(s) {
2915
+ if (!s.signInfoInconsistent) return "replace";
2916
+ if (s.isSystemBundle && !s.allowUninstall) return "refuse";
2917
+ return "uninstall-install";
2918
+ }
2919
+ function decideReboot(s) {
2920
+ if (s.assetsChanged) return { reboot: true, reason: "asset-cache" };
2921
+ if (s.isSystemBundle && s.preInstallPid !== null && s.postInstallPid === s.preInstallPid) {
2922
+ return { reboot: true, reason: "pid-unchanged" };
2923
+ }
2924
+ return { reboot: false, reason: "none" };
2925
+ }
2926
+ var SIGN_INFO_INCONSISTENT = /9568332|sign.*info.*inconsistent/i;
2927
+ async function pidOf(config, bundle, deviceSerial) {
2928
+ const proc = await findRunningProcess({ config, bundle, deviceSerial });
2929
+ return proc ? Number(proc.pid) : null;
2930
+ }
2931
+ async function resolveHapPath(opts) {
2932
+ let hapPath = opts.hapPath;
2933
+ if (hapPath) {
2934
+ if (!path25.isAbsolute(hapPath) && opts.projectDir) hapPath = path25.join(opts.projectDir, hapPath);
2935
+ } else {
2936
+ if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
2937
+ const haps = await discoverHaps({ projectDir: opts.projectDir });
2938
+ if (opts.module) {
2939
+ const signed = haps[opts.module]?.signed ?? [];
2940
+ if (signed.length === 0) throw new OniroError(`No signed HAP for module ${opts.module}. Build first.`);
2941
+ hapPath = signed[0];
2942
+ } else {
2943
+ const withSigned = Object.entries(haps).filter(([, m]) => m.signed.length > 0);
2944
+ if (withSigned.length === 0) throw new OniroError("No signed HAP found. Build first.");
2945
+ if (withSigned.length > 1) {
2946
+ throw new OniroError(
2947
+ `Multiple modules have signed HAPs (${withSigned.map(([k]) => k).join(", ")}). Pass module to choose.`
2948
+ );
2949
+ }
2950
+ hapPath = withSigned[0][1].signed[0];
2951
+ }
2952
+ }
2953
+ if (!fs27.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
2954
+ return hapPath;
2955
+ }
2956
+ async function applyChanges(opts) {
2957
+ const logger = scopedLogger(opts.logger ?? noopLogger, "apply");
2958
+ const { config, bundle, deviceSerial } = opts;
2959
+ const isSystemBundle = opts.isSystemBundle ?? false;
2960
+ const allowUninstall = opts.allowUninstall ?? false;
2961
+ const hapPath = await resolveHapPath(opts);
2962
+ let assetsChanged = false;
2963
+ if (opts.installedHapPath && fs27.existsSync(opts.installedHapPath)) {
2964
+ try {
2965
+ const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
2966
+ assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
2967
+ } catch (err) {
2968
+ logger.debug(`asset diff skipped: ${err.message}`);
2969
+ }
2970
+ }
2971
+ const preInstallPid = await pidOf(config, bundle, deviceSerial);
2972
+ const replaceRes = await hdcExec({ config, args: ["install", "-r", hapPath], deviceSerial, timeoutMs: 3e5 });
2973
+ const signInfoInconsistent = SIGN_INFO_INCONSISTENT.test(`${replaceRes.stdout}${replaceRes.stderr}`);
2974
+ const decision = decideInstallMethod({ signInfoInconsistent, isSystemBundle, allowUninstall });
2975
+ let installMethod;
2976
+ if (decision === "refuse") {
2977
+ throw new OniroError(
2978
+ `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.`
2979
+ );
2980
+ }
2981
+ if (decision === "uninstall-install") {
2982
+ logger.warn(`sign-info inconsistent for ${bundle}; uninstalling then installing fresh.`);
2983
+ await uninstallApp({ config, bundle, deviceSerial });
2984
+ ensureOk(await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }), `hdc install ${hapPath}`);
2985
+ installMethod = "uninstall-install";
2986
+ } else {
2987
+ ensureOk(replaceRes, `hdc install -r ${hapPath}`);
2988
+ installMethod = "replace";
2989
+ }
2990
+ let postInstallPid = await pidOf(config, bundle, deviceSerial);
2991
+ const rebootDecision = decideReboot({ assetsChanged, isSystemBundle, preInstallPid, postInstallPid });
2992
+ let method = installMethod;
2993
+ let cacheCleared = false;
2994
+ if (rebootDecision.reboot) {
2995
+ logger.info(`rebooting (${rebootDecision.reason}) to apply changes to ${bundle}.`);
2996
+ await reboot({ config, waitForBundle: bundle, deviceSerial, abortSignal: opts.abortSignal, logger: opts.logger });
2997
+ postInstallPid = await pidOf(config, bundle, deviceSerial);
2998
+ method = "reboot";
2999
+ cacheCleared = rebootDecision.reason === "asset-cache";
3000
+ }
3001
+ return { method, preInstallPid, postInstallPid, replaced: installMethod === "replace", cacheCleared };
3002
+ }
3003
+
1773
3004
  // src/index.ts
1774
- var VERSION = "0.6.2";
3005
+ var VERSION = "0.7.0";
1775
3006
  export {
1776
3007
  ALL_SDKS,
1777
3008
  APL_VALUES,
@@ -1779,33 +3010,59 @@ export {
1779
3010
  CancelledError,
1780
3011
  ChecksumMismatchError,
1781
3012
  CmdToolsNotInstalledError,
3013
+ CommandFailedError,
1782
3014
  OHOS_URL_BASE,
1783
3015
  OniroError,
1784
3016
  SdkNotInstalledError,
1785
3017
  UnsupportedPlatformError,
1786
3018
  VERSION,
3019
+ applyChanges,
1787
3020
  attemptHdcConnection,
3021
+ buildGestureCommands,
3022
+ buildHap,
3023
+ buildHvigorwArgs,
3024
+ buildInputCommand,
3025
+ buildRawTouchCommand,
3026
+ captureBurst,
1788
3027
  consoleLogger,
1789
3028
  createMaterial,
1790
3029
  createScaffold,
3030
+ decideInstallMethod,
3031
+ decideReboot,
1791
3032
  decryptPwd,
3033
+ dedupEntries,
1792
3034
  defaultContext,
1793
3035
  defaultPaths,
3036
+ detectAaStartFailure,
1794
3037
  detectProjectSdkVersion,
1795
3038
  detectSigningConfigNames,
3039
+ diffEntryNames,
3040
+ diffHapAssets,
3041
+ discoverHaps,
1796
3042
  downloadAndInstallSdk,
1797
3043
  downloadFile,
3044
+ dumpLayout,
3045
+ dumpLog,
3046
+ dumpRenderService,
3047
+ dumpScreen,
3048
+ dumpWindow,
1798
3049
  encryptPwd,
3050
+ ensureOhModules,
3051
+ ensureOk,
1799
3052
  extractTarball,
1800
3053
  extractZipWithProgress,
1801
3054
  findAppProcessId,
1802
3055
  findCmdToolsSourceDir,
3056
+ findRunningProcess,
3057
+ forceStop,
1803
3058
  generateSigningConfigs,
1804
3059
  getBundleName,
1805
3060
  getCmdToolsBin,
1806
3061
  getCmdToolsDownloadUrl,
1807
3062
  getCmdToolsPath,
1808
3063
  getCmdToolsStatus,
3064
+ getDeviceInfo,
3065
+ getDisplaySize,
1809
3066
  getEmulatorDir,
1810
3067
  getHdcPath,
1811
3068
  getHvigorwPath,
@@ -1813,10 +3070,12 @@ export {
1813
3070
  getKey,
1814
3071
  getMainAbility,
1815
3072
  getOhosBaseSdkHome,
3073
+ getOhpmPath,
1816
3074
  getOsFolder,
1817
3075
  getSdkFilename,
1818
3076
  getSdkRootDir,
1819
3077
  getSupportedSdksForUi,
3078
+ hdcExec,
1820
3079
  installApp,
1821
3080
  installCmdTools,
1822
3081
  installEmulator,
@@ -1825,26 +3084,55 @@ export {
1825
3084
  isValidBundleName,
1826
3085
  isValidProjectName,
1827
3086
  launchApp,
3087
+ listAbilities,
3088
+ listDevices,
3089
+ listModules,
1828
3090
  listRunningProcesses,
1829
3091
  listTemplates,
1830
3092
  nonInteractivePrompter,
1831
3093
  noopLogger,
1832
3094
  noopProgress,
3095
+ paramGet,
3096
+ paramSet,
3097
+ parseBounds,
3098
+ parseCodelinterFindings,
3099
+ parseDeviceList,
1833
3100
  parseHilogLine,
1834
3101
  pickSigningKind,
3102
+ prepareSigning,
3103
+ pruneLayout,
1835
3104
  readJson5File,
1836
3105
  readJsonFile,
3106
+ reboot,
3107
+ recvFile,
1837
3108
  removeCmdTools,
1838
3109
  removeEmulator,
1839
3110
  removeSdk,
3111
+ runCodelinter,
1840
3112
  runHvigorw,
3113
+ runOhpm,
3114
+ runProcess,
3115
+ scopedLogger,
3116
+ selectDevice,
3117
+ sendFile,
3118
+ sendGesture,
3119
+ sendInput,
3120
+ sendRawTouch,
1841
3121
  setHilogLevel,
3122
+ shell,
1842
3123
  startEmulator,
1843
3124
  staticConfig,
1844
3125
  stopEmulator,
1845
3126
  streamHilog,
3127
+ takeScreenshot,
3128
+ uninstallApp,
1846
3129
  validateTemplateLayout,
1847
3130
  verifySha256,
3131
+ waitForBoot,
3132
+ waitForBundle,
3133
+ waitForCondition,
3134
+ waitForLog,
3135
+ watchLog,
1848
3136
  writeJson5File,
1849
3137
  writeJsonFile
1850
3138
  };