@oniroproject/core 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -33,14 +33,18 @@ __export(index_exports, {
33
33
  ALL_SDKS: () => ALL_SDKS,
34
34
  APL_VALUES: () => APL_VALUES,
35
35
  APP_FEATURE_VALUES: () => APP_FEATURE_VALUES,
36
+ AgcError: () => AgcError,
36
37
  CancelledError: () => CancelledError,
37
38
  ChecksumMismatchError: () => ChecksumMismatchError,
38
39
  CmdToolsNotInstalledError: () => CmdToolsNotInstalledError,
39
40
  CommandFailedError: () => CommandFailedError,
41
+ HARMONYOS_SDK_VERSIONS: () => HARMONYOS_SDK_VERSIONS,
42
+ HUAWEI_SITES: () => HUAWEI_SITES,
40
43
  INSTALL_TMP_DIRNAME: () => INSTALL_TMP_DIRNAME,
41
44
  InsufficientSpaceError: () => InsufficientSpaceError,
42
45
  OHOS_URL_BASE: () => OHOS_URL_BASE,
43
46
  OniroError: () => OniroError,
47
+ SIGNING_ERRORS: () => SIGNING_ERRORS,
44
48
  SdkNotInstalledError: () => SdkNotInstalledError,
45
49
  TMP_DIR_HINT: () => TMP_DIR_HINT,
46
50
  UnsupportedPlatformError: () => UnsupportedPlatformError,
@@ -56,10 +60,12 @@ __export(index_exports, {
56
60
  buildRawTouchCommand: () => buildRawTouchCommand,
57
61
  captureBurst: () => captureBurst,
58
62
  consoleLogger: () => consoleLogger,
63
+ createHarmonyOsSession: () => createHarmonyOsSession,
59
64
  createInstallTempDir: () => createInstallTempDir,
60
65
  createMaterial: () => createMaterial,
61
66
  createScaffold: () => createScaffold,
62
67
  createTempWorkDir: () => createTempWorkDir,
68
+ createTokenStore: () => createTokenStore,
63
69
  decideInstallMethod: () => decideInstallMethod,
64
70
  decideReboot: () => decideReboot,
65
71
  decryptPwd: () => decryptPwd,
@@ -68,6 +74,7 @@ __export(index_exports, {
68
74
  defaultPaths: () => defaultPaths,
69
75
  detectAaStartFailure: () => detectAaStartFailure,
70
76
  detectProjectSdkVersion: () => detectProjectSdkVersion,
77
+ detectRuntimeOs: () => detectRuntimeOs,
71
78
  detectSigningConfigNames: () => detectSigningConfigNames,
72
79
  diffEntryNames: () => diffEntryNames,
73
80
  diffHapAssets: () => diffHapAssets,
@@ -90,6 +97,7 @@ __export(index_exports, {
90
97
  extractZipWithProgress: () => extractZipWithProgress,
91
98
  findAppProcessId: () => findAppProcessId,
92
99
  findCmdToolsSourceDir: () => findCmdToolsSourceDir,
100
+ findHarmonyOsSdk: () => findHarmonyOsSdk,
93
101
  findRunningProcess: () => findRunningProcess,
94
102
  forceStop: () => forceStop,
95
103
  formatBytes: () => formatBytes,
@@ -114,6 +122,8 @@ __export(index_exports, {
114
122
  getSdkFilename: () => getSdkFilename,
115
123
  getSdkRootDir: () => getSdkRootDir,
116
124
  getSupportedSdksForUi: () => getSupportedSdksForUi,
125
+ harmonyOsAutoSign: () => harmonyOsAutoSign,
126
+ harmonyOsSdkLabelForApi: () => harmonyOsSdkLabelForApi,
117
127
  hdcExec: () => hdcExec,
118
128
  installApp: () => installApp,
119
129
  installCmdTools: () => installCmdTools,
@@ -148,12 +158,14 @@ __export(index_exports, {
148
158
  pruneLayout: () => pruneLayout,
149
159
  readJson5File: () => readJson5File,
150
160
  readJsonFile: () => readJsonFile,
161
+ readProvisionProfile: () => readProvisionProfile,
151
162
  reboot: () => reboot,
152
163
  recvFile: () => recvFile,
153
164
  removeCmdTools: () => removeCmdTools,
154
165
  removeEmulator: () => removeEmulator,
155
166
  removeSdk: () => removeSdk,
156
167
  removeTempWorkDir: () => removeTempWorkDir,
168
+ requireHarmonyOsSdk: () => requireHarmonyOsSdk,
157
169
  resolveTmpRoot: () => resolveTmpRoot,
158
170
  runCodelinter: () => runCodelinter,
159
171
  runHvigorw: () => runHvigorw,
@@ -230,6 +242,8 @@ var defaultPaths = {
230
242
  sdkRootDir: () => path.join(os.homedir(), "setup-ohos-sdk"),
231
243
  cmdToolsPath: () => path.join(os.homedir(), "command-line-tools"),
232
244
  emulatorDir: () => path.join(os.homedir(), "oniro-emulator"),
245
+ harmonyosAuthDir: () => path.join(os.homedir(), ".oniro", "harmonyos"),
246
+ harmonyosSigningDir: () => path.join(os.homedir(), ".ohos", "config"),
233
247
  hapPath: "entry/build/default/outputs/default/entry-default-signed.hap",
234
248
  emulatorUrl: "https://github.com/eclipse-oniro4openharmony/device_board_oniro/releases/latest/download/oniro_emulator.zip",
235
249
  // The Huawei mirror only hosts a public Linux build of the command-line tools.
@@ -394,14 +408,94 @@ function getSdkFilename(version) {
394
408
  }
395
409
 
396
410
  // src/sdk/paths.ts
411
+ var fs2 = __toESM(require("fs"), 1);
412
+ var os4 = __toESM(require("os"), 1);
413
+ var path3 = __toESM(require("path"), 1);
414
+
415
+ // src/harmonyos/sdk.ts
397
416
  var fs = __toESM(require("fs"), 1);
398
417
  var os3 = __toESM(require("os"), 1);
399
418
  var path2 = __toESM(require("path"), 1);
419
+ function readSdkPkg(sdkPath) {
420
+ let data;
421
+ try {
422
+ data = JSON.parse(fs.readFileSync(path2.join(sdkPath, "default", "sdk-pkg.json"), "utf8")).data;
423
+ } catch {
424
+ return {};
425
+ }
426
+ const text = (value) => typeof value === "string" || typeof value === "number" ? String(value) || void 0 : void 0;
427
+ const platformVersion = text(data?.platformVersion);
428
+ const apiVersion = text(data?.apiVersion);
429
+ return {
430
+ displayName: text(data?.displayName),
431
+ version: platformVersion && apiVersion ? `${platformVersion}(${apiVersion})` : void 0
432
+ };
433
+ }
434
+ function resolveSdk(root, requireHarmonyOs) {
435
+ const nested = path2.join(root, "sdk");
436
+ const sdkPath = [nested, root].find((dir) => fs.existsSync(path2.join(dir, "default", "openharmony")));
437
+ if (!sdkPath) return null;
438
+ const pkg = readSdkPkg(sdkPath);
439
+ if (requireHarmonyOs && !pkg.displayName?.startsWith("HarmonyOS")) return null;
440
+ return { sdkPath, installRoot: sdkPath === nested ? root : path2.dirname(root), ...pkg };
441
+ }
442
+ function studioCandidates() {
443
+ if (process.platform === "darwin") {
444
+ return [
445
+ "/Applications/DevEco-Studio.app/Contents",
446
+ path2.join(os3.homedir(), "Applications", "DevEco-Studio.app", "Contents")
447
+ ];
448
+ }
449
+ if (process.platform === "win32") {
450
+ return [path2.join(process.env.ProgramFiles ?? "C:\\Program Files", "Huawei", "DevEco Studio")];
451
+ }
452
+ return [];
453
+ }
454
+ function findHarmonyOsSdk(opts) {
455
+ const logger = opts.logger ?? noopLogger;
456
+ const configured = opts.config.get("harmonyosSdkPath", "");
457
+ if (configured) {
458
+ const sdk = resolveSdk(configured, false);
459
+ if (sdk) return sdk;
460
+ logger.warn(`ONIRO_HARMONYOS_SDK_PATH points at ${configured}, which holds no SDK; ignoring it.`);
461
+ }
462
+ const candidates = [
463
+ ...studioCandidates(),
464
+ opts.config.get("cmdToolsPath", defaultPaths.cmdToolsPath())
465
+ ];
466
+ for (const candidate of candidates) {
467
+ const sdk = resolveSdk(candidate, true);
468
+ if (sdk) {
469
+ logger.debug(`[harmonyos] Using the ${sdk.displayName} SDK at ${sdk.sdkPath}.`);
470
+ return sdk;
471
+ }
472
+ }
473
+ return null;
474
+ }
475
+ function requireHarmonyOsSdk(opts) {
476
+ const sdk = findHarmonyOsSdk(opts);
477
+ if (sdk) return sdk;
478
+ throw new OniroError(
479
+ "No HarmonyOS SDK found. It ships inside DevEco Studio or the HarmonyOS command-line tools and cannot be downloaded the way the OpenHarmony SDK is. Install one, then set ONIRO_HARMONYOS_SDK_PATH to the install (or its sdk directory)."
480
+ );
481
+ }
482
+ function findHarmonyOsTool(sdk, name) {
483
+ const names = process.platform === "win32" ? [`${name}.bat`, `${name}.cmd`, `${name}.exe`] : [name];
484
+ for (const dir of [path2.join(sdk.installRoot, "bin"), path2.join(sdk.installRoot, "tools", name, "bin")]) {
485
+ for (const candidate of names) {
486
+ const full = path2.join(dir, candidate);
487
+ if (fs.existsSync(full)) return full;
488
+ }
489
+ }
490
+ return null;
491
+ }
492
+
493
+ // src/sdk/paths.ts
400
494
  function getSdkRootDir(config) {
401
495
  return config.get("sdkRootDir", defaultPaths.sdkRootDir());
402
496
  }
403
497
  function getOhosBaseSdkHome(config) {
404
- return path2.join(getSdkRootDir(config), getOsFolder());
498
+ return path3.join(getSdkRootDir(config), getOsFolder());
405
499
  }
406
500
  function getCmdToolsPath(config) {
407
501
  return config.get("cmdToolsPath", defaultPaths.cmdToolsPath());
@@ -409,31 +503,31 @@ function getCmdToolsPath(config) {
409
503
  function getEmulatorDir(config) {
410
504
  return config.get("emulatorDir", defaultPaths.emulatorDir());
411
505
  }
412
- function toLongPath(p, platform7 = os3.platform()) {
506
+ function toLongPath(p, platform7 = os4.platform()) {
413
507
  if (platform7 !== "win32") return p;
414
508
  try {
415
- return fs.realpathSync.native(p);
509
+ return fs2.realpathSync.native(p);
416
510
  } catch {
417
511
  return p;
418
512
  }
419
513
  }
420
514
  function pickExisting(candidates) {
421
515
  for (const c of candidates) {
422
- if (fs.existsSync(c)) return c;
516
+ if (fs2.existsSync(c)) return c;
423
517
  }
424
518
  return candidates[0];
425
519
  }
426
520
  function getCmdToolsBin(config, name = "ohpm") {
427
- const binDir = path2.join(getCmdToolsPath(config), "bin");
428
- if (os3.platform() === "win32") {
521
+ const binDir = path3.join(getCmdToolsPath(config), "bin");
522
+ if (os4.platform() === "win32") {
429
523
  return pickExisting([
430
- path2.join(binDir, `${name}.exe`),
431
- path2.join(binDir, `${name}.cmd`),
432
- path2.join(binDir, `${name}.bat`),
433
- path2.join(binDir, name)
524
+ path3.join(binDir, `${name}.exe`),
525
+ path3.join(binDir, `${name}.cmd`),
526
+ path3.join(binDir, `${name}.bat`),
527
+ path3.join(binDir, name)
434
528
  ]);
435
529
  }
436
- return path2.join(binDir, name);
530
+ return path3.join(binDir, name);
437
531
  }
438
532
  function getOhpmPath(config) {
439
533
  return getCmdToolsBin(config, "ohpm");
@@ -441,45 +535,52 @@ function getOhpmPath(config) {
441
535
  function getHdcPath(config) {
442
536
  const override = config.get("hdcPath", "");
443
537
  if (override) return override;
444
- const base = path2.join(getCmdToolsPath(config), "sdk", "default", "openharmony", "toolchains");
445
- if (os3.platform() === "win32") {
538
+ const hdc = hdcInSdk(path3.join(getCmdToolsPath(config), "sdk"));
539
+ if (fs2.existsSync(hdc)) return hdc;
540
+ const harmonySdk = findHarmonyOsSdk({ config });
541
+ const harmonyHdc = harmonySdk ? hdcInSdk(harmonySdk.sdkPath) : void 0;
542
+ return harmonyHdc && fs2.existsSync(harmonyHdc) ? harmonyHdc : hdc;
543
+ }
544
+ function hdcInSdk(sdkDir) {
545
+ const base = path3.join(sdkDir, "default", "openharmony", "toolchains");
546
+ if (os4.platform() === "win32") {
446
547
  return pickExisting([
447
- path2.join(base, "hdc.exe"),
448
- path2.join(base, "hdc.bat"),
449
- path2.join(base, "hdc.cmd"),
450
- path2.join(base, "hdc")
548
+ path3.join(base, "hdc.exe"),
549
+ path3.join(base, "hdc.bat"),
550
+ path3.join(base, "hdc.cmd"),
551
+ path3.join(base, "hdc")
451
552
  ]);
452
553
  }
453
- return path2.join(base, "hdc");
554
+ return path3.join(base, "hdc");
454
555
  }
455
- function getHvigorwPath(config, projectDir, platform7 = os3.platform()) {
556
+ function getHvigorwPath(config, projectDir, platform7 = os4.platform()) {
456
557
  const posixFirst = ["hvigorw", "hvigorw.bat", "hvigorw.cmd"];
457
558
  const windowsFirst = ["hvigorw.bat", "hvigorw.cmd", "hvigorw"];
458
559
  const names = platform7 === "win32" ? windowsFirst : posixFirst;
459
- const local = pickExisting(names.map((n) => path2.join(projectDir, n)));
460
- if (fs.existsSync(local) && isProjectLocalHvigorwWorking(projectDir)) {
560
+ const local = pickExisting(names.map((n) => path3.join(projectDir, n)));
561
+ if (fs2.existsSync(local) && isProjectLocalHvigorwWorking(projectDir)) {
461
562
  return local;
462
563
  }
463
564
  return getCmdToolsBin(config, "hvigorw");
464
565
  }
465
566
  function isProjectLocalHvigorwWorking(projectDir) {
466
- const wrapper = path2.join(projectDir, "hvigor", "hvigor-wrapper.js");
467
- const nodeModules = path2.join(projectDir, "hvigor", "node_modules");
468
- return fs.existsSync(wrapper) && fs.existsSync(nodeModules);
567
+ const wrapper = path3.join(projectDir, "hvigor", "hvigor-wrapper.js");
568
+ const nodeModules = path3.join(projectDir, "hvigor", "node_modules");
569
+ return fs2.existsSync(wrapper) && fs2.existsSync(nodeModules);
469
570
  }
470
571
 
471
572
  // src/sdk/detectProjectSdk.ts
472
- var fs2 = __toESM(require("fs"), 1);
473
- var path3 = __toESM(require("path"), 1);
573
+ var fs3 = __toESM(require("fs"), 1);
574
+ var path4 = __toESM(require("path"), 1);
474
575
  var import_json5 = __toESM(require("json5"), 1);
475
576
  function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
476
- const buildProfilePath = path3.join(projectRoot, "build-profile.json5");
477
- if (!fs2.existsSync(buildProfilePath)) {
577
+ const buildProfilePath = path4.join(projectRoot, "build-profile.json5");
578
+ if (!fs3.existsSync(buildProfilePath)) {
478
579
  logger.warn(`build-profile.json5 not found at ${projectRoot}`);
479
580
  return void 0;
480
581
  }
481
582
  try {
482
- const content = fs2.readFileSync(buildProfilePath, "utf-8");
583
+ const content = fs3.readFileSync(buildProfilePath, "utf-8");
483
584
  const config = import_json5.default.parse(content);
484
585
  const product = config.app?.products?.[0];
485
586
  const v = product?.compileSdkVersion;
@@ -491,26 +592,26 @@ function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
491
592
  }
492
593
 
493
594
  // src/sdk/tmp.ts
494
- var fs3 = __toESM(require("fs"), 1);
495
- var os4 = __toESM(require("os"), 1);
496
- var path4 = __toESM(require("path"), 1);
595
+ var fs4 = __toESM(require("fs"), 1);
596
+ var os5 = __toESM(require("os"), 1);
597
+ var path5 = __toESM(require("path"), 1);
497
598
  var INSTALL_TMP_DIRNAME = ".oniro-tmp";
498
599
  var TMPFS_MAGIC = 16914836;
499
600
  var RAMFS_MAGIC = 2240043254;
500
- var HINT_EXAMPLE = os4.platform() === "win32" ? "D:\\oniro-tmp" : "~/.cache/oniro-tmp";
601
+ var HINT_EXAMPLE = os5.platform() === "win32" ? "D:\\oniro-tmp" : "~/.cache/oniro-tmp";
501
602
  var TMP_DIR_HINT = `Point the scratch directory at a filesystem with more room using --tmp-dir <path> (or the ONIRO_TMP_DIR environment variable), e.g. --tmp-dir ${HINT_EXAMPLE}.`;
502
603
  function resolveTmpRoot(config, override, installRoot) {
503
604
  const explicit = (override ?? "").trim();
504
- if (explicit) return path4.resolve(explicit);
605
+ if (explicit) return path5.resolve(explicit);
505
606
  const configured = config.get("tmpDir", "").trim();
506
- if (configured) return path4.resolve(configured);
507
- if (installRoot) return path4.join(path4.resolve(installRoot), INSTALL_TMP_DIRNAME);
508
- return os4.tmpdir();
607
+ if (configured) return path5.resolve(configured);
608
+ if (installRoot) return path5.join(path5.resolve(installRoot), INSTALL_TMP_DIRNAME);
609
+ return os5.tmpdir();
509
610
  }
510
611
  function createTempWorkDir(root, prefix) {
511
612
  try {
512
- fs3.mkdirSync(root, { recursive: true });
513
- return fs3.mkdtempSync(path4.join(root, prefix));
613
+ fs4.mkdirSync(root, { recursive: true });
614
+ return fs4.mkdtempSync(path5.join(root, prefix));
514
615
  } catch (err) {
515
616
  if (isOutOfSpaceError(err)) throw toSpaceError(err, root, "a temporary directory");
516
617
  throw new OniroError(
@@ -521,10 +622,10 @@ ${TMP_DIR_HINT}`,
521
622
  }
522
623
  }
523
624
  function removeTempWorkDir(dir, root) {
524
- fs3.rmSync(dir, { recursive: true, force: true });
525
- if (!root || path4.basename(root) !== INSTALL_TMP_DIRNAME) return;
625
+ fs4.rmSync(dir, { recursive: true, force: true });
626
+ if (!root || path5.basename(root) !== INSTALL_TMP_DIRNAME) return;
526
627
  try {
527
- fs3.rmdirSync(root);
628
+ fs4.rmdirSync(root);
528
629
  } catch {
529
630
  }
530
631
  }
@@ -534,18 +635,18 @@ function createInstallTempDir(opts) {
534
635
  try {
535
636
  return { dir: createTempWorkDir(root, opts.prefix), root };
536
637
  } catch (err) {
537
- const fallback = os4.tmpdir();
538
- if (requested || isOutOfSpaceError(err) || path4.resolve(fallback) === path4.resolve(root)) throw err;
539
- const reason = err instanceof Error ? err.message.split("\n")[0] : String(err);
638
+ const fallback = os5.tmpdir();
639
+ if (requested || isOutOfSpaceError(err) || path5.resolve(fallback) === path5.resolve(root)) throw err;
640
+ const reason = err instanceof Error ? err.message.split(/\r?\n/)[0] : String(err);
540
641
  (opts.logger ?? noopLogger).warn(`${reason} Falling back to '${fallback}'.`);
541
642
  return { dir: createTempWorkDir(fallback, opts.prefix), root: fallback };
542
643
  }
543
644
  }
544
645
  function nearestExisting(dir) {
545
- let current = path4.resolve(dir);
646
+ let current = path5.resolve(dir);
546
647
  for (; ; ) {
547
- if (fs3.existsSync(current)) return current;
548
- const parent = path4.dirname(current);
648
+ if (fs4.existsSync(current)) return current;
649
+ const parent = path5.dirname(current);
549
650
  if (parent === current) return null;
550
651
  current = parent;
551
652
  }
@@ -554,7 +655,7 @@ function getFreeSpaceBytes(dir) {
554
655
  const target = nearestExisting(dir);
555
656
  if (!target) return null;
556
657
  try {
557
- const stats = fs3.statfsSync(target);
658
+ const stats = fs4.statfsSync(target);
558
659
  return Number(stats.bsize) * Number(stats.bavail);
559
660
  } catch {
560
661
  return null;
@@ -564,7 +665,7 @@ function isRamBackedPath(dir) {
564
665
  const target = nearestExisting(dir);
565
666
  if (!target) return false;
566
667
  try {
567
- const type = Number(fs3.statfsSync(target).type);
668
+ const type = Number(fs4.statfsSync(target).type);
568
669
  return type === TMPFS_MAGIC || type === RAMFS_MAGIC;
569
670
  } catch {
570
671
  return false;
@@ -628,16 +729,16 @@ function errorPath(err) {
628
729
  return null;
629
730
  }
630
731
  function isInside(child, parent) {
631
- const rel = path4.relative(path4.resolve(parent), path4.resolve(child));
632
- return rel === "" || !rel.startsWith("..") && !path4.isAbsolute(rel);
732
+ const rel = path5.relative(path5.resolve(parent), path5.resolve(child));
733
+ return rel === "" || !rel.startsWith("..") && !path5.isAbsolute(rel);
633
734
  }
634
735
  function toSpaceError(err, tmpRoot, what) {
635
736
  if (err instanceof InsufficientSpaceError) return err;
636
737
  if (!isOutOfSpaceError(err)) return err;
637
738
  const failed = errorPath(err);
638
- const failedDir = failed ? path4.dirname(path4.resolve(failed)) : path4.resolve(tmpRoot);
739
+ const failedDir = failed ? path5.dirname(path5.resolve(failed)) : path5.resolve(tmpRoot);
639
740
  const inTmp = isInside(failedDir, tmpRoot);
640
- const dir = inTmp ? path4.resolve(tmpRoot) : failedDir;
741
+ const dir = inTmp ? path5.resolve(tmpRoot) : failedDir;
641
742
  return new InsufficientSpaceError(
642
743
  spaceMessage(dir, what, { freeBytes: getFreeSpaceBytes(dir), tmpDirHint: inTmp }),
643
744
  dir,
@@ -646,8 +747,8 @@ function toSpaceError(err, tmpRoot, what) {
646
747
  }
647
748
 
648
749
  // src/sdk/download.ts
649
- var fs4 = __toESM(require("fs"), 1);
650
- var path5 = __toESM(require("path"), 1);
750
+ var fs5 = __toESM(require("fs"), 1);
751
+ var path6 = __toESM(require("path"), 1);
651
752
  var crypto = __toESM(require("crypto"), 1);
652
753
  var import_node_stream = require("stream");
653
754
  var import_node_util = require("util");
@@ -657,26 +758,26 @@ var pipelineAsync = (0, import_node_util.promisify)(import_node_stream.pipeline)
657
758
  async function downloadFile(opts) {
658
759
  const { url, dest, progress, abortSignal } = opts;
659
760
  const proto = url.startsWith("https") ? https : http;
660
- return new Promise((resolve4, reject) => {
761
+ return new Promise((resolve6, reject) => {
661
762
  const s = Math.max(0, Math.min(100, opts.start ?? 0));
662
763
  const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
663
764
  let settled = false;
664
765
  const done = (err) => {
665
766
  if (settled) return;
666
767
  settled = true;
667
- err ? reject(err) : resolve4();
768
+ err ? reject(err) : resolve6();
668
769
  };
669
770
  if (abortSignal?.aborted) {
670
771
  done(new CancelledError("Download cancelled before start."));
671
772
  return;
672
773
  }
673
- const destDir = path5.dirname(path5.resolve(dest));
774
+ const destDir = path6.dirname(path6.resolve(dest));
674
775
  const what = opts.what ?? `the download of '${url}'`;
675
776
  const writeFailed = (err) => isOutOfSpaceError(err) ? toSpaceError(err, opts.tmpRoot ?? destDir, what) : new OniroError(`Error writing '${dest}': ${err instanceof Error ? err.message : String(err)}`, err);
676
- const file = fs4.createWriteStream(dest);
777
+ const file = fs5.createWriteStream(dest);
677
778
  let activeResponse;
678
779
  const failWith = (err) => {
679
- file.close(() => fs4.rm(dest, { force: true }, () => done(err)));
780
+ file.close(() => fs5.rm(dest, { force: true }, () => done(err)));
680
781
  };
681
782
  file.on("error", (err) => {
682
783
  try {
@@ -748,9 +849,9 @@ async function downloadFile(opts) {
748
849
  });
749
850
  }
750
851
  async function verifySha256(filePath, sha256Path) {
751
- const expected = fs4.readFileSync(sha256Path, "utf8").split(/\s+/)[0];
852
+ const expected = fs5.readFileSync(sha256Path, "utf8").split(/\s+/)[0];
752
853
  const hash = crypto.createHash("sha256");
753
- await pipelineAsync(fs4.createReadStream(filePath), hash);
854
+ await pipelineAsync(fs5.createReadStream(filePath), hash);
754
855
  const actual = hash.digest("hex");
755
856
  if (actual !== expected) {
756
857
  throw new ChecksumMismatchError(expected, actual);
@@ -758,9 +859,9 @@ async function verifySha256(filePath, sha256Path) {
758
859
  }
759
860
 
760
861
  // src/sdk/extract.ts
761
- var fs5 = __toESM(require("fs"), 1);
762
- var os5 = __toESM(require("os"), 1);
763
- var path6 = __toESM(require("path"), 1);
862
+ var fs6 = __toESM(require("fs"), 1);
863
+ var os6 = __toESM(require("os"), 1);
864
+ var path7 = __toESM(require("path"), 1);
764
865
  var import_node_stream2 = require("stream");
765
866
  var import_node_util2 = require("util");
766
867
  var tar = __toESM(require("tar"), 1);
@@ -777,12 +878,12 @@ async function extractZipWithProgress(opts) {
777
878
  const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
778
879
  let processed = 0;
779
880
  let lastOverall = Math.round(s);
780
- await fs5.promises.mkdir(dest, { recursive: true });
781
- const destRoot = path6.resolve(dest);
881
+ await fs6.promises.mkdir(dest, { recursive: true });
882
+ const destRoot = path7.resolve(dest);
782
883
  const safeResolveTarget = (entryName) => {
783
- const resolved = path6.resolve(destRoot, entryName);
784
- const rel = path6.relative(destRoot, resolved);
785
- if (rel.startsWith("..") || path6.isAbsolute(rel)) {
884
+ const resolved = path7.resolve(destRoot, entryName);
885
+ const rel = path7.relative(destRoot, resolved);
886
+ if (rel.startsWith("..") || path7.isAbsolute(rel)) {
786
887
  throw new OniroError(`Blocked ZIP entry with illegal path: ${entryName}`);
787
888
  }
788
889
  return resolved;
@@ -794,25 +895,25 @@ async function extractZipWithProgress(opts) {
794
895
  const attr = file.attr ? file.attr >>> 16 : 0;
795
896
  const isSymlink = "isSymbolicLink" in file ? Boolean(file.isSymbolicLink) : (attr & 61440) === 40960;
796
897
  if (file.isDirectory) {
797
- await fs5.promises.mkdir(targetPath, { recursive: true });
898
+ await fs6.promises.mkdir(targetPath, { recursive: true });
798
899
  } else if (isSymlink) {
799
- await fs5.promises.mkdir(path6.dirname(targetPath), { recursive: true });
900
+ await fs6.promises.mkdir(path7.dirname(targetPath), { recursive: true });
800
901
  const linkTargetBuffer = await zip.entryData(file);
801
902
  const linkTarget = linkTargetBuffer.toString("utf8");
802
903
  try {
803
- const isWin = os5.platform() === "win32";
804
- await fs5.promises.symlink(linkTarget, targetPath, isWin ? "file" : void 0);
904
+ const isWin = os6.platform() === "win32";
905
+ await fs6.promises.symlink(linkTarget, targetPath, isWin ? "file" : void 0);
805
906
  } catch (e) {
806
907
  logger.warn(`Failed to create symlink at ${targetPath}: ${String(e)}`);
807
908
  }
808
909
  } else {
809
- await fs5.promises.mkdir(path6.dirname(targetPath), { recursive: true });
910
+ await fs6.promises.mkdir(path7.dirname(targetPath), { recursive: true });
810
911
  const readStream = await zip.stream(file);
811
- const writeStream = fs5.createWriteStream(targetPath);
912
+ const writeStream = fs6.createWriteStream(targetPath);
812
913
  await pipelineAsync2(readStream, writeStream);
813
914
  if (attr > 0) {
814
915
  try {
815
- await fs5.promises.chmod(targetPath, attr & 511);
916
+ await fs6.promises.chmod(targetPath, attr & 511);
816
917
  } catch {
817
918
  }
818
919
  }
@@ -844,41 +945,41 @@ async function extractTarball(tarPath, dest, strip = 0) {
844
945
  }
845
946
 
846
947
  // src/sdk/list.ts
847
- var fs6 = __toESM(require("fs"), 1);
848
- var path7 = __toESM(require("path"), 1);
948
+ var fs7 = __toESM(require("fs"), 1);
949
+ var path8 = __toESM(require("path"), 1);
849
950
  function getSupportedSdksForUi(config) {
850
- const base = path7.join(getSdkRootDir(config), getOsFolder());
951
+ const base = path8.join(getSdkRootDir(config), getOsFolder());
851
952
  return ALL_SDKS.map((sdk) => ({
852
953
  version: sdk.version,
853
954
  api: sdk.api,
854
- installed: fs6.existsSync(path7.join(base, sdk.api))
955
+ installed: fs7.existsSync(path8.join(base, sdk.api))
855
956
  })).sort((a, b) => Number(b.api) - Number(a.api));
856
957
  }
857
958
  function getInstalledSdks(config) {
858
959
  const sdkRoot = getSdkRootDir(config);
859
960
  const versions = /* @__PURE__ */ new Set();
860
- if (!fs6.existsSync(sdkRoot)) return [];
961
+ if (!fs7.existsSync(sdkRoot)) return [];
861
962
  for (const osFolder of ["linux", "darwin", "windows"]) {
862
- const osPath = path7.join(sdkRoot, osFolder);
863
- if (!fs6.existsSync(osPath) || !fs6.statSync(osPath).isDirectory()) continue;
864
- for (const api of fs6.readdirSync(osPath)) {
865
- const apiPath = path7.join(osPath, api);
866
- if (fs6.statSync(apiPath).isDirectory()) versions.add(api);
963
+ const osPath = path8.join(sdkRoot, osFolder);
964
+ if (!fs7.existsSync(osPath) || !fs7.statSync(osPath).isDirectory()) continue;
965
+ for (const api of fs7.readdirSync(osPath)) {
966
+ const apiPath = path8.join(osPath, api);
967
+ if (fs7.statSync(apiPath).isDirectory()) versions.add(api);
867
968
  }
868
969
  }
869
970
  return ALL_SDKS.filter((sdk) => versions.has(sdk.api)).map((sdk) => sdk.version);
870
971
  }
871
972
 
872
973
  // src/sdk/remove.ts
873
- var fs7 = __toESM(require("fs"), 1);
874
- var path8 = __toESM(require("path"), 1);
974
+ var fs8 = __toESM(require("fs"), 1);
975
+ var path9 = __toESM(require("path"), 1);
875
976
  function removeSdk(config, api) {
876
977
  const sdkRoot = getSdkRootDir(config);
877
978
  let removed = false;
878
979
  for (const osFolder of ["linux", "darwin", "windows"]) {
879
- const sdkPath = path8.join(sdkRoot, osFolder, api);
880
- if (fs7.existsSync(sdkPath)) {
881
- fs7.rmSync(sdkPath, { recursive: true, force: true });
980
+ const sdkPath = path9.join(sdkRoot, osFolder, api);
981
+ if (fs8.existsSync(sdkPath)) {
982
+ fs8.rmSync(sdkPath, { recursive: true, force: true });
882
983
  removed = true;
883
984
  }
884
985
  }
@@ -886,18 +987,18 @@ function removeSdk(config, api) {
886
987
  }
887
988
 
888
989
  // src/sdk/install.ts
889
- var fs9 = __toESM(require("fs"), 1);
890
- var path9 = __toESM(require("path"), 1);
990
+ var fs10 = __toESM(require("fs"), 1);
991
+ var path10 = __toESM(require("path"), 1);
891
992
 
892
993
  // src/sdk/move.ts
893
- var fs8 = __toESM(require("fs"), 1);
994
+ var fs9 = __toESM(require("fs"), 1);
894
995
  function movePath(src, dest) {
895
996
  try {
896
- fs8.renameSync(src, dest);
997
+ fs9.renameSync(src, dest);
897
998
  } catch (err) {
898
999
  if (err.code !== "EXDEV") throw err;
899
- fs8.cpSync(src, dest, { recursive: true });
900
- fs8.rmSync(src, { recursive: true, force: true });
1000
+ fs9.cpSync(src, dest, { recursive: true });
1001
+ fs9.rmSync(src, { recursive: true, force: true });
901
1002
  }
902
1003
  }
903
1004
 
@@ -918,12 +1019,12 @@ async function downloadAndInstallSdk(opts) {
918
1019
  prefix: "oniro-sdk-",
919
1020
  logger
920
1021
  });
921
- const tarPath = path9.join(tmpDir, filename);
922
- const sha256Path = path9.join(tmpDir, `${filename}.sha256`);
923
- const extractDir = path9.join(tmpDir, "extract");
924
- fs9.mkdirSync(extractDir);
925
- const sdkInstallDir = path9.join(sdkRoot, osFolder, api);
926
- fs9.mkdirSync(path9.dirname(sdkInstallDir), { recursive: true });
1022
+ const tarPath = path10.join(tmpDir, filename);
1023
+ const sha256Path = path10.join(tmpDir, `${filename}.sha256`);
1024
+ const extractDir = path10.join(tmpDir, "extract");
1025
+ fs10.mkdirSync(extractDir);
1026
+ const sdkInstallDir = path10.join(sdkRoot, osFolder, api);
1027
+ fs10.mkdirSync(path10.dirname(sdkInstallDir), { recursive: true });
927
1028
  const checkCancelled = () => {
928
1029
  if (abortSignal?.aborted) throw new CancelledError();
929
1030
  };
@@ -940,13 +1041,13 @@ async function downloadAndInstallSdk(opts) {
940
1041
  await extractTarball(tarPath, extractDir, strip);
941
1042
  progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 10 });
942
1043
  checkCancelled();
943
- const osContentPath = path9.join(extractDir, osFolder);
944
- if (!fs9.existsSync(osContentPath)) {
1044
+ const osContentPath = path10.join(extractDir, osFolder);
1045
+ if (!fs10.existsSync(osContentPath)) {
945
1046
  throw new OniroError(
946
1047
  `Expected folder '${osFolder}' not found in extracted SDK. Tarball layout may have changed.`
947
1048
  );
948
1049
  }
949
- const zipFiles = fs9.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
1050
+ const zipFiles = fs10.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
950
1051
  const componentsStart = 60;
951
1052
  const componentsBudget = 35;
952
1053
  const n = zipFiles.length;
@@ -961,16 +1062,16 @@ async function downloadAndInstallSdk(opts) {
961
1062
  for (const zipFile of zipFiles) {
962
1063
  checkCancelled();
963
1064
  logger.info(`Extracting component ${zipFile}`);
964
- const zipPath = path9.join(osContentPath, zipFile);
1065
+ const zipPath = path10.join(osContentPath, zipFile);
965
1066
  const thisBudget = base + (rem > 0 ? 1 : 0);
966
1067
  if (rem > 0) rem--;
967
1068
  await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
968
1069
  cursor += thisBudget;
969
- fs9.unlinkSync(zipPath);
1070
+ fs10.unlinkSync(zipPath);
970
1071
  }
971
1072
  progress?.report({ message: "Finalizing installation...", increment: 0 });
972
- if (fs9.existsSync(sdkInstallDir)) {
973
- fs9.rmSync(sdkInstallDir, { recursive: true, force: true });
1073
+ if (fs10.existsSync(sdkInstallDir)) {
1074
+ fs10.rmSync(sdkInstallDir, { recursive: true, force: true });
974
1075
  }
975
1076
  movePath(osContentPath, sdkInstallDir);
976
1077
  progress?.report({ message: "Finalizing installation...", increment: 3 });
@@ -986,13 +1087,13 @@ async function downloadAndInstallSdk(opts) {
986
1087
  }
987
1088
 
988
1089
  // src/cmdTools/install.ts
989
- var fs10 = __toESM(require("fs"), 1);
990
- var os6 = __toESM(require("os"), 1);
991
- var path11 = __toESM(require("path"), 1);
1090
+ var fs11 = __toESM(require("fs"), 1);
1091
+ var os7 = __toESM(require("os"), 1);
1092
+ var path12 = __toESM(require("path"), 1);
992
1093
  var import_node_child_process2 = require("child_process");
993
1094
 
994
1095
  // src/hdc/spawnCompat.ts
995
- var path10 = __toESM(require("path"), 1);
1096
+ var path11 = __toESM(require("path"), 1);
996
1097
  var import_node_child_process = require("child_process");
997
1098
  var WINDOWS_BATCH_RE = /\.(bat|cmd)$/i;
998
1099
  var CMD_META_RE = /([()[\]%!^"`<>&|;, *?])/g;
@@ -1016,7 +1117,7 @@ function escapeCmdCommand(command) {
1016
1117
  function buildCmdWrapper(command, args) {
1017
1118
  assertCmdSafe(command, `command ${command}`);
1018
1119
  args.forEach((a, i) => assertCmdSafe(a, `argument ${i} (${a})`));
1019
- const normalized = path10.normalize(command);
1120
+ const normalized = path11.normalize(command);
1020
1121
  const double = CMD_SHIM_RE.test(normalized);
1021
1122
  const line = [escapeCmdCommand(normalized), ...args.map((a) => escapeCmdArgument(a, double))].join(" ");
1022
1123
  return {
@@ -1064,7 +1165,7 @@ function killProcessTree(child) {
1064
1165
  }
1065
1166
 
1066
1167
  // src/cmdTools/install.ts
1067
- function getCmdToolsDownloadUrl(config, platform7 = os6.platform()) {
1168
+ function getCmdToolsDownloadUrl(config, platform7 = os7.platform()) {
1068
1169
  if (platform7 === "linux") {
1069
1170
  return config.get("cmdToolsUrlLinux", defaultPaths.cmdToolsUrlLinux);
1070
1171
  }
@@ -1090,16 +1191,16 @@ function getCmdToolsDownloadUrl(config, platform7 = os6.platform()) {
1090
1191
  }
1091
1192
  function findCmdToolsSourceDir(extractPath) {
1092
1193
  const known = [
1093
- path11.join(extractPath, "command-line-tools"),
1094
- path11.join(extractPath, "oh-command-line-tools"),
1095
- path11.join(extractPath, "commandline-tools")
1194
+ path12.join(extractPath, "command-line-tools"),
1195
+ path12.join(extractPath, "oh-command-line-tools"),
1196
+ path12.join(extractPath, "commandline-tools")
1096
1197
  ];
1097
1198
  for (const c of known) {
1098
- if (fs10.existsSync(c)) return c;
1199
+ if (fs11.existsSync(c)) return c;
1099
1200
  }
1100
- const entries = fs10.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path11.join(extractPath, e.name));
1201
+ const entries = fs11.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path12.join(extractPath, e.name));
1101
1202
  for (const dir of entries) {
1102
- if (fs10.existsSync(path11.join(dir, "bin"))) return dir;
1203
+ if (fs11.existsSync(path12.join(dir, "bin"))) return dir;
1103
1204
  }
1104
1205
  throw new OniroError("Could not locate command line tools folder in the extracted archive.");
1105
1206
  }
@@ -1111,12 +1212,12 @@ async function installCmdTools(opts) {
1111
1212
  const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
1112
1213
  config,
1113
1214
  override: opts.tmpDir,
1114
- installRoot: path11.dirname(CMD_PATH),
1215
+ installRoot: path12.dirname(CMD_PATH),
1115
1216
  prefix: "oniro-cmdtools-",
1116
1217
  logger
1117
1218
  });
1118
- const zipPath = opts.localZipPath ?? path11.join(tmpDir, "oh-command-line-tools.zip");
1119
- const extractPath = path11.join(tmpDir, "oh-command-line-tools");
1219
+ const zipPath = opts.localZipPath ?? path12.join(tmpDir, "oh-command-line-tools.zip");
1220
+ const extractPath = path12.join(tmpDir, "oh-command-line-tools");
1120
1221
  try {
1121
1222
  if (!opts.localZipPath) {
1122
1223
  const url = getCmdToolsDownloadUrl(config);
@@ -1125,22 +1226,22 @@ async function installCmdTools(opts) {
1125
1226
  }
1126
1227
  progress?.report({ message: "Extracting tools...", increment: 0 });
1127
1228
  await extractZipWithProgress({ zipPath, dest: extractPath, progress, start: 50, range: 45, logger });
1128
- fs10.mkdirSync(CMD_PATH, { recursive: true });
1229
+ fs11.mkdirSync(CMD_PATH, { recursive: true });
1129
1230
  const srcDir = findCmdToolsSourceDir(extractPath);
1130
- for (const entry of fs10.readdirSync(srcDir)) {
1131
- const src = path11.join(srcDir, entry);
1132
- const dest = path11.join(CMD_PATH, entry);
1133
- if (fs10.statSync(src).isDirectory()) {
1134
- if (fs10.existsSync(dest)) fs10.rmSync(dest, { recursive: true, force: true });
1231
+ for (const entry of fs11.readdirSync(srcDir)) {
1232
+ const src = path12.join(srcDir, entry);
1233
+ const dest = path12.join(CMD_PATH, entry);
1234
+ if (fs11.statSync(src).isDirectory()) {
1235
+ if (fs11.existsSync(dest)) fs11.rmSync(dest, { recursive: true, force: true });
1135
1236
  movePath(src, dest);
1136
1237
  } else {
1137
- fs10.copyFileSync(src, dest);
1238
+ fs11.copyFileSync(src, dest);
1138
1239
  }
1139
1240
  }
1140
- const binDir = path11.join(CMD_PATH, "bin");
1141
- if (fs10.existsSync(binDir) && os6.platform() !== "win32") {
1142
- for (const file of fs10.readdirSync(binDir)) {
1143
- fs10.chmodSync(path11.join(binDir, file), 493);
1241
+ const binDir = path12.join(CMD_PATH, "bin");
1242
+ if (fs11.existsSync(binDir) && os7.platform() !== "win32") {
1243
+ for (const file of fs11.readdirSync(binDir)) {
1244
+ fs11.chmodSync(path12.join(binDir, file), 493);
1144
1245
  }
1145
1246
  }
1146
1247
  progress?.report({ message: "Finalizing installation...", increment: 5 });
@@ -1153,16 +1254,16 @@ async function installCmdTools(opts) {
1153
1254
  }
1154
1255
  function getCmdToolsStatus(config) {
1155
1256
  const cmdPath = getCmdToolsPath(config);
1156
- const binDir = path11.join(cmdPath, "bin");
1157
- const candidates = os6.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
1158
- const bin = candidates.map((c) => path11.join(binDir, c)).find((p) => fs10.existsSync(p));
1257
+ const binDir = path12.join(cmdPath, "bin");
1258
+ const candidates = os7.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
1259
+ const bin = candidates.map((c) => path12.join(binDir, c)).find((p) => fs11.existsSync(p));
1159
1260
  if (!bin) {
1160
1261
  return { installed: false, status: "Not installed" };
1161
1262
  }
1162
1263
  try {
1163
- const versionFile = path11.join(cmdPath, "version.txt");
1164
- if (fs10.existsSync(versionFile)) {
1165
- const content = fs10.readFileSync(versionFile, "utf8");
1264
+ const versionFile = path12.join(cmdPath, "version.txt");
1265
+ if (fs11.existsSync(versionFile)) {
1266
+ const content = fs11.readFileSync(versionFile, "utf8");
1166
1267
  for (const line of content.split(/\r?\n/)) {
1167
1268
  const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
1168
1269
  if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
@@ -1189,14 +1290,14 @@ function isCmdToolsInstalled(config) {
1189
1290
  }
1190
1291
  function removeCmdTools(config) {
1191
1292
  const cmdPath = getCmdToolsPath(config);
1192
- if (fs10.existsSync(cmdPath)) {
1193
- fs10.rmSync(cmdPath, { recursive: true, force: true });
1293
+ if (fs11.existsSync(cmdPath)) {
1294
+ fs11.rmSync(cmdPath, { recursive: true, force: true });
1194
1295
  }
1195
1296
  }
1196
1297
 
1197
1298
  // src/sign/encryptKey.ts
1198
- var fs11 = __toESM(require("fs"), 1);
1199
- var path12 = __toESM(require("path"), 1);
1299
+ var fs12 = __toESM(require("fs"), 1);
1300
+ var path13 = __toESM(require("path"), 1);
1200
1301
  var crypto2 = __toESM(require("crypto"), 1);
1201
1302
  var DAEMON_ROOT_KEY_COMPONENT_LENGTH = 16;
1202
1303
  var DAEMON_SALT_KEY_LENGTH = 16;
@@ -1268,33 +1369,33 @@ function getRootKey(fdComponents, salt) {
1268
1369
  function createAndStoreKey(dir, keyLength) {
1269
1370
  const key = crypto2.randomBytes(keyLength);
1270
1371
  const hash = crypto2.createHash("sha256").update(key).digest("hex");
1271
- const filePath = path12.join(dir, hash);
1272
- fs11.writeFileSync(filePath, key);
1273
- fs11.chmodSync(filePath, KEY_FILE_PERMISSIONS);
1372
+ const filePath = path13.join(dir, hash);
1373
+ fs12.writeFileSync(filePath, key);
1374
+ fs12.chmodSync(filePath, KEY_FILE_PERMISSIONS);
1274
1375
  return key;
1275
1376
  }
1276
1377
  function createAndStoreEnKey(rootKey, ceDir) {
1277
1378
  const workKey = crypto2.randomBytes(DAEMON_WORK_KEY_LENGTH);
1278
1379
  const encrypted = encrypt(rootKey, workKey);
1279
1380
  const hash = crypto2.createHash("sha256").update(encrypted).digest("hex");
1280
- const filePath = path12.join(ceDir, hash);
1281
- fs11.writeFileSync(filePath, encrypted);
1282
- fs11.chmodSync(filePath, KEY_FILE_PERMISSIONS);
1381
+ const filePath = path13.join(ceDir, hash);
1382
+ fs12.writeFileSync(filePath, encrypted);
1383
+ fs12.chmodSync(filePath, KEY_FILE_PERMISSIONS);
1283
1384
  }
1284
1385
  function createMaterial(materialPath) {
1285
- fs11.mkdirSync(materialPath, { recursive: true });
1286
- fs11.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
1287
- const fdDir = path12.join(materialPath, "fd");
1288
- const acDir = path12.join(materialPath, "ac");
1289
- const ceDir = path12.join(materialPath, "ce");
1290
- fs11.mkdirSync(fdDir, { recursive: true });
1291
- fs11.mkdirSync(acDir, { recursive: true });
1292
- fs11.mkdirSync(ceDir, { recursive: true });
1386
+ fs12.mkdirSync(materialPath, { recursive: true });
1387
+ fs12.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
1388
+ const fdDir = path13.join(materialPath, "fd");
1389
+ const acDir = path13.join(materialPath, "ac");
1390
+ const ceDir = path13.join(materialPath, "ce");
1391
+ fs12.mkdirSync(fdDir, { recursive: true });
1392
+ fs12.mkdirSync(acDir, { recursive: true });
1393
+ fs12.mkdirSync(ceDir, { recursive: true });
1293
1394
  const fdSubDirs = ["0", "1", "2"];
1294
1395
  const fdComponents = [];
1295
1396
  for (const sub of fdSubDirs) {
1296
- const subDir = path12.join(fdDir, sub);
1297
- fs11.mkdirSync(subDir, { recursive: true });
1397
+ const subDir = path13.join(fdDir, sub);
1398
+ fs12.mkdirSync(subDir, { recursive: true });
1298
1399
  const comp = createAndStoreKey(subDir, DAEMON_ROOT_KEY_COMPONENT_LENGTH);
1299
1400
  fdComponents.push(comp);
1300
1401
  }
@@ -1303,24 +1404,24 @@ function createMaterial(materialPath) {
1303
1404
  createAndStoreEnKey(rootKey, ceDir);
1304
1405
  }
1305
1406
  function readSingleFile(dir) {
1306
- if (!fs11.existsSync(dir) || !fs11.statSync(dir).isDirectory()) {
1407
+ if (!fs12.existsSync(dir) || !fs12.statSync(dir).isDirectory()) {
1307
1408
  throw new Error(`Missing signing material directory: ${dir}`);
1308
1409
  }
1309
- const files = fs11.readdirSync(dir).filter((f) => f !== ".DS_Store");
1410
+ const files = fs12.readdirSync(dir).filter((f) => f !== ".DS_Store");
1310
1411
  if (files.length !== 1) {
1311
1412
  throw new Error(`Signing material in ${dir} is illegal (expected exactly one file).`);
1312
1413
  }
1313
- return fs11.readFileSync(path12.join(dir, files[0]));
1414
+ return fs12.readFileSync(path13.join(dir, files[0]));
1314
1415
  }
1315
1416
  function getKey(materialPath) {
1316
- if (!fs11.existsSync(materialPath) || !fs11.statSync(materialPath).isDirectory()) {
1417
+ if (!fs12.existsSync(materialPath) || !fs12.statSync(materialPath).isDirectory()) {
1317
1418
  throw new Error("Material directory does not exist.");
1318
1419
  }
1319
- const fdDir = path12.join(materialPath, "fd");
1320
- const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(path12.join(fdDir, sub)));
1321
- const salt = readSingleFile(path12.join(materialPath, "ac"));
1420
+ const fdDir = path13.join(materialPath, "fd");
1421
+ const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(path13.join(fdDir, sub)));
1422
+ const salt = readSingleFile(path13.join(materialPath, "ac"));
1322
1423
  const rootKey = getRootKey(fdComponents, salt);
1323
- const workMaterial = readSingleFile(path12.join(materialPath, "ce"));
1424
+ const workMaterial = readSingleFile(path13.join(materialPath, "ce"));
1324
1425
  return decrypt(rootKey, workMaterial);
1325
1426
  }
1326
1427
  function encryptPwd(password, materialPath) {
@@ -1335,8 +1436,8 @@ function decryptPwd(encryptedHex, materialPath) {
1335
1436
  }
1336
1437
 
1337
1438
  // src/sign/generateSigningConfigs.ts
1338
- var fs12 = __toESM(require("fs"), 1);
1339
- var path13 = __toESM(require("path"), 1);
1439
+ var fs13 = __toESM(require("fs"), 1);
1440
+ var path14 = __toESM(require("path"), 1);
1340
1441
  var import_node_child_process3 = require("child_process");
1341
1442
  var import_json52 = __toESM(require("json5"), 1);
1342
1443
 
@@ -1406,17 +1507,17 @@ var SIGNING_KIND_CONFIG = {
1406
1507
  };
1407
1508
  function copyFilesToProject(projectDir, paths, kind, logger, applicationCertPath) {
1408
1509
  logger.info("[sign] Copying signing material into project...");
1409
- const signaturesDir = path13.join(projectDir, "signatures");
1410
- fs12.mkdirSync(signaturesDir, { recursive: true });
1411
- fs12.copyFileSync(paths.keystore, path13.join(signaturesDir, "OpenHarmony.p12"));
1412
- fs12.copyFileSync(paths.profileCert, path13.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
1413
- fs12.copyFileSync(paths.unsignedProfileTemplate, path13.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
1510
+ const signaturesDir = path14.join(projectDir, "signatures");
1511
+ fs13.mkdirSync(signaturesDir, { recursive: true });
1512
+ fs13.copyFileSync(paths.keystore, path14.join(signaturesDir, "OpenHarmony.p12"));
1513
+ fs13.copyFileSync(paths.profileCert, path14.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
1514
+ fs13.copyFileSync(paths.unsignedProfileTemplate, path14.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
1414
1515
  if (kind === "application-release") {
1415
- const dest = path13.join(signaturesDir, "OpenHarmonyApplication.cer");
1516
+ const dest = path14.join(signaturesDir, "OpenHarmonyApplication.cer");
1416
1517
  if (applicationCertPath) {
1417
- fs12.copyFileSync(applicationCertPath, dest);
1518
+ fs13.copyFileSync(applicationCertPath, dest);
1418
1519
  } else {
1419
- fs12.writeFileSync(dest, OPEN_HARMONY_APPLICATION_CERT);
1520
+ fs13.writeFileSync(dest, OPEN_HARMONY_APPLICATION_CERT);
1420
1521
  }
1421
1522
  }
1422
1523
  }
@@ -1425,7 +1526,7 @@ function resolveAppFeature(apl, override) {
1425
1526
  return apl === "normal" ? "hos_normal_app" : "hos_system_app";
1426
1527
  }
1427
1528
  function extractLeafCertificate(chainPath) {
1428
- const certContent = fs12.readFileSync(chainPath, "utf-8");
1529
+ const certContent = fs13.readFileSync(chainPath, "utf-8");
1429
1530
  const parts = certContent.split("-----END CERTIFICATE-----");
1430
1531
  if (parts.length < 3) {
1431
1532
  throw new OniroError(`${chainPath} does not contain enough certificates.`);
@@ -1435,20 +1536,20 @@ function extractLeafCertificate(chainPath) {
1435
1536
  function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}) {
1436
1537
  const kind = options.kind ?? pickSigningKind(apl);
1437
1538
  logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature}, kind=${kind})...`);
1438
- const appJsonPath = path13.join(projectDir, "AppScope/app.json5");
1439
- const profileTemplatePath = path13.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
1440
- if (!fs12.existsSync(appJsonPath)) {
1539
+ const appJsonPath = path14.join(projectDir, "AppScope/app.json5");
1540
+ const profileTemplatePath = path14.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
1541
+ if (!fs13.existsSync(appJsonPath)) {
1441
1542
  throw new OniroError(`${appJsonPath} does not exist.`);
1442
1543
  }
1443
1544
  let appJson;
1444
1545
  try {
1445
- appJson = import_json52.default.parse(fs12.readFileSync(appJsonPath, "utf-8"));
1546
+ appJson = import_json52.default.parse(fs13.readFileSync(appJsonPath, "utf-8"));
1446
1547
  } catch (e) {
1447
1548
  throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
1448
1549
  }
1449
1550
  let profileTemplate;
1450
1551
  try {
1451
- profileTemplate = JSON.parse(fs12.readFileSync(profileTemplatePath, "utf-8"));
1552
+ profileTemplate = JSON.parse(fs13.readFileSync(profileTemplatePath, "utf-8"));
1452
1553
  } catch (e) {
1453
1554
  throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
1454
1555
  }
@@ -1461,7 +1562,7 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
1461
1562
  profileTemplate["bundle-info"]["bundle-name"] = appJson.app.bundleName;
1462
1563
  profileTemplate["bundle-info"]["apl"] = apl;
1463
1564
  profileTemplate["bundle-info"]["app-feature"] = appFeature;
1464
- const distCertChainPath = path13.join(
1565
+ const distCertChainPath = path14.join(
1465
1566
  projectDir,
1466
1567
  "signatures",
1467
1568
  SIGNING_KIND_CONFIG[kind].certBasename
@@ -1471,13 +1572,13 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
1471
1572
  profileTemplate.acls = profileTemplate.acls ?? {};
1472
1573
  profileTemplate.acls["allowed-acls"] = options.acls;
1473
1574
  }
1474
- fs12.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
1575
+ fs13.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
1475
1576
  }
1476
1577
  function generateP7bFile(projectDir, paths, logger, passwords) {
1477
1578
  logger.info("[sign] Generating P7b profile via hap-sign-tool...");
1478
- const signaturesDir = path13.join(projectDir, "signatures");
1479
- const profileTemplatePath = path13.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
1480
- const outputProfilePath = path13.join(signaturesDir, "app1-profile.p7b");
1579
+ const signaturesDir = path14.join(projectDir, "signatures");
1580
+ const profileTemplatePath = path14.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
1581
+ const outputProfilePath = path14.join(signaturesDir, "app1-profile.p7b");
1481
1582
  const args = [
1482
1583
  "-jar",
1483
1584
  paths.signTool,
@@ -1511,11 +1612,11 @@ function generateP7bFile(projectDir, paths, logger, passwords) {
1511
1612
  }
1512
1613
  }
1513
1614
  function detectSigningConfigNames(projectDir) {
1514
- const buildProfilePath = path13.join(projectDir, "build-profile.json5");
1515
- if (!fs12.existsSync(buildProfilePath)) return ["default"];
1615
+ const buildProfilePath = path14.join(projectDir, "build-profile.json5");
1616
+ if (!fs13.existsSync(buildProfilePath)) return ["default"];
1516
1617
  let parsed;
1517
1618
  try {
1518
- parsed = import_json52.default.parse(fs12.readFileSync(buildProfilePath, "utf-8"));
1619
+ parsed = import_json52.default.parse(fs13.readFileSync(buildProfilePath, "utf-8"));
1519
1620
  } catch {
1520
1621
  return ["default"];
1521
1622
  }
@@ -1532,14 +1633,14 @@ function detectSigningConfigNames(projectDir) {
1532
1633
  }
1533
1634
  function updateBuildProfile(projectDir, kind, logger, passwords) {
1534
1635
  logger.info("[sign] Writing signing configs into build-profile.json5...");
1535
- const materialDir = path13.join(projectDir, "signatures", "material");
1536
- const buildProfilePath = path13.join(projectDir, "build-profile.json5");
1636
+ const materialDir = path14.join(projectDir, "signatures", "material");
1637
+ const buildProfilePath = path14.join(projectDir, "build-profile.json5");
1537
1638
  const encryptedStorePassword = encryptPwd(passwords.store, materialDir);
1538
1639
  const encryptedKeyPassword = encryptPwd(passwords.key, materialDir);
1539
1640
  let buildProfile = { app: {} };
1540
- if (fs12.existsSync(buildProfilePath)) {
1641
+ if (fs13.existsSync(buildProfilePath)) {
1541
1642
  try {
1542
- buildProfile = import_json52.default.parse(fs12.readFileSync(buildProfilePath, "utf-8"));
1643
+ buildProfile = import_json52.default.parse(fs13.readFileSync(buildProfilePath, "utf-8"));
1543
1644
  } catch (e) {
1544
1645
  throw new OniroError(`Error parsing ${buildProfilePath}: ${e.message}`, e);
1545
1646
  }
@@ -1560,13 +1661,13 @@ function updateBuildProfile(projectDir, kind, logger, passwords) {
1560
1661
  storeFile: "./signatures/OpenHarmony.p12"
1561
1662
  }
1562
1663
  }));
1563
- fs12.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1664
+ fs13.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1564
1665
  }
1565
1666
  function prepareMaterialDirectory(projectDir, logger) {
1566
1667
  logger.info("[sign] Generating fresh signing material...");
1567
- const materialDir = path13.join(projectDir, "signatures", "material");
1568
- if (fs12.existsSync(materialDir)) {
1569
- fs12.rmSync(materialDir, { recursive: true, force: true });
1668
+ const materialDir = path14.join(projectDir, "signatures", "material");
1669
+ if (fs13.existsSync(materialDir)) {
1670
+ fs13.rmSync(materialDir, { recursive: true, force: true });
1570
1671
  }
1571
1672
  createMaterial(materialDir);
1572
1673
  }
@@ -1589,15 +1690,15 @@ function generateSigningConfigs(options) {
1589
1690
  "Could not detect project SDK version (compileSdkVersion missing in build-profile.json5)."
1590
1691
  );
1591
1692
  }
1592
- const sdkPath = path13.join(sdkHome, String(sdkVersion));
1593
- if (!fs12.existsSync(sdkPath)) {
1693
+ const sdkPath = path14.join(sdkHome, String(sdkVersion));
1694
+ if (!fs13.existsSync(sdkPath)) {
1594
1695
  throw new OniroError(`SDK path does not exist: ${sdkPath}`);
1595
1696
  }
1596
1697
  const paths = {
1597
- signTool: path13.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
1598
- keystore: path13.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
1599
- profileCert: path13.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
1600
- unsignedProfileTemplate: path13.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
1698
+ signTool: path14.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
1699
+ keystore: path14.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
1700
+ profileCert: path14.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
1701
+ unsignedProfileTemplate: path14.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
1601
1702
  };
1602
1703
  const kind = pickSigningKind(apl);
1603
1704
  const passwords = options.passwords ?? DEFAULT_PASSWORDS;
@@ -1611,8 +1712,8 @@ function generateSigningConfigs(options) {
1611
1712
  }
1612
1713
 
1613
1714
  // src/emulator/install.ts
1614
- var fs13 = __toESM(require("fs"), 1);
1615
- var path14 = __toESM(require("path"), 1);
1715
+ var fs14 = __toESM(require("fs"), 1);
1716
+ var path15 = __toESM(require("path"), 1);
1616
1717
  async function installEmulator(opts) {
1617
1718
  const { config, progress, abortSignal } = opts;
1618
1719
  const logger = opts.logger ?? noopLogger;
@@ -1622,20 +1723,20 @@ async function installEmulator(opts) {
1622
1723
  const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
1623
1724
  config,
1624
1725
  override: opts.tmpDir,
1625
- installRoot: path14.dirname(emulatorDir),
1726
+ installRoot: path15.dirname(emulatorDir),
1626
1727
  prefix: "oniro-emulator-",
1627
1728
  logger
1628
1729
  });
1629
- const tmpZip = path14.join(tmpDir, "oniro_emulator.zip");
1730
+ const tmpZip = path15.join(tmpDir, "oniro_emulator.zip");
1630
1731
  try {
1631
- fs13.mkdirSync(emulatorDir, { recursive: true });
1732
+ fs14.mkdirSync(emulatorDir, { recursive: true });
1632
1733
  progress?.report({ message: "Downloading emulator...", increment: 0 });
1633
1734
  await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50, what, tmpRoot });
1634
1735
  progress?.report({ message: "Extracting emulator...", increment: 0 });
1635
1736
  await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
1636
- const runSh = path14.join(emulatorDir, "images", "run.sh");
1637
- if (fs13.existsSync(runSh)) {
1638
- fs13.chmodSync(runSh, 493);
1737
+ const runSh = path15.join(emulatorDir, "images", "run.sh");
1738
+ if (fs14.existsSync(runSh)) {
1739
+ fs14.chmodSync(runSh, 493);
1639
1740
  }
1640
1741
  progress?.report({ message: "Finalizing installation...", increment: 5 });
1641
1742
  } catch (err) {
@@ -1645,24 +1746,24 @@ async function installEmulator(opts) {
1645
1746
  }
1646
1747
  }
1647
1748
  function isEmulatorInstalled(config) {
1648
- const imagesDir = path14.join(getEmulatorDir(config), "images");
1649
- return fs13.existsSync(path14.join(imagesDir, "run.sh")) || fs13.existsSync(path14.join(imagesDir, "run.bat"));
1749
+ const imagesDir = path15.join(getEmulatorDir(config), "images");
1750
+ return fs14.existsSync(path15.join(imagesDir, "run.sh")) || fs14.existsSync(path15.join(imagesDir, "run.bat"));
1650
1751
  }
1651
1752
  function removeEmulator(config) {
1652
1753
  const emulatorDir = getEmulatorDir(config);
1653
- if (fs13.existsSync(emulatorDir)) {
1654
- fs13.rmSync(emulatorDir, { recursive: true, force: true });
1754
+ if (fs14.existsSync(emulatorDir)) {
1755
+ fs14.rmSync(emulatorDir, { recursive: true, force: true });
1655
1756
  }
1656
1757
  }
1657
1758
 
1658
1759
  // src/emulator/lifecycle.ts
1659
- var fs15 = __toESM(require("fs"), 1);
1660
- var os7 = __toESM(require("os"), 1);
1661
- var path15 = __toESM(require("path"), 1);
1760
+ var fs16 = __toESM(require("fs"), 1);
1761
+ var os8 = __toESM(require("os"), 1);
1762
+ var path16 = __toESM(require("path"), 1);
1662
1763
  var import_node_child_process4 = require("child_process");
1663
1764
 
1664
1765
  // src/hdc/exec.ts
1665
- var fs14 = __toESM(require("fs"), 1);
1766
+ var fs15 = __toESM(require("fs"), 1);
1666
1767
  function errorMessage(err) {
1667
1768
  return err instanceof Error ? err.message : String(err);
1668
1769
  }
@@ -1670,12 +1771,12 @@ function runProcess(opts) {
1670
1771
  const { command, args, cwd, env, onOutput } = opts;
1671
1772
  const timeoutMs = opts.timeoutMs ?? 6e4;
1672
1773
  const { abortSignal } = opts;
1673
- return new Promise((resolve4, reject) => {
1774
+ return new Promise((resolve6, reject) => {
1674
1775
  if (abortSignal?.aborted) {
1675
1776
  reject(new CancelledError(`Command aborted before start: ${command}`));
1676
1777
  return;
1677
1778
  }
1678
- if (needsCmdWrapper(command) && !fs14.existsSync(command)) {
1779
+ if (needsCmdWrapper(command) && !fs15.existsSync(command)) {
1679
1780
  reject(new OniroError(`Failed to spawn ${command}: ENOENT (no such file)`));
1680
1781
  return;
1681
1782
  }
@@ -1735,7 +1836,7 @@ ${stderr.trim()}` : "")
1735
1836
  );
1736
1837
  return;
1737
1838
  }
1738
- resolve4({ code: code ?? -1, stdout, stderr });
1839
+ resolve6({ code: code ?? -1, stdout, stderr });
1739
1840
  });
1740
1841
  });
1741
1842
  }
@@ -1783,7 +1884,7 @@ function ensureRemoteOk(result, command) {
1783
1884
  }
1784
1885
 
1785
1886
  // src/emulator/lifecycle.ts
1786
- var PID_FILE = path15.join(os7.tmpdir(), "oniro_emulator.pid");
1887
+ var PID_FILE = path16.join(os8.tmpdir(), "oniro_emulator.pid");
1787
1888
  async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
1788
1889
  try {
1789
1890
  await hdcExec({ config, args: ["start", "-r"], logger });
@@ -1796,24 +1897,24 @@ async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger
1796
1897
  }
1797
1898
  }
1798
1899
  function resolveLauncher(imagesPath, headless, connect) {
1799
- const runSh = path15.join(imagesPath, "run.sh");
1800
- const runBat = path15.join(imagesPath, "run.bat");
1900
+ const runSh = path16.join(imagesPath, "run.sh");
1901
+ const runBat = path16.join(imagesPath, "run.bat");
1801
1902
  const extra = [];
1802
1903
  if (headless) extra.push("--headless");
1803
1904
  if (connect) extra.push("--connect", connect);
1804
- if (os7.platform() === "win32") {
1805
- if (fs15.existsSync(runBat)) {
1905
+ if (os8.platform() === "win32") {
1906
+ if (fs16.existsSync(runBat)) {
1806
1907
  return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
1807
1908
  }
1808
- if (fs15.existsSync(runSh)) {
1909
+ if (fs16.existsSync(runSh)) {
1809
1910
  return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1810
1911
  }
1811
1912
  throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
1812
1913
  }
1813
- if (fs15.existsSync(runSh)) {
1914
+ if (fs16.existsSync(runSh)) {
1814
1915
  return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1815
1916
  }
1816
- if (fs15.existsSync(runBat)) {
1917
+ if (fs16.existsSync(runBat)) {
1817
1918
  return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
1818
1919
  }
1819
1920
  throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
@@ -1821,9 +1922,9 @@ function resolveLauncher(imagesPath, headless, connect) {
1821
1922
  async function startEmulator(opts) {
1822
1923
  const { config } = opts;
1823
1924
  const logger = opts.logger ?? noopLogger;
1824
- if (os7.platform() !== "win32" && fs15.existsSync(PID_FILE)) {
1925
+ if (os8.platform() !== "win32" && fs16.existsSync(PID_FILE)) {
1825
1926
  try {
1826
- const pid = Number(fs15.readFileSync(PID_FILE, "utf8").trim());
1927
+ const pid = Number(fs16.readFileSync(PID_FILE, "utf8").trim());
1827
1928
  if (Number.isFinite(pid) && pid > 0) {
1828
1929
  try {
1829
1930
  process.kill(pid, 0);
@@ -1839,13 +1940,13 @@ async function startEmulator(opts) {
1839
1940
  logger.warn(`Could not read PID file: ${err.message}`);
1840
1941
  }
1841
1942
  }
1842
- const imagesPath = path15.join(getEmulatorDir(config), "images");
1843
- if (!fs15.existsSync(imagesPath)) {
1943
+ const imagesPath = path16.join(getEmulatorDir(config), "images");
1944
+ if (!fs16.existsSync(imagesPath)) {
1844
1945
  throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
1845
1946
  }
1846
1947
  const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
1847
- const logPath = opts.logFile ?? (os7.platform() === "win32" ? "NUL" : "/dev/null");
1848
- const logFd = fs15.openSync(logPath, "a");
1948
+ const logPath = opts.logFile ?? (os8.platform() === "win32" ? "NUL" : "/dev/null");
1949
+ const logFd = fs16.openSync(logPath, "a");
1849
1950
  let child;
1850
1951
  try {
1851
1952
  child = (0, import_node_child_process4.spawn)(launcher.command, [...launcher.args], {
@@ -1855,19 +1956,19 @@ async function startEmulator(opts) {
1855
1956
  windowsHide: true
1856
1957
  });
1857
1958
  } finally {
1858
- fs15.closeSync(logFd);
1959
+ fs16.closeSync(logFd);
1859
1960
  }
1860
1961
  if (!child.pid) {
1861
1962
  throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
1862
1963
  }
1863
- fs15.writeFileSync(PID_FILE, String(child.pid));
1964
+ fs16.writeFileSync(PID_FILE, String(child.pid));
1864
1965
  child.unref();
1865
1966
  logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
1866
- const earlyExit = new Promise((resolve4) => {
1867
- child.once("exit", (code) => resolve4(code));
1868
- child.once("error", () => resolve4(-1));
1967
+ const earlyExit = new Promise((resolve6) => {
1968
+ child.once("exit", (code) => resolve6(code));
1969
+ child.once("error", () => resolve6(-1));
1869
1970
  });
1870
- const liveness = new Promise((resolve4) => setTimeout(() => resolve4("alive"), 2e3));
1971
+ const liveness = new Promise((resolve6) => setTimeout(() => resolve6("alive"), 2e3));
1871
1972
  const outcome = await Promise.race([earlyExit, liveness]);
1872
1973
  if (outcome !== "alive") {
1873
1974
  const tail = readTail(logPath, 4e3);
@@ -1907,24 +2008,24 @@ ${tail}` : ""}`);
1907
2008
  }
1908
2009
  function readTail(filePath, bytes) {
1909
2010
  try {
1910
- const stat = fs15.statSync(filePath);
2011
+ const stat = fs16.statSync(filePath);
1911
2012
  const start = Math.max(0, stat.size - bytes);
1912
- const fd = fs15.openSync(filePath, "r");
2013
+ const fd = fs16.openSync(filePath, "r");
1913
2014
  try {
1914
2015
  const buf = Buffer.alloc(stat.size - start);
1915
- fs15.readSync(fd, buf, 0, buf.length, start);
2016
+ fs16.readSync(fd, buf, 0, buf.length, start);
1916
2017
  return buf.toString("utf8");
1917
2018
  } finally {
1918
- fs15.closeSync(fd);
2019
+ fs16.closeSync(fd);
1919
2020
  }
1920
2021
  } catch {
1921
2022
  return "";
1922
2023
  }
1923
2024
  }
1924
2025
  async function stopEmulator(logger = noopLogger) {
1925
- if (fs15.existsSync(PID_FILE)) {
2026
+ if (fs16.existsSync(PID_FILE)) {
1926
2027
  try {
1927
- const pid = Number(fs15.readFileSync(PID_FILE, "utf8").trim());
2028
+ const pid = Number(fs16.readFileSync(PID_FILE, "utf8").trim());
1928
2029
  if (Number.isFinite(pid) && pid > 0) {
1929
2030
  try {
1930
2031
  process.kill(pid, "SIGTERM");
@@ -1937,13 +2038,13 @@ async function stopEmulator(logger = noopLogger) {
1937
2038
  logger.warn(`Could not read PID file: ${err.message}`);
1938
2039
  }
1939
2040
  }
1940
- const [killCmd, ...killArgs] = os7.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
2041
+ const [killCmd, ...killArgs] = os8.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
1941
2042
  try {
1942
2043
  await runProcess({ command: killCmd, args: killArgs, logger });
1943
2044
  } catch (err) {
1944
2045
  logger.warn(`[emulator] could not run ${killCmd}: ${err.message}`);
1945
2046
  }
1946
- fs15.rm(PID_FILE, { force: true }, (err) => {
2047
+ fs16.rm(PID_FILE, { force: true }, (err) => {
1947
2048
  if (err) logger.warn(`Could not remove PID file: ${err.message}`);
1948
2049
  });
1949
2050
  }
@@ -2066,7 +2167,7 @@ async function getDisplaySize(config, opts = {}) {
2066
2167
 
2067
2168
  // src/hdc/wait.ts
2068
2169
  function delay(ms, signal) {
2069
- return new Promise((resolve4, reject) => {
2170
+ return new Promise((resolve6, reject) => {
2070
2171
  if (signal?.aborted) {
2071
2172
  reject(new CancelledError("Wait cancelled."));
2072
2173
  return;
@@ -2081,7 +2182,7 @@ function delay(ms, signal) {
2081
2182
  };
2082
2183
  const timer = setTimeout(() => {
2083
2184
  cleanup();
2084
- resolve4();
2185
+ resolve6();
2085
2186
  }, ms);
2086
2187
  signal?.addEventListener("abort", onAbort, { once: true });
2087
2188
  });
@@ -2110,20 +2211,20 @@ async function waitForCondition(opts) {
2110
2211
  }
2111
2212
 
2112
2213
  // src/hdc/app.ts
2113
- var fs17 = __toESM(require("fs"), 1);
2114
- var path17 = __toESM(require("path"), 1);
2214
+ var fs18 = __toESM(require("fs"), 1);
2215
+ var path18 = __toESM(require("path"), 1);
2115
2216
 
2116
2217
  // src/hdc/project.ts
2117
- var fs16 = __toESM(require("fs"), 1);
2118
- var path16 = __toESM(require("path"), 1);
2218
+ var fs17 = __toESM(require("fs"), 1);
2219
+ var path17 = __toESM(require("path"), 1);
2119
2220
  var import_json53 = __toESM(require("json5"), 1);
2120
2221
  function readJson5(filePath) {
2121
- const content = fs16.readFileSync(filePath, "utf-8");
2222
+ const content = fs17.readFileSync(filePath, "utf-8");
2122
2223
  return import_json53.default.parse(content);
2123
2224
  }
2124
2225
  function getBundleName(projectDir) {
2125
- const appJsonPath = path16.join(projectDir, "AppScope", "app.json5");
2126
- if (!fs16.existsSync(appJsonPath)) {
2226
+ const appJsonPath = path17.join(projectDir, "AppScope", "app.json5");
2227
+ if (!fs17.existsSync(appJsonPath)) {
2127
2228
  throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
2128
2229
  }
2129
2230
  const appJson = readJson5(appJsonPath);
@@ -2133,8 +2234,8 @@ function getBundleName(projectDir) {
2133
2234
  return appJson.app.bundleName;
2134
2235
  }
2135
2236
  function getMainAbility(projectDir, moduleName = "entry", abilityName) {
2136
- const moduleJsonPath = path16.join(projectDir, moduleName, "src", "main", "module.json5");
2137
- if (!fs16.existsSync(moduleJsonPath)) {
2237
+ const moduleJsonPath = path17.join(projectDir, moduleName, "src", "main", "module.json5");
2238
+ if (!fs17.existsSync(moduleJsonPath)) {
2138
2239
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
2139
2240
  }
2140
2241
  const moduleJson = readJson5(moduleJsonPath);
@@ -2157,11 +2258,16 @@ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
2157
2258
  async function installApp(opts) {
2158
2259
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2159
2260
  const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
2160
- const hapPath = path17.isAbsolute(relativeHapPath) ? relativeHapPath : path17.join(opts.projectDir, relativeHapPath);
2161
- if (!fs17.existsSync(hapPath)) {
2261
+ const hapPath = path18.isAbsolute(relativeHapPath) ? relativeHapPath : path18.join(opts.projectDir, relativeHapPath);
2262
+ if (!fs18.existsSync(hapPath)) {
2162
2263
  throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
2163
2264
  }
2164
- const result = await hdcExec({ config: opts.config, args: ["install", hapPath], timeoutMs: 3e5 });
2265
+ const result = await hdcExec({
2266
+ config: opts.config,
2267
+ args: ["install", hapPath],
2268
+ deviceSerial: opts.deviceSerial,
2269
+ timeoutMs: 3e5
2270
+ });
2165
2271
  if (result.stdout.trim()) logger.info(result.stdout.trim());
2166
2272
  if (result.stderr.trim()) logger.warn(result.stderr.trim());
2167
2273
  ensureOk(result, `hdc install ${hapPath}`);
@@ -2242,7 +2348,7 @@ function listRunningProcesses(config, options = {}) {
2242
2348
  const logger = options.logger ?? noopLogger;
2243
2349
  const timeoutMs = options.timeoutMs ?? 1e3;
2244
2350
  const hdc = getHdcPath(config);
2245
- return new Promise((resolve4, reject) => {
2351
+ return new Promise((resolve6, reject) => {
2246
2352
  let proc;
2247
2353
  try {
2248
2354
  proc = spawnCompat(hdc, ["track-jpid"]);
@@ -2253,7 +2359,7 @@ function listRunningProcesses(config, options = {}) {
2253
2359
  const processes = [];
2254
2360
  let resolved = false;
2255
2361
  proc.stdout?.on("data", (data) => {
2256
- for (const line of data.toString().split("\n").filter(Boolean)) {
2362
+ for (const line of data.toString().split(/\r?\n/).filter(Boolean)) {
2257
2363
  const match = line.match(/^(\d+)\s+(.+)$/);
2258
2364
  if (!match) continue;
2259
2365
  const pid = match[1];
@@ -2262,7 +2368,7 @@ function listRunningProcesses(config, options = {}) {
2262
2368
  if (options.targetProcessName && name === options.targetProcessName && !resolved) {
2263
2369
  resolved = true;
2264
2370
  killProcessTree(proc);
2265
- resolve4(pid);
2371
+ resolve6(pid);
2266
2372
  return;
2267
2373
  }
2268
2374
  }
@@ -2283,7 +2389,7 @@ function listRunningProcesses(config, options = {}) {
2283
2389
  if (options.targetProcessName) {
2284
2390
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
2285
2391
  } else {
2286
- resolve4(processes);
2392
+ resolve6(processes);
2287
2393
  }
2288
2394
  }, timeoutMs);
2289
2395
  proc.on("close", () => {
@@ -2293,7 +2399,7 @@ function listRunningProcesses(config, options = {}) {
2293
2399
  if (options.targetProcessName) {
2294
2400
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
2295
2401
  } else {
2296
- resolve4(processes);
2402
+ resolve6(processes);
2297
2403
  }
2298
2404
  });
2299
2405
  });
@@ -2437,12 +2543,12 @@ async function reboot(opts) {
2437
2543
  }
2438
2544
 
2439
2545
  // src/hdc/screen.ts
2440
- var fs18 = __toESM(require("fs"), 1);
2441
- var os8 = __toESM(require("os"), 1);
2442
- var path18 = __toESM(require("path"), 1);
2546
+ var fs19 = __toESM(require("fs"), 1);
2547
+ var os9 = __toESM(require("os"), 1);
2548
+ var path19 = __toESM(require("path"), 1);
2443
2549
  var import_node_crypto = require("crypto");
2444
2550
  function tmpName(ext) {
2445
- return path18.join(os8.tmpdir(), `oniro_screen_${(0, import_node_crypto.randomBytes)(6).toString("hex")}.${ext}`);
2551
+ return path19.join(os9.tmpdir(), `oniro_screen_${(0, import_node_crypto.randomBytes)(6).toString("hex")}.${ext}`);
2446
2552
  }
2447
2553
  async function takeScreenshot(opts) {
2448
2554
  const remote = "/data/local/tmp/oniro_screenshot.jpeg";
@@ -2452,11 +2558,11 @@ async function takeScreenshot(opts) {
2452
2558
  const local = tmpName("jpeg");
2453
2559
  try {
2454
2560
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2455
- const pixels = await fs18.promises.readFile(local);
2561
+ const pixels = await fs19.promises.readFile(local);
2456
2562
  const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
2457
2563
  return { pixels, width: size.width, height: size.height };
2458
2564
  } finally {
2459
- fs18.promises.unlink(local).catch(() => {
2565
+ fs19.promises.unlink(local).catch(() => {
2460
2566
  });
2461
2567
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2462
2568
  });
@@ -2475,9 +2581,9 @@ async function captureBurst(opts) {
2475
2581
  const local = tmpName(`burst_${i}.jpeg`);
2476
2582
  try {
2477
2583
  await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
2478
- frames.push(await fs18.promises.readFile(local));
2584
+ frames.push(await fs19.promises.readFile(local));
2479
2585
  } finally {
2480
- fs18.promises.unlink(local).catch(() => {
2586
+ fs19.promises.unlink(local).catch(() => {
2481
2587
  });
2482
2588
  }
2483
2589
  }
@@ -2603,9 +2709,9 @@ async function sendRawTouch(opts) {
2603
2709
  }
2604
2710
 
2605
2711
  // src/hdc/dump.ts
2606
- var fs19 = __toESM(require("fs"), 1);
2607
- var os9 = __toESM(require("os"), 1);
2608
- var path19 = __toESM(require("path"), 1);
2712
+ var fs20 = __toESM(require("fs"), 1);
2713
+ var os10 = __toESM(require("os"), 1);
2714
+ var path20 = __toESM(require("path"), 1);
2609
2715
  var import_node_crypto2 = require("crypto");
2610
2716
  function parseBounds(s) {
2611
2717
  const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
@@ -2690,10 +2796,10 @@ async function dumpLayout(opts) {
2690
2796
  if (!m) throw new OniroError(`Could not parse dumpLayout output:
2691
2797
  ${dump.stdout}`);
2692
2798
  const remote = m[1];
2693
- const local = path19.join(os9.tmpdir(), `oniro_layout_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}.json`);
2799
+ const local = path20.join(os10.tmpdir(), `oniro_layout_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}.json`);
2694
2800
  try {
2695
2801
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2696
- const parsed = JSON.parse(await fs19.promises.readFile(local, "utf8"));
2802
+ const parsed = JSON.parse(await fs20.promises.readFile(local, "utf8"));
2697
2803
  const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
2698
2804
  if (opts.prune === false) {
2699
2805
  return { display, tree: parsed };
@@ -2702,7 +2808,7 @@ ${dump.stdout}`);
2702
2808
  const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
2703
2809
  return { display, tree };
2704
2810
  } finally {
2705
- fs19.promises.unlink(local).catch(() => {
2811
+ fs20.promises.unlink(local).catch(() => {
2706
2812
  });
2707
2813
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2708
2814
  });
@@ -2713,7 +2819,7 @@ ${dump.stdout}`);
2713
2819
  function setHilogLevel(opts) {
2714
2820
  const logger = opts.logger ?? noopLogger;
2715
2821
  const hdc = getHdcPath(opts.config);
2716
- return new Promise((resolve4, reject) => {
2822
+ return new Promise((resolve6, reject) => {
2717
2823
  let child;
2718
2824
  try {
2719
2825
  child = spawnCompat(hdc, ["shell", "hilog", "-b", opts.level]);
@@ -2733,7 +2839,7 @@ function setHilogLevel(opts) {
2733
2839
  });
2734
2840
  child.once("close", (code) => {
2735
2841
  if (code === 0) {
2736
- resolve4();
2842
+ resolve6();
2737
2843
  } else {
2738
2844
  const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
2739
2845
  logger.error(`[hilog] ${msg}`);
@@ -2788,7 +2894,7 @@ async function resolveProcessId(config, bundle, deviceSerial) {
2788
2894
  return proc?.pid;
2789
2895
  }
2790
2896
  function waitForLog(opts) {
2791
- return new Promise((resolve4, reject) => {
2897
+ return new Promise((resolve6, reject) => {
2792
2898
  const deadline = Date.now() + opts.timeoutMs;
2793
2899
  let settled = false;
2794
2900
  let child = null;
@@ -2807,7 +2913,7 @@ function waitForLog(opts) {
2807
2913
  if (settled) return;
2808
2914
  settled = true;
2809
2915
  cleanup();
2810
- resolve4(entry);
2916
+ resolve6(entry);
2811
2917
  };
2812
2918
  const fail = (err) => {
2813
2919
  if (settled) return;
@@ -2830,10 +2936,9 @@ function waitForLog(opts) {
2830
2936
  child = stream;
2831
2937
  stream.stdout.on("data", (d) => {
2832
2938
  buf += d.toString();
2833
- let idx;
2834
- while ((idx = buf.indexOf("\n")) >= 0) {
2835
- const line = buf.slice(0, idx);
2836
- buf = buf.slice(idx + 1);
2939
+ const lines = buf.split(/\r?\n/);
2940
+ buf = lines.pop() ?? "";
2941
+ for (const line of lines) {
2837
2942
  const entry = parseHilogLine(line);
2838
2943
  if (entry && opts.pattern.test(entryText(entry))) {
2839
2944
  finish(entry);
@@ -2855,7 +2960,7 @@ function waitForLog(opts) {
2855
2960
  });
2856
2961
  }
2857
2962
  function watchLog(opts) {
2858
- return new Promise((resolve4, reject) => {
2963
+ return new Promise((resolve6, reject) => {
2859
2964
  const matches = [];
2860
2965
  let settled = false;
2861
2966
  let child = null;
@@ -2872,7 +2977,7 @@ function watchLog(opts) {
2872
2977
  if (settled) return;
2873
2978
  settled = true;
2874
2979
  cleanup();
2875
- resolve4(opts.dedup === false ? matches : dedupEntries(matches));
2980
+ resolve6(opts.dedup === false ? matches : dedupEntries(matches));
2876
2981
  };
2877
2982
  const onAbort = () => {
2878
2983
  if (settled) return;
@@ -2889,10 +2994,9 @@ function watchLog(opts) {
2889
2994
  child = stream;
2890
2995
  stream.stdout.on("data", (d) => {
2891
2996
  buf += d.toString();
2892
- let idx;
2893
- while ((idx = buf.indexOf("\n")) >= 0) {
2894
- const line = buf.slice(0, idx);
2895
- buf = buf.slice(idx + 1);
2997
+ const lines = buf.split(/\r?\n/);
2998
+ buf = lines.pop() ?? "";
2999
+ for (const line of lines) {
2896
3000
  const entry = parseHilogLine(line);
2897
3001
  if (entry && opts.pattern.test(entryText(entry))) matches.push(entry);
2898
3002
  }
@@ -2925,12 +3029,47 @@ async function dumpLog(opts) {
2925
3029
  timeoutMs: opts.timeoutMs ?? 3e4,
2926
3030
  logger: opts.logger
2927
3031
  });
2928
- return res.stdout.split("\n").map((line) => parseHilogLine(line)).filter((e) => e !== null);
3032
+ return res.stdout.split(/\r?\n/).map((line) => parseHilogLine(line)).filter((e) => e !== null);
3033
+ }
3034
+
3035
+ // src/build/runHvigorw.ts
3036
+ var fs22 = __toESM(require("fs"), 1);
3037
+ var path22 = __toESM(require("path"), 1);
3038
+
3039
+ // src/harmonyos/target.ts
3040
+ var fs21 = __toESM(require("fs"), 1);
3041
+ var path21 = __toESM(require("path"), 1);
3042
+ var import_json54 = __toESM(require("json5"), 1);
3043
+ function detectRuntimeOs(projectDir, productName) {
3044
+ let products = [];
3045
+ try {
3046
+ const parsed = import_json54.default.parse(fs21.readFileSync(path21.join(projectDir, "build-profile.json5"), "utf-8"));
3047
+ if (Array.isArray(parsed.app?.products)) products = parsed.app.products;
3048
+ } catch {
3049
+ return "OpenHarmony";
3050
+ }
3051
+ const product = products.find((p) => p.name === productName) ?? products[0];
3052
+ return product?.runtimeOS === "HarmonyOS" ? "HarmonyOS" : "OpenHarmony";
3053
+ }
3054
+ var HARMONYOS_SDK_VERSIONS = /* @__PURE__ */ new Map([
3055
+ [12, "5.0.0"],
3056
+ [13, "5.0.1"],
3057
+ [14, "5.0.2"],
3058
+ [15, "5.0.3"],
3059
+ [16, "5.0.4"],
3060
+ [17, "5.0.5"],
3061
+ [18, "5.1.0"],
3062
+ [19, "5.1.1"],
3063
+ [20, "6.0.0"],
3064
+ [21, "6.0.1"],
3065
+ [24, "6.1.1"]
3066
+ ]);
3067
+ function harmonyOsSdkLabelForApi(api) {
3068
+ const version = HARMONYOS_SDK_VERSIONS.get(api);
3069
+ return version ? `${version}(${api})` : void 0;
2929
3070
  }
2930
3071
 
2931
3072
  // src/build/runHvigorw.ts
2932
- var fs20 = __toESM(require("fs"), 1);
2933
- var path20 = __toESM(require("path"), 1);
2934
3073
  function buildHvigorwArgs(opts) {
2935
3074
  const args = [opts.task ?? "assembleHap", "--mode", "module"];
2936
3075
  args.push("-p", `product=${opts.product ?? "default"}`);
@@ -2945,28 +3084,36 @@ function runHvigorw(opts) {
2945
3084
  const { config } = opts;
2946
3085
  const projectDir = toLongPath(opts.projectDir);
2947
3086
  const logger = opts.logger ?? noopLogger;
2948
- if (!fs20.existsSync(path20.join(projectDir, "build-profile.json5"))) {
3087
+ if (!fs22.existsSync(path22.join(projectDir, "build-profile.json5"))) {
2949
3088
  throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
2950
3089
  }
2951
- if (!fs20.existsSync(getCmdToolsPath(config))) {
2952
- throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
3090
+ let hvigorw;
3091
+ let sdkEnv;
3092
+ if (detectRuntimeOs(projectDir, opts.product) === "HarmonyOS") {
3093
+ const sdk = requireHarmonyOsSdk({ config, logger });
3094
+ sdkEnv = { DEVECO_SDK_HOME: sdk.sdkPath };
3095
+ const resolved = getHvigorwPath(config, projectDir);
3096
+ hvigorw = resolved.startsWith(projectDir) ? resolved : findHarmonyOsTool(sdk, "hvigorw") ?? resolved;
3097
+ logger.info(`[build] Building for HarmonyOS against the SDK at ${sdk.sdkPath}.`);
3098
+ } else {
3099
+ if (!fs22.existsSync(getCmdToolsPath(config))) {
3100
+ throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
3101
+ }
3102
+ sdkEnv = { OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config) };
3103
+ hvigorw = getHvigorwPath(config, projectDir);
2953
3104
  }
2954
- const hvigorw = getHvigorwPath(config, projectDir);
2955
- if (!fs20.existsSync(hvigorw)) {
3105
+ if (!fs22.existsSync(hvigorw)) {
2956
3106
  throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
2957
3107
  }
2958
3108
  const args = buildHvigorwArgs(opts);
2959
3109
  if (process.platform !== "win32") {
2960
3110
  try {
2961
- fs20.chmodSync(hvigorw, 493);
3111
+ fs22.chmodSync(hvigorw, 493);
2962
3112
  } catch {
2963
3113
  }
2964
3114
  }
2965
- const env = {
2966
- ...process.env,
2967
- OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
2968
- };
2969
- return new Promise((resolve4, reject) => {
3115
+ const env = { ...process.env, ...sdkEnv };
3116
+ return new Promise((resolve6, reject) => {
2970
3117
  if (opts.abortSignal?.aborted) {
2971
3118
  reject(new CancelledError("hvigorw build cancelled."));
2972
3119
  return;
@@ -3010,15 +3157,15 @@ function runHvigorw(opts) {
3010
3157
  settled = true;
3011
3158
  cleanup();
3012
3159
  const exitCode = code ?? 1;
3013
- if (exitCode === 0) resolve4({ exitCode });
3160
+ if (exitCode === 0) resolve6({ exitCode });
3014
3161
  else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
3015
3162
  });
3016
3163
  });
3017
3164
  }
3018
3165
 
3019
3166
  // src/build/discoverHaps.ts
3020
- var fs21 = __toESM(require("fs"), 1);
3021
- var path21 = __toESM(require("path"), 1);
3167
+ var fs23 = __toESM(require("fs"), 1);
3168
+ var path23 = __toESM(require("path"), 1);
3022
3169
  function moduleOfHap(hapFileName) {
3023
3170
  const dash = hapFileName.indexOf("-");
3024
3171
  return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
@@ -3029,9 +3176,9 @@ async function discoverHaps(opts) {
3029
3176
  const result = {};
3030
3177
  const walk = async (dir, depth) => {
3031
3178
  if (depth > maxDepth) return;
3032
- const entries = await fs21.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
3179
+ const entries = await fs23.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
3033
3180
  for (const e of entries) {
3034
- const full = path21.join(dir, e.name);
3181
+ const full = path23.join(dir, e.name);
3035
3182
  if (e.isDirectory()) {
3036
3183
  if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
3037
3184
  await walk(full, depth + 1);
@@ -3048,11 +3195,11 @@ async function discoverHaps(opts) {
3048
3195
  }
3049
3196
 
3050
3197
  // src/build/ohpm.ts
3051
- var fs22 = __toESM(require("fs"), 1);
3052
- var path22 = __toESM(require("path"), 1);
3198
+ var fs24 = __toESM(require("fs"), 1);
3199
+ var path24 = __toESM(require("path"), 1);
3053
3200
  function runOhpm(opts) {
3054
3201
  return runProcess({
3055
- command: getOhpmPath(opts.config),
3202
+ command: opts.ohpmPath ?? getOhpmPath(opts.config),
3056
3203
  args: [...opts.args],
3057
3204
  cwd: opts.projectDir,
3058
3205
  timeoutMs: opts.timeoutMs ?? 6e5,
@@ -3062,12 +3209,13 @@ function runOhpm(opts) {
3062
3209
  });
3063
3210
  }
3064
3211
  async function ensureOhModules(opts) {
3065
- if (fs22.existsSync(path22.join(opts.projectDir, "oh_modules"))) {
3212
+ if (fs24.existsSync(path24.join(opts.projectDir, "oh_modules"))) {
3066
3213
  return { installed: false };
3067
3214
  }
3068
3215
  const res = await runOhpm({
3069
3216
  config: opts.config,
3070
3217
  projectDir: opts.projectDir,
3218
+ ohpmPath: opts.ohpmPath,
3071
3219
  args: ["install", "--all"],
3072
3220
  abortSignal: opts.abortSignal,
3073
3221
  onOutput: opts.onOutput,
@@ -3115,10 +3263,13 @@ async function buildHap(opts) {
3115
3263
  const warnings = [];
3116
3264
  const start = Date.now();
3117
3265
  const projectDir = toLongPath(opts.projectDir);
3266
+ const isHarmonyOs = detectRuntimeOs(projectDir, opts.product) === "HarmonyOS";
3118
3267
  if (opts.autoInstallDeps !== false) {
3268
+ const harmonySdk = isHarmonyOs ? findHarmonyOsSdk({ config: opts.config, logger: opts.logger }) : null;
3119
3269
  const r = await ensureOhModules({
3120
3270
  config: opts.config,
3121
3271
  projectDir,
3272
+ ohpmPath: (harmonySdk && findHarmonyOsTool(harmonySdk, "ohpm")) ?? void 0,
3122
3273
  abortSignal: opts.abortSignal,
3123
3274
  onOutput: opts.onOutput,
3124
3275
  logger: opts.logger
@@ -3141,7 +3292,7 @@ async function buildHap(opts) {
3141
3292
  const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
3142
3293
  if (!anySigned) {
3143
3294
  warnings.push(
3144
- "No signed HAPs found \u2014 configure signingConfigs in build-profile.json5 (unsigned HAPs are rejected by most devices)."
3295
+ isHarmonyOs ? "No signed HAPs found \u2014 run `oniro-app sign --harmonyos` to have AppGallery Connect issue a certificate and profile." : "No signed HAPs found \u2014 configure signingConfigs in build-profile.json5 (unsigned HAPs are rejected by most devices)."
3145
3296
  );
3146
3297
  }
3147
3298
  return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
@@ -3158,21 +3309,21 @@ function isValidBundleName(bundleName) {
3158
3309
  }
3159
3310
 
3160
3311
  // src/project/templates.ts
3161
- var fs23 = __toESM(require("fs"), 1);
3162
- var path23 = __toESM(require("path"), 1);
3312
+ var fs25 = __toESM(require("fs"), 1);
3313
+ var path25 = __toESM(require("path"), 1);
3163
3314
  function toHumanTemplateName(folderName) {
3164
3315
  return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
3165
3316
  }
3166
3317
  function listTemplates(templateRoot) {
3167
- if (!fs23.existsSync(templateRoot)) return [];
3168
- const entries = fs23.readdirSync(templateRoot, { withFileTypes: true });
3318
+ if (!fs25.existsSync(templateRoot)) return [];
3319
+ const entries = fs25.readdirSync(templateRoot, { withFileTypes: true });
3169
3320
  return entries.filter((e) => e.isDirectory()).map((e) => {
3170
- const dir = path23.join(templateRoot, e.name);
3171
- const metaPath = path23.join(dir, "template.json");
3321
+ const dir = path25.join(templateRoot, e.name);
3322
+ const metaPath = path25.join(dir, "template.json");
3172
3323
  let meta = {};
3173
- if (fs23.existsSync(metaPath)) {
3324
+ if (fs25.existsSync(metaPath)) {
3174
3325
  try {
3175
- meta = JSON.parse(fs23.readFileSync(metaPath, "utf8"));
3326
+ meta = JSON.parse(fs25.readFileSync(metaPath, "utf8"));
3176
3327
  } catch {
3177
3328
  }
3178
3329
  }
@@ -3187,57 +3338,57 @@ function listTemplates(templateRoot) {
3187
3338
  function validateTemplateLayout(templateDir, defaultModuleName) {
3188
3339
  const required = [
3189
3340
  "build-profile.json5",
3190
- path23.join("AppScope", "app.json5"),
3191
- path23.join(defaultModuleName, "src", "main", "module.json5"),
3192
- path23.join(defaultModuleName, "oh-package.json5"),
3341
+ path25.join("AppScope", "app.json5"),
3342
+ path25.join(defaultModuleName, "src", "main", "module.json5"),
3343
+ path25.join(defaultModuleName, "oh-package.json5"),
3193
3344
  "hvigorfile.ts"
3194
3345
  ];
3195
- return required.filter((rel) => !fs23.existsSync(path23.join(templateDir, rel)));
3346
+ return required.filter((rel) => !fs25.existsSync(path25.join(templateDir, rel)));
3196
3347
  }
3197
3348
 
3198
3349
  // src/project/createScaffold.ts
3199
- var fs25 = __toESM(require("fs"), 1);
3200
- var path24 = __toESM(require("path"), 1);
3350
+ var fs27 = __toESM(require("fs"), 1);
3351
+ var path26 = __toESM(require("path"), 1);
3201
3352
 
3202
3353
  // src/project/jsonHelpers.ts
3203
- var fs24 = __toESM(require("fs"), 1);
3204
- var import_json54 = __toESM(require("json5"), 1);
3354
+ var fs26 = __toESM(require("fs"), 1);
3355
+ var import_json55 = __toESM(require("json5"), 1);
3205
3356
  function readJson5File(filePath) {
3206
- return import_json54.default.parse(fs24.readFileSync(filePath, "utf8"));
3357
+ return import_json55.default.parse(fs26.readFileSync(filePath, "utf8"));
3207
3358
  }
3208
3359
  function writeJson5File(filePath, value) {
3209
- fs24.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3360
+ fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3210
3361
  }
3211
3362
  function readJsonFile(filePath) {
3212
- return JSON.parse(fs24.readFileSync(filePath, "utf8"));
3363
+ return JSON.parse(fs26.readFileSync(filePath, "utf8"));
3213
3364
  }
3214
3365
  function writeJsonFile(filePath, value) {
3215
- fs24.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3366
+ fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3216
3367
  }
3217
3368
 
3218
3369
  // src/project/createScaffold.ts
3219
3370
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
3220
3371
  async function pathExists(p) {
3221
3372
  try {
3222
- await fs25.promises.access(p);
3373
+ await fs27.promises.access(p);
3223
3374
  return true;
3224
3375
  } catch {
3225
3376
  return false;
3226
3377
  }
3227
3378
  }
3228
3379
  async function copyDirRecursive(srcDir, destDir) {
3229
- await fs25.promises.mkdir(destDir, { recursive: true });
3230
- const entries = await fs25.promises.readdir(srcDir, { withFileTypes: true });
3380
+ await fs27.promises.mkdir(destDir, { recursive: true });
3381
+ const entries = await fs27.promises.readdir(srcDir, { withFileTypes: true });
3231
3382
  for (const entry of entries) {
3232
- const src = path24.join(srcDir, entry.name);
3233
- const dest = path24.join(destDir, entry.name);
3383
+ const src = path26.join(srcDir, entry.name);
3384
+ const dest = path26.join(destDir, entry.name);
3234
3385
  if (entry.isDirectory()) {
3235
3386
  await copyDirRecursive(src, dest);
3236
3387
  } else if (entry.isSymbolicLink()) {
3237
3388
  continue;
3238
3389
  } else if (entry.isFile()) {
3239
3390
  if (entry.name === "template.json") continue;
3240
- await fs25.promises.copyFile(src, dest);
3391
+ await fs27.promises.copyFile(src, dest);
3241
3392
  }
3242
3393
  }
3243
3394
  }
@@ -3245,9 +3396,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
3245
3396
  const stack = [projectDir];
3246
3397
  while (stack.length > 0) {
3247
3398
  const currentDir = stack.pop();
3248
- const entries = await fs25.promises.readdir(currentDir, { withFileTypes: true });
3399
+ const entries = await fs27.promises.readdir(currentDir, { withFileTypes: true });
3249
3400
  for (const entry of entries) {
3250
- const fullPath = path24.join(currentDir, entry.name);
3401
+ const fullPath = path26.join(currentDir, entry.name);
3251
3402
  if (entry.isDirectory()) {
3252
3403
  if (IGNORED_DIRS.has(entry.name)) continue;
3253
3404
  stack.push(fullPath);
@@ -3269,23 +3420,23 @@ function toJavaPropertiesPath(p) {
3269
3420
  function createOrUpdateLocalProperties(projectDir, sdkDir) {
3270
3421
  const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
3271
3422
  `;
3272
- fs25.writeFileSync(path24.join(projectDir, "local.properties"), content, "utf8");
3423
+ fs27.writeFileSync(path26.join(projectDir, "local.properties"), content, "utf8");
3273
3424
  }
3274
3425
  function renameIfExists(fromPath, toPath) {
3275
- if (fs25.existsSync(fromPath) && fromPath !== toPath) {
3276
- fs25.renameSync(fromPath, toPath);
3426
+ if (fs27.existsSync(fromPath) && fromPath !== toPath) {
3427
+ fs27.renameSync(fromPath, toPath);
3277
3428
  }
3278
3429
  }
3279
3430
  function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3280
- const appJsonPath = path24.join(projectDir, "AppScope", "app.json5");
3281
- if (fs25.existsSync(appJsonPath)) {
3431
+ const appJsonPath = path26.join(projectDir, "AppScope", "app.json5");
3432
+ if (fs27.existsSync(appJsonPath)) {
3282
3433
  const appJson = readJson5File(appJsonPath);
3283
3434
  appJson.app = appJson.app ?? {};
3284
3435
  appJson.app.bundleName = args.bundleName;
3285
3436
  writeJson5File(appJsonPath, appJson);
3286
3437
  }
3287
- const stringsPath = path24.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
3288
- if (fs25.existsSync(stringsPath)) {
3438
+ const stringsPath = path26.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
3439
+ if (fs27.existsSync(stringsPath)) {
3289
3440
  const strings = readJsonFile(stringsPath);
3290
3441
  if (Array.isArray(strings.string)) {
3291
3442
  const appName = strings.string.find((s) => s.name === "app_name");
@@ -3293,13 +3444,13 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3293
3444
  }
3294
3445
  writeJsonFile(stringsPath, strings);
3295
3446
  }
3296
- const buildProfilePath = path24.join(projectDir, "build-profile.json5");
3297
- if (fs25.existsSync(buildProfilePath)) {
3447
+ const buildProfilePath = path26.join(projectDir, "build-profile.json5");
3448
+ if (fs27.existsSync(buildProfilePath)) {
3298
3449
  const buildProfile = readJson5File(buildProfilePath);
3299
3450
  buildProfile.app = buildProfile.app ?? {};
3300
3451
  if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
3301
- buildProfile.app.products[0].compileSdkVersion = args.sdkApi;
3302
- buildProfile.app.products[0].compatibleSdkVersion = args.sdkApi;
3452
+ buildProfile.app.products[0].compileSdkVersion = args.sdkVersion;
3453
+ buildProfile.app.products[0].compatibleSdkVersion = args.sdkVersion;
3303
3454
  }
3304
3455
  if (Array.isArray(buildProfile.modules) && buildProfile.modules.length > 0) {
3305
3456
  buildProfile.modules[0].name = args.moduleName;
@@ -3307,23 +3458,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3307
3458
  }
3308
3459
  writeJson5File(buildProfilePath, buildProfile);
3309
3460
  }
3310
- const moduleJsonPath = path24.join(projectDir, args.moduleName, "src", "main", "module.json5");
3311
- if (fs25.existsSync(moduleJsonPath)) {
3461
+ const moduleJsonPath = path26.join(projectDir, args.moduleName, "src", "main", "module.json5");
3462
+ if (fs27.existsSync(moduleJsonPath)) {
3312
3463
  const moduleJson = readJson5File(moduleJsonPath);
3313
3464
  moduleJson.module = moduleJson.module ?? {};
3314
3465
  moduleJson.module.name = args.moduleName;
3315
3466
  writeJson5File(moduleJsonPath, moduleJson);
3316
3467
  }
3317
- const moduleOhPackagePath = path24.join(projectDir, args.moduleName, "oh-package.json5");
3318
- if (fs25.existsSync(moduleOhPackagePath)) {
3468
+ const moduleOhPackagePath = path26.join(projectDir, args.moduleName, "oh-package.json5");
3469
+ if (fs27.existsSync(moduleOhPackagePath)) {
3319
3470
  const pkg = readJson5File(moduleOhPackagePath);
3320
3471
  pkg.name = args.moduleName;
3321
3472
  writeJson5File(moduleOhPackagePath, pkg);
3322
3473
  }
3323
- const vscodeDir = path24.join(projectDir, ".vscode");
3324
- fs25.mkdirSync(vscodeDir, { recursive: true });
3474
+ const vscodeDir = path26.join(projectDir, ".vscode");
3475
+ fs27.mkdirSync(vscodeDir, { recursive: true });
3325
3476
  const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
3326
- writeJsonFile(path24.join(vscodeDir, "settings.json"), {
3477
+ writeJsonFile(path26.join(vscodeDir, "settings.json"), {
3327
3478
  "oniro.hapPath": hapPath,
3328
3479
  "files.associations": { "*.json5": "jsonc" }
3329
3480
  });
@@ -3340,7 +3491,7 @@ async function createScaffold(opts) {
3340
3491
  if (!opts.location || !await pathExists(opts.location)) {
3341
3492
  throw new OniroError(`Location does not exist: ${opts.location}`);
3342
3493
  }
3343
- const templateDir = path24.join(opts.templateRoot, opts.templateId);
3494
+ const templateDir = path26.join(opts.templateRoot, opts.templateId);
3344
3495
  if (!await pathExists(templateDir)) {
3345
3496
  throw new OniroError(`Template not found: ${templateDir}`);
3346
3497
  }
@@ -3353,38 +3504,60 @@ async function createScaffold(opts) {
3353
3504
  throw new OniroError(`Template '${opts.templateId}' is missing required files:
3354
3505
  - ${missing.join("\n- ")}`);
3355
3506
  }
3356
- const projectDir = path24.join(opts.location, opts.projectName);
3507
+ let sdkVersion = opts.sdkApi;
3508
+ let sdkBaseDir = getOhosBaseSdkHome(opts.config);
3509
+ if (detectRuntimeOs(templateDir) === "HarmonyOS") {
3510
+ const harmonySdk = findHarmonyOsSdk({ config: opts.config, logger });
3511
+ const label = harmonySdk?.version?.endsWith(`(${opts.sdkApi})`) ? harmonySdk.version : harmonyOsSdkLabelForApi(opts.sdkApi);
3512
+ if (!label) {
3513
+ throw new OniroError(
3514
+ `No HarmonyOS release is known for API ${opts.sdkApi}. Known: ${[...HARMONYOS_SDK_VERSIONS.keys()].join(", ")}` + (harmonySdk?.version ? `, and the installed SDK's ${harmonySdk.version}.` : ".")
3515
+ );
3516
+ }
3517
+ sdkVersion = label;
3518
+ if (!harmonySdk) {
3519
+ logger.warn("[create] No HarmonyOS SDK found; set ONIRO_HARMONYOS_SDK_PATH before building.");
3520
+ } else {
3521
+ sdkBaseDir = harmonySdk.sdkPath;
3522
+ if (harmonySdk.version && harmonySdk.version !== label) {
3523
+ logger.warn(
3524
+ `[create] The HarmonyOS SDK at ${harmonySdk.sdkPath} is ${harmonySdk.version}, so a project declaring ${label} will not build against it. Install ${label}, or create with the installed API level.`
3525
+ );
3526
+ }
3527
+ }
3528
+ }
3529
+ const projectDir = path26.join(opts.location, opts.projectName);
3357
3530
  if (await pathExists(projectDir)) {
3358
3531
  if (!opts.overwrite) {
3359
3532
  throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
3360
3533
  }
3361
- await fs25.promises.rm(projectDir, { recursive: true, force: true });
3534
+ await fs27.promises.rm(projectDir, { recursive: true, force: true });
3362
3535
  }
3363
3536
  logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
3364
3537
  await copyDirRecursive(templateDir, projectDir);
3365
3538
  if (moduleName !== defaultModuleName) {
3366
- renameIfExists(path24.join(projectDir, defaultModuleName), path24.join(projectDir, moduleName));
3539
+ renameIfExists(path26.join(projectDir, defaultModuleName), path26.join(projectDir, moduleName));
3367
3540
  }
3368
3541
  updateTemplateConfigs(
3369
3542
  projectDir,
3370
3543
  {
3371
3544
  projectName: opts.projectName,
3372
3545
  bundleName: opts.bundleName,
3373
- sdkApi: opts.sdkApi,
3546
+ sdkVersion,
3374
3547
  moduleName
3375
3548
  },
3376
- getOhosBaseSdkHome(opts.config)
3549
+ sdkBaseDir
3377
3550
  );
3378
3551
  await normalizeJson5ToJson(projectDir, logger);
3379
3552
  return { projectDir };
3380
3553
  }
3381
3554
 
3382
3555
  // src/project/listModules.ts
3383
- var fs26 = __toESM(require("fs"), 1);
3384
- var path25 = __toESM(require("path"), 1);
3556
+ var fs28 = __toESM(require("fs"), 1);
3557
+ var path27 = __toESM(require("path"), 1);
3385
3558
  function listModules(opts) {
3386
- const buildProfile = path25.join(opts.projectDir, "build-profile.json5");
3387
- if (!fs26.existsSync(buildProfile)) {
3559
+ const buildProfile = path27.join(opts.projectDir, "build-profile.json5");
3560
+ if (!fs28.existsSync(buildProfile)) {
3388
3561
  throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
3389
3562
  }
3390
3563
  const parsed = readJson5File(buildProfile);
@@ -3396,12 +3569,12 @@ function listModules(opts) {
3396
3569
  }
3397
3570
 
3398
3571
  // src/project/abilities.ts
3399
- var fs27 = __toESM(require("fs"), 1);
3400
- var path26 = __toESM(require("path"), 1);
3572
+ var fs29 = __toESM(require("fs"), 1);
3573
+ var path28 = __toESM(require("path"), 1);
3401
3574
  function listAbilities(opts) {
3402
3575
  const moduleName = opts.moduleName ?? "entry";
3403
- const moduleJsonPath = path26.join(opts.projectDir, moduleName, "src", "main", "module.json5");
3404
- if (!fs27.existsSync(moduleJsonPath)) {
3576
+ const moduleJsonPath = path28.join(opts.projectDir, moduleName, "src", "main", "module.json5");
3577
+ if (!fs29.existsSync(moduleJsonPath)) {
3405
3578
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
3406
3579
  }
3407
3580
  const parsed = readJson5File(moduleJsonPath);
@@ -3409,12 +3582,12 @@ function listAbilities(opts) {
3409
3582
  }
3410
3583
 
3411
3584
  // src/project/prepareSigning.ts
3412
- var fs28 = __toESM(require("fs"), 1);
3413
- var path27 = __toESM(require("path"), 1);
3585
+ var fs30 = __toESM(require("fs"), 1);
3586
+ var path29 = __toESM(require("path"), 1);
3414
3587
  function hasSigningArtifacts(dir) {
3415
- if (!fs28.existsSync(path27.join(dir, "signatures"))) return false;
3416
- const buildProfile = path27.join(dir, "build-profile.json5");
3417
- if (!fs28.existsSync(buildProfile)) return false;
3588
+ if (!fs30.existsSync(path29.join(dir, "signatures"))) return false;
3589
+ const buildProfile = path29.join(dir, "build-profile.json5");
3590
+ if (!fs30.existsSync(buildProfile)) return false;
3418
3591
  try {
3419
3592
  const parsed = readJson5File(buildProfile);
3420
3593
  return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
@@ -3504,8 +3677,8 @@ async function diffHapAssets(opts) {
3504
3677
  }
3505
3678
 
3506
3679
  // src/install/apply.ts
3507
- var fs29 = __toESM(require("fs"), 1);
3508
- var path28 = __toESM(require("path"), 1);
3680
+ var fs31 = __toESM(require("fs"), 1);
3681
+ var path30 = __toESM(require("path"), 1);
3509
3682
  function decideInstallMethod(s) {
3510
3683
  if (!s.signInfoInconsistent) return "replace";
3511
3684
  if (s.isSystemBundle && !s.allowUninstall) return "refuse";
@@ -3526,7 +3699,7 @@ async function pidOf(config, bundle, deviceSerial) {
3526
3699
  async function resolveHapPath(opts) {
3527
3700
  let hapPath = opts.hapPath;
3528
3701
  if (hapPath) {
3529
- if (!path28.isAbsolute(hapPath) && opts.projectDir) hapPath = path28.join(opts.projectDir, hapPath);
3702
+ if (!path30.isAbsolute(hapPath) && opts.projectDir) hapPath = path30.join(opts.projectDir, hapPath);
3530
3703
  } else {
3531
3704
  if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
3532
3705
  const haps = await discoverHaps({ projectDir: opts.projectDir });
@@ -3545,7 +3718,7 @@ async function resolveHapPath(opts) {
3545
3718
  hapPath = withSigned[0][1].signed[0];
3546
3719
  }
3547
3720
  }
3548
- if (!fs29.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
3721
+ if (!fs31.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
3549
3722
  return hapPath;
3550
3723
  }
3551
3724
  async function applyChanges(opts) {
@@ -3555,7 +3728,7 @@ async function applyChanges(opts) {
3555
3728
  const allowUninstall = opts.allowUninstall ?? false;
3556
3729
  const hapPath = await resolveHapPath(opts);
3557
3730
  let assetsChanged = false;
3558
- if (opts.installedHapPath && fs29.existsSync(opts.installedHapPath)) {
3731
+ if (opts.installedHapPath && fs31.existsSync(opts.installedHapPath)) {
3559
3732
  try {
3560
3733
  const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
3561
3734
  assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
@@ -3596,21 +3769,1202 @@ async function applyChanges(opts) {
3596
3769
  return { method, preInstallPid, postInstallPid, replaced: installMethod === "replace", cacheCleared };
3597
3770
  }
3598
3771
 
3772
+ // src/harmonyos/auth/endpoints.ts
3773
+ var HUAWEI_APP_ID = "1009";
3774
+ var HUAWEI_API_VERSION = "1.0.0";
3775
+ var LOGIN_TIMEOUT_MS = 6e5;
3776
+ var HUAWEI_SITES = {
3777
+ CN: {
3778
+ code: "CN",
3779
+ siteId: "1",
3780
+ siteParam: "CN",
3781
+ loginUrl: "https://cn.devecostudio.huawei.com",
3782
+ agcBaseUrl: "https://connect-api.cloud.huawei.com",
3783
+ language: "zh_CN"
3784
+ },
3785
+ SG: {
3786
+ code: "SG",
3787
+ siteId: "5",
3788
+ siteParam: "SG",
3789
+ loginUrl: "https://sg.devecostudio.huawei.com",
3790
+ agcBaseUrl: "https://connect-api-dra.cloud.huawei.com",
3791
+ language: "zh_CN"
3792
+ },
3793
+ EU: {
3794
+ code: "EU",
3795
+ siteId: "7",
3796
+ siteParam: "DE",
3797
+ loginUrl: "https://de.devecostudio.huawei.com",
3798
+ agcBaseUrl: "https://connect-api-dre.cloud.huawei.com",
3799
+ language: "de_DE"
3800
+ },
3801
+ RU: {
3802
+ code: "RU",
3803
+ siteId: "8",
3804
+ siteParam: "RU",
3805
+ loginUrl: "https://ru.devecostudio.huawei.com",
3806
+ agcBaseUrl: "https://connect-api-drru.cloud.huawei.com",
3807
+ language: "ru_RU"
3808
+ }
3809
+ };
3810
+ var AGC_TEAMS_URL = "https://connect-api.cloud.huawei.com/api/ups/user-permission-service/v1/user-team-list";
3811
+ function siteForId(siteId) {
3812
+ return Object.values(HUAWEI_SITES).find((s) => s.siteId === siteId) ?? null;
3813
+ }
3814
+ var AUTH_PATHS = {
3815
+ /** The browser authenticates here, then redirects to the loopback server. */
3816
+ AUTH_APPLY: "console/DevEcoIDE/apply",
3817
+ /** Exchanges the browser's temp token for a JWT. */
3818
+ TEMP_TOKEN_CHECK: "authrouter/auth/api/temptoken/check",
3819
+ /** Validates a JWT and, with `refresh`, mints an AGC access token. */
3820
+ JWT_TOKEN_CHECK: "authrouter/auth/api/jwToken/check",
3821
+ LOGIN_SUCCESS: "console/DevEcoCLI/loginSuccess",
3822
+ LOGIN_FAILED: "console/DevEcoCLI/loginFailed",
3823
+ LOGOUT: "authrouter/auth/api/logout",
3824
+ /**
3825
+ * Whether an account without real-name verification accepted the developer
3826
+ * agreement, which is what lets it sign. From DevEco Studio (`space.agr.url`).
3827
+ */
3828
+ UNREALNAME_AGREEMENT: "authrouter/unrealname/agreement"
3829
+ };
3830
+
3831
+ // src/harmonyos/auth/tokenStore.ts
3832
+ var fs32 = __toESM(require("fs"), 1);
3833
+ var os11 = __toESM(require("os"), 1);
3834
+ var path31 = __toESM(require("path"), 1);
3835
+ var crypto3 = __toESM(require("crypto"), 1);
3836
+ var ALGORITHM = "aes-256-gcm";
3837
+ var KEY_LENGTH = 32;
3838
+ var IV_LENGTH = 12;
3839
+ var TOKEN_FILE = "token.enc";
3840
+ var KEY_FILE = "kek.bin";
3841
+ var DIR_MODE = 448;
3842
+ var FILE_MODE = 384;
3843
+ function encrypt2(key, plaintext) {
3844
+ const iv = crypto3.randomBytes(IV_LENGTH);
3845
+ const cipher = crypto3.createCipheriv(ALGORITHM, key, iv);
3846
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
3847
+ return {
3848
+ version: 1,
3849
+ algorithm: ALGORITHM,
3850
+ ciphertext: ciphertext.toString("base64"),
3851
+ iv: iv.toString("base64"),
3852
+ authTag: cipher.getAuthTag().toString("base64")
3853
+ };
3854
+ }
3855
+ function decrypt2(key, blob) {
3856
+ const decipher = crypto3.createDecipheriv(ALGORITHM, key, Buffer.from(blob.iv, "base64"));
3857
+ decipher.setAuthTag(Buffer.from(blob.authTag, "base64"));
3858
+ return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "base64")), decipher.final()]);
3859
+ }
3860
+ function isEncryptedBlob(value) {
3861
+ if (!value || typeof value !== "object") return false;
3862
+ const b = value;
3863
+ return b.algorithm === ALGORITHM && typeof b.ciphertext === "string" && typeof b.iv === "string" && typeof b.authTag === "string";
3864
+ }
3865
+ function permissionHint(dir) {
3866
+ return process.platform === "win32" ? `Permission denied writing ${dir}. Grant write permission to that directory.` : `Permission denied writing ${dir}. Try: chown -R "$(whoami)" ${dir}`;
3867
+ }
3868
+ function ensureDir(dir) {
3869
+ try {
3870
+ fs32.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
3871
+ } catch (err) {
3872
+ if (err.code === "EACCES") {
3873
+ throw new OniroError(permissionHint(dir), err);
3874
+ }
3875
+ throw err;
3876
+ }
3877
+ }
3878
+ function createTokenStore(opts) {
3879
+ const logger = opts.logger ?? noopLogger;
3880
+ const authDir = opts.config.get("harmonyosAuthDir", defaultPaths.harmonyosAuthDir());
3881
+ const tokenPath = path31.join(authDir, TOKEN_FILE);
3882
+ const keyPath = path31.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
3883
+ function loadOrCreateKey() {
3884
+ if (fs32.existsSync(keyPath)) {
3885
+ const existing = fs32.readFileSync(keyPath);
3886
+ if (existing.length === KEY_LENGTH) return existing;
3887
+ logger.warn("[harmonyos] The token key file was malformed; regenerating (you will need to log in again).");
3888
+ }
3889
+ ensureDir(path31.dirname(keyPath));
3890
+ const key = crypto3.randomBytes(KEY_LENGTH);
3891
+ fs32.writeFileSync(keyPath, key, { mode: FILE_MODE });
3892
+ return key;
3893
+ }
3894
+ return {
3895
+ tokenPath,
3896
+ save(session) {
3897
+ if (!session.jwtToken) throw new OniroError("Refusing to store an empty token.");
3898
+ const payload = Buffer.from(JSON.stringify(session), "utf8");
3899
+ const blob = encrypt2(loadOrCreateKey(), payload);
3900
+ ensureDir(authDir);
3901
+ fs32.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
3902
+ },
3903
+ load() {
3904
+ if (!fs32.existsSync(tokenPath)) return null;
3905
+ try {
3906
+ const blob = JSON.parse(fs32.readFileSync(tokenPath, "utf8"));
3907
+ if (!isEncryptedBlob(blob)) return null;
3908
+ const { jwtToken, siteId } = JSON.parse(decrypt2(loadOrCreateKey(), blob).toString("utf8"));
3909
+ return typeof jwtToken === "string" && jwtToken && typeof siteId === "string" ? { jwtToken, siteId } : null;
3910
+ } catch (err) {
3911
+ const code = err.code;
3912
+ if (code === "EACCES" || code === "EPERM") {
3913
+ logger.warn(`[harmonyos] Cannot read ${tokenPath}: ${permissionHint(authDir)}`);
3914
+ return null;
3915
+ }
3916
+ logger.debug(`[harmonyos] Discarding unreadable token at ${tokenPath}.`);
3917
+ try {
3918
+ fs32.rmSync(tokenPath, { force: true });
3919
+ } catch {
3920
+ }
3921
+ return null;
3922
+ }
3923
+ },
3924
+ clear() {
3925
+ try {
3926
+ fs32.rmSync(tokenPath, { force: true });
3927
+ } catch (err) {
3928
+ throw new OniroError(`Failed to remove ${tokenPath}.`, err);
3929
+ }
3930
+ }
3931
+ };
3932
+ }
3933
+
3934
+ // src/harmonyos/auth/session.ts
3935
+ var crypto5 = __toESM(require("crypto"), 1);
3936
+
3937
+ // src/harmonyos/http.ts
3938
+ var import_undici = require("undici");
3939
+ var DEFAULT_HEADERS = {
3940
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
3941
+ "accept-language": "zh-CN",
3942
+ accept: "application/json, text/plain, */*"
3943
+ };
3944
+ var DEFAULT_TIMEOUT_MS = 2e4;
3945
+ var SENSITIVE_PARAMS = /* @__PURE__ */ new Set(["temptoken", "jwttoken", "code", "oauth2token", "accesstoken"]);
3946
+ function redactUrl(url) {
3947
+ let parsed;
3948
+ try {
3949
+ parsed = new URL(url);
3950
+ } catch {
3951
+ return url;
3952
+ }
3953
+ for (const key of [...parsed.searchParams.keys()]) {
3954
+ if (SENSITIVE_PARAMS.has(key.toLowerCase())) parsed.searchParams.set(key, "***");
3955
+ }
3956
+ return parsed.toString();
3957
+ }
3958
+ function previewBody(body, limit = 300) {
3959
+ const trimmed = body.trim();
3960
+ if (!trimmed) return "(empty body)";
3961
+ if (/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(trimmed)) return "(a token-shaped value)";
3962
+ const collapsed = trimmed.replace(/\s+/g, " ");
3963
+ return collapsed.length > limit ? `${collapsed.slice(0, limit)}\u2026` : collapsed;
3964
+ }
3965
+ function describeResponse(response) {
3966
+ const status = `HTTP ${response.statusCode}${response.statusText ? ` ${response.statusText}` : ""}`;
3967
+ return `${status}: ${previewBody(response.data)}`;
3968
+ }
3969
+ function buildUrl(url, query) {
3970
+ if (!query) return url;
3971
+ const parsed = new URL(url);
3972
+ for (const [key, value] of Object.entries(query)) {
3973
+ if (value !== void 0 && value !== null) parsed.searchParams.set(key, String(value));
3974
+ }
3975
+ return parsed.toString();
3976
+ }
3977
+ function createHttpClient(opts = {}) {
3978
+ const logger = opts.logger ?? noopLogger;
3979
+ const dispatcher = new import_undici.EnvHttpProxyAgent();
3980
+ async function send(method, url, options) {
3981
+ const target = buildUrl(url, options.query);
3982
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
3983
+ const headers = { ...DEFAULT_HEADERS, ...options.headers };
3984
+ let body;
3985
+ if (options.body !== void 0) {
3986
+ body = JSON.stringify(options.body);
3987
+ headers["content-type"] = "application/json";
3988
+ }
3989
+ logger.debug(`[harmonyos] ${method} ${redactUrl(target)}`);
3990
+ try {
3991
+ return await (0, import_undici.fetch)(target, {
3992
+ method,
3993
+ headers,
3994
+ body,
3995
+ signal: AbortSignal.timeout(timeoutMs),
3996
+ dispatcher
3997
+ });
3998
+ } catch (err) {
3999
+ const reason = err instanceof Error && err.name === "TimeoutError" ? `timed out after ${timeoutMs}ms` : err instanceof Error ? err.message : String(err);
4000
+ throw new OniroError(
4001
+ `${method} ${redactUrl(target)} failed: ${reason}. Check the network connection and any proxy configuration (HTTPS_PROXY / HTTP_PROXY).`,
4002
+ err
4003
+ );
4004
+ }
4005
+ }
4006
+ async function request(method, url, options = {}) {
4007
+ const response = await send(method, url, options);
4008
+ return { data: await response.text(), statusCode: response.status, statusText: response.statusText };
4009
+ }
4010
+ return {
4011
+ get: (url, o) => request("GET", url, o),
4012
+ post: (url, o) => request("POST", url, o),
4013
+ delete: (url, o) => request("DELETE", url, o),
4014
+ async getBinary(url, o = {}) {
4015
+ const response = await send("GET", url, o);
4016
+ if (!response.ok) {
4017
+ throw new OniroError(`Download of ${redactUrl(response.url || url)} failed (HTTP ${response.status}).`);
4018
+ }
4019
+ return Buffer.from(await response.arrayBuffer());
4020
+ }
4021
+ };
4022
+ }
4023
+
4024
+ // src/harmonyos/auth/callbackServer.ts
4025
+ var http2 = __toESM(require("http"), 1);
4026
+ var crypto4 = __toESM(require("crypto"), 1);
4027
+ var CALLBACK_PATH = "/callback";
4028
+ var MAX_BODY_BYTES = 65536;
4029
+ function codeMatches(code, secret) {
4030
+ const a = Buffer.from(code ?? "", "utf8");
4031
+ const b = Buffer.from(secret, "utf8");
4032
+ return a.length === b.length && crypto4.timingSafeEqual(a, b);
4033
+ }
4034
+ function isQuit(quit) {
4035
+ return quit === "true" || quit === "access_denied" || quit === "quit";
4036
+ }
4037
+ async function startCallbackServer(opts) {
4038
+ let resolveCallback;
4039
+ let rejectCallback;
4040
+ const callback = new Promise((resolve6, reject) => {
4041
+ resolveCallback = resolve6;
4042
+ rejectCallback = reject;
4043
+ });
4044
+ callback.catch(() => {
4045
+ });
4046
+ const server = http2.createServer((req, res) => {
4047
+ const host = (req.headers.host ?? "").toLowerCase();
4048
+ const port2 = server.address()?.port;
4049
+ if (host !== `127.0.0.1:${port2}` && host !== `localhost:${port2}`) {
4050
+ res.writeHead(400);
4051
+ res.end("Bad Host");
4052
+ return;
4053
+ }
4054
+ const url = new URL(req.url ?? "", `http://${host}`);
4055
+ if (url.pathname !== CALLBACK_PATH) {
4056
+ res.writeHead(404);
4057
+ res.end("Not Found");
4058
+ return;
4059
+ }
4060
+ const finish = (params) => {
4061
+ if (!codeMatches(params.get("code"), opts.clientSecret)) {
4062
+ res.writeHead(400);
4063
+ res.end("Bad Request");
4064
+ return;
4065
+ }
4066
+ if (isQuit(params.get("quit"))) {
4067
+ res.writeHead(302, { Location: `${opts.baseUrl}/${opts.failedPath}` });
4068
+ res.end();
4069
+ rejectCallback(new CancelledError("Login was cancelled in the browser."));
4070
+ return;
4071
+ }
4072
+ const tempToken = params.get("tempToken");
4073
+ const siteId = params.get("siteId");
4074
+ if (!tempToken || !siteId) {
4075
+ res.writeHead(400);
4076
+ res.end("Bad Request");
4077
+ return;
4078
+ }
4079
+ res.writeHead(302, { Location: `${opts.baseUrl}/${opts.successPath}` });
4080
+ res.end();
4081
+ resolveCallback({ tempToken, siteId });
4082
+ };
4083
+ if (req.method === "POST") {
4084
+ let body = "";
4085
+ let size = 0;
4086
+ req.on("data", (chunk) => {
4087
+ size += chunk.length;
4088
+ if (size > MAX_BODY_BYTES) {
4089
+ req.destroy(new Error("Request body too large"));
4090
+ return;
4091
+ }
4092
+ body += chunk.toString();
4093
+ });
4094
+ req.on("end", () => finish(body.trim() ? new URLSearchParams(body) : url.searchParams));
4095
+ } else {
4096
+ finish(url.searchParams);
4097
+ }
4098
+ });
4099
+ server.keepAliveTimeout = 1;
4100
+ await new Promise((resolve6, reject) => {
4101
+ server.once("error", (err) => reject(new OniroError("Failed to start the local login callback server.", err)));
4102
+ server.listen(0, "127.0.0.1", resolve6);
4103
+ });
4104
+ const port = server.address().port;
4105
+ return {
4106
+ port,
4107
+ waitForCallback(timeoutMs) {
4108
+ let timer;
4109
+ const timeout = new Promise((_, reject) => {
4110
+ timer = setTimeout(
4111
+ () => reject(new OniroError(`Timed out after ${Math.round(timeoutMs / 1e3)}s waiting for the browser login to complete.`)),
4112
+ timeoutMs
4113
+ );
4114
+ });
4115
+ return Promise.race([callback, timeout]).finally(() => clearTimeout(timer));
4116
+ },
4117
+ stop() {
4118
+ return new Promise((resolve6) => {
4119
+ server.closeAllConnections?.();
4120
+ const timer = setTimeout(resolve6, 100);
4121
+ server.close(() => {
4122
+ clearTimeout(timer);
4123
+ resolve6();
4124
+ });
4125
+ });
4126
+ }
4127
+ };
4128
+ }
4129
+
4130
+ // src/harmonyos/auth/browser.ts
4131
+ var import_node_child_process5 = require("child_process");
4132
+ function isSafeUrl(url) {
4133
+ try {
4134
+ const parsed = new URL(url);
4135
+ return ["http:", "https:"].includes(parsed.protocol) && parsed.hostname !== "" && !url.includes('"');
4136
+ } catch {
4137
+ return false;
4138
+ }
4139
+ }
4140
+ function escapeForCmd(s) {
4141
+ return s.replace(/[&|<>()^%!]/g, (c) => `^${c}`);
4142
+ }
4143
+ function openBrowser(url) {
4144
+ if (!isSafeUrl(url)) {
4145
+ return Promise.reject(new OniroError(`Refusing to open unsafe URL: ${JSON.stringify(url)}`));
4146
+ }
4147
+ const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", '""', escapeForCmd(url)]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
4148
+ const child = (0, import_node_child_process5.spawn)(command, args, {
4149
+ stdio: "ignore",
4150
+ shell: false,
4151
+ detached: true,
4152
+ windowsHide: true
4153
+ });
4154
+ child.unref();
4155
+ return new Promise((resolve6, reject) => {
4156
+ child.on("error", (err) => reject(new OniroError(`Failed to open a browser (${command}).`, err)));
4157
+ child.on(
4158
+ "exit",
4159
+ (code) => code === 0 ? resolve6() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
4160
+ );
4161
+ });
4162
+ }
4163
+
4164
+ // src/harmonyos/auth/session.ts
4165
+ var NOT_SIGNED_IN = "Not signed in. Run `oniro-app auth login` first.";
4166
+ function createHarmonyOsSession(opts) {
4167
+ const logger = opts.logger ?? noopLogger;
4168
+ const http3 = opts.http ?? createHttpClient({ logger });
4169
+ const store = opts.tokenStore ?? createTokenStore({ config: opts.config, logger });
4170
+ async function exchangeTempToken(site, tempToken) {
4171
+ const response = await http3.get(`${site.loginUrl}/${AUTH_PATHS.TEMP_TOKEN_CHECK}`, {
4172
+ query: {
4173
+ // The page sometimes appends extra `&`-joined parameters to the token.
4174
+ tempToken: tempToken.split("&")[0],
4175
+ site: site.siteParam,
4176
+ version: HUAWEI_API_VERSION,
4177
+ appid: HUAWEI_APP_ID
4178
+ }
4179
+ });
4180
+ const jwtToken = response.data.trim();
4181
+ if (response.statusCode === 200 && /^[^.]+\.[^.]+\.[^.]+$/.test(jwtToken)) return jwtToken;
4182
+ throw new OniroError(
4183
+ `${site.loginUrl} would not exchange the login token for a session (${describeResponse(response)}). Login tokens are single-use and short-lived; run \`oniro-app auth login\` again.`
4184
+ );
4185
+ }
4186
+ async function userFromJwt(site, jwtToken, refresh) {
4187
+ const response = await http3.get(`${site.loginUrl}/${AUTH_PATHS.JWT_TOKEN_CHECK}`, {
4188
+ headers: { refresh: String(refresh), jwtToken }
4189
+ });
4190
+ let info;
4191
+ try {
4192
+ if (response.statusCode !== 200) throw new Error();
4193
+ info = JSON.parse(response.data);
4194
+ } catch {
4195
+ throw new OniroError(`Could not validate the session with ${site.loginUrl} (${describeResponse(response)}).`);
4196
+ }
4197
+ const user = info.userInfo;
4198
+ if (!info.status || !user?.accessToken) return null;
4199
+ return {
4200
+ userId: user.userId ?? "",
4201
+ userName: user.name ?? "",
4202
+ accessToken: user.accessToken,
4203
+ countryCode: user.nationalCode ?? "",
4204
+ siteCode: site.code,
4205
+ isRealName: String(user.realName) === "true"
4206
+ };
4207
+ }
4208
+ async function getUserInfo(o = {}) {
4209
+ const stored = store.load();
4210
+ const site = stored && siteForId(stored.siteId);
4211
+ const user = site ? await userFromJwt(site, stored.jwtToken, o.refresh ?? false) : null;
4212
+ if (stored && !user) {
4213
+ logger.debug("[harmonyos] The stored session is no longer valid; clearing it.");
4214
+ store.clear();
4215
+ }
4216
+ return user;
4217
+ }
4218
+ async function requireUser() {
4219
+ const user = await getUserInfo({ refresh: true });
4220
+ if (!user) throw new OniroError(NOT_SIGNED_IN);
4221
+ return user;
4222
+ }
4223
+ async function getDeveloperAgreement(user) {
4224
+ try {
4225
+ const response = await http3.get(`${HUAWEI_SITES[user.siteCode].loginUrl}/${AUTH_PATHS.UNREALNAME_AGREEMENT}`, {
4226
+ headers: { accessToken: user.accessToken }
4227
+ });
4228
+ const payload = JSON.parse(response.data);
4229
+ if (response.statusCode !== 200 || String(payload.success) !== "true" || !payload.body) return null;
4230
+ return { signed: payload.body.signedFlag === true, latest: payload.body.signLastestFlag === true };
4231
+ } catch {
4232
+ return null;
4233
+ }
4234
+ }
4235
+ return {
4236
+ tokenPath: store.tokenPath,
4237
+ getUserInfo,
4238
+ getDeveloperAgreement,
4239
+ async login(o = {}) {
4240
+ const start = HUAWEI_SITES.CN.loginUrl;
4241
+ const clientSecret = crypto5.randomUUID().replace(/-/g, "");
4242
+ const server = await startCallbackServer({
4243
+ clientSecret,
4244
+ baseUrl: start,
4245
+ successPath: AUTH_PATHS.LOGIN_SUCCESS,
4246
+ failedPath: AUTH_PATHS.LOGIN_FAILED
4247
+ });
4248
+ try {
4249
+ const loginUrl = `${start}/${AUTH_PATHS.AUTH_APPLY}?port=${server.port}&appid=${HUAWEI_APP_ID}&code=${clientSecret}`;
4250
+ opts.onLoginUrl?.(loginUrl);
4251
+ openBrowser(loginUrl).catch((err) => {
4252
+ logger.warn(`[harmonyos] Could not open a browser automatically: ${err.message}`);
4253
+ });
4254
+ const callback = await server.waitForCallback(o.timeoutMs ?? LOGIN_TIMEOUT_MS);
4255
+ const site = siteForId(callback.siteId);
4256
+ if (!site) {
4257
+ throw new OniroError(`The account belongs to a Huawei region oniro-app does not know (siteId ${callback.siteId}).`);
4258
+ }
4259
+ const jwtToken = await exchangeTempToken(site, callback.tempToken);
4260
+ const user = await userFromJwt(site, jwtToken, false);
4261
+ if (!user) throw new OniroError("Login did not yield a usable session. Try again.");
4262
+ store.save({ jwtToken, siteId: site.siteId });
4263
+ logger.info(`[harmonyos] Signed in as ${user.userName || user.userId} (${site.code}).`);
4264
+ return user;
4265
+ } finally {
4266
+ await server.stop();
4267
+ }
4268
+ },
4269
+ async logout() {
4270
+ const stored = store.load();
4271
+ if (!stored) return false;
4272
+ const site = siteForId(stored.siteId);
4273
+ try {
4274
+ if (site) {
4275
+ await http3.post(`${site.loginUrl}/${AUTH_PATHS.LOGOUT}`, {
4276
+ query: { jwtToken: stored.jwtToken },
4277
+ timeoutMs: 5e3
4278
+ });
4279
+ }
4280
+ } catch {
4281
+ } finally {
4282
+ store.clear();
4283
+ }
4284
+ return true;
4285
+ },
4286
+ async listTeams() {
4287
+ const user = await requireUser();
4288
+ const response = await http3.get(AGC_TEAMS_URL, {
4289
+ headers: {
4290
+ oauth2Token: user.accessToken,
4291
+ uid: user.userId,
4292
+ source: "cli",
4293
+ lang: HUAWEI_SITES[user.siteCode].language
4294
+ },
4295
+ timeoutMs: 15e3
4296
+ });
4297
+ if (response.statusCode === 401) {
4298
+ throw new OniroError("The session has expired. Run `oniro-app auth login` again.");
4299
+ }
4300
+ if (response.statusCode !== 200) {
4301
+ throw new OniroError(`Could not list AGC teams (${describeResponse(response)}).`);
4302
+ }
4303
+ return parseTeamList(response.data);
4304
+ },
4305
+ async resolveAgcAuth(o = {}) {
4306
+ const user = await requireUser();
4307
+ if (!user.isRealName) {
4308
+ const agreement = await getDeveloperAgreement(user);
4309
+ if (agreement && !(agreement.signed && agreement.latest)) {
4310
+ throw new OniroError(
4311
+ "This Huawei account has not accepted the current HUAWEI Developer Basic Service Agreement, which accounts without real-name verification need before AppGallery Connect issues signing material. Accept it in AppGallery Connect (https://developer.huawei.com/consumer/en/service/josp/agc/index.html) or by running automatic signing once in DevEco Studio, then try again."
4312
+ );
4313
+ }
4314
+ }
4315
+ return {
4316
+ uid: user.userId,
4317
+ // Personal accounts sign under their own user id; `--team-id` picks another team.
4318
+ teamId: o.teamId ?? user.userId,
4319
+ accessToken: user.accessToken,
4320
+ baseUrl: HUAWEI_SITES[user.siteCode].agcBaseUrl
4321
+ };
4322
+ }
4323
+ };
4324
+ }
4325
+ function parseTeamList(body) {
4326
+ let payload;
4327
+ try {
4328
+ payload = JSON.parse(body);
4329
+ } catch {
4330
+ return [];
4331
+ }
4332
+ if (payload.ret && payload.ret.code !== 0) {
4333
+ throw new OniroError(
4334
+ `AppGallery Connect refused the team list (code ${payload.ret.code}${payload.ret.msg ? `: ${payload.ret.msg}` : ""}).`
4335
+ );
4336
+ }
4337
+ if (!Array.isArray(payload.teams)) return [];
4338
+ return payload.teams.map((t) => ({ id: String(t?.id ?? ""), name: String(t?.name ?? "") })).filter((t) => t.id);
4339
+ }
4340
+
4341
+ // src/harmonyos/signing/errors.ts
4342
+ var AgcError = class extends OniroError {
4343
+ constructor(message, statusCode, retCode) {
4344
+ super(message);
4345
+ this.statusCode = statusCode;
4346
+ this.retCode = retCode;
4347
+ this.name = "AgcError";
4348
+ }
4349
+ statusCode;
4350
+ retCode;
4351
+ };
4352
+ var SIGNING_ERRORS = {
4353
+ FORBIDDEN: "This account has no AppGallery Connect permission for the selected team. Ask the team administrator for access, or pass --team-id for a team you do have access to (`oniro-app auth team list`).",
4354
+ UNAUTHORIZED: "AppGallery Connect rejected the access token. Run `oniro-app auth login` again.",
4355
+ NETWORK: "Could not reach AppGallery Connect. Check the network connection and any proxy configuration (HTTPS_PROXY / HTTP_PROXY).",
4356
+ USER_NOT_HARMONY: "This Huawei account is not enrolled for HarmonyOS development. Request HarmonyOS access for it in AppGallery Connect.",
4357
+ CERT_REQUEST: "AppGallery Connect did not issue the debug certificate.",
4358
+ CERT_LIMIT: "This team has reached its debug-certificate limit. Delete an unused certificate in AppGallery Connect and retry.",
4359
+ DEVICE_ADD: "AppGallery Connect did not register the device.",
4360
+ DEVICE_LIMIT: "This team has reached its device limit. Remove an unused device in AppGallery Connect and retry.",
4361
+ DEVICE_NAME_REPEAT: "A device with that name is already registered. Retry.",
4362
+ DEVICE_NONE: "No device is connected and none is registered to this team, but a debug profile must name at least one. Connect a device and retry.",
4363
+ PROFILE_ADD: "AppGallery Connect did not issue the provisioning profile.",
4364
+ PROFILE_NAME_REPEAT: "A provisioning profile with that name already exists in AppGallery Connect. Retry.",
4365
+ PROVISION_LIMIT: "This team has reached its debug-profile limit. Delete unused profiles in AppGallery Connect and retry.",
4366
+ CERT_INVALID: "AppGallery Connect returned an unreadable certificate. Retry with --force.",
4367
+ CERT_EXPIRED: "The signing certificate is outside its validity window. Check the system clock, then retry with --force.",
4368
+ CERT_PROFILE_MISMATCH: "The provisioning profile was not issued for the downloaded certificate. Retry with --force.",
4369
+ KEYSTORE_CERT_MISMATCH: "The local keystore does not hold the key the downloaded certificate was issued for. Retry with --force.",
4370
+ BUNDLE_NAME_INVALID: "Invalid bundle name. It must be 7\u2013128 characters, start with a letter, and have at least three dot-separated segments (for example com.example.app)."
4371
+ };
4372
+ var RET_CODE_ERRORS = {
4373
+ "205389872": SIGNING_ERRORS.CERT_LIMIT,
4374
+ "205389904": SIGNING_ERRORS.USER_NOT_HARMONY,
4375
+ "205389859": SIGNING_ERRORS.DEVICE_LIMIT,
4376
+ "205389857": SIGNING_ERRORS.DEVICE_NAME_REPEAT,
4377
+ "205389938": SIGNING_ERRORS.PROVISION_LIMIT,
4378
+ "205389830": SIGNING_ERRORS.PROFILE_NAME_REPEAT
4379
+ };
4380
+ var PROXY_BLOCKED = "Openproxy_Blocked_URL_list";
4381
+ function readRet(body) {
4382
+ try {
4383
+ let ret = JSON.parse(body).ret;
4384
+ if (typeof ret === "string") ret = JSON.parse(ret);
4385
+ return ret && typeof ret === "object" ? ret : null;
4386
+ } catch {
4387
+ return null;
4388
+ }
4389
+ }
4390
+ var FALLBACK = {
4391
+ cert: SIGNING_ERRORS.CERT_REQUEST,
4392
+ device: SIGNING_ERRORS.DEVICE_ADD,
4393
+ profile: SIGNING_ERRORS.PROFILE_ADD
4394
+ };
4395
+ function mapAgcError(operation, statusCode, reasonPhrase, body) {
4396
+ if (statusCode === 403) {
4397
+ return new AgcError(reasonPhrase === PROXY_BLOCKED ? SIGNING_ERRORS.NETWORK : SIGNING_ERRORS.FORBIDDEN, 403);
4398
+ }
4399
+ if (statusCode === 401) return new AgcError(SIGNING_ERRORS.UNAUTHORIZED, 401);
4400
+ const ret = readRet(body);
4401
+ const retCode = ret?.code === void 0 ? void 0 : String(ret.code);
4402
+ const msg = typeof ret?.msg === "string" && ret.msg.trim() ? ret.msg : void 0;
4403
+ return new AgcError(retCode && RET_CODE_ERRORS[retCode] || msg || FALLBACK[operation], statusCode, retCode);
4404
+ }
4405
+
4406
+ // src/harmonyos/signing/profile.ts
4407
+ var fs33 = __toESM(require("fs"), 1);
4408
+ var import_node_crypto3 = require("crypto");
4409
+ var import_node_forge = __toESM(require("node-forge"), 1);
4410
+ function extractProfileJson(buffer) {
4411
+ const text = buffer.toString("latin1");
4412
+ for (let start = text.indexOf("{"); start !== -1; start = text.indexOf("{", start + 1)) {
4413
+ let depth = 0;
4414
+ let inString = false;
4415
+ let escaped = false;
4416
+ for (let i = start; i < text.length; i++) {
4417
+ const ch = text[i];
4418
+ if (escaped) escaped = false;
4419
+ else if (inString) {
4420
+ if (ch === "\\") escaped = true;
4421
+ else if (ch === '"') inString = false;
4422
+ } else if (ch === '"') inString = true;
4423
+ else if (ch === "{") depth++;
4424
+ else if (ch === "}" && --depth === 0) {
4425
+ const json = Buffer.from(text.slice(start, i + 1), "latin1").toString("utf8");
4426
+ try {
4427
+ if ("bundle-info" in JSON.parse(json)) return json;
4428
+ } catch {
4429
+ }
4430
+ break;
4431
+ }
4432
+ }
4433
+ }
4434
+ return null;
4435
+ }
4436
+ function readProvisionProfile(profilePath) {
4437
+ let raw;
4438
+ try {
4439
+ const json = extractProfileJson(fs33.readFileSync(profilePath));
4440
+ if (!json) return null;
4441
+ raw = JSON.parse(json);
4442
+ } catch {
4443
+ return null;
4444
+ }
4445
+ const str = (v) => typeof v === "string" ? v : void 0;
4446
+ const strings = (v) => Array.isArray(v) ? v.filter((x2) => typeof x2 === "string") : [];
4447
+ const notAfter = raw.validity?.["not-after"];
4448
+ const bundle = raw["bundle-info"] ?? {};
4449
+ return {
4450
+ bundleName: str(bundle["bundle-name"]),
4451
+ developerId: str(bundle["developer-id"]),
4452
+ // Epoch seconds.
4453
+ notAfter: typeof notAfter === "number" ? new Date(notAfter * 1e3) : void 0,
4454
+ deviceIds: strings(raw["debug-info"]?.["device-ids"]).map((d) => d.toUpperCase()),
4455
+ aclPermissions: strings(raw.acls?.["allowed-acls"]),
4456
+ developmentCertificate: str(bundle["development-certificate"])
4457
+ };
4458
+ }
4459
+ function parseCertificate(pem) {
4460
+ try {
4461
+ return new import_node_crypto3.X509Certificate(pem);
4462
+ } catch {
4463
+ throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
4464
+ }
4465
+ }
4466
+ function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
4467
+ try {
4468
+ const der = import_node_forge.default.util.createBuffer(fs33.readFileSync(p12Path).toString("binary"));
4469
+ const p12 = import_node_forge.default.pkcs12.pkcs12FromAsn1(import_node_forge.default.asn1.fromDer(der), keyPwd);
4470
+ const certBag = import_node_forge.default.pki.oids.certBag ?? "1.2.840.113549.1.12.10.1.3";
4471
+ const wanted = certificate.publicKey.export({ type: "spki", format: "der" });
4472
+ return (p12.getBags({ bagType: certBag })[certBag] ?? []).some((bag) => {
4473
+ if (bag.attributes?.friendlyName?.[0]?.toLowerCase() !== keyAlias.toLowerCase()) return false;
4474
+ const asn1 = bag.cert ? import_node_forge.default.pki.certificateToAsn1(bag.cert) : bag.asn1;
4475
+ if (!asn1) return false;
4476
+ const bagCert = new import_node_crypto3.X509Certificate(Buffer.from(import_node_forge.default.asn1.toDer(asn1).getBytes(), "binary"));
4477
+ return bagCert.publicKey.export({ type: "spki", format: "der" }).equals(wanted);
4478
+ });
4479
+ } catch {
4480
+ return false;
4481
+ }
4482
+ }
4483
+ function verifySigningMaterial(opts) {
4484
+ const pems = fs33.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
4485
+ const chain = (pems ?? []).map(parseCertificate);
4486
+ const leaf = chain.find((c) => !c.ca) ?? chain.at(-1);
4487
+ if (!leaf) throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
4488
+ const now = opts.now ?? /* @__PURE__ */ new Date();
4489
+ if (chain.some((c) => now < new Date(c.validFrom) || now > new Date(c.validTo))) {
4490
+ throw new AgcError(SIGNING_ERRORS.CERT_EXPIRED);
4491
+ }
4492
+ const issuedFor = readProvisionProfile(opts.profilePath)?.developmentCertificate;
4493
+ if (!issuedFor || parseCertificate(issuedFor).fingerprint256 !== leaf.fingerprint256) {
4494
+ throw new AgcError(SIGNING_ERRORS.CERT_PROFILE_MISMATCH);
4495
+ }
4496
+ if (!keystoreHoldsKey(opts.p12Path, opts.keyAlias, opts.keyPwd, leaf)) {
4497
+ throw new AgcError(SIGNING_ERRORS.KEYSTORE_CERT_MISMATCH);
4498
+ }
4499
+ }
4500
+
4501
+ // src/harmonyos/signing/autoSign.ts
4502
+ var crypto9 = __toESM(require("crypto"), 1);
4503
+ var fs38 = __toESM(require("fs"), 1);
4504
+ var path37 = __toESM(require("path"), 1);
4505
+ var import_json57 = __toESM(require("json5"), 1);
4506
+
4507
+ // src/harmonyos/signing/agc.ts
4508
+ var crypto6 = __toESM(require("crypto"), 1);
4509
+ var fs34 = __toESM(require("fs"), 1);
4510
+ var path32 = __toESM(require("path"), 1);
4511
+ var ENDPOINTS = {
4512
+ CERT_LIST: "/api/cps/harmony-cert-manage/v1/cert/list",
4513
+ CERT_ADD: "/api/cps/harmony-cert-manage/v1/cert/add",
4514
+ CERT_DELETE: "/api/cps/harmony-cert-manage/v1/cert/delete",
4515
+ /** Exchanges an object id for a short-lived signed download URL. */
4516
+ OBJECT_URL: "/api/amis/app-manage/v1/objects/url/reapply",
4517
+ DEVICE_LIST: "/api/cps/device-manage/v1/device/list",
4518
+ DEVICE_ADD: "/api/cps/device-manage/v1/device/add",
4519
+ /** Debug ("test") provisioning profiles — the only kind oniro-app issues. */
4520
+ PROVISION_ADD: "/api/cps/provision-manage/v1/ide/test/provision/add",
4521
+ PROVISION_DELETE: "/api/cps/provision-manage/v1/provision/delete"
4522
+ };
4523
+ var DEVICE_TYPE_CODES = { liteWearable: 1, wearable: 2, tv: 3, phone: 4 };
4524
+ function createAgcClient(http3, auth) {
4525
+ async function call(operation, method, endpoint, opts = {}) {
4526
+ const response = await http3[method](`${auth.baseUrl}${endpoint}`, {
4527
+ ...opts,
4528
+ headers: { uid: auth.uid, teamId: auth.teamId, oauth2Token: auth.accessToken }
4529
+ });
4530
+ if (response.statusCode !== 200 || readRet(response.data)?.code !== 0) {
4531
+ throw mapAgcError(operation, response.statusCode, response.statusText, response.data);
4532
+ }
4533
+ return JSON.parse(response.data);
4534
+ }
4535
+ return {
4536
+ async listCertificates() {
4537
+ return (await call("cert", "post", ENDPOINTS.CERT_LIST)).certList ?? [];
4538
+ },
4539
+ async addCertificate(certName, csr) {
4540
+ await call("cert", "post", ENDPOINTS.CERT_ADD, { body: { csr, certName, certType: "1" } });
4541
+ },
4542
+ async deleteCertificate(certId) {
4543
+ await call("cert", "delete", ENDPOINTS.CERT_DELETE, { body: { certIds: [certId] } });
4544
+ },
4545
+ /** Every device registered to the team, across pages. */
4546
+ async listDevices() {
4547
+ const pageSize = 100;
4548
+ const devices = [];
4549
+ for (let start = 1; ; start++) {
4550
+ const page = await call("device", "get", ENDPOINTS.DEVICE_LIST, {
4551
+ query: { encodeFlag: 0, start, pageSize }
4552
+ });
4553
+ devices.push(...page.list ?? []);
4554
+ if (!page.list?.length || devices.length >= (page.totalCount ?? 0)) return devices;
4555
+ }
4556
+ },
4557
+ async addDevice(udid, deviceKind) {
4558
+ const deviceName = `oniro_app_device_${crypto6.randomBytes(4).toString("hex")}`;
4559
+ await call("device", "post", ENDPOINTS.DEVICE_ADD, {
4560
+ body: { deviceName, udid, deviceType: String(DEVICE_TYPE_CODES[deviceKind] ?? DEVICE_TYPE_CODES.phone) }
4561
+ });
4562
+ },
4563
+ /** Issue a debug profile; returns its id and object handle. */
4564
+ async addDebugProfile(opts) {
4565
+ return call("profile", "post", ENDPOINTS.PROVISION_ADD, {
4566
+ body: {
4567
+ provisionName: opts.provisionName,
4568
+ packageName: opts.bundleName,
4569
+ certList: [opts.certId],
4570
+ deviceList: opts.deviceIds,
4571
+ ...opts.aclPermissions.length ? { aclPermissionList: opts.aclPermissions } : {}
4572
+ }
4573
+ });
4574
+ },
4575
+ async deleteProfile(id) {
4576
+ await call("profile", "delete", ENDPOINTS.PROVISION_DELETE, { query: { id } });
4577
+ },
4578
+ /**
4579
+ * Download a certificate or profile object to `dest`, checking the digest AGC
4580
+ * supplies with the URL. Nothing is written unless it matches.
4581
+ */
4582
+ async download(operation, objectId, dest) {
4583
+ const { urlsInfo } = await call(
4584
+ operation,
4585
+ "post",
4586
+ ENDPOINTS.OBJECT_URL,
4587
+ { body: { sourceUrls: objectId } }
4588
+ );
4589
+ const target = urlsInfo?.[0];
4590
+ if (!target) throw mapAgcError(operation, 200, "", "");
4591
+ const buffer = await http3.getBinary(target.newUrl, { timeoutMs: 6e4 });
4592
+ const actual = crypto6.createHash("sha256").update(buffer).digest("hex");
4593
+ if (target.sha256 && actual !== target.sha256.toLowerCase()) {
4594
+ throw new ChecksumMismatchError(target.sha256, actual);
4595
+ }
4596
+ fs34.mkdirSync(path32.dirname(dest), { recursive: true, mode: 448 });
4597
+ fs34.writeFileSync(dest, buffer, { mode: 384 });
4598
+ }
4599
+ };
4600
+ }
4601
+
4602
+ // src/harmonyos/signing/devices.ts
4603
+ function parseUdid(stdout) {
4604
+ return stdout.match(/\b[A-Fa-f0-9]{64}\b/)?.[0].toUpperCase() ?? "";
4605
+ }
4606
+ function parseDeviceKind(stdout) {
4607
+ return ["liteWearable", "wearable", "tv"].find((kind) => stdout.includes(kind)) ?? "phone";
4608
+ }
4609
+ async function collectLocalDevices(config, logger) {
4610
+ const devices = [];
4611
+ for (const target of await listDevices(config, { logger, timeoutMs: 15e3 })) {
4612
+ if (target.status !== "Connected") continue;
4613
+ const shell2 = async (...args) => (await hdcExec({ config, deviceSerial: target.serial, args: ["shell", ...args], timeoutMs: 15e3, logger })).stdout;
4614
+ try {
4615
+ const udid = parseUdid(await shell2("bm", "get", "-u"));
4616
+ if (!udid) {
4617
+ logger.warn(`[harmonyos] ${target.serial} did not report a UDID; skipping it.`);
4618
+ continue;
4619
+ }
4620
+ devices.push({ serial: target.serial, udid, kind: parseDeviceKind(await shell2("getprop", "hw_sc.build.os.deviceType")) });
4621
+ } catch (err) {
4622
+ logger.warn(`[harmonyos] Could not read ${target.serial}: ${err.message}`);
4623
+ }
4624
+ }
4625
+ return devices;
4626
+ }
4627
+ async function registerDevices(agc, connected, logger) {
4628
+ let devices = await agc.listDevices();
4629
+ const registered = new Set(devices.map((d) => d.udid.toUpperCase()));
4630
+ const unregistered = connected.filter((d) => !registered.has(d.udid));
4631
+ for (const device of unregistered) {
4632
+ logger.info(`[harmonyos] Registering ${device.serial} with AppGallery Connect...`);
4633
+ await agc.addDevice(device.udid, device.kind);
4634
+ }
4635
+ if (unregistered.length > 0) devices = await agc.listDevices();
4636
+ const deviceIds = devices.map((d) => d.id);
4637
+ if (deviceIds.length === 0) throw new AgcError(SIGNING_ERRORS.DEVICE_NONE);
4638
+ if (connected.length === 0) {
4639
+ logger.warn(
4640
+ `[harmonyos] No device is connected; the profile will name the ${deviceIds.length} device(s) already registered to this team.`
4641
+ );
4642
+ }
4643
+ return deviceIds;
4644
+ }
4645
+
4646
+ // src/harmonyos/signing/keystore.ts
4647
+ var crypto7 = __toESM(require("crypto"), 1);
4648
+ var fs36 = __toESM(require("fs"), 1);
4649
+ var path34 = __toESM(require("path"), 1);
4650
+
4651
+ // src/harmonyos/java.ts
4652
+ var fs35 = __toESM(require("fs"), 1);
4653
+ var path33 = __toESM(require("path"), 1);
4654
+ var JAVA_BIN = process.platform === "win32" ? "java.exe" : "java";
4655
+ function findJava(sdk, env = process.env) {
4656
+ const candidates = [
4657
+ env.JAVA_HOME && path33.join(env.JAVA_HOME, "bin", JAVA_BIN),
4658
+ sdk && path33.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
4659
+ // macOS: the JBR is itself a bundle inside Studio's Contents.
4660
+ sdk && path33.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
4661
+ ...(env.PATH ?? "").split(path33.delimiter).map((dir) => dir && path33.join(dir, JAVA_BIN))
4662
+ ];
4663
+ const found = candidates.find((c) => !!c && fs35.existsSync(c));
4664
+ if (found) return found;
4665
+ throw new OniroError(
4666
+ "No Java runtime found. HarmonyOS signing runs the SDK's hap-sign-tool.jar, which needs one: install a JDK and set JAVA_HOME, or install DevEco Studio, whose bundled runtime is used automatically."
4667
+ );
4668
+ }
4669
+
4670
+ // src/harmonyos/signing/keystore.ts
4671
+ var KEY_ALIAS = "debugKey";
4672
+ var SIGN_ALG = "SHA256withECDSA";
4673
+ function redactArgs(args) {
4674
+ return args.map((arg, i) => args[i - 1] === "-keyPwd" || args[i - 1] === "-keystorePwd" ? "******" : arg);
4675
+ }
4676
+ async function generateKeystoreAndCsr(opts) {
4677
+ const jar = path34.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
4678
+ if (!fs36.existsSync(jar)) {
4679
+ throw new OniroError(`hap-sign-tool.jar not found at ${jar}; the HarmonyOS SDK looks incomplete.`);
4680
+ }
4681
+ const java = findJava(opts.sdk);
4682
+ const keyPwd = crypto7.randomBytes(12).toString("base64url").replaceAll(/[-_]/g, "");
4683
+ fs36.mkdirSync(path34.dirname(opts.p12Path), { recursive: true, mode: 448 });
4684
+ const run = async (what, args) => {
4685
+ const argv = ["-jar", jar, ...args, "-keyAlias", KEY_ALIAS, "-keystoreFile", opts.p12Path, "-keystorePwd", keyPwd, "-keyPwd", keyPwd];
4686
+ opts.logger.debug(`[harmonyos] ${java} ${redactArgs(argv).join(" ")}`);
4687
+ const result = await runProcess({ command: java, args: argv, timeoutMs: 12e4, logger: opts.logger });
4688
+ if (result.code !== 0) {
4689
+ const detail = (result.stderr || result.stdout || "").trim();
4690
+ throw new OniroError(`hap-sign-tool failed to ${what} (exit ${result.code}).${detail ? `
4691
+ ${detail}` : ""}`);
4692
+ }
4693
+ };
4694
+ opts.logger.info("[harmonyos] Generating the local keystore and certificate signing request...");
4695
+ await run("generate the key pair", ["generate-keypair", "-keyAlg", "ECC", "-keySize", "NIST-P-256"]);
4696
+ await run("generate the CSR", ["generate-csr", "-subject", "CN=DebugKey", "-signAlg", SIGN_ALG, "-outFile", opts.csrPath]);
4697
+ return { keyPwd, csr: fs36.readFileSync(opts.csrPath, "utf8") };
4698
+ }
4699
+
4700
+ // src/harmonyos/signing/paths.ts
4701
+ var crypto8 = __toESM(require("crypto"), 1);
4702
+ var path35 = __toESM(require("path"), 1);
4703
+ function sanitizeNamePart(raw) {
4704
+ const safe = (raw?.trim() ?? "").replace(/[\\/:*?"<>|=.-]+/g, "_");
4705
+ return safe.slice(0, 64) || "default";
4706
+ }
4707
+ function resolveSigningMaterialPaths(config, productName, projectRoot) {
4708
+ const hash = crypto8.createHash("sha256").update(projectRoot, "utf8").digest("base64url").replaceAll(/[-_]/g, "");
4709
+ const base = path35.join(
4710
+ config.get("harmonyosSigningDir", defaultPaths.harmonyosSigningDir()),
4711
+ `${sanitizeNamePart(productName)}_${path35.basename(projectRoot)}_${hash}=`
4712
+ );
4713
+ return { p12Path: `${base}.p12`, csrPath: `${base}.csr`, cerPath: `${base}.cer`, profilePath: `${base}.p7b` };
4714
+ }
4715
+
4716
+ // src/harmonyos/signing/project.ts
4717
+ var fs37 = __toESM(require("fs"), 1);
4718
+ var path36 = __toESM(require("path"), 1);
4719
+ var import_json56 = __toESM(require("json5"), 1);
4720
+
4721
+ // src/harmonyos/signing/aclPermissionList.ts
4722
+ var ACL_PERMISSIONS = /* @__PURE__ */ new Set([
4723
+ "ohos.permission.ACCESS_DDK_HID",
4724
+ "ohos.permission.ACCESS_DDK_SCSI_PERIPHERAL",
4725
+ "ohos.permission.ACCESS_DDK_USB",
4726
+ "ohos.permission.ACCESS_DDK_USB_SERIAL",
4727
+ "ohos.permission.ACCESS_DISK_PHY_INFO",
4728
+ "ohos.permission.ACCESS_FIDO2_ONLINEAUTH",
4729
+ "ohos.permission.ACCESS_NET_TRACE_INFO",
4730
+ "ohos.permission.ACCESS_USER_FULL_DISK",
4731
+ "ohos.permission.ACCESS_VIRTUAL_SCREEN",
4732
+ "ohos.permission.CUSTOMIZE_SAVE_BUTTON",
4733
+ "ohos.permission.CUSTOM_SCREEN_RECORDING",
4734
+ "ohos.permission.DLP_GET_HIDE_STATUS",
4735
+ "ohos.permission.FILE_ACCESS_PERSIST",
4736
+ "ohos.permission.GET_ABILITY_INFO",
4737
+ "ohos.permission.GET_ETHERNET_LOCAL_MAC",
4738
+ "ohos.permission.GET_IP_MAC_INFO",
4739
+ "ohos.permission.GET_WIFI_LOCAL_MAC",
4740
+ "ohos.permission.GET_WIFI_PEERS_MAC",
4741
+ "ohos.permission.HOOK_KEY_EVENT",
4742
+ "ohos.permission.INPUT_MONITORING",
4743
+ "ohos.permission.INTERCEPT_INPUT_EVENT",
4744
+ "ohos.permission.KEEP_BACKGROUND_RUNNING_SYSTEM",
4745
+ "ohos.permission.LINKTURBO",
4746
+ "ohos.permission.MANAGE_APN_SETTING",
4747
+ "ohos.permission.MANAGE_PASTEBOARD_APP_SHARE_OPTION",
4748
+ "ohos.permission.MANAGE_SCREEN_TIME_GUARD",
4749
+ "ohos.permission.MANAGE_UDMF_APP_SHARE_OPTION",
4750
+ "ohos.permission.PERSISTENT_BLUETOOTH_PEERS_MAC",
4751
+ "ohos.permission.PERSONAL_MANAGE_RESTRICTIONS",
4752
+ "ohos.permission.PRELOAD_FILE",
4753
+ "ohos.permission.READ_AUDIO",
4754
+ "ohos.permission.READ_CONTACTS",
4755
+ "ohos.permission.READ_IMAGEVIDEO",
4756
+ "ohos.permission.READ_LOCAL_DEVICE_NAME",
4757
+ "ohos.permission.READ_PASTEBOARD",
4758
+ "ohos.permission.READ_WRITE_DESKTOP_DIRECTORY",
4759
+ "ohos.permission.READ_WRITE_DOCUMENTS_DIRECTORY",
4760
+ "ohos.permission.READ_WRITE_DOWNLOAD_DIRECTORY",
4761
+ "ohos.permission.READ_WRITE_USB_DEV",
4762
+ "ohos.permission.READ_WRITE_USER_FILE",
4763
+ "ohos.permission.SET_PAC_URL",
4764
+ "ohos.permission.SET_SYSTEMSHARE_APPLAUNCHTRUSTLIST",
4765
+ "ohos.permission.SHORT_TERM_WRITE_IMAGEVIDEO",
4766
+ "ohos.permission.START_PROVISIONING_MESSAGE",
4767
+ "ohos.permission.SUBSCRIBE_NOTIFICATION",
4768
+ "ohos.permission.SYSTEM_FLOAT_WINDOW",
4769
+ "ohos.permission.USE_FLOAT_BALL",
4770
+ "ohos.permission.USE_FRAUD_APP_PICKER",
4771
+ "ohos.permission.USE_FRAUD_CALL_LOG_PICKER",
4772
+ "ohos.permission.USE_FRAUD_MESSAGES_PICKER",
4773
+ "ohos.permission.WEB_NATIVE_MESSAGING",
4774
+ "ohos.permission.WRITE_AUDIO",
4775
+ "ohos.permission.WRITE_CONTACTS",
4776
+ "ohos.permission.WRITE_IMAGEVIDEO",
4777
+ "ohos.permission.atomicService.MANAGE_STORAGE",
4778
+ "ohos.permission.kernel.ALLOW_EXECUTABLE_FORT_MEMORY",
4779
+ "ohos.permission.kernel.ALLOW_WRITABLE_CODE_MEMORY",
4780
+ "ohos.permission.kernel.DISABLE_CODE_MEMORY_PROTECTION",
4781
+ "ohos.permission.kernel.DISABLE_GOTPLT_RO_PROTECTION"
4782
+ ]);
4783
+
4784
+ // src/harmonyos/signing/project.ts
4785
+ function readRequestedPermissions(moduleConfigPath) {
4786
+ if (!fs37.existsSync(moduleConfigPath)) return [];
4787
+ try {
4788
+ const parsed = import_json56.default.parse(fs37.readFileSync(moduleConfigPath, "utf-8"));
4789
+ return (parsed.module?.requestPermissions ?? []).map((p) => p?.name).filter((n) => typeof n === "string" && n.length > 0);
4790
+ } catch {
4791
+ return [];
4792
+ }
4793
+ }
4794
+ function collectAclPermissions(projectDir, logger = noopLogger) {
4795
+ const requested = /* @__PURE__ */ new Set();
4796
+ for (const module2 of listModules({ projectDir })) {
4797
+ const moduleRoot = path36.resolve(projectDir, module2.srcPath);
4798
+ for (const sourceSet of ["main", "ohosTest"]) {
4799
+ for (const name of readRequestedPermissions(
4800
+ path36.join(moduleRoot, "src", sourceSet, "module.json5")
4801
+ )) {
4802
+ requested.add(name);
4803
+ }
4804
+ }
4805
+ }
4806
+ const acls = [...requested].filter((p) => ACL_PERMISSIONS.has(p)).sort();
4807
+ if (acls.length > 0) {
4808
+ logger.info(`[harmonyos] Project requests ${acls.length} ACL permission(s): ${acls.join(", ")}`);
4809
+ }
4810
+ return acls;
4811
+ }
4812
+
4813
+ // src/harmonyos/signing/autoSign.ts
4814
+ function shouldRegenerate(input) {
4815
+ if (input.force) return "--force was passed";
4816
+ const missing = Object.values(input.paths).find((file) => !fs38.existsSync(file));
4817
+ if (missing) return `${missing} is missing`;
4818
+ const config = input.buildProfile.app?.signingConfigs?.find((c) => c.name === input.productName);
4819
+ if (config?.type !== "HarmonyOS" || config.material?.profile !== input.paths.profilePath) {
4820
+ return "build-profile.json5 has no signingConfig for this material";
4821
+ }
4822
+ const profile = readProvisionProfile(input.paths.profilePath);
4823
+ if (!profile) return "the provisioning profile could not be read";
4824
+ if (profile.bundleName !== input.bundleName) return `the profile is for bundle '${profile.bundleName}'`;
4825
+ if (profile.developerId && profile.developerId !== input.teamId) {
4826
+ return `the profile was issued to team '${profile.developerId}'`;
4827
+ }
4828
+ if (profile.notAfter && profile.notAfter <= (input.now ?? /* @__PURE__ */ new Date())) return "the profile has expired";
4829
+ const unnamed = input.connectedUdids.filter((udid) => !profile.deviceIds.includes(udid.toUpperCase()));
4830
+ if (unnamed.length > 0) return `${unnamed.length} connected device(s) are not named in the profile`;
4831
+ const acls = input.aclPermissions.filter((p) => !profile.aclPermissions.includes(p));
4832
+ if (acls.length > 0) return `the profile lacks ACL permission(s) ${acls.join(", ")}`;
4833
+ return null;
4834
+ }
4835
+ function writeSigningConfig(opts) {
4836
+ const app = opts.buildProfile.app ??= {};
4837
+ const entry = {
4838
+ name: opts.productName,
4839
+ // `type` is what makes hvigor treat this as HarmonyOS signing material.
4840
+ type: "HarmonyOS",
4841
+ material: {
4842
+ certpath: opts.paths.cerPath,
4843
+ keyAlias: KEY_ALIAS,
4844
+ keyPassword: opts.encryptedPassword,
4845
+ profile: opts.paths.profilePath,
4846
+ signAlg: SIGN_ALG,
4847
+ storeFile: opts.paths.p12Path,
4848
+ storePassword: opts.encryptedPassword
4849
+ }
4850
+ };
4851
+ app.signingConfigs = [...(app.signingConfigs ?? []).filter((c) => c.name !== opts.productName), entry];
4852
+ for (const product of app.products ?? []) {
4853
+ if (product.name === opts.productName) product.signingConfig = opts.productName;
4854
+ }
4855
+ fs38.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
4856
+ }
4857
+ async function harmonyOsAutoSign(opts) {
4858
+ const logger = opts.logger ?? noopLogger;
4859
+ const productName = opts.productName ?? "default";
4860
+ const projectDir = path37.resolve(opts.projectDir);
4861
+ const buildProfilePath = path37.join(projectDir, "build-profile.json5");
4862
+ let buildProfile;
4863
+ try {
4864
+ buildProfile = import_json57.default.parse(fs38.readFileSync(buildProfilePath, "utf-8"));
4865
+ } catch (err) {
4866
+ throw new OniroError(`Could not read ${buildProfilePath}: ${err.message}`, err);
4867
+ }
4868
+ const product = buildProfile.app?.products?.find((p) => p.name === productName);
4869
+ if (!product) throw new OniroError(`${buildProfilePath} declares no product named '${productName}'.`);
4870
+ const bundleName = product.bundleName || getBundleName(projectDir);
4871
+ if (!isValidBundleName(bundleName) || bundleName.split(".").length < 3 || bundleName.length < 7 || bundleName.length > 128) {
4872
+ throw new AgcError(SIGNING_ERRORS.BUNDLE_NAME_INVALID);
4873
+ }
4874
+ const sdk = requireHarmonyOsSdk({ config: opts.config, logger });
4875
+ const auth = await opts.session.resolveAgcAuth({ teamId: opts.teamId });
4876
+ const aclPermissions = collectAclPermissions(projectDir, logger);
4877
+ const paths = resolveSigningMaterialPaths(opts.config, productName, projectDir);
4878
+ logger.info(`[harmonyos] Signing ${bundleName} (product '${productName}', team ${auth.teamId}).`);
4879
+ const connected = await collectLocalDevices(opts.config, logger);
4880
+ const result = (regenerated) => ({
4881
+ bundleName,
4882
+ teamId: auth.teamId,
4883
+ paths,
4884
+ regenerated,
4885
+ deviceIds: readProvisionProfile(paths.profilePath)?.deviceIds ?? []
4886
+ });
4887
+ const reason = shouldRegenerate({
4888
+ paths,
4889
+ buildProfile,
4890
+ productName,
4891
+ bundleName,
4892
+ teamId: auth.teamId,
4893
+ connectedUdids: connected.map((d) => d.udid),
4894
+ aclPermissions,
4895
+ force: opts.force
4896
+ });
4897
+ if (!reason) {
4898
+ logger.info("[harmonyos] Reusing the existing signing material, which is still valid.");
4899
+ return result(false);
4900
+ }
4901
+ logger.info(`[harmonyos] Regenerating signing material: ${reason}.`);
4902
+ const agc = createAgcClient(opts.http ?? createHttpClient({ logger }), auth);
4903
+ const deviceIds = await registerDevices(agc, connected, logger);
4904
+ for (const file of Object.values(paths)) fs38.rmSync(file, { force: true });
4905
+ const certName = `oniro_debug_${auth.teamId.replace(/[\\/.:]/g, "")}.cer`;
4906
+ const stale = (await agc.listCertificates()).find((c) => c.certName === certName);
4907
+ if (stale) {
4908
+ logger.info(`[harmonyos] Replacing the previous '${certName}' certificate in AppGallery Connect...`);
4909
+ await agc.deleteCertificate(stale.id);
4910
+ }
4911
+ const { keyPwd, csr } = await generateKeystoreAndCsr({ sdk, p12Path: paths.p12Path, csrPath: paths.csrPath, logger });
4912
+ logger.info("[harmonyos] Requesting a debug certificate from AppGallery Connect...");
4913
+ await agc.addCertificate(certName, csr);
4914
+ const cert = (await agc.listCertificates()).find((c) => c.certName === certName);
4915
+ if (!cert) throw new AgcError(SIGNING_ERRORS.CERT_REQUEST);
4916
+ await agc.download("cert", cert.certObjectId, paths.cerPath);
4917
+ logger.info("[harmonyos] Issuing the debug provisioning profile...");
4918
+ const profile = await agc.addDebugProfile({
4919
+ provisionName: `oniro_${crypto9.randomBytes(6).toString("hex")}`,
4920
+ bundleName,
4921
+ certId: cert.id,
4922
+ deviceIds,
4923
+ aclPermissions
4924
+ });
4925
+ try {
4926
+ await agc.download("profile", profile.provisionFileUrl, paths.profilePath);
4927
+ } finally {
4928
+ await agc.deleteProfile(profile.id).catch((err) => {
4929
+ logger.debug(`[harmonyos] Could not delete profile ${profile.id} from AppGallery Connect: ${err.message}`);
4930
+ });
4931
+ }
4932
+ try {
4933
+ verifySigningMaterial({ ...paths, keyAlias: KEY_ALIAS, keyPwd });
4934
+ } catch (err) {
4935
+ fs38.rmSync(paths.profilePath, { force: true });
4936
+ throw err;
4937
+ }
4938
+ const materialDir = path37.join(path37.dirname(paths.p12Path), "material");
4939
+ try {
4940
+ getKey(materialDir);
4941
+ } catch {
4942
+ createMaterial(materialDir);
4943
+ }
4944
+ writeSigningConfig({ buildProfilePath, buildProfile, productName, paths, encryptedPassword: encryptPwd(keyPwd, materialDir) });
4945
+ logger.info(`[harmonyos] Wrote signingConfigs['${productName}'] to ${buildProfilePath}.`);
4946
+ return result(true);
4947
+ }
4948
+
3599
4949
  // src/index.ts
3600
- var VERSION = "0.9.0";
4950
+ var VERSION = "0.11.0";
3601
4951
  // Annotate the CommonJS export names for ESM import in node:
3602
4952
  0 && (module.exports = {
3603
4953
  ALL_SDKS,
3604
4954
  APL_VALUES,
3605
4955
  APP_FEATURE_VALUES,
4956
+ AgcError,
3606
4957
  CancelledError,
3607
4958
  ChecksumMismatchError,
3608
4959
  CmdToolsNotInstalledError,
3609
4960
  CommandFailedError,
4961
+ HARMONYOS_SDK_VERSIONS,
4962
+ HUAWEI_SITES,
3610
4963
  INSTALL_TMP_DIRNAME,
3611
4964
  InsufficientSpaceError,
3612
4965
  OHOS_URL_BASE,
3613
4966
  OniroError,
4967
+ SIGNING_ERRORS,
3614
4968
  SdkNotInstalledError,
3615
4969
  TMP_DIR_HINT,
3616
4970
  UnsupportedPlatformError,
@@ -3626,10 +4980,12 @@ var VERSION = "0.9.0";
3626
4980
  buildRawTouchCommand,
3627
4981
  captureBurst,
3628
4982
  consoleLogger,
4983
+ createHarmonyOsSession,
3629
4984
  createInstallTempDir,
3630
4985
  createMaterial,
3631
4986
  createScaffold,
3632
4987
  createTempWorkDir,
4988
+ createTokenStore,
3633
4989
  decideInstallMethod,
3634
4990
  decideReboot,
3635
4991
  decryptPwd,
@@ -3638,6 +4994,7 @@ var VERSION = "0.9.0";
3638
4994
  defaultPaths,
3639
4995
  detectAaStartFailure,
3640
4996
  detectProjectSdkVersion,
4997
+ detectRuntimeOs,
3641
4998
  detectSigningConfigNames,
3642
4999
  diffEntryNames,
3643
5000
  diffHapAssets,
@@ -3660,6 +5017,7 @@ var VERSION = "0.9.0";
3660
5017
  extractZipWithProgress,
3661
5018
  findAppProcessId,
3662
5019
  findCmdToolsSourceDir,
5020
+ findHarmonyOsSdk,
3663
5021
  findRunningProcess,
3664
5022
  forceStop,
3665
5023
  formatBytes,
@@ -3684,6 +5042,8 @@ var VERSION = "0.9.0";
3684
5042
  getSdkFilename,
3685
5043
  getSdkRootDir,
3686
5044
  getSupportedSdksForUi,
5045
+ harmonyOsAutoSign,
5046
+ harmonyOsSdkLabelForApi,
3687
5047
  hdcExec,
3688
5048
  installApp,
3689
5049
  installCmdTools,
@@ -3718,12 +5078,14 @@ var VERSION = "0.9.0";
3718
5078
  pruneLayout,
3719
5079
  readJson5File,
3720
5080
  readJsonFile,
5081
+ readProvisionProfile,
3721
5082
  reboot,
3722
5083
  recvFile,
3723
5084
  removeCmdTools,
3724
5085
  removeEmulator,
3725
5086
  removeSdk,
3726
5087
  removeTempWorkDir,
5088
+ requireHarmonyOsSdk,
3727
5089
  resolveTmpRoot,
3728
5090
  runCodelinter,
3729
5091
  runHvigorw,