@oniroproject/core 0.13.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -89,14 +89,19 @@ var OniroError = class extends Error {
89
89
  cause;
90
90
  };
91
91
  var SdkNotInstalledError = class extends OniroError {
92
- constructor(api, expectedPath) {
93
- super(`SDK API ${api} is not installed at ${expectedPath}.`);
92
+ /** `version` is the release that ships `api` (e.g. `6.0` for API 20), when one is known. */
93
+ constructor(api, expectedPath, version) {
94
+ super(
95
+ `SDK API ${api} is not installed at ${expectedPath}.` + (version ? ` Install it with \`oniro-app sdk install ${version}\`.` : "")
96
+ );
94
97
  this.api = api;
95
98
  this.expectedPath = expectedPath;
99
+ this.version = version;
96
100
  this.name = "SdkNotInstalledError";
97
101
  }
98
102
  api;
99
103
  expectedPath;
104
+ version;
100
105
  };
101
106
  var CmdToolsNotInstalledError = class extends OniroError {
102
107
  constructor(expectedPath) {
@@ -372,6 +377,24 @@ function isProjectLocalHvigorwWorking(projectDir) {
372
377
  import * as fs3 from "fs";
373
378
  import * as path4 from "path";
374
379
  import JSON5 from "json5";
380
+ function localPropertiesSdkDir(projectRoot) {
381
+ try {
382
+ const content = fs3.readFileSync(path4.join(projectRoot, "local.properties"), "utf-8");
383
+ const m = /^\s*sdk\.dir\s*[=:]\s*(.+?)\s*$/m.exec(content);
384
+ return m ? m[1].replace(/\\(.)/g, "$1") : void 0;
385
+ } catch {
386
+ return void 0;
387
+ }
388
+ }
389
+ function findMissingProjectSdk(config, projectRoot) {
390
+ const api = detectProjectSdkVersion(projectRoot);
391
+ if (api === void 0) return null;
392
+ const expected = path4.join(getOhosBaseSdkHome(config), String(api));
393
+ if (fs3.existsSync(expected)) return null;
394
+ const localSdkDir = localPropertiesSdkDir(projectRoot);
395
+ if (localSdkDir && fs3.existsSync(path4.join(localSdkDir, String(api)))) return null;
396
+ return new SdkNotInstalledError(String(api), expected, ALL_SDKS.find((s) => s.api === String(api))?.version);
397
+ }
375
398
  function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
376
399
  const buildProfilePath = path4.join(projectRoot, "build-profile.json5");
377
400
  if (!fs3.existsSync(buildProfilePath)) {
@@ -557,14 +580,14 @@ var pipelineAsync = promisify(pipeline);
557
580
  async function downloadFile(opts) {
558
581
  const { url, dest, progress, abortSignal } = opts;
559
582
  const proto = url.startsWith("https") ? https : http;
560
- return new Promise((resolve6, reject) => {
583
+ return new Promise((resolve7, reject) => {
561
584
  const s = Math.max(0, Math.min(100, opts.start ?? 0));
562
585
  const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
563
586
  let settled = false;
564
587
  const done = (err) => {
565
588
  if (settled) return;
566
589
  settled = true;
567
- err ? reject(err) : resolve6();
590
+ err ? reject(err) : resolve7();
568
591
  };
569
592
  if (abortSignal?.aborted) {
570
593
  done(new CancelledError("Download cancelled before start."));
@@ -1051,21 +1074,27 @@ async function installCmdTools(opts) {
1051
1074
  removeTempWorkDir(tmpDir, tmpRoot);
1052
1075
  }
1053
1076
  }
1077
+ function detectCmdToolsFlavour(cmdToolsPath) {
1078
+ return fs11.existsSync(path12.join(cmdToolsPath, "sdk", "default", "hms")) ? "HarmonyOS" : "OpenHarmony";
1079
+ }
1054
1080
  function getCmdToolsStatus(config) {
1055
1081
  const cmdPath = getCmdToolsPath(config);
1082
+ const hdcPath = getHdcPath(config);
1083
+ const base = { path: cmdPath, hdc: { path: hdcPath, exists: fs11.existsSync(hdcPath) } };
1056
1084
  const binDir = path12.join(cmdPath, "bin");
1057
1085
  const candidates = os7.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
1058
1086
  const bin = candidates.map((c) => path12.join(binDir, c)).find((p) => fs11.existsSync(p));
1059
1087
  if (!bin) {
1060
- return { installed: false, status: "Not installed" };
1088
+ return { installed: false, status: "Not installed", ...base };
1061
1089
  }
1090
+ const installed = { installed: true, ...base, flavour: detectCmdToolsFlavour(cmdPath) };
1062
1091
  try {
1063
1092
  const versionFile = path12.join(cmdPath, "version.txt");
1064
1093
  if (fs11.existsSync(versionFile)) {
1065
1094
  const content = fs11.readFileSync(versionFile, "utf8");
1066
1095
  for (const line of content.split(/\r?\n/)) {
1067
1096
  const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
1068
- if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
1097
+ if (m?.[1]) return { ...installed, status: `Installed (${m[1].trim()})` };
1069
1098
  }
1070
1099
  }
1071
1100
  } catch {
@@ -1079,9 +1108,9 @@ function getCmdToolsStatus(config) {
1079
1108
  });
1080
1109
  if (res.error || res.status !== 0) throw res.error ?? new Error(`exit ${String(res.status)}`);
1081
1110
  const version = res.stdout.trim();
1082
- return { installed: true, status: `Installed (${version})` };
1111
+ return { ...installed, status: `Installed (${version})` };
1083
1112
  } catch {
1084
- return { installed: true, status: "Installed (version unknown)" };
1113
+ return { ...installed, status: "Installed (version unknown)" };
1085
1114
  }
1086
1115
  }
1087
1116
  function isCmdToolsInstalled(config) {
@@ -1371,6 +1400,9 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
1371
1400
  profileTemplate.acls = profileTemplate.acls ?? {};
1372
1401
  profileTemplate.acls["allowed-acls"] = options.acls;
1373
1402
  }
1403
+ if (options.appPrivilegeCapabilities !== void 0) {
1404
+ profileTemplate["app-privilege-capabilities"] = options.appPrivilegeCapabilities;
1405
+ }
1374
1406
  fs13.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
1375
1407
  }
1376
1408
  function generateP7bFile(projectDir, paths, logger, passwords) {
@@ -1460,6 +1492,12 @@ function updateBuildProfile(projectDir, kind, logger, passwords) {
1460
1492
  storeFile: "./signatures/OpenHarmony.p12"
1461
1493
  }
1462
1494
  }));
1495
+ for (const product of buildProfile.app.products ?? []) {
1496
+ if (typeof product.signingConfig !== "string" || product.signingConfig === "") {
1497
+ product.signingConfig = configNames[0];
1498
+ logger.info(`[sign] Pointing a product with no signingConfig at "${configNames[0]}".`);
1499
+ }
1500
+ }
1463
1501
  fs13.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1464
1502
  }
1465
1503
  function prepareMaterialDirectory(projectDir, logger) {
@@ -1491,7 +1529,7 @@ function generateSigningConfigs(options) {
1491
1529
  }
1492
1530
  const sdkPath = path14.join(sdkHome, String(sdkVersion));
1493
1531
  if (!fs13.existsSync(sdkPath)) {
1494
- throw new OniroError(`SDK path does not exist: ${sdkPath}`);
1532
+ throw new SdkNotInstalledError(String(sdkVersion), sdkPath, ALL_SDKS.find((s) => s.api === String(sdkVersion))?.version);
1495
1533
  }
1496
1534
  const paths = {
1497
1535
  signTool: path14.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
@@ -1503,7 +1541,11 @@ function generateSigningConfigs(options) {
1503
1541
  const passwords = options.passwords ?? DEFAULT_PASSWORDS;
1504
1542
  logger.info("[sign] Starting signing configuration generation...");
1505
1543
  copyFilesToProject(projectDir, paths, kind, logger, options.applicationCertPath);
1506
- modifyProfileTemplate(projectDir, apl, appFeature, logger, { kind, acls: options.acls });
1544
+ modifyProfileTemplate(projectDir, apl, appFeature, logger, {
1545
+ kind,
1546
+ acls: options.acls,
1547
+ appPrivilegeCapabilities: options.appPrivilegeCapabilities
1548
+ });
1507
1549
  generateP7bFile(projectDir, paths, logger, passwords);
1508
1550
  prepareMaterialDirectory(projectDir, logger);
1509
1551
  updateBuildProfile(projectDir, kind, logger, passwords);
@@ -1563,6 +1605,7 @@ import { spawn as spawn2 } from "child_process";
1563
1605
 
1564
1606
  // src/hdc/exec.ts
1565
1607
  import * as fs15 from "fs";
1608
+ import { StringDecoder } from "string_decoder";
1566
1609
  function errorMessage(err) {
1567
1610
  return err instanceof Error ? err.message : String(err);
1568
1611
  }
@@ -1570,7 +1613,7 @@ function runProcess(opts) {
1570
1613
  const { command, args, cwd, env, onOutput } = opts;
1571
1614
  const timeoutMs = opts.timeoutMs ?? 6e4;
1572
1615
  const { abortSignal } = opts;
1573
- return new Promise((resolve6, reject) => {
1616
+ return new Promise((resolve7, reject) => {
1574
1617
  if (abortSignal?.aborted) {
1575
1618
  reject(new CancelledError(`Command aborted before start: ${command}`));
1576
1619
  return;
@@ -1590,6 +1633,8 @@ function runProcess(opts) {
1590
1633
  let stderr = "";
1591
1634
  let settled = false;
1592
1635
  let timedOut = false;
1636
+ const stdoutDecoder = new StringDecoder("utf8");
1637
+ const stderrDecoder = new StringDecoder("utf8");
1593
1638
  const cleanup = () => {
1594
1639
  clearTimeout(timer);
1595
1640
  abortSignal?.removeEventListener("abort", onAbort);
@@ -1607,14 +1652,14 @@ function runProcess(opts) {
1607
1652
  }, timeoutMs);
1608
1653
  abortSignal?.addEventListener("abort", onAbort, { once: true });
1609
1654
  child.stdout?.on("data", (d) => {
1610
- const s = d.toString();
1655
+ const s = stdoutDecoder.write(d);
1611
1656
  stdout += s;
1612
- onOutput?.(s, "stdout");
1657
+ if (s) onOutput?.(s, "stdout");
1613
1658
  });
1614
1659
  child.stderr?.on("data", (d) => {
1615
- const s = d.toString();
1660
+ const s = stderrDecoder.write(d);
1616
1661
  stderr += s;
1617
- onOutput?.(s, "stderr");
1662
+ if (s) onOutput?.(s, "stderr");
1618
1663
  });
1619
1664
  child.on("error", (err) => {
1620
1665
  if (settled) return;
@@ -1626,6 +1671,12 @@ function runProcess(opts) {
1626
1671
  if (settled) return;
1627
1672
  settled = true;
1628
1673
  cleanup();
1674
+ const stdoutTail = stdoutDecoder.end();
1675
+ const stderrTail = stderrDecoder.end();
1676
+ stdout += stdoutTail;
1677
+ stderr += stderrTail;
1678
+ if (stdoutTail) onOutput?.(stdoutTail, "stdout");
1679
+ if (stderrTail) onOutput?.(stderrTail, "stderr");
1629
1680
  if (timedOut) {
1630
1681
  reject(
1631
1682
  new OniroError(
@@ -1635,21 +1686,41 @@ ${stderr.trim()}` : "")
1635
1686
  );
1636
1687
  return;
1637
1688
  }
1638
- resolve6({ code: code ?? -1, stdout, stderr });
1689
+ resolve7({ code: code ?? -1, stdout, stderr });
1639
1690
  });
1640
1691
  });
1641
1692
  }
1642
- function hdcExec(opts) {
1693
+ function resolveDeviceSerial(explicit, env = process.env) {
1694
+ if (explicit && explicit !== "auto") return explicit;
1695
+ const fromEnv = env.ONIRO_DEVICE_SERIAL || env.DEVICE_SERIAL;
1696
+ return fromEnv && fromEnv !== "auto" ? fromEnv : void 0;
1697
+ }
1698
+ function hdcTargetArgs(explicit) {
1699
+ const serial = resolveDeviceSerial(explicit);
1700
+ return serial ? ["-t", serial] : [];
1701
+ }
1702
+ var HOST_COMMANDS = /* @__PURE__ */ new Set(["list", "tconn", "start", "kill", "version", "checkserver"]);
1703
+ function hdcTargetArgsFor(args, explicit) {
1704
+ if (HOST_COMMANDS.has(args[0] ?? "")) return explicit ? ["-t", explicit] : [];
1705
+ return hdcTargetArgs(explicit);
1706
+ }
1707
+ function hdcClientFailure(output) {
1708
+ const first = output.split(/\r?\n/).find((l) => l.trim() !== "")?.trim() ?? "";
1709
+ return first.startsWith("[Fail]") ? first : null;
1710
+ }
1711
+ async function hdcExec(opts) {
1643
1712
  const hdc = getHdcPath(opts.config);
1644
- const argv = opts.deviceSerial ? ["-t", opts.deviceSerial, ...opts.args] : [...opts.args];
1645
- return runProcess({
1713
+ const res = await runProcess({
1646
1714
  command: hdc,
1647
- args: argv,
1715
+ args: [...hdcTargetArgsFor(opts.args, opts.deviceSerial), ...opts.args],
1648
1716
  timeoutMs: opts.timeoutMs,
1649
1717
  abortSignal: opts.abortSignal,
1650
1718
  onOutput: opts.onOutput,
1651
1719
  logger: opts.logger
1652
1720
  });
1721
+ if (res.code === 0 && hdcClientFailure(`${res.stdout}
1722
+ ${res.stderr}`)) return { ...res, code: 1 };
1723
+ return res;
1653
1724
  }
1654
1725
  function shell(opts) {
1655
1726
  const { command, ...rest } = opts;
@@ -1657,10 +1728,15 @@ function shell(opts) {
1657
1728
  }
1658
1729
  function ensureOk(result, command) {
1659
1730
  if (result.code !== 0) {
1660
- throw new CommandFailedError(command, result.code, result.stderr.trim());
1731
+ throw new CommandFailedError(command, result.code, withTargetHint(result.stderr.trim() || result.stdout.trim()));
1661
1732
  }
1662
1733
  return result;
1663
1734
  }
1735
+ function withTargetHint(detail) {
1736
+ if (!/need connect-key|Not match target/i.test(detail)) return detail;
1737
+ return `${detail}
1738
+ More than one target is connected, or the serial is unknown: pass --device <serial> or set ONIRO_DEVICE_SERIAL to one listed by \`oniro-app devices\`.`;
1739
+ }
1664
1740
  var REMOTE_RC_RE = /__ONIRO_RC=(-?\d+)__/g;
1665
1741
  function parseRemoteExit(stdout) {
1666
1742
  let code = null;
@@ -1677,7 +1753,7 @@ async function shellChecked(opts) {
1677
1753
  }
1678
1754
  function ensureRemoteOk(result, command) {
1679
1755
  if (result.code !== 0) {
1680
- throw new CommandFailedError(command, result.code, result.stderr.trim() || result.stdout.trim());
1756
+ throw new CommandFailedError(command, result.code, withTargetHint(result.stderr.trim() || result.stdout.trim()));
1681
1757
  }
1682
1758
  return result;
1683
1759
  }
@@ -1763,11 +1839,11 @@ async function startEmulator(opts) {
1763
1839
  fs16.writeFileSync(PID_FILE, String(child.pid));
1764
1840
  child.unref();
1765
1841
  logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
1766
- const earlyExit = new Promise((resolve6) => {
1767
- child.once("exit", (code) => resolve6(code));
1768
- child.once("error", () => resolve6(-1));
1842
+ const earlyExit = new Promise((resolve7) => {
1843
+ child.once("exit", (code) => resolve7(code));
1844
+ child.once("error", () => resolve7(-1));
1769
1845
  });
1770
- const liveness = new Promise((resolve6) => setTimeout(() => resolve6("alive"), 2e3));
1846
+ const liveness = new Promise((resolve7) => setTimeout(() => resolve7("alive"), 2e3));
1771
1847
  const outcome = await Promise.race([earlyExit, liveness]);
1772
1848
  if (outcome !== "alive") {
1773
1849
  const tail = readTail(logPath, 4e3);
@@ -1847,10 +1923,48 @@ async function stopEmulator(logger = noopLogger) {
1847
1923
  if (err) logger.warn(`Could not remove PID file: ${err.message}`);
1848
1924
  });
1849
1925
  }
1926
+ async function isQemuRunning(logger) {
1927
+ if (os8.platform() !== "win32") {
1928
+ try {
1929
+ const pid = Number(fs16.readFileSync(PID_FILE, "utf8").trim());
1930
+ if (Number.isFinite(pid) && pid > 0) {
1931
+ process.kill(pid, 0);
1932
+ return true;
1933
+ }
1934
+ } catch {
1935
+ }
1936
+ }
1937
+ const [cmd, ...args] = os8.platform() === "win32" ? ["tasklist", "/FI", "IMAGENAME eq qemu-system-x86_64.exe", "/NH"] : ["pgrep", "-f", "qemu-system-x86_64"];
1938
+ try {
1939
+ const res = await runProcess({ command: cmd, args, timeoutMs: 1e4, logger });
1940
+ return os8.platform() === "win32" ? /qemu-system-x86_64/i.test(res.stdout) : res.code === 0;
1941
+ } catch {
1942
+ return false;
1943
+ }
1944
+ }
1945
+ async function getEmulatorStatus(config, opts = {}) {
1946
+ const logger = opts.logger ?? noopLogger;
1947
+ const address = opts.address ?? "127.0.0.1:55555";
1948
+ const dir = getEmulatorDir(config);
1949
+ let connected = false;
1950
+ try {
1951
+ const targets = await hdcExec({ config, args: ["list", "targets", "-v"], timeoutMs: 1e4, logger });
1952
+ connected = targets.stdout.split("\n").some((l) => l.trim().startsWith(address) && /\bConnected\b/.test(l));
1953
+ } catch {
1954
+ }
1955
+ return {
1956
+ installed: isEmulatorInstalled(config),
1957
+ dir,
1958
+ running: await isQemuRunning(logger),
1959
+ address,
1960
+ connected
1961
+ };
1962
+ }
1850
1963
 
1851
1964
  // src/hdc/devices.ts
1852
1965
  var STATUS_TOKENS = /* @__PURE__ */ new Set([
1853
1966
  "Connected",
1967
+ "Ready",
1854
1968
  "Offline",
1855
1969
  "Unauthorized",
1856
1970
  "Authorizing",
@@ -1885,9 +1999,8 @@ async function listDevices(config, opts = {}) {
1885
1999
  return parseDeviceList(res.stdout);
1886
2000
  }
1887
2001
  async function selectDevice(config, serial, opts = {}) {
1888
- if (serial && serial !== "auto") return serial;
1889
- const envSerial = process.env.ONIRO_DEVICE_SERIAL || process.env.DEVICE_SERIAL;
1890
- if (envSerial && envSerial !== "auto") return envSerial;
2002
+ const resolved = resolveDeviceSerial(serial);
2003
+ if (resolved) return resolved;
1891
2004
  const usable = (await listDevices(config, opts)).filter(
1892
2005
  (d) => d.status === "Connected" || d.status === "Unknown"
1893
2006
  );
@@ -1901,6 +2014,7 @@ async function selectDevice(config, serial, opts = {}) {
1901
2014
  }
1902
2015
 
1903
2016
  // src/hdc/param.ts
2017
+ var PARAM_MISSING_RE = /^Get parameter ".*" fail!/;
1904
2018
  async function paramGet(config, key, opts = {}) {
1905
2019
  const res = await shell({
1906
2020
  config,
@@ -1910,7 +2024,30 @@ async function paramGet(config, key, opts = {}) {
1910
2024
  logger: opts.logger
1911
2025
  });
1912
2026
  ensureOk(res, `hdc shell param get ${key}`);
1913
- return res.stdout.trim();
2027
+ const value = res.stdout.trim();
2028
+ if (PARAM_MISSING_RE.test(value)) throw new OniroError(`System parameter ${key} is not set: ${value}`);
2029
+ return value;
2030
+ }
2031
+ var PARAM_SEPARATOR = "__ONIRO_PARAM__";
2032
+ function parseParamValues(keys, stdout) {
2033
+ const parts = stdout.split(PARAM_SEPARATOR);
2034
+ const values = {};
2035
+ keys.forEach((key, i) => {
2036
+ const value = parts[i]?.trim();
2037
+ values[key] = value && !PARAM_MISSING_RE.test(value) ? value : void 0;
2038
+ });
2039
+ return values;
2040
+ }
2041
+ async function paramGetMany(config, keys, opts = {}) {
2042
+ const res = await shell({
2043
+ config,
2044
+ command: keys.map((k) => `param get ${k}; echo ${PARAM_SEPARATOR}`).join("; "),
2045
+ deviceSerial: opts.deviceSerial,
2046
+ timeoutMs: opts.timeoutMs ?? 1e4,
2047
+ logger: opts.logger
2048
+ });
2049
+ ensureOk(res, "hdc shell param get \u2026");
2050
+ return parseParamValues(keys, res.stdout);
1914
2051
  }
1915
2052
  async function paramSet(config, key, value, opts = {}) {
1916
2053
  const res = await shell({
@@ -1924,29 +2061,66 @@ async function paramSet(config, key, value, opts = {}) {
1924
2061
  }
1925
2062
 
1926
2063
  // src/hdc/files.ts
2064
+ import * as path17 from "path";
2065
+ function normaliseLocalPath(local) {
2066
+ const abs = path17.resolve(local);
2067
+ const long = toLongPath(abs);
2068
+ return long !== abs ? long : path17.join(toLongPath(path17.dirname(abs)), path17.basename(abs));
2069
+ }
2070
+ function assertDevicePath(remote) {
2071
+ if (/^[A-Za-z]:[\\/]/.test(remote)) {
2072
+ throw new OniroError(
2073
+ `The device path ${remote} is a Windows path. Git Bash rewrites arguments that start with /: run the command with MSYS_NO_PATHCONV=1, or from PowerShell or cmd.`
2074
+ );
2075
+ }
2076
+ }
2077
+ function appSandboxDir(bundle) {
2078
+ return `/data/app/el2/100/base/${bundle}`;
2079
+ }
2080
+ function shQuote(s) {
2081
+ return `'${s.replace(/'/g, `'\\''`)}'`;
2082
+ }
1927
2083
  async function sendFile(opts) {
2084
+ assertDevicePath(opts.remote);
2085
+ const local = normaliseLocalPath(opts.local);
2086
+ const sandbox = opts.bundle ? appSandboxDir(opts.bundle) : void 0;
2087
+ const remote = sandbox && !opts.remote.startsWith("/") ? path17.posix.join(sandbox, opts.remote) : opts.remote;
2088
+ if (sandbox && !remote.startsWith(`${sandbox}/`)) {
2089
+ throw new OniroError(`${remote} is outside the sandbox of ${opts.bundle} (${sandbox}).`);
2090
+ }
2091
+ const device = { config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger };
2092
+ if (sandbox) {
2093
+ const mkdir = `mkdir -p ${shQuote(path17.posix.dirname(remote))}`;
2094
+ ensureRemoteOk(await shellChecked({ ...device, command: mkdir }), `hdc shell ${mkdir}`);
2095
+ }
1928
2096
  const res = await hdcExec({
1929
- config: opts.config,
1930
- args: ["file", "send", opts.local, opts.remote],
1931
- deviceSerial: opts.deviceSerial,
2097
+ ...device,
2098
+ args: ["file", "send", local, remote],
1932
2099
  timeoutMs: opts.timeoutMs ?? 6e5,
1933
2100
  abortSignal: opts.abortSignal,
1934
- onOutput: opts.onOutput,
1935
- logger: opts.logger
2101
+ onOutput: opts.onOutput
1936
2102
  });
1937
- ensureOk(res, `hdc file send ${opts.local} ${opts.remote}`);
2103
+ ensureOk(res, `hdc file send ${local} ${remote}`);
2104
+ if (sandbox) {
2105
+ const top = path17.posix.join(sandbox, path17.posix.relative(sandbox, remote).split("/")[0]);
2106
+ const chown = `chown -R "$(stat -c %u:%g ${shQuote(sandbox)})" ${shQuote(top)}`;
2107
+ ensureRemoteOk(await shellChecked({ ...device, command: chown }), `hdc shell ${chown}`);
2108
+ }
2109
+ return remote;
1938
2110
  }
1939
2111
  async function recvFile(opts) {
2112
+ assertDevicePath(opts.remote);
2113
+ const local = normaliseLocalPath(opts.local);
1940
2114
  const res = await hdcExec({
1941
2115
  config: opts.config,
1942
- args: ["file", "recv", opts.remote, opts.local],
2116
+ args: ["file", "recv", opts.remote, local],
1943
2117
  deviceSerial: opts.deviceSerial,
1944
2118
  timeoutMs: opts.timeoutMs ?? 6e5,
1945
2119
  abortSignal: opts.abortSignal,
1946
2120
  onOutput: opts.onOutput,
1947
2121
  logger: opts.logger
1948
2122
  });
1949
- ensureOk(res, `hdc file recv ${opts.remote} ${opts.local}`);
2123
+ ensureOk(res, `hdc file recv ${opts.remote} ${local}`);
1950
2124
  }
1951
2125
 
1952
2126
  // src/hdc/display.ts
@@ -1966,7 +2140,7 @@ async function getDisplaySize(config, opts = {}) {
1966
2140
 
1967
2141
  // src/hdc/wait.ts
1968
2142
  function delay(ms, signal) {
1969
- return new Promise((resolve6, reject) => {
2143
+ return new Promise((resolve7, reject) => {
1970
2144
  if (signal?.aborted) {
1971
2145
  reject(new CancelledError("Wait cancelled."));
1972
2146
  return;
@@ -1981,7 +2155,7 @@ function delay(ms, signal) {
1981
2155
  };
1982
2156
  const timer = setTimeout(() => {
1983
2157
  cleanup();
1984
- resolve6();
2158
+ resolve7();
1985
2159
  }, ms);
1986
2160
  signal?.addEventListener("abort", onAbort, { once: true });
1987
2161
  });
@@ -2010,20 +2184,73 @@ async function waitForCondition(opts) {
2010
2184
  }
2011
2185
 
2012
2186
  // src/hdc/app.ts
2013
- import * as fs18 from "fs";
2187
+ import * as fs19 from "fs";
2188
+ import * as path20 from "path";
2189
+
2190
+ // src/build/discoverHaps.ts
2191
+ import * as fs17 from "fs";
2014
2192
  import * as path18 from "path";
2193
+ function moduleOfHap(hapFileName) {
2194
+ const dash = hapFileName.indexOf("-");
2195
+ return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
2196
+ }
2197
+ async function discoverHaps(opts) {
2198
+ const { projectDir } = opts;
2199
+ const maxDepth = opts.maxDepth ?? 8;
2200
+ const result = {};
2201
+ const walk = async (dir, depth) => {
2202
+ if (depth > maxDepth) return;
2203
+ const entries = await fs17.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
2204
+ for (const e of entries) {
2205
+ const full = path18.join(dir, e.name);
2206
+ if (e.isDirectory()) {
2207
+ if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
2208
+ await walk(full, depth + 1);
2209
+ } else if (e.isFile() && e.name.endsWith(".hap")) {
2210
+ const moduleName = moduleOfHap(e.name);
2211
+ const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
2212
+ if (e.name.includes("unsigned")) bucket.unsigned.push(full);
2213
+ else if (e.name.includes("signed")) bucket.signed.push(full);
2214
+ }
2215
+ }
2216
+ };
2217
+ await walk(projectDir, 0);
2218
+ const mtime = (p) => fs17.statSync(p).mtimeMs;
2219
+ for (const bucket of Object.values(result)) {
2220
+ bucket.signed.sort((a, b) => mtime(b) - mtime(a));
2221
+ bucket.unsigned.sort((a, b) => mtime(b) - mtime(a));
2222
+ }
2223
+ return result;
2224
+ }
2225
+ function pickHap(haps, module) {
2226
+ const pick = (name) => {
2227
+ const signed = haps[name]?.signed[0];
2228
+ if (signed) return { module: name, path: signed, signed: true };
2229
+ const unsigned = haps[name]?.unsigned[0];
2230
+ return unsigned ? { module: name, path: unsigned, signed: false } : null;
2231
+ };
2232
+ if (module) return pick(module);
2233
+ const names = Object.keys(haps);
2234
+ const withSigned = names.filter((n) => haps[n].signed.length > 0);
2235
+ const candidates = withSigned.length > 0 ? withSigned : names.filter((n) => haps[n].unsigned.length > 0);
2236
+ if (candidates.length === 0) return null;
2237
+ if (candidates.length > 1) {
2238
+ throw new OniroError(`Multiple modules have built HAPs (${candidates.join(", ")}). Pass a module to choose.`);
2239
+ }
2240
+ return pick(candidates[0]);
2241
+ }
2015
2242
 
2016
2243
  // src/hdc/project.ts
2017
- import * as fs17 from "fs";
2018
- import * as path17 from "path";
2244
+ import * as fs18 from "fs";
2245
+ import * as path19 from "path";
2019
2246
  import JSON53 from "json5";
2020
2247
  function readJson5(filePath) {
2021
- const content = fs17.readFileSync(filePath, "utf-8");
2248
+ const content = fs18.readFileSync(filePath, "utf-8");
2022
2249
  return JSON53.parse(content);
2023
2250
  }
2024
2251
  function getBundleName(projectDir) {
2025
- const appJsonPath = path17.join(projectDir, "AppScope", "app.json5");
2026
- if (!fs17.existsSync(appJsonPath)) {
2252
+ const appJsonPath = path19.join(projectDir, "AppScope", "app.json5");
2253
+ if (!fs18.existsSync(appJsonPath)) {
2027
2254
  throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
2028
2255
  }
2029
2256
  const appJson = readJson5(appJsonPath);
@@ -2033,8 +2260,8 @@ function getBundleName(projectDir) {
2033
2260
  return appJson.app.bundleName;
2034
2261
  }
2035
2262
  function getMainAbility(projectDir, moduleName = "entry", abilityName) {
2036
- const moduleJsonPath = path17.join(projectDir, moduleName, "src", "main", "module.json5");
2037
- if (!fs17.existsSync(moduleJsonPath)) {
2263
+ const moduleJsonPath = path19.join(projectDir, moduleName, "src", "main", "module.json5");
2264
+ if (!fs18.existsSync(moduleJsonPath)) {
2038
2265
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
2039
2266
  }
2040
2267
  const moduleJson = readJson5(moduleJsonPath);
@@ -2054,13 +2281,67 @@ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
2054
2281
  }
2055
2282
 
2056
2283
  // src/hdc/app.ts
2284
+ function detectInstallFailure(output) {
2285
+ return /\[Fail\]|msg:\s*error|error:\s*failed to install|code:\s*95\d{5}/i.test(output) ? output.trim() : null;
2286
+ }
2287
+ function isUnsignedHap(hapPath) {
2288
+ return path20.basename(hapPath).includes("unsigned");
2289
+ }
2290
+ function installFailureReason(output) {
2291
+ const code = /code:\s*(\d+)/i.exec(output);
2292
+ if (!code) return output.trim().split(/\r?\n/)[0] ?? "";
2293
+ const message = /error:\s*([^\r\n]+)/i.exec(output.slice(code.index + code[0].length))?.[1]?.trim().replace(/\.$/, "");
2294
+ return message ? `code ${code[1]}: ${message}` : `code ${code[1]}`;
2295
+ }
2296
+ function unsignedHapRejection(output, hap) {
2297
+ const name = path20.basename(hap.path);
2298
+ const reason = installFailureReason(output);
2299
+ const what = hap.fallback ? `No signed HAP was found, so the unsigned ${name} was tried, and the target rejected it (${reason}).` : `The target rejected the unsigned ${name} (${reason}).`;
2300
+ if (!/9568320|signature|pkcs7/i.test(reason)) return what;
2301
+ return `${what} This target installs only signed HAPs. Sign the project for it, build again, and install the signed HAP: \`oniro-app sign\` for an OpenHarmony device or the Oniro emulator, \`oniro-app sign --harmonyos\` for a HarmonyOS device.`;
2302
+ }
2303
+ function ensureInstallOk(result, command, hap) {
2304
+ const output = `${result.stdout}
2305
+ ${result.stderr}`;
2306
+ if (hap && isUnsignedHap(hap.path) && /code:\s*95\d{5}/i.test(output)) {
2307
+ throw new OniroError(unsignedHapRejection(output, hap), new CommandFailedError(command, result.code || 1, output.trim()));
2308
+ }
2309
+ ensureOk(result, command);
2310
+ const failure = detectInstallFailure(output);
2311
+ if (failure) throw new CommandFailedError(command, 1, failure);
2312
+ return result;
2313
+ }
2314
+ function existingHapOrUnsigned(hapPath) {
2315
+ if (fs19.existsSync(hapPath)) return { path: hapPath };
2316
+ const sibling = hapPath.replace(/-signed\.hap$/i, "-unsigned.hap");
2317
+ return sibling !== hapPath && fs19.existsSync(sibling) ? { path: sibling, fallback: true } : void 0;
2318
+ }
2319
+ async function resolveInstallHap(opts, logger) {
2320
+ const warnIfFallback = (hap) => {
2321
+ if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path20.basename(hap.path)}.`);
2322
+ return hap;
2323
+ };
2324
+ const configured = opts.hapPath ?? opts.config.get("hapPath", "");
2325
+ const relative3 = configured || defaultPaths.hapPath;
2326
+ const hapPath = path20.isAbsolute(relative3) ? relative3 : path20.join(opts.projectDir, relative3);
2327
+ if (configured) {
2328
+ const hap = existingHapOrUnsigned(hapPath);
2329
+ if (!hap) throw new OniroError(`HAP file not found at: ${hapPath}. Build the app first.`);
2330
+ return warnIfFallback(hap);
2331
+ }
2332
+ if (fs19.existsSync(hapPath)) return { path: hapPath };
2333
+ const haps = await discoverHaps({ projectDir: opts.projectDir });
2334
+ const anySigned = Object.values(haps).some((m) => m.signed.length > 0);
2335
+ const entryUnsigned = anySigned ? void 0 : existingHapOrUnsigned(hapPath);
2336
+ if (entryUnsigned) return warnIfFallback(entryUnsigned);
2337
+ const picked = pickHap(haps);
2338
+ if (!picked) throw new OniroError(`No HAP found under ${opts.projectDir}. Build the app first (\`oniro-app build\`).`);
2339
+ return warnIfFallback({ path: picked.path, fallback: !picked.signed });
2340
+ }
2057
2341
  async function installApp(opts) {
2058
2342
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2059
- const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
2060
- const hapPath = path18.isAbsolute(relativeHapPath) ? relativeHapPath : path18.join(opts.projectDir, relativeHapPath);
2061
- if (!fs18.existsSync(hapPath)) {
2062
- throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
2063
- }
2343
+ const hap = await resolveInstallHap(opts, logger);
2344
+ const hapPath = hap.path;
2064
2345
  const result = await hdcExec({
2065
2346
  config: opts.config,
2066
2347
  args: ["install", hapPath],
@@ -2069,7 +2350,7 @@ async function installApp(opts) {
2069
2350
  });
2070
2351
  if (result.stdout.trim()) logger.info(result.stdout.trim());
2071
2352
  if (result.stderr.trim()) logger.warn(result.stderr.trim());
2072
- ensureOk(result, `hdc install ${hapPath}`);
2353
+ ensureInstallOk(result, `hdc install ${hapPath}`, hap);
2073
2354
  let bundleName = "";
2074
2355
  try {
2075
2356
  bundleName = getBundleName(opts.projectDir);
@@ -2077,6 +2358,18 @@ async function installApp(opts) {
2077
2358
  }
2078
2359
  return { installed: true, bundleName, hapPath, output: `${result.stdout}${result.stderr}`.trim() };
2079
2360
  }
2361
+ async function isBundleInstalled(opts) {
2362
+ const safe = opts.bundle.replace(/'/g, `'\\''`);
2363
+ const res = await shell({
2364
+ config: opts.config,
2365
+ command: `bm dump -n '${safe}'`,
2366
+ deviceSerial: opts.deviceSerial,
2367
+ timeoutMs: opts.timeoutMs ?? 15e3,
2368
+ logger: opts.logger
2369
+ });
2370
+ ensureOk(res, `hdc shell bm dump -n ${opts.bundle}`);
2371
+ return res.stdout.includes("{") && !/failed to get information/i.test(res.stdout);
2372
+ }
2080
2373
  function detectAaStartFailure(output) {
2081
2374
  return /failed to start ability|Error Code:\s*\d|Error Message:/i.test(output) ? output.trim() : null;
2082
2375
  }
@@ -2147,10 +2440,10 @@ function listRunningProcesses(config, options = {}) {
2147
2440
  const logger = options.logger ?? noopLogger;
2148
2441
  const timeoutMs = options.timeoutMs ?? 1e3;
2149
2442
  const hdc = getHdcPath(config);
2150
- return new Promise((resolve6, reject) => {
2443
+ return new Promise((resolve7, reject) => {
2151
2444
  let proc;
2152
2445
  try {
2153
- proc = spawnCompat(hdc, ["track-jpid"]);
2446
+ proc = spawnCompat(hdc, [...hdcTargetArgs(options.deviceSerial), "track-jpid"]);
2154
2447
  } catch (err) {
2155
2448
  reject(new OniroError(`Failed to spawn ${hdc}: ${err instanceof Error ? err.message : String(err)}`, err));
2156
2449
  return;
@@ -2167,7 +2460,7 @@ function listRunningProcesses(config, options = {}) {
2167
2460
  if (options.targetProcessName && name === options.targetProcessName && !resolved) {
2168
2461
  resolved = true;
2169
2462
  killProcessTree(proc);
2170
- resolve6(pid);
2463
+ resolve7(pid);
2171
2464
  return;
2172
2465
  }
2173
2466
  }
@@ -2188,7 +2481,7 @@ function listRunningProcesses(config, options = {}) {
2188
2481
  if (options.targetProcessName) {
2189
2482
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
2190
2483
  } else {
2191
- resolve6(processes);
2484
+ resolve7(processes);
2192
2485
  }
2193
2486
  }, timeoutMs);
2194
2487
  proc.on("close", () => {
@@ -2198,7 +2491,7 @@ function listRunningProcesses(config, options = {}) {
2198
2491
  if (options.targetProcessName) {
2199
2492
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
2200
2493
  } else {
2201
- resolve6(processes);
2494
+ resolve7(processes);
2202
2495
  }
2203
2496
  });
2204
2497
  });
@@ -2220,7 +2513,11 @@ async function findRunningProcess(opts) {
2220
2513
  const pid = res.stdout.trim().split(/\s+/).filter(Boolean)[0];
2221
2514
  if (res.code === 0 && pid && /^\d+$/.test(pid)) return { pid, name: opts.bundle };
2222
2515
  try {
2223
- const list = await listRunningProcesses(opts.config, { timeoutMs: 1200, logger: opts.logger });
2516
+ const list = await listRunningProcesses(opts.config, {
2517
+ timeoutMs: 1200,
2518
+ deviceSerial: opts.deviceSerial,
2519
+ logger: opts.logger
2520
+ });
2224
2521
  if (Array.isArray(list)) {
2225
2522
  const mine = (name) => name === opts.bundle || name.startsWith(`${opts.bundle}/`) || name.startsWith(`${opts.bundle}:`);
2226
2523
  const match = list.find((p) => p.name === opts.bundle) ?? list.find((p) => mine(p.name));
@@ -2363,12 +2660,12 @@ async function reboot(opts) {
2363
2660
  }
2364
2661
 
2365
2662
  // src/hdc/screen.ts
2366
- import * as fs19 from "fs";
2663
+ import * as fs20 from "fs";
2367
2664
  import * as os9 from "os";
2368
- import * as path19 from "path";
2665
+ import * as path21 from "path";
2369
2666
  import { randomBytes as randomBytes2 } from "crypto";
2370
2667
  function tmpName(ext) {
2371
- return path19.join(os9.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
2668
+ return path21.join(os9.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
2372
2669
  }
2373
2670
  async function takeScreenshot(opts) {
2374
2671
  const remote = "/data/local/tmp/oniro_screenshot.jpeg";
@@ -2378,11 +2675,11 @@ async function takeScreenshot(opts) {
2378
2675
  const local = tmpName("jpeg");
2379
2676
  try {
2380
2677
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2381
- const pixels = await fs19.promises.readFile(local);
2678
+ const pixels = await fs20.promises.readFile(local);
2382
2679
  const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
2383
2680
  return { pixels, width: size.width, height: size.height };
2384
2681
  } finally {
2385
- fs19.promises.unlink(local).catch(() => {
2682
+ fs20.promises.unlink(local).catch(() => {
2386
2683
  });
2387
2684
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2388
2685
  });
@@ -2401,9 +2698,9 @@ async function captureBurst(opts) {
2401
2698
  const local = tmpName(`burst_${i}.jpeg`);
2402
2699
  try {
2403
2700
  await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
2404
- frames.push(await fs19.promises.readFile(local));
2701
+ frames.push(await fs20.promises.readFile(local));
2405
2702
  } finally {
2406
- fs19.promises.unlink(local).catch(() => {
2703
+ fs20.promises.unlink(local).catch(() => {
2407
2704
  });
2408
2705
  }
2409
2706
  }
@@ -2578,9 +2875,9 @@ async function sendRawTouch(opts) {
2578
2875
  }
2579
2876
 
2580
2877
  // src/hdc/dump.ts
2581
- import * as fs20 from "fs";
2878
+ import * as fs21 from "fs";
2582
2879
  import * as os10 from "os";
2583
- import * as path20 from "path";
2880
+ import * as path22 from "path";
2584
2881
  import { randomBytes as randomBytes3 } from "crypto";
2585
2882
 
2586
2883
  // src/hdc/ime.ts
@@ -2843,10 +3140,10 @@ async function dumpLayout(opts) {
2843
3140
  if (!m) throw new OniroError(`Could not parse dumpLayout output:
2844
3141
  ${dump.stdout}`);
2845
3142
  const remote = m[1];
2846
- const local = path20.join(os10.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
3143
+ const local = path22.join(os10.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
2847
3144
  try {
2848
3145
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2849
- const parsed = JSON.parse(await fs20.promises.readFile(local, "utf8"));
3146
+ const parsed = JSON.parse(await fs21.promises.readFile(local, "utf8"));
2850
3147
  const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
2851
3148
  if (opts.prune === false) {
2852
3149
  return { display, tree: parsed };
@@ -2859,7 +3156,7 @@ ${dump.stdout}`);
2859
3156
  const keyboard = kb ? { shown: true, bundle: kb.bundle, ...kb.b ? { b: kb.b } : {} } : { shown: false };
2860
3157
  return { display, tree, keyboard };
2861
3158
  } finally {
2862
- fs20.promises.unlink(local).catch(() => {
3159
+ fs21.promises.unlink(local).catch(() => {
2863
3160
  });
2864
3161
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2865
3162
  });
@@ -2869,7 +3166,7 @@ ${dump.stdout}`);
2869
3166
  // src/hdc/uiActions.ts
2870
3167
  var FOCUS_SETTLE_MS = 400;
2871
3168
  var KEYBOARD_SETTLE_MS = 400;
2872
- var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
3169
+ var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
2873
3170
  function targetTree(layout) {
2874
3171
  const kb = layout.keyboard;
2875
3172
  return kb?.shown && kb.bundle ? withoutWindow(layout.tree, kb.bundle) : layout.tree;
@@ -3022,13 +3319,14 @@ async function waitForTarget(opts) {
3022
3319
  }
3023
3320
 
3024
3321
  // src/hdc/hilog.ts
3322
+ import * as fs22 from "fs";
3025
3323
  function setHilogLevel(opts) {
3026
3324
  const logger = opts.logger ?? noopLogger;
3027
3325
  const hdc = getHdcPath(opts.config);
3028
- return new Promise((resolve6, reject) => {
3326
+ return new Promise((resolve7, reject) => {
3029
3327
  let child;
3030
3328
  try {
3031
- child = spawnCompat(hdc, ["shell", "hilog", "-b", opts.level]);
3329
+ child = spawnCompat(hdc, [...hdcTargetArgs(opts.deviceSerial), "shell", "hilog", "-b", opts.level]);
3032
3330
  } catch (err) {
3033
3331
  const msg = err instanceof Error ? err.message : String(err);
3034
3332
  logger.error(`[hilog] failed to spawn hdc: ${msg}`);
@@ -3045,7 +3343,7 @@ function setHilogLevel(opts) {
3045
3343
  });
3046
3344
  child.once("close", (code) => {
3047
3345
  if (code === 0) {
3048
- resolve6();
3346
+ resolve7();
3049
3347
  } else {
3050
3348
  const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
3051
3349
  logger.error(`[hilog] ${msg}`);
@@ -3056,8 +3354,7 @@ function setHilogLevel(opts) {
3056
3354
  }
3057
3355
  function streamHilog(opts) {
3058
3356
  const hdc = getHdcPath(opts.config);
3059
- const args = [];
3060
- if (opts.deviceSerial) args.push("-t", opts.deviceSerial);
3357
+ const args = [...hdcTargetArgs(opts.deviceSerial)];
3061
3358
  args.push("shell", "hilog");
3062
3359
  if (opts.processId && opts.processId.trim() !== "") {
3063
3360
  args.push("-P", opts.processId.trim());
@@ -3100,7 +3397,7 @@ async function resolveProcessId(config, bundle, deviceSerial) {
3100
3397
  return proc?.pid;
3101
3398
  }
3102
3399
  function waitForLog(opts) {
3103
- return new Promise((resolve6, reject) => {
3400
+ return new Promise((resolve7, reject) => {
3104
3401
  const deadline = Date.now() + opts.timeoutMs;
3105
3402
  let settled = false;
3106
3403
  let child = null;
@@ -3119,7 +3416,7 @@ function waitForLog(opts) {
3119
3416
  if (settled) return;
3120
3417
  settled = true;
3121
3418
  cleanup();
3122
- resolve6(entry);
3419
+ resolve7(entry);
3123
3420
  };
3124
3421
  const fail = (err) => {
3125
3422
  if (settled) return;
@@ -3166,7 +3463,7 @@ function waitForLog(opts) {
3166
3463
  });
3167
3464
  }
3168
3465
  function watchLog(opts) {
3169
- return new Promise((resolve6, reject) => {
3466
+ return new Promise((resolve7, reject) => {
3170
3467
  const matches = [];
3171
3468
  let settled = false;
3172
3469
  let child = null;
@@ -3183,7 +3480,7 @@ function watchLog(opts) {
3183
3480
  if (settled) return;
3184
3481
  settled = true;
3185
3482
  cleanup();
3186
- resolve6(opts.dedup === false ? matches : dedupEntries(matches));
3483
+ resolve7(opts.dedup === false ? matches : dedupEntries(matches));
3187
3484
  };
3188
3485
  const onAbort = () => {
3189
3486
  if (settled) return;
@@ -3215,6 +3512,94 @@ function watchLog(opts) {
3215
3512
  });
3216
3513
  });
3217
3514
  }
3515
+ async function captureLog(opts) {
3516
+ const logger = opts.logger ?? noopLogger;
3517
+ if (opts.clear) {
3518
+ const res = await shell({ config: opts.config, command: "hilog -r", deviceSerial: opts.deviceSerial, timeoutMs: 1e4, logger });
3519
+ ensureOk(res, "hdc shell hilog -r");
3520
+ }
3521
+ const fd = fs22.openSync(opts.file, "a");
3522
+ let lines = 0;
3523
+ let reconnects = 0;
3524
+ try {
3525
+ await new Promise((resolve7, reject) => {
3526
+ const deadline = opts.durationMs !== void 0 ? Date.now() + opts.durationMs : Infinity;
3527
+ let settled = false;
3528
+ let child = null;
3529
+ let reconnectTimer;
3530
+ let buf = "";
3531
+ let open = true;
3532
+ const write = (line) => {
3533
+ if (!open || opts.pattern && !opts.pattern.test(line)) return;
3534
+ fs22.writeSync(fd, `${line}
3535
+ `);
3536
+ lines++;
3537
+ };
3538
+ const cleanup = () => {
3539
+ if (timer) clearTimeout(timer);
3540
+ if (reconnectTimer) clearTimeout(reconnectTimer);
3541
+ opts.abortSignal?.removeEventListener("abort", finish);
3542
+ if (buf.trim()) write(buf.trimEnd());
3543
+ buf = "";
3544
+ open = false;
3545
+ if (child) {
3546
+ child.stdout.removeAllListeners("data");
3547
+ killProcessTree(child);
3548
+ child = null;
3549
+ }
3550
+ };
3551
+ function finish() {
3552
+ if (settled) return;
3553
+ settled = true;
3554
+ cleanup();
3555
+ resolve7();
3556
+ }
3557
+ const fail = (err) => {
3558
+ if (settled) return;
3559
+ settled = true;
3560
+ cleanup();
3561
+ reject(err instanceof Error ? err : new Error(String(err)));
3562
+ };
3563
+ const timer = opts.durationMs !== void 0 ? setTimeout(finish, opts.durationMs) : void 0;
3564
+ if (opts.abortSignal?.aborted) {
3565
+ finish();
3566
+ return;
3567
+ }
3568
+ opts.abortSignal?.addEventListener("abort", finish, { once: true });
3569
+ const spawnStream = () => {
3570
+ if (settled) return;
3571
+ const stream = streamHilog({ config: opts.config, domain: opts.domain, deviceSerial: opts.deviceSerial });
3572
+ child = stream;
3573
+ stream.stdout.on("data", (d) => {
3574
+ buf += d.toString();
3575
+ const parts = buf.split(/\r?\n/);
3576
+ buf = parts.pop() ?? "";
3577
+ for (const line of parts) write(line);
3578
+ });
3579
+ const onEnd = () => {
3580
+ if (settled || child !== stream) return;
3581
+ child = null;
3582
+ if (Date.now() >= deadline) return;
3583
+ reconnects++;
3584
+ logger.warn(`[hilog] stream ended; reconnecting (${reconnects}).`);
3585
+ reconnectTimer = setTimeout(() => {
3586
+ try {
3587
+ spawnStream();
3588
+ } catch (err) {
3589
+ fail(err);
3590
+ }
3591
+ }, 2e3);
3592
+ };
3593
+ stream.once("close", onEnd);
3594
+ stream.once("error", onEnd);
3595
+ };
3596
+ spawnStream();
3597
+ });
3598
+ } finally {
3599
+ fs22.closeSync(fd);
3600
+ }
3601
+ return { file: opts.file, lines, reconnects };
3602
+ }
3218
3603
  async function dumpLog(opts) {
3219
3604
  let pipe = "hilog -x";
3220
3605
  if (opts.domain !== void 0 && `${opts.domain}` !== "") pipe += ` -D ${opts.domain}`;
@@ -3239,17 +3624,17 @@ async function dumpLog(opts) {
3239
3624
  }
3240
3625
 
3241
3626
  // src/build/runHvigorw.ts
3242
- import * as fs22 from "fs";
3243
- import * as path22 from "path";
3627
+ import * as fs24 from "fs";
3628
+ import * as path24 from "path";
3244
3629
 
3245
3630
  // src/harmonyos/target.ts
3246
- import * as fs21 from "fs";
3247
- import * as path21 from "path";
3631
+ import * as fs23 from "fs";
3632
+ import * as path23 from "path";
3248
3633
  import JSON54 from "json5";
3249
3634
  function detectRuntimeOs(projectDir, productName) {
3250
3635
  let products = [];
3251
3636
  try {
3252
- const parsed = JSON54.parse(fs21.readFileSync(path21.join(projectDir, "build-profile.json5"), "utf-8"));
3637
+ const parsed = JSON54.parse(fs23.readFileSync(path23.join(projectDir, "build-profile.json5"), "utf-8"));
3253
3638
  if (Array.isArray(parsed.app?.products)) products = parsed.app.products;
3254
3639
  } catch {
3255
3640
  return "OpenHarmony";
@@ -3290,7 +3675,7 @@ function runHvigorw(opts) {
3290
3675
  const { config } = opts;
3291
3676
  const projectDir = toLongPath(opts.projectDir);
3292
3677
  const logger = opts.logger ?? noopLogger;
3293
- if (!fs22.existsSync(path22.join(projectDir, "build-profile.json5"))) {
3678
+ if (!fs24.existsSync(path24.join(projectDir, "build-profile.json5"))) {
3294
3679
  throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
3295
3680
  }
3296
3681
  let hvigorw;
@@ -3302,24 +3687,24 @@ function runHvigorw(opts) {
3302
3687
  hvigorw = resolved.startsWith(projectDir) ? resolved : findHarmonyOsTool(sdk, "hvigorw") ?? resolved;
3303
3688
  logger.info(`[build] Building for HarmonyOS against the SDK at ${sdk.sdkPath}.`);
3304
3689
  } else {
3305
- if (!fs22.existsSync(getCmdToolsPath(config))) {
3690
+ if (!fs24.existsSync(getCmdToolsPath(config))) {
3306
3691
  throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
3307
3692
  }
3308
3693
  sdkEnv = { OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config) };
3309
3694
  hvigorw = getHvigorwPath(config, projectDir);
3310
3695
  }
3311
- if (!fs22.existsSync(hvigorw)) {
3696
+ if (!fs24.existsSync(hvigorw)) {
3312
3697
  throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
3313
3698
  }
3314
3699
  const args = buildHvigorwArgs(opts);
3315
3700
  if (process.platform !== "win32") {
3316
3701
  try {
3317
- fs22.chmodSync(hvigorw, 493);
3702
+ fs24.chmodSync(hvigorw, 493);
3318
3703
  } catch {
3319
3704
  }
3320
3705
  }
3321
3706
  const env = { ...process.env, ...sdkEnv };
3322
- return new Promise((resolve6, reject) => {
3707
+ return new Promise((resolve7, reject) => {
3323
3708
  if (opts.abortSignal?.aborted) {
3324
3709
  reject(new CancelledError("hvigorw build cancelled."));
3325
3710
  return;
@@ -3363,46 +3748,15 @@ function runHvigorw(opts) {
3363
3748
  settled = true;
3364
3749
  cleanup();
3365
3750
  const exitCode = code ?? 1;
3366
- if (exitCode === 0) resolve6({ exitCode });
3751
+ if (exitCode === 0) resolve7({ exitCode });
3367
3752
  else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
3368
3753
  });
3369
3754
  });
3370
3755
  }
3371
3756
 
3372
- // src/build/discoverHaps.ts
3373
- import * as fs23 from "fs";
3374
- import * as path23 from "path";
3375
- function moduleOfHap(hapFileName) {
3376
- const dash = hapFileName.indexOf("-");
3377
- return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
3378
- }
3379
- async function discoverHaps(opts) {
3380
- const { projectDir } = opts;
3381
- const maxDepth = opts.maxDepth ?? 8;
3382
- const result = {};
3383
- const walk = async (dir, depth) => {
3384
- if (depth > maxDepth) return;
3385
- const entries = await fs23.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
3386
- for (const e of entries) {
3387
- const full = path23.join(dir, e.name);
3388
- if (e.isDirectory()) {
3389
- if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
3390
- await walk(full, depth + 1);
3391
- } else if (e.isFile() && e.name.endsWith(".hap")) {
3392
- const moduleName = moduleOfHap(e.name);
3393
- const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
3394
- if (e.name.includes("unsigned")) bucket.unsigned.push(full);
3395
- else if (e.name.includes("signed")) bucket.signed.push(full);
3396
- }
3397
- }
3398
- };
3399
- await walk(projectDir, 0);
3400
- return result;
3401
- }
3402
-
3403
3757
  // src/build/ohpm.ts
3404
- import * as fs24 from "fs";
3405
- import * as path24 from "path";
3758
+ import * as fs25 from "fs";
3759
+ import * as path25 from "path";
3406
3760
  function runOhpm(opts) {
3407
3761
  return runProcess({
3408
3762
  command: opts.ohpmPath ?? getOhpmPath(opts.config),
@@ -3415,7 +3769,7 @@ function runOhpm(opts) {
3415
3769
  });
3416
3770
  }
3417
3771
  async function ensureOhModules(opts) {
3418
- if (fs24.existsSync(path24.join(opts.projectDir, "oh_modules"))) {
3772
+ if (fs25.existsSync(path25.join(opts.projectDir, "oh_modules"))) {
3419
3773
  return { installed: false };
3420
3774
  }
3421
3775
  const res = await runOhpm({
@@ -3432,23 +3786,91 @@ async function ensureOhModules(opts) {
3432
3786
  }
3433
3787
 
3434
3788
  // src/build/codelinter.ts
3435
- var FINDING_RE = /^(.*?):(\d+):(?:\d+:)?\s*(error|warning|warn|info)\b[:\s-]*(.*)$/i;
3789
+ var FINDING_RE = /^(.*?):(\d+):(?:(\d+):)?\s*(error|warning|warn|suggestion|info)\b[:\s-]*(.*)$/i;
3436
3790
  var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
3437
- function parseCodelinterFindings(output) {
3791
+ var FILE_HEADER_RE = /^(.+\.\w+)\((\d+)\)$/;
3792
+ var ROW_RE = /^(\d+):(\d+)\s+(error|warning|warn|suggestion|info)\s+(.*)$/i;
3793
+ var RULE_RE = /^@?[\w.-]+(?:\/[\w.-]+)+$/;
3794
+ var SUMMARY_RE = /Defects:\s*(\d+);\s*Errors:\s*(\d+);\s*Warns:\s*(\d+);\s*Suggestions:\s*(\d+)/i;
3795
+ var NOTICE_RE = /does not match that of the project|error occurred during linting|incomplete report/i;
3796
+ var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
3797
+ function cleanCodelinterOutput(output) {
3798
+ return output.replace(ANSI_RE, "").split("\n").map((line) => (line.split("\r").filter((s) => s.trim() !== "").pop() ?? "").trimEnd()).join("\n");
3799
+ }
3800
+ function normaliseSeverity(s) {
3801
+ const lower = s.toLowerCase();
3802
+ return lower === "warn" ? "warning" : lower;
3803
+ }
3804
+ function parseCodelinterReport(output) {
3438
3805
  const findings = [];
3439
- for (const raw of output.split("\n")) {
3440
- const m = FINDING_RE.exec(raw.trim());
3441
- if (!m) continue;
3442
- let message = m[4].trim();
3443
- let code = "";
3444
- const codeMatch = TRAILING_CODE_RE.exec(message);
3445
- if (codeMatch) {
3446
- code = codeMatch[1];
3447
- message = message.slice(0, codeMatch.index).trim();
3806
+ const notices = [];
3807
+ let summary = null;
3808
+ let file = "";
3809
+ for (const rawLine of cleanCodelinterOutput(output).split("\n")) {
3810
+ const line = rawLine.trim();
3811
+ if (!line) continue;
3812
+ const row = ROW_RE.exec(line);
3813
+ if (row) {
3814
+ if (!file) continue;
3815
+ const columns = row[4].trim().split(/\s{2,}/);
3816
+ const last = columns[columns.length - 1];
3817
+ const code = columns.length > 1 && RULE_RE.test(last) ? last : "";
3818
+ const message = (code ? columns.slice(0, -1) : columns).join(" ").trim();
3819
+ findings.push({
3820
+ file,
3821
+ line: Number(row[1]),
3822
+ column: Number(row[2]),
3823
+ severity: normaliseSeverity(row[3]),
3824
+ code,
3825
+ message
3826
+ });
3827
+ continue;
3828
+ }
3829
+ const header = FILE_HEADER_RE.exec(line);
3830
+ if (header) {
3831
+ file = header[1];
3832
+ continue;
3833
+ }
3834
+ const legacy = FINDING_RE.exec(line);
3835
+ if (legacy) {
3836
+ let message = legacy[5].trim();
3837
+ let code = "";
3838
+ const codeMatch = TRAILING_CODE_RE.exec(message);
3839
+ if (codeMatch) {
3840
+ code = codeMatch[1];
3841
+ message = message.slice(0, codeMatch.index).trim();
3842
+ }
3843
+ const finding = {
3844
+ file: legacy[1].trim(),
3845
+ line: Number(legacy[2]),
3846
+ severity: normaliseSeverity(legacy[4]),
3847
+ code,
3848
+ message
3849
+ };
3850
+ if (legacy[3]) finding.column = Number(legacy[3]);
3851
+ findings.push(finding);
3852
+ continue;
3448
3853
  }
3449
- findings.push({ file: m[1].trim(), line: Number(m[2]), severity: m[3].toLowerCase(), code, message });
3854
+ const totals = SUMMARY_RE.exec(line);
3855
+ if (totals) {
3856
+ summary = {
3857
+ defects: Number(totals[1]),
3858
+ errors: Number(totals[2]),
3859
+ warnings: Number(totals[3]),
3860
+ suggestions: Number(totals[4])
3861
+ };
3862
+ continue;
3863
+ }
3864
+ if (/no defects found/i.test(line)) {
3865
+ summary = { defects: 0, errors: 0, warnings: 0, suggestions: 0 };
3866
+ continue;
3867
+ }
3868
+ if (NOTICE_RE.test(line)) notices.push(line);
3450
3869
  }
3451
- return findings;
3870
+ return { findings, summary, notices, incomplete: notices.length > 0 };
3871
+ }
3872
+ function parseCodelinterFindings(output) {
3873
+ return parseCodelinterReport(output).findings;
3452
3874
  }
3453
3875
  async function runCodelinter(opts) {
3454
3876
  const res = await runProcess({
@@ -3460,16 +3882,28 @@ async function runCodelinter(opts) {
3460
3882
  logger: opts.logger
3461
3883
  });
3462
3884
  const raw = `${res.stdout}${res.stderr}`;
3463
- return { code: res.code, findings: parseCodelinterFindings(raw), raw };
3885
+ return { code: res.code, ...parseCodelinterReport(raw), raw: cleanCodelinterOutput(raw) };
3464
3886
  }
3465
3887
 
3466
3888
  // src/build/buildHap.ts
3889
+ var DIAGNOSIS_OUTPUT_LIMIT = 2e6;
3890
+ function diagnoseBuildFailure(output) {
3891
+ if (/is not exported from Kit|has no exported member .*@systemapi/i.test(output)) {
3892
+ return "The project calls system APIs (`@systemapi`), which the public SDK leaves out. Build against the full OpenHarmony SDK: unpack its components into <root>/<os>/<api> of a separate SDK root and set ONIRO_SDK_ROOT_DIR to that root. Installing such an app also needs privileged signing (`oniro-app sign --apl system_core --acls \u2026 --app-privilege-capabilities \u2026`).";
3893
+ }
3894
+ if (/00308018|SDK path does not exist|SDK license agreement is not accepted/i.test(output)) {
3895
+ return "hvigor cannot find the SDK the project compiles against: check `oniro-app sdk list` against compileSdkVersion.";
3896
+ }
3897
+ return void 0;
3898
+ }
3467
3899
  async function buildHap(opts) {
3468
3900
  const logger = scopedLogger(opts.logger ?? noopLogger, "build");
3469
3901
  const warnings = [];
3470
3902
  const start = Date.now();
3471
3903
  const projectDir = toLongPath(opts.projectDir);
3472
3904
  const isHarmonyOs = detectRuntimeOs(projectDir, opts.product) === "HarmonyOS";
3905
+ const missingSdk = isHarmonyOs ? null : findMissingProjectSdk(opts.config, projectDir);
3906
+ if (missingSdk) throw missingSdk;
3473
3907
  if (opts.autoInstallDeps !== false) {
3474
3908
  const harmonySdk = isHarmonyOs ? findHarmonyOsSdk({ config: opts.config, logger: opts.logger }) : null;
3475
3909
  const r = await ensureOhModules({
@@ -3482,23 +3916,35 @@ async function buildHap(opts) {
3482
3916
  });
3483
3917
  if (r.installed) logger.info("ran ohpm install --all (oh_modules was missing)");
3484
3918
  }
3485
- const { exitCode } = await runHvigorw({
3486
- config: opts.config,
3487
- projectDir,
3488
- product: opts.product,
3489
- module: opts.module,
3490
- buildMode: opts.mode,
3491
- task: opts.task,
3492
- parallel: opts.parallel,
3493
- abortSignal: opts.abortSignal,
3494
- onOutput: opts.onOutput,
3495
- logger: opts.logger
3496
- });
3919
+ let output = "";
3920
+ let exitCode;
3921
+ try {
3922
+ ({ exitCode } = await runHvigorw({
3923
+ config: opts.config,
3924
+ projectDir,
3925
+ product: opts.product,
3926
+ module: opts.module,
3927
+ buildMode: opts.mode,
3928
+ task: opts.task,
3929
+ parallel: opts.parallel,
3930
+ abortSignal: opts.abortSignal,
3931
+ onOutput: (chunk, stream) => {
3932
+ output = (output + chunk).slice(-DIAGNOSIS_OUTPUT_LIMIT);
3933
+ opts.onOutput?.(chunk, stream);
3934
+ },
3935
+ logger: opts.logger
3936
+ }));
3937
+ } catch (err) {
3938
+ const hint = diagnoseBuildFailure(output);
3939
+ if (hint && err instanceof OniroError) throw new OniroError(`${err.message}
3940
+ ${hint}`, err);
3941
+ throw err;
3942
+ }
3497
3943
  const discoveredHaps = await discoverHaps({ projectDir });
3498
3944
  const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
3499
3945
  if (!anySigned) {
3500
3946
  warnings.push(
3501
- 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)."
3947
+ isHarmonyOs ? "No signed HAPs found. The HarmonyOS emulator installs the unsigned HAP as it is; for a device, run `oniro-app sign --harmonyos` to have AppGallery Connect issue a certificate and profile." : "No signed HAPs found: most OpenHarmony targets reject unsigned HAPs. Run `oniro-app sign` to generate local signing material, then build again."
3502
3948
  );
3503
3949
  }
3504
3950
  return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
@@ -3515,21 +3961,21 @@ function isValidBundleName(bundleName) {
3515
3961
  }
3516
3962
 
3517
3963
  // src/project/templates.ts
3518
- import * as fs25 from "fs";
3519
- import * as path25 from "path";
3964
+ import * as fs26 from "fs";
3965
+ import * as path26 from "path";
3520
3966
  function toHumanTemplateName(folderName) {
3521
3967
  return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
3522
3968
  }
3523
3969
  function listTemplates(templateRoot) {
3524
- if (!fs25.existsSync(templateRoot)) return [];
3525
- const entries = fs25.readdirSync(templateRoot, { withFileTypes: true });
3970
+ if (!fs26.existsSync(templateRoot)) return [];
3971
+ const entries = fs26.readdirSync(templateRoot, { withFileTypes: true });
3526
3972
  return entries.filter((e) => e.isDirectory()).map((e) => {
3527
- const dir = path25.join(templateRoot, e.name);
3528
- const metaPath = path25.join(dir, "template.json");
3973
+ const dir = path26.join(templateRoot, e.name);
3974
+ const metaPath = path26.join(dir, "template.json");
3529
3975
  let meta = {};
3530
- if (fs25.existsSync(metaPath)) {
3976
+ if (fs26.existsSync(metaPath)) {
3531
3977
  try {
3532
- meta = JSON.parse(fs25.readFileSync(metaPath, "utf8"));
3978
+ meta = JSON.parse(fs26.readFileSync(metaPath, "utf8"));
3533
3979
  } catch {
3534
3980
  }
3535
3981
  }
@@ -3544,57 +3990,57 @@ function listTemplates(templateRoot) {
3544
3990
  function validateTemplateLayout(templateDir, defaultModuleName) {
3545
3991
  const required = [
3546
3992
  "build-profile.json5",
3547
- path25.join("AppScope", "app.json5"),
3548
- path25.join(defaultModuleName, "src", "main", "module.json5"),
3549
- path25.join(defaultModuleName, "oh-package.json5"),
3993
+ path26.join("AppScope", "app.json5"),
3994
+ path26.join(defaultModuleName, "src", "main", "module.json5"),
3995
+ path26.join(defaultModuleName, "oh-package.json5"),
3550
3996
  "hvigorfile.ts"
3551
3997
  ];
3552
- return required.filter((rel) => !fs25.existsSync(path25.join(templateDir, rel)));
3998
+ return required.filter((rel) => !fs26.existsSync(path26.join(templateDir, rel)));
3553
3999
  }
3554
4000
 
3555
4001
  // src/project/createScaffold.ts
3556
- import * as fs27 from "fs";
3557
- import * as path26 from "path";
4002
+ import * as fs28 from "fs";
4003
+ import * as path27 from "path";
3558
4004
 
3559
4005
  // src/project/jsonHelpers.ts
3560
- import * as fs26 from "fs";
4006
+ import * as fs27 from "fs";
3561
4007
  import JSON55 from "json5";
3562
4008
  function readJson5File(filePath) {
3563
- return JSON55.parse(fs26.readFileSync(filePath, "utf8"));
4009
+ return JSON55.parse(fs27.readFileSync(filePath, "utf8"));
3564
4010
  }
3565
4011
  function writeJson5File(filePath, value) {
3566
- fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4012
+ fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3567
4013
  }
3568
4014
  function readJsonFile(filePath) {
3569
- return JSON.parse(fs26.readFileSync(filePath, "utf8"));
4015
+ return JSON.parse(fs27.readFileSync(filePath, "utf8"));
3570
4016
  }
3571
4017
  function writeJsonFile(filePath, value) {
3572
- fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4018
+ fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3573
4019
  }
3574
4020
 
3575
4021
  // src/project/createScaffold.ts
3576
4022
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
3577
4023
  async function pathExists(p) {
3578
4024
  try {
3579
- await fs27.promises.access(p);
4025
+ await fs28.promises.access(p);
3580
4026
  return true;
3581
4027
  } catch {
3582
4028
  return false;
3583
4029
  }
3584
4030
  }
3585
4031
  async function copyDirRecursive(srcDir, destDir) {
3586
- await fs27.promises.mkdir(destDir, { recursive: true });
3587
- const entries = await fs27.promises.readdir(srcDir, { withFileTypes: true });
4032
+ await fs28.promises.mkdir(destDir, { recursive: true });
4033
+ const entries = await fs28.promises.readdir(srcDir, { withFileTypes: true });
3588
4034
  for (const entry of entries) {
3589
- const src = path26.join(srcDir, entry.name);
3590
- const dest = path26.join(destDir, entry.name);
4035
+ const src = path27.join(srcDir, entry.name);
4036
+ const dest = path27.join(destDir, entry.name);
3591
4037
  if (entry.isDirectory()) {
3592
4038
  await copyDirRecursive(src, dest);
3593
4039
  } else if (entry.isSymbolicLink()) {
3594
4040
  continue;
3595
4041
  } else if (entry.isFile()) {
3596
4042
  if (entry.name === "template.json") continue;
3597
- await fs27.promises.copyFile(src, dest);
4043
+ await fs28.promises.copyFile(src, dest);
3598
4044
  }
3599
4045
  }
3600
4046
  }
@@ -3602,9 +4048,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
3602
4048
  const stack = [projectDir];
3603
4049
  while (stack.length > 0) {
3604
4050
  const currentDir = stack.pop();
3605
- const entries = await fs27.promises.readdir(currentDir, { withFileTypes: true });
4051
+ const entries = await fs28.promises.readdir(currentDir, { withFileTypes: true });
3606
4052
  for (const entry of entries) {
3607
- const fullPath = path26.join(currentDir, entry.name);
4053
+ const fullPath = path27.join(currentDir, entry.name);
3608
4054
  if (entry.isDirectory()) {
3609
4055
  if (IGNORED_DIRS.has(entry.name)) continue;
3610
4056
  stack.push(fullPath);
@@ -3626,23 +4072,23 @@ function toJavaPropertiesPath(p) {
3626
4072
  function createOrUpdateLocalProperties(projectDir, sdkDir) {
3627
4073
  const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
3628
4074
  `;
3629
- fs27.writeFileSync(path26.join(projectDir, "local.properties"), content, "utf8");
4075
+ fs28.writeFileSync(path27.join(projectDir, "local.properties"), content, "utf8");
3630
4076
  }
3631
4077
  function renameIfExists(fromPath, toPath) {
3632
- if (fs27.existsSync(fromPath) && fromPath !== toPath) {
3633
- fs27.renameSync(fromPath, toPath);
4078
+ if (fs28.existsSync(fromPath) && fromPath !== toPath) {
4079
+ fs28.renameSync(fromPath, toPath);
3634
4080
  }
3635
4081
  }
3636
4082
  function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3637
- const appJsonPath = path26.join(projectDir, "AppScope", "app.json5");
3638
- if (fs27.existsSync(appJsonPath)) {
4083
+ const appJsonPath = path27.join(projectDir, "AppScope", "app.json5");
4084
+ if (fs28.existsSync(appJsonPath)) {
3639
4085
  const appJson = readJson5File(appJsonPath);
3640
4086
  appJson.app = appJson.app ?? {};
3641
4087
  appJson.app.bundleName = args.bundleName;
3642
4088
  writeJson5File(appJsonPath, appJson);
3643
4089
  }
3644
- const stringsPath = path26.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
3645
- if (fs27.existsSync(stringsPath)) {
4090
+ const stringsPath = path27.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
4091
+ if (fs28.existsSync(stringsPath)) {
3646
4092
  const strings = readJsonFile(stringsPath);
3647
4093
  if (Array.isArray(strings.string)) {
3648
4094
  const appName = strings.string.find((s) => s.name === "app_name");
@@ -3650,8 +4096,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3650
4096
  }
3651
4097
  writeJsonFile(stringsPath, strings);
3652
4098
  }
3653
- const buildProfilePath = path26.join(projectDir, "build-profile.json5");
3654
- if (fs27.existsSync(buildProfilePath)) {
4099
+ const buildProfilePath = path27.join(projectDir, "build-profile.json5");
4100
+ if (fs28.existsSync(buildProfilePath)) {
3655
4101
  const buildProfile = readJson5File(buildProfilePath);
3656
4102
  buildProfile.app = buildProfile.app ?? {};
3657
4103
  if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
@@ -3664,23 +4110,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3664
4110
  }
3665
4111
  writeJson5File(buildProfilePath, buildProfile);
3666
4112
  }
3667
- const moduleJsonPath = path26.join(projectDir, args.moduleName, "src", "main", "module.json5");
3668
- if (fs27.existsSync(moduleJsonPath)) {
4113
+ const moduleJsonPath = path27.join(projectDir, args.moduleName, "src", "main", "module.json5");
4114
+ if (fs28.existsSync(moduleJsonPath)) {
3669
4115
  const moduleJson = readJson5File(moduleJsonPath);
3670
4116
  moduleJson.module = moduleJson.module ?? {};
3671
4117
  moduleJson.module.name = args.moduleName;
3672
4118
  writeJson5File(moduleJsonPath, moduleJson);
3673
4119
  }
3674
- const moduleOhPackagePath = path26.join(projectDir, args.moduleName, "oh-package.json5");
3675
- if (fs27.existsSync(moduleOhPackagePath)) {
4120
+ const moduleOhPackagePath = path27.join(projectDir, args.moduleName, "oh-package.json5");
4121
+ if (fs28.existsSync(moduleOhPackagePath)) {
3676
4122
  const pkg = readJson5File(moduleOhPackagePath);
3677
4123
  pkg.name = args.moduleName;
3678
4124
  writeJson5File(moduleOhPackagePath, pkg);
3679
4125
  }
3680
- const vscodeDir = path26.join(projectDir, ".vscode");
3681
- fs27.mkdirSync(vscodeDir, { recursive: true });
4126
+ const vscodeDir = path27.join(projectDir, ".vscode");
4127
+ fs28.mkdirSync(vscodeDir, { recursive: true });
3682
4128
  const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
3683
- writeJsonFile(path26.join(vscodeDir, "settings.json"), {
4129
+ writeJsonFile(path27.join(vscodeDir, "settings.json"), {
3684
4130
  "oniro.hapPath": hapPath,
3685
4131
  "files.associations": { "*.json5": "jsonc" }
3686
4132
  });
@@ -3697,7 +4143,7 @@ async function createScaffold(opts) {
3697
4143
  if (!opts.location || !await pathExists(opts.location)) {
3698
4144
  throw new OniroError(`Location does not exist: ${opts.location}`);
3699
4145
  }
3700
- const templateDir = path26.join(opts.templateRoot, opts.templateId);
4146
+ const templateDir = path27.join(opts.templateRoot, opts.templateId);
3701
4147
  if (!await pathExists(templateDir)) {
3702
4148
  throw new OniroError(`Template not found: ${templateDir}`);
3703
4149
  }
@@ -3732,17 +4178,17 @@ async function createScaffold(opts) {
3732
4178
  }
3733
4179
  }
3734
4180
  }
3735
- const projectDir = path26.join(opts.location, opts.projectName);
4181
+ const projectDir = path27.join(opts.location, opts.projectName);
3736
4182
  if (await pathExists(projectDir)) {
3737
4183
  if (!opts.overwrite) {
3738
4184
  throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
3739
4185
  }
3740
- await fs27.promises.rm(projectDir, { recursive: true, force: true });
4186
+ await fs28.promises.rm(projectDir, { recursive: true, force: true });
3741
4187
  }
3742
4188
  logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
3743
4189
  await copyDirRecursive(templateDir, projectDir);
3744
4190
  if (moduleName !== defaultModuleName) {
3745
- renameIfExists(path26.join(projectDir, defaultModuleName), path26.join(projectDir, moduleName));
4191
+ renameIfExists(path27.join(projectDir, defaultModuleName), path27.join(projectDir, moduleName));
3746
4192
  }
3747
4193
  updateTemplateConfigs(
3748
4194
  projectDir,
@@ -3759,11 +4205,11 @@ async function createScaffold(opts) {
3759
4205
  }
3760
4206
 
3761
4207
  // src/project/listModules.ts
3762
- import * as fs28 from "fs";
3763
- import * as path27 from "path";
4208
+ import * as fs29 from "fs";
4209
+ import * as path28 from "path";
3764
4210
  function listModules(opts) {
3765
- const buildProfile = path27.join(opts.projectDir, "build-profile.json5");
3766
- if (!fs28.existsSync(buildProfile)) {
4211
+ const buildProfile = path28.join(opts.projectDir, "build-profile.json5");
4212
+ if (!fs29.existsSync(buildProfile)) {
3767
4213
  throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
3768
4214
  }
3769
4215
  const parsed = readJson5File(buildProfile);
@@ -3775,12 +4221,12 @@ function listModules(opts) {
3775
4221
  }
3776
4222
 
3777
4223
  // src/project/abilities.ts
3778
- import * as fs29 from "fs";
3779
- import * as path28 from "path";
4224
+ import * as fs30 from "fs";
4225
+ import * as path29 from "path";
3780
4226
  function listAbilities(opts) {
3781
4227
  const moduleName = opts.moduleName ?? "entry";
3782
- const moduleJsonPath = path28.join(opts.projectDir, moduleName, "src", "main", "module.json5");
3783
- if (!fs29.existsSync(moduleJsonPath)) {
4228
+ const moduleJsonPath = path29.join(opts.projectDir, moduleName, "src", "main", "module.json5");
4229
+ if (!fs30.existsSync(moduleJsonPath)) {
3784
4230
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
3785
4231
  }
3786
4232
  const parsed = readJson5File(moduleJsonPath);
@@ -3788,12 +4234,12 @@ function listAbilities(opts) {
3788
4234
  }
3789
4235
 
3790
4236
  // src/project/prepareSigning.ts
3791
- import * as fs30 from "fs";
3792
- import * as path29 from "path";
4237
+ import * as fs31 from "fs";
4238
+ import * as path30 from "path";
3793
4239
  function hasSigningArtifacts(dir) {
3794
- if (!fs30.existsSync(path29.join(dir, "signatures"))) return false;
3795
- const buildProfile = path29.join(dir, "build-profile.json5");
3796
- if (!fs30.existsSync(buildProfile)) return false;
4240
+ if (!fs31.existsSync(path30.join(dir, "signatures"))) return false;
4241
+ const buildProfile = path30.join(dir, "build-profile.json5");
4242
+ if (!fs31.existsSync(buildProfile)) return false;
3797
4243
  try {
3798
4244
  const parsed = readJson5File(buildProfile);
3799
4245
  return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
@@ -3811,6 +4257,7 @@ function prepareSigning(opts) {
3811
4257
  apl: opts.apl,
3812
4258
  appFeature: opts.appFeature,
3813
4259
  acls: opts.acls,
4260
+ appPrivilegeCapabilities: opts.appPrivilegeCapabilities,
3814
4261
  passwords: opts.passwords,
3815
4262
  logger: opts.logger
3816
4263
  });
@@ -3839,19 +4286,31 @@ var PROPS = [
3839
4286
  ["model", "const.product.model"],
3840
4287
  ["manufacturer", "const.product.manufacturer"],
3841
4288
  ["brand", "const.product.brand"],
4289
+ ["osDistName", "const.product.os.dist.name"],
4290
+ ["osDistVersion", "const.product.os.dist.version"],
3842
4291
  ["osFullName", "const.ohos.fullname"],
3843
4292
  ["osReleaseType", "const.ohos.releasetype"],
3844
4293
  ["sdkApiVersion", "const.ohos.apiversion"],
3845
- ["buildVersion", "const.product.software.version"]
4294
+ ["buildVersion", "const.product.software.version"],
4295
+ ["abi", "const.product.cpu.abilist"],
4296
+ ["deviceType", "const.product.devicetype"]
3846
4297
  ];
3847
4298
  async function getDeviceInfo(opts) {
3848
4299
  const info = { serial: opts.deviceSerial ?? "(default)", display: null };
3849
- for (const [field, prop] of PROPS) {
3850
- try {
3851
- const value = await paramGet(opts.config, prop, { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
4300
+ try {
4301
+ const values = await paramGetMany(
4302
+ opts.config,
4303
+ PROPS.map(([, prop]) => prop),
4304
+ { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger }
4305
+ );
4306
+ for (const [field, prop] of PROPS) {
4307
+ const value = values[prop];
3852
4308
  if (value) info[field] = value;
3853
- } catch {
3854
4309
  }
4310
+ } catch {
4311
+ }
4312
+ if (info.osDistName || info.osFullName) {
4313
+ info.osFamily = /harmonyos/i.test(info.osDistName ?? "") ? "HarmonyOS" : "OpenHarmony";
3855
4314
  }
3856
4315
  const screen = await dumpScreen({ config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger });
3857
4316
  if (screen) info.display = { width: screen.width, height: screen.height };
@@ -3883,8 +4342,8 @@ async function diffHapAssets(opts) {
3883
4342
  }
3884
4343
 
3885
4344
  // src/install/apply.ts
3886
- import * as fs31 from "fs";
3887
- import * as path30 from "path";
4345
+ import * as fs32 from "fs";
4346
+ import * as path31 from "path";
3888
4347
  function decideInstallMethod(s) {
3889
4348
  if (!s.signInfoInconsistent) return "replace";
3890
4349
  if (s.isSystemBundle && !s.allowUninstall) return "refuse";
@@ -3902,39 +4361,31 @@ async function pidOf(config, bundle, deviceSerial) {
3902
4361
  const proc = await findRunningProcess({ config, bundle, deviceSerial });
3903
4362
  return proc ? Number(proc.pid) : null;
3904
4363
  }
3905
- async function resolveHapPath(opts) {
3906
- let hapPath = opts.hapPath;
3907
- if (hapPath) {
3908
- if (!path30.isAbsolute(hapPath) && opts.projectDir) hapPath = path30.join(opts.projectDir, hapPath);
4364
+ async function resolveHap(opts, logger) {
4365
+ let hap;
4366
+ if (opts.hapPath) {
4367
+ const hapPath = !path31.isAbsolute(opts.hapPath) && opts.projectDir ? path31.join(opts.projectDir, opts.hapPath) : opts.hapPath;
4368
+ hap = existingHapOrUnsigned(hapPath);
4369
+ if (!hap) throw new OniroError(`HAP not found at ${hapPath}.`);
3909
4370
  } else {
3910
4371
  if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
3911
- const haps = await discoverHaps({ projectDir: opts.projectDir });
3912
- if (opts.module) {
3913
- const signed = haps[opts.module]?.signed ?? [];
3914
- if (signed.length === 0) throw new OniroError(`No signed HAP for module ${opts.module}. Build first.`);
3915
- hapPath = signed[0];
3916
- } else {
3917
- const withSigned = Object.entries(haps).filter(([, m]) => m.signed.length > 0);
3918
- if (withSigned.length === 0) throw new OniroError("No signed HAP found. Build first.");
3919
- if (withSigned.length > 1) {
3920
- throw new OniroError(
3921
- `Multiple modules have signed HAPs (${withSigned.map(([k]) => k).join(", ")}). Pass module to choose.`
3922
- );
3923
- }
3924
- hapPath = withSigned[0][1].signed[0];
3925
- }
4372
+ const picked = pickHap(await discoverHaps({ projectDir: opts.projectDir }), opts.module);
4373
+ if (!picked) throw new OniroError(`No HAP found${opts.module ? ` for module ${opts.module}` : ""}. Build first.`);
4374
+ hap = { path: picked.path, fallback: !picked.signed };
3926
4375
  }
3927
- if (!fs31.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
3928
- return hapPath;
4376
+ if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path31.basename(hap.path)}.`);
4377
+ return hap;
3929
4378
  }
3930
4379
  async function applyChanges(opts) {
3931
4380
  const logger = scopedLogger(opts.logger ?? noopLogger, "apply");
3932
4381
  const { config, bundle, deviceSerial } = opts;
3933
4382
  const isSystemBundle = opts.isSystemBundle ?? false;
3934
4383
  const allowUninstall = opts.allowUninstall ?? false;
3935
- const hapPath = await resolveHapPath(opts);
4384
+ const hap = await resolveHap(opts, logger);
4385
+ const hapPath = hap.path;
4386
+ logger.info(`installing ${hapPath}`);
3936
4387
  let assetsChanged = false;
3937
- if (opts.installedHapPath && fs31.existsSync(opts.installedHapPath)) {
4388
+ if (opts.installedHapPath && fs32.existsSync(opts.installedHapPath)) {
3938
4389
  try {
3939
4390
  const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
3940
4391
  assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
@@ -3955,12 +4406,21 @@ async function applyChanges(opts) {
3955
4406
  if (decision === "uninstall-install") {
3956
4407
  logger.warn(`sign-info inconsistent for ${bundle}; uninstalling then installing fresh.`);
3957
4408
  await uninstallApp({ config, bundle, deviceSerial });
3958
- ensureOk(await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }), `hdc install ${hapPath}`);
4409
+ ensureInstallOk(
4410
+ await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }),
4411
+ `hdc install ${hapPath}`,
4412
+ hap
4413
+ );
3959
4414
  installMethod = "uninstall-install";
3960
4415
  } else {
3961
- ensureOk(replaceRes, `hdc install -r ${hapPath}`);
4416
+ ensureInstallOk(replaceRes, `hdc install -r ${hapPath}`, hap);
3962
4417
  installMethod = "replace";
3963
4418
  }
4419
+ if (!await isBundleInstalled({ config, bundle, deviceSerial })) {
4420
+ throw new OniroError(
4421
+ `hdc reported ${path31.basename(hapPath)} as installed, but bm dump does not list ${bundle}. Check that the HAP was built from this project (its bundleName must match).`
4422
+ );
4423
+ }
3964
4424
  let postInstallPid = await pidOf(config, bundle, deviceSerial);
3965
4425
  const rebootDecision = decideReboot({ assetsChanged, isSystemBundle, preInstallPid, postInstallPid });
3966
4426
  let method = installMethod;
@@ -4035,9 +4495,9 @@ var AUTH_PATHS = {
4035
4495
  };
4036
4496
 
4037
4497
  // src/harmonyos/auth/tokenStore.ts
4038
- import * as fs32 from "fs";
4498
+ import * as fs33 from "fs";
4039
4499
  import * as os11 from "os";
4040
- import * as path31 from "path";
4500
+ import * as path32 from "path";
4041
4501
  import * as crypto3 from "crypto";
4042
4502
  var ALGORITHM = "aes-256-gcm";
4043
4503
  var KEY_LENGTH = 32;
@@ -4073,7 +4533,7 @@ function permissionHint(dir) {
4073
4533
  }
4074
4534
  function ensureDir(dir) {
4075
4535
  try {
4076
- fs32.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
4536
+ fs33.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
4077
4537
  } catch (err) {
4078
4538
  if (err.code === "EACCES") {
4079
4539
  throw new OniroError(permissionHint(dir), err);
@@ -4084,17 +4544,17 @@ function ensureDir(dir) {
4084
4544
  function createTokenStore(opts) {
4085
4545
  const logger = opts.logger ?? noopLogger;
4086
4546
  const authDir = opts.config.get("harmonyosAuthDir", defaultPaths.harmonyosAuthDir());
4087
- const tokenPath = path31.join(authDir, TOKEN_FILE);
4088
- const keyPath = path31.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
4547
+ const tokenPath = path32.join(authDir, TOKEN_FILE);
4548
+ const keyPath = path32.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
4089
4549
  function loadOrCreateKey() {
4090
- if (fs32.existsSync(keyPath)) {
4091
- const existing = fs32.readFileSync(keyPath);
4550
+ if (fs33.existsSync(keyPath)) {
4551
+ const existing = fs33.readFileSync(keyPath);
4092
4552
  if (existing.length === KEY_LENGTH) return existing;
4093
4553
  logger.warn("[harmonyos] The token key file was malformed; regenerating (you will need to log in again).");
4094
4554
  }
4095
- ensureDir(path31.dirname(keyPath));
4555
+ ensureDir(path32.dirname(keyPath));
4096
4556
  const key = crypto3.randomBytes(KEY_LENGTH);
4097
- fs32.writeFileSync(keyPath, key, { mode: FILE_MODE });
4557
+ fs33.writeFileSync(keyPath, key, { mode: FILE_MODE });
4098
4558
  return key;
4099
4559
  }
4100
4560
  return {
@@ -4104,12 +4564,12 @@ function createTokenStore(opts) {
4104
4564
  const payload = Buffer.from(JSON.stringify(session), "utf8");
4105
4565
  const blob = encrypt2(loadOrCreateKey(), payload);
4106
4566
  ensureDir(authDir);
4107
- fs32.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
4567
+ fs33.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
4108
4568
  },
4109
4569
  load() {
4110
- if (!fs32.existsSync(tokenPath)) return null;
4570
+ if (!fs33.existsSync(tokenPath)) return null;
4111
4571
  try {
4112
- const blob = JSON.parse(fs32.readFileSync(tokenPath, "utf8"));
4572
+ const blob = JSON.parse(fs33.readFileSync(tokenPath, "utf8"));
4113
4573
  if (!isEncryptedBlob(blob)) return null;
4114
4574
  const { jwtToken, siteId } = JSON.parse(decrypt2(loadOrCreateKey(), blob).toString("utf8"));
4115
4575
  return typeof jwtToken === "string" && jwtToken && typeof siteId === "string" ? { jwtToken, siteId } : null;
@@ -4121,7 +4581,7 @@ function createTokenStore(opts) {
4121
4581
  }
4122
4582
  logger.debug(`[harmonyos] Discarding unreadable token at ${tokenPath}.`);
4123
4583
  try {
4124
- fs32.rmSync(tokenPath, { force: true });
4584
+ fs33.rmSync(tokenPath, { force: true });
4125
4585
  } catch {
4126
4586
  }
4127
4587
  return null;
@@ -4129,7 +4589,7 @@ function createTokenStore(opts) {
4129
4589
  },
4130
4590
  clear() {
4131
4591
  try {
4132
- fs32.rmSync(tokenPath, { force: true });
4592
+ fs33.rmSync(tokenPath, { force: true });
4133
4593
  } catch (err) {
4134
4594
  throw new OniroError(`Failed to remove ${tokenPath}.`, err);
4135
4595
  }
@@ -4243,8 +4703,8 @@ function isQuit(quit) {
4243
4703
  async function startCallbackServer(opts) {
4244
4704
  let resolveCallback;
4245
4705
  let rejectCallback;
4246
- const callback = new Promise((resolve6, reject) => {
4247
- resolveCallback = resolve6;
4706
+ const callback = new Promise((resolve7, reject) => {
4707
+ resolveCallback = resolve7;
4248
4708
  rejectCallback = reject;
4249
4709
  });
4250
4710
  callback.catch(() => {
@@ -4303,9 +4763,9 @@ async function startCallbackServer(opts) {
4303
4763
  }
4304
4764
  });
4305
4765
  server.keepAliveTimeout = 1;
4306
- await new Promise((resolve6, reject) => {
4766
+ await new Promise((resolve7, reject) => {
4307
4767
  server.once("error", (err) => reject(new OniroError("Failed to start the local login callback server.", err)));
4308
- server.listen(0, "127.0.0.1", resolve6);
4768
+ server.listen(0, "127.0.0.1", resolve7);
4309
4769
  });
4310
4770
  const port = server.address().port;
4311
4771
  return {
@@ -4321,12 +4781,12 @@ async function startCallbackServer(opts) {
4321
4781
  return Promise.race([callback, timeout]).finally(() => clearTimeout(timer));
4322
4782
  },
4323
4783
  stop() {
4324
- return new Promise((resolve6) => {
4784
+ return new Promise((resolve7) => {
4325
4785
  server.closeAllConnections?.();
4326
- const timer = setTimeout(resolve6, 100);
4786
+ const timer = setTimeout(resolve7, 100);
4327
4787
  server.close(() => {
4328
4788
  clearTimeout(timer);
4329
- resolve6();
4789
+ resolve7();
4330
4790
  });
4331
4791
  });
4332
4792
  }
@@ -4358,11 +4818,11 @@ function openBrowser(url) {
4358
4818
  windowsHide: true
4359
4819
  });
4360
4820
  child.unref();
4361
- return new Promise((resolve6, reject) => {
4821
+ return new Promise((resolve7, reject) => {
4362
4822
  child.on("error", (err) => reject(new OniroError(`Failed to open a browser (${command}).`, err)));
4363
4823
  child.on(
4364
4824
  "exit",
4365
- (code) => code === 0 ? resolve6() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
4825
+ (code) => code === 0 ? resolve7() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
4366
4826
  );
4367
4827
  });
4368
4828
  }
@@ -4610,7 +5070,7 @@ function mapAgcError(operation, statusCode, reasonPhrase, body) {
4610
5070
  }
4611
5071
 
4612
5072
  // src/harmonyos/signing/profile.ts
4613
- import * as fs33 from "fs";
5073
+ import * as fs34 from "fs";
4614
5074
  import { X509Certificate } from "crypto";
4615
5075
  import forge from "node-forge";
4616
5076
  function extractProfileJson(buffer) {
@@ -4642,7 +5102,7 @@ function extractProfileJson(buffer) {
4642
5102
  function readProvisionProfile(profilePath) {
4643
5103
  let raw;
4644
5104
  try {
4645
- const json = extractProfileJson(fs33.readFileSync(profilePath));
5105
+ const json = extractProfileJson(fs34.readFileSync(profilePath));
4646
5106
  if (!json) return null;
4647
5107
  raw = JSON.parse(json);
4648
5108
  } catch {
@@ -4671,7 +5131,7 @@ function parseCertificate(pem) {
4671
5131
  }
4672
5132
  function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
4673
5133
  try {
4674
- const der = forge.util.createBuffer(fs33.readFileSync(p12Path).toString("binary"));
5134
+ const der = forge.util.createBuffer(fs34.readFileSync(p12Path).toString("binary"));
4675
5135
  const p12 = forge.pkcs12.pkcs12FromAsn1(forge.asn1.fromDer(der), keyPwd);
4676
5136
  const certBag = forge.pki.oids.certBag ?? "1.2.840.113549.1.12.10.1.3";
4677
5137
  const wanted = certificate.publicKey.export({ type: "spki", format: "der" });
@@ -4687,7 +5147,7 @@ function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
4687
5147
  }
4688
5148
  }
4689
5149
  function verifySigningMaterial(opts) {
4690
- const pems = fs33.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
5150
+ const pems = fs34.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
4691
5151
  const chain = (pems ?? []).map(parseCertificate);
4692
5152
  const leaf = chain.find((c) => !c.ca) ?? chain.at(-1);
4693
5153
  if (!leaf) throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
@@ -4706,14 +5166,14 @@ function verifySigningMaterial(opts) {
4706
5166
 
4707
5167
  // src/harmonyos/signing/autoSign.ts
4708
5168
  import * as crypto9 from "crypto";
4709
- import * as fs38 from "fs";
4710
- import * as path37 from "path";
5169
+ import * as fs39 from "fs";
5170
+ import * as path38 from "path";
4711
5171
  import JSON57 from "json5";
4712
5172
 
4713
5173
  // src/harmonyos/signing/agc.ts
4714
5174
  import * as crypto6 from "crypto";
4715
- import * as fs34 from "fs";
4716
- import * as path32 from "path";
5175
+ import * as fs35 from "fs";
5176
+ import * as path33 from "path";
4717
5177
  var ENDPOINTS = {
4718
5178
  CERT_LIST: "/api/cps/harmony-cert-manage/v1/cert/list",
4719
5179
  CERT_ADD: "/api/cps/harmony-cert-manage/v1/cert/add",
@@ -4799,8 +5259,8 @@ function createAgcClient(http3, auth) {
4799
5259
  if (target.sha256 && actual !== target.sha256.toLowerCase()) {
4800
5260
  throw new ChecksumMismatchError(target.sha256, actual);
4801
5261
  }
4802
- fs34.mkdirSync(path32.dirname(dest), { recursive: true, mode: 448 });
4803
- fs34.writeFileSync(dest, buffer, { mode: 384 });
5262
+ fs35.mkdirSync(path33.dirname(dest), { recursive: true, mode: 448 });
5263
+ fs35.writeFileSync(dest, buffer, { mode: 384 });
4804
5264
  }
4805
5265
  };
4806
5266
  }
@@ -4851,22 +5311,22 @@ async function registerDevices(agc, connected, logger) {
4851
5311
 
4852
5312
  // src/harmonyos/signing/keystore.ts
4853
5313
  import * as crypto7 from "crypto";
4854
- import * as fs36 from "fs";
4855
- import * as path34 from "path";
5314
+ import * as fs37 from "fs";
5315
+ import * as path35 from "path";
4856
5316
 
4857
5317
  // src/harmonyos/java.ts
4858
- import * as fs35 from "fs";
4859
- import * as path33 from "path";
5318
+ import * as fs36 from "fs";
5319
+ import * as path34 from "path";
4860
5320
  var JAVA_BIN = process.platform === "win32" ? "java.exe" : "java";
4861
5321
  function findJava(sdk, env = process.env) {
4862
5322
  const candidates = [
4863
- env.JAVA_HOME && path33.join(env.JAVA_HOME, "bin", JAVA_BIN),
4864
- sdk && path33.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
5323
+ env.JAVA_HOME && path34.join(env.JAVA_HOME, "bin", JAVA_BIN),
5324
+ sdk && path34.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
4865
5325
  // macOS: the JBR is itself a bundle inside Studio's Contents.
4866
- sdk && path33.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
4867
- ...(env.PATH ?? "").split(path33.delimiter).map((dir) => dir && path33.join(dir, JAVA_BIN))
5326
+ sdk && path34.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
5327
+ ...(env.PATH ?? "").split(path34.delimiter).map((dir) => dir && path34.join(dir, JAVA_BIN))
4868
5328
  ];
4869
- const found = candidates.find((c) => !!c && fs35.existsSync(c));
5329
+ const found = candidates.find((c) => !!c && fs36.existsSync(c));
4870
5330
  if (found) return found;
4871
5331
  throw new OniroError(
4872
5332
  "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."
@@ -4880,13 +5340,13 @@ function redactArgs(args) {
4880
5340
  return args.map((arg, i) => args[i - 1] === "-keyPwd" || args[i - 1] === "-keystorePwd" ? "******" : arg);
4881
5341
  }
4882
5342
  async function generateKeystoreAndCsr(opts) {
4883
- const jar = path34.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
4884
- if (!fs36.existsSync(jar)) {
5343
+ const jar = path35.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
5344
+ if (!fs37.existsSync(jar)) {
4885
5345
  throw new OniroError(`hap-sign-tool.jar not found at ${jar}; the HarmonyOS SDK looks incomplete.`);
4886
5346
  }
4887
5347
  const java = findJava(opts.sdk);
4888
5348
  const keyPwd = crypto7.randomBytes(12).toString("base64url").replaceAll(/[-_]/g, "");
4889
- fs36.mkdirSync(path34.dirname(opts.p12Path), { recursive: true, mode: 448 });
5349
+ fs37.mkdirSync(path35.dirname(opts.p12Path), { recursive: true, mode: 448 });
4890
5350
  const run = async (what, args) => {
4891
5351
  const argv = ["-jar", jar, ...args, "-keyAlias", KEY_ALIAS, "-keystoreFile", opts.p12Path, "-keystorePwd", keyPwd, "-keyPwd", keyPwd];
4892
5352
  opts.logger.debug(`[harmonyos] ${java} ${redactArgs(argv).join(" ")}`);
@@ -4900,28 +5360,28 @@ ${detail}` : ""}`);
4900
5360
  opts.logger.info("[harmonyos] Generating the local keystore and certificate signing request...");
4901
5361
  await run("generate the key pair", ["generate-keypair", "-keyAlg", "ECC", "-keySize", "NIST-P-256"]);
4902
5362
  await run("generate the CSR", ["generate-csr", "-subject", "CN=DebugKey", "-signAlg", SIGN_ALG, "-outFile", opts.csrPath]);
4903
- return { keyPwd, csr: fs36.readFileSync(opts.csrPath, "utf8") };
5363
+ return { keyPwd, csr: fs37.readFileSync(opts.csrPath, "utf8") };
4904
5364
  }
4905
5365
 
4906
5366
  // src/harmonyos/signing/paths.ts
4907
5367
  import * as crypto8 from "crypto";
4908
- import * as path35 from "path";
5368
+ import * as path36 from "path";
4909
5369
  function sanitizeNamePart(raw) {
4910
5370
  const safe = (raw?.trim() ?? "").replace(/[\\/:*?"<>|=.-]+/g, "_");
4911
5371
  return safe.slice(0, 64) || "default";
4912
5372
  }
4913
5373
  function resolveSigningMaterialPaths(config, productName, projectRoot) {
4914
5374
  const hash = crypto8.createHash("sha256").update(projectRoot, "utf8").digest("base64url").replaceAll(/[-_]/g, "");
4915
- const base = path35.join(
5375
+ const base = path36.join(
4916
5376
  config.get("harmonyosSigningDir", defaultPaths.harmonyosSigningDir()),
4917
- `${sanitizeNamePart(productName)}_${path35.basename(projectRoot)}_${hash}=`
5377
+ `${sanitizeNamePart(productName)}_${path36.basename(projectRoot)}_${hash}=`
4918
5378
  );
4919
5379
  return { p12Path: `${base}.p12`, csrPath: `${base}.csr`, cerPath: `${base}.cer`, profilePath: `${base}.p7b` };
4920
5380
  }
4921
5381
 
4922
5382
  // src/harmonyos/signing/project.ts
4923
- import * as fs37 from "fs";
4924
- import * as path36 from "path";
5383
+ import * as fs38 from "fs";
5384
+ import * as path37 from "path";
4925
5385
  import JSON56 from "json5";
4926
5386
 
4927
5387
  // src/harmonyos/signing/aclPermissionList.ts
@@ -4989,9 +5449,9 @@ var ACL_PERMISSIONS = /* @__PURE__ */ new Set([
4989
5449
 
4990
5450
  // src/harmonyos/signing/project.ts
4991
5451
  function readRequestedPermissions(moduleConfigPath) {
4992
- if (!fs37.existsSync(moduleConfigPath)) return [];
5452
+ if (!fs38.existsSync(moduleConfigPath)) return [];
4993
5453
  try {
4994
- const parsed = JSON56.parse(fs37.readFileSync(moduleConfigPath, "utf-8"));
5454
+ const parsed = JSON56.parse(fs38.readFileSync(moduleConfigPath, "utf-8"));
4995
5455
  return (parsed.module?.requestPermissions ?? []).map((p) => p?.name).filter((n) => typeof n === "string" && n.length > 0);
4996
5456
  } catch {
4997
5457
  return [];
@@ -5000,10 +5460,10 @@ function readRequestedPermissions(moduleConfigPath) {
5000
5460
  function collectAclPermissions(projectDir, logger = noopLogger) {
5001
5461
  const requested = /* @__PURE__ */ new Set();
5002
5462
  for (const module of listModules({ projectDir })) {
5003
- const moduleRoot = path36.resolve(projectDir, module.srcPath);
5463
+ const moduleRoot = path37.resolve(projectDir, module.srcPath);
5004
5464
  for (const sourceSet of ["main", "ohosTest"]) {
5005
5465
  for (const name of readRequestedPermissions(
5006
- path36.join(moduleRoot, "src", sourceSet, "module.json5")
5466
+ path37.join(moduleRoot, "src", sourceSet, "module.json5")
5007
5467
  )) {
5008
5468
  requested.add(name);
5009
5469
  }
@@ -5019,7 +5479,7 @@ function collectAclPermissions(projectDir, logger = noopLogger) {
5019
5479
  // src/harmonyos/signing/autoSign.ts
5020
5480
  function shouldRegenerate(input) {
5021
5481
  if (input.force) return "--force was passed";
5022
- const missing = Object.values(input.paths).find((file) => !fs38.existsSync(file));
5482
+ const missing = Object.values(input.paths).find((file) => !fs39.existsSync(file));
5023
5483
  if (missing) return `${missing} is missing`;
5024
5484
  const config = input.buildProfile.app?.signingConfigs?.find((c) => c.name === input.productName);
5025
5485
  if (config?.type !== "HarmonyOS" || config.material?.profile !== input.paths.profilePath) {
@@ -5058,16 +5518,16 @@ function writeSigningConfig(opts) {
5058
5518
  for (const product of app.products ?? []) {
5059
5519
  if (product.name === opts.productName) product.signingConfig = opts.productName;
5060
5520
  }
5061
- fs38.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
5521
+ fs39.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
5062
5522
  }
5063
5523
  async function harmonyOsAutoSign(opts) {
5064
5524
  const logger = opts.logger ?? noopLogger;
5065
5525
  const productName = opts.productName ?? "default";
5066
- const projectDir = path37.resolve(opts.projectDir);
5067
- const buildProfilePath = path37.join(projectDir, "build-profile.json5");
5526
+ const projectDir = path38.resolve(opts.projectDir);
5527
+ const buildProfilePath = path38.join(projectDir, "build-profile.json5");
5068
5528
  let buildProfile;
5069
5529
  try {
5070
- buildProfile = JSON57.parse(fs38.readFileSync(buildProfilePath, "utf-8"));
5530
+ buildProfile = JSON57.parse(fs39.readFileSync(buildProfilePath, "utf-8"));
5071
5531
  } catch (err) {
5072
5532
  throw new OniroError(`Could not read ${buildProfilePath}: ${err.message}`, err);
5073
5533
  }
@@ -5107,7 +5567,7 @@ async function harmonyOsAutoSign(opts) {
5107
5567
  logger.info(`[harmonyos] Regenerating signing material: ${reason}.`);
5108
5568
  const agc = createAgcClient(opts.http ?? createHttpClient({ logger }), auth);
5109
5569
  const deviceIds = await registerDevices(agc, connected, logger);
5110
- for (const file of Object.values(paths)) fs38.rmSync(file, { force: true });
5570
+ for (const file of Object.values(paths)) fs39.rmSync(file, { force: true });
5111
5571
  const certName = `oniro_debug_${auth.teamId.replace(/[\\/.:]/g, "")}.cer`;
5112
5572
  const stale = (await agc.listCertificates()).find((c) => c.certName === certName);
5113
5573
  if (stale) {
@@ -5138,10 +5598,10 @@ async function harmonyOsAutoSign(opts) {
5138
5598
  try {
5139
5599
  verifySigningMaterial({ ...paths, keyAlias: KEY_ALIAS, keyPwd });
5140
5600
  } catch (err) {
5141
- fs38.rmSync(paths.profilePath, { force: true });
5601
+ fs39.rmSync(paths.profilePath, { force: true });
5142
5602
  throw err;
5143
5603
  }
5144
- const materialDir = path37.join(path37.dirname(paths.p12Path), "material");
5604
+ const materialDir = path38.join(path38.dirname(paths.p12Path), "material");
5145
5605
  try {
5146
5606
  getKey(materialDir);
5147
5607
  } catch {
@@ -5153,7 +5613,7 @@ async function harmonyOsAutoSign(opts) {
5153
5613
  }
5154
5614
 
5155
5615
  // src/index.ts
5156
- var VERSION = "0.13.0";
5616
+ var VERSION = "0.14.1";
5157
5617
  export {
5158
5618
  ALL_SDKS,
5159
5619
  APL_VALUES,
@@ -5175,7 +5635,9 @@ export {
5175
5635
  TargetNotFoundError,
5176
5636
  UnsupportedPlatformError,
5177
5637
  VERSION,
5638
+ appSandboxDir,
5178
5639
  applyChanges,
5640
+ assertDevicePath,
5179
5641
  assertLayoutSelector,
5180
5642
  attemptHdcConnection,
5181
5643
  buildCmdWrapper,
@@ -5186,6 +5648,8 @@ export {
5186
5648
  buildInputCommand,
5187
5649
  buildRawTouchCommand,
5188
5650
  captureBurst,
5651
+ captureLog,
5652
+ cleanCodelinterOutput,
5189
5653
  consoleLogger,
5190
5654
  createHarmonyOsSession,
5191
5655
  createInstallTempDir,
@@ -5201,9 +5665,12 @@ export {
5201
5665
  defaultPaths,
5202
5666
  describeLayoutNode,
5203
5667
  detectAaStartFailure,
5668
+ detectCmdToolsFlavour,
5669
+ detectInstallFailure,
5204
5670
  detectProjectSdkVersion,
5205
5671
  detectRuntimeOs,
5206
5672
  detectSigningConfigNames,
5673
+ diagnoseBuildFailure,
5207
5674
  diffEntryNames,
5208
5675
  diffHapAssets,
5209
5676
  discoverHaps,
@@ -5216,11 +5683,13 @@ export {
5216
5683
  dumpWindow,
5217
5684
  encryptPwd,
5218
5685
  ensureFreeSpace,
5686
+ ensureInstallOk,
5219
5687
  ensureOhModules,
5220
5688
  ensureOk,
5221
5689
  ensureRemoteOk,
5222
5690
  escapeCmdArgument,
5223
5691
  escapeCmdCommand,
5692
+ existingHapOrUnsigned,
5224
5693
  extractTarball,
5225
5694
  extractZipWithProgress,
5226
5695
  findAppProcessId,
@@ -5229,6 +5698,7 @@ export {
5229
5698
  findHarmonyOsSdk,
5230
5699
  findKeyboardWindow,
5231
5700
  findLayoutNodes,
5701
+ findMissingProjectSdk,
5232
5702
  findRunningProcess,
5233
5703
  flattenLayout,
5234
5704
  forceStop,
@@ -5242,6 +5712,7 @@ export {
5242
5712
  getDeviceInfo,
5243
5713
  getDisplaySize,
5244
5714
  getEmulatorDir,
5715
+ getEmulatorStatus,
5245
5716
  getFreeSpaceBytes,
5246
5717
  getHdcPath,
5247
5718
  getHvigorwPath,
@@ -5257,15 +5728,21 @@ export {
5257
5728
  getSupportedSdksForUi,
5258
5729
  harmonyOsAutoSign,
5259
5730
  harmonyOsSdkLabelForApi,
5731
+ hdcClientFailure,
5260
5732
  hdcExec,
5733
+ hdcTargetArgs,
5734
+ hdcTargetArgsFor,
5261
5735
  hideKeyboard,
5262
5736
  installApp,
5263
5737
  installCmdTools,
5264
5738
  installEmulator,
5739
+ installFailureReason,
5740
+ isBundleInstalled,
5265
5741
  isCmdToolsInstalled,
5266
5742
  isEmulatorInstalled,
5267
5743
  isOutOfSpaceError,
5268
5744
  isRamBackedPath,
5745
+ isUnsignedHap,
5269
5746
  isValidBundleName,
5270
5747
  isValidProjectName,
5271
5748
  killProcessTree,
@@ -5280,16 +5757,21 @@ export {
5280
5757
  nonInteractivePrompter,
5281
5758
  noopLogger,
5282
5759
  noopProgress,
5760
+ normaliseLocalPath,
5283
5761
  paramGet,
5762
+ paramGetMany,
5284
5763
  paramSet,
5285
5764
  parseBounds,
5286
5765
  parseCodelinterFindings,
5766
+ parseCodelinterReport,
5287
5767
  parseDeviceList,
5288
5768
  parseHilogLine,
5289
5769
  parseImeBundles,
5290
5770
  parseMainAbilityFromBmDump,
5771
+ parseParamValues,
5291
5772
  parseRemoteExit,
5292
5773
  performInput,
5774
+ pickHap,
5293
5775
  pickSigningKind,
5294
5776
  pickTarget,
5295
5777
  prepareSigning,
@@ -5304,6 +5786,7 @@ export {
5304
5786
  removeSdk,
5305
5787
  removeTempWorkDir,
5306
5788
  requireHarmonyOsSdk,
5789
+ resolveDeviceSerial,
5307
5790
  resolveKeyEvent,
5308
5791
  resolveTmpRoot,
5309
5792
  runCodelinter,
@@ -5328,6 +5811,7 @@ export {
5328
5811
  toLongPath,
5329
5812
  toSpaceError,
5330
5813
  uninstallApp,
5814
+ unsignedHapRejection,
5331
5815
  validateTemplateLayout,
5332
5816
  verifySha256,
5333
5817
  waitForBoot,