@oniroproject/core 0.6.1 → 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,41 +863,120 @@ 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";
870
+
871
+ // src/sign/applicationCert.ts
872
+ var OPEN_HARMONY_APPLICATION_CERT = `-----BEGIN CERTIFICATE-----
873
+ MIICRDCCAcmgAwIBAgIED+E4izAMBggqhkjOPQQDAwUAMGgxCzAJBgNVBAYTAkNO
874
+ MRQwEgYDVQQKEwtPcGVuSGFybW9ueTEZMBcGA1UECxMQT3Blbkhhcm1vbnkgVGVh
875
+ bTEoMCYGA1UEAxMfT3Blbkhhcm1vbnkgQXBwbGljYXRpb24gUm9vdCBDQTAeFw0y
876
+ MTAyMDIxMjE0MThaFw00OTEyMzExMjE0MThaMGgxCzAJBgNVBAYTAkNOMRQwEgYD
877
+ VQQKEwtPcGVuSGFybW9ueTEZMBcGA1UECxMQT3Blbkhhcm1vbnkgVGVhbTEoMCYG
878
+ A1UEAxMfT3Blbkhhcm1vbnkgQXBwbGljYXRpb24gUm9vdCBDQTB2MBAGByqGSM49
879
+ AgEGBSuBBAAiA2IABE023XmRaw2DnO8NSsb+KG/uY0FtS3u5LQucdr3qWVnRW5ui
880
+ QIL6ttNZBEeLTUeYcJZCpayg9Llf+1SmDA7dY4iP2EcRo4UN3rilovtfFfsmH4ty
881
+ 3SApHVFzWUl+NwdH8KNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
882
+ AQYwHQYDVR0OBBYEFBc6EKGrGXzlAE+s0Zgnsphadw7NMAwGCCqGSM49BAMDBQAD
883
+ ZwAwZAIwd1p3JzHN93eoPped1li0j64npgqNzwy4OrkehYAqNXpcpaEcLZ7UxW8E
884
+ I2lZJ3SbAjAkqySHb12sIwdSFKSN9KCMMEo/eUT5dUXlcKR2nZz0MJdxT5F51qcX
885
+ 1CumzkcYhgU=
886
+ -----END CERTIFICATE-----
887
+ -----BEGIN CERTIFICATE-----
888
+ MIICYTCCAeWgAwIBAgIEHmXAPTAMBggqhkjOPQQDAwUAMGgxCzAJBgNVBAYTAkNO
889
+ MRQwEgYDVQQKEwtPcGVuSGFybW9ueTEZMBcGA1UECxMQT3Blbkhhcm1vbnkgVGVh
890
+ bTEoMCYGA1UEAxMfT3Blbkhhcm1vbnkgQXBwbGljYXRpb24gUm9vdCBDQTAeFw0y
891
+ MTAyMDIxMjE1MzJaFw00OTEyMzExMjE1MzJaMGMxCzAJBgNVBAYTAkNOMRQwEgYD
892
+ VQQKEwtPcGVuSGFybW9ueTEZMBcGA1UECxMQT3Blbkhhcm1vbnkgVGVhbTEjMCEG
893
+ A1UEAxMaT3Blbkhhcm1vbnkgQXBwbGljYXRpb24gQ0EwdjAQBgcqhkjOPQIBBgUr
894
+ gQQAIgNiAAQhnu7Hna8XNa2KyqRf5+lBJScE4xqf89N0g0OuqAb2re8nGsvWkw26
895
+ uDekfnBYicd+G3Cydqa2zFIwV7Talyg2ULW3r8KbGpyl84mJEPPRmCGJ+H9gtCsf
896
+ +OrJ4Y76LVWjYzBhMB8GA1UdIwQYMBaAFBc6EKGrGXzlAE+s0Zgnsphadw7NMA8G
897
+ A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTbhrciFtUL
898
+ oUu33SV7ufEFfaItRzAMBggqhkjOPQQDAwUAA2gAMGUCMG3cXjiDmXTvf7D4Omhf
899
+ qcc2nuO+EMfWE+N9ZhBP5UhV34mAGWi3SfLU6rcV0urWEQIxAMYIb3epOnKhUrcm
900
+ Lfu1WKzFlpYQwmw73RaCHP2I3k6NcuWOYeNwWXSNZ8o0nzvaLg==
901
+ -----END CERTIFICATE-----
902
+ -----BEGIN CERTIFICATE-----
903
+ MIICMzCCAbegAwIBAgIEaOC/zDAMBggqhkjOPQQDAwUAMGMxCzAJBgNVBAYTAkNO
904
+ MRQwEgYDVQQKEwtPcGVuSGFybW9ueTEZMBcGA1UECxMQT3Blbkhhcm1vbnkgVGVh
905
+ bTEjMCEGA1UEAxMaT3Blbkhhcm1vbnkgQXBwbGljYXRpb24gQ0EwHhcNMjEwMjAy
906
+ MTIxOTMxWhcNNDkxMjMxMTIxOTMxWjBoMQswCQYDVQQGEwJDTjEUMBIGA1UEChML
907
+ T3Blbkhhcm1vbnkxGTAXBgNVBAsTEE9wZW5IYXJtb255IFRlYW0xKDAmBgNVBAMT
908
+ H09wZW5IYXJtb255IEFwcGxpY2F0aW9uIFJlbGVhc2UwWTATBgcqhkjOPQIBBggq
909
+ hkjOPQMBBwNCAATbYOCQQpW5fdkYHN45v0X3AHax12jPBdEDosFRIZ1eXmxOYzSG
910
+ JwMfsHhUU90E8lI0TXYZnNmgM1sovubeQqATo1IwUDAfBgNVHSMEGDAWgBTbhrci
911
+ FtULoUu33SV7ufEFfaItRzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFPtxruhl
912
+ cRBQsJdwcZqLu9oNUVgaMAwGCCqGSM49BAMDBQADaAAwZQIxAJta0PQ2p4DIu/ps
913
+ LMdLCDgQ5UH1l0B4PGhBlMgdi2zf8nk9spazEQI/0XNwpft8QAIwHSuA2WelVi/o
914
+ zAlF08DnbJrOOtOnQq5wHOPlDYB4OtUzOYJk9scotrEnJxJzGsh/
915
+ -----END CERTIFICATE-----
916
+ `;
917
+
918
+ // src/sign/generateSigningConfigs.ts
825
919
  var APL_VALUES = ["normal", "system_basic", "system_core"];
826
920
  var APP_FEATURE_VALUES = ["hos_normal_app", "hos_system_app"];
827
- function copyFilesToProject(projectDir, paths, logger) {
921
+ var DEFAULT_PASSWORDS = { store: "123456", key: "123456" };
922
+ function pickSigningKind(apl) {
923
+ return apl === "normal" ? "profile-release" : "application-release";
924
+ }
925
+ var SIGNING_KIND_CONFIG = {
926
+ "profile-release": {
927
+ hapKeyAlias: "openharmony application profile release",
928
+ certBasename: "OpenHarmonyProfileRelease.pem"
929
+ },
930
+ "application-release": {
931
+ hapKeyAlias: "OpenHarmony Application Release",
932
+ certBasename: "OpenHarmonyApplication.cer"
933
+ }
934
+ };
935
+ function copyFilesToProject(projectDir, paths, kind, logger, applicationCertPath) {
828
936
  logger.info("[sign] Copying signing material into project...");
829
937
  const signaturesDir = path10.join(projectDir, "signatures");
830
- fs10.mkdirSync(signaturesDir, { recursive: true });
831
- fs10.copyFileSync(paths.keystore, path10.join(signaturesDir, "OpenHarmony.p12"));
832
- fs10.copyFileSync(paths.profileCert, path10.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
833
- 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"));
942
+ if (kind === "application-release") {
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
+ }
949
+ }
834
950
  }
835
951
  function resolveAppFeature(apl, override) {
836
952
  if (override) return override;
837
953
  return apl === "normal" ? "hos_normal_app" : "hos_system_app";
838
954
  }
839
- function modifyProfileTemplate(projectDir, apl, appFeature, logger) {
840
- logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature})...`);
955
+ function extractLeafCertificate(chainPath) {
956
+ const certContent = fs11.readFileSync(chainPath, "utf-8");
957
+ const parts = certContent.split("-----END CERTIFICATE-----");
958
+ if (parts.length < 3) {
959
+ throw new OniroError(`${chainPath} does not contain enough certificates.`);
960
+ }
961
+ return parts[2].trim() + "\n-----END CERTIFICATE-----\n";
962
+ }
963
+ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}) {
964
+ const kind = options.kind ?? pickSigningKind(apl);
965
+ logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature}, kind=${kind})...`);
841
966
  const appJsonPath = path10.join(projectDir, "AppScope/app.json5");
842
967
  const profileTemplatePath = path10.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
843
- const profileCertFilePath = path10.join(projectDir, "signatures/OpenHarmonyProfileRelease.pem");
844
- if (!fs10.existsSync(appJsonPath)) {
968
+ if (!fs11.existsSync(appJsonPath)) {
845
969
  throw new OniroError(`${appJsonPath} does not exist.`);
846
970
  }
847
971
  let appJson;
848
972
  try {
849
- appJson = JSON52.parse(fs10.readFileSync(appJsonPath, "utf-8"));
973
+ appJson = JSON52.parse(fs11.readFileSync(appJsonPath, "utf-8"));
850
974
  } catch (e) {
851
975
  throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
852
976
  }
853
977
  let profileTemplate;
854
978
  try {
855
- profileTemplate = JSON.parse(fs10.readFileSync(profileTemplatePath, "utf-8"));
979
+ profileTemplate = JSON.parse(fs11.readFileSync(profileTemplatePath, "utf-8"));
856
980
  } catch (e) {
857
981
  throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
858
982
  }
@@ -865,16 +989,19 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger) {
865
989
  profileTemplate["bundle-info"]["bundle-name"] = appJson.app.bundleName;
866
990
  profileTemplate["bundle-info"]["apl"] = apl;
867
991
  profileTemplate["bundle-info"]["app-feature"] = appFeature;
868
- const certContent = fs10.readFileSync(profileCertFilePath, "utf-8");
869
- const certs = certContent.split("-----END CERTIFICATE-----");
870
- if (certs.length < 3) {
871
- throw new OniroError(`${profileCertFilePath} does not contain enough certificates.`);
992
+ const distCertChainPath = path10.join(
993
+ projectDir,
994
+ "signatures",
995
+ SIGNING_KIND_CONFIG[kind].certBasename
996
+ );
997
+ profileTemplate["bundle-info"]["distribution-certificate"] = extractLeafCertificate(distCertChainPath);
998
+ if (options.acls !== void 0) {
999
+ profileTemplate.acls = profileTemplate.acls ?? {};
1000
+ profileTemplate.acls["allowed-acls"] = options.acls;
872
1001
  }
873
- const thirdCert = certs[2].trim() + "\n-----END CERTIFICATE-----\n";
874
- profileTemplate["bundle-info"]["distribution-certificate"] = thirdCert;
875
- fs10.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
1002
+ fs11.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
876
1003
  }
877
- function generateP7bFile(projectDir, paths, logger) {
1004
+ function generateP7bFile(projectDir, paths, logger, passwords) {
878
1005
  logger.info("[sign] Generating P7b profile via hap-sign-tool...");
879
1006
  const signaturesDir = path10.join(projectDir, "signatures");
880
1007
  const profileTemplatePath = path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
@@ -898,9 +1025,9 @@ function generateP7bFile(projectDir, paths, logger) {
898
1025
  "-outFile",
899
1026
  outputProfilePath,
900
1027
  "-keyPwd",
901
- "123456",
1028
+ passwords.key,
902
1029
  "-keystorePwd",
903
- "123456"
1030
+ passwords.store
904
1031
  ];
905
1032
  try {
906
1033
  execFileSync2("java", args, { stdio: "inherit" });
@@ -911,42 +1038,63 @@ function generateP7bFile(projectDir, paths, logger) {
911
1038
  );
912
1039
  }
913
1040
  }
914
- function updateBuildProfile(projectDir, logger) {
1041
+ function detectSigningConfigNames(projectDir) {
1042
+ const buildProfilePath = path10.join(projectDir, "build-profile.json5");
1043
+ if (!fs11.existsSync(buildProfilePath)) return ["default"];
1044
+ let parsed;
1045
+ try {
1046
+ parsed = JSON52.parse(fs11.readFileSync(buildProfilePath, "utf-8"));
1047
+ } catch {
1048
+ return ["default"];
1049
+ }
1050
+ const products = parsed.app?.products;
1051
+ if (!Array.isArray(products) || products.length === 0) return ["default"];
1052
+ const names = [];
1053
+ for (const product of products) {
1054
+ const name = product.signingConfig;
1055
+ if (typeof name === "string" && name.length > 0 && !names.includes(name)) {
1056
+ names.push(name);
1057
+ }
1058
+ }
1059
+ return names.length > 0 ? names : ["default"];
1060
+ }
1061
+ function updateBuildProfile(projectDir, kind, logger, passwords) {
915
1062
  logger.info("[sign] Writing signing configs into build-profile.json5...");
916
1063
  const materialDir = path10.join(projectDir, "signatures", "material");
917
1064
  const buildProfilePath = path10.join(projectDir, "build-profile.json5");
918
- const encryptedStorePassword = encryptPwd("123456", materialDir);
919
- const encryptedKeyPassword = encryptPwd("123456", materialDir);
1065
+ const encryptedStorePassword = encryptPwd(passwords.store, materialDir);
1066
+ const encryptedKeyPassword = encryptPwd(passwords.key, materialDir);
920
1067
  let buildProfile = { app: {} };
921
- if (fs10.existsSync(buildProfilePath)) {
1068
+ if (fs11.existsSync(buildProfilePath)) {
922
1069
  try {
923
- buildProfile = JSON52.parse(fs10.readFileSync(buildProfilePath, "utf-8"));
1070
+ buildProfile = JSON52.parse(fs11.readFileSync(buildProfilePath, "utf-8"));
924
1071
  } catch (e) {
925
1072
  throw new OniroError(`Error parsing ${buildProfilePath}: ${e.message}`, e);
926
1073
  }
927
1074
  }
1075
+ const { hapKeyAlias, certBasename } = SIGNING_KIND_CONFIG[kind];
1076
+ const configNames = detectSigningConfigNames(projectDir);
1077
+ logger.info(`[sign] Writing signingConfigs entries: ${configNames.join(", ")}`);
928
1078
  buildProfile.app = buildProfile.app ?? {};
929
- buildProfile.app.signingConfigs = [
930
- {
931
- name: "default",
932
- material: {
933
- certpath: "./signatures/OpenHarmonyProfileRelease.pem",
934
- storePassword: encryptedStorePassword,
935
- keyAlias: "openharmony application profile release",
936
- keyPassword: encryptedKeyPassword,
937
- profile: "./signatures/app1-profile.p7b",
938
- signAlg: "SHA256withECDSA",
939
- storeFile: "./signatures/OpenHarmony.p12"
940
- }
1079
+ buildProfile.app.signingConfigs = configNames.map((name) => ({
1080
+ name,
1081
+ material: {
1082
+ certpath: `./signatures/${certBasename}`,
1083
+ storePassword: encryptedStorePassword,
1084
+ keyAlias: hapKeyAlias,
1085
+ keyPassword: encryptedKeyPassword,
1086
+ profile: "./signatures/app1-profile.p7b",
1087
+ signAlg: "SHA256withECDSA",
1088
+ storeFile: "./signatures/OpenHarmony.p12"
941
1089
  }
942
- ];
943
- fs10.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1090
+ }));
1091
+ fs11.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
944
1092
  }
945
1093
  function prepareMaterialDirectory(projectDir, logger) {
946
1094
  logger.info("[sign] Generating fresh signing material...");
947
1095
  const materialDir = path10.join(projectDir, "signatures", "material");
948
- if (fs10.existsSync(materialDir)) {
949
- fs10.rmSync(materialDir, { recursive: true, force: true });
1096
+ if (fs11.existsSync(materialDir)) {
1097
+ fs11.rmSync(materialDir, { recursive: true, force: true });
950
1098
  }
951
1099
  createMaterial(materialDir);
952
1100
  }
@@ -970,7 +1118,7 @@ function generateSigningConfigs(options) {
970
1118
  );
971
1119
  }
972
1120
  const sdkPath = path10.join(sdkHome, String(sdkVersion));
973
- if (!fs10.existsSync(sdkPath)) {
1121
+ if (!fs11.existsSync(sdkPath)) {
974
1122
  throw new OniroError(`SDK path does not exist: ${sdkPath}`);
975
1123
  }
976
1124
  const paths = {
@@ -979,17 +1127,19 @@ function generateSigningConfigs(options) {
979
1127
  profileCert: path10.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
980
1128
  unsignedProfileTemplate: path10.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
981
1129
  };
1130
+ const kind = pickSigningKind(apl);
1131
+ const passwords = options.passwords ?? DEFAULT_PASSWORDS;
982
1132
  logger.info("[sign] Starting signing configuration generation...");
983
- copyFilesToProject(projectDir, paths, logger);
984
- modifyProfileTemplate(projectDir, apl, appFeature, logger);
985
- generateP7bFile(projectDir, paths, logger);
1133
+ copyFilesToProject(projectDir, paths, kind, logger, options.applicationCertPath);
1134
+ modifyProfileTemplate(projectDir, apl, appFeature, logger, { kind, acls: options.acls });
1135
+ generateP7bFile(projectDir, paths, logger, passwords);
986
1136
  prepareMaterialDirectory(projectDir, logger);
987
- updateBuildProfile(projectDir, logger);
1137
+ updateBuildProfile(projectDir, kind, logger, passwords);
988
1138
  logger.info("[sign] Signing configuration generated successfully.");
989
1139
  }
990
1140
 
991
1141
  // src/emulator/install.ts
992
- import * as fs11 from "fs";
1142
+ import * as fs12 from "fs";
993
1143
  import * as os7 from "os";
994
1144
  import * as path11 from "path";
995
1145
  async function installEmulator(opts) {
@@ -997,63 +1147,142 @@ async function installEmulator(opts) {
997
1147
  const logger = opts.logger ?? noopLogger;
998
1148
  const url = config.get("emulatorUrl", defaultPaths.emulatorUrl);
999
1149
  const emulatorDir = getEmulatorDir(config);
1000
- const tmpDir = fs11.mkdtempSync(path11.join(os7.tmpdir(), "oniro-emulator-"));
1150
+ const tmpDir = fs12.mkdtempSync(path11.join(os7.tmpdir(), "oniro-emulator-"));
1001
1151
  const tmpZip = path11.join(tmpDir, "oniro_emulator.zip");
1002
1152
  try {
1003
- fs11.mkdirSync(emulatorDir, { recursive: true });
1153
+ fs12.mkdirSync(emulatorDir, { recursive: true });
1004
1154
  progress?.report({ message: "Downloading emulator...", increment: 0 });
1005
1155
  await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50 });
1006
1156
  progress?.report({ message: "Extracting emulator...", increment: 0 });
1007
1157
  await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
1008
1158
  const runSh = path11.join(emulatorDir, "images", "run.sh");
1009
- if (fs11.existsSync(runSh)) {
1010
- fs11.chmodSync(runSh, 493);
1159
+ if (fs12.existsSync(runSh)) {
1160
+ fs12.chmodSync(runSh, 493);
1011
1161
  }
1012
1162
  progress?.report({ message: "Finalizing installation...", increment: 5 });
1013
1163
  } finally {
1014
- fs11.rmSync(tmpDir, { recursive: true, force: true });
1164
+ fs12.rmSync(tmpDir, { recursive: true, force: true });
1015
1165
  }
1016
1166
  }
1017
1167
  function isEmulatorInstalled(config) {
1018
- 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"));
1019
1170
  }
1020
1171
  function removeEmulator(config) {
1021
1172
  const emulatorDir = getEmulatorDir(config);
1022
- if (fs11.existsSync(emulatorDir)) {
1023
- fs11.rmSync(emulatorDir, { recursive: true, force: true });
1173
+ if (fs12.existsSync(emulatorDir)) {
1174
+ fs12.rmSync(emulatorDir, { recursive: true, force: true });
1024
1175
  }
1025
1176
  }
1026
1177
 
1027
1178
  // src/emulator/lifecycle.ts
1028
- import * as fs12 from "fs";
1179
+ import * as fs13 from "fs";
1029
1180
  import * as os8 from "os";
1030
1181
  import * as path12 from "path";
1031
- import { exec, spawn } from "child_process";
1032
- var PID_FILE = path12.join(os8.tmpdir(), "oniro_emulator.pid");
1033
- 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;
1034
1190
  return new Promise((resolve2, reject) => {
1035
- exec(cmd, { cwd }, (error, stdout, stderr) => {
1036
- if (stdout?.trim()) logger.info(`[emulator] ${stdout.trim()}`);
1037
- if (stderr?.trim()) logger.warn(`[emulator] ${stderr.trim()}`);
1038
- if (error) {
1039
- logger.error(`[emulator] ${error.message}`);
1040
- reject(error);
1041
- } else {
1042
- 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 {
1043
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");
1044
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;
1250
+ }
1251
+ resolve2({ code: code ?? -1, stdout, stderr });
1252
+ });
1253
+ });
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
1045
1265
  });
1046
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");
1047
1280
  async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
1048
- const hdc = getHdcPath(config);
1049
1281
  try {
1050
- await execPromise(`"${hdc}" start -r`, void 0, logger);
1051
- await execPromise(`"${hdc}" tconn ${address}`, void 0, logger);
1052
- return await new Promise((resolve2) => {
1053
- exec(`"${hdc}" list targets`, (_error, stdout) => {
1054
- resolve2(stdout?.includes(address) ?? false);
1055
- });
1056
- });
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);
1057
1286
  } catch (err) {
1058
1287
  logger.warn(`hdc connection attempt failed: ${err.message}`);
1059
1288
  return false;
@@ -1066,18 +1295,18 @@ function resolveLauncher(imagesPath, headless, connect) {
1066
1295
  if (headless) extra.push("--headless");
1067
1296
  if (connect) extra.push("--connect", connect);
1068
1297
  if (os8.platform() === "win32") {
1069
- if (fs12.existsSync(runBat)) {
1298
+ if (fs13.existsSync(runBat)) {
1070
1299
  return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
1071
1300
  }
1072
- if (fs12.existsSync(runSh)) {
1301
+ if (fs13.existsSync(runSh)) {
1073
1302
  return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1074
1303
  }
1075
1304
  throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
1076
1305
  }
1077
- if (fs12.existsSync(runSh)) {
1306
+ if (fs13.existsSync(runSh)) {
1078
1307
  return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1079
1308
  }
1080
- if (fs12.existsSync(runBat)) {
1309
+ if (fs13.existsSync(runBat)) {
1081
1310
  return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
1082
1311
  }
1083
1312
  throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
@@ -1085,9 +1314,9 @@ function resolveLauncher(imagesPath, headless, connect) {
1085
1314
  async function startEmulator(opts) {
1086
1315
  const { config } = opts;
1087
1316
  const logger = opts.logger ?? noopLogger;
1088
- if (os8.platform() !== "win32" && fs12.existsSync(PID_FILE)) {
1317
+ if (os8.platform() !== "win32" && fs13.existsSync(PID_FILE)) {
1089
1318
  try {
1090
- const pid = Number(fs12.readFileSync(PID_FILE, "utf8").trim());
1319
+ const pid = Number(fs13.readFileSync(PID_FILE, "utf8").trim());
1091
1320
  if (Number.isFinite(pid) && pid > 0) {
1092
1321
  try {
1093
1322
  process.kill(pid, 0);
@@ -1104,27 +1333,27 @@ async function startEmulator(opts) {
1104
1333
  }
1105
1334
  }
1106
1335
  const imagesPath = path12.join(getEmulatorDir(config), "images");
1107
- if (!fs12.existsSync(imagesPath)) {
1336
+ if (!fs13.existsSync(imagesPath)) {
1108
1337
  throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
1109
1338
  }
1110
1339
  const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
1111
1340
  const logPath = opts.logFile ?? (os8.platform() === "win32" ? "NUL" : "/dev/null");
1112
- const logFd = fs12.openSync(logPath, "a");
1341
+ const logFd = fs13.openSync(logPath, "a");
1113
1342
  let child;
1114
1343
  try {
1115
- child = spawn(launcher.command, [...launcher.args], {
1344
+ child = spawn2(launcher.command, [...launcher.args], {
1116
1345
  cwd: launcher.cwd,
1117
1346
  detached: true,
1118
1347
  stdio: ["ignore", logFd, logFd],
1119
1348
  windowsHide: true
1120
1349
  });
1121
1350
  } finally {
1122
- fs12.closeSync(logFd);
1351
+ fs13.closeSync(logFd);
1123
1352
  }
1124
1353
  if (!child.pid) {
1125
1354
  throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
1126
1355
  }
1127
- fs12.writeFileSync(PID_FILE, String(child.pid));
1356
+ fs13.writeFileSync(PID_FILE, String(child.pid));
1128
1357
  child.unref();
1129
1358
  logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
1130
1359
  const earlyExit = new Promise((resolve2) => {
@@ -1171,24 +1400,24 @@ ${tail}` : ""}`);
1171
1400
  }
1172
1401
  function readTail(filePath, bytes) {
1173
1402
  try {
1174
- const stat = fs12.statSync(filePath);
1403
+ const stat = fs13.statSync(filePath);
1175
1404
  const start = Math.max(0, stat.size - bytes);
1176
- const fd = fs12.openSync(filePath, "r");
1405
+ const fd = fs13.openSync(filePath, "r");
1177
1406
  try {
1178
1407
  const buf = Buffer.alloc(stat.size - start);
1179
- fs12.readSync(fd, buf, 0, buf.length, start);
1408
+ fs13.readSync(fd, buf, 0, buf.length, start);
1180
1409
  return buf.toString("utf8");
1181
1410
  } finally {
1182
- fs12.closeSync(fd);
1411
+ fs13.closeSync(fd);
1183
1412
  }
1184
1413
  } catch {
1185
1414
  return "";
1186
1415
  }
1187
1416
  }
1188
1417
  async function stopEmulator(logger = noopLogger) {
1189
- if (fs12.existsSync(PID_FILE)) {
1418
+ if (fs13.existsSync(PID_FILE)) {
1190
1419
  try {
1191
- const pid = Number(fs12.readFileSync(PID_FILE, "utf8").trim());
1420
+ const pid = Number(fs13.readFileSync(PID_FILE, "utf8").trim());
1192
1421
  if (Number.isFinite(pid) && pid > 0) {
1193
1422
  try {
1194
1423
  process.kill(pid, "SIGTERM");
@@ -1201,27 +1430,194 @@ async function stopEmulator(logger = noopLogger) {
1201
1430
  logger.warn(`Could not read PID file: ${err.message}`);
1202
1431
  }
1203
1432
  }
1204
- 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"];
1205
1434
  try {
1206
- await execPromise(killCmd, void 0, logger);
1207
- } catch {
1435
+ await runProcess({ command: killCmd, args: killArgs, logger });
1436
+ } catch (err) {
1437
+ logger.warn(`[emulator] could not run ${killCmd}: ${err.message}`);
1208
1438
  }
1209
- fs12.rm(PID_FILE, { force: true }, (err) => {
1439
+ fs13.rm(PID_FILE, { force: true }, (err) => {
1210
1440
  if (err) logger.warn(`Could not remove PID file: ${err.message}`);
1211
1441
  });
1212
1442
  }
1213
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
+
1214
1610
  // src/hdc/project.ts
1215
- import * as fs13 from "fs";
1611
+ import * as fs14 from "fs";
1216
1612
  import * as path13 from "path";
1217
1613
  import JSON53 from "json5";
1218
1614
  function readJson5(filePath) {
1219
- const content = fs13.readFileSync(filePath, "utf-8");
1615
+ const content = fs14.readFileSync(filePath, "utf-8");
1220
1616
  return JSON53.parse(content);
1221
1617
  }
1222
1618
  function getBundleName(projectDir) {
1223
1619
  const appJsonPath = path13.join(projectDir, "AppScope", "app.json5");
1224
- if (!fs13.existsSync(appJsonPath)) {
1620
+ if (!fs14.existsSync(appJsonPath)) {
1225
1621
  throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
1226
1622
  }
1227
1623
  const appJson = readJson5(appJsonPath);
@@ -1230,59 +1626,72 @@ function getBundleName(projectDir) {
1230
1626
  }
1231
1627
  return appJson.app.bundleName;
1232
1628
  }
1233
- function getMainAbility(projectDir, moduleName = "entry") {
1629
+ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
1234
1630
  const moduleJsonPath = path13.join(projectDir, moduleName, "src", "main", "module.json5");
1235
- if (!fs13.existsSync(moduleJsonPath)) {
1631
+ if (!fs14.existsSync(moduleJsonPath)) {
1236
1632
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
1237
1633
  }
1238
1634
  const moduleJson = readJson5(moduleJsonPath);
1239
- if (!moduleJson.module?.mainElement) {
1240
- throw new OniroError("mainElement not found in module.json5");
1241
- }
1242
- 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");
1243
1648
  }
1244
1649
 
1245
1650
  // src/hdc/app.ts
1246
- import * as fs14 from "fs";
1247
- import * as path14 from "path";
1248
- import { exec as exec2, spawn as spawn2 } from "child_process";
1249
- function execPromise2(cmd, logger) {
1250
- return new Promise((resolve2, reject) => {
1251
- exec2(cmd, (error, stdout, stderr) => {
1252
- if (stdout?.trim()) logger.info(`[hdc] ${stdout.trim()}`);
1253
- if (stderr?.trim()) logger.warn(`[hdc] ${stderr.trim()}`);
1254
- if (error) {
1255
- logger.error(`[hdc] ${error.message}`);
1256
- reject(error);
1257
- } else {
1258
- resolve2();
1259
- }
1260
- });
1261
- });
1262
- }
1263
1651
  async function installApp(opts) {
1264
- const logger = opts.logger ?? noopLogger;
1652
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
1265
1653
  const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
1266
1654
  const hapPath = path14.isAbsolute(relativeHapPath) ? relativeHapPath : path14.join(opts.projectDir, relativeHapPath);
1267
- if (!fs14.existsSync(hapPath)) {
1655
+ if (!fs15.existsSync(hapPath)) {
1268
1656
  throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
1269
1657
  }
1270
- const hdc = getHdcPath(opts.config);
1271
- 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;
1272
1671
  }
1273
1672
  async function launchApp(opts) {
1274
- const logger = opts.logger ?? noopLogger;
1673
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
1275
1674
  const bundleName = getBundleName(opts.projectDir);
1276
- const mainAbility = getMainAbility(opts.projectDir, opts.moduleName);
1277
- const hdc = getHdcPath(opts.config);
1278
- 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
+ }
1279
1688
  }
1280
1689
  function listRunningProcesses(config, options = {}) {
1281
1690
  const logger = options.logger ?? noopLogger;
1282
1691
  const timeoutMs = options.timeoutMs ?? 1e3;
1283
1692
  const hdc = getHdcPath(config);
1284
1693
  return new Promise((resolve2, reject) => {
1285
- const proc = spawn2(hdc, ["track-jpid"]);
1694
+ const proc = spawn3(hdc, ["track-jpid"]);
1286
1695
  const processes = [];
1287
1696
  let resolved = false;
1288
1697
  proc.stdout.on("data", (data) => {
@@ -1336,14 +1745,395 @@ async function findAppProcessId(config, projectDir, logger) {
1336
1745
  const result = await listRunningProcesses(config, { targetProcessName: bundleName, logger });
1337
1746
  return result;
1338
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
+ }
1339
2129
 
1340
2130
  // src/hdc/hilog.ts
1341
- import { spawn as spawn3 } from "child_process";
2131
+ import { spawn as spawn4 } from "child_process";
1342
2132
  function setHilogLevel(opts) {
1343
2133
  const logger = opts.logger ?? noopLogger;
1344
2134
  const hdc = getHdcPath(opts.config);
1345
2135
  return new Promise((resolve2, reject) => {
1346
- const child = spawn3(hdc, ["shell", "hilog", "-b", opts.level]);
2136
+ const child = spawn4(hdc, ["shell", "hilog", "-b", opts.level]);
1347
2137
  let stderr = "";
1348
2138
  child.stderr.on("data", (chunk) => {
1349
2139
  stderr += chunk.toString();
@@ -1365,11 +2155,16 @@ function setHilogLevel(opts) {
1365
2155
  }
1366
2156
  function streamHilog(opts) {
1367
2157
  const hdc = getHdcPath(opts.config);
1368
- const args = ["shell", "hilog"];
2158
+ const args = [];
2159
+ if (opts.deviceSerial) args.push("-t", opts.deviceSerial);
2160
+ args.push("shell", "hilog");
1369
2161
  if (opts.processId && opts.processId.trim() !== "") {
1370
2162
  args.push("-P", opts.processId.trim());
1371
2163
  }
1372
- return spawn3(hdc, args);
2164
+ if (opts.domain !== void 0 && `${opts.domain}` !== "") {
2165
+ args.push("-D", `${opts.domain}`);
2166
+ }
2167
+ return spawn4(hdc, args);
1373
2168
  }
1374
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+(.*)$/;
1375
2170
  function parseHilogLine(line) {
@@ -1384,30 +2179,194 @@ function parseHilogLine(line) {
1384
2179
  message: match[6]
1385
2180
  };
1386
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
+ }
1387
2341
 
1388
2342
  // src/build/runHvigorw.ts
1389
- import * as fs15 from "fs";
1390
- import * as path15 from "path";
1391
- 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
+ }
1392
2356
  function runHvigorw(opts) {
1393
2357
  const { config, projectDir } = opts;
1394
2358
  const logger = opts.logger ?? noopLogger;
1395
- if (!fs15.existsSync(path15.join(projectDir, "build-profile.json5"))) {
2359
+ if (!fs18.existsSync(path17.join(projectDir, "build-profile.json5"))) {
1396
2360
  throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
1397
2361
  }
1398
- if (!fs15.existsSync(getCmdToolsPath(config))) {
2362
+ if (!fs18.existsSync(getCmdToolsPath(config))) {
1399
2363
  throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
1400
2364
  }
1401
2365
  const hvigorw = getHvigorwPath(config, projectDir);
1402
- const args = [opts.task ?? "assembleHap", "--mode", "module"];
1403
- args.push("-p", `product=${opts.product ?? "default"}`);
1404
- if (opts.module) args.push("-p", `module=${opts.module}`);
1405
- if (opts.buildMode) args.push("-p", `buildMode=${opts.buildMode}`);
1406
- args.push("--stacktrace", "--no-parallel", "--no-daemon");
1407
- if (opts.extraArgs?.length) args.push(...opts.extraArgs);
2366
+ const args = buildHvigorwArgs(opts);
1408
2367
  if (process.platform !== "win32") {
1409
2368
  try {
1410
- fs15.chmodSync(hvigorw, 493);
2369
+ fs18.chmodSync(hvigorw, 493);
1411
2370
  } catch {
1412
2371
  }
1413
2372
  }
@@ -1416,8 +2375,25 @@ function runHvigorw(opts) {
1416
2375
  OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
1417
2376
  };
1418
2377
  return new Promise((resolve2, reject) => {
2378
+ if (opts.abortSignal?.aborted) {
2379
+ reject(new CancelledError("hvigorw build cancelled."));
2380
+ return;
2381
+ }
1419
2382
  logger.info(`[build] ${hvigorw} ${args.join(" ")}`);
1420
- 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 });
1421
2397
  child.stdout.on("data", (chunk) => {
1422
2398
  const text = chunk.toString();
1423
2399
  opts.onOutput?.(text, "stdout");
@@ -1428,8 +2404,16 @@ function runHvigorw(opts) {
1428
2404
  opts.onOutput?.(text, "stderr");
1429
2405
  logger.warn(text.trimEnd());
1430
2406
  });
1431
- 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
+ });
1432
2413
  child.on("close", (code) => {
2414
+ if (settled) return;
2415
+ settled = true;
2416
+ cleanup();
1433
2417
  const exitCode = code ?? 1;
1434
2418
  if (exitCode === 0) resolve2({ exitCode });
1435
2419
  else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
@@ -1437,6 +2421,137 @@ function runHvigorw(opts) {
1437
2421
  });
1438
2422
  }
1439
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
+
1440
2555
  // src/project/validators.ts
1441
2556
  function isValidProjectName(name) {
1442
2557
  if (!name) return false;
@@ -1448,21 +2563,21 @@ function isValidBundleName(bundleName) {
1448
2563
  }
1449
2564
 
1450
2565
  // src/project/templates.ts
1451
- import * as fs16 from "fs";
1452
- import * as path16 from "path";
2566
+ import * as fs21 from "fs";
2567
+ import * as path20 from "path";
1453
2568
  function toHumanTemplateName(folderName) {
1454
2569
  return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
1455
2570
  }
1456
2571
  function listTemplates(templateRoot) {
1457
- if (!fs16.existsSync(templateRoot)) return [];
1458
- const entries = fs16.readdirSync(templateRoot, { withFileTypes: true });
2572
+ if (!fs21.existsSync(templateRoot)) return [];
2573
+ const entries = fs21.readdirSync(templateRoot, { withFileTypes: true });
1459
2574
  return entries.filter((e) => e.isDirectory()).map((e) => {
1460
- const dir = path16.join(templateRoot, e.name);
1461
- const metaPath = path16.join(dir, "template.json");
2575
+ const dir = path20.join(templateRoot, e.name);
2576
+ const metaPath = path20.join(dir, "template.json");
1462
2577
  let meta = {};
1463
- if (fs16.existsSync(metaPath)) {
2578
+ if (fs21.existsSync(metaPath)) {
1464
2579
  try {
1465
- meta = JSON.parse(fs16.readFileSync(metaPath, "utf8"));
2580
+ meta = JSON.parse(fs21.readFileSync(metaPath, "utf8"));
1466
2581
  } catch {
1467
2582
  }
1468
2583
  }
@@ -1477,57 +2592,57 @@ function listTemplates(templateRoot) {
1477
2592
  function validateTemplateLayout(templateDir, defaultModuleName) {
1478
2593
  const required = [
1479
2594
  "build-profile.json5",
1480
- path16.join("AppScope", "app.json5"),
1481
- path16.join(defaultModuleName, "src", "main", "module.json5"),
1482
- 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"),
1483
2598
  "hvigorfile.ts"
1484
2599
  ];
1485
- return required.filter((rel) => !fs16.existsSync(path16.join(templateDir, rel)));
2600
+ return required.filter((rel) => !fs21.existsSync(path20.join(templateDir, rel)));
1486
2601
  }
1487
2602
 
1488
2603
  // src/project/createScaffold.ts
1489
- import * as fs18 from "fs";
1490
- import * as path17 from "path";
2604
+ import * as fs23 from "fs";
2605
+ import * as path21 from "path";
1491
2606
 
1492
2607
  // src/project/jsonHelpers.ts
1493
- import * as fs17 from "fs";
2608
+ import * as fs22 from "fs";
1494
2609
  import JSON54 from "json5";
1495
2610
  function readJson5File(filePath) {
1496
- return JSON54.parse(fs17.readFileSync(filePath, "utf8"));
2611
+ return JSON54.parse(fs22.readFileSync(filePath, "utf8"));
1497
2612
  }
1498
2613
  function writeJson5File(filePath, value) {
1499
- fs17.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
2614
+ fs22.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
1500
2615
  }
1501
2616
  function readJsonFile(filePath) {
1502
- return JSON.parse(fs17.readFileSync(filePath, "utf8"));
2617
+ return JSON.parse(fs22.readFileSync(filePath, "utf8"));
1503
2618
  }
1504
2619
  function writeJsonFile(filePath, value) {
1505
- fs17.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
2620
+ fs22.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
1506
2621
  }
1507
2622
 
1508
2623
  // src/project/createScaffold.ts
1509
2624
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
1510
2625
  async function pathExists(p) {
1511
2626
  try {
1512
- await fs18.promises.access(p);
2627
+ await fs23.promises.access(p);
1513
2628
  return true;
1514
2629
  } catch {
1515
2630
  return false;
1516
2631
  }
1517
2632
  }
1518
2633
  async function copyDirRecursive(srcDir, destDir) {
1519
- await fs18.promises.mkdir(destDir, { recursive: true });
1520
- 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 });
1521
2636
  for (const entry of entries) {
1522
- const src = path17.join(srcDir, entry.name);
1523
- const dest = path17.join(destDir, entry.name);
2637
+ const src = path21.join(srcDir, entry.name);
2638
+ const dest = path21.join(destDir, entry.name);
1524
2639
  if (entry.isDirectory()) {
1525
2640
  await copyDirRecursive(src, dest);
1526
2641
  } else if (entry.isSymbolicLink()) {
1527
2642
  continue;
1528
2643
  } else if (entry.isFile()) {
1529
2644
  if (entry.name === "template.json") continue;
1530
- await fs18.promises.copyFile(src, dest);
2645
+ await fs23.promises.copyFile(src, dest);
1531
2646
  }
1532
2647
  }
1533
2648
  }
@@ -1535,9 +2650,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
1535
2650
  const stack = [projectDir];
1536
2651
  while (stack.length > 0) {
1537
2652
  const currentDir = stack.pop();
1538
- const entries = await fs18.promises.readdir(currentDir, { withFileTypes: true });
2653
+ const entries = await fs23.promises.readdir(currentDir, { withFileTypes: true });
1539
2654
  for (const entry of entries) {
1540
- const fullPath = path17.join(currentDir, entry.name);
2655
+ const fullPath = path21.join(currentDir, entry.name);
1541
2656
  if (entry.isDirectory()) {
1542
2657
  if (IGNORED_DIRS.has(entry.name)) continue;
1543
2658
  stack.push(fullPath);
@@ -1559,23 +2674,23 @@ function toJavaPropertiesPath(p) {
1559
2674
  function createOrUpdateLocalProperties(projectDir, sdkDir) {
1560
2675
  const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
1561
2676
  `;
1562
- fs18.writeFileSync(path17.join(projectDir, "local.properties"), content, "utf8");
2677
+ fs23.writeFileSync(path21.join(projectDir, "local.properties"), content, "utf8");
1563
2678
  }
1564
2679
  function renameIfExists(fromPath, toPath) {
1565
- if (fs18.existsSync(fromPath) && fromPath !== toPath) {
1566
- fs18.renameSync(fromPath, toPath);
2680
+ if (fs23.existsSync(fromPath) && fromPath !== toPath) {
2681
+ fs23.renameSync(fromPath, toPath);
1567
2682
  }
1568
2683
  }
1569
2684
  function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
1570
- const appJsonPath = path17.join(projectDir, "AppScope", "app.json5");
1571
- if (fs18.existsSync(appJsonPath)) {
2685
+ const appJsonPath = path21.join(projectDir, "AppScope", "app.json5");
2686
+ if (fs23.existsSync(appJsonPath)) {
1572
2687
  const appJson = readJson5File(appJsonPath);
1573
2688
  appJson.app = appJson.app ?? {};
1574
2689
  appJson.app.bundleName = args.bundleName;
1575
2690
  writeJson5File(appJsonPath, appJson);
1576
2691
  }
1577
- const stringsPath = path17.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
1578
- if (fs18.existsSync(stringsPath)) {
2692
+ const stringsPath = path21.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
2693
+ if (fs23.existsSync(stringsPath)) {
1579
2694
  const strings = readJsonFile(stringsPath);
1580
2695
  if (Array.isArray(strings.string)) {
1581
2696
  const appName = strings.string.find((s) => s.name === "app_name");
@@ -1583,8 +2698,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
1583
2698
  }
1584
2699
  writeJsonFile(stringsPath, strings);
1585
2700
  }
1586
- const buildProfilePath = path17.join(projectDir, "build-profile.json5");
1587
- if (fs18.existsSync(buildProfilePath)) {
2701
+ const buildProfilePath = path21.join(projectDir, "build-profile.json5");
2702
+ if (fs23.existsSync(buildProfilePath)) {
1588
2703
  const buildProfile = readJson5File(buildProfilePath);
1589
2704
  buildProfile.app = buildProfile.app ?? {};
1590
2705
  if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
@@ -1597,23 +2712,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
1597
2712
  }
1598
2713
  writeJson5File(buildProfilePath, buildProfile);
1599
2714
  }
1600
- const moduleJsonPath = path17.join(projectDir, args.moduleName, "src", "main", "module.json5");
1601
- if (fs18.existsSync(moduleJsonPath)) {
2715
+ const moduleJsonPath = path21.join(projectDir, args.moduleName, "src", "main", "module.json5");
2716
+ if (fs23.existsSync(moduleJsonPath)) {
1602
2717
  const moduleJson = readJson5File(moduleJsonPath);
1603
2718
  moduleJson.module = moduleJson.module ?? {};
1604
2719
  moduleJson.module.name = args.moduleName;
1605
2720
  writeJson5File(moduleJsonPath, moduleJson);
1606
2721
  }
1607
- const moduleOhPackagePath = path17.join(projectDir, args.moduleName, "oh-package.json5");
1608
- if (fs18.existsSync(moduleOhPackagePath)) {
2722
+ const moduleOhPackagePath = path21.join(projectDir, args.moduleName, "oh-package.json5");
2723
+ if (fs23.existsSync(moduleOhPackagePath)) {
1609
2724
  const pkg = readJson5File(moduleOhPackagePath);
1610
2725
  pkg.name = args.moduleName;
1611
2726
  writeJson5File(moduleOhPackagePath, pkg);
1612
2727
  }
1613
- const vscodeDir = path17.join(projectDir, ".vscode");
1614
- fs18.mkdirSync(vscodeDir, { recursive: true });
2728
+ const vscodeDir = path21.join(projectDir, ".vscode");
2729
+ fs23.mkdirSync(vscodeDir, { recursive: true });
1615
2730
  const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
1616
- writeJsonFile(path17.join(vscodeDir, "settings.json"), {
2731
+ writeJsonFile(path21.join(vscodeDir, "settings.json"), {
1617
2732
  "oniro.hapPath": hapPath,
1618
2733
  "files.associations": { "*.json5": "jsonc" }
1619
2734
  });
@@ -1630,7 +2745,7 @@ async function createScaffold(opts) {
1630
2745
  if (!opts.location || !await pathExists(opts.location)) {
1631
2746
  throw new OniroError(`Location does not exist: ${opts.location}`);
1632
2747
  }
1633
- const templateDir = path17.join(opts.templateRoot, opts.templateId);
2748
+ const templateDir = path21.join(opts.templateRoot, opts.templateId);
1634
2749
  if (!await pathExists(templateDir)) {
1635
2750
  throw new OniroError(`Template not found: ${templateDir}`);
1636
2751
  }
@@ -1643,17 +2758,17 @@ async function createScaffold(opts) {
1643
2758
  throw new OniroError(`Template '${opts.templateId}' is missing required files:
1644
2759
  - ${missing.join("\n- ")}`);
1645
2760
  }
1646
- const projectDir = path17.join(opts.location, opts.projectName);
2761
+ const projectDir = path21.join(opts.location, opts.projectName);
1647
2762
  if (await pathExists(projectDir)) {
1648
2763
  if (!opts.overwrite) {
1649
2764
  throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
1650
2765
  }
1651
- await fs18.promises.rm(projectDir, { recursive: true, force: true });
2766
+ await fs23.promises.rm(projectDir, { recursive: true, force: true });
1652
2767
  }
1653
2768
  logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
1654
2769
  await copyDirRecursive(templateDir, projectDir);
1655
2770
  if (moduleName !== defaultModuleName) {
1656
- renameIfExists(path17.join(projectDir, defaultModuleName), path17.join(projectDir, moduleName));
2771
+ renameIfExists(path21.join(projectDir, defaultModuleName), path21.join(projectDir, moduleName));
1657
2772
  }
1658
2773
  updateTemplateConfigs(
1659
2774
  projectDir,
@@ -1669,8 +2784,225 @@ async function createScaffold(opts) {
1669
2784
  return { projectDir };
1670
2785
  }
1671
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
+
1672
3004
  // src/index.ts
1673
- var VERSION = "0.6.1";
3005
+ var VERSION = "0.7.0";
1674
3006
  export {
1675
3007
  ALL_SDKS,
1676
3008
  APL_VALUES,
@@ -1678,32 +3010,59 @@ export {
1678
3010
  CancelledError,
1679
3011
  ChecksumMismatchError,
1680
3012
  CmdToolsNotInstalledError,
3013
+ CommandFailedError,
1681
3014
  OHOS_URL_BASE,
1682
3015
  OniroError,
1683
3016
  SdkNotInstalledError,
1684
3017
  UnsupportedPlatformError,
1685
3018
  VERSION,
3019
+ applyChanges,
1686
3020
  attemptHdcConnection,
3021
+ buildGestureCommands,
3022
+ buildHap,
3023
+ buildHvigorwArgs,
3024
+ buildInputCommand,
3025
+ buildRawTouchCommand,
3026
+ captureBurst,
1687
3027
  consoleLogger,
1688
3028
  createMaterial,
1689
3029
  createScaffold,
3030
+ decideInstallMethod,
3031
+ decideReboot,
1690
3032
  decryptPwd,
3033
+ dedupEntries,
1691
3034
  defaultContext,
1692
3035
  defaultPaths,
3036
+ detectAaStartFailure,
1693
3037
  detectProjectSdkVersion,
3038
+ detectSigningConfigNames,
3039
+ diffEntryNames,
3040
+ diffHapAssets,
3041
+ discoverHaps,
1694
3042
  downloadAndInstallSdk,
1695
3043
  downloadFile,
3044
+ dumpLayout,
3045
+ dumpLog,
3046
+ dumpRenderService,
3047
+ dumpScreen,
3048
+ dumpWindow,
1696
3049
  encryptPwd,
3050
+ ensureOhModules,
3051
+ ensureOk,
1697
3052
  extractTarball,
1698
3053
  extractZipWithProgress,
1699
3054
  findAppProcessId,
1700
3055
  findCmdToolsSourceDir,
3056
+ findRunningProcess,
3057
+ forceStop,
1701
3058
  generateSigningConfigs,
1702
3059
  getBundleName,
1703
3060
  getCmdToolsBin,
1704
3061
  getCmdToolsDownloadUrl,
1705
3062
  getCmdToolsPath,
1706
3063
  getCmdToolsStatus,
3064
+ getDeviceInfo,
3065
+ getDisplaySize,
1707
3066
  getEmulatorDir,
1708
3067
  getHdcPath,
1709
3068
  getHvigorwPath,
@@ -1711,10 +3070,12 @@ export {
1711
3070
  getKey,
1712
3071
  getMainAbility,
1713
3072
  getOhosBaseSdkHome,
3073
+ getOhpmPath,
1714
3074
  getOsFolder,
1715
3075
  getSdkFilename,
1716
3076
  getSdkRootDir,
1717
3077
  getSupportedSdksForUi,
3078
+ hdcExec,
1718
3079
  installApp,
1719
3080
  installCmdTools,
1720
3081
  installEmulator,
@@ -1723,25 +3084,55 @@ export {
1723
3084
  isValidBundleName,
1724
3085
  isValidProjectName,
1725
3086
  launchApp,
3087
+ listAbilities,
3088
+ listDevices,
3089
+ listModules,
1726
3090
  listRunningProcesses,
1727
3091
  listTemplates,
1728
3092
  nonInteractivePrompter,
1729
3093
  noopLogger,
1730
3094
  noopProgress,
3095
+ paramGet,
3096
+ paramSet,
3097
+ parseBounds,
3098
+ parseCodelinterFindings,
3099
+ parseDeviceList,
1731
3100
  parseHilogLine,
3101
+ pickSigningKind,
3102
+ prepareSigning,
3103
+ pruneLayout,
1732
3104
  readJson5File,
1733
3105
  readJsonFile,
3106
+ reboot,
3107
+ recvFile,
1734
3108
  removeCmdTools,
1735
3109
  removeEmulator,
1736
3110
  removeSdk,
3111
+ runCodelinter,
1737
3112
  runHvigorw,
3113
+ runOhpm,
3114
+ runProcess,
3115
+ scopedLogger,
3116
+ selectDevice,
3117
+ sendFile,
3118
+ sendGesture,
3119
+ sendInput,
3120
+ sendRawTouch,
1738
3121
  setHilogLevel,
3122
+ shell,
1739
3123
  startEmulator,
1740
3124
  staticConfig,
1741
3125
  stopEmulator,
1742
3126
  streamHilog,
3127
+ takeScreenshot,
3128
+ uninstallApp,
1743
3129
  validateTemplateLayout,
1744
3130
  verifySha256,
3131
+ waitForBoot,
3132
+ waitForBundle,
3133
+ waitForCondition,
3134
+ waitForLog,
3135
+ watchLog,
1745
3136
  writeJson5File,
1746
3137
  writeJsonFile
1747
3138
  };